Commit Graph
600 Commits
Author SHA1 Message Date
Henrik Rydgård 31e94fcf68 Merge pull request #22747 from hrydgard/virtual-pops-eboot
POPS: Boot PS1 disc images (cue/bin, ccd/img, mds/mdf, CHD, iso) as converted EBOOTs made on the fly
2026-10-23 16:51:55 +02:00
Henrik RydgårdandClaude Opus 5.5 f4f68d37cc File dialogs: Offer PS1 disc images (.cue, .ccd, .mds, .mdf, .bin, .img)
Windows also gets a filter of its own for them. iOS's picker takes any
file and Android's doesn't filter by extension.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 15:50:28 +02:00
Henrik RydgårdandClaude Opus 5.5 34a0090a18 Install PS1 discs from zip and 7z archives, and show them as playable only with a firmware that runs them
A zip or 7z with a PS1 disc in it (a cue sheet and the files it names, a
.ccd with its .img and .sub, or a lone raw .bin or .img) is now installed
like a zipped ISO. A disc of several files goes into a folder of its own,
named after the cue sheet or .ccd. An ISO in the archive still takes
priority, and a lone .bin or .img only counts when nothing else does.

The game screen offers Play for a PS1 disc image only when the installed
firmware's POPS can run it: 3.95 to 6.61, with popsman.prx.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 15:01:22 +02:00
Henrik RydgårdandClaude Opus 5.5 020e39baa9 RecentFiles: Make the loaded list visible right away
Load() only queued the list for the worker thread, so reading it right
after loading the config could still see an empty list. Android's grant
pruning does exactly that, and would then treat recent games as
unreferenced, release their grants, and the queued clean would drop them
from the list as missing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 13:54:31 +02:00
Henrik RydgårdandClaude Opus 5.5 a1ade54aac UMD Video: Boot the XMB for a video disc however it's started
The game list already did, but booting a video disc any other way (the command line, a file
association, dropping it on a running game, headless) tried it as a game and failed. The loader now
swaps in vshmain.prx with the disc mounted and auto-start on whenever it identifies a UMD Video
disc and the installed firmware can play it. GetUmdVideoVSHPath() moves from the UI to
Core/Util/PSARUnpack.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 12:08:10 +02:00
Henrik RydgårdandClaude Opus 5.5 69ef4ce9fb UMD Video: Say which firmware is needed, instead of that it isn't supported
Without firmware that can play UMD Video (2.71 or later), the info screen and the boot error said
"PPSSPP doesn't support UMD Video", which is no longer true. They now say the firmware version
needed, from UMD_VIDEO_MIN_FIRMWARE, the same constant the check uses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 11:36:50 +02:00
Henrik RydgårdandClaude Opus 5.5 dc41268bfc Firmware: Ignore empty updaters on discs
UMD Video discs ship an updater PARAM.SFO next to a 0-byte DATA.BIN, and PSN releases zero-fill it.
MountedDiscHasUpdater() only checked that DATA.BIN existed, so booting the EUR Video Demo Disc's
game with no firmware installed started an auto-install of "2.00" that failed with an OSD error.
It now wants a PSAR archive, like ReadBundledUpdateInfo() already did. The game info screen no
longer looks for an updater on UMD Video discs at all.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:55:27 +02:00
Henrik RydgårdandClaude Opus 5.5 d331bd6c8c UI: Play UMD Video discs in the XMB, straight from the game list
With firmware 2.71 or later installed, picking a UMD Video disc boots the XMB with the disc in the
drive and UMD auto-start forced on for that boot, so the disc's menu comes up. Without one, the
info screen saying UMD Video isn't supported shows as before. The info screen's Play does the same.

A disc with both a game and video asks which to start. For the video, the XMB comes up with
auto-start forced off, since auto-start runs the game.

Older firmware can't play these: 1.50, 2.00 and 2.60 crash in video_plugin or paf with a video disc
in the drive, and 2.50 refuses it. 2.71 through 6.61 reach Bourne Supremacy's menu.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:46:59 +02:00
LunaMooandClaude Opus 5.5 3ce3973781 Add overlay (bezel) support
Draws a user image around or behind the PSP screen, to fill the black bars
on displays whose aspect ratio doesn't match the PSP's (#20325).

- Overlays are PNGs in PSP/OVERLAYS. A plain PNG there just works: it's
  drawn in front of the game, and its transparent hole is found by walking
  from the image center to each edge until a nearly opaque pixel. Existing
  RetroArch overlay images can be dropped in as-is.
- A subfolder with an overlay.ini can define overlays with a Name, Layer
  (Foreground/Background) and an explicit Hole=x,y,w,h.
- "Load overlay image..." in the display layout screen imports any PNG
  (including Android content URIs) into the overlays folder and selects it.
- The overlay is fit to the screen (aspect preserved, centered). The hole is
  mapped with the same scale, and the game is fit into it with the normal
  stretch / integer scale / aspect ratio options. Offset and scale don't
  apply while an overlay with a hole is active. This goes through
  CalculateDisplayOutputRect, so touch mapping and post shaders agree.
- The selection is a per-orientation, per-game DisplayLayout setting.
- Disabled in VR, Cardboard, and with skip buffer effects (except software
  rendering), since the presentation path doesn't run there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBg32EFPsTXjAFu7dnTcz
2026-10-22 14:09:51 +02:00
Henrik RydgårdandClaude Opus 5.5 7dd1475ed7 HLE: Pass the MIPSState to syscalls and their implementations
HLEFunc and ReplaceFunc now take the MIPSState they run on. The dispatch
paths (both interpreters and every JIT backend) pass in the state they
belong to, and CallSyscall*, hleFinishSyscall and the HLE mips call
machinery use it instead of currentMIPS.

The wrappers in FunctionWrappers.h take the function as `auto` and call it
through HLECall, which also accepts an implementation with an extra leading
MIPSState * parameter and passes the state to it. Any other signature still
fails to compile, as before. PARAM/RETURN now refer to a local `mips`.

The HLE functions (and replacement functions) that used currentMIPS now take
the state that way. Callers outside a syscall's own frame - CoreTiming
events, the adhoc matching thread, dialogs, PPGe, the debugger UIs - pass
currentMIPS or &mipsr4k explicitly. The kernel's thread internals still use
currentMIPS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:45:14 +02:00
Henrik RydgårdandClaude Opus 5.5 d40137a007 SaveState: Fixes from a review of the IO and dialog savestates
- Savedata list entries lost their directory across a load. Confirming a
  delete in a list dialog loaded from a state failed with "delete failed"
  and deleted nothing. LISTALLDELETE can't rebuild it from the names, so
  it's saved now, with the broken flag.
- Registry values the game wrote (CLANNAD's font plugin registers its font
  there) weren't saved, and a state without a sceReg section wiped them.
- Slots sharing the "no save" icon came back as separate images of one
  texture, which Clear() then freed once per slot.
- Old directory states left a file opened with TRUNCATE due for truncation,
  so it read as empty and was truncated on close. Old async IO states were
  loaded on top of this session's results.
- From a broken or crafted state: OSK keyboard and Korean composition
  indices, gamedata install file counts, font ownership modes (an internal
  font could be deleted) and FontLib's per-font vector sizes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:20:24 +02:00
Henrik RydgårdandClaude Opus 5.5 9ddfcca670 Firmware install: Fewer and parallel file operations into the staging directory
Through Android's storage framework, every exists check, create and open is a round trip to the
storage provider, and writing each file took four of them (a check of its directory, a check of
the file, the create, the open). That made a 6.61 install take around 30 s on a phone, against a
fraction of a second of decrypting and inflating.

The install unpacks into a staging directory it has just cleared, so nothing in it can exist yet.
With the new outputDirIsEmpty option, each directory is created once and each file is created and
opened without checking for it first (File::WriteDataToNewFile), on the IO threads while decoding
goes on. A name that turns up twice still goes the careful way, overwriting the first. The swap
into place at the end is unchanged.

Headless --unpack-updater uses the same path when the output directory doesn't exist yet. Its
output is identical either way. With SIMULATE_SLOW_IO, unpacking the fonts went from 17 s to 7.5 s,
most of what's left being the fixed cost of opening the updater.

The UI's install already runs on a thread pool IO thread, so writes queued on the same pool
could stall behind other long IO tasks, and the pool drops queued tasks unrun at teardown,
which would have left the install waiting forever on its in-flight count. Eight threads owned by
the writer (attached for JNI) avoid both, and headless no longer has to start the pool for it.

Also compare file names case-insensitively when looking for repeats, since the storage usually
is case-insensitive and a second create there makes a renamed copy.

Checked with ThreadSanitizer, and on a read-only destination (every file fails, no hang).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 16:06:47 +02:00
Henrik RydgårdandClaude Opus 5.5 d68d45a441 Headless: Default to the app's memory stick on Mac and Linux
Headless used ~/.ppsspp, which nothing else uses, while the app uses
~/.config/ppsspp (or $XDG_CONFIG_HOME/ppsspp on Linux). The derivation
is now shared, and the app's default is unchanged. Windows and iOS keep
their own.

test.py and frametests.py pass a memory stick next to the binary, as
Windows headless already defaulted to, so the tests still don't write
into the user's memory stick or see its firmware.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 12:37:04 +02:00
Henrik RydgårdandClaude Opus 5.5 9b1395db8e ARM64 emitter: Add vector CLZ, LD4, UMULL by element, ABS and SLI
The disassembler decodes the widening integer multiplies by element.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 11:02:17 +02:00
Henrik RydgårdandClaude Opus 5.5 bebe527e48 ARM64 emitter: Add vector ADD/SUB, UMAXP, USHL, UMULL/UMULL2, ADDV and USRA
The disassembler now decodes the across-lanes and three-different groups,
which it only printed as raw words. The encodings match clang's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 4163737bbc ARM64 disassembler: Cover everything the emitter can emit, add a test
Rewrites most of DisArm64.cpp. Previously missing: test-and-branch,
exceptions other than brk, hints and barriers, adc/sbc, conditional
compare, extr, literal loads, exclusives, unscaled and FP register
offset loads, ldpsw, SIMD structure loads/stores, scalar pairwise,
fixed-point conversions, fccmp, most two-reg misc ops, modified
immediates, smov/umov/dup from GPR, and the scalar shift forms.

Fixed: sp printed as x31, unsigned load offsets sign-extended, ldrsb and
friends shown as stores, adrp ignoring the page, double precision
1-source ops shown as single, fcmpe shown as fcmp, smulh/umulh, the
bitmask decode for 64-bit elements, and fpcr shown as fpsr. Common
aliases are used now (cset, mul, tst, cmn, lsl/asr/ubfx, sxtw...).

TestArm64Disasm runs two examples of every emitter function through it.
The expected strings were verified with Tools/arm64_disasm_check.py,
which compares all 833 instruction words against llvm-mc.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 20:59:51 +02:00
Henrik Rydgård f8c1e39315 Merge pull request #22535 from hrydgard/tvos-support
Apple tvOS support prototype (and CI)
2026-10-13 10:03:15 +02:00
Acts1631 8220a0f855 Validate ZIP paths after stripping their common prefix
ExtractZipContents checked each archive path for parent components
before removing stripChars. A name could become ../ only after
stripping and escape the install directory, allowing a crafted archive
to overwrite files in sibling PSP directories.

Validate both archive and output paths, and apply stripping consistently
when checking for overwrites.
2026-10-12 22:17:53 -04:00
Henrik RydgårdandClaude Opus 5.5 9512d619da tvOS: Read the Siri Remote's ring buttons, memstick in Caches/PPSSPP
The 2nd generation remote's ring has real direction buttons and a center
button, which take GCSupportsMultipleMicroGamepads. Its buttonA is pressed
along with all of them, which gave stray enters. Swiping is off for now, it
fought with the ring. The memory stick moves into a folder of its own, out
of the way of what the system keeps in Caches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:59:30 +02:00
Henrik RydgårdandClaude Opus 5.5 b58dd17c2e iOS: Show paths in the app's container as ~/...
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:52:48 +02:00
Henrik RydgårdandClaude Opus 5.5 3a8a1799ee Add a tvOS build of the app
Shares the iOS frontend, with camera, location, motion, photo/document
pickers, share sheet, clipboard, microphone, battery and rotation compiled
out. The view controller is a GCEventViewController, so controllers only
reach us through GCController, and the Siri Remote is read as a micro
gamepad and sent as arrow/enter/escape/back keys. tvOS has no persistent
storage besides NSUserDefaults, so the memory stick lives in Caches for now.

Configure with -DIOS_PLATFORM=TVOS. The MoltenVK submodule gets the tvOS
slice from the same stock v1.4.2 release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:06:55 +02:00
Henrik Rydgård 6b203f050d Merge pull request #22521 from hrydgard/savedata-zip-root
Install savedata zips that contain a single save folder's files in the root
2026-10-12 13:58:02 +02:00
Henrik Rydgård 04f5994ea6 Create .nomedia files in the NAND directory when installing firmware
Avoids Android looking for images.
2026-10-12 12:43:36 +02:00
Henrik RydgårdandClaude Opus 5.5 abeb890d0f Install savedata zips that contain a single save folder's files in the root
Some shared saves are zipped without their folder, with the zip named after it
instead (ULUS10019000.zip). Detect these by a root PARAM.SFO with a
SAVEDATA_DIRECTORY, which holds the folder name (game ID + save name), and
extract into that folder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 12:27:24 +02:00
Henrik Rydgård 0ce2330b97 Merge pull request #22505 from hrydgard/fix-savestate-zip-install
Fix installing save state zips with certain structures
2026-10-11 18:58:41 +02:00
Henrik RydgårdandClaude Opus 5.5 1b1e3aba1f Fix installing save state zips
Save state zips are detected with stripChars past the last slash, since
the whole directory is stripped. ExtractZipContents then computed a
negative directory length, created a directory named after each file,
failed to open it for writing and reported "Storage full".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 18:27:30 +02:00
Henrik RydgårdandClaude Opus 5.5 81efd601f0 iOS: Merge the Metal view controller into the base one
With the GL view controller gone, PPSSPPViewControllerMetal was the only
subclass of PPSSPPBaseViewController. They're now one class,
PPSSPPViewController, in ios/ViewController.mm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:13:29 +02:00
Henrik RydgårdandClaude Opus 5.5 ebab68ac0a iOS: Remove availability checks for versions below the 15.0 minimum
Also drops what they guarded: the pre-14 UIImagePickerController and
document picker fallbacks, the psychic paper entitlement workaround
(fixed in 13.5), and the CS_EXECSEG_ALLOW_UNSIGNED JIT probe, which
returned false on everything from 14.4 on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:13:29 +02:00
Henrik RydgårdandClaude Opus 5.5 2e08b54402 iOS: Move off deprecated APIs
- Request an orientation update with setNeedsUpdateOfSupportedInterfaceOrientations
  on iOS 16+, where attemptRotationToDeviceOrientation is deprecated.
- Open the document picker with UTTypes on iOS 14+, and present it from
  sharedViewController rather than through the deprecated keyWindow.
- List cameras with a discovery session instead of devicesWithMediaType.
- IS_IPAD() instead of UI_USER_INTERFACE_IDIOM().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:28:27 +02:00
Henrik RydgårdandClaude Opus 5.5 6e0d6f4357 Savestates: Terminate fixed-size name strings on load
Add DoCString() for standalone fixed-size strings, and
TerminateLoadedCString() for names inside structs that are loaded whole.
These all get used as C strings (GetName(), logs, the debugger), and are
always terminated when created. Module names are copied from the ELF with
strncpy, so terminate those on creation too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 15:32:06 +02:00
499cdd55ad Validate untrusted IDs before using them as directory names
Texture pack installs took the destination directory from the archive's
textures.ini, and PKG update installs from the PBOOT's DISC_ID, both
unchecked, so a crafted archive or package could write outside
PSP/TEXTURES or PSP/GAME. Add IsSafePathComponent() to PathUtil and check
both, replacing the WebServer's own upload filename check with it too.

DISC_ID from any PARAM.SFO also names configs, cheats, textures, exdata
and savestates, so ReadSFO now drops one that isn't a safe name and the
game gets a fake ID like homebrew. The same goes for a frame dump's ID.

Combines #22419 and #22420 by Acts1631.

Co-Authored-By: Acts1631 <acts1631kjv@proton.me>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:39:06 -06:00
Henrik RydgårdandClaude Opus 5.5 1288c294fd Savedata dialog: Draw icons over black, with alpha blending
The PSP blends save icons over a black background, so their transparent
parts come out black. 0a5fa27957 turned blending off instead, which is
wrong for icons that rely on it. Revert that, and draw a black rectangle
under each icon. Fixes #22280.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 13:21:24 -06:00
Henrik RydgårdandClaude Opus 5.5 3062ffd695 Map the PSP-2000 extra RAM for homebrew PBPs, outside the user partition
Homebrew written for a PSP-2000+ under custom firmware can use the top
32MB of RAM directly without setting MEMSIZE, which leaves the user
partition at its normal size. NJEMU's slim builds do this (#8925).
Previously we didn't map that memory at all for PBPs; MEMSIZE=1 isn't a
workaround either, since it grows the partition and the heap and stacks
land where the program writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 12:40:59 -06:00
Henrik RydgårdandClaude Opus 5.5 84f64a9fdf Savedata: Don't free stale icon textures after a state load
The old icons stayed in PPGe's decimation list with kernel addresses from
before the load, and got freed out of whatever the loaded state had there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 09:34:05 -06:00
Henrik Rydgård 0a5fa27957 Disable alpha for save/load icons in sceUtility UI 2026-09-21 10:41:35 -06:00
Henrik RydgårdandClaude Opus 5 45727ef2be Tighten the comments on the mpeg PRX changes
Cut restatement and asides that only made sense against earlier, wrong versions
of the code, and prefer parentheses over paired dashes. Also fix two comments
left stale by the descriptor rework, and record the rule in AGENTS.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 10:09:48 -06:00
Henrik RydgårdandClaude Opus 5 16bf3a6519 BlockAllocator: let an allocator with no blocks save and load
An allocator that nothing has Init'd yet is a real state, not a broken one -
sceVideocodec keeps one for Media Engine memory that stays empty until a game
plays a video - but DoState asserted on bottom_ when writing, and on reading
treated a block count of zero as corrupt. Both ends handle it now, and the
write loop no longer special-cases the first block.

The stream layout is unchanged, so states written before this still load: they
always had at least one block, and take the same path they always did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 11:06:56 -06:00
Henrik Rydgård 21fa7f10e1 Minor fixes 2026-09-10 16:04:39 -06:00
Henrik RydgårdandClaude Opus 5 ad06cbe2c4 Auto-install firmware from the game disc on boot
Most UMDs carry a firmware updater, and having a real firmware in the NAND is
what the LLE modules want. On boot, if the disc's updater is newer than what's
installed - or nothing identifiable is installed, which is what a fonts-only
NAND looks like - unpack it, with a progress bar on the OSD. Controlled by
bAutoUpgradeFirmware, on by default, with a checkbox on the firmware screen.
Off in headless, which shouldn't rewrite the NAND during a test run.

The install itself is now shared with the install screen, and hardened, since
it can run without anyone watching:

- It stages into <NAND>/install-staging and only erases the installed firmware
  once the new one is complete on disk, so a failure leaves what's there alone.
- A single entry that didn't unpack fails the whole install. A firmware with
  holes still looks installed, so nothing would ever replace it.
- A truncated archive is rejected instead of unpacking to half a firmware that
  every stat reports as a clean install. The PSAR header records the length;
  it was being clamped to the buffer rather than checked against it.

Also falls back to the version in the archive's own header when the PARAM.SFO
next to the updater is unreadable, which it is on a fair number of discs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 15:52:53 -06:00
Henrik RydgårdandClaude Opus 5 e4d940604e Name both versions in the firmware overwrite warning
"Firmware 6.20 is installed. It will be erased and replaced with 6.60." is the
thing worth double-checking before wiping a firmware - installing off whatever
disc is to hand makes going backwards easy to do by accident.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:52:53 -06:00
Henrik RydgårdandClaude Opus 5 63eb3d5b21 Boot the VSH on every firmware, 1.50 through 6.61
Backwards from 5.01, one release at a time, against all 39 versions that ship
on a disc plus the download-only 6.61. Nothing here is an offset - it's almost
entirely Sony renumbering the kernel *_driver NIDs, which sends an import we
mean to HLE into the real firmware module instead.

Four more NIDs each for sceRtc_driver/sceRtcSetAlarmTick and
sceHprm_driver/sceHprmReadLatch, covering 1.50 up. The rtc one is what parked
every thread on a SceSysconSync semaphore; the hprm one runs once a frame, so
unresolved it was most of the boot log. Also sceImposeGetParam/sceImposeChanges
(1.50 - 2.xx) and sceKernelLoadModuleVSH (1.x, which is how the shell loads its
own plugins - unresolved it got module id 0 and StartModule failed).

sceRtcIsAlarmed had to be implemented too; it returns 0, as in JPCSP. As a null
entry it returned LIBRARY_NOT_YET_LINKED, and the 3.0x-3.5x VSH read that as
"ask the hardware instead" and went back to blocking on syscon.

Two structural findings:

- Up to 4.05, scePaf's heap allocator is a separate heaparea1.prx that paf
  imports as scePafHeaparea. Load it when it's there. Its pool pointer needs
  the same pre-fill paf's does, at gp - 0x7FCC rather than gp - 0x7E88.
- 1.50's vshmain.prx declares no module attributes at all - PSP_MODULE_VSH_MODE
  only appears from 1.52 - so the whole VSH bootstrap was being skipped. Accept
  the module name too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 14:43:39 -06:00
Henrik RydgårdandClaude Opus 5 480c5e44e2 Boot the VSH on every firmware from 5.01 up
The blocker below 6.60 wasn't offsets, it was that Sony renumbered the kernel
*_driver NIDs between versions. A function we HLE under its 6.6x NID is a
stranger on an older build, so the import lands in the real firmware module
instead - and that's where it goes wrong:

- sceRtc_driver sceRtcSetAlarmTick. Without the HLE the VSH's alarm call ran
  the real rtc.prx, which called on into syscon.prx and blocked forever on a
  SceSysconSync semaphore. That was the whole "stalls with every thread parked"
  symptom; the tell was a fourth SceSysconSync waiter a healthy boot lacks.
- sceHprm_driver sceHprmReadLatch, called once a frame - so before this an
  older firmware's 12-second boot logged ~20000 lines of one unresolved import.

Three extra NIDs each, found by disassembling the module from both firmwares
and matching on the address of the user-mode export whose NID never changed
(sceRtc/0x7D1FBED3, sceHprm/0x40D2F9F0).

5.55 additionally needed two PRX decryption keys we didn't have (0x4C941AF0
and 0x4C941BF0) - without them none of flash0:/kd decrypted and the shell came
up with no drivers behind it at all.

Checked one release at a time against every version that ships on a disc, plus
6.61. 4.05 and below still die on a null write inside vsh_module.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 13:40:37 -06:00
Henrik RydgårdandClaude Opus 5 b21e40a53f Boot the VSH on firmware 6.60 too, and stop patching builds blind
6.60 ships byte-identical paf.prx and vshmain.prx to 6.61 - all 6338 + 669
functions disassemble the same - and boots to an interactive XMB, so let
FirmwareVersionSupportsVSH accept it. That matters because no UMD carries 6.61
(it was download-only), so 6.60 is the best a disc-installed firmware can be.

The two module patches were hardcoded offsets from the module base applied to
any module of the right name, which is quietly wrong on any other build:

- The scePaf heap arena slot moves with every build (0x18CCD8 on 6.00 through
  0x18D728 on 6.60/6.61) but sits at gp - 0x7E88 in all of them, so find it
  that way. On its own this turns an immediate SIGSEGV inside scePaf into a
  clean stall on 6.00 through 6.39 - they still don't reach an XMB, they get
  stuck in sceVshBridge_Driver instead.
- The vsh_module alarm-category offset has no such anchor, so check the word
  there is the one the patch was derived from. On 6.20 and 6.00 it's ASCII
  string data - the unconditional write was corrupting a string table.

Also resolve the per-model kernel drivers (memlmd, loadexec, wlanfirm) to the
model being emulated. They were asked for as _01g, which a firmware unpacked
for a single model doesn't have - and our own updater unpack defaults to 02g.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 12:56:34 -06:00
Henrik RydgårdandClaude Opus 5 57e96b2d81 Add a PSP Firmware screen under Settings > Tools
Shows what's actually in PSP/NAND - nothing, a fonts-only partial install, or
a full firmware with its version, build date and region - read from
flash0:/vsh/etc/version.txt, which is present both in a PSAR-unpacked install
and a NAND dumped off hardware.

Also offers to install an updater, erase the NAND, and launch the XMB, the
last one gated on FirmwareVersionSupportsVSH() since the module patches that
get vshmain.prx running are tied to 6.61's offsets.

Installing a firmware now erases flash0/flash1/ipl first - two firmwares can't
be merged, a file the new one doesn't have would linger and still get loaded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 12:07:28 -06:00
Henrik RydgårdandClaude Opus 5 f9bd5682db libkirk: let C++ callers include its headers directly
kirk_engine.h and amctrl.h guard their declarations, but AES.h and SHA1.h
never did, and kirk_engine.h includes them from outside its own guard. So the
AES_* and SHA1* functions got C++ linkage in any C++ file that reached them
through there, and only linked for callers that happened to wrap the whole
header in an extern "C" of their own. Nothing had called AES_* from C++
before, so it stayed hidden until something did.

Guarding the two headers instead lets every caller include them plainly, and
the wrappers scattered around the tree come out. Both are pure declarations
over kirk_common.h's typedefs with no system headers behind them, so there's
nothing in there that shouldn't be wrapped.

kirk_engine.h also uses size_t without including anything that defines it,
which only held together because its includers happened to have it already.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:15:15 -06:00
Henrik RydgårdandClaude Opus 5 f0dafdee10 Show and remove installed game updates from the game info screen
An installed update silently replaces what the game boots, so the info
pane now says when there is one - version, size and where it lives - and
the context menu offers to remove it again.

Removing takes the whole PSP/GAME/<DISC_ID> folder when the update is all
that's in it. When a digital game shares the folder, only PBOOT.PBP goes,
since deleting the folder would take the game with it and nothing records
what the install wrote. The confirmation names the exact path either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018izZ1mGTWhz2RqeudqsDQR
2026-09-08 11:15:15 -06:00
Henrik RydgårdandClaude Opus 5 3a70c6b149 Install PKG game updates, and boot them
Opening a .pkg now offers to install it, the way a .zip does - see the new
InstallPkgScreen, which shows what the update patches, what it'll take up
on disk (exact, since package contents aren't compressed) and where it's
going. The package's PS3-style USRDIR/CONTENT wrapping is stripped so the
files land where the PSP expects them, in PSP/GAME/<DISC_ID>.

Booting a disc then looks for PSP/GAME/<DISC_ID>/PBOOT.PBP and boots that
instead of the disc's own EBOOT, leaving the disc mounted - so the update
overrides the files it ships and the disc supplies the rest. The update's
DISC_ID has to match; a DISC_VERSION mismatch only warns, since updates do
get used with slightly different dumps in practice.

Verified against the whole corpus: every one installs, and the
digital NP* update/base-image pairs that could be assembled all boot the
patch rather than the disc's executable. That includes Super Robot Taisen
Operation Extend from a real NPUMDIMG EBOOT.PBP, which settles that
ISO.BIN.EDAT does not re-key the PBOOT - a digital title's patched EBOOT is
encrypted exactly like a UMD one. On the UMD side, the patched
LittleBigPlanet reads PATCH.ARC out of the install alongside the disc's own
archive.

The DISC_VERSION warning turns out to be load-bearing: many of the pairs
mismatch, because the dumps in circulation are later disc revisions than the
updates were built against. docs/pkg_notes.md has the numbers, and the one
thing that doesn't work - PGD-wrapped .sprx modules, which
sceKernelLoadModuleNpDrm can't load.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 11:15:15 -06:00
Henrik RydgårdandClaude Opus 5 5089caf2a0 Add a reader for PKG game update packages
PSP game updates were distributed as NPDRM .pkg files holding a patched
EBOOT (PBOOT.PBP) plus the data files the patch replaces. PkgUnpack reads
one: header, item table, both PARAM.SFOs, and the AES-128-CTR that covers
everything past the header - including the per-item key split, where an
item's pspType byte picks between the PSP and PS3 keys.

Nothing new is needed to decrypt these. All packages checked use PRX
tag 0x2E5E10F0 for their PBOOT, which PrxDecrypter already has a key for.

Also adds "PPSSPPHeadless --install-pkg=DIR", a sibling of --unpack-updater,
which installs without any UI. All packages install through it byte
-identically to a reference implementation.

Format notes are in docs/pkg_notes.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 11:15:11 -06:00
Henrik RydgårdandClaude Opus 5 917cb1b0fc Implement KL4E/KL3E decompression, so firmware modules that use it can load
KL4E is Sony's second compression scheme for ~PSP modules, alongside gzip:
LZ77 tokens where every bit is arithmetic-coded, structurally close to LZMA.
PPSSPP could detect it but not decode it, so any module packed with it failed
to load at all - the loader reached the gzip path and bailed there.

Which scheme a compressed module uses is now decided by the payload's own
magic rather than assuming gzip. On a 6.61 flash0 dump this takes the kd/
modules that load from 126 to 129 of 129; libmp3.prx, libaac.prx and
libmp4.prx were the ones affected, and libmp3.prx decompresses to exactly the
elf_size its PRX header declares.

Two bounds problems in the format are fixed rather than reproduced: the match
copy is unchecked against the output buffer on real hardware, so a crafted
stream can write up to 255 bytes past it, and a long enough distance code
indexes copyDistProbs out of range. Input reads are bounded too - the format
carries no length and trusts the stream to terminate itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 11:06:17 -06:00
Henrik Rydgård 86de3307fb Merge pull request #22230 from hrydgard/sceFont-install-fonts
Install sceFont fonts off the disc when we don't have them in the NAND folder
2026-09-05 11:46:56 -06:00