chdman createcd makes CD CHDs of PSP discs too, which were taken for PS1 games. A CHD is now a PS1
disc only when its volume descriptor's system ID is PLAYSTATION, and otherwise goes on to the PSP
disc checks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A CD CHD is read through its track metadata: each track's frames are
padded to a multiple of four, a pregap is stored only when its type
starts with V, and audio is big-endian. A .chd that's a CD is a PS1 disc,
PSP discs being DVDs. The FileLoader-backed core_file the PSP CHD block
device used is now OpenCHDThroughLoader(), for both.
An .mds gives each track's start, pregap and length, and where in the
.mdf it's stored, with 96 bytes of subchannel data after each sector.
Anything between tracks isn't stored. A bare .mdf goes through its .mds,
which the game browser lists, and archives install with both.
MODE2/2336 cue sheets, which only had their sector headers made before,
are now tested too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
The EUR Video Demo Disc's UMD_DATA.BIN says GVA, but its EBOOT.BIN and BOOT.BIN are all zeroes, so
"Play game" could only fail. It now goes straight to the XMB, like "Watch video". What the disc
reports to the emulated PSP doesn't change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#22737
Some video discs (Spider-Man) have a PSP_GAME holding only a firmware update. We reported them as
game discs too, and the XMB said "The disc could not be read". The type now comes from the fourth
field of UMD_DATA.BIN (G, V, A; the EUR Video Demo Disc says GVA), with the directories as a
fallback, and PSP_GAME without SYSDIR/EBOOT.BIN doesn't make a mixed disc a game. Identify_File uses
the same check, so these discs list as UMD Video.
Ejecting and inserting a disc from the pause menu without resuming in between left the old disc in
the XMB: callback notifications don't queue, so the insert's argument replaced the eject's before
the drive callback ran. The insert now waits until the callback has seen the drive empty.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A UMD Video image can now be inserted, and the drive's disc type (devctl
0x01F20001) comes from what's on the disc: PSP_GAME, UMD_VIDEO and UMD_AUDIO
each set their bit. With a video disc the XMB moves to the Video column,
shows the disc with its icon and background, and hands it to the firmware's
video player when selected. sceUmdGetDiscInfo still tells games they're on
a game disc.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The XMB, booted without a game, now starts with an empty drive: when
vsh_module loads with nothing at disc0:, the drive is marked empty, and
activating it tells the shell's drive callback (SceVshMediaDetectUMD)
there's no disc. Activation used to report a readable disc regardless,
so the shell asked for its type and showed "The disc could not be read."
The disc type devctl (0x01F20001) answers 0 without a disc, as JPCSP
does.
game.umd.insert (path) mounts a disc image into the empty drive and
calls the drive callback as inserting one does; game.umd.eject takes it
out. UmdMount() is UmdReplace() without its need for a disc already in.
Inserted into the 6.61 XMB, Daxter shows in the Game column with its
icon, background and animated preview, and launching it boots the game
to its title menu.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
Memory::IsValidAddress and friends tested the extended-RAM range with
(address & 0x3F000000), i.e. at 16MB granularity, so they accepted the whole 16MB
block containing the end of RAM. That's harmless at 32MB and 64MB, but the Sora no
Kiseki SC/3rd HD remasters run with 0x04C00000, so addresses from 0x0CC00000 to
0x0CFFFFFF read as valid, and MaxSizeAtAddress then underflowed to ~4GB there -
which defeats ClampValidSizeAt and IsValidRange entirely for that window. Mask
with 0x3FFFFFFF instead, in all five helpers and the copies in MemMapFunctions.cpp.
IsValidTextureAddress's extended-RAM branch repeated the first branch's whole mask
rather than just its alignment bits, so it was dead code and extended RAM was never
accepted as a texture source.
ComputeTextureHash checked IsValidAddress(addr + sizeInRAM), i.e. only the end
address, which can land in a different valid region than the start - a VRAM texture
with a large enough computed size ends exactly at the base of RAM and "passes"
while reading far past the 8MB VRAM view. Use IsValidRange.
TextureReplacer::ComputeHash's strided path had no range check at all, unlike the
contiguous path right above it. Also clamp the pack-supplied reduce-hash factor to
1.0 - it's a reduction, and the ini parser only rejects exactly 0.
ZipExtractFileToMemory read an uninitialized zip_stat when zip_stat_index failed
(it ignored the return value) and sized a host allocation directly from the zip's
declared uncompressed size. Reached just by opening an archive.
Memory::Reinit ignored Init()'s return value, and DoState fed it a memory size
taken straight from the savestate. A bogus size made the map fail to allocate and
left base null, after which DoMemoryVoid wrote RAM through it. Validate the size,
propagate the failure, and roll back to the previous size if reinit fails.
314 pspautotests pass, all unit tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
A firmware dumped off a console has its modules decrypted, while one unpacked
from an updater still has them as ~PSP blobs. IdentifyFile knew about ELF and
PBP but not that, so the loader refused those outright - even though
__KernelLoadELFFromPtr decrypts ~PSP perfectly well once it gets that far, which
is how every encrypted game EBOOT loads.
Checked against a vshmain.prx unpacked from the 6.61 updater: it now gets as far
as "Decrypting ~PSP file" in the module loader instead of stopping at
"CPU_Init didn't recognize file".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9