- 根因:新版 main.7cfa4058 重拆后游戏符号整体平移,工具里写死的字段名(pyt_1/q100_1/r11l/oyz/jyw/cyw_1/dyw_1…)全部对不上 → 找不到组件/相机/场景 - 新增 tools/apply_symbol_map.js:按 map_symbols 映射批量替换,4 道安全规则:① 长名或含下划线 ② 排除 JS 关键字/内置名黑名单 ③ 游戏符号命名特征(含数字或 _数字 结尾) ④ 双向校验(旧名在新 bundle 仍存在→视为未变跳过;新名不存在→映射错误丢弃) - 单遍替换,避免链式污染(映射表新旧同名复用时会被二次替换) - 应用到 12 个 hornet_*.js 共 231 处(跳过 25764 个未变符号) - 重新生成 hornet_bundle.js(10 模块 / 249263 字节)并同步 unpacked
101 lines
5.5 KiB
JavaScript
101 lines
5.5 KiB
JavaScript
// 临时脚本:按 tmp_symbol_map.json 的映射,批量替换 tools/hornet_*.js 里的符号名
|
||
// 用法:node tools/tmp_apply_symbol_map.js
|
||
// 安全规则(上一版全量替换误伤了 function/return/window/length 等 JS 关键字与内置名):
|
||
// ① 旧名必须是"长名或含下划线"(>=4 字符 或 含 '_')—— 游戏符号基本都长这样;
|
||
// ② 旧名/新名都不能落在 JS 关键字/内置名黑名单里;
|
||
// ③ 只替换独立标识符(带词边界)。
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const MapPath = path.resolve(__dirname, '..', '3DTank', 'tmp_symbol_map.json');
|
||
const Map = (require(MapPath)._merged) || {};
|
||
|
||
// JS 关键字 + 内置名 + 常见通用词:绝不能参与替换
|
||
const BLACK = new Set([
|
||
'function', 'return', 'var', 'let', 'const', 'null', 'undefined', 'true', 'false', 'this',
|
||
'typeof', 'instanceof', 'new', 'delete', 'void', 'in', 'of', 'if', 'else', 'for', 'while',
|
||
'do', 'switch', 'case', 'break', 'continue', 'throw', 'try', 'catch', 'finally', 'class',
|
||
'extends', 'super', 'yield', 'await', 'async', 'default', 'export', 'import',
|
||
'window', 'document', 'console', 'Math', 'Object', 'Array', 'String', 'Number', 'Boolean',
|
||
'JSON', 'Date', 'Error', 'Promise', 'Map', 'Set', 'Symbol', 'Proxy', 'Reflect',
|
||
'length', 'name', 'value', 'text', 'call', 'apply', 'bind', 'prototype', 'constructor',
|
||
'toString', 'valueOf', 'equals', 'hashCode', 'push', 'pop', 'slice', 'splice', 'indexOf',
|
||
'forEach', 'filter', 'reduce', 'join', 'split', 'replace', 'match', 'test', 'keys', 'values',
|
||
'entries', 'has', 'get', 'set', 'add', 'clear', 'size', 'run', 'self', 'globalThis',
|
||
'requestAnimationFrame', 'setTimeout', 'setInterval', 'parseInt', 'parseFloat', 'isNaN',
|
||
'NaN', 'Infinity', 'arguments', 'prototype_', 'undefined_',
|
||
]);
|
||
|
||
// ===== 双向校验用的"新 bundle 符号表" =====
|
||
// 背景:map_symbols 的投票在"通用字段名"上会算错(例如把未变的 f20_1 投成 w9p_1)。
|
||
// 这里直接拿新 bundle 原文做事实校验:
|
||
// ① 旧名在新 bundle 里**仍然存在** → 说明它根本没变,跳过(保守,避免误改);
|
||
// ② 新名在新 bundle 里**不存在** → 说明这条映射是错的,丢弃。
|
||
const BUNDLE_PATH = path.resolve(__dirname, '..', 'main.7cfa4058.js');
|
||
const BUNDLE = fs.existsSync(BUNDLE_PATH) ? fs.readFileSync(BUNDLE_PATH, 'utf8') : '';
|
||
const CountCache = {};
|
||
function CountInBundle(Name) {
|
||
if (!BUNDLE) return 1; // 拿不到 bundle 就不做校验(退回旧行为)
|
||
if (CountCache[Name] !== undefined) return CountCache[Name];
|
||
const Re = new RegExp('(?<![A-Za-z0-9_$])' + Name.replace(/\$/g, '\\$') + '(?![A-Za-z0-9_$])', 'g');
|
||
const M = BUNDLE.match(Re);
|
||
CountCache[Name] = M ? M.length : 0;
|
||
return CountCache[Name];
|
||
}
|
||
|
||
// 收集要替换的条目
|
||
const Entries = [];
|
||
const Skipped = { keeps: [], bad: [] };
|
||
for (const K of Object.keys(Map)) {
|
||
const V = Map[K];
|
||
if (typeof V !== 'string' || !V || V === K) continue;
|
||
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(K)) continue;
|
||
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(V)) continue;
|
||
if (BLACK.has(K) || BLACK.has(V)) continue;
|
||
if (K.length < 4 && K.indexOf('_') < 0) continue; // 规则①:长名或含下划线
|
||
// 规则④:游戏符号的命名特征 —— 必须含数字,或以 _数字 结尾(如 pyt_1 / q100_1 / r11l / cyw_1)。
|
||
// 这样能排除工具自身的普通英文单词变量(placeholder / mode / length 之类)。
|
||
if (!/[0-9]/.test(K) && !/_[0-9]+$/.test(K)) continue;
|
||
// 规则⑤/⑥:双向校验
|
||
if (CountInBundle(K) > 0) { Skipped.keeps.push(K); continue; }
|
||
if (CountInBundle(V) === 0) { Skipped.bad.push(K + '→' + V); continue; }
|
||
Entries.push([K, V]);
|
||
}
|
||
if (Skipped.keeps.length) console.log('跳过(旧名在新版仍存在,视为未变) ' + Skipped.keeps.length + ' 个: ' + Skipped.keeps.slice(0, 20).join(' ') + (Skipped.keeps.length > 20 ? ' …' : ''));
|
||
if (Skipped.bad.length) console.log('丢弃(新名在新版不存在,映射有误) ' + Skipped.bad.length + ' 条: ' + Skipped.bad.slice(0, 20).join(' ') + (Skipped.bad.length > 20 ? ' …' : ''));
|
||
// 长符号优先,避免短符号先吃掉长符号的一部分
|
||
Entries.sort((A, B) => B[0].length - A[0].length);
|
||
|
||
const Dir = path.resolve(__dirname);
|
||
const Files = fs.readdirSync(Dir).filter((F) => /^hornet_.*\.js$/.test(F));
|
||
|
||
// 单遍替换:一个正则同时匹配所有旧名,回调里查映射。
|
||
// 这样能避免"上一轮的替换结果又被下一轮规则二次替换"(映射表里新旧符号可能同名复用,会造成链式污染)。
|
||
const Alt = new RegExp(
|
||
'(?<![A-Za-z0-9_$])(' + Entries.map((E) => E[0].replace(/\$/g, '\\$')).join('|') + ')(?![A-Za-z0-9_$])',
|
||
'g'
|
||
);
|
||
let HitsOf = {}; // 注意:别用 new Map()——本文件里 Map 已被映射表变量占用
|
||
|
||
let TotalHits = 0;
|
||
for (const F of Files) {
|
||
const P = path.join(Dir, F);
|
||
const Before = fs.readFileSync(P, 'utf8');
|
||
HitsOf = {};
|
||
const S = Before.replace(Alt, (M) => {
|
||
const V = Map[M];
|
||
if (!V || V === M) return M;
|
||
HitsOf[M + '→' + V] = (HitsOf[M + '→' + V] || 0) + 1;
|
||
return V;
|
||
});
|
||
if (S !== Before) {
|
||
fs.writeFileSync(P, S, 'utf8');
|
||
let Hits = 0;
|
||
Object.keys(HitsOf).forEach((K) => (Hits += HitsOf[K]));
|
||
TotalHits += Hits;
|
||
const Detail = Object.keys(HitsOf).map((K) => K + '(' + HitsOf[K] + ')');
|
||
console.log(F + ':' + Hits + ' 处 ' + Detail.slice(0, 12).join(' ') + (Detail.length > 12 ? ' …共' + Detail.length + '种' : ''));
|
||
}
|
||
}
|
||
console.log('— 合计 ' + TotalHits + ' 处替换 —');
|