WinUAE and Ghidra work best as a paired lab: the emulator shows what the original program does, while Ghidra explains the instructions behind an observed change. Use a fixed starting state, change one input, capture the result at a known update boundary, inspect the code that changed it, and repeat. This guide uses {theappwearreversing} as a placeholder. All paths, keys, addresses, and sample state values are fictional examples; verify them against your own program and WinUAE build.
1. Fix the machine and checkpoint
Keep a known-good .uae profile and an untouched disk image. Record the WinUAE version, CPU, chipset, chip and fast RAM, ROM identity, disk SHA-256, display mode, and keyboard mapping. A .uss savestate captures one running moment; it does not replace the profile or prove that the state is valid. Reload a checkpoint once and confirm the expected screen and program counter before using it as a baseline.
Use separate state files for each experiment. WinUAE’s configuration can name the state directory and the file written when the emulator exits:
; example-lab.uae
statefile_path=D:\amiga-lab\states
statefile_quit=D:\amiga-lab\states\run-01.uss
Launch the profile and request a particular saved state with a command-line override. The exact switch spelling can vary by build; check the installed build’s help if it rejects the option.
$winuae = 'C:\Program Files\WinUAE\winuae64.exe'
$profile = (Resolve-Path '.\example-lab.uae').Path
$state = (Resolve-Path '.\states\menu-ready.uss').Path
$p = Start-Process -FilePath $winuae -ArgumentList @(
'-f', $profile,
'-cfgparam', "statefile=`"$state`""
) -PassThru
"WinUAE PID: $($p.Id)"
Get-FileHash $state -Algorithm SHA256
For an unattended run, generate a distinct profile whose statefile_quit points at a new output file, then close WinUAE normally to write it. Do not let two emulator runs share a profile, debugger port, log, or output state. A normal-exit save is convenient, but it is only a checkpoint: it does not prove that the CPU was stopped at a complete application update. Record the PC and reload-test the result before treating it as a measured frame.
2. Send host keys and prove they arrived
A host key travels through Windows focus, WinUAE’s host mapping, and the emulated keyboard. AutoHotkey v2 can send a down/up pair. This is useful for testing the real keyboard route:
#Requires AutoHotkey v2.0
win := "ahk_exe winuae64.exe"
if !WinExist(win)
throw Error("WinUAE window not found")
WinActivate(win)
if !WinWaitActive(win, , 2)
throw Error("WinUAE did not become the foreground window")
try {
Send "{h down}"
Sleep 100
} finally {
Send "{h up}"
}
Replace h with a key that the target application actually reads. Always release it, even if the script errors. For stronger automation, query the foreground window and verify its owning process ID is the WinUAE process before sending. Capture the screen and relevant guest state before and after; a successful automation call only proves Windows accepted the event. Host sleeps use wall-clock time, so they do not guarantee which emulated update polls the key.
Some WinUAE debugger builds expose a debugger command for scheduling configured input events, but command syntax and event names are build-specific. If your build documents that feature, verify a key-down and key-up separately and record the event name and emulated frame. Do not copy a debugger command from another fork without checking its parser.
3. Use GDB for controlled guest-state experiments
When available, WinUAE’s optional GDB remote server can pause the emulated CPU, inspect registers and RAM, set PC breakpoints, and write guest memory. Enable it in the profile or as command-line overrides:
debugging_features=gdbserver
gdb_port=2345
$winuae = 'C:\Program Files\WinUAE\winuae64.exe'
$profile = (Resolve-Path '.\example-lab.uae').Path
Start-Process -FilePath $winuae -ArgumentList @(
'-f', $profile,
'-cfgparam', 'debugging_features=gdbserver',
'-cfgparam', 'gdb_port=2345'
)
Test-NetConnection 127.0.0.1 -Port 2345
Attach with a GDB client that supports Motorola 68k and the remote protocol:
gdb-multiarch
(gdb) set architecture m68k
(gdb) target remote 127.0.0.1:2345
(gdb) info registers
(gdb) x/16i $pc
(gdb) x/32xb 0x55fde
The server is a local, unauthenticated control interface. Keep it bound to localhost and do not expose the port to a network. Attaching or stopping the CPU changes timing, so use GDB for state inspection and deterministic boundary experiments; use the host keyboard path when the question is whether ordinary input reaches the guest.
Guest-memory input is a separate, explicit test. For example, if you verified that a fictional 128-byte raw-key table begins at $55FDE and H’s raw code is $25, the H byte is at $56003. A debugger write there bypasses host focus and key translation. Label it as injected guest state, read the byte back, and restore its prior value after the controlled interval. Never confuse that result with a physical-keyboard test.
4. Capture at complete update boundaries
Find the boundary that runs after the state you care about has been updated. Confirm it in the live disassembly and then map it to Ghidra. For illustration, suppose a fictional image uses $57F28 for an outer-frame entry and $57FE6 for simulation entry. Those are example addresses, not standard Amiga locations. If a breakpoint times out or the reported PC differs, mark the capture invalid instead of assuming it stopped correctly.
Take two runs from the same hash: a neutral baseline and one with a single input. Record the checkpoint hash, profile and emulator version, initial and stop PC/SR, registers, key-down and key-up boundaries, number of updates held, memory ranges, and a screenshot. Dump the same addresses and lengths in both runs:
(gdb) info registers
(gdb) x/16i $pc
(gdb) dump binary memory before-objects.bin 0x23cd6 0x245d6
(gdb) dump binary memory before-keys.bin 0x55fde 0x5605e
Compare the first differing byte and identify its owning field before reading later differences. Screenshots reveal menus, display modes, and obvious rendering errors; they cannot prove internal state. Compare pixels only when both systems show the same state and display phase.
5. Connect live execution to Ghidra
Import the executable or ROM into Ghidra, not the .uss file. For a Hunk executable, use a loader that understands Amiga Hunk segments and relocations. For a raw memory dump, choose a big-endian Motorola 68000 language and set its true runtime base. Record how file offsets, loaded segments, and live addresses map to one another.
At a stopped PC, compare a short byte range from WinUAE memory with the proposed Ghidra address. If the bytes differ, fix the mapping before trusting the disassembly. Then inspect the instructions around the stop, references to the changed RAM, and callers of the routine. Set a breakpoint or watchpoint on a suspected write and confirm the instruction that causes it. Keep labels provisional until a second controlled run supports them.
6. Write the behavior spec, then reproduce it
For each experiment, keep a short record: start-state hash → input source and schedule → first changed fields → relevant routine and branch → predicted result → repeat-run result. Note whether the run used host input, injected guest state, a debugger pause, or a patched image. These methods answer different questions.
Before porting behavior, specify the initial state, input polling point, update order, timing, bounds, randomness, and rendering assumptions. Replay the same checkpoint and input schedule in the implementation. Compare state first, then screenshots at matching completed updates. Find the earliest mismatch and trace that transition in WinUAE and Ghidra before changing later behavior.
{theappwearreversing} is only a placeholder label. Replace it with your own test application’s name and paths; verify every setting, key mapping, address, and debugger command against your installed emulator and target image.