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>
Upstream moved the sources into api/, client/, runtime/ and util/
directories, renamed most of them, and split rc_client.c into several
files. The lists now match upstream's src/Makefile.common, which also
drops rc_validate.c (nothing calls it).
Also follow the API rename of core/unofficial achievements to
promoted/unpromoted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RadioButton is now a ClickableItem, so it fills its parent's width by
default and draws its hint text below the title, lined up with the text
after the radio circle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Game mode master and replica data was copied by the game's size after
checking one byte, into host buffers allocated by that size (up to 2 GB).
If the copy failed, the uninitialized buffer was sent to peers.
- sceNetAdhocctlCreateEnterGameMode read up to 16 member MACs after checking
one byte.
- sceNetAdhocMatchingCreate allocated and cleared the game's receive buffer
size on the host with no upper bound. Cap it at 1 MB.
- PMP video walked its block descriptors with only the first one checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Block-mode sceIoRead counts its size in sectors, but was clamped to memory
in bytes, so a large count read size * 2048 bytes past the buffer.
- sceIoDread wrote the short and long names to d_private after checking one
byte; the PGD key and sector seek ioctls read 16 bytes after one.
- Savedata: GetSize walked the game's entry arrays by its counts (unchecked),
GetList and GetFilesList wrote entries past the first unchecked, and Save
read the data and icon/pic/sound buffers by their sizes without checking
them. Also keep the encrypted size from going negative.
- sceFont: font style, font info and char info structs, and the font list,
were only checked at their first byte. Style names in logs needn't be
terminated.
- PGF glyph rendering wrote 3-4 bytes per pixel after checking one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sceGeSaveContext/RestoreContext and list contexts from sceGeListEnQueue
save and restore 512 words, but only their first bytes were checked.
- The display range check computed (271 * stride + 480) * bpp in 32 bits.
sceDisplaySetFramebuf allows any multiple of 64, so a huge stride wrapped
the size to a few bytes, passed, and the display was then read far past
memory. Compute it in 64 bits, here and in the software renderer, which
also checks its debug framebuffer copy (used for screenshots) and doesn't
create textures for strides over 4096.
- Frame dump replays check a DISPLAY command like sceDisplaySetFramebuf, and
bound the drawn-area tracking a crafted dump could make quadratic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
These checked the first byte of a buffer the game sized, then read or wrote all
of it on the host:
- sceDeflt: the output length went straight to zlib.
- sceSfmt19937: array end checks wrapped in 32 bits (FillArray32 with a huge
count wrote gigabytes), and the 2.5 KB state struct was only checked at its
start. The same for the MT19937 contexts in sceMt19937 and
sceKernelUtilsMt19937*.
- sceChnnlsv: sceSdRemoveValue took any length (a negative one passed the
key length check), sceSdSetMember decrypted alignedLen bytes in place, and
the 16-byte hash, key and data buffers were checked for one byte.
- MD5, SHA-1 and SHA-256 digests: check the data length and digest size
(SHA-256 checked data + len, which wraps).
- sceCcc: clamp the destination size to memory, and require the source to be
terminated in mapped memory, since the converters walk it to its end.
- sceRtc: the RFC formatters write 32 bytes, the Win32 file time functions a
whole date struct, and the date parser needs a terminated string.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Atrac: the partial-frame buffer was sized from the codec field in the guest's
context, which a game can change after SetData, so an Atrac3+ decoder could
write 2048 samples into a 1024-sample buffer. Size it for any frame, and
decide from the decoder whether a frame fits the output. Check the output
buffer holds a frame and the input holds the frame read, not just their
first bytes, and range-check the streaming buffer copy.
- Atrac track parsing is bounded by mapped memory, not only the game's size.
- sceMp3LowLevelDecode and sceAudiocodecDecode check their output holds a
frame, and the MP3 input is clamped. FindMp3Header read 3 bytes past its
range.
- PSMF: the EP map address could wrap around to a valid one while the entries
were read from the unwrapped host pointer, and scePsmfSetPsmf checked one
byte of the 0x82-byte header it reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>