Files
TankIC/3DTank/tools/client_registry_dump.js
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

249 lines
8.6 KiB
JavaScript
Raw Permalink 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.
// client_registry_dump.js - dump 协议注册表(空间层命令语义的正解)
// 前置:本地镜像的 module_61860.js 已加 3 行 [Hornet 调试] 暴露句柄(见下),
// 所以这里不用再"猜着找",直接读 window 上挂好的实例。
// window.__HornetModelReg__ = mr RPC 分发器 : v7n_1(methodId→modelId) / u7n_1(modelId→模型)
// / w7n_1(modelId→codec) / t7n_1 / s7n_1
// window.__HornetSpaceReg__ = or 空间命令注册表 : x7m_1 / y7m_1(id→codec)
// window.__HornetConn__ = ur 连接实例 : o7m_1(懒容器,含 "spaceRegistry") / p7m_1(分发器)
// 用法:进游戏(大厅/车库都行)→ 控制台整段粘贴 → copy(JSON.stringify(window.__regDump__))
(function () {
// ---------- 自检:确认浏览器跑的是"带钩子"的那份 module_61860 ==========
var M = window.__modules__ && window.__modules__[61860];
var SelfCheck = {
url: location.href,
frames: window.frames.length,
modules: window.__modules__ ? Object.keys(window.__modules__).length : 0,
mod61860Loaded: !!M,
mod61860HasHook: M ? String(M).indexOf('__Hornet') >= 0 : null,
wpRequire: typeof window.__wp_require__,
gameMode: typeof window.__gameMode__,
handleModelReg: typeof window.__HornetModelReg__,
handleSpaceReg: typeof window.__HornetSpaceReg__,
handleConn: typeof window.__HornetConn__,
};
console.log('[RegDump/自检] ' + JSON.stringify(SelfCheck));
if (SelfCheck.mod61860Loaded && SelfCheck.mod61860HasHook === false) {
console.warn('[RegDump/自检] 加载的是旧模块(无 __Hornet 钩子)→ 请硬刷新(Ctrl+Shift+R)或勾上 DevTools 的 Disable cache 再刷新');
}
if (SelfCheck.mod61860HasHook === true && SelfCheck.handleConn === 'undefined') {
console.warn('[RegDump/自检] 钩子已加载但没执行过 → 说明该连接类/引导函数在此流程里没被调用,需要换挂载点');
}
function CtorHead(V) {
try {
if (typeof V === 'function') return String(V).replace(/\s+/g, ' ').slice(0, 130);
var C = V && V.constructor;
return C ? String(C).replace(/\s+/g, ' ').slice(0, 130) : '';
} catch (E) {
return '';
}
}
function CtorName(V) {
try {
return (V && V.constructor && V.constructor.name) || typeof V;
} catch (E) {
return '?';
}
}
function Keys(V) {
try {
return Object.keys(V).slice(0, 50);
} catch (E) {
return [];
}
}
// 这些注册表字段是 Kotlin 的 Map 包装(.g3=put / .z2=get / .a3=keys),不是原生 Map
function IsKollection(V) {
return !!V && typeof V === 'object' && (typeof V.a3 === 'function' || typeof V.t === 'function');
}
// Kotlin 迭代器协议:t() 取迭代器,u() 有下一个,v() 取值
function Iterate(C) {
var Out = [];
var It = null;
try {
It = C.t ? C.t() : null;
} catch (E) {
It = null;
}
if (It && typeof It.u === 'function' && typeof It.v === 'function') {
var Guard = 0;
try {
while (It.u() && Guard++ < 5000) Out.push(It.v());
} catch (E2) {}
return Out;
}
try {
return Array.from(C);
} catch (E3) {
return Out;
}
}
// 统一取「键值对」:原生 Map / Kotlin Map 都吃
function Entries(M, Cap) {
var Out = [];
if (M instanceof Map) {
var N = 0;
M.forEach(function (Val, Key) {
if (N++ < Cap) Out.push({ key: Key, value: Val });
});
return Out;
}
if (IsKollection(M)) {
var Ks = [];
try {
Ks = typeof M.a3 === 'function' ? Iterate(M.a3()) : Iterate(M);
} catch (E) {
Ks = [];
}
for (var I = 0; I < Ks.length && I < Cap; I++) {
var K = Ks[I];
var V = K;
try {
if (typeof M.z2 === 'function') V = M.z2(K);
} catch (E2) {}
Out.push({ key: K, value: V });
}
return Out;
}
return Out;
}
function SizeOf(M) {
try {
if (typeof M.e1 === 'function') return M.e1();
} catch (E) {}
if (M instanceof Map) return M.size;
return -1;
}
// 键/值的可读渲染:这些是 Kotlin 对象,String() 常常是 "[object Object]"
// 优先 p57()(协议 id),其次 toString()(数据类都实现了,如 UserLabelData(userId=…)),
// 最后退化成「类名 + 前几个标量字段」
function KeyLabel(K) {
if (K === null || K === undefined) return String(K);
var T = typeof K;
if (T !== 'object' && T !== 'function') return String(K);
if (T === 'function') {
// 类当键(t7n_1 = 类→模型):直接用类名
try {
return 'class:' + (K.name || '?');
} catch (E0) {
return 'class:?';
}
}
try {
if (typeof K.p57 === 'function') {
var Id = K.p57();
if (Id !== null && Id !== undefined && typeof Id !== 'object') return 'id:' + String(Id);
}
} catch (E) {}
try {
var S = String(K);
if (S && S !== '[object Object]' && S !== '[object Function]') return S;
} catch (E2) {}
var Own = [];
var Ks = Object.keys(K).slice(0, 6);
for (var I = 0; I < Ks.length; I++) {
var V;
try {
V = K[Ks[I]];
} catch (E3) {
continue;
}
var R;
if (V === null || V === undefined) R = String(V);
else if (typeof V !== 'object') R = String(V);
else if (typeof V.p57 === 'function') {
try {
R = 'id:' + String(V.p57());
} catch (E4) {
R = '{obj}';
}
} else R = '{obj}';
Own.push(Ks[I] + '=' + R);
}
return CtorName(K) + '(' + Own.join(',') + ')';
}
function DumpMap(M, Cap) {
if (!(M instanceof Map) && !IsKollection(M)) return { kind: typeof M, size: -1, head: CtorHead(M) };
var Out = [];
var Ent = Entries(M, Cap);
for (var I = 0; I < Ent.length; I++) {
var Val = Ent[I].value;
var Key = Ent[I].key;
var T = typeof Val;
var IsPrim = Val === null || T === 'number' || T === 'string' || T === 'boolean' || T === 'bigint';
Out.push({
key: KeyLabel(Key),
raw: IsPrim ? String(Val) : undefined, // 保留原始标量(v7n_1 的值=模型哈希,join 要用)
ctor: CtorName(Val),
keys: Keys(Val),
head: CtorHead(Val),
text: (function () {
try {
var S = String(Val);
return S && S !== '[object Object]' ? S.slice(0, 300) : '';
} catch (E) {
return '';
}
})(),
});
}
return { kind: 'MapLike', size: SizeOf(M), entries: Out };
}
var Sources = {
ModelReg: window.__HornetModelReg__,
SpaceReg: window.__HornetSpaceReg__,
Conn: window.__HornetConn__,
};
var Dump = { handles: {}, maps: {} };
for (var Name in Sources) {
var H = Sources[Name];
Dump.handles[Name] = H ? { ctor: CtorName(H), keys: Keys(H) } : null;
if (!H) continue;
var Ks = Keys(H);
for (var I = 0; I < Ks.length; I++) {
var V;
try {
V = H[Ks[I]];
} catch (E) {
continue;
}
if (V instanceof Map || IsKollection(V)) Dump.maps[Name + '.' + Ks[I]] = DumpMap(V, 800);
else if (V && typeof V === 'object' && !Array.isArray(V)) {
// 懒容器(如 Conn.o7m_1)里可能嵌着 Map/集合,再挖一层
var K2 = Keys(V);
for (var J = 0; J < K2.length; J++) {
try {
var V2 = V[K2[J]];
if (V2 instanceof Map || IsKollection(V2)) Dump.maps[Name + '.' + Ks[I] + '.' + K2[J]] = DumpMap(V2, 400);
} catch (E2) {}
}
}
}
}
window.__regDump__ = Dump;
console.log('[RegDump] 句柄: ' + Object.keys(Dump.handles).map(function (N) {
return N + '=' + (Dump.handles[N] ? Dump.handles[N].keys.length + '字段' : '缺失');
}).join(' '));
Object.keys(Dump.maps).forEach(function (P) {
console.log(' ' + P + ' size=' + Dump.maps[P].size);
});
// 直接上传到本地服务器落盘(serve.js 的 /__dump__/ 端点,写到 3DTank/names/ 下)
try {
var Body = JSON.stringify(Dump);
fetch('/__dump__/regdump.json', { method: 'POST', body: Body }).then(function (R) {
console.log('[RegDump] 已上传 /__dump__/regdump.json → HTTP ' + R.status + '(' + Body.length + ' 字节)');
}).catch(function (E) {
console.warn('[RegDump] 上传失败,请手动 copy(JSON.stringify(window.__regDump__)):' + E.message);
});
} catch (E5) {
console.warn('[RegDump] 上传异常:' + E5.message);
}
console.log('[RegDump] 备用导出:copy(JSON.stringify(window.__regDump__))');
})();