# You Don't Know Jack — Static Recompilation > Turning the PS3 disc binary of Jellyvision's irreverent trivia classic into a native PC executable — no emulator required. **You Don't Know Jack** (2011, THQ / Jellyvision) is the high-definition revival of the snarkiest quiz show in video games. Dis-or-Dat, the Jack Attack, the host's relentless sarcasm, and a fake-commercial sponsor break every other question — all driven by a Scaleform Flash UI and a *mountain* of pre-recorded audio. It shipped on PS3, Xbox 360, Wii, and PC… but the PS3 version, like most PS3 games, has never run anywhere but a PS3 or RPCS3. This project takes the decrypted PS3 `EBOOT.elf`, lifts every PowerPC function to C/C++, and links it against [**ps3recomp**](https://github.com/sp00nznet/ps3recomp) — a set of HLE runtime libraries that replace the Cell/LV2 operating system with native host implementations. The goal: a standalone Windows executable that boots Jack straight to the host's barbs, no emulator in sight. > **You bring your own legally-dumped disc.** No game binaries, audio, or trivia content are included in this repository — only analysis metadata and the recompilation harness. --- ## 🎉 It renders ![You Don't Know Jack running as a native Windows executable — the Scaleform/CRIWARE/FMOD attract screen](docs/ydkj-attract-screen.png) The PS3 disc binary, statically recompiled to native x86-64, drawing its own attract/legal screen through a **D3D12 backend** — the real Scaleform Flash UI, the game's own shaders (`VS_Glyph`, `PS_TextTextureAlpha`, `PS_CxformTexture`), logos, and dynamic text. No emulator. The window title is live telemetry: `FPS: 32.00 | draws: 119 | 1280x720`. Text glyphs are still partially garbled (a dynamic glyph-cache/upload fidelity bug — next on the list), but the pipeline is end-to-end functional. --- ## Why this is a *great* recomp target Your average AAA PS3 game is a Cell-saturated nightmare of SPU compute and bespoke renderers. You Don't Know Jack is the opposite. Under the hood it's almost entirely **menus, a Flash UI, and audio** — exactly the profile that static recompilation eats for breakfast: - **One self-contained 5.3 MB EBOOT.** No separate `.sprx` modules to relocate and stitch in. - **5,859 functions.** Tiny next to a PhyreEngine title (flOw weighed in at 100k+). - **265 imports across 23 libraries — and ps3recomp already implements 19 of them.** The four it doesn't are all *optional online/social* features (matchmaking, PSN store, voice chat) that an offline single-player port can simply stub out. - The 2 GB of disc data is **601 trivia/"prize" content packs, FMOD `.fsb` audio banks, and Scaleform `.gfx` flash** — data, not code. --- ## Current Status | Metric | Value | |---|---| | Title | YOU DON'T KNOW JACK | | Title ID | **BLUS30569** (USA, disc) | | Engine | Custom (Scaleform Flash UI + FMOD audio) | | Binary | `EBOOT.elf` — 5.2 MB, ELF64 big-endian PowerPC64, `ET_EXEC` | | Entry | OPD `0x5309e8` → code `0x5ecf4`, TOC `0x544370` | | Functions detected | **5,859** (6,630 OPD descriptors) | | Functions lifted to C | **14,380** (5,859 base + 1,467 jump-table cases + 7,054 mid-function tail-entry wrappers) | | Generated source | **19 chunks, 656 MB** *(was 5.2 GB before the .rodata fix — see note)* | | Unique call targets | 6,104 | | Imported libraries | **23** | | Imported functions | **265** | | Module coverage | **19 / 23** libraries already implemented in ps3recomp | | SPU usage | SPURS framework only (management-level; no raw SPU compute observed yet) | | Target | Windows x86-64 (Linux planned) | ### Phase Progress | Phase | Status | Notes | |---|---|---| | Disc inventory | ✅ **Complete** | 2,709 files — 1 EBOOT, 601 `prize_*.zip` packs, `.fsb` audio banks, Scaleform `.gfx` | | SELF decryption | ✅ **Complete** | `EBOOT.BIN` → `EBOOT.elf` via `ps3sce` (retail key rev 7, APP SELF) | | ELF structural analysis | ✅ **Complete** | 2 PT_LOAD segments, OPD/TOC resolved, entry point located | | Function boundary detection | ✅ **Complete** | 5,859 functions seeded from the `.opd` descriptor table | | Import / NID extraction | ✅ **Complete** | 265 NIDs across 23 libraries catalogued | | Module coverage triage | ✅ **Complete** | 19/23 ready; 4 to stub (see below) | | PPU lifting (→ C/C++) | ✅ **Complete** | 14,380 functions → 19 C++ chunks (656 MB) via `ppu_lifter.py` | | Lifter `.rodata` fix | ✅ **Complete** | new `--code-end` bound; killed a 5.2 GB → 656 MB source explosion | | HLE NID table | ✅ **Complete** | 357 handlers / 18 modules (`src/gen/ppu_hle_nids.cpp`) | | Boot harness (CMake) | ✅ **Complete** | clang-cl, links prebuilt `ps3recomp_runtime.lib` | | Build & link | ✅ **Complete** | all 26 objects compile; **`ydkj_boot.exe` (64 MB) links** (1 fall-through stub) | | First boot | ✅ **Runs real code** | entry dispatched, TLS up, **170 MB heap allocated** | | Import-stub → HLE wiring | ✅ **Solved** | new lifter `--hle-stubs`: all 265 stubs → `ps3_hle_call(nid)`. **Cleared the `0x39800000` wall** (where gunstar is stuck) | | Thread creation | ✅ **Wired** | `sys_ppu_thread_create` NID → real lv2 thread spawn + generic **thread-entry trampoline**; the `"AsyncLoad"` worker runs | | TOC-save on imports | ✅ **Fixed** | save r2 to `0x28(r1)` per PPC64 ABI — **eliminated all the static-init corruption** (the vtable sweep + 40 OOB) | | `bcctrl` mistranslation | ✅ **Fixed** | 5,596 vtable calls were jumping to a garbage address; now dispatch via `ps3_indirect_call` | | Thread / main stacks | ✅ **Fixed** | VM grown to cover the `0xD0000000` stack region; 256 MB host stacks | | Native VA mapping | 🧪 **Evaluated → reverted** | guest==host (`vm_base=0`) is elegant but Windows can't map guest page 0 or `0x60000000–0xD0000000` (the game uses both); flat calloc VM kept | | Display / GCM init | ✅ **Wired** | `cellVideoOut*`, `_cellGcmInitBody`, GCM offset/config funcs | | HLE pointer bridge | ✅ **Init path done** | ~26 pointer-out funcs across cellGame/VideoOut/NetCtl/Gcm/Audio/Spurs translated to `vm_write*` | | **Boots & runs game code** | 🎉 **Reached** | clears the full init sequence — **registers RSX vblank/flip handlers, opens audio, inits SPURS, and runs the game's own FMOD engine.** No crash (runs continuously) | | Audio engine (FMOD) | ✅ **Inits** | guest-memory audio buffers → the game's **FMOD engine initializes cleanly** (no more error 37) | | RSX vblank driver | ✅ **Wired** | `ppu_guest_call` + 60 Hz vblank-ticker thread; D3D12/null backends present, ready once the loop runs | | **SPU recompilation** | 🎉 **Built** | all **22 embedded SPU images lift + compile + link** into the boot exe (~4,400 fns, 6.4 MB C). Added 9 missing SPU opcodes to the lifter; registered each image by fingerprint → lifted entry; `cellSpursCreateTask` dispatches to it (DMA shares `vm_base`, so SPU writes reach the PPU). Ready to run once boot reaches the first task. | | **Lifter correctness** | ✅ **3 bugs fixed** | drilled the post-init hang to real PPC→x86 lifter bugs: **`rA=0` atomic addressing** (infinite spin), **non-trapping integer divide** (SIGFPE), and **merged function boundaries** (frame/callee-saved corruption). Boundary recovery recovered **4303 functions** and cut lifted output **656 MB → 124 MB**. | | **Frame-drift root-cause** | ✅ **fixed** | proved a callee-saved register (`r29`, a loop count) was being reloaded as the **TOC pointer** after a callee with a drifted stack pointer clobbered its frame slot → 5.5M-iteration hang. Fix: restore `r14–r31` from an **entry snapshot** (C local), not the corruptible frame slot — 42,869 restores rewritten. **OOB accesses dropped to 0.** | | **Merged-function splitting** | ✅ **452 → 1** | a function allocates its frame once, so every `stdu r1,-N` prologue is a distinct function — split at all of them. Plus tail-call routing (`b`/`bc`/fall-through to a prologue → trampoline, not goto). Eliminated the shared-frame tail-entries that caused frame drift. **`func_003DC468` (a long-standing hang) now clears.** | | **cellGcmGetLabelAddress** | ✅ **guest ptr** | returned a host pointer (→ OOB write); now returns a guest address into a label window. The boot reaches **RSX label setup**. | | **`XER[CA]` carry-lifter fix** | ✅ **crash killer** | `srawi` (and the `subfc; shift; addze` container-size idiom) weren't setting the PPC carry flag → off-by-one sizes → the `0xC708C708` poison crash. Fixed **2,953 sites**; the crash is gone and boot advances through the full render setup. | | **Boot → main game loop** | 🎉 **Reached** | with the carry fix in, boot clears NP/trophy/pad/save-data init and enters the game's **main loop** (`loc_000149E4`), running continuously. CRT, static ctors, module load, FMOD — all cleared along the way. | | **Trophy / NP / pad ABI** | ✅ **fixed** | `sceNpTrophyRegisterContext` corrected to the real 5-arg ABI + fires the status callback; `sceNpBasicGetEvent` exact no-event code; **cellPad NIDs corrected** + big-endian guest-struct writes + a virtual pad on port 0. Each unblocked a boot phase. | | **Event-poll pacing** | ✅ **~6× faster** | the main loop polls event queues with a **30 µs** timeout; Windows' ~15.6 ms timer granularity inflated each poll to ~15 ms → a ~500× global slowdown. Sub-millisecond `sys_event_queue_receive` now returns immediately when empty (+ `timeBeginPeriod(1)`). Reaches online-init in ~20 s vs ~120 s; runs at ~60 Hz. | | **Render pipeline** | ✅ **live D3D12** | RSX command FIFO drained at 60 Hz, GCM sync-fence labels advance, and the D3D12 backend clears + flips a **1280×720 window** every frame. | | **Scratch-slot callee-save lifter bug** | ✅ **the systemic root** | drilled the "only `CLEAR_SURFACE`" wall (via an RPCS3 golden-log oracle) to a single lifter bug: when the compiler **reuses a callee-save register's stack slot for a local** (`std r29,0x90(r1)` of a computed value, later `ld r29,0x90(r1)` to reload it), the lifter mistook the reload for a callee-save *restore* and rewrote it to the register's stale entry value. The first casualty was `data.toc`'s file size (a stale pointer instead of `0x19B00`) → its buffer alloc failed → total downstream corruption. Fix: a `std rN,off(r1)` only counts as a save if `rN` hasn't been reassigned first (it must store the *entry* value). One rule, applied at the lifter — cleared the corruption globally. | | **🎉 First pixels — the game renders** | ✅ **REACHED** | with the lifter fix in, `data.toc` loads (105,216 B), the `abort()` is gone, and the RSX FIFO issues **real `DRAW_ARRAYS` / `DRAW_INDEX_ARRAY`** through the D3D12 backend. The **Scaleform attract/legal screen draws** — logos, shapes, and text via the game's own shaders. See the screenshot at the top. | | **Glyph-cache text fidelity** | ⏳ **known** | logos (static textures) are pixel-perfect; some dynamic **text runs are torn/garbled** — the 1024×1024 glyph atlas (`PS_TextTextureAlpha`) is sampled with wrong per-run texgen/UV data (the atlas itself is clean). A shader/vertex-level fidelity bug on top of a fully-working pipeline. | | **SPURS / libsre bring-up** | ✅ **init + taskset work** | libsre (Sony's `cellSpurs`/`cellSync` PRX) is lifted with the same toolchain — it had two bugs: the systemic callee-save issue above, and a re-lift **`--code-end` that excluded the import PLT** so every `bl` to `_sys_memset`/`_sys_spu_image_import` became a silent no-op (→ SPURS attribute never zeroed → `cellSpursInitializeWithAttribute`/`CreateTaskset` failed `0x80410702`/`0x80410811`). With `--code-end 0x3001DB18` the imports resolve: **SPURS init + taskset creation now succeed**, and the completion event queue (q=1) starts receiving events. (Firmware/SDK mismatch ruled out — same libsre builds *and* validates the attribute.) | | **Advance past the attract screen** | ⏳ **current frontier** | the legal/attract screen is the **logo-video screen**, waiting for `Logos.usm` to play and finish. A 5.5-min run confirms it's not timer-gated — it waits indefinitely. The cri taskset is created but **`cellSpursCreateTask` is never called**: the movie decode never starts, so no SPU decode task, so no frames, so the video never completes. Getting past it = the `cri_mpv → SPU video-decode` chain (see `CRI_MPV_CONTENT_PLAN.md`): stream/buffer the movie → cri_mpv creates the decode task → SPU decodes → real q=1 completion → video plays. 🎯 | > **🔬 War story: the 5.2 GB lift, and the one-line ISA detail that caused it.** > The first full lift produced **5.2 GB** of C++ (≈600k lines/chunk) — absurd for a > 5 MB binary (flОw's *100k+* functions were ~156 MB). The hunt: > > 1. Found one mis-detected 634 KB "function" that ran to the end of the code > segment — but clipping it changed *nothing*. > 2. Found **1,617 garbage functions at `.rodata` addresses** (`func_005xxxxx`), > each lifting tens of thousands of `/* TODO: .word */` junk "instructions." > 3. Root cause: PS3 EBOOTs pack **`.rodata` into the same R-X segment as `.text`**. > A `bc`-form (conditional branch) *data word* whose 16-bit immediate happens to > point into that rodata was promoted by the lifter to a `func_X` jump target; > the mid-function pass then re-emitted it to the next boundary — a quadratic > blowup seeded by **random data that merely decodes as a branch**. > > **Fix:** a new opt-in `--code-end` bound in `ppu_lifter.py`. The ELF section > headers say executable code ends at `0x4874E0`; any branch/call/jump-table > target at or beyond that is treated as data, not a function. Result: > **5.2 GB → 656 MB**, 131 → 19 chunks, 0 bogus rodata functions. The remaining > `.word`/`vmx` TODOs are genuine undecoded AltiVec/VMX SIMD in *real* functions > (the same class flОw carries) — implemented as the boot progresses, not bloat. --- ## First Boot `ydkj_boot.exe` loads the decrypted EBOOT, registers the 14,380 lifted functions and 357 HLE handlers, and dispatches the real entry point. It gets impressively far: ``` [ppu] loaded 2 PT_LOAD segments, entry OPD 0x005309E8 [ppu] TLS image 0x10F00000 (r13/TP=0x10F07000) [ppu] run: code 0x0005ECF4, toc 0x00544370, sp 0x0FF00000 [sys_memory] allocate(size=0xAA00000, flags=0x400) ← game requests its 170 MB main heap [sys_memory] allocate -> 0x40000000 ← granted [ppu] unresolved indirect call -> 0x39800000 (r12=0x00470870, the real target) [ppu] lv2_syscall 988 (stub) × N [ppu] FATAL: stuck calling 0x39800000 (2000 times) -- aborting run ``` The first build stalled on `bctrl → 0x39800000` (the bytes of `li r12,0`) — the **firmware-import-stub wall** that also blocks Gunstar. The cause: the lifter emitted the *literal* `.lib.stub` trampolines, which dereference an import pointer table the recomp never fills. The fix is a new generic lifter flag **`--hle-stubs`** that rewrites each of the 265 import stubs to `ps3_hle_call(nid, ctx)` — dispatching straight to the registered HLE handler. That **cleared the wall**: ``` [crt] sys_initialize_tls: block 0x0E000000, r13=0x0E007000 ← TLS init runs [sys_memory] allocate(0xAA00000) -> 0x40000000 ← 170 MB heap [hle] unresolved NID 0x24A1EA07 ← = sys_ppu_thread_create [ppu] unresolved indirect call -> 0x40000030 .. 0x40000120 ← static-init vtable sweep ``` So the recompiled CRT now runs **much deeper** — TLS, heap, and into thread creation / C++ static constructors. The next blocker is identified precisely: `sys_ppu_thread_create` (NID `0x24A1EA07`) is imported via `sysPrxForUser` but ps3recomp implements thread creation as a *syscall*, so the NID is unwired; the CRT's thread/static-init then walks an uninitialized object table at the heap base and calls heap addresses as function pointers. Bridging the `sys_ppu_thread_*` NIDs is the next step. Full trace: [`docs/BOOT_TRACE.txt`](docs/BOOT_TRACE.txt). ## Module Coverage Of the 23 firmware libraries the game imports, ps3recomp already ships real host implementations for **19**. The remaining four are all online/social and can be stubbed for an offline port. | Library | NIDs | ps3recomp status | |---|---:|---| | `sysPrxForUser` | 26 | ✅ Complete — threads, lwmutex/lwcond, heap, libc | | `cellGcmSys` | 22 | ✅ Complete — RSX command buffer + state | | `sys_fs` | 18 | ✅ Complete — file I/O w/ host path mapping | | `cellRudp` | 17 | ✅ Complete — reliable UDP | | `cellSysutil` | 17 | ✅ Complete — callbacks, system params, BGM | | `cellSpurs` | 15 | 🟡 Partial — management only (no SPU execution) | | `sys_net` | 14 | ✅ Complete — BSD sockets | | `cellAudio` | 10 | ✅ Complete — WASAPI / SDL2 mixing | | `sceNpTrophy` | 9 | ✅ Complete — persistent trophy storage | | `cellHttp` | 6 | ✅ Complete — HTTP/1.1 | | `sys_io` | 6 | 🟡 Stub — low-level pad I/O syscalls | | `cellGame` | 5 | ✅ Complete — boot/content dirs | | `cellNetCtl` | 5 | ✅ Complete — network control | | `sceNp` | 27 | ✅ Complete — NP core | | `cellSync` | 3 | ✅ Complete — sync primitives | | `cellSysmodule` | 3 | ✅ Complete — module tracking | | `cellSsl` | 3 | ✅ Complete — SSL/TLS | | `cellRtc` | 2 | ✅ Complete — real-time clock | | `cellUserInfo` | 2 | ✅ Complete — default user | | `cellSaveData` | 1 | ✅ Complete — save/load | | `sceNp2` | 28 | ⛔ **TODO** — NP2 matchmaking/rooms (stub for offline) | | `sceNpCommerce2` | 16 | ⛔ **TODO** — PSN store / DLC (stub for offline) | | `cellSysutilAvc2` | 10 | ⛔ **TODO** — audio/video (voice) chat (stub for offline) | --- ## How It Works ``` EBOOT.BIN ──ps3sce──▶ EBOOT.elf ──ppu_loader──▶ meta/*.json (SELF, encrypted) (PPC64 BE) │ (image, OPD/TOC, functions, imports) ▼ ppu_lifter ──▶ src/recomp/*.cpp (lifted PPC → C) │ ▼ ps3recomp runtime + generated HLE NID table ──CMake/clang-cl──▶ ydkj.exe (LV2 syscalls, cellXxx HLE, vm, big-endian memory) ``` `ps3recomp` does the heavy lifting on the runtime side; this repo is the **per-game project**: the decryption recipe, the analysis metadata, the module configuration, and (soon) the lifted code + boot harness. ## Repository Layout ``` project/ ├── config.toml # module map + loader/lift constants for this title ├── CMakeLists.txt # clang-cl boot harness (links ps3recomp_runtime.lib) ├── meta/ # analysis output (committed — pure metadata, no game code) │ ├── EBOOT.functions.json # 5,859 function boundaries (rodata-clipped) │ ├── EBOOT.functions.raw.json # raw detector output, for provenance │ ├── EBOOT.imports.json # 265 NIDs × 23 libraries │ ├── EBOOT.image.json # segment manifest │ ├── EBOOT.loader.json # entry / OPD / TOC │ └── elf_analysis.json # ELF headers / segments / sections ├── src/ │ ├── gen/ # generated HLE NID table (committed) │ ├── compat/ # Win32 / shims (committed) │ └── recomp/ # 656 MB of lifted PPU→C (generated; gitignored) └── input/ # YOUR decrypted EBOOT goes here (gitignored) ``` ## Reproducing the analysis ```bash # 1. Extract the EBOOT from your own disc dump 7z e "You Don't Know Jack (USA).7z" "...\PS3_GAME\USRDIR\EBOOT.BIN" -oinput/ # 2. Decrypt the retail SELF → ELF ps3sce -d input/EBOOT.BIN input/EBOOT.elf # 3. Load: image + OPD/TOC + function table + imports python /tools/ppu_loader.py input/EBOOT.elf --output meta/ # 4. Lift PPU -> C, bounding targets to .text so .rodata doesn't explode python /tools/ppu_lifter.py input/EBOOT.elf \ --functions meta/EBOOT.functions.json --output src/recomp \ --header-name ppu_recomp.h --source-name ppu_recomp \ --jobs 1 --code-end 0x4874E0 --hle-stubs meta/EBOOT.imports.json # 5. Generate the HLE NID dispatch table for the imported libraries python /tools/gen_hle_nids.py cellAudio cellGcmSys ... \ --out src/gen/ppu_hle_nids.cpp # 6. Build the boot harness (clang-cl, links the prebuilt runtime lib) cmake -S . -B build -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl cmake --build build ./build/ydkj_boot input/EBOOT.elf ``` --- ## Credits & Legal Built on [**ps3recomp**](https://github.com/sp00nznet/ps3recomp), in the lineage of [N64Recomp](https://github.com/N64Recomp/N64Recomp), [UnleashedRecomp](https://github.com/hedge-dev/UnleashedRecomp), and friends. *You Don't Know Jack* is © Jellyvision / THQ. This project contains **no copyrighted game code, assets, or audio** — only analysis metadata derived from a binary you must legally own. It exists for preservation, interoperability, and the sheer joy of making a Cell processor's worst nightmare run on a laptop. Bring your own disc.