functionInformations, ushort entrySegment,
71 | Machine machine, ILoggerService loggerService, Configuration configuration) :
72 | base(functionInformations, machine, loggerService, configuration) {
73 | // Main code
74 | this.cs1 = 0x1000;
75 | // Vga driver is remapped here
76 | this.cs2 = DriverLoadToolbox.DRIVER1_SEGMENT;
77 | // PCM driver
78 | this.cs3 = DriverLoadToolbox.DRIVER2_SEGMENT;
79 | // Midi driver
80 | this.cs4 = DriverLoadToolbox.DRIVER2_SEGMENT;
81 | // Bios, This does not depend on the entry segment.
82 | this.cs5 = DriverLoadToolbox.INTERRUPT_HANDLER_SEGMENT;
83 | globalsOnDs = new ExtraGlobalsOnDs(machine.Memory, machine.Cpu.State.SegmentRegisters);
84 | globalsOnCsSegment0X2538 = new ExtraGlobalsOnCsSegment0x2538(machine.Memory, cs2);
85 |
86 | DefineOverrides();
87 | DefineStaticDefinitionsFunctions();
88 | }
89 |
90 | ///
91 | /// Registers all override functions and inline hooks across different game subsystems.
92 | ///
93 | ///
94 | /// This method orchestrates the registration of overrides for:
95 | ///
96 | /// - Data structures and memory management
97 | /// - VGA graphics operations
98 | /// - Dialogue system
99 | /// - Display and rendering
100 | /// - HNM video playback
101 | /// - Initialization sequences
102 | /// - Map rendering and navigation
103 | /// - Menu system
104 | /// - Scripted scenes and cutscenes
105 | /// - Time and timer management
106 | /// - MT-32 MIDI driver
107 | /// - Driver remapping infrastructure
108 | /// - Memory dump triggers for debugging
109 | ///
110 | /// Generated code overrides are commented out as they crash for various reasons.
111 | ///
112 | public void DefineOverrides() {
113 | DefineDataStructureOverrides();
114 | DefineVgaDriverCodeOverrides();
115 | DefineDialoguesCodeOverrides();
116 | DefineDisplayCodeOverrides();
117 | DefineHnmCodeOverrides();
118 | DefineInitCodeOverrides();
119 | DefineMapCodeOverrides();
120 | DefineMenuCodeOverrides();
121 | DefineScriptedSceneCodeOverrides();
122 | DefineTimeCodeOverrides();
123 | DefineTimerCodeOverrides();
124 | DefineUnknownCodeOverrides();
125 | DefineVideoCodeOverrides();
126 |
127 | DefineDriversRemapping();
128 | DetectDriversEntryPoints();
129 | // Dump memory at the proper time. Too soon and drivers wont be loaded, too late and init code will be erased
130 | DefineMemoryDumpsMapping();
131 | DefineMT32DriverCodeOverrides();
132 |
133 | // Generated code, crashes for various reasons
134 | //DefineGeneratedCodeOverrides();
135 | }
136 |
137 | ///
138 | /// Registers memory dump triggers at strategic points during game initialization.
139 | ///
140 | ///
141 | /// Memory dumps are triggered at specific addresses to capture game state:
142 | ///
143 | /// - After driver loading (CS1:000C)
144 | /// - After self-modifying code updates (CS4:02DC and CS4:03EE)
145 | ///
146 | /// Dumps must be timed carefully - too early and drivers won't be loaded,
147 | /// too late and initialization code will be erased by self-modifying code.
148 | ///
149 | private void DefineMemoryDumpsMapping() {
150 | DoOnTopOfInstruction(cs1, 0x000C, () => {
151 | DumpMemoryWithSuffix("_" + ConvertUtils.ToHex16WithoutX(cs1) + "_000C_After_driver_load");
152 | });
153 | DoOnTopOfInstruction(cs4, 0x02DC, () => {
154 | callsTo02DB++;
155 | DumpMemoryWithSuffix("_" + ConvertUtils.ToHex16WithoutX(cs4) + "_02DC_After_code_modification_" +
156 | callsTo02DB);
157 | });
158 | DoOnTopOfInstruction(cs4, 0x03EE, () => {
159 | callsTo03ED++;
160 | DumpMemoryWithSuffix("_" + ConvertUtils.ToHex16WithoutX(cs4) + "_03EE_After_code_modification_" +
161 | callsTo03ED);
162 | });
163 | }
164 |
165 | /// Counter for memory dumps at CS4:02DC to create unique filenames.
166 | private int callsTo02DB = 0;
167 |
168 | /// Counter for memory dumps at CS4:03EE to create unique filenames.
169 | private int callsTo03ED = 0;
170 |
171 | ///
172 | /// Exports a memory dump with the specified filename suffix.
173 | ///
174 | /// Suffix to append to the dump filename for identification.
175 | private void DumpMemoryWithSuffix(string suffix) {
176 | new MemoryDataExporter(Memory, Machine.CallbackHandler, Configuration, Configuration.RecordedDataDirectory, _loggerService).DumpMemory(suffix);
177 | }
178 |
179 | ///
180 | /// Registers hooks for driver remapping at the beginning and end of the driver load routine.
181 | ///
182 | ///
183 | /// Injects calls to at CS1:E57B
184 | /// and at CS1:E593 to control
185 | /// driver segment allocation.
186 | ///
187 | private void DefineDriversRemapping() {
188 | DoOnTopOfInstruction(cs1, 0xE57B, () => {
189 | DriverLoadToolbox.RemapDrivers(State, Memory);
190 | });
191 | DoOnTopOfInstruction(cs1, 0xE593, () => {
192 | DriverLoadToolbox.ResetAllocator(State, Memory);
193 | });
194 | }
195 |
196 | ///
197 | /// Registers a hook to automatically detect and define driver entry point functions.
198 | ///
199 | ///
200 | /// Injects a call to at CS1:E589
201 | /// to parse driver export tables and register their functions for tracing.
202 | ///
203 | private void DetectDriversEntryPoints() {
204 | DoOnTopOfInstruction(cs1, 0xE589, () => {
205 | DriverLoadToolbox.ReadDriverFunctionTable(State, Memory, this);
206 | });
207 | }
208 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 |
5 | # CRYOGENIC
6 |
7 | [](https://github.com/OpenRakis/Cryogenic/actions/workflows/pr-validation.yml)
8 | [](https://github.com/OpenRakis/Cryogenic/actions/workflows/release.yml)
9 | [](https://github.com/OpenRakis/Cryogenic/actions/workflows/static.yml)
10 | [](https://github.com/OpenRakis/Cryogenic/security/code-scanning)
11 | [](LICENSE)
12 | [](https://github.com/OpenRakis/Cryogenic/releases/latest)
13 | [](https://github.com/OpenRakis/Cryogenic/stargazers)
14 | [](https://github.com/OpenRakis/Cryogenic/network/members)
15 | [](https://github.com/OpenRakis/Cryogenic/issues)
16 | [](https://dotnet.microsoft.com/)
17 |
18 | **Reverse Engineering the Classic Dune Game**
19 |
20 | 📚 **[GitHub Page](https://openrakis.github.io/Cryogenic/)**
21 |
22 | ---
23 |
24 | ## Table of Contents
25 |
26 | - [About the Project](#about-the-project)
27 | - [Project Goals](#project-goals)
28 | - [Technology - Spice86](#technology---spice86)
29 | - [Status](#status)
30 | - [Prerequisites](#prerequisites)
31 | - [Build & Run](#build--run)
32 | - [Building](#building)
33 | - [Running (Basic Mode)](#running-basic-mode)
34 | - [Running with Audio](#running-with-audio)
35 | - [Screenshots](#screenshots)
36 | - [Contributing](#contributing)
37 | - [Resources](#resources)
38 | - [License](#license)
39 |
40 | ---
41 |
42 | ## About the Project
43 |
44 | Cryogenic is an ambitious reverse engineering project dedicated to understanding and modernizing **Cryo's Dune** (CD Version, 1992) - one of the most atmospheric adventure games of the early 90s.
45 |
46 | Using the powerful [Spice86](https://github.com/OpenRakis/Spice86) reverse engineering toolkit, we're gradually rewriting the game from x86 assembly into readable, maintainable C# code. The game is **fully playable** right now, complete with sound and music support.
47 |
48 | As we continue to replace assembly routines with C# implementations, the codebase becomes more accessible to modern developers while preserving the exact behavior of the original DOS executable.
49 |
50 | ### Supported Version
51 |
52 | **SHA256 signature of supported DNCDPRG.EXE:**
53 | ```
54 | 5f30aeb84d67cf2e053a83c09c2890f010f2e25ee877ebec58ea15c5b30cfff9
55 | ```
56 |
57 | ⚠️ **Note:** The CD release of DUNE (version 3.7, the most widely available one) must be obtained separately, as it is **copyrighted material**.
58 |
59 | ---
60 |
61 | ## Project Goals
62 |
63 | 🔍 **Understand the Game** - Deep dive into the inner workings of Dune's game engine, uncovering how Cryo implemented dialogue systems, resource management, and real-time strategy elements in the DOS era.
64 |
65 | 🔄 **Incremental Rewriting** - Gradually replace x86 assembly routines with clean, documented C# code while maintaining 100% behavioral compatibility with the original game.
66 |
67 | 📚 **Document Everything** - Create comprehensive documentation of game mechanics, data structures, and algorithms to preserve this knowledge for future developers and gaming historians.
68 |
69 | 🎮 **Preserve Gaming History** - Ensure this classic adventure game remains playable on modern systems and provide a foundation for potential future enhancements and ports.
70 |
71 | 🛠️ **Educational Resource** - Serve as a reference implementation for reverse engineering techniques and demonstrate the power of hybrid emulation approaches.
72 |
73 | 🌐 **Cross-Platform Support** - Leverage .NET 8's cross-platform capabilities to run the game natively on Windows, macOS, and Linux without emulation layers.
74 |
75 | ---
76 |
77 | ## Technology - Spice86
78 |
79 | ### What is Spice86?
80 |
81 | [Spice86](https://github.com/OpenRakis/Spice86) is a revolutionary reverse engineering toolkit and PC emulator specifically designed for 16-bit real mode x86 programs. Unlike traditional emulators that simply run old software, Spice86 enables you to **gradually modernize** legacy DOS applications by replacing assembly code with high-level C# implementations.
82 |
83 | Built on .NET 8, Spice86 provides a unique hybrid execution model where the original DOS executable runs alongside your C# overrides, allowing for incremental reverse engineering and testing.
84 |
85 | ### Key Features
86 |
87 | - 🔄 **Hybrid Execution** - Run original DOS binaries while selectively replacing functions with C# implementations. Test your reverse-engineered code against the real thing in real-time.
88 | - 🔬 **Runtime Analysis** - Collect memory dumps, execution traces, and runtime data while the program runs.
89 | - 🎯 **Ghidra Integration** - Import runtime data into Ghidra for static analysis.
90 | - 🐛 **Advanced Debugging** - Built-in debugger with GDB remote protocol support.
91 | - 🎮 **Hardware Emulation** - Complete support for VGA/EGA/CGA graphics, PC Speaker, AdLib, SoundBlaster, keyboard, and mouse.
92 | - 🌍 **Cross-Platform** - Built on .NET 8 for Windows, macOS, and Linux.
93 |
94 | ### How Cryogenic Uses Spice86
95 |
96 | 1. **Override Registration** - Cryogenic registers C# function overrides in `DuneCdOverrideSupplier` that replace specific assembly routines at runtime.
97 | 2. **Segment Management** - Memory segments (CS1=0x1000 for main code, CS2/3/4 for drivers, CS5=0xF000 for BIOS) are carefully managed.
98 | 3. **Hybrid Execution** - When DNCDPRG.EXE calls an overridden function, Spice86 redirects execution to the C# implementation.
99 | 4. **State Synchronization** - Global game state accessors like `globalsOnDs` ensure C# code and assembly share the same memory.
100 |
101 | ---
102 |
103 | ## Status
104 |
105 | Thanks to the hybrid ASM / .NET mode provided by Spice86, the game is **fully playable**, including sound and music.
106 |
107 | The goal is to have more and more logic written in human-readable C#, making the codebase accessible to modern developers while preserving the classic gameplay experience.
108 |
109 | ---
110 |
111 | ## Prerequisites
112 |
113 | Before you begin, ensure you have:
114 |
115 | 1. **[.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)** - Required to build and run the project
116 | 2. **Dune CD Version 3.7** - You must obtain `DNCDPRG.EXE` and `DUNE.DAT` separately (copyrighted material)
117 |
118 | You can verify your executable with PowerShell:
119 | ```powershell
120 | Get-FileHash DNCDPRG.EXE -Algorithm SHA256
121 | ```
122 |
123 | Or on Linux/Mac:
124 | ```bash
125 | sha256sum DNCDPRG.EXE
126 | ```
127 |
128 | ---
129 |
130 | ## Build & Run
131 |
132 | ### Building
133 |
134 | Clone the repository and build the project:
135 |
136 | ```bash
137 | git clone https://github.com/OpenRakis/Cryogenic
138 | cd Cryogenic/src
139 | dotnet build
140 | ```
141 |
142 | ### Running (Basic Mode)
143 |
144 | Ensure that `DUNE.DAT` and `DNCDPRG.EXE` are in the same folder, then run:
145 |
146 | ```bash
147 | cd Cryogenic/src
148 | dotnet run --Exe C:/path/to/dunecd/DNCDPRG.EXE --UseCodeOverride true -p 4096
149 | ```
150 |
151 | ⚠️ **Important:** Always use `--UseCodeOverride true` or your C# code won't execute!
152 |
153 | ### Running with Audio
154 |
155 | For the full experience with AdLib music and PCM sound effects:
156 |
157 | ```bash
158 | cd Cryogenic/src/Cryogenic
159 | dotnet publish
160 | bin/Release/net8.0/publish/Cryogenic --Exe C:/path/to/dunecd/DNCDPRG.EXE --UseCodeOverride true -p 4096 -a "ADL220 SBP2227"
161 | ```
162 |
163 | ---
164 |
165 | ## Screenshots
166 |
167 |
168 |
169 | 
170 | *Encounter with the mighty sandworm*
171 |
172 | 
173 | *Meeting with Chani*
174 |
175 | 
176 | *Sending Spice to the Emperor*
177 |
178 | 
179 | *Harkonnen confrontation*
180 |
181 |
182 |
183 | ---
184 |
185 | ## Contributing
186 |
187 | We welcome contributions from developers of all skill levels! Whether you're experienced in reverse engineering or just getting started, there's a place for you in this project.
188 |
189 | ### Ways to Contribute
190 |
191 | - 🔍 **Reverse Engineering** - Analyze assembly code, identify functions, and create C# implementations
192 | - 📝 **Documentation** - Help document game mechanics, data structures, and code functionality
193 | - 🧪 **Testing** - Play the game, find bugs, and verify that C# overrides behave identically to the original
194 | - 🛠️ **Code Quality** - Improve existing C# code, refactor for clarity, and add helpful comments
195 |
196 | ### Getting Started
197 |
198 | 1. Fork the repository on GitHub
199 | 2. Clone your fork locally
200 | 3. Read the [CONTRIBUTING.md](CONTRIBUTING.md) file for detailed guidelines
201 | 4. Pick an issue labeled `good first issue` or `help wanted`
202 | 5. Create a feature branch for your work
203 | 6. Make your changes and test thoroughly
204 | 7. Submit a pull request
205 |
206 | For detailed contribution guidelines, architecture notes, and coding conventions, please see our [CONTRIBUTING.md](CONTRIBUTING.md) file.
207 |
208 | ---
209 |
210 | ## Resources
211 |
212 | - 🌐 **[Project Website](https://openrakis.github.io/Cryogenic/)** - Comprehensive documentation
213 | - 🐙 **[GitHub Repository](https://github.com/OpenRakis/Cryogenic)** - Source code, issues, and releases
214 | - 🌶️ **[Spice86 Project](https://github.com/OpenRakis/Spice86)** - The reverse engineering toolkit powering Cryogenic
215 | - 📦 **[Releases](https://github.com/OpenRakis/Cryogenic/releases)** - Download the latest version
216 | - 📖 **[Dune (1992) - Wikipedia](https://en.wikipedia.org/wiki/Dune_(video_game))** - Learn about the original game
217 |
218 | ---
219 |
220 | ## License
221 |
222 | Cryogenic is open-source software licensed under the **Apache License 2.0**.
223 |
224 | © 2021-2024 Kevin Ferrare and contributors.
225 |
226 | Dune is © Cryo Interactive Entertainment. This project is not affiliated with or endorsed by Cryo Interactive Entertainment or any rights holders.
227 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - master
8 |
9 | permissions:
10 | contents: write
11 | security-events: write
12 | actions: read
13 |
14 | jobs:
15 | build-test-scan:
16 | name: Build, Test & CodeQL Scan
17 | runs-on: ubuntu-latest
18 | outputs:
19 | semver: ${{ steps.gitversion.outputs.semVer }}
20 | assemblysemver: ${{ steps.gitversion.outputs.assemblySemVer }}
21 | assemblysemfilever: ${{ steps.gitversion.outputs.assemblySemFileVer }}
22 | informationalversion: ${{ steps.gitversion.outputs.informationalVersion }}
23 |
24 | steps:
25 | - name: Checkout code
26 | uses: actions/checkout@v4
27 | with:
28 | fetch-depth: 0 # Full history for GitVersion
29 | fetch-tags: true
30 |
31 | - name: Setup .NET
32 | uses: actions/setup-dotnet@v4
33 | with:
34 | dotnet-version: '8.0.x'
35 |
36 | - name: Install GitVersion
37 | uses: gittools/actions/gitversion/setup@v3.1.1
38 | with:
39 | versionSpec: '6.0.x'
40 |
41 | - name: Determine Version
42 | id: gitversion
43 | uses: gittools/actions/gitversion/execute@v3.1.1
44 | with:
45 | useConfigFile: true
46 | configFilePath: GitVersion.yml
47 |
48 | - name: Display GitVersion outputs
49 | run: |
50 | echo "SemVer: ${{ steps.gitversion.outputs.semVer }}"
51 | echo "AssemblySemVer: ${{ steps.gitversion.outputs.assemblySemVer }}"
52 | echo "InformationalVersion: ${{ steps.gitversion.outputs.informationalVersion }}"
53 |
54 | - name: Initialize CodeQL
55 | uses: github/codeql-action/init@v3
56 | with:
57 | languages: csharp
58 | queries: security-and-quality
59 |
60 | - name: Restore dependencies
61 | working-directory: ./src
62 | run: dotnet restore
63 |
64 | - name: Build
65 | working-directory: ./src
66 | run: |
67 | dotnet build --configuration Release --no-restore \
68 | /p:Version=${{ steps.gitversion.outputs.semVer }} \
69 | /p:AssemblyVersion=${{ steps.gitversion.outputs.assemblySemVer }} \
70 | /p:FileVersion=${{ steps.gitversion.outputs.assemblySemFileVer }} \
71 | /p:InformationalVersion=${{ steps.gitversion.outputs.informationalVersion }}
72 |
73 | - name: Run tests
74 | working-directory: ./src
75 | run: dotnet test --configuration Release --no-build --verbosity normal
76 | continue-on-error: true # Continue if no tests exist
77 |
78 | - name: Perform CodeQL Analysis
79 | uses: github/codeql-action/analyze@v3
80 | with:
81 | category: "/language:csharp"
82 |
83 | publish-and-release:
84 | name: Publish & Create Release
85 | needs: build-test-scan
86 | runs-on: ubuntu-latest
87 |
88 | steps:
89 | - name: Checkout code
90 | uses: actions/checkout@v4
91 | with:
92 | fetch-depth: 0
93 |
94 | - name: Setup .NET
95 | uses: actions/setup-dotnet@v4
96 | with:
97 | dotnet-version: '8.0.x'
98 |
99 | - name: Restore dependencies
100 | working-directory: ./src/Cryogenic
101 | run: dotnet restore
102 |
103 | - name: Publish for all platforms
104 | working-directory: ./src/Cryogenic
105 | env:
106 | VERSION: ${{ needs.build-test-scan.outputs.semver }}
107 | ASSEMBLY_VERSION: ${{ needs.build-test-scan.outputs.assemblysemver }}
108 | FILE_VERSION: ${{ needs.build-test-scan.outputs.assemblysemfilever }}
109 | INFORMATIONAL_VERSION: ${{ needs.build-test-scan.outputs.informationalversion }}
110 | run: |
111 | # Release builds
112 | dotnet publish -c Release -r linux-arm64 --self-contained true \
113 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
114 | -p:Version=$VERSION \
115 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
116 | -p:FileVersion=$FILE_VERSION \
117 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
118 | -o Release/linux-arm64
119 |
120 | dotnet publish -c Release -r linux-x64 --self-contained true \
121 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
122 | -p:Version=$VERSION \
123 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
124 | -p:FileVersion=$FILE_VERSION \
125 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
126 | -o Release/linux-x64
127 |
128 | dotnet publish -c Release -r win-x64 --self-contained true \
129 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
130 | -p:Version=$VERSION \
131 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
132 | -p:FileVersion=$FILE_VERSION \
133 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
134 | -o Release/windows-x64
135 |
136 | dotnet publish -c Release -r win-arm64 --self-contained true \
137 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
138 | -p:Version=$VERSION \
139 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
140 | -p:FileVersion=$FILE_VERSION \
141 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
142 | -o Release/windows-arm64
143 |
144 | dotnet publish -c Release -r osx-x64 --self-contained true \
145 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
146 | -p:Version=$VERSION \
147 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
148 | -p:FileVersion=$FILE_VERSION \
149 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
150 | -o Release/macos-x64
151 |
152 | dotnet publish -c Release -r osx-arm64 --self-contained true \
153 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
154 | -p:Version=$VERSION \
155 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
156 | -p:FileVersion=$FILE_VERSION \
157 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
158 | -o Release/macos-arm64
159 |
160 | # Debug builds
161 | dotnet publish -c Debug -r linux-x64 --self-contained true \
162 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
163 | -p:Version=$VERSION \
164 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
165 | -p:FileVersion=$FILE_VERSION \
166 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
167 | -o Debug/linux-x64
168 |
169 | dotnet publish -c Debug -r linux-arm64 --self-contained true \
170 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
171 | -p:Version=$VERSION \
172 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
173 | -p:FileVersion=$FILE_VERSION \
174 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
175 | -o Debug/linux-arm64
176 |
177 | dotnet publish -c Debug -r win-x64 --self-contained true \
178 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
179 | -p:Version=$VERSION \
180 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
181 | -p:FileVersion=$FILE_VERSION \
182 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
183 | -o Debug/windows-x64
184 |
185 | dotnet publish -c Debug -r win-arm64 --self-contained true \
186 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
187 | -p:Version=$VERSION \
188 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
189 | -p:FileVersion=$FILE_VERSION \
190 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
191 | -o Debug/windows-arm64
192 |
193 | dotnet publish -c Debug -r osx-x64 --self-contained true \
194 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
195 | -p:Version=$VERSION \
196 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
197 | -p:FileVersion=$FILE_VERSION \
198 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
199 | -o Debug/macos-x64
200 |
201 | dotnet publish -c Debug -r osx-arm64 --self-contained true \
202 | -p:PublishReadyToRun=true -p:PublishSingleFile=true \
203 | -p:Version=$VERSION \
204 | -p:AssemblyVersion=$ASSEMBLY_VERSION \
205 | -p:FileVersion=$FILE_VERSION \
206 | -p:InformationalVersion=$INFORMATIONAL_VERSION \
207 | -o Debug/macos-arm64
208 |
209 | - name: Create distribution packages
210 | working-directory: ./src/Cryogenic/bin
211 | run: |
212 | zip -qq -r Cryogenic-${{ needs.build-test-scan.outputs.semver }}-Debug.zip Debug
213 | zip -qq -r Cryogenic-${{ needs.build-test-scan.outputs.semver }}-Release.zip Release
214 |
215 | - name: Generate Release Notes
216 | id: release_notes
217 | run: |
218 | # Get the last tag
219 | LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
220 |
221 | if [ -z "$LAST_TAG" ]; then
222 | echo "## What's Changed" > release_notes.md
223 | echo "" >> release_notes.md
224 | echo "Initial release" >> release_notes.md
225 | else
226 | echo "## What's Changed" > release_notes.md
227 | echo "" >> release_notes.md
228 | git log ${LAST_TAG}..HEAD --pretty=format:"* %s (%h)" --no-merges >> release_notes.md
229 | fi
230 |
231 | echo "" >> release_notes.md
232 | echo "" >> release_notes.md
233 | echo "## Assets" >> release_notes.md
234 | echo "" >> release_notes.md
235 | echo "Download the appropriate build for your platform:" >> release_notes.md
236 | echo "- **Release builds**: Production-ready optimized builds" >> release_notes.md
237 | echo "- **Debug builds**: Builds with debug symbols for troubleshooting" >> release_notes.md
238 | echo "" >> release_notes.md
239 | echo "### Supported Platforms" >> release_notes.md
240 | echo "- Linux (x64, ARM64)" >> release_notes.md
241 | echo "- Windows (x64, ARM64)" >> release_notes.md
242 | echo "- macOS (x64, ARM64)" >> release_notes.md
243 |
244 | - name: Create GitHub Release
245 | uses: softprops/action-gh-release@v2
246 | with:
247 | tag_name: v${{ needs.build-test-scan.outputs.semver }}
248 | name: Release v${{ needs.build-test-scan.outputs.semver }}
249 | body_path: release_notes.md
250 | draft: false
251 | prerelease: false
252 | files: |
253 | ./src/Cryogenic/bin/Cryogenic-${{ needs.build-test-scan.outputs.semver }}-Debug.zip
254 | ./src/Cryogenic/bin/Cryogenic-${{ needs.build-test-scan.outputs.semver }}-Release.zip
255 | env:
256 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
257 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2021 Kevin Ferrare
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/src/Cryogenic/DriverLoadToolbox.cs:
--------------------------------------------------------------------------------
1 | namespace Cryogenic;
2 |
3 | using Spice86.Core.Emulator.CPU;
4 | using Spice86.Shared.Emulator.Memory;
5 |
6 | ///
7 | /// Provides utilities for remapping game drivers to clean segment boundaries for easier analysis.
8 | ///
9 | ///
10 | ///
11 | /// The purpose is to load the VGA, PCM, and MIDI drivers at multiples of 0x1000 (D000, E000, F000)
12 | /// which makes it easier to identify code locations in disassembly listings and works around
13 | /// Ghidra issue https://github.com/NationalSecurityAgency/ghidra/issues/981.
14 | ///
15 | ///
16 | /// The remapping process works in two phases:
17 | ///
18 | ///
19 | /// -
20 | /// is called at the beginning of CS1:E57B to:
21 | /// - Remove the allocator max segment limit
22 | /// - Set next free pointer to D000, E000, or F000 depending on the driver being loaded
23 | ///
24 | /// -
25 | /// is called at the end of CS1:E57B (address CS1:E593) to:
26 | /// - Reset the allocator max segment limit to its original value
27 | /// - Reset the next free pointer to its original value
28 | ///
29 | ///
30 | ///
31 | /// After the sequence completes, the driver is loaded but not allocated. This is acceptable
32 | /// because memory above VRAM (0xA000) is not used by the game.
33 | ///
34 | ///
35 | public class DriverLoadToolbox {
36 | ///
37 | /// Segment address for interrupt handlers (0x800), freeing 0xF000 for driver3.
38 | ///
39 | public const ushort INTERRUPT_HANDLER_SEGMENT = 0x800;
40 |
41 | ///
42 | /// Target segment address for VGA driver (DNVGA) remapping.
43 | ///
44 | public const ushort DRIVER1_SEGMENT = 0xD000;
45 |
46 | ///
47 | /// Target segment address for PCM audio driver (DNPCS2, DNSBP) remapping.
48 | ///
49 | public const ushort DRIVER2_SEGMENT = 0xE000;
50 |
51 | ///
52 | /// Target segment address for MIDI music driver (DNPCS, DNMID) remapping.
53 | ///
54 | public const ushort DRIVER3_SEGMENT = 0xF000;
55 |
56 | ///
57 | /// Stores the original allocator limit before modification.
58 | ///
59 | private static ushort InitialFreeLimit = 0;
60 |
61 | ///
62 | /// Stores the original free segment pointer before modification.
63 | ///
64 | private static ushort InitialFreeSegment = 0;
65 |
66 | ///
67 | /// Stores the original free offset pointer before modification.
68 | ///
69 | private static ushort InitialFreeOffset = 0;
70 |
71 | ///
72 | /// Tracks the currently loading driver by its index.
73 | ///
74 | private static int CurrentDriver = -1;
75 |
76 | ///
77 | /// Indicates whether a driver override has occurred in the current load sequence.
78 | ///
79 | private static bool DriverOverrideHappened = false;
80 |
81 | ///
82 | /// Enumeration of driver identifiers corresponding to AX register values during driver loading.
83 | ///
84 | private enum DriverIndex { DNVGA, DN386, DNPCS, DNADL, DNADP, DNADG, DNMID, DNPCS2, DNSDB, DNSBP }
85 |
86 | ///
87 | /// Maps driver index values to their string names for identification and logging purposes.
88 | ///
89 | private static Dictionary _driverNames = new() {
90 | { (int)DriverIndex.DNVGA, "DNVGA" }, // VGA graphics driver
91 | { (int)DriverIndex.DN386, "DN386" }, // 386 driver
92 | { (int)DriverIndex.DNPCS, "DNPCS" }, // PC Speaker sound driver
93 | { (int)DriverIndex.DNADL, "DNADL" }, // AdLib sound driver
94 | { (int)DriverIndex.DNADP, "DNADP" }, // AdLib Pro driver
95 | { (int)DriverIndex.DNADG, "DNADG" }, // AdLib Gold driver
96 | { (int)DriverIndex.DNMID, "DNMID" }, // MIDI music driver
97 | { (int)DriverIndex.DNPCS2, "DNPCS2" }, // PC Speaker variant 2
98 | { (int)DriverIndex.DNSDB, "DNSDB" }, // Sound Blaster driver
99 | { (int)DriverIndex.DNSBP, "DNSBP" } // Sound Blaster Pro driver
100 | };
101 |
102 | ///
103 | /// Remaps specific game drivers to predetermined segment addresses for easier analysis.
104 | /// Should be called at the beginning of CS1:E57B during driver loading.
105 | ///
106 | /// CPU state containing the AX register with the driver index.
107 | /// Memory interface for reading and writing game memory.
108 | ///
109 | /// The AX register indicates which driver is being loaded:
110 | ///
111 | /// - 0: DNVGA - VGA graphics driver
112 | /// - 1: DN386 - 386 driver
113 | /// - 2: DNPCS - PC Speaker sound driver
114 | /// - 3: DNADL - AdLib sound driver
115 | /// - 4: DNADP - AdLib Pro driver
116 | /// - 5: DNADG - AdLib Gold driver
117 | /// - 6: DNMID - MIDI music driver
118 | /// - 7: DNPCS2 - PC Speaker variant 2
119 | /// - 8: DNSDB - Sound Blaster driver
120 | /// - 9: DNSBP - Sound Blaster Pro driver
121 | ///
122 | /// Only VGA, PCM (DNPCS2/DNSBP), and MIDI (DNPCS/DNMID) drivers are remapped.
123 | ///
124 | static public void RemapDrivers(State state, IMemory memory) {
125 | CurrentDriver = state.AX;
126 | ushort? newSegment = ComputeNewSegment(CurrentDriver);
127 | if (newSegment != null) {
128 | DisableAllocatorLimit(state, memory);
129 | DriverOverrideHappened = true;
130 | memory.UInt16[state.DS, 0x39B9] = (ushort)(newSegment.Value + 0x10);
131 | } else {
132 | DriverOverrideHappened = false;
133 | }
134 | }
135 |
136 | ///
137 | /// Computes the target segment address for a driver based on its identifier.
138 | ///
139 | /// The driver index from the AX register.
140 | ///
141 | /// The target segment address for remapping, or null if the driver should not be remapped.
142 | ///
143 | ///
144 | /// Only VGA, PCM audio, and MIDI music drivers are remapped to clean boundaries:
145 | ///
146 | /// - DNVGA → 0xD000 (DRIVER1_SEGMENT)
147 | /// - DNPCS2, DNSBP → 0xE000 (DRIVER2_SEGMENT)
148 | /// - DNPCS, DNMID → 0xF000 (DRIVER3_SEGMENT)
149 | ///
150 | /// Other drivers are left at their original allocations.
151 | ///
152 | private static ushort? ComputeNewSegment(int driverId) {
153 | switch (driverId) {
154 | // VGA Driver Segment
155 | case (int)DriverIndex.DNVGA: return DRIVER1_SEGMENT;
156 | // PCM Driver segment
157 | case (int)DriverIndex.DNPCS2: return DRIVER2_SEGMENT;
158 | case (int)DriverIndex.DNSBP: return DRIVER2_SEGMENT;
159 | // Music driver segment
160 | case (int)DriverIndex.DNPCS: return DRIVER3_SEGMENT;
161 | case (int)DriverIndex.DNMID: return DRIVER3_SEGMENT;
162 | }
163 |
164 | return null;
165 | }
166 |
167 | ///
168 | /// Disables the memory allocator's segment limit to allow driver loading above 0xA000 (VRAM boundary).
169 | ///
170 | /// CPU state providing the DS register value.
171 | /// Memory interface for reading and writing allocator state.
172 | ///
173 | /// Backs up the original allocator state and sets the limit to 0xFFFF, allowing allocation
174 | /// in the high memory region. This is necessary because drivers need to be loaded above the
175 | /// video RAM at 0xA000, which is normally restricted by the allocator.
176 | ///
177 | public static void DisableAllocatorLimit(State state, IMemory memory) {
178 | InitialFreeLimit = memory.UInt16[state.DS, 0xce68];
179 | // Override free limit so that allocator accepts to let us load above 0xA000
180 | memory.UInt16[state.DS, 0xce68] = 0xFFFF;
181 | // Backup the allocator last free segment and offset
182 | InitialFreeOffset = memory.UInt16[state.DS, 0x39B7];
183 | InitialFreeSegment = memory.UInt16[state.DS, 0x39B9];
184 | }
185 |
186 | ///
187 | /// Restores the memory allocator to its original state after driver remapping is complete.
188 | /// Should be called at the end of CS1:E57B (address CS1:E593).
189 | ///
190 | /// CPU state providing the DS register value.
191 | /// Memory interface for writing allocator state.
192 | ///
193 | /// Restores the allocator limit and free pointers to their original values, allowing
194 | /// normal memory allocation to continue in the region below 0xA000. Only performs
195 | /// restoration if a driver override actually occurred.
196 | ///
197 | public static void ResetAllocator(State state, IMemory memory) {
198 | if (!DriverOverrideHappened) {
199 | return;
200 | }
201 |
202 | // Restore allocator state to what it was so that allocation can continue in memory below 0xA000
203 | memory.UInt16[state.DS, 0xce68] = InitialFreeLimit;
204 | memory.UInt16[state.DS, 0x39B7] = InitialFreeOffset;
205 | memory.UInt16[state.DS, 0x39B9] = InitialFreeSegment;
206 | }
207 |
208 | ///
209 | /// Reads and auto-registers driver function table entries for Spice86 function tracing.
210 | /// Should be injected at address CS1:E589.
211 | ///
212 | /// CPU state containing AX (segment), SI (table offset), and CX (function count) registers.
213 | /// Memory interface for reading the driver's function table.
214 | /// Helper for registering function definitions with Spice86.
215 | ///
216 | ///
217 | /// Parses the driver's exported function table and automatically defines entry points and
218 | /// their internal implementations in Spice86's function information database. This enables
219 | /// better tracing, debugging, and potential overriding of driver functions.
220 | ///
221 | ///
222 | /// The function table is an array of near pointers, often pointing to JMP instructions
223 | /// that redirect to the actual implementation. This method decodes both the entry point
224 | /// and the jump target to create complete function definitions.
225 | ///
226 | ///
227 | /// Supports both near (0xE9) and short (0xEB) jump instructions.
228 | ///
229 | ///
230 | public static void ReadDriverFunctionTable(State state, IMemory memory, CSharpOverrideHelper cSharpOverrideHelper) {
231 | _driverNames.TryGetValue(CurrentDriver, out string? driverName);
232 | if (driverName == null) {
233 | return;
234 | }
235 |
236 | ushort segment = (ushort)(state.AX - 0x10);
237 | // -2 because SI points to the first segment but we want the offset.
238 | ushort functionTableEntryOffset = (ushort)(state.SI - 2);
239 | int numberOfFunctions = state.CX;
240 | for (int i = 0; i < numberOfFunctions; i++) {
241 | ushort pointerTableOffset = memory.UInt16[state.DS, (ushort)(functionTableEntryOffset + i * 4)];
242 | SegmentedAddress entryAddress = new SegmentedAddress(segment, pointerTableOffset);
243 | DefineFunctionIfNotPresent(entryAddress, $"{driverName}_entry_{i.ToString("D2")}", cSharpOverrideHelper);
244 | // Parse the jump to create the target function address in the driver
245 | SegmentedAddress? jumpTargetAddress = null;
246 | if (memory.UInt8[entryAddress.Linear] == 0xE9) {
247 | // 16bit offset
248 | short jumpDisp = (short)memory.UInt16[entryAddress.Linear + 1];
249 | ushort jumpOffset = (ushort)(entryAddress.Offset + 3 + jumpDisp);
250 | jumpTargetAddress = new SegmentedAddress(segment, jumpOffset);
251 | }
252 |
253 | if (memory.UInt8[entryAddress.Linear] == 0xEB) {
254 | // 8 bit offset
255 | sbyte jumpDisp = (sbyte)memory.UInt8[entryAddress.Linear + 1];
256 | ushort jumpOffset = (ushort)(entryAddress.Offset + 2 + jumpDisp);
257 | jumpTargetAddress = new SegmentedAddress(segment, jumpOffset);
258 | }
259 |
260 | DefineFunctionIfNotPresent(jumpTargetAddress, $"{driverName}_internal_function_{i.ToString("D2")}",
261 | cSharpOverrideHelper);
262 | }
263 | }
264 |
265 | ///
266 | /// Defines a function in Spice86's function information database if it doesn't already exist.
267 | ///
268 | /// The segmented address of the function, or null to skip registration.
269 | /// The symbolic name to assign to the function for debugging and tracing.
270 | /// Helper for registering the function definition.
271 | ///
272 | /// Prevents duplicate function definitions by checking if the address is already registered.
273 | /// Does nothing if the address is null.
274 | ///
275 | private static void DefineFunctionIfNotPresent(SegmentedAddress? address, string name,
276 | CSharpOverrideHelper cSharpOverrideHelper) {
277 | if (address == null) {
278 | return;
279 | }
280 |
281 | if (cSharpOverrideHelper.FunctionInformations.ContainsKey(address.Value)) {
282 | return;
283 | }
284 | cSharpOverrideHelper.DefineFunction(address.Value.Segment, address.Value.Offset, name);
285 | }
286 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Cryogenic
2 |
3 | Thank you for your interest in contributing to Cryogenic! This document provides guidelines and information to help you contribute effectively to the project.
4 |
5 | ## Table of Contents
6 |
7 | - [Code of Conduct](#code-of-conduct)
8 | - [Getting Started](#getting-started)
9 | - [Development Environment](#development-environment)
10 | - [Project Architecture](#project-architecture)
11 | - [Contributing Guidelines](#contributing-guidelines)
12 | - [Coding Conventions](#coding-conventions)
13 | - [Reverse Engineering Workflow](#reverse-engineering-workflow)
14 | - [Testing Your Changes](#testing-your-changes)
15 | - [Submitting Changes](#submitting-changes)
16 | - [Communication](#communication)
17 |
18 | ## Code of Conduct
19 |
20 | We are committed to providing a welcoming and inclusive environment for all contributors. Please:
21 |
22 | - Be respectful and constructive in all interactions
23 | - Welcome newcomers and help them get started
24 | - Focus on what is best for the community and the project
25 | - Show empathy towards other community members
26 | - Provide and gracefully accept constructive feedback
27 |
28 | ## Getting Started
29 |
30 | ### Prerequisites
31 |
32 | Before you begin, ensure you have:
33 |
34 | 1. **.NET 8 SDK** - Download from [dotnet.microsoft.com](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
35 | 2. **Dune CD Version 3.7** - You must obtain DNCDPRG.EXE and DUNE.DAT separately (copyrighted material)
36 | 3. **Git** - For version control
37 | 4. **A code editor** - Visual Studio, VS Code, or Rider recommended
38 |
39 | ### Verify Your Executable
40 |
41 | Ensure your DNCDPRG.EXE has the correct SHA256 hash:
42 | ```
43 | 5f30aeb84d67cf2e053a83c09c2890f010f2e25ee877ebec58ea15c5b30cfff9
44 | ```
45 |
46 | You can verify with PowerShell:
47 | ```powershell
48 | Get-FileHash DNCDPRG.EXE -Algorithm SHA256
49 | ```
50 |
51 | Or on Linux/Mac:
52 | ```bash
53 | sha256sum DNCDPRG.EXE
54 | ```
55 |
56 | ### Fork and Clone
57 |
58 | 1. Fork the repository on GitHub
59 | 2. Clone your fork locally:
60 | ```bash
61 | git clone https://github.com/YOUR-USERNAME/Cryogenic.git
62 | cd Cryogenic
63 | ```
64 |
65 | 3. Add the upstream repository:
66 | ```bash
67 | git remote add upstream https://github.com/OpenRakis/Cryogenic.git
68 | ```
69 |
70 | 4. Build the project:
71 | ```bash
72 | cd src
73 | dotnet build
74 | ```
75 |
76 | 5. Test that it runs:
77 | ```bash
78 | dotnet run --Exe /path/to/DNCDPRG.EXE --UseCodeOverride true -p 4096
79 | ```
80 |
81 | ## Development Environment
82 |
83 | ### Recommended Tools
84 |
85 | - **IDE**: Visual Studio 2022, JetBrains Rider, or VS Code with C# extensions
86 | - **Debugger**: Built-in IDE debugger for C# code
87 | - **Ghidra**: For static analysis of assembly code (optional but recommended)
88 | - **Hex Editor**: For examining data files (optional)
89 |
90 | ### Project Structure
91 |
92 | ```
93 | Cryogenic/
94 | ├── src/
95 | │ └── Cryogenic/
96 | │ ├── Program.cs # Entry point
97 | │ ├── DuneCdOverrideSupplier.cs # Override registration
98 | │ ├── DriverLoadToolbox.cs # Driver remapping utilities
99 | │ ├── Overrides/ # C# implementations
100 | │ │ ├── Overrides.cs # Main override class (partial)
101 | │ │ ├── VgaDriverCode.cs # VGA driver overrides
102 | │ │ ├── MenuCode.cs # Menu system overrides
103 | │ │ ├── DialoguesCode.cs # Dialogue system overrides
104 | │ │ └── ...
105 | │ ├── Generated/ # Auto-generated properties for globals
106 | │ └── Globals/ # Game state accessors
107 | ├── doc/ # Documentation and screenshots
108 | ├── dump/ # Memory dumps and analysis configs
109 | └── README.md
110 | ```
111 |
112 | ## Project Architecture
113 |
114 | ### How Cryogenic Works
115 |
116 | Cryogenic uses **Spice86** to run the original DNCDPRG.EXE while selectively replacing assembly routines with C# implementations. This hybrid approach allows:
117 |
118 | 1. **Incremental rewriting** - Replace one function at a time
119 | 2. **Immediate testing** - Run the game after each change
120 | 3. **Behavioral verification** - Compare C# output with original assembly
121 |
122 | ### Key Concepts
123 |
124 | #### Segments
125 |
126 | Memory segments are the foundation of address translation:
127 |
128 | - `cs1 = 0x1000` - Main game code
129 | - `cs2, cs3, cs4` - Mapped driver segments
130 | - `cs5 = 0xF000` - BIOS/IRQ handlers
131 |
132 | **Important**: Never change segment values without auditing ALL `DefineFunction` and `DoOnTopOfInstruction` calls.
133 |
134 | #### Override Registration
135 |
136 | Overrides are registered in `Overrides.DefineOverrides()` using:
137 |
138 | - `DefineFunction(segment, offset, method)` - Replaces identified functions (note: Dune sometimes modifies the stack to change return addresses instead of using CALL instructions)
139 | - `DoOnTopOfInstruction(segment, offset, method)` - Inline hooks
140 |
141 | Example:
142 | ```csharp
143 | DefineFunction(cs1, 0xC085, SetBackBufferAsActiveFrameBuffer_1000_C085_01C085, false);
144 | ```
145 |
146 | #### Return Methods
147 |
148 | Use the correct return method based on the original assembly:
149 |
150 | - `NearRet()` - For near CALL/RET (within same segment)
151 | - `FarRet()` - For far CALL/RET (across segments)
152 |
153 | Using the wrong one will corrupt the stack!
154 |
155 | ## Contributing Guidelines
156 |
157 | ### Ways to Contribute
158 |
159 | #### 1. Reverse Engineering
160 |
161 | Analyze assembly code and create C# implementations.
162 |
163 | **Skills needed**: Assembly language, debugging, patience
164 |
165 | **Example tasks**:
166 | - Identify function boundaries in disassembly
167 | - Document function parameters and return values
168 | - Implement C# equivalent that produces identical behavior
169 |
170 | #### 2. Documentation
171 |
172 | Document code, game mechanics, and project processes.
173 |
174 | **Skills needed**: Technical writing, understanding of the codebase
175 |
176 | **Example tasks**:
177 | - Add XML doc comments to override methods
178 | - Document game data structures
179 | - Create tutorials for new contributors
180 | - Explain complex algorithms
181 |
182 | #### 3. Testing & Validation
183 |
184 | Play the game and verify correct behavior.
185 |
186 | **Skills needed**: Attention to detail, systematic testing
187 |
188 | **Example tasks**:
189 | - Test specific game scenarios
190 | - Verify that overrides match original behavior
191 | - Report discrepancies or bugs
192 | - Create test cases
193 |
194 | #### 4. Code Quality
195 |
196 | Improve existing C# implementations.
197 |
198 | **Skills needed**: C# programming, refactoring
199 |
200 | **Example tasks**:
201 | - Refactor complex methods for clarity
202 | - Add error handling
203 | - Improve performance
204 | - Remove code duplication
205 |
206 | ## Coding Conventions
207 |
208 | ### C# Style
209 |
210 | Follow standard C# conventions with these project-specific rules:
211 |
212 | 1. **Keep generated naming** - Override methods use pattern `{Segment}_{Offset}_{Linear}` (e.g., `SetBackBufferAsActiveFrameBuffer_1000_C085_01C085`)
213 |
214 | 2. **Use provided accessors** - Access game state through `globalsOnDs` and `globalsOnCsSegment0x2538` instead of manual pointer math
215 |
216 | 3. **Document extensively** - Explain what the override does, not just how:
217 | ```csharp
218 | ///
219 | /// Switches the active framebuffer to the back buffer for double-buffered rendering.
220 | /// Original assembly at CS1:C085.
221 | ///
222 | public void SetBackBufferAsActiveFrameBuffer_1000_C085_01C085() {
223 | // Implementation
224 | NearRet();
225 | }
226 | ```
227 |
228 | 4. **Handle edge cases** - Dune code often has surprising edge cases. Document and handle them:
229 | ```csharp
230 | // Original assembly has special handling for value 0xFFFF
231 | if (value == 0xFFFF) {
232 | // Special case logic
233 | }
234 | ```
235 |
236 | 5. **Logging** - Use `_loggerService.Debug` for diagnostics:
237 | ```csharp
238 | _loggerService.Debug("Loading dialogue {DialogueId}", dialogueId);
239 | ```
240 |
241 | ### Override Implementation Guidelines
242 |
243 | 1. **Match original behavior exactly** - Dune is hand-written assembly; we must achieve identical behavior, not necessarily byte-for-byte compatibility
244 | 2. **Test thoroughly** - Play the game and verify the override works in all scenarios
245 | 3. **Add safety checks** - Throw `FailAsUntested` for unobserved code paths
246 | 4. **Keep it simple** - Don't over-engineer; match the original logic
247 | 5. **Use correct return** - `NearRet()` or `FarRet()` based on original instruction
248 |
249 | ### File Organization
250 |
251 | - **Keep related overrides together** - VGA code in `VgaDriverCode.cs`, menu code in `MenuCode.cs`, etc.
252 | - **Extend partial classes** - Add new overrides to `Overrides` partial class in domain-specific files
253 | - **Don't edit Generated/** - Add custom code in `Globals/Extra*` or new override files
254 |
255 | ## Reverse Engineering Workflow
256 |
257 | ### Typical Process
258 |
259 | 1. **Identify a function** in the assembly code
260 | 2. **Understand its behavior** through static analysis or runtime tracing
261 | 3. **Document parameters and return values**
262 | 4. **Implement in C#** maintaining exact behavior
263 | 5. **Register the override** in `DefineOverrides()`
264 | 6. **Test thoroughly** by playing the game
265 | 7. **Document** with comments explaining the function's purpose
266 |
267 | ### Tools and Techniques
268 |
269 | #### Using Spice86's CFGCPU
270 |
271 | Spice86's CFGCPU (Control Flow Graph CPU) is the recommended approach for analyzing Dune's code, as it:
272 | - Handles self-modifying code that Dune uses
273 | - Works reliably with 16-bit x86 assembly
274 | - Is compatible with C# overrides
275 | - Already supports Dune and other games
276 |
277 | **Note**: The Ghidra plugin is largely abandoned and has numerous bugs with 16-bit x86 support. CFGCPU will eventually enable automated code generation.
278 |
279 | #### Using Spice86 Debugger
280 |
281 | 1. Run with debugger enabled
282 | 2. Set breakpoints at function entry
283 | 3. Inspect registers and memory
284 | 4. Step through execution
285 | 5. Compare with your C# implementation
286 |
287 | #### Memory Dumps
288 |
289 | The project includes memory dump functionality. Enable in `DefineMemoryDumpsMapping()` to capture game state at specific points.
290 |
291 | ### Example: Implementing a Simple Override
292 |
293 | Let's say you've identified a function at CS1:1234 that clears a buffer:
294 |
295 | 1. **Analyze the assembly**:
296 | ```asm
297 | CS1:1234 mov cx, 0x100 ; Counter
298 | CS1:1237 mov di, 0x0 ; Destination offset
299 | CS1:123A xor ax, ax ; Value to write (0)
300 | CS1:123C rep stosw ; Fill buffer
301 | CS1:123E ret ; Return
302 | ```
303 |
304 | 2. **Implement in C#**:
305 | ```csharp
306 | ///
307 | /// Clears a 256-word buffer at ES:0000.
308 | /// Original assembly at CS1:1234.
309 | ///
310 | public void ClearBuffer_1000_1234_011234() {
311 | var buffer = MemoryUtils.ToMemorySpan(_state.Memory, _state.ES.BaseAddress, 512);
312 | buffer.Fill(0);
313 | NearRet();
314 | }
315 | ```
316 |
317 | 3. **Register in DefineOverrides()**:
318 | ```csharp
319 | DefineFunction(cs1, 0x1234, ClearBuffer_1000_1234_011234, false);
320 | ```
321 |
322 | 4. **Test**: Run the game and verify the buffer is cleared at the right time
323 |
324 | 5. **Document**: Add comments explaining when and why this is called
325 |
326 | ## Testing Your Changes
327 |
328 | ### Manual Testing
329 |
330 | 1. **Build the project**:
331 | ```bash
332 | dotnet build
333 | ```
334 |
335 | 2. **Run the game**:
336 | ```bash
337 | dotnet run --Exe /path/to/DNCDPRG.EXE --UseCodeOverride true -p 4096
338 | ```
339 |
340 | 3. **Test specific scenarios** that exercise your code:
341 | - Main menu navigation
342 | - Dialogue sequences
343 | - Combat
344 | - Resource management
345 | - Save/Load
346 |
347 | 4. **Compare with original**: If possible, run the original game and compare behavior
348 |
349 | ### Important Test Cases
350 |
351 | - **Main menu** - Navigation, option selection
352 | - **New game start** - Initial setup and intro sequence
353 | - **Dialogue system** - Character conversations
354 | - **Map exploration** - Movement and interactions
355 | - **Resource management** - Spice collection and allocation
356 | - **Combat** - Battle sequences
357 | - **Save/Load** - Game state persistence
358 |
359 | ### Debugging
360 |
361 | Use your IDE's debugger to step through C# code:
362 |
363 | 1. Set breakpoints in your override methods
364 | 2. Run with debugger attached
365 | 3. Inspect variables and execution flow
366 | 4. Verify behavior matches expectations
367 |
368 | ## Submitting Changes
369 |
370 | ### Before You Submit
371 |
372 | - [ ] Code builds without errors
373 | - [ ] Game runs and your changes work as expected
374 | - [ ] You've tested relevant game scenarios
375 | - [ ] Code follows project conventions
376 | - [ ] You've added appropriate comments/documentation
377 | - [ ] No unrelated changes or debugging code left in
378 |
379 | ### Creating a Pull Request
380 |
381 | 1. **Create a feature branch**:
382 | ```bash
383 | git checkout -b feature/your-feature-name
384 | ```
385 |
386 | 2. **Make your changes and commit**:
387 | ```bash
388 | git add .
389 | git commit -m "Brief description of changes"
390 | ```
391 |
392 | 3. **Push to your fork**:
393 | ```bash
394 | git push origin feature/your-feature-name
395 | ```
396 |
397 | 4. **Open a Pull Request** on GitHub with:
398 | - **Clear title** describing the change
399 | - **Description** explaining what and why
400 | - **Testing notes** describing how you verified it works
401 | - **Screenshots** if relevant (UI changes, bug fixes)
402 |
403 | ### Pull Request Template
404 |
405 | ```markdown
406 | ## Description
407 | Brief description of changes
408 |
409 | ## Changes Made
410 | - List of specific changes
411 | - What was added/modified/removed
412 |
413 | ## Testing
414 | - [ ] Built successfully
415 | - [ ] Tested in-game
416 | - [ ] Verified correct behavior
417 |
418 | ## Testing Notes
419 | Describe how you tested and what scenarios work correctly
420 |
421 | ## Related Issues
422 | Fixes #123 (if applicable)
423 | ```
424 |
425 | ### Code Review
426 |
427 | Maintainers will review your PR and may:
428 | - Request changes or clarifications
429 | - Suggest improvements
430 | - Merge when everything looks good
431 |
432 | Be patient and responsive to feedback!
433 |
434 | ## Communication
435 |
436 | ### GitHub Issues
437 |
438 | Use GitHub issues for:
439 | - Bug reports
440 | - Feature requests
441 | - Questions about the code
442 | - Proposals for significant changes
443 |
444 | ### Discussions
445 |
446 | For general discussion, questions, or showing off progress, use GitHub Discussions.
447 |
448 | ### Finding Tasks
449 |
450 | Look for issues labeled:
451 | - `good first issue` - Good for newcomers
452 | - `help wanted` - Community help needed
453 | - `documentation` - Documentation tasks
454 | - `reverse-engineering` - RE work needed
455 |
456 | ## Additional Resources
457 |
458 | - **Spice86 Documentation**: [github.com/OpenRakis/Spice86](https://github.com/OpenRakis/Spice86)
459 | - **Project Website**: [openrakis.github.io/Cryogenic](https://openrakis.github.io/Cryogenic)
460 | - **Copilot Instructions**: See `.github/copilot-instructions.md` for detailed architectural notes
461 |
462 | ## Questions?
463 |
464 | If you have questions:
465 | 1. Check existing documentation and issues
466 | 2. Open a GitHub issue with the `question` label
467 | 3. Reach out in GitHub Discussions
468 |
469 | Thank you for contributing to Cryogenic! Your efforts help preserve and modernize this classic game for future generations.
470 |
--------------------------------------------------------------------------------
/src/Cryogenic/Overrides/VgaDriverCode.cs:
--------------------------------------------------------------------------------
1 | namespace Cryogenic.Overrides;
2 |
3 | using Spice86.Core.Emulator.Devices.Video;
4 | using Spice86.Core.Emulator.Memory;
5 | using Spice86.Shared.Emulator.Errors;
6 | using Spice86.Shared.Utils;
7 | using Spice86.Shared.Interfaces;
8 |
9 | using System;
10 |
11 | ///
12 | /// Partial class containing VGA graphics driver (DNVGA) function overrides.
13 | ///
14 | ///
15 | ///
16 | /// This file provides C# implementations for the custom VGA driver used by Dune CD.
17 | /// The driver is loaded at segment 0xD000 (cs2) and provides hardware-accelerated
18 | /// graphics operations including blitting, palette management, retrace synchronization,
19 | /// and specialized rendering for maps and globe views.
20 | ///
21 | ///
22 | /// Method names contain underscores to separate segment, offset, and linear addresses
23 | /// for traceability back to the original DOS disassembly.
24 | ///
25 | ///
26 | public partial class Overrides {
27 | ///
28 | /// Offset in video memory where the image under the mouse cursor is saved (0xFA00).
29 | ///
30 | private const ushort IMAGE_UNDER_MOUSE_CURSOR_START = 0xFA00;
31 |
32 | ///
33 | /// Registers VGA driver function overrides with Spice86.
34 | ///
35 | ///
36 | /// Defines overrides for DNVGA driver functions including mode setting, blitting,
37 | /// palette operations, mouse cursor handling, retrace synchronization, and specialized
38 | /// rendering routines for maps and textures.
39 | ///
40 | public void DefineVgaDriverCodeOverrides() {
41 | DefineFunction(cs2, 0x100, "VgaFunc00SetMode");
42 | DefineFunction(cs2, 0x103, VgaFunc01GetInfoInAxCxBp_334B_0103_0335B3);
43 | DefineFunction(cs2, 0x109, "VgaFunc03DrawMouseCursor");
44 | DefineFunction(cs2, 0x10C, VgaFunc04RestoreImageUnderMouseCursor_334B_010C_0335BC);
45 | DefineFunction(cs2, 0x10F, "VgaFunc05Blit");
46 | DefineFunction(cs2, 0x118, VgaFunc08FillWithZeroFor64000AtES_334B_0118_0335C8);
47 | DefineFunction(cs2, 0x121, VgaFunc11MemcpyDSToESFor64000_334B_0121_0335D1);
48 | DefineFunction(cs2, 0x124, "VgaFunc12CopyRectangle");
49 | DefineFunction(cs2, 0x12A, VgaFunc14CopySquareOfPixelsSiIsSourceSegment_334B_012A_0335DA);
50 | DefineFunction(cs2, 0x12D, VgaFunc15MemcpyDSToESFor64000_334B_012D_0335DD);
51 | DefineFunction(cs2, 0x130, VgaFunc16CopySquareOfPixels_334B_0130_0335E0);
52 | DefineFunction(cs2, 0x133, "VgaFunc17CopyframebufferExplodeAndCenter");
53 | DefineFunction(cs2, 0x13C, VgaFunc20NoOp_334B_013C_0335EC);
54 | DefineFunction(cs2, 0x13F, "VgaFunc21SetPixel");
55 | DefineFunction(cs2, 0x163, VgaFunc33UpdateVgaOffset01A3FromLineNumberAsAx_334B_0163_033613);
56 | DefineFunction(cs2, 0x16C, VgaFunc36GenerateTextureOutBP_334B_016C_03361C);
57 | DefineFunction(cs2, 0x17B, "VgaFunc41CopyPalette2toPalette1");
58 | DefineFunction(cs2, 0x9B8, WaitForRetrace_334B_09B8_033E68);
59 | DefineFunction(cs2, 0xA21, SetBxCxPaletteRelated_334B_0A21_033ED1);
60 | DefineFunction(cs2, 0xA58, CopyCsRamB5FToB2F_334B_0A58_033F08);
61 | DefineFunction(cs2, 0xB68, LoadPaletteInVgaDac_334B_0B68_034018);
62 | DefineFunction(cs2, 0xC10, SetDiFromXYCordsDxBx_334B_0C10_0340C0);
63 | DefineFunction(cs2, 0x1B7C, MemcpyDSToESFor64000_334B_1B7C_03502C);
64 | DefineFunction(cs2, 0x1B8E, CopySquareOfPixels_334B_1B8E_03503E);
65 |
66 | // called in globe, without it globe rotation works but stutters when clicking
67 | DefineFunction(cs2, 0x1D07, "UnknownGlobeRelated");
68 | DefineFunction(cs2, 0x1D5A, UnknownGlobeInitRelated_334B_1D5A_03520A);
69 | DefineFunction(cs2, 0x2025, "UnknownMapRelated");
70 | DefineFunction(cs2, 0x2343, CopyMapBlock_334B_2343_0357F3);
71 | DefineFunction(cs2, 0x253D, RetraceRelatedCalledOnEnterGlobe_334B_253D_0359ED);
72 | DefineFunction(cs2, 0x2572, WaitForRetraceInTransitions_334B_2572_035A22);
73 | DefineFunction(cs2, 0x261D, WaitForRetraceDuringIntroVideo_334B_261D_035ACD);
74 | DefineFunction(cs2, 0x32C1, "GenerateMenuTransitionFrame");
75 | }
76 |
77 | public Action CopyCsRamB5FToB2F_334B_0A58_033F08(int gotoAddress) {
78 | _loggerService.Debug("CopyCsRamB5FToB2F");
79 |
80 | // No jump
81 | uint sourceAddress = MemoryUtils.ToPhysicalAddress(CS, 0x5BF);
82 | uint destinationAddress = MemoryUtils.ToPhysicalAddress(CS, 0x2BF);
83 |
84 | // 768 times (3 blocks of 256)
85 | Memory.MemCopy(sourceAddress, destinationAddress, 768);
86 | return NearRet();
87 | }
88 |
89 | public Action CopyMapBlock_334B_2343_0357F3(int gotoAddress) {
90 | // 37 lines in ghidra
91 | ushort blockSize = CX;
92 | uint baseSourceAddress = MemoryUtils.ToPhysicalAddress(DS, SI);
93 | uint baseDestinationAddress = MemoryUtils.ToPhysicalAddress(ES, DI);
94 | _loggerService.Debug("unknownMapCopyMapBlock blockSize={@BlockSize}, baseSourceAddress:{@BaseSourceAddress},baseDestinationAddress:{@BaseDestinationAddress}", blockSize, baseSourceAddress, baseDestinationAddress);
95 | for (int i = 0; i < 4; i++) {
96 | for (int j = 0; j < blockSize; j++) {
97 | byte value = Memory.UInt8[baseSourceAddress + j + 400 * i];
98 | value = (byte)(((value >> 4) & 0xF) + 0x10);
99 | Memory.UInt8[baseDestinationAddress + j + 320 * i] = value;
100 | }
101 | }
102 |
103 | // point to next block
104 | SI = (ushort)(SI + 4 * 400);
105 | DI = (ushort)(DI + 4 * 320);
106 | return NearRet();
107 | }
108 |
109 | public Action VgaFunc16CopySquareOfPixels_334B_0130_0335E0(int gotoAddress) {
110 | // 26F0E
111 | return CopySquareOfPixels_334B_1B8E_03503E(0);
112 | }
113 |
114 | public Action VgaFunc14CopySquareOfPixelsSiIsSourceSegment_334B_012A_0335DA(int gotoAddress) {
115 | // 26F0C
116 | DS = SI;
117 | return CopySquareOfPixels_334B_1B8E_03503E(0);
118 | }
119 |
120 | public Action VgaFunc08FillWithZeroFor64000AtES_334B_0118_0335C8(int gotoAddress) {
121 | // 26D77
122 | uint address = MemoryUtils.ToPhysicalAddress(ES, 0);
123 | _loggerService.Debug("fillWithZeroFor64000AtES address:{@Address}", address);
124 | Memory.Memset8(address, 0, 64000);
125 | return FarRet();
126 | }
127 |
128 | // when disabled floors disappear in some rooms.
129 | public Action VgaFunc36GenerateTextureOutBP_334B_016C_03361C(int gotoAddress) {
130 | // 28D69, 30 lines in ghidra
131 | uint destinationBaseAddress = MemoryUtils.ToPhysicalAddress(ES, 0);
132 | ushort initialColor = AX;
133 | ushort colorIncrement = DI;
134 | ushort xorNoise = BP;
135 | ushort xorNoisePattern = SI;
136 | ushort length = CX;
137 | SetDiFromXYCordsDxBx_334B_0C10_0340C0(0);
138 | ushort destinationOffsetAddress = DI;
139 | int direction = DirectionFlag ? -1 : 1;
140 | if (_loggerService.IsEnabled(Serilog.Events.LogEventLevel.Debug)) {
141 | _loggerService.Debug("generateFloors xy:{@X},{@Y} destinationBaseAddress:{@DestinationBaseAddress},destinationOffsetAddress:{@DestinationOffsetAddress}," + "colorIncrement:{@ColorIncrement},initialColor:{@InitialColor},xorNoise:{@XorNoise},xorNoisePattern:{@XorNoisePattern},length:{@Length},direction:{@Direction}", DX, BX, destinationBaseAddress,
142 | destinationOffsetAddress, colorIncrement, initialColor, xorNoise, xorNoisePattern, length, direction);
143 | }
144 |
145 | uint destinationAddress = destinationBaseAddress + destinationOffsetAddress;
146 | for (int i = 0; i < length; i++) {
147 | bool shouldXor = (xorNoise & 1) == 1;
148 | xorNoise >>= 1;
149 | if (shouldXor) {
150 | xorNoise ^= xorNoisePattern;
151 | }
152 |
153 | byte valueToStore = (byte)((uint)ConvertUtils.Int8((byte)((xorNoise & 0x3) - 1)) + (uint)ConvertUtils.Int8((byte)(initialColor >> 8)));
154 | Memory.UInt8[destinationAddress + i * direction] = valueToStore;
155 | initialColor += colorIncrement;
156 | }
157 |
158 | // Needed for next calls
159 | BP = xorNoise;
160 | return FarRet();
161 | }
162 |
163 | public ushort GetBaseSegment() {
164 | return cs2;
165 | }
166 |
167 | public Action VgaFunc01GetInfoInAxCxBp_334B_0103_0335B3(int gotoAddress) {
168 | // 25D59
169 | _loggerService.Debug("getInfoInAxCxBp");
170 | AX = MemoryMap.GraphicVideoMemorySegment;
171 | CX = IMAGE_UNDER_MOUSE_CURSOR_START;
172 | BP = 0;
173 | return FarRet();
174 | }
175 |
176 | public Action LoadPaletteInVgaDac_334B_0B68_034018(int gotoAddress) {
177 | // No jump, 49 lines in ghidra
178 | try {
179 | VgaCard vgaCard = Machine.VgaCard;
180 | uint baseAddress = MemoryUtils.ToPhysicalAddress(ES, DX);
181 | byte writeIndex = BL;
182 | ushort numberOfColors = CX;
183 | byte loadMode = globalsOnCsSegment0X2538.Get2538_01BD_Byte8_PaletteLoadMode();
184 | _loggerService.Debug("loadPaletteInVgaDac, baseAddress:{@BaseAddress}, writeIndex:{@Writeindex}, loadMode:{@LoadMode}, numberOfColors:{@NumberOfColors}", baseAddress, writeIndex, loadMode, numberOfColors);
185 | IVideoState videoState = Machine.VgaRegisters;
186 | videoState.DacRegisters.IndexRegisterWriteMode = writeIndex;
187 |
188 | if (loadMode == 0) {
189 | for (uint i = 0; i < numberOfColors * 3; i++) {
190 | byte value = UInt8[baseAddress + i];
191 | videoState.DacRegisters.DataRegister = value;
192 | }
193 | } else {
194 | // Untested ... 25f29 in ghidra, 2538:BA9 in dosbox, probably wrong
195 | throw FailAsUntested("This palette code path was converted to C# but never executed...");
196 | for (ushort i = 0; i < numberOfColors * 3; i += 3) {
197 | byte r = (byte)(UInt8[baseAddress + i] & 0x3F);
198 | byte g = (byte)(UInt8[baseAddress + i + 1] & 0x3F);
199 | byte b = (byte)(UInt8[baseAddress + i + 2] & 0x3F);
200 | byte value = (byte)((r * 5 + g * 9 + b * 2) >> 4);
201 | videoState.DacRegisters.DataRegister = value;
202 | videoState.DacRegisters.DataRegister = value;
203 | videoState.DacRegisters.DataRegister = value;
204 | }
205 | }
206 |
207 | return NearRet();
208 | } catch (ArgumentOutOfRangeException e) {
209 | throw new UnrecoverableException(e.Message);
210 | }
211 | }
212 |
213 | public Action VgaFunc11MemcpyDSToESFor64000_334B_0121_0335D1(int gotoAddress) {
214 | return MemcpyDSToESFor64000_334B_1B7C_03502C(0);
215 | }
216 |
217 | public Action VgaFunc15MemcpyDSToESFor64000_334B_012D_0335DD(int gotoAddress) {
218 | // 26EFC, seems used when moving rooms
219 | return MemcpyDSToESFor64000_334B_1B7C_03502C(0);
220 | }
221 |
222 | public Action MemcpyDSToESFor64000_334B_1B7C_03502C(int gotoAddress) {
223 | // No jump, 22 lines in ghidra
224 | uint sourceAddress = MemoryUtils.ToPhysicalAddress(DS, 0);
225 | uint destinationAddress = MemoryUtils.ToPhysicalAddress(ES, 0);
226 | _loggerService.Debug("memcpyDSToESFor64000 sourceAddress:{@SourceAddress},destinationAddress:{@DestinationAddress}", sourceAddress, destinationAddress);
227 | Memory.MemCopy(sourceAddress, destinationAddress, 64000);
228 | return FarRet();
229 | }
230 |
231 | public Action CopySquareOfPixels_334B_1B8E_03503E(int gotoAddress) {
232 | // No jump, 30 instructions 67 lines in ghidra
233 | // warning: we dont set registers at the end but no idea if their values are used or not.
234 | SetDiFromXYCordsDxBx_334B_0C10_0340C0(0);
235 | ushort baseOffsetDi = DI;
236 | uint sourceAddress = MemoryUtils.ToPhysicalAddress(DS, baseOffsetDi);
237 | uint destinationAddress = MemoryUtils.ToPhysicalAddress(ES, baseOffsetDi);
238 | ushort rowCount = BP;
239 | ushort columnCount = AX;
240 | _loggerService.Debug("moveSquareOfPixels sourceBuffer:{@SourceBuffer}, destinationBuffer:{@DestinationBuffer},startX:{@StartX},startY:{@StartY},columnCount:{@ColumnCount},rowCount:{@RowCount}", DS, ES, DX, BX, columnCount, rowCount);
241 | for (ushort y = 0; y < columnCount; y++) {
242 | for (ushort x = 0; x < rowCount; x++) {
243 | int disp = y * 320 + x;
244 | UInt8[destinationAddress + disp] = UInt8[sourceAddress + disp];
245 | }
246 | }
247 |
248 | return FarRet();
249 | }
250 |
251 | public Action VgaFunc20NoOp_334B_013C_0335EC(int gotoAddress) {
252 | return FarRet();
253 | }
254 |
255 | ///
256 | /// Restores image under mouse cursor. No input apart from globals and no output.
257 | ///
258 | public Action VgaFunc04RestoreImageUnderMouseCursor_334B_010C_0335BC(int gotoAddress) {
259 | // 26CC0
260 | ushort mouseCursorAddressInVram = this.globalsOnCsSegment0X2538.Get2538_018A_Word16_MouseCursorAddressInVram();
261 | ushort columns = this.globalsOnCsSegment0X2538.Get2538_018C_Word16_ColumnsOfMouseCursorCount();
262 | ushort lines = this.globalsOnCsSegment0X2538.Get2538_018E_Word16_LinesOfMouseCursorCount();
263 | _loggerService.Debug("restoreImageUnderMouseCursor mouseCursorAddressInVram:{@MouseCursorAddressInVram},columns:{@Columns},lines:{@Lines}", mouseCursorAddressInVram, columns, lines);
264 | uint sourceAddress = MemoryUtils.ToPhysicalAddress(MemoryMap.GraphicVideoMemorySegment, IMAGE_UNDER_MOUSE_CURSOR_START);
265 | uint destinationAddress = MemoryUtils.ToPhysicalAddress(MemoryMap.GraphicVideoMemorySegment, mouseCursorAddressInVram);
266 | for (ushort i = 0; i < lines; i++) {
267 | Memory.MemCopy((uint)(sourceAddress + columns * i), (uint)(destinationAddress + 320 * i), columns);
268 | }
269 |
270 | return FarRet();
271 | }
272 |
273 | public Action RetraceRelatedCalledOnEnterGlobe_334B_253D_0359ED(int gotoAddress) {
274 | return NearRet();
275 | }
276 |
277 | public Action SetBxCxPaletteRelated_334B_0A21_033ED1(int gotoAddress) {
278 | // No jump
279 | BX = (ushort)(BX / 3);
280 | ushort unknownValue = CX;
281 | if (unknownValue < 0x300) {
282 | CX = (ushort)(unknownValue / 3);
283 | return NearRet();
284 | }
285 |
286 | // crashes when executed, but never reached...
287 | CX = 0x100;
288 | return NearRet();
289 | }
290 |
291 | public Action UnknownGlobeInitRelated_334B_1D5A_03520A(int gotoAddress) {
292 | // no jump
293 | globalsOnCsSegment0X2538.Set2538_1CA6_Word16_UnknownGlobeRelated(DI);
294 | globalsOnCsSegment0X2538.Set2538_1EA6_Word16_UnknownGlobeRelated(0xFEDD);
295 | globalsOnCsSegment0X2538.Set2538_1F29_Word16_UnknownGlobeRelated(0xFE5A);
296 | globalsOnCsSegment0X2538.Set2538_1CAE_Word16_UnknownGlobeRelated(0x6360 - 1);
297 | globalsOnCsSegment0X2538.Set2538_1CB0_Word16_UnknownGlobeRelated(0x6360);
298 | globalsOnCsSegment0X2538.Set2538_1CB2_Word16_UnknownGlobeRelated(0x6360);
299 | DS = SS;
300 | CarryFlag = true;
301 | return FarRet();
302 | }
303 |
304 | // line number in AX, offset address in 01A3
305 | public Action VgaFunc33UpdateVgaOffset01A3FromLineNumberAsAx_334B_0163_033613(int gotoAddress) {
306 | // 25F86
307 | ushort lineNumber = AX;
308 | this.globalsOnCsSegment0X2538.Set2538_01A3_Word16_VgaOffset((ushort)(lineNumber * 320));
309 | _loggerService.Debug("updateVgaOffset01A3FromLineNumberAsAx lineNumber:{@LineNumber},vgaOffset01A3:{@VgaOffset01A3}", lineNumber, globalsOnCsSegment0X2538.Get2538_01A3_Word16_VgaOffset());
310 | return FarRet();
311 | }
312 |
313 | public Action WaitForRetrace_334B_09B8_033E68(int gotoAddress) {
314 | // no jump, 28 lines in ghidra, part of the function is not executed in the logs and DX is always 3DA.
315 | // Wait for retrace.
316 | Thread.Sleep(15);
317 | State.CarryFlag = true;
318 | return NearRet();
319 | }
320 |
321 | public Action WaitForRetraceDuringIntroVideo_334B_261D_035ACD(int gotoAddress) {
322 | // Calls 0x2538_2572_278F2 when 01A1 is not 0 which we dont need
323 | return NearRet();
324 | }
325 |
326 | public Action WaitForRetraceInTransitions_334B_2572_035A22(int gotoAddress) {
327 | // Calls part of 0x2538_253D_278BD which we dont need
328 | return NearRet();
329 | }
330 |
331 | private Action SetDiFromXYCordsDxBx_334B_0C10_0340C0(int gotoAddress) {
332 | ushort x = DX;
333 | ushort y = BX;
334 | int offset = globalsOnCsSegment0X2538.Get2538_01A3_Word16_VgaOffset();
335 | if (y >= 200) {
336 | y = 199;
337 | }
338 |
339 | ushort res = (ushort)(320 * y + x + offset);
340 | DI = res;
341 | _loggerService.Debug("setDiFromXYCordsDxBx x:{@X},y:{@Y},offset:{@Offset},res:{@Res}", x, y, offset, res);
342 | return NearRet();
343 | }
344 | }
--------------------------------------------------------------------------------