@honor-minigame/cli
Version:
honor minigame pack cli
202 lines (186 loc) • 7.76 kB
JavaScript
/**
* GameGlobal polyfill —— 荣耀快游戏平台适配
*
* 背景:
* Unity 构建产物 buildUnity/webgl.wasm.framework.unityweb.js 中被注入了
* 一段"纹理压缩预下载"处理代码(_glCompressedTexImage2D /
* _glCompressedTexSubImage2D 内的 getMatchId / renderTexture 等),该代码
* 依赖微信小游戏的全局对象 GameGlobal:
* - GameGlobal.USED_TEXTURE_COMPRESSION
* - GameGlobal.TextureCompressedFormat
* - GameGlobal.NoneLimitSupportedTexture
* - GameGlobal.DownloadedTextures
* 并在 matchId 命中时调用 window.wasmsdk.QGDownloadTexture 预下载纹理。
*
* 问题:
* 荣耀快游戏环境不存在 GameGlobal 全局对象,且本项目并未实现上述纹理预下载
* 机制(无 wasmsdk、未填充 DownloadedTextures)。framework 在 getMatchId()
* 内执行 `isMiniProgram && GameGlobal.USED_TEXTURE_COMPRESSION` 时,因访问
* 未声明的 GameGlobal 直接抛出:
* ReferenceError: GameGlobal is not defined
* 导致游戏崩溃。
*
* 修复:
* 本文件由 main.js 第一行 require,早于 UnityLoader.instantiate 加载
* framework.unityweb.js。在此创建一个空的 GameGlobal,并显式将
* USED_TEXTURE_COMPRESSION 置为 false。这样 getMatchId() 恒返回
* [-1, "8x8", false],_glCompressedTexImage2D / _glCompressedTexSubImage2D
* 走标准 WebGL compressedTexImage2D 路径,不再访问 GameGlobal 上的任何纹理
* 缓存,也不再调用未实现的 QGDownloadTexture。
*/
(function () {
// 兼容不同运行时,定位全局对象
var globalObj =
(typeof globalThis !== "undefined") ? globalThis :
(typeof window !== "undefined") ? window :
this;
if (!globalObj.GameGlobal) {
globalObj.GameGlobal = {};
}
var G = globalObj.GameGlobal;
// 关闭纹理压缩"预下载"机制,改走标准 WebGL 上传路径
G.USED_TEXTURE_COMPRESSION = false;
// 以下三项在 USED_TEXTURE_COMPRESSION=false 时不会被访问到,仅作占位以保证健壮性
G.TextureCompressedFormat = "";
G.NoneLimitSupportedTexture = "";
G.DownloadedTextures = {};
// Image适配
const _Image = window.Image
// 缓存代理构造器:getter 始终返回同一引用,保证 Image === Image 为 true,
// 避免某些第三方库对 Image 做身份比较时失败;命名为 Image 也让堆栈更友好。
const ImageProxy = function Image(width, height) {
return qg.createImage(width, height)
}
Object.defineProperty(window, 'Image', {
set() { },
get() {
return ImageProxy
}
});
const data = [
["internal://tmp/", "rt-temp:/"],
["internal://cache/", "rt-user:/"],
["internal://files/", "rt-user:/"]
]
const regCache = {};
const transformPath = (data, path) => {
for (let i = 0; i < data.length; i++) {
regCache[data[i][0]] = regCache[data[i][0]] || new RegExp(data[i][0], 'i');
if (regCache[data[i][0]].test(path)) {
return path.replace(regCache[data[i][0]], data[i][1])
}
}
return null
};
const formatPath = (path) => {
return transformPath(data, path) || path
}
Object.defineProperty(window.qg, 'createImage', {
enumerable: true,
configurable: true,
value: () => {
const img = new _Image()
// 在原型链上查找src属性描述符
let proto = Object.getPrototypeOf(img)
let descriptor
while (proto && !descriptor) {
descriptor = Object.getOwnPropertyDescriptor(proto, 'src')
proto = Object.getPrototypeOf(proto)
}
if (!descriptor) {
console.error('Cannot find src property descriptor')
descriptor = img
}
const originalSetter = descriptor.set
const originalGetter = descriptor.get
Object.defineProperty(img, 'src', {
configurable: true,
enumerable: true,
get: function () {
return this._src || originalGetter.call(this)
},
set: function (value) {
this._src = value
const transformedPath = formatPath(value)
return originalSetter.call(this, transformedPath)
}
});
return img
}
})
let subpackageRoots = []
try {
let manifest = qg.getFileSystemManager().readFileSync('manifest.json', 'utf-8')
if (manifest) {
let manifestInfo = JSON.parse(manifest)
if (manifestInfo.subpackages && manifestInfo.subpackages.length > 0) {
// 存储分包 root 列表,用于 require 时匹配调用者所在分包
subpackageRoots = manifestInfo.subpackages.map(item => {
let root = item.root.endsWith('/') ? item.root : `${item.root}/`
return root
})
}
}
} catch (error) {
console.info('manifest.json解析异常', error)
}
// require适配
let _require = require
/**
* 通过文件路径获取所在目录路径
* @param {string} path 文件路径
* @return {string} 目录路径
*/
function getFileDirPath(path) {
if (!path) return ""
const idx = path.lastIndexOf("/")
return idx !== -1 ? path.substring(0, idx) : ""
}
function getStackTraceAt(index = 0) {
const error = new Error()
const stackLines = error.stack?.split('\n') || []
// 跳过两行:Error 消息行 + getStackTraceAt 自身所在的帧
const targetLine = stackLines[index + 2]
if (!targetLine) return null
// 支持多种 stack 帧格式:
// 1. Chrome/V8 正常: " at func (path:line:col)" 或 " at func (path)"
// 2. Firefox/Safari: "func@path:line:col" 或 "@path"
// 3. V8 顶层匿名调用: " at /path:line:col" (路径直接跟在 "at " 之后,无函数名)
// 4. 异常格式: " at <anonymous> (path)"
// 注意:路径中可能含 ":"(Windows "C:\..."、URL "http://..."、file://、Android 内部路径等),
// 因此路径用 .+? 非贪婪匹配,:行号:列号 用可选匹配以兼容无行号的精简格式。
let match = targetLine.match(/\((.+?)(?::\d+:\d+)?\)/)
if (match) return match[1]
match = targetLine.match(/@(.+?)(?::\d+:\d+)?\s*$/)
if (match) return match[1]
// 顶层匿名调用:路径直接跟在 "at " 之后,整行无括号无 @
match = targetLine.match(/^\s*at\s+(.+?)(?::\d+:\d+)?\s*$/)
return match ? match[1].trim() : null
}
const newRequire = function (cls) {
// 当 require("./game.js") 且存在分包时,根据调用者路径判断所在分包,拼接分包 root
if (cls === './game.js' && subpackageRoots && subpackageRoots.length > 0) {
const callerPath = getStackTraceAt(1)
if (callerPath) {
const callerDir = getFileDirPath(callerPath)
// 查找调用者所在的分包 root
const matchedRoot = qg.subpackageRoots.find(root => {
// 统一去掉前导 ./、/、\ 以及结尾的 /,保证 callerDir.includes 能正确匹配
const normalizedRoot = root
.replace(/\\/g, '/')
.replace(/^\.?\/?/, '')
.replace(/\/$/, '')
return callerDir.includes(normalizedRoot)
})
if (matchedRoot) {
// 拼接分包路径:./分包root/game.js
const cleanRoot = matchedRoot.replace(/\/$/, '')
console.log('加载的分包路径:', `./${cleanRoot}/game.js`)
return _require(`./${cleanRoot}/game.js`)
}
}
}
return _require(cls)
}
require = newRequire
})();