Commit Graph
47505 Commits
Author SHA1 Message Date
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å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
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
Henrik Rydgård c1b822ad07 Merge pull request #22190 from hrydgard/texture-bounds-fixes
TextureCache: fix two host-memory overruns from GE texture state
2026-09-05 13:48:41 -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 dcc23fca7c Merge pull request #22234 from hrydgard/naett-fixes
naett: Fix leaks, undefined behaviour and error handling across all backends
2026-09-05 12:52:24 -06:00
Henrik Rydgård a15e654f11 naett: Don't hand a closing response to backends that can't see its request
naettClose cleared res->request and then asked the backend to close the
response - but the request is what a backend needs to do that. On Windows it
owns the WinHTTP handles, so the close had nothing to work with, which is part
of why it did nothing at all. Backend first, then clear.

With that in place, the Windows close unhooks the status callback and shuts the
request handle, so a completion raised later can't write through the response
after it's freed. It stops short of a full cancel: a callback already running on
another thread isn't waited for, which needs the HANDLE_CLOSING handshake.

On Apple, invalidateAndCancel returns before the session has finished with its
delegate, so clear the delegate's pointer back to the response first and have
didReceiveData check it, the way didCompleteWithError already did.

Android is still the only backend that genuinely cancels and waits, so naett.h
now says a response should be complete before it's closed rather than leaving
that to be discovered.
2026-09-05 12:07:03 -06:00
Henrik Rydgård da2f30ad46 naett: Stop leaving threads attached to the JVM, and check JNI results
getEnv called AttachCurrentThread and nothing detached. processRequest gets away
with it by detaching its own thread at the end, but naettPlatformInitRequest and
naettPlatformFreeRequest run on whatever thread the caller is using - and a
thread that exits while still attached takes the process down on Android, with
"Native thread exiting without having called DetachCurrentThread". They now
attach only when the thread isn't already, and detach on the way out.

pthread_create's result was ignored. Without a worker nothing ever sets
res->complete, so the caller polls naettComplete forever.

getOutputStream throws for anything from a refused connection onwards, and the
calls after it ran with that exception still pending, which most of JNI doesn't
allow. GetMethodID also returns NULL for a method it can't find, and calling
with a NULL jmethodID aborts the VM - so the call helpers check. Same for a
header whose value list is empty, which handed GetStringUTFChars a null.

Checked with the NDK's clang; the other backends were syntax-checked the same
way, against stub headers.
2026-09-05 12:04:31 -06:00
Henrik Rydgård 60919a2ba9 naett: Fix the curl worker's pipe read, queueing and handle cleanup
The worker reads a queued CURL* out of a pipe eight bytes at a time, tracking
how much it has so far - but it always read into the start of the buffer rather
than at that offset. A short read would have resumed mid-pointer and eventually
handed curl_multi_add_handle a spliced pointer. It never bit because a write
that size to a pipe is atomic, so reads are all-or-nothing, but the code is
written as though it isn't.

The write that queues the request was unchecked. If it ever failed, the request
was never run and never completed, and the caller sits in naettComplete
forever. It reports the failure now, and retries on EINTR.

curl wants an easy handle out of its multi before cleanup; that needs
curl_multi_remove_handle, which meant adding it - and curl_multi_cleanup, for
the init failure paths that leaked the multi handle and the pipe - to the dlopen
table.

workerRunning is written by the worker and read by the request path, so it's an
atomic rather than a plain int. And the read/write callbacks passed the body
callbacks' int return straight back to curl, which takes a size_t: a negative
came through as an enormous count rather than the error it was.
2026-09-05 12:02:22 -06:00
Henrik Rydgård 0e9397b8b4 naett: Harden the WinHTTP backend's header and read paths
WinHttpQueryHeaders only writes the size it needs when it fails with
ERROR_INSUFFICIENT_BUFFER. Any other failure left bufSize at zero, so we
allocated nothing and unpackHeaders walked wcslen over it looking for the
double-null that terminates the list. Check the size, check the second query,
and allocate zeroed with room for a terminator.

winToUTF8, winFromUTF8 and wcsndup can all return NULL - on a failed conversion
or a failed allocation - and not one caller checked. packHeaders is the one that
mattered: its result goes straight into headers[0].

res->bytesLeft is a size_t counting down from what WinHTTP announced. If a read
ever returned more than that, it wrapped to an enormous count and the loop kept
reading.
2026-09-05 11:55:59 -06:00
Henrik Rydgård cb2de6317c naett: Fix the Apple backend's unregistered class and stack-sized headers
createDelegate built its NSURLSessionDataDelegate with objc_allocateClassPair
and then sent it +alloc without ever calling objc_registerClassPair. The runtime
requires registration before a class pair can be used; everything up to then is
still being assembled. Register it, after the methods and the ivar go on.

The response header arrays were VLAs sized from the count the server sent, so a
response with enough headers walked the stack off the end, and one with no
headers at all declared zero-length VLAs, which is undefined by itself. Heap
now, and skipped when there's nothing to read.

The NSURLSession was also kept in the response without retaining it, while the
autorelease pool it came from is drained on the way out of the function. It only
survived because a session keeps itself alive while it has tasks running. Retain
it, release it when the response closes.

Finally, addMethod/addIvar signalled failure with assert alone, which is
compiled out in release - a delegate missing its methods would just never
receive data and the request would hang with nothing logged.
2026-09-05 11:54:49 -06:00
Henrik Rydgård 28962036ef naett: Fix two leaks and the response buffer's overflow
naettFree frees the method and url it strdup'd but never the user agent, which
stringSetter allocates exactly the same way. We set a user agent on every
request, so that leaked on every one of them, on all platforms.

On Linux, headerCallback strndup's each header line and only hands it to the
header list when it finds a colon - the status line and the blank line that ends
the block don't have one, so it leaked those every response, and again per hop
when following redirects.

defaultBodyWriter grew its capacity by doubling an int until the new data fit.
Both the length and the resulting capacity come from the response, so that's
signed overflow on a large one, and a negative capacity then reaches realloc as
a huge size_t. It also assigned the realloc result straight over the old
pointer, so a failed allocation lost the buffer and the memcpy below went
through NULL. Grow in int64_t, cap at INT_MAX, and report failure by returning
short - which is what every caller already checks for.
2026-09-05 11:53:32 -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ård 2de11efe4e TextureCache: stop clamping DXT decoding to bufw either
DecodeDXTBlocks limited its x loop to min(bufw, w), so when w > bufw everything
from bufw to w was simply never written - the destination is sized for w, so
those columns kept whatever the buffer held.

The software sampler doesn't do that. It addresses a block as
(v >> 2) * (texbufw >> 2) + (u >> 2), which for u past bufw runs on into the
next row's blocks, the same way the linear formats run into the next row. So the
two renderers disagreed on the same texture.

Follow the sampler: decode w texels per row and let the block index carry on,
with the range check widened to cover the blocks that reach past the last row's
stride. Shares the SourceExtent helper from the previous commit, counting 4x4
blocks rather than texels.

Rendering-visible where w > bufw for a DXT texture, which is the case that used
to leave stale contents behind.
2026-09-05 11:25:55 -06:00
Henrik Rydgård ca9d76fef3 TextureCache: bound the source by w as well as bufw, instead of clamping w
The non-DXT decode paths read w texels per row from a source whose stride, range
check and unswizzle buffer were all sized from bufw alone. w and bufw are
independent GE registers, so w > bufw is reachable, and the last row then runs
off the end of both the validated guest range and the temp buffer.

The previous commit clamped w down to bufw, which stops the overrun but is the
wrong shape twice over: the game asked for w texels and the destination is sized
for w, so the tail of every row is left holding whatever was in the buffer, and
it silently narrows a texture the hardware would have decoded in full.

Size the checks from both instead. The extent a decode touches is bufw per row
plus however far the last row reaches past its own stride - the same adjustment
TextureReplacer::ComputeHash and the GE recorder already make - so the range
check, the height it falls back to when the range is short, and the unswizzle
buffer are all computed that way now.

Pull the five copies of "resize tmpTexBuf32_, unswizzle into it" into a helper
while we're here, since they all need the same sizing. It zeroes the buffer when
w reaches past bufw, as UnswizzleFromMem only fills the stride and the tail would
otherwise be stale heap.

ComputeTextureHash has the same bufw-only assumption, but it's left alone
deliberately - changing what goes into a texture hash isn't worth the risk here.

DXT is left alone too - it clamps to minw, but there the limit is the block index
within a bufw/4 block row, so its range check already covers what it reads.
2026-09-05 10:26:14 -06:00
Henrik RydgårdandClaude Opus 5 ed5d9813bb TextureCache: fix two host-memory overruns from GE texture state
PrepareBuildTexture's mip scan stopped at the first level with a dimension of 1
*before* running the mip size check for that level, so a level like 1x256 under a
256x256 level 0 was accepted as a valid mip. The backend then sized the level from
the halved level-0 dimensions (128x128) while LoadTextureLevel re-read the real
height (256) from the GE state, writing past the allocation. Do the check first.

That check was also gated on GPU_USE_SAMPLER_LOD_CONTROL, so backends without it
did no mip dimension validation at all - there's no reason for the guard, the
result only feeds badMipSizes, so drop it.

Separately, the non-DXT decode paths read w texels per row from a source whose
stride, range check and unswizzle buffer are all computed from bufw. w and bufw
are independent GE registers, so w > bufw is reachable and read past the end of
both the validated guest range and the temp buffer. Clamp w to bufw in
DecodeTextureLevel, which is what the DXT paths have always done via minw.

Note: ungating the mip check means backends without SAMPLER_LOD_CONTROL can now
set badMipSizes where they previously didn't, which collapses such textures to a
single level. That's the intended behavior, but it is a rendering-visible change
on those backends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
2026-09-05 10:25:47 -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