# pe32_emu

> **Tool `tools/emu/pe32_emu.py`** (program, Emulators). Call routines inside a 32-bit Windows (x86) game under Unicorn, without running the game.

Run it with `./gameframe run pe32_emu ARGS` or `python3 tools/emu/pe32_emu.py`. [Source on GitHub](https://github.com/tanepiper/game-frame/blob/main/tools/emu/pe32_emu.py).

## Its own help

This is the help text in the tool itself, as `./gameframe tool pe32_emu` prints it.

```text
Call routines inside a 32-bit Windows (x86) game under Unicorn, without running the game.

The x64 emulator `pe_emu.py` covers 64-bit games. Games of the 1990s are 32-bit, and much of
their fastest code is hand-written assembly that takes its inputs in registers, not by a C
calling convention: a span filler with the destination in EDI, a mixer with the channel in
ESI. This module runs such routines exactly as they are, MMX and x87 included.

    from pe32_emu import Emu32
    emu = Emu32('GAME.EXE')                        # mapped at its own image base
    spans = emu.alloc(0x1000)
    emu.write(spans, data)
    out = emu.call(0x4dde10, esi=spans, ebx=mask, edx=rows)       # registers in
    out = emu.call(0x4d1460, args=[channel, buffer, 64])          # or cdecl/stdcall args
    print(out['eax'], emu.read(mask, 0x50))

    emu = Emu32.raw(code_bytes, base=0x401000)     # or bare machine code, for tests

What it does:

- maps the image at its preferred base, as the loader would; Watcom images whose section
  headers give a virtual size of 0 are mapped from their raw sizes (pefile does this). A section
  with a file pointer of 0 has no file data (Watcom's .bss): it is zeroed, as Windows does,
  instead of holding the file's first bytes (docs/knowhow/pe-bss-file-pointer-zero.md);
- starts the x87 as Windows starts a program, with the control word 0x037f (round to nearest,
  64-bit precision). Unicorn alone starts at 0, which rounds every result to 24 bits
  (docs/knowhow/unicorn-fpu-starts-single-precision.md);
- points every import at a stub that stops the run with the import's name, so a routine that
  calls Windows fails loudly instead of running off into nowhere (`stubs=` gives a Python
  handler for an import: it receives the emulator and returns EAX; the stub then returns with
  the callee popping `pop_bytes` of arguments, as stdcall does);
- gives the code a stack and a bump allocator (`alloc`); `reset()` puts the image's data back
  and clears the allocator, so each trial starts from the same state;
- lays out the game's structures by hand. `array(count, stride)` gives each record's address.
  `put(addr, '<iI', value, pointer)` writes fields. `get(addr, '<i')` reads them. A linked
  list, a graph or a table of records with pointers then takes a few lines;
- `call(addr, args=[...], **registers)` pushes `args` right to left (cdecl and stdcall), then a
  return address that stops the run, sets the named registers and runs until the routine
  returns. It returns every general register afterwards.
- `call(..., fpu_cw=0x0f7f)` loads an x87 control word first. The FPU's rounding mode changes
  the result of any float-to-integer conversion that does not set its own mode, such as the
  "magic number" `fadd` of 1.5 x 2^68. `NEAREST` (0x037f) is what `fninit` leaves, and what most
  C start-up code runs with; `CHOP` (0x0f7f) truncates.
- `call(..., until=ADDR)` stops when the code reaches ADDR, before that instruction runs, and
  adds `stack` (the eight words at ESP) and `stopped_at` to the result. Stop at a `call` to a
  library routine that needs the C runtime (`sprintf`, `printf`), and its arguments are
  `stack[0]`, `stack[1]` and so on.
- `fpu_modes(addr, cases, cws=(NEAREST, CHOP))` runs the routine on each case (a list of args)
  under each control word, and returns the cases whose EAX differs: the inputs where the game's
  FPU mode matters (docs/knowhow/x87-magic-add-rounding.md).

What it does not do: run DllMain or the C runtime start-up, create threads, or emulate
Windows. Routines that need the game's globals set up need them written first (`write`), from
the original's own initialisation or from a captured memory dump.

Usage as a program (prints the registers after the call):

    pe32_emu.py BINARY ADDRESS [REG=VALUE ...] [--args V,V,...] [--dump ADDRESS:LENGTH]
                [--fpu-cw WORD] [--until ADDRESS]

Needs `unicorn` and `pefile` (scripts/setup.sh puts them in .venv).
```

## Named by

Skills: [re-emulate](/skills/re-emulate).

Agents: [re-vectoriser](/agents/re-vectoriser).
