// entry.js - 入口层:握手 / 进空间 / 客户端日志 // // c2s 1 Hash Request 体 = [varint N][N × 字符串 键][varint M][M × 字符串 值](两个各自计数的表) // c2s 3 Auth/EnterSpace 体 = [32 字节 digest][int64 spaceId](digest = 回显服务器下发的 hash) // c2s 32 ClientLog 体 = [1 字节类型][字符串 名称][字符串 正文](正文常是 UI 事件 JSON,按钮事件来源) // s2c 2 Hash Response 体 = [1 字节 2][32 字节 hash][1 字节 是否启用链路加密] // s2c 32 Open Space 体 = [1 字节 32][int64 spaceId] // // 注 1:命令 ID 可跨方向复用(32 在两个方向含义不同),故按方向分表。 // 注 2:实测真实服务器的「加密」标志是 1(整条链路转加密,抓包里后续帧都要解 K); // 本服务器发 0,客户端接受并保持明文(fake 模式与 M1 冒烟都验证过),省掉密钥轮换。 'use strict'; const { ReadVarint } = require('../lib_space_codec'); const C2S_NAME = { 1: 'Hash Request', 3: 'Auth/EnterSpace', 32: 'ClientLog' }; const S2C_NAME = { 2: 'Hash Response', 32: 'Open Space' }; // varint 长度前缀 + utf8 字节 function ReadString(Buf, Off) { const N = ReadVarint(Buf, Off); const End = N.Next + N.Value; if (End > Buf.length) return { Str: '<越界>', Next: Buf.length, Bad: true }; return { Str: Buf.slice(N.Next, End).toString('utf8'), Next: End }; } function WriteString(S) { const B = Buffer.from(String(S), 'utf8'); return Buffer.concat([WriteVarint(B.length), B]); } function WriteVarint(N) { const V = Number(N); if (V < 0x80) return Buffer.from([V]); if (V < 0x4000) return Buffer.from([0x80 | (V >> 8), V & 0xff]); if (V < 0x400000) return Buffer.from([0xc0 | (V >> 16), (V >> 8) & 0xff, V & 0xff]); throw new Error('varint 过大: ' + V); } // At = 命令体起始(已跳过 nullmap 与命令 ID 字节) // 布局实测(lobby 抓包 563B / 572B 两例,均吃满到帧体末尾): // [varint N][N × (varint 长度 + utf8 键)][varint M][M × (varint 长度 + utf8 值)] // 两个计数各自独立(实测 N == M),按序号配对。 function ParseHashRequest(Buf, At) { const Cn = ReadVarint(Buf, At); const Keys = []; const Values = []; let Off = Cn.Next; for (let I = 0; I < Cn.Value; I++) { const R = ReadString(Buf, Off); if (R.Bad) return { Count: Cn.Value, Keys, Values, Ok: false, Next: Off }; Keys.push(R.Str); Off = R.Next; } const Cm = ReadVarint(Buf, Off); Off = Cm.Next; for (let I = 0; I < Cm.Value; I++) { const R = ReadString(Buf, Off); if (R.Bad) return { Count: Cn.Value, Keys, Values, Ok: false, Next: Off }; Values.push(R.Str); Off = R.Next; } return { Count: Cn.Value, CountValues: Cm.Value, Keys, Values, Ok: Off === Buf.length, Next: Off }; } function ParseEnterSpace(Buf, At) { if (At + 40 > Buf.length) return null; return { Digest: Buf.slice(At, At + 32).toString('hex'), SpaceId: Buf.readBigInt64BE(At + 32).toString() }; } function ParseClientLog(Buf, At) { if (At >= Buf.length) return null; const S1 = ReadString(Buf, At + 1); const S2 = ReadString(Buf, S1.Next); return { Type: Buf[At], Name: S1.Str, Text: S2.Str }; } // 回调 hash:32 字节随机;Plaintext = true → 置 0,整条链路明文 function BuildHashResponse(Hash, Plaintext) { if (Hash.length !== 32) throw new Error('hash 必须 32 字节'); const Out = Buffer.alloc(34); Out[0] = 2; Hash.copy(Out, 1); Out[33] = Plaintext ? 0 : 1; return Out; } function BuildOpenSpace(SpaceId) { const Out = Buffer.alloc(9); Out[0] = 32; Out.writeBigInt64BE(BigInt(SpaceId), 1); return Out; } function MakeHash() { const Out = Buffer.alloc(32); for (let I = 0; I < 32; I++) Out[I] = Math.floor(Math.random() * 256); return Out; } module.exports = { C2S_NAME, S2C_NAME, ReadString, WriteString, WriteVarint, ParseHashRequest, ParseEnterSpace, ParseClientLog, BuildHashResponse, BuildOpenSpace, MakeHash, };