// 临时脚本:按 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('(? 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( '(? 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 + ' 处替换 —');