From 2b95e5de2890ee951922f8e4bc0dc060386938db Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 12 Oct 2026 10:13:32 +0800 Subject: [PATCH] =?UTF-8?q?chore(3DTank):=20=E6=96=B0=E5=A2=9E=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=86=85=E6=8E=A2=E9=92=88=E7=9A=84=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E8=BD=AC=E5=82=A8=E9=80=9A=E9=81=93=EF=BC=88/=5F=5Fprobe=5F=5F?= =?UTF-8?q?=20+=20probe=5Frailgun.js=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tools/serve.js: POST /__probe__ 追加写 3DTank/names/probe_railgun.log - sockets/probe_railgun.js: 电磁炮/武器链路现场探针(组件清单 + 采样 + WS 上行帧),fetch 批量转储,POST 不通自动回落控制台 --- 3DTank/tools/serve.js | 23 +++++++ sockets/probe_railgun.js | 143 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 sockets/probe_railgun.js diff --git a/3DTank/tools/serve.js b/3DTank/tools/serve.js index e00b4f1..efed03b 100644 --- a/3DTank/tools/serve.js +++ b/3DTank/tools/serve.js @@ -24,6 +24,8 @@ const PORT = Number(Arg('port', '8080')); const WEB_DIR = path.resolve(Arg('web', path.join(PROJECT, 'web'))); const JS_DIR = path.join(ROOT, '3dtank.com', 'play', 'static', 'js'); const MISSING_LOG = path.join(PROJECT, 'names', 'missing.txt'); +// 页面内探针(sockets/probe_railgun.js)的网络转储落盘位置:POST /__probe__ 追加写这里 +const PROBE_LOG = path.join(PROJECT, 'names', 'probe_railgun.log'); const CACHE_DIR = path.join(PROJECT, 'cache'); const NO_PROXY = Argv.indexOf('--no-proxy') >= 0; const VERBOSE = Argv.indexOf('--verbose') >= 0; @@ -230,6 +232,27 @@ const Server = http.createServer((Req, Res) => { }).end(Hook); return; } + // /__probe__:页面内探针的**网络转储**入口(控制台刷屏太扰,改为 POST 落盘) + // 探针(sockets/probe_railgun.js)把组件清单与采样分批 POST 到这里, + // 这里追加写入 3DTank/names/probe_railgun.log,供离线分析。 + if (UrlPath.split('?')[0] === '/__probe__') { + if (Req.method !== 'POST') { + Res.writeHead(405, { 'Content-Type': 'text/plain; charset=utf-8' }).end('POST only'); + return; + } + const Chunks = []; + Req.on('data', (C) => Chunks.push(C)); + Req.on('end', () => { + const Body = Buffer.concat(Chunks).toString('utf8'); + try { + fs.appendFileSync(PROBE_LOG, Body.endsWith('\n') ? Body : Body + '\n'); + } catch (Err) { + console.warn('[probe] 落盘失败: ' + Err.message); + } + Res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-cache' }).end('ok'); + }); + return; + } // /config.xml 本地覆盖:存在 local/config.xml 时用它,把游戏指向本地 wss_server if (!REAL_CONFIG && UrlPath.split('?')[0] === '/config.xml' && fs.existsSync(LOCAL_CONFIG)) { const Conf = fs.readFileSync(LOCAL_CONFIG); diff --git a/sockets/probe_railgun.js b/sockets/probe_railgun.js new file mode 100644 index 0000000..654265f --- /dev/null +++ b/sockets/probe_railgun.js @@ -0,0 +1,143 @@ +// probe_railgun.js (v4) —— 电磁炮链路探针:**网络转储**版(不再刷控制台) +// +// 数据流向:探针 → POST /__probe__ → serve.js 追加写 3DTank/names/probe_railgun.log +// 分析时直接读那个文件即可(离线 grep)。 +// +// 内容: +// ① 全量组件清单:玩家坦克 oyt_1.c2a_1 里每个组件(#id + 构造函数头 + 字段名) +// ② 现场采样:每 300ms 只记录**发生变化**的采样对象(z4t / PYt / 模式对象 vWt·pWt 等) +// ③ WS 上行:包装 WebSocket.prototype.send,记录帧长 + 前 16 字节(用于对齐开火时刻) +// +// 用法:进战场后整段粘到控制台。停:__probeRailgun__(false) +// 若 POST 不通会自动回落到 console(并有 [probe] 提示)。 +(function () { + 'use strict'; + var ENDPOINT = '/__probe__'; + var T0 = (window.performance && performance.now) ? performance.now() : Date.now(); + var Now = function () { return +(((window.performance && performance.now) ? performance.now() : Date.now()) - T0).toFixed(1); }; + var Src = function (o) { try { return String(o).replace(/\s+/g, ' '); } catch (e) { return '?'; } }; + var Ctor = function (o) { try { return Src(o.constructor).slice(0, 90); } catch (e) { return '?'; } }; + var Num = function (v) { + if (v === null) return 'null'; + if (v === undefined) return 'undef'; + if (typeof v === 'number') return Math.round(v * 1000) / 1000; + if (typeof v === 'boolean') return v; + if (typeof v === 'object') { try { return 'obj:' + Src(v.constructor).slice(9, 24); } catch (e) { return 'obj'; } } + return String(v).slice(0, 24); + }; + + // ---- 转储队列 ---- + var Buf = []; + var Fallback = false; + var flush = function () { + if (!Buf.length) return; + var text = Buf.join('\n') + '\n'; + Buf = []; + if (Fallback) { console.log('[probe] ' + text); return; } + try { + fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: text }) + .then(function (R) { if (!R.ok) throw new Error('HTTP ' + R.status); }) + .catch(function (E) { + Fallback = true; + console.warn('[probe] POST 失败,回落控制台: ' + (E && E.message)); + Buf.push(text.trim()); + flush(); + }); + } catch (e) { Fallback = true; } + }; + var LOG = function () { + var s = '[+' + Now() + 'ms] ' + [].slice.call(arguments).map(function (a) { return typeof a === 'string' ? a : JSON.stringify(a); }).join(' '); + Buf.push(s); + if (Buf.length >= 40) flush(); + }; + + // ---- ③ WS 上行记录 ---- + try { + var W = window.WebSocket; + if (W && W.prototype && !W.prototype.__probeWrapped__) { + var Orig = W.prototype.send; + W.prototype.send = function (d) { + try { + if (d && d.byteLength !== undefined) { + var u = new Uint8Array(d.slice ? d.slice(0, 16) : d, 0, Math.min(16, d.byteLength)); + var h = ''; + for (var i = 0; i < u.length; i++) h += (u[i] < 16 ? '0' : '') + u[i].toString(16); + Buf.push('[+' + Now() + 'ms] SEND len=' + d.byteLength + ' hex=' + h); + } + } catch (e) {} + return Orig.apply(this, arguments); + }; + W.prototype.__probeWrapped__ = true; + Buf.push('WS send 已挂(记帧长+前16字节)'); + } + } catch (e) {} + + var Gm = window.__gameMode__; + if (!Gm) { LOG('__gameMode__ 未就绪'); flush(); return; } + LOG('location=' + location.href + ' 87072含c18v_1=' + (String(window.__modules__ && window.__modules__[87072] || '').indexOf('c18v_1') >= 0)); + + // ---- 找玩家坦克(取组件数最多者)---- + var Tank = null, Best = -1; + [Gm.y100_1, Gm.x100_1, Gm.fzr_1, Gm.ezr_1].forEach(function (t) { + if (!t || !t.oyt_1 || !t.oyt_1.c2a_1) return; + var n = 0; + try { + var m = t.oyt_1.c2a_1; + if (typeof m.forEach === 'function' && !(m instanceof Array)) m.forEach(function () { n++; }); + else n = Object.keys(m).length; + } catch (e) {} + if (n > Best) { Best = n; Tank = t; } + }); + if (!Tank) { LOG('没找到带 oyt_1.c2a_1 的坦克'); flush(); return; } + LOG('玩家坦克组件数=' + Best + ' ctor=' + Ctor(Tank)); + + // ---- ① 全量清单 ---- + var Entries = []; + (function () { + var m = Tank.oyt_1.c2a_1; + if (typeof m.forEach === 'function' && !(m instanceof Array)) m.forEach(function (v, k) { Entries.push([k, v]); }); + else Object.keys(m).forEach(function (k) { Entries.push([k, m[k]]); }); + })(); + LOG('===== 全部组件 ' + Entries.length + ' 个 ====='); + Entries.forEach(function (e) { LOG('#' + e[0] + ' ' + Ctor(e[1]) + ' ▸ ' + (e[1] ? Object.keys(e[1]).join(',') : '(无)')); }); + LOG('===== 组件清单结束 ====='); + + // ---- ② 采样 ---- + var Watch = []; + Entries.forEach(function (e) { + var v = e[1]; + if (!v) return; + var s = Src(v.constructor); + if (/function (z4t|PYt|h_n|vWt|pWt|xen)\(/.test(s) || (typeof v.j3t === 'function' && typeof v.p10g === 'function') || + ('l1ah_1' in v) || ('c18v_1' in v) || ('q1bx_1' in v) || ('h1bh_1' in v)) { + Watch.push({ id: e[0], o: v }); + } + }); + LOG('采样对象 ' + Watch.length + ' 个: ' + Watch.map(function (w) { return '#' + w.id + ' ' + Ctor(w.o).slice(9, 32); }).join(' | ')); + + var Last = ''; + var Timer = setInterval(function () { + var Out = {}; + Watch.forEach(function (w) { + var o = w.o, d = {}; + Object.keys(o).forEach(function (k) { + var v = o[k]; + if (typeof v === 'number' || typeof v === 'boolean' || v === null) d[k] = Num(v); + else if (v && typeof v === 'object' && typeof v.f20_1 === 'number') d[k] = 'V(' + Num(v.f20_1) + ',' + Num(v.g20_1) + ',' + Num(v.h20_1) + ')'; + }); + Out['#' + w.id + ' ' + Ctor(o).slice(9, 32)] = d; + }); + var cur = JSON.stringify(Out); + if (cur !== Last) { Last = cur; LOG('SAMPLE ' + cur); } + flush(); + }, 300); + + window.addEventListener('beforeunload', flush); + window.__probeRailgun__ = function (a) { + if (a === false) { clearInterval(Timer); flush(); return 'stopped'; } + if (a === 'flush') { flush(); return 'flushed'; } + return 'running'; + }; + flush(); + console.log('[probe] 已启动:网络转储 → ' + ENDPOINT + '(落盘 3DTank/names/probe_railgun.log)。停:__probeRailgun__(false)'); +})();