Skip to content

Inputs: everything that comes from outside ​

A reimplementation is exact when it gives the same results as the original for the same inputs. To check this, you must know every input. Besides the keys the player presses, that includes the clock, the files, the hardware the game detects and the order the operating system delivers events. This page lists the kinds of input, where you will find each in a game's code, and how to handle each in a port.

Every input enters the port through one entry point, as a record per frame (real-time games) or per command (games that wait). Then a recording, a demo or a test script can drive the port. Two runs with the same records give the same results. An input that reaches the game's rules any other way makes two runs differ. A recording cannot repeat it.

Ask your agent ​

List every input. Copy this prompt into your coding agent:

text
In my GameFrame project, list every input the game reads from outside.

1. Read docs/observe/inputs.md.
2. Search the decompile for the signatures it lists: interrupts, ports, API imports, clock
   reads, file opens and network calls.
3. For each input, write the address that reads it, what it reads and how often, in a table in
   docs/FINDINGS.md.
4. Propose the port's input record: one record per frame or per command. Mark which inputs
   change the rules and which only change the presentation.

How games read input ​

There are two patterns. Many games use both.

PatternWhat it looks likeWhat to record
Polled stateAn interrupt handler or the message pump writes key and button state into a table; the frame reads the table at fixed pointsThe table's contents at each read
EventsA handler acts on each key press or click as it arrives (a window procedure, a callback, an event queue the frame drains)Each event, in order, with the frame or step at which the game handles it

Find every place that reads input. A game may poll in the game loop, drain an event queue in its menus, and react to a key inside the window procedure for the pause key. The port must model each of them.

The kinds of input ​

The player's controls ​

InputWhere you see it in DOS gamesWhere you see it in Windows gamesHow to handle it
Keyboardan INT 9 handler reading port 0x60 (scan codes) into a key table; BIOS INT 16h for menus and textWM_KEYDOWN, WM_KEYUP, WM_CHAR in the window procedure; GetAsyncKeyState, GetKeyboardState; DirectInput GetDeviceState or buffered data; raw input (WM_INPUT)Record the game's own key table (or its events) per frame. Map host keys to the original's scan codes in the platform layer, never in the rules
Text entryINT 16h, or the key table with the game's own conversion to charactersWM_CHAR, IME messages for Asian languagesRecord characters, not keys, where the game reads characters
MouseINT 33h (the mouse driver): position, buttons, mickeys (motion counters)WM_MOUSEMOVE, WM_LBUTTONDOWN and so on; GetCursorPos; DirectInput relative motion; ClipCursor, SetCaptureRecord what the game reads: absolute position in its own coordinates, or motion summed since the last read. Port the game's own scale and acceleration
Joystickthe game port: IN from port 0x201, timing a loop until each axis bit falls (the count is the axis value); BIOS INT 15h function 84hjoyGetPos, joyGetPosEx (WinMM); DirectInput; XInput (XInputGetState)Record the value after the game's own calibration and dead zones, and port the calibration
Touch, pens, wheels, pedalsrareWM_TOUCH, WM_POINTER, DirectInput force feedbackSame as joystick: the value the game reads

Time ​

InputWhere you see itHow to handle it
A timer interruptDOS: the PIT reprogrammed through ports 0x40 and 0x43, a new INT 8 (or INT 1Ch) handler counting ticksThe tick count is an input per frame. Find the rate from the divisor written to the PIT
The display's refreshDOS: polling port 0x3DA for the vertical retrace; Windows: Present with v-sync, WaitForVerticalBlankThe refresh sets the frame rate. The port counts frames and does not time them
The wall clockDOS: INT 1Ah, INT 21h function 2Ch, the BIOS tick count at 0040:006C; Windows: GetTickCount, timeGetTime, QueryPerformanceCounter, GetSystemTime, time, clock; RDTSCEvery read is an input. Record its value, or replace it with frame-based time where the original only measured elapsed time
Speed calibrationDOS: a delay loop timed at start-up to measure the CPU's speedThe measured count is an input: record it, and pin it in the port

Randomness ​

InputWhere you see itHow to handle it
A seed from the clocksrand(time(NULL)), Randomize in Pascal (reads the clock), a seed from the timer tick countThe seed is an input: record it once, at the point the game seeds
A seed from the playera seed field in a new-game dialog, a deal numberPart of the command that starts the game
Hardware randomnessrare: RDRAND, CryptGenRandomRecord every value

Files and the system ​

InputWhere you see itHow to handle it
Game dataINT 21h open and read; CreateFile, ReadFile, fopen, memory-mapped filesRead-only and the same every run: not an input to record, but the port must read the same files (versions differ)
ConfigurationSETUP.CFG, .INI files, the registry (RegQueryValueEx)An input at start: record it, and pass it in with the starting state
Savesread when the player loadsAn input like a command: "load this file"
Command line and environmentPSP command tail, GetCommandLine, getenv (BLASTER for the Sound Blaster)An input at start
Hardware detectionvideo card, sound card, memory size (EMS, XMS), CPU typeUsually changes only drivers and quality. If the rules read it (detail levels that change the simulation), record it
CD audio and CD checksMSCDEX (INT 2Fh function 15h), mciSendCommandUsually not rules; breaking copy protection is restricted in many countries (Legal)

The operating system and the window ​

InputWhere you see itHow to handle it
Window messagesfocus lost and gained (WM_ACTIVATE, WM_ACTIVATEAPP), minimise, resize, closeMany games pause when focus is lost. Pausing is a command: record it
Message orderthe message pump (PeekMessage, GetMessage, DispatchMessage) runs between frames, or inside a frameFind where the pump runs relative to the update. Events that arrive between two updates must reach the port at the same point
Screen sizethe window or display mode the player choseIf the rules use it (picking, the camera, what is visible and so updated), it is an input
Audio timinga music driver on its own interrupt, a sound callback that advances game timeIf the game's time follows the audio clock, the audio clock is an input

Other machines ​

InputWhere you see itHow to handle it
Network messagesIPX (INT 7Ah), serial ports (INT 14h, ports 0x3F8), Winsock (recv, WSARecv), DirectPlayIn lockstep games, the other players' commands are inputs per tick. In client-server games, the server's messages are the inputs (Online games)

Things that are easy to miss ​

These apply to many games:

  • Two keyboard paths. Besides the key table the frame reads, a game may ask the BIOS "is a key waiting?" to skip a screen or end a demo. That is a second input, read at a different point.
  • Devices that are not there. With no joystick or mouse installed, the polling code may write nothing, or only clear a flag. Find out what an absent device leaves in memory, and model that state too.
  • Demo files leave state in memory. A demo read through the runtime's file routines (Pascal Text, C FILE) keeps its buffer, the open DOS handle and the file position in the program's memory. A capture taken mid-demo must save them, or the port cannot carry on from it.
  • Counters owned by interrupts. Timer interrupts keep counting while the frame waits, so their counters differ from run to run. Take the clock value the frame read as an input (from the original's state after the read), and leave the interrupt's own counters out of comparisons, with the reason written next to the check.
  • Put the outputs in the record too. What the frame sends out (the finished picture copied to the display, a song request, sounds queued per channel) goes in the same per-frame record as the input. Then the core can run headless. A test can compare every output.

Recording inputs from the original ​

MethodGivesTools
The game's own demo or replay filesthe exact input record the game itself usesread the format with re-format; play it in the original and the port
Breakpoints on the readsthe value at every read, in orderDOSBox-X debugger (BPINT 16, memory breakpoints on the key table); x64dbg; GameFrame's emulators (call_trace.py, dos_env)
Hooks on the APIevery Windows input call and its resultFrida, API Monitor, a proxy DLL (on your own copy only)
Recording the host's inputkeys and mouse as the player gave thema capture script driving the original through an emulator or a remote stream (Running the original)

When the original has demos, start there. Their format is the game's own input record.

Designing the port's input record ​

  1. List every input above that the game reads, with the address of each read.
  2. Put the ones that change the rules into one record type per frame or per command. Keep inputs that only change the presentation (window size, volume) outside it.
  3. Make the platform layer (keyboard, mouse, window) produce that record. The rules read only the record.
  4. Write recordings as the start state (seed, configuration, files' versions) plus the list of records. Two runs from the same recording must give identical state, every frame.
  5. Tests and tools use the record too. A test script, a fuzzer or an agent driving the game produces records. It never writes the state directly (A model to query).

Code: Apache 2.0. Content: CC0 1.0. No game material: bring your own copy.