Commit Graph
15371 Commits
Author SHA1 Message Date
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å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
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
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
Henrik Rydgård 5d77d5daff Merge pull request #22229 from hrydgard/psp-file-attrs
Report PSP file attributes rather than the host's
2026-09-05 13:48:15 -06:00
Henrik RydgårdandClaude Opus 5 7d14efc331 Say why a savestate is refused while online, instead of dropping it silently
Same problem as the hardcore checks: SaveState.cpp asked NetworkAllowSaveState()
and just returned, so a load or save refused because you're connected did
nothing at all, with no explanation. Switched all eight to
NetworkWarnUserIfOnlineAndCantSavestate(), which is the same predicate plus the
standard message; its OSD id already collapses duplicates for the paths that
check twice on the way in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
2026-09-05 13:34:12 -06:00
Henrik RydgårdandClaude Opus 5 be834b4446 Ban freeze-frame in hardcore mode, and tell the user when a savestate is refused
Freeze-frame restores a savestate every frame, straight through
SaveState::LoadFromRam(), so it never touched the operation queue and neither
hardcore check saw it. Blocked at the toggle in the dev menu, and again in the
render loop, since hardcore mode can come up after the fact once the game has
been identified.

Enqueue also just dropped operations silently, so a load that arrived through a
path with no check of its own (--state, auto-load) did nothing with no
explanation. Both it and Process now go through WarnUserIfHardcoreModeActive,
which is the same predicate plus the standard message. Callers that already ask
it themselves return before reaching Enqueue, so nothing shows the message twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
2026-09-05 13:29:50 -06:00
Henrik RydgårdandClaude Opus 5 255896e89f Re-check hardcore mode when a savestate op is actually applied
The check lived only in Enqueue, but operations don't run there - they're
queued and applied later by Process(). During boot, HardcoreModeActive() reads
false even when hardcore is on, since it requires rc_client_is_processing_required(),
which only becomes true once RetroAchievements has finished identifying the game
asynchronously. Anything queued in that window passed the check, and was then
applied by Process() after identification completed and hardcore came up.

Auto-load wasn't even a race: EmuScreen::bootComplete() calls Achievements::SetGame(),
which starts the identify, and then checks HardcoreModeActive() a few lines below -
always false at that point. So "Auto load savestate" quietly worked in hardcore mode.
--state and a load-state hotkey pressed during boot got through the same way.

Re-checking per operation in Process() covers every entry point at once, and by
then identification has finished, so the answer is authoritative.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
2026-09-05 13:27:28 -06:00
Henrik Rydgård b7258cab12 Merge pull request #22224 from hrydgard/kernel-hle-fixes
Claude code review: Kernel
2026-09-05 13:02:27 -06:00
Henrik Rydgård 86de3307fb Merge pull request #22230 from hrydgard/sceFont-install-fonts
Install sceFont fonts off the disc when we don't have them in the NAND folder
2026-09-05 11:46:56 -06:00
Henrik RydgårdandClaude Opus 5 0e6614cf19 Report PSP file attributes rather than the host's
DirectoryFileSystem passed the host's permission bits and directory
size straight through to the game. The PSP has neither - its FAT driver
makes a mode up from the entry type and whether it's writable, and
reports no size for a directory. So a game saw 0644/0755 and a 4096
byte directory on Linux, but 0664/0777 and 0 on Windows, where the file
layer already synthesizes those bits.

Now both platforms report what the PSP does: 0777 for directories, 0664
for writable files, 0444 for read-only ones, and no size on a
directory. The parent ".." entry keeps its 4096, which is what the
hardware reports for that one.

Also fixes the synthetic PSP directory entry using 0x777 where 0777 was
meant.

Fixes io/directory/directory, moved to tests_good.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 09:58:10 -06:00
Henrik Rydgård 6d4bc5f261 Merge pull request #22207 from acts-1631/security/fix-pgf-bpe-validation
Validate PGF bits-per-entry fields
2026-09-05 09:48:59 -06:00
Henrik Rydgård 8b69031197 Merge pull request #22215 from hrydgard/shutdown-crash-fixes
Misc shutdown fixes on Windows
2026-09-05 09:47:39 -06:00
Henrik Rydgård 981250daf3 ISOFileSystem: Don't crash when the image has no ISO9660 volume
The constructor leaves treeroot null when it can't find a CD001 volume
descriptor, but GetFromPath walked into it anyway - TreeEntry *entry = treeroot;
then entry->valid - so any path lookup on a failed mount dereferenced null.

Reachable from the firmware installer, which mounts whatever file it's handed
and asks for PSP_GAME/SYSDIR/UPDATE without consulting Error() first. Point it
at a PlayStation disc image, whose descriptor sits behind a Mode 2 subheader and
so fails the signature check, and PPSSPP goes down. Identify_File checks for
CD001 before reporting PSP_ISO, so the game browser was never exposed.

Return null instead, which is what the rest of the function already does for a
path that isn't there, and what every caller expects.
2026-09-05 09:47:00 -06:00
Henrik Rydgård 00939f76fc sceFont: Only require the fonts the running game's firmware could have
The disc-updater install ran when ltn0.pgf was missing, so a font set unpacked
from an old UMD looked complete forever, and a later game wanting a font its own
firmware added silently got a bundled substitute instead.

Requiring the whole registry doesn't work either: firmwares older than a font
can never satisfy it, so we'd unpack the same updater on every launch and
announce it each time. What settles it is that a game can't ask for a font that
didn't exist when it was made. Record the earliest firmware known to ship each
font in the registry, and only require the ones the running game's firmware
would have had.

The version comes from PARAM.SFO's PSP_SYSTEM_VER, with the bundled updater's
version as a fallback. That keeps the whole thing stateless - nothing recorded
that could go stale when flash0 or the ini gets moved around.

Survey of a large library, unpacking flash0:/font from each disc's updater
across firmware 1.50 to 6.60: jpn0 and ltn0..ltn15 are in every one of them, and
kr0.pgf is the only registry font that arrived later - absent in 1.50, present
from 1.52.

Uses one directory listing rather than a stat per font, since on Android's
scoped storage the individual checks are slow.
2026-09-05 09:36:26 -06:00
Henrik Rydgård 5d608e39a3 Merge pull request #22223 from hrydgard/iso-metadata-hardening
Clamp ISO metadata and file sizes to the max possible extent
2026-09-05 09:28:29 -06:00
Henrik Rydgård 64cdcbfca2 Merge pull request #22225 from hrydgard/misc-fixes
Assortment of minor fixes
2026-09-05 09:28:10 -06:00
Henrik RydgårdandClaude Opus 5 111f01481c PSAR: don't decrypt entries a filter is going to reject
Decrypting an entry's contents is by far the most expensive part of
walking an archive, and it happened for every entry before the filter
was even consulted. Now it waits until entryData()/entryCompression()
asks, so pulling just the fonts out of an updater no longer costs a
full firmware decrypt. Records are decrypted independently of each
other, so deferring one is safe.

Unpacking fonts from a 3.11 updater goes 0.365s -> 0.133s; a full
unpack is unchanged and produces identical output. The compression
counts now describe the entries we actually decoded rather than
everything in the archive.

Also adds --unpack-updater-filter to headless, which is how the above
was measured.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 18:35:07 -06:00
Henrik RydgårdandClaude Opus 5 a641305c75 sceFont: use the fonts in NAND, and take them off the disc if we have none
sceFont never looked at flash0:, so a firmware the user installed was
ignored and we always fell back to our bundled substitutes - despite
the "ignoring NAND" warning suggesting otherwise. It reads flash0:/font
now, after the game's own fonts and the classic ms0 override.

And if there's nothing in NAND, we unpack just flash0:/font out of the
firmware updater on the running disc, which most UMDs carry. That turns
"install a firmware first" into something that happens by itself for
anyone playing a retail game.

EmulatedModelGeneration moves from InstallUpdateScreen into PSARUnpack
so both callers pick the same firmware file list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 18:35:07 -06:00
Henrik Rydgård f929a74780 Merge pull request #22221 from 4RH1T3CT0R7/fix/debugger-breakpoint-list-refresh
Win32 debugger: refresh the breakpoint list after memcheck changes
2026-09-04 18:17:31 -06:00
Henrik RydgårdandClaude Opus 5 2389b8ce96 headless: add --save-state, the counterpart to --state
--state could load a savestate but nothing could produce one without a
GUI, so savestate bugs couldn't be reproduced or regression-tested from
a script. This saves one partway through the run, once the game is
actually up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 18:14:41 -06:00
Henrik RydgårdandClaude Opus 5 5a84f287c4 sceIoRename: refuse wildcards, an existing destination, and don't wait on XDEV
Three ways our rename differed from the PSP's:

- A wildcard in either path was passed through to the host, so
  renaming "test*.txt" could quietly rename a real file. The PSP
  doesn't expand them here, it rejects them outright.
- Renaming onto a file that already exists succeeded, because the host
  rename() replaces the destination. The PSP refuses, and renaming a
  file onto itself counts as that too.
- Crossing devices returned the right error, but after the same wait
  as everything else. The hardware fails that one immediately.

Fixes io/file/rename, moved to tests_good.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 18:13:10 -06:00
Henrik RydgårdandClaude Opus 5 fd72f308af sceAudio: fix a crash when reserving a channel with none free
GetFreeChannel counted down with an unsigned loop variable, so i >= 0
was always true. With every channel already reserved it wrapped past
zero and kept indexing g_audioChans until it walked off the end -
sceAudioChReserve(-1, ...) segfaulted the emulator instead of returning
"no channels available". Reproduces on audio/sceaudio/reserve, which
crashed before printing anything.

Also gives sceVaudioChReserve the parameter checks it never had. It
took any sample count, channel count and frequency; the hardware allows
256, 1024 or 2048 samples, stereo only, and the same sample rates the
SRC channel accepts. Every value in the test now matches - what's left
there is only reschedule markers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 18:12:31 -06:00
Henrik RydgårdandClaude Opus 5 d675b467c1 sceUtility: fix the system param string size check and the adhoc channel error
Two things utility/systemparam caught:

A negative size passed to sceUtilityGetSystemParamString went through
Memory::IsValidRange, where it became an enormous range and came back
as a generic -1. The PSP just reports that the string doesn't fit, same
as any other size too small to hold it.

sceUtilityGetSystemParamInt returned 0x800ADF4 for an automatic adhoc
channel unconditionally. The FIXME there wondered whether the hardware
only does that once adhocctl is initialized - it does. Before any adhoc
module is up, which is the state nearly every game asks this in, the
hardware returns 0 and writes the channel out.

Fixes utility/systemparam/systemparam, moved to tests_good.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 18:12:24 -06:00
Henrik Rydgård bd3da95236 sceKernelMbx: Don't hang on a corrupt message list
ReceiveMessage walks the packet list until it comes back around to the head.
Every pointer it follows is validated, so a list containing a loop that doesn't
include the head just keeps walking - the emulator hangs inside the syscall with
no way out. Bound the walk by the message count.
2026-09-04 18:10:10 -06:00
Henrik Rydgård 61cb592b36 sceKernelMemory: Check the aligned pool size for overflow
sceKernelCreateFpl and sceKernelCreateTlspl validate blockSize * count with the
formula from hardware, but that works in 4 byte alignment while the actual
allocation uses the alignment from the options struct, which the caller picks.
A large alignment inflates each block enough that the aligned total can wrap:
we'd then allocate a small block but keep the full block count, and
sceKernelAllocateTlspl / the Fpl allocate paths hand out
address + block * alignedSize well outside the allocation.

Also give PartitionMemoryBlock::address a default. Only the savestate
constructor leaves it unset, and DoState returns early when the section is
missing, which left the destructor freeing an uninitialized address.
2026-09-04 18:10:10 -06:00
Henrik Rydgård ed2d118812 sysclib: Fix buffer overflows in strcpy and strcat
Both wrote past the end of guest memory with game-supplied pointers:

- strcpy validated the destination with IsValidAddress, which covers a single
  byte, then wrote strlen(src) + 1 bytes there.
- strcat validated both strings as they were, but the concatenation is longer
  than either, and that's what gets written.

Check that the result actually fits, and copy with an explicit size instead of
the unbounded str* functions.

Also, snprintf with a negative size fell into result.resize(limit - 1), which
converts to a huge size_t and throws.
2026-09-04 18:10:10 -06:00
Henrik Rydgård abed1ee9eb sceKernelModule: Bound the module import parsing
KernelImportModuleFuncs walks data straight out of the module being loaded, and
validated addresses without validating extents:

- The variable relocation list was scanned until it happened to hit a zero word,
  with no bound at all - an unterminated list runs off the end of guest memory.
  Now bounded by what's actually mapped from that address.
- nidData and varData were checked with IsValidAddress, which covers one word,
  and then indexed numFuncs (up to 65535) and numVars times. Check the whole
  array instead.
- The entry walk could read a final PspLibStubEntry that starts just short of
  libstubend and extends past it.

All of these need a corrupt or crafted module, not a normal game.
2026-09-04 18:10:10 -06:00
Henrik Rydgård c4160b4eed HLE: Small kernel cleanups
Initialize the wokeThreads locals that were passed by reference uninitialized in
the event flag, VPL and semaphore timeout handlers. Harmless today since the
callee only ever assigns to them, but every other use in the same files starts
at false.

Do the semaphore overflow check in 64-bit, so a large signal value can't wrap
past it into currentCount.
2026-09-04 18:10:10 -06:00
Henrik Rydgård 00e2e89914 sceKernelHeap: Return a null pointer on failure, not the allocator's -1
sceKernelAllocHeapMemory and sceKernelAllocHeapMemoryWithOption return a
pointer, so failure has to be 0 - JPCSP documents it as "the address of the
allocated memory block, or NULL on error". We passed BlockAllocator::Alloc's
result straight out, which is (u32)-1 when the allocation fails, and returned
UID error codes for a bad heap id. Anything checking for a null pointer took
those for success.
2026-09-04 18:10:10 -06:00
Henrik Rydgård 58c59f2cb4 sceKernelAlarm: Fix crash when a handler deletes its own alarm
AlarmIntrHandler::handleResult looked the alarm up again and passed the result
straight to __KernelScheduleAlarm without a null check - run() a few lines above
does check. An alarm handler that cancels its own alarm and then returns a
positive reschedule value dereferenced null.

Also, sceKernelSetSysClockAlarm validated four bytes and then read eight, and
returned a bare -1 instead of an error code.
2026-09-04 18:10:10 -06:00
Henrik Rydgård 0f94e01241 HLE: Clamp the wait timeout remaining time to zero
CoreTiming::UnscheduleEvent returns the scheduled time minus the current time,
which is negative when the event is overdue but hasn't been processed yet - the
exact situation when a wait is satisfied right around its own timeout. Only the
semaphore clamped it; everywhere else we wrote (u32)cyclesToUs(negative) into
the game's timeout variable, i.e. a huge bogus "remaining time".

Pulled the shared shape into HLEKernel::WriteRemainingTimeout so it can't drift
apart again - event flags, mbx, fpl, vpl, msgpipe, mutex, lwmutex and semaphore
all go through it now. The two thread-end sites keep their own copy since they
unschedule even when the game passed no timeout pointer, and sceUsb just gets
the clamp.
2026-09-04 18:10:10 -06:00
Artem Lytkin 20e0185707 Win32 debugger: refresh the breakpoint list after memcheck changes
The list reloads on SystemNotification::DISASSEMBLY, which every CPU
breakpoint mutation in BreakpointManager has posted since 8d0d601b5, but
none of the MemCheck ones do, so a memory breakpoint added from the list
(or the disasm view's dialog, the ImDebugger, the WebSocket API) only
showed up after the next Break. The enable checkbox then toggled against
the list's stale copy of the memcheck, so re-enabling one it had just
disabled disabled it again. Post the notification from the memcheck
mutations too, and from the condition setters of both kinds, since the
list's cached copy also feeds the edit dialog.
2026-09-05 00:54:50 +03:00
Henrik RydgårdandClaude Opus 5 fbdb54300e Put a ceiling on the CSO frame size
Follow-up to #22208, which bounded the index table but left the frame
size itself unbounded - readBuffer and zlibBuffer are sized straight
from it, so a 96-byte header could still ask for a couple of gigabytes.
Harmless enough on 64-bit, where the pages never get touched, but a
32-bit build would just fail the allocation.

Real images use 2KB to 64KB frames, so 16MB leaves plenty of headroom.
All 19 CSOs I have on hand still load.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 13:10:54 -06:00
Henrik RydgårdandClaude Opus 5 a506d0be6f Lowercase the extension reported by ZipFileLoader
Identify_File and friends compare against lowercase extensions, the way
Path::GetFileExtension returns them, but ZipFileLoader passed the name
from the zip through unchanged. So a file stored as e.g. "DUMP.PPDMP"
inside a zip failed to load, while "dump.ppdmp" worked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 13:10:54 -06:00
Henrik RydgårdandClaude Opus 5 fbdf611fc4 Chat: keep more lines of history, and add timestamps
The log was trimmed to 50 lines, which isn't enough to scroll back
through a conversation - now 250. Chat entries also carry the time they
arrived, shown as a dimmed HH:MM in front of the name, behind a new
"Show timestamps in chat" setting.

The timestamp is kept next to the text rather than baked into it, since
the chat view finds the sender by splitting the line at the first colon.

Fixes #15444

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 13:10:54 -06:00
Henrik RydgårdandClaude Opus 5 eb1bc34123 Map the right analog stick by default on Android pads
The generic Android pad map and the Retroid map were the only pad
defaults without it, so the right stick did nothing until mapped by
hand. Uses the same axes and directions as the desktop pad default.

Fixes #21591

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 13:10:54 -06:00
Henrik RydgårdandClaude Opus 5 b5bafa43b8 Prefer upper case PSP/SHADERS and PSP/THEMES
These two were lower case for legacy reasons, unlike every other
directory, which actually matters on case sensitive file systems like
the one iOS uses. An existing lower case directory is still used if
there's no upper case one, so nobody has to move their files.

Fixes #20527

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 13:10:53 -06:00
Henrik RydgårdandClaude Opus 5 6bb91bf2f0 Remove the "Up to 2" buffered frames option
Three frames in flight showed no performance benefit over two, so drop
the option and clamp existing configs (and the default) down to 2.
The now-unused "Up to 2" translation keys are left in place.

Fixes #21419

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 13:08:21 -06:00
Henrik RydgårdandClaude Opus 5 1c7f393e2b Misc shutdown fixes
* Destroy the ImGui debugger explicitly, not at static destruction time

* Windows: destroy the debugger windows before NativeShutdown()

* Clear the disassembly cache where it goes stale, not in view destructors
  (behind a no-op stub for libretro, which doesn't build DisassemblyManager.cpp)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 13:06:24 -06:00