Skip to content
Lesson 13.9Physical Memory, Machines, and Hardware

How Emulators Work

An emulator recreates a console in software through instruction execution, memory-mapped I/O, timing, graphics, audio, and save states.

Lesson 13.9
Read or listenPrint chapter or book
About 18 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 13.9Key ideas, reference and questions

A one-page summary of “How Emulators Work” you can keep beside your notes. Plain Markdown.

Lesson 13.9 — How Emulators Work: cheatsheet

  • Remember: A save state needs registers and timing state as well as RAM, because unfinished work may not have reached memory yet.
  • Also useful: A dynamic recompiler keeps a translated recipe for a guest-code block, instead of decoding the same recipe again on every loop iteration.

Key terms

  • emulator: An emulator is a program that imitates another computer's hardware closely enough that software written for that computer runs on it unchanged.
  • guest: The imitated machine and its software are the guest; the computer running the emulator is the host.
  • host: The imitated machine and its software are the guest; the computer running the emulator is the host.
  • interpreter: An emulator that runs this loop once per guest instruction is an interpreter.
  • JIT: A dynamic recompiler, also called a JIT (just-in-time compiler), removes most of that repetition.
  • framebuffer: An emulator imitates that chip's rules, writes the resulting pixels into an array called a framebuffer, and hands the array to the host's GPU as a texture, one of the objects Chapter 7 observes.
  • save state: A save state is everything the machine is, written out: every register, all of RAM, the I/O registers, and the emulator's own timing position.

Review

  • Q: Why can a cheat code for an old console game be as simple as “write 99 to address 0x80 every frame”, while a tool for a PC game usually needs a pointer path? A: One fixed memory map with no virtual memory or ASLR keeps each variable at the same address
  • Q: A recompiler has translated a guest loop into host code and stored it. The guest program then writes a new byte into the middle of that loop, over the operand of an ADD instruction. What must the emulator do before the loop runs again? A: Discard the stored translation that covers the changed address and translate it again
Download cheatsheet (.md)

Lesson 11.8 promised that an emulator is a program whose data is the software of an entire other computer. This lesson keeps the promise. An emulator is a program that imitates another computer’s hardware closely enough that software written for that computer runs on it unchanged. The imitated machine and its software are the guest; the computer running the emulator is the host.

The console architecture from the preceding lessons gives the emulator a concrete target. Processor instructions, devices, timing, and saved state have to agree closely enough for the same game code to behave as expected.

Lesson 11.9 built a virtual machine for bytecode that was designed to be interpreted. An emulator interprets machine code that was designed for a real chip, and it has to reproduce whatever that chip does, including its quirks.

Lesson 2.3 described the steps a CPU repeats: fetch the bytes at the instruction pointer, decode them, execute, and move on. In a real CPU, circuits do that. In an emulator, a loop does:

loop:
opcode = memory[pc] fetch the byte the program counter points at
pc = pc + 1 move past it
decode opcode work out which instruction it is
execute it change registers, memory, and flags
add its cycles keep time

The program counter, or PC, holds the address of the next byte to fetch; x86 calls the same register the instruction pointer. The last line counts cycles, ticks of the guest CPU’s clock, so the emulator knows how long the real chip would have taken. An emulator that runs this loop once per guest instruction is an interpreter. Everything else in this lesson is either a faster way to run the loop or a way to imitate the rest of the machine around it.

To watch the loop work, take a made-up 8-bit console, the one Lesson 13.10 emulates in full. Like the processors in 1980s consoles, its CPU has a program counter and a few small registers, and it does its arithmetic in one called A, the accumulator. Each instruction is one or two bytes long. The first byte, the opcode, says which instruction it is; some instructions carry a second byte, the operand.

The bytes 03 05 mean ADD #5: add the number 5 to A. Suppose they sit at addresses 0x04 and 0x05, PC holds 0x04, and A holds 10:

PC0x04
A10
0x0403
0x0505
CPU registers
ADD #5 in memory
Before the loop runs: PC points at the opcode 03, and A holds 10.

One pass of the loop does four things:

  1. Fetch: read the byte at PC, 03, and add 1 to PC, making it 0x05.
  2. Decode: 03 is ADD, which takes one operand byte.
  3. Execute: fetch the operand, 05, which adds 1 to PC again and makes it 0x06. Then set A = 10 + 5 = 15.
  4. Keep time: this CPU spends one cycle on every byte it reads or writes in memory. ADD #5 read two bytes, so it costs 2 cycles.
PC0x06
A15
0x0403
0x0505
CPU registers
ADD #5 in memory
After one pass: A holds 15, and PC = 0x04 + 2 = 0x06, the first byte of the next instruction.

Nothing in memory marks where one instruction ends and the next begins. The CPU knows each instruction’s length from its opcode, and fetching the bytes moves PC past them, so PC lands on the next instruction without any extra work.

The toy emulator fetches and runs ADD #5Two bytes in memory make one instruction. Fetching reads them; executing changes A and PC and spends cycles.
  1. 1 Fetch the opcode 03 at PC 0x04

    Memory bytes, then CPU registers
    03at 0x04
    05at 0x05
    10A
    0x04PC
    0cycles

    A starts at 10. Opcode 03 means ADD with an immediate operand.

  2. 2 Fetch the operand 05 at 0x05

    Memory bytes, then CPU registers
    03at 0x04
    05at 0x05
    10A
    0x06PC
    1cycles

    The operand byte follows the opcode.

  3. 3 Execute: A = 10 + 5 = 15

    Memory bytes, then CPU registers
    03at 0x04
    05at 0x05
    15A
    0x06PC
    2cycles

    PC has advanced to 0x06 and the instruction cost two cycles. The instruction bytes stay in memory.

Two bytes in memory make one instruction. Fetching reads them; executing changes A and PC and spends cycles.

Lesson 13.10 lists all seven instructions of this CPU, loads a 14-byte program, and follows it instruction by instruction.

An emulator performs one guest add instruction. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

PC = 0x04; A = 10
opcode = memory[PC]
operand = memory[PC + 1]
decode 0x03 as ADD immediate
A = (A + operand) modulo 256; PC += 2
report the accumulator and next fetch

Result: A=15, next fetch at 0x06. The result fits in the 8-bit register.

Final values: operand = 5; PC = 0x06; A = 15; opcode = 0x03; immediate = 5; operation = ADD; mathematical_sum = 15.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with the selected inputs. Follow the highlighted operation and the changing state below.

    operand = 5.

  2. Step 2, line 1. Start at the opcode with accumulator 10.

    operand = 5; PC = 0x04; A = 10.

    Memory: 0x04 = 03; 0x05 = 05.

  3. Step 3, line 2. Fetch the opcode byte.

    operand = 5; PC = 0x04; A = 10; opcode = 0x03.

    Memory: 0x04 = 03; 0x05 = 05.

  4. Step 4, line 3. Fetch the immediate byte after the opcode.

    operand = 5; PC = 0x04; A = 10; opcode = 0x03; immediate = 5.

    Memory: 0x04 = 03; 0x05 = 05.

  5. Step 5, line 4. The made-up instruction set defines 0x03 as an immediate add.

    operand = 5; PC = 0x04; A = 10; opcode = 0x03; immediate = 5; operation = ADD.

    Memory: 0x04 = 03; 0x05 = 05.

  6. Step 6, line 5. Add in the 8-bit accumulator and advance past both instruction bytes.

    operand = 5; PC = 0x06; A = 15; opcode = 0x03; immediate = 5; operation = ADD; mathematical_sum = 15.

    Memory: 0x04 = 03; 0x05 = 05.

  7. Step 7, line 6. Result: A=15, next fetch at 0x06. The result fits in the 8-bit register.

    operand = 5; PC = 0x06; A = 15; opcode = 0x03; immediate = 5; operation = ADD; mathematical_sum = 15.

    Memory: 0x04 = 03; 0x05 = 05.

An interpreter pays for fetching and decoding every time an instruction runs, typically many host instructions for each guest instruction. Suppose a loop of five guest instructions runs 1,000 times. The interpreter decodes 5 × 1,000 = 5,000 instructions, although only five different ones exist.

A dynamic recompiler, also called a JIT (just-in-time compiler), removes most of that repetition. The first time guest code runs, it translates it into host machine code and stores the translation; every later time, it runs the stored host code directly. It translates a block, a run of instructions that ends at a jump, because after a jump the next instruction could be anywhere:

no
yes
the block ends at a jump,
which decides the next address
next guest address
translation for this
address already stored?
decode the block's instructions
and write host machine code for them
store the host code
under this guest address
run the stored host code

With a recompiler, the 1,000 passes of that loop cost 5 decodes and 1,000 runs of native code instead of 5,000 decodes. Consoles whose CPUs execute hundreds of millions of instructions per second, such as the GameCube and the PlayStation 2, are emulated with recompilers, as in the Dolphin and PCSX2 emulators, because an interpreter cannot keep up.

The speed has two costs:

  • Self-modifying code. Code is data, so a guest program may overwrite its own instructions. A stored translation then describes code that no longer exists. The recompiler must notice writes to addresses it has translated and throw those translations away.
  • Writable, then executable memory. A recompiler writes host machine code into memory and then runs it. Lesson 13.6 showed that consoles, and some phone platforms, refuse ordinary programs exactly that combination. Emulators on those platforms have to interpret.

The toy console’s PC is 8 bits wide, and eight bits can name 2⁸ = 256 addresses, 0x00 to 0xFF. Not all of them are RAM. The console’s memory map says what answers at each address:

Addresses What answers
0x00 to 0xEF RAM: 0xF0 = 15 × 16 = 240 bytes
0xF0 the display: writing a byte here shows it on the screen
0xF1 the buttons: reading here returns which buttons are held
0xF2 to 0xFF nothing: reads give 0, and writes vanish

A device that answers at an address this way uses memory-mapped I/O. To the CPU, storing A at 0xF0 is an ordinary store instruction. The address alone decides that the byte goes to the display instead of RAM. In hardware, address-decoding circuits make that choice. In the emulator, a pair of functions does, one for reads and one for writes, and every memory access the CPU makes goes through them:

0x00 to 0xEF
0xF0
0xF1
0xF2 to 0xFF
CPU stores A
at 0xF0
bus: which range
holds 0xF0?
RAM array,
240 bytes
display register:
the number on screen
buttons register:
filled from the host keyboard
nothing wired:
reads give 0, writes vanish

Real consoles have far bigger maps, with more RAM, a picture chip, a sound chip, and controller ports, but an emulator handles them the same way. Lesson 13.10 writes the toy console’s map as code and looks at part of the NES’s.

Inside a real console, the CPU, the picture chip, and the sound chip all run at the same time, each from its own clock. Older games depended on that timing exactly. A game might change a picture-chip register partway down the screen to split it into a scrolling playfield and a fixed status bar; if the emulated CPU reaches that write too early or too late, the split lands on the wrong line. So emulators count cycles and keep the parts in step: run the CPU for a slice of cycles, advance the other chips by the same amount of time, and repeat.

Suppose the toy console’s clock runs at 1.2 MHz, which is 1,200,000 cycles per second, and the screen redraws 60 times a second. Each frame then lasts 1,200,000 ÷ 60 = 20,000 cycles. The emulator’s frame loop runs instructions until 20,000 cycles are used, draws the frame, and waits until 1/60 of a second of real time has passed, so the game plays at its original speed on a host that could run it thousands of times faster.

An instruction cannot stop halfway, so a frame can end slightly past its budget. The emulator then takes the extra cycles off the next frame, which keeps the average exact. Lesson 13.10 ends a frame partway through a program and works out the numbers.

Consoles of the 8-bit and 16-bit eras had a dedicated picture chip that built each frame line by line from tiles, small blocks of pixels, and sprites, movable images, kept in video memory, in step with the television’s scanning beam. An emulator imitates that chip’s rules, writes the resulting pixels into an array called a framebuffer, and hands the array to the host’s GPU as a texture, one of the objects Chapter 7 observes.

3D consoles are harder. Their GPUs run command lists and shaders written for that GPU’s own design, so the emulator translates the guest’s commands into a host graphics interface such as Vulkan or Direct3D, and the guest’s shaders into host shaders. That translation happens while the game runs, the first time each effect appears, which is why emulated games can stutter the first time they show something new, and why emulators keep a cache of translated shaders. The original console never had this problem, because its games shipped shaders already compiled for its one GPU (Lesson 13.6).

Sound works the same way in a different currency. The guest’s sound chip produces a stream of samples, numbers that describe the sound wave, and a typical host plays 48,000 of them per second. At 60 frames per second the emulator must therefore produce 48,000 ÷ 60 = 800 samples every frame. Audio is the least forgiving part of the machine. A late video frame just leaves the previous picture up a moment longer, but if the samples run out, the speakers click and crackle. Many emulators pace themselves by the audio device for that reason.

A save state is everything the machine is, written out: every register, all of RAM, the I/O registers, and the emulator’s own timing position. Loading one overwrites all of it, so the machine carries on as though nothing had happened. A game’s own save file, like the ones in Lesson 10.2, stores only what the game chooses, in the game’s format. A save state works for any game, at any instant, even in the middle of a frame.

The registers matter as much as RAM. Partway through a calculation, a new value may exist only in a register, not yet stored to memory, so a snapshot of RAM alone would lose it. Lesson 13.10 catches the toy console at exactly such a moment, writes its state out byte by byte, and checks the bytes again when they are loaded back.

Chapters 1 and 2 needed scanners, debuggers, and pointer paths because Windows keeps each process’s memory private and moves things around. An emulator removes both obstacles.

  • The guest’s RAM is one array. The emulator can display it, search it, and change it directly. A RAM search in an emulator is the scan of Lesson 1.9, keeping the candidates whose value changed as expected, run over the guest’s RAM: on an NES, 2 KiB, which is 2 × 1,024 = 2,048 bytes, instead of the gigabytes a PC game can occupy.
  • Addresses do not move. Old consoles have no virtual memory and no ASLR (Lesson 2.7), and their games keep variables at fixed addresses. Gold found at an address today is at the same address tomorrow, and on every other copy of the same game, with no pointer path (Lesson 2.8) needed.
  • The emulator is already a debugger. Every guest memory access passes through the emulator’s read and write functions. A watchpoint on a variable is one if in the write function that checks the address and records PC. That is the answer the debug registers of Lesson 2.4 gave, with no debug registers involved.

Fixed addresses are also what made commercial cheat devices possible. The Game Genie sat between the cartridge and the console, and whenever the console read a particular cartridge address, it substituted a different byte, patching the game’s code or data as it was read. Action Replay and GameShark codes worked on RAM instead, writing a chosen value to a chosen address once every frame. A cheat code of that second kind is just an address and a value.

Such a code can lose a race with the game: a game that has already copied the value into a register, or that recomputes it from other data, later stores its own number over the cheat’s. Lesson 13.10 shows the race cycle by cycle.

All of this is for single-player games played alone. An emulator that plays online with other people, or that submits scores to a shared leaderboard, is part of a multiplayer service, and the same limits apply as everywhere else in this book.

Writing and using an emulator is legal in many countries. In the United States, a 2000 appeals court decision in Sony v. Connectix held that copying the PlayStation’s BIOS during reverse engineering, in order to build a compatible emulator, was fair use. A 1992 decision had already found the Game Genie lawful, because it changed a game only while the owner played it.

What the emulator runs is a different matter. A console’s BIOS or system firmware and its games are copyrighted, and downloading them from the internet is generally copyright infringement. Emulator projects that shipped decryption keys or firmware, or that were promoted for piracy, have been sued, and some have shut down. Several emulators avoid needing the original firmware by replacing it with their own code.

That leaves plenty to run legitimately:

  • homebrew games that their authors release for anyone to play;
  • public-domain or freely licensed programs;
  • test programs that emulator developers write to check CPU and video behaviour;
  • dumps you make yourself of games you own, where your country’s law allows making them.

The toy console of Lesson 13.10 sidesteps the question entirely: it emulates a machine that does not exist, running a program written for this book.

Knowledge check

Why cheat codes are just addresses

Why can a cheat code for an old console game be as simple as “write 99 to address 0x80 every frame”, while a tool for a PC game usually needs a pointer path?

Finished Lesson 13.9?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 13: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.