The user modules started after the manager now come from the firmware's
own boot configuration (pspbtcnf.bin's POPS mode), since they differ by
version: 4.05's pafmini.prx needs heaparea2.prx first. 6.60's also
lists common_util.prx, which wasn't started before.
4.05 renumbered the kernel NIDs again. Its aliases are checked like
5.55's, and the sysreg ones by what they do to the registers: the call
sites got five of those wrong. Its manager allocates from a pool with
the kernel's sceKernelAllocateFpl, which stays unresolved for kernel
modules (see ThreadManForKernel), so only the manager's import gets the
user one. scePower_driver gets 6.60's clock frequency NID, which 6.20's
and 4.05's old one now map to.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They import by 5.55's NIDs, and their managers are 5.55's at other
layouts. 5.0x's scePopsManLoadModule only loads the emulator when its
second argument is nonzero (later ones skip it on 2), so the layout says
what to pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5.55's manager is an older design: the kernel hands it the version key
(there's no license probe to replace), and the emulator opens the game
through a manager export that first looks up its license. A converted
PBP has none, so that lookup now returns success, as the MAC checks do.
Store games still need 6.00 or later, so the 5.55 layout only matches
for converted PBPs.
Its code changed too much to map every import by call site, so its NID
aliases are only the ones used here, each checked by its call site (and
the sysreg ones by lowio.prx's code).
The emulator's disc read call is now found by the block size next to
it, which 5.55's pops.prx passes from the other side of the jal: the
same single site as before in every 6.x emulator.
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>
Sony renumbered the kernel NIDs between firmware versions, so a module
from 6.3x imports the functions we implement under 6.60's NIDs by other
ones. Rather than a second table entry per function, an alias table maps
the older NID to the implemented one, and GetHLEFuncIndexByNib() falls
back on it. Savestates store the table index, which this doesn't touch.
The 6.30-6.39 entries cover what popsman.prx imports (the same NIDs in
all of them). They come from matching the call sites in popsman.prx's
6.39 and 6.60 builds, whose code is the same, and agree with matching
the exporting modules' code where that's unchanged.
6.30/6.31's popsman.prx also takes the semaphore the main CPU and the ME
share through sysreg calls, which 6.60's does inline: implemented here as
lowio.prx does them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The game browser left out EBOOTs with the PS1 category, which couldn't
run. Now they're listed where the Media Engine can be emulated
(Memory::CanCreateMESpace). And as a PS1 game's PARAM.SFO has no
DISC_TOTAL, the browser took it for homebrew and gave it a made-up ID
(SCUS00580 for Crash Bandicoot's SCUS94900): its own is kept now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
How the firmware's POPS manager and emulator are booted, what stands in for
the Media Engine, what's patched in them, the rendering changes they need,
and what's not done yet.
Both as CFW's popcorn does, for any EBOOT. The emulator only shows the
manual when a name the manager hands it matches one it keeps, and popcorn
makes that check pass.
It only takes an 80x80 PNG as ICON0. A converted EBOOT without one hangs,
and one with a 144x80 ICON0 (the size of a PSP game's) crashes it. Such an
ICON0 now reads as a blank 80x80 PNG: its size and its read are replaced.
Both are found by their code, which is the same in pops_01g and pops_02g.
In impose.prx (6.60), it returns a flag impose's thread sets each pass while
HOME is held, but only if impose leaves HOME to the application. Its init
does that for application types 0x300 (POPS) and 0x400, and
sceImpose_9DBCE0C4 changes it. So games get 0, and the PS1 emulator, which
polls it to open its in-game menu, gets HOME.
It was unimplemented, so it returned SCE_KERNEL_ERROR_LIBRARY_NOT_YET_LINKED.
The manager's module_start registers its license check with SysMem
(SysMemForKernel_310802A9), and the kernel calls it before loading the game,
which needs the game's license (.rif) and activation. The init thread now
calls it, replaced by one that takes the version key from the disc image's
PGD header (its MAC_70 is the key's MAC), as popcorn does on the Vita. It
latches the key and the content ID fingerprint where the manager's first
successful check would. The disc image's MACs are checked for real, with
sceAmctrl_driver.
Crash Bandicoot's store release is playable, with sound.
EBOOTs made with popstation and similar tools have a plain disc image of
raw DEFLATE blocks, where a store game has a PGD in the emulator's own
format, and no MACs. Like popcorn: the emulator's decoder calls go to its
scePopsMan_0090B2C8 import, which now inflates; its CD audio track index
check always takes the full-length branch, as the converted image adds CD
audio tracks the index doesn't count; and the manager's two MAC checks
pass.
A converted EBOOT is one whose disc image doesn't start with a PGD.
Crash Bandicoot is playable.
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>
Yabause-PSP's me.prx ORs 0xA0000000 onto its own addresses to share a
buffer with the ME uncached, which is 0xA8xxxxxx for a module in kernel
RAM. The main CPU's space has no views there, so it crashed.
All of kernel segment 1 (0xA0000000-0xBFFFFFFF) now counts as MMIO, and a
device below the hardware registers maps RAM and VRAM onto their uncached
mirrors at 0x40000000. The main CPU's native kernel loads and stores send
the whole segment to that path; the ME keeps 0xBC000000 as the boundary,
since its space maps the rest.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ME runs a slice behind the main CPU, so a main CPU polling a mailbox
in the boot page saw the ME's progress up to a slice late. Reading the
boot page now runs the ME up to where the main CPU is in its slice first,
and the end of the slice runs only the rest. me/audiofifo, which times
the ME's playing from the main CPU, read 43900Hz for 44100.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The audio block's FIFO, measured from the ME on a PSP-1000. In the next
set: it needs the ME on, and Sony's ME firmware to hand the ME back to.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The audio hardware at 0xBE000000, as POPS's ME loop (popsman.prx +2F88)
drives it: after setting it up, it writes one stereo sample (left in the
low half) to 0x70 whenever status 0x28 bit 5 says the FIFO has room,
and sleeps in halt in between on the ME's interrupt source 12. Measured
from the ME on a PSP-1000 (pspautotest me/audiofifo): the FIFO holds 24
samples and plays them at 44.1kHz while 0x04 bit 1 is set; status 0x28
has bit 4 while playing, bit 5 while there's room, and bit 1 once it has
played out its last sample, a sample after the FIFO empties; and source
12 is a level, up while there's room, which halt wakes on with
interrupts disabled. What it plays is mixed into the output.
A halted ME used to sleep until the main CPU's next slice. It now wakes
within the slice when the full FIFO next has room, as POPS's loop sleeps
for every sample.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Kernel code the ME runs calls a few firmware functions directly: POPS's
ME loop calls sceKernelDcacheWritebackInvalidateAll for every sample. On
a PSP the loader links kernel imports as direct jumps, so the ME runs
the function's code; here they're HLE syscall stubs. The cache functions
only work on the calling CPU's caches, and the ME's aren't emulated,
so they return 0 and do nothing, as does an unresolved import (as on
the main CPU). Others are still an error, now naming the function.
The IR ends the block after one, as for a syscall on the main CPU: a jr
with it in its delay slot leaves the exit to it, and the ME ran on into
the next stub.
A syscall instruction of the ME's own should take its exception vector
(ExcCode 8); none has come up yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
popsman.prx starts the Media Engine and sets up audio through them. The
ME reset, bus clock and AVC reset are the calls the older NIDs already
have. The others set bits of registers lowio.prx 6.61 writes for them
(0x50 bit 15+n, 0x58 bit 16+n, 0x60 bit 18, 0x78 bits 7+n and 11), and
C36775AD (called with 0, 1 before audio is set up) isn't read yet. The
system register device now keeps every register it doesn't model.
Under --vsh these stand in for lowio.prx's own, which write the same
registers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
amctrl.prx's PGD (BB) MAC and cipher functions, for kernel modules that
import them: the firmware's POPS manager checks and decrypts a store PS1
game's disc image with them. libkirk's amctrl.c reimplements the module, and
its MAC_KEY and CIPHER_KEY have the layout of the contexts the callers keep in
their own memory, so those are used in place. 6.60 NIDs.
MIST (and psp-media-engine-safe-task, and NJEMU through it) restart the
ME through me_wrapper's bootstrap at 0xBFC00040, after patching it to
point a0 at main RAM, which makes the firmware take its resume-from-
sleep path: restore eDRAM from a0 and go back to the main loop. They
then put a task's address in the firmware's command table with
DMACplus and send a command with the soft interrupt.
- The boot now leaves a bootstrap with 6.61's layout at 0xBFC00040,
and a stand-in for me_wrapper's ME stub (which isn't loaded under
HLE) that runs the image.
- The firmware never sets Status.IE: it gates interrupts with mtic
alone, so either one enables them now. Its main loop enables them for
only a few instructions, so mtic takes a pending one right away.
- Opcode 0x2C, which the resume path runs, is a no-op on the ME.
- Sysreg 0x00 reads 0 (the bootstrap's debug handler check), 0x70 is
kept, and the DDR flush register (0xBD000004) is always done.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Instead of only putting the image's code at 0x88300000, the ME now runs
it to where a PSP's ME is after boot: idle in its main loop's halt,
waiting for commands. As me_wrapper's boot (6.61, sceMeCore_driver
5dff5c50 and 0x1c30) and its ME stub leave things: the code at
0x88300000, the image's second stream still compressed after it, the
boot page's parameters (0x70C, 0x710 the CPU clock in MHz, 0x714), and
the ME at 0x88300000 with a0 at the second stream, which the code
decompresses into eDRAM itself. The stub's part (KL4E, caches) is done
here, since it runs main CPU kernel routines.
On a PSP the firmware has long since booted when a game starts, so it
runs to its idle halt the first time the ME runs, outside emulated time
(the JIT can't run earlier, at kernel init). About 80ms of startup in a
Debug build.
The compressed copy and the starting stack go below our idle threads'
stacks at the top of kernel memory, where a PSP has them higher up.
The firmware also needs:
- the ME to ignore the low two bits of jr/jalr targets: its command
table keeps flags there (interpreter, and the IR, for the ME only)
- source 5 of the ME's interrupt controller, the codec hardware it
starts and waits for in halt during init, which isn't emulated, so it
finishes at once
- sysreg 0x54 and 0x5C kept
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ME's startup code (custom-core, libmecore) sets them, and Sony's ME
firmware lowers them around its halt. Only kept, like the other ME
devices, and only while the ME is emulated.
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>
The calls homebrew starts the ME with (1.50's NIDs, and 3.71's for the
ME's reset and bus clock) set or clear a bit of the reset or bus clock
register the way the firmware's driver does, returning whether it was
set. Only those NIDs, so a later firmware's lowio.prx keeps its own
exports under --vsh.
Kernel modules copy their ME code to the boot page with SysclibForKernel's
memcpy, which now goes through MMIO for hardware addresses like the
replaced memcpy already did (MMIO::Copy, shared).
pspsdk's mebasic, MElib's test, PSP Bunny, snes9xTYL and ProTrekkr's test
now start their ME.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As the firmware's DMACplus driver programs it (lowio.prx 6.61,
sceDmacplus_driver_d126494b for AVC and 9c492b9b for VME, the same in
1.50's dmacplus.prx): eight source buffers at 0x120-0x13C (two half
height sets), size in macroblocks and the mode at 0x140, two destinations
at 0x144/0x148, stride and output format (0 8888, 1 565, 2 5551, 3 4444)
at 0x14C, a Q3.7 matrix at 0x150-0x158 and the luma offset at 0x15C,
whose default (0x110, 16) is the firmware's. Writing 0x160 runs it, and
it finishes at once, with 0x04 bit 1 set.
AVC sources are in the decoder's tiled layout (UntileYCbCr): interleaved
into one destination they're the frame's even and odd rows, and going
to two, each set is a picture of its own with its own chroma, as
mcidclan's me-avc-csc-hd lays out its data. VME sources are planar.
Not checked on hardware: 0x140 bits 1 and 2 (taken as chroma line
replication and leaving out the second set, from mcidclan's samples)
and 0x15C bit 8 (taken as offsetting chroma by 128). mcidclan's
vme-hardware-csc, avc-csc-hd and vme-csc-hd-992x992 show their pictures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A PL080-like controller: channels 4-7 at 0xBC800180, run at once when
enabled, following linked list items of five words (source, destination,
next, control, configuration). Addresses are physical, and on the ME's
bus (a master bit in the control word) the low ones are its eDRAM.
Transfer size is in units of the source width. A finished channel with
its interrupt bit set shows in 0x04, cleared through 0x08.
0xBC800100-0x10C set the LCD's framebuffer, which goes to sceDisplay.
Pixel format 0 is 8888 (mcidclan's samples); others are logged. The
colour space conversion registers (0x120-0x160) are only kept so far.
Like the other ME devices, this answers only while the ME is emulated.
mcidclan's dmacplus-me2sc and sc2me-then-me2sc samples show their
transferred eDRAM.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Most homebrew ME code waits in a loop like lw t0, flag; beq t0, zero,
loop; nop: Daedalus/MElib/NooDS's mediaengine.prx, snes9xTYL, PMP Mod,
Yabause, FBA, the Touhou ports (with a few nops in it). Such a loop stores
nothing, has only plain loads, integer ALU ops, nops and branches, and
writes every register it reads before reading it in a pass, so every
pass goes the same way while memory stays the same - which it does while
the ME runs, since the main CPU doesn't. At the top of one, between
parts of a slice, the ME interprets a pass, and if it comes back to the
top, waits out the rest of the slice.
Loops that also store (a heartbeat counter) or span several blocks
aren't recognized, nor are delay loops counting down a register.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
width/height is volatile and influenced by the framebuffer size detection
of the latest draw so chck bufferHeight instead.
The old check broke the homebrew OOT port.
MIPS.h includes MemMap.h for the address space's definition. Of the
files that include MIPS.h, nearly all already got MemMap.h another way.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The native dispatcher generated for the ME leaves when its downcount
runs out instead of calling CoreTiming::Advance, and IRNativeJit gets
RunDowncount. Pointerify is decided from the CPU's own memory base. ARM32,
RISC-V and LoongArch64 still give the ME the IR interpreter.
LoadKernel/StoreKernel are now compiled on ARM64 and x64 rather than
going through the IR interpreter one op at a time: memory directly, or a
call to fixed code (which saves the caller-saved registers guest values
live in) for 0xBC000000-0xBFFFFFFF. ARM64's block size estimate counts
them as such. The 16-bit ones stay generic while the software renderer's
depth mirrors are active, as Load16/Store16 do. That matters for the main
CPU's kernel code too, and for all code while the ME is emulated, which
runs in kernel mode everywhere. mcidclan's me-soft-interrupt went from
476 billion to 6.4 billion host instructions for 3 emulated seconds,
ending with the same ME counter as the IR interpreter.
The ME unit tests run on the native JIT too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Unless the interpreter is selected, the ME gets its own IRInterpreterJit,
with its own block shadow, and runs it without CoreTiming
(RunDowncount, split out of the dispatcher loop). Its MIPSState never
touches the main CPU's replacement hook.
The IR frontend compiles through the CPU's address space: instructions
read as they are in the ME's memory (no main CPU replacements or hooks),
kernel mode always, jump targets and block bookkeeping checked against
its rules, syscall left to the interpreter (which traps it). An
interpreted instruction that ends a block now gets its own pc first, so
halt goes on from the right place.
The main CPU's icache invalidations are passed on to the ME, which runs
code from the same RAM.
The unit tests run the ME on both cores. mcidclan's me-soft-interrupt
runs about four times as fast.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sysreg (0xBC100000) gets the ME's reset bit (0x4C bit 2), the soft
interrupt (0x44, which interrupts the other CPU: the ME's controller from
the main CPU, PSP_MECODEC_INTR from the ME), the hardware mutex (0x48,
each CPU sets its own bit only while the other's is clear) and the clock
and config registers it writes. The ME's interrupt controller (0xBC300000,
write-1-to-clear, mask at 8) answers the ME only. All of it is the ME's
state and stays unhandled without an ME.
On the ME: interrupts to EBase with EPC and EXL, halt sleeping until an
unmasked interrupt, and Count, which doesn't run: it stays 0 across 10ms
with ERL set after reset and with Status cleared, and reads back what was
written. Reset keeps the GPRs and sets Status ERL/BEV/SR, which homebrew
relies on: mcidclan's samples run C on the stack Sony's ME code left. The
values are from a kernel probe on a PSP-1000 (sp 0x801FFFE0, COP0 22 = 1,
Status 0x20500404, mfic 0, which doesn't gate interrupts).
The main CPU runs base interrupt handlers the way interruptman calls them
(a0 intno, a1 arg, a2 interrupted pc; -2 disables), and interrupt 31 has
a stand-in for me_wrapper's handler, which homebrew releases first.
While the ME is emulated, any main CPU code may access hardware registers:
homebrew calls its own code in kernel mode through a kcall module. This
is a stopgap (g_kernelModeAnywhere, marked HACK): kernel mode should be
part of the CPU's state, switched on while a kernel module's export runs.
The memcpy/memmove replacements copy through MMIO when an end is a
register address, for code copied to the boot page.
mcidclan's me-soft-interrupt sample now runs, counting each interrupt in
both directions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MIPSState::IsMediaEngine() forwards to its address space, so callers
don't have to reach through mips->space for it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
get-debloated-pkgs.sh hits the GitHub API unauthenticated, so on shared
runner IPs it gets 403 rate limit, sleeps 10 minutes and fails. It uses
GITHUB_TOKEN when set. The pkgforge pacman mirror also drops downloads
now and then, which failed the whole job.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Any JIT block invalidation, such as loading a module, sets
CORE_REENTER_DISPATCH. Core_Break only acted in CORE_RUNNING_CPU, so a
memory exception in that window was logged and dropped on the JITs and
the IR interpreter, while the interpreter (which never asks to re-enter)
stopped. That's how modules/cutrel failed only with --cpu=interpreter.
Stepping supersedes the re-entry request, so break there too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>