NEE's Blog

逆向工程 1998 年《网络创世纪》演示服务器

May 06, 2026

本文翻译自 Reverse-engineering the 1998 Ultima Online demo server,原载于 Hacker News。

Blacksmithy

项目背景

经过断断续续十年的工作,作者终于发布了对 1998 年 Ultima Online(网络创世纪)演示服务器的完整逆向工程项目:ouo

这个项目的规模令人叹服——约 5000 个函数从 MSVC x86 反汇编代码翻译为可移植的 C99 代码,每个函数都与原始二进制文件逐指令对比验证。

UoDemo.exe 是什么

对于不太了解的读者,Ultima Online(简称 UO)是 Origin Systems 于 1997 年开发的 MMORPG,也是最早取得商业成功的 MMORPG 之一。客户端运行在 Windows 上,而每个服务器(称为”shard”)则运行在多台 Solaris 机器上(地图按区域划分)。

1998 年 10 月发布的”The Second Age”资料片首批版本中附带了一个独立的 UO 演示程序(UoDemo.exeUoDemo.dat),它捆绑了客户端和完整服务器代码的 Windows 移植版。UoDemo.exe 的日期为 1998-09-02,服务器数据从 1998 年 6 月 2 日的生产服务器中提取。

虽然演示版做了一些删减——可玩地图缩减为 Ocllo 岛(不列颠尼亚南岸的一个小镇),部分功能被 stub 掉——但其余部分是 1998 年中期线上 UO 运行的真实生产代码。多年来,许多 UO 服务器模拟器复用了其中的部分代码,但此前从未有人完成过完整的逆向工程。

值得注意的是,UoDemo.exe 使用 Microsoft Visual C++ 5.0(Visual Studio 97)编译,目标是 C++98 标准化之前的一个 C++ 方言。作者断断续续工作了十年,直到 LLM 的最新进展才让这个看似永无止境的任务终于完成。

笔者感言:这恰恰是 LLM 在工程领域最优雅的应用场景之一——不是生成新代码,而是辅助人类理解和翻译已有二进制代码。人提供架构理解和工程判断,LLM 提供模式匹配和规模化分析能力,两者缺一不可。

方法论

整个逆向工程的方法论值得所有做类似项目的开发者参考:

  • 反汇编工具:使用 radare2 进行反汇编。
  • 符号恢复:从一个实验性的 UO 客户端 1.25.37 Linux 移植版中推断符号名称,该版本附带了 C++ 符号信息。
  • 手工翻译:每个函数手动翻译为 C99,保持与二进制相同的控制流、结构体布局和分支。当存在差异时,要么是对演示版中真实 bug 的修复,要么是平台适配,都会在源码中标注。
  • 逐指令验证:将 C 编译结果用 radare2 重新反汇编,与原始二进制逐条对比。只有两者完全匹配,函数才标记为完成。
  • 辅助函数最小化:仅对重复出现的内联模式使用辅助函数,且确保辅助函数展开后与内联版本生成相同代码。

其中最关键的早期工作是理清 类层次结构

CEntity (0x10) -> CResourceEntity (0x1C) -> CItem (0x50)
  -> CContainer (0x5C) -> CMobile (0x37C) -> CPlayer (0x458)

虚函数通过 vtable 槽位分派(vtable[0x18]IsPlayer[0xD0]IsMobile[0xE4]IsNPC 等)。一旦这些内存布局确定下来,大部分二进制代码的翻译就变得相对直接了。

这种”先建立类型系统骨架,再逐个填充函数”的思路,对所有逆向工程项目都很有借鉴意义。在大型二进制项目中,盲目逐函数反编译只会让人迷失。

重要发现

与原始代码相比,作者修复了大量问题:

稳定性修复:崩溃、缓冲区溢出、未初始化变量等。

游戏性修复:技能提升机制、声望/恶名方向、刷新密度等。每处修复都在源码中打了标签,任何人都可以通过 diff 看到改了什么以及为什么改。

被禁用的系统:一些功能(如刷怪系统和衰减系统)虽然代码完好,但因为演示版的特殊处理而没有任何调用路径触达。将它们独立反编译并重新接入调度系统即可恢复功能。

数据缺失:游戏地图只覆盖了 Ocllo 岛。作者编写了一整套工具来处理服务器数据格式,并完整重建了整个世界的门、招牌、装饰、传送点、陷阱、宝箱和刷怪位置。

最令人惊喜的发现是——著名的已废弃生态系统(ecology system)仍然存在于代码中,只是不再被调用。作者重新接入了捕食者/猎物/食腐动物系统:你现在可以看到狼追逐兔子、乌鸦啄食物品。不过由于缺乏精确数据,完整的资源/生产系统尚未实现。

Raph Koster(UO 首席设计师)曾写过关于 UO 生态系统资源系统的博客文章,感兴趣的读者可以深入了解这段历史。

UO 的生态系统是游戏设计史上的经典案例——开发者设计了一个复杂的生态平衡系统,但玩家行为完全打破了设计者的预期(玩家把所有动物都杀了),最终不得不放弃这个系统。在代码中看到它的残留,像是打开了一个时间胶囊。

新增功能

作者还添加了一些新功能:

  • ** Meditation、Stealth、Remove Trap 技能**:这些技能由 OSI 在 1999 年 2 月添加,但在演示版代码中已能找到早期痕迹。大部分新功能可通过 -features 参数在启动时启用或禁用。
  • 账户系统:演示服务器完全缺少账户系统,作者根据对原始开发者思路的推测重新实现了它,并做了适度现代化。
  • 多客户端支持:原始演示服务器仅支持客户端 1.25.33,现在支持从 1.25.30 到 5.0.9.1(2007-03-27)的所有客户端,包括加密和非加密版本。由于多年来存在五种完全不同的加密机制,作者不得不在客户端二进制中逐一逆向工程。

关于 32 位到 64 位的迁移也很有意思:原始二进制是 32 位的,但默认构建现在面向 64 位。类层次结构使用 C 结构体嵌套来重现原始 C++ 继承——因此 CMobile* 仍然可以传递给期望 CContainer* 的函数。为了防止 64 位平台上指针扩展导致继承字段偏移,某些结构体特意进行了填充(padding),以保持继承和 vtable 布局在两种模式下都与原始二进制一致。

Britain Cemetery

相关链接

  • 源代码github.com/draxinar/ouo
  • 数据文件(基于 UoDemo.dat,包含修复、补全数据和新功能):github.com/draxinar/rundir
  • 测试服务器uo.serpent-isle.com — 这不是正式 shard,而是一个测试环境,欢迎加入体验 1998 年 UO 服务器的忠实复现。
  • 灵感来源:Batlin 和 Derrick 的 UO:98 项目,正是 2016 年引领作者走上这条道路的起点。

作者强烈建议阅读代码和数据,其中有无数关于 UO 内部实现的鲜为人知的细节,能解释很多关于这款游戏的历史和设计决策。

要点总结

  1. 逆向工程的系统化方法论:先确立类型系统和内存布局,再逐函数翻译,配合逐指令验证——这套方法适用于任何大型二进制逆向项目。
  2. LLM 作为工程辅助:LLM 不是替代人类完成逆向工程,而是作为”放大器”帮助处理规模化的模式匹配工作,人类的架构理解和工程判断仍然不可替代。
  3. 历史代码的价值:即使是 25 年前的游戏代码,也包含着丰富的设计思想和工程实践。UO 的生态系统、虚拟机架构、网络协议都值得现代开发者学习。
  4. 开源社区的力量:一个人十年的坚持,加上开源协作,最终实现了商业公司都没有做到的事情——完整重建一个经典 MMORPG 的服务器端。

如果你对游戏服务器架构、逆向工程或 MMORPG 历史感兴趣,这个项目绝对值得深入研究。

comments powered by Disqus