Static recompilation runtime libraries for PlayStation 3 titles.
C
382
1,545 commits
updated Oct 7, 2026
Static recompilation runtime libraries for PlayStation 3 titles. Turn PS3 binaries into native executables. No emulator required.
Join the sp00nznet recomp Discord — the community hub for sp00nznet's recomp projects, where ps3recomp development happens in the open. Good place to ask questions, show a port you are working on, or find out what people are stuck on before you duplicate the effort.
ps3recomp is an open-source toolkit that provides the runtime libraries, system stubs, and analysis tools needed to statically recompile PlayStation 3 games into native PC executables.
Instead of interpreting or dynamically recompiling PowerPC/SPU instructions at runtime (what emulators like RPCS3 do brilliantly), we take the opposite approach: translate everything ahead of time into C/C++ that compiles with your favorite compiler on any modern platform.
This is the same philosophy behind:
...but for the PS3's glorious, terrifying Cell Broadband Engine.
The PlayStation 3 has over 3,000 titles and some of the most beloved games ever made. RPCS3 has done heroic work making ~70% of the library playable through emulation, but static recompilation offers something different:
Every row is exercised by CI on every push, and all three are green on
master — the PPU boot scaffold ratchet that had been red since before v0.11.0
(#139) is at zero on all
four toolchains. The rows below describe what those jobs actually do, not what a
green tick would imply: building the runtime and passing the suites is not the
same as running a game, and only one row claims that.
| Windows | macOS (arm64) | Linux | |
|---|---|---|---|
| Runtime library builds | yes | yes | yes |
| Lifter + 8 test suites | yes | yes | yes |
| Render backend | D3D12 | Metal | null (headless software); Vulkan (opt-in) |
| Runs a recompiled game | yes | no | no |
The gap on macOS is the PPU boot scaffold — ppu_loader.cpp, boot_main.cpp
and the HLE dispatch are still Win32-only (thread creation, VirtualAlloc, SEH,
stack walking), and they are compiled per-game rather than into the library.
So macOS builds the runtime, renders through Metal and passes the full test
suite, but cannot yet load and run a title. ps3recomp_host drives
cellGcm → RSX → Metal end to end without a game, which is what makes further
graphics work possible there.
Linux builds and passes the same suites under both GCC and Clang, and runs
ps3recomp_host end to end, on every push. Its backend is the null backend's
headless software path: a host-memory framebuffer with a CPU triangle filler,
enough to prove the guest's command stream produced the pixels it asked for on
a runner with no display and no GPU. It is not a renderer — no depth, no
blending, no textures — so Linux is still one real backend, plus the same PPU
boot scaffold macOS needs, away from running a title.
Both non-Windows platforms are blocked on the same two things, in this order.
1. The PPU boot scaffold, on POSIX. This is the shared blocker and the only
one that gates "runs a recompiled game" on both. ppu_loader.cpp,
boot_main.cpp and the HLE dispatch are compiled per game rather than into
the library, and were Win32-only — thread creation, VirtualAlloc, SEH, stack
walking. v0.10.0 took it to zero errors on POSIX and put it under CI so it
cannot rot again, and ported the guest-pointer trap, thread-locals and host
backtraces. What is left is running it: load a lifted image on POSIX and reach
the guest's first instruction, then main. Until that works, neither platform
can run a title no matter how good its renderer is.
2. One real backend each. The RSX abstraction landed in v0.10.0 (#101, #102,
#103) — texture conversion, the TEXTURE_CONTROL1 crossbar, texture layout and
a neutral draw record all live outside the D3D12 file now, so a backend no
longer has to reimplement them to draw anything.
ps3recomp_host already drives
cellGcm → RSX → Metal with no lifted game, so each step is testable before a
title exists to run.-DPS3RECOMP_RSX_VULKAN=ON). With
guest programs on (glslang at build time, PS3RECOMP_VK_GUEST_PROGRAMS=1) it
runs on the shared register-file draw engine, the path Metal takes, and every
ps3recomp_host scene passes on it -- including render-to-texture, MRT and
depth textures; without them it draws the null backend's fixed-function
contract. It has not run a recompiled title yet. The default is still the
headless backend, a CPU triangle filler for CI, deliberately.Deliberately not claimed: the table above says "no" for running a game on
macOS and Linux, and it will keep saying "no" until a title actually boots to
gameplay on one of them. Building the runtime, passing the suites and rendering
through ps3recomp_host are all real, and none of them is that.
The PS3's Cell processor is a beast unlike anything else:
| Component | What It Is | Why It's Hard |
|---|---|---|
| PPU | PowerPC 64-bit main CPU | Custom ISA extensions, VMX/AltiVec SIMD |
| 6x SPU | 128-bit SIMD vector processors | Proprietary ISA, 256KB local store, explicit DMA |
| Memory | 256MB XDR RAM | 128-byte alignment requirements, split bus |
| LV2 Kernel | Proprietary OS layer | ~300+ system calls, complex threading model |
| PRX Modules | Dynamic libraries | 100+ system libraries with intricate interdependencies |
We don't shy away from hard problems. We just break them into smaller ones.
ps3recomp/
├── tools/ # Binary analysis & recompilation pipeline
│ ├── elf_parser.py # Parse PS3 ELF/SELF/PRX binaries
│ ├── ppu_disasm.py # PowerPC disassembler with PS3 extensions
│ ├── ppu_lifter.py # PPU assembly -> C code lifter
│ ├── spu_disasm.py # SPU instruction set disassembler
│ ├── spu_lifter.py # SPU assembly -> C code lifter
│ ├── extract_spu_images.py # Extract embedded SPU images from PRX/ELF
│ ├── find_spu_functions.py # SPU function boundary detection
│ ├── wrap_spu_elf.py # Wrap a raw SPU blob as a loadable ELF
│ ├── prx_analyzer.py # Analyze PRX imports/exports and NIDs
│ ├── nid_database.py # Function Name ID (NID) resolver
│ ├── find_functions.py # Function boundary detection
│ └── generate_stubs.py # Auto-generate HLE stubs from RPCS3 modules
│
├── runtime/ # Core runtime for recompiled executables
│ ├── ppu/ # PPU execution context
│ │ ├── ppu_context.h # Register file, CR, LR, CTR, FPSCR
│ │ ├── ppu_memory.h # Memory access macros (loads, stores, byte-swap)
│ │ └── ppu_ops.h # Instruction operation macros
│ ├── spu/ # SPU execution context
│ │ ├── spu_context.h # 128×128-bit register file, 256KB local store, image_id
│ │ ├── spu_channels.c # Channel ops + per-image function-table dispatch
│ │ ├── spu_helpers.h # SPU intrinsics (incl. float conversions)
│ │ ├── spu_dma.h # DMA (MFC) transfer engine
│ │ └── tests/ # Self-contained SPU runtime tests
│ ├── memory/ # Memory subsystem
│ │ ├── vm.h # Virtual memory manager (256MB address space)
│ │ └── atomic.h # PS3 atomic operations (lwarx/stwcx)
│ └── syscalls/ # LV2 kernel system call implementations
│ ├── sys_ppu_thread.h # Thread creation/management
│ ├── sys_mutex.h # Mutex/cond synchronization
│ ├── sys_memory.h # Memory allocation/mapping
│ ├── sys_fs.h # Filesystem operations
│ ├── sys_prx.h # PRX module loading
│ └── lv2_syscall_table.h # Full syscall dispatch table
│
├── libs/ # HLE library implementations (from RPCS3's module system)
│ ├── audio/ # cellAudio, cellAdec, libmixer, libsnd3, libsynth2
│ ├── video/ # cellGcmSys, cellResc, cellVideoOut, cellVpost, cellVdec
│ ├── input/ # cellPad, cellKb, cellMouse, cellGem
│ ├── network/ # cellNetCtl, cellHttp, cellHttps, cellSsl, sceNp*
│ ├── filesystem/ # cellFs, cellGame, cellSaveData, cellStorage
│ ├── system/ # cellSysutil, cellSysmodule, cellRtc, sysPrxForUser
│ ├── spurs/ # cellSpurs, cellFiber, cellSpursJq, cellDaisy (SPU task management)
│ ├── sync/ # cellSync, cellSync2 (cross-PPU/SPU synchronization)
│ ├── codec/ # cellJpgDec, cellPngDec, cellGifDec, cellPamf, cellDmux, cellAdecAtrac3p, cellAdecCelp8, cellVdecDivx
│ ├── font/ # cellFont, cellFontFT, cellFreeType, cellL10n
│ └── misc/ # cellScreenshot, cellSubdisplay, cellImeJp, cellVideoExport, cellMusicExport, cellGameRecording, etc.
│
├── include/ps3emu/ # Public API headers
│ ├── ps3types.h # Fundamental PS3 types (u8-u128, s8-s64, be_t<>)
│ ├── endian.h # Big-endian <-> little-endian conversion
│ ├── nid.h # NID hashing and lookup
│ ├── module.h # Module registration framework
│ └── error_codes.h # PS3 system error codes (CELL_OK, CELL_E*)
│
├── templates/project/ # Starter template for new game ports
│ ├── CMakeLists.txt # Build system
│ ├── main.cpp # Application entry point
│ ├── stubs.cpp # Game-specific function overrides
│ └── config.toml # Recompiler configuration
│
├── config/ # Reference configurations
│ └── example.toml # Example recompilation config
│
├── patches/ # Patches for upstream tools
│ └── xenonrecomp-ppu.patch # XenonRecomp adaptations for PPU (Cell != Xenon)
│
└── docs/ # Documentation (incl. ARCHITECTURE, RUNTIME, SPU_LIFTER, RSX_GRAPHICS, ...)
├── ARCHITECTURE.md # Cell processor and recomp pipeline deep-dive
├── GETTING_STARTED.md # How to recompile your first PS3 game
├── MODULE_STATUS.md # Implementation status of all HLE modules
├── MODULES_REFERENCE.md # Detailed per-module documentation
├── RUNTIME.md # Runtime reference: VM, PPU/SPU contexts, types
├── SYSCALLS.md # All LV2 kernel syscall implementations
├── NID_SYSTEM.md # PS3 NID linking system explained
├── TOOLS.md # Recompiler pipeline tools reference
├── BUILDING.md # Build system, compilers, platform notes
├── GAME_PORTING_GUIDE.md # Step-by-step game porting walkthrough
└── PLATFORM_ABSTRACTION.md # Win32/POSIX cross-platform details
The pipeline follows the same proven approach as our burnout3 project, adapted for the Cell architecture:
PS3 Game Disc / PKG
│
▼
┌─────────────┐
│ SELF/EBOOT │ Encrypted PS3 executable
│ Decryption │ (requires keys / fSELF)
└──────┬──────┘
│
▼
┌─────────────┐
│ ELF Parser │ Extract sections, segments, relocations
│ + PRX Scan │ Identify imported NIDs & library deps
└──────┬──────┘
│
▼
┌─────────────┐
│ PPU Disasm │ Disassemble PowerPC 64-bit + VMX
│ + Analysis │ Function boundaries, jump tables, ABI
└──────┬──────┘
│
▼
┌─────────────┐
│ PPU Lifter │ PowerPC → C code generation
│ + SPU Lift │ SPU programs → C code generation
└──────┬──────┘
│
▼
┌─────────────┐
│ Link with │ ps3recomp runtime + HLE libs
│ Runtime │ Provides all PS3 OS services
└──────┬──────┘
│
▼
┌─────────────┐
│ Compile & │ MSVC / GCC / Clang
│ Ship! │ Native x86-64 executable
└─────────────┘
The SPU is the elephant in the room. Each SPU has its own instruction set, its own 256KB memory, and communicates with the PPU via DMA and mailbox channels. Our approach:
extract_spu_images.py pulls them out of PRX/ELF)spu_disasm.py + spu_lifter.py handle the SPU ISAspu_dma.h)image_id lets multiple SPU programs (e.g. a SPURS kernel, a policy module, and a job) coexist at overlapping local-store addressesThis mirrors how RPCS3 handles SPU but at compile time rather than runtime. The
disassembler, lifter, and runtime are functional and covered by self-contained
tests (runtime/spu/tests/: sum, shufb, DMA, brsl-return).
We're building HLE implementations based on RPCS3's module system. 97 modules complete, 1 partial (media decode) — 219 files and ~76,000 lines under libs/.
| Category | Modules | Status |
|---|---|---|
| Kernel Threading | sys_ppu_thread, sys_mutex, sys_cond, sys_semaphore, sys_rwlock | ✅ Complete |
| Kernel Events | sys_event (queues, ports, flags), sys_timer | ✅ Complete |
| Kernel Memory | sys_memory, sys_mmapper (containers, shared mem) | ✅ Complete |
| Kernel Filesystem | sys_fs (path mapping, real I/O) | ✅ Complete |
| Filesystem | cellFs (real file I/O, dir ops, stat, path translation) | ✅ Complete |
| Save System | cellSaveData (callback-driven, PARAM.SFO), cellGame | ✅ Complete |
| Input | cellPad (XInput/SDL2), cellKb, cellMouse | ✅ Complete |
| Audio | cellAudio (WASAPI/SDL2, mixing thread, multi-port) | ✅ Complete |
| Video Output | cellVideoOut (resolution config, 720p default) | ✅ Complete |
| Codecs | cellPngDec, cellJpgDec, cellGifDec (stb_image) | ✅ Complete |
| Font | cellFont (stb_truetype backend + fallback metrics) | ✅ Complete |
| Network | sys_net (BSD sockets, P2P), cellNet, cellNetCtl, cellHttpUtil, cellSsl, sceNp*, sceNpTrophy, sceNpMatching2 + Score over psnr | ✅ Complete |
| Hardware | cellUsbd (USB), cellCamera (PS Eye), cellGem (PS Move) — stub, no devices | ✅ Complete |
| Sync | cellSync (atomic spinlocks, LF queue), cellSync2 (OS-backed) | ✅ Complete |
| System | cellRtc, cellMsgDialog, cellOskDialog, cellUserInfo, cellGameExec | ✅ Complete |
| Localization | cellL10n (UTF-8/16/32/UCS-2, ISO-8859-1, generic converter) | ✅ Complete |
| Resolution | cellResc (display modes, scaling, interlace, aspect ratio) | ✅ Complete |
| Fibers | cellFiber (PPU fibers via Windows Fibers/ucontext) | ✅ Complete |
| AV Config | cellAvconfExt (audio output info, gamma, sound availability) | ✅ Complete |
| Input Util | cellKey2char (HID scancode → Unicode, US-101 layout) | ✅ Complete |
| Graphics | cellGcmSys (cmd buffer, IO mapping, tile/zcull, flip handlers, timestamps, FIFO walker + ring recycle), RSX command processor (NV47xx parsing, state tracking, backend callbacks), live D3D12 backend (executes the title's real NV4097 vertex/fragment programs — indexed draws, per-draw VP constants + textures, render-to-texture, MRT, FP predication), null backend (Win32 window) | ✅ Complete |
| SPURS | cellSpurs (management APIs, event flags, task/workload tracking), cellSpursJq (job queues with wait/signal), cellDaisy (real ring buffer FIFO) | ✅ Complete |
| Core Runtime | cellSysutil (BGM, cache, disc), cellSysmodule, sysPrxForUser (real lwmutex/lwcond/threads) | ✅ Complete |
| Media Pipeline | cellPamf (real PAMF header + stream descriptor parsing), cellDmux (callback sequencing), cellVdec/cellAdec (PICOUT/PCMOUT callbacks, no actual decode — needs FFmpeg), cellSail (state machine) | 🔨 Partial |
| HTTP/HTTPS | cellHttp (real HTTP/1.1 via native sockets), cellHttps (TLS stub with cert management) | ✅ Complete |
| Misc/Export | cellSubdisplay, cellImeJp, cellVideoExport, cellMusicExport, cellPhotoExport/Import, cellGameRecording, cellPrint, cellRemotePlay | ✅ Complete |
Full status tracking: docs/MODULE_STATUS.md
We've written extensive docs covering every aspect of the project. Whether you're contributing, porting a game, or just trying to understand how PS3 static recompilation works, there's a doc for you.
| Document | What It Covers |
|---|---|
| Getting Started | Prerequisites, installation, first recompilation walkthrough |
| Architecture | Cell processor overview, recomp pipeline stages, memory model, comparison with RPCS3 |
| Building | CMake build system, compiler support, platform-specific notes, troubleshooting |
| Game Porting Guide | Full 12-phase walkthrough for porting a PS3 game — with flOw case study |
| Custom Modules | Step-by-step tutorial for writing new HLE modules — NID system, calling convention bridge, patterns |
| FAQ & Troubleshooting | Common questions, build errors, runtime crashes, lifter issues, performance tips |
| Contributing | Development setup, code style, PR guidelines, ways to contribute |
| Runtime Reference | Virtual memory manager, PPU/SPU execution contexts, type system, endianness, syscall dispatch, DMA engine |
| Syscall Reference | All LV2 kernel syscalls: threading, sync, events, timers, memory, filesystem |
| NID System | How PS3 function linking works, NID computation, module registration framework |
| Module Reference | Detailed documentation for every HLE module — what they do and how they're implemented |
| Module Status | Quick-reference status table for all modules |
| RSX Graphics | RSX GPU translation architecture: command processor, D3D12 and Metal backends, shader strategy |
| Tools Reference | Every recompiler pipeline tool documented: ELF parser, disassembler, lifter, NID database |
| SPU Lifter | SPU ISA decoding/lifting, the runtime model (local store, channels, DMA, per-image dispatch), and the SPU tests |
| Platform Abstraction | How we handle Win32 vs POSIX: threading, sockets, timers, audio, memory, fibers |
| SPU Fallback | The PPU-side fallback registry — a hand-written replacement for an SPU job, or a bring-up shim before an image is lifted |
| Debug Console | Ask a title that is already running what it is doing, without a rebuild |
| Runtime Diagnostics | Generated index of every diagnostic environment variable the runtime reads |
| Prototype & Debug Builds | Why unencrypted, symbol-bearing prototype builds are the pipeline's ground truth |
| Online Play | Matchmaking, leaderboards and P2P play through a psnr server — --psnr host --username NAME |
| Regression Gate | How five ports are checked against recorded goldens before a shared-runtime change lands — and how to tell a real red from the four kinds of measurement artefact |
Prerequisites: Python 3.10+, CMake 3.20+, a C17/C++20 compiler (MSVC 19.x, GCC 12+, or Clang 15+)
# Clone the repo
git clone https://github.com/sp00nznet/ps3recomp.git
cd ps3recomp
# Install Python tools
pip install -r requirements.txt
# Stage 1: image, OPD function table, TOC and firmware imports
python tools/ppu_loader.py /path/to/EBOOT.ELF -o out/
# Stage 2: lift to C++. --hle-stubs turns each firmware import stub into
# ps3_hle_call(nid); leave it out and the first import call crashes the port.
python tools/ppu_lifter.py /path/to/EBOOT.ELF \
--functions out/EBOOT.functions.json \
--hle-stubs out/EBOOT.imports.json \
--output src/recomp/
# Stage 3: the NID -> HLE handler table for this runtime
python tools/gen_hle_nids.py --all --out src/gen/ppu_hle_nids.cpp
# Build with the runtime
cd templates/project
cmake -B build -G Ninja
cmake --build build
See docs/GETTING_STARTED.md for the full walkthrough.
Status as of 2026-10-07. Each port's own README carries the live detail and the run recipe.
| Game | Title ID | Status | Repo |
|---|---|---|---|
| The Simpsons Arcade Game (Konami) | NPUB30563 | Playable, and playable online. Boots through the Konami intro and character select into Stage 1, Downtown Springfield, with its ATRAC3plus music (#186) and correct flashes and glyphs (#185). Two players meet over psnr and play a match together: lobby, character select, the host's game setup, then Stage 1 in sync on both machines with each player's input moving their character on both screens — including with one player behind a NAT, through psnr's relay (#200, see docs/ONLINE.md). A Konami arcade emulator EBOOT: the 1991 coin-op ROM lives in SIMPSONS.SR and the binary emulates the hardware around it | sp00nznet/simpsonsarcade-ps3 |
| Tornado Outbreak (Loose Cannon / Konami) | BLUS30371 | In game, rendering correctly with no per-title overrides. Logos and Bink intro, title, main menu, New Game (the save is created), the story intro and dialogue, then the first level under stick control at ~10–16 fps. The runtime fixes it needed — raw-SPU proxy DMA, vertex-program flow control, GPU resolve copies — are shared (#189) | sp00nznet/tornado |
Not under active development; they are where much of the runtime was first proven, and several stay in the regression gate.
| Game | Title ID | Where it got to | Repo |
|---|---|---|---|
| Virtua Fighter 5 (Sega AM2) | BLUS30020 | Renders its NOW LOADING, CRIWARE and AM2 screens; zero SPURS and zero PSN imports | sp00nznet/vf5 |
| Tokyo Jungle (Crispy's/SCE Japan) | NPUA80523 | Data install, SPURS jobs, title screen and the main menus | sp00nznet/tokyojungle |
| You Don't Know Jack (Jellyvision/THQ) | BLUS30569 | Menus and answerable quiz questions; movie-backed screens still draw noise | sp00nznet/youdontknowjack |
| Twisted Metal (Eat Sleep Play) | BCUS98106 | Drove the live NV4097 engine (#113); boots real game code and its asset install | sp00nznet/twistedmetal |
| flOw (thatgamecompany) | NPUA80001 | First draws with the real libsre.prx; assets load | sp00nznet/flow |
| Rubber Ducky (Sony E3 2006 demo) | NPEA00003 | A debug build with full symbols — the lifter's ground truth, not a game target | sp00nznet/rubberducky |
Ports other people are running on ps3recomp, and whose fixes come back upstream: LittleBigPlanet (@sagemono), Yakuza: Dead Souls (@canersaka), God of War II HD and Ben 10 Omniverse (@andrebrumdev), and DBZ Budokai HD (@gnome41). Ask in the Discord to get yours listed.
Want to port a game? Start with the Getting Started section, check docs/MODULE_STATUS.md for system library coverage, and see the Game Porting Guide for a real-world walkthrough.
| Project | Role |
|---|---|
| RPCS3 | Our primary reference for HLE module behavior and system call semantics. Standing on the shoulders of giants. |
| XenonRecomp | PowerPC recompiler for Xbox 360. Both CPUs are PowerPC — we adapt its lifter for PPU-specific extensions. |
| N64Recomp | Pioneered the modern static recomp approach. Our architecture follows the same "recompile to C, link with runtime" philosophy. |
| PS2Recomp | Sibling project for PS2. Different ISA (MIPS vs PowerPC) but same spirit. |
| ReXGlue | The closest sibling this project has: a static recompilation SDK for the Xbox 360, turning PowerPC into portable C++ compiled ahead of time rather than interpreted. Same shape as ps3recomp — a toolkit plus a runtime, with game ports built on top — and a generation of Xenon hardware away from Cell. Their release engineering covers five platforms from one reusable build workflow, with nightlies and tagged releases that publish artifacts. |
| pcrecomp | Our PC recompilation toolkit. Shared philosophy of "analyze → disassemble → lift → link → ship". |
cellPad and cellAudio select their SDL2 backends unconditionally on non-Windows hosts, so it is required there; on Windows they use XInput and WASAPI. It does no windowing here — each graphics backend makes its ownThis is a massive undertaking and we need help. Here's how to get involved:
See CONTRIBUTING.md for guidelines.
Not sure where to start, or want to sanity-check an idea first? Ask in the Discord — several of the people working on ports and on the lifter are there.
This project does not contain any proprietary Sony code, encryption keys, or copyrighted material. It provides clean-room implementations of system library interfaces based on publicly documented behavior. You must provide your own legally obtained PS3 game files.
Contributors are credited in CONTRIBUTORS.md.
MIT License. See LICENSE for details.
ps3recomp is built by a growing community. See CONTRIBUTORS.md for who did what — thank you, everyone.
Two titles in game, and one of them online. The Simpsons Arcade Game plays a two-player match over the network, and Tornado Outbreak reaches its first level rendering correctly with no per-title overrides. Thirty-odd pull requests from six people since v0.12.1.
Online
PS3_NET_ONLINE; sceNpMatching2 rooms, signaling and
callbacks, sceNpScore leaderboards, sceNpLookup, cellSysutilAvc2; NAT probes,
hole punching and a relay for peers that cannot reach each other.
--psnr host --username NAME on the runtime's command line. Offline is still
the default and unchanged. See docs/ONLINE.md.Rendering
ps3recomp_host scene under the validation layer.SET_TRANSFORM_CONSTANT is a 32-dword window — @andrebrumdev
(#208); three registers were corrupting God of War II's skinning palette.Lifter
vcmpequb./vcmpequh. set CR6 (#207).lwax tables.SPU — @sagemono, from LittleBigPlanet:
non-r0 link returns (#174), data-only job-module entries (#175), drain resume
and iret unwind (#176), the taskset syscall on a planted context (#177),
_sys_spu_image_import segment source (#178).
Audio and media — cellAtrac with ATRAC3plus (#186), cellVdec/cellAdec query and open fixes (#197, #198, #201), cellSail state changes (#190), job-chain completion carrying the job index (#204).
Build — Linux and macOS building again (@gabryboy12-ai
#191, #205), -msse4.1 probed (@andrebrumdev #206),
spu_lifter --header-name honoured (@tomspilman #181),
the onboarding docs rewritten to the pipeline the tools implement (#180), and
AnyPS5 added to the sibling projects
(@bojogon7 #209).
A packaging release. v0.10.0 and v0.11.0 were both tagged and neither was ever
published — their release runs died on macOS with an undeclared
RtlCaptureStackBackTrace, so the newest download anyone could get was v0.9.1
from August. That is fixed, and Linux now builds alongside Windows and macOS.
VERSION.txt says what its platform can actually do — Windows renders and runs
titles through D3D12, macOS renders through Metal but cannot yet load one
because the PPU boot scaffold does not run on POSIX, and Linux has no renderer
at all. Someone downloading the Linux zip should learn that from the file, not
after building something with it.workflow_dispatch set the bundle version
from the branch name, so a branch with a slash in it nested the staging
directory and the upload glob found nothing — every platform built, tested and
staged, and the run failed anyway.cellGame content root strips /dev_hdd0 — @sagemono
(#163, landed as #173). ppu_fs strips the mount prefix, so guest
/dev_hdd0/game/<id> resolves to <root>/game/<id>; spelling the device out
recreated the very split v0.12.0 set out to close, one directory over.Nineteen open pull requests land on one branch. They came from the two people
who have games actually running — @canersaka
with Yakuza: Dead Souls on macOS, @sagemono
with LittleBigPlanet — and the capability they represent existed for months
without existing on master. Every one of caner's carried the same sentence:
"Windows gameplay has not been rerun for this batch." Nobody could answer that,
because nothing ran the ports together. That is what this release is.
The nineteen
cellGameContentErrorDialog;
cellGame content root — the host directory /dev_hdd0/game maps to must be
the one ppu_fs resolves it to, or a title writes its save data to one
directory, reads it from another, and reports the data corrupt rather than
missing; bi $rN classified by basic block rather than by the lifted function,
because the lifter splits straight-line runs into functions that chain by
fallthrough and a split is not a control-flow edge; hbra/hbrr decoded as
the 7-bit opcodes they are; --extra-funcs passthrough for images entered by
an interrupt vector, which --auto-functions cannot see; EDAT license types 1
and 2 with the missing HMAC-SHA1 hash modes; list-DMA issue tracing.What unifying them found
cellGameSetParamString was missing. Sage's cellGame work makes Twisted
Metal reach cellGameCreateGameData for the first time, and that path calls a
function nothing implemented. Exactly the kind of gap a port owner cannot see
from inside their own title — which is the argument for unifying, stated as a
bug.hbra/hbrr decode is byte-identical to
one already in the tree, reached independently from a different witness.The gate had to be rebuilt before it could say anything
Four things it could not do, each discovered by it letting something through:
ps3_ms_dump() ran only at
atexit(), and every gated port is killed on its timeout — so DROPPED, the
rule whose own docstring reads "Rendering stopping looks like this", had
nothing on either side to compare. Counts now checkpoint on a timer.--shots runs each port in the configuration its
README documents and compares against the port's own reference screenshot.
Frames are judged by distinct colour count, never brightness: a cleared buffer
scores 100% non-black while showing nothing.PS3_VFS_ROOT was doubled for two ports. The guest path is appended whole,
so a root of vfs/PS3_GAME/USRDIR produced
vfs/PS3_GAME/USRDIR/PS3_GAME/USRDIR/…. VF5 spun on 686,996 failed opens
and looked like a dead showcase title; Simpsons opened zero files and drew
9,391 groups of nothing, which looked like a broken renderer. Both render.
Their goldens were invalid rather than stale and are re-recorded.UNRESOLVED NID, a fact about the binary rather than about a
run.rubberducky leaves the gate: a Sony SDK sample recompiled from a debug fSELF
with its symbol map exposed is excellent ground truth for the lifter and says
nothing about whether a change is safe for retail titles. New:
docs/REGRESSION_GATE.md.
Fourteen open pull requests land at once. Most of them are correctness fixes found by driving real titles into real walls: Tokyo Jungle reaches its main menu, and the bugs it exposed — a mutex that never ran its initialiser, a FIFO resync that ate fences, two guest pointers dereferenced as host pointers — were never title-specific.
Execution correctness
sys_ppu_thread_once was unimplemented and returned success without
running the initialiser (#136). Anything waiting on state that a once-routine
was supposed to publish waited forever. This was the keystone bug behind
Tokyo Jungle's boot.xswd read the wrong words and stored the halves backwards (#127) —
small positive values became zero. GETLLAR must snapshot the reservation
from local store, not re-read memory (#126).vmsumshs, vsumsws, vmulesh and five byte-order bugs.Guest pointers are not host pointers
Two crashes, same root cause, both reachable from ordinary gameplay:
cellSaveData called the title's funcStat and funcFile as host function
pointers. They are guest OPDs, so this jumped into guest memory as code —
every autosave died on it. Both now marshal through guest memory the way
cellSaveDataAutoLoad2 already did.cellSail printed its uri and path arguments as host strings, killing
the process the moment a title opened a movie. Both are copied out of guest
memory now, and the descriptor's long-unused uri[512] field is finally
filled.Graphics and SPURS
get to put keeps
the ring alive, but a SET_REFERENCE in the skipped range means the
cellGcmFinish waiting on it never wakes. Losing draws costs a frame; losing
a fence deadlocks the title. The skipped range is now swept first, and each
resync site is named in the log.ShutdownJobChain (#129).cellSail: a player handle is a guest pointer, not an index (#130), and
async call completion is reported to the title's event handler (#133).Diagnostics and tooling
Documentation, honestly
ps3::memory /
ps3::thread / ps3::modules API that does not exist, and since the porting
guide tells you to copy that template, the documented path could only ever
produce undefined references (#114, #89). Rewritten against the real runtime
API, and its CMake now compiles the per-game PPU scaffold and sets /bigobj.RSX_LIVE_DRAW is not
required to get a picture.SPU_FALLBACK.md claimed ps3recomp cannot execute SPU code, which stopped
being true some time ago.CI
cellSync.c called getenv with no <stdlib.h> — invisible on Windows,
fatal on POSIX, and one reason every Linux job had been red.xswd case that built asymmetric vectors
with a helper documented as only safe for symmetric ones. Windows CI was
never running that suite at all; it does now.Known issue: the PPU scaffold ratchet still fails on Linux and macOS (
ppu_loader.cpp, 0 → 110 on gcc). It predates this release and is tracked in #139. Windows CI is green.
Windows, macOS and Linux all build the runtime and pass the suites. A second draw engine lands beside the first — @canersaka's, brought across from his Yakuza port — the RSX backend finally gets a real abstraction instead of one D3D12 file, and You Don't Know Jack reaches gameplay.
A second draw engine
rsx_live_draw.c, rsx_dispatch.*, rsx_vertex_compact.*,
rsx_restart_cuts.h and the VP/FP decompiler updates are
@canersaka's work (#113). Opt-in behind
RSX_LIVE_DRAW, so the existing backend path is unchanged when it is off.
This sits beside @sagemono's draw engine from
v0.7.0 — two independent renderers, not a replacement.SET_SHADER_PROGRAM dropped, so every draw ran one constant-black fragment
program); HDR colour surfaces (F_W16Z16Y16X16 targets were created as
R8G8B8A8_UNORM, clamping every value above 1.0 on write); and fragment-output
NaN guards.Three platforms
TEXTURE_CONTROL1 crossbar decode (#102), texture layout, and one primitive
truth table with a draw record that is not a D3D structure (#103) all lifted
out of the D3D12 backend — so Metal and the headless backend read the same
neutral values instead of reimplementing them.dirent.h.Runtime correctness
CellSyncMutex is a ticket lock, not a 0/1 flag (#118) — the guest word
is big-endian m_freed/m_order and free means m_freed == m_order. We
stored it host-endian and CAS'd it from zero, so a mutex the SPU left free at
1/1 read as 0x01000100 and TryLock spun forever — 160 million times, in
the title that found it.sys_mmapper_allocate_memory implemented (#112), cellSpurs
CreateJobChain + KickJobChain actually start the chain (#98),
cellDiscGameGetBootDiscInfo takes one argument, not three (#109),
sys_event yields before refusing a receive on an invalid queue (#110),
and SPU MFC PUTs into the null page are rejected (#107).Diagnostics
PPU_WVAL watches PPU stores by value rather than by address (#108), the
write watch says when it stops printing and lets the cap be raised (#111),
the guard prints the writer's live arguments on every watched-line hit
(#99), plus PS3_HLE_ARGS, PS3_WAIT_OBJ and a settable SPU atomic-trace cap.Ports
A packaging release. No runtime or lifter behaviour changes: the same tree as v0.9.0, plus the CI and release plumbing that makes it downloadable.
v* tag builds an SDK bundle per platform
— the runtime library, its public headers, the full Python pipeline and the
docs — and attaches it with checksums. Previously a release was source-only,
so trying the project meant reproducing the whole toolchain setup first.
lib/ and include/ come from the project's own install rules, so the
bundle cannot drift from what a local cmake --install produces.The first tagged release, and the first that another platform can verify. Two outside contributions from @slushiimusic gave the project CI and a second render backend; wiring the existing test suites into that CI immediately found fifteen silent miscompiles.
macOS, and the project's first CI (#94, #95, #96)
docs/BUILDING.md had listed Apple Clang 14+ under "full support" while the
tree did not compile there at all. Reported in #94, which was correct.Fifteen VMX miscompiles, found by running the tests somewhere new
COMPILE FAILED and executed nothing — and a
missing compiler exited 0, so a skip read as a pass.vupkhsb/vupklsb/vupkhsh/vupklsh (12 cases) read
and wrote the big-endian vector file through host int16_t*/int32_t*,
byte-reversing every result element. vcmpgtsw/vcmpgtsh compared
byte-reversed values, so a mixed-sign vector produced an all-zero mask.
vadduwm propagated carries the wrong way and spilled into neighbouring
elements. The byte-width members of each family passed by luck.vadduhm, vsubuhm, vsubuwm had the same latent bug with no test
covering them; fixed together. 1311 checks, 0 failures.mtfsb0/mtfsb1/mtfsfi reached the unhandled catch-all. mfspr VRSAVE
was a bare no-op that left the destination register stale.Runtime fixes found while driving two titles
0x03000000 —
inside main memory, where a title's own allocator hands out blocks. flOw's
heap swallowed it: the game memset over the control register, the FIFO drain
read ASCII as put and discarded whole frames. Moved to
VM_HLE_INJECT_BASE, with every user deriving from it.lwz r30,d1(r2); lwz r11,d2(r30)) were rejected
outright, including the common case where the base is loaded in the prologue
hundreds of instructions earlier.PS3_GAME level).Earlier versions in this changelog predate tagging. v0.9.0 was the first tagged release; v0.9.1 is the first with downloadable artifacts.
A correctness release about the guest/host boundary, and about instrumentation
that can support its own claims. Two batches: #92 — the guest-pointer sweep,
a SPURS teardown fix, and making the logging stop changing what titles do — and
everything folded via integrate/fold-2026-08-14,
@sagemono's guest-ABI HLE batch and SPU-lifter
faithful adoption plus @canersaka's
guest-callback/lv2/ABI batch.
The guest-pointer sweep — libs/ is clean (#92)
vm_write*,
not a bare store); and layout — a struct holding a pointer is 4 bytes per
field to the guest and 8 to us, so every field after it shifts, and a u64
cannot be moved word-at-a-time. Only the first is a translation.cellFont is the case that shows why: CellFontRenderSurface and the glyph
structs lead with a host pointer, so casting a guest EA to them misplaced
every field and stored floats little-endian — a title reads a glyph advance
of 4.6e-41 and lays every string on top of itself.cellGcmGetOffsetTable handed the title two truncated host pointers through
a struct whose guest form is two 4-byte fields. The host tables were always
correct; the bug was on the way out.tools/audit_guest_ptrs.py finds the bug class, tools/audit_entry_calls.py
finds a library calling its own entry points (which double-translates — it
caught two live ones), and tools/test_audit_guest_ptrs.py pins the audit in
both directions, because the heuristic silently stopped matching three times
during the sweep and a clean report meant nothing until it was checked.libs/guest_struct.h documents when the easy helpers do not apply.Runtime
sys_event: release queue waiters when SPURS finalizes. A title's worker
waits on the SPURS completion queue with an infinite timeout and only re-tests
its quit flag when a completion wakes it — but cellSpursFinalize has already
stopped the job chains, so none can arrive. The shutdown that called finalize
then blocks in sys_ppu_thread_join forever. Cancelling the attached queues
returns CELL_ECANCELED to the waiters, which is the path titles already
handle. Lifted again on re-attach, so teardown-then-setup works.ps3_log_verbose() treated a
non-tty stderr as "be verbose" with no way to say otherwise, so capturing a
log turned the firehose on; and the SPURS job path wrote two lines with an
explicit fflush per job — ~6k writes/s through one FILE lock, enough to
starve a guest thread for seconds. Tokyo Jungle presents four frames in 40 s
with the log discarded and one with it redirected to a file. PS3_VERBOSE=0
now decides, and the per-job pair is verbose-only.g_hle_inflight[tid] and g_sc_inflight[tid]
record the HLE call and lv2 syscall each guest thread is inside. The
watchdog's CTR is the last bctrl target and goes stale the moment a call
returns — it named sys_lwmutex_unlock for a thread that had long since left
it. The write-watch also covers ppu_memory.h's inline vm_write* now, so
"nothing writes this address" is a claim it can actually support.HLE & guest ABI
cellUserInfo, trophy u64s, cellGameGetParamInt DISC type, open PSID, cellRtc, save-data user paths, cellJpgDecDecodeData, cellPngDec, and the cellAudio read index as a BE u64 — @sagemono (#79, #81)cellFsSdataOpen with real SDATA/EDAT (NPD) decryption, cellFsMkdir + the game-data dir created where the VFS looks, and a /dev_hdd0 overlay onto the installed-update tree (PS3_HDD0_ROOT) — @sagemono (#79)CELL_OK — a module name matching no file fails; net reports a real offline state with real SDK NP error codes; sys_time_get_system_time returns real microseconds instead of a call counter — @sagemono (#79)cellFsReaddir writes the real 258-byte dirent ABI + a corrected CellOS generic error-code table (#85), and cellGame firmware parameter IDs, error values and buffer bounds matched to the SDK (#84) — @canersakacellAudio clears each consumed ring-buffer block — an underrun replayed the previous block instead of silence — @canersaka (#83)lv2
sys_semaphore_info::value and in the Win32 handle), so timeout/wake races could lose tokens or park a waiter while value was already positive. value under value_lock is now the only count, the handle is a wake channel, and overflow returns lv2 CELL_EBUSY. The sync_stress target is wired into the build so this stays executable outside a generated title — @canersaka (#88)Lift & decode
spu_channels.c, plus LBP SPU/SPURS bring-up and RSX texture support (G8B8/DXT, deswizzle). Credit for the underlying decoder/lifter design belongs to @sp00nznet and @canersaka — @sagemono (#82)sync/dsync/syncc emit real host memory fences, with the sync C bit decoded instead of folded into plain sync — @canersaka (#87)toc list was concatenated as an int, and the caller swallowed the exception; plus rA=0 base resolution and generic ld/offset-table discovery (#78, #81)The RSX draw engine lands. @sagemono's renderer executes the title's own NV4097 vertex and fragment programs on D3D12, and You Don't Know Jack now renders — a real double-buffered flip loop, verified by booting it. Plus @canersaka's PPU/SPU correctness batch, and the SPU image-import path SPURS titles need before any SPU work can run.
Rendering
0xC708C708. A real command-buffer-full callback now drains and resets the ring — @sagemonoLift & decode
blrl dispatched as an indirect call — it was lumped with blr and emitted a bare return;, dropping the call and skipping the frame epilogue, so r1 leaked the frame size on every function-descriptor call — @sagemono (#42)ET_EXEC) import parsing — the lib-stub table was only reachable via the PRX module_info path, so every retail main executable reported 0 imports; now located via PT_PROC_PRX_PARAM. Minecraft: 0 → 345 NIDs across 30 modules — @sagemono (#41)lift_function indexed by address — an O(refs·N) mid-function pass hung the lifter indefinitely after "100% lifted" on large titles; now bisect over a cached sorted index — @sagemono (#41)v >> (64 - n) at n=0 is UB, and clang miscompiled the clrldi truncation idiom every 32-bit address cast lowers to — @sagemono (#41)rA=0 as a literal zero base, not GPR[0] (#72); update forms write back rA for indexed stores (#71) and FP loads/stores (#73) — dropping the write-back leaves rA stale and corrupts every subsequent (rA)-relative access — @canersakalmw/stmw (#68), mulldo overflow form (#70), stvlx/stvrx/stvlxl/stvrxl Cell unaligned vector stores (#61), value-verified CAS for lwarx/stwcx and ldarx/stdcx (#62), and an FP correctness batch — fcmpu NaN ordering, fcti saturation, single rounding, vctsxs/vctuxs (#60) — @canersakafpscr bit ops + vrsave decoded as visible no-ops — mtfsb0/mtfsb1/mtfsfi were undecoded and emitted as a raw .word, an invisible dropped instruction — @canersaka (#69)brsl target parse broken by the disasm link-register fix, so every call lifted as a silent no-op (#58); bisl/bisled target register; single-round fma/fms/fnms, fesd/frds from the left word slot, fi/frest/frsqest interpolation (#51) — @canersakaRuntime & lv2
sys_spu_image_import implemented + SPU-image syscall numbers fixed — import was mis-numbered 157 (that's _sys_spu_image_import) and collided with SYS_SPU_IMAGE_CLOSE, so a title's import call dispatched to the close stub and the image struct was left zeroed (entry=0 → matched no fallback → the SPU thread "instantly completed"). Replaced the zero-init stub with a real SPU-ELF32 PT_LOAD → sys_spu_segment[] parser — @sagemono (#57)_sys_spu_image_import (sysPrxForUser NID 0xEBE5F72F) — the user-space wrapper libsre invokes during cellSpursInitialize; previously unresolved, so the SPURS SPU kernel image was never parsed and the SPU threads came up at entry 0sys/syscall.h (475.001) — the authoritative table. Three were live collisions: the lwmutex block sat on SYS_RAW_SPU_CREATE_INTERRUPT_TAG/SET_INT_MASK/GET_INT_MASK (150–152) and lwcond on SYS_SPU_IMAGE_OPEN (156). Now 125/125 match the SDK, 0 duplicates — @canersaka (#65)sys_mutex_trylock recursive fast path pairs the host critical section (#67); sys_spu_thread_group_create reads r5 as priority and the name from the attr struct (#66) — @canersakaThe second half of the two-port review pass: @canersaka's remaining decode/ABI/timing fixes plus a lifter/HLE audit toolkit, and the title-agnostic robustness fixes from @JonathanDC64's port that could be ported and build-verified cleanly.
Lift & decode
spu_disasm: channel field widened 5→7 bits (channels ≥32 no longer wrap), hbra/hbrr field layout, bisl/brsl/brasl link register shown, ri7 sign-extension, stop-code preserved — @canersaka (#49)stop signal code preserved to ctx->stop_code — SPURS leaf tasks invoke kernel syscalls via stop <code> (EXIT/YIELD/WAIT/…), which was being discarded — @JonathanDC64Runtime & lv2
sys_event_flag, sys_semaphore, and sys_mutex timed waits (the same 15.6 ms-granularity fix as v0.6.5's event-queue path) — @canersaka (#50)g_active_ctx restored after guest callbacks — it was left dangling at a stack-local scratch ctx, corrupting the crash handler / diagnostics; applied to both ppu_guest_call and ppu_guest_call_ct — @JonathanDC64HLE
cellGameDataCheckCreate2 (NID 0xC9645C41) implemented — marshals StatGet/StatSet and fires the guest funcStat callback — @JonathanDC64Tooling
Two independent retail-game ports kept fuzzing the toolkit: @canersaka's Yakuza: Dead Souls and @JonathanDC64's Demon's Souls. This release distills the title-agnostic correctness/robustness wins each surfaced — the port-specific scaffolding stays in the forks.
PPU / SPU lift
fsqrt/fsqrts source-register decode + vspltis printed as a signed immediate (ppu_disasm) — @canersaka (#46)addeo/subfeo/mulhwo/mulhwuo overflow forms (previously fell through to a no-op stub) + vupklsb/vupkhsb/vupklsh unpacks — @canersaka (#52)musttail — a guest loop whose back-edge crosses a lifted-function boundary was nesting one host C frame per iteration and silently overflowing the stack (a stack-overflow SE runs no unhandled filter, so the process died with no log and exit code 0); now an O(1)-stack jump under clang — @JonathanDC64Runtime & lv2
sys_timer usleep — was a single SwitchToThread(), so usleep(<1000) was a no-op; now paces to a QPC deadline — @canersaka (#44)sys_event_queue_receive returns the event in r4–r7 (lv2 ABI), not just the guest memory buffer — callers reading the registers saw stale values — @JonathanDC64sys_memory_get_page_attribute renumbered 358→351 (0x15F) and implemented — @JonathanDC64CellFsStat runtime-side layout corrected to the 52-byte / 4-byte-aligned ABI — fixes the ppu_fs.cpp / sys_fs.c stat writers the v0.6.5 libs-side packing fix didn't cover — @JonathanDC64HLE libs
cellNetCtl integer out-params written big-endian — @canersaka (#45)cellAudio delivers real period events to the registered notify queues (games blocked on the audio event now wake) — @canersaka (#54)Driving You Don't Know Jack (Scaleform UI + FMOD, 5,859 functions) from an instant crash to its running main loop surfaced one global performance bug worth more than any single lift fix — plus a batch of community lifter/HLE correctness PRs.
Runtime — the event-poll timer-resolution fix (sys_event.c, tests/boot_main.cpp):
sys_event_queue_receive floored the timeout to a 1 ms SleepConditionVariableCS, but Windows' default ~15.6 ms timer granularity inflates any sub-15 ms wait to a full tick — a two-queue-per-frame poll loop burned ~30 ms/frame doing nothing. Sub-millisecond timeouts now do a true non-blocking check (immediate ETIMEDOUT when empty), and the harness calls timeBeginPeriod(1). YDKJ went from ~0.5 fps effective to ~60 Hz, reaching online-init ~6× faster.present() latency.Community PRs incorporated — thank you:
CellFsStat PS3 packing — @canersaka (#22)PARAM.SFO — @canersaka (#24)sys_rwlock → EDEADLK/EPERM on writer self-relock / bad unlock — @canersaka (#25)ppu_disasm: opcodes 33/225 are crnor/crnand (were swapped) — @canersaka (#40)tools/lift_prx.py + docs/FIRMWARE_LLE.md) — relocate a decrypted PRX and lift the real firmware module (e.g. the libsre SPURS kernel) instead of HLE-ing it; a bring-your-own-firmware method that ships no firmware — @canersaka (#53)You Don't Know Jack port — now public at sp00nznet/youdontknowjack: boots the full init stack to its main game loop with a live D3D12 clear+flip at 60 Hz. Current frontier: the GCM command buffer's ring-wrap flush callback is null and a computed 0xC708C708 poison reaches the scene-graph dispatch — so only black clears render so far.
A pass of hard-won correctness fixes surfaced by driving flOw (PhyreEngine, ~104k functions) deep into its boot — plus the SPU-side plumbing to get a SPURS taskset actually dispatching. Most of these are silent-corruption bugs: the lift produced valid C for the wrong computation, so nothing crashed until a data structure quietly filled with garbage thousands of frames later.
Community PRs incorporated — thank you @canersaka, who found and fixed all of the below and sent them in as pull requests:
PPU lift
sraw/srad/srawi/sradi) + mtcrf field mask — (#21)subfe/subfme/addme — (#26)cntlzw(0) is 32, not undefined __builtin_clz garbage — (#35)vcmpgtsb/sh/sw/ub/uh/uw handlers (dot forms set CR6) — (#39)SPU lift
bi $r0 reloaded via lqa/lqr is a computed tail jump, not a return — (#36)shufb, cbd/chd/cwd/cdd) — (#32)il negative-immediate double sign-extension — (#33)rchcnt — (#34)Runtime
mftb/mftbu read a real timebase — (#38)PPU lift — the carry/borrow bug class (ppu_lifter.py):
add never wrote XER[CA] and truncated the carry-out to 32 bits. Any adde/addze/subfe consumer downstream (bignum math, 64-bit pointer arithmetic, the CRT's own __eabi helpers) then read a stale carry. Now sets CA from the true unsigned 64-bit carry-out.subfic computed a 32-bit result and dropped the borrow — rewritten as EXTS(SI) - RA over the full 64 bits with XER[CA] = NOT borrow (EXTS(SI) >= RA).subfme was missing its XER[CA] update entirely; adde/addme carry-out recomputed to match the PowerISA / RPCS3 add64_flags ADC semantics.sraw/srad/srawi/sradi now set XER[CA] (was never written) via new ppc_sraw/ppc_srad helpers — CA = rS<0 AND any 1-bits shifted out, which feeds the same adde/subfe consumers.bctr dispatch leaked the frame: these were lifted as ps3_indirect_call(ctx); return; without the function epilogue, leaking r1 and the callee-saved GPRs on every jump-table switch and tail call. Now emits the full epilogue before the tail jump.ppu_disasm.py --va: disassemble a window at a virtual address, mapping va→file offset through the ELF's PT_LOAD segments — fixing the long-standing footgun where --raw --offset <vaddr> read the wrong bytes (off by the .text segment base).SPU lift — computed jumps & wide ops (spu_lifter.py, spu_disasm.py):
bi $r0 computed tail-jumps vs. returns: a bi $r0 whose r0 was just reloaded via lqa/lqr is a computed tail jump, not a return. Treating them all as returns made the SPURS taskset launcher fall through and the dispatcher loop forever — the exact SPU analogue of the PPU tail-call bug above. compute_bi_r0_jumps() now classifies them by backward-scanning for the nearest r0 writer.RC source, not the dest) — every selb/shufb/fma-family op wrote the wrong register.fesd/frds single↔double convert, and the dfma/dfms/dfnms/dfnma double FMA ops (3-register, RT is the accumulator).cgx extended carry-generate (RT is also a source), mpyhha/mpyhhau high-half multiply-accumulate, and a br . self-loop trap (target == self is a deliberate infinite-hang trap on real SPU, not forward progress).Runtime robustness:
ppu_loader.cpp, PPU_HASH_BITS 16→18): the open-addressed registration table live-locked filling past 65,536 functions — flOw registers ~104k. Titles above ~50k functions now register in bounded time./app_home/ VFS mapping (sys_fs.c): the install-dir prefix (opened as /app_home/..., app_home/..., e:/app_home/...) now maps to the USRDIR root instead of appending a literal app_home directory that isn't on disk.ppu_guard_page(), Windows): arm a guest EA and a VEH logs the faulting host RIP of any raw store into that 4 KB page — catches vector/memcpy stores that vm_write* instrumentation can't see. Env-gated; a diagnostic aid, off by default.cellGcmGetTiledPitchSize ABI fix + real flip/vblank handler dispatch (see prior commits) — the wrong return convention was corrupting the caller's stack.SPU SPURS plumbing: new libs/spurs/spurs_taskset.{c,h} + spurs_pm.c (taskset descriptor + power-management scaffolding) and runtime/spu/ workload-dispatch wiring toward running a real SPURS task kernel. Still in progress — the taskset dispatches but completion-event delivery back to the PPU isn't verified yet.
Tooling: tools/test_ppu_lift.py (PPU lifter unit harness) and tools/rpcs3_probe/ oracle-introspection scripts used to diff our lift against the running RPCS3 reference.
nid_database now auto-scans libs/ + runtime/ for every function the toolkit implements (load_implemented()), and ships 44 more curated names recovered by exact-NID brute-force against the harness corpus. Resolution went from 175 → 421 of the 660 distinct NIDs the sample titles import; the harness stub-prioritization ranking is now fully named through the high-frequency tiers.imports stage (via ppu_loader's proc_prx_param → libstub walk) extracts each EBOOT's firmware imports and resolves them, and the report ranks the most-imported libraries and functions across titles — the data that says which stubs to write next.cellNetCtl net-start dialog implemented (LoadAsync/UnloadAsync, imported by every sampled title): posts the LOADED/FINISHED sysutil callback events so games clear the boot-time "connect to PSN" gate, and reports a connected result.cellGcmSetupContext() — reusable core of _cellGcmInitBody (the function cellGcmInit's SDK macro actually calls). Captures the proven CellGcmContextData layout (begin/end/current/callback) from the shipping Simpsons port so game bridges stop hand-rolling it..opd function detection hardened: the TOC base may live in BSS / a segment gap, so detection no longer requires it inside a file-backed range — recovered ~20–23k address-taken functions each on flOw and Marvel Ultimate Alliance.runtime/ppu/ (loader, HLE dispatch, sysprx, filesystem) + runtime/host/host_main.c — the per-game path that loads a lifted PPU image, links it with the HLE runtime, and boots it. Built per-game against the lifter-generated ppu_recomp.h, so it's excluded from the game-agnostic runtime library. See docs/PPU_RECOMP.md.rsx_fp_decompiler / rsx_vp_decompiler translate NV40 fragment/vertex programs to host shaders, with a validation-test corpus. See docs/RSX_FRAGMENT_PROGRAM.md.tools/ppu_loader.py (image manifest / OPD table / TOC / imports) and tools/gen_hle_nids.py.nid_database changes were not taken — they predate and would regress the v0.6.0 SPU subsystem and the v0.6.1 NID fix.The community showed up. Most of this release came in over the wall as pull requests — almost all of it discovered by @canersaka while stress-testing the toolkit against a 22 MB / ~45,000-function Yakuza: Dead Souls port, which turns out to be a fantastic fuzzer for everything we got subtly wrong. Huge thanks to every contributor below.
Correctness — decode & lift (thanks @canersaka):
nid_database.py): the suffix constant was truncated to 12 bytes and corrupted past byte 8, and the digest was read big-endian. The authoritative 16-byte suffix + little-endian read now matches real EBOOT import tables — cellSysmoduleLoadModule → 0x32267A31. Previously matched 0 of 354 import NIDs on a real game; now 343/354 resolve. (Our own include/ps3emu/nid.h already documented the correct values; the Python tool just disagreed with it.)ppu_disasm.py): dozens of mnemonics mapped to the wrong codes (vmaxfp/vminfp, vcfsx/vcfux swapped with vrefp/vrsqrtefp, the whole vpk* pack family permuted, signed/unsigned vcmpgt* swapped, vsumsws, …). These decoded silently and lifted to valid C for the wrong operation — vcfsx alone was wrong 9,728× in one title.addc/subfc with carry-out into XER[CA] (86% of all unlifted instructions in Yakuza — 63k of 73k), the unaligned vector loads lvlx/lvrx/lvlxl, ldbrx, vsrw, vsububs, vsum2sws, vupkhsh, vrfim, and vand (which had an implementation but was missing from the dispatch list).sradi decode for shifts ≥ 32: the 6-bit shift carries its top bit in instruction bit 30, so the XO field reads 827 (which was unmapped) for shifts of 32+. Now composed correctly. (Follow-up: removed a dead, incorrect XO 827 → lhaux block — real lhaux is XO 375.).opd table (find_functions.py): PS3 executables list a descriptor for every address-taken function (every C++ virtual, every callback) — 21,893 of them in Yakuza, of which exactly 1 was being detected by prologue scanning. Now seeds starts from .opd (located by shape, since section names are often stripped) + the ELF entry, bounds them at the first blr, and filters phantom branch targets from data decoded as code.--jobs N) — a multi-hour single-core lift on a 45k-function game drops to minutes. Plus record-form CR0 handling, runtime-matched context, and split-file C output so the generated source is actually buildable.Runtime & libs (thanks @canersaka):
sys_vm syscall family (300–313) implemented — sys_vm_memory_map returned ENOSYS with an unwritten out-pointer, crashing callers on first deref. Mappings now bump-allocated from a committed 0x60000000 window.sys_memory allocations now come from a committed-on-demand 0x40000000–0x50000000 window (the old bump base at 0x20000000 failed every allocate with silent ENOMEM); addresses match real-hardware traces byte-for-byte, and the bump pointer is now thread-safe.sys_lwmutex_destroy lv2 semantics: returns ESRCH for a dead mutex / EBUSY for a held one (libc teardown branches on these) instead of unconditional OK; slot allocation is now locked.lv2_syscall_table.h had 400/402).cellVideoOut + corrected CellGcmContextData layout (callback at +0xC): GetResolution was writing host-endian, so the guest read 720×480 as 53250×57345 and sized display buffers from garbage.Build & tooling:
st_atime/st_mtime macros were shadowing CellFsStat members; five translation units now compile clean so downstream projects get a Linux runtime to link against.runtime/spu/tests/ defines its own main(); the library now builds clean with MSVC.tools/show_func.py (thanks @canersaka): dump a single lifted function's C, its original PowerPC disassembly, or both, straight from the chunked output.spu_disasm.py's opcode tables from rpcs3's authoritative SPUOpcodes.h. Pervasive mis-decodes (lqr/stqr/fsmbi were excluded from RI16 and shadowed by spurious RI10 rotate entries; RI16 branch/load forms at wrong opcodes; ~15 wrong SPU_RR entries; missing RI8 float conversions) had silently corrupted every SPU lift.spu_lifter.py now emits a tail-call when a function ends in a non-branch — previously such functions fell off the end and truncated execution mid-image. Added cflts/cfltu/csflt/cuflt float conversions.image_id to the SPU context + an image-tagged function registry (spu_channels.c, spu_begin_image()), so a SPURS kernel, a policy module, and a job can coexist at overlapping local-store addresses.extract_spu_images.py (pull SPU images from PRX/ELF), find_spu_functions.py (SPU function bounds), wrap_spu_elf.py (wrap raw SPU blobs).runtime/spu/tests/, plus test_spu_helpers.c. New docs/SPU_LIFTER.md.gcm_flush_guest_cmdbuf() now initializes RSX state via rsx_state_init(), passes NV40 commands to rsx_process_command_buffer(), and dispatches to the backend. Games can write GCM commands and see results on screen.ctrl->put for changes, scans command buffer for SET_REFERENCE (method 0x0050) and WRITE_BACK_END_LABEL (0x1D6C), updates ctrl->get/ctrl->ref automatically. Breaks cellGcmFinish spin loops without needing HLE bridge interception.stwbrx/lwbrx in recompiled code (host-endian, no byte-swap). The HLE now reads/writes these in host-endian via direct uint32_t* access instead of vm_write32 (which byte-swaps for big-endian).cellGcmInit now maps the command buffer region (1MB-aligned) via cellGcmMapMainMemory so cellGcmAddressToOffset succeeds for buffer addresses. Previously only the game's explicit mapping was registered.rsx_process_command_buffer, updates control register put/get, then resets ctx->current. Previously only reset current without processing.__declspec(thread) fix: g_trampoline_fn declaration mismatch caused MSVC to compile 37K+ writes as direct stores to the read-only TLS template in .rdata, crashing with AV WRITE. Fixed by declaring __declspec(thread) consistently across all translation units.bl (direct call) and bctrl (indirect call). Previously only emitted in the preamble; callers that set g_trampoline_fn and returned wouldn't have their chains drained.bdnz/bdz decremented CTR as uint64, causing loops with 32-bit counts sign-extended to ~0xFFFFFFFF_8XXXXXXX to wrap past zero into 18-quintillion-iteration infinite loops. Fixed: ctx->ctr = (uint32_t)(ctx->ctr - 1).__builtin_clz replaced with _BitScanReverse, __int128 multiply replaced with _mul128/_umul128 intrinsics, all function/memory declarations wrapped in extern "C" for correct C++ linkage.ppu_vr vr[32], vscr, fpscr, cia, reserve_addr/reserve_value, thread_id fields. Enables full VMX vector operations, atomic reservations, and thread identification.rldcl/rldcr decoding.vm_read/vm_write as inline functions in ppu_recomp.cpp preamble, bypassing function call overhead. Eliminates ~100x slowdown on byte-by-byte memory operations (critical for CRT memset of multi-GB address spaces).cellSysmoduleLoadModule returns CELL_OK for already-loaded modules instead of CELL_SYSMODULE_ERROR_DUPLICATED. Games like flOw treat the error as fatal..ctors section, resolved through OPD to real function addresses (0x12F14, 0x1CD2C, etc.), and execute them directly. Constructors now allocate PhyreEngine objects (3 objects with malloc + lwmutex_create).func_000CB9CC with clean stack. Loads modules (GCM_SYS, SPURS, USBD, JPGDEC, NET), registers sysutil callback. First PS3 recomp project to reach game-level code execution.convert_trampolines.py and post_lift.py transform fallthrough edges between split functions into explicit trampoline calls. When the lifter splits a large function at branch targets, the resulting pieces are chained via trampolines so control flow is preserved without relying on physical code adjacency.bl call site in ppu_recomp.cpp (143K+ sites) to drain pending trampoline returns. Prevents unbounded host stack growth when recompiled split-function chains would otherwise nest deeply through call/return mismatches. Converts deep recursion into a flat loop.indirect_dispatch.cpp supports registering stubs for mid-function entry points that the lifter does not emit as standalone functions. Allows bctrl indirect calls to reach branch targets inside split functions via hash table lookup.dcbz (data cache block zero) now zeroes 128 bytes instead of being a no-op. This was THE root cause of the flOw PhyreEngine init stall — dcbz loops ran trillions of no-op iterations.rsx_commands.h/.c) — FIFO command buffer parsing, state tracking for surfaces, viewport, scissor, clear, blend, depth/stencil, culling, color mask, alpha test, textures (16 units × 8 registers), vertex attributes (16 × format/offset), shader programs (fragment/vertex), draw arrays + draw indexed. Backend callback interface (rsx_backend) with 12 dispatch points for pluggable rendering.rsx_null_backend.h/.c) — creates a window, displays RSX clear color via GDI, FPS counter and debug overlay. Proves command pipeline works before D3D12/Vulkan.rsx_d3d12_backend.h/.c) — D3D12 device (FL 11.0), DXGI swap chain (double-buffered flip-discard), RTV descriptor heap, fence-based frame sync, clear to RSX color, VSync present. Ready for vertex buffer upload and shader translation.cellfs_translate_path().libs/hardware/ directoryl10n_convert() APICreateFiber/POSIX ucontext), 64 concurrent fibers, sleep/wakeup"The Cell Processor was ahead of its time. Now it's time to bring it to ours."
Static recompilation runtime libraries for PlayStation 3 titles.
C
382
1,545 commits
updated Oct 7, 2026
Static recompilation runtime libraries for PlayStation 3 titles. Turn PS3 binaries into native executables. No emulator required.
Join the sp00nznet recomp Discord — the community hub for sp00nznet's recomp projects, where ps3recomp development happens in the open. Good place to ask questions, show a port you are working on, or find out what people are stuck on before you duplicate the effort.
ps3recomp is an open-source toolkit that provides the runtime libraries, system stubs, and analysis tools needed to statically recompile PlayStation 3 games into native PC executables.
Instead of interpreting or dynamically recompiling PowerPC/SPU instructions at runtime (what emulators like RPCS3 do brilliantly), we take the opposite approach: translate everything ahead of time into C/C++ that compiles with your favorite compiler on any modern platform.
This is the same philosophy behind:
...but for the PS3's glorious, terrifying Cell Broadband Engine.
The PlayStation 3 has over 3,000 titles and some of the most beloved games ever made. RPCS3 has done heroic work making ~70% of the library playable through emulation, but static recompilation offers something different:
Every row is exercised by CI on every push, and all three are green on
master — the PPU boot scaffold ratchet that had been red since before v0.11.0
(#139) is at zero on all
four toolchains. The rows below describe what those jobs actually do, not what a
green tick would imply: building the runtime and passing the suites is not the
same as running a game, and only one row claims that.
| Windows | macOS (arm64) | Linux | |
|---|---|---|---|
| Runtime library builds | yes | yes | yes |
| Lifter + 8 test suites | yes | yes | yes |
| Render backend | D3D12 | Metal | null (headless software); Vulkan (opt-in) |
| Runs a recompiled game | yes | no | no |
The gap on macOS is the PPU boot scaffold — ppu_loader.cpp, boot_main.cpp
and the HLE dispatch are still Win32-only (thread creation, VirtualAlloc, SEH,
stack walking), and they are compiled per-game rather than into the library.
So macOS builds the runtime, renders through Metal and passes the full test
suite, but cannot yet load and run a title. ps3recomp_host drives
cellGcm → RSX → Metal end to end without a game, which is what makes further
graphics work possible there.
Linux builds and passes the same suites under both GCC and Clang, and runs
ps3recomp_host end to end, on every push. Its backend is the null backend's
headless software path: a host-memory framebuffer with a CPU triangle filler,
enough to prove the guest's command stream produced the pixels it asked for on
a runner with no display and no GPU. It is not a renderer — no depth, no
blending, no textures — so Linux is still one real backend, plus the same PPU
boot scaffold macOS needs, away from running a title.
Both non-Windows platforms are blocked on the same two things, in this order.
1. The PPU boot scaffold, on POSIX. This is the shared blocker and the only
one that gates "runs a recompiled game" on both. ppu_loader.cpp,
boot_main.cpp and the HLE dispatch are compiled per game rather than into
the library, and were Win32-only — thread creation, VirtualAlloc, SEH, stack
walking. v0.10.0 took it to zero errors on POSIX and put it under CI so it
cannot rot again, and ported the guest-pointer trap, thread-locals and host
backtraces. What is left is running it: load a lifted image on POSIX and reach
the guest's first instruction, then main. Until that works, neither platform
can run a title no matter how good its renderer is.
2. One real backend each. The RSX abstraction landed in v0.10.0 (#101, #102,
#103) — texture conversion, the TEXTURE_CONTROL1 crossbar, texture layout and
a neutral draw record all live outside the D3D12 file now, so a backend no
longer has to reimplement them to draw anything.
ps3recomp_host already drives
cellGcm → RSX → Metal with no lifted game, so each step is testable before a
title exists to run.-DPS3RECOMP_RSX_VULKAN=ON). With
guest programs on (glslang at build time, PS3RECOMP_VK_GUEST_PROGRAMS=1) it
runs on the shared register-file draw engine, the path Metal takes, and every
ps3recomp_host scene passes on it -- including render-to-texture, MRT and
depth textures; without them it draws the null backend's fixed-function
contract. It has not run a recompiled title yet. The default is still the
headless backend, a CPU triangle filler for CI, deliberately.Deliberately not claimed: the table above says "no" for running a game on
macOS and Linux, and it will keep saying "no" until a title actually boots to
gameplay on one of them. Building the runtime, passing the suites and rendering
through ps3recomp_host are all real, and none of them is that.
The PS3's Cell processor is a beast unlike anything else:
| Component | What It Is | Why It's Hard |
|---|---|---|
| PPU | PowerPC 64-bit main CPU | Custom ISA extensions, VMX/AltiVec SIMD |
| 6x SPU | 128-bit SIMD vector processors | Proprietary ISA, 256KB local store, explicit DMA |
| Memory | 256MB XDR RAM | 128-byte alignment requirements, split bus |
| LV2 Kernel | Proprietary OS layer | ~300+ system calls, complex threading model |
| PRX Modules | Dynamic libraries | 100+ system libraries with intricate interdependencies |
We don't shy away from hard problems. We just break them into smaller ones.
ps3recomp/
├── tools/ # Binary analysis & recompilation pipeline
│ ├── elf_parser.py # Parse PS3 ELF/SELF/PRX binaries
│ ├── ppu_disasm.py # PowerPC disassembler with PS3 extensions
│ ├── ppu_lifter.py # PPU assembly -> C code lifter
│ ├── spu_disasm.py # SPU instruction set disassembler
│ ├── spu_lifter.py # SPU assembly -> C code lifter
│ ├── extract_spu_images.py # Extract embedded SPU images from PRX/ELF
│ ├── find_spu_functions.py # SPU function boundary detection
│ ├── wrap_spu_elf.py # Wrap a raw SPU blob as a loadable ELF
│ ├── prx_analyzer.py # Analyze PRX imports/exports and NIDs
│ ├── nid_database.py # Function Name ID (NID) resolver
│ ├── find_functions.py # Function boundary detection
│ └── generate_stubs.py # Auto-generate HLE stubs from RPCS3 modules
│
├── runtime/ # Core runtime for recompiled executables
│ ├── ppu/ # PPU execution context
│ │ ├── ppu_context.h # Register file, CR, LR, CTR, FPSCR
│ │ ├── ppu_memory.h # Memory access macros (loads, stores, byte-swap)
│ │ └── ppu_ops.h # Instruction operation macros
│ ├── spu/ # SPU execution context
│ │ ├── spu_context.h # 128×128-bit register file, 256KB local store, image_id
│ │ ├── spu_channels.c # Channel ops + per-image function-table dispatch
│ │ ├── spu_helpers.h # SPU intrinsics (incl. float conversions)
│ │ ├── spu_dma.h # DMA (MFC) transfer engine
│ │ └── tests/ # Self-contained SPU runtime tests
│ ├── memory/ # Memory subsystem
│ │ ├── vm.h # Virtual memory manager (256MB address space)
│ │ └── atomic.h # PS3 atomic operations (lwarx/stwcx)
│ └── syscalls/ # LV2 kernel system call implementations
│ ├── sys_ppu_thread.h # Thread creation/management
│ ├── sys_mutex.h # Mutex/cond synchronization
│ ├── sys_memory.h # Memory allocation/mapping
│ ├── sys_fs.h # Filesystem operations
│ ├── sys_prx.h # PRX module loading
│ └── lv2_syscall_table.h # Full syscall dispatch table
│
├── libs/ # HLE library implementations (from RPCS3's module system)
│ ├── audio/ # cellAudio, cellAdec, libmixer, libsnd3, libsynth2
│ ├── video/ # cellGcmSys, cellResc, cellVideoOut, cellVpost, cellVdec
│ ├── input/ # cellPad, cellKb, cellMouse, cellGem
│ ├── network/ # cellNetCtl, cellHttp, cellHttps, cellSsl, sceNp*
│ ├── filesystem/ # cellFs, cellGame, cellSaveData, cellStorage
│ ├── system/ # cellSysutil, cellSysmodule, cellRtc, sysPrxForUser
│ ├── spurs/ # cellSpurs, cellFiber, cellSpursJq, cellDaisy (SPU task management)
│ ├── sync/ # cellSync, cellSync2 (cross-PPU/SPU synchronization)
│ ├── codec/ # cellJpgDec, cellPngDec, cellGifDec, cellPamf, cellDmux, cellAdecAtrac3p, cellAdecCelp8, cellVdecDivx
│ ├── font/ # cellFont, cellFontFT, cellFreeType, cellL10n
│ └── misc/ # cellScreenshot, cellSubdisplay, cellImeJp, cellVideoExport, cellMusicExport, cellGameRecording, etc.
│
├── include/ps3emu/ # Public API headers
│ ├── ps3types.h # Fundamental PS3 types (u8-u128, s8-s64, be_t<>)
│ ├── endian.h # Big-endian <-> little-endian conversion
│ ├── nid.h # NID hashing and lookup
│ ├── module.h # Module registration framework
│ └── error_codes.h # PS3 system error codes (CELL_OK, CELL_E*)
│
├── templates/project/ # Starter template for new game ports
│ ├── CMakeLists.txt # Build system
│ ├── main.cpp # Application entry point
│ ├── stubs.cpp # Game-specific function overrides
│ └── config.toml # Recompiler configuration
│
├── config/ # Reference configurations
│ └── example.toml # Example recompilation config
│
├── patches/ # Patches for upstream tools
│ └── xenonrecomp-ppu.patch # XenonRecomp adaptations for PPU (Cell != Xenon)
│
└── docs/ # Documentation (incl. ARCHITECTURE, RUNTIME, SPU_LIFTER, RSX_GRAPHICS, ...)
├── ARCHITECTURE.md # Cell processor and recomp pipeline deep-dive
├── GETTING_STARTED.md # How to recompile your first PS3 game
├── MODULE_STATUS.md # Implementation status of all HLE modules
├── MODULES_REFERENCE.md # Detailed per-module documentation
├── RUNTIME.md # Runtime reference: VM, PPU/SPU contexts, types
├── SYSCALLS.md # All LV2 kernel syscall implementations
├── NID_SYSTEM.md # PS3 NID linking system explained
├── TOOLS.md # Recompiler pipeline tools reference
├── BUILDING.md # Build system, compilers, platform notes
├── GAME_PORTING_GUIDE.md # Step-by-step game porting walkthrough
└── PLATFORM_ABSTRACTION.md # Win32/POSIX cross-platform details
The pipeline follows the same proven approach as our burnout3 project, adapted for the Cell architecture:
PS3 Game Disc / PKG
│
▼
┌─────────────┐
│ SELF/EBOOT │ Encrypted PS3 executable
│ Decryption │ (requires keys / fSELF)
└──────┬──────┘
│
▼
┌─────────────┐
│ ELF Parser │ Extract sections, segments, relocations
│ + PRX Scan │ Identify imported NIDs & library deps
└──────┬──────┘
│
▼
┌─────────────┐
│ PPU Disasm │ Disassemble PowerPC 64-bit + VMX
│ + Analysis │ Function boundaries, jump tables, ABI
└──────┬──────┘
│
▼
┌─────────────┐
│ PPU Lifter │ PowerPC → C code generation
│ + SPU Lift │ SPU programs → C code generation
└──────┬──────┘
│
▼
┌─────────────┐
│ Link with │ ps3recomp runtime + HLE libs
│ Runtime │ Provides all PS3 OS services
└──────┬──────┘
│
▼
┌─────────────┐
│ Compile & │ MSVC / GCC / Clang
│ Ship! │ Native x86-64 executable
└─────────────┘
The SPU is the elephant in the room. Each SPU has its own instruction set, its own 256KB memory, and communicates with the PPU via DMA and mailbox channels. Our approach:
extract_spu_images.py pulls them out of PRX/ELF)spu_disasm.py + spu_lifter.py handle the SPU ISAspu_dma.h)image_id lets multiple SPU programs (e.g. a SPURS kernel, a policy module, and a job) coexist at overlapping local-store addressesThis mirrors how RPCS3 handles SPU but at compile time rather than runtime. The
disassembler, lifter, and runtime are functional and covered by self-contained
tests (runtime/spu/tests/: sum, shufb, DMA, brsl-return).
We're building HLE implementations based on RPCS3's module system. 97 modules complete, 1 partial (media decode) — 219 files and ~76,000 lines under libs/.
| Category | Modules | Status |
|---|---|---|
| Kernel Threading | sys_ppu_thread, sys_mutex, sys_cond, sys_semaphore, sys_rwlock | ✅ Complete |
| Kernel Events | sys_event (queues, ports, flags), sys_timer | ✅ Complete |
| Kernel Memory | sys_memory, sys_mmapper (containers, shared mem) | ✅ Complete |
| Kernel Filesystem | sys_fs (path mapping, real I/O) | ✅ Complete |
| Filesystem | cellFs (real file I/O, dir ops, stat, path translation) | ✅ Complete |
| Save System | cellSaveData (callback-driven, PARAM.SFO), cellGame | ✅ Complete |
| Input | cellPad (XInput/SDL2), cellKb, cellMouse | ✅ Complete |
| Audio | cellAudio (WASAPI/SDL2, mixing thread, multi-port) | ✅ Complete |
| Video Output | cellVideoOut (resolution config, 720p default) | ✅ Complete |
| Codecs | cellPngDec, cellJpgDec, cellGifDec (stb_image) | ✅ Complete |
| Font | cellFont (stb_truetype backend + fallback metrics) | ✅ Complete |
| Network | sys_net (BSD sockets, P2P), cellNet, cellNetCtl, cellHttpUtil, cellSsl, sceNp*, sceNpTrophy, sceNpMatching2 + Score over psnr | ✅ Complete |
| Hardware | cellUsbd (USB), cellCamera (PS Eye), cellGem (PS Move) — stub, no devices | ✅ Complete |
| Sync | cellSync (atomic spinlocks, LF queue), cellSync2 (OS-backed) | ✅ Complete |
| System | cellRtc, cellMsgDialog, cellOskDialog, cellUserInfo, cellGameExec | ✅ Complete |
| Localization | cellL10n (UTF-8/16/32/UCS-2, ISO-8859-1, generic converter) | ✅ Complete |
| Resolution | cellResc (display modes, scaling, interlace, aspect ratio) | ✅ Complete |
| Fibers | cellFiber (PPU fibers via Windows Fibers/ucontext) | ✅ Complete |
| AV Config | cellAvconfExt (audio output info, gamma, sound availability) | ✅ Complete |
| Input Util | cellKey2char (HID scancode → Unicode, US-101 layout) | ✅ Complete |
| Graphics | cellGcmSys (cmd buffer, IO mapping, tile/zcull, flip handlers, timestamps, FIFO walker + ring recycle), RSX command processor (NV47xx parsing, state tracking, backend callbacks), live D3D12 backend (executes the title's real NV4097 vertex/fragment programs — indexed draws, per-draw VP constants + textures, render-to-texture, MRT, FP predication), null backend (Win32 window) | ✅ Complete |
| SPURS | cellSpurs (management APIs, event flags, task/workload tracking), cellSpursJq (job queues with wait/signal), cellDaisy (real ring buffer FIFO) | ✅ Complete |
| Core Runtime | cellSysutil (BGM, cache, disc), cellSysmodule, sysPrxForUser (real lwmutex/lwcond/threads) | ✅ Complete |
| Media Pipeline | cellPamf (real PAMF header + stream descriptor parsing), cellDmux (callback sequencing), cellVdec/cellAdec (PICOUT/PCMOUT callbacks, no actual decode — needs FFmpeg), cellSail (state machine) | 🔨 Partial |
| HTTP/HTTPS | cellHttp (real HTTP/1.1 via native sockets), cellHttps (TLS stub with cert management) | ✅ Complete |
| Misc/Export | cellSubdisplay, cellImeJp, cellVideoExport, cellMusicExport, cellPhotoExport/Import, cellGameRecording, cellPrint, cellRemotePlay | ✅ Complete |
Full status tracking: docs/MODULE_STATUS.md
We've written extensive docs covering every aspect of the project. Whether you're contributing, porting a game, or just trying to understand how PS3 static recompilation works, there's a doc for you.
| Document | What It Covers |
|---|---|
| Getting Started | Prerequisites, installation, first recompilation walkthrough |
| Architecture | Cell processor overview, recomp pipeline stages, memory model, comparison with RPCS3 |
| Building | CMake build system, compiler support, platform-specific notes, troubleshooting |
| Game Porting Guide | Full 12-phase walkthrough for porting a PS3 game — with flOw case study |
| Custom Modules | Step-by-step tutorial for writing new HLE modules — NID system, calling convention bridge, patterns |
| FAQ & Troubleshooting | Common questions, build errors, runtime crashes, lifter issues, performance tips |
| Contributing | Development setup, code style, PR guidelines, ways to contribute |
| Runtime Reference | Virtual memory manager, PPU/SPU execution contexts, type system, endianness, syscall dispatch, DMA engine |
| Syscall Reference | All LV2 kernel syscalls: threading, sync, events, timers, memory, filesystem |
| NID System | How PS3 function linking works, NID computation, module registration framework |
| Module Reference | Detailed documentation for every HLE module — what they do and how they're implemented |
| Module Status | Quick-reference status table for all modules |
| RSX Graphics | RSX GPU translation architecture: command processor, D3D12 and Metal backends, shader strategy |
| Tools Reference | Every recompiler pipeline tool documented: ELF parser, disassembler, lifter, NID database |
| SPU Lifter | SPU ISA decoding/lifting, the runtime model (local store, channels, DMA, per-image dispatch), and the SPU tests |
| Platform Abstraction | How we handle Win32 vs POSIX: threading, sockets, timers, audio, memory, fibers |
| SPU Fallback | The PPU-side fallback registry — a hand-written replacement for an SPU job, or a bring-up shim before an image is lifted |
| Debug Console | Ask a title that is already running what it is doing, without a rebuild |
| Runtime Diagnostics | Generated index of every diagnostic environment variable the runtime reads |
| Prototype & Debug Builds | Why unencrypted, symbol-bearing prototype builds are the pipeline's ground truth |
| Online Play | Matchmaking, leaderboards and P2P play through a psnr server — --psnr host --username NAME |
| Regression Gate | How five ports are checked against recorded goldens before a shared-runtime change lands — and how to tell a real red from the four kinds of measurement artefact |
Prerequisites: Python 3.10+, CMake 3.20+, a C17/C++20 compiler (MSVC 19.x, GCC 12+, or Clang 15+)
# Clone the repo
git clone https://github.com/sp00nznet/ps3recomp.git
cd ps3recomp
# Install Python tools
pip install -r requirements.txt
# Stage 1: image, OPD function table, TOC and firmware imports
python tools/ppu_loader.py /path/to/EBOOT.ELF -o out/
# Stage 2: lift to C++. --hle-stubs turns each firmware import stub into
# ps3_hle_call(nid); leave it out and the first import call crashes the port.
python tools/ppu_lifter.py /path/to/EBOOT.ELF \
--functions out/EBOOT.functions.json \
--hle-stubs out/EBOOT.imports.json \
--output src/recomp/
# Stage 3: the NID -> HLE handler table for this runtime
python tools/gen_hle_nids.py --all --out src/gen/ppu_hle_nids.cpp
# Build with the runtime
cd templates/project
cmake -B build -G Ninja
cmake --build build
See docs/GETTING_STARTED.md for the full walkthrough.
Status as of 2026-10-07. Each port's own README carries the live detail and the run recipe.
| Game | Title ID | Status | Repo |
|---|---|---|---|
| The Simpsons Arcade Game (Konami) | NPUB30563 | Playable, and playable online. Boots through the Konami intro and character select into Stage 1, Downtown Springfield, with its ATRAC3plus music (#186) and correct flashes and glyphs (#185). Two players meet over psnr and play a match together: lobby, character select, the host's game setup, then Stage 1 in sync on both machines with each player's input moving their character on both screens — including with one player behind a NAT, through psnr's relay (#200, see docs/ONLINE.md). A Konami arcade emulator EBOOT: the 1991 coin-op ROM lives in SIMPSONS.SR and the binary emulates the hardware around it | sp00nznet/simpsonsarcade-ps3 |
| Tornado Outbreak (Loose Cannon / Konami) | BLUS30371 | In game, rendering correctly with no per-title overrides. Logos and Bink intro, title, main menu, New Game (the save is created), the story intro and dialogue, then the first level under stick control at ~10–16 fps. The runtime fixes it needed — raw-SPU proxy DMA, vertex-program flow control, GPU resolve copies — are shared (#189) | sp00nznet/tornado |
Not under active development; they are where much of the runtime was first proven, and several stay in the regression gate.
| Game | Title ID | Where it got to | Repo |
|---|---|---|---|
| Virtua Fighter 5 (Sega AM2) | BLUS30020 | Renders its NOW LOADING, CRIWARE and AM2 screens; zero SPURS and zero PSN imports | sp00nznet/vf5 |
| Tokyo Jungle (Crispy's/SCE Japan) | NPUA80523 | Data install, SPURS jobs, title screen and the main menus | sp00nznet/tokyojungle |
| You Don't Know Jack (Jellyvision/THQ) | BLUS30569 | Menus and answerable quiz questions; movie-backed screens still draw noise | sp00nznet/youdontknowjack |
| Twisted Metal (Eat Sleep Play) | BCUS98106 | Drove the live NV4097 engine (#113); boots real game code and its asset install | sp00nznet/twistedmetal |
| flOw (thatgamecompany) | NPUA80001 | First draws with the real libsre.prx; assets load | sp00nznet/flow |
| Rubber Ducky (Sony E3 2006 demo) | NPEA00003 | A debug build with full symbols — the lifter's ground truth, not a game target | sp00nznet/rubberducky |
Ports other people are running on ps3recomp, and whose fixes come back upstream: LittleBigPlanet (@sagemono), Yakuza: Dead Souls (@canersaka), God of War II HD and Ben 10 Omniverse (@andrebrumdev), and DBZ Budokai HD (@gnome41). Ask in the Discord to get yours listed.
Want to port a game? Start with the Getting Started section, check docs/MODULE_STATUS.md for system library coverage, and see the Game Porting Guide for a real-world walkthrough.
| Project | Role |
|---|---|
| RPCS3 | Our primary reference for HLE module behavior and system call semantics. Standing on the shoulders of giants. |
| XenonRecomp | PowerPC recompiler for Xbox 360. Both CPUs are PowerPC — we adapt its lifter for PPU-specific extensions. |
| N64Recomp | Pioneered the modern static recomp approach. Our architecture follows the same "recompile to C, link with runtime" philosophy. |
| PS2Recomp | Sibling project for PS2. Different ISA (MIPS vs PowerPC) but same spirit. |
| ReXGlue | The closest sibling this project has: a static recompilation SDK for the Xbox 360, turning PowerPC into portable C++ compiled ahead of time rather than interpreted. Same shape as ps3recomp — a toolkit plus a runtime, with game ports built on top — and a generation of Xenon hardware away from Cell. Their release engineering covers five platforms from one reusable build workflow, with nightlies and tagged releases that publish artifacts. |
| pcrecomp | Our PC recompilation toolkit. Shared philosophy of "analyze → disassemble → lift → link → ship". |
cellPad and cellAudio select their SDL2 backends unconditionally on non-Windows hosts, so it is required there; on Windows they use XInput and WASAPI. It does no windowing here — each graphics backend makes its ownThis is a massive undertaking and we need help. Here's how to get involved:
See CONTRIBUTING.md for guidelines.
Not sure where to start, or want to sanity-check an idea first? Ask in the Discord — several of the people working on ports and on the lifter are there.
This project does not contain any proprietary Sony code, encryption keys, or copyrighted material. It provides clean-room implementations of system library interfaces based on publicly documented behavior. You must provide your own legally obtained PS3 game files.
Contributors are credited in CONTRIBUTORS.md.
MIT License. See LICENSE for details.
ps3recomp is built by a growing community. See CONTRIBUTORS.md for who did what — thank you, everyone.
Two titles in game, and one of them online. The Simpsons Arcade Game plays a two-player match over the network, and Tornado Outbreak reaches its first level rendering correctly with no per-title overrides. Thirty-odd pull requests from six people since v0.12.1.
Online
PS3_NET_ONLINE; sceNpMatching2 rooms, signaling and
callbacks, sceNpScore leaderboards, sceNpLookup, cellSysutilAvc2; NAT probes,
hole punching and a relay for peers that cannot reach each other.
--psnr host --username NAME on the runtime's command line. Offline is still
the default and unchanged. See docs/ONLINE.md.Rendering
ps3recomp_host scene under the validation layer.SET_TRANSFORM_CONSTANT is a 32-dword window — @andrebrumdev
(#208); three registers were corrupting God of War II's skinning palette.Lifter
vcmpequb./vcmpequh. set CR6 (#207).lwax tables.SPU — @sagemono, from LittleBigPlanet:
non-r0 link returns (#174), data-only job-module entries (#175), drain resume
and iret unwind (#176), the taskset syscall on a planted context (#177),
_sys_spu_image_import segment source (#178).
Audio and media — cellAtrac with ATRAC3plus (#186), cellVdec/cellAdec query and open fixes (#197, #198, #201), cellSail state changes (#190), job-chain completion carrying the job index (#204).
Build — Linux and macOS building again (@gabryboy12-ai
#191, #205), -msse4.1 probed (@andrebrumdev #206),
spu_lifter --header-name honoured (@tomspilman #181),
the onboarding docs rewritten to the pipeline the tools implement (#180), and
AnyPS5 added to the sibling projects
(@bojogon7 #209).
A packaging release. v0.10.0 and v0.11.0 were both tagged and neither was ever
published — their release runs died on macOS with an undeclared
RtlCaptureStackBackTrace, so the newest download anyone could get was v0.9.1
from August. That is fixed, and Linux now builds alongside Windows and macOS.
VERSION.txt says what its platform can actually do — Windows renders and runs
titles through D3D12, macOS renders through Metal but cannot yet load one
because the PPU boot scaffold does not run on POSIX, and Linux has no renderer
at all. Someone downloading the Linux zip should learn that from the file, not
after building something with it.workflow_dispatch set the bundle version
from the branch name, so a branch with a slash in it nested the staging
directory and the upload glob found nothing — every platform built, tested and
staged, and the run failed anyway.cellGame content root strips /dev_hdd0 — @sagemono
(#163, landed as #173). ppu_fs strips the mount prefix, so guest
/dev_hdd0/game/<id> resolves to <root>/game/<id>; spelling the device out
recreated the very split v0.12.0 set out to close, one directory over.Nineteen open pull requests land on one branch. They came from the two people
who have games actually running — @canersaka
with Yakuza: Dead Souls on macOS, @sagemono
with LittleBigPlanet — and the capability they represent existed for months
without existing on master. Every one of caner's carried the same sentence:
"Windows gameplay has not been rerun for this batch." Nobody could answer that,
because nothing ran the ports together. That is what this release is.
The nineteen
cellGameContentErrorDialog;
cellGame content root — the host directory /dev_hdd0/game maps to must be
the one ppu_fs resolves it to, or a title writes its save data to one
directory, reads it from another, and reports the data corrupt rather than
missing; bi $rN classified by basic block rather than by the lifted function,
because the lifter splits straight-line runs into functions that chain by
fallthrough and a split is not a control-flow edge; hbra/hbrr decoded as
the 7-bit opcodes they are; --extra-funcs passthrough for images entered by
an interrupt vector, which --auto-functions cannot see; EDAT license types 1
and 2 with the missing HMAC-SHA1 hash modes; list-DMA issue tracing.What unifying them found
cellGameSetParamString was missing. Sage's cellGame work makes Twisted
Metal reach cellGameCreateGameData for the first time, and that path calls a
function nothing implemented. Exactly the kind of gap a port owner cannot see
from inside their own title — which is the argument for unifying, stated as a
bug.hbra/hbrr decode is byte-identical to
one already in the tree, reached independently from a different witness.The gate had to be rebuilt before it could say anything
Four things it could not do, each discovered by it letting something through:
ps3_ms_dump() ran only at
atexit(), and every gated port is killed on its timeout — so DROPPED, the
rule whose own docstring reads "Rendering stopping looks like this", had
nothing on either side to compare. Counts now checkpoint on a timer.--shots runs each port in the configuration its
README documents and compares against the port's own reference screenshot.
Frames are judged by distinct colour count, never brightness: a cleared buffer
scores 100% non-black while showing nothing.PS3_VFS_ROOT was doubled for two ports. The guest path is appended whole,
so a root of vfs/PS3_GAME/USRDIR produced
vfs/PS3_GAME/USRDIR/PS3_GAME/USRDIR/…. VF5 spun on 686,996 failed opens
and looked like a dead showcase title; Simpsons opened zero files and drew
9,391 groups of nothing, which looked like a broken renderer. Both render.
Their goldens were invalid rather than stale and are re-recorded.UNRESOLVED NID, a fact about the binary rather than about a
run.rubberducky leaves the gate: a Sony SDK sample recompiled from a debug fSELF
with its symbol map exposed is excellent ground truth for the lifter and says
nothing about whether a change is safe for retail titles. New:
docs/REGRESSION_GATE.md.
Fourteen open pull requests land at once. Most of them are correctness fixes found by driving real titles into real walls: Tokyo Jungle reaches its main menu, and the bugs it exposed — a mutex that never ran its initialiser, a FIFO resync that ate fences, two guest pointers dereferenced as host pointers — were never title-specific.
Execution correctness
sys_ppu_thread_once was unimplemented and returned success without
running the initialiser (#136). Anything waiting on state that a once-routine
was supposed to publish waited forever. This was the keystone bug behind
Tokyo Jungle's boot.xswd read the wrong words and stored the halves backwards (#127) —
small positive values became zero. GETLLAR must snapshot the reservation
from local store, not re-read memory (#126).vmsumshs, vsumsws, vmulesh and five byte-order bugs.Guest pointers are not host pointers
Two crashes, same root cause, both reachable from ordinary gameplay:
cellSaveData called the title's funcStat and funcFile as host function
pointers. They are guest OPDs, so this jumped into guest memory as code —
every autosave died on it. Both now marshal through guest memory the way
cellSaveDataAutoLoad2 already did.cellSail printed its uri and path arguments as host strings, killing
the process the moment a title opened a movie. Both are copied out of guest
memory now, and the descriptor's long-unused uri[512] field is finally
filled.Graphics and SPURS
get to put keeps
the ring alive, but a SET_REFERENCE in the skipped range means the
cellGcmFinish waiting on it never wakes. Losing draws costs a frame; losing
a fence deadlocks the title. The skipped range is now swept first, and each
resync site is named in the log.ShutdownJobChain (#129).cellSail: a player handle is a guest pointer, not an index (#130), and
async call completion is reported to the title's event handler (#133).Diagnostics and tooling
Documentation, honestly
ps3::memory /
ps3::thread / ps3::modules API that does not exist, and since the porting
guide tells you to copy that template, the documented path could only ever
produce undefined references (#114, #89). Rewritten against the real runtime
API, and its CMake now compiles the per-game PPU scaffold and sets /bigobj.RSX_LIVE_DRAW is not
required to get a picture.SPU_FALLBACK.md claimed ps3recomp cannot execute SPU code, which stopped
being true some time ago.CI
cellSync.c called getenv with no <stdlib.h> — invisible on Windows,
fatal on POSIX, and one reason every Linux job had been red.xswd case that built asymmetric vectors
with a helper documented as only safe for symmetric ones. Windows CI was
never running that suite at all; it does now.Known issue: the PPU scaffold ratchet still fails on Linux and macOS (
ppu_loader.cpp, 0 → 110 on gcc). It predates this release and is tracked in #139. Windows CI is green.
Windows, macOS and Linux all build the runtime and pass the suites. A second draw engine lands beside the first — @canersaka's, brought across from his Yakuza port — the RSX backend finally gets a real abstraction instead of one D3D12 file, and You Don't Know Jack reaches gameplay.
A second draw engine
rsx_live_draw.c, rsx_dispatch.*, rsx_vertex_compact.*,
rsx_restart_cuts.h and the VP/FP decompiler updates are
@canersaka's work (#113). Opt-in behind
RSX_LIVE_DRAW, so the existing backend path is unchanged when it is off.
This sits beside @sagemono's draw engine from
v0.7.0 — two independent renderers, not a replacement.SET_SHADER_PROGRAM dropped, so every draw ran one constant-black fragment
program); HDR colour surfaces (F_W16Z16Y16X16 targets were created as
R8G8B8A8_UNORM, clamping every value above 1.0 on write); and fragment-output
NaN guards.Three platforms
TEXTURE_CONTROL1 crossbar decode (#102), texture layout, and one primitive
truth table with a draw record that is not a D3D structure (#103) all lifted
out of the D3D12 backend — so Metal and the headless backend read the same
neutral values instead of reimplementing them.dirent.h.Runtime correctness
CellSyncMutex is a ticket lock, not a 0/1 flag (#118) — the guest word
is big-endian m_freed/m_order and free means m_freed == m_order. We
stored it host-endian and CAS'd it from zero, so a mutex the SPU left free at
1/1 read as 0x01000100 and TryLock spun forever — 160 million times, in
the title that found it.sys_mmapper_allocate_memory implemented (#112), cellSpurs
CreateJobChain + KickJobChain actually start the chain (#98),
cellDiscGameGetBootDiscInfo takes one argument, not three (#109),
sys_event yields before refusing a receive on an invalid queue (#110),
and SPU MFC PUTs into the null page are rejected (#107).Diagnostics
PPU_WVAL watches PPU stores by value rather than by address (#108), the
write watch says when it stops printing and lets the cap be raised (#111),
the guard prints the writer's live arguments on every watched-line hit
(#99), plus PS3_HLE_ARGS, PS3_WAIT_OBJ and a settable SPU atomic-trace cap.Ports
A packaging release. No runtime or lifter behaviour changes: the same tree as v0.9.0, plus the CI and release plumbing that makes it downloadable.
v* tag builds an SDK bundle per platform
— the runtime library, its public headers, the full Python pipeline and the
docs — and attaches it with checksums. Previously a release was source-only,
so trying the project meant reproducing the whole toolchain setup first.
lib/ and include/ come from the project's own install rules, so the
bundle cannot drift from what a local cmake --install produces.The first tagged release, and the first that another platform can verify. Two outside contributions from @slushiimusic gave the project CI and a second render backend; wiring the existing test suites into that CI immediately found fifteen silent miscompiles.
macOS, and the project's first CI (#94, #95, #96)
docs/BUILDING.md had listed Apple Clang 14+ under "full support" while the
tree did not compile there at all. Reported in #94, which was correct.Fifteen VMX miscompiles, found by running the tests somewhere new
COMPILE FAILED and executed nothing — and a
missing compiler exited 0, so a skip read as a pass.vupkhsb/vupklsb/vupkhsh/vupklsh (12 cases) read
and wrote the big-endian vector file through host int16_t*/int32_t*,
byte-reversing every result element. vcmpgtsw/vcmpgtsh compared
byte-reversed values, so a mixed-sign vector produced an all-zero mask.
vadduwm propagated carries the wrong way and spilled into neighbouring
elements. The byte-width members of each family passed by luck.vadduhm, vsubuhm, vsubuwm had the same latent bug with no test
covering them; fixed together. 1311 checks, 0 failures.mtfsb0/mtfsb1/mtfsfi reached the unhandled catch-all. mfspr VRSAVE
was a bare no-op that left the destination register stale.Runtime fixes found while driving two titles
0x03000000 —
inside main memory, where a title's own allocator hands out blocks. flOw's
heap swallowed it: the game memset over the control register, the FIFO drain
read ASCII as put and discarded whole frames. Moved to
VM_HLE_INJECT_BASE, with every user deriving from it.lwz r30,d1(r2); lwz r11,d2(r30)) were rejected
outright, including the common case where the base is loaded in the prologue
hundreds of instructions earlier.PS3_GAME level).Earlier versions in this changelog predate tagging. v0.9.0 was the first tagged release; v0.9.1 is the first with downloadable artifacts.
A correctness release about the guest/host boundary, and about instrumentation
that can support its own claims. Two batches: #92 — the guest-pointer sweep,
a SPURS teardown fix, and making the logging stop changing what titles do — and
everything folded via integrate/fold-2026-08-14,
@sagemono's guest-ABI HLE batch and SPU-lifter
faithful adoption plus @canersaka's
guest-callback/lv2/ABI batch.
The guest-pointer sweep — libs/ is clean (#92)
vm_write*,
not a bare store); and layout — a struct holding a pointer is 4 bytes per
field to the guest and 8 to us, so every field after it shifts, and a u64
cannot be moved word-at-a-time. Only the first is a translation.cellFont is the case that shows why: CellFontRenderSurface and the glyph
structs lead with a host pointer, so casting a guest EA to them misplaced
every field and stored floats little-endian — a title reads a glyph advance
of 4.6e-41 and lays every string on top of itself.cellGcmGetOffsetTable handed the title two truncated host pointers through
a struct whose guest form is two 4-byte fields. The host tables were always
correct; the bug was on the way out.tools/audit_guest_ptrs.py finds the bug class, tools/audit_entry_calls.py
finds a library calling its own entry points (which double-translates — it
caught two live ones), and tools/test_audit_guest_ptrs.py pins the audit in
both directions, because the heuristic silently stopped matching three times
during the sweep and a clean report meant nothing until it was checked.libs/guest_struct.h documents when the easy helpers do not apply.Runtime
sys_event: release queue waiters when SPURS finalizes. A title's worker
waits on the SPURS completion queue with an infinite timeout and only re-tests
its quit flag when a completion wakes it — but cellSpursFinalize has already
stopped the job chains, so none can arrive. The shutdown that called finalize
then blocks in sys_ppu_thread_join forever. Cancelling the attached queues
returns CELL_ECANCELED to the waiters, which is the path titles already
handle. Lifted again on re-attach, so teardown-then-setup works.ps3_log_verbose() treated a
non-tty stderr as "be verbose" with no way to say otherwise, so capturing a
log turned the firehose on; and the SPURS job path wrote two lines with an
explicit fflush per job — ~6k writes/s through one FILE lock, enough to
starve a guest thread for seconds. Tokyo Jungle presents four frames in 40 s
with the log discarded and one with it redirected to a file. PS3_VERBOSE=0
now decides, and the per-job pair is verbose-only.g_hle_inflight[tid] and g_sc_inflight[tid]
record the HLE call and lv2 syscall each guest thread is inside. The
watchdog's CTR is the last bctrl target and goes stale the moment a call
returns — it named sys_lwmutex_unlock for a thread that had long since left
it. The write-watch also covers ppu_memory.h's inline vm_write* now, so
"nothing writes this address" is a claim it can actually support.HLE & guest ABI
cellUserInfo, trophy u64s, cellGameGetParamInt DISC type, open PSID, cellRtc, save-data user paths, cellJpgDecDecodeData, cellPngDec, and the cellAudio read index as a BE u64 — @sagemono (#79, #81)cellFsSdataOpen with real SDATA/EDAT (NPD) decryption, cellFsMkdir + the game-data dir created where the VFS looks, and a /dev_hdd0 overlay onto the installed-update tree (PS3_HDD0_ROOT) — @sagemono (#79)CELL_OK — a module name matching no file fails; net reports a real offline state with real SDK NP error codes; sys_time_get_system_time returns real microseconds instead of a call counter — @sagemono (#79)cellFsReaddir writes the real 258-byte dirent ABI + a corrected CellOS generic error-code table (#85), and cellGame firmware parameter IDs, error values and buffer bounds matched to the SDK (#84) — @canersakacellAudio clears each consumed ring-buffer block — an underrun replayed the previous block instead of silence — @canersaka (#83)lv2
sys_semaphore_info::value and in the Win32 handle), so timeout/wake races could lose tokens or park a waiter while value was already positive. value under value_lock is now the only count, the handle is a wake channel, and overflow returns lv2 CELL_EBUSY. The sync_stress target is wired into the build so this stays executable outside a generated title — @canersaka (#88)Lift & decode
spu_channels.c, plus LBP SPU/SPURS bring-up and RSX texture support (G8B8/DXT, deswizzle). Credit for the underlying decoder/lifter design belongs to @sp00nznet and @canersaka — @sagemono (#82)sync/dsync/syncc emit real host memory fences, with the sync C bit decoded instead of folded into plain sync — @canersaka (#87)toc list was concatenated as an int, and the caller swallowed the exception; plus rA=0 base resolution and generic ld/offset-table discovery (#78, #81)The RSX draw engine lands. @sagemono's renderer executes the title's own NV4097 vertex and fragment programs on D3D12, and You Don't Know Jack now renders — a real double-buffered flip loop, verified by booting it. Plus @canersaka's PPU/SPU correctness batch, and the SPU image-import path SPURS titles need before any SPU work can run.
Rendering
0xC708C708. A real command-buffer-full callback now drains and resets the ring — @sagemonoLift & decode
blrl dispatched as an indirect call — it was lumped with blr and emitted a bare return;, dropping the call and skipping the frame epilogue, so r1 leaked the frame size on every function-descriptor call — @sagemono (#42)ET_EXEC) import parsing — the lib-stub table was only reachable via the PRX module_info path, so every retail main executable reported 0 imports; now located via PT_PROC_PRX_PARAM. Minecraft: 0 → 345 NIDs across 30 modules — @sagemono (#41)lift_function indexed by address — an O(refs·N) mid-function pass hung the lifter indefinitely after "100% lifted" on large titles; now bisect over a cached sorted index — @sagemono (#41)v >> (64 - n) at n=0 is UB, and clang miscompiled the clrldi truncation idiom every 32-bit address cast lowers to — @sagemono (#41)rA=0 as a literal zero base, not GPR[0] (#72); update forms write back rA for indexed stores (#71) and FP loads/stores (#73) — dropping the write-back leaves rA stale and corrupts every subsequent (rA)-relative access — @canersakalmw/stmw (#68), mulldo overflow form (#70), stvlx/stvrx/stvlxl/stvrxl Cell unaligned vector stores (#61), value-verified CAS for lwarx/stwcx and ldarx/stdcx (#62), and an FP correctness batch — fcmpu NaN ordering, fcti saturation, single rounding, vctsxs/vctuxs (#60) — @canersakafpscr bit ops + vrsave decoded as visible no-ops — mtfsb0/mtfsb1/mtfsfi were undecoded and emitted as a raw .word, an invisible dropped instruction — @canersaka (#69)brsl target parse broken by the disasm link-register fix, so every call lifted as a silent no-op (#58); bisl/bisled target register; single-round fma/fms/fnms, fesd/frds from the left word slot, fi/frest/frsqest interpolation (#51) — @canersakaRuntime & lv2
sys_spu_image_import implemented + SPU-image syscall numbers fixed — import was mis-numbered 157 (that's _sys_spu_image_import) and collided with SYS_SPU_IMAGE_CLOSE, so a title's import call dispatched to the close stub and the image struct was left zeroed (entry=0 → matched no fallback → the SPU thread "instantly completed"). Replaced the zero-init stub with a real SPU-ELF32 PT_LOAD → sys_spu_segment[] parser — @sagemono (#57)_sys_spu_image_import (sysPrxForUser NID 0xEBE5F72F) — the user-space wrapper libsre invokes during cellSpursInitialize; previously unresolved, so the SPURS SPU kernel image was never parsed and the SPU threads came up at entry 0sys/syscall.h (475.001) — the authoritative table. Three were live collisions: the lwmutex block sat on SYS_RAW_SPU_CREATE_INTERRUPT_TAG/SET_INT_MASK/GET_INT_MASK (150–152) and lwcond on SYS_SPU_IMAGE_OPEN (156). Now 125/125 match the SDK, 0 duplicates — @canersaka (#65)sys_mutex_trylock recursive fast path pairs the host critical section (#67); sys_spu_thread_group_create reads r5 as priority and the name from the attr struct (#66) — @canersakaThe second half of the two-port review pass: @canersaka's remaining decode/ABI/timing fixes plus a lifter/HLE audit toolkit, and the title-agnostic robustness fixes from @JonathanDC64's port that could be ported and build-verified cleanly.
Lift & decode
spu_disasm: channel field widened 5→7 bits (channels ≥32 no longer wrap), hbra/hbrr field layout, bisl/brsl/brasl link register shown, ri7 sign-extension, stop-code preserved — @canersaka (#49)stop signal code preserved to ctx->stop_code — SPURS leaf tasks invoke kernel syscalls via stop <code> (EXIT/YIELD/WAIT/…), which was being discarded — @JonathanDC64Runtime & lv2
sys_event_flag, sys_semaphore, and sys_mutex timed waits (the same 15.6 ms-granularity fix as v0.6.5's event-queue path) — @canersaka (#50)g_active_ctx restored after guest callbacks — it was left dangling at a stack-local scratch ctx, corrupting the crash handler / diagnostics; applied to both ppu_guest_call and ppu_guest_call_ct — @JonathanDC64HLE
cellGameDataCheckCreate2 (NID 0xC9645C41) implemented — marshals StatGet/StatSet and fires the guest funcStat callback — @JonathanDC64Tooling
Two independent retail-game ports kept fuzzing the toolkit: @canersaka's Yakuza: Dead Souls and @JonathanDC64's Demon's Souls. This release distills the title-agnostic correctness/robustness wins each surfaced — the port-specific scaffolding stays in the forks.
PPU / SPU lift
fsqrt/fsqrts source-register decode + vspltis printed as a signed immediate (ppu_disasm) — @canersaka (#46)addeo/subfeo/mulhwo/mulhwuo overflow forms (previously fell through to a no-op stub) + vupklsb/vupkhsb/vupklsh unpacks — @canersaka (#52)musttail — a guest loop whose back-edge crosses a lifted-function boundary was nesting one host C frame per iteration and silently overflowing the stack (a stack-overflow SE runs no unhandled filter, so the process died with no log and exit code 0); now an O(1)-stack jump under clang — @JonathanDC64Runtime & lv2
sys_timer usleep — was a single SwitchToThread(), so usleep(<1000) was a no-op; now paces to a QPC deadline — @canersaka (#44)sys_event_queue_receive returns the event in r4–r7 (lv2 ABI), not just the guest memory buffer — callers reading the registers saw stale values — @JonathanDC64sys_memory_get_page_attribute renumbered 358→351 (0x15F) and implemented — @JonathanDC64CellFsStat runtime-side layout corrected to the 52-byte / 4-byte-aligned ABI — fixes the ppu_fs.cpp / sys_fs.c stat writers the v0.6.5 libs-side packing fix didn't cover — @JonathanDC64HLE libs
cellNetCtl integer out-params written big-endian — @canersaka (#45)cellAudio delivers real period events to the registered notify queues (games blocked on the audio event now wake) — @canersaka (#54)Driving You Don't Know Jack (Scaleform UI + FMOD, 5,859 functions) from an instant crash to its running main loop surfaced one global performance bug worth more than any single lift fix — plus a batch of community lifter/HLE correctness PRs.
Runtime — the event-poll timer-resolution fix (sys_event.c, tests/boot_main.cpp):
sys_event_queue_receive floored the timeout to a 1 ms SleepConditionVariableCS, but Windows' default ~15.6 ms timer granularity inflates any sub-15 ms wait to a full tick — a two-queue-per-frame poll loop burned ~30 ms/frame doing nothing. Sub-millisecond timeouts now do a true non-blocking check (immediate ETIMEDOUT when empty), and the harness calls timeBeginPeriod(1). YDKJ went from ~0.5 fps effective to ~60 Hz, reaching online-init ~6× faster.present() latency.Community PRs incorporated — thank you:
CellFsStat PS3 packing — @canersaka (#22)PARAM.SFO — @canersaka (#24)sys_rwlock → EDEADLK/EPERM on writer self-relock / bad unlock — @canersaka (#25)ppu_disasm: opcodes 33/225 are crnor/crnand (were swapped) — @canersaka (#40)tools/lift_prx.py + docs/FIRMWARE_LLE.md) — relocate a decrypted PRX and lift the real firmware module (e.g. the libsre SPURS kernel) instead of HLE-ing it; a bring-your-own-firmware method that ships no firmware — @canersaka (#53)You Don't Know Jack port — now public at sp00nznet/youdontknowjack: boots the full init stack to its main game loop with a live D3D12 clear+flip at 60 Hz. Current frontier: the GCM command buffer's ring-wrap flush callback is null and a computed 0xC708C708 poison reaches the scene-graph dispatch — so only black clears render so far.
A pass of hard-won correctness fixes surfaced by driving flOw (PhyreEngine, ~104k functions) deep into its boot — plus the SPU-side plumbing to get a SPURS taskset actually dispatching. Most of these are silent-corruption bugs: the lift produced valid C for the wrong computation, so nothing crashed until a data structure quietly filled with garbage thousands of frames later.
Community PRs incorporated — thank you @canersaka, who found and fixed all of the below and sent them in as pull requests:
PPU lift
sraw/srad/srawi/sradi) + mtcrf field mask — (#21)subfe/subfme/addme — (#26)cntlzw(0) is 32, not undefined __builtin_clz garbage — (#35)vcmpgtsb/sh/sw/ub/uh/uw handlers (dot forms set CR6) — (#39)SPU lift
bi $r0 reloaded via lqa/lqr is a computed tail jump, not a return — (#36)shufb, cbd/chd/cwd/cdd) — (#32)il negative-immediate double sign-extension — (#33)rchcnt — (#34)Runtime
mftb/mftbu read a real timebase — (#38)PPU lift — the carry/borrow bug class (ppu_lifter.py):
add never wrote XER[CA] and truncated the carry-out to 32 bits. Any adde/addze/subfe consumer downstream (bignum math, 64-bit pointer arithmetic, the CRT's own __eabi helpers) then read a stale carry. Now sets CA from the true unsigned 64-bit carry-out.subfic computed a 32-bit result and dropped the borrow — rewritten as EXTS(SI) - RA over the full 64 bits with XER[CA] = NOT borrow (EXTS(SI) >= RA).subfme was missing its XER[CA] update entirely; adde/addme carry-out recomputed to match the PowerISA / RPCS3 add64_flags ADC semantics.sraw/srad/srawi/sradi now set XER[CA] (was never written) via new ppc_sraw/ppc_srad helpers — CA = rS<0 AND any 1-bits shifted out, which feeds the same adde/subfe consumers.bctr dispatch leaked the frame: these were lifted as ps3_indirect_call(ctx); return; without the function epilogue, leaking r1 and the callee-saved GPRs on every jump-table switch and tail call. Now emits the full epilogue before the tail jump.ppu_disasm.py --va: disassemble a window at a virtual address, mapping va→file offset through the ELF's PT_LOAD segments — fixing the long-standing footgun where --raw --offset <vaddr> read the wrong bytes (off by the .text segment base).SPU lift — computed jumps & wide ops (spu_lifter.py, spu_disasm.py):
bi $r0 computed tail-jumps vs. returns: a bi $r0 whose r0 was just reloaded via lqa/lqr is a computed tail jump, not a return. Treating them all as returns made the SPURS taskset launcher fall through and the dispatcher loop forever — the exact SPU analogue of the PPU tail-call bug above. compute_bi_r0_jumps() now classifies them by backward-scanning for the nearest r0 writer.RC source, not the dest) — every selb/shufb/fma-family op wrote the wrong register.fesd/frds single↔double convert, and the dfma/dfms/dfnms/dfnma double FMA ops (3-register, RT is the accumulator).cgx extended carry-generate (RT is also a source), mpyhha/mpyhhau high-half multiply-accumulate, and a br . self-loop trap (target == self is a deliberate infinite-hang trap on real SPU, not forward progress).Runtime robustness:
ppu_loader.cpp, PPU_HASH_BITS 16→18): the open-addressed registration table live-locked filling past 65,536 functions — flOw registers ~104k. Titles above ~50k functions now register in bounded time./app_home/ VFS mapping (sys_fs.c): the install-dir prefix (opened as /app_home/..., app_home/..., e:/app_home/...) now maps to the USRDIR root instead of appending a literal app_home directory that isn't on disk.ppu_guard_page(), Windows): arm a guest EA and a VEH logs the faulting host RIP of any raw store into that 4 KB page — catches vector/memcpy stores that vm_write* instrumentation can't see. Env-gated; a diagnostic aid, off by default.cellGcmGetTiledPitchSize ABI fix + real flip/vblank handler dispatch (see prior commits) — the wrong return convention was corrupting the caller's stack.SPU SPURS plumbing: new libs/spurs/spurs_taskset.{c,h} + spurs_pm.c (taskset descriptor + power-management scaffolding) and runtime/spu/ workload-dispatch wiring toward running a real SPURS task kernel. Still in progress — the taskset dispatches but completion-event delivery back to the PPU isn't verified yet.
Tooling: tools/test_ppu_lift.py (PPU lifter unit harness) and tools/rpcs3_probe/ oracle-introspection scripts used to diff our lift against the running RPCS3 reference.
nid_database now auto-scans libs/ + runtime/ for every function the toolkit implements (load_implemented()), and ships 44 more curated names recovered by exact-NID brute-force against the harness corpus. Resolution went from 175 → 421 of the 660 distinct NIDs the sample titles import; the harness stub-prioritization ranking is now fully named through the high-frequency tiers.imports stage (via ppu_loader's proc_prx_param → libstub walk) extracts each EBOOT's firmware imports and resolves them, and the report ranks the most-imported libraries and functions across titles — the data that says which stubs to write next.cellNetCtl net-start dialog implemented (LoadAsync/UnloadAsync, imported by every sampled title): posts the LOADED/FINISHED sysutil callback events so games clear the boot-time "connect to PSN" gate, and reports a connected result.cellGcmSetupContext() — reusable core of _cellGcmInitBody (the function cellGcmInit's SDK macro actually calls). Captures the proven CellGcmContextData layout (begin/end/current/callback) from the shipping Simpsons port so game bridges stop hand-rolling it..opd function detection hardened: the TOC base may live in BSS / a segment gap, so detection no longer requires it inside a file-backed range — recovered ~20–23k address-taken functions each on flOw and Marvel Ultimate Alliance.runtime/ppu/ (loader, HLE dispatch, sysprx, filesystem) + runtime/host/host_main.c — the per-game path that loads a lifted PPU image, links it with the HLE runtime, and boots it. Built per-game against the lifter-generated ppu_recomp.h, so it's excluded from the game-agnostic runtime library. See docs/PPU_RECOMP.md.rsx_fp_decompiler / rsx_vp_decompiler translate NV40 fragment/vertex programs to host shaders, with a validation-test corpus. See docs/RSX_FRAGMENT_PROGRAM.md.tools/ppu_loader.py (image manifest / OPD table / TOC / imports) and tools/gen_hle_nids.py.nid_database changes were not taken — they predate and would regress the v0.6.0 SPU subsystem and the v0.6.1 NID fix.The community showed up. Most of this release came in over the wall as pull requests — almost all of it discovered by @canersaka while stress-testing the toolkit against a 22 MB / ~45,000-function Yakuza: Dead Souls port, which turns out to be a fantastic fuzzer for everything we got subtly wrong. Huge thanks to every contributor below.
Correctness — decode & lift (thanks @canersaka):
nid_database.py): the suffix constant was truncated to 12 bytes and corrupted past byte 8, and the digest was read big-endian. The authoritative 16-byte suffix + little-endian read now matches real EBOOT import tables — cellSysmoduleLoadModule → 0x32267A31. Previously matched 0 of 354 import NIDs on a real game; now 343/354 resolve. (Our own include/ps3emu/nid.h already documented the correct values; the Python tool just disagreed with it.)ppu_disasm.py): dozens of mnemonics mapped to the wrong codes (vmaxfp/vminfp, vcfsx/vcfux swapped with vrefp/vrsqrtefp, the whole vpk* pack family permuted, signed/unsigned vcmpgt* swapped, vsumsws, …). These decoded silently and lifted to valid C for the wrong operation — vcfsx alone was wrong 9,728× in one title.addc/subfc with carry-out into XER[CA] (86% of all unlifted instructions in Yakuza — 63k of 73k), the unaligned vector loads lvlx/lvrx/lvlxl, ldbrx, vsrw, vsububs, vsum2sws, vupkhsh, vrfim, and vand (which had an implementation but was missing from the dispatch list).sradi decode for shifts ≥ 32: the 6-bit shift carries its top bit in instruction bit 30, so the XO field reads 827 (which was unmapped) for shifts of 32+. Now composed correctly. (Follow-up: removed a dead, incorrect XO 827 → lhaux block — real lhaux is XO 375.).opd table (find_functions.py): PS3 executables list a descriptor for every address-taken function (every C++ virtual, every callback) — 21,893 of them in Yakuza, of which exactly 1 was being detected by prologue scanning. Now seeds starts from .opd (located by shape, since section names are often stripped) + the ELF entry, bounds them at the first blr, and filters phantom branch targets from data decoded as code.--jobs N) — a multi-hour single-core lift on a 45k-function game drops to minutes. Plus record-form CR0 handling, runtime-matched context, and split-file C output so the generated source is actually buildable.Runtime & libs (thanks @canersaka):
sys_vm syscall family (300–313) implemented — sys_vm_memory_map returned ENOSYS with an unwritten out-pointer, crashing callers on first deref. Mappings now bump-allocated from a committed 0x60000000 window.sys_memory allocations now come from a committed-on-demand 0x40000000–0x50000000 window (the old bump base at 0x20000000 failed every allocate with silent ENOMEM); addresses match real-hardware traces byte-for-byte, and the bump pointer is now thread-safe.sys_lwmutex_destroy lv2 semantics: returns ESRCH for a dead mutex / EBUSY for a held one (libc teardown branches on these) instead of unconditional OK; slot allocation is now locked.lv2_syscall_table.h had 400/402).cellVideoOut + corrected CellGcmContextData layout (callback at +0xC): GetResolution was writing host-endian, so the guest read 720×480 as 53250×57345 and sized display buffers from garbage.Build & tooling:
st_atime/st_mtime macros were shadowing CellFsStat members; five translation units now compile clean so downstream projects get a Linux runtime to link against.runtime/spu/tests/ defines its own main(); the library now builds clean with MSVC.tools/show_func.py (thanks @canersaka): dump a single lifted function's C, its original PowerPC disassembly, or both, straight from the chunked output.spu_disasm.py's opcode tables from rpcs3's authoritative SPUOpcodes.h. Pervasive mis-decodes (lqr/stqr/fsmbi were excluded from RI16 and shadowed by spurious RI10 rotate entries; RI16 branch/load forms at wrong opcodes; ~15 wrong SPU_RR entries; missing RI8 float conversions) had silently corrupted every SPU lift.spu_lifter.py now emits a tail-call when a function ends in a non-branch — previously such functions fell off the end and truncated execution mid-image. Added cflts/cfltu/csflt/cuflt float conversions.image_id to the SPU context + an image-tagged function registry (spu_channels.c, spu_begin_image()), so a SPURS kernel, a policy module, and a job can coexist at overlapping local-store addresses.extract_spu_images.py (pull SPU images from PRX/ELF), find_spu_functions.py (SPU function bounds), wrap_spu_elf.py (wrap raw SPU blobs).runtime/spu/tests/, plus test_spu_helpers.c. New docs/SPU_LIFTER.md.gcm_flush_guest_cmdbuf() now initializes RSX state via rsx_state_init(), passes NV40 commands to rsx_process_command_buffer(), and dispatches to the backend. Games can write GCM commands and see results on screen.ctrl->put for changes, scans command buffer for SET_REFERENCE (method 0x0050) and WRITE_BACK_END_LABEL (0x1D6C), updates ctrl->get/ctrl->ref automatically. Breaks cellGcmFinish spin loops without needing HLE bridge interception.stwbrx/lwbrx in recompiled code (host-endian, no byte-swap). The HLE now reads/writes these in host-endian via direct uint32_t* access instead of vm_write32 (which byte-swaps for big-endian).cellGcmInit now maps the command buffer region (1MB-aligned) via cellGcmMapMainMemory so cellGcmAddressToOffset succeeds for buffer addresses. Previously only the game's explicit mapping was registered.rsx_process_command_buffer, updates control register put/get, then resets ctx->current. Previously only reset current without processing.__declspec(thread) fix: g_trampoline_fn declaration mismatch caused MSVC to compile 37K+ writes as direct stores to the read-only TLS template in .rdata, crashing with AV WRITE. Fixed by declaring __declspec(thread) consistently across all translation units.bl (direct call) and bctrl (indirect call). Previously only emitted in the preamble; callers that set g_trampoline_fn and returned wouldn't have their chains drained.bdnz/bdz decremented CTR as uint64, causing loops with 32-bit counts sign-extended to ~0xFFFFFFFF_8XXXXXXX to wrap past zero into 18-quintillion-iteration infinite loops. Fixed: ctx->ctr = (uint32_t)(ctx->ctr - 1).__builtin_clz replaced with _BitScanReverse, __int128 multiply replaced with _mul128/_umul128 intrinsics, all function/memory declarations wrapped in extern "C" for correct C++ linkage.ppu_vr vr[32], vscr, fpscr, cia, reserve_addr/reserve_value, thread_id fields. Enables full VMX vector operations, atomic reservations, and thread identification.rldcl/rldcr decoding.vm_read/vm_write as inline functions in ppu_recomp.cpp preamble, bypassing function call overhead. Eliminates ~100x slowdown on byte-by-byte memory operations (critical for CRT memset of multi-GB address spaces).cellSysmoduleLoadModule returns CELL_OK for already-loaded modules instead of CELL_SYSMODULE_ERROR_DUPLICATED. Games like flOw treat the error as fatal..ctors section, resolved through OPD to real function addresses (0x12F14, 0x1CD2C, etc.), and execute them directly. Constructors now allocate PhyreEngine objects (3 objects with malloc + lwmutex_create).func_000CB9CC with clean stack. Loads modules (GCM_SYS, SPURS, USBD, JPGDEC, NET), registers sysutil callback. First PS3 recomp project to reach game-level code execution.convert_trampolines.py and post_lift.py transform fallthrough edges between split functions into explicit trampoline calls. When the lifter splits a large function at branch targets, the resulting pieces are chained via trampolines so control flow is preserved without relying on physical code adjacency.bl call site in ppu_recomp.cpp (143K+ sites) to drain pending trampoline returns. Prevents unbounded host stack growth when recompiled split-function chains would otherwise nest deeply through call/return mismatches. Converts deep recursion into a flat loop.indirect_dispatch.cpp supports registering stubs for mid-function entry points that the lifter does not emit as standalone functions. Allows bctrl indirect calls to reach branch targets inside split functions via hash table lookup.dcbz (data cache block zero) now zeroes 128 bytes instead of being a no-op. This was THE root cause of the flOw PhyreEngine init stall — dcbz loops ran trillions of no-op iterations.rsx_commands.h/.c) — FIFO command buffer parsing, state tracking for surfaces, viewport, scissor, clear, blend, depth/stencil, culling, color mask, alpha test, textures (16 units × 8 registers), vertex attributes (16 × format/offset), shader programs (fragment/vertex), draw arrays + draw indexed. Backend callback interface (rsx_backend) with 12 dispatch points for pluggable rendering.rsx_null_backend.h/.c) — creates a window, displays RSX clear color via GDI, FPS counter and debug overlay. Proves command pipeline works before D3D12/Vulkan.rsx_d3d12_backend.h/.c) — D3D12 device (FL 11.0), DXGI swap chain (double-buffered flip-discard), RTV descriptor heap, fence-based frame sync, clear to RSX color, VSync present. Ready for vertex buffer upload and shader translation.cellfs_translate_path().libs/hardware/ directoryl10n_convert() APICreateFiber/POSIX ucontext), 64 concurrent fibers, sleep/wakeup"The Cell Processor was ahead of its time. Now it's time to bring it to ours."