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>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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>
Type-to-search asks for the title of every game in the list, queueing a
load for each, and launching a game whose info wasn't loaded yet then
blocked until the whole queue had drained. Search's loads are now LOW,
and launching asks at HIGH, which queues its own load rather than wait
for a pending one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Put the anisotropy level in the sampler key, so changing it applies on
Vulkan and D3D11.
- Release CLUT textures at shutdown on GLES and D3D11.
- Fix the depth readback viewport, which squeezed the image whenever the
read rectangle was smaller than the fbo.
- Test the computed depth, not the unset result, in the equal-depth clear
check.
- Don't read back a CLUT from a framebuffer without an fbo.
- Tolerate null entries when releasing post-shader objects and CLUT
textures after a failed creation.
- ImGe: Don't crash on a framebuffer without an fbo, or on GetVFB under the
software renderer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GLSLtoSPV takes an optional SPIRVCache, keyed on a 32-bit hash of the
source, stage and variant, plus the source length. A changed shader
simply misses. thin3d's shaders and the other fixed ones use a global
cache in PSP/SYSTEM/CACHE/vulkan_spirv.cache, loaded on first use and
saved after graphics init, when a game's cache is saved, and at
shutdown; it's flushed once it reaches 32 entries, about twice what a
session compiles, so outdated ones don't pile up. Game shaders keep
theirs in the .vkshadercache, ahead of the shader IDs so that the
compiles on load find it (version 60), and only what the session used
is saved.
A cold glslang costs about 40ms before its first shader here, and
0.3-0.9ms per shader after that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like the UI atlas, keep the decoded image around and only recreate the
texture when a new UIContext asks for it, rather than reading the
metadata and decoding the ZIM from disk each time.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MP3 emulation already went through FFmpeg, leaving MiniMp3Audio dead.
The one live user was loading MP3 UI sound effects (custom achievement
sounds), which now splits the file into frames and decodes them with
the FFmpeg MP3 decoder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
'PPSSPPDebug64.exe' (Win32): Unloaded 'C:\Windows\System32\mfreadwrite.dll'
The thread 'RecentISOThreadFunc' (9920) has exited with code 0 (0x0).
The thread 'Console' (10488) has exited with code 0 (0x0).
Detected memory leaks!
Dumping objects ->
{17571338} normal block at 0x00000214CC4A3FE0, 16 bytes long.
Data: < Cf > E8 43 66 CD 14 02 00 00 00 00 00 00 00 00 00 00
{17571337} normal block at 0x00000214CC4A3B80, 16 bytes long.
Data: < Cf > C8 43 66 CD 14 02 00 00 00 00 00 00 00 00 00 00
D:\project\memory_leak\ppsspp\Windows\Debugger\Debugger_Disasm.cpp(174) : {17571336} normal block at 0x00000214CD664190, 640 bytes long.
Data: < C > 90 43 F2 C0 F6 7F 00 00 F6 0C 04 00 00 00 00 00
D:\project\memory_leak\ppsspp\UI\NativeApp.cpp(879) : {84582} normal block at 0x00000214CE8C5DB0, 4224 bytes long.
Data: < > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
{2432} normal block at 0x00000214D6255C20, 16 bytes long.
Data: <0 $ > 30 E7 24 D6 14 02 00 00 00 00 00 00 00 00 00 00
D:\project\memory_leak\ppsspp\Common\Net\HTTPNaettRequest.cpp(43) : {2431} normal block at 0x00000214D624E730, 32 bytes long.
Data: < \% > 20 5C 25 D6 14 02 00 00 00 00 00 00 00 00 00 00
Object dump complete.
The thread 22000 has exited with code 0 (0x0).
The thread 21248 has exited with code 0 (0x0).
The thread 38104 has exited with code 0 (0x0).
The thread 27860 has exited with code 0 (0x0).
The thread 33240 has exited with code 0 (0x0).
The thread 29352 has exited with code 0 (0x0).
The thread 13964 has exited with code 0 (0x0).
The thread 5640 has exited with code 0 (0x0).
The thread 10528 has exited with code 0 (0x0).
The thread 15252 has exited with code 0 (0x0).
The thread 23016 has exited with code 0 (0x0).
The thread 31424 has exited with code 0 (0x0).
The thread 7000 has exited with code 0 (0x0).
The thread 11620 has exited with code 0 (0x0).
The thread 36264 has exited with code 0 (0x0).
The thread 27248 has exited with code 0 (0x0).
The thread 24780 has exited with code 0 (0x0).
The thread 26228 has exited with code 0 (0x0).
The thread 13676 has exited with code 0 (0x0).
The thread 16828 has exited with code 0 (0x0).
The thread 27388 has exited with code 0 (0x0).
The thread 1668 has exited with code 0 (0x0).
The thread 37272 has exited with code 0 (0x0).
The program '[23456] PPSSPPDebug64.exe' has exited with code 0 (0x0).
The blit cost remembered only the last buffer a decoder wrote into, forever.
Move the texture cache's video list (with its ageing out a few flips after
the last write) into GPUCommon, so the texture cache, the blit cost and
SoftGPU all share one. That also counts both of a double-buffered player's
frames, which exposed that a clear drawn with texturing still enabled was
being charged as a blit - skip clears and draws without texture coordinates.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A request started since the last RequestManager::Update (headless never
calls it) sat in newDownloads_, which CancelAll skipped. It was then
destroyed along with the static g_DownloadManager at exit, and its
destructor removed its progress bar from the already destroyed g_OSD:
"mutex lock failed". Seen with a Netconf dialog still downloading the
infra DNS json when a test ended.
CancelAll now takes the new ones too, and runs at shutdown while g_OSD is
still there. The Netconf json request is also let go of when the emulator
shuts down, rather than living on into the next game.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The vertex decoder JIT was enabled only when g_Config.iCpuCore was one of the
JIT cores, which tied an unrelated GPU path to the CPU setting. Headless leaned
on that by forcing iCpuCore to the interpreter after ApplyToConfig(), to keep
the decoder JIT off in the tests.
Add CoreParameter::bUseVertexDecoderJit instead. Standalone and libretro set it
wherever the host can JIT, so IR interpreter users on such hosts now get the
decoder JIT too. Headless sets it false, which keeps the test results as they
were and lets --cpu go through ApplyToConfig() like every other option.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deleting a savestate from the savedata screen goes through GameInfo::Delete,
not SaveState::DeleteSlot, and it only knew about the .jpg - so the slot's
.name.txt was left behind with nothing to belong to.
Rather than teach the UI the naming scheme a third time, SaveState now answers
what sits beside a state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From 947aa9c97 to 98a66d8c8 on the docking branch - the latest release there,
plus the viewport fixes that landed on top of it. The only local changes to
upstream files are the "#undef new" hack at the top of imgui.h and enabling
IMGUI_DISABLE_OBSOLETE_FUNCTIONS in imconfig.h; both re-applied.
1.92 reworked fonts and textures, so the thin3d backend now advertises
ImGuiBackendFlags_RendererHasTextures and creates/updates/destroys imgui's
textures on request instead of building the font atlas itself. ImTextureIDs
below 256 now index those, above it the per-frame temp textures as before.
thin3d can't replace part of a texture yet, so an update re-uploads the whole
thing - fine for an atlas that only changes when a new glyph appears.
Also: AddRect() swapped its thickness and flags parameters in 1.92.8.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same shape as the audio table: one row per open context, with its EDRAM block
and frame buffer allocation. Both are in ME memory, so the addresses shown are
in that space, not in PSP RAM.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It used to return the path unchanged when the path didn't end in oldExtension,
so a caller that guessed wrong silently went on using the original file - and
"the screenshot next to this savestate" quietly becomes "this savestate".
Every caller had to know to check the extension first, and most didn't.
Now it's [[nodiscard]] bool with an out-param, in the style of ComputePathTo
next door, so the mismatch has to be handled. Changing the signature rather
than the behaviour means no call site can keep the old assumption by accident.
All four callers wanted "skip it" or "fall back", which they now say out loud.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nine channels were one array of one struct, but only index 8 ever used the
two DMA descriptors, the played/fraction position and the waiting-thread vector,
and only 0-7 ever used the buffer slot - so eight copies of a std::vector sat
there dead. They are two different pieces of hardware and now they are two
structs: AudioChannel for the eight the mixer walks, and a single AudioSRCChannel
for the one the codec reads directly.
Found while rereading it:
- __AudioUpdate can now run from inside an output call, and it reschedules when
it wakes a thread. A context switch there lands before the syscall writes its
return value, so the caller loses it and the woken thread gets it instead.
Deferred with hleReSchedule when a syscall is in flight. The same fix applies
to the release path, which had the problem before this branch existed.
- A finished SRC buffer whose waiting thread had given up dropped the completion
entirely, so the next caller waited a buffer too long. Now it walks past dead
waiters and banks the completion if nobody is left.
- An output with a null pointer changed the channel volume, where the hardware
returns before touching it.
- A busy channel came back as an error from the Output2 path and as debug from
the mixer path. It is an ordinary answer a game polls on, so both are debug
now; the old behavior filled the log with 18k error lines in a minute of F1
2009.
- AudioChannel::reset had no callers left: releasing a channel with a thread
parked on it is refused, so there is nobody to wake.
- The two routing modes are globals and were written once per channel. They get
their own small savestate block.
Savestate: the sceAudio section goes to 3, and the SRC channel gets a section of
its own. States from released builds carry it as a ninth channel record, which is
read and discarded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I deduced that this was the case, and attempted implementing this path long ago,
but I could never quite get it to work in all games. Set Claude on a quest to
research and implement it, and lo and behold, it works. A bit sobering.
Fixes#12888 and likely more. Additionally, audio latency is likely slightly
improved overall, and memory usage is down by 4.6MB.
Claude says:
The blocking output calls are not a queue that callers line up behind. Each
mixer channel holds exactly one buffer and at most one parked thread; a second
thread arriving while the first is waiting is told the channel is busy and is
expected to skip its turn. The Output2/SRC channel holds two DMA descriptors and
refuses a third caller outright, without waiting at all.
We blocked everyone instead, so a game running a movie thread and a sound-effect
thread over one output made the two alternate - a frame of movie audio, a frame
of effects silence - and the movie played at half rate. That is #12888, seen in
F1 2009 and Colin McRae: DiRT 2. With this, the movie thread keeps the channel
for the whole cutscene and the effects thread is refused, which is what the
hardware trace shows.
The driver also never copies a buffer on the way in: it stores the pointer and
its mixer walks it forward 64 samples at a time out of the game's own memory.
Modelling that fixes#20095 as a side effect, and drops the 4.6MB of per-channel
sample rings we were carrying. The mix event is re-phased to the moment a DMA
starts, since the mixer thread outranks its caller and gets a block in before the
output call returns.
Along the way: the two rest-length calls differ after a null-pointer output,
sceAudioChRelease reports not-reserved rather than not-init,
sceAudioChangeChannelConfig validates the format,
sceAudioChangeChannelVolume validates nothing, and sceAudioOutput2ChangeLength
takes a range of 17..4111. Details in docs/sceAudio.md.
Savestates: AudioChannel goes to version 4. Older ones stored mixed samples that
can't become a pointer and a position again, so they load with the pending audio
dropped and any parked threads released.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The five new [System] keys, in the 33 languages that already had a rendering of
"firmware" to follow - they disagree on it (fastvare, prosjivka, systemprogramvara,
laiteohjelmisto), so each one matches whatever its own file already chose. The
other 13 fall back to English.
Firmware screen: call it "Uninstall firmware", not "Erase firmware"
Translate the rest of the firmware screen strings
"Uninstall firmware", "No firmware installed" and "Launch XMB", in the same 33
languages as the auto-install strings. XMB stays untranslated - it's Sony's
name for it, and pl_PL already wrote it bare.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Most UMDs carry a firmware updater, and having a real firmware in the NAND is
what the LLE modules want. On boot, if the disc's updater is newer than what's
installed - or nothing identifiable is installed, which is what a fonts-only
NAND looks like - unpack it, with a progress bar on the OSD. Controlled by
bAutoUpgradeFirmware, on by default, with a checkbox on the firmware screen.
Off in headless, which shouldn't rewrite the NAND during a test run.
The install itself is now shared with the install screen, and hardened, since
it can run without anyone watching:
- It stages into <NAND>/install-staging and only erases the installed firmware
once the new one is complete on disk, so a failure leaves what's there alone.
- A single entry that didn't unpack fails the whole install. A firmware with
holes still looks installed, so nothing would ever replace it.
- A truncated archive is rejected instead of unpacking to half a firmware that
every stat reports as a clean install. The PSAR header records the length;
it was being clamped to the buffer rather than checked against it.
Also falls back to the version in the archive's own header when the PARAM.SFO
next to the updater is unreadable, which it is on a fair number of discs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Firmware 6.20 is installed. It will be erased and replaced with 6.60." is the
thing worth double-checking before wiping a firmware - installing off whatever
disc is to hand makes going backwards easy to do by accident.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The game screen already reports which updater a disc carries, but there was no
way to act on it - the only route to installing one was picking an updater PBP
out of the browser. The unpacker already handles being pointed at a disc, so
this just wires the menu entry to it.
Shown only when the disc actually has one, and not while a game is running:
installing wipes the NAND that game has mounted.
InstallUpdateScreen takes the archive size now, because for a disc the size of
the file it was handed is the game's and says nothing about the firmware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With no kernel modules there's no firmware to speak of - it's the fonts we
pulled off a game's disc - and rows reading "Kernel modules: 0 / XMB: No"
say nothing. Keep the font count and the size.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Shows what's actually in PSP/NAND - nothing, a fonts-only partial install, or
a full firmware with its version, build date and region - read from
flash0:/vsh/etc/version.txt, which is present both in a PSAR-unpacked install
and a NAND dumped off hardware.
Also offers to install an updater, erase the NAND, and launch the XMB, the
last one gated on FirmwareVersionSupportsVSH() since the module patches that
get vshmain.prx running are tied to 6.61's offsets.
Installing a firmware now erases flash0/flash1/ipl first - two firmwares can't
be merged, a file the new one doesn't have would linger and still get loaded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two fudge factors in the reverb path cancelled, which is why the
overall level felt roughly right.
- The send is accumulator * 0x20 >> 16, i.e. sample >> 2. We used >> 1,
driving the reverb 6dB hot.
- The return is (evol * out) >> 11. We used >> 12, i.e. 6dB quiet.
Net level is therefore unchanged, but the reverb now runs at the level
the presets were designed around. That matters because the filter clamps
internally, so a 6dB hot input changes how the feedback path saturates -
worst on the presets with heavy feedback.
Also adds a slider in the imgui.
An installed update silently replaces what the game boots, so the info
pane now says when there is one - version, size and where it lives - and
the context menu offers to remove it again.
Removing takes the whole PSP/GAME/<DISC_ID> folder when the update is all
that's in it. When a digital game shares the folder, only PBOOT.PBP goes,
since deleting the folder would take the game with it and nothing records
what the install wrote. The confirmation names the exact path either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018izZ1mGTWhz2RqeudqsDQR
Opening a .pkg now offers to install it, the way a .zip does - see the new
InstallPkgScreen, which shows what the update patches, what it'll take up
on disk (exact, since package contents aren't compressed) and where it's
going. The package's PS3-style USRDIR/CONTENT wrapping is stripped so the
files land where the PSP expects them, in PSP/GAME/<DISC_ID>.
Booting a disc then looks for PSP/GAME/<DISC_ID>/PBOOT.PBP and boots that
instead of the disc's own EBOOT, leaving the disc mounted - so the update
overrides the files it ships and the disc supplies the rest. The update's
DISC_ID has to match; a DISC_VERSION mismatch only warns, since updates do
get used with slightly different dumps in practice.
Verified against the whole corpus: every one installs, and the
digital NP* update/base-image pairs that could be assembled all boot the
patch rather than the disc's executable. That includes Super Robot Taisen
Operation Extend from a real NPUMDIMG EBOOT.PBP, which settles that
ISO.BIN.EDAT does not re-key the PBOOT - a digital title's patched EBOOT is
encrypted exactly like a UMD one. On the UMD side, the patched
LittleBigPlanet reads PATCH.ARC out of the install alongside the disc's own
archive.
The DISC_VERSION warning turns out to be load-bearing: many of the pairs
mismatch, because the dumps in circulation are later disc revisions than the
updates were built against. docs/pkg_notes.md has the numbers, and the one
thing that doesn't work - PGD-wrapped .sprx modules, which
sceKernelLoadModuleNpDrm can't load.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>