VBlank firing in psx_check_interrupts was gated on dispatch_count (block-dispatch count, ~5-6 cycles each), not on guest cycles. 50000 dispatches ≈ 289k cycles per VBlank, vs real PSX 564480 cycles per VBlank. Effect: game-internal time ran at ~51% of real PSX rate, breaking every cycle-paced game flow including FMV. This was the root cause of TombaRecomp ISSUES.md #4 — FMV played at 6.7 fps because Tomba's FMV state machine is driven by DMA Ch1 (MDEC-out done) IRQ, which in turn fires once per full FMV-frame pipeline iteration, which is bounded by cycle-paced steps (CDROM sector ready, MDEC decode "complete", DMA transfer). At 51% guest-time rate, a Tomba-designed 15-fps FMV becomes 7-8 fps. Fix: - New VBLANK_CYCLES = 564480 (33.8688 MHz / 60 Hz NTSC). - Add interrupts_advance_cycles(cycles) called from psx_advance_cycles() so the cycle counter for VBlank gating tracks emitted guest cycles. - Change the trigger from `dispatch_count >= VBLANK_INTERVAL` to `cycles_since_vblank >= VBLANK_CYCLES`. - On VBlank fire, subtract one period (not reset to 0) so cycle overshoot from long blocks carries forward. - dispatch_count is kept for the SIO card-protocol stale check — that uses total_checks, which is the real load-bearing field there, dispatch_count itself is now incidental. Verified: - Cycles/VBlank: 289k -> 564k (exactly VBLANK_CYCLES). - Cycles/sec: 20.6 M -> 41 M (121% of real PSX; slight wall-clock overshoot to investigate, but game-internal pacing now correct). - FMV decode rate: 0.111 -> 0.250 decodes/VBlank = 15.0 fps exactly, matching Tomba's designed FMV rate. - Measured via tools/_mdec_pace.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PSXRecomp
Generic static recompiler framework for PlayStation 1 — MIPS R3000A → C → native x64.
Background on the original prototype: I Built a PS1 Static Recompiler With No Prior Experience (and Claude Code)
What it is
PSXRecomp translates a PS1 program's MIPS binary into C once, then compiles that C as a native x64 binary linked against a thin runtime. The result runs on PC without an emulator loop.
This is the v2 revision of PSXRecomp. It is a ground-up rewrite of the original public project (preserved here on the legacy-2026-05-11 branch).
The defining v2 rule: no HLE BIOS, no stubs, no interpreter for static code. v1 implemented PSX kernel calls as C "shims" that emulated what the BIOS would have done. v2 recompiles the real SCPH1001 BIOS to C and runs the recompiled BIOS as the kernel. The only interpreted code is the handful of 4-instruction dispatch stubs the BIOS installs into kernel RAM at runtime; everything else is statically recompiled native code.
PSXRecomp is a framework, not a single program. Game projects live in their own repositories and pull this one in to build a game-specific binary. The applied example is TombaRecomp.
Status
This is heavy WIP. The repository compiles and produces a working psx-runtime that boots the SCPH1001 BIOS through to the memory card management UI. Game-loading is partially implemented and is not part of the recommended use yet.
What works
| Subsystem | State |
|---|---|
BIOS recompilation (SCPH1001.BIN) |
Boots to memory card management screen |
| GPU (basic 2D, BIOS shell UI) | Renders the BIOS shell correctly |
| Memory cards (read/write, files persist on disk) | Working |
| SIO0 (pad input via keyboard) | Working |
| Interrupts, COP0, timers | Working |
| Dirty-RAM interpreter for kernel-installed stubs | Working |
What does NOT work / is untested
- CD-ROM / disc-based game loading is untested and almost certainly does not work in
psx-runtime. The CDROM controller emulation is partial and the game-EXE-load path has not been validated end-to-end in the framework binary. Loading a disc via--discmay stall or panic. - MDEC FMV playback is partial. Audio decode works (XA-ADPCM); video decode is incomplete and runs slowly.
- SPU mixes 24 direct ADPCM voices but does not yet model reverb, noise, sweep, or accurate IRQ timing.
- GPU does not implement the full PSX command set — many primitives are not yet supported.
- GTE (geometry coprocessor) coverage is partial.
Game projects that pull in PSXRecomp will hit these limits long before reaching gameplay. The TombaRecomp prototype reaches the main menu with FMVs that play slowly; in-game state is not reachable.
If you want to use this repository today, the supported path is the BIOS-only memory card editor described under "How to use" below.
Setup
Requirements
- Windows 10/11 x64 (Linux/macOS are not currently supported by the runtime build).
- Sony SCPH1001 BIOS ROM (
SCPH1001.BIN, 512 KB) — you must provide your own legally-obtained dump. Not included. - Tools (for building from source): MSYS2 with the
mingw-w64-x86_64toolchain, CMake ≥ 3.20, SDL2.
Option A — use the release binary
- Download the latest
psxrecomp-v2-windows-x64.zipfrom Releases. - Extract anywhere.
- Place your
SCPH1001.BINin the extractedbios/folder. The path must be exactlybios/SCPH1001.BIN. - Run
psx-runtime.exe. - A blank
card1.mcd/card2.mcdis created insaves/on first launch.
Option B — build from source
# MSYS2 mingw64 shell:
pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2
git clone https://github.com/mstan/psxrecomp.git psxrecomp-v4
cd psxrecomp-v4
mkdir -p bios
cp /path/to/your/SCPH1001.BIN bios/
# Recompile the BIOS to C (one-time; regenerate when the BIOS changes):
cmake -S recompiler -B recompiler/build -G "Unix Makefiles"
cmake --build recompiler/build
./recompiler/build/psxrecomp-bios --config bios/SCPH1001.toml
# Build the runtime:
cmake -S runtime -B runtime/build -G "Unix Makefiles"
cmake --build runtime/build --target psx-runtime
# Run:
./runtime/build/psx-runtime.exe
How to use
BIOS shell + memory card management
Launch psx-runtime.exe with no arguments (or with --disc ""). With no disc inserted, the BIOS goes to its built-in shell — the same shell you get on a real PS1 with no disc in the tray. From there you can:
- View memory card contents on cards 1 and 2.
- Copy save blocks between cards.
- Delete save blocks.
- Format a card.
Memory card files live in saves/card1.mcd and saves/card2.mcd. They are standard 128 KB raw PS1 memory card images and are interchangeable with the equivalent files from pcsx-redux, duckstation, epsxe, mednafen, etc. — drop them into saves/ to bring saves with you, or copy them out to share.
Keyboard map (default)
| PSX button | Keyboard |
|---|---|
| D-Pad Up / Down / Left / Right | Arrow keys |
| Cross (✕) | X |
| Square (□) | Z |
| Circle (○) | S |
| Triangle (△) | A |
| L1 / R1 | Q / W |
| L2 / R2 | E / R |
| Start | Enter |
| Select | Right Shift |
| Turbo (fast-forward, no render) | Tab (hold) |
Command-line options
psx-runtime.exe [--bios <path>] [--disc <path>] [--memcard-dir <path>] [--game-root <path>]
--bios— override defaultbios/SCPH1001.BIN.--disc— point at a.cue/.binPS1 disc image. See the warnings above; this path is not reliable yet.--memcard-dir— directory holdingcard1.mcd/card2.mcd. Defaultsaves/.
Architecture (one paragraph)
The recompiler reads the BIOS as a flat ROM at 0xBFC00000 and walks function boundaries from explicit seed addresses, emitting generated/SCPH1001_full.c (one C function per recompiled MIPS function) and generated/SCPH1001_dispatch.c (function-pointer dispatch table). The runtime loads SCPH1001.BIN into emulated PSX memory, links the recompiled BIOS as native C, simulates the hardware (GPU, SPU, CD-ROM, DMA, timers, COP0, SIO0, MDEC, memcards) via per-MMIO C, and routes BIOS calls (A0 / B0 / C0 vectors) through the recompiled functions. The only interpreted code is the handful of 4-instruction dispatch stubs the BIOS writes into RAM at boot. See CLAUDE.md for the full architecture rules.
Difference from v1
The original public version of PSXRecomp (preserved on the legacy-2026-05-11 branch) used:
- HLE BIOS shims — handwritten C reimplementations of
OpenEvent,StartCard,alloc_kernel_memory, etc., living inrunner/src/bios.c. - A MIPS interpreter for unknown code paths (
runner/src/interpreter.c, ~900 LOC). - Tomba bundled in the framework repo as the example game.
v2 (this branch) replaces all of that with real BIOS recompilation and breaks Tomba out into its own repository (TombaRecomp). The interpreter is gone except for an ~300-LOC dirty-RAM interpreter dedicated to BIOS-installed runtime stubs, which is a fundamentally different concept from v1's general-purpose fallback interpreter.
The legacy-2026-05-11 branch is preserved unchanged for anyone who needs the v1 codebase as a reference.
License
PolyForm Noncommercial 1.0.0 — see LICENSE. Non-commercial use (personal, educational, research, hobbyist) is welcome. Commercial use requires a separate license — contact via 1379.tech.
The PSX BIOS and any game disc images you supply remain copyrighted by their respective owners. This project distributes neither.
