Commit Graph
193 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 31fd8e8a1c POPS: Multi-disc on every firmware from 5.01, by leaving the descrambling to the emulator
With the converted header's key of zeros, the emulator's descrambling of its disc switching strings only
reverses them, so ours go in backwards and its code is left alone. The tables are found by the code that
indexes them, which is the same from 3.95 on with only the registers differing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 19:36:02 +02:00
Henrik RydgårdandClaude Opus 5.5 572260101c POPS: Multi-disc on firmware 6.20 and later, the reset dialog's disc number, and a POPS string category
Every MAC comparison the manager makes passes for a converted EBOOT, rather
than the one in 6.60's disc header check: the export it's in has a
different NID in each firmware. The disc switching text is found in the
emulators of 6.20 to 6.61 (earlier ones have the same descrambling,
compiled differently), so a playlist on an earlier firmware fails with a
message saying so.

The reset dialog's disc number is the disc picker label's, moved by a
table of its own per language, which is now laid out for our text too.

The PS1 strings get a category of their own, POPS, with the experimental
warning moved there. langtool gets add-section, to make the section for a
new category, and inserts into an empty section rather than dropping the
line (which move-key had already removed from the old one).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 18:05:06 +02:00
Henrik RydgårdandClaude Opus 5.5 9312bf06d1 POPS: Boot PS1 discs from CHD and Alcohol .mds/.mdf images
A CD CHD is read through its track metadata: each track's frames are
padded to a multiple of four, a pregap is stored only when its type
starts with V, and audio is big-endian. A .chd that's a CD is a PS1 disc,
PSP discs being DVDs. The FileLoader-backed core_file the PSP CHD block
device used is now OpenCHDThroughLoader(), for both.

An .mds gives each track's start, pregap and length, and where in the
.mdf it's stored, with 96 bytes of subchannel data after each sector.
Anything between tracks isn't stored. A bare .mdf goes through its .mds,
which the game browser lists, and archives install with both.

MODE2/2336 cue sheets, which only had their sector headers made before,
are now tested too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 15:01:22 +02:00
Henrik RydgårdandClaude Opus 5.5 23dffa9db3 POPS: Boot PS1 disc images as converted EBOOTs made on the fly
A cue sheet, a CloneCD .ccd, or a bare .bin, .img or .iso of a PS1 disc
now boots on the firmware's POPS, as the converted EBOOT.PBP popstation
would make from it. PS1EbootFileLoader builds the PBP header, PARAM.SFO
(the disc ID from SYSTEM.CNF) and the PSISOIMG header, TOC and block
index, and serves the disc image from the image's own files. Each block
of 16 sectors is a stored DEFLATE block: the manager won't read an image
of uncompressed blocks in its own format, but the converted PBP's inflate
takes stored ones, and with every block the same size, reads map straight
onto the files. Nothing is compressed or written anywhere.

Cue sheets can have any number of files, AUDIO tracks, INDEX 00 and
PREGAP, and a missing FILE falls back to the cue sheet's own name. A
2048-byte .iso gets Mode 2 Form 1 sectors made around its user data.

The EBOOT is mounted as the only file in host0:, through BlobFileSystem,
which now saves its open handles in savestates. The game browser lists
.cue, .ccd, .bin and .img, leaving out the .bin and .img files a cue
sheet or .ccd next to them describes, and a bare image opened directly
goes through the cue sheet that names it. GameInfoCache reads the title
and ID of these, and of real PS1 EBOOTs, from their PARAM.SFO.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 15:01:22 +02:00
Henrik RydgårdandClaude Opus 5.5 b547e01c9d VSH: Boot games the XMB starts like any other game launch
A disc game or memory stick EBOOT started from the XMB used to be swapped in under the XMB's session,
so it ran without its PARAM.SFO, compat flags, game config, window title, savestates and so on.
Much of that is decided once, at boot (the memory map, the GPU backend, the shader cache), so
sceKernelLoadExec now asks the frontend for a reboot into the disc image or EBOOT (PSP_RequestBoot)
and stops. The app and headless boot it like a game from the game list.

The game keeps the XMB's launch args (CoreParameter::launchedFromVSH), so exiting boots the XMB
again, with the disc that's in the drive and those args, and the XMB comes back where it was without
auto-starting the disc. Homebrew keeps the disc that was in the drive. A disc whose executables are
all zeroes (the EUR Video Demo Disc's "game") still loads in place and fails back to the XMB.

In headless, the run carries on through the reboot, starting stopped under the debugger like the
first boot, since the reboot loses a pending cpu.runUntilTime.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 13:36:28 +02:00
Henrik RydgårdandClaude Opus 5.5 a1ade54aac UMD Video: Boot the XMB for a video disc however it's started
The game list already did, but booting a video disc any other way (the command line, a file
association, dropping it on a running game, headless) tried it as a game and failed. The loader now
swaps in vshmain.prx with the disc mounted and auto-start on whenever it identifies a UMD Video
disc and the installed firmware can play it. GetUmdVideoVSHPath() moves from the UI to
Core/Util/PSARUnpack.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 12:08:10 +02:00
Henrik RydgårdandClaude Opus 5.5 d331bd6c8c UI: Play UMD Video discs in the XMB, straight from the game list
With firmware 2.71 or later installed, picking a UMD Video disc boots the XMB with the disc in the
drive and UMD auto-start forced on for that boot, so the disc's menu comes up. Without one, the
info screen saying UMD Video isn't supported shows as before. The info screen's Play does the same.

A disc with both a game and video asks which to start. For the video, the XMB comes up with
auto-start forced off, since auto-start runs the game.

Older firmware can't play these: 1.50, 2.00 and 2.60 crash in video_plugin or paf with a video disc
in the drive, and 2.50 refuses it. 2.71 through 6.61 reach Bourne Supremacy's menu.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:46:59 +02:00
Henrik RydgårdandClaude Opus 5.5 5799bdee59 Outputs: Rebuild the post-shader chain when the game moves to another output
Post-shader framebuffers (for shaders at output resolution, and those
that keep the previous frame) are sized for the output they were built
for. Moving the game to a TV or back, or the TV changing size, left them
sized for the other one. SetOutputOverride now asks for a rebuild when
the output changes.

Also corrects the design doc: "auto" render resolution follows the
device's screen, not the TV.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:00:13 +02:00
Henrik RydgårdandClaude Opus 5.5 04ae3fea5b External display setting: Show the game, or mirror the screen
Graphics > "External display" on iOS and Android. "Show the game" (the
default) is the TV mode, "Mirror the screen" leaves the TV to the system.
A change applies right away. It's disabled on backends that can't draw to
a second swapchain.

On Android 12 and up, a second screen that's part of the device (like a
dual-screen handheld's) isn't used, so the game doesn't move there by
itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:31:07 +02:00
Henrik RydgårdandClaude Opus 5.5 5fbd134a7d Android: Show the game on a second display, with the controls on the phone
A presentation display (HDMI or USB-C video out, some wireless displays,
or the simulated ones in Developer options) gets a Presentation with a
SurfaceView, whose surface becomes a Game output, when the backend is
Vulkan. Otherwise Android keeps mirroring.

The surface mustn't be touched after surfaceDestroyed returns, so the
output is removed with the new Native_RemoveOutputAndWait(), which
returns once the render thread has let go of it (or right away if the
graphics are down). The UI drawn on an output now leaves out the main
swapchain's pre-rotation, as the game already did.

"In menus on %1" gets the device name from Settings.Global.DEVICE_NAME.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:31:07 +02:00
Henrik Rydgård f6fa84329e Merge pull request #22736 from hrydgard/avc-spliced-stream-timing
AvcDecoder: Keep the output clock going when a new stream is spliced on
2026-10-22 22:14:54 +02:00
Henrik RydgårdandClaude Opus 5.5 56c8d067af pspautotests: avchold covers a stream spliced on without a flush
Updates the submodule for video/mp4/avchold's ref3b1hrdsplice, recorded
on a PSP, which fails without the AvcDecoder fix on this branch: the pictures after
the splice never come out.

avchold and decodeend only test anything with Sony's mpeg and mp4
modules running, which headless never does for homebrew unless asked,
and which CI doesn't have. docs/pspautotests.md now says how to run
them (--disable-hle=0x110 and a memory stick with a firmware dump).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:47:45 +02:00
Henrik RydgårdandClaude Opus 5.5 c542d186bd docs: Multi-swapchain status: Linux and Windows, no OpenGL
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:55:52 +02:00
Henrik RydgårdandClaude Opus 5.5 8e8cf5dbb4 Vulkan: Multiple swapchains
Swapchains other than the main one are VKRSwapchains, owned by the
render manager, each with its own surface, image views, framebuffers
and depth buffer, in the main swapchain's format so the backbuffer render
pass and pipelines work for all of them. A render step with no
framebuffer can name one.

They don't pace the frame: acquiring doesn't wait, and the pass is
skipped if there's no image. The frame's last submit waits on and
signals the semaphores of every swapchain acquired that frame, and each
is presented after the main one. When one goes out of date, it's
recreated at the start of the next frame.

The main swapchain's path is unchanged; frametests on Vulkan match.
VulkanContext::CreateSurface is the old per-platform surface code, now
also used for the extra ones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:11:36 +02:00
Henrik RydgårdandClaude Opus 5.5 d39df0b45c iOS: Render the TV output at the display's full resolution
The 1080p cap was meant to save Wi-Fi bandwidth, but an Apple TV 4K
only offers 3840x2160 over AirPlay, so the stream was 4K anyway, just
upscaled.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 7bd4b13baa iOS: Show the game on an AirPlay TV, with the controls on the phone
An external display (like an Apple TV over AirPlay) becomes a Game
output, when the backend is Metal. The game is shown there full screen,
and the phone shows the touch controls with "Playing on TV" in place of
the game. The menus stay on the phone. With other backends, iOS keeps
mirroring.

Up to iOS 26 the external display scene comes from the Info.plist role.
iOS 27 ignores that, and only offers the scene to apps that register a
scene accessory for it, so we do that too. The registration is withdrawn
when the view controller shuts down, so it follows a backend change, and
a restart now goes to the app's scene rather than the first one.

An Apple TV 4K only offers 3840x2160 over AirPlay, so the stream stays
4K, but we render at 1920x1080 and let the layer scale it up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 56c76ace35 thin3d: Swapchain objects, and multiple swapchains on Metal
The backbuffer is now a Swapchain, from GetPrimarySwapchain(), and
callers bind, measure and read it back through that instead of nullptr.
nullptr still means it too. Backends with one swapchain map the primary
to their old backbuffer path.

On Metal, CreateSwapchain() adds more, each with its own layer, depth
buffer and drawable. Passes merge per swapchain, and Present() presents
every drawable acquired that frame. The backbuffer is just the first
of them. Nothing creates a second one yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 26e302aa86 docs: Screens draw to secondary outputs through renderOutput()
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 a2d18d21cd iOS: Make Metal the default graphics backend
Not on tvOS, where the Metal backend hasn't been run yet. Existing
installs keep the backend saved in their ini.

The multi-swapchain plan now does Metal before Vulkan to match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 7da7e71aea docs: Design for multiple swapchains and an iOS TV mode
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 a1b4a93cac HLE: Resolve the kernel sceKernelAllocateFpl
It was left unresolved with the kernel mutex calls, as they all let the
flash drivers reach the NAND controller during a VSH boot. It's the
mutexes that do that: with only the FPL resolved, sceIdStorage_Service
allocates from its pool and every firmware's VSH in 1.50-6.61 boots to
the same XMB picture as without it. Resolving the mutexes still hangs on
0xbd101300.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 11:24:14 +02:00
Henrik RydgårdandClaude Opus 5.5 6453d7f0b3 docs: What works around the XMB
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 19:00:01 +02:00
Henrik RydgårdandClaude Opus 5.5 0877c3d19a SaveState: Refuse savestates and rewind in a session booted into the XMB
Inserting and ejecting a disc there adds and removes the umd0:/umd1:/
umd:/disc0: mounts, and MetaFileSystem's state pairs sections with mounts
by position without recording which disc was in. A state could then fail
to load, or load one disc's open files onto another. The pause screen
already hid its save buttons for vshmain.prx; the save hotkeys, rewind,
the ImDebugger and the WebSocket debugger all went around that. The check
is now in SaveState itself, and rewind snapshots aren't taken. Verify
still runs, since it reads back with the mounts as they are.

A proper fix records the mounted image in sceUmd's state and remounts it
on load.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 18:25:01 +02:00
Henrik RydgårdandClaude Opus 5.5 7f9488c599 docs: UMD Video and the pause screen's disc buttons in the VSH notes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 17:32:44 +02:00
Henrik RydgårdandClaude Opus 5.5 eee153b220 VSH: An empty UMD drive, and inserting a disc over the WebSocket debugger
The XMB, booted without a game, now starts with an empty drive: when
vsh_module loads with nothing at disc0:, the drive is marked empty, and
activating it tells the shell's drive callback (SceVshMediaDetectUMD)
there's no disc. Activation used to report a readable disc regardless,
so the shell asked for its type and showed "The disc could not be read."
The disc type devctl (0x01F20001) answers 0 without a disc, as JPCSP
does.

game.umd.insert (path) mounts a disc image into the empty drive and
calls the drive callback as inserting one does; game.umd.eject takes it
out. UmdMount() is UmdReplace() without its need for a disc already in.

Inserted into the 6.61 XMB, Daxter shows in the Game column with its
icon, background and animated preview, and launching it boots the game
to its title menu.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 17:32:43 +02:00
Henrik RydgårdandClaude Opus 5.5 6e3994aac0 CmdLine: Drop the legacy cpu core flags, use --cpu=
-i, -j, -r, -J, --ir and --jit-ir are gone. -r was also --root's short form,
and --root won, so -r took the next argument as the root path and nothing
booted (test.py -r failed every test).

-i, -j and -J now fail with an error pointing at --cpu= instead of quietly
running on the default core. Docs and test.py updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 16:05:55 +02:00
Francisco José García García a42bcba455 Docs: How PS1 EBOOTs are booted
How the firmware's POPS manager and emulator are booted, what stands in for
the Media Engine, what's patched in them, the rendering changes they need,
and what's not done yet.
2026-10-21 13:15:30 +02:00
Francisco José García García 4ed0bfd08a Headless: Scripted button presses and timed screenshots
--press=SECONDS:MASK[:DURATION],... holds sceCtrl buttons at emulated
times, and --screenshot-at=SECONDS:FILE,... saves screenshots along the way,
to drive a game past its menus in a run. With presses, the run loop steps
1/60 of a second at a time so short ones land.
2026-10-21 08:58:10 +02:00
Henrik RydgårdandClaude Opus 5.5 7dd1475ed7 HLE: Pass the MIPSState to syscalls and their implementations
HLEFunc and ReplaceFunc now take the MIPSState they run on. The dispatch
paths (both interpreters and every JIT backend) pass in the state they
belong to, and CallSyscall*, hleFinishSyscall and the HLE mips call
machinery use it instead of currentMIPS.

The wrappers in FunctionWrappers.h take the function as `auto` and call it
through HLECall, which also accepts an implementation with an extra leading
MIPSState * parameter and passes the state to it. Any other signature still
fails to compile, as before. PARAM/RETURN now refer to a local `mips`.

The HLE functions (and replacement functions) that used currentMIPS now take
the state that way. Callers outside a syscall's own frame - CoreTiming
events, the adhoc matching thread, dialogs, PPGe, the debugger UIs - pass
currentMIPS or &mipsr4k explicitly. The kernel's thread internals still use
currentMIPS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:45:14 +02:00
Henrik RydgårdandClaude Opus 5.5 52afb9a677 Headless: Count --timeout-emulated from when --state has loaded
The game boots until the queued state is read, decompressed and applied, which takes a varying amount
of host time, and those boot frames counted towards the timeout. So the same state ended on different
frames depending on the host, for example while texture replacements competed for the IO threads.
After the load the emulation is identical: syscall traces of Fate/Extra CCC with no texture pack, and
with one loading slowly or instantly, are byte for byte the same.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 26d9e54059 SaveState: Don't fold a save into one from before a load
A save to a path with a save still being written counted as a repeated
key press, even with a load applied in between, so save, load, save
didn't save the loaded state. Now a repeat only joins the earlier save if
no load has been applied or queued since its snapshot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:18:47 +02:00
Henrik RydgårdandClaude Opus 5.5 5bbeb6254b Debugger: Savestate slots in game.state.save/load, and async savestate docs
game.state.save and game.state.load take a slot of the running game
instead of a path, going through the same code as the save and load keys
(undo rotation, load undo, repeats), so that can be tested headless.

docs/async-savestates.md now describes what was built, with the results.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 18:43:27 +02:00
Henrik RydgårdandClaude Opus 5.5 d8df483fc4 docs: Design for async savestates
Move compression and file I/O off the emu thread, leaving it only the
snapshot and the apply, without wasting memory along the way. Not
implemented yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 18:17:52 +02:00
Henrik RydgårdandClaude Opus 5.5 ca72577702 Rewind: Keep the restored state, and measure "seconds ago" in game time
A rewind used up the state it restored, so rewinding, playing on a bit and
rewinding again went further back than the state you just came from. Now
the state stays, and only a rewind within 0.8 seconds of the last one
steps back past it.

"Seconds ago" was wall-clock time since the snapshot, which counted
pauses, fast-forward and time spent in states rewound to earlier. It's now
emulated time from where the rewinding started.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:58:36 +02:00
Henrik RydgårdandClaude Opus 5.5 79c2b9b0fd Debugger: Add game.state.rewind and game.state.verify
Same as game.state.load/save, and the wait response now carries the
metadata string (for a rewind, how long ago the snapshot was taken).

Also fixes Broadcast() erasing a waiting entry while holding its mutex,
which could free the mutex while locked (the debug CRT aborts on it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:29:01 +02:00
Henrik RydgårdandClaude Opus 5.5 ae9a0c47e7 Metal: MSAA
Like the Vulkan backend: a multisampled framebuffer has multisampled
color and depth textures that are drawn to and kept between passes, and
each pass resolves them into the ordinary ones (depth and stencil to
sample 0), which textures, copies and readbacks use.
BindFramebufferSamplesAsTexture binds the samples, for the shared code's
sample 0 copies. Copies between multisampled framebuffers copy the
samples too, both depth and stencil, since Metal can't copy one plane of
a multisampled texture.

Also skips empty draws, which Metal doesn't allow.

With 4x MSAA, 1012 of the 1061 frame dumps are identical to Vulkan's.
Most of the rest differ at edges because programmable blending blends
each sample with its own color. In Resistance: Retribution, the sample 0
copy for a palette lookup matches the render without MSAA here, and
Vulkan's doesn't.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 12:48:12 +02:00
Henrik RydgårdandClaude Opus 5.5 b0552deb35 docs: Describe the Metal backend as built
Replaces the feasibility study written before it existed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 12:28:15 +02:00
Henrik RydgårdandClaude Opus 5.5 4bfeb59fcd Debugger: Add wait to game.state.load/save
With wait: true, respond once the savestate operation has finished (with its
message), or with an error if it failed or was dropped without running.
Otherwise a client only learns the operation was queued.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 10:10:25 +02:00
Henrik RydgårdandClaude Opus 5.5 b000d557e2 Debugger: Never drop stepping and game events when the queue is full
A log-only breakpoint in a hot loop fills the per-connection queue, and the
cap then also discarded the cpu.stepping that followed, so the client never
heard that the CPU stopped. Only cap breakpoint hits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 10:05:43 +02:00
Henrik RydgårdandClaude Opus 5.5 ab94767522 VFPU: Drop the inaccurate dot product paths
- Fixes #12082

The bit-exact VFPU dot product is the default everywhere and fast enough,
so the plain multiply-add paths in the interpreter and the IR frontend go,
along with the AccurateVdot setting, --accurate-vdot, the ForceAccurateVdot
compat flag and the Vec4Dot IR op, which nothing emits any more.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 12:11:46 +02:00
Henrik RydgårdandClaude Opus 5.5 16a03f16e8 Adhoc: Restore the inited flags from savestates
Loading a savestate kept the current session's netAdhocInited,
netAdhocctlInited and matching-inited flags rather than the saved ones.
Loaded into a fresh boot, a state saved with adhocctl initialized (but not
connected) then failed every adhocctl call with NOT_INITIALIZED. MGS Peace
Walker shuts ad hoc down before a cutscene and retried
sceNetAdhocctlDisconnect forever, leaving the screen black.

Take the flags from the state, and bring the host side in line: start the
friendFinder and log in again when the state has adhocctl up and we don't,
stop it in the opposite case. Also save the product code, which that login
sends.

Also add a game.state.save debugger command, used to reproduce this.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 09:15:51 +02:00
Henrik RydgårdandClaude Opus 5.5 5e0cafaa52 docs: Say that the cpu-equivalence fixes for the ARM64 and x86 JITs are history
Those JITs were removed, and every JIT is IR-based now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 14:49:24 +02:00
Henrik RydgårdandClaude Opus 5.5 57cc1a6cb7 Headless: Run on iOS as an app, and frametests.py --ios
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>
2026-10-17 11:11:31 +02:00
Henrik RydgårdandClaude Opus 5.5 01ed2c3cd5 Docs: Write down where shader compile time goes on MoltenVK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 11:23:35 +02:00
Henrik RydgårdandClaude Opus 5.5 1d7487e5a7 GPU: Add a null GPU, and headless --graphics=null to use it
GPU_Null derives straight from GPUCommon and has no thin3d context, framebuffer
manager, texture cache or shaders. It runs display lists with the flow control,
bounding boxes, matrix loads (the hardware backends'), immediate vertices and
block transfers of the others, moves the vertex and index pointers past each
draw and curve, and charges the per-vertex and fill cost the software renderer
does. A DrawEngineCommon that queues nothing provides the vertex decoders.

It's for measuring the CPU emulation. From savestates, God of War, Tekken 6,
Wipeout Pure and LocoRoco flip the same frames in 10 emulated seconds as with
Vulkan and software, in 44-77% fewer instructions than Vulkan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:12:38 +02:00
Henrik Rydgård d36af22430 Merge pull request #22589 from hrydgard/cpu-timing-determinism
CPU: Make the interpreter and IR cores agree on timing
2026-10-15 22:27:54 +02:00
Henrik RydgårdandClaude Opus 5.5 5631c3c9b6 Interpreter: Continue past branches where the IR cores do
The IR frontend compiles on past a conditional branch that isn't taken,
so the IR cores don't check downcount there, while the interpreter
ended its block at every branch. Events then landed on different
instructions, and a game's trace left the interpreter's within a few
hundred syscalls.

The interpreter now applies the same rule at run time: not likely, the
operands not decided by their registers, no branch in the delay slot,
no prefix pending after the delay slot, and the same instruction and
branch limits, counted the same way. It also goes on past a beql/bnel
over a break, which the IR frontend drops. The limits move to IRInst.h
so both use them.

With it, the interpreter, IR interpreter and ARM64 JIT produce the same
trace of syscalls and thread switches over 5 emulated seconds of God of
War, Tekken 6 and Daxter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 17:43:31 +02:00
Henrik RydgårdandClaude Opus 5.5 6a2fa313e6 CPU: Make the interpreter and IR cores agree on timing
The cores charged cycles differently and checked downcount at different
places, so interrupts and thread switches landed on different
instructions, and a game could take a different path on each core.

- IR: j, jal, bc1t and bc1f now charge their delay slot.
- IR: a syscall sees every earlier instruction charged, including a
  branch it's the delay slot of, and is charged once it returns. It used
  to charge itself first, or, in a delay slot, never.
- Interpreter: a skipped likely delay slot costs what the instruction
  would, as in the IR cores, instead of 1.
- Interpreter: both run loops go on to the end of what the IR cores run
  as a block before checking downcount, and charge each instruction as
  it runs.

The CPUEquivalence fuzzer now compares the cycles up to its terminating
syscall. With continueBranches off, the three cores now produce the same
trace of syscalls and thread switches, ticks included, over several
emulated seconds of commercial games. With it on, the IR cores leave the
interpreter within a few hundred syscalls, as it moves block ends.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 17:43:21 +02:00
Henrik Rydgård 8c379781aa Merge pull request #22585 from hrydgard/dnas-install
Install Killzone: Liberation's DLC download without a PSP
2026-10-15 16:33:34 +02:00
Henrik RydgårdandClaude Opus 5.5 09a9be3484 Only look for a DRM BB install package when booting a game that had one
Saves every other game a memory stick access at boot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 15:45:45 +02:00