Spice86 turns a DOS binary into a runnable C# project, one function at a time
Reverse engineer and rewrite real mode DOS programs!
At a glance
- What is it?
- Spice86 is a .NET emulator for real mode DOS programs that builds a control flow graph while the program runs and then generates a self-contained C# project overriding the original assembly. It is a port and continuation of the original Java Spice86, it runs on Windows, macOS and Linux, and it needs .NET 10.
- Who is it for?
- Adopt Spice86 if you have a real mode DOS binary with no source that you need to understand, port or keep alive, and you are willing to work function by function against generated C#. Do not adopt it for protected mode or 32 bit Windows programs, for protected software, or as a general purpose PC emulator, since the scope here is real mode DOS and the tooling assumes you are rewriting what you observe.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The output is a runnable C# project, not a disassembly listing
The stated purpose of Spice86 is to execute, reverse engineer and rewrite real mode DOS programs for which source code is not available. What makes it different from a disassembler is the artifact it produces. The README is explicit: from the control flow graph, Spice86 directly generates a self-contained, runnable C# project that overrides the original assembly with equivalent C# code.
The general process is a divide and conquer loop. You start by emulating the program in the Spice86 emulator, and as the program runs, Spice86 builds a control flow graph of the executed code. At the end of each run, the emulator dumps runtime data, specifically a memory dump, the execution flow and the CFG. From that CFG you get the generated C# project, and then you gradually rewrite the generated C# functions, replacing the mechanical translation with readable, intentful code, a task the README notes an LLM can help with.
The reasoning behind that order is worth repeating because it explains the design. Small sequences of assembly can be statically analyzed and are generally easy to translate to a higher level language. You work all the time with a fully working version of the program, so it is relatively easy to catch mistakes early. And rewriting code function by function allows you to discover the intent of the author.
That last point is the real deliverable. A disassembly tells you what the code does. A rewritten C# function tells you why.
The project is a port and a continuation from the original Java Spice86, and it requires .NET 10 while running on Windows, macOS and Linux. Releases are on NuGet as the Spice86 package, and pre-releases are published on the repository's release page under the latest tag.
Running a DOS executable is a single -e argument
The command line surface for just running a program is very small. The README gives a table of four invocations, and the pattern in all of them is the same:
The invocation is Spice86 -e file.exe for an executable.
The -e flag takes the file to run, and the table covers the four kinds of input: a .exe executable, a .com COM file, a .bat DOS batch file and a .bin BIOS file. So the emulator handles the DOS program formats directly rather than requiring a disk image or a configuration of a virtual machine.
The rest of the options are documented in a longer command line options list whose visible entries cover only the first few options. What is visible is still informative. The --Debug flag, default false, starts the program paused and pauses once again when stopping, which is the behaviour you want when you are stepping through a program rather than watching it run. The --Cycles option sets a target CPU cycles per millisecond, defaulting to 3000, and is described as being for the rare speed sensitive game, which tells you the emulator is not aiming for wall clock accuracy.
The --Xms option, default true, enables 15 MB of XMS memory, and the --Ems entry is cut off mid-list. Those defaults are the DOS memory model rather than a minimal machine, which is a sensible choice for compatibility with period software that allocates what it thinks is available.
There is a debug and analysis vocabulary in the option list that points at the rest of the tool. The topics list names GDB commands among the labels, and the repository carries a .github/ directory with workflows for the pre-release build.
Dumps are namespaced by SHA-256 so one game does not overwrite another
The dump directory is configurable in two ways, and the behaviour once you have chosen one is worth knowing. The environment variable SPICE86_DUMPS_FOLDER controls the base directory where data is dumped, and the --RecordedDataDirectory command line option specifies the base dump location. If neither is given, the current directory is used.
The important detail is what happens next. Regardless of the base directory setting, dumps are always placed in a subdirectory named with the program's SHA-256 hash. The README explains why: this ensures that multiple executables from the same game, with examples like SETUP.EXE and GAME.EXE, have isolated dump folders.
The list of files produced is fairly long, and each one serves a different stage of the workflow. The memory dump, spice86dumpMemoryDump.bin, is a copy of the real mode address space. The execution flow file, spice86dumpExecutionFlow.json, contains function addresses, labels and executed instructions. The Ghidra symbols file, spice86dumpGhidraSymbols.txt, holds Ghidra compatible symbols for function and label addresses, for optional import into Ghidra. Then there are the CFG files: spice86dumpCfgBlocks.json with the explored CFG as basic blocks with execution context metadata, and spice86dumpCfgPartitions.json with the recovered function and helper partitions and inter partition transfers, derived from the block graph. The generated overrides land in spice86dumpCfgGeneratedOverrides.cs, and there is a GeneratedProject/ directory holding the self-contained runnable override project.
Two behaviours around these files are easy to trip over. When there is already data in the specified location, the emulator loads it first and complements it, so a second run builds on the first rather than starting over. And the CFG instruction graph is dumped separately as spice86dumpCfgReload.json, and by default reloaded from the recorded data directory at startup so previously explored program structure is preserved across runs. You disable that with --ReloadCfgGraph false, and it is a no-op if the file is absent.
Code overrides turn the emulator into a loader for your own rewritten code
The most interesting engineering in Spice86 is the code override mechanism, and it changes the relationship between the tool and the program. When code overrides are active, which the README describes as --UseCodeOverride true with an --OverrideSupplierClassName, the CFG and execution flow dumps are not written at all. The reason given is precise: overrides replace the emulated asm, so the recorded graph would jump between overrides and no longer reflect the program.
That is the right call and it explains the architecture. Once you start substituting your own C# for the original assembly, the control flow graph describes a program that no longer exists, and keeping it would be misleading. The dumps are a by-product of observation, and observation stops when you start editing.
The override mechanism also means the tool has two modes with different outputs. In exploration mode you get the CFG, the partitions, the memory dump and the generated C# project, and you are building a picture of the program. In override mode the emulated code is being replaced by yours, so what matters is that the program still behaves, and the tool gets out of the way.
The Ghidra path is described as optional and explicitly no longer required to produce C# overrides, just an extra analysis aid. The dumps of Ghidra compatible symbols feed the spice86-ghidra-plugin, and the README frames loading the memory dump into Ghidra as being for deeper static analysis. So Ghidra is a companion for the hard cases rather than a prerequisite for the workflow.
CPU heavy logging records every instruction, at a price
For debugging, the emulator can log every executed instruction to a file. The README warns that this feature has a significant performance impact and should only be used for debugging purposes, and the option table gives the details.
The --CpuHeavyLog flag enables it and defaults to false. The --CpuHeavyLogDumpFile option sets a custom path for the log file, and if it is set it also enables CpuHeavyLog. The default location is {DumpDirectory}/cpu_heavy.log. The third option, --CpuHeavyLogExpressions, takes named expressions that are appended to every log line, and the option can be repeated, for example once for a register expression and once for a memory read. The expressions are only active when --CpuHeavyLog is true.
The log format is a segmented address followed by the instruction string, and the README gives three example lines showing a register move, a near call and another register move, each prefixed with a segment and offset.
The usage block shows how the options combine, with a default location case, a custom log file path case, and a case where two named expressions are passed after a line continuation:
# Enable with default location
Spice86 -e program.exe --CpuHeavyLog
# Use custom log file path
Spice86 -e program.exe --CpuHeavyLog --CpuHeavyLogDumpFile "C:\logs\cpu.log"Being able to name a memory location or a register and have it evaluated on every instruction is the feature that turns the log from a disassembly trace into something you can read as state.
A cross-platform .NET codebase with a compatibility ledger
The repository layout tells you the scale of the work. Alongside src/ and tests/ there is a 3rdparty/ directory, a doc/ and a docs/ directory, and a COMPATIBILITY.md file that presumably tracks which DOS programs have been verified. There is an AGENTS.md and a CODE_STYLE.md, a CODE_OF_CONDUCT.md and a CONTRIBUTING.md, a Directory.Packages.props for central package version management, a NuGet.Config, and a NOTICE file alongside the Apache 2.0 LICENSE.
There are also two shell scripts at the root, run.sh and clean.sh, plus regen-asm-bins.sh, which suggests that regenerating the assembly binary blobs is part of the workflow, and an .editorconfig for formatting. The avalonia.png and jetbrainsrider.svg images are documentation assets rather than anything functional.
The primary language is C#, the licence is Apache 2.0 as recorded in the repository and the badge, and the packages are distributed through NuGet under the name Spice86.
On maintenance, the picture is thin in one respect and healthy in another. The release list contains a single entry, a latest Development Build dated 2026-10-13, which is a rolling pre-release rather than a numbered version. The last push was on 2026-10-16 and the repository is not archived. So the project is being worked on, and it ships continuously under a moving tag, but there is no numbered release history to diff against, which means pinning a specific build for a long lived project requires choosing a NuGet version rather than a tag.
Editorial conclusion
Adopt Spice86 if you have a real mode DOS binary with no source that you need to understand, port or keep alive, and you are willing to work function by function against generated C#. Do not adopt it for protected mode or 32 bit Windows programs, for protected software, or as a general purpose PC emulator, since the scope here is real mode DOS and the tooling assumes you are rewriting what you observe. Verify first that you have .NET 10 available, that the target is a real mode DOS executable you are permitted to analyse, and that the generated project builds on your machine before you invest in rewriting functions.
Frequently asked questions
How do I run a DOS program in Spice86?
Use the -e option with the file to run, for example Spice86 -e file.exe. The README lists the supported inputs as a .exe executable, a .com COM file, a .bat DOS batch file and a .bin BIOS file.
What does Spice86 generate from a DOS binary?
It generates a self-contained, runnable C# project that overrides the original assembly with equivalent C# code, derived from the control flow graph it builds while the program runs. The generated overrides are also written to spice86dumpCfgGeneratedOverrides.cs.
Where does Spice86 write its dump files?
The base directory comes from the SPICE86_DUMPS_FOLDER environment variable or the --RecordedDataDirectory option, and the current directory is used if neither is set. Dumps always go into a subdirectory named with the program's SHA-256 hash so that several executables from one game do not share a folder.
What requirements does Spice86 have?
It requires .NET 10 and runs on Windows, macOS and Linux. The project is a port and continuation of the original Java Spice86, with stable releases on NuGet and pre-releases published on the release page under the latest tag.
How do I log every executed instruction in Spice86?
Enable it with --CpuHeavyLog, optionally set a path with --CpuHeavyLogDumpFile, and add named expressions with the repeatable --CpuHeavyLogExpressions option. The README warns that the feature has a significant performance impact and is for debugging only.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/openrakis-spice86)Was this useful?