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