原版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结构
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:
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顺序已经重新定位:
- 读取/求解随机组池。
- 保存和恢复同步随机状态边界。
- 按对象树读取并应用属性。
- 附加逻辑数据再加载/连接。
- 统一初始化已创建对象。
对象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。