Step 06 · Emulation foundations
What an emulator is
Before any of the detail: what problem is an emulator solving, what are the two ways to solve it, and which one is KytyPS5? Answer that and the rest of the project stops looking arbitrary.
Before this page: Instructions, addresses and the guest. View the learning route.
An emulator makes one computer behave like another, so that software built for the second runs on the first. That is the whole idea. Everything else is engineering trade-offs about where you cut.
The simplest possible emulator
Imagine a toy machine with four registers and a handful of instructions. An emulator for it is a loop:
uint8_t memory[65536]{}; // load the toy program before executing
uint32_t reg[4]{};
uint16_t pc = 0;
auto fetch = [&]() { return memory[pc++]; }; // 16-bit PC wraps
for (;;) {
switch (fetch()) {
case 0x01: { // LOAD reg, little-endian imm32
const uint8_t r = fetch();
if (r >= 4) return; // invalid register
uint32_t value = 0;
for (unsigned byte = 0; byte < 4; ++byte)
value |= uint32_t(fetch()) << (8 * byte);
reg[r] = value;
break;
}
case 0x02: { // ADD dst, src
const uint8_t d = fetch(), s = fetch();
if (d >= 4 || s >= 4) return;
reg[d] += reg[s];
break;
}
case 0xFF: return; // HALT
default: return; // invalid opcode
}
}
This illustrates an interpreter: fetch an instruction, decode it, perform its operation, repeat. A complete implementation also defines invalid instructions, exceptions and every other part of the guest machine.
The loop adds decoding and dispatch work to every guest instruction. Its cost depends on the interpreter, host and workload; CPU clock rates alone cannot predict emulation speed. Caching translated code or executing compatible guest instructions natively can avoid much of that repeated work.
Execution strategies and HLE
| Strategy | How it works | Cost | Used by |
|---|---|---|---|
| Interpretation | Decode and execute one guest instruction at a time, every time. | Repeated decode/dispatch overhead. Instruction stepping is straightforward, but accuracy still depends on the implementation. | Reference implementations, fallback paths and systems whose workloads fit the host’s available performance. |
| Dynamic recompilation (JIT) | Translate a block of guest instructions into host instructions once, cache the result, then jump into the cached block whenever that address is reached again. | Amortises translation cost. Self-modifying code, precise exceptions and flags require careful handling. | Common in emulators for systems with a different guest CPU, including PS2 and GameCube. |
| Native execution | Guest and host share an instruction set, so guest code is simply mapped into memory and jumped to. | Avoids general CPU translation. Host feature gaps, ABI crossings, services and faults still have costs. | KytyPS5 on a compatible x86-64 execution environment. |
| High-level emulation (HLE) | Not a CPU strategy but a library strategy: instead of running the console's operating system, reimplement what its functions do in native code. | Performance varies. Correctness depends on matching guest-visible results, side effects, ordering and timing. | KytyPS5’s service replacements; graphics also requires packet interpretation and shader translation. |
Low-level emulation (LLE) reproduces lower-level hardware interfaces, often allowing original firmware or libraries to execute. It can preserve behavior that an HLE replacement omits, but neither accuracy nor speed follows from the label alone.
High-level emulation (HLE) replaces those libraries with reimplementations. It can avoid executing those system-library binaries, but each replacement must match the guest-visible contract. A missing function can fail immediately or fall back to a logging zero-return stub; silent incorrect results can cause failures much later. HLE does not by itself establish compatibility.
KytyPS5 primarily replaces system services with HLE. Its README says no external low-level emulation modules are required. The loader can still execute a game-supplied /app0/sce_module/libc.prx; “HLE” does not mean every instruction outside the main executable is replaced.
What an emulator must actually reproduce
A CPU is only one of the things a program depends on. To run an unmodified console game you must reproduce all of:
- The instruction set — so the code executes at all.
- The calling convention — so a function's arguments arrive where it expects them.
- The memory layout — so pointers, allocations and alignment assumptions hold.
- The operating system's services — threads, files, memory, timers, networking, audio, input.
- The GPU — including its command format, its register set, its memory layouts and its shader instruction set.
- The timing model — vertical blank, frame pacing, audio clocks. Games are full of code that assumes 60 Hz.
Most ordinary guest CPU instructions execute directly in a compatible x86-64 environment. Targeted instruction compatibility is still necessary; the remaining responsibilities account for most of this project.
- Guideemudev.org — Getting StartedThe community's entry point. Points at per-system references and the r/EmuDev subreddit, which is where most emulator authors actually ask questions.
- ArticleHow Do Emulators Work? A Deep Dive into Emulator DesignWalks the interpreter → dynamic recompiler → HLE spectrum in more depth than this chapter had room for.
- VideoHow does an emulator work? Building an Atari ST emulator from scratchA whole emulator built on screen for a simple machine. The best way to make the fetch–decode–execute loop concrete before tackling anything modern.
- Codeawesome-emu-resourcesA curated index of emulator development material, organised by system and by topic.
You cannot emulate what you do not understand. Here is the PlayStation 5, described in the terms that matter to an emulator author rather than to a shopper.
The hardware
| Component | What it is | Emulation consequence |
|---|---|---|
| CPU | 8-core AMD Zen 2, x86-64, 16 hardware threads, ~3.5 GHz. | Shares the x86-64 ISA with an x86-64 PC; individual instruction features still need checking. |
| GPU | AMD RDNA 2, 36 compute units, custom. Programmed through a command-packet interface, not a driver API. | Must be modelled in software: packet parser, register file, and a shader compiler from RDNA 2 to SPIR-V. |
| Memory | 16 GB GDDR6, unified — CPU and GPU address the same physical memory. | Host Vulkan resources can have separate backing or layouts. Kyty must keep CPU-visible guest bytes and cached GPU resources coherent, including on unified-memory hosts. |
| Page size | 16 KB, not the 4 KB PCs use. | Guest VM APIs use 16 KB granularity where required. The renderer’s tracker uses 4 KB pages; one guest page spans four of them. |
| Storage | Custom NVMe with hardware decompression. | Implemented through file I/O, asynchronous requests and supported decompression paths. Ordering, completion and error behavior matter alongside throughput. |
| Audio | "Tempest" 3D audio engine plus a fixed-function decode block (AJM). | AJM takes batched decode jobs, so Kyty parses the job format and routes to FFmpeg / LibAtrac9. |
The software stack
The PS5's operating system is a descendant of FreeBSD. That heritage shows everywhere in the API surface:
libkernel, Graphics5Driver (graphics), libSceAudioOut, libSceNet, libSceSaveData… Dozens of shared libraries, each exporting hashed symbols.kqueue-style event queues, mmap-style memory, a Unix-like file system.KytyPS5 replaces the middle two layers wholesale with native C++, and models the fourth in software. The game executes natively, with relocations and targeted compatibility patches applied by the loader.
Prospero is Sony's internal codename for the PS5, and it is the namespace name used throughout Kyty's graphics code (Libs::Graphics::Prospero). AGC is the PS5's low-level graphics API, referred to as Gen5 in the source. GNM was its PS4 predecessor, and some symbol names still carry that lineage.
What a game file actually is
A PS5 title directory contains, at minimum:
MyGame/ ├─ eboot.bin the main executable — a SELF (Signed ELF) ├─ sce_sys/ │ ├─ param.json title ID, version, memory budget, capabilities │ ├─ icon0.png │ └─ … ├─ *.prx / *.sprx optional extra modules shipped with the game └─ (game data files)
Kyty reads param.json for the title ID and the flexible-memory budget, mounts the directory as /app0, then loads eboot.bin.
- ArticleArchitecture of Consoles — Rodrigo CopettiDeep, readable hardware analyses of essentially every console. Read the PlayStation entries to see how the design lineage arrives at the PS5. Widely recommended as the first thing to read before emulating any system.
- VideoConsole Hacking 2016 — "PS4: PC Master Race" (33C3)Hector Martin and fail0verflow on PS4 hardware and how they reverse-engineered it far enough to run Linux with 3D acceleration. The closest thing to a primary source on how this hardware actually differs from a PC. Also on YouTube, with slides.
- Articlefail0verflow blogWrite-ups behind the talks, including the liner notes for each Console Hacking presentation.
The whole machine in one picture. Every later topic is one of these boxes opened up — come back to this animation whenever you lose your bearings.
Two ways to build an emulator, side by side. Step it and watch what each approach commits you to writing.
You do not need the emulator running for this one. It is about getting the vocabulary straight, because everything after it assumes these words mean something specific.
Checkpoint
The single most consequential design decision follows immediately: there is no CPU emulator at all. That is the next topic, and it explains the shape of everything else.