Commit Graph
15798 Commits
Author SHA1 Message Date
Henrik Rydgård b0ea555b79 Fix a couple of problems with recent commits in MSVC debug builds 2026-10-09 17:52:55 +02:00
Henrik RydgårdandClaude Opus 5.5 a521f257ce CmdLine: Add --msaa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:49:15 +02:00
Henrik Rydgård 06fbbf1218 Merge pull request #22471 from hrydgard/parallel-loop-max-threads
Allow specifying a cap on the number of threads used in ParallelLoop
2026-10-09 15:38:09 +02:00
Henrik Rydgård 0810fee60f Merge pull request #22469 from hrydgard/virtkey-create-frame-dump
Add bindable key to create frame dumps, allow headless to save high-res screenshots
2026-10-09 14:49:12 +02:00
Henrik Rydgård 831acb099e Allow specifying a cap on the number of threads used in ParallelLoop 2026-10-09 14:33:58 +02:00
Henrik Rydgård 9948c57e13 Make MIPSState standard-layout again, so offsetof is valid
Turns out that the actual problem here was "private", not the vector.
2026-10-09 13:54:28 +02:00
Henrik Rydgård 88f9379376 Merge pull request #22450 from hrydgard/atrac-mpeg-security-fix
Fix Atrac heap overflow and MPEG uninitialized memory disclosure
2026-10-09 13:51:22 +02:00
Henrik Rydgård 1862efe392 Add a bindable key to create frame dumps 2026-10-09 13:26:22 +02:00
Mohammad Syuhada d2f3d93cad DirectoryFileSystem: Skip the stripped PSP/ prefix when fixing path case
With STRIP_PSP (memstick directory named PSP), FixPathCase walked
<memstick>/PSP/... which doesn't exist, so on case-sensitive storage every
write (savedata, mkdir, rename, ...) failed with not-found, and games
reported the memory stick as full.
2026-10-09 17:56:29 +08:00
Henrik RydgårdandClaude Opus 5.5 0dc6381ba4 Headless: Add --dump-compat, to apply compat settings to frame dumps
Headless leaves a game's compat.ini settings out when replaying a frame
dump, so comparisons with the PSP see what the renderer itself does. This
puts them back, for checking what a compat entry does to a dump (Puyo Puyo
Fever 2's depth rounding).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:59:59 +02:00
Henrik RydgårdandClaude Opus 5.5 8b4d9c3b74 Headless: Don't apply compat flags to frame dumps
Headless replays frame dumps to compare them with references and with the PSP, which a game's
workarounds skew: DarkStalkers' present hack (16510) replaces the game's final upscale, so the
replay didn't match the PSP's. The app still applies them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 02:03:31 +02:00
Henrik RydgårdandClaude Fable 5.1 ce19265949 Don't save log levels set from the command line
--loglevel, -d and -v are applied to the channels directly, and the next
config save wrote them to [Log], permanently replacing the user's levels.
The other command line options avoid this with DoNotSaveSetting.

The debugger had the same problem and a flag for it. Generalize that one
(NotifyChannelsChangedByDebugger -> DoNotSaveChannels) and set it here too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Opus 5.5 ae32477764 Core: Let 16-bit CPU accesses through the depth mirrors see the GE's layout
With the software renderer, which now stores depth as the GE does, a
16-bit read or write at VRAM's 0x04200000 or 0x04600000 mirror goes to
where the GE's 16-bit or 32-bit color layout puts that depth pixel
(ppsspp-re geprobe exp88: the mirrors are the layout's exact inverse).
Games access depth there with nothing else, so other widths stay plain.

The accessors check the address, then whether the GPU core is the
software renderer (CoreParameter); the hardware renderers keep depth plain
and handle the layout their own way. The renderer keeps the layouts up to
date with the EDRAM address translation. With it, the JITs send 16-bit
loads and stores to the interpreter, and the IR passes don't change a
load's width to or from 16 bits.
The pspautotests that read depth through the mirrors now do so 16 bits
at a time, re-recorded on hardware unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:37:23 +02:00
Henrik RydgårdandClaude Opus 5 bd94e8141f Android: Converge the GL and Vulkan lifecycles on a shared RunGraphicsLoop
Stage 5 of the JavaGL removal. Now that the GL path owns its own EGL context and
render thread, Android's two lifecycle branches and the Win32 one in
MainThreadFunc were the same code three times over, so this moves the branch into
one place - RunGraphicsLoop() in Core/EmuThread.cpp. It picks the thread
arrangement the context asks for: either this thread becomes the render thread
and spawns the emu thread (OpenGL), or it just runs the main loop because the
backend spawns its own render thread (Vulkan, D3D11). Android, Windows and
headless all go through it now, which is what the "TODO: Merge with the Win32
EmuThread and so on" in app-android.cpp was asking for.

Falls out of having one implementation instead of three:

- MainThreadFunc called graphicsContext->ThreadStart() right after
  EmuThread_Start(), which already calls it, and ThreadEnd() right after
  EmuThread_Join(), which already calls that too. So Windows and headless GL ran
  CreateDeviceObjects()/DestroyDeviceObjects() twice per context, leaking a VAO
  on the way in and double-deleting it on the way out. Dates back to 16206d18ab,
  which moved those calls into EmuThread_Start/Join without removing the
  originals.

- RECREATE_VIEWS is gone. Only the GL path ever posted it; Vulkan goes through
  the same destroy/recreate without it and always has. Verified on device: views
  survive pause/resume, and both a real rotation and a density change - each one
  an activity destroy and recreate - lay them out again by themselves.

- The non-separate path is now named EmuThread rather than MainThread on
  Windows. It's the thread that runs NativeFrame(), which is what
  DebuggerThreading.md calls the emu thread, and the real main thread there is
  the GUI message pump.

The loop keeps an explicit exit flag, as a shouldExit callback, rather than
relying on ThreadFrame() returning false. In VR we skip ThreadFrame() entirely
while the XR session is idle, and the session going idle is exactly what
backgrounding the app does - so the exit would never be observed, the render
thread would sit in that sleep forever, and the UI thread joining it would hang.
That is not hypothetical: it deadlocked a Quest 2 on the first try.

Also drops a dead EmuThreadState enum and an unused JNIEnv local in
app-android.cpp, both orphaned when the JavaGL JNI entry points went away.

Tested on a nubia Z2464N (Mali-G57, Android 15) on both backends: cold start,
booting a homebrew, pause/resume in the menu, in a settings screen and in-game,
rotation back and forth, a density-change activity recreate with the game live,
the pause menu, and exit to menu. ThreadStart/ThreadEnd log exactly once per
context, and every one of those cycles tears the EGL context down and builds a
new one.

Also tested on a Quest 2 with the vr flavor, which is what shook out the deadlock
above: cold start into a focused XR session at 72fps, backgrounding (render
thread joins in 27ms where it used to never return), resuming back into VR with
a fresh EGL context, and exiting from in-app. xrCreateSession runs on the render
loop thread, which is where the EGL context is current - VRBase.cpp builds its
graphics binding from eglGetCurrentDisplay/eglGetCurrentContext, so that ordering
matters.

Windows x64 debug and the Android gradle builds are clean.

Not converged, and not a small change: NeedsSeparateEmuThread() itself. GL needs
its API calls on the thread its context is current on, so somebody has to pump
ThreadFrame() there, whereas VulkanRenderManager owns its thread outright.
Getting rid of the virtual means teaching GLRenderManager to spawn and own its
own render thread, for every GL platform at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 15:29:12 +02:00
Henrik RydgårdandClaude Opus 5 812d8f4c09 Android GL: Delete the JavaGL path, leaving only native EGL
The EGL path from the previous commit is now the only one, so GLSurfaceView
and everything that existed to serve it are gone:

- NativeRenderer.java and NativeGLSurfaceView.java, deleted.
- The javaGL branches in PpssppActivity. GL and Vulkan now share one
  lifecycle: a plain SurfaceView, startRenderLoopThread() when the surface
  arrives, joinRenderLoopThread() when it goes away.
- The "androidJavaGL" queryConfig, and the AndroidNativeEGL setting and its
  developer-tools checkbox.
- displayInit/displayRender, and with them the "Second time, joining the
  emuthread and starting it up again" dance - restarting the emu thread from
  inside a JNI callback because a lost context was only observable as a
  second onSurfaceCreated.
- GraphicsContext::NotifyContextLost, which existed solely because that join
  drained the render queue on a thread with no current context. The render
  thread now joins the emu thread with the context still current, so the
  drain is real work and nothing has to be skipped.
- g_emuThread, now that nothing starts an emu thread outside the render loop.

Three things displayInit/displayRender did that nothing else did, moved
rather than dropped:

- EnterVR and the per-frame StartVRRender/UpdateVRInput/FinishVRRender hooks
  now live in RenderLoopThread's GL branch. Java's GL thread could simply
  return early and wait for the next vsync when the VR session was inactive;
  this loop drives itself, so that case sleeps 16ms instead of spinning.
  Not tested - no headset here.
- RECREATE_VIEWS after the emu thread starts, as before. Vulkan never did
  this, so it's a candidate for removal when the two paths converge.
- The thin3d error callback, which surfaces GL errors as OSD messages. That
  one moved into AndroidEGLGraphicsContext::InitSurface, next to the rest of
  the context's own setup, matching WindowsGLContext.

Tested on a nubia Z2464N (Unisoc UMS9632, Mali-G57, Android 15): cold start,
UI, booting a homebrew, backgrounding and resuming both in the menu and
in-game, and a density change, which destroys and recreates the activity the
way a rotation does. Every cycle tears the EGL context down and builds a new
one, and the renderer comes back with FBOs and textures intact. The config
ladder's first choice, RGBA8888 D24S8, is what the Java chooser landed on
here too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 15:22:25 +02:00
Henrik RydgårdandClaude Opus 5 f868976af2 Android GL: Add a native EGL path, behind a dev toggle
First step towards removing JavaGL. The point isn't EGL itself - it's who
owns the render thread. With GLSurfaceView, Java creates the thread and the
context and calls us back, so a lost context is only observable after the
fact as a second onSurfaceCreated. That's what forces displayInit to tear
down and restart the emu thread from inside a JNI callback, and to call
NotifyContextLost first because the queue is about to be drained on a thread
with no context. None of that is needed when we own EGL: the render thread
joins the emu thread itself, with the context still current, so the drain is
real work rather than something to skip.

AndroidEGLGraphicsContext subclasses OpenGLGraphicsContext (whose own
InitSurface assumes someone else already made a context current) and adds
display/config/surface/context plus a swap function. With it, GL reuses the
Vulkan lifecycle wholesale: queryConfig("androidJavaGL") returns false, so
Java builds a plain SurfaceView and starts the native render loop when a
surface arrives - including all of the surface/pause ordering fixes from the
previous branch. runVulkanRenderLoop/requestExitVulkanRenderLoop are now
runRenderLoop/requestExitRenderLoop and serve both backends; the thread they
spawn branches on NeedsSeparateEmuThread, because the two backends split the
work in opposite directions (Vulkan: this thread is the main loop and the
render manager spawns the render thread; GL: this thread *is* the render
thread and spawns the emu thread).

Deliberately a port, not an improvement:

- GLES client version still follows NativeRenderer - 3 for VR, 2 otherwise.
  Not a cap; GLES3 is detected from GL_VERSION at runtime.
- No swap-interval function. GLSurfaceView gave us no way to set one, so
  PresentMode::IMMEDIATE has always been ignored here, and honoring it now
  would be a behavior change. Worth revisiting once this path is the only one.

The config ladder does take the user's word that Mali 400 and the Xperia Play
are no longer a factor: it asks for RGBA8888/D24S8 and falls back twice,
rather than reproducing GLSurfaceView's ask-for-nothing default that the
comments in PpssppActivity.onCreate exist to explain. ANativeWindow's format
is set from EGL_NATIVE_VISUAL_ID, which is the proper fix for the EGL_BAD_MATCH
those comments describe.

Off by default, behind Developer tools / Graphics / "Use native EGL instead
of GLSurfaceView" (OpenGL backend, Android only). Needs an app restart, since
the context is chosen in NativeApp.init.

Builds for arm64-v8a and on Windows; NOT yet run on a device.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 15:22:25 +02:00
Henrik RydgårdandClaude Opus 5.5 70d64311af MediaEngine: Don't leak uninitialized host memory when swizzling video
When writing a decoded frame to swizzled VRAM, the staging buffer was
allocated for the guest's frame width but only the decoded width was
filled, so the row padding (heap residue including host pointers) was
swizzled into guest memory. Swizzling also reads whole 8-row tiles, past
the end of the buffer when the height isn't a multiple of 8.

Zero the staging buffer and size it (and the guest range check) for whole
tiles, with checked arithmetic. Rows are capped to the frame width so
they can't overlap or run past the end. writeVideoImageWithRange() also
swizzled from, and freed, the advanced row pointer instead of the start.

Discovered and reported by redthing1 (https://github.com/redthing1) of
V12 Security.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 14:27:01 +02:00
Henrik RydgårdandClaude Opus 5.5 283ec4db4c Atrac: Fix host heap overflow from a loop start past the end of the file
A crafted RIFF smpl chunk could put the stream read offset past the end
of the file. CalculateStreamInfo() and AddStreamData() then subtracted it
from the file size in unsigned arithmetic, the remaining size wrapped, and
the guest's data was copied to a guest-chosen distance past dataBuf_.

Check the offset before subtracting, and route every copy into dataBuf_
through a helper that clamps it to the file. SeekToSample() no longer
prefills from past the end, and dataBuf_'s slack covers a whole frame.

This is the old Atrac implementation, which is only used when chosen in
the config or when loading old savestates.

Discovered and reported by redthing1 (https://github.com/redthing1) of
V12 Security.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 14:27:01 +02:00
Henrik Rydgård c8814f545f Merge pull request #22449 from hrydgard/stencil-write-mask
Shader write mask: Remove the compat flag (after tweaking it), improve emulation
2026-10-08 13:10:10 +02:00
Henrik RydgårdandClaude Opus 5.5 ec8a590c81 Read a frame dump's game ID after a leading issue number in its name
Dumps before version 4 don't store the game ID, so it's taken from the file
name, which PPSSPP writes as ULUS10064_0001.ppdmp. The ppsspp-framedumps
collection prefixes the GitHub issue number ("11928 ULUS10064_0001 OutRun
water.ppdmp"); skip that, so those dumps get their compat settings too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:33:09 +02:00
Henrik RydgårdandClaude Opus 5.5 feaa41fadb Always emulate partial color write masks in the shader, drop ShaderColorBitmask
Partial red, green and blue write masks now always go through the shader with
a framebuffer read. Across the frame dump set this only improves games outside
the old list (Split/Second, Ultimate Ghosts 'n Goblins, older DiRT 2 and OutRun
dumps without a game ID). Partial alpha masks keep the old heuristic: alpha is
the stencil, with its own write mask, and taking the shader path for them made
Pro Yakyu Spirits and Gangs of London worse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:18:05 +02:00
Henrik Rydgård 8e596032f1 Merge pull request #22446 from hrydgard/misc-rendering-fixes
Misc rendering fixes
2026-10-08 11:16:14 +02:00
Henrik RydgårdandClaude Opus 5.5 6c04b9f068 Headless: Save the depth buffer from the hardware renderers too
--depth-save converts their float depth (z / 65536) to the PSP's 16-bit
values, or writes the unrounded values when the path ends in .f32.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:36:19 +02:00
Henrik Rydgård 3c6c7f548b Merge pull request #22433 from hrydgard/softgpu-threading
SoftGPU: Improve threading
2026-10-06 08:09:17 -06:00
Henrik RydgårdandClaude Opus 5.5 3c3025a087 SoftGPU: Keep drawing queued when a display list stalls
Returning to the CPU flushed all queued drawing, also when a list only reached its stall address.
Wipeout Pure builds its lists as it goes and stalls about a hundred times a frame, so the main thread
spent a third of its time finishing the drawing at those flushes. A stalled list isn't done, so now
only a finished one flushes there, which makes Wipeout Pure 15% faster.

What relied on that flush now flushes itself: memory copies, sets, readbacks and uploads that overlap
the queued drawing, and save states, before memory is saved or loaded (FlushPendingDrawing). Syncs and
the display already did. Queued drawing still reads textures from PSP memory when it runs, so a game
that rewrites a texture between stalls without syncing would see the new one; on hardware the GE has
usually drawn with the old one by then.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:04 -06:00
499cdd55ad Validate untrusted IDs before using them as directory names
Texture pack installs took the destination directory from the archive's
textures.ini, and PKG update installs from the PBOOT's DISC_ID, both
unchecked, so a crafted archive or package could write outside
PSP/TEXTURES or PSP/GAME. Add IsSafePathComponent() to PathUtil and check
both, replacing the WebServer's own upload filename check with it too.

DISC_ID from any PARAM.SFO also names configs, cheats, textures, exdata
and savestates, so ReadSFO now drops one that isn't a safe name and the
game gets a fake ID like homebrew. The same goes for a frame dump's ID.

Combines #22419 and #22420 by Acts1631.

Co-Authored-By: Acts1631 <acts1631kjv@proton.me>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:39:06 -06:00
sum2012 a66249683d Add support sctrlHENSetStartModuleHandler by Zcode 2026-10-06 02:28:15 +08:00
Henrik RydgårdandClaude Opus 5.5 27ce1be6e9 Module start/stop threads: Honor the whole SceKernelSMOption
sceKernelStartModule, sceKernelStopModule and the self-stop calls now
check their option first: with SDK 2.08 or later its size must be 0x14,
and attribute bits outside 0x00F06000 are an error.

The option's attribute is added to the module's thread attribute rather
than ignored (or, when stopping, replacing it), and the default attribute
is 0 rather than the module's own attribute. A nonzero mpidstack puts the
stack in that partition, which has to be user-accessible for a user
module and kernel-only for a kernel module.

A failed thread creation is now returned instead of asserting.

Hardware test: modules/startoptions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 10:10:22 -06:00
Henrik RydgårdandClaude Opus 5.5 efc1969177 Module start/stop threads: 0x1000 default stack for kernel modules
modulemgr only defaults a module_start/module_stop thread's stack to 0x40000
for user modules. Kernel modules get 0x1000. We gave every
module 0x40000, which mostly cost kernel memory when booting the VSH.

User modules, and so games, are unaffected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 09:42:04 -06:00
Henrik Rydgård 9a2f5eb191 Merge pull request #22414 from hrydgard/ge-bit-exact
SoftGPU: Make it mostly bit exact vs PSP hardware
2026-10-05 09:15:24 -06:00
Acts1631 7de6a6ccfb Avoid overflow in matching packet length checks
Matching packet handlers added untrusted payload lengths to fixed header
sizes before comparing them with the datagram length. Large values could
overflow and permit out-of-bounds reads. Compare against the remaining
packet size instead, and reject negative ACCEPT sibling counts.
2026-10-03 21:55:32 -04:00
Henrik RydgårdandClaude Opus 5.5 2c5ff39238 Headless: Add --screenshot-render-target
Lets a dump replay stopped with --replay-end be compared against the PSP on
an offscreen buffer. The framebuffer commands after the draw limit are
dropped too, so it shows the buffer the last drawn primitive went to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:42 -06:00
Henrik RydgårdandClaude Opus 5.5 3c450eed4b Headless: Add --depth-save and --replay-end for frame dump comparisons
--depth-save writes the depth buffer next to the screenshot (u32 width, u32
height, then u16 depths), and --replay-end stops a frame dump replay after
that many primitives. Both match pspautotests' ppdmp-playback, so a dump's
depth can be compared with the PSP's at any point in the frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:42 -06:00
Henrik Rydgård a2f4ce214f Merge pull request #22412 from hrydgard/wsdbg-nowait
Wsdbg, headless improvements
2026-10-01 17:17:12 -06:00
Henrik RydgårdandClaude Opus 5.5 1772448638 HLE: Don't delay results in an interrupt handler
hleDelayResult and Sas's delay put the wait on whatever thread was
current, which in an interrupt handler is the idle thread it runs on.
With both idle threads waiting there's nothing left to schedule, the
same crash as sceDisplaySetMode in Gods Eater Burst's vblank handler.

Where pspautotests intr/delays shows what hardware returns there,
match it: sceIoOpen and __sceSasCore fail with ILLEGAL_CONTEXT,
sceMpegCreate with 80618008, and sceAtracReinit can deinit but not set
up the codec (807f00fd).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 15:49:26 -06:00
Henrik RydgårdandClaude Opus 5.5 2fdc96e9df Headless: Always print the debugger port when --debugger=0 picks one
wsdbg --launch learns the port from our output, which without --log was
nothing. Also set the port before reporting the server as running, so
WebServerWaitForStartup() callers can read it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 15:39:08 -06:00
Henrik RydgårdandClaude Opus 5.5 8c419a182a sceDisplay: Don't wait for vblank from an interrupt or with dispatch off
Gods Eater Burst calls sceDisplaySetMode from its vblank handler. Its
implicit vblank wait went to the idle thread the handler runs on. Since
vblank waiters are released ~48us after the vblank (02b2b7b699), the next
frame's handler runs, on the other idle thread, before the first one is
released, so both idle threads end up waiting and the scheduler asserts
with an empty ready queue on leaving the interrupt.

On hardware sceDisplaySetMode returns 0 without waiting there, and with
interrupts or dispatch disabled (pspautotests intr/waits).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 15:36:32 -06:00
Henrik RydgårdandClaude Opus 5.5 932d1e3b36 IconCache: Expire only avatars after a day, other icons after 30 days
Achievement badges and game icons don't change, so re-downloading them
daily was wasted. InsertIcon now takes a max age, and each entry saves
its expiry time. Leaderboard avatars get a day, everything else 30 days.
Bumps the cache version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 ab8fc0d1bb Achievements: Remove unused ResetRuntime
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 77cc6a8071 Achievements: Don't pass a null error message to %s
error_message is null on success, which is when these two lines log.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 39797c4a44 Achievements: Reload RAIntegration when achievements are re-enabled
With RAIntegration, Initialize leaves login to the DLL load, but WinMain
only calls InitializeRAIntegration once and Shutdown unloads the DLL, so
turning achievements off and on left you logged out until restart.
Remember the window and load the DLL again on its thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 8aac115e91 Achievements: Use the sized progress (de)serializers for savestates
The deprecated unsized rc_client_deserialize_progress parses with no
bound, so a truncated or corrupt state could make it read past the
buffer. data_size is known, so pass it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 19082ce1af Achievements: Don't start a password login while a token login is pending
rc_client answers a second login with RC_INVALID_STATE, synchronously,
and login_password_callback then cleared g_isLoggingIn while the token
login was still running. IsBlockingExecution stopped blocking, and a game
started then ran without achievements.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 efc3f5d556 Achievements: Stop retrying a login token the server rejected
On invalid credentials the token was kept, and Idle() retried it every
10 seconds for the rest of the session, silently after the first attempt.
Clear it, as was already done for an expired token, so the settings show
the login form. On access denied, stop retrying.

Show a rejection even when it comes from a background retry, since it
won't repeat, and have LoginProblems return the server's message.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 5c0a802518 Achievements: Clamp a truncated rich presence message
rc_client_get_rich_presence_message returns the length it needed rather
than what it wrote, so a message of 512 characters or more made the pause
screen read past its buffer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 c8b32dda0a Achievements: Keep a shut-down client alive until its requests finish
rc_client keeps raw pointers to itself in the callback data of award,
leaderboard submit and ping requests, so destroying it with one in flight
(disabling achievements mid-game) had the late callback write to freed
memory. Count requests per client, retire a busy client instead of
destroying it, and destroy it when its last request completes.

Request timeouts in naett are per operation and the plain HTTP client has
none for the body, so a retired client is also destroyed after 60 seconds
regardless. Its late callbacks are then dropped, leaking their data.
Clients are tracked by ID so a new client at a reused address can't get an
old one's callbacks. At app exit, pending requests are cancelled without
callbacks, so everything is destroyed immediately.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 fde1a467ca Achievements: Report transport failures to rc_client as retryable
naett reports a connection failure as -1, which rcheevos reads as
RC_API_SERVER_RESPONSE_CLIENT_ERROR, a non-retryable client error. An
unlock or leaderboard submission during a network dropout was then
treated as a server rejection and dropped, on every platform using naett.
Map any non-HTTP result to RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 2291a4a9a7 Achievements: Don't leave boot blocked after an aborted login or load
rc_client doesn't call back for a game load aborted by unload, or for
anything pending when the client is destroyed. g_isIdentifying or
g_isLoggingIn then stayed set, and IsBlockingExecution kept every later
boot on a black screen until restart. Quitting during identification, or
disabling achievements during a token login, was enough.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 00208d3d80 Achievements: Enable HTTPS on UWP, retry failed icon downloads
UWP compiled naett with the WinHTTP backend but never reported
SYSPROP_SUPPORTS_HTTPS, so achievement traffic and badge icons went over
plain http. When the media server redirected to https, icons failed.

A failed icon download also left the URL marked pending, so it was never
retried for the rest of the session. Mark it failed instead, and let
IconCache retry it after 30 seconds.

The set-completed badge also skipped RemoveHttpsIfNeeded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 aa603905db sceIo: Log sceIoWaitAsync with nothing pending at debug level
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00