├── web
├── local
├── glue
│ ├── .gitignore
│ ├── src
│ │ ├── lib.rs
│ │ └── log.rs
│ ├── Cargo.toml
│ └── build.sh
├── .gitignore
├── archive
├── wasm.wasm
├── 9p-active.png
├── 9p-inactive.png
├── main.ts
├── debugger
│ ├── util.ts
│ ├── cpus.tsx
│ ├── tabs.tsx
│ ├── stack.tsx
│ ├── mappings.tsx
│ ├── code.tsx
│ ├── ddraw.tsx
│ ├── labels.ts
│ └── trace.tsx
├── Makefile
├── package.json
├── run.html
├── win2k.css
├── README.md
├── debugger.html
├── index.tmpl
└── web.tsx
├── .vscode
├── .gitignore
└── settings.json
├── win32
├── extract
│ ├── .gitignore
│ ├── Cargo.toml
│ └── README.md
├── dll
│ ├── oleaut32
│ │ ├── src
│ │ │ ├── lib.rs
│ │ │ └── builtin.rs
│ │ ├── oleaut32.dll
│ │ └── Cargo.toml
│ ├── bass
│ │ ├── bass.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── lib.rs
│ ├── ddraw
│ │ ├── ddraw.dll
│ │ ├── src
│ │ │ ├── lib.rs
│ │ │ ├── clipper.rs
│ │ │ ├── palette.rs
│ │ │ └── ddraw3.rs
│ │ └── Cargo.toml
│ ├── gdi32
│ │ ├── gdi32.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ ├── lib.rs
│ │ │ ├── state.rs
│ │ │ └── palette.rs
│ ├── ntdll
│ │ ├── ntdll.dll
│ │ ├── src
│ │ │ ├── lib.rs
│ │ │ └── misc.rs
│ │ └── Cargo.toml
│ ├── ole32
│ │ ├── ole32.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── lib.rs
│ ├── winmm
│ │ ├── winmm.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ ├── misc.rs
│ │ │ ├── joy.rs
│ │ │ ├── mci.rs
│ │ │ ├── lib.rs
│ │ │ ├── mixer.rs
│ │ │ └── midi.rs
│ ├── dinput
│ │ ├── dinput.dll
│ │ ├── src
│ │ │ └── lib.rs
│ │ └── Cargo.toml
│ ├── dsound
│ │ ├── dsound.dll
│ │ └── Cargo.toml
│ ├── user32
│ │ ├── user32.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ ├── lib.rs
│ │ │ ├── keyboard.rs
│ │ │ ├── state.rs
│ │ │ ├── rect.rs
│ │ │ └── printf.rs
│ ├── shlwapi
│ │ ├── shlwapi.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── builtin.rs
│ ├── version
│ │ ├── version.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── lib.rs
│ ├── wininet
│ │ ├── wininet.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── builtin.rs
│ ├── advapi32
│ │ ├── advapi32.dll
│ │ └── Cargo.toml
│ ├── comctl32
│ │ ├── comctl32.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── lib.rs
│ ├── kernel32
│ │ ├── kernel32.dll
│ │ ├── src
│ │ │ ├── sync
│ │ │ │ ├── mod.rs
│ │ │ │ ├── interlocked.rs
│ │ │ │ ├── mutex.rs
│ │ │ │ ├── once.rs
│ │ │ │ ├── srw_lock.rs
│ │ │ │ ├── critical_section.rs
│ │ │ │ └── event.rs
│ │ │ ├── file
│ │ │ │ ├── mapping.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── stdio.rs
│ │ │ │ ├── misc.rs
│ │ │ │ └── file16.rs
│ │ │ ├── pipe.rs
│ │ │ ├── lib.rs
│ │ │ ├── process.rs
│ │ │ └── resource.rs
│ │ └── Cargo.toml
│ ├── ucrtbase
│ │ ├── ucrtbase.dll
│ │ ├── src
│ │ │ ├── lib.rs
│ │ │ ├── rand.rs
│ │ │ ├── time.rs
│ │ │ ├── math.rs
│ │ │ ├── memory.rs
│ │ │ └── misc.rs
│ │ └── Cargo.toml
│ ├── vcruntime140
│ │ ├── vcruntime140.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── lib.rs
│ ├── retrowin32_test
│ │ ├── retrowin32_test.dll
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── builtin.rs
│ ├── .gitignore
│ └── README.md
├── lib
│ ├── retrowin32.def
│ ├── retrowin32.lib
│ ├── retrowin32_test.def
│ ├── retrowin32_test.lib
│ ├── build.sh
│ └── README.md
├── winapi
│ ├── README.md
│ ├── Cargo.toml
│ └── src
│ │ ├── lib.rs
│ │ ├── com.rs
│ │ ├── types.rs
│ │ ├── point.rs
│ │ ├── rect.rs
│ │ └── error.rs
├── system
│ ├── src
│ │ ├── lib.rs
│ │ ├── wait.rs
│ │ ├── event.rs
│ │ ├── dll.rs
│ │ └── resource.rs
│ ├── README.md
│ └── Cargo.toml
├── derive
│ ├── Cargo.toml
│ └── README.md
├── src
│ ├── lib.rs
│ └── shims.rs
└── Cargo.toml
├── exe
├── .gitignore
├── cpp
│ ├── dib.exe
│ ├── gdi.exe
│ ├── ddraw.exe
│ ├── errors.exe
│ ├── thread.exe
│ ├── cmdline.exe
│ ├── metrics.exe
│ ├── README.md
│ ├── .clangd
│ ├── metrics.cc
│ ├── cmdline.cc
│ ├── errors.cc
│ ├── util.h
│ └── thread.cc
├── ops
│ ├── ops.exe
│ ├── run.sh
│ ├── ops.cc
│ ├── util.h
│ ├── .clangd
│ ├── out.txt
│ └── util.cc
├── trace
│ ├── trace.exe
│ └── build.sh
├── env.bat
├── winapi
│ ├── winapi.exe
│ ├── winapi.cc
│ └── build.sh
├── zig_hello
│ ├── hello.exe
│ ├── build.sh
│ └── hello.zig
└── callback
│ ├── callback.exe
│ ├── build.sh
│ └── callback.zig
├── misc
├── pre-commit.sh
├── lldb-dump-fn.sh
├── print-asm.sh
├── quiet.reg
├── fmt.sh
├── ai.sh
├── wasm-size.sh
├── dump-fn.sh
└── lldb-trace.py
├── doc
├── qemu.png
├── wine.png
├── native.png
├── boxedwine.png
├── retrowin32.png
├── build_setup.md
├── how_to.md
├── performance.md
└── comparison.md
├── appdb
├── go.mod
├── go.sum
├── entries
│ ├── minesweeper.toml
│ ├── demo
│ │ ├── gleam.toml
│ │ ├── mofo.toml
│ │ ├── win4k.toml
│ │ ├── magnus.toml.notes
│ │ ├── effect.toml
│ │ ├── jkf.toml
│ │ ├── metazlo.toml
│ │ ├── kill_the_clone.toml
│ │ ├── anatyda.toml
│ │ ├── chillin.toml
│ │ ├── followme.toml
│ │ ├── monolife.toml
│ │ └── stream.toml
│ ├── solitaire.toml
│ ├── retrowin32
│ │ ├── zig_hello.toml
│ │ ├── callback.toml
│ │ ├── gdi.toml
│ │ ├── exit.toml
│ │ ├── zip.toml
│ │ └── invalid_addr.toml
│ └── BasicDD.toml
├── run.sh
└── README.md
├── .cargo
└── config.toml
├── cli
├── sdl-manual.sh
├── sdl-brew.sh
├── src
│ ├── time.rs
│ ├── logging.rs
│ ├── headless.rs
│ └── host.rs
├── Cargo.toml
├── build-linux-64.sh
└── build-rosetta.sh
├── minibuild
└── Cargo.toml
├── memory
├── Cargo.toml
└── src
│ ├── rawmem.rs
│ ├── lib.rs
│ ├── boxmem.rs
│ └── pod.rs
├── x86
├── src
│ ├── lib.rs
│ └── ops
│ │ ├── math
│ │ ├── mod.rs
│ │ ├── int.rs
│ │ └── div.rs
│ │ ├── mod.rs
│ │ ├── cpuid.rs
│ │ └── bits.rs
└── Cargo.toml
├── .gitignore
├── pe
├── README.md
├── Cargo.toml
└── src
│ ├── lib.rs
│ ├── exports.rs
│ ├── parse.rs
│ └── relocations.rs
├── deploy.sh
├── dprint.json
├── .github
└── workflows
│ └── ops.yml
└── Cargo.toml
/web/local:
--------------------------------------------------------------------------------
1 | ../
--------------------------------------------------------------------------------
/web/glue/.gitignore:
--------------------------------------------------------------------------------
1 | /pkg
2 |
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/web/archive:
--------------------------------------------------------------------------------
1 | ../deploy/archive/
--------------------------------------------------------------------------------
/.vscode/.gitignore:
--------------------------------------------------------------------------------
1 | /launch.json
2 |
--------------------------------------------------------------------------------
/web/wasm.wasm:
--------------------------------------------------------------------------------
1 | glue/pkg/glue_bg.wasm
--------------------------------------------------------------------------------
/win32/extract/.gitignore:
--------------------------------------------------------------------------------
1 | *.winmd
2 |
--------------------------------------------------------------------------------
/exe/.gitignore:
--------------------------------------------------------------------------------
1 | *.obj
2 | *.pdb
3 |
4 | zig-cache
5 |
--------------------------------------------------------------------------------
/misc/pre-commit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | exec ./misc/fmt.sh --check
4 |
--------------------------------------------------------------------------------
/doc/qemu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/doc/qemu.png
--------------------------------------------------------------------------------
/doc/wine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/doc/wine.png
--------------------------------------------------------------------------------
/win32/dll/oleaut32/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod builtin;
2 |
3 | pub use builtin::DLL;
4 |
--------------------------------------------------------------------------------
/doc/native.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/doc/native.png
--------------------------------------------------------------------------------
/exe/cpp/dib.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/cpp/dib.exe
--------------------------------------------------------------------------------
/exe/cpp/gdi.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/cpp/gdi.exe
--------------------------------------------------------------------------------
/exe/ops/ops.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/ops/ops.exe
--------------------------------------------------------------------------------
/web/glue/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod debugger;
2 | mod emulator;
3 | mod host;
4 | mod log;
5 |
--------------------------------------------------------------------------------
/doc/boxedwine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/doc/boxedwine.png
--------------------------------------------------------------------------------
/doc/retrowin32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/doc/retrowin32.png
--------------------------------------------------------------------------------
/exe/cpp/ddraw.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/cpp/ddraw.exe
--------------------------------------------------------------------------------
/exe/cpp/errors.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/cpp/errors.exe
--------------------------------------------------------------------------------
/exe/cpp/thread.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/cpp/thread.exe
--------------------------------------------------------------------------------
/web/9p-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/web/9p-active.png
--------------------------------------------------------------------------------
/win32/lib/retrowin32.def:
--------------------------------------------------------------------------------
1 | LIBRARY retrowin32
2 | EXPORTS
3 | retrowin32_syscall@0
4 |
--------------------------------------------------------------------------------
/exe/cpp/cmdline.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/cpp/cmdline.exe
--------------------------------------------------------------------------------
/exe/cpp/metrics.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/cpp/metrics.exe
--------------------------------------------------------------------------------
/exe/trace/trace.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/trace/trace.exe
--------------------------------------------------------------------------------
/web/9p-inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/web/9p-inactive.png
--------------------------------------------------------------------------------
/exe/env.bat:
--------------------------------------------------------------------------------
1 | "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\vsdevcmd.bat"
--------------------------------------------------------------------------------
/exe/winapi/winapi.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/winapi/winapi.exe
--------------------------------------------------------------------------------
/exe/zig_hello/hello.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/zig_hello/hello.exe
--------------------------------------------------------------------------------
/win32/dll/bass/bass.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/bass/bass.dll
--------------------------------------------------------------------------------
/win32/winapi/README.md:
--------------------------------------------------------------------------------
1 | Lowest-level types necessary for modules to implement Windows APIs.
2 |
--------------------------------------------------------------------------------
/exe/callback/callback.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/exe/callback/callback.exe
--------------------------------------------------------------------------------
/win32/dll/ddraw/ddraw.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/ddraw/ddraw.dll
--------------------------------------------------------------------------------
/win32/dll/gdi32/gdi32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/gdi32/gdi32.dll
--------------------------------------------------------------------------------
/win32/dll/ntdll/ntdll.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/ntdll/ntdll.dll
--------------------------------------------------------------------------------
/win32/dll/ole32/ole32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/ole32/ole32.dll
--------------------------------------------------------------------------------
/win32/dll/winmm/winmm.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/winmm/winmm.dll
--------------------------------------------------------------------------------
/win32/lib/retrowin32.lib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/lib/retrowin32.lib
--------------------------------------------------------------------------------
/win32/lib/retrowin32_test.def:
--------------------------------------------------------------------------------
1 | LIBRARY retrowin32_test
2 | EXPORTS
3 | retrowin32_test_callback1@8
4 |
--------------------------------------------------------------------------------
/win32/dll/dinput/dinput.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/dinput/dinput.dll
--------------------------------------------------------------------------------
/win32/dll/dsound/dsound.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/dsound/dsound.dll
--------------------------------------------------------------------------------
/win32/dll/user32/user32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/user32/user32.dll
--------------------------------------------------------------------------------
/exe/cpp/README.md:
--------------------------------------------------------------------------------
1 | Test programs to run under retrowin32.
2 |
3 | Build these by running `cargo minibuild`.
4 |
--------------------------------------------------------------------------------
/win32/dll/shlwapi/shlwapi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/shlwapi/shlwapi.dll
--------------------------------------------------------------------------------
/win32/dll/version/version.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/version/version.dll
--------------------------------------------------------------------------------
/win32/dll/wininet/wininet.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/wininet/wininet.dll
--------------------------------------------------------------------------------
/win32/lib/retrowin32_test.lib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/lib/retrowin32_test.lib
--------------------------------------------------------------------------------
/win32/dll/advapi32/advapi32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/advapi32/advapi32.dll
--------------------------------------------------------------------------------
/win32/dll/comctl32/comctl32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/comctl32/comctl32.dll
--------------------------------------------------------------------------------
/win32/dll/kernel32/kernel32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/kernel32/kernel32.dll
--------------------------------------------------------------------------------
/win32/dll/oleaut32/oleaut32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/oleaut32/oleaut32.dll
--------------------------------------------------------------------------------
/win32/dll/ucrtbase/ucrtbase.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/ucrtbase/ucrtbase.dll
--------------------------------------------------------------------------------
/web/main.ts:
--------------------------------------------------------------------------------
1 | export { main as debuggerMain } from './debugger/debugger';
2 | export { main as runMain } from './run';
3 |
--------------------------------------------------------------------------------
/appdb/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/evmar/retrowin32/appdb
2 |
3 | go 1.22.3
4 |
5 | require github.com/BurntSushi/toml v1.4.0
6 |
--------------------------------------------------------------------------------
/exe/zig_hello/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | exec zig build-exe hello.zig -O ReleaseSmall -target x86-windows-msvc -fsingle-threaded
4 |
--------------------------------------------------------------------------------
/win32/dll/vcruntime140/vcruntime140.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/vcruntime140/vcruntime140.dll
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.i686-pc-windows-msvc]
2 | linker = "rust-lld"
3 |
4 | [alias]
5 | minibuild = "run -q -p minibuild --"
6 |
--------------------------------------------------------------------------------
/web/debugger/util.ts:
--------------------------------------------------------------------------------
1 | export function hex(i: number, digits = 2): string {
2 | return i.toString(16).padStart(digits, '0');
3 | }
4 |
--------------------------------------------------------------------------------
/exe/zig_hello/hello.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | pub fn main() void {
4 | std.debug.print("Hello, world!\n", .{});
5 | }
6 |
--------------------------------------------------------------------------------
/win32/dll/retrowin32_test/retrowin32_test.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evmar/retrowin32/HEAD/win32/dll/retrowin32_test/retrowin32_test.dll
--------------------------------------------------------------------------------
/cli/sdl-manual.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Set up environment for building with a manually-built x86 SDL.
3 |
4 | export LIBRARY_PATH=~/win/SDL2-2.28.2/x86/lib
--------------------------------------------------------------------------------
/minibuild/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "minibuild"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | anyhow = "1.0"
8 | glob = "0.3.1"
9 |
--------------------------------------------------------------------------------
/win32/dll/ntdll/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | mod builtin;
5 | mod misc;
6 |
7 | pub use builtin::DLL;
8 |
--------------------------------------------------------------------------------
/cli/sdl-brew.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Set up environment for building with SDL found in homebrew.
3 |
4 | export LIBRARY_PATH="$LIBRARY_PATH:$(brew --prefix)/lib"
5 |
6 |
--------------------------------------------------------------------------------
/win32/extract/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "win32-extract"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | windows-metadata = "0.58.0"
8 |
--------------------------------------------------------------------------------
/appdb/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
2 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
3 |
--------------------------------------------------------------------------------
/exe/ops/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eo pipefail
4 |
5 | cd "$(dirname "$0")"
6 | out=$(cargo run -p retrowin32 -F x86-emu -- ops.exe | tr -d '\r')
7 | difft out.txt <(echo "$out")
8 |
--------------------------------------------------------------------------------
/win32/lib/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | llvm-dlltool -m i386 -d retrowin32_test.def -l retrowin32_test.lib -k
6 | llvm-dlltool -m i386 -d retrowin32.def -l retrowin32.lib -k
7 |
--------------------------------------------------------------------------------
/misc/lldb-dump-fn.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Dumps assembly of a given function using lldb.
4 |
5 | # -m: include source
6 | # -n: specify function name
7 | exec lldb "$1" -b -o "dis -m -n $2"
8 |
--------------------------------------------------------------------------------
/win32/dll/dinput/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 | #![allow(non_upper_case_globals)]
4 |
5 | mod builtin;
6 | mod dinput;
7 |
8 | pub use builtin::DLL;
9 |
--------------------------------------------------------------------------------
/appdb/entries/minesweeper.toml:
--------------------------------------------------------------------------------
1 | category = "windows"
2 | title = "Minesweeper"
3 | desc = "the classic Windows game"
4 | cmdline = "archive/win2k/winmine.exe"
5 |
6 | [origin]
7 | desc = "Windows 2000"
8 |
--------------------------------------------------------------------------------
/win32/dll/.gitignore:
--------------------------------------------------------------------------------
1 | # We check in the .dll files so people can build retrowin32 without the clang
2 | # toolchain, but otherwise we don't want to check in the build artifacts.
3 | *.s
4 | *.lib
5 | *.def
6 |
--------------------------------------------------------------------------------
/memory/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "memory"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | log = { workspace = true }
8 |
9 | [features]
10 | mem-box = []
11 | mem-raw = []
12 |
--------------------------------------------------------------------------------
/appdb/entries/demo/gleam.toml:
--------------------------------------------------------------------------------
1 | title = "gleam"
2 | cmdline = "archive/demo/gleam.exe"
3 | category = "demoscene"
4 | broken = true
5 | status = "opengl"
6 |
7 | [origin]
8 | url = "https://www.pouet.net/prod.php?which=2819"
9 |
--------------------------------------------------------------------------------
/exe/trace/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Use cpu=i686 here to avoid generating SSE instructions.
4 |
5 | opt="-O ReleaseSmall"
6 | exec zig build-exe trace.zig -mcpu=i686 $opt -target x86-windows-msvc -fsingle-threaded "$@"
7 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/sync/mod.rs:
--------------------------------------------------------------------------------
1 | pub(crate) mod critical_section;
2 | pub(crate) mod event;
3 | pub(crate) mod interlocked;
4 | pub(crate) mod mutex;
5 | pub(crate) mod once;
6 | pub(crate) mod srw_lock;
7 | pub(crate) mod wait;
8 |
--------------------------------------------------------------------------------
/x86/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod debug;
2 | mod fpu;
3 | mod icache;
4 | pub mod ops;
5 | mod registers;
6 | mod x86;
7 |
8 | pub use crate::x86::{CPU, CPUState, X86};
9 | pub use iced_x86::Register;
10 | pub use ops::set_edx_eax;
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /deploy
3 |
4 | # mac so annoying
5 | .DS_Store
6 |
7 | # ghidra
8 | *.gpr
9 | *.rep
10 | *.lock*
11 |
12 | # Visual Studio (for use in profiling)
13 | /.vs
14 | # For running on Windows
15 | /SDL2.dll
16 |
--------------------------------------------------------------------------------
/exe/cpp/.clangd:
--------------------------------------------------------------------------------
1 | CompileFlags:
2 | Compiler: clang-cl
3 | Add: [-target, i686-pc-windows-msvc, /std:c++20, /winsysroot, /Users/evmar/.xwin-cache/splat]
4 |
5 | Diagnostics:
6 | ClangTidy:
7 | Remove: misc-definitions-in-headers
8 |
--------------------------------------------------------------------------------
/appdb/entries/demo/mofo.toml:
--------------------------------------------------------------------------------
1 | title = "mofo"
2 | desc = "mofo by Psikorp (1999)"
3 | cmdline = "archive/demo/psi_mofo.exe"
4 | category = "demoscene"
5 |
6 | [origin]
7 | desc = "pouet.net"
8 | url = "https://www.pouet.net/prod.php?which=519"
9 |
--------------------------------------------------------------------------------
/appdb/entries/solitaire.toml:
--------------------------------------------------------------------------------
1 | category = "windows"
2 | title = "Solitaire"
3 | desc = "the classic Windows game"
4 | dir = "archive/win2k/"
5 | files = ["cards.dll"]
6 | cmdline = "sol.exe"
7 |
8 | [origin]
9 | desc = "Windows 2000"
10 |
--------------------------------------------------------------------------------
/appdb/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -ex
4 |
5 | cd "$(dirname "$0")"
6 |
7 | go run . -tmpl ../web/index.tmpl render > ../web/index.html
8 | go run . -tmpl ../web/index.tmpl -broken render > ../web/broken.html
9 | go run . deploy
10 |
--------------------------------------------------------------------------------
/exe/callback/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Use cpu=i686 here to avoid generating SSE instructions.
4 |
5 | opt="-O ReleaseSmall"
6 | exec zig build-exe callback.zig -mcpu=i686 $opt -target x86-windows-msvc -fsingle-threaded -L ../../win32/lib "$@"
7 |
--------------------------------------------------------------------------------
/exe/ops/ops.cc:
--------------------------------------------------------------------------------
1 | #include "util.h"
2 |
3 | void math_tests();
4 | void fpu_tests();
5 |
6 | extern "C" int mainCRTStartup() {
7 | hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
8 | math_tests();
9 | fpu_tests();
10 | return 0;
11 | }
12 |
--------------------------------------------------------------------------------
/misc/print-asm.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Given an input assembly file, print the assembled machine code as bytes.
4 |
5 | set -e -o pipefail
6 |
7 | cd $(dirname "$0")
8 |
9 | clang -c -target i386-apple-darwin "$1" -o - | objdump -D -
10 |
--------------------------------------------------------------------------------
/appdb/entries/demo/win4k.toml:
--------------------------------------------------------------------------------
1 | title = "Win4k"
2 | desc = "Win4k by Power Flower Crew (2000)"
3 | cmdline = "archive/demo/win003.exe"
4 | category = "demoscene"
5 |
6 | [origin]
7 | desc = "pouet.net"
8 | url = "https://www.pouet.net/prod.php?which=6568"
9 |
--------------------------------------------------------------------------------
/pe/README.md:
--------------------------------------------------------------------------------
1 | # PE parser
2 |
3 | This crate traverses Windows Portable Executable (PE) files, aka .exe and .dll.
4 |
5 | It doesn't interact with any of the emulation machinery, it just accepts byte
6 | buffers and returns different views on to them.
7 |
--------------------------------------------------------------------------------
/appdb/README.md:
--------------------------------------------------------------------------------
1 | `entries/` contains metadata about specific programs that retrowin32 is known to
2 | work with, with program names and paths to binaries.
3 |
4 | `appdb.go` is a program that parses those and generates the deploy bundle and
5 | the website.
6 |
--------------------------------------------------------------------------------
/appdb/entries/demo/magnus.toml.notes:
--------------------------------------------------------------------------------
1 | title = "magnus effect"
2 | desc = "magnus effect by Aspirine (1999)"
3 | category = "demoscene"
4 | status = "4.3mb, many files"
5 |
6 | [origin]
7 | desc = "pouet.net"
8 | url = "https://www.pouet.net/prod.php?which=3247"
9 |
--------------------------------------------------------------------------------
/appdb/entries/demo/effect.toml:
--------------------------------------------------------------------------------
1 | title = "effect #8"
2 | desc = "effect #8 by Infuse Project & Trinity (1999)"
3 | cmdline = "archive/demo/effect.exe"
4 | category = "demoscene"
5 |
6 | [origin]
7 | desc = "pouet.net"
8 | url = "https://www.pouet.net/prod.php?which=17038"
9 |
--------------------------------------------------------------------------------
/appdb/entries/demo/jkf.toml:
--------------------------------------------------------------------------------
1 | title = "jävla kuk fitta"
2 | cmdline = "archive/demo/jkf-unpacked.exe"
3 | category = "demoscene"
4 | broken = true
5 | status = "opengl"
6 |
7 | [origin]
8 | desc = "pouet.net"
9 | url = "https://www.pouet.net/prod.php?which=237"
10 |
--------------------------------------------------------------------------------
/appdb/entries/demo/metazlo.toml:
--------------------------------------------------------------------------------
1 | title = "MetaZlo"
2 | cmdline = "archive/demo/metaZlo_final_640x480.exe"
3 | category = "demoscene"
4 | broken = true
5 | status = "opengl"
6 |
7 | [origin]
8 | desc = "pouet.net"
9 | url = "https://www.pouet.net/prod.php?which=237"
10 |
--------------------------------------------------------------------------------
/appdb/entries/demo/kill_the_clone.toml:
--------------------------------------------------------------------------------
1 | title = "kill the clone"
2 | dir = "archive/demo/"
3 | cmdline = "kill the clone unpacked.exe"
4 | category = "demoscene"
5 | broken = true
6 |
7 | [origin]
8 | desc = "pouet.net"
9 | url = "https://www.pouet.net/prod.php?which=2017"
10 |
--------------------------------------------------------------------------------
/win32/winapi/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "win32-winapi"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | log = { workspace = true }
8 | memory = { workspace = true }
9 | win32-derive = { workspace = true }
10 |
11 | typed-path = { version = "0.9.1" }
12 |
--------------------------------------------------------------------------------
/appdb/entries/demo/anatyda.toml:
--------------------------------------------------------------------------------
1 | title = "Anatyda"
2 | desc = "Anatyda by Astral (1999)"
3 | cmdline = "archive/demo/anatyda.exe"
4 | category = "demoscene"
5 | status = "works but slow"
6 |
7 | [origin]
8 | desc = "pouet.net"
9 | url = "https://www.pouet.net/prod.php?which=32369"
10 |
--------------------------------------------------------------------------------
/appdb/entries/retrowin32/zig_hello.toml:
--------------------------------------------------------------------------------
1 | category = "retrowin32 test"
2 | title = "zig hello world"
3 | desc = "trivial Zig program"
4 | cmdline = "local/exe/zig_hello/hello.exe"
5 |
6 | [origin]
7 | desc = "retrowin32"
8 | url = "https://github.com/evmar/retrowin32/tree/main/exe/zig_hello"
9 |
--------------------------------------------------------------------------------
/win32/dll/bass/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-bass"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/appdb/entries/demo/chillin.toml:
--------------------------------------------------------------------------------
1 | title = "chillin"
2 | desc = "chillin by Haujobb (2000)"
3 | cmdline = "archive/demo/chillfix.exe"
4 | category = "demoscene"
5 | status = "stuck in first scene"
6 |
7 | [origin]
8 | desc = "pouet.net"
9 | url = "https://www.pouet.net/prod.php?which=567"
10 |
--------------------------------------------------------------------------------
/win32/dll/dinput/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-dinput"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/win32/dll/ole32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-ole32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/win32/dll/shlwapi/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-shlwapi"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/win32/dll/version/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-version"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/win32/dll/wininet/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-wininet"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/x86/src/ops/math/mod.rs:
--------------------------------------------------------------------------------
1 | mod add;
2 | mod div;
3 | mod int;
4 | mod math;
5 | mod mul;
6 | mod rotate;
7 | mod shift;
8 | mod sub;
9 |
10 | pub use add::*;
11 | pub use div::*;
12 | pub use math::*;
13 | pub use mul::*;
14 | pub use rotate::*;
15 | pub use shift::*;
16 | pub use sub::*;
17 |
--------------------------------------------------------------------------------
/win32/dll/advapi32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-advapi32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/win32/dll/oleaut32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-oleaut32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/appdb/entries/retrowin32/callback.toml:
--------------------------------------------------------------------------------
1 | category = "retrowin32 test"
2 | title = "callback test"
3 | desc = "tests x86 calling Windows calling x86"
4 | cmdline = "local/exe/callback/callback.exe"
5 |
6 | [origin]
7 | desc = "retrowin32"
8 | url = "https://github.com/evmar/retrowin32/tree/main/exe/callback"
9 |
--------------------------------------------------------------------------------
/memory/src/rawmem.rs:
--------------------------------------------------------------------------------
1 | use crate::Mem;
2 |
3 | #[derive(Default)]
4 | pub struct RawMem {}
5 |
6 | impl RawMem {
7 | pub fn mem(&self) -> Mem {
8 | Mem::from_ptrs(0 as *mut u8..(1 << 30) as *mut u8)
9 | }
10 | pub fn len(&self) -> u32 {
11 | 0xFFFF_FFFF
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/win32/dll/vcruntime140/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-vcruntime140"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/appdb/entries/BasicDD.toml:
--------------------------------------------------------------------------------
1 | category = "retrowin32 test"
2 | title = "DirectDraw spinning car"
3 | desc = "simplest DirectDraw app"
4 | cmdline = "archive/BasicDD.exe"
5 |
6 | [origin]
7 | desc = "DirectDraw tutorial"
8 | url = "https://www.codeproject.com/Articles/2370/Introduction-to-DirectDraw-and-Surface-Blitting"
9 |
--------------------------------------------------------------------------------
/appdb/entries/retrowin32/gdi.toml:
--------------------------------------------------------------------------------
1 | category = "retrowin32 test"
2 | title = "gdi test"
3 | desc = "empty window Rust GDI program"
4 | cmdline = "local/target/i686-pc-windows-msvc/release/gdi.exe"
5 |
6 | [origin]
7 | desc = "retrowin32"
8 | url = "https://github.com/evmar/retrowin32/blob/main/exe/rust/src/bin/gdi.rs"
9 |
--------------------------------------------------------------------------------
/exe/winapi/winapi.cc:
--------------------------------------------------------------------------------
1 | #define WIN32_LEAN_AND_MEAN
2 | #define STRICT
3 | #include
4 |
5 | void mainCRTStartup(void) {
6 | auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
7 | static const char buf[] = "hello\n";
8 | bool ok = WriteFile(hStdout, buf, sizeof(buf) - 1, nullptr, nullptr);
9 | }
10 |
--------------------------------------------------------------------------------
/win32/dll/retrowin32_test/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-retrowin32_test"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
--------------------------------------------------------------------------------
/x86/src/ops/mod.rs:
--------------------------------------------------------------------------------
1 | mod basic;
2 | mod bits;
3 | mod control;
4 | mod cpuid;
5 | mod flags;
6 | mod fpu;
7 | mod helpers;
8 | mod math;
9 | mod mmx;
10 | mod mov;
11 | mod stack;
12 | mod string;
13 | mod table;
14 | mod test;
15 |
16 | pub use helpers::{pop, push, set_edx_eax};
17 | pub use table::{Op, decode};
18 |
--------------------------------------------------------------------------------
/win32/dll/gdi32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-gdi32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | log = { workspace = true }
8 | memory = { workspace = true }
9 | win32-derive = { workspace = true }
10 | win32-system = { workspace = true }
11 | win32-winapi = { workspace = true }
12 |
--------------------------------------------------------------------------------
/appdb/entries/retrowin32/exit.toml:
--------------------------------------------------------------------------------
1 | category = "retrowin32 test"
2 | title = "exit test"
3 | desc = "tests exiting with status code"
4 | cmdline = "local/target/i686-pc-windows-msvc/release/errors.exe exit"
5 |
6 | [origin]
7 | desc = "retrowin32"
8 | url = "https://github.com/evmar/retrowin32/blob/main/exe/rust/src/bin/errors.rs"
9 |
--------------------------------------------------------------------------------
/win32/dll/dsound/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-dsound"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | bitflags = { workspace = true }
8 | memory = { workspace = true }
9 | win32-derive = { workspace = true }
10 | win32-system = { workspace = true }
11 | win32-winapi = { workspace = true }
12 |
--------------------------------------------------------------------------------
/appdb/entries/demo/followme.toml:
--------------------------------------------------------------------------------
1 | title = "Follow Me"
2 | desc = "Follow Me by 2 Many People (2000)"
3 | cmdline = "archive/demo/followme/fm_ddraw.exe"
4 | category = "demoscene"
5 | broken = true
6 | status = "missing VirtualQuery"
7 |
8 | [origin]
9 | desc = "pouet.net"
10 | url = "https://www.pouet.net/prod.php?which=3946"
11 |
--------------------------------------------------------------------------------
/win32/dll/ucrtbase/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! The C runtime library. This module is also the implementation of msvcrt.dll.
2 |
3 | #![allow(non_snake_case)]
4 | #![allow(non_upper_case_globals)]
5 |
6 | mod builtin;
7 | mod init;
8 | mod math;
9 | mod memory;
10 | mod misc;
11 | mod rand;
12 | mod time;
13 |
14 | pub use builtin::DLL;
15 |
--------------------------------------------------------------------------------
/win32/system/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod dll;
2 | mod event;
3 | mod heap;
4 | pub mod host;
5 | pub mod memory;
6 | pub mod resource;
7 | mod system;
8 | pub mod trace;
9 | mod wait;
10 |
11 | pub use event::{ArcEvent, Event};
12 | pub use heap::Heap;
13 | pub use system::{System, generic_get_state};
14 | pub use wait::{Wait, WaitResult};
15 |
--------------------------------------------------------------------------------
/appdb/entries/retrowin32/zip.toml:
--------------------------------------------------------------------------------
1 | category = "retrowin32 test"
2 | title = "zip/unzip round trip"
3 | desc = "retrowin32's own performance test"
4 | dir = "local/target/i686-pc-windows-msvc/release/"
5 | cmdline = "zip.exe 1 zip.exe"
6 |
7 | [origin]
8 | desc = "retrowin32"
9 | url = "https://github.com/evmar/retrowin32/tree/main/exe/zip"
10 |
--------------------------------------------------------------------------------
/win32/dll/comctl32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-comctl32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
12 | builtin-user32 = { path = "../user32" }
13 |
--------------------------------------------------------------------------------
/win32/dll/ntdll/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-ntdll"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
12 | builtin-kernel32 = { path = "../kernel32" }
13 |
--------------------------------------------------------------------------------
/win32/dll/ucrtbase/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-ucrtbase"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | memory = { workspace = true }
8 | win32-derive = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
12 | builtin-user32 = { path = "../user32" }
13 |
--------------------------------------------------------------------------------
/win32/dll/winmm/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-winmm"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | bitflags = { workspace = true }
8 | log = { workspace = true }
9 | memory = { workspace = true }
10 | win32-derive = { workspace = true }
11 | win32-system = { workspace = true }
12 | win32-winapi = { workspace = true }
13 |
--------------------------------------------------------------------------------
/appdb/entries/retrowin32/invalid_addr.toml:
--------------------------------------------------------------------------------
1 | category = "retrowin32 test"
2 | title = "invalid address test"
3 | desc = "tests dereferencing an invalid address"
4 | cmdline = "local/target/i686-pc-windows-msvc/release/errors.exe write-high"
5 |
6 | [origin]
7 | desc = "retrowin32"
8 | url = "https://github.com/evmar/retrowin32/blob/main/exe/rust/src/bin/errors.rs"
9 |
--------------------------------------------------------------------------------
/misc/quiet.reg:
--------------------------------------------------------------------------------
1 | REGEDIT4
2 |
3 | ; WINEDEBUG=relay wine ...
4 | ; will print DLL calls, but there are so many the log is a lot.
5 | ; Install this file with
6 | ; wine regedit quiet.reg
7 | ; to suppress some.
8 |
9 | [HKEY_CURRENT_USER\Software\Wine\Debug]
10 | "RelayExclude"="ntdll.*;win32u.*;wow64cpu.*;wow64.*;combase.*;winecoreaudio.*;kernel32.HeapFree;"
11 |
--------------------------------------------------------------------------------
/win32/lib/README.md:
--------------------------------------------------------------------------------
1 | # .lib files
2 |
3 | Some code in retrowin32 is built as win32 binaries. This code sometimes needs to
4 | link against DLLs. This directory defines the .lib files passed to the linker
5 | for doing this.
6 |
7 | - `retrowin32.dll`: builtin "syscall" function for calls from x86->retrowin32.
8 | - `retrowin32_test.dll`: builtin for testing retrowin32.
9 |
--------------------------------------------------------------------------------
/appdb/entries/demo/monolife.toml:
--------------------------------------------------------------------------------
1 | title = "Monolife"
2 | desc = "Monolife by Hatha (1997)"
3 | dir = "archive/demo/monolife/"
4 | cmdline = "monolife.exe"
5 | files = ["monolife.dat"]
6 | category = "demoscene"
7 | status = "stuck on first scene, possibly audio related"
8 |
9 | [origin]
10 | desc = "pouet.net"
11 | url = "https://www.pouet.net/prod.php?which=7698"
12 |
--------------------------------------------------------------------------------
/pe/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pe"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | log = { workspace = true }
8 | memory = { workspace = true }
9 |
10 | anyhow = "1.0"
11 | bitflags = { workspace = true }
12 | serde = { version = "1.0", optional = true, features = ["derive"] }
13 |
14 | [features]
15 | serde = ["dep:serde", "bitflags/serde"]
16 |
--------------------------------------------------------------------------------
/win32/extract/README.md:
--------------------------------------------------------------------------------
1 | Prints function prototypes for Windows API.
2 |
3 | Use like:
4 |
5 | ```
6 | $ cargo run GetDriveTypeA FileTimeToLocalFileTime
7 | ```
8 |
9 | and it will print the appropriate winapi stub functions.
10 |
11 | Requires Windows.Win32.winmd as downloaded from
12 | https://github.com/microsoft/windows-rs/tree/master/crates/libs/bindgen/default
13 |
--------------------------------------------------------------------------------
/win32/system/README.md:
--------------------------------------------------------------------------------
1 | This crate corresponds roughly to the Windows OS, as seen by the Windows API
2 | implementation:
3 |
4 | - a `System` trait that Windows APIs all take as their first argument
5 | - cross-DLL concepts like memory mapping, heaps, etc.
6 |
7 | This will let us build the Windows DLLs independently from one another and from
8 | the implementation of `System`.
9 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # This script is used to deploy the website.
4 | # It expects the `pages` branch to be checked out in the `deploy` subdir;
5 | # set that up with:
6 | # git worktree add deploy pages
7 |
8 | set -e
9 |
10 | (cd appdb && ./run.sh)
11 | make -C web profile=lto
12 | (cd web && npm run build)
13 | cp web/*.css web/*.html web/*.wasm web/*.png deploy
14 |
--------------------------------------------------------------------------------
/exe/cpp/metrics.cc:
--------------------------------------------------------------------------------
1 | // Dump GetSystemMetrics() values.
2 |
3 | #include "util.h"
4 | #include
5 |
6 | extern "C" void mainCRTStartup() {
7 | print(fmt().str("GetSystemMetrics():\r\n"));
8 |
9 | for (int i = 0; i < 100; ++i) {
10 | int metric = GetSystemMetrics(i);
11 | print(fmt().dec(i).str(" => ").dec(metric).nl());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/misc/fmt.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Run all code formatters.
4 |
5 | set -e
6 |
7 | if [ "$1" = "--check" ]; then
8 | if ! (
9 | cargo fmt -- --check &&
10 | dprint check
11 | ); then
12 | echo
13 | echo "error: formatting check failed; run 'misc/fmt.sh' to fix"
14 | exit 1
15 | fi
16 | else
17 | cargo fmt
18 | dprint fmt
19 | fi
20 |
--------------------------------------------------------------------------------
/win32/dll/winmm/src/misc.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 | use win32_winapi::Str16;
3 |
4 | #[win32_derive::dllexport]
5 | pub fn PlaySoundW(sys: &dyn System, pszSound: Option<&Str16>, hmod: u32, fdwSound: u32) -> bool {
6 | todo!();
7 | }
8 |
9 | #[win32_derive::dllexport]
10 | pub fn sndPlaySoundA(sys: &dyn System, pszSound: Option<&str>, fuSound: u32) -> bool {
11 | false
12 | }
13 |
--------------------------------------------------------------------------------
/exe/ops/util.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | extern HANDLE hStdout;
8 |
9 | void print(std::string_view sv);
10 | void print(uint32_t x);
11 | void printv(const char *fmt...);
12 | void print_flags(uint32_t flags);
13 |
14 | #define clear_flags() __asm push 0 __asm popfd
15 | #define get_flags() __asm pushfd __asm pop flags
16 |
--------------------------------------------------------------------------------
/misc/ai.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Use OpenAI's API to translate win32 prototypes to retrowin32 Rust definitions.
4 | # https://inuh.net/@evmar/112001414385042731
5 | # The 'ai' command here is https://github.com/evmar/ai
6 |
7 | set -e
8 |
9 | cd ~/projects/ai
10 | source apikey.sh
11 | exec ./ai -server openai text -sys 'translate c to rust, following pattern from examples' -multi "$(&2
11 | else
12 | # In older versions, Makefile was at the root of the project.
13 | (make wasm opt=1) 1>&2
14 | fi
15 |
16 | wc -c < web/wasm.wasm
17 |
--------------------------------------------------------------------------------
/win32/winapi/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod calling_convention;
2 | pub mod com;
3 | pub mod encoding;
4 | mod error;
5 | mod handle;
6 | mod point;
7 | mod rect;
8 | mod types;
9 |
10 | pub use error::ERROR;
11 | pub use handle::{HANDLE, Handle, Handles};
12 | pub use memory::str16::{Str16, String16};
13 | pub use point::POINT;
14 | pub use rect::RECT;
15 | pub use typed_path::{UnixPath, WindowsPath, WindowsPathBuf};
16 | pub use types::*;
17 |
--------------------------------------------------------------------------------
/win32/dll/retrowin32_test/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Definition of a "retrowin32_test" builtin dll, used for testing retrowin32.
2 | //! See win32/lib/README.md.
3 |
4 | mod builtin;
5 |
6 | pub use builtin::DLL;
7 |
8 | use win32_system::System;
9 |
10 | #[win32_derive::dllexport]
11 | pub async fn retrowin32_test_callback1(sys: &mut dyn System, func: u32, data: u32) -> u32 {
12 | sys.call_x86(func, vec![data]).await;
13 | 1
14 | }
15 |
--------------------------------------------------------------------------------
/win32/dll/ddraw/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-ddraw"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | bitflags = { workspace = true }
8 | log = { workspace = true }
9 | memory = { workspace = true }
10 | win32-derive = { workspace = true }
11 | win32-system = { workspace = true }
12 | win32-winapi = { workspace = true }
13 |
14 | builtin-gdi32 = { path = "../gdi32" }
15 | builtin-user32 = { path = "../user32" }
16 |
--------------------------------------------------------------------------------
/win32/derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "win32-derive"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [lib]
7 | proc-macro = true
8 |
9 | [dependencies]
10 | anyhow = "1.0"
11 | proc-macro2 = { version = "1.0", features = ["span-locations"] }
12 | quote = "1.0"
13 | walkdir = "2.5.0"
14 |
15 | [dependencies.syn]
16 | version = "2.0"
17 | default-features = false
18 | features = ["extra-traits", "full", "parsing", "printing", "proc-macro"]
19 |
--------------------------------------------------------------------------------
/cli/src/time.rs:
--------------------------------------------------------------------------------
1 | //! Implementation of the `ticks()` method of win32::host trait.
2 |
3 | #[derive(Clone)] // shared with SDL
4 | pub struct Time {
5 | pub start: std::time::Instant,
6 | }
7 |
8 | impl Time {
9 | pub fn new() -> Self {
10 | Time {
11 | start: std::time::Instant::now(),
12 | }
13 | }
14 |
15 | pub fn ticks(&self) -> u32 {
16 | self.start.elapsed().as_millis() as u32
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "serve": "esbuild --bundle --format=esm --sourcemap --servedir=. --outfile=bundle.js main.ts",
6 | "build": "esbuild --bundle --format=esm --sourcemap --outfile=../deploy/bundle.js main.ts"
7 | },
8 | "devDependencies": {
9 | "esbuild": "0.25.0",
10 | "typescript": "^5.6.2"
11 | },
12 | "dependencies": {
13 | "preact": "^10.26.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/win32/dll/wininet/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | mod builtin;
5 |
6 | pub use builtin::DLL;
7 |
8 | use win32_system::System;
9 |
10 | #[win32_derive::dllexport]
11 | pub fn InternetOpenA(
12 | sys: &dyn System,
13 | lpszAgent: Option<&str>,
14 | dwAccessType: u32,
15 | lpszProxy: Option<&str>,
16 | lpszProxyBypass: Option<&str>,
17 | dwFlags: u32,
18 | ) -> u32 {
19 | 0
20 | }
21 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/sync/interlocked.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | #[win32_derive::dllexport]
4 | pub fn InterlockedIncrement(sys: &dyn System, addend: Option<&mut u32>) -> u32 {
5 | let addend = addend.unwrap();
6 | *addend += 1;
7 | *addend
8 | }
9 |
10 | #[win32_derive::dllexport]
11 | pub fn InterlockedDecrement(sys: &dyn System, addend: Option<&mut u32>) -> u32 {
12 | let addend = addend.unwrap();
13 | *addend -= 1;
14 | *addend
15 | }
16 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-kernel32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | anyhow = "1.0"
8 | bitflags = { workspace = true }
9 | chrono = { workspace = true }
10 | log = { workspace = true }
11 | memory = { workspace = true }
12 | pe = { workspace = true }
13 | win32-derive = { workspace = true }
14 | win32-system = { workspace = true }
15 | win32-winapi = { workspace = true }
16 |
17 | [features]
18 | x86-emu = []
19 |
--------------------------------------------------------------------------------
/win32/dll/user32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "builtin-user32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | anyhow = "1.0"
8 | bitflags = { workspace = true }
9 | log = { workspace = true }
10 | memory = { workspace = true }
11 | num-traits = { workspace = true }
12 | pe = { workspace = true }
13 | win32-derive = { workspace = true }
14 | win32-system = { workspace = true }
15 | win32-winapi = { workspace = true }
16 |
17 | builtin-gdi32 = { path = "../gdi32" }
18 |
--------------------------------------------------------------------------------
/win32/dll/gdi32/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | mod builtin;
5 |
6 | pub use builtin::DLL;
7 |
8 | pub mod bitmap;
9 | mod bitmap_api;
10 | mod dc;
11 | mod draw;
12 | mod object;
13 | mod palette;
14 | mod state;
15 | mod text;
16 |
17 | pub use dc::{DC, DCTarget, HDC};
18 | pub use draw::{Brush, COLORREF, fill_rect};
19 | pub use object::{HGDIOBJ, LOWEST_HGDIOBJ, Object};
20 | pub use palette::PALETTEENTRY;
21 | pub use state::{GDIHandles, State, get_state};
22 |
--------------------------------------------------------------------------------
/appdb/entries/demo/stream.toml:
--------------------------------------------------------------------------------
1 | title = "stream"
2 | desc = "stream by mfx"
3 | dir = "archive/demo/stream/"
4 | cmdline = "stream.exe"
5 | category = "demoscene"
6 | broken = true
7 | files = [
8 | "an_mfx.tga",
9 | "code.tga",
10 | "design.tga",
11 | "graphics.tga",
12 | "joulua.tga",
13 | "mfx-psyk.tga",
14 | "music.tga",
15 | "naamake2.tga",
16 | "str-psyk.tga",
17 | "tu_hhu.tga",
18 | "tu_uhh.tga",
19 | ]
20 |
21 | [origin]
22 | desc = "pouet.net"
23 | url = "https://www.pouet.net/prod.php?which=1382"
24 |
--------------------------------------------------------------------------------
/exe/winapi/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | XWIN="${XWIN:-$HOME/.xwin-cache/splat}"
6 | clang_flags="-fuse-ld=lld -target i686-pc-windows-msvc"
7 | # reproducible builds, optimize for size, no security cookies
8 | # note: /Zi for debug info (useful for ghidra) but it breaks build reproducibility
9 | cflags="/Brepro /std:c++20 /Os /GS-"
10 | sdk_flags="/winsysroot $XWIN"
11 | link_flags="/nodefaultlib /subsystem:console kernel32.lib"
12 |
13 | exec clang-cl $clang_flags $cflags $sdk_flags winapi.cc /link $link_flags
14 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/file/mapping.rs:
--------------------------------------------------------------------------------
1 | use super::HFILE;
2 | use crate::SECURITY_ATTRIBUTES;
3 | use win32_system::System;
4 | use win32_winapi::HANDLE;
5 |
6 | #[win32_derive::dllexport]
7 | pub fn CreateFileMappingA(
8 | sys: &dyn System,
9 | hFile: HFILE,
10 | lpFileMappingAttributes: Option<&mut SECURITY_ATTRIBUTES>,
11 | flProtect: u32, /* PAGE_PROTECTION_FLAGS */
12 | dwMaximumSizeHigh: u32,
13 | dwMaximumSizeLow: u32,
14 | lpName: Option<&str>,
15 | ) -> HANDLE<()> {
16 | todo!()
17 | }
18 |
--------------------------------------------------------------------------------
/pe/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod exports;
2 | mod file;
3 | mod imports;
4 | pub mod parse;
5 | mod relocations;
6 | mod resources;
7 |
8 | pub use exports::*;
9 | pub use file::*;
10 | pub use imports::*;
11 | pub use relocations::*;
12 | pub use resources::*;
13 |
14 | /// Read a C-style nul terminated string from a buffer.
15 | /// Various PE structures use these, sometimes with an optional nul.
16 | pub(crate) fn c_str(buf: &[u8]) -> &[u8] {
17 | let len = buf.iter().position(|b| *b == 0).unwrap_or(buf.len());
18 | &buf[..len]
19 | }
20 |
--------------------------------------------------------------------------------
/win32/dll/winmm/src/joy.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | pub type JOYCAPSA = u32;
4 | pub type JOYINFOEX = u32;
5 |
6 | #[win32_derive::dllexport]
7 | pub fn joyGetNumDevs(sys: &dyn System) -> u32 {
8 | 0
9 | }
10 |
11 | #[win32_derive::dllexport]
12 | pub fn joyGetDevCapsA(sys: &dyn System, uJoyID: u32, pjc: Option<&mut JOYCAPSA>, cbjc: u32) -> u32 {
13 | todo!()
14 | }
15 |
16 | #[win32_derive::dllexport]
17 | pub fn joyGetPosEx(sys: &dyn System, uJoyID: u32, pji: Option<&mut JOYINFOEX>) -> u32 {
18 | todo!()
19 | }
20 |
--------------------------------------------------------------------------------
/win32/system/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "win32-system"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | log = { workspace = true }
8 | memory = { workspace = true }
9 | pe = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
12 | chrono = "0.4.38"
13 |
14 | serde = { version = "1.0", optional = true, features = ["derive"] }
15 | tsify = { workspace = true, optional = true }
16 | wasm-bindgen = { workspace = true, optional = true }
17 |
18 | [features]
19 | wasm = ["dep:tsify", "dep:wasm-bindgen", "dep:serde", "pe/serde"]
20 |
--------------------------------------------------------------------------------
/win32/dll/comctl32/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 | #![allow(non_upper_case_globals)]
4 |
5 | mod builtin;
6 |
7 | pub use builtin::DLL;
8 |
9 | pub use builtin_user32::TRACKMOUSEEVENT;
10 | use win32_system::System;
11 |
12 | #[win32_derive::dllexport(ordinal = 17)]
13 | pub fn InitCommonControls(sys: &dyn System) {}
14 |
15 | #[win32_derive::dllexport]
16 | pub fn _TrackMouseEvent(sys: &mut dyn System, lpEventTrack: Option<&mut TRACKMOUSEEVENT>) -> bool {
17 | builtin_user32::TrackMouseEvent(sys, lpEventTrack)
18 | }
19 |
--------------------------------------------------------------------------------
/win32/dll/oleaut32/src/builtin.rs:
--------------------------------------------------------------------------------
1 | #![doc = r" Generated code, do not edit. See winapi/builtin.rs for an overview."]
2 | #![allow(unused_imports)]
3 | #![allow(unused_variables)]
4 | use win32_system::dll::*;
5 | mod wrappers {
6 | use crate as oleaut32;
7 | use crate::*;
8 | use ::memory::Extensions;
9 | use win32_system::{System, trace};
10 | use win32_winapi::{calling_convention::*, *};
11 | }
12 | const SHIMS: [Shim; 0usize] = [];
13 | pub const DLL: BuiltinDLL = BuiltinDLL {
14 | file_name: "oleaut32.dll",
15 | shims: &SHIMS,
16 | raw: std::include_bytes!("../oleaut32.dll"),
17 | };
18 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/sync/mutex.rs:
--------------------------------------------------------------------------------
1 | use crate::SECURITY_ATTRIBUTES;
2 | use win32_system::System;
3 | use win32_winapi::HANDLE;
4 |
5 | #[win32_derive::dllexport]
6 | pub fn CreateMutexA(
7 | sys: &dyn System,
8 | lpMutexAttributes: Option<&mut SECURITY_ATTRIBUTES>,
9 | bInitialOwner: bool,
10 | lpName: Option<&str>,
11 | ) -> HANDLE<()> {
12 | HANDLE::null() // fail
13 | }
14 |
15 | #[win32_derive::dllexport]
16 | pub fn OpenMutexA(
17 | sys: &dyn System,
18 | dwDesiredAccess: u32,
19 | bInheritHandle: bool,
20 | lpName: Option<&str>,
21 | ) -> HANDLE<()> {
22 | HANDLE::null() // fail
23 | }
24 |
--------------------------------------------------------------------------------
/win32/dll/shlwapi/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | mod builtin;
5 |
6 | pub use builtin::DLL;
7 | use memory::{Extensions, ExtensionsMut};
8 | use win32_system::System;
9 |
10 | #[win32_derive::dllexport]
11 | pub fn PathRemoveFileSpecA(sys: &mut dyn System, pszPath: u32) -> bool {
12 | let path = sys.mem().slicez(pszPath);
13 | let path = sys.mem().sub32_mut(pszPath, path.len() as u32);
14 | for (i, c) in path.iter_mut().enumerate().rev() {
15 | if *c == b'\\' {
16 | *c = 0;
17 | return true;
18 | }
19 | }
20 | false
21 | }
22 |
--------------------------------------------------------------------------------
/dprint.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript": {
3 | "quoteStyle": "preferSingle"
4 | },
5 | "json": {},
6 | "markdown": {
7 | "textWrap": "always"
8 | },
9 | "toml": {},
10 | "includes": [
11 | "**/*.{ts,tsx,js,jsx,cjs,mjs,json,md,toml}"
12 | ],
13 | "excludes": [
14 | "deploy/*.js",
15 | "target/**",
16 | "**/node_modules",
17 | "**/*-lock.json"
18 | ],
19 | "plugins": [
20 | "https://plugins.dprint.dev/typescript-0.73.1.wasm",
21 | "https://plugins.dprint.dev/json-0.15.6.wasm",
22 | "https://plugins.dprint.dev/markdown-0.14.1.wasm",
23 | "https://plugins.dprint.dev/toml-0.5.4.wasm"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/exe/ops/.clangd:
--------------------------------------------------------------------------------
1 | # Note:
2 | # Specify clang-cl as compiler to pick up Windows flags, but then we must
3 | # pass /TP to make it believe our .h files are C++.
4 |
5 | # -ferror-limit=0 seems to work around it saying "too many errors" when there are
6 | # no errors, something about asm blocks?
7 |
8 | CompileFlags:
9 | Compiler: clang-cl
10 | Add:
11 | - -I.
12 | - /TP
13 | - -target
14 | - i686-pc-windows-msvc
15 | - /W4
16 | - /std:c++17
17 | - /vctoolsdir
18 | - /Users/evmar/redist/crt
19 | - /winsdkdir
20 | - /Users/evmar/redist/sdk
21 | - -ferror-limit=0
22 |
--------------------------------------------------------------------------------
/win32/derive/README.md:
--------------------------------------------------------------------------------
1 | # win32 derive macros
2 |
3 | This crate implements some macros used by the win32 library as well as a code
4 | generator that works over the same macros.
5 |
6 | The main attribute is `dllexport`, which is used to mark a function as available
7 | in the Windows API. The code generator gathers all of these to generate a
8 | `builtins` module that plumbs arguments to/from the x86 stack and registers into
9 | the Rust versions. The same attribute also triggers the tracing infrastructure,
10 | which logs winapi functions as they're called.
11 |
12 | The `TryFromEnum` derive macro adds a `try_from()` method to enums, mapping
13 | integers back to enum values.
14 |
--------------------------------------------------------------------------------
/exe/cpp/cmdline.cc:
--------------------------------------------------------------------------------
1 | // Exercise command line functions.
2 |
3 | #include "util.h"
4 |
5 | extern "C" void mainCRTStartup() {
6 | char buf[256];
7 |
8 | DWORD ret = GetModuleFileNameA(NULL, buf, sizeof(buf));
9 | if (ret > 0) {
10 | print(fmt().str("GetModuleFileNameA: ").str(buf).nl());
11 | } else {
12 | print(fmt().str("GetModuleFileNameA failed: ").dec(GetLastError()).nl());
13 | }
14 |
15 | LPCSTR cmdline = GetCommandLineA();
16 | if (cmdline != NULL) {
17 | print(fmt().str("GetCommandLineA: ").str(cmdline).nl());
18 | } else {
19 | print(fmt().str("GetCommandLineA failed: ").dec(GetLastError()).nl());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/x86/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "x86"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | log = { workspace = true }
8 | memory = { workspace = true }
9 |
10 | bitflags = { workspace = true }
11 | extended = "0.1.0"
12 | # We bring in the whole dang futures crate just to create a noop waker, jeez.
13 | futures = "0.3.31"
14 | iced-x86 = "1.17.0"
15 | num-traits = { workspace = true }
16 |
17 | # wasm-only:
18 | serde = { version = "1.0", optional = true, features = ["derive"] }
19 | tsify = { workspace = true, optional = true }
20 | wasm-bindgen = { workspace = true, optional = true }
21 |
22 | [features]
23 | wasm = ["dep:tsify", "dep:wasm-bindgen", "dep:serde"]
24 |
--------------------------------------------------------------------------------
/win32/dll/user32/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | mod builtin;
5 | mod dialog;
6 | mod keyboard;
7 | mod menu;
8 | mod message;
9 | mod misc;
10 | mod paint;
11 | pub mod printf;
12 | mod rect;
13 | mod resource;
14 | mod state;
15 | mod timer;
16 | mod window;
17 | mod wndclass;
18 |
19 | pub use builtin::DLL;
20 |
21 | pub use menu::HMENU;
22 | pub use message::{MSG, WM};
23 | pub use misc::HINSTANCE;
24 | // Used by comctl32.
25 | pub use misc::{TRACKMOUSEEVENT, TrackMouseEvent};
26 | pub use resource::HBRUSH;
27 | pub use state::{State, get_state};
28 | pub use window::activate_window;
29 |
30 | pub use win32_system::resource::ResourceKey;
31 |
--------------------------------------------------------------------------------
/win32/dll/README.md:
--------------------------------------------------------------------------------
1 | This directory contains crates for the Windows system DLLs.
2 |
3 | There are two pices to a system DLL:
4 |
5 | 1. There is a Rust implementation crate that contains the code that runs
6 | natively.
7 | 2. There is a generated `.dll` file that doesn't contain much code; instead each
8 | function immediately invokes sysenter which transfers control to the above
9 | Rust code. We generate these real DLL files though so the system DLLs behave
10 | similarly at runtime to real DLLs.
11 |
12 | These latter DLLs are generated from the dllexport annotations the functions in
13 | the crate. The win32/derive generator parses them and generated the
14 | assembly/.def inputs.
15 |
--------------------------------------------------------------------------------
/win32/dll/ucrtbase/src/rand.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | // MSDN: "Calling rand before any call to srand generates the same sequence as calling srand with seed passed as 1."
4 | static mut RAND_STATE: u32 = 1;
5 |
6 | #[win32_derive::dllexport(cdecl)]
7 | pub fn srand(sys: &mut dyn System, seed: u32) {
8 | unsafe {
9 | RAND_STATE = seed % (1 << 31);
10 | }
11 | }
12 |
13 | #[win32_derive::dllexport(cdecl)]
14 | pub fn rand(sys: &mut dyn System) -> u32 {
15 | // https://en.wikipedia.org/wiki/Linear_congruential_generator
16 | unsafe {
17 | RAND_STATE = ((RAND_STATE.wrapping_mul(134775813)).wrapping_add(1)) % (1 << 31);
18 | RAND_STATE
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/cli/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "retrowin32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | chrono = { workspace = true }
8 | log = { workspace = true }
9 | memory = { workspace = true }
10 | pe = { workspace = true }
11 | win32 = { workspace = true }
12 |
13 | anyhow = "1.0"
14 | argh = { version = "0.1.13", default-features = false, features = ["help"] }
15 | libc = { version = "0.2", optional = true }
16 |
17 | [dependencies.sdl2]
18 | version = "0.35.2"
19 | features = ["unsafe_textures"]
20 | optional = true
21 |
22 | [features]
23 | default = ["x86-emu", "sdl"]
24 | sdl = ["dep:sdl2"]
25 | x86-emu = ["win32/x86-emu"]
26 | x86-64 = ["win32/x86-64", "dep:libc"]
27 | x86-unicorn = ["win32/x86-unicorn"]
28 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/file/mod.rs:
--------------------------------------------------------------------------------
1 | pub(crate) mod file;
2 | pub(crate) mod file16;
3 | pub(crate) mod find;
4 | pub(crate) mod fs;
5 | pub(crate) mod mapping;
6 | pub(crate) mod metadata;
7 | pub(crate) mod misc;
8 | pub(crate) mod path;
9 | pub(crate) mod stdio;
10 |
11 | pub use file::{HFILE, write_file};
12 | pub use metadata::FileAttribute;
13 | pub use stdio::{STDERR_HFILE, STDIN_HFILE, STDOUT_HFILE};
14 | use win32_system::{System, generic_get_state, host};
15 | use win32_winapi::Handles;
16 |
17 | #[derive(Default)]
18 | pub struct State {
19 | pub files: Handles>,
20 | }
21 |
22 | #[inline]
23 | pub fn get_state(sys: &dyn System) -> std::cell::RefMut<'_, State> {
24 | generic_get_state::(sys)
25 | }
26 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/pipe.rs:
--------------------------------------------------------------------------------
1 | use super::{SECURITY_ATTRIBUTES, file::HFILE};
2 | use win32_system::System;
3 |
4 | #[win32_derive::dllexport]
5 | pub fn CreatePipe(
6 | sys: &dyn System,
7 | hReadPipe: Option<&mut HFILE>,
8 | hWritePipe: Option<&mut HFILE>,
9 | lpPipeAttributes: Option<&mut SECURITY_ATTRIBUTES>,
10 | nSize: u32,
11 | ) -> bool {
12 | todo!()
13 | }
14 |
15 | #[win32_derive::dllexport]
16 | pub fn PeekNamedPipe(
17 | sys: &dyn System,
18 | hNamedPipe: HFILE,
19 | lpBuffer: Option<&mut u32>, // TODO
20 | nBufferSize: u32,
21 | lpBytesRead: Option<&mut u32>,
22 | lpTotalBytesAvail: Option<&mut u32>,
23 | lpBytesLeftThisMessage: Option<&mut u32>,
24 | ) -> bool {
25 | todo!()
26 | }
27 |
--------------------------------------------------------------------------------
/win32/dll/ucrtbase/src/time.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | #[win32_derive::dllexport(cdecl)]
4 | pub fn time(sys: &mut dyn System, destTime: Option<&mut u32>) -> u32 {
5 | // Officially time.h conditionally provides time as 32-bit or 64-bit
6 | // based on the define _USE_32BIT_TIME_T, but the msvcrt DLL
7 | // contains the 32-bit one.
8 | let time = sys.host().system_time().timestamp() as u32;
9 | if let Some(destTime) = destTime {
10 | *destTime = time;
11 | }
12 | time
13 | }
14 |
15 | #[win32_derive::dllexport(cdecl)]
16 | pub fn _time64(sys: &mut dyn System, destTime: Option<&mut u64>) -> u64 {
17 | let time = sys.host().system_time().timestamp() as u64;
18 | if let Some(destTime) = destTime {
19 | *destTime = time;
20 | }
21 | time
22 | }
23 |
--------------------------------------------------------------------------------
/web/run.html:
--------------------------------------------------------------------------------
1 |
2 | retrowin32
3 |
4 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/web/debugger/cpus.tsx:
--------------------------------------------------------------------------------
1 | import * as preact from 'preact';
2 | import * as wasm from '../glue/pkg/glue';
3 | import { hex } from './util';
4 |
5 | namespace CPUs {
6 | export interface Props {
7 | cpus: wasm.CPU[];
8 | }
9 | export interface State {
10 | }
11 | }
12 | export class CPUs extends preact.Component {
13 | render() {
14 | const rows = this.props.cpus.map((cpu) => {
15 | return (
16 |
17 | | {hex(cpu.eip, 8)} |
18 | {cpu.state()} |
19 |
20 | );
21 | });
22 | return (
23 |
24 |
25 |
26 | | ip |
27 | state |
28 |
29 | {rows}
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | mod builtin;
5 | mod command_line;
6 | mod console;
7 | mod dll;
8 | mod env;
9 | pub mod file;
10 | mod ini;
11 | mod init;
12 | mod libc;
13 | pub mod loader;
14 | mod memory;
15 | mod misc;
16 | mod nls;
17 | mod pipe;
18 | mod process;
19 | mod resource;
20 | mod state;
21 | mod sync;
22 | pub mod thread;
23 | mod time;
24 |
25 | pub use builtin::DLL;
26 |
27 | pub use file::HFILE;
28 | pub use init::peb_mut;
29 | pub use misc::SECURITY_ATTRIBUTES;
30 | pub use process::CURRENT_PROCESS_HANDLE;
31 | pub use state::{KernelObject, KernelObjectsMethods, State, get_state};
32 | pub use sync::{event::HEVENT, wait::wait_for_events};
33 | pub use thread::{Thread, create_thread, current_thread, teb_mut};
34 | pub use time::{FILETIME, Sleep};
35 |
--------------------------------------------------------------------------------
/exe/callback/callback.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const windows = std.os.windows;
3 |
4 | // This declares a function named `retrowin32_callback1` exists in `retrowin32.dll`,
5 | // which is true in the emulator enivronment because the emulator exposes this function
6 | // just for this program. See win32/lib/ for related support files.
7 | pub extern "retrowin32_test" fn retrowin32_test_callback1(
8 | func: *const fn (u32) callconv(.Stdcall) u32,
9 | data: u32,
10 | ) callconv(windows.WINAPI) u32;
11 |
12 | fn callback0(data: u32) callconv(windows.WINAPI) u32 {
13 | std.debug.print("callback0 invoked: {x}\n", .{data});
14 | return 0x4567;
15 | }
16 |
17 | pub fn main() !void {
18 | const ret = retrowin32_test_callback1(callback0, 0x1234);
19 | std.debug.print("retrowin32_callback1 returned: {x}\n", .{ret});
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/ops.yml:
--------------------------------------------------------------------------------
1 | name: exe/ops test suite
2 | on:
3 | push:
4 | paths:
5 | - 'exe/ops/ops.exe'
6 | - 'exe/ops/out.txt'
7 | - '.github/workflows/ops.yml'
8 | branches-ignore:
9 | - main
10 | - pages
11 |
12 | defaults:
13 | run:
14 | shell: cmd
15 |
16 | jobs:
17 | win:
18 | runs-on: windows-2022
19 | steps:
20 | - uses: actions/checkout@v3
21 | - name: Run
22 | run: |
23 | cd exe\ops
24 | ops > new.txt
25 | diff --strip-trailing-cr -u out.txt new.txt
26 | move new.txt out.txt
27 |
28 | - name: Add results to PR
29 | run: |
30 | git config user.name "GitHub action"
31 | git config user.email "bot@github.com"
32 | git add exe
33 | git commit -m "update from CI"
34 | git push
35 |
--------------------------------------------------------------------------------
/win32/dll/version/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 |
3 | mod builtin;
4 |
5 | pub use builtin::DLL;
6 |
7 | use win32_system::System;
8 |
9 | #[win32_derive::dllexport]
10 | pub fn GetFileVersionInfoSizeA(
11 | sys: &dyn System,
12 | lptstrFilename: Option<&str>,
13 | lpdwHandle: Option<&mut u32>,
14 | ) -> u32 {
15 | 0 // TODO
16 | }
17 |
18 | #[win32_derive::dllexport]
19 | pub fn GetFileVersionInfoA(
20 | sys: &dyn System,
21 | lptstrFilename: Option<&str>,
22 | dwHandle: u32,
23 | dwLen: u32,
24 | lpData: u32,
25 | ) -> bool {
26 | false // fail
27 | }
28 |
29 | #[win32_derive::dllexport]
30 | pub fn VerQueryValueA(
31 | sys: &dyn System,
32 | pBlock: u32,
33 | lpSubBlock: Option<&str>,
34 | lplpBuffer: u32,
35 | puLen: Option<&mut u32>,
36 | ) -> bool {
37 | false // fail
38 | }
39 |
--------------------------------------------------------------------------------
/win32/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod builtin_dlls;
2 | mod machine;
3 | mod segments;
4 | pub mod shims;
5 |
6 | #[cfg(feature = "x86-emu")]
7 | mod machine_emu;
8 |
9 | #[cfg(feature = "x86-emu")]
10 | pub use x86;
11 |
12 | #[cfg(all(feature = "x86-64", not(target_os = "linux")))]
13 | mod ldt;
14 | #[cfg(feature = "x86-64")]
15 | mod machine_raw;
16 | #[cfg(feature = "x86-64")]
17 | mod shims_raw;
18 |
19 | #[cfg(feature = "x86-unicorn")]
20 | mod machine_unicorn;
21 |
22 | #[cfg(feature = "x86-unicorn")]
23 | mod gdt;
24 |
25 | pub use builtin_ddraw as ddraw; // exposed so debugger can poke at internal state
26 | pub use builtin_kernel32 as kernel32;
27 | pub use kernel32::loader::Module;
28 | pub use machine::{Machine, Status};
29 | pub use win32_system::{host, memory::LOWEST_ADDRESS, trace};
30 | pub use win32_winapi::{ERROR, RECT, UnixPath, WindowsPath, WindowsPathBuf};
31 |
--------------------------------------------------------------------------------
/win32/winapi/src/com.rs:
--------------------------------------------------------------------------------
1 | #[allow(non_snake_case)]
2 | #[repr(C)]
3 | #[derive(PartialEq)]
4 | pub struct GUID(pub (u32, u16, u16, [u8; 8]));
5 | unsafe impl memory::Pod for GUID {}
6 |
7 | impl std::fmt::Debug for GUID {
8 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9 | write!(
10 | f,
11 | "{:08x}-{:04x}-{:04x}-{:04x}-",
12 | self.0.0,
13 | self.0.1,
14 | self.0.2,
15 | u16::from_le_bytes(self.0.3[..2].try_into().unwrap())
16 | )?;
17 | for b in &self.0.3[2..] {
18 | write!(f, "{:02x}", b)?;
19 | }
20 | Ok(())
21 | }
22 | }
23 |
24 | #[macro_export]
25 | macro_rules! vtable {
26 | ($($fn:ident: $impl:tt,)*) => {
27 | // macro is parsed by win32-derive codegen
28 | };
29 | }
30 |
31 | pub use vtable;
32 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/sync/once.rs:
--------------------------------------------------------------------------------
1 | use memory::Pod;
2 | use win32_system::System;
3 |
4 | #[repr(C)]
5 | #[derive(Debug)]
6 | pub struct INIT_ONCE {
7 | ptr: u32,
8 | }
9 | unsafe impl Pod for INIT_ONCE {}
10 |
11 | #[win32_derive::dllexport]
12 | pub fn InitOnceBeginInitialize(
13 | sys: &dyn System,
14 | lpInitOnce: Option<&mut INIT_ONCE>,
15 | dwFlags: u32,
16 | fPending: Option<&mut u32>,
17 | lpContext: u32,
18 | ) -> bool {
19 | if dwFlags != 0 {
20 | todo!();
21 | }
22 | *fPending.unwrap() = 1;
23 | true
24 | }
25 |
26 | #[win32_derive::dllexport]
27 | pub fn InitOnceComplete(
28 | sys: &dyn System,
29 | lpInitOnce: Option<&mut INIT_ONCE>,
30 | dwFlags: u32,
31 | lpContext: u32,
32 | ) -> bool {
33 | if dwFlags != 0 {
34 | todo!();
35 | }
36 | lpInitOnce.unwrap().ptr = lpContext;
37 | true
38 | }
39 |
--------------------------------------------------------------------------------
/misc/dump-fn.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Dumps assembly of a given function in a Mac binary.
4 | # See also lldb-dump-fn.sh, possibly better output.
5 |
6 | set -e
7 |
8 | path="$1"
9 | if [[ ! $path ]]; then
10 | echo "usage: $0 path/to/binary [filter] [index]"
11 | exit 1
12 | fi
13 |
14 | nm() {
15 | llvm-nm "$@" | cut -w -f3 | grep -v '^$' | uniq
16 | }
17 |
18 | filter="$2"
19 | if [[ ! $filter ]]; then
20 | nm "$path"
21 | exit 0
22 | fi
23 |
24 | index="$3"
25 | if [[ ! $index ]]; then
26 | i=1
27 | for sym in $(nm "$path" | grep "$filter"); do
28 | demangled=$(c++filt "$sym")
29 | echo "$i $sym ($demangled)"
30 | i=$((i + 1))
31 | if ((i > 10)); then
32 | break
33 | fi
34 | done
35 | exit 0
36 | fi
37 |
38 | sym=$(nm "$path" | grep "$filter" | sed "${index}q;d")
39 | llvm-otool -tV -p "$sym" "$path" | c++filt
40 |
--------------------------------------------------------------------------------
/memory/src/boxmem.rs:
--------------------------------------------------------------------------------
1 | use crate::Mem;
2 |
3 | // TODO: Unicorn needs this to be Pin.
4 | // In theory x86-emu could work with it not being pinned, not sure.
5 | // Maybe make a different impl?
6 | pub struct BoxMem(Box<[u8]>);
7 |
8 | impl BoxMem {
9 | pub fn new(size: usize) -> Self {
10 | let mut buf = Vec::::with_capacity(size);
11 | unsafe {
12 | buf.set_len(size);
13 | }
14 | Self(buf.into_boxed_slice())
15 | }
16 |
17 | // TODO: we can support growing under wasm by using a custom allocator that
18 | // ensures this is the last thing in the heap.
19 | // pub fn grow();
20 |
21 | pub fn len(&self) -> u32 {
22 | self.0.len() as u32
23 | }
24 |
25 | pub fn mem(&self) -> Mem<'_> {
26 | Mem::from_slice(&self.0)
27 | }
28 |
29 | pub fn as_ptr(&self) -> *const u8 {
30 | self.0.as_ptr()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/cli/build-linux-64.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Builds retrowin32 binary as a x86-64 Linux executable
4 | # using the CPU's existing support for 32-bit code.
5 | #
6 | # The binary needs particular flags and layout for this to work.
7 |
8 | set -e
9 |
10 | linker_args="--image-base=0x7f000000"
11 | # It appears the rest gets laid out immediately after.
12 | # All we really care about is it not being earlier.
13 | #linker_args="$linker_args -Trodata-segment=0x7f400000"
14 | link_flag="-C link_arg=-Wl"
15 | for arg in $linker_args; do
16 | link_flag="$link_flag,$arg"
17 | done
18 | export RUSTFLAGS="-C panic=abort -C relocation-model=static $link_flag"
19 |
20 | # Note: explicitly passing --target here causes Rust to obey RUSTFLAGS
21 | # only for the target, not the host (e.g. proc macros), which is the
22 | # behavior we need.
23 | exec cargo build --target x86_64-unknown-linux-gnu -p retrowin32 --no-default-features --features x86-64
24 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/sync/srw_lock.rs:
--------------------------------------------------------------------------------
1 | use memory::Pod;
2 | use win32_system::System;
3 |
4 | #[repr(C)]
5 | #[derive(Debug)]
6 | pub struct SRWLOCK {
7 | ptr: u32,
8 | }
9 | unsafe impl Pod for SRWLOCK {}
10 |
11 | #[win32_derive::dllexport]
12 | pub fn AcquireSRWLockShared(sys: &dyn System, SRWLock: Option<&mut SRWLOCK>) -> u32 {
13 | 0
14 | }
15 |
16 | #[win32_derive::dllexport]
17 | pub fn ReleaseSRWLockShared(sys: &dyn System, SRWLock: Option<&mut SRWLOCK>) -> u32 {
18 | 0
19 | }
20 |
21 | #[win32_derive::dllexport]
22 | pub fn AcquireSRWLockExclusive(sys: &dyn System, SRWLock: Option<&mut SRWLOCK>) -> u32 {
23 | 0
24 | }
25 |
26 | #[win32_derive::dllexport]
27 | pub fn TryAcquireSRWLockExclusive(sys: &dyn System, SRWLock: Option<&mut SRWLOCK>) -> u32 {
28 | 0
29 | }
30 |
31 | #[win32_derive::dllexport]
32 | pub fn ReleaseSRWLockExclusive(sys: &dyn System, SRWLock: Option<&mut SRWLOCK>) -> u32 {
33 | 0
34 | }
35 |
--------------------------------------------------------------------------------
/win32/dll/ole32/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | mod builtin;
5 |
6 | pub use builtin::DLL;
7 |
8 | use win32_system::System;
9 | use win32_winapi::HRESULT;
10 |
11 | #[win32_derive::dllexport]
12 | pub fn OleInitialize(sys: &dyn System, _pvReserved: u32) -> u32 {
13 | 0
14 | }
15 |
16 | #[win32_derive::dllexport]
17 | pub fn CoInitialize(sys: &dyn System, pvReserved: u32) -> HRESULT {
18 | todo!();
19 | }
20 |
21 | #[win32_derive::dllexport]
22 | pub fn CoInitializeEx(sys: &dyn System, pvReserved: Option<&mut u32>, dwCoInit: u32) -> u32 {
23 | 0 // ok
24 | }
25 |
26 | #[win32_derive::dllexport]
27 | pub fn CoUninitialize(sys: &dyn System) {}
28 |
29 | #[win32_derive::dllexport]
30 | pub fn CoCreateInstance(
31 | sys: &dyn System,
32 | rclsid: u32,
33 | pUnkOuter: u32,
34 | dwClsContext: u32,
35 | riid: u32,
36 | ppv: u32,
37 | ) -> HRESULT {
38 | todo!();
39 | }
40 |
--------------------------------------------------------------------------------
/cli/src/logging.rs:
--------------------------------------------------------------------------------
1 | //! Hook up the log crate to print to stdout.
2 |
3 | static LOGGER: Logger = Logger;
4 | struct Logger;
5 |
6 | impl log::Log for Logger {
7 | fn enabled(&self, _metadata: &log::Metadata) -> bool {
8 | true
9 | }
10 |
11 | fn flush(&self) {}
12 |
13 | fn log(&self, record: &log::Record) {
14 | let level = match record.level() {
15 | log::Level::Error => "ERROR",
16 | log::Level::Warn => "WARN",
17 | log::Level::Info => "INFO",
18 | log::Level::Trace => "TRACE",
19 | log::Level::Debug => "DEBUG",
20 | };
21 | eprintln!(
22 | "{level} {}:{} {}",
23 | record.file().unwrap_or(""),
24 | record.line().unwrap_or(0),
25 | record.args()
26 | );
27 | }
28 | }
29 |
30 | pub fn init(max_level: log::LevelFilter) {
31 | log::set_logger(&LOGGER).unwrap();
32 | log::set_max_level(max_level);
33 | }
34 |
--------------------------------------------------------------------------------
/doc/build_setup.md:
--------------------------------------------------------------------------------
1 | # Build setup
2 |
3 | To build retrowin32 you need [the Rust toolchain](https://rustup.rs/).
4 |
5 | If running graphical programs locally, you will need
6 | [SDL](https://www.libsdl.org/) (see below for setup).
7 |
8 | If you are building the web-based retrowin32, see
9 | [the web/ docs](../web/README.md).
10 |
11 | If you're making Windows API changes or see build errors about missing
12 | `clang-cl`, you need
13 | [the Clang toolchain](https://releases.llvm.org/download.html). See
14 | [win32/README.md](../win32/README.md).
15 |
16 | If you are building the various helper test programs found under `exe/`, see the
17 | [README there](../exe/README.md).
18 |
19 | ## SDL
20 |
21 | Mac:
22 |
23 | ```
24 | $ brew install sdl2
25 | $ export LIBRARY_PATH="$LIBRARY_PATH:$(brew --prefix)/lib"
26 | ```
27 |
28 | (If targeting Rosetta you need an x86 SDL, see [x86-64 docs](x86-64.md).)
29 |
30 | Debian/Ubuntu:
31 |
32 | ```
33 | $ sudo apt install libsdl2-dev
34 | ```
35 |
--------------------------------------------------------------------------------
/web/glue/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "glue"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 |
9 | [dependencies]
10 | log = { workspace = true }
11 | win32 = { workspace = true, features = ["wasm"] }
12 | x86 = { workspace = true, features = ["wasm"] }
13 |
14 | anyhow = "1.0"
15 | chrono = { workspace = true }
16 | js-sys = "0.3.69"
17 | serde = "1.0"
18 | serde_json = "1.0"
19 | tsify = { workspace = true }
20 | wasm-bindgen = { workspace = true }
21 |
22 | # [dependencies.wasm-bindgen]
23 | # features = ["xxx_debug_only_print_generated_code"]
24 |
25 | [dependencies.web-sys]
26 | version = "0.3.69"
27 | features = [
28 | "CanvasRenderingContext2d",
29 | "ImageData",
30 | "Event",
31 | "HtmlCanvasElement",
32 | "MouseEvent",
33 | "Performance",
34 | ]
35 |
36 | [features]
37 | # We always need x86-emu to build this, but leaving it off by default
38 | # lets rust-analyzer analyze the project in non-x86-emu modes.
39 | x86-emu = ["win32/x86-emu"]
40 |
--------------------------------------------------------------------------------
/win32/system/src/wait.rs:
--------------------------------------------------------------------------------
1 | use crate::host::Host;
2 |
3 | const INFINITE: u32 = 0xffff_ffff;
4 |
5 | pub enum Wait {
6 | None,
7 | Forever,
8 | // In absolute time as measured by host.ticks().
9 | Millis(u32),
10 | }
11 |
12 | impl Wait {
13 | pub fn from_millis(host: &dyn Host, ms: u32) -> Self {
14 | if ms == 0 {
15 | Wait::None
16 | } else if ms == INFINITE {
17 | Wait::Forever
18 | } else {
19 | Wait::Millis(host.ticks() + ms)
20 | }
21 | }
22 | }
23 |
24 | pub enum WaitResult {
25 | Object(u32),
26 | Timeout,
27 | // Abandoned
28 | }
29 |
30 | const WAIT_OBJECT_0: u32 = 0;
31 | //const WAIT_ABANDONED_0: u32 = 0x80;
32 | const WAIT_TIMEOUT: u32 = 0x102;
33 |
34 | impl WaitResult {
35 | pub fn to_code(&self) -> u32 {
36 | match self {
37 | WaitResult::Object(index) => WAIT_OBJECT_0 + index,
38 | WaitResult::Timeout => WAIT_TIMEOUT,
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/file/stdio.rs:
--------------------------------------------------------------------------------
1 | use super::HFILE;
2 | use win32_system::System;
3 |
4 | #[derive(Debug, win32_derive::TryFromEnum)]
5 | pub enum STD {
6 | INPUT_HANDLE = -10,
7 | OUTPUT_HANDLE = -11,
8 | ERROR_HANDLE = -12,
9 | }
10 |
11 | // For now, a magic variable that makes it easier to spot.
12 | pub const STDIN_HFILE: HFILE = HFILE::from_raw(0xF11E_0100);
13 | pub const STDOUT_HFILE: HFILE = HFILE::from_raw(0xF11E_0101);
14 | pub const STDERR_HFILE: HFILE = HFILE::from_raw(0xF11E_0102);
15 |
16 | #[win32_derive::dllexport]
17 | pub fn GetStdHandle(sys: &dyn System, nStdHandle: Result) -> HFILE {
18 | match nStdHandle {
19 | Ok(STD::INPUT_HANDLE) => STDIN_HFILE,
20 | Ok(STD::OUTPUT_HANDLE) => STDOUT_HFILE,
21 | Ok(STD::ERROR_HANDLE) => STDERR_HFILE,
22 | _ => HFILE::invalid(),
23 | }
24 | }
25 |
26 | #[win32_derive::dllexport]
27 | pub fn SetStdHandle(sys: &dyn System, nStdHandle: Result, hHandle: u32) -> bool {
28 | true // success
29 | }
30 |
--------------------------------------------------------------------------------
/win32/dll/winmm/src/mci.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 | use win32_winapi::{HWND, calling_convention::ArrayOut};
3 |
4 | const MCIERR_BASE: u32 = 256;
5 | const MCIERR_DRIVER: u32 = MCIERR_BASE + 22;
6 |
7 | #[win32_derive::dllexport]
8 | pub fn mciGetErrorStringA(
9 | sys: &dyn System,
10 | mcierr: u32,
11 | pszText: Option<&str>,
12 | cchText: u32,
13 | ) -> bool {
14 | todo!()
15 | }
16 |
17 | #[win32_derive::dllexport]
18 | pub fn mciSendStringA(
19 | sys: &dyn System,
20 | lpstrCommand: Option<&str>,
21 | lpstrReturnString: ArrayOut,
22 | hwndCallback: HWND,
23 | ) -> u32 {
24 | let cmd = lpstrCommand.unwrap();
25 | log::info!("mci: {:?}", cmd);
26 | if cmd.find("notify").is_some() {
27 | todo!("mci notify not implemented");
28 | }
29 | 0 // success
30 | }
31 |
32 | #[win32_derive::dllexport]
33 | pub fn mciSendCommandA(
34 | sys: &dyn System,
35 | mciId: u32,
36 | uMsg: u32,
37 | dwParam1: u32,
38 | dwParam2: u32,
39 | ) -> u32 {
40 | MCIERR_DRIVER
41 | }
42 |
--------------------------------------------------------------------------------
/web/debugger/tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as preact from 'preact';
2 |
3 | namespace Tabs {
4 | export interface Props {
5 | style: preact.JSX.CSSProperties;
6 | tabs: { [name: string]: () => preact.ComponentChild };
7 | selected: string;
8 | switchTab: (name: string) => void;
9 | }
10 | }
11 | export class Tabs extends preact.Component {
12 | render() {
13 | const { style, tabs, selected, switchTab } = this.props;
14 | const content = tabs[selected]();
15 | return (
16 |
17 |
18 | |
19 | {Object.keys(tabs).map((name) => {
20 | let button = switchTab(name)}>{name};
21 | if (name === selected) {
22 | button = {button};
23 | }
24 | return <> {button} |>;
25 | })}
26 |
27 | {content}
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/win32/dll/winmm/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_upper_case_globals)]
3 | #![allow(non_camel_case_types)]
4 |
5 | mod builtin;
6 | mod joy;
7 | mod mci;
8 | mod midi;
9 | mod misc;
10 | mod mixer;
11 | mod time;
12 | mod wave;
13 |
14 | pub use builtin::DLL;
15 | use std::cell::RefMut;
16 | use time::TimeThread;
17 | use wave::WaveState;
18 | use win32_system::{System, generic_get_state};
19 | use win32_winapi::calling_convention::ABIReturn;
20 |
21 | #[derive(Copy, Clone, Debug)]
22 | pub enum MMRESULT {
23 | MMSYSERR_NOERROR = 0,
24 | MMSYSERR_NOTENABLED = 3,
25 | }
26 |
27 | impl Into for MMRESULT {
28 | fn into(self) -> ABIReturn {
29 | (self as u32).into()
30 | }
31 | }
32 |
33 | // TODO: we could have separate state for wave and time.
34 | #[derive(Default)]
35 | pub struct State {
36 | pub audio_enabled: bool,
37 | pub time_thread: Option,
38 | pub wave: WaveState,
39 | }
40 |
41 | #[inline]
42 | pub fn get_state(sys: &dyn System) -> RefMut<'_, State> {
43 | generic_get_state::(sys)
44 | }
45 |
--------------------------------------------------------------------------------
/x86/src/ops/math/int.rs:
--------------------------------------------------------------------------------
1 | /// This trait is implemented for u32/u16/u8 and lets us write operations generically
2 | /// over all those bit sizes.
3 | pub(crate) trait Int: num_traits::PrimInt {
4 | fn as_usize(self) -> usize;
5 | fn bits() -> usize;
6 |
7 | fn high_bit(&self) -> Self {
8 | *self >> (Self::bits() - 1)
9 | }
10 |
11 | fn low_byte(&self) -> u8 {
12 | self.as_usize() as u8
13 | }
14 | }
15 |
16 | impl Int for u64 {
17 | fn as_usize(self) -> usize {
18 | unimplemented!()
19 | }
20 | fn bits() -> usize {
21 | 64
22 | }
23 | }
24 |
25 | impl Int for u32 {
26 | fn as_usize(self) -> usize {
27 | self as usize
28 | }
29 | fn bits() -> usize {
30 | 32
31 | }
32 | }
33 |
34 | impl Int for u16 {
35 | fn as_usize(self) -> usize {
36 | self as usize
37 | }
38 | fn bits() -> usize {
39 | 16
40 | }
41 | }
42 |
43 | impl Int for u8 {
44 | fn as_usize(self) -> usize {
45 | self as usize
46 | }
47 | fn bits() -> usize {
48 | 8
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/sync/critical_section.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | #[win32_derive::dllexport]
4 | pub fn InitializeCriticalSection(sys: &dyn System, lpCriticalSection: u32) {}
5 |
6 | #[win32_derive::dllexport]
7 | pub fn InitializeCriticalSectionEx(
8 | sys: &dyn System,
9 | lpCriticalSection: u32,
10 | dwSpinCount: u32,
11 | flags: u32,
12 | ) -> bool {
13 | true
14 | }
15 |
16 | #[win32_derive::dllexport]
17 | pub fn InitializeCriticalSectionAndSpinCount(
18 | sys: &dyn System,
19 | lpCriticalSection: u32,
20 | dwSpinCount: u32,
21 | ) -> bool {
22 | // "On single-processor systems, the spin count is ignored and the critical section spin count is set to 0 (zero)."
23 | // "This function always succeeds and returns a nonzero value."
24 | true
25 | }
26 |
27 | #[win32_derive::dllexport]
28 | pub fn DeleteCriticalSection(sys: &dyn System, lpCriticalSection: u32) {}
29 |
30 | #[win32_derive::dllexport]
31 | pub fn EnterCriticalSection(sys: &dyn System, lpCriticalSection: u32) {}
32 |
33 | #[win32_derive::dllexport]
34 | pub fn LeaveCriticalSection(sys: &dyn System, lpCriticalSection: u32) {}
35 |
--------------------------------------------------------------------------------
/win32/winapi/src/types.rs:
--------------------------------------------------------------------------------
1 | //! Types exposed by the Windows API.
2 |
3 | use crate::handle::HANDLE;
4 | use std::borrow::Cow;
5 |
6 | pub use std::ffi::CStr;
7 |
8 | pub trait StrExt<'a> {
9 | fn to_str_or_warn(&'a self) -> Cow<'a, str>;
10 | }
11 |
12 | impl<'a> StrExt<'a> for &'a CStr {
13 | fn to_str_or_warn(&'a self) -> Cow<'a, str> {
14 | match self.to_str() {
15 | Ok(str) => Cow::Borrowed(str),
16 | Err(_err) => {
17 | log::warn!("[TODO: {:?}]", self);
18 | Cow::Owned(format!("TODO: convert {:?} using code page", self))
19 | }
20 | }
21 | }
22 | }
23 |
24 | pub type WORD = u16;
25 | pub type DWORD = u32;
26 | pub type LPARAM = u32;
27 |
28 | pub type HRESULT = u32;
29 |
30 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
31 | pub struct HWNDT;
32 | pub type HWND = HANDLE;
33 |
34 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
35 | pub struct HMODULET;
36 | /// HMODULE is the address of the loaded PE image.
37 | // (BASS.dll calls LoadLibrary and reads the PE header found at the returned address.)
38 | pub type HMODULE = HANDLE;
39 |
--------------------------------------------------------------------------------
/win32/system/src/event.rs:
--------------------------------------------------------------------------------
1 | use std::sync::{Arc, Mutex};
2 |
3 | pub type ArcEvent = Arc;
4 |
5 | /// Event objects, used for synchronization between Windows threads.
6 | //
7 | // In the emulator there are no real threads, but we still use event objects
8 | // for signaling all the way up to the Host layer, which itself may use threads.
9 | // (In particular SDL audio notifications come in on a separate thread.)
10 | // So internally, Events use Mutex/Arc instead of Cell/Rc.
11 | // Under wasm Mutex/Arc are implemented as Cell/Rc anyway:
12 | // https://github.com/rust-lang/rust/issues/77839
13 | pub struct Event {
14 | pub name: Option,
15 | pub manual_reset: bool,
16 | pub signaled: Mutex,
17 | }
18 |
19 | impl Event {
20 | pub fn new(name: Option, manual_reset: bool, signaled: bool) -> Arc {
21 | Arc::new(Self {
22 | name,
23 | manual_reset,
24 | signaled: Mutex::new(signaled),
25 | })
26 | }
27 |
28 | pub fn signal(&self) {
29 | *self.signaled.lock().unwrap() = true;
30 | // TODO: wake up waiting host threads somehow.
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/web/win2k.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --gray: #d6d3ce;
3 | }
4 |
5 | body {
6 | font-family: tahoma, sans-serif;
7 | font-size: 12px;
8 | background: #3a6ea5;
9 | }
10 |
11 | button {
12 | font-family: inherit;
13 | font-size: inherit;
14 | }
15 |
16 | button,
17 | .panel {
18 | background: var(--gray);
19 | border: outset 2px;
20 | image-rendering: pixelated;
21 | border-image: url("9p-inactive.png") 2;
22 | }
23 |
24 | button:active {
25 | border: solid 2px #404040;
26 | border-image: url("9p-active.png") 2;
27 | }
28 |
29 | [popover] {
30 | margin: 0;
31 | padding: 0;
32 | border: 0;
33 | position: absolute;
34 | position-area: span-right span-bottom;
35 | }
36 |
37 | .window {
38 | font-size: 11px;
39 | position: absolute;
40 | min-width: 32px;
41 | min-height: 32px;
42 | box-shadow: 2px 2px 16px 4px rgba(0, 0, 0, 0.3);
43 | border: 2px outset;
44 | cursor: default;
45 | background: #c3c3c3;
46 | }
47 |
48 | .window .titlebar {
49 | font-weight: bold;
50 | background-image: linear-gradient(to right, #082468, #a0c8f0);
51 | color: white;
52 | padding: 2px 16px;
53 | }
--------------------------------------------------------------------------------
/doc/how_to.md:
--------------------------------------------------------------------------------
1 | # How to contribute to retrowin32
2 |
3 | In this doc I hope to collect steps and examples of how to extend or improve
4 | retrowin32.
5 |
6 | ## How to add a new builtin DLL
7 |
8 | See commit 38c7f18b017914c87cd10444294184be981b5a0d, which added a stub DLL.
9 |
10 | ## How to add a new win32 function
11 |
12 | Given some missing function like `TrackMouseEvent`, you can run:
13 |
14 | ```
15 | $ (cd win32/extract && cargo run TrackMouseEvent)
16 | ```
17 |
18 | and that program will try to generate the proper function definition from the
19 | metadata published by Microsoft. It also works for structs. Be careful about
20 | mistakes; examine the output for errors.
21 |
22 | Whenever you add a function or modify a function's parameters, you must run
23 | `cargo minibuild` again to regenerate the related code.
24 |
25 | See commit 9f436905e650eb7c4e996fd799eb260f1238356d for an example.
26 |
27 | The generated function will contain a `todo!()`, which means retrowin32 will
28 | panic once it's called. You can run with the flag `--win32-trace "^*"` (note the
29 | `^`) to log function parameters as they are called to see what parameters your
30 | new function is getting called with.
31 |
--------------------------------------------------------------------------------
/win32/winapi/src/point.rs:
--------------------------------------------------------------------------------
1 | use memory::Extensions;
2 |
3 | #[repr(C, packed)]
4 | #[derive(Copy, Clone, Debug, Default)]
5 | pub struct POINT {
6 | pub x: i32,
7 | pub y: i32,
8 | }
9 | unsafe impl memory::Pod for POINT {}
10 |
11 | impl POINT {
12 | pub fn add(&self, delta: POINT) -> POINT {
13 | POINT {
14 | x: self.x + delta.x,
15 | y: self.y + delta.y,
16 | }
17 | }
18 |
19 | pub fn sub(&self, delta: POINT) -> POINT {
20 | POINT {
21 | x: self.x - delta.x,
22 | y: self.y - delta.y,
23 | }
24 | }
25 |
26 | pub fn mul(&self, o: POINT) -> POINT {
27 | POINT {
28 | x: self.x * o.x,
29 | y: self.y * o.y,
30 | }
31 | }
32 |
33 | pub fn div(&self, o: POINT) -> POINT {
34 | POINT {
35 | x: self.x / o.x,
36 | y: self.y / o.y,
37 | }
38 | }
39 | }
40 |
41 | impl<'a> crate::calling_convention::FromStack<'a> for POINT {
42 | fn from_stack(mem: memory::Mem<'a>, sp: u32) -> Self {
43 | let x = mem.get_pod::(sp);
44 | let y = mem.get_pod::(sp + 4);
45 | POINT { x, y }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/web/glue/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # cargo install wasm-bindgen-cli
4 | # cargo install wasm-opt
5 | # target_dir="$(cargo metadata --format-version 1 --no-deps | jq -r .target_directory)"
6 |
7 | set -e
8 |
9 | cd "$(dirname "$0")"
10 |
11 | profile="${profile:-release}"
12 |
13 | case $profile in
14 | debug)
15 | cargo build -F x86-emu --target wasm32-unknown-unknown --profile dev
16 | wasm-bindgen --out-dir pkg --typescript --target web --reference-types \
17 | "../../target/wasm32-unknown-unknown/debug/glue.wasm"
18 | ;;
19 | release)
20 | cargo build -F x86-emu --target wasm32-unknown-unknown --profile release
21 | wasm-bindgen --out-dir pkg --typescript --target web --reference-types \
22 | "../../target/wasm32-unknown-unknown/$profile/glue.wasm"
23 | ;;
24 | lto)
25 | cargo build -F x86-emu --target wasm32-unknown-unknown --profile lto
26 | wasm-bindgen --out-dir pkg --typescript --target web --reference-types \
27 | "../../target/wasm32-unknown-unknown/$profile/glue.wasm"
28 | wasm-opt -O --enable-reference-types pkg/glue_bg.wasm -o pkg/glue_bg.wasm-opt
29 | mv pkg/glue_bg.wasm-opt pkg/glue_bg.wasm
30 | ;;
31 | *)
32 | echo "error: profile=debug or release"
33 | exit 1
34 | esac
35 |
--------------------------------------------------------------------------------
/web/debugger/stack.tsx:
--------------------------------------------------------------------------------
1 | import * as preact from 'preact';
2 | import { Emulator, Register } from '../glue/pkg/glue';
3 | import { Labels } from './labels';
4 | import { MemoryView, Number } from './memory';
5 |
6 | namespace Stack {
7 | export interface Props extends MemoryView {
8 | emu: Emulator;
9 | labels: Labels;
10 | }
11 | }
12 | export class Stack extends preact.Component {
13 | render() {
14 | const { emu } = this.props;
15 | const esp = emu.cpu().get(Register.ESP);
16 | if (esp === 0) {
17 | return ;
18 | }
19 | const memory = emu.memory();
20 | const rows = [];
21 | for (let addr = esp - 0x10; addr < esp + 0x20; addr += 4) {
22 | const value = memory.getUint32(addr, true);
23 | const label = this.props.labels.get(value);
24 | let row = (
25 |
26 | {addr}
27 |
28 | {value}
29 |
30 | {label}
31 |
32 | );
33 | if (addr === esp) {
34 | row = {row};
35 | }
36 | rows.push(row);
37 | }
38 | return {rows};
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # retrowin32 for web
2 |
3 | retrowin32 can build the emulator as a WebAssembly bundle. There is a central
4 | `Host` abstraction (see [win32 docs](../win32/README.md)) that collects
5 | functions exposed by the hosting environment, and on web these mostly forward
6 | out to TypeScript implementations.
7 |
8 | (Why TypeScript? The edit-compile cycle and debugging experience of using
9 | TypeScript is currently a lot better than Rust.)
10 |
11 | There are two web frontends that share the emulator integration logic:
12 |
13 | 1. `run.html`/`run.tsx` is the smaller "run a program" UI
14 | 2. `debugger.html`/`debugger.tsx` implements the x86 debugger
15 |
16 | ## Code layout
17 |
18 | - `glue/` -- the Rust library that becomes wasm
19 | - `debugger/` -- the debugging UI
20 |
21 | ## The retrowin32 website
22 |
23 | The retrowin32 website is a mixture of files generated by the [appdb](../appdb)
24 | tools and some hand-written files which also live in this directory.
25 |
26 | The deployed website lives in the `pages` Git branch. The [deploy.sh](deploy.sh)
27 | script runs the build process for generated files, which includes the wasm
28 | bundle, the TypeScript, and the appdb. It has some comments about how it expects
29 | to be set up to run.
30 |
--------------------------------------------------------------------------------
/web/glue/src/log.rs:
--------------------------------------------------------------------------------
1 | //! Hooks up the log crate (log::info!() etc.) to forward to a JS-based host.
2 | //! This doesn't map directly to console.log etc. because we want to (eventually)
3 | //! surface these logs in the UI.
4 |
5 | use crate::emulator;
6 |
7 | struct JsLogger;
8 |
9 | const JS_LOGGER: JsLogger = JsLogger;
10 |
11 | impl log::Log for JsLogger {
12 | fn log(&self, record: &log::Record) {
13 | emulator::js_host().log(
14 | record.level() as u8,
15 | format!(
16 | "{}:{} {}",
17 | record.file().unwrap_or(""),
18 | record.line().unwrap_or(0),
19 | record.args()
20 | ),
21 | );
22 | }
23 |
24 | fn enabled(&self, _metadata: &log::Metadata) -> bool {
25 | true
26 | }
27 |
28 | fn flush(&self) {}
29 | }
30 |
31 | pub fn init() {
32 | log::set_logger(&JS_LOGGER).unwrap();
33 | log::set_max_level(log::LevelFilter::Debug);
34 | std::panic::set_hook(Box::new(move |info| {
35 | // Don't use log::error!() here, because that includes the current file
36 | // and line, which just points at the logging code.
37 | emulator::js_host().log(log::Level::Error as u8, format!("panic: {}", info));
38 | }));
39 | }
40 |
--------------------------------------------------------------------------------
/win32/system/src/dll.rs:
--------------------------------------------------------------------------------
1 | use crate::System;
2 | use win32_winapi::calling_convention::ABIReturn;
3 |
4 | pub type SyncHandler = unsafe fn(&mut dyn System, u32) -> ABIReturn;
5 | pub type AsyncHandler =
6 | unsafe fn(
7 | &mut dyn System,
8 | u32,
9 | ) -> std::pin::Pin + '_>>;
10 |
11 | #[derive(Debug, Clone, Copy)]
12 | pub enum Handler {
13 | Sync(SyncHandler),
14 | Async(AsyncHandler),
15 | }
16 |
17 | #[derive(Debug)]
18 | pub struct Shim {
19 | pub name: &'static str,
20 | pub func: Handler,
21 | }
22 |
23 | pub struct BuiltinDLL {
24 | pub file_name: &'static str,
25 | /// The xth function in the DLL represents a call to shims[x].
26 | pub shims: &'static [Shim],
27 | /// Raw bytes of generated .dll.
28 | pub raw: &'static [u8],
29 | }
30 |
31 | /// The result of resolving a DLL name, after string normalization and aliasing.
32 | pub enum DLLResolution {
33 | Builtin(&'static BuiltinDLL),
34 | External(String),
35 | }
36 |
37 | impl DLLResolution {
38 | pub fn name(&self) -> &str {
39 | match self {
40 | DLLResolution::Builtin(builtin) => builtin.file_name,
41 | DLLResolution::External(name) => name,
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/x86/src/ops/cpuid.rs:
--------------------------------------------------------------------------------
1 | use crate::CPU;
2 | use bitflags::bitflags;
3 | use iced_x86::{Instruction, Register};
4 | use memory::Mem;
5 |
6 | bitflags! {
7 | pub struct EDXFeatures: u32 {
8 | const MMX = 1 << 23;
9 | }
10 | }
11 |
12 | /// cpuid: CPU Identification
13 | pub fn cpuid(cpu: &mut CPU, _mem: Mem, _instr: &Instruction) {
14 | match cpu.regs.get32(Register::EAX) {
15 | 0 => {
16 | // basic information
17 | cpu.regs.set32(Register::EAX, /* Pentium */ 0x2);
18 | // "GenuineIntel"
19 | cpu.regs.set32(Register::EBX, u32::from_le_bytes(*b"Genu"));
20 | cpu.regs.set32(Register::EDX, u32::from_le_bytes(*b"ineI"));
21 | cpu.regs.set32(Register::ECX, u32::from_le_bytes(*b"ntel"));
22 | }
23 | 1 => {
24 | // CPUID_GETFEATURES
25 | // Just enough to convince heaven7 that we support MMX.
26 | cpu.regs.set32(Register::EAX, 0);
27 | cpu.regs.set32(Register::ECX, 0);
28 | cpu.regs.set32(Register::EDX, EDXFeatures::MMX.bits());
29 | }
30 | 0x8000_0000 => {
31 | // maximum extended function
32 | cpu.regs.set32(Register::EAX, 0);
33 | }
34 | mode => todo!("cpuid {mode:x}"),
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/win32/dll/ucrtbase/src/math.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | #[win32_derive::dllexport(cdecl)]
4 | pub fn __setusermatherr(sys: &mut dyn System, pf: u32) {
5 | todo!();
6 | }
7 |
8 | #[win32_derive::dllexport]
9 | pub const _adjust_fdiv: &'static str = "_adjust_fdiv";
10 |
11 | // ftol is unique(?) in that it does not follow the calling convention,
12 | // but rather pops a value from the FPU stack and returns it in edx:eax.
13 | // We just use x86 assembly for this.
14 | // TODO: implementations of this online typically set the RC flags of the
15 | // FPU control register to 11, round towards zero.
16 | #[win32_derive::dllexport(raw_asm)]
17 | pub const _ftol: &'static str = "
18 | sub esp, 8
19 | fistp qword ptr [esp]
20 | mov eax, dword ptr [esp]
21 | mov edx, dword ptr [esp + 4]
22 | ret 8
23 | ";
24 |
25 | #[win32_derive::dllexport(cdecl)]
26 | pub fn sqrt(sys: &mut dyn System, x: f64) -> f64 {
27 | x.sqrt()
28 | }
29 |
30 | #[win32_derive::dllexport(cdecl)]
31 | pub fn sin(sys: &mut dyn System, x: f64) -> f64 {
32 | x.sin()
33 | }
34 |
35 | #[win32_derive::dllexport(cdecl)]
36 | pub fn cos(sys: &mut dyn System, x: f64) -> f64 {
37 | x.cos()
38 | }
39 |
40 | #[win32_derive::dllexport(cdecl)]
41 | pub fn floor(sys: &mut dyn System, x: f64) -> f64 {
42 | x.floor()
43 | }
44 |
--------------------------------------------------------------------------------
/win32/dll/vcruntime140/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 |
3 | mod builtin;
4 |
5 | pub use builtin::DLL;
6 |
7 | use memory::{Extensions, ExtensionsMut};
8 | use win32_system::System;
9 |
10 | #[win32_derive::dllexport(cdecl)]
11 | pub fn memcpy(sys: &mut dyn System, dst: u32, src: u32, len: u32) -> u32 {
12 | // TODO: this probably violates Rust rules around aliasing, if callers expect memmove.
13 | sys.mem()
14 | .sub32_mut(dst, len)
15 | .copy_from_slice(sys.mem().sub32(src, len));
16 | dst
17 | }
18 |
19 | #[win32_derive::dllexport(cdecl)]
20 | pub fn memset(sys: &mut dyn System, dst: u32, val: u32, len: u32) -> u32 {
21 | sys.mem().sub32_mut(dst, len).fill(val as u8);
22 | dst
23 | }
24 |
25 | #[win32_derive::dllexport(cdecl)]
26 | pub fn memcmp(sys: &mut dyn System, lhs: u32, rhs: u32, len: u32) -> u32 {
27 | let left = sys.mem().sub32(lhs, len);
28 | let right = sys.mem().sub32(rhs, len);
29 | match left.cmp(right) {
30 | std::cmp::Ordering::Less => -1i32 as u32,
31 | std::cmp::Ordering::Equal => 0,
32 | std::cmp::Ordering::Greater => 1,
33 | }
34 | }
35 |
36 | #[win32_derive::dllexport(cdecl)]
37 | pub fn _CxxThrowException(sys: &dyn System, pExceptionObject: u32, pThrowInfo: u32) -> u32 {
38 | panic!("exception");
39 | }
40 |
--------------------------------------------------------------------------------
/win32/dll/winmm/src/mixer.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | use super::MMRESULT;
4 |
5 | pub type HMIXEROBJ = u32;
6 |
7 | #[win32_derive::dllexport]
8 | pub fn mixerOpen(
9 | sys: &dyn System,
10 | phmx: u32, //Option<&mut HMIXER>,
11 | uMxId: u32,
12 | dwCallback: u32,
13 | dwInstance: u32,
14 | fdwOpen: u32,
15 | ) -> MMRESULT {
16 | todo!();
17 | }
18 |
19 | #[win32_derive::dllexport]
20 | pub fn mixerGetLineControlsA(
21 | sys: &dyn System,
22 | hmxobj: u32,
23 | pmxlc: u32,
24 | fdwControls: u32,
25 | ) -> MMRESULT {
26 | todo!();
27 | }
28 |
29 | #[win32_derive::dllexport]
30 | pub fn mixerClose(sys: &dyn System, hmx: u32) -> MMRESULT {
31 | todo!();
32 | }
33 |
34 | #[win32_derive::dllexport]
35 | pub fn mixerSetControlDetails(
36 | sys: &dyn System,
37 | hmxobj: u32, //HMIXEROBJ,
38 | pmxcd: u32, //&MIXERCONTROLDETAILS,
39 | fdwDetails: u32,
40 | ) -> MMRESULT {
41 | todo!();
42 | }
43 |
44 | #[win32_derive::dllexport]
45 | pub fn mixerGetControlDetailsA(sys: &dyn System) -> MMRESULT {
46 | todo!()
47 | }
48 |
49 | #[win32_derive::dllexport]
50 | pub fn mixerGetLineInfoA(
51 | sys: &dyn System,
52 | hmxobj: HMIXEROBJ,
53 | pmxl: u32, //Option<&mut MIXERLINEA>,
54 | fdwInfo: u32,
55 | ) -> MMRESULT {
56 | todo!();
57 | }
58 |
--------------------------------------------------------------------------------
/win32/dll/ddraw/src/clipper.rs:
--------------------------------------------------------------------------------
1 | use super::types::*;
2 | use win32_system::System;
3 | use win32_winapi::{HWND, com::vtable};
4 |
5 | #[win32_derive::dllexport]
6 | pub fn DirectDrawCreateClipper(
7 | sys: &mut dyn System,
8 | dwFlags: u32,
9 | lplpDDClipper: Option<&mut u32>,
10 | pUnkOuter: u32,
11 | ) -> DD {
12 | assert!(dwFlags == 0);
13 | *lplpDDClipper.unwrap() = IDirectDrawClipper::new(sys);
14 | DD::OK
15 | }
16 |
17 | #[win32_derive::dllexport]
18 | pub mod IDirectDrawClipper {
19 | use super::*;
20 |
21 | vtable![
22 | QueryInterface: todo,
23 | AddRef: todo,
24 | Release: ok,
25 |
26 | GetClipList: todo,
27 | GetHWnd: todo,
28 | Initialize: todo,
29 | IsClipListChanged: todo,
30 | SetClipList: todo,
31 | SetHWnd: ok,
32 | ];
33 |
34 | pub fn new(sys: &mut dyn System) -> u32 {
35 | let vtable = sys.get_symbol("ddraw.dll", "IDirectDrawClipper");
36 | sys.memory().store(vtable)
37 | }
38 |
39 | #[win32_derive::dllexport]
40 | pub fn Release(sys: &dyn System, this: u32) -> u32 {
41 | 0 // TODO: return refcount?
42 | }
43 |
44 | #[win32_derive::dllexport]
45 | pub fn SetHWnd(sys: &dyn System, this: u32, unused: u32, hwnd: HWND) -> DD {
46 | DD::OK
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/win32/src/shims.rs:
--------------------------------------------------------------------------------
1 | //! "Shims" are my word for the mechanism for x86 -> retrowin32 (and back) calls.
2 | //!
3 | //! The win32_derive::dllexport attribute on our shim functions wraps them with
4 | //! a prologue/epilogue that does the required stack manipulation to read
5 | //! arguments off the x86 stack and transform them into Rust types, so the Rust
6 | //! functions can act as if they're just being called from Rust.
7 | //!
8 | //! There are three underlying implementations of Shims:
9 | //! 1. shims_emu.rs, which is used with the in-tree CPU emulator
10 | //! 2. shims_raw.rs, which is used when executing x86 natively
11 | //! 3. shims_unicorn.rs, which is used with the Unicorn CPU emulator
12 |
13 | use std::collections::HashMap;
14 | use win32_system::dll::Shim;
15 |
16 | #[derive(Default)]
17 | pub struct Shims {
18 | shims: HashMap>,
19 | }
20 |
21 | impl Shims {
22 | pub fn register(&mut self, addr: u32, shim: Result<&'static Shim, String>) {
23 | self.shims.insert(addr, shim);
24 | }
25 |
26 | pub fn get(&self, addr: u32) -> Result<&Shim, &str> {
27 | match self.shims.get(&addr) {
28 | Some(Ok(shim)) => Ok(shim),
29 | Some(Err(name)) => Err(name),
30 | None => panic!("unknown import reference at {:x}", addr),
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = [
4 | "cli",
5 | "minibuild",
6 | "pe",
7 | "web/glue",
8 | "win32/derive",
9 | "win32/extract",
10 | "win32/system",
11 | "win32/winapi",
12 | "win32/dll/advapi32",
13 | "win32/dll/bass",
14 | "win32/dll/comctl32",
15 | "win32/dll/ddraw",
16 | "win32/dll/dinput",
17 | "win32/dll/dsound",
18 | "win32/dll/gdi32",
19 | "win32/dll/kernel32",
20 | "win32/dll/ntdll",
21 | "win32/dll/ole32",
22 | "win32/dll/oleaut32",
23 | "win32/dll/retrowin32_test",
24 | "win32/dll/shlwapi",
25 | "win32/dll/ucrtbase",
26 | "win32/dll/user32",
27 | "win32/dll/vcruntime140",
28 | "win32/dll/version",
29 | "win32/dll/wininet",
30 | "win32/dll/winmm",
31 | ]
32 |
33 | [workspace.dependencies]
34 | memory = { path = "memory" }
35 | pe = { path = "pe" }
36 | win32 = { path = "win32" }
37 | win32-derive = { path = "win32/derive" }
38 | win32-system = { path = "win32/system" }
39 | win32-winapi = { path = "win32/winapi" }
40 | x86 = { path = "x86" }
41 |
42 | bitflags = { version = "2.9.0" }
43 | chrono = "0.4.38"
44 | log = "0.4"
45 | num-traits = "0.2"
46 | tsify = "0.4.1"
47 | # Must match version of wasm-bindgen-cli, with version pinned in CI.
48 | wasm-bindgen = "=0.2.100"
49 |
50 | [profile.release]
51 | debug = true
52 |
53 | [profile.lto]
54 | inherits = "release"
55 | debug = false
56 | lto = true
57 |
--------------------------------------------------------------------------------
/win32/winapi/src/rect.rs:
--------------------------------------------------------------------------------
1 | use crate::POINT;
2 |
3 | #[repr(C, packed)]
4 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
5 | pub struct RECT {
6 | pub left: i32,
7 | pub top: i32,
8 | pub right: i32,
9 | pub bottom: i32,
10 | }
11 | unsafe impl memory::Pod for RECT {}
12 |
13 | impl RECT {
14 | pub fn clip(&self, other: &RECT) -> RECT {
15 | RECT {
16 | left: self.left.max(other.left),
17 | top: self.top.max(other.top),
18 | right: self.right.min(other.right),
19 | bottom: self.bottom.min(other.bottom),
20 | }
21 | }
22 |
23 | pub fn origin(&self) -> POINT {
24 | POINT {
25 | x: self.left,
26 | y: self.top,
27 | }
28 | }
29 |
30 | pub fn size(&self) -> POINT {
31 | POINT {
32 | x: self.right - self.left,
33 | y: self.bottom - self.top,
34 | }
35 | }
36 |
37 | pub fn contains(&self, point: POINT) -> bool {
38 | point.x >= self.left && point.x < self.right && point.y >= self.top && point.y < self.bottom
39 | }
40 |
41 | pub fn add(&self, delta: POINT) -> RECT {
42 | RECT {
43 | left: self.left + delta.x,
44 | top: self.top + delta.y,
45 | right: self.right + delta.x,
46 | bottom: self.bottom + delta.y,
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/win32/dll/winmm/src/midi.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 |
3 | pub type HMIDIOUT = u32;
4 |
5 | #[win32_derive::dllexport]
6 | pub fn midiOutOpen(
7 | sys: &dyn System,
8 | phmo: Option<&mut HMIDIOUT>,
9 | uDeviceID: u32,
10 | dwCallback: u32,
11 | dwInstance: u32,
12 | fdwOpen: u32, /* MIDI_WAVE_OPEN_TYPE */
13 | ) -> u32 {
14 | todo!()
15 | }
16 |
17 | pub type MIDIOUTCAPSA = u32; // TODO
18 |
19 | #[win32_derive::dllexport]
20 | pub fn midiOutGetDevCapsA(
21 | sys: &dyn System,
22 | uDeviceID: u32,
23 | pmoc: Option<&mut MIDIOUTCAPSA>,
24 | cbmoc: u32,
25 | ) -> u32 {
26 | todo!()
27 | }
28 |
29 | #[win32_derive::dllexport]
30 | pub fn midiOutGetNumDevs(sys: &dyn System) -> u32 {
31 | todo!()
32 | }
33 |
34 | #[win32_derive::dllexport]
35 | pub fn midiOutReset(sys: &dyn System, hmo: HMIDIOUT) -> u32 {
36 | todo!()
37 | }
38 |
39 | #[win32_derive::dllexport]
40 | pub fn midiOutClose(sys: &dyn System, hmo: HMIDIOUT) -> u32 {
41 | todo!()
42 | }
43 |
44 | #[win32_derive::dllexport]
45 | pub fn midiOutSetVolume(sys: &dyn System, hmo: HMIDIOUT, dwVolume: u32) -> u32 {
46 | todo!()
47 | }
48 |
49 | #[win32_derive::dllexport]
50 | pub fn midiOutShortMsg(sys: &dyn System, hmo: HMIDIOUT, dwMsg: u32) -> u32 {
51 | todo!()
52 | }
53 |
54 | #[win32_derive::dllexport]
55 | pub fn midiInGetNumDevs(sys: &dyn System) -> u32 {
56 | 0
57 | }
58 |
--------------------------------------------------------------------------------
/exe/cpp/errors.cc:
--------------------------------------------------------------------------------
1 | // Subcommands exercise various ways a program can fail.
2 |
3 | #include "util.h"
4 |
5 | extern "C" void mainCRTStartup() {
6 | LPCSTR cmdlinep = GetCommandLineA();
7 | if (cmdlinep == NULL) {
8 | print(fmt().str("GetCommandLineA failed: ").dec(GetLastError()).nl());
9 | ExitProcess(1);
10 | }
11 | std::string_view cmdline(cmdlinep);
12 |
13 | std::string_view mode;
14 | size_t space = 0;
15 | for (; space < cmdline.size(); space++) {
16 | if (cmdline[space] == ' ') {
17 | mode = std::string_view(cmdline.data() + space + 1, cmdline.size() - space - 1);
18 | break;
19 | }
20 | }
21 | if (mode.empty()) {
22 | print(fmt().str("usage: errors.exe ").nl());
23 | ExitProcess(1);
24 | }
25 |
26 | if (mode == "exit") {
27 | print(fmt().str("expect: exit code 2").nl());
28 | ExitProcess(2);
29 | } else if (mode == "write-null") {
30 | print(fmt().str("writing mem[0]").nl());
31 | uint32_t* ptr = reinterpret_cast(0);
32 | *ptr = 1;
33 | } else if (mode == "write-high") {
34 | print(fmt().str("writing mem[0xFFFF_F000]").nl());
35 | uint32_t* ptr = reinterpret_cast(0xFFFFF000);
36 | *ptr = 1;
37 | } else {
38 | print(fmt().str("unknown mode: ").str(mode).nl());
39 | ExitProcess(1);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/win32/dll/ntdll/src/misc.rs:
--------------------------------------------------------------------------------
1 | use builtin_kernel32 as kernel32;
2 | pub use kernel32::file::HFILE;
3 | use win32_system::System;
4 | use win32_winapi::calling_convention::ArrayOut;
5 |
6 | const STATUS_SUCCESS: u32 = 0;
7 |
8 | #[repr(C)]
9 | #[derive(Debug)]
10 | pub struct IO_STATUS_BLOCK {
11 | pub Status: u32,
12 | pub Information: u32,
13 | }
14 | unsafe impl memory::Pod for IO_STATUS_BLOCK {}
15 |
16 | #[win32_derive::dllexport]
17 | pub fn NtReadFile(
18 | sys: &dyn System,
19 | FileHandle: HFILE,
20 | Event: u32,
21 | ApcRoutine: u32,
22 | ApcContext: u32,
23 | IoStatusBlock: Option<&mut IO_STATUS_BLOCK>,
24 | mut Buffer: ArrayOut,
25 | ByteOffset: Option<&mut u64>,
26 | Key: u32,
27 | ) -> u32 {
28 | let mut state = kernel32::file::get_state(sys);
29 | let file = state.files.get_mut(FileHandle).unwrap();
30 | if Event != 0 {
31 | todo!();
32 | }
33 | let status_block = IoStatusBlock.unwrap();
34 | let buf = &mut Buffer;
35 | if ByteOffset.is_some() {
36 | todo!();
37 | }
38 |
39 | let len = file.read(buf).unwrap();
40 | *status_block = IO_STATUS_BLOCK {
41 | Status: STATUS_SUCCESS,
42 | Information: len as u32,
43 | };
44 | STATUS_SUCCESS
45 | }
46 |
47 | #[win32_derive::dllexport]
48 | pub fn NtCurrentTeb(sys: &dyn System) -> u32 {
49 | sys.teb_addr()
50 | }
51 |
52 | #[win32_derive::dllexport]
53 | pub fn RtlExitUserProcess(sys: &mut dyn System, exit_code: u32) {
54 | sys.exit(exit_code);
55 | }
56 |
--------------------------------------------------------------------------------
/memory/src/pod.rs:
--------------------------------------------------------------------------------
1 | // Idea for this Pod type comes from https://github.com/CasualX/pelite.
2 | // I didn't copy the code but it's MIT-licensed anyway.
3 |
4 | /// A trait for types where it's safe to:
5 | /// - reintepret_cast<> from/to random memory blocks;
6 | /// - initialize from all-zeros memory
7 | pub unsafe trait Pod: 'static + Sized {
8 | fn zeroed() -> Self {
9 | // Safety: the all-zeroes struct is valid per Pod requirements.
10 | unsafe { std::mem::zeroed() }
11 | }
12 |
13 | /// Clear a Pod by a specified count of bytes.
14 | /// This is for cases where there's a variable-length C struct, with a header
15 | /// and some runtime-determined quantity of bytes following.
16 | unsafe fn clear_memory(&mut self, count: u32) {
17 | unsafe {
18 | std::ptr::write_bytes(self as *mut Self as *mut u8, 0, count as usize);
19 | }
20 | }
21 |
22 | fn as_bytes(s: &Self) -> &[u8] {
23 | unsafe {
24 | std::slice::from_raw_parts(s as *const Self as *const u8, std::mem::size_of::())
25 | }
26 | }
27 | }
28 |
29 | // See discussion of endianness in doc/design_notes.md.
30 | unsafe impl Pod for u8 {}
31 | unsafe impl Pod for [u8; 4] {} // pixels
32 | unsafe impl Pod for [u8; 10] {} // long double
33 | unsafe impl Pod for u16 {}
34 | unsafe impl Pod for i16 {}
35 | unsafe impl Pod for u32 {}
36 | unsafe impl Pod for i32 {}
37 | unsafe impl Pod for u64 {}
38 | unsafe impl Pod for i64 {}
39 | unsafe impl Pod for f32 {}
40 | unsafe impl Pod for f64 {}
41 |
--------------------------------------------------------------------------------
/win32/dll/user32/src/keyboard.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 | use win32_winapi::calling_convention::ArrayOut;
3 |
4 | pub type HKL = u32;
5 |
6 | #[win32_derive::dllexport]
7 | pub fn GetKeyState(sys: &dyn System, nVirtKey: u32) -> u32 {
8 | 0
9 | }
10 |
11 | #[win32_derive::dllexport]
12 | pub fn GetKeyboardState(sys: &dyn System, lpKeyState: Option<&mut u8>) -> bool {
13 | todo!()
14 | }
15 |
16 | #[win32_derive::dllexport]
17 | pub fn keybd_event(
18 | sys: &dyn System,
19 | bVk: u8,
20 | bScan: u8,
21 | dwFlags: u32, /* KEYBD_EVENT_FLAGS */
22 | dwExtraInfo: u32,
23 | ) {
24 | todo!()
25 | }
26 |
27 | #[win32_derive::dllexport]
28 | pub fn GetKeyboardType(sys: &dyn System, nTypeFlag: i32) -> i32 {
29 | 0 // fail
30 | }
31 |
32 | #[win32_derive::dllexport]
33 | pub fn GetKeyboardLayout(sys: &dyn System, idThread: u32) -> u32 {
34 | log::warn!("GetKeyboardLayout: stub");
35 | 0 // garbage value, unclear if callers care
36 | }
37 |
38 | #[win32_derive::dllexport]
39 | pub fn GetKeyboardLayoutList(sys: &dyn System, nBuff: i32, lpList: Option<&mut HKL>) -> i32 {
40 | log::warn!("GetKeyboardLayoutList: stub");
41 | 0 // no layouts
42 | }
43 |
44 | #[win32_derive::dllexport]
45 | pub fn MapVirtualKeyA(
46 | sys: &dyn System,
47 | uCode: u32,
48 | uMapType: u32, /* MAP_VIRTUAL_KEY_TYPE */
49 | ) -> u32 {
50 | 0 // no translation
51 | }
52 |
53 | #[win32_derive::dllexport]
54 | pub fn GetKeyNameTextA(sys: &dyn System, lParam: i32, lpString: ArrayOut) -> i32 {
55 | 0 // fail
56 | }
57 |
--------------------------------------------------------------------------------
/cli/build-rosetta.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Builds retrowin32 as a x86_64 Darwin executable
4 | # using the CPU's (or Rosetta's) existing support for 32-bit code.
5 | #
6 | # The binary needs particular flags and layout for this to work.
7 |
8 | # Arguments passed through to the underlying linker.
9 | linker_args=""
10 | # - Shrink pagezero from 4gb to 64kb so we can use lower 32 bits of memory
11 | # (see discussion in resv32.rs):
12 | linker_args="$linker_args -segalign 0x10000 -pagezero_size 0x10000"
13 | # - Put all our own content above 3gb:
14 | linker_args="$linker_args -image_base 0xc0010000"
15 | # - Disable PIE, required for moving image base:
16 | linker_args="$linker_args -no_pie -no_fixup_chains"
17 | # - Put our RESV32 section at 0x10000 to ensure nothing like malloc claims
18 | # the now available lower memory:
19 | linker_args="$linker_args -segaddr RESV32 0x10000"
20 |
21 | # To pass the linker args through all the intermediate build layers,
22 | # we want to end up with a RUSTFLAGS like
23 | # -C link_arg=-Wl,-segalign,0x1000,etc
24 | link_flag="-C link_arg=-Wl"
25 | for arg in $linker_args; do
26 | link_flag="$link_flag,$arg"
27 | done
28 |
29 | # relocation=model=dynamic-no-pic needed for disabling PIE as well.
30 | # --print link-args
31 | # TODO: try the `-C relocation-model=static` + --target trick used in build-linux-64.sh instead.
32 | export RUSTFLAGS="$RUSTFLAGS -C relocation-model=dynamic-no-pic $link_flag"
33 |
34 | # note: faster debug cycle if you remove 'sdl'
35 | exec cargo build --target x86_64-apple-darwin -p retrowin32 --no-default-features --features x86-64,sdl "$@"
36 |
--------------------------------------------------------------------------------
/win32/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "win32"
3 | version = "0.1.0"
4 | edition = "2024"
5 |
6 | [dependencies]
7 | log = { workspace = true }
8 | memory = { workspace = true }
9 | win32-system = { workspace = true }
10 | win32-winapi = { workspace = true }
11 |
12 | builtin-advapi32 = { path = "dll/advapi32" }
13 | builtin-bass = { path = "dll/bass" }
14 | builtin-comctl32 = { path = "dll/comctl32" }
15 | builtin-ddraw = { path = "dll/ddraw" }
16 | builtin-dinput = { path = "dll/dinput" }
17 | builtin-dsound = { path = "dll/dsound" }
18 | builtin-gdi32 = { path = "dll/gdi32" }
19 | builtin-kernel32 = { path = "dll/kernel32" }
20 | builtin-ntdll = { path = "dll/ntdll" }
21 | builtin-ole32 = { path = "dll/ole32" }
22 | builtin-oleaut32 = { path = "dll/oleaut32" }
23 | builtin-retrowin32_test = { path = "dll/retrowin32_test" }
24 | builtin-shlwapi = { path = "dll/shlwapi" }
25 | builtin-ucrtbase = { path = "dll/ucrtbase" }
26 | builtin-user32 = { path = "dll/user32" }
27 | builtin-vcruntime140 = { path = "dll/vcruntime140" }
28 | builtin-version = { path = "dll/version" }
29 | builtin-wininet = { path = "dll/wininet" }
30 | builtin-winmm = { path = "dll/winmm" }
31 |
32 | futures = { version = "0.3.31", optional = true }
33 | libc = { version = "0.2", optional = true }
34 | unicorn-engine = { version = "2.0.0", optional = true }
35 | x86 = { workspace = true, optional = true }
36 |
37 | [features]
38 | x86-emu = ["dep:x86", "memory/mem-box", "builtin-kernel32/x86-emu"]
39 | x86-64 = ["memory/mem-raw", "dep:futures", "dep:libc"]
40 | x86-unicorn = ["dep:unicorn-engine", "memory/mem-box"]
41 | wasm = ["win32-system/wasm"]
42 |
--------------------------------------------------------------------------------
/win32/dll/shlwapi/src/builtin.rs:
--------------------------------------------------------------------------------
1 | #![doc = r" Generated code, do not edit. See winapi/builtin.rs for an overview."]
2 | #![allow(unused_imports)]
3 | #![allow(unused_variables)]
4 | use win32_system::dll::*;
5 | mod wrappers {
6 | use crate as shlwapi;
7 | use crate::*;
8 | use ::memory::Extensions;
9 | use win32_system::{System, trace};
10 | use win32_winapi::{calling_convention::*, *};
11 | pub unsafe fn PathRemoveFileSpecA(sys: &mut dyn System, stack_args: u32) -> ABIReturn {
12 | use shlwapi::*;
13 | unsafe {
14 | let mem = sys.mem().detach();
15 | let pszPath = ::from_stack(mem, stack_args + 0u32);
16 | let __trace_record = if trace::enabled("shlwapi") {
17 | trace::Record::new(
18 | shlwapi::PathRemoveFileSpecA_pos,
19 | "shlwapi",
20 | "PathRemoveFileSpecA",
21 | &[("pszPath", &pszPath)],
22 | )
23 | .enter()
24 | } else {
25 | None
26 | };
27 | let result = shlwapi::PathRemoveFileSpecA(sys, pszPath);
28 | if let Some(mut __trace_record) = __trace_record {
29 | __trace_record.exit(&result);
30 | }
31 | result.into()
32 | }
33 | }
34 | }
35 | const SHIMS: [Shim; 1usize] = [Shim {
36 | name: "PathRemoveFileSpecA",
37 | func: Handler::Sync(wrappers::PathRemoveFileSpecA),
38 | }];
39 | pub const DLL: BuiltinDLL = BuiltinDLL {
40 | file_name: "shlwapi.dll",
41 | shims: &SHIMS,
42 | raw: std::include_bytes!("../shlwapi.dll"),
43 | };
44 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/file/misc.rs:
--------------------------------------------------------------------------------
1 | use memory::str16::Str16;
2 | use win32_system::System;
3 |
4 | #[win32_derive::dllexport]
5 | pub fn GetDiskFreeSpaceA(
6 | sys: &dyn System,
7 | lpRootPathName: Option<&str>,
8 | lpSectorsPerCluster: Option<&mut u32>,
9 | lpBytesPerSector: Option<&mut u32>,
10 | lpNumberOfFreeClusters: Option<&mut u32>,
11 | lpTotalNumberOfClusters: Option<&mut u32>,
12 | ) -> bool {
13 | let sector_size = 512;
14 | let cluster_size = 4 << 10; // 4kb
15 | let free_space = 4 << 20; // 4mb
16 | let total_space = 64 << 20; // 64mb
17 |
18 | if let Some(sectors_per_cluster) = lpSectorsPerCluster {
19 | *sectors_per_cluster = cluster_size / sector_size;
20 | }
21 | if let Some(bytes_per_sector) = lpBytesPerSector {
22 | *bytes_per_sector = sector_size;
23 | }
24 | if let Some(number_of_free_clusters) = lpNumberOfFreeClusters {
25 | *number_of_free_clusters = free_space / cluster_size;
26 | }
27 | if let Some(total_number_of_clusters) = lpTotalNumberOfClusters {
28 | *total_number_of_clusters = total_space / cluster_size;
29 | }
30 | true
31 | }
32 |
33 | #[win32_derive::dllexport]
34 | pub fn GetDriveTypeW(sys: &dyn System, lpRootPathName: Option<&Str16>) -> u32 {
35 | todo!()
36 | }
37 |
38 | #[win32_derive::dllexport]
39 | pub fn GetDriveTypeA(sys: &dyn System, lpRootPathName: Option<&str>) -> u32 {
40 | const DRIVE_FIXED: u32 = 3; // hard drive
41 | DRIVE_FIXED
42 | }
43 |
44 | #[win32_derive::dllexport]
45 | pub fn GetLogicalDrives(sys: &dyn System) -> u32 {
46 | let mut drives = 0;
47 | drives |= 1 << 2; // C:
48 | drives
49 | }
50 |
--------------------------------------------------------------------------------
/web/debugger/mappings.tsx:
--------------------------------------------------------------------------------
1 | import * as preact from 'preact';
2 | import * as wasm from '../glue/pkg/glue';
3 | import { MemoryView, Number } from './memory';
4 | import { hex } from './util';
5 |
6 | namespace Mappings {
7 | export interface Props extends MemoryView {
8 | mappings: wasm.Mapping[];
9 | highlight?: number;
10 | }
11 | }
12 | export class Mappings extends preact.Component {
13 | render() {
14 | const rows = this.props.mappings.map(mapping => {
15 | let className: string | undefined;
16 | const highlight = this.props.highlight;
17 | if (highlight !== undefined && highlight >= mapping.addr && highlight < (mapping.addr + mapping.size)) {
18 | className = 'highlight';
19 | }
20 | return (
21 |
22 |
23 |
24 | {mapping.addr}
25 |
26 | |
27 |
28 | {hex(mapping.size)}
29 | |
30 | {mapping.module} |
31 | {mapping.desc} |
32 |
33 | );
34 | });
35 | return (
36 |
37 |
38 |
39 |
40 | | addr |
41 | size |
42 | module |
43 | desc |
44 |
45 |
46 | {rows}
47 |
48 |
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/pe/src/exports.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case)]
2 | #![allow(non_camel_case_types)]
3 |
4 | use crate::c_str;
5 | use memory::Extensions;
6 |
7 | #[derive(Debug)]
8 | #[repr(C)]
9 | pub struct IMAGE_EXPORT_DIRECTORY {
10 | Characteristics: u32,
11 | TimeDateStamp: u32,
12 | MajorVersion: u16,
13 | MinorVersion: u16,
14 | Name: u32,
15 | /// Ordinal offset. Symbol DLL@x => functions[Base + x].
16 | pub Base: u32,
17 | NumberOfFunctions: u32,
18 | NumberOfNames: u32,
19 | AddressOfFunctions: u32,
20 | AddressOfNames: u32,
21 | AddressOfNameOrdinals: u32,
22 | }
23 | unsafe impl memory::Pod for IMAGE_EXPORT_DIRECTORY {}
24 |
25 | impl IMAGE_EXPORT_DIRECTORY {
26 | #[allow(dead_code)]
27 | pub fn name<'a>(&self, image: &'a [u8]) -> &'a [u8] {
28 | c_str(&image[self.Name as usize..])
29 | }
30 |
31 | /// Returns an iterator of function addresses in ordinal order.
32 | pub fn fns<'a>(&self, image: &'a [u8]) -> impl Iterator- {
33 | image.iter_pod::(self.AddressOfFunctions, self.NumberOfFunctions)
34 | }
35 |
36 | /// Returns an iterator of (name, index) pairs, where index is an index into fn()s.
37 | pub fn names<'a>(&self, image: &'a [u8]) -> impl Iterator
- {
38 | let names = image.iter_pod::(self.AddressOfNames, self.NumberOfNames);
39 | let ords = image.iter_pod::(self.AddressOfNameOrdinals, self.NumberOfNames);
40 |
41 | let ni = names.map(move |addr| c_str(&image[addr as usize..]));
42 | ni.zip(ords)
43 | }
44 | }
45 |
46 | pub fn read_exports(section: &[u8]) -> IMAGE_EXPORT_DIRECTORY {
47 | section.get_pod::(0)
48 | }
49 |
--------------------------------------------------------------------------------
/exe/cpp/util.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | void print(std::string_view str) {
6 | WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), str.data(), str.size(), nullptr, nullptr);
7 | }
8 |
9 | struct fmt {
10 | char buf[1024];
11 | size_t ofs;
12 | fmt() : ofs(0) {}
13 |
14 | fmt& ch(char c) {
15 | buf[ofs++] = c;
16 | return *this;
17 | }
18 | fmt& str(std::string_view str) {
19 | for (size_t i = 0; i < str.size(); i++) {
20 | buf[ofs++] = str[i];
21 | }
22 | return *this;
23 | }
24 | fmt& dec(int value) {
25 | if (value < 0) {
26 | ch('-');
27 | value = -value;
28 | }
29 | int start = ofs;
30 | do {
31 | ch('0' + value % 10);
32 | value /= 10;
33 | } while (value);
34 | for (int end = ofs; end > start + 1; ) {
35 | std::swap(buf[start++], buf[--end]);
36 | }
37 | return *this;
38 | }
39 | fmt& nl() {
40 | ch('\n');
41 | return *this;
42 | }
43 |
44 | operator std::string_view() const {
45 | return std::string_view(buf, ofs);
46 | }
47 | };
48 |
49 | static size_t strlen(const char* str) {
50 | int len = 0;
51 | while (*str++) len++;
52 | return len;
53 | }
54 |
55 | static int memcmp(const void* ptr1, const void* ptr2, size_t num) {
56 | const uint8_t* p1 = static_cast(ptr1);
57 | const uint8_t* p2 = static_cast(ptr2);
58 |
59 | for (size_t i = 0; i < num; i++) {
60 | if (p1[i] != p2[i]) {
61 | return p1[i] - p2[i];
62 | }
63 | }
64 | return 0;
65 | }
66 |
--------------------------------------------------------------------------------
/web/debugger.html:
--------------------------------------------------------------------------------
1 |
2 | retrowin32
3 |
4 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/win32/dll/ddraw/src/palette.rs:
--------------------------------------------------------------------------------
1 | use super::ddraw::get_state;
2 | use super::types::*;
3 | use builtin_gdi32::PALETTEENTRY;
4 | use memory::Extensions;
5 | use std::cell::RefCell;
6 | use win32_system::System;
7 | use win32_winapi::vtable;
8 |
9 | pub struct Palette {
10 | pub ptr: u32,
11 | pub entries: RefCell>,
12 | }
13 |
14 | #[win32_derive::dllexport]
15 | pub mod IDirectDrawPalette {
16 | use super::*;
17 |
18 | vtable![
19 | QueryInterface: todo,
20 | AddRef: todo,
21 | Release: ok,
22 | GetCaps: todo,
23 | GetEntries: todo,
24 | Initialize: todo,
25 | SetEntries: ok,
26 | ];
27 |
28 | pub fn new(sys: &mut dyn System) -> u32 {
29 | let vtable = sys.get_symbol("ddraw.dll", "IDirectDrawPalette");
30 | sys.memory().store(vtable)
31 | }
32 |
33 | #[win32_derive::dllexport]
34 | pub fn Release(sys: &dyn System, this: u32) -> u32 {
35 | log::warn!("{this:x}->Release()");
36 | 0 // TODO: return refcount?
37 | }
38 |
39 | #[win32_derive::dllexport]
40 | pub fn SetEntries(
41 | sys: &mut dyn System,
42 | this: u32,
43 | unused: u32,
44 | start: u32,
45 | count: u32,
46 | entries: u32,
47 | ) -> DD {
48 | let state = get_state(sys);
49 | let mut dst = state.palettes.get(&this).unwrap().entries.borrow_mut();
50 | // TODO: if palette is DDPCAPS_8BITENTRIES then entries are one byte, not 4.
51 | let src = sys.mem().iter_pod::(entries, count);
52 | for (dst, src) in dst[start as usize..][..count as usize].iter_mut().zip(src) {
53 | *dst = src;
54 | }
55 | DD::OK
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/doc/performance.md:
--------------------------------------------------------------------------------
1 | # Performance notes
2 |
3 | ## Dumping assembly
4 |
5 | ```
6 | $ cargo install cargo-show-asm
7 | $ cargo asm --wasm -p win32 mov_r32
8 | ```
9 |
10 | ## Profiling on Mac
11 |
12 | ```
13 | $ brew install cargo-instruments
14 | $ cargo instruments --release -t time -p retrowin32 -- exe/zip/zip.exe 200
15 | ```
16 |
17 | ## Registers struct
18 |
19 | Registers are known named slots, e.g. eax, ebx. It's natural to represent them
20 | as like
21 |
22 | ```
23 | struct Registers {
24 | eax: u32,
25 | ebx: u32,
26 | ...
27 | }
28 | ```
29 |
30 | But most instructions refer to registers indirectly, as an integer. So to look
31 | up a register you might write code like:
32 |
33 | ```
34 | enum Reg { EAX, EBX, ... }
35 | fn get_reg(regs: &Registers, reg: Reg) {
36 | match reg {
37 | Reg::EAX => regs.eax,
38 | ...
39 | }
40 | }
41 | ```
42 |
43 | Unfortunately it seems that, even if the values of the `Reg` enum are integers
44 | that cleanly map to "the nth u32 in the registers struct", the above `get_reg`
45 | function gets generated by LLVM as a switch table rather than math. (The
46 | behavior seems the same between C++ and Rust so it seems to be an LLVM thing; it
47 | generates more efficient code when `regs` is a global but it's still not ideal.)
48 |
49 | If you instead do something that has the same layout in memory but is more
50 | clearly integer-indexed:
51 |
52 | ```
53 | struct Registers {
54 | r32: [u32; 8],
55 | }
56 | ```
57 |
58 | then the code generated is ideal. But then accessing those registers in Rust
59 | code ends up pretty miserable relative to the named registers.
60 |
61 | So instead we just use the first struct with `#[repr(C)]` and do some casting to
62 | get the efficient codegen of the latter.
63 |
--------------------------------------------------------------------------------
/win32/winapi/src/error.rs:
--------------------------------------------------------------------------------
1 | // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
2 |
3 | use crate::calling_convention;
4 |
5 | /// Windows error codes.
6 | #[allow(non_camel_case_types)]
7 | #[derive(Debug, Copy, Clone, win32_derive::TryFromEnum)]
8 | pub enum ERROR {
9 | SUCCESS = 0,
10 | FILE_NOT_FOUND = 2,
11 | ACCESS_DENIED = 5,
12 | INVALID_HANDLE = 6,
13 | INVALID_ACCESS = 12,
14 | INVALID_DATA = 13,
15 | OUT_OF_PAPER = 28,
16 | FILE_EXISTS = 80,
17 | INVALID_PARAMETER = 87,
18 | OPEN_FAILED = 110,
19 | INSUFFICIENT_BUFFER = 122,
20 | MOD_NOT_FOUND = 126,
21 | ALREADY_EXISTS = 183,
22 | }
23 |
24 | impl From for ERROR {
25 | fn from(err: std::io::Error) -> Self {
26 | match err.kind() {
27 | std::io::ErrorKind::NotFound => ERROR::FILE_NOT_FOUND,
28 | std::io::ErrorKind::PermissionDenied => ERROR::ACCESS_DENIED,
29 | std::io::ErrorKind::InvalidData => ERROR::INVALID_DATA,
30 | std::io::ErrorKind::AlreadyExists => ERROR::FILE_EXISTS,
31 | std::io::ErrorKind::InvalidInput => ERROR::INVALID_ACCESS,
32 | _ => unimplemented!(),
33 | }
34 | }
35 | }
36 |
37 | impl From for u32 {
38 | fn from(err: ERROR) -> u32 {
39 | err as u32
40 | }
41 | }
42 |
43 | impl Into for ERROR {
44 | fn into(self) -> calling_convention::ABIReturn {
45 | (self as u32).into()
46 | }
47 | }
48 |
49 | impl Into for Result {
50 | fn into(self) -> calling_convention::ABIReturn {
51 | match self {
52 | Ok(err) => err.into(),
53 | Err(err) => err.into(),
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/web/debugger/code.tsx:
--------------------------------------------------------------------------------
1 | import * as preact from 'preact';
2 | import { Instruction } from '../glue/pkg/glue';
3 | import { Labels } from './labels';
4 | import { MemoryView, Number } from './memory';
5 | import { hex } from './util';
6 |
7 | namespace Code {
8 | export interface Props extends MemoryView {
9 | labels: Labels;
10 | runTo: (addr: number) => void;
11 | instrs: Instruction[];
12 | }
13 | }
14 | export class Code extends preact.Component {
15 | render() {
16 | const instrs = this.props.instrs.map(instr => {
17 | let code = instr.code.map(({ kind, text }) => {
18 | switch (kind) {
19 | case 'FunctionAddress':
20 | case 'LabelAddress':
21 | case 'Number': {
22 | const addr = parseInt(text, 16);
23 | let label = this.props.labels.get(addr);
24 | if (label) {
25 | label = ` ${label}`;
26 | }
27 | return (
28 | <>
29 | {addr}
30 | {label}
31 | >
32 | );
33 | }
34 | default:
35 | return text;
36 | }
37 | });
38 | return (
39 |
40 | {
44 | this.props.runTo(instr.addr);
45 | }}
46 | >
47 | {hex(instr.addr, 8)}
48 |
49 |
50 | {code}
51 |
52 | );
53 | });
54 | return (
55 |
56 | {instrs}
57 |
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/sync/event.rs:
--------------------------------------------------------------------------------
1 | use crate::{KernelObject, KernelObjectsMethods, get_state};
2 | use win32_system::{Event, System};
3 | use win32_winapi::HANDLE;
4 |
5 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
6 | pub struct HEVENTT;
7 | pub type HEVENT = HANDLE;
8 |
9 | #[win32_derive::dllexport]
10 | pub fn CreateEventA(
11 | sys: &dyn System,
12 | lpEventAttributes: u32,
13 | bManualReset: bool,
14 | bInitialState: bool,
15 | lpName: Option<&str>,
16 | ) -> HEVENT {
17 | let name = if let Some(name) = lpName {
18 | let state = get_state(sys);
19 | if let Some(ev) = state.objects.iter().find(|(_, ev)| {
20 | if let KernelObject::Event(ev) = ev {
21 | if let Some(n) = &ev.name {
22 | if n == name {
23 | return true;
24 | }
25 | }
26 | }
27 | false
28 | }) {
29 | todo!("CreateEventA: reusing named event");
30 | }
31 | Some(name.to_string())
32 | } else {
33 | None
34 | };
35 |
36 | HEVENT::from_raw(
37 | get_state(sys)
38 | .objects
39 | .add(KernelObject::Event(Event::new(
40 | name,
41 | bManualReset,
42 | bInitialState,
43 | )))
44 | .to_raw(),
45 | )
46 | }
47 |
48 | #[win32_derive::dllexport]
49 | pub fn SetEvent(sys: &dyn System, hEvent: HEVENT) -> bool {
50 | get_state(sys).objects.get_event(hEvent).unwrap().signal();
51 | true
52 | }
53 |
54 | #[win32_derive::dllexport]
55 | pub fn ResetEvent(sys: &dyn System, hEvent: HEVENT) -> bool {
56 | todo!()
57 | }
58 |
59 | #[win32_derive::dllexport]
60 | pub fn PulseEvent(sys: &dyn System, hEvent: HEVENT) -> bool {
61 | todo!()
62 | }
63 |
--------------------------------------------------------------------------------
/win32/dll/bass/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! This module stubs bass.dll as found in monolife.
2 | //! monolife ships its own bass.dll, but I wrote this module before
3 | //! retrowin32 could load external dlls.
4 | //!
5 | //! Today retrowin32 is capable of loading the dll, but it appears
6 | //! to be packed with some packer that fails when we load it.
7 |
8 | #![allow(non_snake_case)]
9 |
10 | mod builtin;
11 |
12 | pub use builtin::DLL;
13 |
14 | use win32_system::System;
15 |
16 | /// Hack: time since BASS_Start etc. was called.
17 | static mut T: u32 = 0;
18 |
19 | #[win32_derive::dllexport]
20 | pub fn BASS_Init(sys: &dyn System, arg1: u32, arg2: u32, arg3: u32, arg4: u32) -> u32 {
21 | 1
22 | }
23 |
24 | #[win32_derive::dllexport]
25 | pub fn BASS_MusicLoad(
26 | sys: &dyn System,
27 | arg1: u32,
28 | arg2: u32,
29 | arg3: u32,
30 | arg4: u32,
31 | arg5: u32,
32 | ) -> u32 {
33 | 1
34 | }
35 |
36 | #[win32_derive::dllexport]
37 | pub fn BASS_Start(sys: &mut dyn System) -> u32 {
38 | unsafe {
39 | T = sys.host().ticks();
40 | }
41 | 1
42 | }
43 |
44 | #[win32_derive::dllexport]
45 | pub fn BASS_MusicPlay(sys: &mut dyn System, arg1: u32) -> u32 {
46 | unsafe {
47 | T = sys.host().ticks();
48 | }
49 | 1
50 | }
51 |
52 | #[win32_derive::dllexport]
53 | pub fn BASS_ChannelGetPosition(sys: &mut dyn System, mode: u32) -> u32 {
54 | let dur = sys.host().ticks() - unsafe { T };
55 | match mode {
56 | 0 => {
57 | // BASS_POS_BYTE
58 | (dur as f32 * 44.1) as u32 // 44.1khz
59 | }
60 | 1 => {
61 | // TODO: BASS_POS_MUSIC_ORDER
62 | 0
63 | }
64 | _ => 0,
65 | }
66 | }
67 |
68 | #[win32_derive::dllexport]
69 | pub fn BASS_MusicSetPositionScaler(sys: &dyn System, arg1: u32, arg2: u32) -> u32 {
70 | 1
71 | }
72 |
73 | #[win32_derive::dllexport]
74 | pub fn BASS_Free(sys: &dyn System, arg1: u32) -> u32 {
75 | 1
76 | }
77 |
--------------------------------------------------------------------------------
/exe/ops/out.txt:
--------------------------------------------------------------------------------
1 | add 3,5 => 8
2 | add 3,fd => 0 CF PF ZF
3 | add 3,fb => fe SF
4 | adc (CF=1) ff,0 => 0 CF PF ZF
5 | adc (CF=1) ff,1 => 1 CF
6 | adc (CF=1) ff,fe => fe CF SF
7 | adc (CF=1) ff,ff => ff CF PF SF
8 | sbb (CF=1) 0,0 => ff CF PF SF
9 | sbb (CF=1) 0,1 => fe CF SF
10 | sbb (CF=1) 0,fe => 1 CF
11 | sbb (CF=1) 0,ff => 0 CF PF ZF
12 | shr 3,0 => 3
13 | shr 3,1 => 1 CF
14 | shr 3,2 => 0 CF PF ZF
15 | shr 80,1 => 40 OF
16 | shr 80,2 => 20
17 | shr 81,1 => 40 CF OF
18 | shr 81,2 => 20
19 | sar 3,1 => 1 CF
20 | sar 3,2 => 0 CF PF ZF
21 | sar 80,1 => c0 PF SF
22 | sar 80,2 => e0 SF
23 | sar 81,1 => c0 CF PF SF
24 | sar 81,2 => e0 SF
25 | sar 82,1 => c1 SF
26 | sar 82,2 => e0 CF SF
27 | sar 3,0 => 3
28 | sar 3,1 => 6 PF
29 | sar 3,2 => c PF
30 | sar 80,1 => 0 CF PF ZF OF
31 | sar 80,2 => 0 PF ZF
32 | sar d1,1 => a2 CF SF
33 | sar d1,2 => 44 CF PF
34 | sar e2,1 => c4 CF SF
35 | sar e2,2 => 88 CF PF SF
36 | rol 80,0 => 80
37 | rol 80,1 => 1 CF OF
38 | rol 80,2 => 2
39 | rol c0,1 => 81 CF
40 | rol c0,2 => 3 CF
41 | rol a0,1 => 41 CF OF
42 | rol a0,2 => 82
43 | rol 6,1 => c
44 | rol 60,2 => 81 CF
45 | rcl a0,0 => a0 CF
46 | rcl a0,1 => 41 CF OF
47 | rcl a0,2 => 83
48 | rcl a1,0 => a1 CF
49 | rcl a1,1 => 43 CF OF
50 | rcl a1,2 => 87
51 | ror 1,0 => 1
52 | ror 1,1 => 80 CF OF
53 | ror 1,2 => 40
54 | ror 3,1 => 81 CF OF
55 | ror 3,2 => c0 CF
56 | ror 2,1 => 1
57 | ror 2,2 => 80 CF
58 | ror 6,1 => 3
59 | ror 6,2 => 81 CF
60 | fld 1,0,pi,l2e => 1.000 0 3.141 1.442
61 | fld => 1.100 2.200 1.100
62 | fld negative => -1.100 -2.200 -1.100
63 | fild => 4321.000 44321.000 454321.000
64 | fild neg => -4321.000 -44321.000 -454321.000
65 | fst => 3.141 3.141
66 | fst neg => -3.141 -3.141
67 | fist => 3 3 3
68 | fist neg => fffd fffffffd fffffffd
69 | fchs => -3.141 3.141
70 | fabs => -3.141 3.141
71 | trig => 0.841 0.540 0.841 0.540 1.140
72 | fadd st => 3.141 4.584 4.584 1.442 4.584
73 | fadd mem => 46.351 435.241
74 | fadd mem neg => -40.068 -428.958
75 | fiadd => 46.141 47.141
76 | fiadd neg => -39.858 -40.858
77 | fsub mem => -472.168
78 | f2xm1 => -0.384 0.624
79 | fscale => 11.541
80 |
--------------------------------------------------------------------------------
/web/debugger/ddraw.tsx:
--------------------------------------------------------------------------------
1 | import * as preact from 'preact';
2 | import * as wasm from '../glue/pkg/glue';
3 | import { MemoryView, Number } from './memory';
4 |
5 | namespace DirectDraw {
6 | export interface Props extends MemoryView {
7 | surfaces: wasm.SurfaceDebug[];
8 | }
9 | export interface State {
10 | hover?: wasm.SurfaceDebug;
11 | }
12 | }
13 | export class DirectDraw extends preact.Component {
14 | canvasContainer = (parent: HTMLElement | null) => {
15 | if (!parent) return;
16 | parent.appendChild(this.state.hover!.canvas);
17 | };
18 |
19 | render() {
20 | const rows = this.props.surfaces.map((surface) => {
21 | return (
22 | this.setState({ hover: surface })}
24 | onMouseLeave={() => this.setState({ hover: undefined })}
25 | >
26 | |
27 | {surface.ptr}
28 | |
29 |
30 | {surface.width}x{surface.height}x{surface.bytes_per_pixel}
31 | |
32 | {surface.primary ? 'yes' : 'no'} |
33 |
34 | {surface.pixels && {surface.pixels}}
35 | |
36 |
37 | {surface.palette && {surface.palette}}
38 | |
39 |
40 | );
41 | });
42 | return (
43 |
44 |
45 |
46 | | ptr |
47 | format |
48 | primary |
49 | pixels |
50 | palette |
51 |
52 | {rows}
53 |
54 | {this.state.hover &&
}
55 |
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/win32/dll/gdi32/src/state.rs:
--------------------------------------------------------------------------------
1 | use crate::dc::{DC, DCTarget, HDC, ScreenDCTarget};
2 | use crate::{HGDIOBJ, LOWEST_HGDIOBJ, Object, bitmap::Bitmap};
3 | use std::{cell::RefCell, rc::Rc};
4 | use win32_system::{System, generic_get_state};
5 | use win32_winapi::Handles;
6 |
7 | pub struct State {
8 | pub dcs: Handles>>,
9 | pub screen_dc: HDC,
10 | pub objects: Handles,
11 | }
12 |
13 | pub trait DCHandles {
14 | fn add_dc(&mut self, dc: DC) -> HDC;
15 | }
16 | impl DCHandles for Handles>> {
17 | fn add_dc(&mut self, dc: DC) -> HDC {
18 | self.add(Rc::new(RefCell::new(dc)))
19 | }
20 | }
21 |
22 | pub trait GDIHandles {
23 | fn add_bitmap(&mut self, bmp: Bitmap) -> HGDIOBJ;
24 | fn get_bitmap(&self, handle: HGDIOBJ) -> Option<&Rc>>;
25 | }
26 | impl GDIHandles for Handles {
27 | fn add_bitmap(&mut self, bmp: Bitmap) -> HGDIOBJ {
28 | self.add(Object::Bitmap(Rc::new(RefCell::new(bmp))))
29 | }
30 |
31 | fn get_bitmap(&self, handle: HGDIOBJ) -> Option<&Rc>> {
32 | let object = self.get(handle)?;
33 | let Object::Bitmap(bmp) = object else {
34 | return None;
35 | };
36 | Some(bmp)
37 | }
38 | }
39 |
40 | impl Default for State {
41 | fn default() -> Self {
42 | State {
43 | dcs: Default::default(),
44 | screen_dc: HDC::null(),
45 | objects: Handles::new(LOWEST_HGDIOBJ),
46 | }
47 | }
48 | }
49 |
50 | impl State {
51 | pub fn new_dc(&mut self, target: Box) -> HDC {
52 | self.dcs.add_dc(DC::new(target))
53 | }
54 |
55 | pub fn screen_dc(&mut self) -> HDC {
56 | if self.screen_dc.is_null() {
57 | self.screen_dc = self.dcs.add_dc(DC::new(Box::new(ScreenDCTarget)));
58 | }
59 | self.screen_dc
60 | }
61 | }
62 |
63 | #[inline]
64 | pub fn get_state(sys: &dyn System) -> std::cell::RefMut<'_, State> {
65 | generic_get_state::(sys)
66 | }
67 |
--------------------------------------------------------------------------------
/cli/src/headless.rs:
--------------------------------------------------------------------------------
1 | use crate::time::Time;
2 |
3 | pub struct Window {}
4 | impl win32::host::Window for Window {
5 | fn set_title(&self, _title: &str) {}
6 | fn set_size(&self, _width: u32, _height: u32) {}
7 | fn fullscreen(&self) {}
8 | }
9 |
10 | pub struct Surface {}
11 | impl win32::host::Surface for Surface {
12 | fn write_pixels(&self, _pixels: &[u8]) {}
13 | fn show(&self) {}
14 | fn bit_blt(
15 | &self,
16 | _dst_rect: &win32::RECT,
17 | _src: &dyn win32::host::Surface,
18 | _src_rect: &win32::RECT,
19 | ) {
20 | }
21 | }
22 |
23 | pub struct Audio {}
24 | impl win32::host::Audio for Audio {
25 | fn write(&mut self, _buf: &[u8]) {}
26 | fn pos(&mut self) -> usize {
27 | todo!()
28 | }
29 | }
30 |
31 | pub struct GUI {
32 | time: Time,
33 | }
34 |
35 | impl GUI {
36 | pub fn new(time: Time) -> anyhow::Result {
37 | Ok(GUI { time })
38 | }
39 |
40 | pub fn get_message(&mut self) -> Option {
41 | None
42 | }
43 |
44 | pub fn block(&mut self, wait: Option) -> bool {
45 | if let Some(wait) = wait {
46 | let when = self.time.start + std::time::Duration::from_millis(wait as u64);
47 | if let Some(remaining) = when.checked_duration_since(std::time::Instant::now()) {
48 | std::thread::sleep(remaining);
49 | }
50 | true
51 | } else {
52 | unimplemented!();
53 | }
54 | }
55 |
56 | pub fn create_window(&mut self, _hwnd: u32) -> Box {
57 | Box::new(Window {})
58 | }
59 |
60 | pub fn create_surface(
61 | &mut self,
62 | _opts: &win32::host::SurfaceOptions,
63 | ) -> Box {
64 | Box::new(Surface {})
65 | }
66 |
67 | pub fn init_audio(
68 | &mut self,
69 | _sample_rate: u32,
70 | _callback: win32::host::AudioCallback,
71 | ) -> Box {
72 | Box::new(Audio {})
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/win32/dll/ucrtbase/src/memory.rs:
--------------------------------------------------------------------------------
1 | use memory::ExtensionsMut;
2 | use win32_system::System;
3 | use win32_winapi::CStr;
4 |
5 | #[win32_derive::dllexport(cdecl)]
6 | pub fn malloc(sys: &mut dyn System, size: u32) -> u32 {
7 | let memory = sys.memory();
8 | memory.process_heap.alloc(memory.mem(), size)
9 | }
10 |
11 | #[win32_derive::dllexport(cdecl)]
12 | pub fn calloc(sys: &mut dyn System, count: u32, size: u32) -> u32 {
13 | let memory = sys.memory();
14 | memory.process_heap.alloc(memory.mem(), count * size)
15 | }
16 |
17 | #[win32_derive::dllexport(cdecl)]
18 | pub fn free(sys: &mut dyn System, ptr: u32) -> u32 {
19 | let memory = sys.memory();
20 | memory.process_heap.free(memory.mem(), ptr);
21 | 0
22 | }
23 |
24 | #[win32_derive::dllexport(cdecl)]
25 | pub fn memset(sys: &mut dyn System, dst: u32, val: u32, len: u32) -> u32 {
26 | sys.mem().sub32_mut(dst, len).fill(val as u8);
27 | dst
28 | }
29 |
30 | #[win32_derive::dllexport(cdecl)]
31 | pub fn strlen(sys: &mut dyn System, lpString: Option<&CStr>) -> u32 {
32 | // The mapping to str already computes the string length.
33 | lpString.unwrap().count_bytes() as u32
34 | }
35 |
36 | #[win32_derive::dllexport(cdecl)]
37 | pub fn memcpy(sys: &mut dyn System, dest: u32, src: u32, count: u32) -> u32 {
38 | sys.mem().copy(src, dest, count);
39 | dest
40 | }
41 |
42 | #[win32_derive::dllexport(cdecl, symbol = "??2@YAPAXI@Z")]
43 | pub fn operator_new(sys: &mut dyn System, size: u32) -> u32 {
44 | let memory = sys.memory();
45 | memory.process_heap.alloc(memory.mem(), size)
46 | }
47 |
48 | #[win32_derive::dllexport(cdecl, symbol = "??3@YAXPAX@Z")]
49 | pub fn operator_delete(sys: &mut dyn System, size: u32) {
50 | todo!()
51 | }
52 |
53 | #[win32_derive::dllexport(cdecl)]
54 | pub fn __CxxFrameHandler(
55 | sys: &mut dyn System,
56 | pExcept: u32,
57 | pRN: u32,
58 | pContext: u32,
59 | pDC: u32,
60 | ) -> u32 {
61 | todo!()
62 | }
63 |
64 | #[win32_derive::dllexport(cdecl)]
65 | pub fn _EH_prolog(sys: &mut dyn System) {
66 | // TODO: exception handler setup
67 | }
68 |
--------------------------------------------------------------------------------
/win32/dll/retrowin32_test/src/builtin.rs:
--------------------------------------------------------------------------------
1 | #![doc = r" Generated code, do not edit. See winapi/builtin.rs for an overview."]
2 | #![allow(unused_imports)]
3 | #![allow(unused_variables)]
4 | use win32_system::dll::*;
5 | mod wrappers {
6 | use crate as retrowin32_test;
7 | use crate::*;
8 | use ::memory::Extensions;
9 | use win32_system::{System, trace};
10 | use win32_winapi::{calling_convention::*, *};
11 | pub unsafe fn retrowin32_test_callback1(
12 | sys: &mut dyn System,
13 | stack_args: u32,
14 | ) -> ::std::pin::Pin + '_>> {
15 | use retrowin32_test::*;
16 | unsafe {
17 | let mem = sys.mem().detach();
18 | let func = ::from_stack(mem, stack_args + 0u32);
19 | let data = ::from_stack(mem, stack_args + 4u32);
20 | let __trace_record = if trace::enabled("retrowin32_test") {
21 | trace::Record::new(
22 | retrowin32_test::retrowin32_test_callback1_pos,
23 | "retrowin32_test",
24 | "retrowin32_test_callback1",
25 | &[("func", &func), ("data", &data)],
26 | )
27 | .enter()
28 | } else {
29 | None
30 | };
31 | let sys = sys as *mut dyn System;
32 | Box::pin(async move {
33 | let sys = &mut *sys;
34 | let result = retrowin32_test::retrowin32_test_callback1(sys, func, data).await;
35 | if let Some(mut __trace_record) = __trace_record {
36 | __trace_record.exit(&result);
37 | }
38 | result.into()
39 | })
40 | }
41 | }
42 | }
43 | const SHIMS: [Shim; 1usize] = [Shim {
44 | name: "retrowin32_test_callback1",
45 | func: Handler::Async(wrappers::retrowin32_test_callback1),
46 | }];
47 | pub const DLL: BuiltinDLL = BuiltinDLL {
48 | file_name: "retrowin32_test.dll",
49 | shims: &SHIMS,
50 | raw: std::include_bytes!("../retrowin32_test.dll"),
51 | };
52 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/process.rs:
--------------------------------------------------------------------------------
1 | use super::SECURITY_ATTRIBUTES;
2 | pub use super::dll::STARTUPINFOA;
3 | use win32_system::System;
4 | use win32_winapi::{HANDLE, Str16};
5 |
6 | pub type HPROCESS = HANDLE<()>;
7 |
8 | // MSDN: "A pseudo handle is a special constant, currently (HANDLE)-1, that is interpreted as the current process handle."
9 | pub const CURRENT_PROCESS_HANDLE: HPROCESS = HPROCESS::from_raw(-1i32 as u32);
10 |
11 | #[win32_derive::dllexport]
12 | pub fn GetCurrentProcess(sys: &dyn System) -> HPROCESS {
13 | CURRENT_PROCESS_HANDLE
14 | }
15 |
16 | #[win32_derive::dllexport]
17 | pub fn GetExitCodeProcess(
18 | sys: &dyn System,
19 | hProcess: HPROCESS,
20 | lpExitCode: Option<&mut u32>,
21 | ) -> bool {
22 | todo!()
23 | }
24 |
25 | pub type PROCESS_INFORMATION = u32; // TODO
26 |
27 | pub type STARTUPINFOW = STARTUPINFOA; // TODO: same layout, different strings
28 |
29 | #[win32_derive::dllexport]
30 | pub fn CreateProcessW(
31 | sys: &dyn System,
32 | lpApplicationName: Option<&Str16>,
33 | lpCommandLine: Option<&Str16>,
34 | lpProcessAttributes: Option<&mut SECURITY_ATTRIBUTES>,
35 | lpThreadAttributes: Option<&mut SECURITY_ATTRIBUTES>,
36 | bInheritHandles: bool,
37 | dwCreationFlags: u32, /* PROCESS_CREATION_FLAGS */
38 | lpEnvironment: Option<&mut u32>,
39 | lpCurrentDirectory: Option<&Str16>,
40 | lpStartupInfo: Option<&mut STARTUPINFOW>,
41 | lpProcessInformation: Option<&mut PROCESS_INFORMATION>,
42 | ) -> bool {
43 | todo!()
44 | }
45 |
46 | #[win32_derive::dllexport]
47 | pub fn CreateProcessA(
48 | sys: &dyn System,
49 | lpApplicationName: Option<&str>,
50 | lpCommandLine: Option<&str>,
51 | lpProcessAttributes: Option<&mut SECURITY_ATTRIBUTES>,
52 | lpThreadAttributes: Option<&mut SECURITY_ATTRIBUTES>,
53 | bInheritHandles: bool,
54 | dwCreationFlags: u32, /* PROCESS_CREATION_FLAGS */
55 | lpEnvironment: Option<&mut u8>,
56 | lpCurrentDirectory: Option<&str>,
57 | lpStartupInfo: Option<&mut STARTUPINFOA>,
58 | lpProcessInformation: Option<&mut PROCESS_INFORMATION>,
59 | ) -> bool {
60 | todo!()
61 | }
62 |
--------------------------------------------------------------------------------
/win32/dll/kernel32/src/resource.rs:
--------------------------------------------------------------------------------
1 | use std::ops::Range;
2 | use win32_system::{System, generic_get_state, resource::find_resource};
3 | use win32_winapi::{HANDLE, HMODULE, Handles, Str16};
4 |
5 | pub use win32_system::resource::ResourceKey;
6 |
7 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
8 | pub struct HRSRCT;
9 | pub type HRSRC = HANDLE;
10 |
11 | pub struct ResourceHandle(Range);
12 |
13 | type State = Handles;
14 |
15 | #[inline]
16 | pub fn get_state(sys: &dyn System) -> std::cell::RefMut<'_, State> {
17 | generic_get_state::(sys)
18 | }
19 |
20 | #[win32_derive::dllexport]
21 | pub fn FindResourceA(
22 | sys: &dyn System,
23 | hModule: HMODULE,
24 | lpName: ResourceKey<&str>,
25 | lpType: ResourceKey<&str>,
26 | ) -> HRSRC {
27 | let name = lpName.to_string16();
28 | let type_ = lpType.to_string16();
29 | FindResourceW(sys, hModule, name.as_ref(), type_.as_ref())
30 | }
31 |
32 | #[win32_derive::dllexport]
33 | pub fn FindResourceW(
34 | sys: &dyn System,
35 | hModule: HMODULE,
36 | lpName: ResourceKey<&Str16>,
37 | lpType: ResourceKey<&Str16>,
38 | ) -> HRSRC {
39 | match find_resource(sys, hModule, lpType, &lpName) {
40 | None => HRSRC::null(),
41 | Some(mem) => get_state(sys).add(ResourceHandle(mem)),
42 | }
43 | }
44 |
45 | #[win32_derive::dllexport]
46 | pub fn LoadResource(sys: &dyn System, hModule: HMODULE, hResInfo: HRSRC) -> u32 {
47 | hResInfo.to_raw()
48 | }
49 |
50 | #[win32_derive::dllexport]
51 | pub fn LockResource(sys: &dyn System, hResData: HRSRC) -> u32 {
52 | match get_state(sys).get(hResData) {
53 | None => 0,
54 | Some(handle) => handle.0.start,
55 | }
56 | }
57 |
58 | #[win32_derive::dllexport]
59 | pub fn FreeResource(sys: &dyn System, hResData: u32) -> bool {
60 | // todo
61 | true // success
62 | }
63 |
64 | #[win32_derive::dllexport]
65 | pub fn SizeofResource(sys: &dyn System, hModule: HMODULE, hResInfo: HRSRC) -> u32 {
66 | match get_state(sys).get(hResInfo) {
67 | None => 0,
68 | Some(handle) => handle.0.len() as u32,
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/web/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 | retrowin32
3 |
4 |
42 |
43 |
44 |
45 |
46 | retrowin32
47 |
48 |
49 | retrowin32 is a still-early Windows emulator for the web (and other non-Windows platforms).
50 |
51 | Take a win32 .exe file and run it in a web browser or a Mac.
52 |
53 | See the README for more.
54 |
55 | {{range .Categories}}
56 | {{.Category}}
57 |
58 |
59 | {{range .Entries}}
60 | -
61 | {{.Title}}
62 | {{with .Origin}}(from
63 | {{if .URL}}
64 | {{.Desc}}
65 | {{- else}}
66 | {{.Desc}}
67 | {{- end}})
68 | {{end}}
69 |
70 | -
71 | {{.Desc}}
72 | {{with .Status}}
status: {{.}}{{end}}
73 |
74 | {{end}}
75 |
76 |
77 | {{end}}
78 |
79 |
80 |
--------------------------------------------------------------------------------
/win32/dll/ddraw/src/ddraw3.rs:
--------------------------------------------------------------------------------
1 | use win32_system::System;
2 | use win32_winapi::{com::GUID, vtable};
3 |
4 | pub const IID_IDirectDraw3: GUID = GUID((
5 | 0xda044e00,
6 | 0x69b2,
7 | 0x11d0,
8 | [0xa1, 0xd5, 0x00, 0xaa, 0x00, 0xb8, 0xdf, 0xbb],
9 | ));
10 |
11 | #[win32_derive::dllexport]
12 | pub mod IDirectDrawSurface3 {
13 | use super::*;
14 |
15 | vtable![
16 | QueryInterface: todo,
17 | AddRef: todo,
18 | Release: ok,
19 | AddAttachedSurface: todo,
20 | AddOverlayDirtyRect: todo,
21 | Blt: (IDirectDrawSurface7::Blt),
22 | BltBatch: todo,
23 | BltFast: todo,
24 | DeleteAttachedSurface: todo,
25 | EnumAttachedSurfaces: todo,
26 | EnumOverlayZOrders: todo,
27 | Flip: (IDirectDrawSurface7::Flip),
28 | GetAttachedSurface: todo,
29 | GetBltStatus: todo,
30 | GetCaps: todo,
31 | GetClipper: todo,
32 | GetColorKey: todo,
33 | GetDC: (IDirectDrawSurface7::GetDC),
34 | GetFlipStatus: todo,
35 | GetOverlayPosition: todo,
36 | GetPalette: todo,
37 | GetPixelFormat: (IDirectDrawSurface7::GetPixelFormat),
38 | GetSurfaceDesc: todo,
39 | Initialize: todo,
40 | IsLost: todo,
41 | Lock: todo,
42 | ReleaseDC: (IDirectDrawSurface7::ReleaseDC),
43 | Restore: todo,
44 | SetClipper: (IDirectDrawSurface7::SetClipper),
45 | SetColorKey: todo,
46 | SetOverlayPosition: todo,
47 | SetPalette: (IDirectDrawSurface7::SetPalette),
48 | Unlock: todo,
49 | UpdateOverlay: todo,
50 | UpdateOverlayDisplay: todo,
51 | UpdateOverlayZOrder: todo,
52 |
53 | GetDDInterface: todo,
54 | PageLock: todo,
55 | PageUnlock: todo,
56 |
57 | SetSurfaceDesc: todo,
58 | ];
59 |
60 | pub fn new(sys: &mut dyn System) -> u32 {
61 | let vtable = sys.get_symbol("ddraw.dll", "IDirectDrawSurface3");
62 | sys.memory().store(vtable)
63 | }
64 |
65 | #[win32_derive::dllexport]
66 | pub fn Release(sys: &dyn System, this: u32) -> u32 {
67 | 0 // TODO: return refcount?
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/web/debugger/labels.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * For debugging purposes we support labelling addresses.
3 | * These labels come either from ghidra CSV dumps, or from import tables.
4 | * When displaying an address we find the nearest label and display at as e.g.
5 | * SomeFunction+3c
6 | */
7 |
8 | import { hex } from './util';
9 |
10 | /**
11 | * Parses a Ghidra-generated .csv file contains labelled addresses.
12 | */
13 | export function* parseCSV(text: string): Iterable<[number, string]> {
14 | for (const line of text.split('\n')) {
15 | const [name, addr] = line.split('\t');
16 | yield [parseInt(addr, 16), name];
17 | }
18 | }
19 |
20 | /** Manages the collection of labels, as an ordered list. */
21 | export class Labels {
22 | byAddr: Array<[number, string]> = [];
23 |
24 | load(labels: Iterable<[number, string]>) {
25 | this.byAddr.push(...labels);
26 | // Avoid labelling small numbers.
27 | this.byAddr = this.byAddr.filter(([addr, _]) => addr > 0x1000);
28 | this.byAddr.sort(([a, _], [b, __]) => a - b);
29 | }
30 |
31 | find(target: number): [string, number] | undefined {
32 | // binary search for addr
33 | if (this.byAddr.length === 0) return undefined;
34 | let lo = 0, hi = this.byAddr.length;
35 | while (lo < hi - 1) {
36 | const mid = Math.floor((lo + hi) / 2);
37 | const [cur, label] = this.byAddr[mid];
38 | if (cur < target) {
39 | lo = mid;
40 | } else if (cur > target) {
41 | hi = mid;
42 | } else if (cur === target) {
43 | return [label, 0];
44 | }
45 | }
46 | const [cur, label] = this.byAddr[lo];
47 | if (cur <= target) {
48 | // Show the offset relative to the nearest labelled entry.
49 | const delta = target - cur;
50 | // We don't want very high addresses to appear as last+largenumber, so cap delta.
51 | if (delta < 0x100) {
52 | return [label, delta];
53 | }
54 | }
55 | return undefined;
56 | }
57 |
58 | get(addr: number): string | undefined {
59 | const match = this.find(addr);
60 | if (!match) return;
61 | let str = match[0];
62 | if (match[1]) str += `+${hex(match[1], 0)}`;
63 | return str;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/exe/cpp/thread.cc:
--------------------------------------------------------------------------------
1 | // Run two threads printing in parallel.
2 | // Purpose: exercise CreateThread and threading in general.
3 |
4 | #include "util.h"
5 |
6 | struct ThreadParams {
7 | const char* name;
8 | int steps;
9 | DWORD tls_key;
10 | };
11 |
12 | void run_thread(const ThreadParams* params) {
13 | for (int i = 0; i < params->steps; ++i) {
14 | DWORD thread_id = GetCurrentThreadId();
15 | DWORD tls = reinterpret_cast(TlsGetValue(params->tls_key));
16 | print(fmt().str("thread_id=").dec(thread_id).str(" name=").str(params->name).str(" tls=").dec(tls).str(" i=").dec(i).nl());
17 | Sleep(1000 / params->steps);
18 | }
19 | DWORD thread_id = GetCurrentThreadId();
20 | DWORD tls = reinterpret_cast(TlsGetValue(params->tls_key));
21 | print(fmt().str("thread_id=").dec(thread_id).str(" name=").str(params->name).str(" tls=").dec(tls).str(" returning").nl());
22 | }
23 |
24 | DWORD WINAPI thread_proc(LPVOID param) {
25 | ThreadParams* params = reinterpret_cast(param);
26 | TlsSetValue(params->tls_key, reinterpret_cast(2));
27 | run_thread(params);
28 | return 0;
29 | }
30 |
31 | DWORD WINAPI short_thread_proc(LPVOID) {
32 | DWORD thread_id = GetCurrentThreadId();
33 | print(fmt().str("thread_id=").dec(thread_id).str(" short_thread_proc exiting").nl());
34 | return 0;
35 | }
36 |
37 | extern "C" void mainCRTStartup() {
38 | // Create a thread that starts and exits quickly, to exercise thread teardown.
39 | CreateThread(
40 | nullptr,
41 | 0x1000,
42 | short_thread_proc,
43 | nullptr,
44 | 0,
45 | nullptr
46 | );
47 |
48 | DWORD tls_key = TlsAlloc();
49 | TlsSetValue(tls_key, reinterpret_cast(1));
50 |
51 | ThreadParams thread_params = {
52 | .name = "i_am_thread",
53 | .steps = 5,
54 | .tls_key = tls_key,
55 | };
56 |
57 | CreateThread(
58 | nullptr,
59 | 0x8000,
60 | thread_proc,
61 | &thread_params,
62 | 0,
63 | nullptr
64 | );
65 |
66 | ThreadParams main_thread = {
67 | .name = "main",
68 | .steps = 10,
69 | .tls_key = tls_key,
70 | };
71 | run_thread(&main_thread);
72 | }
73 |
--------------------------------------------------------------------------------
/win32/dll/user32/src/state.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | HBRUSH,
3 | message::MessageQueue,
4 | timer::Timers,
5 | window::{SW, WS, Window, WindowType},
6 | wndclass::{CS, WndClass, WndClasses},
7 | };
8 | use std::{cell::RefCell, rc::Rc};
9 | use win32_system::{System, generic_get_state};
10 | use win32_winapi::{HWND, Handles};
11 |
12 | pub struct State {
13 | pub wndclasses: WndClasses,
14 |
15 | /// Count of user-registered message ids, see RegisterWindowMessageA.
16 | pub user_window_message_count: u32,
17 |
18 | pub windows: Handles>>,
19 | pub desktop_window: HWND,
20 | pub active_window: HWND,
21 |
22 | pub messages: MessageQueue,
23 | pub timers: Timers,
24 | }
25 |
26 | impl Default for State {
27 | fn default() -> Self {
28 | Self {
29 | wndclasses: WndClasses::default(),
30 | user_window_message_count: 0,
31 | // Start window handles at 5, to make accidents more obvious.
32 | windows: Handles::new(5),
33 | desktop_window: HWND::null(),
34 | active_window: HWND::null(),
35 | messages: MessageQueue::default(),
36 | timers: Timers::default(),
37 | }
38 | }
39 | }
40 |
41 | impl State {
42 | pub fn desktop_window(&mut self) -> HWND {
43 | if self.desktop_window.is_null() {
44 | let wndclass = Rc::new(RefCell::new(WndClass {
45 | name: "Desktop".to_string(),
46 | style: CS::empty(),
47 | wndproc: 0,
48 | background: HBRUSH::null(),
49 | wnd_extra: 0,
50 | }));
51 | self.desktop_window = self.windows.add(Rc::new(RefCell::new(Window {
52 | id: 0,
53 | typ: WindowType::Desktop,
54 | width: 640,
55 | height: 480,
56 | wndclass,
57 | window_style: WS::empty(),
58 | other_style: 0,
59 | show_cmd: SW::SHOW,
60 | user_data: 0,
61 | extra: None,
62 | })));
63 | }
64 | self.desktop_window
65 | }
66 | }
67 |
68 | #[inline]
69 | pub fn get_state(sys: &dyn System) -> std::cell::RefMut<'_, State> {
70 | generic_get_state::(sys)
71 | }
72 |
--------------------------------------------------------------------------------
/pe/src/parse.rs:
--------------------------------------------------------------------------------
1 | //! Parsing PE files.
2 | //!
3 | //! Caller must call the functions in the proper sequence to successfully parse;
4 | //! use File::parse() for the simpler interface.
5 |
6 | use crate::{IMAGE_DATA_DIRECTORY, IMAGE_NT_HEADERS32, IMAGE_SECTION_HEADER};
7 | use anyhow::bail;
8 | use memory::Extensions;
9 |
10 | pub fn dos_header(buf: &[u8]) -> anyhow::Result {
11 | let sig = &buf[0..2];
12 | if sig != b"MZ" {
13 | bail!("invalid DOS signature; wanted 'MZ', got {:?}", sig);
14 | }
15 | let pe_offset = buf.get_pod::(0x3c);
16 | if pe_offset > buf.len() as u32 {
17 | bail!("invalid PE offset in DOS header, might be a DOS executable?");
18 | }
19 | Ok(pe_offset)
20 | }
21 |
22 | pub fn pe_header(buf: &[u8], ofs: &mut u32) -> anyhow::Result {
23 | let header = buf.get_pod::(*ofs);
24 | if header.Signature != *b"PE\0\0" {
25 | bail!(
26 | "invalid PE signature; wanted 'PE\\0\\0', got {:x?}",
27 | header.Signature
28 | );
29 | }
30 | let machine_i386 = 0x14c;
31 | if header.FileHeader.Machine != machine_i386 {
32 | bail!(
33 | "bad machine; wanted {machine_i386:x}, got {:x?}",
34 | header.FileHeader.Machine
35 | );
36 | }
37 | *ofs += std::mem::size_of::() as u32;
38 |
39 | Ok(header)
40 | }
41 |
42 | pub fn data_directory(
43 | header: &IMAGE_NT_HEADERS32,
44 | buf: &[u8],
45 | ofs: &mut u32,
46 | ) -> anyhow::Result> {
47 | let data_directory = buf
48 | .iter_pod::(*ofs, header.OptionalHeader.NumberOfRvaAndSizes)
49 | .collect();
50 | *ofs += std::mem::size_of::() as u32
51 | * header.OptionalHeader.NumberOfRvaAndSizes;
52 | Ok(data_directory)
53 | }
54 |
55 | pub fn sections(
56 | header: &IMAGE_NT_HEADERS32,
57 | buf: &[u8],
58 | ofs: &mut u32,
59 | ) -> anyhow::Result> {
60 | let sections = buf
61 | .iter_pod::(*ofs, header.FileHeader.NumberOfSections as u32)
62 | .collect();
63 | *ofs += std::mem::size_of::() as u32
64 | * header.FileHeader.NumberOfSections as u32;
65 | Ok(sections)
66 | }
67 |
--------------------------------------------------------------------------------
/doc/comparison.md:
--------------------------------------------------------------------------------
1 | # Comparison with similar systems
2 |
3 | To begin with, retrowin32 is relatively immature and cannot execute most
4 | programs.
5 |
6 | If you are just looking to run Windows software, I recommend instead using:
7 |
8 | - on x86 hardware: [Wine](https://www.winehq.org/)
9 | - on non-x86 hardware: [qemu](https://www.qemu.org/)
10 | - in a browser: [v86](https://copy.sh/v86/) or
11 | [Boxedwine](https://www.boxedwine.org/)
12 |
13 | ## Architectural differences
14 |
15 | Executing a Windows .exe broadly
16 | [requires two components](https://neugierig.org/software/blog/2023/01/emulating-win32.html):
17 | executing x86 instructions and interpreting the Windows calls.
18 |
19 | To run an exe natively requires both an x86 processor and the Windows OS:
20 |
21 |
22 |
23 | [Wine](https://www.winehq.org/) translates Windows calls onto a different OS,
24 | but still requires an x86 processor for the x86 instructions:
25 |
26 |
27 |
28 | On non-x86 platforms (including browsers),
29 | [Boxedwine](https://www.boxedwine.org/) combines Wine with a CPU emulator and
30 | implementation of the bits of Linux that Wine expects:
31 |
32 |
33 |
34 | x86 emulators like [qemu](https://www.qemu.org/) and [v86](https://copy.sh/v86/)
35 | emulate an x86 processor, but at a level where it still requires you to run the
36 | whole Windows OS within the emulator:
37 |
38 |
39 |
40 | Finally, retrowin32 aims to run a win32 executable directly, in a manner similar
41 | to how video game emulators work: by both emulating the executable and mapping
42 | its calls directly into local OS calls.
43 |
44 |
45 |
46 | Among the alternatives, retrowin32 is most similar to boxedwine. Note that while
47 | this drawing is visually shorter, it still encompasses most of the same
48 | complexity. The primary differences are:
49 |
50 | - the bulk of retrowin32's win32 implementation happens outside of the x86
51 | emulator;
52 | - retrowin32's win32 implementation doesn't depend on any Linux emulation, and
53 | can target the browser directly instead of SDL.
54 |
--------------------------------------------------------------------------------
/win32/system/src/resource.rs:
--------------------------------------------------------------------------------
1 | //! Windows resource system.
2 | //! Used by kernel32 and user32.
3 |
4 | use memory::Mem;
5 | use std::ops::Range;
6 | use win32_winapi::{HMODULE, Str16, String16, calling_convention::FromArg};
7 |
8 | use crate::System;
9 |
10 | fn is_intresource(x: u32) -> bool {
11 | x >> 16 == 0
12 | }
13 |
14 | /// ResourceKey is the type of queries into the Windows resources system, including
15 | /// e.g. LoadResource() as well as LoadBitmap() etc.
16 | /// It's parameterized over the type of name to handle both A() and W() variants.
17 | #[derive(Debug)]
18 | pub enum ResourceKey {
19 | Id(u32),
20 | Name(T),
21 | }
22 |
23 | impl ResourceKey {
24 | pub fn map_name<'a, R>(&'a self, f: impl Fn(&'a T) -> R) -> ResourceKey {
25 | match *self {
26 | ResourceKey::Id(id) => ResourceKey::Id(id),
27 | ResourceKey::Name(ref name) => ResourceKey::Name(f(name)),
28 | }
29 | }
30 | }
31 |
32 | impl ResourceKey<&str> {
33 | pub fn to_string16(&self) -> ResourceKey {
34 | self.map_name(|name| String16::from(name))
35 | }
36 | }
37 |
38 | impl ResourceKey {
39 | pub fn as_ref<'a>(&'a self) -> ResourceKey<&'a Str16> {
40 | self.map_name(|name| name.as_str16())
41 | }
42 | }
43 |
44 | impl ResourceKey<&Str16> {
45 | pub fn into_pe(&self) -> pe::ResourceName<'_> {
46 | match *self {
47 | ResourceKey::Id(id) => pe::ResourceName::Id(id),
48 | ResourceKey::Name(name) => pe::ResourceName::Name(name),
49 | }
50 | }
51 | }
52 |
53 | impl<'a, T> FromArg<'a> for ResourceKey
54 | where
55 | Option: FromArg<'a>,
56 | {
57 | fn from_arg(mem: Mem<'a>, arg: u32) -> Self {
58 | if is_intresource(arg) {
59 | ResourceKey::Id(arg)
60 | } else {
61 | ResourceKey::Name(