On a PSP, sceKernelExitGame in a game the XMB launched goes back to the
XMB. Ours ended the session. In a session booted as vshmain.prx, an exit
from anything but the VSH itself now LoadExecs vshmain.prx again; the
shell then plays its opening and comes back on the Game column.
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>
What the firmware sets up for the ME at boot, which mcidclan's custom-core
libraries rely on, at kernel init when the ME is emulated:
- Sony's ME image at 0x88300000, as me_wrapper loads it: meimg.img on a
PSP-1000, me_t2img.img on later models, from the installed firmware's
flash0:/kd/resource, decrypted and KL4E-decompressed (only the first of
its two streams, the code). Custom-core calls functions in it, picking
per-model address tables by the word at 0x18. Without the firmware it's
logged and left out.
- A "SceMeRpc" sysevent handler record, which sceKernelReferSysEventHandler
(new, sceSysEventForKernel) returns, and custom-core takes over.
Also the VME's control registers (0xBCC00000), kept, with the status at
0x10 reading as done: custom-core's ME startup writes 1 there and waits.
mcidclan's custom-core poc, hw-mutex, gu-shared-list and exceptions
samples now run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ThreadManForKernel's sceKernelPollEventFlag, imported by Yabause-PSP's
me.prx, and sceCtrl_driver's 3.71-3.73 NIDs for sceCtrlSetSamplingMode
and sceCtrlSetSamplingCycle (at the same addresses as the user exports
in ctrl.prx), imported by LightMP3's support.prx.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The firmware's POPS manager imports these by their 6.60 NIDs:
sceKernelLoadModuleForKernel and sceKernelStartModule from ModuleMgrForKernel,
SysMem's sceKernelMemset and sceKernelGetUIDcontrolBlock, Sysclib's strnlen,
and sceKernelExtendKernelStack, which runs the callback like
sceKernelExtendThreadStack does.
sceKernelGetUIDcontrolBlock reports no such UID: the manager uses it to make
a descriptor it opened in kernel mode usable from user mode, and ours aren't
access-checked.
ExceptionManagerForKernel's table was never registered, so kernel
modules importing sceKernelRegisterDefaultExceptionHandler (LightMP3's
exception.prx) were left with an unresolved import. It now holds only
that one: registering the unimplemented entries would take them from
the firmware's exceptionman.prx under --vsh. Nothing could have saved
the old layout, since it was never registered.
sceCtrl_driver's sceCtrlSetButtonIntercept hides buttons from user mode
readers, which Daedalus does with the system buttons games don't get
here anyway: a no-op. Daedalus's Media Engine build needs both.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Custom firmware calls, which homebrew that runs ME code uses to reach
its own kernel modules: KUBridge's kuKernelCall runs a function with the
first eight of its arguments and stores v0/v1 in ret1/ret2 afterwards;
sctrlHENFindFunction (SystemCtrlForKernel, and a new SystemCtrlForUser)
finds a loaded module's export by module, library and NID.
Tanaka's Touhou ports find and call their GE4 wrapper's exports with
them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
- A partition memory block restored its allocator through
BlockAllocatorFromID, whose privilege check asked the thread current
before the load, already deleted with the kernel objects. Loading a
kernel-partition block under a user thread left it without an allocator,
crashing when it was freed. The saved id maps straight back now, and the
current thread is cleared along with the objects.
- The thread LoadExec leaves waiting (with no timeout) for plugins and VSH
modules to start wasn't saved, so a state saved while one was starting
hung the game thread after loading. The VSH modules still to start
weren't saved either, nor cleared.
- A state without the exit callback id left the previous session's.
- From a broken or crafted state: thread ids that GetFast trusts (current,
idle and ready), interrupt handler numbers, FPL and TLSPL indices, and a
MIPS call or action slot that isn't there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
EmuScreen only draws the crash info when an exception was recorded, and
showed just the Resume/Reset/Back buttons otherwise. Several ways of
stopping the core with CORE_RUNTIME_ERROR recorded nothing:
- A bad memory access in JIT code that MemFault can't skip, either
because it can't decode the host instruction or because ignoring is
only configured through the exception action, went to the crash
handler, while Core_MemoryException returned early under the default
Ignore action. A halting access is now always recorded.
- HLEReturnFromMipsCall on a corrupt stack, and a failed LoadExec, set
the state directly. They now go through Core_HLEError, which records a
new HLE exception type with a message that the crash screen shows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sceReg:made the registry writable in-memory (sceRegSetKeyValue/sceRegCreateKey overlay + merged cached lookups), which is what fixed the Chinese font. Fix#14032
__DmacDoState and __UsbCamDoState existed but weren't in the module list,
so the memcpy deadline and camera state carried over from before a load.
The NpDrm licensee key wasn't saved or reset at all, so EDATA opened after
a load in a fresh session couldn't be decrypted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
KernelHeap was missing from CreateByIDType, and an unknown type returned
without an error, desyncing the rest of the load. States from before exit
callbacks kept the boot-time action slot, which sceMpeg's restore took over.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The third argument is a timeout pointer, as threadman.prx shows. A kernel
address from user mode is ILLEGAL_ADDR there; we used to write through it.
Also, no lookup by index: the syscall requires the exact uid.
Adds the threads/tls/allocate test, recorded on hardware.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This is the syscall usersystemlib's sceKernelGetTlsAddr makes when the
thread's cached TLS address is null, as (uid, &addr, 0). Code that has to
run before usersystemlib.prx is loaded (like plugins built with a Rust SDK)
inlines sceKernelGetTlsAddr and imports this directly.
Shares the allocation with sceKernelGetTlsAddr. A thread waiting on a full
pool stores its address pointer as the wait value, so no state changes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
__MpegBaseInit was called by __MpegInit rather than by __KernelInit, and
__MpegBaseShutdown had just been added the same way. Every other module is
started and stopped directly by the kernel - __MpegBaseDoState already was -
so do the same here and let sceMpeg.cpp mind only its own state.
The order is unchanged: __MpegBaseInit ran first inside __MpegInit and now sits
just before it, __MpegBaseShutdown ran last and now sits just after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The output pixel mode decides both the colour packing and the bytes per pixel of
the conversion, and a game sets it once per movie rather than per frame - so a
state resumed mid-movie converted at the default until the next
sceMpegBaseCscInit, which may never come. The gathered PES payloads go in too,
since a state can land between the copy and the decode that consumes it.
The section is optional (minimum version 0), so states written before it still
load.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The last piece mpeg.prx needs from us. It decodes through AvcDecoder and writes
the result where sceMpegbase expects to read it: for type 0, the eight-buffer
tiled layout, produced here as the exact inverse of the reader added with the
colour conversion. Type 1 hands back plain planar YUV instead.
The descriptor mpeg.prx passes in is empty - on hardware the Media Engine owns
the frame buffers and reports back where it put them - so they are allocated
here and their addresses filled in, and sceMpegbase can recover the full set
from that allocation afterwards. The list is only read as addresses once it
holds addresses: a caller that leaves small integers there used to take us
straight into a memory exception.
Both those buffers and the block sceVideocodecGetEDRAM hands out live in a
model of the ME's own 2MB of embedded DRAM rather than in either PSP partition,
with a BlockAllocator carving it up. The CPU can't reach ME memory on hardware:
mpeg.prx keeps the EDRAM value and hands it back without ever dereferencing it,
and it has no way to say where the frame buffers should go - sceVideocodecSetMemory
is given a frame size and a buffer count, 480, 272 and 2 for a full-screen movie.
Taking either from the game would push its own allocations around or spend memory
a real PSP never spends, so sceMpegbase reads the frame through a host pointer
into that block instead of through Memory::.
A game can hold several contexts at once - Silent Hill Origins runs two, one
that owns the EDRAM and one that does every decode - so the decoder, the frame
buffers and the EDRAM address are per context, keyed by context address the way
sceAudiocodec keys its decoders. The EDRAM itself the firmware tracks in the
caller's context struct and nowhere else, including refusing a second request
rather than replacing the first, as videocodec_260.prx shows, so we do the same.
Registered at the end of RegisterAllModules, since a savestate stores the
syscall opcode encoding that order.
GetSEI, ScanHeader, GetFrameCrop and the two unnamed NIDs return 0 and log -
mpeg.prx calls them but nothing yet shows what they need to return, and
guessing seemed worse than being loud about it.
Untested: nothing calls this until mpeg.prx is loaded, which is the next step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mostly small stubs:
- sceImpose: GetParam/SetParam/Changes/SetStatus, plus the 6.60 alias of
sceImposeGetBatteryIconStatus. Also fixes that function's first output - it
is a plain "is it charging" boolean, not a BATTICON_ value. We wrote
PSP_IMPOSE_BATTICON_NONE (0x80000000) there, which games ignore but which the
VSH reads as "no battery" and draws the empty-battery indicator for. These
are the bulk of the traffic: the VSH calls sceImposeChanges once a frame, so
this alone removes ~10000 trapped calls from a boot.
- SysMemForKernel: sceKernelSetRebootKernel, sceKernelSetUmdCacheOn.
- scePower_driver: scePowerSetWakeupCondition.
- sceHprm_driver, sceUsb: one NID-named call each, as in JPCSP.
Three groups are deliberately left unresolved, with comments explaining why,
because resolving them lets real flash0 drivers walk into hardware we do not
emulate and the boot dies where it used to reach the shell:
- ThreadManForKernel mutex/fpl NIDs: the NAND and ID storage drivers use these
to init, then poll the NAND controller at 0xbd101300 forever.
- InterruptManagerForKernel intr registration: 31 calls, then a stall in GE
list execution with no plugin module ever started.
73 unresolved import hits over 37 distinct module/NID pairs remain in a VSH
boot, mostly sceSysEventForKernel, sceSuspendForKernel and the various
*_driver modules that need real hardware behind them.
The sceImpose savestate section goes to v2 for the two new state variables.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
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)
This is stuff encountered in the VSH boot research.
sceRtc_driver, scePower_driver, sceImpose_driver, ThreadManForKernel funcs,
sceRtcGetAlarmTick, sceHprm_driver/sceHprmReadLatch
sceVshBridge_Driver imports sceKernelResumeDispatchThread, SuspendDispatchThread,
and NotifyCallback from ThreadManForKernel, but they were only registered under
ThreadManForUser. Added sceKernelGetUserLevel and sceKernelIsUserModeThread (new).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
Adds SceKernelLoadExecVSHParam (matching JPCSP's reference layout - its
first four fields line up with the existing SceKernelLoadExecParam,
which is why the plain sceKernelLoadExec already worked for
sceKernelLoadExecVSHMs2) and fills in the rest of LoadExecForKernel's
NIDs from JPCSP: real implementations for sceKernelExitVSHVSH/Kernel
(mirrors sceKernelExitGame) and sceKernelLoadExecBufferVSHUsbWlan (loads
an exec from an in-RAM buffer instead of a file - the VSH's "push a game
over USB/WLAN" path), plus UNIMPL stubs for everything JPCSP itself only
knows by NID.
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 - a pure
extract-method refactor of the single most heavily used boot path in the
emulator. Verified no regression: same 11 passed / 9 pre-existing-failed
split on pspautotests/tests/cpu/*, and loader/bss still passes, before
and after this change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU