"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>
He used Claude, never thought of this use case, but it's logical - it
can go reverse engineer the functions to come up with likely names,
making it easier to match the SHA1 hash. (NIDs are truncated SHA1 hashes
of names, except in firmwares later than around 3.50).
SendDebugScreenshot ignores the descriptor entirely and reads the display
framebuffer from the GPU, so filling one in was theatre - and it computed a
pointer from the display address, which is zero whenever the shell has the
display switched off.
Also reset g_screenshotSaved per test, so a test that emits its own screenshot
doesn't stop the next one getting the end-of-run capture, and clear the sceReg
open count on shutdown to match init.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Record the fifth cause (the rejected impose parameter), correct the counts now
that 3.80/3.90 and 2.6x-2.8x are covered, and note that 1.50 through 2.50 never
touch the encrypted XMB index at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same three functions, one more NID each. Those two shells started no plugins at
all and sat on a black screen; both reach an interactive XMB now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2.60 and 2.71 tag their flash0:/vsh/etc/index.dat 0x495BE403 rather than with
the 0x0B2Bxxx0 the rest of this family uses; 2.80, 2.81 and 2.82 use 0x0B2B05F0.
All five now reach an interactive XMB.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every impose.prx from 1.50 to 5.55 dispatches on it and returns a field of the
impose context - the same field that decides where the backlight brightness
answer comes from. We rejected it as not a parameter at all, and 3.11's shell
read the error back, blanked the display with sceDisplaySetFrameBuf(0, 0, 0)
and never turned it on again.
2.00, 3.03 and 3.11 now reach an interactive XMB.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rule was already here in one line and I still added a Claude-Session
trailer to ten commits, because the session's own attribution instructions say
to. Say plainly that this rule wins, and to check git log afterwards.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The old claim of "all of them" measured that the shell renders, which several
versions do while showing only the wallpaper or a full-screen error. Record what
each version actually does, the four causes that were in the way, and how to
find the next one of each kind - including that --timeout is wall-clock, so a
slow-drawing shell can look stuck when it isn't.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The per-model naming only starts around 3.50. Older dumps have plain
memlmd.prx and loadexec.prx, name the wlan firmware after the chip revision
(wlanfirm_magpie.prx became wlanfirm_01g.prx), and don't have lowio.prx at all
until about 3.52 - so every one of those was reported as a failed load on
2.00 through 3.30. Try the emulated model, the model in the path, then those
older spellings, and say plainly when the firmware simply doesn't ship one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Those generations predate the per-model split and have a single
flash0:/vsh/etc/index.dat. 3.30 through 3.52 now reach an interactive XMB;
3.30 to 3.51 share 3.52's key.
3.03's mesg_led.prx is older than keys330_1, so its table was confirmed against
keys300_1 and keys280_1 instead - both match byte for byte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flash0:/vsh/etc/index_XXg.dat is the index of what the XMB shows, and each
firmware generation tags it with a key per PSP model. We only had the 6.6x
triple, so 5.03 through 6.39 decrypted nothing, drew no icons, and gave up with
the red error screen.
The keys come out of each firmware's own mesg_led_XXg.prx, whose tag table is
24-byte entries of tag plus 16-byte key. Extracting the 6.6x triple that way
reproduces the three keys already in this file byte for byte, which is what
establishes the layout.
5.03, 5.50, 5.55, 6.00, 6.20, 6.31 and 6.39 now reach an interactive XMB.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sceKernelLoadModuleVSH, sceKernelGetModel and sceImposeSetStatus each appear
under a different NID in 3.95/4.05, 6.00/6.20 and 6.31/6.39. Without the load
one those shells started no plugins at all and sat on a black screen.
Identified the same way as the 5.xx set: for each firmware, the modulemgr export
with sceKernelLoadModuleVSH's callee set, and the vshbridge export whose body is
the user-level check 6.61 wraps sceKernelGetModel in - which is also the only
SysMemForKernel import those shells actually call.
3.95 and 4.05 now reach an interactive XMB.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dumped 0x66 comes from a 6.6x PSP, and 5.50's shell rejects any schema
version above 0x58 as a corrupt registry - it drew its whole XMB and then
replaced it with the "settings are corrupt, press O to repair" dialog.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
We hand every sceRegOpenRegistry the same handle 0, so closing one used to wipe
every open category - including ones a still-open registry handle owns. On
hardware each open gets its own object and they don't interfere.
The VSH's alarm scan does exactly this: it holds /CONFIG/ALARM open, then opens
and closes the registry once per alarm slot, and found its own category gone by
the end. Count the opens and only clear on the last close.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flash0:/vsh/etc/index_XXg.dat is the index of what the XMB shows, and 5.50 tags
it 0x0B2B11F0, which we had no key for - so the shell decrypted nothing, drew
no icons, and gave up with the red error screen.
The key is read out of that firmware's own mesg_led_02g.prx, whose tag table is
24-byte entries of tag plus 16-byte key. The two neighbouring entries hold
keys330_1 and keys505_a byte for byte, which is how the layout was confirmed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sceKernelLoadModuleVSH, sceKernelGetModel, and four of sceImpose_driver's calls
already have implementations; 5.xx just asks for them under different NIDs, so
the 5.50 shell got nothing back. Each one was identified by disassembling that
firmware's own module and comparing the body against 6.61's, where the same
function is exported under a name - the pairs are instruction-for-instruction
identical apart from context-struct offsets.
GetModel was the one that mattered most: unresolved, vshbridge handed the shell
a garbage model number, and it went looking for PSP-3000 resources on a dump
that has none.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
--nand had a field and an ApplyToConfig branch but nothing ever parsed it, so it
was silently ignored.
--screenshot-save only fired when a GE replay finished or a test used the
EMIT_SCREENSHOT devctl, and even then only under --compare. Anything else - a
game, or --vsh - ran to the timeout and wrote nothing. Capture the display at
the end of the run when nothing else did, which is what makes it usable for
looking at what a booting system actually has on screen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three from a read-through of the sceMp4 firmware-module path:
Clearing the module UIDs when the game says it's done with the MP4 module
didn't unload anything - it only meant the next load brought in a second copy
of libmp4.prx and mp4msv.prx, some 220KB at the top of user memory each time.
Keep them for the boot instead; __UtilityInit clears them per game, which is
the point at which they really are gone.
The flag test read g_Config directly, so it ignored the very fallback
CheckDisableHLEAvailability computes when the dump is missing - it would go and
try to load modules that aren't there while import resolution had correctly
stayed on HLE. It also ignored a boundary restored from a savestate.
sceKernelGetModuleGPByAddress checked one byte of the pointer it writes four to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The game screen already reports which updater a disc carries, but there was no
way to act on it - the only route to installing one was picking an updater PBP
out of the browser. The unpacker already handles being pointed at a disc, so
this just wires the menu entry to it.
Shown only when the disc actually has one, and not while a game is running:
installing wipes the NAND that game has mounted.
InstallUpdateScreen takes the archive size now, because for a disc the size of
the file it was handed is the game's and says nothing about the firmware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
With no kernel modules there's no firmware to speak of - it's the fonts we
pulled off a game's disc - and rows reading "Kernel modules: 0 / XMB: No"
say nothing. Keep the font count and the size.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
KIRK CMD1 writes header + data_offset + align16(data_size) bytes into outbuf,
and all three come out of the header the decrypter just decrypted, not from the
caller. The SHA1 check doesn't bound them - it only covers the header, so it
passes just as happily for a block that's been cut short.
The PSAR walker has to guess how long an updater's second block is (nothing
records it, so it tries the sizes real updaters use), and a wrong guess sent
KIRK off the end of the buffer: unpacking a firmware crashed roughly half the
time, on every version and disc I tried, depending on the heap layout.
Bound the write against the size the caller gave us, in all six decrypt types.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
The two fudge factors in the reverb path cancelled, which is why the
overall level felt roughly right.
- The send is accumulator * 0x20 >> 16, i.e. sample >> 2. We used >> 1,
driving the reverb 6dB hot.
- The return is (evol * out) >> 11. We used >> 12, i.e. 6dB quiet.
Net level is therefore unchanged, but the reverb now runs at the level
the presets were designed around. That matters because the filter clamps
internally, so a 6dB hot input changes how the feedback path saturates -
worst on the presets with heavy feedback.
Also adds a slider in the imgui.
The reverb presets came from nocash's PS1 table. Six of the nine are
identical on the PSP, but three are not.
Also correct our linear interpolation expression: we were close but
our formula can produce an off by 1 at times.
--debugger sets startBreak, so the run sits at the entry point until a client
resumes it. A session that forgets to do that looks like a frozen game rather
than a paused CPU - all the way down to "ticks: 0" - so say so on the way up,
and add --debugger-run for the common case of wanting the debugger attached to
a run that just goes.
Also stop headless forcing HLE for the graduated modules. Those come out of the
game's own disc rather than a firmware dump - libpsmfplayer.prx and friends are
user libraries, always present - so forcing them to HLE made headless quietly
disagree with the app about which code a game runs. Tekken 6 plays its movie
through scePsmfPlayer, and headless was faking it, so the movie never reached
sceMpeg at all; now the disc's real psmfplayer drives the real mpeg.prx.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An ME image is two KL4E streams back to back: the code that runs at
0x88300000, then a blob the image decompresses to ME local RAM at
0x00101000, which is where its data segment lives. Decompressing only the
first left every global unaccounted for.
--re-decrypt now reports how much of the plaintext the first stream used
and writes the remainder to <out>.tail, and accepts an already-plain
KL4E/KL3E file so that tail can be fed straight back in.
--re-decrypt runs pspDecryptPRX() over a file and unpacks the KL4E/KL3E
stream behind it. This opens up flash0:/kd/resource/*.img, the images the
Media Engine actually runs: they are ordinary tagged containers (tag
862648D1, which PrxDecrypter already has a key for) with the ~PSP
signature blanked, so the normal module loader never touches them.
--re-raw-base analyzes --re-module as a flat code image at a given
address rather than as a PRX. The decrypted ME images are raw MIPS with
no ELF around them; the address they were linked for is recoverable from
their own jal targets (0x08300000 for meimg.img).
Also makes PrxDecrypter.h self-contained - PSP_Header is built from _le
types, so it needs Common/Swap.h rather than relying on the includer.
The channel took only 256, 1024 and 2048 samples, so a game that hands it MP3
frames got SCE_KERNEL_ERROR_INVALID_SIZE and no music. Dead or Alive Paradise
does exactly that from its music player: sceVaudioChReserve(1152, 44100, 2),
1152 being the MPEG-1 Layer III frame size.
The format check moves below the sample count check to match: the module
returns 0x80000104 for a bad count before it ever looks at the format, so a
call with both wrong got the wrong error out of us. The two error codes we
already returned are the ones it uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A tool nobody knows about is a tool nobody uses. AGENTS.md gets a short section
pointing at it, plus the two things most likely to be got wrong when reading
the output: that a function's arity can't be inferred from the registers it
reads, since MIPS code passes arguments through untouched, and that a finding
is worth much more when the comment says which module it came from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>