工具链(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/(含数据库口令)不进库,可用上述工具重新生成。
68 lines
2.8 KiB
JavaScript
68 lines
2.8 KiB
JavaScript
// world.js - 服务端世界状态:空间实例表 / 空间分类 / 未实现命令清单
|
||
//
|
||
// M1 只承载「服务器需要记住什么」:客户端连过哪些空间、每个空间收到了哪些命令(尚未实现语义)。
|
||
// 这四个注册表(gameType / model / resource / space)在 M2 才会真正填内容——
|
||
// 现在先把「客户端要什么」记全,那就是施工图。
|
||
'use strict';
|
||
|
||
// 入口空间:真实服务器在 Hash Response 同一帧里下发(实测 0x5f691cc = 100045260)
|
||
const ENTRANCE_SPACE = 100045260;
|
||
|
||
// 空间分类。置信度如实标注:
|
||
// 实测 = 抓包/源码直接对得上;容器 = 从该空间容器的模式表推出来的用途;待核实 = 只有命名线索
|
||
// 注意:超过 2^53 的 id 必须写成字符串(3074457345817190050 用数字字面量会掉精度)
|
||
const SPACE_KIND = {
|
||
100045260: 'Entrance', // 实测:与 Hash Response 同帧下发,只有 3 条 s2c
|
||
100073070: 'Garage', // 实测:客户端上报 button_click "GARAGE" 后服务器 Open Space(100073070)
|
||
100072911: 'Shop', // 容器模式表 = Jj / ShopCategoryCC
|
||
100045720: 'Chat', // 容器模式表 = _t / Kz / ChatCC / ClanChatCC / NewsShowingCC
|
||
920007382011: 'Queue', // 待核实(旧注释写「快速战斗队列」)
|
||
100119080: 'Unknown',
|
||
'3074457345817190050': 'Unknown', // 待核实:帧量极大(3551 条 s2c),疑似战斗
|
||
};
|
||
|
||
function MakeWorld() {
|
||
const Spaces = new Map();
|
||
const Unimplemented = new Map();
|
||
let NoteCount = 0;
|
||
|
||
function EnsureSpace(Id) {
|
||
const Key = String(Id);
|
||
let S = Spaces.get(Key);
|
||
if (!S) {
|
||
S = { Id: Key, Kind: ClassifySpace(Key), Commands: 0, Since: new Date().toISOString() };
|
||
Spaces.set(Key, S);
|
||
}
|
||
return S;
|
||
}
|
||
|
||
// 记一条「还没实现语义」的上行命令(按 空间类别 + 命令名 归并)
|
||
function NoteUnimplemented(Kind, Name, MethodId, Target) {
|
||
NoteCount++;
|
||
const Key = Kind + ' ' + Name;
|
||
let U = Unimplemented.get(Key);
|
||
if (!U) {
|
||
U = { Kind, Name, MethodId, Count: 0, Targets: new Set() };
|
||
Unimplemented.set(Key, U);
|
||
}
|
||
U.Count++;
|
||
if (U.Targets.size < 5) U.Targets.add(String(Target));
|
||
}
|
||
|
||
// 会话结束/定时打印用:按次数排序的未实现清单
|
||
function UnimplementedSummary() {
|
||
const Rows = [...Unimplemented.values()]
|
||
.sort((A, B) => B.Count - A.Count)
|
||
.map((U) => ({ Kind: U.Kind, Name: U.Name, MethodId: U.MethodId, Count: U.Count, Targets: [...U.Targets] }));
|
||
return { Total: NoteCount, Distinct: Rows.length, Rows };
|
||
}
|
||
|
||
return { Spaces, Unimplemented, EnsureSpace, NoteUnimplemented, UnimplementedSummary };
|
||
}
|
||
|
||
function ClassifySpace(Id) {
|
||
return SPACE_KIND[String(Id)] || 'Unknown';
|
||
}
|
||
|
||
module.exports = { MakeWorld, ClassifySpace, ENTRANCE_SPACE, SPACE_KIND };
|