Commit Graph
48148 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 19c57faadc 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) <[email protected]>
2026-09-30 12:28:35 -06:00
Henrik RydgårdandClaude Opus 5.5 70b046285c Store: Remove HttpImageFileView's unused non-icon-cache path
Nothing constructs the view with useIconCache = false, and the path was
broken anyway: download_ was never assigned, so it started a new download
every frame, and the destructor couldn't cancel a callback that captured
this.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:30:39 -06:00
Henrik RydgårdandClaude Opus 5.5 937b00af46 IconCache: Expire saved icons after 24 hours
Icons were kept in icon.cache indefinitely, so one that changes on the
server (a RetroAchievements user avatar, keyed by username) never
refreshed. The insertion timestamp was process-relative, so it couldn't
be compared across sessions. Store wall-clock time, and skip entries
older than a day when loading. Bumps the cache version.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:29:43 -06:00
Henrik RydgårdandClaude Opus 5.5 8b96d109ff IconCache: Know an icon's size before its texture exists
GetDimensions only answered once a texture had been created, so the first
MeasureNotice for a new icon (and after the texture was released for being
unused) measured without it, and the icon was then drawn over the text.
Read the size from the PNG header when the icon is inserted or loaded.

Also remove a width adjustment in MeasureNotice that was overwritten
before use.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:29:17 -06:00
Henrik RydgårdandClaude Opus 5.5 afe4025106 IconCache: Bound failed_, back off failed uploads, fix the disk layout
- Prune expired failed_ entries, which otherwise grew for the whole
  session.
- When CreateTexture fails, wait 5 seconds before decoding and uploading
  again, instead of retrying every frame.
- Bind a newly created texture. Callers draw right after BindIconTexture,
  and got whatever was bound before for the first frame.
- Make DiskCacheEntry's padding explicit. 32-bit x86 Linux aligns double
  to 4, so its icon.cache didn't match other builds. The layout is
  unchanged elsewhere.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:28:26 -06:00
Henrik RydgårdandClaude Opus 5.5 9171c218c3 Achievements: Remove unused ResetRuntime
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:27:08 -06:00
Henrik RydgårdandClaude Opus 5.5 8d98c39b1f 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) <[email protected]>
2026-09-30 11:27:02 -06:00
Henrik RydgårdandClaude Opus 5.5 ba8ad3af63 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) <[email protected]>
2026-09-30 11:26:52 -06:00
Henrik RydgårdandClaude Opus 5.5 3e59842fd9 naett: Complete a Windows request when WinHttpWriteData fails
The return value was ignored, and after a synchronous failure no further
callback arrives, so the request never completed. During achievement game
identification, that blocked boot indefinitely.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:25:55 -06:00
Henrik RydgårdandClaude Opus 5.5 21963d4534 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) <[email protected]>
2026-09-30 11:25:37 -06:00
Henrik RydgårdandClaude Opus 5.5 71bce6343f 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) <[email protected]>
2026-09-30 11:25:37 -06:00
Henrik RydgårdandClaude Opus 5.5 bf2b84db74 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) <[email protected]>
2026-09-30 11:25:22 -06:00
Henrik RydgårdandClaude Opus 5.5 295739f1a9 HTTP: Publish request completion with release/acquire ordering
HTTPRequest::completed_ was a plain bool set on the download thread, and
naett's res->complete a plain int set on its transfer thread. On ARM, the
thread polling Done() could see the flag before the result code or body
written ahead of it, and report a stale code or a partial body.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:14:58 -06:00
Henrik RydgårdandClaude Opus 5.5 d585b4a6ae HTTP: Keep abandoned naett requests' sinks and POST bodies alive
Join() deliberately leaks the naett objects of a request still running at
shutdown and parks its sink, so a late chunk lands somewhere valid. But
HTTPSShutdown, which runs right after CancelAll, deleted those sinks, and
the POST body naett reads in place was owned by the request being
destroyed. On Android, where NativeShutdown isn't process exit, a late
WinHTTP/JNI callback could then write to or read from freed memory.

Only free the list when it's empty (that still fixes the leak the delete
was added for), and keep the POST body in the sink.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:14:58 -06:00
Henrik RydgårdandClaude Opus 5.5 d22f8e90a9 IconCache: Check entry lengths before allocating when loading the cache
The key was resized before its length was checked, and the data length
wasn't checked at all, so a corrupt icon.cache could make a multi-GB
allocation and crash every launch. The cache is only saved at a clean
exit, so a bad file stayed.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:12:53 -06:00
Henrik RydgårdandClaude Opus 5.5 b03374241b IconCache: Drop icons that fail to decode instead of keeping them forever
Any 200 response went into the cache, and one that didn't decode (a
captive portal's HTML, or a PNG over the 256x128 limit) was marked bad,
saved to icon.cache and never downloaded again. Remove it and mark it
failed, so MarkPending allows another download after the retry delay.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:12:53 -06:00
Henrik RydgårdandClaude Opus 5.5 7c71755610 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) <[email protected]>
2026-09-30 11:12:45 -06:00
Henrik RydgårdandClaude Opus 5.5 84291eb82b Achievements: Fix leaderboard screen use-after-free on close
rc_client frees a request's async handle right after calling back. The
error path didn't clear pendingAsyncCall_, so closing the screen after a
failed fetch aborted a freed handle. Switching between Top players and
Around me also overwrote the handle without aborting the old request,
which could then call back into a closed screen, or leak an entry list.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:12:44 -06:00
Henrik RydgårdandClaude Opus 5.5 30b2709762 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) <[email protected]>
2026-09-30 11:12:36 -06:00
Henrik RydgårdandClaude Opus 5.5 a6e8afdb3c docs: The ARM64 fix for PCH build failures is the arm64 MSBuild
PreferredToolArchitecture has no effect; the MSBuild.exe that runs decides
the compiler host. The earlier note claimed otherwise, from a build that
passed by luck.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 11:10:14 -06:00
Henrik RydgårdandClaude Opus 5.5 8a24643d4e 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) <[email protected]>
2026-09-30 11:06:15 -06:00
Henrik RydgårdandClaude Opus 5.5 ef08d06d02 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) <[email protected]>
2026-09-30 11:05:45 -06:00
Henrik RydgårdandClaude Opus 5.5 4509e9b460 docs: UWP build command, and the x86-hosted compiler PCH failure on ARM64
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 10:57:55 -06:00
Henrik RydgårdandClaude Opus 5.5 2869283fd4 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) <[email protected]>
2026-09-30 10:51:07 -06:00
Henrik Rydgård 0a21d2c9e1 Bump rcheevos 2026-09-30 10:48:29 -06:00
Henrik RydgårdandClaude Opus 5.5 e4837deaf8 sceIo: Log sceIoWaitAsync with nothing pending at debug level
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 10:43:00 -06:00
Henrik Rydgård 1b4eca0184 Merge pull request #22395 from hrydgard/thread-timing
Claude hardware testing: Thread timing improvements
2026-09-30 10:40:22 -06:00
Henrik RydgårdandClaude Opus 5.5 3ac43a70a6 Add the utility/savedata/shutdownstatus test
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:58:37 -06:00
Henrik RydgårdandClaude Opus 5.5 cc23f4cf13 AGENTS.md: Hardware claims need a test, one PSP operation at a time
Also that unresolved scePsmfPlayer imports early in a game run are
expected.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:58:37 -06:00
Henrik RydgårdandClaude Opus 5.5 a974440c79 Debugger: Time input.buttons.press in emulated vblanks
The release was counted down on the WebSocket thread, one step per poll
of host time however many vblanks had passed, so how long a scripted
press lasted depended on how fast the emulator ran, and scripted runs
went different ways. sceCtrl now releases it after that many vblank
samples, on the emulator thread; the debugger only reports when it's done.

Also: wsdbg's :screenshot works in headless with Vulkan.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:58:37 -06:00
Henrik RydgårdandClaude Opus 5.5 00c9a2d389 Utility: A savedata shutdown ends at priority 0x20
On hardware the last part of a savedata shutdown runs at priority 0x20,
whatever the dialog's own thread priorities, so a caller at 0x20 gets the
CPU back first and sees SHUTDOWN, and one at 0x21 or worse only sees NONE
(pspautotests utility/savedata/shutdownstatus). We ended it at the access
thread's priority, so Freak Out, which calls ShutdownStart from 0x20 and
waits for SHUTDOWN, sat at 'Please press START' forever. NFL Street 3,
which calls it from 111 and then InitStart straight away, still gets NONE.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:58:37 -06:00
Henrik RydgårdandClaude Opus 5.5 51bac9349e docs: Never run two PSP hardware tests at once
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 54e4be47aa CoreTiming: Grow the event table for events a savestate lacks
Taking the highest unused slot instead could steal one that a state event
restores later, as VBlankWake did to MicBlockingResume, which then had
nowhere to go. Also name the event in the assert.

AGENTS.md: When a savestate fails to load, suspect the branch first.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 d86896bed9 Tlspl: Time out at once like other waits
Recorded on hardware (pspautotests threads/tls/timeout), a Tlspl
allocation follows the same timeout rule as the other waits, including
failing at once for 0 and 1us without writing the timeout back, which the
shared rule it moved to in the last commits didn't give it yet. Before
that it waited the raw timeout, ~30us short.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 80938529a9 Kernel waits: Share waiter ordering and clearing between objects
Priority-ordered waiting lists were sorted with a comparator wrapper per
object (msgpipe, fpl, vpl), or searched with a copy of the same function
(mutex, mbx). HLEKernel::SortWaitingThreadsByPriority() and
FindBestPriorityWaiter() now do both for any waiting list, of thread ids
or of structs with a threadID.

HLEKernel::ClearWaitingThreads() replaces the identical cancel/delete
loops in semaphores, event flags, fpl and vpl.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 cc5ee42dda Kernel waits: One timeout event for every kind of object
Semaphores, event flags, mutexes, lwmutexes, mbx, msgpipes, fpl, vpl,
tlspl and WaitThreadEnd each had their own CoreTiming event, handler
registration and savestate entry for wait timeouts, and their own function
to schedule one. Now one event (WaitThreadEnd's, renamed) times out all of
them, keyed by thread, and dispatches on the thread's wait type to a
timeoutFunc registered alongside the begin/end callback functions.
__KernelWaitCurThreadWithTimeout() starts such a wait, and the HLEKernel
helpers have overloads that use the shared event.

Old savestates still load: each object's section reads its old event id
and points it at the shared handler, so a timeout pending in the state
goes off as before. Checked with a state saved mid-wait by the previous
build, and with four games.

The one behaviour change: tlspl timeouts now follow the same hardware
rule as the others, where they used the raw timeout.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 0c9438e60c CoreTiming: Load states that have event types we no longer register
A state with more event types than are registered now was refused, so no
event could ever be removed or merged. Loading now keeps the extra slots:
modules that still know an old event restore it to a handler, and the rest
stay placeholders that do nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 804dd59df0 Interrupts: Charge for alarm handlers, and stop parking threads on idle
An interrupt with no handler to run, a vblank with none registered for
example, switched the running thread off to idle and left it there until
some later event rescheduled: ~775us of every frame in a game that spins
without a vblank handler. It now reschedules at once. Taking an interrupt
also clears the ll bit directly, which that switch had been doing.

Interrupt handlers can now carry a cost before they run and after the
last queued one returns. Alarms use it: on hardware a thread that keeps
running loses ~70us to an alarm handler, and a thread the handler wakes
runs ~50us after it (pspautotests threads/scheduling/alarmcosts), so
17us in and 40us out. sceKernelSetAlarm's 40us is split evenly around the
deadline, keeping the handler ~1040us after a 1000us alarm.

A handler's return value re-arms its alarm counting from the previous
deadline, so a repeating alarm doesn't drift by those costs, unless
that's already past, as after interrupts were suspended for a while.

The vblank's own cost (~62us of CPU on hardware) isn't charged yet: with
it, a waiter ~90us after the vblank still reads hcount 1 on hardware, but
line 2 here. Hardware evidently raises the interrupt ~40us before the
line count wraps. That's noted where the waiters are released.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 02b2b7b699 sceDisplay: Release vblank waiters ~50us after the vblank
On hardware a thread waiting for vblank returns ~53us after it, where we
had it back in ~5us, and the first of four waiters runs after ~85us: each
waiter beyond the first adds ~9us (pspautotests threads/scheduling/
vblankwake). The waiters are now released by a separate event 48us + 9us
per extra waiter after the vblank. Which vblank a wait is for is still
decided at the vblank, so a thread that starts waiting in between still
waits a whole frame (sceDisplay section version 8).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 f26ca37dad Threads: Context switches cost what they do on hardware
Timed on a PSP, each way of handing the CPU to another thread (the call
and the switch together):

                                     hardware   before   now
  rotate to an equal thread              7        14       7
  signal, better thread runs            10        17      10
  it waits again, back to caller        10        19      12
  wakeup, better thread runs             8        13       6
  it sleeps again, back to caller        7        12       6
  start a better thread, entry          30        28      30
  thread ends, back to its waiter       21        13      20
  notify, better thread's callback      14        13      14

A switch between two threads now costs 1150 cycles instead of 2700.
Starting a better thread costs 2000 cycles more, ending a thread 3300,
and setting up a callback 1800.

Also splits a wait timeout's ~30us into the deadline being taken 12us
into the call and the timeout going off 18us after it. That only changes
the time left written back, which threads/semaphores/wait and
threads/fpl/cancel pin between them. intr/vblank is re-recorded so it
no longer depends on the phase of the frame.

threads/callbacks/combos now passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 f5b4fd739d test.py: Add threads/scheduling dispatchwake and mutexhandoff
Both already pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 a17a9eb147 Callbacks: A notify takes the thread out of its CB wait right away
On hardware, notifying a callback of a thread in a CB wait takes it out of
the wait at once, even though the callback only runs when the thread would
get the CPU. A semaphore signalled in between doesn't end the wait: the
callback runs first, then the wait resumes and takes it (pspautotests
threads/callbacks/combos). We left the thread on the wait list until the
callback started, so the signal ended the wait and the callback didn't
run.

The notify now pauses the wait, as starting a callback used to. If the
callbacks are canceled before the thread's turn comes, the wait just
resumes (Thread savestate section version 7).

threads/callbacks/combos goes in the to-do list: a callback returning to
the thread that notified it still takes ~13us where hardware takes ~9,
part of the context switch cost.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 fb9ac8397e Threads: Wait timeouts work like hardware's, one rule for all of them
Every wait with a timeout behaves the same on hardware (pspautotests
threads/scheduling/waittimeouts). The deadline is taken, and the alarm set
up a moment later. If the deadline has passed by then, the wait fails with
WAIT_TIMEOUT at once, without yielding or writing the timeout back. That's
usual for 0us, half the time for 1us, and rare after; AllocateVpl does more
first. Otherwise it ends max(t, 205us) + ~35us after the call. Each object
had its own guess (24/245, 25/250, 20/250 and so on), and only MsgPipe had
the immediate case.

__KernelWaitTimesOutAtOnce() and __KernelWaitTimeoutUs() now do it for
semaphores, event flags, mutexes, lwmutexes, mbx, msgpipes, fpl, vpl and
WaitThreadEnd. The latency past the deadline isn't counted in the time
left written back.

Outcomes that hardware decides by the clock's phase (these, and
sceKernelDelayThread returning at once) go with the likelier one. Ones
between 50% and certain are instead spread evenly over calls, so a polling
loop can't lock into never yielding (sceKernelThread section version 7).
This replaces the pseudo-random choice for delays.

Also adds threads/scheduling/readyqueue, which already passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 f731e45f40 Alarms: Setting one takes ~40us, and it can't go off within ~215us
On hardware sceKernelSetAlarm takes about 40us, and the handler never runs
sooner than about 215us after the alarm is set, however short it asked
for (pspautotests threads/scheduling/alarmcosts). Also clamps huge
sysclock alarms before converting to cycles; LONG_LONG_MAX used to
overflow and go off at once, which the late-firing events had hidden.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik RydgårdandClaude Opus 5.5 93a0d50384 CoreTiming: An event due before the slice ends shortens it
Each slice is sized to end at the next queued event, but scheduling a
sooner one didn't touch it, so the new event waited for the old slice to
run out. An alarm set by a thread that kept running went off 175-440us
late. GE enqueues worked around this with hleCoreTimingForceCheck(); now
every caller gets it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00
Henrik Rydgård 3118490ecf Merge pull request #22391 from hrydgard/atrac3-joint-stereo
Atrac3: Correct the decoder setup for mono streams (fixes LocoRoco 2 MuiMui house music)
2026-09-30 08:59:00 -06:00
Henrik RydgårdandClaude Opus 5.5 80b7d27c12 Add the audio/atrac/c0mono and audio/audiocodec/at3param tests
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 17:14:03 -06:00
Henrik RydgårdandClaude Opus 5.5 9ba40416da sceAtrac: Charge ME time when SetData's first frame doesn't decode
That failure comes after the codec is set up and the frame has been tried
on the ME, so the thread waits for both, unlike the other SetData errors.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 17:14:03 -06:00
Henrik RydgårdandClaude Opus 5.5 b9ea98d2c5 Atrac3: Set up the decoder the way libatrac3plus does
libatrac3plus picks the codec parameter from the frame size and the
header's joint stereo flag, and the channel count plays no part. We
guessed joint stereo from the frame size and channel count instead, so
LocoRoco 2's MuiMui house music never started: the game writes a 2-channel
normal-stereo header for every track it streams, and that 0xC0 track holds
one mono sound unit per frame. Taken as joint stereo, its first frame failed
during setup, and the game retried forever.

Now the joint stereo flag comes from the track header, and the decoder's
channel count from the parameter it maps to, so that track decodes as mono
into both output channels, as on hardware. Low-level decoding, which has
no header, still goes by the frame size. Atrac2 saves the flag; older
states fall back to the guess.

Fixes #8647.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 17:14:00 -06:00
Henrik Rydgård 3bcd93d594 Merge pull request #22394 from hrydgard/gameinfo-priorities
GameInfoCache: Prioritize loads, so launching doesn't wait behind search
2026-09-29 17:05:27 -06:00