Commit Graph
47529 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5 f9bd5682db libkirk: let C++ callers include its headers directly
kirk_engine.h and amctrl.h guard their declarations, but AES.h and SHA1.h
never did, and kirk_engine.h includes them from outside its own guard. So the
AES_* and SHA1* functions got C++ linkage in any C++ file that reached them
through there, and only linked for callers that happened to wrap the whole
header in an extern "C" of their own. Nothing had called AES_* from C++
before, so it stayed hidden until something did.

Guarding the two headers instead lets every caller include them plainly, and
the wrappers scattered around the tree come out. Both are pure declarations
over kirk_common.h's typedefs with no system headers behind them, so there's
nothing in there that shouldn't be wrapped.

kirk_engine.h also uses size_t without including anything that defines it,
which only held together because its includers happened to have it already.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:15:15 -06:00
Henrik RydgårdandClaude Opus 5 19723f59eb Decrypt the NPDRM modules a PKG game update installs
A .sprx from one of these packages is an NPDRM "\0PSPEDAT" container: a
0x90-byte header, then an ordinary ~PSP PRX. The loader only ever saw the
EDAT magic and gave up with SCE_KERNEL_ERROR_UNSUPPORTED_PRX_TYPE.

Step over the header, then derive the key the PRX inside is really
encrypted against: sceNpDrmGetFixedKey() over the content ID, XOR in the
licensee key the game handed us through sceNpDrmSetLicenseeKey(), then AES
under a module key that had to be added. Both halves of that were already
lying around unused - sceNpDrmGetFixedKey() had no callers at all, and the
licensee key was being kept and never read.

The rest of it is a fixed XOR that the PRX header's decrypt_mode selects
rather than its tag, so it's applied on the mode the way JPCSP does it and
the tag table is left alone - tag 0x407810F0 carries no seed of its own
there either, so ours was never wrong about it. pspDecryptType5() already
had a slot for both XORs; no new decryption logic was needed.

Decryption is only half of it: these modules are KL4E-compressed rather
than gzipped, so they also need Core/Util/KL4E.cpp, which is already there
for the firmware modules that use the same compression. With both halves
Shiren 4 Plus loads its one big .sprx and runs. God Eater 2 needed one
further fix that isn't in this commit - the type-B relocation bug in
ElfReader::LoadRelocations2, issue #8075 - and then plays.

docs/pkg_notes.md has the container layout and the key derivation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:15:15 -06:00
Henrik RydgårdandClaude Opus 5 f0dafdee10 Show and remove installed game updates from the game info screen
An installed update silently replaces what the game boots, so the info
pane now says when there is one - version, size and where it lives - and
the context menu offers to remove it again.

Removing takes the whole PSP/GAME/<DISC_ID> folder when the update is all
that's in it. When a digital game shares the folder, only PBOOT.PBP goes,
since deleting the folder would take the game with it and nothing records
what the install wrote. The confirmation names the exact path either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018izZ1mGTWhz2RqeudqsDQR
2026-09-08 11:15:15 -06:00
Henrik RydgårdandClaude Opus 5 3a70c6b149 Install PKG game updates, and boot them
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>
2026-09-08 11:15:15 -06:00
Henrik RydgårdandClaude Opus 5 5089caf2a0 Add a reader for PKG game update packages
PSP game updates were distributed as NPDRM .pkg files holding a patched
EBOOT (PBOOT.PBP) plus the data files the patch replaces. PkgUnpack reads
one: header, item table, both PARAM.SFOs, and the AES-128-CTR that covers
everything past the header - including the per-item key split, where an
item's pspType byte picks between the PSP and PS3 keys.

Nothing new is needed to decrypt these. All packages checked use PRX
tag 0x2E5E10F0 for their PBOOT, which PrxDecrypter already has a key for.

Also adds "PPSSPPHeadless --install-pkg=DIR", a sibling of --unpack-updater,
which installs without any UI. All packages install through it byte
-identically to a reference implementation.

Format notes are in docs/pkg_notes.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 11:15:11 -06:00
Henrik RydgårdandClaude Opus 5 ddc673916d Tools/pkg.py: read PSP update packages, and notes on the format
A standalone Python reader for the .pkg containers Sony shipped PSP game
updates in: it prints a package's header, metadata, both PARAM.SFOs and its
item table, and extracts the payload. Nothing in PPSSPP calls it - it exists to
work the format out and to have a second implementation to check the C++ one
against, the way Tools/ already holds a few other one-off analysis scripts.

docs/pkg_notes.md is what it was written from: the header and item table
layout, the two AES-CTR keys a single package mixes, and the detail that trips
up a first attempt - which key applies is per item, not per package, so a
reader that picks one produces garbage filenames for most of a package while a
few entries decode perfectly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:14:46 -06:00
Henrik Rydgård 1fc823681f Merge pull request #22252 from hrydgard/scemp4-real-prx
Run the real libmp4.prx/mp4msv.prx instead of our sceMp4 HLE, optionally. Fixes Speedball
2026-09-08 10:54:50 -06:00
Henrik RydgårdandClaude Opus 5 db839baa7e AGENTS.md: strengthen the HLE array-order rule, and stop claiming fixed line endings
The savestate rule about HLEFunction array order only lived under "Adding HLE
modules", so it read as advice for adding a module - not for adding one function
to an existing one, which is where it is easiest to get wrong. Promote it to Core
Safety Checks, where it applies unconditionally.

Also: whether a file is CRLF or LF depends on the checkout, since Windows auto-
converts everything to CRLF and Linux doesn't. Listing files as "CRLF" invited
converting them to match; the actual rule is to preserve what's on disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
2026-09-08 09:44:26 -06:00
Henrik RydgårdandClaude Opus 5 c80f3d33c2 Run the real libmp4.prx/mp4msv.prx instead of our sceMp4 HLE, when asked
The MP4 libraries turn out to be the easiest place to hand a game Sony's own
code: libmp4.prx needs only sceAudiocodecInit and sceAudiocodecDecode from us
plus ordinary kernel calls, and mp4msv.prx - where the 41 functions libmp4
leans on live - imports nothing at all. So with a firmware dump present the
pair can be loaded for real and left to decode through our sceAudiocodec.

Adds DisableHLEFlags::sceMp4, which loads and starts both modules when the
game asks sceUtility for the MP4 module, and a --disable-hle bitmask so a
headless run can ask for this without a config file.

Two things had to be fixed to make it work at all:
 - ModuleMgrForUser 0xD2FBC957 was unimplemented, and libmp4 calls it to get
   the gp of each callback it is handed. Implemented as
   sceKernelGetModuleGPByAddress.
 - Headless forced every module to HLE unconditionally, which silently undid
   the flag, and it did so before ApplyToConfig() had even parsed it.

Tested with Speedball 2 - Evolution, which uses sceMp4 for its music: the game
goes from 255645 calls into our stubs and 39 unresolved imports, to zero of
each and 1943 AAC frames decoded through sceAudiocodec.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 09:44:26 -06:00
Henrik Rydgård d11175bb88 Merge pull request #22254 from hrydgard/elf-relocation2-hi16-pairing
Fix type-B relocations losing the shared lo16 between two HI16s
2026-09-08 09:35:26 -06:00
Henrik Rydgård 3f5ddf9096 Merge pull request #22256 from hrydgard/log-callback-list
External log callback: Allow multiple handlers, fix bug in LogBroadcaster
2026-09-08 09:35:15 -06:00
Henrik Rydgård 0905d5d6d4 Merge pull request #22257 from hrydgard/websocket-mips-tracer
Expose the MIPSTracer over the WebSocket debugger
2026-09-08 09:31:52 -06:00
Henrik RydgårdandClaude Opus 5 fbcc8ee987 Fix type-B relocations losing the shared lo16 between two HI16s
LoadRelocations2 declared last_type, initialised it to -1, read it once - and
never assigned it. So the (flag & 0x38) == 0x08 case, which means "reuse the
lo16 the previous relocation carried", always saw last_type != 4 and reset
lo16 to 0 instead.

That matters because R_MIPS_HI16 computes ((op << 16) + lo16) + relocate_to and
then adds 0x10000 if bit 15 of the result is set, to pre-compensate the sign
extension the paired addiu will do. With lo16 wrongly 0 the carry decision is
made on the load address alone, so for any base whose low half has bit 15 set
the high half comes out one too high and the pointer lands 0x10000 past what it
should be.

A compiler emits exactly this pattern around a branch-likely: one lui in the
delay slot, another on the fall-through path, both for the same symbol, sharing
a single addiu after the paths converge. Only the second lui is adjacent to a
HI16, so the first one silently got the wrong high half.

last_type is assigned where JPCSP assigns its R_TYPE_OLD: at the end of the
branch that actually relocates something, so the commands that only move the
base around don't count as "the previous relocation" and a HI16/HI16/LO16 group
still pairs up across them. R_MIPS_NONE stops continuing the loop for the same
reason - it has to clear last_type, or a HI16 after it would reuse a lo16 that
isn't its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 08:55:57 -06:00
Henrik RydgårdandClaude Opus 5 ccdaa94f53 Expose the MIPSTracer over the WebSocket debugger
The tracer records every basic block the CPU executes and can write the
instruction stream out to a file, which is the tool you want when something
corrupts state and the question is "what actually ran just before". It was
only reachable from Developer Tools in the UI, so a scripted session had no
way to turn it on, and reconstructing the same thing from log-only breakpoints
means guessing what to watch before you know what happened.

Five events: cpu.tracer.start/stop/flush/clear/status. start takes the two
buffer sizes and clears the JIT cache by default, because blocks compiled
before tracing was on don't carry the LogIRBlock instruction the tracer feeds
on - without that a hot loop compiled earlier simply never appears. The trace
ring is cyclic, so a finished recording holds the last maxTraceSize blocks:
start it, run into a crash, and the tail of the file is the instructions that
led there.

Only the IR cores drive the tracer, so start refuses on the others and says
which core is loaded rather than recording nothing; status reports the same
thing as `supported` so a client can tell that apart from "nothing executed".
Everything that mutates tracer state goes through Core_RunOnCPUThread, per
docs/DebuggerThreading.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 18:43:38 -06:00
Henrik RydgårdandClaude Opus 5 4e24c195ac Fix the debugger log stream going out of step after 1024 messages
DebuggerLogListener keeps a ring of the last BUFFER_SIZE messages. read_ and
count_ count messages ever read/written, while messages_ is indexed modulo
BUFFER_SIZE - but the non-overflow path used read_ directly as the index. Once
a session had logged BUFFER_SIZE messages that index was past the end of the
array, so the first copy loop (bounded by BUFFER_SIZE) ran zero times and the
second one handed back messages_[0..readCount-1] instead: real log messages,
just the wrong ones, and every later poll stayed the same distance out of step.

It looks like the tail of the log going missing rather than being wrong, which
is a bad way to find out. A log-only breakpoint in a hot loop reaches 1024
messages in seconds, and then "the last thing logged before the breakpoint hit"
- exactly what such a breakpoint is for - names an event thousands of messages
old. Confirmed against a case with an independently known answer: a log-only
breakpoint recording a register in a loop that runs ~20k times now ends with
the value that register actually held at the final hit, where before it ended
several hundred iterations short of it.

The overflow path was already correct - it starts from nextMessage_, which is
an index - so only the one line changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 18:42:39 -06:00
Henrik Rydgård 98e70c8ca3 Merge pull request #22251 from hrydgard/log-callback-list
Let more than one thing receive the log stream at a time
2026-09-07 15:51:23 -06:00
Henrik RydgårdandClaude Opus 5 e26e2d28a0 Let more than one thing receive the log stream at a time
The log manager had a single external-callback slot, but the WebSocket
debugger registers one per *connection* - LogBroadcaster is a local in the
per-connection handler. So with two clients attached (the bundled JS debugger
in a browser and Tools/wsdbg, say) the second one to connect silently took the
log stream away from the first, and then whichever disconnected first cleared
the slot and stopped delivery to the other as well. A one-shot wsdbg command is
enough to do it: connect, take the stream, exit, and the long-lived listener
that was watching the log goes quiet with nothing to say why.

Make it a list with add/remove by handle. The dispatch loop holds the lock
across the callbacks so a listener can't be freed while one is running - which
is what lets LogBroadcaster delete its listener straight after removing it.
Enabling and disabling LogOutput::ExternalCallback belongs to the list now, and
disabling only happens when the last callback goes away.

libretro registers one of these too, and never removes it; it just moves to the
new call. It can't actually collide with the debugger - the libretro build
doesn't compile Core/Debugger/WebSocket at all - but there's no reason for it
to keep using an API that only has room for one caller.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 15:23:50 -06:00
Henrik RydgårdandClaude Opus 5 9799085c3a Let more than one thing receive the log stream at a time
The log manager had a single external-callback slot, but the WebSocket
debugger registers one per *connection* - LogBroadcaster is a local in the
per-connection handler. So with two clients attached (the bundled JS debugger
in a browser and Tools/wsdbg, say) the second one to connect silently took the
log stream away from the first, and then whichever disconnected first cleared
the slot and stopped delivery to the other as well. A one-shot wsdbg command is
enough to do it: connect, take the stream, exit, and the long-lived listener
that was watching the log goes quiet with nothing to say why.

Make it a list with add/remove by handle. The dispatch loop holds the lock
across the callbacks so a listener can't be freed while one is running - which
is what lets LogBroadcaster delete its listener straight after removing it.
Enabling and disabling LogOutput::ExternalCallback belongs to the list now, and
disabling only happens when the last callback goes away.

libretro registers one of these too, and never removes it; it just moves to the
new call. It can't actually collide with the debugger - the libretro build
doesn't compile Core/Debugger/WebSocket at all - but there's no reason for it
to keep using an API that only has room for one caller.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 15:15:10 -06:00
Henrik Rydgård fdd9baaba1 Merge pull request #22250 from hrydgard/audiocodec-field-names
sceAudiocodec: Use more information for decoding, describe more fields
2026-09-07 13:56:11 -06:00
Henrik Rydgård 8206ed88f7 Merge pull request #22249 from hrydgard/thread-run-clocks
sceKernelThread: actually accumulate runForClocks
2026-09-07 13:51:28 -06:00
Henrik Rydgård 1da84afbb0 Merge pull request #22243 from 4RH1T3CT0R7/fix/debugger-vfpu-register-view
Win32 debugger: show VFPU values, make the register list scrollable
2026-09-07 13:48:54 -06:00
Henrik Rydgård 8559ed971f Merge pull request #22240 from acts-1631/security/fix-infra-dns-json-validation
Validate infra DNS JSON responses
2026-09-07 13:42:54 -06:00
Henrik Rydgård b0af7a8dfe Merge pull request #22239 from acts-1631/security/fix-png-replacement-limits
Bound replacement PNG dimensions safely
2026-09-07 13:10:57 -06:00
Acts1631 90f7c6d195 Rely on PNG header dimension bounds
PNG replacement dimensions are validated by PNGHeaderPeek before the

decoded buffer is allocated, so the additional size_t overflow checks are

redundant.
2026-09-07 14:52:06 -04:00
Acts1631 4d51079ea8 Use HTTPS for infra DNS when supported
Select the metadata URL based on the platform HTTPS capability so

legacy platforms continue using HTTP while capable platforms avoid

downgradeable transport. Keep cache lookup and invalidation on the

same URL.
2026-09-07 14:51:47 -04:00
Henrik RydgårdandClaude Opus 5 a91448b318 sceKernelThread: actually accumulate runForClocks
nt.runForClocks was zeroed when a thread was created and copied out by
sceKernelReferThreadStatus, but nothing ever added to it, so every thread
reported having run for zero time forever.

Crazy Taxi: Fare Wars uses it as a liveness check. Its music state machine
samples the mp3 thread's run time once every 60 frames and compares it with
the previous two samples; when it doesn't move it concludes playback is
wedged, sets the stop bit, and the thread tears itself down and exits. The
game restarts it, and about a second later decides it's wedged again - custom
soundtracks restarted roughly once a second forever, whatever the file.

Bill the time since the previous switch to the outgoing thread, which is
exactly the thread that was running for it. The field is already part of the
serialized thread struct, so savestates don't change format; the timestamp
itself is re-based on load rather than saved, and only on load - saving runs
a measure pass and a write pass, and re-basing in those would discard the
time the running thread had accumulated since the last switch, letting a save
change what the game can observe.

Risk: this runs on every context switch, the hottest path in the scheduler.
It adds one CoreTiming read and a 64-bit add. Games that poll thread run
times will now see them move, which is correct but is new behavior.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 12:48:22 -06:00
Henrik Rydgård d1f33c9fd5 Merge pull request #22247 from hrydgard/kl4e
Implement KL4E/KL3E decompression, so firmware modules that use it can load
2026-09-07 12:22:16 -06:00
Henrik RydgårdandClaude Opus 5 909673f4ee sceAudiocodec: correct the 0x1004 note, record the ME's 0x68-byte view
me_wrapper.prx's dispatch table gives 0x1004 a handler that returns -1, so it
is plumbed through avcodec.prx but not implemented on 6.61 - not the real
sixth codec the earlier comment claimed.

Also records the bound that matters for the Atrac3 frame-size question: the ME
is handed a context whose first 0x68 bytes are the only ones made coherent, so
nothing outside that can be reaching it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 12:20:18 -06:00
Henrik RydgårdandClaude Opus 5 53eb468a28 sceAudiocodec: use the codec context for Atrac3 and MP3 instead of guessing
Atrac3 no longer hardcodes 384 bytes per frame. The context only carries the
joint-stereo flag for Atrac3 - libatrac3plus.prx writes nothing else there, and
AtracCtx2 already mirrors that - but exactly one of the five supported frame
sizes is joint stereo (66kbps stereo, 0xC0 bytes), so that flag identifies it
on its own. Everything else keeps the old 132kbps assumption, now via the
existing at3HeaderMap rather than a magic number.

MP3 was passing srcBytesRead as the input length, which is an output field
holding what the *previous* call consumed - zero on the first frame. Use the
bound at 0x28 instead, which is what the hardware uses and which the caller
guarantees is readable at inBuf, since it does a cache writeback over exactly
that range. Channels and sample rate now come from the context's channel
configuration and its version/sample-rate index pair, using the same table
avcodec.prx indexes, rather than being assumed stereo 44100.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 12:18:46 -06:00
Henrik RydgårdandClaude Opus 5 2c482a48b2 sceAudiocodec: name the codec context fields, and make the tail a union
The first 0x28 bytes of the context are the same for every codec - and for
sceVideocodec's own context, which annotates the same fields - so this is one
ME codec-context ABI. Everything after that is per-codec, with each library
writing a different set of fields, so it becomes a union.

Also documents that 0x28 is not a frame size for MP3: the hardware only uses it as a
cache-writeback length, so it is an upper bound - which is why the firmware
never bothers computing an exact one anywhere.

Adds codec id 0x1004, which the hardware accepts and handles much like MP3.
Unidentified, but the range check really does accept six codecs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 12:17:16 -06:00
Henrik Rydgård c0e09f8ef0 Merge pull request #22244 from hrydgard/raintegration-elevated-install
Don't load RAIntegration from an install we can't write to
2026-09-07 11:22:08 -06:00
Henrik Rydgård cae47b3df5 Merge pull request #22246 from hrydgard/more-misc-changes
sceFont: let glyphs draw past bytesPerLine, like the hardware does
2026-09-07 11:21:34 -06:00
Henrik RydgårdandClaude Opus 5 917cb1b0fc Implement KL4E/KL3E decompression, so firmware modules that use it can load
KL4E is Sony's second compression scheme for ~PSP modules, alongside gzip:
LZ77 tokens where every bit is arithmetic-coded, structurally close to LZMA.
PPSSPP could detect it but not decode it, so any module packed with it failed
to load at all - the loader reached the gzip path and bailed there.

Which scheme a compressed module uses is now decided by the payload's own
magic rather than assuming gzip. On a 6.61 flash0 dump this takes the kd/
modules that load from 126 to 129 of 129; libmp3.prx, libaac.prx and
libmp4.prx were the ones affected, and libmp3.prx decompresses to exactly the
elf_size its PRX header declares.

Two bounds problems in the format are fixed rather than reproduced: the match
copy is unchecked against the output buffer on real hardware, so a crafted
stream can write up to 255 bytes past it, and a long enough distance code
indexes copyDistProbs out of range. Input reads are bounded too - the format
carries no length and trusts the stream to terminate itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 11:06:17 -06:00
Henrik Rydgård 284472ae13 Merge pull request #22245 from hrydgard/mp3-stream-buffer
sceMp3: Stream buffering fixes
2026-09-07 10:20:22 -06:00
Henrik RydgårdandClaude Opus 5 27ca0f80fc sceFont: let glyphs draw past bytesPerLine, like the hardware does
SetFontPixel refused to write any pixel whose x fell outside
bytesPerLine, so a glyph drawn into a buffer with a bytesPerLine
narrower than its rows came out mostly blank. The hardware doesn't
bound it that way - it works out an address and writes, so the rows
overlap and the glyph smears across them. The declared bufWidth and
bufHeight, plus the address check, are what keep it in bounds.

Cache invalidation now covers the wider of bytesPerLine * bufHeight and
where the last row actually ends, since those are no longer the same
thing when the rows overlap.

Fixes font/charglyphimage and font/charglyphimageclip, moved from
tests_next to tests_good. The other font tests are unaffected, so
whatever ails them is something else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-07 09:27:13 -06:00
Henrik RydgårdandClaude Opus 5 5a0006efe1 sceMp3: take the lowest free handle, not the map size
sceMp3ReserveMp3Handle derived the new handle from g_mp3Map.size(), which
collides as soon as handles are released out of order: with 0 and 1 open,
releasing 0 leaves size at 1, so the next reserve returns 1 again. That
replaced the live context in the map without deleting it, leaking it and
handing the game a handle aliasing a stream it was still playing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:18:02 -06:00
Henrik RydgårdandClaude Opus 5 b855cec6d4 sceMp3: stop decoding at endPos instead of running off the buffer
A game can notify more data than the file actually had - audio/mp3/stream
asks for 3360 bytes and notifies all of them even when the read came up
short - so the tail of the buffer holds stale bytes from the previous half.
We happily decoded those, six frames past the end of the stream, because the
end flag only suppressed the zero fill and never stopped the decoder.

Check it before decoding too. The post-decode check stays where it was: the
hardware rewinds in the same call that decodes the last frame, so the sum
reads back as zero right after it, which is what audio/mp3/getsumdecoded
records. Moving the whole thing up front breaks that test.

Fixes audio/mp3/stream, added to tests_good - it walks 27 refills end to end,
so it also covers the half-buffer handout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:18:02 -06:00
Henrik Rydgård 24e8d931e0 sceMp3: hand out the stream buffer in halves, like the hardware does
The area after the 0x5c0 workarea is double buffered - a half only becomes
writable again once the decoder has consumed past its end, so decoding a
single frame usually frees nothing at all. We instead reported every byte a
decode had just consumed, which made sceMp3CheckStreamDataNeeded() answer
"yes" after every single frame.

Beats sleeps 50ms whenever that call says the file thread is behind, so it
slept once per decoded frame and delivered audio at 46% of realtime - the
badly stuttering custom soundtracks. It now decodes 3-4 frames per 3360 byte
refill, with the write pointer alternating between the two halves exactly as
audio/mp3/stream records from hardware, and keeps up.

AuGetInfoToAddStreamData/AuNotifyAddStreamData now derive the write position
from how much has been added rather than from how much is still buffered,
since the write pointer walks the halves in turn and doesn't follow the
decoder.

Fixes audio/mp3/notifyadd, moved to tests_good, and the "after decode" case
in audio/mp3/checkneeded.

sceMp3: note that the half-buffer split is only verified at 8192 bytes
2026-09-07 09:17:48 -06:00
Henrik Rydgård 0ab672f87f Merge pull request #22238 from hrydgard/sdl-prefer-wayland
SDL: Prefer Wayland when we're in a Wayland session
2026-09-07 09:10:33 -06:00
Henrik RydgårdandClaude Opus 5 3af6080075 add-string: check the arguments before translating anything
The command splits $1/$2/$3 off the invocation positionally and then states them
as fact, so calling it with a sentence rather than `/add-string <Section> "<Key>"`
yields three arbitrary words - and nothing downstream notices. Ask for them to be
checked against en_US.ini first, and to re-derive the real section and key from
the request if they don't hold up.

Also spell out, in both the command and docs/translations.md, that the en_US line
in the scratch file overwrites en_US.ini like any other language, so for an
existing key it has to match the current English text exactly - otherwise it
quietly rewords the string every other language was translated from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
2026-09-07 09:04:26 -06:00
Henrik RydgårdandClaude Opus 5 2b16e107b9 Translate "RAIntegrationNotWritable"
36 languages, following each file's existing handling of the sibling RAIntegration
string - most keep the name as-is, pl_PL uses "Integracja RA", tr_TR "RA Entegrasyonu",
ja_JP "RAインテグレーション" - and its formality. The rest are left to fall back to
English rather than guessed at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
2026-09-07 09:02:19 -06:00
Henrik Rydgård 856f627b74 Merge pull request #22242 from Kethen/cmake_cleanup
cleanup cmake aemu_postoffice building, allow connecting to 127.0.0.1 adhoc server outside of PPSSPP process
2026-09-06 14:30:22 -06:00
Katharine Chui aa4a4eb18d allow connecting to adhoc server on 127.0.0.1 with built-in server disabled 2026-09-06 14:11:18 +02:00
Katharine Chui 2731918f05 cleanup aemu_postoffice building in cmake project files 2026-09-06 14:09:33 +02:00
Henrik RydgårdandClaude Opus 5 afeab27186 Don't load RAIntegration from an install we can't write to
RAIntegration keeps its cache and local achievement data next to the
executable. If PPSSPP is installed somewhere that needs elevation to write -
Program Files being the obvious case - that write fails and takes the emulator
down as soon as a set or code notes are loaded, with no log to show for it since
the log can't be written either.

Check whether the exe directory is writable before handing the DLL to rcheevos,
and if it isn't, say so and point at the portable .zip instead. Achievements
themselves still work, so carry on to the normal login.

Fixes #21260

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
2026-09-05 16:15:45 -06:00
Acts1631 e4b5511f31 Validate infra DNS JSON responses
Make missing JSON dictionaries null-safe and reject infra DNS data

without the required default object or games array. Fetch the metadata

over HTTPS so a network attacker cannot replace a valid response with a

crash-triggering document in transit.
2026-09-05 16:25:11 -04:00
Acts1631 9ad3b821d9 Bound replacement PNG dimensions safely
Reject non-positive or oversized dimensions independently in the PNG

header peeker, matching the existing 8192 pixel decode limit. Use

checked size_t arithmetic before allocating replacement RGBA data so

crafted dimensions cannot overflow the allocation size.
2026-09-05 16:24:24 -04:00
Henrik RydgårdandClaude Opus 5 463bd7acf9 SDL: Prefer Wayland when we're in a Wayland session
SDL doesn't reliably pick Wayland on its own - on a machine with a
working Wayland compositor but no XDG_SESSION_TYPE it still chose x11,
putting us on XWayland. Ask for Wayland when WAYLAND_DISPLAY is set,
respect SDL_VIDEO_DRIVER if the user set it, and fall back to letting
SDL choose if Wayland then fails to initialize.

Also log the video driver we ended up with, which should help triage
the Wayland/X11 reports.

Fixes #21080

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-05 14:09:24 -06:00
Henrik Rydgård fefdf21db5 Merge pull request #22236 from hrydgard/savestate-hardcore-race
Fix savestate loads slipping past hardcore mode during boot
2026-09-05 14:07:40 -06:00
Henrik RydgårdandClaude Opus 5 124b0a43ae sceMp3: point the game at the end of the buffered data, not the start
sceMp3GetInfoToAddStreamData always handed back the start of the work
area, so the pointer never moved as data was added - the hardware walks
it forward past what's already buffered. AuNotifyAddStreamData now
takes the new bytes from where the game was actually told to write, and
checks that range fits the buffer rather than just comparing the size.

Also compare readPos against endPos as signed. readPos is an int and a
game can notify a negative size, which made it promote to a huge u64
and look like the end of the stream, so we reported nothing left to
write where the hardware still wanted 6721 bytes.

Fixes audio/mp3/infotoadd, moved to tests_good. audio/mp3/notifyadd
gets both of its value differences fixed but still fails: after a
decode the hardware reports no space at all, while we free what the
decode consumed, so we do one round more than it does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-05 13:50:49 -06:00