工具链(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/(含数据库口令)不进库,可用上述工具重新生成。
113 lines
4.0 KiB
JavaScript
113 lines
4.0 KiB
JavaScript
// entry.js - 入口层:握手 / 进空间 / 客户端日志
|
||
//
|
||
// c2s 1 Hash Request 体 = [varint N][N × 字符串 键][varint M][M × 字符串 值](两个各自计数的表)
|
||
// c2s 3 Auth/EnterSpace 体 = [32 字节 digest][int64 spaceId](digest = 回显服务器下发的 hash)
|
||
// c2s 32 ClientLog 体 = [1 字节类型][字符串 名称][字符串 正文](正文常是 UI 事件 JSON,按钮事件来源)
|
||
// s2c 2 Hash Response 体 = [1 字节 2][32 字节 hash][1 字节 是否启用链路加密]
|
||
// s2c 32 Open Space 体 = [1 字节 32][int64 spaceId]
|
||
//
|
||
// 注 1:命令 ID 可跨方向复用(32 在两个方向含义不同),故按方向分表。
|
||
// 注 2:实测真实服务器的「加密」标志是 1(整条链路转加密,抓包里后续帧都要解 K);
|
||
// 本服务器发 0,客户端接受并保持明文(fake 模式与 M1 冒烟都验证过),省掉密钥轮换。
|
||
'use strict';
|
||
const { ReadVarint } = require('../lib_space_codec');
|
||
|
||
const C2S_NAME = { 1: 'Hash Request', 3: 'Auth/EnterSpace', 32: 'ClientLog' };
|
||
const S2C_NAME = { 2: 'Hash Response', 32: 'Open Space' };
|
||
|
||
// varint 长度前缀 + utf8 字节
|
||
function ReadString(Buf, Off) {
|
||
const N = ReadVarint(Buf, Off);
|
||
const End = N.Next + N.Value;
|
||
if (End > Buf.length) return { Str: '<越界>', Next: Buf.length, Bad: true };
|
||
return { Str: Buf.slice(N.Next, End).toString('utf8'), Next: End };
|
||
}
|
||
|
||
function WriteString(S) {
|
||
const B = Buffer.from(String(S), 'utf8');
|
||
return Buffer.concat([WriteVarint(B.length), B]);
|
||
}
|
||
|
||
function WriteVarint(N) {
|
||
const V = Number(N);
|
||
if (V < 0x80) return Buffer.from([V]);
|
||
if (V < 0x4000) return Buffer.from([0x80 | (V >> 8), V & 0xff]);
|
||
if (V < 0x400000) return Buffer.from([0xc0 | (V >> 16), (V >> 8) & 0xff, V & 0xff]);
|
||
throw new Error('varint 过大: ' + V);
|
||
}
|
||
|
||
// At = 命令体起始(已跳过 nullmap 与命令 ID 字节)
|
||
// 布局实测(lobby 抓包 563B / 572B 两例,均吃满到帧体末尾):
|
||
// [varint N][N × (varint 长度 + utf8 键)][varint M][M × (varint 长度 + utf8 值)]
|
||
// 两个计数各自独立(实测 N == M),按序号配对。
|
||
function ParseHashRequest(Buf, At) {
|
||
const Cn = ReadVarint(Buf, At);
|
||
const Keys = [];
|
||
const Values = [];
|
||
let Off = Cn.Next;
|
||
for (let I = 0; I < Cn.Value; I++) {
|
||
const R = ReadString(Buf, Off);
|
||
if (R.Bad) return { Count: Cn.Value, Keys, Values, Ok: false, Next: Off };
|
||
Keys.push(R.Str);
|
||
Off = R.Next;
|
||
}
|
||
const Cm = ReadVarint(Buf, Off);
|
||
Off = Cm.Next;
|
||
for (let I = 0; I < Cm.Value; I++) {
|
||
const R = ReadString(Buf, Off);
|
||
if (R.Bad) return { Count: Cn.Value, Keys, Values, Ok: false, Next: Off };
|
||
Values.push(R.Str);
|
||
Off = R.Next;
|
||
}
|
||
return { Count: Cn.Value, CountValues: Cm.Value, Keys, Values, Ok: Off === Buf.length, Next: Off };
|
||
}
|
||
|
||
function ParseEnterSpace(Buf, At) {
|
||
if (At + 40 > Buf.length) return null;
|
||
return { Digest: Buf.slice(At, At + 32).toString('hex'), SpaceId: Buf.readBigInt64BE(At + 32).toString() };
|
||
}
|
||
|
||
function ParseClientLog(Buf, At) {
|
||
if (At >= Buf.length) return null;
|
||
const S1 = ReadString(Buf, At + 1);
|
||
const S2 = ReadString(Buf, S1.Next);
|
||
return { Type: Buf[At], Name: S1.Str, Text: S2.Str };
|
||
}
|
||
|
||
// 回调 hash:32 字节随机;Plaintext = true → 置 0,整条链路明文
|
||
function BuildHashResponse(Hash, Plaintext) {
|
||
if (Hash.length !== 32) throw new Error('hash 必须 32 字节');
|
||
const Out = Buffer.alloc(34);
|
||
Out[0] = 2;
|
||
Hash.copy(Out, 1);
|
||
Out[33] = Plaintext ? 0 : 1;
|
||
return Out;
|
||
}
|
||
|
||
function BuildOpenSpace(SpaceId) {
|
||
const Out = Buffer.alloc(9);
|
||
Out[0] = 32;
|
||
Out.writeBigInt64BE(BigInt(SpaceId), 1);
|
||
return Out;
|
||
}
|
||
|
||
function MakeHash() {
|
||
const Out = Buffer.alloc(32);
|
||
for (let I = 0; I < 32; I++) Out[I] = Math.floor(Math.random() * 256);
|
||
return Out;
|
||
}
|
||
|
||
module.exports = {
|
||
C2S_NAME,
|
||
S2C_NAME,
|
||
ReadString,
|
||
WriteString,
|
||
WriteVarint,
|
||
ParseHashRequest,
|
||
ParseEnterSpace,
|
||
ParseClientLog,
|
||
BuildHashResponse,
|
||
BuildOpenSpace,
|
||
MakeHash,
|
||
};
|