With the converted header's key of zeros, the emulator's descrambling of its disc switching strings only
reverses them, so ours go in backwards and its code is left alone. The tables are found by the code that
indexes them, which is the same from 3.95 on with only the registers differing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every MAC comparison the manager makes passes for a converted EBOOT, rather
than the one in 6.60's disc header check: the export it's in has a
different NID in each firmware. The disc switching text is found in the
emulators of 6.20 to 6.61 (earlier ones have the same descrambling,
compiled differently), so a playlist on an earlier firmware fails with a
message saying so.
The reset dialog's disc number is the disc picker label's, moved by a
table of its own per language, which is now laid out for our text too.
The PS1 strings get a category of their own, POPS, with the experimental
warning moved there. langtool gets add-section, to make the section for a
new category, and inserts into an empty section rather than dropping the
line (which move-key had already removed from the old one).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A cue sheet, a CloneCD .ccd, or a bare .bin, .img or .iso of a PS1 disc
now boots on the firmware's POPS, as the converted EBOOT.PBP popstation
would make from it. PS1EbootFileLoader builds the PBP header, PARAM.SFO
(the disc ID from SYSTEM.CNF) and the PSISOIMG header, TOC and block
index, and serves the disc image from the image's own files. Each block
of 16 sectors is a stored DEFLATE block: the manager won't read an image
of uncompressed blocks in its own format, but the converted PBP's inflate
takes stored ones, and with every block the same size, reads map straight
onto the files. Nothing is compressed or written anywhere.
Cue sheets can have any number of files, AUDIO tracks, INDEX 00 and
PREGAP, and a missing FILE falls back to the cue sheet's own name. A
2048-byte .iso gets Mode 2 Form 1 sectors made around its user data.
The EBOOT is mounted as the only file in host0:, through BlobFileSystem,
which now saves its open handles in savestates. The game browser lists
.cue, .ccd, .bin and .img, leaving out the .bin and .img files a cue
sheet or .ccd next to them describes, and a bare image opened directly
goes through the cue sheet that names it. GameInfoCache reads the title
and ID of these, and of real PS1 EBOOTs, from their PARAM.SFO.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A disc game or memory stick EBOOT started from the XMB used to be swapped in under the XMB's session,
so it ran without its PARAM.SFO, compat flags, game config, window title, savestates and so on.
Much of that is decided once, at boot (the memory map, the GPU backend, the shader cache), so
sceKernelLoadExec now asks the frontend for a reboot into the disc image or EBOOT (PSP_RequestBoot)
and stops. The app and headless boot it like a game from the game list.
The game keeps the XMB's launch args (CoreParameter::launchedFromVSH), so exiting boots the XMB
again, with the disc that's in the drive and those args, and the XMB comes back where it was without
auto-starting the disc. Homebrew keeps the disc that was in the drive. A disc whose executables are
all zeroes (the EUR Video Demo Disc's "game") still loads in place and fails back to the XMB.
In headless, the run carries on through the reboot, starting stopped under the debugger like the
first boot, since the reboot loses a pending cpu.runUntilTime.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
--mount ISO used to be headless-only, and EmuScreen always cleared
CoreParameter::mountIso. Pass it from the command line into the first
EmuScreen, which keeps it across resets and drops it when another game
is booted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6.10's popsman.prx imports by 6.20's NIDs, and has the same code at
another layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6.20 renumbered the kernel NIDs again, so it gets aliases of its own,
mapped through 6.30's from the call sites in popsman.prx. Its manager has
the same code at another layout, and its scePopsManLoadModule has a
bigger frame (it also picks a model-specific emulator module), so the
layouts now carry that function's frame size for the check.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The manager's code used here is the same from 6.30 on, but 6.30/6.31's
sits at other offsets. The offsets are now a table of layouts, picked by
the code at them when the manager loads, and kept in the savestate (the
patches change that code). With the NID aliases, Crash Bandicoot plays
the same on 6.30, 6.31, 6.35, 6.36, 6.37 and 6.39 as on 6.60/6.61.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A PS1 EBOOT now boots the installed firmware's POPS manager
(flash0:/kd/popsman.prx), which loads its PS1 emulator (pops_0Xg.prx) for
the game. Load_PSP_POPS stands in for the kernel's reboot into its POPS
configuration: it loads the manager and that configuration's user modules,
and a helper thread makes the kernel init's scePopsManLoadModule(path, 0)
call. InitForKernel reports a POPS boot from the memory stick.
The emulator plays its sound on the Media Engine, so a PS1 boot always
gets the emulated ME, whatever the homebrew setting says, and is refused
on platforms that can't emulate it. The manager boots the ME with its own
code (POPS's reboot has no me_wrapper, so Sony's ME firmware isn't booted
for it), and its ME loop runs there unchanged, playing through the audio
block.
Everything in POPS that reaches hardware registers is kernel code, so a
POPS boot leaves the ME's kernel mode stopgap (g_kernelModeAnywhere) off:
7% fewer host instructions in Crash Bandicoot.
The manager also drives the GE directly: its stall register and FINISH
status are an MMIO device that only answers during a POPS boot, and stall
writes extend the HLE GPU's current list.
The 6.60 and 6.61 popsman.prx and pops_01g/02g.prx have the same code. The
manager's is checked when it's loaded, and any other one fails the boot
with a message. Savestates include the boot's threads.
Reading a game's disc image needs the next two changes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The download (a DRM BB install package) is installed by the game's boot program only when it's
started with -i, which nothing does any more. PPSSPP now installs it itself when the game boots, the
way psheet does. Unpacking the package needs only known keys: a fixed one from psheet, and the
secure install ID, which comes out of the package's MAC.
The module inside is a type 3 PRX whose first step is encrypted for KIRK command 2, with a key
that isn't known. Command 2's result is the same on every PSP, though, so it is now stored for the
EU and US modules (taken on a PSP through sceMesgIns_driver_D062B635, the call the install makes,
and cross-checked against an install). The type 3 decrypt uses it in place of that step, so the
installed module needs no Fuse ID.
--re-decrypt also takes such a package now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It does file I/O, which goes through Java for a content URI memstick, and
getEnv() asserts on an unattached thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
InitMemorySizeForGame read all of disc0:/UMD_DATA.BIN into a vector and then copied
it into a string, with no size limit, from an image we don't control - and the
DISC_ID that gets us here is equally forgeable, it just has to match one of the 16
g_HDRemasters entries. A real UMD_DATA.BIN is a few dozen bytes; anything larger is
a mistake or an attack, so check the size before reading.
Adds SceKernelLoadExecVSHParam and fills in the rest of LoadExecForKernel's
NIDs: real implementations for sceKernelExitVSHVSH/Kernel
(mirrors sceKernelExitGame) and sceKernelLoadExecBufferVSHUsbWlan (loads
an exec from an in-RAM buffer instead of a file, plus UNIMPL stubs.
sceKernelLoadExecBufferVSHUsbWlan needed __KernelLoadExec split into a
file-reading front end and a shared __KernelLoadExecFromPtr back end
that both it and the new buffer-based path call into.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU
(cherry picked from commit 4adbedec9e2e3221113c9e5ad00b248bba1153e1)
NormalizePath's Windows branch ignored GetFullPathName failing. A zero return
matched neither of its two branches, so the 512-character buffer was handed back
with whatever was in it.
The content-URI branch then computed its relative path by substr()ing the
decoded FilePath() of the two URIs - but the containment check above it compared
the URIs themselves, which doesn't imply the same prefix relationship for the
decoded paths. When it doesn't hold, substr() throws out_of_range, uncaught.
Checked explicitly now.
More minor fixes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
Texture pack filenames/aliases from textures.ini were used to build read
and write paths with no '..' check, letting a malicious pack read or
write files outside the pack directory.
- LoadIniValues rejects entries with a parent dir component via
HasParentDirComponent.
- ReplacedTexture::Prepare skips such filenames as defense in depth.
- PSPLoaders savestate migration now uses the shared HasPathTraversal
helper instead of inline separator checks.
DISC_ID/DISC_VERSION/homebrewName are attacker-controlled and were used
unsanitized to build migration rename paths. Skip the migration step if
any of them contain a '/' or '\' so the destination can't escape the
savestate directory.