Files
TankIC/3DTank/tools/match_methods.js
T
TermiNexus b6f2cb71ee feat(3dtank): 协议逆向工程与自研服务器入库(工具链 + 协议表 + 空间回放 + MySQL)
工具链(3DTank/tools/):extract(模块提取)、mine_evidence/apply_names(命名证据与别名回填)、extract_codec_fields(字段表 + 全库槽位类型 + PrimClassWire/ResourceClasses)、mine_downlink(下行字段表)、extract_space_init/rebase_space_init/audit_space_init(空间模板与记录切分审计)、extract_space_session(把真实会话切成按上行触发的回放模板)、pair_flow(请求→应答配对)、probe_containers、ingest/decrypt_frames(抓包入库与解密)、serve/fetch_assets(本地站点镜像)、import_db(抓包对象仓库导入 MySQL)。

服务器(3DTank/tools/server/):link/entry/codec/container/dispatch/world/space/semantic(协议层与 lt/pt/zt 三个容器布局)、session(会话回放,顺序推进)、db + schema.sql(MySQL 连接层与对象仓库表结构)、store/responder(查库组帧与定向应答;接管范围默认只开 ba.lcr)。

同时修 pair_flow 的时间轴偏移:timeline_c2s 与 timeline_s2c 的 0 点相差 144~4453ms,改前配对整体偏约 2 秒(实测 space-100073070 偏 2084ms),改后 22.4% 的触发者被纠正。

src/、src_named/、packets/、3DTank/local/(含数据库口令)不进库,可用上述工具重新生成。
2026-09-17 20:00:41 +08:00

81 lines
2.9 KiB
JavaScript

// match_methods.js - 用客户端 dump 出的 methodId 表去扫抓包帧,验证"线上哪个字段是 methodId"
// 用法: node match_methods.js [--root ../packets/plain] [--table ../names/method_table.json] [--limit 40]
// 原理:
// names/method_table.json 是 methodId(64 位) → modelId → 模型类/codec(客户端注册表 dump)。
// 空间层的 RPC 分发是 mr.l7n(ctx, methodId, data),所以帧里应能找到 methodId 的 8 字节大端。
// 命中即说明该帧属于哪个模型(命令),从而把命令语义一个个钉死。
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const Argv = process.argv.slice(2);
const Arg = (Name, Def) => {
const I = Argv.indexOf('--' + Name);
return I >= 0 && Argv[I + 1] ? Argv[I + 1] : Def;
};
const PROJECT = path.resolve(__dirname, '..');
const Root = path.resolve(Arg('root', path.join(PROJECT, 'packets', 'plain')));
const TablePath = path.resolve(Arg('table', path.join(PROJECT, 'names', 'method_table.json')));
const Limit = Number(Arg('limit', '40')) || 40;
const Table = JSON.parse(fs.readFileSync(TablePath, 'utf8'));
// methodId → 模型信息;同时准备 8 字节大端字节串
const ByHex = new Map();
for (const R of Table.Rows) {
const Big = BigInt(R.MethodId);
const Buf = Buffer.alloc(8);
Buf.writeBigInt64BE(Big);
ByHex.set(Buf.toString('hex'), R);
}
function Unwrap(Buf) {
const B0 = Buf[0];
const HeaderSize = B0 & 0x80 ? 4 : 2;
let Body = Buf.slice(HeaderSize);
if (B0 & 0x40) {
try {
Body = zlib.inflateSync(Body);
} catch (Err) {
return null;
}
}
return Body;
}
if (!fs.existsSync(Root)) {
console.log('目录不存在: ' + Root);
process.exit(1);
}
const Hits = [];
let Scanned = 0;
for (const Space of fs.readdirSync(Root).sort()) {
const Dir = path.join(Root, Space);
if (!fs.statSync(Dir).isDirectory()) continue;
for (const F of fs.readdirSync(Dir)) {
if (!/\.bin$/.test(F)) continue;
const Raw = fs.readFileSync(path.join(Dir, F));
const Body = Unwrap(Raw);
if (!Body || Body.length < 8) continue;
Scanned++;
for (let Off = 0; Off + 8 <= Body.length; Off++) {
const Hex = Body.slice(Off, Off + 8).toString('hex');
const R = ByHex.get(Hex);
if (R) Hits.push({ File: Space + '/' + F, Off, Size: Raw.length, Model: R.Model, MethodId: R.MethodId, Codec: R.Codec, CodecFields: R.CodecFields });
}
}
}
console.log('扫描文件 ' + Scanned + ' 个,命中 methodId ' + Hits.length + ' 处');
const Count = {};
for (const H of Hits) Count[H.Model] = (Count[H.Model] || 0) + 1;
console.log('命中模型分布: ' + JSON.stringify(Count));
console.log('');
for (const H of Hits.slice(0, Limit)) {
console.log(
H.File.padEnd(30) + ' @' + String(H.Off).padStart(5) + ' ' + H.Size + 'B model=' + H.Model +
' codec=' + H.Codec + ' codecFields=' + String(H.CodecFields).slice(0, 70),
);
}