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>
Now that sideload builds share the App Store install settings
(SKIP_INSTALL NO), archiving moves the app into the xcarchive and
leaves nothing in Release-iphoneos.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the GL view controller gone, PPSSPPViewControllerMetal was the only
subclass of PPSSPPBaseViewController. They're now one class,
PPSSPPViewController, in ios/ViewController.mm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also drops what they guarded: the pre-14 UIImagePickerController and
document picker fallbacks, the psychic paper entitlement workaround
(fixed in 13.5), and the CS_EXECSEG_ALLOW_UNSIGNED JIT probe, which
returned false on everything from 14.4 on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sideload build ran ios/macbundle.sh after building, which looked for
git-version.cpp in the source root instead of the build dir, so sideload
builds shipped with an empty version in Info.plist. Both now use
iosbundle.sh, and share the Xcode target settings and scheme generation
except for bundle ID and signing.
RESOURCE was listed three times in one set_target_properties, and only
the last one counted, so the launch storyboard never got compiled into
either bundle. Settings.bundle only made it into sideload builds, and is
deleted: its one toggle, for external displays, hasn't been read since
the external display code was removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sideload builds used to dlopen a bundled libMoltenVK.dylib, which was
only worth having while they could still fall back to OpenGL. Now both
variants link the static MoltenVK from ios/MoltenVK and call Vulkan
directly. PPSSPP_VK_STATIC replaces the IOS_APP_STORE checks in the
Vulkan loader (the iOS libretro core still loads at runtime).
The static MoltenVK needs iOS 15.0, so that's now the deployment target
for all iOS builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app now always uses the Metal view controller with Vulkan (MoltenVK),
for both the App Store and sideload builds. The GL view controller is gone,
and Common/GPU/OpenGL (except GLFeatures, used by the shader generators) and
GPU/GLES are no longer compiled for it. PPSSPP_API_ANY_GL is now off for
all of iOS except the libretro core, which still uses GLES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spinner copies the displayed framebuffer under it into the transparent
texels of its frames, which came out as stale VRAM around the disc. Hook it
after it gets the framebuffer and read back the rows it covers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A memcpy of part of one row into a framebuffer was drawn at the buffer's full
width, pulling in whatever followed the source. Everybody's Golf copies its
loading screen spinner back to the screen 64 pixels at a time, so its sprite's
next rows were drawn alongside, as a row of discs across the bottom.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first game builds its portrait like Golf 2 and Tennis, but its copy
function keeps y and h in different registers and loads the framebuffer pointer
at a different offset. Move the readbacks into a helper and add a hook for this
layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tennis builds its portraits like Everybody's Golf 2: it memcpys a 64x64 rect of
the framebuffer and sets each texel's alpha bit from depth read straight from
VRAM, so the circles in the corners came out empty. Its copy function is
scheduled differently and hashes differently, so add it as a second entry for
the same hook, which is renamed to fit both.
Fixes#17826.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
StreamBufferToBase64 sent width * height bytes, so any buffer with more than one
byte per pixel came back truncated: half a D16 depth buffer, a quarter of a
D32F or 8888 one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After more than three partial readbacks of one buffer in a frame,
ReadFramebufferToMemory read the whole buffer instead for the rest of the
session, writing rows the game never asked for.
Everybody's Golf 2 reads back four portraits' color and depth per frame, which
turned it on. The whole 480x273 depth buffer then got written to VRAM, and its
last row is where the game keeps its texture heap. A texture header was
overwritten: the portrait showed the scene behind it, and the next time the
portraits were built the game crashed writing through a null pointer.
It was added for speed in Grand Knights History (c42d04829f), which copies many
slivers of a framebuffer, so that game may now do more readbacks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Queues loading a savestate file, for scripting repros that load a state partway
through a run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The game builds each 64x64 portrait by memcpying a rect of the framebuffer one
partial row at a time, then setting each texel's alpha bit from the depth
buffer, which it reads directly from VRAM. Neither got read back: the row
copies don't line up with a framebuffer, and depth is never downloaded.
Hook the copy function to read back both, whole rows at a time. That needed two
fixes to requested depth copies: they were dropped when the depth buffer lies
above all color buffers, and only an exact full-buffer size matched.
The portrait texture now matches the software renderer, though it still
doesn't show up on screen with Vulkan.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With block entries out of memory, the only fake opcode left is the one the CPU cores fetch in
place of a hooked instruction. Call it MIPS_CALL_REPLACEMENT_OP, and its handlers
Int_CallReplacement and Dis_CallReplacement, and remove the sub-op defines and the unused
MIPS_IS_REPLACEMENT.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Memory no longer holds the hooks, so the disassembly fetches the op the CPU runs, which is the
CallRepl pseudo-op at a hooked address. That lookup can come from the Win32 debugger's UI thread,
so the CPU thread now changes the hook map under a lock (and reads it without, being the only
writer).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The JITs used to find a block by overwriting its first instruction with an "emuhack" holding
the offset into the code space, and replacement functions installed their own emuhack the same
way. Anything that read code (savestates, the debugger, memory dumps, cheats, the function
hasher, games reading their own code) had to translate them back.
Blocks now live in a block shadow: one u32 per guest instruction, indexed by the PC masked with
BLOCK_SHADOW_MASK (1GB on 64-bit, folding the kernel and uncached mirrors; 256MB on 32-bit, iOS
and Switch, where address space is short), with the low bits cleared so a misaligned PC can't
read across two entries. Only the ranges under memory views are committed, so a bad PC still
faults in the dispatcher, and the shadow is reserved before the memory map so it can't land in
a gap of the 4GB window. Entries hold full 32-bit offsets, lifting the 16MB limit. The
dispatchers on every backend, and the IR interpreter's loop, look blocks up there.
A hooked instruction without a compiled block gets the JIT's hook value in its entry: the offset
of the dispatcher's compile path (1 for the interpreters, the IR arena keeping that offset
empty), so dispatching there compiles a block, and the frontends substitute the hook. Compiling
replaces the entry with the block's, and destroying the block puts the hook value back. The hook
value is rewritten when the JIT changes or regenerates its dispatcher.
Hooks live in a map keyed by the masked address, and are dropped when the game overwrites the
hooked instruction, or when an icache invalidation finds the function's hash (immediates masked,
as when matching it) changed. Those checks run when queued invalidations are processed, since
cheats and the debugger queue them before writing. Installing and removing hooks invalidates
deferred, as it happens in HLE calls that can run inside a block. The cache instruction goes
through MIPSState, so it drops changed hooks too.
With clean memory, the save/clear/restore around savestates, memory reads and dumps goes away,
as do the source-range invalidations in the memcpy replacements, sceDmac and the cheats.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Helper thread stubs, fake syscall stubs and the module start trampoline go into memory that may
have held code (freed kernel memory, a reused thread stack), and the debugger memory views can
edit code. Invalidate what they write, which so far only happened by accident when the write hit
the first instruction of a JIT block.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
78c75e1a0b dropped IRNativeJit::InvalidateCacheAt as a duplicate, and with it the only call to
the backend's InvalidateBlock. Since then a block linked to an invalidated one kept jumping into
its old code, past the dispatcher. Call it from IRJit::InvalidateCacheAt through a virtual, like
the other native block hooks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
update-dispatcher.py only knew x64 paths, so on Windows-on-ARM it regenerated from a stale x64
build. headless_bench.py handed the options after -- to argparse's REMAINDER, which newer
Pythons treat as more binaries.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The audio dumping code is compiled out with MOBILE_DEVICE, which broke the
Android headless link.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Waking a thread from the mixer reschedules, which runs any due CoreTiming
events. When an audio tick runs late, the next tick would already be due, and
it would mix over mixBuffer before this one had been output. The next block
would then be played twice and this one dropped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6.60 and 6.61 crashed in vsh_module's alarm-task scan on a count nothing
initialized, and a data patch zeroed it. With the shell's modules started
in order, both reach the XMB, show icons and launch homebrew without it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel starts modules one by one, each module_start returning before
the next starts. We started them all as threads side by side, and
vsh_module's start ran before paf's. Its 12MB request for game_plugin's
surface pools then went to the 8.3MB heap we pre-filled scePaf's pool
slot with, failed, and game_plugin threw away every decoded ICON0 and
PIC1 (white tiles on 3.80-5.05, grey placeholders from 5.50).
heaparea1, vshbridge, paf, common_gui and common_util now start in order,
and vsh_module waits for the last. The kd/ drivers still start side by
side, since syscon's start blocks on its hardware interrupt. paf fills
its own pool slot now, so the pre-fill is gone. Icons show on every
version from 1.50 to 6.61, and homebrew launches on the same ones as
before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both in tests_next. unloadfill's fill matches (text becomes break 1,
data and bss 0xFFFFFFFF, as we already do), but sceKernelQueryModuleInfo
reports a text_size 6 words larger than the PSP's. deletetiming's
sceMpegDelete timing matches (it reschedules, in the 20-60 ms range); the
rest of the mpeg setup has older differences.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VSH's sceVshBridge_Driver calls both when launching a game, and some
firmware versions give up on the launch unless they succeed (5.xx for the
first, 2.00 for the second). Neither has anything to do here: our HLE
LoadExec reads the PBP itself, and there's no AVC decoder to power.
Added under the NIDs whose names are known: sceKernelSetParamSfo's old,
5.xx and 6.60 NIDs (from DC-M33's NID resolver, Adrenaline's NID table and
uOFW), and sceMePowerControlAvcPower's old and 6.60 NIDs (jpcsp's
psplibdoc, uOFW). Appended at the end of SysMemForKernel, and
sceMePower_driver registered last.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VSH launches games through the VSH variants of LoadExecForKernel,
whose NIDs most firmware versions changed. Where HLE doesn't know the NID,
the firmware's own loadexec.prx ran instead, and the launch got nowhere,
so homebrew launched from the XMB only on firmware 3.73 and older, and
6.60/6.61.
Each variant is the same small wrapper, loadExecVSH(apiType, file, opt,
0x10000), so when loadexec.prx exports one we don't know, look at the API
type it loads into a0, and for Disc (0x120) and MS2 (0x141), overwrite it
with a call to our HLE implementation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instead of 6.61's offsets, look for the checks by their instructions,
each of which has to match exactly once. Checked against vshmain.prx from
every firmware version, 1.50 to 6.61:
- The DISC_ID check is there from 2.00 on.
- The DATA.PSP header check from 5.00 on, in two forms: 5.xx compares
the header bytes against ~PSP and ~SCE itself, and 6.30 on test a type
a helper worked out. 6.00 to 6.20 have both. For the byte check, the
"first byte isn't ~" branch is pointed at where a good header goes.
- 1.50 and 1.52 have neither, and run unsigned code anyway.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApplyToConfig() set nandRootDirectory from it, but headless then reset it to
the memstick's PSP/NAND unconditionally, so --nand did nothing there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picking homebrew in the XMB gave "The game cannot be started. The data
is corrupted." without ever calling LoadExec: vshmain.prx rejects a
PARAM.SFO without a valid DISC_ID, and a DATA.PSP that's a plain ELF
rather than ~PSP. NOP the three branches that do that, the same patches
ProCFW's vshctrl makes and JPCSP applies. They're for 6.61's build, and
are only applied if all three instructions are what we expect.
With it, Kosmodrones launches from the XMB, through the PSP boot logo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It has done nothing since 2017. iCade input came through iCadeReaderView,
a hidden UIKeyInput view that decoded the controller's keystrokes, and
f0ba66fcd6 ("add support for smart keyboard") commented out the code
that created it. Nothing has made one since, so the view controller's
iCadeEventDelegate methods and the ICadeTracker behind them were never
called.
iCade controllers present themselves to iOS as Bluetooth keyboards, so
their keys still arrive as hardware keyboard presses, which can be
mapped like any other keys. Modern controllers go through
GameController.framework anyway.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The view controller was the keyboard's text input, and UIKit hands first
responder back to it whenever something it presented goes away: a share
sheet, a photo picker, the alert after an app icon change. Each time,
up came the keyboard, and hideKeyboard calls were sprinkled around to
put it away again, one on a four second timer.
Text input now goes to a small view that only becomes first responder
when a text field gets focus. The view controller is first responder
otherwise, for hardware keyboard presses, but takes no text, so it can't
bring up the keyboard. The workaround hideKeyboard calls are gone, and
so is the endEditing on rotation, which dropped the keyboard if you
turned the phone while typing. Showing and hiding are synchronous now,
since every caller is on the main thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Offers the file through the share sheet, which works out the type from
the extension. Also gives NativeInit a cache directory, Library/Caches,
which ShareFilesAsZip needs for its zips. That moves the SPIR-V and
download caches there from Documents/PSP/SYSTEM/CACHE, out of backups
and the Files app, which is where Apple wants caches anyway.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It allocated the new buffer, pointed buf_ at it, then popped from buf_,
so it copied the uninitialized new buffer onto itself. That happened
whenever a game asked for a larger block than before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>