P3P3DS is an independent research and engineering project working toward running Shin Megami Tensei: Persona 3 Portable (ULUS-10512, North American release) natively on the New Nintendo 3DS / New 3DS XL / New 2DS XL family of consoles.
Status: PLAYABLE, EARLY (New 3DS: 28-30 fps in the field, battles 16-25 fps, saves work; no sound, no opening movie)
IMPORTANT: Since 0.3.0 the game is playable on a New 3DS without sound and without the opening movie (no audio output on the 3DS and no video decoder yet). The maintainer has played on a New 2DS XL through the first days and the first battle, and 0.3.1 was tested on hardware by a user too (97-99 % speed); later parts of the game are untested. On hardware: about 28-30 fps in the dorm, 30 fps most of the time in dialogue scenes, 16-25 fps in battle (the GE back end runs on the second CPU core). See Known issues below.
On the PC development runner it boots, shows its logos, plays the opening movie (as black frames: there is no video decoder yet), reaches the title screen with audio and, driven by a scripted controller input, starts a New Game and plays through name entry, the first night in the dorm and the walk to school, where the navigation cursor responds to input. Input comes from deterministic scripts (or an XInput gamepad); there is no window yet, so frames are written as images. On a real New 3DS the game is rendered by the PICA200 GPU, with no sound yet. Saving and loading work (save/load menu on the bottom screen). The first battle renders correctly since 0.2.7 (faces, roof, sky, the Shadow; before, a back-face culling rule inverted for 3D draws turned the models inside out and the surroundings black), and its speed went from 5-7.5 fps on hardware in 0.2.7 to 16-25 fps in 0.3.0. The opening movie is skipped automatically (no video decoder yet).
Every known issue is tracked on GitHub Issues; the tables below are a summary.
| Issue | Where | State |
|---|---|---|
| ATLUS and CRIWARE startup logos were missing (#5) | first seconds after start, 0.2.8-0.3.1 | fixed in 0.3.2 by rebinding textures after presenting a frame. The fix was verified in Azahar, and the maintainer reported working logos on console. |
| Scene transitions: one or more black frames instead of a fade (#3); garbage on hardware until 0.3.0 | cutscene and scene changes | since 0.3.1 black on hardware and in Azahar alike (new render targets are filled black); in 0.3.2 a user reports normal transitions on a New 2DS XL, with no black screens or garbage (#66) |
| Light fringe around changed facial expressions in dialogue portraits (#4) | dialogue | [UNVERIFIED] linear filtering of the overlay sprite samples outside its rectangle |
Up to 0.3.3: on a slow SD card the game freezes every 5 s while report.txt is rewritten (0.3.0: the game itself waited, 36 % speed; 0.3.1-0.3.3: the rewrite runs in the background but the game's reads can wait behind it, 64 % speed in one run) |
everywhere | the report is no longer written during play unless you touch the bottom screen; not yet confirmed on hardware |
| Battle: 16-25 fps, 63-86 % of real time (0.3.0, hardware; was 5-7.5 fps in 0.2.7) (#16) | battles | game targets 30 fps; the game's own code is now the largest share (aot 63-70 %) |
| Dorm: 28-30 fps (0.3.0, hardware) (#17) | field | game targets 30 fps |
| No sound (#14) | everywhere | not implemented on 3DS (the PC runner mixes it) |
| Opening movie skipped (#19) | boot | no H.264 / ATRAC3plus decoder |
| Saves stored in their own format (#20) | sdmc:/p3p3ds/ms0/PSP/SAVEDATA/ |
not compatible with PSP or PPSSPP saves |
| Old 3DS / 2DS stops at start with "Out of memory" (#24) | boot | not supported; needs a different approach (about 112 MB needed, at most 96 MB available) |
Defects found in the code review of 2026-10-08, not fixed yet (evidence and tests: docs/CURRENT_STATE.md, "Code review"):
| Issue | Effect | State |
|---|---|---|
| Back-face culling uses opposite rules for 2D and 3D draws (PPSSPP uses one rule) (#6) | may hide another orientation bug; risk for scenes not yet seen | suspected |
| Thread stacks and the PSP heap can overlap; stacks and freed heap memory are never reused (#7) | memory corruption or out-of-memory in long sessions | reproduced in a unit test; P3P's own peak not measured |
sceIoOpenAsync returns open errors at once instead of through the async result (#10) |
a game waiting for the async result may misread a failed open | confirmed in code |
| GE finish callbacks run too early, SIGNAL handlers are never called (#11) | timing differs from the PSP | confirmed in code |
FPU/VFPU edge cases (vsin/vcos, divu by zero, NaN signs) differ from hardware (#12) |
small numeric differences | confirmed by pspautotests and host demos |
| Every file read, also from the SD card and mods, is slowed to UMD speed in game time (#17) | longer loads; a candidate cause of the dorm frame drops | suspected; test on the PC route planned |
| Save names from the game are not checked before building SD card paths (#13) | a corrupted or modded name could write or delete outside the save folder | confirmed in code |
Reports help: the bottom screen and sdmc:/p3p3ds/report.txt show the build, speed and where the time goes.
On New 3DS (details in docs/3DS_PLATFORM.md sections 8.3 and 8.5):
| Milestone | Result |
|---|---|
.3dsx build |
devkitARM, 44 MB of recompiled ARM code (cut from 77 MB), clean build in about 9 minutes |
| Boot | logos, title screen and main menu, drawn by the PICA200; logo positions pixel-identical to the PC renderer; the opening movie is skipped (no video decoder yet) |
| Speed | 0.3.0: 28-30 game frames/s in the dorm, 30 most of the time in dialogue, 16-25 in battle; 90 % of real time on average over 7.6 minutes including a battle (CPU rendering at first: 5 %, 2 fps; first battle 5-7.5 fps in 0.2.7) |
| Second CPU core | the GE back end (draw preparation and citro3d) runs on core 2 beside the game's code; start probe on hardware: core2 ok, parallel x1.98 |
| Saves | save and load at the dorm desk and from LOAD GAME, menu on the bottom screen |
| First battle | starts since 0.2.5; transform, skinning and lighting on the GPU since 0.2.6; renders correctly since 0.2.7 (culling fix); slow (see Known issues) |
| Memory | heap 71,220 KiB with 60,284 KiB in use (hardware, 2026-10-02; 12 MiB linear heap); about 10.7 MiB free |
| Screens | top: the game; bottom: authorship and debug data (fps, speed, where the time went over the last 10 s, memory), also saved to sdmc:/p3p3ds/report.txt at the start, on exit and when you touch the bottom screen |
Measured on the PC runner (ULUS-10512, details and evidence in docs/CURRENT_STATE.md):
| Milestone | Result |
|---|---|
Whole .text statically recompiled |
237 C++ units, 751,674 instruction PCs; leftovers run through an interpreter fallback (0 mismatches vs AOT on 1,200 differential cases) |
| Boot | ThreadMan, SysMem, IoFileMgr (UMD ISO + memory stick + the community mod chain), ModuleMgr, UMD, Display services drive the game through CRI middleware startup |
| First rendered frames | ATLUS and CRIWARE logos drawn by the GE display-list executor + software renderer |
| Opening movie | 100 s PSMF movie demultiplexed by sceMpeg (on the pspautotests sample movie, container behaviour matches PSP hardware output line for line); picture and movie audio are placeholders (black / silence) |
| Title screen | "PRESS ANY BUTTON" from vblank 6627 (~110 s virtual time), menus at 30 frames/s, title music recorded to WAV |
| Main menu and New Game | START opens NEW GAME / LOAD GAME / CONFIG / DATA INSTALL; NEW GAME loads the protagonist selection scene ("Welcome to the world of P3P.") |
| First game day | Dark Hour dorm lobby (3D), name entry, Mitsuru introduction, own room with the navigation cursor, next morning with Yukari, school main lobby; 38 virtual minutes without a blocker |
| Stability | 38,314 frames (24 min virtual) of the attract loop without a blocker; runs with the same input script are bit-for-bit deterministic |
| Tests | CTest suites for HLE contracts, renderer, input and AOT/interpreter differential, several replaying pspautotests hardware transcripts |
- Analyze P3P executable structure (ELF32 PRX, segments, sections)
- Full PRX relocation and library import table extraction (178,513 relocations, 221 import stubs)
- Independent relocation-aware validation tooling matching PSPRecomp 100%
- Whole-
.textAhead-of-Time (AOT) MIPS-to-C++ generation in fixed 16 KiB units, with an interpreter fallback - Core PSP kernel services (SysMem, ThreadMan with waits/callbacks/virtual time, UtilsForUser, ModuleMgr)
- Virtual File System: UMD ISO9660, memory stick, CWCheat "Mod Support" chain (
bind/→mod.cpk→mod1-3.cpk→ original CPKs) - GE display-list execution with a reference software renderer (first visible frames, title screen)
-
sceAudio,sceSasCoreand PCM output to WAV on PC -
sceMpegcontainer/ringbuffer behaviour (PSMF demux) - H.264 / ATRAC3plus decoding for the opening movie
- Controller input: scripted (deterministic) and XInput gamepad
- Live window and audio device on PC
- Main menu and New Game start
- Name entry, introduction and first controllable section (scripted input)
- Complete renderer (filtering, lines, lighting/skinning as the game needs) and movie decoding
- PICA200 (citro3d) renderer: logos, title screen, main menu
- P3P3DS Builder: ISO ->
.3dsxon the user's PC (Windows wizard) - Runs on a real New 3DS (about 30 fps in the first days)
- Savedata on hardware (bottom-screen menu)
- Battles rendered correctly (lighting, skinning on the GPU)
- Battles at full speed, sound (ndsp), movie decoding
- ndsp audio, live controls tested and
.ciafor New 3DS - Playable game on New Nintendo 3DS hardware (early: first days and first battle, no sound, no opening movie)
There is no ready-made download of the game: the .3dsx contains code generated from the game's executable (and the decrypted executable itself), so it must never be shared. Instead, the P3P3DS Builder turns your own copy of the game into p3p3ds.3dsx on your PC.
You need:
- a New 3DS / New 3DS XL / New 2DS XL with custom firmware and the Homebrew Launcher (Old 3DS models are not supported yet);
- your own Persona 3 Portable
ULUS-10512ISO; - Windows 10/11 or Linux x86-64, and devkitPro with the 3DS Development component (Windows installer, or
dkp-pacman -S 3ds-devon Linux; the builder uses its ARM compiler and 3DS libraries).
- Download
P3P3DS-Builder-<version>.zipfrom the GitHub Releases page and unpack it anywhere. - Run
P3P3DS-Builder.exe, choose your ISO (devkitPro is found automatically when installed in the usual place) and press Build. - Wait about 5-15 minutes: the game's code is translated to C++ and compiled for the 3DS on all CPU cores, with a progress bar, a time estimate and Persona 3 trivia. An interrupted build continues where it stopped.
- The builder can copy everything to your SD card itself; otherwise copy as shown below.
When a newer Builder is released, the window shows Update: Builder X.Y.Z is available in its bottom left corner; the button opens the release page. The Windows wizard only asks GitHub for the latest release tag when it starts (nothing is downloaded or installed, no identifiers are sent); --no-update-check or the environment variable P3P3DS_NO_UPDATE_CHECK=1 turns that off, and --cli builds never check. P3P3DS-Builder.exe --check-update prints the result in a console, --version shows the version.
On Linux the builder is a console program (P3P3DS-Builder-<version>-linux.tar.gz): ./P3P3DS-Builder --iso <iso> [--devkitpro <dir>] [--out <file>] [--jobs <n>]; devkitPro is looked up in $DEVKITPRO, then /opt/devkitpro. On NixOS run it inside p3p-3ds from shell.nix, where devkitPro's prebuilt binaries work.
The builder package (about 2 MB zipped) contains no game data: only the prebuilt P3P3DS runtime for the 3DS, the code generator and the community CWCheat patch list; everything made from the game is generated on your PC. Measured clean builds on a 12-thread CPU: 4 min 23 s with 12 compiler jobs, 4 min 55 s with 7 (the builder picks the number from CPU cores and free memory).
- Prepare the game files once:
python tools/prepare_game.py "/path/to/Persona 3 Portable.iso"(Game Setup). - Build the PC tools (Build), then the 3DS runner (Build for New 3DS). The result is
build/3ds/p3p3ds.3dsx.
- Copy to the microSD card:
The runner creates
SD card ├── 3ds/ │ └── p3p3ds.3dsx <- build/3ds/p3p3ds.3dsx └── p3p3ds/ └── <any name>.iso <- your ULUS-10512 image (the first .iso found is used)p3p3ds/ms0/(memory stick),p3p3ds/mods/(mod chain, see below) andp3p3ds/report.txtitself. - Start P3P3DS from the Homebrew Launcher.
The top screen shows the game, the bottom screen authorship and a live debug report (fps, speed, memory, time split), also written to sdmc:/p3p3ds/report.txt at the start, on exit (START+SELECT) and whenever you touch the bottom screen — please attach it to bug reports. An empty sdmc:/p3p3ds/report_live.txt also writes it every 5 s, for runs that crash before you can quit; on a slow SD card that can make the game freeze for a moment.
| 3DS | PSP |
|---|---|
| B / A / Y / X | Cross (confirm) / Circle (back) / Square / Triangle — by position |
| D-Pad, Circle Pad | D-Pad, analog stick |
| L / R, START, SELECT | L / R, START, SELECT |
| START + SELECT | quit P3P3DS |
What to expect today: logos, title screen, main menu, NEW GAME and the following scenes, rendered by the GPU at about 30 fps on a New 3DS (the runner never runs faster than real time); no sound; battles start but render almost black (no lighting or skinning yet); the opening movie is skipped automatically (no video decoder yet; an empty sdmc:/p3p3ds/play_movies.txt disables the skip); saves are chosen in a menu on the bottom screen (D-Pad, B confirm, A back) and stored in sdmc:/p3p3ds/ms0/PSP/SAVEDATA/ - they are not compatible with PSP or PPSSPP saves; 3D scenes have no lighting yet.
Old 3DS / 2DS cannot run P3P3DS yet. Support is planned, but not soon: today it needs about 112 MB of application memory and the New 3DS CPU speed, while the Old 3DS offers at most 96 MB and a 268 MHz CPU without L2 cache, so it needs a different approach rather than tuning (see docs/NEXT_STEPS.md).
Memory [UNVERIFIED on hardware]: the runner needs about 112 MB of application memory (44 MB of code, 32 MB of PSP RAM, the runtime and GPU buffers). If the bottom screen reports that it ran out of memory, the Homebrew Launcher was started with a smaller memory mode than the game needs.
Emulator: Azahar works too — enable New 3DS mode, put the ISO in its virtual SD card under p3p3ds/, and open the .3dsx.
The long-term goal of this project is a smooth, high-fidelity experience of Persona 3 Portable running natively on New Nintendo 3DS hardware, with support for user-supplied community mods and fan translations.
We are not building a general-purpose PSP emulator, nor an all-encompassing PPSSPP replacement. The scope is specifically tailored to Persona 3 Portable.
Persona 3 Portable is uniquely well-suited for a static recompilation port to the New Nintendo 3DS:
- Visual Novel Navigation: The 2D exploration and dialogue format of P3P maps naturally to dual-screen handheld hardware.
- Relatively Low VFPU Density: Only ~0.31% of the instruction stream uses PSP VFPU vector instructions (compared to heavily math-intensive titles like Monster Hunter or racing games).
- Atlus Script Engine: Story flow and cutscene logic run via an internal bytecode engine (
.bf), meaning gameplay scripting is isolated from low-level MIPS machine code. - No Evidence of Self-Modifying Code Observed So Far
[UNVERIFIED]: Static analysis shows standard relocatable code with clean function prologues/epilogues; runtime verification across deeper gameplay loops remains ongoing.
The project employs Hybrid Static Recompilation (AOT):
Decrypted P3P Executable (Allegrex MIPS ELF) + community CWCheat patches
│
▼
Offline Static Recompiler (recomp/PSPRecomp, psp_recomp --auto)
(lowers all of .text to 237 C++ translation units of 16 KiB each)
│
▼
Native Host C++ Compilation
(GCC on PC, devkitARM GCC on New 3DS)
│
▼
P3P3DS Runtime (core/)
├── Interpreter fallback for PCs without an AOT entry
├── PSP HLE kernel: SysMem, ThreadMan (deterministic virtual clock),
│ IoFileMgr, ModuleMgr, UMD, Display, Ctrl, Audio, SasCore, Mpeg, ...
├── Virtual File System (UMD ISO9660 / host directories, mod overlay)
├── GE display-list executor → GeRenderer interface
│ └── SoftwareRenderer (reference backend, draws into guest VRAM)
└── Platform backends:
├── platform/pc — development runner: frame dumps, WAV output, event traces
└── platform/3ds — New 3DS runner (.3dsx): top screen game, bottom screen
debug report; Citro3D renderer + NDSP audio planned
- Game Machine Code: Recompiled offline into native C++ translation units; PCs the static analysis missed are executed by an interpreter whose semantics mirror the code generator (checked by a differential test).
- PSP Kernel & Services: A lightweight High-Level Emulation (HLE) runtime implementing only what P3P imports, with contracts taken from uOFW, PSPSDK and pspautotests hardware output.
- Graphics Engine (GE): Display lists are executed in
core/; a renderer interface lets the PC reference rasterizer and a future PICA200 (citro3d) backend share the same command processing. - Audio: Guest PCM (
sceAudio,sceSasCore, CRI middleware) is mixed on the virtual clock; on 3DS it is intended for the hardware DSP (ndsp).
This project targets the New Nintendo 3DS / New 3DS XL / New 2DS XL exclusively (Old 3DS / 2DS is not supported yet; planned for much later):
- CPU: Quad-core ARM11 MPCore @ 804 MHz with L2 cache enabled via
osSetSpeedupEnable(true)(vs 268 MHz on Old 3DS). - RAM: 256 MB FCRAM (vs 128 MB on Old 3DS). The
.3dsxcurrently holds 44 MB of recompiled code plus the 32 MB guest RAM and the runtime; a.ciacan request a larger memory mode. - Worker Core: Core 2 needs exheader kernel flag
0x2000(libctruthread.h); whether the.3dsxcan use it under the Homebrew Launcher is not yet tested. The runtime currently uses only Core 0 (docs/3DS_PLATFORM.mdsection 3).
While keeping classic 1:1 PSP presentation intact, the architecture is designed to support enhanced dual-screen modes:
- Top Screen (400x240): 3D Tartarus exploration, battle scenes, and animated event sequences.
- Bottom Screen (320x240): Dialogue boxes, choice selections, minimaps, party status, and touch-screen shortcuts.
Modding support and fan translations are first-class architectural requirements:
- Language-Agnostic Core: No language strings, fonts, or specific localization hacks will be hardcoded into the runtime engine.
- VFS Fallback Pipeline: P3P on PSP loads mods through the community CWCheat "Mod Support" patch (
p3p/p3p-patches), which the recompiler applies to the executable. The runtime serves the paths it opens, mapped to the SD card on 3DS:On PC,sdmc:/p3p3ds/mods/bind/<relative_path> (loose file overrides, ms0:/PSP/P3P/bind/) ↓ sdmc:/p3p3ds/mods/mod.cpk (user mod package) ↓ sdmc:/p3p3ds/mods/mod1.cpk ... mod3.cpk (additional mod archives) ↓ disc0:/PSP_GAME/USRDIR/umd0.cpk, umd1.cpk (original game archives)--mods <dir>mapsms0:/PSP/P3Pto a host directory. - Texture packs (work in progress, #23, #56): existing P3P packs are PPSSPP packs (
textures.ini+ PNG).tools/texpack(Rust, host only) checks a pack and encodes PNG into New 3DS texture data; the runtime does not load packs yet. Fromtools/texpack:cargo run --release -- inspect <pack dir>andcargo run --release -- encode <image.png> --format rgba8|rgb8|rgb565|rgba5551|rgba4 --out <file>(the same bytes as devkitProtex3ds -r -z none). The runtime computes PPSSPP-compatible texture keys (core/include/p3p3ds/ge/texture_replacement.hpp); to see how much of a pack a PC run would find:cargo run --release -- keys <pack dir> > keys.txt, thenp3p_pc_bootstrap ... --texture-keys keys.txt --texture-keys-report keys.csv(prints[TEXKEYS], the CSV lists every texture key and whether the pack has it).cargo run --release -- build <pack dir> <out dir> [--max-size 512] [--format auto|rgba8|...]converts a pack into the New 3DS pack format (index.bin+textures.bin, documented intools/texpack/src/build.rs): images downscaled to at most--max-sizetexels per side, RGB565 / RGBA5551 / RGBA4 chosen from the alpha channel. Using a converted pack: copyindex.binandtextures.bintosdmc:/p3p3ds/textures/on the New 3DS (the bottom-screen report'stexpack:line shows the texture count, replaced uploads, KiB and the milliseconds spent reading them), or run the PC runner with--texture-pack <out dir>. - Translation & Mod Support (Future Goal): The architecture aims to support community translations (such as Russian, Spanish, German, French) and mods via VFS redirection. Note that translations are not guaranteed to be drop-in asset-only packages: individual localizations may require custom font sheets, character encoding tables,
.bmd/.bfscript handling, or runtime executable hooks (such as glyph-spacing/kerning adjustments).
P3P3DS/
├── 3ds/ # (not in git) 3DS library sources for reading: libctru, citro3d, picasso
├── core/ # Target-agnostic runtime: HLE kernel, VFS, interpreter, GE executor + software renderer
├── docs/ # Current state, architecture, verification registry, research notes
├── experiments/ # Standalone analyses (decoder audit, CPK checker, microtests)
├── platform/
│ ├── pc/ # PC development runner (frame dumps, WAV output, event traces)
│ └── 3ds/ # New 3DS runner (.3dsx), built with devkitARM
├── profiles/p3p/ # P3P profile: AOT layout, patches, addresses, game inputs (local only)
├── recomp/PSPRecomp/ # Static recompilation framework the build uses (other recompilers: not in git)
├── references/ # (not in git) reference emulators and hardware autotests (PPSSPP, pspautotests, uOFW)
├── psp/ # (not in git) PSP SDK, VFPU documentation, Ghidra definitions
├── tests/ # CTest suites (HLE contracts, renderer, differential AOT/interpreter)
└── tools/ # Build and analysis scripts (asset tools such as CriFsV2Lib: not in git)
To build the PC runner and development tools:
- CMake (version 3.20 or newer)
- Ninja
- C++20 Compiler (measured with GCC 16.1 / MinGW-w64; other compilers are untested)
- Python 3.10+ (code generation helpers, analysis and verification scripts)
- About 5 GB of RAM and ~11 minutes on a 12-thread CPU for a clean build of the 237 generated units (
-j10) - Optional: Zig 0.17.1 for
tools/bindiffandtools/cpk(CPK inspection); .NET SDK only forexperiments/cpk-check, the independent check oftools/cpk - For the New 3DS build: devkitPro with the
3ds-devpackages (devkitARM, libctru, citro3d, picasso, 3dsxtool)
This repository contains no copyrighted game assets or proprietary executables. You must supply files from your own legally owned copy of Shin Megami Tensei: Persona 3 Portable (ULUS-10512).
Run the automated game preparation tool pointing to your retail ISO image:
python tools/prepare_game.py "/path/to/Persona 3 Portable.iso"The tool automatically:
- Verifies ISO9660 disc integrity and
PARAM.SFOmetadata (ULUS10512). - Decrypts the Allegrex executable (
EBOOT.BIN->profiles/p3p/game/eboot.elf) via the PSP AES-128 engine. - Validates the decrypted binary against the verified reference SHA-256 (
be2abbd4...). - Extracts game assets (
USRDIR/containing CPK archives) intoprofiles/p3p/game/USRDIR/.
Steps 1-3 use build/p3p_prepare_elf when the host build has it (the same C++ code as the P3P3DS Builder; the host CMake project builds it without any game files). Without it, the script uses its own Python implementation. --native-tool <path> picks another build of the tool, --python-only forces the Python path.
(All extracted files in profiles/p3p/game/ are strictly local and gitignored.)
(Optional fallback: If you already have a pre-decrypted ELF, pass --decrypted-eboot /path/to/eboot.elf.)
The runner reads game data directly from the ISO at run time: keep the .iso in the repository root (it is gitignored) or pass --umd <iso>.
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build -j10
ctest --test-dir build -j6-DP3P_AOT_TRANSFER_RECORDS=OFF generates code without per-branch diagnostics (smaller and ~17% faster on PC, but without the transfer trace used by the frontier tooling).
Needs devkitPro with the 3DS packages and the host build above (it provides psp_recomp). devkitPro's CMake toolchain only works from its own msys2 shell:
export DEVKITPRO=/opt/devkitpro DEVKITARM=/opt/devkitpro/devkitARM
cmake -S platform/3ds -B build/3ds -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake -DCMAKE_BUILD_TYPE=Release
make -C build/3ds -j10A clean build takes about 9 minutes; after a runtime change only a few files and the link are rebuilt. Installing and running it: Running on a New 3DS. The .3dsx embeds code generated from your game executable: it is for your own use, never for distribution.
Debugging aids: a number N in sdmc:/p3p3ds/dump_every.txt saves the top screen every N vblanks to sdmc:/p3p3ds/frames/ (compare with the PC runner's --frames-dir). An empty sdmc:/p3p3ds/no_vertex_cache.txt turns the model vertex cache off, for speed comparisons (bottom-screen line vtx: fill ... cache hit ...%). The GE renderer runs on the New 3DS's second application core when the start-up check finds it (cores : line); an empty sdmc:/p3p3ds/no_gpu_thread.txt keeps it on the main core. An empty sdmc:/p3p3ds/gpu_trace.txt writes the first 1,200 presented frames and their screen-space draws (target, texture, blend, vertices) to sdmc:/p3p3ds/gpu_trace.log.
cmake --build build --target p3p3ds_builder # P3P3DS-Builder.exe (Win32 wizard) / P3P3DS-Builder (Linux console), static
python platform/pc/builder/make_release.py --version 0.1.0 --devkitpro <devkitPro folder>make_release.py needs the host build (psp_recomp.exe) and the New 3DS build in build/3ds/ (runtime objects, CWCheat patch list); it packages the build of the platform it runs on: out/P3P3DS-Builder-<version>/ and a .zip on Windows, out/P3P3DS-Builder-<version>-linux/ and a .tar.gz on Linux. P3P3DS-Builder.exe --cli --iso <iso> [--devkitpro <dir>] [--jobs <n>] runs the same build without the window. Sources: platform/pc/builder/ (pipeline, EBOOT decryption, wizard, trivia).
tools/bindiff is an optional host-only CLI, separate from the CMake/runtime and Builder builds. It compares arbitrary files positionally, or decrypted ELF32 little-endian MIPS/PSP PRX headers, segments and sections with --elf. It does not decrypt files, apply relocations or match functions; use the existing preparation and psp_analyze tools for those stages.
From the repository root, with Python 3.12+ and Zig 0.17.1:
python tools/bindiff/check.py # format check, Debug/safe unit tests, safe build, CLI tests
python tools/bindiff/check.py --download # alternatively download the pinned official Zig, verify SHA-256, then check
build/bindiff/bin/p3p-bindiff --json old.dat new.dat
build/bindiff/bin/p3p-bindiff --elf --json old.elf new.elfOn Windows use p3p-bindiff.exe. --zig <path> selects an existing compiler. The helper keeps compiler downloads and all caches under .cache/, temporary fixtures under .tmp/, and the executable under build/bindiff/; --download supports x86-64 Windows/Linux and does not modify PATH. CI uses the same helper on both systems; the compiler version is pinned in tools/bindiff/zig-version and enforced by build.zig.
Exit codes: 0 byte-identical, 1 different, 2 usage/input/output error. --help, --version and -- (for paths starting with -) are supported. Inputs are limited to less than 256 MiB each. Comparison is positional: an insertion can change all subsequent offsets. changed_bytes counts mismatches in the common prefix; added_bytes/removed_bytes count the extra tail. Ranges are half-open [start,end) and include missing tails. Only the first 256 ranges are emitted; range_count and totals remain complete and ranges_truncated signals the limit.
JSON schema version 1 includes mode, input paths, byte equality, bytes totals/ranges, and an optional elf object with both headers and explicit header/segment/section changes. ELF entries are paired by table index. Added/removed entries have a null side; modified entries include both versions, with file offsets and ELF virtual addresses (no runtime load base added). content_equal compares file payloads and is null for missing entries or NOBITS. Section names are UTF-8 strings, or byte arrays when not valid UTF-8. Unsupported ELF class/endianness/machine, extended numbering, encrypted ~PSP, PBP containers and malformed metadata are rejected with code 2; byte mode can still compare their raw bytes.
tools/cpk is an optional host-only CLI that reads CPK archives straight from the ISO, or a loose .cpk such as a mod.cpk, without the .NET SDK. Build and test it from the repository root, with Python 3.12+ and Zig 0.17.1:
python tools/cpk/check.py # format check, Debug/safe unit tests, safe build, CLI tests
python tools/cpk/check.py --download # alternatively download the pinned official Zig, verify SHA-256, then check
build/cpk/bin/p3p-cpk list game.iso /PSP_GAME/USRDIR/umd0.cpk .bmd
build/cpk/bin/p3p-cpk list mods/mod.cpk
build/cpk/bin/p3p-cpk extract game.iso /PSP_GAME/USRDIR/umd0.cpk data/battle .tmp/extract
build/cpk/bin/p3p-cpk diff mods/mod.cpk game.iso /PSP_GAME/USRDIR/umd0.cpk /PSP_GAME/USRDIR/umd1.cpk
build/cpk/bin/p3p-cpk diff mods/bind game.iso /PSP_GAME/USRDIR/umd0.cpk /PSP_GAME/USRDIR/umd1.cpk
build/cpk/bin/p3p-cpk bind mods/bind my-mod game.iso /PSP_GAME/USRDIR/umd0.cpk /PSP_GAME/USRDIR/umd1.cpk
build/cpk/bin/p3p-cpk check-trace game.iso .tmp/io-trace.csv ms0:/PSP/P3P=.tmp/modslistprints directory and name, offset, stored and extracted size, and whether the file is CRILAYLA-compressed; the optional last argument is a case-insensitive substring ofdir/name. An archive it can't read fully is an error, which makes it a quick check of a communitymod.cpk.extractwrites files under their CPK directories and decompresses them. All output paths are checked before the first write:.., absolute paths and names that are unsafe on Windows stop the extraction.diffshows what amod.cpk, abind/folder or an Aemulus package does to the game's archives: each file isadded,changedorsame, by extracted content, so a stored copy of a compressed file counts as the same. A file under a folder named after a game file, such asdata/init/a.tmxfordata/init.bin, isin-container: a mod manager has to build it into that file first. Names match without regard to case, and a match that differs in case is markedcase-differs.bindmakes a mod usable without Aemulus: it writes abind/folder with the changed files and rebuilds the game's containers around thein-containerfiles. Copy that folder tosdmc:/p3p3ds/mods/bind/; this was tested in Azahar, not yet on a console. A file it can't place, such as one inside an.spr, is listed asunresolved, and the exit code is 1.patch <source> <patch.xdelta> <output>applies the VCDIFF patches that mods ship, such as an EBOOT patch, and checks their checksums;p3p-bindiff --elfthen shows what changed.check-traceverifiesp3p_pc_bootstrap --io-trace: each read of a.cpkmust be metadata or lie inside one file with the same FNV-1a 64.
Exit codes: 0 success, 1 check-trace found differences, diff found anything but same, or bind left files unresolved, 2 usage/input/output error. On Windows use p3p-cpk.exe. Keep game files and extracts under .tmp/.
Without the game files (no profiles/p3p/game/eboot.elf) both CMake projects build everything that does not come from the game: the host runtime, tools, P3P3DS Builder and tests (the AOT code, the PC runner and the tests that run game code are left out), and the New 3DS runtime the Builder ships (no .3dsx). GitHub Actions uses this:
.github/workflows/ci.yml, on every push tomasterand every pull request: Linux (GCC 14) and Windows (MSYS2 UCRT64 GCC) host build + CTest, New 3DS runtime in thedevkitpro/devkitarmcontainer (which also regenerates thetools/texpackfixtures with itstex3dsand fails on any difference), andtools/texpackclippy + tests with the pinned Rust toolchain.- The same workflow independently builds/tests BinDiff and the CPK tool on Windows/Linux using SHA-256-verified Zig 0.17.1 archives and synthetic fixtures; it requires no game data.
.github/workflows/automerge.yml: a pull request opened by the maintainer (not a draft) is merged automatically, with a merge commit, once the required CI jobs pass (the host and New 3DS builds, texpack, and BinDiff and the CPK tool on both systems);masterrequires them and takes changes only through pull requests..github/workflows/release.yml: pushing a tagv<version>that matchesFileVersioninplatform/pc/builder/builder.rcbuilds the New 3DS runtime (debug info stripped), the Windows and Linux Builder packages withmake_release.py, and publishes them as a GitHub release (an existing release of that tag gets the files replaced). Started by hand (Actions -> Release -> Run workflow, with a branch or commit) it only builds the packages as workflow artifacts, for a dry run.
To release: bump the version in builder.rc in a pull request; after it is merged, tag master: git tag v<version> and git push origin v<version>.
# Boot until the title screen (~45 s wall), dumping every 60th frame and all audio
./build/p3p_pc_bootstrap.exe --run-until-blocker --max-dispatches 20000000 \
--frames-dir .tmp/frames --frame-every 60 --wav .tmp/p3p.wav --dump-events .tmp/run.jsonThe runner prints the stop reason and a summary; --dump-events writes a JSON trace of HLE calls, thread switches, GE lists, frames (with hashes) and audio statistics.
| Option | Purpose |
|---|---|
--run-until-blocker |
run until a missing service, a fault or the dispatch budget |
--max-dispatches <n> |
dispatch budget (20M ≈ title screen) |
--umd <iso> |
game image mounted as disc0: (default: the single *.iso in the working directory) |
--ms0 <dir> / --mods <dir> |
memory stick root (default out/ms0) / directory mapped to ms0:/PSP/P3P for mods |
--input <file> |
vblank-keyed controller script, e.g. profiles/p3p/input/new_game.txt (format in core/include/p3p3ds/input.hpp) |
--gamepad |
read XInput controller 0 |
--profile |
wall-time split between HLE, GE rendering and interpreter |
--frames-dir <dir> --frame-every <n> |
write displayed frames as BMP |
--wav <file> |
mix all game audio on the virtual clock into a 44.1 kHz stereo WAV |
--dump-events <json> / --io-trace <csv> |
execution trace / file reads (for p3p-cpk check-trace) |
--no-interpreter |
stop at PCs without AOT code instead of interpreting |
--verify-bootstrap |
fixed early-boot checkpoint used by CTest |
Execution is deterministic: two runs with the same options produce byte-identical traces, frames and audio.
bash experiments/p3p-analysis/reproduce_analysis.shdocs/CURRENT_STATE.md— what works today, with evidence for every claim.docs/NEXT_STEPS.md— roadmap and the movie/audio decoding plan.docs/VERIFICATION.md— registry of technical claims and their status.docs/3DS_PLATFORM.md— New 3DS backend design and measurements.AGENTS.md,CLAUDE.md— engineering rules for contributors and coding agents; project skills live in.claude/skills/.
The original P3P3DS code and documentation are released under the MIT License. Third-party code in this repository: recomp/PSPRecomp (MIT) and p3p/p3p-patches (community CWCheat list); both keep their own licenses. recomp/PSPRecomp/profiles/vcs/ (another game's profile, with FFmpeg headers) is part of the upstream PSPRecomp tree and is not used by this build. The research sources (references/, psp/, 3ds/, the other recompilers in recomp/, the asset tools in tools/, the P3P mod menu) are not part of the repository: python tools/fetch_references.py clones them from their upstreams (the build does not need them; the tests that compare against pspautotests skip without it). Game data is never part of the repository.
- This project is an independent reverse-engineering and open-source research effort.
- It is not affiliated with, endorsed by, or connected to Atlus, SEGA, Sony Interactive Entertainment, or Nintendo.
- No proprietary game binaries, copyrighted assets, encrypted firmware keys, or commercial code are distributed in this repository.
- All reverse engineering data and HLE implementations are derived from independent analysis, open-source references (
pspsdk,uofw), and publicly available community research.
P3P3DS stands on many open projects. Full inventory with licences and how each is used: docs/REPOSITORIES.md. Code is never copied from GPL emulators; they are read as behaviour references.
Static recompilation
- PSPRecomp by Jessica Natalia — C++20 static recompilation framework (vendored in
recomp/PSPRecomp, with small P3P3DS patches). - Yakumo by TeamGDB — Static recompilation port of Monster Hunter Portable 3rd HD Ver.
- PSP-recompilation-project by sal063, psprecomp by sp00nznet, psprecomp by wizardengineer — other PSP static recompilers, design comparison.
PSP behaviour and hardware
- PPSSPP by Henrik Rydgård & contributors — PSP emulation ground truth and HLE reference.
- pspautotests — Hardware behavioral test suite (the HLE tests replay its expected outputs).
- uOFW — Reverse-engineered PSP firmware modules (kernel, ThreadMan, IoFileMgr, audio).
- PSPSDK, vfpu-docs, prxtool, psp-ghidra-scripts by pspdev — SDK headers and GE command list, VFPU documentation, PRX and NID tooling.
- ghidra-allegrex by kotcrab — Allegrex/VFPU processor module for Ghidra.
- JPCSP — Independent PSP emulator, second behaviour reference.
Nintendo 3DS
- libctru, citro3d, citro2d, 3ds-examples and picasso by devkitPro — 3DS homebrew SDK, GPU library, samples and the PICA200 shader assembler.
- nihstro by neobrain — PICA200 shader assembler/disassembler, encoding cross-check.
- Azahar — 3DS emulator used for testing builds.
- DaedalusX64-3DS by MasterFeizz — Battle-tested Citro3D rendering and NDSP audio pipeline for MIPS on 3DS.
- GLASS by kynex7510 — OpenGL ES 2 layer over the PICA200, platform research.
Persona 3 Portable and modding
- zarroboogs & DniweTamp — P3P community patches and reverse engineering research.
- Atlus-Script-Tools, Amicitia and AtlusFileSystemLibrary by TGE, AemulusModManager by TekkaGB, CriFsV2Lib by Sewer56, CriPakTools by esperknight — Atlus script, archive and CPK tools.
- Persona 3 Dual by the p3d-project team — Nintendo dual-screen Persona UI/presentation and constrained handheld-rendering reference.
Other static recompilation and runtime references
- XenonRecomp by hedge-dev — Xbox 360 PPC-to-C++ static recompilation architecture/analysis reference.
- ReXGlue by Tom Clay & contributors — Xbox 360 AOT runtime and Xenia-derived platform architecture reference.
- Xenia by Ben Vanik & contributors — Kernel, memory, and GPU architecture reference for Xbox recomp ecosystem.
- N64Recomp by Mr-Wiseguy & contributors — MIPS static recompilation, indirect calls, relocations and jump-table reference.
- N64ModernRuntime, Zelda64Recomp and UnleashedRecomp — runtimes and complete recompiled games.