Step 22 · Loading and execution · C5

The system around the GPU

Apply the HLE-library pattern to console services: audio, video, network, input, the file sandbox and the launcher. Locate their initialization and guest-facing calls before the boot trace brings startup together.

Before this page: HLE libraries · Threads and TLS · C++ language, by example. View the learning route.

Read

Every one of these libraries is the same shape as the ContentExport example from HLE libraries: a LIB_VERSION, functions marked KYTY_SYSV_ABI with a PRINT_NAME() trace, and a LIB_DEFINE registration block. Once you can read one, you can read them all — so this page is mostly about what each one does.

The boot order — why order matters

Init() in src/emulator.cpp initialises subsystems in a deliberate order, because each assumes the ones before it exist:

src/emulator.cpp — the subsystem sequence
subsystems.Initialize<Config::Lifecycle>();
subsystems.Initialize<Log::Lifecycle>();
subsystems.Initialize<Loader::Timer::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::PthreadLifecycle>();
subsystems.Initialize<Profiler::Lifecycle>();
subsystems.Initialize<Libs::Network::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::Memory::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::FileSystem::Lifecycle>();
subsystems.Initialize<Libs::Controller::Lifecycle>();
subsystems.Initialize<Libs::Audio::Lifecycle>();
subsystems.Initialize<Libs::Graphics::Lifecycle>();

Read the explicit sequence as the dependency contract: config first (everything reads it), logging second (everything writes it), then timer, pthread, profiler, network, memory, files, input, audio and finally graphics. Each Lifecycle supplies a name and static constexpr callbacks; successful shutdown and emergency-shutdown callbacks are recorded and later invoked in reverse order. This pattern is introduced in topic C1.

Audio — ports, graphs and decoders

The implementation is now split by responsibility. audio.cpp owns AudioOut/AudioOut2 and AudioIn; acm.cpp owns ACM; audio3d.cpp owns Audio3D; and ngs2.cpp owns NGS2 systems, racks, voices and sampler graphs. Follow those files rather than looking for every API in the former monolithic audio implementation.

AudioOut2 supports 12-channel (7.1.4) data with downmix, user-capability queries, even blocking-write cadence and capture of submitted PCM before shared guest buffers change. NGS2 plays PCM16 and ATRAC9 sampler blocks, preserves stream/loop positions and supports rear low-pass filtering; unsupported custom modules warn and are skipped.

ajm.cpp parses asynchronous jobs through libs/ajm/decoder.h. ATRAC9 uses LibAtrac9; AAC, MP3 and Opus use FFmpeg. libs/ajm/opus_decoder.h adds Opus decode/control handling, and shared reset logic handles gapless state.

--mic <name> selects SDL3 microphone capture for AudioIn; omitting it produces paced silence. dualSenseHaptics.cpp routes vibration-audio ports to DualSense haptics and pad-speaker ports to the controller speaker when the host device is available. Audio haptics are a separate path from PadSetVibration() motor rumble.

Video — FFmpeg behind a console API

Two layers: avPlayer.cpp for full-motion-video playback, and libVideoDec2.cpp plus videoDec2Decoder.cpp for the general video-decode API. Both sit on FFmpeg; the README credits shadPS4 as a reference for the AVPlayer implementation. Games use these for cutscenes — the part of the run where the GPU thread is suddenly rendering video frames instead of the game.

Network — real sockets, offline-safe PlayStation

libNet.cpp plus network.cpp implement sockets, resolvers, SSL and HTTP shims — increasingly by forwarding straight to the host socket API and translating error codes, rather than by re-implementing behaviour. The subtle part is the NP surface — PlayStation Network APIs that a game will call at boot. Kyty stubs these carefully enough that the game continues offline rather than hanging on a login that will never succeed. That is the "pragmatic stub" philosophy from HLE libraries applied to an entire service:

src/libs/network.h — a slice of the surface
int KYTY_SYSV_ABI NetResolverCreate(const char* name, int memid, int flags);
int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, ...);
int KYTY_SYSV_ABI Socket(int family, int type, int protocol);
namespace Ssl {
int KYTY_SYSV_ABI SslInit(uint64_t pool_size);
int KYTY_SYSV_ABI SslTerm(int ssl_ctx_id);
}

Trace each API’s return code, output fields and completion state together. Offline implementations provide only part of the console service contract; returning success alone does not establish that a login or network operation completed.

Notice-screen preference

--skip-notice-screen true sets ConfigOptions::skip_notice_screen, which defaults to false. The launcher persists the same option and exposes it as Skip boot logos. Follow config.cpp → mainDialog.cpp → src/main.cpp → Config::SkipNoticeScreen() to trace a saved preference into the guest API.

src/libs/libSystemService.cpp — the guest query
static int KYTY_SYSV_ABI SystemServiceGetNoticeScreenSkipFlag(bool* value) {
    PRINT_NAME();

    if (value == nullptr) {
        return SYSTEM_SERVICE_ERROR_PARAMETER;
    }

    *value = Config::SkipNoticeScreen();

    return OK;
}

The game decides how to use the returned flag. This can skip notices or logos in games that honor the query; it does not remove every game’s intro sequence. The neighboring set/auto-set entry points currently return success without changing this configured value.

Input — SDL events become DualSense state

libPad.cpp, libs/controller.cpp, and presentation/window/hostInput.cpp form a clean mapping example. SDL's event loop runs on the main thread; controller, keyboard, and mouse input become the padData.h state returned to the game. Repeatable --keymap Control=Input arguments customize PC controls, and guest vibration commands travel back to the physical controller through SDL rumble.

SDL forwards light-bar and adaptive-trigger settings as well as rumble. dualSenseBluetooth.cpp handles the Bluetooth audio/haptics transport using Opus; dualSenseHaptics.cpp also routes USB controller audio. Device support and transport determine which outputs are available. The default keyboard keys 1, 2 and 3 cycle speaker volume, vibration intensity and trigger-effect intensity. --controller-color, --controller-volume and --controller-vibration set startup preferences.

SDL3 also supplies gyroscope and accelerometer samples for pad motion APIs. The default keyboard mapping uses Z/C for L2/R2. Windows are resizable and recreate the swapchain on resize; Space no longer acts as a pause shortcut. VideoOut supports RGB10A2 PQ buffers and layered presentation. --vr enables a virtual HMD2 model (libHmd2.cpp), including field-of-view/reprojection state, not a physical-headset backend.

The file sandbox — guest paths, host files

The game believes it lives on a console with /app0, /download0, /temp0 and /savedata0. The emulator synthesises that tree from host directories at boot:

src/emulator.cpp — mounting the sandbox
static void MountSandboxDirs() {
    std::string title_id;
    if (!Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) || title_id.empty()) {
        title_id = "UNKNOWN";
    }
    MountOrCreateDir("_DownloadData/" + title_id, "/download0");
    MountOrCreateDir("_TempData/"     + title_id, "/temp0");
    MountOrCreateDir("_TempData/"     + title_id, "/temp");
}

kernel/fileSystem.cpp holds the mount table and GetRealFilename(), which every guest path passes through before touching the host. Two bugs live here in the real world: case sensitivity (host filesystems differ, so mounted-path lookup includes a case-insensitive fallback) and path translation (a game asking for /app0/eboot.bin is handed a host path by the loader, never the guest path).

Directories and ZArchive content

--game accepts a directory, ELF or .zar ZArchive with eboot.bin at its root. common/archive.cpp exposes virtual paths such as game.zar!/sce_sys/param.json through Common::File. A retained ArchiveReader shares the index and block cache while a mount or launcher scan is active. This is read-only game content; saves and temporary files use writable host locations.

Windows starts through wmain, converts arguments to UTF-8 and preserves native std::filesystem::path values through file operations. Guest paths without a matching mount stay outside the host namespace. The launcher uses gameContent.cpp to inspect folders and archives uniformly, including icons, title metadata and game size.

libSaveData.cpp persists SaveDataMemory2 data and allocation metadata on disk, including search/mount capacity and timestamps. Save-data dialog locking and CP932 conversion have dedicated regression tests. Treat these as observable guest behavior when debugging missing progress or stuck dialogs.

The launcher — Qt in front of the emulator

src/launcher/ is a small Qt 6 application that searches the configured game folders recursively for directories containing eboot.bin and supported .zar archives, lists them with their compatibility status, and runs kyty_emulator --game <path> with the flags from a per-game or global configuration. It is deliberately thin — all the interesting state lives in emulator.cpp and the config layer (common/emulatorConfig.cpp, which receives the flags built from the launcher's persisted profiles).

The launcher provides a release update checker (updateChecker.cpp, which reads the website feed and cross-checks GitHub release dates), a theme with a proper light mode (launcherTheme.cpp + styles/launcher.qss) and settings grouped into resizable sections, GPU selection (gpu_index, mirroring --gpu), the linear-image readback toggle, and 1440p/2160p output resolutions. Two defaults differ from the command line: the launcher turns shader validation on and asks spirv-opt for Performance, while a bare kyty_emulator run uses neither.

The launcher now exposes microphone choice and tessellation, an explicit game-list refresh action, a size column and a native macOS folder picker. Changing settings does not trigger a full content rescan. AVPlayer fixes cover startup without an event callback, end-of-file delivery, loop timestamps, decoded padding and aspect ratio; its file callbacks also read archive members.

The one pattern

Every library is a console API surface, not a design you invent. The work is mapping: which host thing stands in for which console thing, and what the game needs to believe. Ports → an audio mix thread. Decode jobs → FFmpeg. Sockets → the OS socket layer. SDL events → DualSense state. Guest paths → host paths. The code reads like a translation table, because it is one.

Local trophies and notifications

libTrophy.cpp and common/trophies.cpp implement local trophy registration, unlock/progress state and persistence using title trophy metadata. UDS events can map to trophy progress. The system overlay displays unlock notifications and the launcher provides a trophy viewer. --trophy-notifications false disables the notification presentation; these local paths are not PSN synchronization. Read tests/TrophySystemTests.cpp alongside the implementation.

Click a trophy in the launcher to open ShowTrophyInspector() in src/launcher/src/trophyViewerDialog.cpp. It shows its grade, locked/unlocked status, local earned time, description and reward when present. A locked hidden trophy initially masks its metadata; Show Hidden Trophy reveals the details within that dialog without unlocking it. Labels use plain text, and the callback owns the trophy metadata and unlock snapshot through move captures.

WrapTrophyTitle() in src/graphics/presentation/systemOverlay.cpp wraps notification titles by words and explicit line breaks, then grows the toast height to fit. The icon stays at its base size and is vertically centered; a word wider than the text area remains intact and is clipped. This presentation path is separate from trophy persistence.

Library contracts worth tracing

libPngEnc.cpp encodes PNG output using stb_image_write and validates the supplied buffers and parameters. Socket operations in network.cpp translate host errors and timeout units, including microsecond guest timeouts; URI building is covered with parsing in HttpUriParseTests.cpp. Sign-in dialogs remain active until dismissed. Bluetooth HID, DeviceService and USB library entry points include stubs, which must be distinguished from the host controller transport.

The launcher exposes controller preferences, per-game configuration import/export, cheat browsing and the trophy viewer. For audio timing, follow both SDL stream conversion and guest submission cadence: resampling alone does not establish the correct blocking interval. AudioOutTimingTests.cpp covers that distinction.

Learn more · the rest of the system
  • Reference
    ALSA / audio backendsThe audio layer's host side — SDL3 sits on top of whatever the OS provides, and the port API sits on top of SDL3.
  • Spec
    FFmpeg documentationThe decoder behind AJM, AVPlayer and VideoDec2. The avcodec/avformat split is the mental model for all three.
  • Reference
    SDL3 wikiThe window, input and audio host layer. SDL is what makes one binary run on Windows, Linux and macOS.
  • Guide
    Qt 6 documentationFor the launcher — a stock Qt widgets app; nothing here is unusual.

APR command order and waits

src/libs/libAmpr.cpp stores file reads, memory operations, address writes/waits and events in one append-ordered command sequence. The worker advances a submission cursor one command at a time. A blocked address wait holds up its own priority queue while other ready priorities can proceed; completion is published only when the sequence completes or fails. APR file reads reuse worker-owned staging storage rather than allocating a new buffer for each read.

Watch

The call path from HLE libraries, in one picture: how a guest call reaches a function in src/libs/ and returns — the same route for every one of the libraries above.

Try

The fastest way to feel the "translation table" shape is to pick one library and read its full cycle.

A ten-minute lab

Open src/libs/libPad.cpp and trace one button press end to end: where SDL delivers the event, where it becomes a PadData bit, and which KYTY_SYSV_ABI function the game's padReadState lands in. It is the shortest complete "console API → host reality" loop in the tree.

Do

These checkpoints turn the map into reading you can reproduce.

Checkpoint