工具链(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/(含数据库口令)不进库,可用上述工具重新生成。
81 lines
2.9 KiB
JavaScript
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),
|
|
);
|
|
}
|