Commit Graph
49 Commits
Author SHA1 Message Date
J1coding b09a0bac75 Merge master into jni-thread-ownership 2026-07-26 14:20:09 +02:00
J1coding 5031a4d2ba iOS: gate the JIT keepalive canary on the VM being parked
The canary flip from the keepalive fix drops execute on the arena's
first page, but the didBecomeActive prewarm re-runs it on every app
switch while the CPU thread is executing the dispatcher on that exact
page -> Instruction Abort. The old 'every caller runs parked' claim was
only a comment; now ValidateJITAlive asks the scene layer for real
VM/worker state and skips the probe (canary=skipped-vm-active) while
anything JIT is running or still initializing. A live VM is its own
proof the grant works.
2026-07-26 13:48:45 +02:00
J1coding 54191be600 iOS: fix Legacy-mode SIGBUS in the JIT keepalive canary
Main-thread boot crash on iOS 18 under LiveContainer (Legacy W^X mode):
EXC_BAD_ACCESS KERN_PROTECTION_FAILURE in ValidateJITAlive, a strb of the
0x42 canary into the arena base page. Under a dual-mapping g_code_rw_base
is the RW alias and the bare store is exactly the probe we want, but under
an identity mapping it is the live r-x code page -- the EE dispatcher sits
at arena offset 0 once the idle prewarm has run. The prewarm reordering
exposed it: before it, the boot gate ran with no arena allocated and the
canary was silently skipped.

Scope the canary per mode: Legacy flips just the first page RW and back
via mprotect, reporting a failed flip as alive=0 (grant died) instead of
faulting; the MAP_JIT toggle mode uses Begin/EndCodeWrite. Every caller
runs with the VM parked, so the brief execute-drop cannot race JIT
execution. Also covers the 12s idle keepalive timer, which would hit the
same fault after a prewarm.
2026-07-26 13:48:45 +02:00
J1coding dac184c894 iOS: fix Legacy-mode SIGBUS on cross-window JIT code patches
Boot crash on iOS 18 under LiveContainer (Legacy W^X mode): CPU thread,
EXC_BAD_ACCESS KERN_PROTECTION_FAILURE in Arm64BaseBlocks::New -- a str of
a branch encoding into an r-x page. In Legacy mode armStartBlock flips
only the current block's 1 MiB window to RW, but New()'s link-repoint
loop, Remove()'s entry stubs and the backedge patch write into EARLIER
blocks, whose windows are execute-protected by then. PatchWord trusted
its callers to hold a Begin/EndCodeWrite scope; no caller on those paths
did. The toggle modes (macOS, Simulator) masked it because the compile
thread's write-protect is off arena-wide during recRecompile.

Give the patch primitive its own scope instead: armPatchCodeWord stores
through the dual-map alias and, when the target page is outside the open
emit window, wraps the store in a page-granular Begin/EndCodeWriteRange.
The page check matters -- the bump allocator routinely puts the previous
block's link site on the same page as the current block's start, and
RX-flipping that page mid-emit would kill the compile in a new way.

The whole-arena Begin/EndCodeWrite scopes that only existed to cover
those patches are gone with it. They were their own crash: Legacy range
windows bypass the refcount, so recClear's paired EndCodeWrite RX-flipped
the open emit window whenever the stale-overlap walk cleared blocks
mid-compile, and recClearIOP paid a whole-arena mprotect pair per covered
IOP store. The fastmem backpatch and the cold-island patches route
through armPatchCodeWord for the same reason, and microVU's code-cache
scope narrows to its own buffer span so an MTVU close can no longer
RX-flip an EE emit window open on the other thread.
2026-07-26 13:48:45 +02:00
J1coding 80782e3781 iOS: fix two warnings that were real bugs
MTLCopyAllDevices is iOS 18 only and we deploy to 17, so on 17 it's a null
weak symbol and GSDeviceMTL::Create crashes on it before ever reaching the
MTLCreateSystemDefaultDevice fallback. Guard both call sites with @available;
below 18 there's one GPU and nothing to enumerate.

CocoaTools::GetBundlePath was defined twice on iOS -- once in MacOSStubs.cpp
returning nullopt, once in HostImpls.mm returning std::string where the header
says std::optional<std::string>. Return types aren't mangled, so both are the
same symbol, the linker picked one, and DynamicLibrary.cpp read an
uninitialised has_value byte off the stack. HostImpls.mm has the real
implementation, so drop the stub and give it the declared signature.

CreateMetalLayer and DestroyMetalLayer were duplicated the same way.
CreateMetalLayer also returned void* against a bool declaration, which reads
back the low byte of a pointer -- fine until a layer lands on a 256-byte
boundary. Nothing calls it on iOS today (Vulkan is off) but it's the only
definition left, so make it match.
2026-07-26 13:48:45 +02:00
J1coding 7823d20d05 iOS: close the gaps around per-game stick inversion
Follow-up to the per-game inversion fix. Three things the first pass left:

Reset All Overrides didn't mean it. The save path only knows about the
compatibility keys, so virtual-pad layout, skin and stick inversion all
survived the reset. Clear them alongside. The per-game master toggle also
never counted an inversion override as an override, so a game with nothing
but inverted camera read as having no per-game settings at all.

stickInversion(for:) opened and parsed the per-game INI on every stick
sample. Cache the overridden keys instead and rebuild only when the running
game changes or something writes an override. Globals stay uncached, so
changing one still takes effect without a rebuild.

The Stick Inversion pickers were gated on the pad layout identity, which
comes from a different store than the file the pickers write to. Gate them
on the game-settings identity, the same flag the rest of the panel uses,
so the controls can't appear for a game they'd silently write nowhere for.

Adds perGameIdentityKeyForCurrentGame/ForISO to the bridge for the cache
key and the gate.
2026-07-26 13:48:45 +02:00
J1coding 2ee9eca871 iOS: fix per-game stick inversion silently discarding writes
PadTab always used the ...ForCurrentGame per-game INI accessors. Those
resolve the game identity from the running VM and return early when there
isn't one, so opening Per-Game Settings from the library made all four
inversion pickers read "Use Global" no matter what was stored, and threw
every write away without a word.

Thread savesToRunningGame and the boot ISO into PadTab and branch on them,
the same way every other per-game control in the panel already does.

Also:
- Drop the redundant ContainsValue probe in stickInversion(for:) -- the
  getter already falls back to the default, and this runs per stick sample.
- Say in the Stick Inversion footer that the tab writes as you edit it.
  Nothing in PadTab is staged, so Save staying greyed is correct, but the
  panel never told anyone that.
2026-07-26 13:48:45 +02:00
J1coding c5a28d8377 iOS: fix tab background toggle snapping and cross-tab loading
The per-tab background toggle (backgroundEnabledInBIOS/Help/Settings)
was switching the TabView content between two different view trees
on toggle -- the view with a background vs SafeAreaProtectedMenuTabContent.
SwiftUI saw two different trees and destroyed/recreated the
NavigationStack, snapping back to root and breaking adjacent tabs.

Always render each tab's view directly. Each view already manages
its own background ZStack internally, and NavigationStack handles
landscape safe area correctly on iOS 17+. No if/else branch switch
means the view tree is stable across toggles.
2026-07-26 13:48:45 +02:00
J1coding 8a9c1aaef7 iOS: fix RA toast crash on NSDictionary bridge
consumePendingRetroAchievementsToast used Dictionary(uniqueKeysWithValues:)
to bridge the pending notification NSDictionary to Swift, which crashes
with -[NSConcreteAttributedString count] when the dictionary contains
an attributed string (upstream dynamic UI changes can inject one).

Extract the known fields (title, message, badgePath, duration) by key
directly from the NSDictionary instead, building a clean Swift
dictionary with only the expected types.
2026-07-25 00:31:36 +02:00
J1coding fd92b8eaea iOS: fix SIGTRAP race in JIT alloc when universal TXM times out
The detached Universal TXM worker thread can still be stuck in
brk #0xf00d when the main thread falls back to legacy brk #0x69.
Previously the old SIGTRAP handler was restored immediately after
the legacy path, so a late trap from the worker hit the default
handler and killed the process.

Move the sigaction restore to AFTER vm_remap + mprotect complete,
and add it to every error-return path. This keeps our handler
installed during the entire allocation so late worker traps are
caught safely.
2026-07-25 00:31:36 +02:00
J1coding 90a9d14002 iOS: extract skin zip before importSkin
importSkin expects a directory of loose files, not a raw zip.
The previous code passed the downloaded zip URL directly, so
skinImportFiles returned [zipURL] and isSupportedImageFile
rejected .zip → noUsableSkinImages (error 1).

Now downloads the zip, renames it to .zip, extracts it via
ARMSX2Bridge.extractControllerSkinArchive to a temp directory,
then passes the directory to importSkin — matching the existing
manual import flow in VirtualPadSettingsView.
2026-07-25 00:31:36 +02:00
J1coding df4031016f iOS: fix skin browser — tappable error alert, full-screen preview, larger thumbnails
The orange warning triangle used .help(error) which is macOS-only
and shows nothing on iOS. Replaced with a tappable button that
opens an alert with the actual error message.

Preview thumbnails were 64x40 and not interactive. Now 80x50 and
tappable — opens a full-screen sheet with the skin preview.

The error alert will also reveal why a specific skin fails to
install (URL encoding, unsupported images, corrupt zip, etc.) so
the cause is visible instead of hidden.
2026-07-25 00:31:36 +02:00
J1coding effc6abc56 iOS: browse and install community controller skins in-app
Adds an in-app skin browser that fetches the community catalog
(bagasromadon/ARMSX2-CustomControllerSkins) and installs skins
through the existing VPadSkinLibraryStore.importSkin path.

SkinCatalog fetches manifest.json, filters to iOS-ready skins
(those with an iosLayout field). SkinInstaller downloads the
zip and calls importSkin(from:) which handles extraction, button
images, and the linked layout preset in one call.

SkinBrowserView shows a searchable list with preview thumbnails,
Get/Installed state, swipe-to-remove, and inline error display.
Reachable from Virtual Pad settings under Browse Skins.
2026-07-25 00:31:36 +02:00
J1coding bbdfe15aaf ci(nightly): run iOS build on macos-26 with Xcode selected
The nightly iOS job ran on macos-15 without selecting full Xcode, so
build-ios-ipa.sh bailed at its xcrun --sdk iphoneos --find metal check
(Command Line Tools have no iOS SDK). build-all's iOS job builds on
macos-26 after sudo xcode-select -switch and is green, so match that:
same runner + select /Applications/Xcode.app before the build.
2026-07-24 13:48:25 +02:00
J1coding ac786b03bd iOS: fix GetOSVersionString build break in HTTP user-agent
GetIOSVersionForUserAgent() fell back to Host::GetOSVersionString(), but
GetOSVersionString() is a free function (common/HostSys.h, DarwinMisc),
not a Host member. The other user-agent call sites already call it
unqualified; this one broke the iOS device build.
2026-07-24 13:09:17 +02:00
J1coding 2710346dad iOS: bump app version to 2.5.0 2026-07-24 12:15:28 +02:00
J1coding 3704ec939d iOS: re-measure overlay containers on rotation to fix the pause-menu squish
After a landscape-to-portrait rotation the in-game pause menu and the
per-game settings panel were laid out into a stale landscape size and
looked squished. The body GeometryReader is the only surface SwiftUI
re-measures on rotation (ARMSX2HostingController's viewWillTransition
nudge covers it); the conditionally-presented .overlay subtrees that host
those panels are not re-measured by that nudge.

Lift the orientation off the body GeometryReader via the existing
GameScreenSizePreferenceKey preference, store it in screenIsLandscape, and
key the per-game-settings and pause-menu overlay containers on it with
.id(screenIsLandscape). On a flip the id change rebuilds GameOverlayContainer
so its nested GeometryReader measures the now-portrait proposed size. The
rebuild is an instant swap with no presentation animation, masked by the
device rotation.
2026-07-24 12:15:28 +02:00
J1coding 38f4358b0c iOS: expose GS Back Thread mode in Graphics settings
Surface the GS Back Thread (GSBackThreadMode) setting in the Graphics
panel under a Performance section, with a picker for Disabled (default),
Inline Records and Lockstep debug modes, and Pipelined, which splits GS
emulation across a second thread on multi-core devices. The two debug
modes are flagged as much slower and not for play.

The setting is persisted and applied through the same Setting<Int> path
as the other EmuCore/GS options, using the guarded graphics-apply helper
so it cannot fire while SettingsStore is loading.
2026-07-24 12:15:28 +02:00
J1coding 622887f093 iOS: Frame Pacing presets, ProMotion unlock, and per-game overrides
Consolidated Frame Pacing surface over the EmuCore/GS, SPU2/Output, and
Framerate INI keys.

Presets and migration
  FramePacingPreset (Optimal / Smooth / Low Latency / Battery Saver / Custom)
  drives the individual keys through applyFramePacingPreset, which writes them
  via the existing clamped Setting setters. A one-shot migration moves a fresh
  install (or a user still on the PCSX2 v1.0 defaults) onto Optimal; anything
  customized is preserved as Custom.

ProMotion
  Unlock 120 Hz panels with CADisableMinimumFrameDurationOnPhone in Info.plist.
  SceneDelegate no longer tries to pin preferredFrameRateRange via
  -[UIUpdateLink initWithWindowScene:], a selector that does not exist and
  crashed every cold launch.

Per-game
  The per-game Frame Pacing tab mirrors the main-menu panel (preset + frame
  limiter + FPS target + vsync queue + sync-to-host + audio buffer/latency),
  each with a "Use Global" option. The FPS target maps to Framerate/NominalScalar,
  so the per-game bridge gains Float getters/setters and a shared encoder; a
  named preset applies its full profile per-game, including the FPS target.

Init safety
  Starting the adaptive-resolution controller reads SettingsStore.shared, which
  re-enters the swift_once token during SettingsStore.init and deadlocks
  dispatch_once, so that bootstrap is deferred to the next run-loop tick. The
  graphics Setting onSet closures route through requestGraphicsApplyGuarded,
  which no-ops while the INI is loading.

The Adaptive Resolution toggle is hidden from the UI (the controller stays
dormant). The pause-menu Frame Pacing panel is removed; Frame Pacing lives in
the main Settings and the per-game settings only.
2026-07-24 12:15:28 +02:00
J1coding 29190ff8eb iOS: custom background support for BIOS, Help, and Settings tabs
The Games library already rendered a user-chosen background behind its
list; the other menu tabs did not, and the background helpers that drew it
lived privately inside GameListView. This promotes those helpers to shared
code and lets the remaining menu tabs opt in.

  - New MenuBackgroundSupport.swift holds MenuBackgroundLayer (the edge-to-
    edge wallpaper view) and the menuBackgroundListRow modifier (the rounded
    material row backing used so list rows stay readable over a wallpaper).
    GameListView now uses these instead of its private copies.
  - BIOSListView, HelpView, and SettingsRootView draw the background inside
    their own NavigationStack when their per-tab toggle is on, matching how
    GameListView already worked. RootView (MenuTabView) no longer wraps those
    tabs in a background ZStack; a tab that owns its wallpaper must not also
    be wrapped in SafeAreaProtectedMenuTabContent, or the safe-area padding
    would clip the wallpaper.
  - Three per-tab toggles (backgroundEnabledInBIOS/Help/Settings, persisted
    in UserDefaults) are surfaced in Appearance settings under "Show
    Background In". Games keeps the background unconditionally; the others
    default off so existing users see no change until they opt in.

SafeAreaProtectedMenuTabContent is also tightened for iOS 26+: SwiftUI now
reports the correct landscape safe-area inset for a TabView page, so the
manual notch-clearing pad is only applied when SwiftUI itself reports no
horizontal inset. Applying it unconditionally doubled the inset on iOS 26+
and made the menu tabs feel cramped in landscape.
2026-07-24 12:15:28 +02:00
J1coding 80feae5f31 iOS: route RetroAchievements through native UI instead of ImGui FullscreenUI
The iOS app renders its own SwiftUI UI, so the shared core's ImGui
FullscreenUI overlay never appears on screen. Before this change every
RetroAchievements event still initialized FullscreenUI and posted the
notification through it, adding per-frame render work for an overlay that
is invisible on iOS, and the native toast layer never saw the events at all.

Add two Host callbacks so a platform can take over notification rendering:

  bool Host::HasNativeAchievementNotifications()
  void Host::OnAchievementNotification(key, duration, title, message, badge_path)

When HasNativeAchievementNotifications() is true the shared core hands each
RA event (unlocks, mastery, leaderboard start/submit/scoreboard,
login, connect/disconnect, summary) to OnAchievementNotification and skips
ImGuiManager::InitializeFullscreenUI() entirely — in BeginLoadingScreen,
ClientLoadGameCallback, DisplayHardcoreDeferredMessage, and
SetHardcoreMode — so the invisible overlay and its render loop stay down.
The existing ImGui path is unchanged for desktop/Android, which return
false from the new callback. Every frontend (eerunner, gsrunner, libretro,
sdl, qt, android, test stub, macOS stubs) gets a no-op implementation; iOS
provides the real one, posting the notification to its SwiftUI toast layer
through ARMSX2_PostRetroAchievementsNotification. The notification now also
carries the configured display duration.

Also flesh out Achievements::GetCurrentUserStats / GetCurrentGameStats /
GetCurrentAchievementList, which were previously unimplemented stubs
returning false. The iOS bridge already wired these up to the
RetroAchievements panel; they now return the logged-in user's score, the
active game's unlock progress, and a bucket-ordered achievement list so the
native panel has real data instead of an empty state.

Stray RetroAchievements debug fprintf spam in the iOS bridge and overlay
defaults is dropped.
2026-07-24 12:15:28 +02:00
J1coding 2e50c8bfbc iOS: gate MetalFX upscaler out of simulator builds
MetalFX.framework is not part of the iphonesimulator SDK, so building the
iOS app for the simulator failed at compile time on every MetalFX reference
(`<MetalFX/MetalFX.h>`, `MTLFXSpatialScalerDescriptor`, the cached scaler
members) even though the upscaler is correctly runtime-gated on real
hardware.

Introduce a compile-time switch that follows the SDK target:

- GSDeviceMTL.h defines PCSX2_HAS_METALFX (1 on device, 0 on sim) and
  wraps the MetalFX include, the m_mfx_spatial cache members, and the
  scaler function declarations behind it. The sim build gets a stub
  EnsureMetalFXSpatial that returns false, and DoMetalFXSpatial short-
  circuits the same way; m_features.metalfx_spatial keeps its default
  false so the UI reports the upscaler as unavailable.
- ARMSX2Bridge.mm gates its MetalFX import the same way (ARMSX2_HAS_METALFX)
  and isMetalFXSupported returns NO on sim without touching the descriptor.
- pcsx2/CMakeLists.txt only emits -weak_framework MetalFX for device or
  non-iOS builds; on sim there is nothing to link against.

Adds generate-ios-sim-xcode.sh, a simulator counterpart to the existing
generate-ios-xcode.sh, so the CMake-generated Xcode project can target
iphonesimulator (ARMSX2_REAL_DEVICE=OFF) for simulator debug and test
workflows. Device IPA builds keep using the existing scripts unchanged.
2026-07-24 12:15:28 +02:00
J1coding 6e5569a794 ci(nightly): add iOS IPA 2026-07-24 07:46:56 +02:00
J1coding 1cf72321ed iOS: Ignore unrelated UIKit text fields in SDL text input
SDL_uikitviewcontroller registers its UITextFieldTextDidChangeNotification
observer with object:nil, so textFieldTextDidChange: fires for every
UITextField in the app, not just SDL's own hidden field. The handler
unconditionally calls SDL_StartTextInput(window) (and the marked-text
path later calls SDL_StopTextInput), so any app-level text input --
notably SwiftUI-hosted login forms -- was stealing first responder on
every keystroke: the user typed one character, the keyboard was
dismissed and re-presented, and they had to tap the field again before
the next character.

Filter the notification at the top of the handler: if notification.object
is not this controller's textField, return early. SDL's own field still
goes through the existing password-manager workaround unchanged.

The SDL3 sources in this repo are vendored, not a submodule, so this
patch lives in tree until the next SDL sync.

Verified by typing into the RetroAchievements login sheet: each
keystroke now keeps first responder and the keyboard stays up for the
whole credential entry.
2026-07-18 17:46:15 +02:00
J1coding 6621011409 iOS: Restore fullscreen SDL window frame at landscape launch
At cold launch the app's root surface rendered as a left-aligned square
inside a landscape screen. The window was sized for portrait because
SDL 3.5.0's UIKit_CreateWindow creates the UIWindow via
[[UIWindow alloc] initWithWindowScene:scene], which inherits the
scene's bounds at a moment when iOS has not yet autorotated out of the
first plist orientation (portrait). SDL 3.3.0 used
initWithFrame:screen.bounds, which filled the screen regardless of the
scene's initial orientation. SDL_SetWindowSize is a no-op for this
path on iOS (only visionOS implements UIKit_SetWindowSize).

The SDL3 sources in this repo are vendored, not a submodule, so patch
UIKit_CreateWindow's scene branch with an #else companion to the
existing visionOS frame-setting block that assigns
uiwindow.frame = data.uiscreen.bounds. This restores the fullscreen
condition that UIKit_ComputeViewFrame's orientation-swap gate checks.

Under host containers that resolve orientation after the
rootViewController is created in willConnectTo:, rootVC.view can stay
pinned to portrait bounds. Add a one-shot re-sync in
sceneDidBecomeActive: that snaps rootVC.view to the window's bounds
when their orientations disagree, then lets Auto Layout propagate to
the SwiftUI child. It is a no-op on the normal launch path where
orientations already agree.

Verified by launching in held-landscape orientation on native sideload
and host-container hosts: the menu fills the screen instead of
rendering in a portrait-width square.
2026-07-18 17:46:04 +02:00
J1coding 818a513e95 iOS: Skip RetroAchievements re-initialization when already active
VMManager::Internal::CPUThreadInitialize() can run more than once in
a process lifetime on mobile hosts that tear down and rebuild the CPU
thread across game launches. Achievements::Initialize() asserts
`!s_client && !s_http_downloader` at the top, so a second call with
RetroAchievements enabled fired pxFailRel("No client and downloader")
and aborted the process. The crash only manifested on hosts that
re-enter CPUThreadInitialize without an intervening Achievements
shutdown, which is why desktop builds never hit it.

Gate the call with !Achievements::IsActive(), which checks the same
s_client pointer the assertion reasons about. The normal cold-start
path (s_client == nullptr) still calls Initialize(); a second entry
with the client already constructed is now a no-op, matching the
expectation the rest of the file already has.

Verified by repeated launch/exit/relaunch cycles on iOS with
RetroAchievements enabled: no abort, and the client initialises
exactly once per process.
2026-07-18 17:45:52 +02:00
J1coding 9b569a6fc5 iOS: Keep keyboard focus while typing RetroAchievements credentials
Each keystroke in the RetroAchievements login sheet dismissed the
keyboard and forced the user to tap the field again before the next
character, which made password entry effectively unusable.

Two SwiftUI mistakes caused it. The sheet's username and password
fields were @Bindings back to @State in the presenting parent, and
the parent's body is what builds the .sheet content closure. Every
keystroke mutated the parent's @State, the parent body re-evaluated,
the sheet content closure re-ran, and SwiftUI rebuilt the sheet.
.presentationDetents([.medium, .large]) re-snapped to .medium on
each rebuild, dismissing and re-presenting the keyboard.

Separately, the parent's .onReceive handler for
ARMSX2RetroAchievementsStateChanged called refresh(), which mutates
more @State on a background cadence. With the parent already
re-evaluating on each keystroke, those notifications forced the same
sheet rebuild from a second source.

Make username and password sheet-local @State, initialised from the
parent's value at presentation so "Log In Again" pre-fill still
works, and pass credentials back through an onLogin(NSString, String)
closure. The parent's body is no longer invalidated by typing, so
the sheet is never rebuilt mid-input. Also guard refresh() with
`guard !showingLogin else { return }` so background notifications
are ignored while the login sheet is up. Switch to a single .medium
detent so the initial keyboard-appearance cannot trigger a one-time
re-snap either.

Verified by entering a multi-character password in the login sheet
without losing keyboard focus between keystrokes.
2026-07-18 17:45:52 +02:00
J1coding 445d7aee1b iOS: Use sandbox-safe shared memory for GS
GSAllocateWrappedMemory's POSIX branch called shm_open("/GS.mem",
O_RDWR | O_CREAT | O_EXCL, 0600) directly. The iOS application
sandbox rejects named POSIX shared memory in the system-wide
namespace, so shm_open returned -1, the function returned nullptr,
and GSLocalMemory::GSLocalMemory() aborted via
pxFailRel("Failed to allocate GS memory storage."). On hosts that
re-enter the CPU thread across launches, this surfaced as a game-
launch abort on the GS thread.

The rest of the codebase already routes shared-memory creation
through HostSys::CreateSharedMemory (pcsx2/Memory.cpp uses it for
EE/IOP RAM), whose Linux/Apple branch selects memfd_create on
Android, shm_open on desktop POSIX, and a file-backed TMPDIR
fallback on iOS so the same call works under the sandbox.
GSAllocateWrappedMemory was the only production caller bypassing
the helper.

Delegate fd creation to HostSys::CreateSharedMemory, drop the
Android-only memfd_create special-case and the redundant ftruncate
(both are handled inside the helper), and use
HostSys::GetFileMappingName so the name is PID-qualified instead of
the fixed "/GS.mem". The MAP_SHARED repeat-mirroring mmap loop is
preserved unchanged so the 4 MB GS VRAM still appears `repeat` times
at contiguous virtual addresses for the PS2 GS address-wrap
behaviour. GSFreeWrappedMemory is updated symmetrically to call
HostSys::DestroySharedMemory.

The Windows branch is untouched, and the caller contract (return
nullptr on failure; the caller's pxFailRel handles the abort) is
preserved.

Verified with the iOS build and repeated game launch/exit/relaunch
cycles: GSLocalMemory construction no longer aborts and the wrapped
memory layout is unchanged.
2026-07-18 17:45:36 +02:00
J1coding 7675402127 iOS: fix post-merge build (AppKit/IOKit/Darwin CDVD gating + MetalFX link)
Upstream's if(APPLE) blocks assumed macOS and broke the iOS build:
- AppKit/IOKit find_library + Darwin CDVD sources use macOS-only frameworks
  absent from the iOS SDK; gate them off iOS (iOS links its frameworks at
  the app target)
- Keep -weak_framework MetalFX for both platforms (iOS uses MetalFX)
- Stub the Achievements::GetCurrent* queries declared upstream but not yet
  implemented, so the iOS bridge links; return false (no data)
2026-07-16 23:39:54 +02:00
Jeen c9072d4f86 CI: remove push trigger from Nightly workflow
Nightly should only run on its daily schedule (08:00 UTC) and manual
dispatch, not on every push to master. The push trigger was added in
e2889838ab under the assumption nightly should run per-push; that
duplicates build-all.yml (which already runs on every push) and wastes
~2h of CI plus an unwanted nightly release on every commit.

Surgical edit only -- e2889838ab also added the changelog generator,
dual-core Android build, and PGO profile, which are correct and stay.
build-all.yml is unchanged (still fires on push to master + PR +
workflow_dispatch).
2026-07-13 18:14:10 +02:00
Jeen 8eada3b11a iOS: add opt-in dSYM CMake option
Add ARMSX2_IOS_DSYM (default OFF) to inject -g into Release builds so a
.dSYM can be extracted for crash symbolication. Off by default to avoid
the link-time and artifact-size overhead on every build; enable with
-DARMSX2_IOS_DSYM=ON when producing tester or debug builds.

The flag must go through add_compile_options (CMake's own flag model) so
the Xcode generator serializes it into each target's compile command.
Four alternatives were tried and all silently failed due to CMake bug
#15224 (the Xcode generator writes per-target OTHER_CFLAGS without
$(inherited), shadowing project/command-line values):
  - XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT on the target
  - CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT project-wide
  - CMAKE_C_FLAGS_RELEASE / CMAKE_CXX_FLAGS_RELEASE
  - OTHER_CFLAGS='$(inherited) -g' on the xcodebuild passthrough

Mirrors the existing if(LINUX) -g1 path at BuildParameters.cmake:225.
No CI change -- the option is purely for manual opt-in.
2026-07-13 17:22:24 +02:00
Jeen 8315f97a50 vtlb: guard fastmem mapping against missing reservation
On low-memory iOS devices (iPhone SE 2, 4 GB RAM, under LiveContainer)
the 4 GB fastmem virtual-address reservation fails at boot. The iOS
boot path correctly disables fastmem and leaves s_fastmem_virtual_mapping
empty. But mid-game, a configuration change (CheckForCPUConfigChanges ->
vtlb_ResetFastmem) can flip EnableFastmem back on, and
vtlb_CreateFastmemMapping then indexes the empty vector -- a NULL deref
that crashes the CPU thread at 0x200 (page 128 * 4 bytes). This was the
long-standing 'NULL+0x200' iPhone SE 2 crash, confirmed by symbolication
against the matching dSYM.

Add two defence-in-depth guards, both mirroring the existing empty-vector
guard in vtlb_RemoveFastmemMappings (commit eb18e0f188):

  1. vtlb_CreateFastmemMapping: early-return when
     s_fastmem_virtual_mapping is empty.
  2. vtlb_ResetFastmem: early-return when s_fastmem_area_unavailable,
     so a config change cannot repopulate mappings into a null area and
     the pointless VTLB_VMAP_ITEMS scan is avoided.

Cross-platform safety: both guards are dead code on non-iOS.
s_fastmem_area_unavailable is only ever set true inside
`#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR` at vtlb.cpp:1379; every
other platform takes the #else branch that aborts boot on reservation
failure (Host::ReportErrorAsync + return false), leaving the flag false.
On platforms where the reservation succeeds, the vector is non-empty so
the .empty() guard never triggers. Verified safe on macOS, Linux,
Windows, Android.
2026-07-13 17:20:57 +02:00
Jeen 4bf3be1f69 GS: write JIT code through RW alias on iOS dual-map
The GS software renderer's ARM64 code generators (DrawScanline and
SetupPrim) handed the raw RX pointer to their vixl MacroAssembler. On
iOS 26+ devices using the LuckTXM JIT mode, executable memory is
dual-mapped: a read-execute alias for the CPU and a separate read-write
alias for the emitter, offset by g_code_rw_offset. Writing to the RX
page faults instantly (KERN_PROTECTION_FAILURE) -- the GS-thread SIGBUS
crash seen on iPhone 16 / iOS 27 beta.

The EE and VU recompilers already handle this via armGetWritableCodePtr
(pcsx2/arm64/AsmHelpers.cpp). Mirror that pattern in the GS path: a
file-local gsGetWritableCodePtr() helper adds the offset on real iOS
devices and is an identity no-op everywhere else (macOS, iOS Simulator,
Android, Legacy-iOS). The original RX pointer is kept as m_code_rx so
GetCode() still returns the executable entry point the rasterizer calls
into -- vixl's GetStartAddress echoes the constructor argument, so
without this override GetCode() would return the non-executable RW
pointer.

Cross-platform safety: the guard (#if __APPLE__ && TARGET_OS_IPHONE &&
!TARGET_OS_SIMULATOR) is byte-identical to the already-shipped EE/VU
helper. On every non-iOS path the helper returns the pointer unchanged,
and g_code_rw_offset is 0, so behaviour is identical to before. The x86
GS path (.all.cpp, selected by CMakeLists.txt:480-490 under ARCH_X86)
shares no code with these arm64 files and is not compiled on any arm64
target.
2026-07-13 17:19:50 +02:00
Jeen 04ea993e99 Merge remote-tracking branch 'armsx2/master' into ios/pr-ready 2026-07-13 00:41:14 +02:00
Jeen 805c161aaf iOS: merge ARMSX2/master into ios/pr-ready 2026-07-13 00:27:07 +02:00
Jeen ecd198419b iOS: fix Games navigation title truncating to Ga...
The inline navigation title 'Games' was being squeezed between the leading
'BIOS Only' button and four trailing toolbar items (import, layout menu,
covers menu, refresh), causing it to truncate to 'Ga...' on the nav bar.

Fix: remove the .navigationBarTitleDisplayMode(.inline) modifier so SwiftUI
uses the default large title mode, which renders on its own row and is not
affected by toolbar item crowding.
2026-07-12 19:14:13 +02:00
Jeen c6b7049664 iOS: disable EE block chaining to fix recompiler crashes
The direct-B block chaining system (s_eeBlockLinkEnabled) is a newer
recompiler optimization that patches direct branch instructions between
JIT-compiled blocks. The proven fork does not use this system and never
crashes, while the monorepo with block chaining enabled crashes on three
different devices (iPhone SE 2, iPhone 17, iPad Pro M1) with signatures
pointing to corrupt JIT-generated code:

  iPhone SE 2: CPU thread data-read translation fault at 0x200 (NULL+0x200)
  iPhone 17: CPU thread instruction-fetch translation fault at 0x100000000
  iPad Pro M1: GS thread write permission fault inside JIT RX code region

All three had JIT-generated recompiled code on the faulting thread's call
stack, one level above the crash site. The common factor is the recompiler
producing or consuming a bad pointer value through the new block-chaining
code path.

Fix: gate s_eeBlockLinkEnabled to false on iOS, falling back to the proven
LUT-indirect dispatch path (adrp+add+ldr+br). This is the same path the
fork uses and has been stable across thousands of gameplay hours. The
performance impact is minimal (one LUT lookup per block exit instead of
a direct branch). Non-Apple platforms keep block chaining enabled.

Root cause traced via systematic debugging:
  Phase 1: 3 crash logs analyzed across 3 devices/iOS versions
  Phase 2: full fork-vs-monorepo diff of aR5900.cpp/AsmHelpers.cpp
  Phase 3: block chaining identified as the only new JIT codegen path
2026-07-12 18:51:33 +02:00
Jeen 922772faf8 Revert "iOS: fix JIT keepalive timer running during gameplay"
This reverts commit b8e94ea84f.
2026-07-12 18:30:51 +02:00
Jeen b8e94ea84f iOS: fix JIT keepalive timer running during gameplay
The keepalive timer was stopped before VMManager::Initialize and skipped
validation while s_vmThreadActive was true. This was wrong: iOS can revoke
CS_DEBUGGED at any time, including mid-frame during active gameplay. When
revocation happened during gameplay, the protection on code and data pages
was silently flipped, crashing the CPU thread (instruction abort), GS thread
(data abort write fault on shared memory), and MTVU thread (translation
fault at null) simultaneously.

Fix: keep the timer running continuously at its 12-second interval during
all app states, including active gameplay. The validation cost is trivial
(one csops syscall plus one byte canary write). When revocation is detected
during gameplay, the timer posts a JITExpired notification and stops — the
next boot attempt will fall back to interpreter mode.

Root cause traced from crash log analysis on iPad Pro M1 (iPad13,8, iOS
26.5): God of War II crashed at frame 71, ~12 seconds after JIT acquisition,
with SIGBUS KERN_PROTECTION_FAILURE on shared memory writes from the GS
thread. Three threads faulted simultaneously, confirming a grant revocation
rather than a single-thread bug.
2026-07-12 18:06:15 +02:00
Jeen d57633224d iOS: merge upstream/master into ios/pr-ready
Resolve two conflicts:
  Threading.h: keep upstream's doc comment for SetNicePriority
  VMManager.cpp: keep our iOS-aware guard that suppresses the controller
  warning on both Android and iOS, replacing upstream's Android-only
  comment-out hack
2026-07-12 17:17:16 +02:00
Jeen ecf0e5c44a iOS: remove fork-only docs and merge device IPA build into build-all.yml
Remove the design specs, troubleshooting guide, changelog, and bringup brief that were fork-local planning artifacts not intended for the upstream monorepo. Merge the confirmed-working real-device IPA build from the fork-local ios_build.yml into the existing build-all.yml iOS job, replacing the simulator-only .app build with the iphoneos SDK device build that produces an unsigned .ipa named with the commit SHA. Remove the now-redundant ios_build.yml.
2026-07-12 17:06:54 +02:00
Jeen fb2c07eb6e iOS: add JIT resilience layer with keepalive, interpreter fallback, and boot watchdog
Build a comprehensive JIT resilience layer that prevents silent black screens when iOS revokes the CS_DEBUGGED grant after approximately 30 to 60 seconds of app inactivity. Add a ValidateJITAlive helper to DarwinMisc that re-probes CS_DEBUGGED via csops and writes a canary byte to the JIT RW alias to detect whether the mapping is still writable, covering the case where the flag lingers but the underlying grant is already dead. Add a 12-second dispatch timer in SceneDelegate that calls ValidateJITAlive while the VM is idle, skipping during active gameplay since the recompiler keeps JIT in constant use, and posts a JITExpired notification on detection. Add interpreter fallback to the boot gate so that when JIT is dead the app falls back to the pure EE, IOP, VU0, and VU1 interpreter instead of blocking boot, wiring up the previously dormant iPSX2_FORCE_EE_INTERP flag and fixing applyFullInterpreterPreset to actually write EnableEE equals false since CoreType was an iOS-UI-only concept the C++ core ignored. Skip executable code-memory allocation in Memory.cpp when in interpreter mode since the interpreter does not generate native code, and null-guard SetJitRange to prevent recording bogus JIT ranges. Add a 15-second VM init watchdog that catches TXM prepare hangs and shows an error dialog instead of leaving a permanent black screen. Add an 8-second timeout to the Universal TXM prepare path by running the brk number 0xf00d cycle on a detached worker thread with thread_local sigjmp_buf, falling back to the Legacy brk number 0x69 protocol on timeout. Add re-boot JIT revalidation so the persistent VM thread validates JIT before signaling, resetting to interpreter mode with s_vmThreadShouldExit to cleanly tear down and recreate the thread with paired CPUThreadShutdown. Restore recompiler settings and fastmem when JIT returns on next app launch. Add the design spec and a user-facing JIT troubleshooting guide documenting black screen causes, interpreter fallback expectations, diagnostic log markers, and workarounds.
2026-07-12 16:41:17 +02:00
Jeen eb18e0f188 iOS: fix fastmem NULL deref crash on low-memory devices and rotation layout break
Fix a NULL dereference crash in vtlb_RemoveFastmemMappings that occurred when the 4 GB fastmem virtual-address reservation failed on low-memory iOS devices such as the iPhone SE 2 under LiveContainer. The s_fastmem_virtual_mapping vector was never resized but was still indexed unconditionally by vtlb_Init through vtlb_VMapUnmap on every boot, dereferencing NULL. Add the same empty-vector early-return guard that the zero-arg overload already has, protecting both the boot path and the COP0 TLB-write runtime path. Fix the portrait and landscape rotation layout break by overriding viewWillTransition in ARMSX2HostingController to re-assign rootView inside the transition coordinator animation block, forcing UIHostingController to invalidate its internal sizing cache and re-measure for the new container size. Without this nudge, SwiftUI kept stale geometry after rotation, producing black bars, cropped viewports, and misplaced touch controls because the GeometryReader never updated and the Metal drawableSize stayed at the pre-rotation value. Revert the two-column pause menu layout in portrait for iPhones since the screen is too narrow even on Plus and Max devices, restoring single-column scroll while keeping two-column in landscape and on iPad.
2026-07-12 16:40:51 +02:00
Jeen 351479e7af iOS: add MetalFX spatial upscaler setting to global and per-game graphics settings
Wire up the MetalFX spatial upscaler as a user-facing setting across the iOS frontend. Add an isMetalFXSupported probe to the ObjC++ bridge that queries MTLCreateSystemDefaultDevice and MTLFXSpatialScalerDescriptor supportsDevice at runtime, using MRCOwned to avoid leaking the device handle under the codebase's manual reference counting. Add isMetalFXAvailable as a computed property and the upscaler Int property with an EmuCore/GS Upscaler INI config to SettingsStore, loaded in init and reload and reset in resetGraphicsDefaults, following the exact tvShader pattern. Add a new Upscaler section to the global Graphics settings view between Upscaling and Filtering, gated on isMetalFXAvailable so the entire section is hidden on unsupported devices. Add per-game Upscaler override to the per-game Graphics tab with the standard Use Global, Off, and MetalFX Spatial options using the minus-one sentinel convention, threaded through PerGameSettingsPanel state, fingerprint, save, and the GraphicsTab binding. Add the hasPerGameUpscaler and perGameUpscaler keys to the bridge dictionary builder so the preload path works without extra round trips. Add the design spec to docs/superpowers/specs.
2026-07-12 16:40:30 +02:00
Jeen 3c3915a70c iOS: enable MetalFX spatial upscaling on iOS 16+ and document in changelog
MetalFX Spatial (MTLFXSpatialScaler) has always been available on iOS 16+ at the Apple API level, but five compile and build gates were stripping the feature entirely on TARGET_OS_IPHONE, leaving iOS users with only plain bilinear stretching. Remove all five gates: hoist the MetalFX include out of the macOS-only branch in GSDeviceMTL.h so iOS gets it too, drop the PCSX2_MTL_USES_UIVIEW guard on the m_mfx_spatial member and widen API_AVAILABLE to macos 13.0 and ios 16.0, remove the TARGET_OS_IPHONE stubs for EnsureMetalFXSpatial and DoMetalFXSpatial so the real implementations compile on iOS and widen the availability check, unguard the supportsDevice feature probe, and add weak_framework MetalFX to the iOS CMake link branch mirroring the macOS pattern. The runtime supportsDevice probe is retained, so MetalFX cleanly no-ops on the iOS Simulator, pre-iOS-16 devices, and any GPU lacking the hardware, falling back to bilinear with a one-shot OSD notice. Update the OSD unsupported message and doc comments to mention iOS 16.
2026-07-12 16:40:01 +02:00
Jeen 3467e72dba iOS: bump version to 2.4.1, activate NEON SPU2 mixing, and port EE recompiler zero-register folds
Bump the iOS app version to 2.4.1 with build number 241 across CMakeLists.txt, Info.plist, and the SwiftUI about screen. Activate the dormant NEON SPU2 voice mixing and reverb backend by wiring up RegisterNEONBackend in InternalReset, which vectorizes volume application, voice accumulation, and clamping using int32x4_t NEON intrinsics with scalar fallbacks preserved. Port five zero-register fold fast paths in the ARM64 EE recompiler shift handlers (SLL, SRL, SRA, their variable-shift variants, and the 64-bit DSLLV, DSRLV, DSRAV) that emit a Mov to zero or a plain register move when the source operand is the zero register, avoiding unnecessary shift instructions.
2026-07-12 16:39:42 +02:00
Jeen 431ca0c063 iOS: port JIT and W^X foundation, refresh SwiftUI frontend, fix critical boot and display bugs
Port the complete JIT and write-xor-execute infrastructure to DarwinMisc with four JitModes (Simulator, Legacy, LuckTXM, LuckNoTXM), dual-mapping via vm_remap for writable code aliases, the csops CS_DEBUGGED probe for JIT availability detection, brk assembly helpers for the TXM protocol, and the W^X toggle functions. Connect the JIT foundation to the code emitters through AsmHelpers dual-map bridge, Memory.cpp MmapCodeDualMap allocation, and the aR5900 LegacyEnsureExecutable path. Refresh the iOS SwiftUI frontend from the iOS-refresh branch, bringing in 11 missing and 20 drifted Swift files plus ios_main.mm integration. Switch the CI to a real device build using the iphoneos SDK. Fix the Achievements crash by gracefully degrading when no HTTPDownloader is available (no CURL on iOS). Fix the Metal surface to reuse the UIView's existing CAMetalLayer instead of an orphaned allocation that caused half-screen crops. Fix GS memory allocation by using mmap and vm_remap instead of shm_open which is blocked by the iOS sandbox. Suppress the false positive Graphics not Automatic OSD warning. Merge upstream master and resolve all resulting compile errors.
2026-07-12 16:39:22 +02:00
Jeen 14fc29c0cd iOS: gate macOS-only APIs behind TARGET_OS_IPHONE and add frontend link stubs
Gate all macOS-only system APIs behind TARGET_OS_IPHONE checks across the shared core: ApplicationServices, IOKit, mouse APIs, AppKit, MetalFX, CDVD Darwin sources, USB, discord-rpc, cubeb CoreAudio HAL, and BSD networking headers in DEV9. Port TARGET_OS_IPHONE guards for DEV9 AdapterUtils with iOS fallbacks for sockaddr_dl, rt_msghdr, and sysctl. Add ARMSX2_ROOT to the include path for the frontend's common include style. Drop the global _M_ARM64 define that triggered fast_float MSVC intrin.h inclusion since __aarch64__ covers the core paths. Gate MTLFeatureSet_macOS_GPUFamily1_v1 behind !TARGET_OS_IPHONE. Fix missing unistd.h and gate pthread_jit_write_protect_np which is unavailable on iOS. Add stubs for DarwinMisc JIT diagnostics, Achievements stats and info APIs, Discord_Register, Host capture callbacks, and the host hotkeys map so the frontend links cleanly. Merge upstream master.
2026-07-12 16:38:59 +02:00
Jeen b5e581b1f3 iOS: remove Android and React Native source, wire CMake build system, and add self-contained build workflow
Strip the legacy Android, React Native, Java, Gradle, and res/ directories from platforms/ios that were inherited from the original port. Configure the CMake build for the iOS target with local module discovery, PCAP and CURL guards, rapidyaml source path fixes, Vulkan disabled, lz4 build flags scoped, libjpeg-turbo skipped on iOS, and the Qt UI and test runners turned off. Set CMAKE_SYSTEM_PROCESSOR to arm64 for cross-compilation. Add a self-contained GitHub Actions workflow that builds an unsigned IPA for real devices using the iphoneos SDK, named with the commit SHA. Merge upstream master to stay current.
2026-07-12 16:38:18 +02:00