Skip to content

feat(psp): a generic runtime that reads app.pocket, and a TypeScript repack into a Memory Stick zip - #561

Merged
doodlewind merged 1 commit into
mainfrom
feat/runtime-psp-vita
Oct 6, 2026
Merged

doodlewind merged 1 commit into
mainfrom
feat/runtime-psp-vita

Conversation

@doodlewind

@doodlewind doodlewind commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Phase 1 of per-device runtimes for Pocket Studio (plan approved by Evan 2026-10-07): one prebuilt PocketJS runtime per device, repacked with a game's files and identity, with no native compile per game. This PR is the PSP part and the shared repack pieces; the PS Vita follows in a second PR.

What changes

Runtime (bun tools/runtime.ts psp → dist/runtime/psp/EBOOT.PBP + runtime.json): tools/psp.ts --runtime builds hosts/psp with the new cargo feature runtime and POCKETJS_EMBED_APP=0. At boot, on the module's main thread (rust-psp has changed to the EBOOT's folder, argv[0] up to its last /) and before the first allocation, src/package_file.rs reads app.pocket into one kernel block, then pocketjs_core::package::select_guest checks the footer hash, the psp variant and host ABI 1. The arena reserves the rest at its first allocation, as before. A missing or refused file shows one line on the debug screen (No app.pocket in this game's folder (next to EBOOT.PBP)., app.pocket is damaged (its footer hash does not match)., app.pocket has no psp variant., app.pocket is built for host ABI 2; this runtime is host ABI 1., …) and parks; HOME exits. The embedded build path is unchanged; build.rs refuses runtime together with an embedded app or a launcher registry. ui.debugStats() gains a memory object (arena capacity, largest free block at arena reservation, bump, tail, appFile bytes, current largest free block).

Repack (tools/repack/psp.ts, repackPsp(input)), TypeScript only, no file system, no native tool, on the shared admission the 3DS repack (#562) added (readRuntime, admitPocket, checkIdentity: runtime files against runtime.json SHA-256, footer, variant, host ABI, app id = identity id):

  • .zip with PSP/, PSP/GAME/, PSP/GAME/Studio<Name>/, EBOOT.PBP, app.pocket (folder from pocket-studio's rule: Studio + the title's ASCII words, then the id's last segment).
  • EBOOT.PBP: PARAM.SFO written again with the runtime's keys, the game's TITLE (room 128, cut to 127 bytes of UTF-8) and MEMSIZE = 1 (52 MB user partition on a PSP-2000/3000 started from the XMB; the runtime holds the whole game file); ICON0 = identity.icon fitted into 144×80 on transparency; DATA.PSP unchanged.
  • app.pocket = the .pocket thinned to its psp variant (variant hash unchanged, footer recomputed).
  • Refuses a runtime for another target, a broken footer, a package without a psp variant, a host ABI that differs from runtime.json, a runtime file that differs from runtime.json, and a plan that asks for io.offload (the runtime has no offload slot).

Shared pieces: tools/repack.ts and tools/runtime.ts (from #562) gain a psp line beside 3ds and android. The zip comes from #563's tools/repack/shared/zip.ts. New: tools/repack/shared/sfo.ts (PSF 1.1 writer/reader with per-key room, used by the Vita PR too); scale.ts gains fitRgba.

tools/pocket-pack.ts build gains --project-root so a game outside the PocketJS checkout (a Studio starter) packs.

Reproduce

bun tools/pocket-pack.ts build --manifest <starter>/pocket.json --project-root <starter> --target psp --target vita -o twenty48.pocket
bun tools/runtime.ts psp
bun tools/repack.ts --target psp --runtime dist/runtime/psp --pocket twenty48.pocket \
  --id dev.pocket-nexus.studio.twenty48 --title Twenty48 --author "Pocket Nexus" --version 0.1.0 --icon icon.png -o twenty48-psp.zip
bun test tests/repack-*.test.ts

Starter: a copy of pocket-studio's starters/twenty48 (2-variant .pocket, 2,923,336 B; psp variant js 217,804 B + pak 502,912 B).

Results

Runtime EBOOT.PBP 2,363,392 B, SHA-256 b2d61455ba452ec9…dba2e7db87eb3d5fe; the same bytes at ef0db4a (hardware run) and at 30e6e24 (this head, rebased on #563); two builds byte-identical
Embedded twenty48 PRX (same commit) 3,072,092 B (runtime PRX 2,363,056 B)
Repack 0.24–0.57 s on this Mac (deflating the 2.3 MB EBOOT dominates); 1,021,415 B zip (EBOOT 2,364,651 B + app.pocket 723,144 B, thinned); two runs byte-identical (c9cdba49…8b743c290); the EBOOT inside is the one run on the PSP
Tests tests/repack-psp.test.ts (structure, SFO fields, ICON0 size, thinned package, determinism, 6 refusals, folder naming, title cut), tests/repack-sfo.test.ts; with #562's and #563's repack tests 56 pass. Added to tools/test.ts unit stage; the new Runtime repack workflow runs tests/repack-*.test.ts

Memory, game loaded from the file vs embedded (arena's largest free block when it reserves, ui.debugStats().memory.arenaInitFree):

Where Embedded Runtime + app.pocket Difference
Real PSP, PSPLINK ldstart, 55 MB partition (ef0db4a) 52,034,560 B 52,024,576 B −9,984 B
PPSSPP, MEMSIZE = 1 EBOOT (ef0db4a) 53,083,136 B 53,073,152 B −9,984 B
PPSSPP, 24 MB partition, no MEMSIZE (first build, 7240b58) 19,528,960 B 19,518,720 B −10,240 B

The arena bump after boot is the same in both (3,680,736 B). The 10 KB are the package's manifest, plan, identity and tables plus block rounding.

What ran where

  • PPSSPP headless (~/ppsspp-src build, software GE, debugger WebSocket for VRAM reads and buttons): the repacked zip unpacked, twenty48 started with ○ and played (score 4), on both the first and the final build. Fault screens checked for a missing file, a damaged footer, a vita-only package, host ABI 2 and a non-package file.
  • Real PSP (PSP-2000/3000 class: 55 MB user partition under PSPLINK; firmware 6.61, PSPLink v3.2.1), lease psp:usb + psp:usb:transport:
    • The repacked EBOOT's DATA.PSP (byte-identical to the runtime PRX) with app.pocket beside it, ldstart host0:/runtime/DATA.PSP: title card, ○ to start, five moves by DevTools replay tape, score 20 (first build) and 16 (final build ef0db4a), screenshots through the mailbox.
    • Installed to ms0:/PSP/GAME/StudioTwenty48Runtime/ (repacked with title "Twenty48 Runtime" so pocket-studio's StudioTwenty48 stays), read back and compared byte for byte; DATA.PSP copied beside it, ldstart ms0:/…/DATA.PSP read app.pocket from the Memory Stick and showed the title card; DATA.PSP removed afterwards. The final build's EBOOT.PBP + app.pocket remain installed (read back identical).
    • DATA.PSP without app.pocket (scrshot): No app.pocket in this game's folder (next to EBOOT.PBP).
  • Not run: a start from the XMB (needs a person at the PSP; PSPLINK ldstart refuses a PBP). Evan can start "Twenty48 Runtime" from Game → Memory Stick to check the XMB path, MEMSIZE and ICON0.

Evidence (not committed): .pocket-build/validation/runtime-psp/ in the worktree.

🤖 Generated with Claude Code

@doodlewind
doodlewind force-pushed the feat/runtime-psp-vita branch 2 times, most recently from 58852c0 to ef0db4a Compare October 6, 2026 17:56
…repack into a Memory Stick zip

The PSP runtime (cargo feature `runtime`, `bun tools/runtime.ts psp`) is an
EBOOT with no game in it. At boot, on the module's main thread and before the
first allocation, it reads app.pocket from its own folder into one kernel
block, checks the footer hash, the psp variant and the host ABI, and runs the
game; a missing or refused file shows one line on screen.

`bun tools/repack.ts --target psp` turns that runtime and a game's .pocket
into PSP/GAME/Studio<Name>/{EBOOT.PBP, app.pocket}: PARAM.SFO written again
with the game's TITLE and MEMSIZE = 1, ICON0 144x80 from the identity's icon,
DATA.PSP unchanged, the .pocket thinned to its psp variant. It uses the shared
runtime and .pocket admission, PNG and scale helpers the 3DS repack added, and
adds a deterministic zip writer and a PARAM.SFO writer to tools/repack/shared/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@doodlewind
doodlewind force-pushed the feat/runtime-psp-vita branch from ef0db4a to 30e6e24 Compare October 6, 2026 18:04
@doodlewind
doodlewind merged commit 193bf8f into main Oct 6, 2026
14 checks passed
@doodlewind
doodlewind deleted the feat/runtime-psp-vita branch October 6, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant