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

66 lines
3.6 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.
// space.js - 空间帧组装 + 空间初始化模板
//
// s2c 空间帧 = [nullmap][(objectId int64)(methodId int64)(负载)…](出处见 dispatch.js 头注 + semantic.js 头注)
// 注意是**每条记录自带 objectId**(src/module_61860.js:2119-2120),不是「帧头一个 spaceId」;
// 单记录帧里两者恰好相同,所以旧的「帧级 spaceId」写法仍然等价(见 BuildSpaceFrame 的兼容路径)。
//
// 「初始化模板」= names/space-init-<spaceId>.json。它存的是**结构化模板**而不是原始帧字节:
// 容器记录存「解出来的对象树」(发送时由 container.js 重新编码),
// 普通字段记录存「字段 JSON」(发送时由 semantic.js 现编),
// 只有解不动的记录才存 hex。
// 这样模板里的每个 id / modelId / 项 / 字段都可以被替换成我们自己的数据(M2 的后续工作),
// 而不是把抓包字节原样回放。
'use strict';
const fs = require('fs');
const path = require('path');
const Link = require('./link');
// Records = [{ ObjectId?, MethodId, Payload: Buffer }]
// NullmapHex:nullmap 原样字节(十六进制)。注意它不是「可有可无的 0」——
// 实测有帧的 nullmap 是 2 字节且带内容(如 `20 3c`),含义是「哪些字段为空、负载里因此没有它们」,
// 写成 0x00 会让客户端按「非空」去读,导致错位。所以能拿到原 nullmap 就必须原样带上。
// (EncodeFrame 自己会拼 nullmap,所以这里 Parts 里不能再放一个。)
// ObjectId 缺省 → 回退用帧级 SpaceId:这是**兼容路径**,只为让「每条记录 objectId 相同」这类旧调用
// (以及还没升级出 ObjectId 的老模板)继续能跑;新模板里每条记录都应带自己的 ObjectId。
function BuildSpaceFrame(SpaceId, Records, NullmapHex) {
const Nm = Buffer.from(NullmapHex || '00', 'hex');
const Parts = [];
for (const R of Records) {
const Id = Buffer.alloc(8);
Id.writeBigInt64BE(BigInt(R.ObjectId === undefined || R.ObjectId === null ? SpaceId : R.ObjectId), 0);
const Method = Buffer.alloc(8);
Method.writeBigInt64BE(BigInt(R.MethodId), 0);
Parts.push(Id, Method, R.Payload);
}
const Body = Buffer.concat(Parts);
return Link.EncodeFrame(Body, Nm);
}
function LoadInitTemplate(NamesDir, SpaceId) {
const File = path.join(NamesDir, 'space-init-' + SpaceId + '.json');
if (!fs.existsSync(File)) return null;
return JSON.parse(fs.readFileSync(File, 'utf8'));
}
// 模板 → [{ File, DelayMs, Frame }];负载由 Container / Semantic 重新编码
// 记录的三种形态:① { Layout, Value } 容器(container.js)② { Fields } 普通字段(semantic.js)③ { Hex } 未解原样透传
function BuildInitFrames(Template, Container, Semantic) {
const Out = [];
for (const F of Template.Frames || []) {
const Records = F.Records.map((R) => {
let Payload;
if (R.Layout !== undefined) Payload = Container.Write(R.Layout, R.Value);
else if (R.Fields !== undefined) {
// 字段 JSON 必须由语义层现编:它才知道该 methodId 的有序字段与类型
if (!Semantic) throw new Error('模板记录是 Fields 形态(methodId=' + R.MethodId + '),但没传 semantic.js 实例');
Payload = Semantic.EncodeRecord(R.MethodId, R);
} else Payload = Buffer.from(R.Hex || '', 'hex');
return { ObjectId: R.ObjectId, MethodId: R.MethodId, Payload };
});
Out.push({ File: F.File, DelayMs: F.DelayMs || 0, Frame: BuildSpaceFrame(F.SpaceId, Records, F.NullmapHex) });
}
return Out;
}
module.exports = { BuildSpaceFrame, LoadInitTemplate, BuildInitFrames };