SimTower(摩天大楼)是 90 年代经典的建筑管理模拟游戏,其存档文件以 .tdt 为后缀,采用纯二进制结构存储。理解这一格式不仅有助于复古游戏存档的修复与迁移,更能帮助开发者掌握游戏引擎数据层的设计思路。本文将系统阐述 SimTower 存档格式的逆向工程过程,聚焦于关键数据结构的解析方法与代码重构实践。
文件格式总览与头部结构
SimTower 存档文件采用小端序(Little-Endian)存储整数,这与当年运行在 IBM PC 兼容机上的 Windows 3.1 环境一致。文件头部固定占用 560 字节,包含游戏的全局状态信息。第一个字节在游戏中似乎被忽略,但第二个字节必须是 0x24,否则会触发 “版本错误” 警告,这表明开发者在分发版本时曾使用该字节作为格式版本标记。头部中的星评(stars)字段取值范围为 1 至 5,数值 6 被用于标识特殊的 “摩天大楼” 模式。
现金余额(cash_balance)字段以 100 美元为单位存储,这一设计巧妙地使有效余额上限达到约 42 亿美元(约 0xFFFFFFFF * 100),既避免了浮点数精度问题,又提供了充足的范围。大厦还记录了其他收入(other_income)、建设成本(construction_costs)以及上一季度的现金数据,这些字段共同构成了经济系统的历史轨迹。帧时间(frame_time)与游戏天数(day)字段则用于记录游戏的时序状态,前者可能与内部模拟循环频率相关。
视口坐标(viewport_x、viewport_y)保存了玩家退出游戏时的观察位置,这对实现 “精确续玩” 体验至关重要。大厅高度(lobby_height)决定了底层商业空间的层高,而命名人物计数(named_people_count)则指向文件末尾的额外数据区。值得注意的是,头部中包含大量未使用或保留字段,这些填充区域的存在可能与内存对齐、版本兼容性或预留扩展有关。
楼层与租户系统的二进制表达
SimTower 的大厦最多包含 120 层(含地下层),每层由一个 Floor_Struct 结构描述。该结构包含租户数量、左边界、右边界以及一个变长租户数组。租户数组的长度由 tenant_count 字段动态决定,这种设计允许每层容纳不同数量的商业实体。租户数据随后通过一个固定长度为 94 的映射数组(tenant_id_to_index)建立 ID 到数组索引的关联,这一冗余设计可能在早期版本中用于加速查找操作。
每条租户记录(Tenant_Struct)占用固定的 18 字节,其字段排列经过精心设计以优化内存访问模式。租户类型以单字节无符号整数编码,涵盖酒店单人间(3)、双人间(4)、套房(5)、餐厅(6)、办公室(7)、公寓(9)、商铺(10)、停车场(11)、快餐店(12)、医疗中心(13)、保卫室(14)、家政服务(15)、电影院(18-19)、回收站(20-21)、大厅(29-30)、地铁站(33)等多种建筑类型。租户的水平位置以 8 像素为单位存储为左闭右开区间 [left_edge, right_edge),这种表示方法简化了碰撞检测与渲染逻辑。
租户状态字段记录了建筑的运营状态,例如空置、营业中或待修复。租金等级(rent_class)决定了每季度的收入水平,这一数值与租户类型存在关联但可独立调整。人员偏移量(people_offset)指向人物子系统中该租户对应的首个居民,这一设计将租户与居民两个子系统解耦,使得人员流动计算可以独立于建筑状态进行。
电梯系统的结构化设计
电梯是 SimTower 最重要的垂直交通系统,其数据结构也是整个存档格式中最复杂的部分。每部电梯(Elevator_Struct)占用 194 字节的固定头部,随后是约 3488 字节的运行时状态数据(当电梯存在时)。电梯类型通过单字节编码:0 表示快速电梯(express),1 表示标准电梯(standard),2 表示服务电梯(service)。快速电梯仅停靠地面层和 15 的倍数楼层,这一约束可能与游戏的渲染批次优化有关。
电梯容量(capacity)是逆向工程中发现的最危险字段之一。根据社区测试,容量值超过 42 将导致游戏崩溃,这很可能是因为游戏内部使用固定大小的数组存储电梯乘客状态,数组上界恰好为 42。实践中,非快速电梯的安全容量上限为 42,而游戏界面允许设置的最高值恰好验证了这一结论。电梯还维护 4 组长度为 14 的调度数组,分别对应工作日与周末的七个时段,其中第一个数组似乎完全未被使用,GUI 仅显示六个时段。
楼层停靠信息存储在 120 字节的数组中,每字节对应一层的停靠状态。快速电梯的停靠设置被严格限制在地面层与 15 的倍数楼层,违反此约束同样可能引发崩溃,因为游戏会基于 “潜在服务楼层数”(potential_floors_served)分配运行时数组。电梯还记录了每辆电梯车的休息楼层(resting_floors),最多支持 8 辆电梯车。
人物与经济系统的运行时数据
人物子系统(Person_Struct)使用 16 字节定长记录,存储每位居民的状态信息。每条记录包含目标租户楼层与索引、租户类型、当前状态、当前所在楼层、压力值与评价指标。压力值(stress)影响居民的满意度,进而影响租金缴纳意愿;评价指标(eval)可能与居民的满意度评分相关。这些字段的实时更新构成了 SimTower 经济模拟的核心驱动力。
经济与人口数据集中在 Finances_Struct 结构中,包含十类租户各自的人口与收入数组,以及整栋大厦的总人口与总收入。维护成本同样按租户类型分别统计,这种细粒度的经济追踪使得玩家可以精准分析每种商业业态的盈利能力。收入数据以 100 美元为单位存储,与现金余额字段保持一致。
实用解析工具与安全参数
基于上述分析,可以建立以下可直接用于生产的解析参数与安全约束。文件解析时应验证头部第二个字节为 0x24,非此值应视为未知版本并拒绝加载。整数解析必须采用小端序,优先使用无符号 32 位(ULInt32)与 16 位(ULInt16)类型。电梯容量写入时强制上限为 42,超出值应截断或拒绝。快速电梯的楼层停靠设置仅允许地面层(楼层 10)与 15 的倍数层(25、40、55、70、85、100、115),非法设置应自动过滤。
文件结构的总大小取决于租户与人物的动态数量,但固定区域(头部、楼层结构、电梯、楼梯、经济数据)约占用 200KB 至 300KB。实际解析时应采用流式读取而非一次性加载,以适应不同存档的实际情况。调试时可利用正则表达式 \x01{14}......\x05......\x05 定位 elevators 数据块,该特征串对应电梯调度数组的初始化状态。
代码重构思路与现代实现
将 SimTower 的二进制格式迁移到现代系统时,建议采用分层架构。底层为二进制解析层,负责字节流到结构体的映射,可复用 Python 的 Construct 库或 C++ 的结构体对齐宏。中层为领域模型层,将二进制结构转换为面向对象的租户、电梯、人物类。上层为业务逻辑层,处理经济模拟、时间推进与居民行为。
数据验证应作为解析层的核心职责,包括数值范围检查、引用完整性验证(例如租户 ID 对应有效租户)以及文件完整性校验。考虑到 SimTower 的年龄,建议实现版本兼容性层,在加载旧格式存档时自动进行数据迁移与字段填充。游戏崩溃往往源于运行时数组越界,因此在将数据写入存档前进行前置条件检查是确保稳定性的关键。
资料来源:本文档结构参考了 OpenSkyscraper 项目(GitHub)的 SimTowerLoader 实现与 Archive Team 的 SimTower saved game 格式文档。