Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
1368 lines
48 KiB
JavaScript
1368 lines
48 KiB
JavaScript
/**
|
||
* 替换 Unity Build/mstest5.loader.js — 保持 createUnityInstance API,内部启动 Cocos。
|
||
* bundle 文件名从 catalog.json 读取(与 Unity Addressables 一致),路径相对包根目录解析。
|
||
*/
|
||
(function (global) {
|
||
var PRODUCT = 'mstest5';
|
||
var files = new Map();
|
||
var origFetch = global.fetch.bind(global);
|
||
var packageRoot = '';
|
||
var streamingBase = '';
|
||
var bundleManifest = null;
|
||
var levelsManifest = null;
|
||
var levelPackPromises = new Map();
|
||
var loadedLevelIds = new Set();
|
||
var levelPrefabsBundlePromise = null;
|
||
|
||
function primeLevelPrefabsBundleScripts() {
|
||
if (!levelsShellReady()) return;
|
||
var idxHit = files.get('assets/level-prefabs/index.js');
|
||
if (idxHit && !global.__tfrhLevelPrefabsIndexLoaded) {
|
||
injectScript(bytesToText(idxHit));
|
||
global.__tfrhLevelPrefabsIndexLoaded = true;
|
||
}
|
||
}
|
||
|
||
function primeResourcesBundleScripts() {
|
||
var idxHit = files.get('assets/resources/index.js');
|
||
if (idxHit && !global.__tfrhResourcesIndexLoaded) {
|
||
injectScript(bytesToText(idxHit));
|
||
global.__tfrhResourcesIndexLoaded = true;
|
||
}
|
||
}
|
||
|
||
/** 拆分包模式下须先 loadBundle('resources'),level-prefabs 预制体依赖 resources */
|
||
function ensureResourcesBundleOnCc(cc, origLoadBundle) {
|
||
var am = cc && cc.assetManager;
|
||
if (!am) return Promise.reject(new Error('assetManager unavailable'));
|
||
var existing = am.getBundle('resources');
|
||
if (existing) return Promise.resolve(existing);
|
||
primeResourcesBundleScripts();
|
||
return new Promise(function (resolve, reject) {
|
||
origLoadBundle('resources', null, function (err, bundle) {
|
||
if (err || !bundle) reject(err || new Error('resources bundle unavailable'));
|
||
else resolve(bundle);
|
||
});
|
||
});
|
||
}
|
||
|
||
/** 串行注册 level-prefabs,避免 ensureCocos + LevelPrefabLoader 并发 loadBundle 竞态 */
|
||
function loadLevelPrefabsBundleOnce(cc, origLoadBundle, onComplete) {
|
||
var am = cc && cc.assetManager;
|
||
if (!am) {
|
||
if (onComplete) onComplete(new Error('assetManager unavailable'), null);
|
||
return;
|
||
}
|
||
var existing = am.getBundle('level-prefabs');
|
||
if (existing) {
|
||
if (onComplete) onComplete(null, existing);
|
||
return existing;
|
||
}
|
||
if (levelPrefabsBundlePromise) {
|
||
levelPrefabsBundlePromise.then(function (bundle) {
|
||
if (onComplete) onComplete(null, bundle);
|
||
}).catch(function (e) {
|
||
if (onComplete) onComplete(e, null);
|
||
});
|
||
return;
|
||
}
|
||
levelPrefabsBundlePromise = ensureResourcesBundleOnCc(cc, origLoadBundle).then(function () {
|
||
if (!levelsShellReady()) {
|
||
return Promise.reject(new Error('level-prefabs shell 不可用(config/index 应在 assets_all)'));
|
||
}
|
||
primeLevelPrefabsBundleScripts();
|
||
return new Promise(function (resolve, reject) {
|
||
var bundleRoot = joinUrl(packageRoot || getPackageRoot(), 'assets/level-prefabs/');
|
||
origLoadBundle('level-prefabs', { baseUrl: bundleRoot }, function (err, bundle) {
|
||
if (err || !bundle) {
|
||
levelPrefabsBundlePromise = null;
|
||
reject(err || new Error('level-prefabs bundle unavailable'));
|
||
return;
|
||
}
|
||
resolve(bundle);
|
||
});
|
||
});
|
||
});
|
||
levelPrefabsBundlePromise.then(function (bundle) {
|
||
if (onComplete) onComplete(null, bundle);
|
||
}).catch(function (e) {
|
||
if (onComplete) onComplete(e, null);
|
||
});
|
||
}
|
||
|
||
function getPackageRoot() {
|
||
var scripts = document.getElementsByTagName('script');
|
||
var i;
|
||
for (i = scripts.length - 1; i >= 0; i--) {
|
||
var src = scripts[i].getAttribute('src') || scripts[i].src || '';
|
||
if (src.indexOf('loader.js') >= 0) {
|
||
var u = new URL(src, global.location.href);
|
||
return u.href.replace(/Build\/[^/?#]+\.loader\.js.*$/i, '');
|
||
}
|
||
}
|
||
return new URL('./', global.location.href).href;
|
||
}
|
||
|
||
function resolveStreamingBase(config) {
|
||
packageRoot = getPackageRoot();
|
||
var sa = (config && config.streamingAssetsUrl) || 'StreamingAssets';
|
||
// 与 Unity 一致:/unity/StreamingAssets 相对站点根目录,不能相对 packageRoot(否则会 /unity/unity/...)
|
||
if (/^https?:\/\//i.test(sa)) {
|
||
streamingBase = sa;
|
||
} else if (sa.charAt(0) === '/') {
|
||
streamingBase = new URL(sa, global.location.href).href;
|
||
} else {
|
||
streamingBase = new URL(sa, packageRoot).href;
|
||
}
|
||
if (!/\/$/.test(streamingBase)) streamingBase += '/';
|
||
return streamingBase;
|
||
}
|
||
|
||
function joinUrl(base, rel) {
|
||
return new URL(String(rel || '').replace(/^\//, ''), base).href;
|
||
}
|
||
|
||
function toPath(url) {
|
||
try {
|
||
var u = new URL(url, global.location.href);
|
||
return decodeURIComponent(u.pathname.replace(/^\/+/, ''));
|
||
} catch (e) {
|
||
return String(url || '');
|
||
}
|
||
}
|
||
|
||
function lookupFile(pathname) {
|
||
var keys = [pathname, pathname.replace(/^\.?\//, '')];
|
||
var parts = pathname.split('/');
|
||
var n;
|
||
for (n = 1; n <= 6 && n < parts.length; n++) {
|
||
keys.push(parts.slice(-n).join('/'));
|
||
}
|
||
for (var i = 0; i < keys.length; i++) {
|
||
var k = keys[i];
|
||
if (files.has(k)) return files.get(k);
|
||
var idx = k.indexOf('StreamingAssets/aa/WebGL/');
|
||
if (idx >= 0 && files.has(k.slice(idx + 'StreamingAssets/aa/WebGL/'.length))) {
|
||
return files.get(k.slice(idx + 'StreamingAssets/aa/WebGL/'.length));
|
||
}
|
||
var ai = k.indexOf('/assets/');
|
||
if (ai >= 0 && files.has('assets/' + k.slice(ai + 8))) {
|
||
return files.get('assets/' + k.slice(ai + 8));
|
||
}
|
||
if (k.indexOf('assets/') === 0 && files.has(k)) return files.get(k);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function mimeForPath(pathname) {
|
||
if (/\.(m?js)$/i.test(pathname)) return 'application/javascript';
|
||
if (/\.json$/i.test(pathname)) return 'application/json';
|
||
if (/\.wasm$/i.test(pathname)) return 'application/wasm';
|
||
return 'application/octet-stream';
|
||
}
|
||
|
||
function getRootPathname() {
|
||
var rootPath = new URL(packageRoot || getPackageRoot(), global.location.href).pathname;
|
||
return /\/$/.test(rootPath) ? rootPath : rootPath + '/';
|
||
}
|
||
|
||
function needsUnityPrefix(pathname) {
|
||
return /^\/(src|assets|cocos-js)\//.test(pathname) || pathname === '/src/settings.json';
|
||
}
|
||
|
||
function relPathFromUrlPath(pathname) {
|
||
var root = getRootPathname();
|
||
if (pathname.indexOf(root) === 0) {
|
||
return pathname.slice(root.length).replace(/^\/+/, '');
|
||
}
|
||
if (needsUnityPrefix(pathname) || /^\/(assets|src)\//.test(pathname)) {
|
||
return pathname.replace(/^\//, '');
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function normalizeBundleRelPath(raw) {
|
||
var s = String(raw || '').replace(/\\/g, '/').trim();
|
||
if (!s) return '';
|
||
if (s.indexOf('blob:') === 0) {
|
||
s = s.slice(5).replace(/^https?:\/?\/?/i, '');
|
||
if (s.charAt(0) === '/') s = s.slice(1);
|
||
}
|
||
var parts = s.split('/');
|
||
var out = [];
|
||
for (var i = 0; i < parts.length; i++) {
|
||
var part = parts[i];
|
||
if (!part || part === '.') continue;
|
||
if (part === '..') {
|
||
if (out.length) out.pop();
|
||
continue;
|
||
}
|
||
out.push(part);
|
||
}
|
||
return out.join('/');
|
||
}
|
||
|
||
function resolveBundleVirtualUrl(raw) {
|
||
if (!files.size || !raw) return null;
|
||
var norm = normalizeBundleRelPath(raw);
|
||
if (!norm) return null;
|
||
var virt = virtualUrlForBundleFile(norm);
|
||
if (virt) return virt;
|
||
if (norm.indexOf('src/chunks/bundle.js') >= 0) return virtualUrlForBundleFile('src/chunks/bundle.js');
|
||
if (norm.indexOf('src/effect.bin') >= 0) return virtualUrlForBundleFile('src/effect.bin');
|
||
return null;
|
||
}
|
||
|
||
function fixEmbeddedUrl(raw) {
|
||
if (!raw) return raw;
|
||
var virtEarly = resolveBundleVirtualUrl(raw);
|
||
if (virtEarly) return virtEarly;
|
||
try {
|
||
var u = new URL(String(raw), global.location.href);
|
||
var p = u.pathname;
|
||
if (files.size > 0) {
|
||
var rel = relPathFromUrlPath(p);
|
||
if (rel) {
|
||
var virt = virtualUrlForBundleFile(rel);
|
||
if (virt) return virt;
|
||
}
|
||
}
|
||
if (p.indexOf(getRootPathname()) === 0) return u.href;
|
||
if (needsUnityPrefix(p)) {
|
||
return joinUrl(packageRoot || getPackageRoot(), p.replace(/^\//, ''));
|
||
}
|
||
} catch (e) { /* ignore */ }
|
||
return raw;
|
||
}
|
||
|
||
function rewriteEmbeddedUrl(input) {
|
||
var raw = typeof input === 'string' ? input : (input && input.url) || '';
|
||
var fixed = fixEmbeddedUrl(raw);
|
||
return fixed === raw ? input : fixed;
|
||
}
|
||
|
||
function installEmbeddedPathFix() {
|
||
if (global.__unityEmbeddedPathFixed) return;
|
||
global.__unityEmbeddedPathFixed = true;
|
||
|
||
global.fetch = function (input, init) {
|
||
return origFetch(rewriteEmbeddedUrl(input), init);
|
||
};
|
||
|
||
var OrigXHR = global.XMLHttpRequest;
|
||
if (OrigXHR && !OrigXHR.__unityPathFixed) {
|
||
global.XMLHttpRequest = function () {
|
||
var xhr = new OrigXHR();
|
||
var origOpen = xhr.open;
|
||
xhr.open = function (method, url) {
|
||
return origOpen.call(xhr, method, fixEmbeddedUrl(url));
|
||
};
|
||
return xhr;
|
||
};
|
||
global.XMLHttpRequest.__unityPathFixed = true;
|
||
}
|
||
|
||
patchElementUrlLoading(HTMLScriptElement, ['src']);
|
||
patchElementUrlLoading(HTMLImageElement, ['src']);
|
||
patchElementUrlLoading(HTMLLinkElement, ['href']);
|
||
patchElementUrlLoading(HTMLVideoElement, ['src', 'poster']);
|
||
patchElementUrlLoading(HTMLAudioElement, ['src']);
|
||
patchElementUrlLoading(HTMLSourceElement, ['src']);
|
||
}
|
||
|
||
function patchElementUrlLoading(Ctor, props) {
|
||
if (!Ctor || !Ctor.prototype || Ctor.prototype.__unityUrlLoadingFixed) return;
|
||
var proto = Ctor.prototype;
|
||
var origSetAttr = proto.setAttribute;
|
||
proto.setAttribute = function (name, value) {
|
||
if (value && props.indexOf(name) >= 0) value = fixEmbeddedUrl(value);
|
||
return origSetAttr.call(this, name, value);
|
||
};
|
||
for (var i = 0; i < props.length; i++) {
|
||
patchUrlProperty(proto, props[i]);
|
||
}
|
||
proto.__unityUrlLoadingFixed = true;
|
||
}
|
||
|
||
function patchUrlProperty(proto, prop) {
|
||
var key = '__unity' + prop + 'Fixed';
|
||
if (proto[key]) return;
|
||
var desc = Object.getOwnPropertyDescriptor(proto, prop);
|
||
if (!desc || !desc.set) return;
|
||
Object.defineProperty(proto, prop, {
|
||
configurable: true,
|
||
enumerable: desc.enumerable,
|
||
get: desc.get,
|
||
set: function (v) { desc.set.call(this, fixEmbeddedUrl(v)); },
|
||
});
|
||
proto[key] = true;
|
||
}
|
||
|
||
var origScriptSrcDesc = null;
|
||
|
||
function resolveEmbeddedAssetUrl(raw) {
|
||
if (!raw) return raw;
|
||
var fixed = fixEmbeddedUrl(raw);
|
||
var p = toPath(typeof fixed === 'string' ? fixed : String(raw || ''));
|
||
var rel = relPathFromUrlPath(p) || p.replace(/^\.?\//, '');
|
||
if (lookupFile(p) || (rel && lookupFile(rel))) {
|
||
return virtualUrlForBundleFile(rel) || fixed;
|
||
}
|
||
return fixed;
|
||
}
|
||
|
||
function assignScriptSrc(el, raw) {
|
||
var url = resolveEmbeddedAssetUrl(raw);
|
||
if (origScriptSrcDesc && origScriptSrcDesc.set) {
|
||
origScriptSrcDesc.set.call(el, url);
|
||
} else {
|
||
el.setAttribute('src', url);
|
||
}
|
||
}
|
||
|
||
function ensureLevelPackForPath(pathname) {
|
||
var lid = levelIdFromPrefabPath(pathname);
|
||
if (!lid) return Promise.resolve();
|
||
return ensureLevelPackLoaded(lid);
|
||
}
|
||
|
||
function assignLevelPrefabScriptSrc(el, value) {
|
||
var apply = function () { assignScriptSrc(el, value); };
|
||
if (levelsShellReady() && fileHit(value)) {
|
||
apply();
|
||
return;
|
||
}
|
||
ensureLevelPackForPath(value).then(apply).catch(apply);
|
||
}
|
||
|
||
function patchLevelPrefabsScriptLoading() {
|
||
if (!HTMLScriptElement || HTMLScriptElement.prototype.__tfrhLevelPrefabScript) return;
|
||
var proto = HTMLScriptElement.prototype;
|
||
origScriptSrcDesc = Object.getOwnPropertyDescriptor(proto, 'src');
|
||
var origSetAttr = proto.setAttribute;
|
||
proto.setAttribute = function (name, value) {
|
||
if (name === 'src' && needsLevelPrefabs(value)) {
|
||
assignLevelPrefabScriptSrc(this, value);
|
||
return;
|
||
}
|
||
if (value && name === 'src') value = resolveEmbeddedAssetUrl(value);
|
||
return origSetAttr.call(this, name, value);
|
||
};
|
||
var desc = origScriptSrcDesc;
|
||
if (desc && desc.set) {
|
||
Object.defineProperty(proto, 'src', {
|
||
configurable: true,
|
||
enumerable: desc.enumerable,
|
||
get: desc.get,
|
||
set: function (v) {
|
||
if (needsLevelPrefabs(v)) {
|
||
assignLevelPrefabScriptSrc(this, v);
|
||
return;
|
||
}
|
||
desc.set.call(this, resolveEmbeddedAssetUrl(v));
|
||
},
|
||
});
|
||
}
|
||
proto.__tfrhLevelPrefabScript = true;
|
||
}
|
||
|
||
function installFetchShim() {
|
||
global.fetch = function (input, init) {
|
||
input = rewriteEmbeddedUrl(input);
|
||
var p = toPath(typeof input === 'string' ? input : (input && input.url) || '');
|
||
var hit = lookupFile(p);
|
||
if (hit) {
|
||
var body = /\.(m?js|json)$/i.test(p) ? bytesToText(hit) : hit;
|
||
return Promise.resolve(new Response(body, {
|
||
status: 200,
|
||
headers: { 'Content-Type': mimeForPath(p) },
|
||
}));
|
||
}
|
||
if (needsLevelPrefabs(p)) {
|
||
return ensureLevelPackForPath(p).then(function () {
|
||
var hit2 = lookupFile(p);
|
||
if (hit2) {
|
||
var body2 = /\.(m?js|json)$/i.test(p) ? bytesToText(hit2) : hit2;
|
||
return new Response(body2, {
|
||
status: 200,
|
||
headers: { 'Content-Type': mimeForPath(p) },
|
||
});
|
||
}
|
||
return origFetch(input, init);
|
||
});
|
||
}
|
||
return origFetch(input, init);
|
||
};
|
||
|
||
var OrigXHR = global.XMLHttpRequest;
|
||
if (OrigXHR) {
|
||
global.XMLHttpRequest = function () {
|
||
var xhr = new OrigXHR();
|
||
var reqUrl = '';
|
||
var origOpen = xhr.open;
|
||
xhr.open = function (method, url) {
|
||
reqUrl = fixEmbeddedUrl(url);
|
||
return origOpen.call(xhr, method, reqUrl);
|
||
};
|
||
var origSend = xhr.send;
|
||
xhr.send = function () {
|
||
var hit = lookupFile(toPath(reqUrl));
|
||
if (!hit && needsLevelPrefabs(reqUrl)) {
|
||
ensureLevelPackForPath(reqUrl).then(function () {
|
||
hit = lookupFile(toPath(reqUrl));
|
||
if (!hit) return origSend.apply(xhr, arguments);
|
||
var isText = /\.(m?js|json|txt|xml|atlas|tmx|tsx|vsh|fsh|fnt|plist)$/i.test(toPath(reqUrl))
|
||
|| xhr.responseType === 'json'
|
||
|| xhr.responseType === 'text'
|
||
|| !xhr.responseType;
|
||
deliverXhrHit(xhr, isText ? bytesToText(hit) : null, isText ? null : hit);
|
||
}).catch(function () { origSend.apply(xhr, arguments); });
|
||
return;
|
||
}
|
||
if (!hit) return origSend.apply(xhr, arguments);
|
||
var isText = /\.(m?js|json|txt|xml|atlas|tmx|tsx|vsh|fsh|fnt|plist)$/i.test(toPath(reqUrl))
|
||
|| xhr.responseType === 'json'
|
||
|| xhr.responseType === 'text'
|
||
|| !xhr.responseType;
|
||
deliverXhrHit(xhr, isText ? bytesToText(hit) : null, isText ? null : hit);
|
||
};
|
||
return xhr;
|
||
};
|
||
}
|
||
patchLevelPrefabsScriptLoading();
|
||
}
|
||
|
||
function deliverXhrHit(xhr, text, rawBytes) {
|
||
var responseType = xhr.responseType || '';
|
||
setTimeout(function () {
|
||
try {
|
||
var response = text;
|
||
if (responseType === 'json') {
|
||
try {
|
||
response = typeof text === 'string' ? JSON.parse(text) : text;
|
||
} catch (parseErr) {
|
||
console.warn('[mstest5] XHR json 解析失败', parseErr);
|
||
}
|
||
} else if (responseType === 'arraybuffer' && rawBytes) {
|
||
response = rawBytes.buffer.slice(rawBytes.byteOffset, rawBytes.byteOffset + rawBytes.byteLength);
|
||
}
|
||
Object.defineProperty(xhr, 'readyState', { value: 4 });
|
||
Object.defineProperty(xhr, 'status', { value: 200 });
|
||
Object.defineProperty(xhr, 'responseText', { value: typeof text === 'string' ? text : '' });
|
||
Object.defineProperty(xhr, 'response', { value: response });
|
||
if (xhr.onreadystatechange) xhr.onreadystatechange();
|
||
if (xhr.onload) xhr.onload();
|
||
} catch (e) { /* ignore */ }
|
||
}, 0);
|
||
}
|
||
|
||
function needsLevelPrefabs(pathname) {
|
||
var p = String(pathname || '');
|
||
return p.indexOf('level-prefabs') >= 0 || p.indexOf('assets/level-prefabs') >= 0;
|
||
}
|
||
|
||
function levelsShellReady() {
|
||
return files.has('assets/level-prefabs/config.json')
|
||
&& files.has('assets/level-prefabs/index.js');
|
||
}
|
||
|
||
function levelIdFromPrefabPath(pathname) {
|
||
var m = /Level(\d+)/.exec(String(pathname || ''));
|
||
if (m) return m[1];
|
||
if (!levelsManifest || !levelsManifest.levels) return null;
|
||
var norm = String(pathname || '').replace(/\\/g, '/');
|
||
var base = norm.split('/').pop() || norm;
|
||
var id;
|
||
for (id in levelsManifest.levels) {
|
||
if (!Object.prototype.hasOwnProperty.call(levelsManifest.levels, id)) continue;
|
||
var entry = levelsManifest.levels[id];
|
||
var filesList = entry && entry.files;
|
||
if (!filesList || !filesList.length) continue;
|
||
for (var i = 0; i < filesList.length; i++) {
|
||
var rel = String(filesList[i] || '');
|
||
if (norm.indexOf(rel) >= 0 || base === rel.split('/').pop()) return id;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function dispatchLevelsBundleProgress(progress) {
|
||
if (typeof global.__tfrhOnLevelsBundleProgress === 'function') {
|
||
try { global.__tfrhOnLevelsBundleProgress(progress); } catch (e) { /* ignore */ }
|
||
}
|
||
try {
|
||
global.dispatchEvent(new CustomEvent('tfrh-levels-bundle-progress', {
|
||
detail: { progress: progress },
|
||
}));
|
||
} catch (e2) { /* ignore */ }
|
||
try {
|
||
global.dispatchEvent(new CustomEvent('tfrh-level-pack-progress', {
|
||
detail: { progress: progress },
|
||
}));
|
||
} catch (e3) { /* ignore */ }
|
||
}
|
||
|
||
function ensureLevelPackLoaded(levelId, onProgress) {
|
||
levelId = String(levelId);
|
||
if (loadedLevelIds.has(levelId)) return Promise.resolve();
|
||
if (levelPackPromises.has(levelId)) {
|
||
var pending = levelPackPromises.get(levelId);
|
||
if (onProgress) pending.then(function () { onProgress(1); }).catch(function () {});
|
||
return pending;
|
||
}
|
||
if (!levelsManifest || !levelsManifest.levels || !levelsManifest.levels[levelId]) {
|
||
return Promise.reject(new Error('关卡包未在 manifest 中: Level' + levelId));
|
||
}
|
||
if (!streamingBase) return Promise.reject(new Error('streamingBase 未初始化'));
|
||
var entry = levelsManifest.levels[levelId];
|
||
var url = joinUrl(streamingBase, 'aa/WebGL/' + entry.bundle);
|
||
dispatchLevelsBundleProgress(0);
|
||
var promise = fetchBinaryWithProgress(
|
||
url,
|
||
function (frac) {
|
||
dispatchLevelsBundleProgress(frac);
|
||
if (onProgress) onProgress(frac);
|
||
},
|
||
0,
|
||
1,
|
||
).then(function (buf) {
|
||
mergeZipIntoFiles(unzip(buf));
|
||
var ok = (entry.files || []).every(function (rel) { return files.has(rel); });
|
||
if (!ok) {
|
||
throw new Error('关卡包解压不完整: Level' + levelId);
|
||
}
|
||
loadedLevelIds.add(levelId);
|
||
return invalidateLevelPrefabsBundle();
|
||
}).then(function () {
|
||
dispatchLevelsBundleProgress(1);
|
||
if (onProgress) onProgress(1);
|
||
}).catch(function (e) {
|
||
levelPackPromises.delete(levelId);
|
||
throw e;
|
||
});
|
||
levelPackPromises.set(levelId, promise);
|
||
return promise;
|
||
}
|
||
|
||
/** 按关解压 import 后须丢弃旧 level-prefabs bundle,否则 bundle.load 仍用壳 config */
|
||
function invalidateLevelPrefabsBundle() {
|
||
levelPrefabsBundlePromise = null;
|
||
global.__tfrhLevelPrefabsIndexLoaded = false;
|
||
return withCocosEngine(function (cc) {
|
||
var am = cc && cc.assetManager;
|
||
if (!am) return;
|
||
var bundle = am.getBundle('level-prefabs');
|
||
if (bundle && typeof am.removeBundle === 'function') {
|
||
try { am.removeBundle(bundle); } catch (e) { /* ignore */ }
|
||
}
|
||
});
|
||
}
|
||
|
||
global.__tfrhEnsureLevelPack = function (levelId, onProgress) {
|
||
return ensureLevelPackLoaded(levelId, onProgress);
|
||
};
|
||
|
||
global.__tfrhInvalidateLevelPrefabsBundle = invalidateLevelPrefabsBundle;
|
||
|
||
/** 兼容旧路径:按路径推断 levelId 再下载 */
|
||
function ensureLevelsBundleLoaded(onProgress) {
|
||
return Promise.resolve();
|
||
}
|
||
|
||
function prefetchLevelsBundleBackground() {}
|
||
|
||
function bytesToText(body) {
|
||
return new TextDecoder().decode(body);
|
||
}
|
||
|
||
function injectScript(code, type) {
|
||
var s = document.createElement('script');
|
||
if (type) s.type = type;
|
||
s.charset = 'utf-8';
|
||
s.text = code;
|
||
document.body.appendChild(s);
|
||
}
|
||
|
||
function fileHit(src) {
|
||
var base = String(src || '').replace(/^\.\//, '').replace(/^\//, '');
|
||
return lookupFile(base) || lookupFile(src) || files.get(base) || files.get(base.split('/').pop());
|
||
}
|
||
|
||
function loadScriptSrc(src, type) {
|
||
return new Promise(function (resolve, reject) {
|
||
var root = packageRoot || getPackageRoot();
|
||
var rel = String(src || '').replace(/^\.\//, '');
|
||
var url = joinUrl(root, rel);
|
||
var s = document.createElement('script');
|
||
if (type) s.type = type;
|
||
s.charset = 'utf-8';
|
||
s.src = url;
|
||
s.onload = function () { resolve(); };
|
||
s.onerror = function () {
|
||
reject(new Error('Failed to load ' + url));
|
||
};
|
||
document.body.appendChild(s);
|
||
});
|
||
}
|
||
|
||
function loadScript(src, type) {
|
||
var hit = fileHit(src);
|
||
if (hit && files.size > 0) {
|
||
return Promise.resolve(injectScript(bytesToText(hit), type));
|
||
}
|
||
var root = packageRoot || getPackageRoot();
|
||
var urls = [
|
||
src,
|
||
joinUrl(root, src),
|
||
joinUrl(root, src.replace(/^\.\//, '')),
|
||
'/unity/' + String(src).replace(/^\//, ''),
|
||
];
|
||
var baseName = String(src).split('/').pop();
|
||
if (baseName === 'cocos-bridge.js') {
|
||
urls.push('/cocos-bridge.js', '/unity/cocos-bridge.js');
|
||
}
|
||
var i = 0;
|
||
function tryNext() {
|
||
if (i >= urls.length) {
|
||
return Promise.reject(new Error(
|
||
'Failed to load ' + src
|
||
+ ' (bundle 内无此文件;若 usecdn=true 请上传新版 StreamingAssets/aa/WebGL/*.bundle 到 CDN)',
|
||
));
|
||
}
|
||
var url = urls[i++];
|
||
return origFetch(url).then(function (res) {
|
||
if (!res.ok) return tryNext();
|
||
return res.text();
|
||
}).then(function (code) {
|
||
if (!code) return tryNext();
|
||
injectScript(code, type);
|
||
}).catch(function () { return tryNext(); });
|
||
}
|
||
return tryNext();
|
||
}
|
||
|
||
function ensureSystemBase() {
|
||
var root = packageRoot || getPackageRoot();
|
||
try {
|
||
if (global.System && typeof global.System.config === 'function') {
|
||
global.System.config({ baseURL: root });
|
||
}
|
||
} catch (e) { /* ignore */ }
|
||
// 禁止修改 document <base>,否则会破坏主站 sw.js、manifest 等相对路径
|
||
}
|
||
|
||
function virtualUrlForBundleFile(relPath) {
|
||
var hit = lookupFile(relPath) || files.get(relPath);
|
||
if (!hit) return null;
|
||
var cacheKey = '__tfrhBlobUrl:' + relPath;
|
||
if (!global[cacheKey]) {
|
||
global[cacheKey] = URL.createObjectURL(new Blob([hit], { type: mimeForPath(relPath) }));
|
||
}
|
||
return global[cacheKey];
|
||
}
|
||
|
||
function patchImportMapCc(map, useLocalDisk) {
|
||
if (!map.imports || !map.imports.cc) return map;
|
||
// bundle 模式:cc.js 仅在 zip 解压后的内存中,不能用磁盘路径
|
||
if (!useLocalDisk) {
|
||
var virtual = virtualUrlForBundleFile('cocos-js/cc.js');
|
||
if (virtual) {
|
||
map.imports.cc = virtual;
|
||
return map;
|
||
}
|
||
}
|
||
var rootHref = packageRoot || getPackageRoot();
|
||
var basePath = new URL(rootHref, global.location.href).pathname;
|
||
if (!/\/$/.test(basePath)) basePath += '/';
|
||
map.imports.cc = basePath + 'cocos-js/cc.js';
|
||
return map;
|
||
}
|
||
|
||
function markCocosImportMapReady() {
|
||
global.__tfrhCocosImportMapReady = true;
|
||
try {
|
||
if (global.System && typeof global.System.prepareImport === 'function') {
|
||
return Promise.resolve(global.System.prepareImport());
|
||
}
|
||
} catch (e) { /* ignore */ }
|
||
return Promise.resolve();
|
||
}
|
||
|
||
function bindCocosEngine(cc) {
|
||
if (cc) {
|
||
global.__tfrhCocosEngine = cc;
|
||
patchAssetManagerLoadBundle(cc);
|
||
}
|
||
return cc;
|
||
}
|
||
|
||
/** loadBundle('level-prefabs') 前先注入 index.js,并保证 levels zip 已解压 */
|
||
function patchAssetManagerLoadBundle(cc) {
|
||
var am = cc && cc.assetManager;
|
||
if (!am || am.__tfrhLoadBundlePatched) return;
|
||
var origLoadBundle = am.loadBundle.bind(am);
|
||
am.__tfrhOrigLoadBundle = origLoadBundle;
|
||
am.loadBundle = function (name, options, onComplete) {
|
||
if (typeof options === 'function') {
|
||
onComplete = options;
|
||
options = null;
|
||
}
|
||
var bundleName = String(name || '');
|
||
if (bundleName.indexOf('level-prefabs') < 0) {
|
||
return origLoadBundle(name, options, onComplete);
|
||
}
|
||
loadLevelPrefabsBundleOnce(cc, origLoadBundle, onComplete);
|
||
};
|
||
am.__tfrhLoadBundlePatched = true;
|
||
}
|
||
|
||
/** 覆盖 LevelPrefabLoader:内置 c() 缓存与 bundle 壳不同步,改走已验证的 loadBundle+load 路径 */
|
||
function loadLevelPrefabViaBundle(cc, path) {
|
||
var raw = String(path || '').trim();
|
||
var m = /Level(\d+)/.exec(raw);
|
||
var lid = m ? parseInt(m[1], 10) : NaN;
|
||
var prep = (typeof global.__tfrhEnsureLevelPack === 'function' && !Number.isNaN(lid))
|
||
? global.__tfrhEnsureLevelPack(lid)
|
||
: Promise.resolve();
|
||
var Prefab = cc.Prefab;
|
||
return prep.then(function () {
|
||
return invalidateLevelPrefabsBundle();
|
||
}).then(function () {
|
||
return new Promise(function (resolve, reject) {
|
||
cc.assetManager.loadBundle('level-prefabs', function (err, bundle) {
|
||
if (err || !bundle) {
|
||
reject(err || new Error('level-prefabs bundle unavailable'));
|
||
return;
|
||
}
|
||
bundle.load(raw, Prefab, function (e, asset) {
|
||
if (e || !asset) reject(e || new Error('missing prefab: ' + raw));
|
||
else resolve(asset);
|
||
});
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
function installLevelPrefabLoadFix(cc) {
|
||
if (!cc) return Promise.resolve();
|
||
global.__tfrhLoadLevelPrefab = function (path) {
|
||
return loadLevelPrefabViaBundle(cc, path);
|
||
};
|
||
if (!global.System) return Promise.resolve();
|
||
return global.System.import('chunks:///_virtual/LevelPrefabLoader.ts').then(function (mod) {
|
||
if (!mod) return;
|
||
mod.loadLevelPrefab = global.__tfrhLoadLevelPrefab;
|
||
mod.__tfrhLoadPatched = true;
|
||
}).catch(function () { /* module not ready */ });
|
||
}
|
||
|
||
/** cc.game.init 后仅确保 resources;level-prefabs 进关时 loadLevelPrefabsBundleOnce 再下 levels_all */
|
||
function ensureCocosResourcesBundle(cc) {
|
||
if (!cc || !cc.assetManager) return Promise.resolve();
|
||
var origLoadBundle = cc.assetManager.__tfrhOrigLoadBundle;
|
||
if (!origLoadBundle) return Promise.resolve();
|
||
return ensureResourcesBundleOnCc(cc, origLoadBundle);
|
||
}
|
||
|
||
function ensureCocosGameRunning(cc) {
|
||
if (!cc || !cc.game) return Promise.resolve();
|
||
if (typeof global.__tfrhSyncEmbeddedCanvasNow === 'function') {
|
||
global.__tfrhSyncEmbeddedCanvasNow();
|
||
}
|
||
if (cc.game.inited) {
|
||
syncEmbeddedCamerasFromLoader(cc);
|
||
return installLevelPrefabLoadFix(cc);
|
||
}
|
||
return cc.game.init({
|
||
debugMode: cc.DebugMode.ERROR,
|
||
settingsPath: 'src/settings.json',
|
||
}).then(function () {
|
||
if (typeof global.__tfrhSyncEmbeddedCanvasNow === 'function') {
|
||
global.__tfrhSyncEmbeddedCanvasNow();
|
||
}
|
||
return ensureCocosResourcesBundle(cc);
|
||
}).then(function () {
|
||
return installLevelPrefabLoadFix(cc);
|
||
}).then(function () {
|
||
return cc.game.run();
|
||
}).then(function () {
|
||
syncEmbeddedCamerasFromLoader(cc);
|
||
if (typeof global.__tfrhSyncEmbeddedCanvas === 'function') {
|
||
global.__tfrhSyncEmbeddedCanvas();
|
||
}
|
||
});
|
||
}
|
||
|
||
function withCocosEngine(fn) {
|
||
if (!global.__tfrhCocosImportMapReady) return Promise.resolve();
|
||
var cached = global.__tfrhCocosEngine;
|
||
if (cached) {
|
||
fn(cached);
|
||
return Promise.resolve();
|
||
}
|
||
if (!global.System) return Promise.resolve();
|
||
return global.System.import('cc').then(function (cc) {
|
||
bindCocosEngine(cc);
|
||
fn(cc);
|
||
}).catch(function () { /* cc not ready */ });
|
||
}
|
||
|
||
function loadImportMapFromDisk() {
|
||
var root = packageRoot || getPackageRoot();
|
||
return origFetch(joinUrl(root, 'src/import-map.json')).then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status + ' for import-map.json');
|
||
return res.json();
|
||
}).then(function (map) {
|
||
var s = document.createElement('script');
|
||
s.type = 'systemjs-importmap';
|
||
s.charset = 'utf-8';
|
||
s.textContent = JSON.stringify(patchImportMapCc(map, true));
|
||
document.head.appendChild(s);
|
||
return markCocosImportMapReady();
|
||
});
|
||
}
|
||
|
||
function loadImportMap() {
|
||
var hit = fileHit('src/import-map.json');
|
||
var mapPromise;
|
||
if (hit && files.size > 0) {
|
||
try {
|
||
mapPromise = Promise.resolve(JSON.parse(bytesToText(hit)));
|
||
} catch (e) {
|
||
mapPromise = Promise.reject(e);
|
||
}
|
||
} else {
|
||
var root = packageRoot || getPackageRoot();
|
||
mapPromise = origFetch(joinUrl(root, 'src/import-map.json')).then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status + ' for import-map.json');
|
||
return res.json();
|
||
});
|
||
}
|
||
return mapPromise.then(function (map) {
|
||
var s = document.createElement('script');
|
||
s.type = 'systemjs-importmap';
|
||
s.charset = 'utf-8';
|
||
s.textContent = JSON.stringify(patchImportMapCc(map, false));
|
||
document.head.appendChild(s);
|
||
return markCocosImportMapReady();
|
||
});
|
||
}
|
||
|
||
function assertCocosRuntimeLoaded() {
|
||
if (files.has('index.js') && (files.has('cocos-bridge.js') || files.has('application.js'))) return;
|
||
throw new Error(
|
||
'scenes bundle 不是 Cocos 运行时包(可能 CDN 仍是旧 Unity bundle)。'
|
||
+ ' 请执行 deploy-to-001code.sh 并上传 StreamingAssets/aa/WebGL/defaultlocalgroup_scenes_all_*.bundle',
|
||
);
|
||
}
|
||
|
||
function ensureCocosDom(canvas) {
|
||
if (!canvas) return;
|
||
canvas.id = 'GameCanvas';
|
||
canvas.tabIndex = 99;
|
||
var inner = document.getElementById('Cocos3dGameContainer');
|
||
if (!inner) {
|
||
inner = document.createElement('div');
|
||
inner.id = 'Cocos3dGameContainer';
|
||
canvas.parentNode.insertBefore(inner, canvas);
|
||
inner.appendChild(canvas);
|
||
}
|
||
inner.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;overflow:hidden;';
|
||
canvas.style.cssText = 'display:block;width:100%;height:100%;';
|
||
installEmbeddedStageLayout(canvas);
|
||
}
|
||
|
||
function applyEmbeddedResolution(cc) {
|
||
if (!cc || !cc.view) return;
|
||
var view = cc.view;
|
||
var RES = cc.ResolutionPolicy;
|
||
view.resizeWithBrowserSize(true);
|
||
view.setDesignResolutionSize(960, 600, RES.FIXED_WIDTH);
|
||
}
|
||
|
||
function syncEmbeddedCamerasFromLoader(cc) {
|
||
if (!cc || !cc.view || !cc.director) return;
|
||
applyEmbeddedResolution(cc);
|
||
var vis = cc.view.getVisibleSize();
|
||
var halfH = (vis.height > 0 ? vis.height : 600) / 2;
|
||
var scene = cc.director.getScene();
|
||
if (!scene) return;
|
||
var find = cc.find;
|
||
['UICamera', 'BgCamera', 'Main Camera'].forEach(function (name) {
|
||
var node = find(name, scene);
|
||
if (!node) return;
|
||
var cam = node.getComponent(cc.Camera);
|
||
if (cam) cam.orthoHeight = halfH;
|
||
});
|
||
var mainNode = find('Main Camera', scene);
|
||
if (mainNode) mainNode.setPosition(0, 0, mainNode.position.z);
|
||
}
|
||
|
||
function installEmbeddedStageLayout(canvas) {
|
||
if (!canvas || canvas.__tfrhEmbeddedLayout) return;
|
||
canvas.__tfrhEmbeddedLayout = true;
|
||
var stageRoot = canvas.parentElement;
|
||
while (stageRoot && stageRoot !== document.body) {
|
||
if (stageRoot.classList && stageRoot.className.indexOf('unity-stage-root') >= 0) break;
|
||
stageRoot = stageRoot.parentElement;
|
||
}
|
||
if (!stageRoot || stageRoot === document.body) {
|
||
stageRoot = canvas.parentElement;
|
||
while (stageRoot && stageRoot !== document.body) {
|
||
if (stageRoot.clientWidth > 0 && stageRoot.clientHeight > 0) break;
|
||
stageRoot = stageRoot.parentElement;
|
||
}
|
||
if (!stageRoot || stageRoot === document.body) {
|
||
stageRoot = canvas.parentElement;
|
||
}
|
||
}
|
||
|
||
var canvasResizeHooked = false;
|
||
var syncRaf = 0;
|
||
var syncing = false;
|
||
|
||
function afterCanvasResize(cc) {
|
||
syncEmbeddedCamerasFromLoader(cc);
|
||
if (typeof global.__tfrhSyncHudOrtho === 'function') global.__tfrhSyncHudOrtho();
|
||
}
|
||
|
||
function setFrameSizeIfChanged(cc, w, h) {
|
||
if (w <= 0 || h <= 0) return false;
|
||
var frame = cc.view.getFrameSize();
|
||
if (Math.floor(frame.width) === w && Math.floor(frame.height) === h) return false;
|
||
cc.view.setFrameSize(w, h);
|
||
return true;
|
||
}
|
||
|
||
function ensureCanvasResizeHook(cc) {
|
||
if (canvasResizeHooked) return;
|
||
canvasResizeHooked = true;
|
||
cc.view.on('canvas-resize', function () {
|
||
// Never call setFrameSize here — it re-triggers canvas-resize and overflows the stack.
|
||
afterCanvasResize(cc);
|
||
});
|
||
}
|
||
|
||
function runSyncLayout() {
|
||
var w = Math.floor((stageRoot && stageRoot.clientWidth) || 0);
|
||
var h = Math.floor((stageRoot && stageRoot.clientHeight) || 0);
|
||
if (w <= 0 || h <= 0) return;
|
||
var container = document.getElementById('Cocos3dGameContainer');
|
||
if (container) {
|
||
container.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;overflow:hidden;';
|
||
}
|
||
canvas.style.cssText = 'display:block;width:100%;height:100%;';
|
||
if (stageRoot) {
|
||
stageRoot.style.position = 'relative';
|
||
stageRoot.style.minHeight = '0';
|
||
if (!stageRoot.classList || stageRoot.className.indexOf('unity-stage-root') < 0) {
|
||
stageRoot.style.width = '100%';
|
||
stageRoot.style.height = '100%';
|
||
}
|
||
stageRoot.style.backgroundColor = '#010101';
|
||
}
|
||
if (!global.__tfrhCocosImportMapReady) return;
|
||
if (syncing) return;
|
||
syncing = true;
|
||
withCocosEngine(function (cc) {
|
||
ensureCanvasResizeHook(cc);
|
||
cc.view.resizeWithBrowserSize(true);
|
||
if (setFrameSizeIfChanged(cc, w, h)) {
|
||
/* afterCanvasResize runs via canvas-resize listener */
|
||
} else {
|
||
afterCanvasResize(cc);
|
||
}
|
||
}).finally(function () { syncing = false; });
|
||
}
|
||
|
||
function syncLayout() {
|
||
if (syncRaf) return;
|
||
syncRaf = requestAnimationFrame(function () {
|
||
syncRaf = 0;
|
||
runSyncLayout();
|
||
});
|
||
}
|
||
|
||
global.__tfrhSyncEmbeddedCanvas = syncLayout;
|
||
global.__tfrhSyncEmbeddedCanvasNow = runSyncLayout;
|
||
runSyncLayout();
|
||
syncLayout();
|
||
if (typeof ResizeObserver !== 'undefined' && stageRoot) {
|
||
var ro = new ResizeObserver(syncLayout);
|
||
ro.observe(stageRoot);
|
||
}
|
||
window.addEventListener('resize', syncLayout);
|
||
}
|
||
|
||
function waitInstance(maxMs) {
|
||
maxMs = maxMs || 120000;
|
||
return new Promise(function (resolve, reject) {
|
||
var t0 = Date.now();
|
||
(function tick() {
|
||
var ins = global.unityInstance || global.cocosIns;
|
||
if (ins && global.__tfrhCocosReady) return resolve(ins);
|
||
if (Date.now() - t0 > maxMs) return reject(new Error('Cocos instance timeout'));
|
||
requestAnimationFrame(tick);
|
||
})();
|
||
});
|
||
}
|
||
|
||
function probeLocalExtracted() {
|
||
var root = packageRoot || getPackageRoot();
|
||
var url = joinUrl(root, 'index.js');
|
||
// 不用 HEAD:dev server 可能对缺失文件仍返回 200/HTML,误判为本地包
|
||
return origFetch(url, { cache: 'no-cache' }).then(function (res) {
|
||
if (!res.ok) return false;
|
||
return res.text().then(function (txt) {
|
||
if (!txt || /<\s*html/i.test(txt)) return false;
|
||
return /System\.register|application\.js/i.test(txt);
|
||
});
|
||
}).catch(function () { return false; });
|
||
}
|
||
|
||
function systemImportEntry(useLocalDisk) {
|
||
if (!useLocalDisk) {
|
||
var hit = fileHit('index.js');
|
||
if (hit) {
|
||
injectScript(bytesToText(hit));
|
||
return Promise.resolve();
|
||
}
|
||
}
|
||
var root = packageRoot || getPackageRoot();
|
||
// 主站嵌入在 /editor.html,不能用相对 ./index.js(会解析到站点根目录)
|
||
var entryUrl = joinUrl(root, 'index.js');
|
||
if (useLocalDisk) {
|
||
entryUrl += '?v=' + Date.now();
|
||
}
|
||
return global.System.import(entryUrl);
|
||
}
|
||
|
||
function unzip(buf) {
|
||
var view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
||
var out = new Map();
|
||
var pos = 0;
|
||
while (pos + 4 < view.byteLength) {
|
||
if (view.getUint32(pos, true) !== 0x04034b50) break;
|
||
var comp = view.getUint16(pos + 8, true);
|
||
var compSize = view.getUint32(pos + 18, true);
|
||
var nameLen = view.getUint16(pos + 26, true);
|
||
var extraLen = view.getUint16(pos + 28, true);
|
||
var name = new TextDecoder().decode(new Uint8Array(buf.buffer, buf.byteOffset + pos + 30, nameLen));
|
||
var dataStart = pos + 30 + nameLen + extraLen;
|
||
var raw = new Uint8Array(buf.buffer, buf.byteOffset + dataStart, compSize);
|
||
var body;
|
||
if (comp === 0) body = raw;
|
||
else throw new Error('Unsupported zip compression method ' + comp + ' (pack with zip -0)');
|
||
var norm = name.replace(/\\/g, '/').replace(/^\/+/, '');
|
||
out.set(norm, body);
|
||
pos = dataStart + compSize;
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function fetchBinary(url, onProgress) {
|
||
return origFetch(url).then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status + ' for ' + url);
|
||
if (onProgress) onProgress(0.3);
|
||
return res.arrayBuffer();
|
||
}).then(function (ab) {
|
||
if (onProgress) onProgress(0.6);
|
||
return new Uint8Array(ab);
|
||
});
|
||
}
|
||
|
||
function fetchBinaryWithProgress(url, onProgress, weightStart, weightEnd) {
|
||
weightStart = weightStart == null ? 0 : weightStart;
|
||
weightEnd = weightEnd == null ? 1 : weightEnd;
|
||
return origFetch(url).then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status + ' for ' + url);
|
||
if (!res.body || !res.body.getReader) {
|
||
return res.arrayBuffer().then(function (ab) {
|
||
if (onProgress) onProgress(weightEnd);
|
||
return new Uint8Array(ab);
|
||
});
|
||
}
|
||
var reader = res.body.getReader();
|
||
var chunks = [];
|
||
var received = 0;
|
||
var total = Number(res.headers.get('content-length')) || 0;
|
||
function pump() {
|
||
return reader.read().then(function (result) {
|
||
if (result.done) {
|
||
if (onProgress) onProgress(weightEnd);
|
||
var out = new Uint8Array(received);
|
||
var off = 0;
|
||
for (var i = 0; i < chunks.length; i++) {
|
||
out.set(chunks[i], off);
|
||
off += chunks[i].length;
|
||
}
|
||
return out;
|
||
}
|
||
chunks.push(result.value);
|
||
received += result.value.length;
|
||
if (onProgress && total > 0) {
|
||
var frac = received / total;
|
||
onProgress(weightStart + (weightEnd - weightStart) * frac);
|
||
}
|
||
return pump();
|
||
});
|
||
}
|
||
return pump();
|
||
});
|
||
}
|
||
|
||
function loadStartupBundles(manifest, onProgress) {
|
||
onProgress(0.05);
|
||
var scenesP = fetchBinaryWithProgress(manifest.scenesUrl, onProgress, 0.05, 0.42);
|
||
var assetsP = fetchBinaryWithProgress(manifest.assetsUrl, onProgress, 0.42, 0.58);
|
||
return Promise.all([scenesP, assetsP]).then(function (bufs) {
|
||
mergeZipIntoFiles(unzip(bufs[0]));
|
||
assertCocosRuntimeLoaded();
|
||
mergeZipIntoFiles(unzip(bufs[1]));
|
||
patchSettingsScriptPackages();
|
||
onProgress(0.6);
|
||
});
|
||
}
|
||
|
||
function mergeZipIntoFiles(zipMap) {
|
||
zipMap.forEach(function (v, k) { files.set(k, v); });
|
||
}
|
||
|
||
function patchSettingsScriptPackages() {
|
||
var hit = files.get('src/settings.json');
|
||
if (!hit) return;
|
||
try {
|
||
var settings = JSON.parse(bytesToText(hit));
|
||
if (settings.scripting && Array.isArray(settings.scripting.scriptPackages)) {
|
||
settings.scripting.scriptPackages = settings.scripting.scriptPackages.map(function (pkg) {
|
||
return resolveBundleVirtualUrl(pkg) || pkg;
|
||
});
|
||
}
|
||
if (settings.rendering && settings.rendering.effectSettingsPath) {
|
||
var effectVirt = resolveBundleVirtualUrl(settings.rendering.effectSettingsPath);
|
||
if (effectVirt) settings.rendering.effectSettingsPath = effectVirt;
|
||
}
|
||
files.set('src/settings.json', new TextEncoder().encode(JSON.stringify(settings)));
|
||
} catch (e) {
|
||
console.warn('[mstest5] patch settings failed', e);
|
||
}
|
||
}
|
||
|
||
function parseCatalogBundles(catalog) {
|
||
var ids = catalog && catalog.m_InternalIds;
|
||
if (!ids || !ids.length) throw new Error('catalog.json missing m_InternalIds');
|
||
var names = [];
|
||
var i;
|
||
for (i = 0; i < ids.length; i++) {
|
||
var id = String(ids[i] || '');
|
||
if (id.indexOf('.bundle') < 0) continue;
|
||
names.push(id.split('/').pop());
|
||
}
|
||
if (!names.length) throw new Error('catalog.json has no .bundle entries');
|
||
var scenes = null;
|
||
var assets = null;
|
||
var levels = null;
|
||
for (i = 0; i < names.length; i++) {
|
||
if (names[i].indexOf('scenes_all') >= 0) scenes = names[i];
|
||
if (names[i].indexOf('assets_all') >= 0) assets = names[i];
|
||
if (names[i].indexOf('levels_all') >= 0) levels = names[i];
|
||
}
|
||
if (!scenes || !assets) {
|
||
throw new Error('catalog.json missing scenes_all/assets_all bundles: ' + names.join(', '));
|
||
}
|
||
if (!levels) {
|
||
/* levels_all 已废弃,改用 levels-manifest.json 按关分包 */
|
||
}
|
||
return { scenes: scenes, assets: assets, levels: levels, all: names };
|
||
}
|
||
|
||
function loadLevelsManifest(base) {
|
||
var manifestUrl = joinUrl(base, 'aa/levels-manifest.json');
|
||
return origFetch(manifestUrl, { cache: 'no-cache' }).then(function (res) {
|
||
if (!res.ok) {
|
||
console.warn('[mstest5] 缺少 levels-manifest.json,将无法按关下载');
|
||
return null;
|
||
}
|
||
return res.json();
|
||
}).catch(function (e) {
|
||
console.warn('[mstest5] levels-manifest.json 读取失败', e);
|
||
return null;
|
||
});
|
||
}
|
||
|
||
function loadBundleManifest(config) {
|
||
var base = resolveStreamingBase(config);
|
||
var catalogUrl = joinUrl(base, 'aa/catalog.json');
|
||
return origFetch(catalogUrl).then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status + ' for ' + catalogUrl);
|
||
return res.json();
|
||
}).then(function (catalog) {
|
||
var bundles = parseCatalogBundles(catalog);
|
||
return loadLevelsManifest(base).then(function (lm) {
|
||
levelsManifest = lm;
|
||
var manifest = {
|
||
scenesUrl: joinUrl(base, 'aa/WebGL/' + bundles.scenes),
|
||
assetsUrl: joinUrl(base, 'aa/WebGL/' + bundles.assets),
|
||
bundleNames: bundles,
|
||
};
|
||
bundleManifest = manifest;
|
||
return manifest;
|
||
});
|
||
});
|
||
}
|
||
|
||
function bootstrapCocos(onProgress, useLocalDisk) {
|
||
onProgress(0.65);
|
||
installEmbeddedPathFix();
|
||
if (!useLocalDisk) installFetchShim();
|
||
var canvas = document.getElementById('unity-canvas') || document.getElementById('GameCanvas');
|
||
var load = useLocalDisk ? loadScriptSrc : loadScript;
|
||
var mapStep = useLocalDisk ? loadImportMapFromDisk : loadImportMap;
|
||
return load('cocos-bridge.js')
|
||
.then(function () { onProgress(0.7); return load('src/polyfills.bundle.js'); })
|
||
.then(function () { onProgress(0.75); return load('src/system.bundle.js'); })
|
||
.then(function () {
|
||
ensureSystemBase();
|
||
onProgress(0.8);
|
||
return mapStep();
|
||
})
|
||
.then(function () {
|
||
ensureCocosDom(canvas);
|
||
onProgress(0.85);
|
||
return systemImportEntry(useLocalDisk);
|
||
})
|
||
.then(function () {
|
||
return global.System.import('cc').then(function (cc) {
|
||
bindCocosEngine(cc);
|
||
return ensureCocosGameRunning(cc);
|
||
});
|
||
})
|
||
.then(function () { onProgress(0.92); return waitInstance(); })
|
||
.then(function (ins) {
|
||
onProgress(1);
|
||
if (!ins.SetFullscreen) {
|
||
ins.SetFullscreen = function () {
|
||
var el = document.getElementById('unity-container') || document.documentElement;
|
||
if (el.requestFullscreen) el.requestFullscreen();
|
||
};
|
||
}
|
||
global.unityInstance = ins;
|
||
prefetchLevelsBundleBackground();
|
||
var sync = global.__tfrhSyncEmbeddedCanvas;
|
||
if (typeof sync === 'function') {
|
||
sync();
|
||
setTimeout(sync, 0);
|
||
setTimeout(sync, 120);
|
||
setTimeout(sync, 400);
|
||
setTimeout(sync, 1000);
|
||
setTimeout(sync, 2000);
|
||
}
|
||
withCocosEngine(function (cc) {
|
||
syncEmbeddedCamerasFromLoader(cc);
|
||
});
|
||
return ins;
|
||
});
|
||
}
|
||
|
||
// loader 解析时即安装路径修复(Cocos 后续动态 script 需要)
|
||
packageRoot = getPackageRoot();
|
||
installEmbeddedPathFix();
|
||
|
||
function resolveLevelsDatabaseUrl(config) {
|
||
if (global.__tfrhLevelsDatabaseUrl) return global.__tfrhLevelsDatabaseUrl;
|
||
var sa = config && config.streamingAssetsUrl;
|
||
if (sa && /^https?:\/\//i.test(sa)) {
|
||
var cdnRoot = String(sa).replace(/\/StreamingAssets\/?$/i, '');
|
||
return joinUrl(cdnRoot, 'levels-database.json');
|
||
}
|
||
return joinUrl(packageRoot || getPackageRoot(), 'levels-database.json');
|
||
}
|
||
|
||
function decodeBrotliToText(ab) {
|
||
if (typeof DecompressionStream !== 'undefined') {
|
||
return new Response(ab).pipeThrough(new DecompressionStream('brotli'))
|
||
.arrayBuffer()
|
||
.then(function (buf) { return new TextDecoder().decode(buf); });
|
||
}
|
||
return Promise.reject(new Error('浏览器不支持 Brotli 解压'));
|
||
}
|
||
|
||
function fetchLevelsDatabaseJson(url) {
|
||
var brUrl = url.replace(/\.json(\?.*)?$/i, '.json.br$1');
|
||
return origFetch(brUrl).then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||
var enc = (res.headers.get('content-encoding') || '').toLowerCase();
|
||
if (enc === 'br' || /\.br(\?|$)/i.test(brUrl)) {
|
||
return res.arrayBuffer().then(decodeBrotliToText).then(function (text) {
|
||
return JSON.parse(text);
|
||
});
|
||
}
|
||
return res.json();
|
||
}).catch(function () {
|
||
return origFetch(url).then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||
return res.json();
|
||
});
|
||
});
|
||
}
|
||
|
||
function prefetchMainSiteLevelDb(config) {
|
||
var url = resolveLevelsDatabaseUrl(config);
|
||
global.__tfrhLevelsDatabaseUrl = url;
|
||
if (!url || global.__tfrhLevelsDatabaseJson) return Promise.resolve();
|
||
return fetchLevelsDatabaseJson(url).then(function (json) {
|
||
global.__tfrhLevelsDatabaseJson = json;
|
||
}).catch(function (e) {
|
||
console.warn('[mstest5] 关卡库预取失败', url, e);
|
||
});
|
||
}
|
||
|
||
function isRemoteCdnConfig(config) {
|
||
var sa = config && config.streamingAssetsUrl;
|
||
return !!(sa && /^https?:\/\//i.test(String(sa)));
|
||
}
|
||
|
||
function shouldUseBundlePackFlow(config) {
|
||
if (isRemoteCdnConfig(config)) {
|
||
return Promise.resolve(true);
|
||
}
|
||
var base = resolveStreamingBase(config);
|
||
var catalogUrl = joinUrl(base, 'aa/catalog.json');
|
||
return origFetch(catalogUrl, { cache: 'no-cache' }).then(function (res) {
|
||
return res.ok;
|
||
}).catch(function () {
|
||
return false;
|
||
});
|
||
}
|
||
|
||
global.createUnityInstance = function (canvas, config, onProgress) {
|
||
onProgress = onProgress || function () {};
|
||
onProgress(0.02);
|
||
resolveStreamingBase(config);
|
||
return prefetchMainSiteLevelDb(config).then(function () {
|
||
return shouldUseBundlePackFlow(config);
|
||
}).then(function (useBundles) {
|
||
if (!useBundles) {
|
||
return probeLocalExtracted().then(function (useLocalDisk) {
|
||
if (useLocalDisk) {
|
||
onProgress(0.35);
|
||
return bootstrapCocos(onProgress, true);
|
||
}
|
||
return loadBundleManifest(config).then(function (manifest) {
|
||
return loadStartupBundles(manifest, onProgress);
|
||
}).then(function () {
|
||
prefetchLevelsBundleBackground();
|
||
return bootstrapCocos(onProgress, false);
|
||
});
|
||
});
|
||
}
|
||
return loadBundleManifest(config).then(function (manifest) {
|
||
return loadStartupBundles(manifest, onProgress);
|
||
}).then(function () {
|
||
prefetchLevelsBundleBackground();
|
||
return bootstrapCocos(onProgress, false);
|
||
});
|
||
});
|
||
};
|
||
})(typeof window !== 'undefined' ? window : globalThis);
|