原版BINLAYOUT属性序列与无损数据入口

本批结论

当前E:/Torchlight 2/MEDIA中的 8987个BINLAYOUT 全为v11,本批全部完成对象/属性框架读取和重编码,逐字节一致。

  • 1492024个对象记录、7401033条属性全部按现有wire schema解码,未知属性0。
  • 随机组尾区结构由既有读取器复核,共47224条组记录。
  • 8974个有对应文本的文件,对象ID预序、描述符与父子对应全部一致。
  • 4144590个共同浮点字段按f32 bits比较无差异;3060762个共同标量/字符串字段对账无差异。
  • 核心IO接入10张源地图的 22354条原始Room Piece记录,按保留的float bits构建显式默认值上的局部姿态。
  • 1207条核心断言、134项相关Python检查通过;Godot C#零warning/error,coresimlint通过。

这些是未展开引用、未进行运行时选池/实例化的原始记录,不是上一批转换器选中的30552个展开物件,也不是已完成的世界碰撞全集。全武器覆盖仍1053/1419。

复用的既有工作

找到并复用了之前Rust打包器的属性wire schema:

D:/WizardProject/TL2/tl2-mikuro-mod-packer-rs/data/binlayout_schema.json。

该表来自既有EditorGuts导出,不伪称本批从发行EXE运行时内存重新抽取。冻结副本在tools/data/native_binlayout_schema.json,保留来源与原文件SHA256。

新读取器以发行EXE读取链和全量实际文件核对:

  • 50BA10:顶层头部、随机组区段、根对象序列。
  • 50B680:对象边界、描述符byte、source ID、名称、附加区及子对象。
  • 50B220:默认记录后依序读取属性,按属性编号派发。
  • 67E2C0:u16 UTF-16单元数量与字符串读取。
  • 4D8D70:有字符串转换入口时的实际wire类型优先为string;不能只看编辑属性的typecode。
  • 4DA040/4D8D80:属性写入与依赖属性回调,尚未整体迁移执行。

核心数字属性表与旧描述符档案用于交叉参考,实际文件的属性顺序不按表中的rank重新排序。

v11结构

text
header: version:u8, flags:u8, group_offset:u32, root_count:u16
object:
  subtree_size:u32, descriptor:u8, source_id:i64
  name: u16 UTF-16-unit count + UTF-16LE
  property_count:u8
  properties[]: length:u16, property_index:u8, payload
  extra_size:u32, extra_bytes
  child_count:u16, children[]

属性length不包括它自己的u16,包含编号byte及payload。 对象长度覆盖整棵子树,从长度字段自身开始计算。 size=0对象槽按原50B680保留为null槽,不挪动后续记录。

Reader严格检查对象/属性边界、UTF-16名称、读取长度和尾部位置。当前仅支持实测覆盖的v11;不猜旧版格式。 未知编号保留原payload,而不是丢掉;零长度属性等需要另行按原类型解释的情况明确拒绝,不用跳过来制造成功。

encode重新组装头部、长度、字符串、属性和子树,不是返回原文件buffer。逻辑/时间线附加区及随机组尾区的原字节保留;前者没有因此被宣称完成语义编译。

对账中发现的三个问题

默认名称被省略

595146个对象在二进制名称字段为空,其文本名称恰好等于描述符名称。 既有编译器也明确执行name==descriptor时写空名称。这不是对象丢失,不应计为场景语义差异。

文本空格不能裁掉

第一轮出现15处字符串差异,均因通用文本读取中的strip处理丢失了前后空格。 本次对账使用保留冒号后原值的读取方式后全部消除,例如JOURNAL中的“ <STAT1> ”。

新二进制字符串入口不Trim。没有顺手改变所有旧文本读取调用方或现有UI表现,避免扩大未验证改动。

source ID不一定唯一

RANDOMBOSSROOM_JT_A.LAYOUT中source ID=3968077233116248545重复8次;二进制和文本的相应预序完全一致。

不能因为重复就丢掉7个对象,也不能把它们折叠进按source ID唯一索引的字典。 读取器和CoreSim.IO使用文件内record offset区分记录,保留全部source ID和parent offset。实际runtime ID分配/源ID重映射仍属于加载器后续步骤。

独立Rust回转与非规范布尔字节

只读运行了既有verify.exe:

text
BINLAYOUT → text → BINLAYOUT:
shipped input 8968/8987 byte-exact
self-compiled fixed point 8987/8987 byte-exact

不能把退出码0当作全量通过,实际还有19个原文件差异。随后对19个文件显式反编译、编译到build目录并逐字节分析:

  • 全部差异是随机组尾区的 24个NO TAG FOUND非0/1字节
  • 文本回转把非零值规范化成1,因此不能保留原始字节。
  • 新读取器原样保留这些字节,19个例外也全部无损往返。
  • 未修改旧Rust工程、原MEDIA或PAK。

这是字节保留结论,不据此推断所有可能的原版bool运算都对非规范值等价。完整选池行为仍需对应原指令验证。

CoreSim.IO入口

core_sim.io/F2BinaryRoomInputs.cs:

  • 保留有序属性记录,浮点值从uint bits恢复,不经过JSON小数重新解析。
  • string值保留空格;未知字段和错误浮点形状显式拒绝。
  • 同一属性重复出现时,空间投影按wire顺序依次覆盖,不排序、不取第一条。
  • ProjectLocalPose要求调用方提供显式NativeSpatialDefaults。
  • 测试使用已验证的CPositionableObject构造默认值,投影10图共22354个原始Room Piece。
  • YAW/统一SCALE等未覆盖的空间setter不会被这条投影接口随意近似。

这不是完整50B220执行器。 描述符默认记录、属性依赖回调、资源装载、父节点创建、初始化事件仍未全部应用;每份导出记录继续标记native_initialization_complete=false。

有序属性提供了后续真正派发所需的数据,不代表“给出一个有限四元数”就完成原版实例化。

初始化与RNG顺序仍需继续

后续核查:默认快照与依赖派发确认默认集合为无getter/setter的值副本。遍历回放并不等于把默认值再次写入对象;不要据本篇的“默认记录”措辞添加额外对象重置。

原50BA10顺序已经重新定位:

  1. 读取/求解随机组池。
  2. 保存和恢复同步随机状态边界。
  3. 按对象树读取并应用属性。
  4. 附加逻辑数据再加载/连接。
  5. 统一初始化已创建对象。

对象byte预序不等于全局最终初始化顺序:选池会跳过子树,Layout Link会产生新的加载过程,属性setter也可能触发资源动作。 289个场景变体不能仅按这个文件顺序加一个假种子就标成解决。

下一批接默认属性记录、依赖回调、runtime ID/父节点关联和真正初始化顺序,再接世界分区与LOS。

验证与范围

  • build/native_binlayout/manifest.json:8987文件、0框架/类型失败、0对账差异。
  • build/native_binlayout/files/**:每文件有序Room Piece属性和对象序列。
  • build/native_binlayout_external/report.json:19个Rust回转例外的全部24个字节差异。
  • build/native_binlayout_core.log:1207断言。
  • build/native_binlayout_python.log:134检查。
  • tests/test_native_binlayout.py另覆盖未知字段、重复编号、重复source ID、null槽、UTF-16代理对、截断和坏边界。

范围限于当前解包目录;其中13个文件没有对应文本,不伪装成已做文本语义对账。附加逻辑payload虽无损保留,但未在本批重新解释执行。

按godot-master的数据/表现分层,本批不改地图画面与默认存档;main原地工作,未提交、未attach原游戏。 默认存档SHA256保持: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f