Spice86

Spice86

A cross-platform PC emulator for executing, analysing, and incrementally rewriting real-mode DOS programs for which no source code is available.

Linux macOS Windows .NET Build NuGet Apache 2.0 downloads

What is Spice86?

Spice86 is a tool and emulator targeting real-mode (16-bit) DOS programs. Its primary use case is reverse engineering without source code: you run a DOS binary inside Spice86, let it gather runtime data (memory snapshots and execution-flow traces), then use those data to incrementally replace assembly functions with equivalent C# code — all while keeping the program fully runnable at every stage.

It is implemented in C# / .NET 10 and runs on Windows, macOS, and Linux. Releases are published on NuGet; pre-releases are available on the GitHub Releases page.

Spice86 is a continuation of the original Java Spice86 by Kevin Ferrare.

Emulation accuracy

8086 through 80386 real-mode, VGA/EGA/CGA, SoundBlaster, OPL, MT-32, PC Speaker, DOS int 21h (partial), EMS 3.2 / XMS 4.0 / HMA.

CFG CPU

Control-flow graph built at runtime. Tracks every executed path, handles self-modifying code via signature-based variant merging, and lays the foundation for future JIT compilation.

GDB remote protocol

Full GDB server on port 10000. Use vanilla GDB or the SeerGDB GUI to set breakpoints, watch memory, and run custom dump commands.

Incremental rewriting

Replace individual DOS functions one-by-one with C# methods while keeping the rest of the program running in the emulator. The Ghidra plugin auto-generates the C# scaffolding.

Reverse Engineering Approach

Rewriting a binary-only program is a divide-and-conquer task. Spice86 structures that process so every step produces a working, testable program:

  1. Run the program in Spice86

    Verify it runs correctly. Identify unimplemented hardware if it crashes.

  2. Dump runtime data

    On exit (or via monitor dumpall in GDB) Spice86 writes spice86dumpMemoryDump.bin (RAM snapshot) and spice86dumpExecutionFlow.json (function addresses, labels, executed instructions) to a per-executable subdirectory named by SHA-256 hash.

  3. Load into Ghidra via the plugin

    The spice86-ghidra-plugin imports the dumps and decompiles functions as C# code ready for use as overrides.

  4. Scaffold a .NET project

    Use spice86-dotnet-templates (dotnet new spice86.project) and import the generated C# files.

  5. Override functions one at a time

    Implement IOverrideSupplier, register C# methods at segmented addresses via DefineFunction, and run with --UseCodeOverride true. Each replacement is immediately testable.

  6. Iterate

    As more functions are rewritten, the emulated code path shrinks. Ultimately the entire program runs natively in C# with no assembly left.

A concrete, fully worked example is the Cryogenic project (Cryo Dune rewrite).

Getting Started

Prerequisites

Install the .NET 10 SDK. No additional runtime dependencies are required on Windows. On Linux, ALSA is used for audio. On macOS, CoreAudio is used.

Install from NuGet (recommended)

dotnet tool install -g Spice86

Build from source

git clone https://github.com/OpenRakis/Spice86.git
cd Spice86/src
dotnet build

Run a DOS executable

# Run an EXE
Spice86 -e path/to/program.exe

# Run a COM file
Spice86 -e path/to/program.com

# Run a BIOS image
Spice86 -e path/to/bios.bin

# Set the C: drive to the directory containing the game files
Spice86 -e game.exe --CDrive /path/to/game/

# Pass arguments to the DOS program
Spice86 -e game.exe --ExeArgs "arg1 arg2"

Dump execution data for reverse engineering

# Dumps go to a subdirectory named with the program's SHA-256 hash
Spice86 -e game.exe --RecordedDataDirectory /path/to/dumps/

# Or set the environment variable
export SPICE86_DUMPS_FOLDER=/path/to/dumps
Spice86 -e game.exe

CLI Options

Core options

OptionDefaultDescription
-e, --ExerequiredPath to the executable (EXE, COM, or BIN)
-a, --ExeArgsArguments passed to the emulated program (max 127 chars)
-c, --CDriveexe parent folderPath to the emulated C: drive
-r, --RecordedDataDirectorySPICE86_DUMPS_FOLDER or cwdDirectory for memory and execution-flow dumps
-x, --ExpectedChecksumHex SHA-256 of the expected executable; fails if mismatch
-p, --ProgramEntryPointSegment0x170Segment where the program is loaded
-v, --InitializeDOSauto (guessed at runtime)Install DOS interrupt vectors

Performance

OptionDefaultDescription
--CyclesTarget CPU cycles per ms (unset = no limit). Overrides --InstructionsPerSecond when set.
-i, --InstructionsPerSecondInstruction-based timer; blank = real-time timer
-t, --TimeMultiplier1Real-time speed multiplier (>1 faster, <1 slower)

Debugging

OptionDefaultDescription
--DebugfalsePause at startup (and on stop) to allow attaching a debugger
-g, --GdbPort10000GDB remote port; 0 disables the GDB server
--CpuHeavyLogfalseLog every executed instruction to a file (significant perf impact)
--CpuHeavyLogDumpFile{DumpDirectory}/cpu_heavy.logOverride the CPU log file path
--AsmRenderingStyleSpice86Assembly syntax: Spice86 or DosBox
--StructureFileC header file describing memory structures (IDA/Ghidra export)
-f, --FailOnUnhandledPortfalseFail on unimplemented I/O port access

Overrides

OptionDefaultDescription
-o, --OverrideSupplierClassNameClass name implementing IOverrideSupplier
-u, --UseCodeOverridetrueExecute C# override code; false uses names only

Memory

OptionDefaultDescription
--XmstrueEnable 15 MB XMS (extended memory)
--EmstrueEnable EMS memory (8 MB via EMM Page Frame)
--A20GatefalseDisable A20 line for programs relying on HMA wrap-around

Display & UI

OptionDefaultDescription
-h, --HeadlessModefalseMinimal (no UI) or Avalonia (full UI, more memory)

Sound

OptionDefaultDescription
--SbTypeSBPro2None | SB1 | SB2 | SBPro1 | SBPro2 | Sb16 | GameBlaster
--SbIrq7Sound Blaster IRQ (common: 5, 7, 9, 10)
--SbDma18-bit DMA channel (common: 0, 1, 3)
--SbHdma516-bit DMA channel (common: 5, 6, 7)
--SbBase0x220Base I/O address (hex; common: 0x220, 0x240)
--OplModeOpl3None | Opl2 | DualOpl2 | Opl3 | Opl3Gold
--SbMixertrueEnable Sound Blaster mixer control of OPL voices
-m, --Mt32RomsPathZIP or directory containing MT-32 ROM files

Logging

OptionDescription
-l, --VerboseLogsEnable verbose log level
-w, --WarningLogsEnable warning log level
-s, --SilencedLogsDisable all logs

GDB Integration

Spice86 speaks the GDB remote serial protocol. The GDB server starts automatically on port 10000 (disabled with --GdbPort 0). Use --Debug to pause execution at startup.

Connecting

(gdb) target remote localhost:10000
(gdb) set architecture i8086

GDB uses physical (linear) addresses. Segmented address A000:0000 maps to physical 0xA0000. The $pc variable reflects the physical address of CS:IP.

Common GDB commands

CommandDescription
watch *0xA0000Break on VRAM write
layout asmAssembly view
find /b 0x0, 0xF0000, 0x53, ...Search memory for byte sequence
x/16xb 0x1000Examine 16 bytes at physical address

Custom monitor commands

CommandDescription
monitor helpList all custom commands
monitor dumpallDump memory snapshot + execution-flow JSON to the dump folder
monitor breakCycles 1000Break after 1000 emulated CPU cycles
monitor breakStopBreak when the emulated program exits

SeerGDB

The SeerGDB GUI client works well with Spice86. A pre-configured project file is provided at doc/spice86.seer. Launch with:

seergdb --project doc/spice86.seer

In Seer, set Settings → Configuration → Assembly → Disassembly Mode to Length for the assembly view to work correctly.

Built-in Debugger

Spice86 includes a UI debugger (documented on the wiki) with the following views:

CPU / Registers

Live view of all CPU registers and flags.

Disassembly

Real-time disassembly of the emulated code at CS:IP.

Memory

Hex view of the full 1 MB address space, with byte-range selection and right-click context for the structure viewer.

Structure Viewer

Supply a C header file (--StructureFile) to view memory as typed structures (DOS PSP, VGA registers, custom game structs). Updates live on pause. Export from IDA or Ghidra to reload structures without restarting.

Stack

Current stack frame with segmented addresses.

Code Override System

The override system lets you replace individual DOS assembly functions with C# methods. The emulator calls the C# method when execution reaches the registered address, and then returns to the emulated program using the appropriate return instruction.

Implementing an override supplier

// Register one C# implementation per assembly function
public class MyProgramOverrideSupplier : IOverrideSupplier {
    public IDictionary<SegmentedAddress, FunctionInformation> GenerateFunctionInformations(
        ILoggerService loggerService, Configuration configuration,
        ushort programStartSegment, Machine machine) {
        Dictionary<SegmentedAddress, FunctionInformation> res = new();
        new MyOverrides(res, machine, loggerService, configuration);
        return res;
    }
}

Implementing overrides

public class MyOverrides : CSharpOverrideHelper {
    private GlobalsOnDs _globals;

    public MyOverrides(
        IDictionary<SegmentedAddress, FunctionInformation> functionInformations,
        Machine machine, ILoggerService loggerService, Configuration configuration)
        : base(functionInformations, machine, loggerService, configuration) {
        _globals = new GlobalsOnDs(machine.Memory, machine.CfgCpu.State.SegmentRegisters);
        DefineFunction(0x1ED, 0xA1E8, IncDialogueCount47A8);
        DefineFunction(0x1ED, 0x0100, AddOneToAX);
    }

    private Action IncDialogueCount47A8(int loadOffset) {
        _globals.SetDialogueCount47A8(_globals.GetDialogueCount47A8() + 1);
        return NearRet();
    }

    private Action AddOneToAX(int loadOffset) {
        State.AX++;
        return FarRet();
    }
}

Memory-mapped data structures

public class GlobalsOnDs : MemoryBasedDataStructureWithDsBaseAddress {
    public GlobalsOnDs(IByteReaderWriter memory, SegmentRegisters regs)
        : base(memory, regs) { }

    public int GetDialogueCount47A8() => UInt16[0x47A8];
    public void SetDialogueCount47A8(int value) => UInt16[0x47A8] = (ushort)value;
}
Remember: Pass --UseCodeOverride true and --OverrideSupplierClassName YourNamespace.MyProgramOverrideSupplier when running with overrides active.

Control-flow return types

MethodEquivalent
NearRet()RETN
FarRet()RETF
NearCall(expectedReturnCs, expectedReturnIp, Func<int, Action> function)Invoke function as a near call and return
FarCall(expectedReturnCs, expectedReturnIp, Func<int, Action> function)Invoke function as a far call and return
FarJump(seg, off)Unconditional far jump
Hlt()HLT

Hardware Support

CPU

CPU FamilyStatusNotes
8086 / 8088FullFully implemented and tested
80186PartialBOUND instruction not implemented
80286PartialProtected mode not implemented
80386PartialPartial support; protected mode not implemented
16-bit instructionsFullComplete
32-bit instructionsPartialMost implemented, not fully tested
FPUNoneDetection instructions only

Memory

FeatureStatus
1 MB real-mode address space with segmented addressingFull
A20 GateFull
EMS 3.2 (8 MB via EMM Page Frame)Full
XMS 4.0 (15 MB extended memory)Full
HMA (High Memory Area)Full
PagingNone

Graphics

FeatureStatus
Text modesFull
VGAFull
EGAPartialBest effort; some bugs possible
CGAPartialBest effort; some bugs possible
VESA (SVGA)None
Screen refreshFull30 FPS, VGA retrace detection

Input / Peripherals

FeatureStatus
KeyboardFull
MouseFull
JoystickNone
CD-ROM (MSCDEX)None
Floppy diskNone

DOS (Int 21h)

Partial DOS 5.0 interrupt implementation. Covers the subset of calls used by most real-mode games.

Sound Support

Audio playback uses the native platform audio subsystem: WASAPI on Windows, ALSA on Linux, and CoreAudio on macOS. No external audio libraries are required.

Sound TypeStatusNotes
PC SpeakerFull
Adlib / OPL2 / OPL3FullPorted from DOSBox Staging
Sound Blaster (all variants)FullPorted from DOSBox Staging; PCM + OPL accuracy improvements
Adlib GoldFullPorted from DOSBox Staging
General MIDIFull
MT-32PartialSupported on Windows and Linux; not available on macOS
Gravis UltrasoundNoneNot implemented

Game Compatibility

This list reflects the state as of early 2026. A program crashing usually indicates an unimplemented BIOS/DOS interrupt or a video mode that has not been implemented.

ProgramStatusNotes
Alpha Waves✓ Fully playable
Alley Cat✗ CrashesCGA not implemented
Alone in the Dark✗ CrashesComplains about 386 CPU
Another World✓ Fully playable
Betrayal at Krondor✓ Fully playable
Bio Menace✓ Fully playable
Blake Stone: Aliens of Gold✓ Fully playable
Cadaver✓ Fully playable
Cruise for a Corpse✓ Fully playable
Cryo Dune✓ Fully playable
Day of the Tentacle✓ Fully playableUse -s (silent logs) to avoid delays from Adlib timing
Doom8088✓ Fully playable
Dune 2✓ Fully playable
Duke Nukem✓ Fully playableNo PC Speaker sound effects
Duke Nukem II✓ Fully playable
Enviro-Kids✓ Fully playable
Hero Quest✓ Fully playable
Indiana Jones and The Fate of Atlantis✓ Fully playable
Jill of the Jungle✓ Fully playable
KGB✓ Fully playable
Knights of Xentar✓ Fully playable
Lands of Lore (floppy)✓ Fully playableUse --SbType SB2 to avoid crash
Legends of Kyrandia✓ Fully playable
Lost Eden✓ Fully playable
Monkey Island✓ Fully playable
Monkey Island 2✓ Fully playable
Oh No! More Lemmings✓ Fully playable
Planet X3✓ Fully playable
Populous✓ Fully playableNo music or sound
Prince of Persia✓ Fully playableNo OPL music
Prince of Persia 2✓ Fully playable
Rules of Engagement 2✓ Fully playable
Stunts✓ Fully playable
Ultima V✓ Fully playable
Where in Space is Carmen Sandiego✓ Fully playable
Wizardry 7✓ Fully playable
Plan 9 From Outer Space~ Not playableBlack screen
Space Quest I~ Not playableProgram exits with code 1
Space Quest IV~ Not playableProgram exits with code 1
Ultima IV~ Not playableBlack screen
Double Dragon 3✗ CrashesInt 10.3 (text mode) not implemented
Dragon's Lair✗ CrashesExits without error
F-15 Strike Eagle II✗ CrashesInvalid opcode
Flight Simulator 5✗ CrashesInvalid opcode
SimCity✗ CrashesInt 10.11 ROM pointer not implemented

A full and up-to-date list is maintained in COMPATIBILITY.md.

CFG CPU Architecture

Spice86 uses a Control Flow Graph CPU (CfgCpu) rather than a traditional interpreter loop. The graph grows at runtime as the program executes, recording every path taken and every instruction variant observed. Detailed documentation is in doc/cfgcpuReadme.md.

Runtime graph construction

Instructions are parsed into typed CfgInstruction nodes as they execute. Successor edges are added to build the complete CFG over time.

Self-modifying code

Memory-write breakpoints detect code changes instantly. Non-final byte changes (e.g., an immediate operand) are merged into the existing node via wildcard fields. Structural changes (e.g., opcode bytes) produce a SelectorNode that dispatches to the matching variant at runtime.

Two-tier instruction cache

CurrentInstructions holds the authoritative live instruction per address. PreviousInstructions retains all previously seen variants; if code is restored, the original node (with its graph edges intact) is reused.

Interrupt isolation

Each hardware interrupt creates a new ExecutionContext. CFG edges are never threaded across context boundaries, keeping interrupt handler graphs independent.

Variant merging

When two instruction variants share the same final signature (same opcodes, different operands), they are merged into one node with operand fields marked "read from memory at runtime", avoiding SelectorNode proliferation.

JIT-ready

The CFG is designed so that hot blocks can be compiled ahead of time in future versions, as all path information and variant data are already present in the graph.

Screenshots

Cryo Dune – Duke Atreides
Cryo Dune – Duke Atreides
Cryo Dune – Sandworm
Cryo Dune – Sandworm
Cryo Dune – Ornithopter
Cryo Dune – Ornithopter
Prince of Persia
Prince of Persia
Stunts – Main menu
Stunts – Main menu
Stunts – Loop
Stunts – Loop
Betrayal at Krondor
Betrayal at Krondor

Credits

Core contributors

Spice86 is developed by OpenRakis. It is a port and continuation of the original Java Spice86 by Kevin Ferrare.

Third-party components

ComponentSourceNotes
SoundBlaster / OPL / Adlib Gold emulation DOSBox Staging Fully ported to C#. Includes PCM/OPL accuracy improvements and complete audio re-architecture.
UI framework Avalonia UI Cross-platform MVVM framework
IDE support JetBrains Rider Supported via JetBrains Open Source Community Support

Related projects

ProjectDescription
spice86-ghidra-plugin Ghidra plugin that imports Spice86 dumps and generates C# override scaffolding
spice86-dotnet-templates dotnet new spice86.project — project template for override-based rewrites
Cryogenic Ongoing rewrite of Cryo Dune using Spice86; reference implementation of the override system

License

Spice86 is released under the Apache 2.0 license. See NOTICE for third-party attribution.