OpenRakis/Cryogenic

GitHub: OpenRakis/Cryogenic

基于 Spice86 的《沙丘》(1992) DOS 游戏 C# 渐进式重实现项目,通过混合执行原始汇编与重写的托管代码来实现跨平台运行。

Stars: 50 | Forks: 3

![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) ![OSX](https://img.shields.io/badge/-OSX-black?logo=apple) ![Windows](https://img.shields.io/badge/-Windows-red?logo=windows) # Cryogenic [![PR Validation](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/3e0a33f18f235011.svg)](https://github.com/OpenRakis/Cryogenic/actions/workflows/pr-validation.yml) [![Release](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/a4db832f6a235012.svg)](https://github.com/OpenRakis/Cryogenic/actions/workflows/release.yml) [![Deploy Pages](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/6197e942a6235013.svg)](https://github.com/OpenRakis/Cryogenic/actions/workflows/static.yml) [![CodeQL](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/7ec0488a85235014.svg)](https://github.com/OpenRakis/Cryogenic/security/code-scanning) [![License](https://img.shields.io/github/license/OpenRakis/Cryogenic)](LICENSE) [![Latest Release](https://img.shields.io/github/v/release/OpenRakis/Cryogenic)](https://github.com/OpenRakis/Cryogenic/releases/latest) [![.NET Version](https://img.shields.io/badge/.NET-10.0-512BD4?logo=dotnet)](https://dotnet.microsoft.com/) 基于 [Spice86](https://github.com/OpenRakis/Spice86) 运行的 Cryo's Dune (CD 版本, 1992) 的 C# 重实现。 **[项目主页](https://openrakis.github.io/Cryogenic/)** ## 目录 - [项目简介](#what-this-is) - [当前状态](#current-state) - [工作原理](#how-it-works) - [代码结构](#code-structure) - [前置条件](#prerequisites) - [构建与运行](#build-and-run) - [截图](#screenshots) - [贡献](#contributing) - [资源](#resources) - [许可证](#license) ## 项目简介 Cryogenic 将 DOS 可执行文件 `DNCDPRG.EXE` 中的 x86 汇编例程替换为 C# 方法。[Spice86](https://github.com/OpenRakis/Spice86) 运行原始二进制文件,并在注册地址将执行重定向到 C# 重写方法。汇编代码和 C# 代码共享相同的模拟内存,因此可以在不破坏程序其余部分的情况下逐个引入重写函数。 ### 支持的可执行文件 所需的 `DNCDPRG.EXE` (Dune CD 版本 3.7) 的 SHA256: ``` 5f30aeb84d67cf2e053a83c09c2890f010f2e25ee877ebec58ea15c5b30cfff9 ``` 游戏文件(`DNCDPRG.EXE`, `DUNE.DAT`)受版权保护,必须单独获取。 ## 当前状态 游戏可以通过混合 ASM/.NET 执行完全游玩,并支持声音和音乐。 正在进行的工作是将更多的汇编例程转换为 C#。每个转换后的例程都注册为重写函数,并通过运行游戏进行测试。 ## 工作原理 1. `Program.cs` 配置命令行参数,并使用预期的 SHA256 校验和调用 `Spice86.Program.RunWithOverrides`。 2. `DuneCdOverrideSupplier` 实例化 `Overrides.Overrides`,后者调用 `DefineOverrides()`。 3. `DefineOverrides()` 使用 `DefineFunction`(替换 CALL 目标)和 `DoOnTopOfInstruction`(内联挂钩)在特定的 segment:offset 地址注册 C# 方法。 4. 在运行时,当模拟 CPU 到达注册地址时,Spice86 执行 C# 方法而不是原始汇编代码。方法通过匹配原始指令的 `NearRet()` 或 `FarRet()` 返回。 5. 游戏状态通过类型化访问器类(`globalsOnDs`, `globalsOnCsSegment0x2538`)读取和写入,这些类直接映射到 DS 和 CS 段基址处的模拟内存。 ### 内存段 `Overrides` 类声明了在注册重写时使用的五个段字段: | 字段 | 值 | 内容 | 已注册的重写 | |-------|-------|----------|---------------------| | `cs1` | `0x1000` | 主游戏代码(`DNCDPRG.EXE` 加载于此) | 跨越大多数重写文件的约 80 个函数 | | `cs2` | `0xD000` | DNVGA — VGA 图形驱动程序 | `VgaDriverCode.cs` 中的 32 个函数,`StaticDefinitions.cs` 中的 1 个 | | `cs3` | `0xE000` | DNPCS2 / DNSBP — PCM 音频驱动程序 | 无(已声明但在当前重写中未使用) | | `cs4` | `0xE000` | 保留用于 MIDI 驱动程序内存转储挂钩 | 偏移量 0x02DC 和 0x03EE 处的 2 个内存转储内联挂钩 | | `cs5` | `0x0800` | 中断处理程序(替换默认 0xF000 的自定义段) | 无(声明用于地址引用) | `cs3` 和 `cs4` 共享地址 `0xE000`。在 `DriverLoadToolbox` 中,PCM 驱动程序(DNPCS2, DNSBP)加载于 `DRIVER2_SEGMENT = 0xE000`,音乐驱动程序(DNMID)加载于 `DRIVER3_SEGMENT = 0xF000`。`MT32DriverCode.cs` 中的 MT-32 重写使用硬编码的 `0xF000`(3 个函数),而不是任何 `cs` 字段。 ### 驱动程序重映射 `DriverLoadToolbox` 临时移除内存分配器的段限制,以便将驱动程序加载到固定地址: | 驱动程序 | 名称 | 重映射到 | 用途 | |--------|------|-------------|---------| | DNVGA | VGA 图形 | `0xD000` | 显示、位块传输、调色板、鼠标光标 | | DNPCS2 | PC Speaker variant 2 | `0xE000` | PCM 音效 | | DNSBP | Sound Blaster Pro | `0xE000` | PCM 音效 | | DNMID | MIDI | `0xF000` | 音乐播放(MT-32, AdLib) | 驱动程序 DN386, DNADL, DNADP, DNADG, DNSDB 未被重映射。在每个驱动程序加载后,`ResetAllocator` 恢复原始分配器状态。重映射挂钩注入在 CS1:E57B(`RemapDrivers`)和 CS1:E593(`ResetAllocator`)。驱动程序函数表在 CS1:E589(`ReadDriverFunctionTable`)自动检测。 ## 代码结构 ``` src/Cryogenic/ ├── Program.cs Entry point; configures args, launches Spice86 ├── DuneCdOverrideSupplier.cs Implements IOverrideSupplier; creates Overrides instance ├── DriverLoadToolbox.cs Remaps driver segments to 0xD000/0xE000/0xF000 ├── Overrides/ │ ├── Overrides.cs Defines segment fields (cs1–cs5), registers all overrides │ ├── VgaDriverCode.cs 23 VGA functions: mode setting, blitting, palette, mouse cursor │ ├── MenuCode.cs 14 menu-type constants, 2 menu state overrides │ ├── DialoguesCode.cs 3 dialogue functions: counter, init, hex-digit conversion │ ├── MapCode.cs 5 map/cursor functions and click-handler address constants │ ├── DisplayCode.cs 11 framebuffer and font selection functions │ ├── VideoCode.cs 3 HNM video playback functions │ ├── HnmCode.cs 1 HNM file I/O function (disk read into buffer) │ ├── TimeCode.cs 2 day/night cycle and hour-of-day functions │ ├── TimerCode.cs 1 PIT 8254 timer configuration function │ ├── ScriptedSceneCode.cs 2 cutscene sequence data functions │ ├── DatastructuresCode.cs 2 memory structure functions (index-to-pointer, sprite lookup) │ ├── InitCode.cs 1 VGA state initialization function │ ├── MT32DriverCode.cs 3 Roland MT-32 MIDI driver functions at segment 0xF000 │ ├── UnknownCode.cs 20 partially understood functions (bit ops, memcpy, state flags) │ └── StaticDefinitions.cs 137+ symbolic names for unoverridden functions (tracing only) ├── Globals/ Typed accessors added manually (Extra* files) └── Generated/ Auto-generated memory accessors (do not edit) ``` ### 重写文件详解 **VgaDriverCode.cs** — 替换加载在段 0xD000 的 DNVGA 驱动程序中的函数。涵盖 VGA 模式设置(`VgaFunc00SetMode`)、帧缓冲区位块传输(`VgaFunc05Blit`)、矩形复制(`VgaFunc12CopyRectangle`)、像素写入(`VgaFunc21SetPixel`)、调色板加载(`LoadPaletteInVgaDac`)、鼠标光标绘制/恢复(`VgaFunc03DrawMouseCursor`, `VgaFunc04RestoreImageUnderMouseCursor`)、缓冲区填充(`VgaFunc08FillWithZeroFor64000AtES`)、纹理生成(`VgaFunc36GenerateTextureOutBP`)、地图块复制(`CopyMapBlock`)以及 VGA 回扫同步(`WaitForRetrace`)。 **MenuCode.cs** — 将 14 个菜单类型常量定义为十六进制偏移量(例如 `MENU_TYPE_DIALOGUE = 0x1F7E`, `MENU_TYPE_GLOBE = 0x204A`, `MENU_TYPE_BOOK = 0x2032`)。包含两个重写:一个用于 CS1:D316 处的菜单动画状态转换,另一个用于 CS1:D41B 处的当前菜单类型设置。 **DialoguesCode.cs** — 三个函数:在 DS:47A8(CS1:A1E8)递增对话计数器,初始化对话状态包括视频索引和面部缩放计时(CS1:C85B),以及将 AL 的低半字节转换为 ASCII 十六进制数字(CS1:A8B1)。 **MapCode.cs** — 定义五种地图模式(平面地图、 globe、游戏内、部队移动、扑翼机)的点击处理程序地址。五个函数设置活动点击处理程序,初始化地图光标类型,并执行从 DS:46E3 开始的 8 字节内存复制。 **DisplayCode.cs** — 管理三个帧缓冲区(前端位于 DBD6,后端位于 DC32,文本位于 DBD8)。包含三个字体选择函数(介绍、菜单、书籍),用于设置字符集地址,以及坐标查找、缓冲区清除和寄存器入栈/出栈辅助程序。 **VideoCode.cs** — 三个用于 HNM 视频播放的函数:从 DS:33A3 处的表进行资源标志查找,播放索引同步,以及通过 DBE7 处的完成标志进行检查。 **HnmCode.cs** — 一个通过 DOS 文件管理器从磁盘读取 HNM 视频数据的函数,更新读取偏移量和缓冲区指针。 **TimeCode.cs** — 从 DS:0002 处的游戏经过时间字的低 4 位提取当前小时。计算下一个可见阳光的日期。 **TimerCode.cs** — 使用控制字节 0x36 将 16 位计数器值写入 PIT 通道 0(端口 0x43/0x40)。主要在游戏退出时调用。 **ScriptedSceneCode.cs** — 从 DS:4854 处的场景序列数据读取 16 位命令并推进序列指针。 **DatastructuresCode.cs** — 通过添加基址偏移量将索引表转换为指针表。通过索引从 DS:DBB0 处的表检索精灵图表资源指针。 **MT32DriverCode.cs** — 段 0xF000 处用于 Roland MT-32 输出的三个函数:主入口点、向端口 0x330 进行 MIDI 字节写入,以及一个未使用的存根。 **UnknownCode.cs** — 20 个具有观察到的行为但用途不明确的函数。包括 DBC8 上的位测试操作、多寄存器移位、8 字节内存复制、状态标志设置器、10 字节结构构建器、DS:47F8 处的 0x5C 字节填充以及三个空操作。 **StaticDefinitions.cs** — 在其地址注册 137+ 个符号函数名(例如 `play_intro`, `open_dune_dat`, `allocator_init`),用于 Spice86 的跟踪输出。这些未被重写;它们在执行日志中提供可读的名称。 ## 前置条件 1. [.NET 10 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) 2. 来自 Dune CD 版本 3.7 的 `DNCDPRG.EXE` 和 `DUNE.DAT`(受版权保护;需单独获取) 验证可执行文件校验和: ``` # Linux/Mac sha256sum DNCDPRG.EXE # PowerShell Get-FileHash DNCDPRG.EXE -Algorithm SHA256 ``` ## 构建与运行 ### 构建 ``` git clone https://github.com/OpenRakis/Cryogenic cd Cryogenic/src dotnet build ``` ### 运行(无音频) 将 `DUNE.DAT` 和 `DNCDPRG.EXE` 放在同一目录下,然后: ``` cd Cryogenic/src dotnet run --Exe /path/to/DNCDPRG.EXE --UseCodeOverride true -p 4096 ``` 必须使用 `--UseCodeOverride true`。如果没有它,将不会执行任何 C# 重写。 ### 带音频运行 OPL3 音乐和 Sound Blaster PCM: ``` cd Cryogenic/src/Cryogenic dotnet publish bin/Release/net10.0/publish/Cryogenic --Exe /path/to/DNCDPRG.EXE --Cycles 8000 --UseCodeOverride true -p 4096 -a "ADP330 SBP2227" ``` ## 截图
![Orni](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/cf393ff453235015.png) *扑翼机* ![Chani dialogue](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/75e824f75e235016.png) *Chani 对话* ![Spice shipment screen](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/9b200bcfed235018.png) *香料运输界面* ![Harkonnen scene](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/60167b2d9c235019.png) *哈克南场景* ![Dune map](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/cf6bdd28c6235020.png) *厄拉科斯地图* ![Worm call](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/267fb0f0d4235021.png) *召唤沙虫*
## 贡献 有关设置说明、编码约定和重写实现工作流,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。 贡献领域: - **逆向工程** — 分析汇编代码,实现 C# 重写,在 `DefineOverrides()` 中注册它们 - **文档** — 添加 XML 文档注释,记录数据结构,解释函数行为 - **测试** — 运行游戏,将行为与原始版本进行比较,报告差异 - **代码质量** — 重构现有重写,改进命名,消除重复 ## 资源 - [项目主页](https://openrakis.github.io/Cryogenic/) - [GitHub 仓库](https://github.com/OpenRakis/Cryogenic) - [Spice86](https://github.com/OpenRakis/Spice86) — 本项目使用的模拟器和逆向工程工具包 - [发布版本](https://github.com/OpenRakis/Cryogenic/releases) - [维基百科上的 Dune (1992)](https://en.wikipedia.org/wiki/Dune_(video_game)) ## 许可证 Apache License 2.0。详见 [LICENSE](LICENSE)。 版权所有 2021–2024 Kevin Ferrare 及贡献者。 Dune 版权归 Cryo Interactive Entertainment 所有。本项目不隶属于或受 Cryo Interactive Entertainment 或任何版权所有者认可。
标签:DOS游戏, Dune, .NET 10, Spice86, 开源游戏, 怀旧游戏, 沙盒, 游戏逆向工程, 程序重构, 重制版