The volume key filter in NativeKey left unmapped volume keys to the OS, so
they never reached the dialog and couldn't be mapped. A new InputMode::AllKeys
lets a screen ask for them; the dialog consumes them so the volume doesn't
change while mapping.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A recent input refactor made NativeKey return false for volume keys before
queueing them, so mapping them to L/R (handy on a phone without a controller)
stopped working in the menus, and in game the OS changed the volume as well.
In the menus they still change the volume too. Unmapped volume keys go to the
OS only, as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
--mount ISO used to be headless-only, and EmuScreen always cleared
CoreParameter::mountIso. Pass it from the command line into the first
EmuScreen, which keeps it across resets and drops it when another game
is booted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tilt used the raw accelerometer as the gravity vector, which jitters with
sensor noise and jumps when the device is shaken. Now, when gyro data is
coming in, a complementary filter integrates the gyro rates and only pulls
the result toward the accelerometer with a 0.5 s time constant, trusting
the accelerometer less the further it reads from 1g. The fused vector goes
through the same ProcessTilt path, so calibration and sensitivity work as
before. Without a gyro, nothing changes.
The fusion lives in TiltEventProcessor behind NativeGyroscope(), so other
sources (DualSense) can feed it too. Android registers TYPE_GYROSCOPE next
to the accelerometer. A "Use gyroscope" checkbox (on by default, per game)
appears in tilt setup once gyro data has been seen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The reload reset them, so options passed through the Args extra (like
--pause-menu-exit) were lost before the game even started.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"Compress game" in the game screen's ... menu, for ISO, CSO, CHD and PSN
EBOOT.PBP games. It opens a list of the CSO presets (and Extract to ISO
for anything that's not an ISO already):
- Best: libdeflate level 12, 64K blocks
- PSP Compatible: zlib level 9, 2K blocks, readable by custom firmware
These match maxcso's presets, and the CSOs come out byte-identical to its.
The writer, Core/FileSystems/CSOCompress, reads the input through PPSSPP's
own block devices, so recompressing is a single pass with no temporary
file.
The original is deleted when done, unless "Keep the original file" is
checked (off by default). When the new name is taken, by a kept original
or another file, a number is added to it.
The work runs on a thread using every core, with a progress bar and time
left at the bottom of the game screen. It keeps going when the screen is
left, and the ... menu offers Cancel meanwhile. On the game list the
game's icon is covered by "Compressing...", and clicking it opens the
game screen instead of playing. When done, recents follow the new file
and the game screen and lists refresh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCxekYqPnzNr5d8Tp1q7aZ
NativeKey runs on the input thread, but timed mouse wheel releases are
sent from NativeFrame. A press landing between the release check and its
reset lost the release and left the wheel key held. The debug F9 hack
recreated all views straight from the input thread; it now runs on the
main thread.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Headless used ~/.ppsspp, which nothing else uses, while the app uses
~/.config/ppsspp (or $XDG_CONFIG_HOME/ppsspp on Linux). The derivation
is now shared, and the app's default is unchanged. Windows and iOS keep
their own.
test.py and frametests.py pass a memory stick next to the binary, as
Windows headless already defaulted to, so the tests still don't write
into the user's memory stick or see its firmware.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NativeInit loaded ppsspp.ini from the platform's memory stick and only
then applied the command line, so with --memstick the app still read
its config from the default location and saved it back there (seen on
Mac, with ~/.config/ppsspp). Set it before the config is loaded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#17608
Text fields took Ctrl or Cmd for copy/paste/undo; now it's Cmd on Apple
platforms and Ctrl elsewhere, as is Ctrl+F (Cmd+F) for game search. Word
delete in the search filter is Option+Backspace on Apple.
The ImGui debugger never got Super, so with ImGui's Mac Cmd/Ctrl swap,
its paste and other Ctrl shortcuts could not be reached at all on Mac.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #21585
Android 17 blocks LAN traffic in both directions (TCP and UDP, including
broadcast and multicast) without ACCESS_LOCAL_NETWORK. Ask for it when
networking, the built-in ad hoc server, UPnP, share-on-startup or the
remote debugger gets turned on, when a game initializes ad hoc with
networking enabled, and at startup if a server is set to start then.
A denied ad hoc send fails with EPERM, which the games never see, so show
a message for it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Android it's set on the Java UI thread right after writing g_display,
and read on the render thread, which then copies the dp size into the UI
bounds just once. With a plain bool the render thread could see the flag
before the new size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NativeIsAtTopLevel() made a virtual call on the top screen from the Java
UI thread, while the render thread could be deleting it. Two quick Back
presses could hit that. Cache the flag in ScreenManager::Update()
instead, like passInputToMapper_ (now atomic too).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only EmuScreen and PauseScreen let it past imgui's keyboard capture, so on
other screens the debugger couldn't be closed by key while a field had focus.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Since db8ff43, a leftover local shadowed lastImguiEnabled_ in runImDebugger,
so TEXT_GOTFOCUS was never sent and SDL never started text input for imgui.
Drive it from io.WantTextInput instead, so it's only on while editing.
The regular UI and the debugger now say independently whether they want text
input, and the platform only gets TEXT_LOSTFOCUS once neither does, so leaving
an imgui field doesn't break type-to-search on the screen underneath.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
UIKit lays out at the new size from the main thread, often while a frame
is being drawn, and the swapchain was only resized after presenting. So
the first frame of a rotation was laid out for one size and drawn at the
other. The view controller now hands the new size to the render thread,
and NativeFrame resizes before drawing instead of after presenting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows now uses the same host interface as SDL and iOS: sceUsbMic sends
startRecording/stopRecording/deviceChanged, and drains buffered input on
the CPU thread with pollRecording. A capture thread asks WASAPI for mono
16-bit at the game's rate (it converts from the mix format, so no more
ffmpeg resampling), and copies packets into a lock-free ring buffer.
It reopens the device when the selection changes, and retries once a
second if the device is missing or gets unplugged.
The device list comes from the active capture endpoints. Their friendly
names are the ones Media Foundation reported, so existing MicDevice
settings still match. An empty setting, now selectable as Auto like on
SDL, means the default capture device.
The Media Foundation audio capture code is removed; the camera still
uses it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every rotation, fold or window resize destroyed the activity and with it
all of graphics, then built it all up again. Declare those config changes
handled, so they arrive as a surfaceChanged and go through the same resize
path as a desktop window resize.
That path needed three things it didn't have on Android:
- The GPU_DISPLAY_RESIZED message, left out for Android since 2020 when
DeviceRestore covered every size change.
- A fixed (hardware scaled) surface size is now recomputed on each in-place
surface change, since surfaceCreated no longer runs on a rotation.
- A 180 degree flip keeps the size but changes the surface transform, so
the display rotation is passed along and a change in it is a resize too.
Co-Authored-By: Claude Fable 5.1 <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>
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>
'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).
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>
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>
Centralizing command line parsing replaced the hand-rolled --gamesettings and
--touchscreentest argv checks in NativeInit with a single --start-screen=<name>
option, and made an unrecognized "--" argument a hard parse error. The two
System_RestartApp callers still passed the old flags.
So changing the graphics backend killed PPSSPP for good: the new process starts,
fails to parse --gamesettings, and returns 1 from WinMain before a window ever
exists. The error goes to stderr, which nobody sees in a GUI build, so it just
looks like the app quit instead of restarting.
Restarts that pass no arguments (the memstick screen, and the edit-then-restore
path) were unaffected, since an empty argument string makes ExitAndRestart reuse
the original command line.
Also move the TouchTestScreen push inside the touchscreentest branch - it looks
like a brace that didn't move during the refactor, and it would otherwise push a
touch test screen for every --start-screen value.
* Destroy the ImGui debugger explicitly, not at static destruction time
* Windows: destroy the debugger windows before NativeShutdown()
* Clear the disassembly cache where it goes stale, not in view destructors
(behind a no-op stub for libretro, which doesn't build DisassemblyManager.cpp)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pass the flags selected from the independent remote sharing and
debugger startup settings instead of enabling every web service.
This keeps remote disc sharing from exposing the unauthenticated
debugger and upload handlers.
GetCurrentDrawAsDebugVertices (GE debugger vertex preview) sized its index scratch
buffer at a fixed 65536 and then ran both expanding steps into it: index generation
turns strips/fans into up to 3 indices per input index, and RunSoftwareTransform can
then expand points/lines/rects into 6 more each. A 30000-vertex triangle strip wrote
~90000 entries. Size the buffer from the count instead.
The Expand{Rectangles,Lines,Points} capacity checks were also off: they compared the
expansion against indsSize but write the expanded indices at inds + vertexCount, so
the input count has to be part of the sum.
ControlMapper::Axis wrote rawAxisValue_[axis.axisId] with no bounds check, one line
below an explicit check on axis.deviceId. axisId comes straight from the device -
Android reports AXIS_GENERIC_13..16 as 44..47, against a 44-entry array - so it wrote
into the neighbouring deviceTimestamps_. NativeAxis had the same unchecked write into
HLEPlugins::PluginDataAxis, where it goes out of the object entirely.
Rewind's LockedDecompress computed its copy-from-base block size as
base.size() - result.size() in size_t and truncated to int, so it went negative once
the output grew past the base, and insert() then ran with last < first. That happens
because a state can outlive the base it was compressed against: there are 20 states
but only 2 bases, rotated every 16 saves. Track a generation per base and refuse to
decode a state whose base is gone, and bound the block size against the base itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
The two long-standing bug reports had a shared root: the service loop could
end up in a state it never left.
- Exit hang: UPNP_CMD_EXIT was queued alongside port requests and only acted
on when it reached the front. A request that couldn't complete was never
popped, so exit sat behind it forever and join() blocked indefinitely.
Exit is a flag now, checked before anything else.
- CPU spin: wait_for() with a predicate returns immediately when the predicate
already holds, so a stuck queue head meant a tight loop. sceNetInet's bind()
queues UPnP_Add regardless of the setting, so this hit whenever UPnP was off
and a game used sockets. The loop always blocks now, and requests are dropped
while UPnP is off.
- Failed discovery was retried every 5s forever, each time a full 2s SSDP round
plus an error toast. Now backs off 5s -> 300s and reports once.
Other things found while in here:
- Every failed Initialize() leaked a UPNPUrls + IGDdatas, so ~every 5 seconds
for anyone with UPnP on and no router. The manual miniwget/parserootdesc/
GetUPNPUrls block was also redundant - UPNP_GetValidIGD does all of it and
memsets over the result, leaking the URLs and costing an extra HTTP round
trip per attempt.
- UPNP_GetValidIGD's status was never checked, so we could go DONE with no
usable IGD and hand a NULL controlURL to UPNP_GetConnectionTypeInfo.
- miniupnpc's strncpy into the port-mapping-entry buffers doesn't guarantee a
terminator; an 80-char description ran std::string off the end of desc[80].
- Add() marked another app's port "taken" only after our own add succeeded, so
a failed add left their mapping deleted and never restored.
- Clear() walked the router's entire table at exit, one HTTP round trip per
index. It now deletes only what we know we mapped, and the exit cleanup has
a time budget so an unreachable router can't stall shutdown.
- The in-flight request stayed in the queue during the router call, so a
same-port request arriving concurrently could erase it and be dropped
unexecuted.
- The queue is bounded, and last-write-wins per port collapses the churn from
games that rebind in a loop.
- The mapping description is built when the request is queued rather than read
off g_paramSFO from the UPnP thread later.
The thread now only exists while the setting is on - turning it off makes it
remove its mappings and exit, turning it on starts one. That means __UPnPInit()
has to run after the config is actually loaded; g_Config.Init() only builds a
lookup table. QueueRequest() reconciles too, so a per-game config or a libretro
core option enabling UPnP works without a notify at every call site.
The settings checkbox is disabled in-game, since sceNet latches related
settings at boot and a game that already mapped its ports wouldn't cope with
them disappearing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
NativeKey builds a copy of the key with the Ctrl/Shift/Alt/Meta flags attached,
but has been queueing the original ever since a47edbf6ef moved the dispatch from
a direct g_screenManager->key(modKey) call to the event queue - so modKey has
just been dead since then, and nothing downstream ever sees a modifier.
That's every shortcut matched on one: Ctrl+Tab tab switching in ChoiceStrip,
Ctrl+F in the game list, and Ctrl+C/V/Z in text fields.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
Our Qt backend has long been left behind and doesn't even support Vulkan
currently. There would be a lot of work to make it viable, and I don't
think anyone is really interested.
ImGui on SDL will soon fulfill the need for a more classic user interface
with a menu bar on Linux, and on Mac we already have a native UI.
Core_RunLoopUntil() is only reached while a game is actually loaded and
running (via EmuScreen). Anything calling Core_RunOnCPUThread() while at
the main menu with no game loaded would hang forever waiting for a queue
that was never drained. Call Core_ProcessCPUQueue() directly from
NativeFrame(), just before screenManager->render(), so it always runs;
Core_RunLoopUntil() still also drains it for the tight-spin-while-stepping
case once a game is running.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH
Debugger windows (register list, disassembly view, memory view, breakpoint/
thread/module/stack lists, watch list) read CPU-thread-owned state directly
from the GUI thread's WM_PAINT/list-fill handlers, racing against the CPU
thread. Routing every read through Core_RunOnCPUThread would be too slow for
something invoked continuously on paint/list-refresh.
Add g_frameMutex (Core.h/Core.cpp), held by NativeFrame() only across the
span where it actually touches that state (running the CPU, processing
breakpoints, running the ImGui debugger) - not across input handling or the
present/frame-pacing waits. Debugger windows now hold the same mutex while
reading, giving synchronized reads without the round-trip cost of queuing
to the CPU thread.
CtrlRegisterList::onPaint() goes back to always reading live values (now
safe under the lock) and grays them out by color alone while the core is
running, rather than the earlier snapshot-caching approach.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH