Hacking on retrowin32
April 27, 2025 ยท View on GitHub
retrowin32 has two primary targets: the native app and the web-hosted version.
The native app is for running executables locally. For development it's useful for faster iteration and better profiling tools.
The web app runs in a browser. For development it's useful for the integrated x86 debugger.
Setup
See build setup docs for initial build requirements.
Native
Build the app:
$ cargo build -p retrowin32 --profile=lto
Run it by passing the path of the executable. Arguments after the exe are passed as arguments to the exe.
$ ./target/lto/retrowin32 [flags] path/to/some.exe arg1 arg2
See retrowin32 --help for a list of flags.
Tracing
The --win32-trace flag controls tracing of win32 API calls. It accepts a
comma-separated list of:
*(which must be quoted from the shell) makes retrowin32 trace all win32 calls.- module prefixes like
kernel32/init, which enables tracing for matching modules - modules can be hidden by prefixing with minus, e.g.
-kernel32/init - tracing can log on entry by prefixing the whole string with
^
Some common variations I use are:
--win32-trace '*'
--win32-trace '*,-kernel32/memory'
On todo() panics it's useful to see which function we were in before the
crash:
--win32-trace '^*,-kernel32/memory'
Rosetta
On Apple Silicon (ARM) Macs there is tentative support for running via the Rosetta x86 emulator. See doc/x86-64.md for instructions.
Web
Build/run the web app:
$ cd web
$ make
$ npm run serve # will print a port
Optionally, make profile=lto for a longer compile but faster binary.
Compile-time features
The above commands cover the main things you'd build. The full configuration is
specified via Rust "features" in the -F flag when building.
To choose the x86 emulation strategy, you must disable the default features (by
passing --no-default-features) and then pick one of:
x86-emu: retrowin32's own x86 emulatorx86-64: generate x86-64 code, requires x86 CPU or Rosettax86-unicorn: use Unicorn (effectively QEMU) for x86 emulation (note: this has bitrotted and is broken now)
To choose the rendering strategy, there is one further toggle:
sdl: use sdl2 for graphics- otherwise
- non-web: headless mode, crash on any graphics calls
- web: render to DOM
Web builds require x86-emu and no sdl.
rust-analyzer
Rust's IDE support doesn't know about which features you're using. In VSCode I
configure it via the workspace config in .vscode/settings.json. You can add a
similar block to your user settings to override which emulator backend you're
working on.
Building while developing
There are three build profiles:
lto: slowest build, highest performancedebug: fastest build, lowest performancerelease: compromise between the above two
To iterate while developing I often use a command like this, which builds and runs in one invocation.
$ cargo run -p retrowin32 -- --win32-trace '*' path/to/my/exe
And sometimes I add:
--releasewhen the debug build runs too slowly, and--no-default-features -F x86-emuto build without SDL support
If you make a change to functions exported in the win32/src/winapi/ layer, you
must re-run the code generator as documented in win32/.
Code layout
The entries in this list that are links are links to READMEs with more information.
x86/-- the x86 emulatorwin32/-- the win32 emulatorpe/-- PE (.exe/.dll) parsercli/-- a command-line emulator runnerweb/-- a webapp that runs the emulator in a browserexe/-- some sample Windows executablesmemory/-- a memory abstraction shared byx86andwin32appdb/-- metadata about particular binariesmisc/-- minor helper tools
The website
The retrowin32 website is documented as part of the web README.