- Fixes#22514
The 3D investigation mode renders an ID picking pass into VRAM and reads
it back on the CPU to build the hit-test table. Hook the decode function
to flush the framebuffers to VRAM first, and force lower resolution for
effects to avoid stutter.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
StopWebServer() only stopped a RUNNING server, while ShutdownWebServer()
joined in any state but STOPPED. Shutting down while the server was
still STARTING joined a thread nobody had told to stop, which then
entered its loop and never left. Stop it while STARTING too, and only
switch to RUNNING if no stop came in meanwhile.
Also make the report.ppsspp.org registration cancellable. It runs at
startup and every nine minutes, and couldn't be interrupted, so exiting
during a stalled one waited out its connect and 900s header timeouts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DualShock, DualSense and Switch Pro pads are read with a blocking
ReadFile on the input thread, and WM_DESTROY joins that thread. If the
pad stops sending reports the read never returns, so the window is gone
but the process stays. Cancel the thread's I/O until it stops, retrying
in case a cancel lands just before a read starts. This also covers the
synchronous writes the pads' shutdown does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MemoryMap_Shutdown deallocated each view, opening holes in the reserved
range, and then released the whole range. A mapping another thread got in
a hole meanwhile was unmapped along with it. On iOS that was Metal's
command buffer memory: headless crashed in vkQueueSubmit about a hundred
frame dumps into a launch. Map inaccessible pages over a released view
instead, so the range stays ours until ReleaseViewSpace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Framebuffer ages are measured in flips, and the count carried over from
the previous game. Headless replaying several frame dumps in one process
(frametests.py --ios does, 100 per launch) then rendered a dump
differently from replaying it alone: Colin McRae Rally lost its car and
trees whenever any dump came before it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fixes#22622
Escape is both the UI back key and the default pause key. The pause
screen listens to the control mapper, which UI screens pass keyboard
input to, so Escape in Settings closed Settings and the pause screen
under it at once, dropping straight back into the game. EmuScreen
already checks IsOnTop() for the same reason.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Recent gets the view mode and grid settings buttons. It has no path,
nothing to navigate, and its list comes from memory, so the path,
navigation and refresh stay out. Its old grid setting is no longer
migrated, like Games and Homebrew.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tekken 6 draws its intro movie as one full-width sprite eight times, each
scissored to a 64-pixel strip. The fill cost model picked the thrashing
rate from the sprite's full 480-pixel width, charging ~51ms per frame, so
the game's sceGeDrawSync held it to 15fps. It plays at 30fps on a PSP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
iOS only runs app bundles, so with HEADLESS=ON the iOS build makes
PPSSPPHeadless an app (org.ppsspp.headless) that starts UIKit and runs
the headless main on a thread with the arguments devicectl launched it
with. Vulkan only, and the IR interpreter since there's no JIT.
frametests.py --ios installs it, copies over the dumps the device lacks,
and compares each render with the host's Vulkan one like --adb does.
Each devicectl call costs a second or two, so the new --screenshot-dir
lets one launch render a batch of dumps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetRootVolume() returns a relative path unchanged, so the path to create
came out empty: nothing was created, and it still returned true.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fixes#20296 (by hacking the game to fix the bug with a replacement func)
A known game bug, on hardware too: in areas light enough to run at 60fps,
holding Square with the dragon armour throws no fire. Each flame looks up a
node in its model at init, and dies on its first update when it isn't there.
The node list comes from a resource that's freed when no flame holds it, and
at 60fps every flame dies before the next spawns, so it is reloaded each time
and never ready at init. At 30fps the flames overlap and keep it loaded.
Hook the update where it checks for the node, and look it up again there,
when the list has been filled.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Overrides the locked CPU speed setting without saving it, so a headless run
can reproduce a game at a different frame rate.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cheat tests left their ini files in the working directory: on Windows the
delete failed while CheatFileParser still held the file open.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The game's requested CPU frequency wasn't stored anywhere, only the effective
CoreTiming clock. So a state saved with a locked clock kept that speed when
loaded with the lock off, and turning the lock off in-game left the CPU at
the locked speed until the game set its clock again (most only do at boot).
Changing the lock in-game didn't take effect either, until a reboot or a
state load.
Now RealcpuFreq is tracked and saved next to the PLL and bus, the setters
record the game's request even under a lock, and one function applies
either the lock or the game's clocks. It runs at init, on state load, and at
vblank whenever the setting has changed. Older states guess the game's clock
from the saved effective one, capped at the game's PLL.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#13200
The game stops any vehicle moving less than 0.005 a frame with its gas
pedal up, so parked vehicles don't drift. Planes, the Bovver '64 hovercraft
among them, never set the pedal, so at a high enough frame rate they stick
to the ground. That happens on a PSP too, in light scenes.
Hook the plane control code where it clears the pedal and set it from the
throttle instead, as Parik27 worked out in the issue. The function is the
same in the US and EU versions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A draw with vertices beyond the near or far plane went through the software
transform unless the GPU had both clip and cull distances. When the viewport's
near and far planes both fall on min/max Z, and those are inside 0..65535,
the min/max clip planes clip exactly where the GE does, and also take away
what's beyond the far plane: no need for cull distances or the CPU.
On Mac Vulkan, Metal Gear Solid: Peace Walker's six dumps go from 15.3k px
off the PSP to 10.4k, and Call of Duty: Roads to Victory from 10.0k to 8.9k.
viewportNearPlaneMatchesOutput was never set back to false.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hardware transform does the matrices on the GPU, software transform on the CPU,
and their depths differ by a few ulps on 40-90% of pixels. That's enough to
fail an equal-depth GEQUAL test where a frame mixes the two, which happens to
draws that cross the near plane on GPUs without cull distances: DBZ Tenkaichi
Tag Team's outlines, Naruto Ultimate Ninja Impact's grass over the ground.
Both paths end in the same vertex shader code, so snapping there makes them
agree: screen Z to 1/8 (whole units break a decal in Star Wars Lethal
Alliance) and W to 16 significant bits. W is left alone without integer ops.
On the framedump set with Mac Vulkan: DBZ 3198 -> 77 px off the PSP, Naruto
3243 -> 906, Monster Hunter 792 -> 21, FF Type-0 559 -> 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the input starts with something it can't decode, each of the six
string converters returns 0 on the PSP and writes only the terminator
(ccc/convertstring, all six "Invalid data" cases). We decoded on
regardless and wrote replacement or garbage characters for every unit.
Stop at the first invalid character instead: for UTF-8, a byte that can't
start a sequence (UTF8::invalid()); for UTF-16 and Shift-JIS, INVALID from
the decoder. Valid characters the tables can't map still get the error
character, as before.
ccc/convertstring passes now and moves to tests_good.
UTF16_Type::next() assumed every surrogate came in a valid pair, so a lone
high surrogate swallowed the unit after it, and one right before the
terminator stepped over it and kept reading. It now returns INVALID and
consumes only the bad unit, which sceCccDecodeUTF16() already checks for.
ShiftJIS::next() returned 0xA0 and 0xFD-0xFF as single-byte characters.
Like 0x80, which it already rejected, they encode nothing (the PSP's JIS
to UCS table in pspautotests/tests/ccc/jis2ucs.bin has no entry for any of
them), so they're INVALID too.
Only sceCcc uses these decoders.
Android 17 ignores fixed orientations on sw600dp+ displays, but exempts
games, and we declare appCategory="game". Tested on an Android 17 tablet
emulator: with the category, Portrait is honored (letterboxed); without it,
the system logs "Ignoring requested fixed orientation". So stop hiding the
rotation setting there, and drop the smallestScreenWidthDp plumbing that
only existed for that check.
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 a fresh install with scoped storage the Games tab has no folder, and the
upload server had nowhere to put files, so every request got a 404.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #21585
Android 17 apps need ACCESS_LOCAL_NETWORK to serve other devices on the LAN.
The upload screen now asks for it when it opens, starts the web server once
it's granted, and shows a warning and a retry button if it's denied.
The permission only exists from API 37, so check for that rather than 35.
On Android 15 and 16 it reads as denied, which left Remote ISO's connect tab
stuck behind a permission request that could never succeed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With depth clip off, the software transform dropped a triangle when any vertex
was beyond the near or far plane, which is the viewport's own Z range. The GE
only drops it when a vertex's screen Z is outside 0..65535 or it's behind the
camera, as the software renderer does. Yu-Gi-Oh GX Tag Force's backdrop has a
10000..50000 viewport and vertices past it: with hardware transform off, parts
of it were missing (4192 px off the PSP, now 64 like with hardware transform).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A non-blocking sceAudioInput keeps filling its buffer from the host capture
after it returns, but WaitInputEnd and PollInputEnd reported it done at once,
so a game that reads the buffer right after the wait (FTB2's voice chat) got
nothing. WaitInputEnd now blocks like the blocking input path, and
PollInputEnd returns 1 until the buffer is full. Both pad with silence after
the same 100 ms of host jitter, so a stalled device cannot hang the game.
The FixupBranch path for BEQZ/BNEZ used BranchInRange (16-bit offset, the B/BEQ
form) rather than BranchZeroInRange (21-bit), which is what the direct
BEQZ(rj, dst) overloads use. So a fixup branch further than 128KB but within the
2MB the instruction can actually encode asserted for no reason. Encoding itself
was already correct.
The skip-first marker suppressed everything at the address you resume or
step from. That's needed for the pause, or you could never get off a
breakpoint, but it also dropped the log and hit count. So a log breakpoint
you'd stepped onto, or were parked on for some other reason, never logged
when execution moved on.
Now the skip only drops the pause, unless the breakpoint at that address is
the one that stopped us, which has already logged and counted this
execution. The check moves into the Exec* functions instead of being
repeated in the interpreter, IR interpreter and three JITs, where the
register breakpoint path only applied it to the pause and so logged twice
when stepping off a log+pause one. The marker gets an explicit valid flag,
since address 0 meant "none", and Core_Break() clears it.
Based on the earlier cleanup-skip-first branch, reduced to one marker plus
a flag. Checked over the WebSocket debugger on the interpreter, IR, x86 JIT
and JIT-IR: log breakpoints on a parked and a stepped-onto address each log
once, and stepping off a log+pause one doesn't report it again.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Buffer::PeekAll resized the destination and then appended, so it returned twice the
data with zeros in front. No callers today.
The assert dialog's caption was a function-local static, so it froze at whichever
kind of assert fired first in the session - a later Critical Assert would still be
titled 'Debug Assert'. Made it a plain const char *.
containsNoCase passed a possibly-negative char to std::toupper, which is UB for
anything but EOF; non-ASCII bytes get here from ELF symbol names.
ApplyDeadzoneXY's circular path lacked the deadzone >= 0.99f guard its own sibling
ApplyDeadzoneAxis has one function above, so it divided by ~0.
ResizeFramebufFBO now asks the framebuffer whether it has a depth/stencil
buffer instead of guessing from the z address and stride.
Vulkan CopyFramebuffer gets the debug asserts and bail-out BlitFramebuffer
already had, and GL copies and blits get the same, so a copy out of a
framebuffer without depth/stencil asserts where it's queued instead of in
the queue runner.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>