Files
TankIC/3DTank/tools/server/entry.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

113 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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,
};