zlib 1.4's uncompress2 returns Z_DATA_ERROR, not Z_BUF_ERROR, when the input
runs out with the output full, so every file in the pre-decrypted 3.60 archive
failed to inflate after the zlib update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 6.61 updater records its second block as 408 bytes, but the block is padded to 416 and
only decrypts at that size, so the install failed with "Couldn't read the PSAR's second block".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CreateSysDirectories() lists the PSP folder once instead of checking each
folder, and only writes .nomedia into folders it creates, instead of
rewriting it into all of them every time. PreferUpperCaseDir() doesn't
check for both spellings on content URIs, where storage is
case-insensitive. Theme and post shader loading don't list an empty
folder of their own a second time through File::GetFilesInDir.
On a phone with the memory stick on a content URI, startup now makes 12
storage provider calls before the game list, down from 82.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android reports the storage permission at every startup, and the handler
took it for a new grant: it created the system directories, reloaded the
whole config, and created them again. With a memory stick on a content
URI that was about half a second of storage calls in every startup. Only
redo it when the config couldn't be loaded before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Given a taken name, the storage provider makes "name (1)" rather than
fail. Renaming onto an existing file did exactly that and reported
success, so a game's sceIoRename onto a save file left a duplicate
instead of failing like on a PSP. Creating, renaming and copying now
check the name the document ended up with, undo a renamed one, and
return ALREADY_EXISTS. A new storage test checks that no operation onto
a taken name leaves a duplicate.
The Java side only ever returned UNKNOWN. It now maps a missing file to
NOT_FOUND and a full disk to DISK_FULL, and OpenFD/OpenCFile set errno
from those, so DirectoryFileSystem reports a full disk on content URIs
like on native paths, instead of "file not found".
That also lets writing open first, and only create when the file is
missing: overwriting a file is one call to the provider instead of two
(18.6 -> 13.2 ms in the storage test on an SD card).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- See #22924
The page reads IdStorage key 0x44 (6 bytes at offset 0) through
vshbridge.prx, as does wlan.prx's sceWlanGetEtherAddr. The real
idstorage.prx never finishes its init (it stops at the kernel mutexes we
leave unresolved), so every lookup failed and the page showed "-".
sceIdStorageLookup is now HLE, answering key 0x44 from the same MAC
sceWlanGetEtherAddr uses. Other keys fail with 0x80000108, what the real
driver returns uninitialized, and the rest of the library still resolves
to idstorage.prx. Taken from the 6.61 modules' disassembly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without one, every query and DocumentsContract call acquires the provider
and releases it again, and when our reference count drops to zero, each
of those is a binder call to the system as well. Holding an unstable
ContentProviderClient keeps the count up, without tying our process to
the provider's. Queries go through it, and a dead provider is dropped
and acquired again.
Storage test timings on an SD card, median of five runs before and after:
exists 9.3 -> 6.7 ms, file info 10.4 -> 7.4, overwrite 21.8 -> 18.6,
read 28.5 -> 26.6. Creating, deleting and listing are dominated by the
provider's own work, and gain a few percent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fstatvfs of a descriptor opened through the storage provider gave the
internal storage's free space for a folder on an SD card. On Android 11
and later, find the volume from the document ID and ask it instead.
Found by the storage tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unit tests have no Java VM on Android, so the content URI storage
code could only be tested by hand. RunStorageTests() exercises file
creation, overwrite, listing, rename, moves, copies, deletes, odd names
and free space in a scratch folder, and times the calls the emulator
makes most.
The new StorageTestScreen (--start-screen=storagetest) runs them in a
picked folder and remembers it. Tools/android_storage_test.py starts it
over adb and collects the results from logcat, and Tools/android_run.py
builds, installs and starts the app with any options. The screen raises
the System log channel to INFO during a run, so the results get through.
The Storage unit test runs the same tests on a native folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI builds ppsspp_unittest for Android but never runs it, and on a device
ten tests failed. Tools/android_unittest.py builds it, pushes it and the
files tests read, and runs each test as its own process, so a crash
doesn't swallow the buffered output of the rest.
- MemArena picked the pre-Android 8 /dev/ashmem path whenever
SYSPROP_SYSTEMVERSION was unknown, which it is outside the app, and
newer Android doesn't allow that. So Memory::Init failed and the JIT
tests crashed. ASharedMemory_create is looked up at runtime anyway, so
try it first. Older versions don't have it and fall back as before.
- JNILocalFrame tolerates a null JNIEnv, so content URI calls fail like
missing files in executables without a Java VM, instead of crashing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lines() strips both LF and CRLF, and write() always wrote LF, so every
run on a Windows checkout rewrote all the language files from CRLF to
LF. Remember which one a file used and write that back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The option is left out of the core options on mobile, Switch, UWP and
32-bit builds, where the cache isn't used. One macro decides both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since b05d6171d4, the RAM cache is only used when the platform reports
SYSPROP_ENOUGH_RAM_FOR_FULL_ISO, which the libretro core never did, so
the ppsspp_cache_iso option silently did nothing. Report it on 64-bit
desktops, like the standalone app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mostly security fixes: a gzwrite() buffer overflow (CVE-2026-85091),
and several out-of-bounds reads and writes in inflate, inflateBack and
deflate on unusual call sequences or invalid data.
Taken from the signed release tarball. Our only local change, the
<unistd.h> include in gzguts.h, is carried over. The library source
file list is unchanged, so no build files needed updating.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Win32 Edit Symbols dialog was the only UI that could do a function
scan or a range removal, and so the only way to rerun ReplaceFunctions().
The disassembly context menu now has a range popup (prefilled from the
selection or the current function) with Scan, Remove and Remove and
rescan, plus a "Reapply function replacements" item.
The range logic moves into MIPSAnalyst::ScanFunctionsInRange() and
RemoveFunctionsInRange(), shared with the WebSocket debugger and the
Win32 dialog. That also fixes the dialog scanning to address + size - 1,
an unaligned end that ScanForFunctions() asserts on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The squeeze scale is availWidth / width, and multiplying the width back by it
could land a hair above availWidth (189.00002 for "PPSSPP Forums" on the credits
screen), so the text got wrapped despite being scaled to fit. Shave off 0.1%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- textures.ini hashranges with a source over 1024 are rejected. A height
of 65536 or more spilled into the width bits of the key, matching a real
texture with a range bigger than it, which overflowed the upload buffer
in CopyLevelTo and the save buffer in NotifyTextureDecoded. Both now
also check the sizes themselves.
- A '|' in a textures.ini filename split into another mip level after the
parent dir check, so it's rejected.
- Post-processing and texture shader inis can't point outside their own
folder, and a texture shader's ConstantBuffer must be a valid uniform
buffer size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Block transfer sizes counted the stride in pixels rather than bytes, so
the range check covered a quarter of a 32-bit copy and the memcpy wrote
past the end of RAM or VRAM.
- Indexed draws, curves and bounding boxes now check the index buffer and
every vertex the indices can reach (IsValidDrawRange). Only the start
address was checked before.
- The run loop's downcount stops at the end of memory, and a list that
runs off the end errors out. The PRIM fast loop, its CALL, the matrix
fast-loaders and END's look at the previous word are bounded the same way.
- sceGeListUpdateStallAddr with an unaligned address no longer hangs the
emulator, since the stall is now word aligned like the PC.
- The software renderer leaves out rows that would reach past the end of
VRAM's mirrors with a wide 32-bit framebuffer stride.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DirectWrite leaves trailing whitespace out of measured widths, so x_ (measured
on a line ending in a space) came out one space short. A word overflowing by
less than that stayed on the line, and the hard newline after it then wrapped
too, leaving an empty line. Re-measure the line with the word on it, and don't
wrap before a word with no width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A 7z folder that can't be streamed (BCJ2, filter chains) is extracted
whole into memory at the size its header claims. Refuse folders over
256 MB rather than run out of memory.
- When streaming, the LZMA and LZMA2 dictionaries are clamped to the
folder size. The header can ask for up to 4 GB.
- Custom MP3 sound effects decode at most 60 seconds, and a sample file
shorter than 4 bytes (or too large) is rejected without reading past it
or leaking it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Translated strings that were printf formats now use %1, %2, %3 and go
through ApplySafeSubstitutions, which fills them in without interpreting
anything else. Lang files can come from downloads, and a value like
"%s%s%s%n" would otherwise crash or write memory. The lang file keys and
values are converted to match, and "%0.2f Hz" is merged into "%1 Hz".
Slider format strings still go through snprintf, so their check now
requires exactly one conversion of the expected type, which keeps out
%n and friends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The friend finder server and other players decide how long several of
our lists get, and none of them had a limit:
- Free the group and friend lists and the matching stacks iteratively.
A server could send enough scan results to overflow the stack.
- Cap the friend list and scan results at 256, and game mode members
at ADHOCCTL_GAMEMODE_MAX_MEMBERS.
- Game mode: drop datagrams larger than the receive buffer (one would
sit at the head of the queue forever) and cap the queue.
- Bound each pass draining a PDP socket, and NetSim's held datagrams,
so a flood can't stall the emulator thread.
- Matching: cap the peer list and the per-MAC port map, and ignore a
BIRTH that would take the parent past maxpeers.
- Server: don't add every unknown product code to the product table,
allow 16 connections per IP, give a connection 30 s to log in, and
terminate client nicknames before logging them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A wait restored from a savestate is retried with arguments nobody
checked: validate them like the entry points do before each retry.
- Fail recvmsg with EMSGSIZE beyond 1024 iovecs (UIO_MAXIOV).
- Windows: a failed socket() is INVALID_SOCKET, which isn't < 0.
- infra-dns.json: skip non-string domain entries, which crashed.
- Resolver: read the host name only up to the end of memory, and try at
most twice, since each attempt blocks the emulator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server could make us buffer a response until we ran out of memory,
inflate a small gzip body to 256 MB, or redirect forever.
- Responses are capped at 64 MB in memory and 1 GB for downloads to a
file (which are buffered in memory too), over both the plain and the
HTTPS client. A larger Content-Length fails up front.
- Gzip output gets the same cap.
- Remote ISO range reads accept no more than the range asked for.
- Stop after 10 redirects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Browsers let any page open a WebSocket to localhost, and the debugger can
write memory and save states to any path. Accept only pages from this
machine, from this server, or the official web debugger; clients that
send no Origin are unaffected.
- Cap request headers at 100 lines, 64 KB and 10 seconds, and stop
leaking repeated User-Agent and Referer values. Header names are now
matched exactly, not by prefix.
- Run at most 64 connections at once, and drop accepted sockets that
select() can't take (fd >= FD_SETSIZE).
- Grow a WebSocket message as its data arrives, instead of to the
length the frame claims.
- Drop a WebSocket client that stops reading, once 128 MB is queued,
and stop waiting for a close frame that can't go out after 2 seconds.
- Uploads wait for data instead of spinning, and range downloads stop
when the client stops reading.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>