客户端实现三种容器布局的读,与 JS 实现(3DTank/tools/server/container.js)逐字段对照:5/5 帧、一致叶子 26950 / 不一致 0,两份规范 JSON SHA256 完全相同(各 795139 B)。覆盖 space-100073070/s2c_0004.bin 的 pt(7210 项,含 515 个 Id=0 字面量与 6695 个模型展开) 与其后 28 条 fields 记录——M1 会把这 28 条全丢掉。协议表由 codec_fields.json 的 1143497 B 裁到 ProtocolTables.json 的 124270 B(削 89.1%),做法:只留解码必需属性 + 闭包裁剪(根=所有带 ModelId 的 codec ∪ 命令/容器方法,沿 TypeCodec 与模型真名取闭包,保留 559/643 个 codec),闭包外如实报「无 codec」。计划外但必需:space-100045260 的 s2c_0000/s2c_0004 是 zlib 压缩帧,M1 直接抛错,现用 Node zlib 解压(browser 侧仍显式抛错);并新增正式产物 client/tools/CompareContainerDecode.mjs。
42 lines
2.4 KiB
Kotlin
42 lines
2.4 KiB
Kotlin
// ModelCodec.kt —— 模型 codec 的字段展开(口径逐条对照 3DTank/tools/lib_space_codec.js 的
|
||
// ReadValue 尾部分支与 ReadFields)
|
||
//
|
||
// 什么时候会走到这里:容器 pt 的每一项(container.js 的 ReadLtItem:id 是 modelId →
|
||
// 就地展开该模型的字段),以及普通字段里类型是「模型类」的字段(字段自带 TypeCodec)。
|
||
//
|
||
// 解析顺序(必须与 lib_space_codec.js 一致,否则同一个字节流会解出不同的边界):
|
||
// ① 字段的 TypeCodec 命中导出的 codec 表 → 用该 codec
|
||
// ② 类型名命中「模型真名 → codec」(CodecByModelName)→ 用该 codec
|
||
// ③ 命中「协议核心类 / 资源类 → 线上基础类型」直译(FieldReader 里做)
|
||
// 三条都不命中 → FieldReader 如实报「无 codec」,绝不用长度或字节数去猜。
|
||
package Protocol
|
||
|
||
internal object ModelCodec {
|
||
|
||
// 命中 ①② 就返回 codec 键;都不命中返回 null(③ 与报错交给 FieldReader)
|
||
fun ResolveCodec(Type: String, TypeCodec: String): String? {
|
||
if (TypeCodec.isNotEmpty() && ProtocolTables.Codec(TypeCodec) != null) return TypeCodec
|
||
return ProtocolTables.CodecByModelName(Type)
|
||
}
|
||
|
||
// 按 codec 键就地展开字段(无长度前缀)。字段名口径同 lib_space_codec.js:306:
|
||
// 匿名字段(表里没有名字)用 `(匿名#序)` 区分,否则同名会互相覆盖。
|
||
// 中途解不动:返回已解出的部分 + Bad + 带字段名/类型/相对偏移的原因串(调用方据此停下并如实标注)。
|
||
fun ReadFields(Buf: ByteArray, Off: Int, CodecKey: String, Depth: Int, Ctx: ReadCtx?): FieldReadResult {
|
||
val Fields = ProtocolTables.Codec(CodecKey)
|
||
?: return FieldReadResult(null, Off, true, "codec 键 $CodecKey 不在导出表里(闭包裁剪掉了,不猜)")
|
||
val Obj = LinkedHashMap<String, Any?>()
|
||
var At = Off
|
||
for (F in Fields) {
|
||
val R = FieldReader.ReadValue(Buf, At, F.Type, F.TypeCodec, Depth, Ctx)
|
||
Obj[F.Name] = R.Value
|
||
if (R.Bad) {
|
||
val Why = "字段 ${F.Name}(${F.Type})在 +${At - Off} 处解不动" + (if (R.Why.isNotEmpty()) ":${R.Why}" else "")
|
||
return FieldReadResult(Obj, At, true, Why)
|
||
}
|
||
At = R.Next
|
||
}
|
||
return FieldReadResult(Obj, At, false)
|
||
}
|
||
}
|