Step 10 · Project orientation · C6
The codebase map & the toolbox
Keep this map open during the loading and graphics lessons that follow: where each subsystem lives, how a run flows through it, and which third-party library does which job.
Before this page: What an emulator is. View the learning route.
Read src/ by responsibility: shared host utilities, loading and linking, guest kernel services, HLE libraries, graphics and the launcher. These are not a strict dependency hierarchy; use the actual includes, callbacks and call sites to trace how two subsystems cooperate.
The map of src/
src/main.cpp, emulator.cpp the entry: parse args, boot, run
src/common/ everything independent: abi, logging, threads, file,
virtual memory, singleton, debug, host exceptions
src/loader/ ELF/SELF loading, the runtime linker, NIDs, JIT stubs,
guest instruction patching and red-zone protection
src/kernel/ the guest OS surface: memory, address space, threads,
event queues, semaphores, filesystem
src/libs/ the HLE libraries — topic C5's translation table
src/graphics/ the GPU half: guest formats, Vulkan host, shader recompiler
src/launcher/ the Qt launcher that finds games, runs the emulator, checks for updates
tests/ 29 portable test executables, plus x86-64 macOS SSE4a — topic C3
Within graphics, four directories separate guest commands, host rendering, shader compilation and presentation:
| Directory | Role | Key files |
|---|---|---|
graphics/guest_gpu/ | how the guest sees the GPU — formats, the PM4 command processor, registers | command_processor/, gpu_defs.h, graphicsRun.cpp (the GPU thread, topic C4) |
graphics/host_gpu/ | the host Vulkan renderer — context, pipeline cache, image/buffer caches, memory tracking | renderer/context.cpp, renderer/cache/, renderer/pipeline/, vulkanCommon.h, pageManager.cpp |
graphics/shader/ | RDNA2 → typed IR → SPIR-V | recompiler/frontend/{decode,cfg,translate}/, recompiler/ir/, recompiler/backend/spirv/, and ShaderRecompiler.cpp (TranslateProgram + CompileProgram) |
graphics/presentation/ | the window, swapchain and the imgui system overlay — the actual frames on screen | presentation/window/, videoOut.cpp, systemOverlay.cpp |
The entry points and boot sequence
The main boot responsibilities follow this sequence, as shown in the Lifetime animation. Follow the source for initialization and error paths between these steps:
main.cpp
└─ ParseArgs() --game, the flag table from topic C3
└─ Run(options) emulator.cpp
└─ Init(...) the subsystem sequence from topic C5
└─ MountSandboxDirs() /app0, /temp0, ... — topic C5
└─ LoadElf(eboot.bin) runtimeLinker: parse SELF/ELF, dynamic tags
└─ Execute(...) emulator.cpp — spawns the guest thread
└─ RuntimeLinker::Execute() load libc if present, resolve relocations, apply patches,
start libc if loaded, call the entry point (topic C2's asm)
└─ Graphics::WindowRun() main thread runs the window/event loop
Three files cover most "where does this happen" questions: emulator.cpp (the sequence), loader/runtimeLinker.cpp (the loading/execution), and common/subsystems.h (the lifecycle machinery that runs each step).
The toolbox — third-party libraries
Dependencies in 3rdparty/ come from submodules, configure-time downloads and prebuilt packages; the CMake list in the root CMakeLists.txt is the dependency manifest. The ones you will actually touch:
| Library | What it does | Where it shows up |
|---|---|---|
SDL3 | window, input, audio host layer | WindowRun, controller.cpp, the audio backend |
FFmpeg | audio & video decode | ajm.cpp, avPlayer.cpp, libVideoDec2 |
Vulkan-Headers + SPIRV-Tools | the Vulkan API and SPIR-V validation/optimisation | the whole host_gpu layer; spirv-tools-opt in the link list |
VulkanMemoryAllocator (VMA) | GPU memory allocation | image/buffer allocation in host_gpu; wrapped entirely inside vma.cpp |
fmt | type-safe printf-style formatting | LOGF, EXIT, every string build — topic C3 |
spdlog | the logging sinks behind common/logging/ | every log line, routed to console or file by --printf-direction |
magic_enum | enum → string, string → enum at compile time | magic_enum::enum_name (relocation/crash logs), magic_enum::enum_cast for --shader-optimization-type parsing |
xxHash | fast, non-cryptographic hashing | shader and cache keys |
stb | image decoding and PNG encoding | libPngDec / libPngEnc |
nlohmann_json | JSON parsing | param.json, libJson2, ETAHen/GoldHEN-style cheats |
Zydis (fetched) | x86-64 disassembler | guestInstructionPatcher.cpp decodes guest instructions with it |
xbyak (fetched) | x86-64 assembler | guestInstructionPatcher.cpp generates patch/trampoline code |
Qt 6 (system) | the launcher UI | src/launcher/ — game list, per-game settings, input mapping, trophy viewer, compatibility database, update checker, theme |
| Opus | DualSense Bluetooth audio encoding | dualSenseBluetooth.cpp |
LibAtrac9 | the console's ATRAC9 audio codec | AJM decode jobs |
imgui | immediate-mode UI | the system overlay in presentation/systemOverlay.cpp — the IME keyboard and the error dialogs drawn over the game |
tracy | frame profiler | --profile (topic C3) |
winpthread | pthread shim on Windows | linked on clang-cl builds |
CTest | test registration and execution | kyty_tests builds the test executables; many use their own assertions and main() rather than GoogleTest (topic C3) |
cpuinfo | CPU feature detection | runtime capability checks |
Two of them are the pair you saw in topic C2 and the patcher topics from the course: Zydis reads the guest's x86 instructions, and xbyak writes new ones. The emulator's own JIT in loader/jit.h is hand-assembled bytes; xbyak is the "proper" assembler used where a lot of code has to be generated.
Reading conventions that save time
- Names map to hardware.
pm4.h,gpu_defs.h,RegisterDefaults— if a name looks like a datasheet field, it is one. The codebase keeps console names rather than inventing friendly ones (thepath.htmlappendix covers this in detail). - Check linkage and registration separately. Namespace-scope
staticfunctions and anonymous namespaces provide internal linkage. An HLE function can still be registered by address throughLIB_FUNC; header declarations and namespace membership alone do not tell you whether the guest can call it. EXIT_IF/EXIT= the error channel. Topic C3's macros. A wall of them at the top of a function is a validation contract.- Follow a value, not a call graph. Start from a struct (a
V#, aSubmission, aKernelEvent) and grep for it — the tree is organised around data, and the data names are stable.
If you want one linear path through the whole tree: main.cpp → emulator.cpp → common/subsystems.h → loader/runtimeLinker.cpp (load one module) → kernel/memory.cpp (allocate one page) → one libs/libPad.cpp function → one draw through commandProcessor. That is roughly the course's own order, and every later page is a deepening of one of those hops.
- GuideReading conventions in this codebaseThe learning path's own appendix — naming, comments, and the habits the maintainers expect.
- SpecKytyPS5 on GitHubThe source this whole site is a reading of. The README's "Important areas" list is the maintainers' own map.
- ReferenceCMake tutorialIf the
CMakeLists.txtis opaque, this is the shortest path to reading the dependency manifest as a manifest.
The boot sequence the map describes, end to end — three threads, three jobs, and where every subsystem in src/ sits in the flow.
Grep is the map's search box. Prove you can navigate it in ten minutes.
Pick a noun from this page — Submission, KernelEvent, ShaderInfo, PageFaultAccess — and rg it across src/. Read the first struct definition you find, then jump to the two or three places it is consumed. That "follow the value" loop is how every page in this course was researched.
These checkpoints are the map committed to memory.
Checkpoint