// 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-.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 };