├── 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 | 27 | 28 | 29 | {rows} 30 |
ipstate
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 | 41 | 42 | 43 | 44 | 45 | 46 | {rows} 47 |
addrsizemoduledesc
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 | 47 | 48 | 49 | 50 | 51 | 52 | {rows} 53 |
ptrformatprimarypixelspalette
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 | Windows component stack 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 | wine component stack 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 | boxedwine component 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 | qemu component stack 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 | retrowin32 component stack 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(>::from_arg(mem, arg).unwrap()) 62 | } 63 | } 64 | } 65 | 66 | pub fn find_resource<'a>( 67 | sys: &dyn System, 68 | hinstance: HMODULE, 69 | typ: ResourceKey<&Str16>, 70 | name: &ResourceKey<&Str16>, 71 | ) -> Option> { 72 | let resources = sys.get_resources(hinstance)?; 73 | let addr = hinstance.to_raw(); 74 | pe::find_resource(resources, typ.into_pe(), name.into_pe()) 75 | .map(|r| (addr + r.start)..(addr + r.end)) 76 | } 77 | -------------------------------------------------------------------------------- /x86/src/ops/math/div.rs: -------------------------------------------------------------------------------- 1 | use crate::{ops::helpers::*, x86::CPU}; 2 | use iced_x86::{Instruction, Register}; 3 | use memory::Mem; 4 | 5 | /// idiv: Signed Divide 6 | pub fn idiv_rm32(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 7 | let x = get_edx_eax(cpu) as i64; 8 | let y = rm32(cpu, mem, instr).get() as i32 as i64; 9 | cpu.regs.set32(Register::EAX, (x / y) as i32 as u32); 10 | cpu.regs.set32(Register::EDX, (x % y) as i32 as u32); 11 | } 12 | 13 | /// idiv: Signed Divide 14 | pub fn idiv_rm16(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 15 | let x = get_dx_ax(cpu) as i32; 16 | let y = rm16(cpu, mem, instr).get() as i16 as i32; 17 | let quotient = x / y; 18 | if quotient > 0x7FFF || quotient < -0x8000 { 19 | panic!("divide error"); 20 | } 21 | cpu.regs.set16(Register::AX, quotient as i16 as u16); 22 | cpu.regs.set16(Register::DX, (x % y) as u16); 23 | } 24 | 25 | /// idiv: Signed Divide 26 | pub fn idiv_rm8(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 27 | let x = cpu.regs.get16(Register::AX) as i16; 28 | let y = rm8(cpu, mem, instr).get() as i8 as i16; 29 | let quotient = x / y; 30 | if quotient > 0x7F || quotient < -0x80 { 31 | panic!("divide error"); 32 | } 33 | let rem = x % y; 34 | cpu.regs 35 | .set16(Register::AX, ((rem << 8) as u16) | (quotient as i8 as u16)); 36 | } 37 | 38 | /// div: Unsigned Divide 39 | pub fn div_rm32(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 40 | let x = get_edx_eax(cpu); 41 | let y = rm32(cpu, mem, instr).get() as u64; 42 | cpu.regs.set32(Register::EAX, (x / y) as u32); 43 | cpu.regs.set32(Register::EDX, (x % y) as u32); 44 | // No flags. 45 | } 46 | 47 | /// div: Unsigned Divide 48 | pub fn div_rm16(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 49 | let x = get_dx_ax(cpu); 50 | let y = rm16(cpu, mem, instr).get() as u32; 51 | cpu.regs.set32(Register::EAX, ((x / y) as u16) as u32); 52 | cpu.regs.set32(Register::EDX, ((x % y) as u16) as u32); 53 | // No flags. 54 | } 55 | 56 | /// div: Unsigned Divide 57 | pub fn div_rm8(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 58 | let x = cpu.regs.get16(Register::AX); 59 | let y = rm8(cpu, mem, instr).get() as u16; 60 | cpu.regs 61 | .set32(Register::EAX, (((x % y) as u32) << 16) | ((x / y) as u32)); 62 | // No flags. 63 | } 64 | -------------------------------------------------------------------------------- /web/debugger/trace.tsx: -------------------------------------------------------------------------------- 1 | import * as preact from 'preact'; 2 | import * as emulator from '../emulator'; 3 | import * as wasm from '../glue/pkg/glue'; 4 | import { hex } from './util'; 5 | 6 | const SHOW_ROWS = 10; 7 | 8 | namespace TraceComponent { 9 | export interface Props { 10 | trace: emulator.Trace[]; 11 | } 12 | export interface State { 13 | row: number; 14 | /** When true, always show most recent row. */ 15 | followTail: boolean; 16 | } 17 | } 18 | export class TraceComponent extends preact.Component { 19 | constructor() { 20 | super(); 21 | this.state = { row: 0, followTail: true }; 22 | } 23 | 24 | static getDerivedStateFromProps(props: TraceComponent.Props, state: TraceComponent.State) { 25 | if (state.followTail) { 26 | return { row: Math.max(state.row, props.trace.length - SHOW_ROWS) }; 27 | } 28 | return null; 29 | } 30 | 31 | onWheel = (ev: WheelEvent) => { 32 | const ofs = Math.round(ev.deltaY); 33 | const row = Math.max(0, Math.min(this.props.trace.length - SHOW_ROWS, this.state.row + ofs)); 34 | this.setState({ row, followTail: row + SHOW_ROWS >= this.props.trace.length }); 35 | }; 36 | 37 | jumpToEnd = () => { 38 | this.setState({ row: Math.max(0, this.props.trace.length - SHOW_ROWS) }); 39 | }; 40 | 41 | render() { 42 | const { trace } = this.props; 43 | const rows = []; 44 | let i; 45 | for (i = this.state.row; i < Math.min(trace.length, this.state.row + SHOW_ROWS); i++) { 46 | const { context, msg } = trace[i]; 47 | rows.push( 48 | 49 | {context} 50 | {msg} 51 | , 52 | ); 53 | } 54 | if (i < trace.length) { 55 | rows.push( 56 | 57 | 58 | 59 | 60 | 61 | , 62 | ); 63 | } 64 | 65 | return ( 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | {rows} 74 | 75 |
contextcall
76 |
77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /win32/dll/gdi32/src/palette.rs: -------------------------------------------------------------------------------- 1 | use crate::{HDC, HGDIOBJ}; 2 | use win32_system::System; 3 | use win32_winapi::HANDLE; 4 | 5 | pub type HPALETTE = HANDLE<()>; // TODO 6 | 7 | #[repr(C)] 8 | #[derive(Clone, Debug)] 9 | pub struct PALETTEENTRY { 10 | pub peRed: u8, 11 | pub peGreen: u8, 12 | pub peBlue: u8, 13 | pub peFlags: u8, 14 | } 15 | unsafe impl memory::Pod for PALETTEENTRY {} 16 | 17 | #[win32_derive::dllexport] 18 | pub fn CreatePalette(sys: &dyn System, plpal: u32) -> HGDIOBJ { 19 | HGDIOBJ::null() 20 | } 21 | 22 | #[win32_derive::dllexport] 23 | pub fn RealizePalette(sys: &dyn System, hdc: HDC) -> u32 { 24 | 0 // number of entries mapped 25 | } 26 | 27 | #[win32_derive::dllexport] 28 | pub fn SelectPalette(sys: &dyn System, hdc: HDC, hPal: HPALETTE, bForceBkgd: bool) -> HPALETTE { 29 | if hPal.is_null() { 30 | // ok, we assume all palettes are null for now 31 | return hPal; 32 | } 33 | todo!() 34 | } 35 | 36 | #[win32_derive::dllexport] 37 | pub fn SetPaletteEntries( 38 | sys: &dyn System, 39 | hpal: HPALETTE, 40 | iStart: u32, 41 | cEntries: u32, 42 | pPalEntries: Option<&mut PALETTEENTRY>, 43 | ) -> u32 { 44 | if hpal.is_null() { 45 | // ok, we assume all palettes are null for now 46 | return 0; 47 | } 48 | todo!() 49 | } 50 | 51 | #[win32_derive::dllexport] 52 | pub fn GetPaletteEntries( 53 | sys: &dyn System, 54 | hpal: HPALETTE, 55 | iStart: u32, 56 | cEntries: u32, 57 | pPalEntries: Option<&mut PALETTEENTRY>, 58 | ) -> u32 { 59 | todo!() 60 | } 61 | 62 | #[win32_derive::dllexport] 63 | pub fn GetSystemPaletteEntries( 64 | sys: &dyn System, 65 | hdc: HDC, 66 | iStart: u32, 67 | cEntries: u32, 68 | pPalEntries: Option<&mut PALETTEENTRY>, 69 | ) -> u32 { 70 | 0 // no system palette entries 71 | } 72 | 73 | #[win32_derive::dllexport] 74 | pub fn SetSystemPaletteUse( 75 | sys: &dyn System, 76 | hdc: HDC, 77 | use_: u32, /* SYSTEM_PALETTE_USE */ 78 | ) -> u32 { 79 | /// System does not support palettes. 80 | const SYSPAL_ERROR: u32 = 0; 81 | SYSPAL_ERROR 82 | } 83 | 84 | #[win32_derive::dllexport] 85 | pub fn ResizePalette(sys: &dyn System, hpal: HPALETTE, n: u32) -> bool { 86 | if hpal.is_null() { 87 | // ok, we assume all palettes are null for now 88 | return true; // success 89 | } 90 | todo!() 91 | } 92 | -------------------------------------------------------------------------------- /cli/src/host.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "sdl"))] 2 | use crate::headless::GUI; 3 | #[cfg(feature = "sdl")] 4 | use crate::sdl::GUI; 5 | use crate::time::Time; 6 | use std::{ 7 | cell::{RefCell, RefMut}, 8 | io::Write, 9 | }; 10 | 11 | pub struct Env { 12 | // TODO: rearrange things so that we don't need this field at all in headless mode. 13 | // I think maybe the Host trait should be more like a subsystem factory, which can 14 | // return a GUI subsystem, kind of like how Audio is set up. 15 | gui: RefCell>, 16 | time: Time, 17 | } 18 | 19 | impl Env { 20 | pub fn new() -> Self { 21 | Env { 22 | gui: RefCell::new(None), 23 | time: Time::new(), 24 | } 25 | } 26 | 27 | pub fn ensure_gui(&self) -> RefMut<'_, GUI> { 28 | let mut gui = self.gui.borrow_mut(); 29 | if gui.is_none() { 30 | *gui = Some(GUI::new(self.time.clone()).unwrap()); 31 | } 32 | RefMut::map(gui, |gui| gui.as_mut().unwrap()) 33 | } 34 | } 35 | 36 | impl win32::host::Host for Env { 37 | fn exit(&self, status: u32) { 38 | std::process::exit(status as i32); 39 | } 40 | 41 | fn ticks(&self) -> u32 { 42 | self.time.ticks() 43 | } 44 | 45 | fn system_time(&self) -> chrono::DateTime { 46 | chrono::Local::now() 47 | } 48 | 49 | fn get_message(&self) -> Option { 50 | self.ensure_gui().get_message() 51 | } 52 | 53 | fn block(&self, wait: Option) -> bool { 54 | self.ensure_gui().block(wait) 55 | } 56 | 57 | fn stdout(&self, buf: &[u8]) { 58 | std::io::stdout().lock().write_all(buf).unwrap(); 59 | } 60 | 61 | fn create_window(&self, hwnd: u32) -> Box { 62 | self.ensure_gui().create_window(hwnd) 63 | } 64 | 65 | fn create_surface( 66 | &self, 67 | _hwnd: u32, 68 | opts: &win32::host::SurfaceOptions, 69 | ) -> Box { 70 | self.ensure_gui().create_surface(opts) 71 | } 72 | 73 | fn init_audio( 74 | &self, 75 | sample_rate: u32, 76 | callback: win32::host::AudioCallback, 77 | ) -> Box { 78 | self.ensure_gui().init_audio(sample_rate, callback) 79 | } 80 | } 81 | 82 | pub fn new_host() -> Env { 83 | Env::new() 84 | } 85 | -------------------------------------------------------------------------------- /win32/dll/wininet/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 wininet; 7 | use crate::*; 8 | use ::memory::Extensions; 9 | use win32_system::{System, trace}; 10 | use win32_winapi::{calling_convention::*, *}; 11 | pub unsafe fn InternetOpenA(sys: &mut dyn System, stack_args: u32) -> ABIReturn { 12 | use wininet::*; 13 | unsafe { 14 | let mem = sys.mem().detach(); 15 | let lpszAgent = >::from_stack(mem, stack_args + 0u32); 16 | let dwAccessType = ::from_stack(mem, stack_args + 4u32); 17 | let lpszProxy = >::from_stack(mem, stack_args + 8u32); 18 | let lpszProxyBypass = >::from_stack(mem, stack_args + 12u32); 19 | let dwFlags = ::from_stack(mem, stack_args + 16u32); 20 | let __trace_record = if trace::enabled("wininet") { 21 | trace::Record::new( 22 | wininet::InternetOpenA_pos, 23 | "wininet", 24 | "InternetOpenA", 25 | &[ 26 | ("lpszAgent", &lpszAgent), 27 | ("dwAccessType", &dwAccessType), 28 | ("lpszProxy", &lpszProxy), 29 | ("lpszProxyBypass", &lpszProxyBypass), 30 | ("dwFlags", &dwFlags), 31 | ], 32 | ) 33 | .enter() 34 | } else { 35 | None 36 | }; 37 | let result = wininet::InternetOpenA( 38 | sys, 39 | lpszAgent, 40 | dwAccessType, 41 | lpszProxy, 42 | lpszProxyBypass, 43 | dwFlags, 44 | ); 45 | if let Some(mut __trace_record) = __trace_record { 46 | __trace_record.exit(&result); 47 | } 48 | result.into() 49 | } 50 | } 51 | } 52 | const SHIMS: [Shim; 1usize] = [Shim { 53 | name: "InternetOpenA", 54 | func: Handler::Sync(wrappers::InternetOpenA), 55 | }]; 56 | pub const DLL: BuiltinDLL = BuiltinDLL { 57 | file_name: "wininet.dll", 58 | shims: &SHIMS, 59 | raw: std::include_bytes!("../wininet.dll"), 60 | }; 61 | -------------------------------------------------------------------------------- /pe/src/relocations.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | #![allow(non_camel_case_types)] 3 | 4 | use memory::Extensions; 5 | 6 | #[repr(C)] 7 | #[derive(Debug)] 8 | struct IMAGE_BASE_RELOCATION { 9 | VirtualAddress: u32, 10 | SizeOfBlock: u32, 11 | } 12 | unsafe impl memory::Pod for IMAGE_BASE_RELOCATION {} 13 | 14 | /// Iterates IMAGE_BASE_RELOCATION+body blocks. 15 | fn block_iter(mut buf: &[u8]) -> impl Iterator { 16 | std::iter::from_fn(move || { 17 | if buf.len() == 0 { 18 | return None; 19 | } 20 | let reloc = buf.get_pod::(0); 21 | if reloc.VirtualAddress == 0 { 22 | // fmod.dll has a block with addr=0, size=8 (header size), and then trailing garbage after that 23 | return None; 24 | } 25 | let body = &buf[std::mem::size_of::()..reloc.SizeOfBlock as usize]; 26 | buf = &buf[reloc.SizeOfBlock as usize..]; 27 | Some((reloc.VirtualAddress, body)) 28 | }) 29 | } 30 | 31 | pub fn apply_relocs( 32 | prev_base: u32, 33 | base: u32, 34 | relocs: &[u8], 35 | mut read: impl FnMut(u32) -> u32, 36 | mut write: impl FnMut(u32, u32), 37 | ) { 38 | // monolife.exe has no IMAGE_DIRECTORY_ENTRY::BASERELOC, but does 39 | // have a .reloc section that is invalid (?). 40 | // Note: IMAGE_SECTION_HEADER itself also has some relocation-related fields 41 | // that appear to only apply to object files (?). 42 | 43 | let offset = base.wrapping_sub(prev_base); 44 | 45 | for (addr, body) in block_iter(relocs) { 46 | for entry in body.into_iter_pod::() { 47 | let etype = entry >> 12; 48 | let ofs = entry & 0x0FFF; 49 | let addr = addr + ofs as u32; 50 | match etype { 51 | 0 => {} // skip 52 | 3 => { 53 | // IMAGE_REL_BASED_HIGHLOW, 32-bit adjustment 54 | // win2k's glu32.dll has a relocation offsetting the value 0x6fa7a09 55 | // despite the image base being 0x6fac000, so it is a reference to memory 56 | // before the image?! 57 | let old = read(addr); 58 | let new = old.wrapping_add(offset); 59 | write(addr, new); 60 | } 61 | _ => panic!("unhandled relocation type {etype}"), 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /misc/lldb-trace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | This script attempts to drive lldb running in Rosetta mode 5 | to reproduce a trace as generated by retrowin32 --trace-points. 6 | """ 7 | 8 | """ 9 | export PYTHONPATH=`/Applications/Xcode.app/Contents/Developer/usr/bin/lldb -P` 10 | /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/Current/bin/python3 -u misc/lldb-trace.py | tee log 11 | """ 12 | 13 | import lldb 14 | import os 15 | 16 | 17 | RETROWIN32 = './target/x86_64-apple-darwin/debug/retrowin32' 18 | TARGET_TRIPLE = 'x86_64-apple-macosx13.0.0' 19 | ARGS = ['-C', '../os/share/Pinball', 'pinball.exe'] 20 | 21 | trace_points = [] 22 | with open('tp') as f: 23 | for line in f: 24 | if line.startswith('@'): 25 | addr = int(line.strip()[1:], 16) 26 | if addr < 0x2000: # filter out retrowin32 syscalls 27 | continue 28 | if addr == 0xffff_fff0: # filter out async executor 29 | continue 30 | trace_points.append(addr) 31 | 32 | assert len(trace_points) > 0 33 | 34 | debugger = lldb.SBDebugger.Create() 35 | debugger.SetAsync(False) 36 | target = debugger.CreateTargetWithFileAndTargetTriple(RETROWIN32, TARGET_TRIPLE) 37 | 38 | print('setting bp in retrowin32_main') 39 | bp = target.BreakpointCreateByName("retrowin32_main", target.GetExecutable().GetFilename()) 40 | err = lldb.SBError() 41 | 42 | print('launching target') 43 | # Note target.LaunchSimple swallows stdout etc :( 44 | process = target.Launch( 45 | lldb.SBListener(), ['--win32-trace', '*', *ARGS], None, 46 | '/dev/stdin', '/dev/stdout', '/dev/stderr', os.getcwd(), 47 | 0, False, err 48 | ) 49 | 50 | while True: 51 | bp.ClearAllBreakpointSites() 52 | thread = process.GetThreadAtIndex(0) 53 | frame = thread.GetFrameAtIndex(0) 54 | 55 | vals = ' '.join( 56 | '%s:%x' % ('e' + reg, frame.reg['r' + reg].unsigned & 0xFFFF_FFFF) 57 | for reg in ['ax', 'bx', 'cx', 'dx', 'si', 'di', 'sp', 'bp'] 58 | ) 59 | print('@%x' % frame.reg['rip'].unsigned) 60 | print(' ' + vals) 61 | # Note: FPU state in lldb is 80-bit floats, and for the life of me I could not 62 | # get it to print those as anything other than arrays of bytes argh. 63 | #print(' fpu: ' + ' '.join(('%f' % frame.reg['stmm%d' % d].data.double[0]) for d in range(0,8))) 64 | 65 | next = trace_points.pop(0) 66 | bp = target.BreakpointCreateByAddress(next) 67 | process.Continue() 68 | 69 | -------------------------------------------------------------------------------- /exe/ops/util.cc: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | HANDLE hStdout; 4 | 5 | void print(std::string_view sv) { 6 | WriteFile(hStdout, sv.data(), sv.size(), nullptr, nullptr); 7 | } 8 | 9 | void print(uint32_t x) { 10 | char buf[9]; 11 | size_t i = sizeof(buf); 12 | if (x == 0) { 13 | buf[--i] = '0'; 14 | } else { 15 | for (; x > 0; x >>= 4) { 16 | auto nybble = (char)(x & 0xf); 17 | if (nybble < 0xa) { 18 | buf[--i] = '0' + nybble; 19 | } else { 20 | buf[--i] = 'a' + (nybble - 0xa); 21 | } 22 | } 23 | } 24 | print(std::string_view(&buf[i], sizeof(buf) - i)); 25 | } 26 | 27 | void print(double f) { 28 | // To print a float, we multiply by 1000 and print as decimal. 29 | 30 | bool neg = f < 0; 31 | if (f < 0) 32 | f = -f; 33 | uint32_t x = (uint32_t)(f * 1000.0); 34 | char buf[64]; 35 | size_t i = sizeof(buf); 36 | if (x == 0) { 37 | buf[--i] = '0'; 38 | } else { 39 | while (x > 0 || i > (sizeof(buf) - 5)) { 40 | buf[--i] = '0' + (x % 10); 41 | x /= 10; 42 | if (i == sizeof(buf) - 3) { 43 | buf[--i] = '.'; 44 | if (x == 0) { 45 | buf[--i] = '0'; 46 | } 47 | } 48 | } 49 | } 50 | if (neg) { 51 | buf[--i] = '-'; 52 | } 53 | print(std::string_view(&buf[i], sizeof(buf) - i)); 54 | } 55 | 56 | void printv(const char *fmt...) { 57 | va_list args; 58 | va_start(args, fmt); 59 | size_t start = 0, end; 60 | for (end = start; fmt[end]; end++) { 61 | if (fmt[end] == '%') { 62 | if (end > start) { 63 | print(std::string_view(&fmt[start], end - start)); 64 | } 65 | end++; 66 | if (fmt[end] == 'x') { 67 | auto n = static_cast(va_arg(args, int)); 68 | print(n); 69 | } else if (fmt[end] == 'f') { 70 | auto n = va_arg(args, double); 71 | print(n); 72 | } else { 73 | print("invalid format specifier\n"); 74 | __builtin_trap(); 75 | } 76 | start = end + 1; 77 | } 78 | } 79 | if (end > start) { 80 | WriteFile(hStdout, &fmt[start], end - start, nullptr, nullptr); 81 | } 82 | va_end(args); 83 | } 84 | 85 | void print_flags(uint32_t flags) { 86 | if ((flags & 1) != 0) 87 | print(" CF"); 88 | if ((flags & (1 << 2)) != 0) 89 | print(" PF"); 90 | if ((flags & (1 << 6)) != 0) 91 | print(" ZF"); 92 | if ((flags & (1 << 7)) != 0) 93 | print(" SF"); 94 | if ((flags & (1 << 10)) != 0) 95 | print(" DF"); 96 | if ((flags & (1 << 11)) != 0) 97 | print(" OF"); 98 | } 99 | -------------------------------------------------------------------------------- /win32/dll/user32/src/rect.rs: -------------------------------------------------------------------------------- 1 | use win32_system::System; 2 | use win32_winapi::{POINT, RECT}; 3 | 4 | #[win32_derive::dllexport] 5 | pub fn SetRect( 6 | sys: &dyn System, 7 | lprc: Option<&mut RECT>, 8 | xLeft: i32, 9 | yTop: i32, 10 | xRight: i32, 11 | yBottom: i32, 12 | ) -> bool { 13 | let rect = lprc.unwrap(); 14 | *rect = RECT { 15 | left: xLeft, 16 | top: yTop, 17 | right: xRight, 18 | bottom: yBottom, 19 | }; 20 | true 21 | } 22 | 23 | #[win32_derive::dllexport] 24 | pub fn PtInRect(sys: &dyn System, lprc: Option<&RECT>, pt: POINT) -> bool { 25 | let rect = lprc.unwrap(); 26 | pt.x >= rect.left && pt.x < rect.right && pt.y >= rect.top && pt.y < rect.bottom 27 | } 28 | 29 | #[win32_derive::dllexport] 30 | pub fn IsRectEmpty(sys: &dyn System, lprc: Option<&RECT>) -> bool { 31 | let rect = lprc.unwrap(); 32 | rect.left >= rect.right || rect.top >= rect.bottom 33 | } 34 | 35 | #[win32_derive::dllexport] 36 | pub fn SetRectEmpty(sys: &dyn System, lprc: Option<&mut RECT>) -> bool { 37 | if lprc.is_none() { 38 | return false; 39 | } 40 | let rect = lprc.unwrap(); 41 | rect.left = 0; 42 | rect.top = 0; 43 | rect.right = 0; 44 | rect.bottom = 0; 45 | true 46 | } 47 | 48 | #[win32_derive::dllexport] 49 | pub fn IntersectRect( 50 | sys: &dyn System, 51 | lprcDst: Option<&mut RECT>, 52 | lprcSrc1: Option<&RECT>, 53 | lprcSrc2: Option<&RECT>, 54 | ) -> bool { 55 | if lprcDst.is_none() || lprcSrc1.is_none() || lprcSrc2.is_none() { 56 | return false; 57 | } 58 | let dst = lprcDst.unwrap(); 59 | let src1 = lprcSrc1.unwrap(); 60 | let src2 = lprcSrc2.unwrap(); 61 | if IsRectEmpty(sys, lprcSrc1) 62 | || IsRectEmpty(sys, lprcSrc2) 63 | || src1.left >= src2.right 64 | || src1.right <= src2.left 65 | || src1.top >= src2.bottom 66 | || src1.bottom <= src2.top 67 | { 68 | return false; 69 | } 70 | dst.left = src1.left.max(src2.left); 71 | dst.right = src1.right.min(src2.right); 72 | dst.top = src1.top.max(src2.top); 73 | dst.bottom = src1.bottom.min(src2.bottom); 74 | dst.left < dst.right && dst.top < dst.bottom 75 | } 76 | 77 | #[win32_derive::dllexport] 78 | pub fn CopyRect(sys: &dyn System, lprcDst: Option<&mut RECT>, lprcSrc: Option<&RECT>) -> bool { 79 | todo!() 80 | } 81 | 82 | #[win32_derive::dllexport] 83 | pub fn InflateRect(sys: &dyn System, lprc: Option<&mut RECT>, dx: i32, dy: i32) -> bool { 84 | todo!() 85 | } 86 | -------------------------------------------------------------------------------- /win32/dll/ucrtbase/src/misc.rs: -------------------------------------------------------------------------------- 1 | use win32_system::{System, host}; 2 | use win32_winapi::calling_convention::VarArgs; 3 | 4 | #[win32_derive::dllexport(cdecl)] 5 | pub fn _exit(sys: &mut dyn System, status: u32) { 6 | sys.exit(status); 7 | } 8 | 9 | #[win32_derive::dllexport(cdecl)] 10 | pub fn exit(sys: &mut dyn System, status: u32) { 11 | sys.exit(status); 12 | } 13 | 14 | #[win32_derive::dllexport(cdecl)] 15 | pub fn _onexit(sys: &mut dyn System, func: u32) -> u32 { 16 | // register onexit handler 17 | func 18 | } 19 | 20 | #[win32_derive::dllexport(cdecl)] 21 | pub fn atexit(sys: &mut dyn System, func: u32) -> u32 { 22 | // register onexit handler 23 | 0 // success 24 | } 25 | 26 | #[win32_derive::dllexport(cdecl)] 27 | pub fn _cexit(sys: &mut dyn System) { 28 | // call atexit handlers but don't exit 29 | } 30 | 31 | #[win32_derive::dllexport(cdecl, symbol = "?terminate@@YAXXZ")] 32 | pub fn terminate(sys: &dyn System) { 33 | todo!() 34 | } 35 | 36 | struct HostStdout<'a>(&'a dyn host::Host); 37 | 38 | impl<'a> std::io::Write for HostStdout<'a> { 39 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 40 | self.0.stdout(buf); 41 | Ok(buf.len()) 42 | } 43 | 44 | fn flush(&mut self) -> std::io::Result<()> { 45 | Ok(()) 46 | } 47 | } 48 | 49 | #[win32_derive::dllexport(cdecl)] 50 | pub fn printf(sys: &dyn System, fmt: Option<&str>, args: VarArgs) -> i32 { 51 | let mut out = HostStdout(sys.host()); 52 | builtin_user32::printf::printf(&mut out, fmt.unwrap(), args, sys.mem()).unwrap(); 53 | 1 // TODO: compute length written 54 | } 55 | 56 | #[win32_derive::dllexport(cdecl)] 57 | pub fn sprintf(sys: &mut dyn System, buf: u32, fmt: Option<&str>, args: VarArgs) -> i32 { 58 | todo!() 59 | } 60 | 61 | #[win32_derive::dllexport(cdecl)] 62 | pub fn vfprintf(sys: &mut dyn System, buf: u32, fmt: Option<&str>, args: VarArgs) -> i32 { 63 | todo!() 64 | } 65 | 66 | // TODO: needs to be an array 67 | #[win32_derive::dllexport] 68 | pub const _iob: &'static str = "_iob"; 69 | 70 | // TODO: needs to be an array 71 | #[win32_derive::dllexport] 72 | pub const _winmajor: &'static str = "_winmajor"; 73 | 74 | #[win32_derive::dllexport(cdecl)] 75 | pub fn abort(sys: &mut dyn System) { 76 | todo!() 77 | } 78 | 79 | #[win32_derive::dllexport(cdecl)] 80 | pub fn fwrite(sys: &mut dyn System, filename: Option<&str>, mode: Option<&str>) -> u32 { 81 | todo!() 82 | } 83 | 84 | #[win32_derive::dllexport(cdecl)] 85 | pub fn signal(sys: &mut dyn System, sig: u32, func: u32) { 86 | todo!() 87 | } 88 | -------------------------------------------------------------------------------- /web/web.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * The Preact compoent that hosts the emulator, mapping emulator Window to Preact DOM. 3 | */ 4 | 5 | import * as preact from 'preact'; 6 | import { Emulator } from './emulator'; 7 | 8 | namespace WindowComponent { 9 | export interface Props { 10 | title: string; 11 | canvas: HTMLCanvasElement; 12 | } 13 | export interface State { 14 | drag?: [number, number]; 15 | pos: [number, number]; 16 | } 17 | } 18 | class WindowComponent extends preact.Component { 19 | state: WindowComponent.State = { 20 | pos: [200, 200], 21 | }; 22 | ref = preact.createRef(); 23 | 24 | beginDrag = (e: PointerEvent) => { 25 | const node = e.currentTarget as HTMLElement; 26 | this.setState({ drag: [e.offsetX, e.offsetY] }); 27 | node.setPointerCapture(e.pointerId); 28 | e.preventDefault(); 29 | }; 30 | onDrag = (e: PointerEvent) => { 31 | if (!this.state.drag) return; 32 | this.setState({ pos: [e.clientX - this.state.drag[0], e.clientY - this.state.drag[1]] }); 33 | e.preventDefault(); 34 | }; 35 | endDrag = (e: PointerEvent) => { 36 | const node = e.currentTarget as HTMLElement; 37 | this.setState({ drag: undefined }); 38 | node.releasePointerCapture(e.pointerId); 39 | e.preventDefault(); 40 | }; 41 | 42 | ensureCanvas() { 43 | // XXX: how to ensure the canvas appears as a child of this widget? 44 | if (this.props.canvas && this.ref.current && !this.ref.current.firstChild) { 45 | this.ref.current.appendChild(this.props.canvas); 46 | } 47 | } 48 | 49 | componentDidMount(): void { 50 | this.ensureCanvas(); 51 | } 52 | 53 | render() { 54 | this.ensureCanvas(); 55 | return ( 56 |
57 |
58 | {this.props.title} 59 |
60 |
61 |
62 | ); 63 | } 64 | } 65 | 66 | namespace EmulatorComponent { 67 | export interface Props { 68 | emulator: Emulator; 69 | } 70 | } 71 | export class EmulatorComponent extends preact.Component { 72 | render() { 73 | return this.props.emulator.windows.map((window) => { 74 | return ( 75 | 80 | ); 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /win32/dll/kernel32/src/file/file16.rs: -------------------------------------------------------------------------------- 1 | //! File API for 16-bit Windows backward compat. 2 | 3 | use super::{HFILE, get_state}; 4 | use std::io::SeekFrom; 5 | use win32_system::System; 6 | use win32_winapi::{ERROR, calling_convention::ArrayOut}; 7 | 8 | #[win32_derive::dllexport] 9 | pub fn _lopen(sys: &dyn System, lpPathName: Option<&str>, iReadWrite: i32) -> HFILE { 10 | if iReadWrite != 0 { 11 | todo!(); 12 | } 13 | super::file::OpenFile(sys, lpPathName, None, 0) 14 | } 15 | 16 | #[win32_derive::dllexport] 17 | pub fn _lclose(sys: &dyn System, hFile: HFILE) -> HFILE { 18 | if get_state(sys).files.remove(hFile).is_none() { 19 | log::debug!("CloseHandle({hFile:?}): unknown handle"); 20 | sys.set_last_error(ERROR::INVALID_HANDLE); 21 | // Docs don't mention any error handling, this is just a guess! 22 | return HFILE::null(); 23 | } 24 | 25 | sys.set_last_error(ERROR::SUCCESS); 26 | hFile 27 | } 28 | 29 | #[win32_derive::dllexport] 30 | pub fn _llseek(sys: &dyn System, hFile: HFILE, lOffset: i32, iOrigin: i32) -> i32 { 31 | let mut state = get_state(sys); 32 | let Some(file) = state.files.get_mut(hFile) else { 33 | sys.set_last_error(ERROR::INVALID_HANDLE); 34 | return -1; 35 | }; 36 | 37 | let seek = match iOrigin { 38 | 0 => SeekFrom::Start(lOffset as u64), 39 | 1 => SeekFrom::Current(lOffset as i64), 40 | 2 => SeekFrom::End(lOffset as i64), 41 | _ => { 42 | sys.set_last_error(ERROR::INVALID_PARAMETER); 43 | return -1; 44 | } 45 | }; 46 | match file.seek(seek) { 47 | Ok(new_pos) => new_pos as i32, 48 | Err(_) => { 49 | sys.set_last_error(ERROR::INVALID_HANDLE); 50 | -1 51 | } 52 | } 53 | } 54 | 55 | #[win32_derive::dllexport] 56 | pub fn _lread(sys: &dyn System, hFile: HFILE, mut lpBuffer: ArrayOut) -> i32 { 57 | let mut state = get_state(sys); 58 | let file = state.files.get_mut(hFile).unwrap(); 59 | let Ok(len) = file.read(lpBuffer.as_mut_slice()) else { 60 | sys.set_last_error(ERROR::INVALID_HANDLE); 61 | return -1; 62 | }; 63 | sys.set_last_error(ERROR::SUCCESS); 64 | len as i32 65 | } 66 | 67 | #[win32_derive::dllexport] 68 | pub fn _hread(sys: &dyn System, hFile: HFILE, lpBuffer: ArrayOut) -> i32 { 69 | // This function isn't documented, but appears to be the same as _lread 70 | // with a slightly different buffer size type (UINT/long) that is 32 bits 71 | // either way. 72 | _lread(sys, hFile, lpBuffer) 73 | } 74 | -------------------------------------------------------------------------------- /win32/dll/user32/src/printf.rs: -------------------------------------------------------------------------------- 1 | use memory::{Extensions, Mem}; 2 | use win32_winapi::calling_convention::VarArgs; 3 | 4 | /// An implementation of the C `printf` function, used in DLLs that need one; 5 | /// both user32 and msvcrt expose variations. 6 | pub fn printf( 7 | out: &mut dyn std::io::Write, 8 | fmt: &str, 9 | mut args: VarArgs, 10 | mem: Mem, 11 | ) -> anyhow::Result<()> { 12 | fn read_number(c: u8) -> usize { 13 | // TODO: multiple digits, error handling, etc. 14 | assert!(c >= b'0' && c <= b'9'); 15 | (c - b'0') as usize 16 | } 17 | 18 | let mut i = fmt.bytes(); 19 | while let Some(c) = i.next() { 20 | if c == b'%' { 21 | let mut c = i.next().unwrap(); 22 | 23 | let mut width = 0; 24 | if c >= b'0' && c <= b'9' { 25 | width = read_number(c); 26 | c = i.next().unwrap(); 27 | } 28 | 29 | let mut precision = 0; 30 | if c == b'.' { 31 | c = i.next().unwrap(); 32 | precision = read_number(c); 33 | c = i.next().unwrap(); 34 | } 35 | 36 | let mut long = false; 37 | if c == b'l' { 38 | long = true; 39 | c = i.next().unwrap(); 40 | } 41 | _ = long; // currently ignored 42 | 43 | match c { 44 | b'u' => write!( 45 | out, 46 | "{:width$.precision$}", 47 | args.pop::(mem), 48 | width = width, 49 | precision = precision 50 | )?, 51 | b'd' => write!( 52 | out, 53 | "{:width$.precision$}", 54 | args.pop::(mem), 55 | width = width, 56 | precision = precision 57 | )?, 58 | b's' => { 59 | let addr = args.pop::(mem); 60 | let str = mem.slicez(addr); 61 | write!(out, "{}", std::str::from_utf8(str)?)?; 62 | } 63 | b'x' => write!( 64 | out, 65 | "{:width$.precision$x}", 66 | args.pop::(mem), 67 | width = width, 68 | precision = precision 69 | )?, 70 | _ => todo!("format string character {:?}", c as char), 71 | } 72 | } else { 73 | out.write(&[c])?; 74 | } 75 | } 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /x86/src/ops/bits.rs: -------------------------------------------------------------------------------- 1 | use crate::{CPU, ops::helpers::*, registers::Flags}; 2 | use iced_x86::Instruction; 3 | use memory::Mem; 4 | 5 | /// bt: Bit Test 6 | pub fn bt_rm32_r32(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 7 | let x = rm32(cpu, mem, instr).get(); 8 | let y = op1_rm32(cpu, mem, instr) % 32; 9 | cpu.flags.set(Flags::CF, ((x >> y) & 1) != 0); 10 | } 11 | 12 | /// bt: Bit Test 13 | pub fn bt_rm32_imm8(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 14 | let x = rm32(cpu, mem, instr).get(); 15 | let y = instr.immediate8() % 32; 16 | cpu.flags.set(Flags::CF, ((x >> y) & 1) != 0); 17 | } 18 | 19 | /// bts: Bit Test and Set 20 | pub fn bts_rm32_r32(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 21 | let y = op1_rm32(cpu, mem, instr) % 32; 22 | let x = rm32(cpu, mem, instr); 23 | let mask = 1u32 << y; 24 | cpu.flags.set(Flags::CF, x.get() & mask != 0); 25 | x.set(x.get() | mask); 26 | } 27 | 28 | /// bts: Bit Test and Set 29 | pub fn bts_rm16_imm8(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 30 | let y = instr.immediate8() % 16; 31 | let x = rm16(cpu, mem, instr); 32 | let mask = 1u16 << y; 33 | cpu.flags.set(Flags::CF, x.get() & mask != 0); 34 | x.set(x.get() | mask); 35 | } 36 | 37 | /// btr: Bit Test and Reset 38 | pub fn btr_rm32_imm8(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 39 | let y = instr.immediate8() % 32; 40 | let x = rm32(cpu, mem, instr); 41 | cpu.flags.set(Flags::CF, ((x.get() >> y) & 1) != 0); 42 | x.set(x.get() & !(1 << y)) 43 | } 44 | 45 | /// bsf — Bit Scan Forward 46 | pub fn bsf_r32_rm32(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 47 | let y = op1_rm32(cpu, mem, instr); 48 | let x = rm32(cpu, mem, instr); 49 | cpu.flags.set(Flags::ZF, y == 0); 50 | for i in 0..32 { 51 | if y & (1 << i) != 0 { 52 | x.set(i); 53 | break; 54 | } 55 | } 56 | } 57 | 58 | /// bsr: Bit Scan Reverse 59 | pub fn bsr_r32_rm32(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 60 | let y = op1_rm32(cpu, mem, instr); 61 | let x = rm32(cpu, mem, instr); 62 | cpu.flags.set(Flags::ZF, y == 0); 63 | for i in (0..32).rev() { 64 | if y & (1 << i) != 0 { 65 | x.set(i); 66 | break; 67 | } 68 | } 69 | } 70 | 71 | /// tzcnt: Count the Number of Trailing Zero Bits 72 | pub fn tzcnt_r32_rm32(cpu: &mut CPU, mem: Mem, instr: &Instruction) { 73 | let y = op1_rm32(cpu, mem, instr); 74 | let x = rm32(cpu, mem, instr); 75 | let count = y.trailing_zeros(); 76 | cpu.flags.set(Flags::CF, count == 32); 77 | cpu.flags.set(Flags::ZF, count == 0); 78 | x.set(count); 79 | } 80 | --------------------------------------------------------------------------------