Files
001code-html--cocos/scratch-gui/static/unity/Build/mstest5.loader.js
刘宇飞 6e0a1fbcbb Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 15:37:45 +08:00

1368 lines
48 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 替换 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 后仅确保 resourceslevel-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');
// 不用 HEADdev 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);