Commit Graph
50407 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 004dcdfe93 PSAR: Accept zlib 1.4's error for streams without a checksum
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>
2026-11-06 14:08:57 +01:00
Henrik RydgårdandClaude Opus 5.5 a0af2bc403 Firmware install: Create flash1/vsh/theme
The shell refuses to install a theme without it, and the updater's archive doesn't have it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:01:34 +01:00
Henrik RydgårdandClaude Opus 5.5 8c31d80b8b PSAR: Pad the second block size from the first block to 16 bytes
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>
2026-11-06 14:01:34 +01:00
Henrik Rydgård 727805e193 Merge pull request #22959 from hrydgard/storage-fixes
Android storage: No numbered duplicates, real errors, faster startup
2026-11-06 13:46:51 +01:00
Henrik Rydgård a4296e57b8 Merge pull request #22958 from hrydgard/xmb-mac-address
VSH: Show the configured MAC address in System Information
2026-11-06 13:43:45 +01:00
Henrik RydgårdandClaude Opus 5.5 cdc4f522f4 Fewer storage calls at startup
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>
2026-11-06 12:53:31 +01:00
Henrik RydgårdandClaude Opus 5.5 ad748f31e9 Android: Don't reload the config for the storage permission startup report
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>
2026-11-06 12:53:31 +01:00
Henrik RydgårdandClaude Opus 5.5 f519f40f65 Android storage: Never make numbered duplicates, report real errors
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>
2026-11-06 12:53:31 +01:00
Henrik Rydgård 37a0d427ac Merge pull request #22957 from hrydgard/storage-tests
Android: Storage tests for content URIs, and fixes from running them
2026-11-06 12:52:42 +01:00
Henrik RydgårdandClaude Opus 5.5 a3cbbd086b VSH: Show the configured MAC address in System Information
- 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>
2026-11-06 12:34:02 +01:00
Henrik RydgårdandClaude Opus 5.5 666dfd2187 Android: Hold a client for the storage provider between calls
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>
2026-11-06 12:09:55 +01:00
Henrik RydgårdandClaude Opus 5.5 97099b4d96 android_run.py: Let our log tag through on devices that silence all tags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:07:32 +01:00
Henrik RydgårdandClaude Opus 5.5 9e1044cf66 Android: Report the free space of the volume a content URI is on
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>
2026-11-06 12:07:32 +01:00
Henrik RydgårdandClaude Opus 5.5 aa4053c2b0 Add a storage test screen, to test content URIs on a device
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>
2026-11-06 12:01:28 +01:00
Henrik Rydgård 22bc3f9db4 Merge pull request #22955 from hrydgard/android-unittest-runner
Android: Run the unit tests on a device, and make them pass there
2026-11-06 11:14:37 +01:00
Henrik Rydgård e237f77358 Merge pull request #22954 from hrydgard/langtool-preserve-line-endings
langtool: Keep each ini file's line endings
2026-11-06 11:05:39 +01:00
Henrik RydgårdandClaude Opus 5.5 f64f806626 Android: Run the unit tests on a device, and make them pass there
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>
2026-11-06 10:39:32 +01:00
Henrik Rydgård 5327f828ec Merge pull request #22940 from hrydgard/hle-hardening
HLE: Range-check buffers and structs from game arguments
2026-11-06 10:01:54 +01:00
Henrik RydgårdandClaude Opus 5.5 f6c5d5014c langtool: Keep each ini file's line endings
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>
2026-11-06 09:59:58 +01:00
Henrik Rydgård cca8b518ca Merge pull request #22953 from hrydgard/libretro-cache-iso-in-ram
libretro: Make "Cache Full ISO in RAM" work again, only offer it on desktops
2026-11-06 09:49:14 +01:00
Henrik Rydgård df65149a08 Merge pull request #22952 from hrydgard/zlib-1.4
Update our vendored copy of zlib to 1.4
2026-11-06 09:38:16 +01:00
Henrik RydgårdandClaude Opus 5.5 674bf9d29d libretro: Only offer "Cache Full ISO in RAM" where it works
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>
2026-11-06 09:27:08 +01:00
Henrik RydgårdandClaude Opus 5.5 5f6db5a178 libretro: Make "Cache ISO in RAM" work again on desktops
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>
2026-11-06 08:38:28 +01:00
Henrik RydgårdandClaude Opus 5.5 009faf8f61 Update our vendored copy of zlib to 1.4
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>
2026-11-06 01:17:04 +01:00
Henrik Rydgård ee714567ee Merge pull request #22947 from hrydgard/gpu-hardening
GE, texture packs, shaders: Bound memory access from GE commands and shared content
2026-11-06 01:07:57 +01:00
Henrik Rydgård 8cbde31a5a Merge pull request #22949 from Nemoumbra/nocash-map-fix
Wrong file type for the Symbol Map in ImDebugger
2026-11-06 01:04:35 +01:00
Henrik Rydgård 75c35f99e2 Merge pull request #22950 from hrydgard/imdebugger-func-scan
ImDebugger: Add scan/remove functions in range, reapply replacements
2026-11-06 01:04:22 +01:00
Henrik Rydgård dbbc14efc8 Merge pull request #22948 from hrydgard/more-ui-work
More UI polish
2026-11-06 00:48:31 +01:00
Henrik RydgårdandClaude Opus 5.5 8f64422929 ImDebugger: Add scan/remove functions in range, reapply replacements
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>
2026-11-06 00:43:56 +01:00
Nemoumbra a78b5fc2af fix: .ppmap -> .sym in ImDebugger 2026-11-06 02:32:03 +03:00
Henrik Rydgård 3039c4ea96 Merge pull request #22946 from hrydgard/misc-hardening
Lang files, 7z, sound effects: Bound what downloaded content can do
2026-11-05 22:49:58 +01:00
Henrik Rydgård fa395a4f31 PauseScreen layout tweaks 2026-11-05 22:39:56 +01:00
Henrik Rydgård c3c6327294 More layout tweaks 2026-11-05 22:28:26 +01:00
Henrik Rydgård 89a24813b3 Merge pull request #22944 from hrydgard/adhoc-hardening
Ad hoc: Bound what the server and peers can make us store
2026-11-05 22:12:48 +01:00
Henrik Rydgård 6c624dbdda Fix displayed icons for homebrew without icons 2026-11-05 22:10:48 +01:00
Henrik RydgårdandClaude Opus 5.5 d7d42ad95a UI: Keep squeezed text from wrapping due to float rounding
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>
2026-11-05 22:03:38 +01:00
Henrik Rydgård 1bce9352fb Layout tweaks 2026-11-05 21:58:56 +01:00
Henrik RydgårdandClaude Opus 5.5 ceab9b7a3f Texture packs, shaders: Bound what shared content can ask for
- 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>
2026-11-05 21:21:22 +01:00
Henrik RydgårdandClaude Opus 5.5 05cca98c31 GE: Bound block transfers, indexed draws and list reads to valid memory
- 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>
2026-11-05 21:21:22 +01:00
Henrik RydgårdandClaude Opus 5.5 f039116483 WordWrapper: Count the space before a word when checking if it fits
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>
2026-11-05 21:14:33 +01:00
Henrik RydgårdandClaude Opus 5.5 2d30b81543 7z, sound effects: Bound memory a crafted file can take
- 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>
2026-11-05 21:12:21 +01:00
Henrik RydgårdandClaude Opus 5.5 5a569135dd Lang files: Don't use translations as printf formats
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>
2026-11-05 21:12:21 +01:00
Henrik RydgårdandClaude Opus 5.5 dacccd29bb UI: Wrap RadioButton's title text
Measure it with MeasureTextRect against the available width, like Choice does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 21:07:27 +01:00
Henrik Rydgård bcfcdb9974 Improve the look of the MemstickScreen 2026-11-05 20:03:37 +01:00
Henrik Rydgård f82a875d17 Merge pull request #22945 from hrydgard/net-hardening
Web server, HTTP client, inet: Check Origin and bound what the network can make us do
2026-11-05 19:41:22 +01:00
Henrik RydgårdandClaude Opus 5.5 0b43ea3272 Ad hoc: Bound what the server and peers can make us store
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>
2026-11-05 18:49:10 +01:00
Henrik RydgårdandClaude Opus 5.5 59b42924b5 Inet, resolver: Check restored waits, bound lookups
- 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>
2026-11-05 18:49:10 +01:00
Henrik RydgårdandClaude Opus 5.5 63a9a53bf9 HTTP client: Cap response size and redirects
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>
2026-11-05 18:49:10 +01:00
Henrik RydgårdandClaude Opus 5.5 bea3fcdeab Web server, debugger: Check Origin, bound connections, headers and buffers
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>
2026-11-05 18:49:10 +01:00
Henrik RydgårdandClaude Opus 5.5 d756893c1c UI: Give RadioButton the same padding and height as CheckBox
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:48:11 +01:00