Skip to content

iOS port - #1767

Draft
Markos-Th09 wants to merge 11 commits into
hedge-dev:mainfrom
Markos-Th09:ios
Draft

Markos-Th09 wants to merge 11 commits into
hedge-dev:mainfrom
Markos-Th09:ios

Conversation

@Markos-Th09

@Markos-Th09 Markos-Th09 commented Jun 6, 2026 •

Copy link
Copy Markdown

Overview

I am working on porting Unleashed Recompiled to iOS. Since this is currently early stage I am opening this for feedback and evaluating the possibility of including this upstream. This is currently not in a mergeable state and is intended for technical review, testing, and collaborative debugging.

This also requires modifications to submodules. I am already working on upstreaming the changes to plume to make it compatible with iOS (renderbag/plume#102). However, for nativefiledialog-extended, it would be unlikely that this could be upstreamed or accepted as the current implementation is very much incomplete and mobile operating systems don't seem to be within the scope of the library. How should this be handled? I have identified the following options

  1. Strip the iOS-specific NFD API implementation out of the submodule tree completely and compile it directly as part of this project's core source directories instead of linking against the actual NFD for iOS.
  2. Fork nativefiledialog-extended and include these changes
  3. Use an iOS specific implementation instead completely bypassing NFD

Known Blocking Issues

I have outlined all currently known in detail at my fork's issues page but to summarize:

Compatibility

Since the game utilizes DXT/BC compressed textures, only very recent iOS devices natively support them. Specifically according to the Metal feature set tables:

  • A17 Pro or later, A18-series or later (Apple9 Family GPUs or later)
  • Apple M-series chips (M1, M2, M3, M4 or newer)

This corresponds to:

  • iPhone 15 Pro / 15 Pro Max and newer
  • iPhone 16 / 16 Plus / 16 Pro / 16 Pro Max / 16e and newer
  • iPad mini (A17 Pro) and newer
  • iPad Pro models with M-series chips (M1 / 11-inch 3rd gen / 12.9-inch 5th gen or newer)
  • iPad Air models with M-series chips (M1 / 5th gen or newer)

The possibility of transcoding textures at installation time to another format like ASTC to improve compatibility could be considered in the future, but this adds significant complexity.

@Markos-Th09

Copy link
Copy Markdown
Author

Will it have controller support, Xbox, Razer Kishi, GameSir?

Yes, the only way to play this currently is using either by connecting a controller or keyboard and mouse to your device.

@Markos-Th09

Markos-Th09 commented Jul 22, 2026 •

Copy link
Copy Markdown
Author

I’m happy to announce the first playable release of this port intended for testing. You can download the IPA from the releases page of my fork. Anyone is welcome to report bugs and issues either here or in the issues page of the fork.

You WILL need a controller or mouse/keyboard to play this.

@ghost

ghost commented Jul 22, 2026

Copy link
Copy Markdown

Which one do I download? I'm new here

@Markos-Th09

Copy link
Copy Markdown
Author

Which one do I download? I'm new here

You can download Unleashed.ipa from here. Please note this is early stage so expect to find issues.
https://github.com/Markos-Th09/UnleashedRecomp/releases/tag/v1.0.3-beta%2Bios

@Markos-Th09

Copy link
Copy Markdown
Author

Do you need a pc to get this working?

Have you sideloaded anything before? If you already know how to do that you don’t need a pc. In order not to fill this thread with unrelated things, you can message me on discord (MarkosTh09) for assistance or open a discussion in my fork.

@ghost

ghost commented Jul 22, 2026

Copy link
Copy Markdown

Didn't I say I was new? Okay then lol

Markos-Th09 and others added 4 commits July 23, 2026 14:00
Co-authored-by: Isaac Marovitz <isaacryu@icloud.com>
…mitives

Fixes synchronization issues observed only in iOS
Use main.cpp as the sole application entry point via SDL_main.h
and remove UnleashedRecompIOSLauncher to avoid duplicated build
logic.
@yorgunkral31

yorgunkral31 commented Sep 20, 2026 •

Copy link
Copy Markdown

Tested this branch on real hardware — iPhone 16 Pro (A18 Pro), built with Xcode 27 on an M4 Pro, running the native Metal backend your branch integrates. Sharing everything we hit, in order, with fixes on this branch: https://github.com/yorgunkral31/UnleashedRecomp/tree/ios

Result: the game boots to the title screen on device with native Metal, and is stable enough at the menu that the idle attract intro plays. Repeat-launch stability testing is in progress. Update: repeated cold launches all reached the title screen cleanly — the boot deadlock is resolved by (3) below.

Findings & fixes

  1. CMake configure fails on iOS — NFD requires AppKit (APPKIT_LIBRARY NOTFOUND). Per your open question in the PR description, went with option 3-ish: gated nativefiledialog-extended out of iOS builds entirely and stubbed the installer's picker with a message pointing at the Files app flow (UIFileSharingEnabled is already set): yorgunkral31/UnleashedRecomp@098546e

  2. plume's Metal backend crashes on iOS hardware with -[AGXG17PDevice location]: unrecognized selector — MTLDevice.location and recommendedMaxWorkingSetSize are macOS-only selectors. Guards + iOS fallbacks: Guard macOS-only MTLDevice selectors for iOS renderbag/plume#115 (I believe this was the first time the native Metal path ran on iOS hardware — it works!)

  3. The conservative guest sync implementation is dormant: nothing defines UNLEASHED_RECOMP_IOS_LAUNCHER anymore. Since the separate iOS launcher target was removed, no build path defines the macro, so iOS builds silently compile the atomic-wait paths — and we reproduced exactly the boot deadlocks from Synchronization bugs in RtlEnterCriticalSection/RtlLeaveCriticalSection Markos-Th09/UnleashedRecomp-iOS#1: a captured crash report shows 23 threads (main thread included) parked in Event::Wait, then the scene-update watchdog (0x8BADF00D) kills the app when it's backgrounded. Notably this reproduced on the native Metal backend, so the deadlock is backend-independent. One-line re-enable: yorgunkral31/UnleashedRecomp@f7de133 — after this, boots have been clean. Happy to share the .ips files.

  4. Xcode 27 / iOS 27 SDK note: builds with the current SDK trap at launch (EXC_BREAKPOINT in __UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption) because SDL2 uses the legacy app-delegate lifecycle. Workaround we used: vtool -set-build-version ios 16.4 18.5 on the binary + re-sign. The real fix is UIScene adoption. Your older-SDK builds won't see this, but anyone building with current Xcode will hit it immediately.

  5. Minor: MinimumOSVersion ends up empty in the built app's Info.plist (the template variable doesn't get filled); we set it manually before signing.

  6. Still open on our side: two newRenderPipelineState failures at boot — Cannot convert attribute from MTLAttributeFormatUInt to float4 (iOS Metal is stricter than macOS about vertex attribute conversion). The game runs regardless; investigating which draws are affected.

Happy to turn any of this into PRs against your branch in whatever shape is useful, and to run more on-device tests — this is a fantastic base. (Credit also to @squidbus for the Metal backend.)

@yorgunkral31

yorgunkral31 commented Sep 21, 2026 •

Copy link
Copy Markdown

Update — the rendering corruption is root-caused and fixed. Finding 6 turned out to be much bigger than two pipelines: in the cross-compiling (iOS) path, the host XenosRecomp is built without the UNLEASHED_RECOMP define. The native path sets it via target_compile_definitions, but the host-tool configure only passes -DUNLEASHED_RECOMP=ON, a cache variable XenosRecomp's CMakeLists never consumes. The host tool therefore compiles with all game-specific shader translation logic silently dropped (including the -DUNLEASHED_RECOMP flag it forwards to DXC/AIR compilation) — producing a ~2 MB smaller shader cache and heavily corrupted rendering on device on both Metal and MoltenVK, while macOS builds are unaffected. This may also explain rendering oddities in any IPA built through this path.

Fix (injected via CMAKE_CXX_FLAGS until the option is wired into XenosRecomp itself): yorgunkral31/UnleashedRecomp@248846a

After regenerating the shader cache with the define, rendering is fully correct on device — and the two newRenderPipelineState failures from finding 6 are gone as well (same root cause: the generic shaders declared mismatched vertex input types).

Current state on iPhone 16 Pro (A18 Pro), native Metal: boots reliably, renders correctly, 35–60 FPS at default settings. Genuinely playable. Offer stands to split any of this into PRs in whatever shape works for you.

@yorgunkral31

yorgunkral31 commented Sep 21, 2026 •

Copy link
Copy Markdown

Final status after extended play sessions on the iPhone 16 Pro (day + night stages, on charger, DualShock 4 over Bluetooth):

  • Zero hangs, crashes, or stutters across the sessions, including moving through menus between stages. The only FPS dips happen in effect-heavy moments (e.g. Werehog object-smashing debris); otherwise it holds the 60 FPS cap steadily.
  • DualShock 4 works out of the box over Bluetooth — and notably, the macOS controller hot-plug crash from Mac Port crashing when controller is plugged in #1771 did not reproduce on iOS.
  • One more small find: the in-game 120 FPS setting has no effect on iPhones because CADisableMinimumFrameDurationOnPhone is missing from the Info.plist template, so iOS clamps third-party Metal apps to 60 Hz even on ProMotion displays. Fix: yorgunkral31/UnleashedRecomp@1ea0850

With the four fixes on our branch, this is now genuinely playable end to end with the native Metal backend. Happy to help get any of it landed here.

@yorgunkral31

Copy link
Copy Markdown

One more: 120 FPS now works on iPhone. Three things are needed together:

  1. The in-game FPS setting set above 60
  2. CADisableMinimumFrameDurationOnPhone in the Info.plist template (yorgunkral31/UnleashedRecomp@1ea0850) — without it iOS clamps third-party Metal apps to 60 Hz on ProMotion displays
  3. An active CADisplayLink with a preferredFrameRateRange requesting the panel's full range (yorgunkral31/plume@c7e2c32) — iOS never raises a ProMotion panel above 60 Hz on its own, and a Metal layer blocked on the 60 Hz vsync can't escape it by presenting faster; an idle display link requesting the range breaks the chicken-and-egg

Confirmed running above 60 on the iPhone 16 Pro. The plume commit (plus a small null-PSO draw safety guard, yorgunkral31/plume@b828441) probably belongs with the iOS support work in renderbag/plume#102 — feel free to fold both in.

@yorgunkral31

Copy link
Copy Markdown

For anyone wanting to try this on device: an up-to-date experimental IPA built from the ios branch with all the fixes above (stable boot, corrected shaders, native Metal, up to 120 FPS on ProMotion) is available here — https://github.com/yorgunkral31/UnleashedRecomp/releases/tag/ios-20260921. Sideload-ready, no game files included; first-time setup currently needs a desktop installation to copy game data from (details in the release notes). All credit to @Markos-Th09 and @squidbus for the foundations.

@Markos-Th09

Markos-Th09 commented Sep 21, 2026 •

Copy link
Copy Markdown
Author

@yorgunkral31 I appreciate the effort you have put into continuing as I did not have the time to continue this. How far along the game have you tested specifically? Although I did not post any updates here, it was stable enough to play to the first level. I experienced issues and new types of deadlocks when I reached level 2 before, so until a full playthrough is completed we can't be confident the deadlock issues are resolved.

As far as NFDe is concerned I have my own implementation of it for iOS, however I have not published a fork of it because it’s somewhat incomplete and its semantics are not really generally correct due to how the iOS file model works, especially protected resource access, which is incompatible with the current NFDe API. However, it works well enough for this purpose.

@yorgunkral31

Copy link
Copy Markdown

Thanks! To be precise about test coverage: so far it's early-game only — the opening day and night stages plus hub/menu navigation on the iPhone 16 Pro (multiple sessions, cold relaunches, DualShock over Bluetooth). Zero deadlocks, hangs or stutters in those sessions — but no full playthrough yet, so your level-2 caution absolutely stands. We'll keep playing further and report anything that surfaces.

One clarification that may explain the difference vs your experience: the current branch head builds without the conservative sync implementation entirely — the UNLEASHED_RECOMP_IOS_LAUNCHER define was orphaned when the separate launcher target was removed, so as-is the branch compiles the atomic-wait paths and deadlocks at boot. Our fix just re-enables what you already wrote. The new deadlock types you saw at level 2 (with your sync active) are therefore likely still out there — that's the next thing we'll be hunting on a longer playthrough.

And good to hear you have an NFD iOS implementation — our gate/stub was purely to unblock configure, so if you publish yours in any form we'd be happy to test it on device and help get the on-device installer working.

@yorgunkral31

Copy link
Copy Markdown

@Markos-Th09 To make the playthrough testing as useful as possible — which specific spots would you prioritize?

  • Where exactly did the level-2 deadlocks hit for you: which stage (day or night act?), and did they trigger mid-gameplay, during loads, or on stage exit? Consistent or random?
  • Any other known hot spots worth hammering: specific cutscenes, hub/town areas, boss loads, long sessions, returning to the main menu after extended play?
  • Anything you suspect is timing/thermal related (backgrounding, device heat, etc.)?

Happy to run targeted repro attempts on the iPhone 16 Pro and pull crash reports / full thread dumps for whatever you point at.

@Markos-Th09

Markos-Th09 commented Sep 21, 2026 •

Copy link
Copy Markdown
Author
  • Where exactly did the level-2 deadlocks hit for you: which stage (day or night act?), and did they trigger mid-gameplay, during loads, or on stage exit? Consistent or random?

It's a been a while so I don't remember exactly and I will have to verify it again. It wasn't very consistent. Could you please state exactly which levels you have completed sucessfully? I had just completed Act 1 and the issues occurred when I tried to go into Act 2. I did not complete any night acts.

@anteyeintelligence

anteyeintelligence commented Sep 22, 2026 •

Copy link
Copy Markdown

For anyone wanting to try this on device: an up-to-date experimental IPA built from the ios branch with all the fixes above (stable boot, corrected shaders, native Metal, up to 120 FPS on ProMotion) is available here — https://github.com/yorgunkral31/UnleashedRecomp/releases/tag/ios-20260921. Sideload-ready, no game files included; first-time setup currently needs a desktop installation to copy game data from (details in the release notes). All credit to @Markos-Th09 and @squidbus for the foundations.

Doesn't launch on iPad Pro M4 8GB iPadOS 26.6.2. https://github.com/Markos-Th09/UnleashedRecomp/releases/tag/v1.0.3-beta%2Bios This build launches, works until the end of the opening cutscene and crashes during the loading screen for the first level.

@yorgunkral31

Copy link
Copy Markdown

@anteyeintelligence Thanks for testing — first iPad report! To narrow it down, could you share:

  1. The crash log, if one exists: Settings → Privacy & Security → Analytics & Improvements → Analytics Data, look for entries named Unleashed Recompiled iOS-....ips and paste/attach the newest one (or the termination section of it).
  2. Which sideloading tool you used (AltStore / SideStore / Sideloadly / other), and whether the app icon appears and bounces/closes instantly, or never gets past install.
  3. Whether it's a free Apple ID or a paid developer account signing it.

The iPhone 16 Pro runs this exact IPA fine, so an iPad-specific launch failure is very interesting — the crash log should tell us immediately whether it's a signing/entitlement issue or something in the port itself.

@yorgunkral31

Copy link
Copy Markdown

@Markos-Th09 Exact list so far, all on the iPhone 16 Pro running our ios branch build:

Completed — zero deadlocks, hangs or crashes:

  • Windmill Isle (Apotos) — Day & Night
  • Savannah Citadel (Mazuri) — Day & Night
  • Dragon Road (Chun-nan) — Day & Night
  • Cool Edge (Holoska) — Day & Night
  • Rooftop Run (Spagonia) — Night

Most of that was one continuous ~48-minute session (plus earlier shorter ones), including all the loads, cutscenes, world-map/hub transitions and plenty of menu round-trips in between — the infinite-load-on-returning-to-menu from your fork's issue #1 hasn't shown up either. Only perf note: FPS dips during heavy combat / mass object destruction, more noticeable in day stages than night ones.

If by "going into Act 2" you mean the secondary/extra acts rather than the main story acts, we haven't specifically targeted those yet — that's next on the list to hit your exact repro point, along with Spagonia Day, the later continents and eventually Eggmanland as the stress test.

@Markos-Th09

Markos-Th09 commented Sep 22, 2026 •

Copy link
Copy Markdown
Author

@yorgunkral31 I have been able to reproduce your success so I will proceed to merge some of your changes to my branch. In the meanwhile, the focus should be shifted to resolving the very important suspend issue. Although I forgot to write a proper bug report for it on my repo, essentially when the app is suspended the swapchain will be invalidated causing all sorts of issues and crashes upon resumption. I had attempted many different methods to fix this to no avail.

@yorgunkral31

Copy link
Copy Markdown

That's great to hear — happy for any of it to land in your branch, and feel free to reshape the commits however you like.

On the suspend issue: we've been seeing the same class of problem from the other side. Backgrounding the app reliably ends in a 0x8BADF00D scene-update watchdog kill (we have full 39-thread dumps from those), and iOS also kills any app that keeps submitting GPU work while suspended — so the swapchain invalidation you're describing and our watchdog deaths look like two faces of the same missing piece: the render/guest loop never actually pauses on suspend.

We'll take a run at it. Rough plan: handle SDL's WILLENTERBACKGROUND/WILLENTERFOREGROUND events, gate drawable acquisition and presents in plume's Metal swap chain while inactive (with a GPU idle on the way out), and recreate the drawable chain on resume. Will report back with findings and thread dumps either way.

iOS caps third-party Metal apps at 60 Hz on iPhones unless
CADisableMinimumFrameDurationOnPhone is declared, so the in-game 120 FPS
setting had no effect on ProMotion displays.
@yorgunkral31

Copy link
Copy Markdown

@Markos-Th09 Glad to see the fixes landing! One heads-up while you're merging, because the biggest rendering fix is easy to miss: -DUNLEASHED_RECOMP=ON in the host-tool configure isn't consumed by XenosRecomp's CMakeLists, so the host tool still builds without the define and silently drops all the game-specific shader logic — the result is a ~2 MB smaller shader cache and the heavily corrupted rendering I hit on device on both backends. I worked around it by injecting the flag directly (yorgunkral31/UnleashedRecomp@248846a); wiring a proper option through XenosRecomp's CMakeLists would be the cleaner fix. Without one of the two, device builds will keep rendering broken geometry even with everything else merged.

@Markos-Th09

Markos-Th09 commented Sep 23, 2026 •

Copy link
Copy Markdown
Author

@Markos-Th09 Glad to see the fixes landing! One heads-up while you're merging, because the biggest rendering fix is easy to miss: -DUNLEASHED_RECOMP=ON in the host-tool configure isn't consumed by XenosRecomp's CMakeLists, so the host tool still builds without the define and silently drops all the game-specific shader logic — the result is a ~2 MB smaller shader cache and the heavily corrupted rendering I hit on device on both backends. I worked around it by injecting the flag directly (yorgunkral31/UnleashedRecomp@248846a); wiring a proper option through XenosRecomp's CMakeLists would be the cleaner fix. Without one of the two, device builds will keep rendering broken geometry even with everything else merged.

This has already been taken care of long before you even started, so I don't need your input on that. I guess this could be more explicit by pointing the submodules to forks of all these although I avoided this on this branch because those changes will eventually need to be upstreamed.

@Markos-Th09

Copy link
Copy Markdown
Author

New build compiled from the latest changes: https://github.com/Markos-Th09/UnleashedRecomp/releases/tag/v1.0.3-beta.ios%2B06026b1

@anteyeintelligence

anteyeintelligence commented Sep 24, 2026 •

Copy link
Copy Markdown

On this IPA: https://github.com/Markos-Th09/UnleashedRecomp/releases/tag/v1.0.3-beta.ios%2B06026b1

The game does not crash on iPad Pro M4 8GB 13-inch, launched in LiveContainer, iPadOS 26.6.2. Heres what I learned.

  • No bugs in the game itself so far. Graphically, the game presents exactly as it should.

  • Perfectly flat 60fps frametime is possible. Initially, I tried 75% resolution scale on a 13-inch iPad Pro M4, just over ~1080p, with maximized shadow resolution, transparent AA enabled, and 4x MSAA (the maximum on Apple9 GPU equipped SOC's), I was able to achieve a perfectly flat 60fps frametime graph. I lowered 3D resolution as I thought the game was too intensive. I was getting an inconsistent 50fps with drops to 40. Although, I found a fix for this:

  • For a perfect 60fps, the "Limit Frame Rate" option in System Settings must be enabled. Otherwise, in less-intensive sections (like the Apotos Hub) the result will be an inconsistent 59-60fps with drops to 58 (imperfect frametimes) instead of a perfectly flat frametime graph. In the opening stage, I got an inconsistent ~50-55fps with some drops to the 40s. With the setting enabled, I get a perfectly flat 60fps at 100% resolution scale with maximized graphical settings.

  • This has not been tested very much. Only in the opening stage, the hub of Apotos and a bit of Stage 1 daytime. I am not sure about performance in the opening stage or subsequent areas. When I have a controller, I'll test further.

  • Real-time cutscenes seem to be perfect, but the 2/3 frame-rate bug (see below) appears frequently so I haven't tested an entire cutscene so far.

  • So far, the app crashes all of the time with actions like minimizing the app and opening it again, opening and closing the control center, etc.

Additionally, I have discovered a bug where taking a screenshot (this most likely also happens when going to the control center or any other system interruption, if the game doesn't crash) drops the frame-rate limit to 40fps. This also applies to in-game interruptions, which seems to be pretty arbitrary here. It doesn't trigger when pausing or doing anything specific, just randomly. Camera cuts in cutscenes seem to be a consistent cause. The opening real-time cutscene was perfectly flat 60fps, then the frame-rate cap randomly switched to 40fps, then back to 60fps when I skipped it. This is a known issue on ProMotion iPads, where in many games, an interruption sets the frame-rate limit to 2/3 of the refresh rate. I don't recall this ever presenting in a 60fps game, it's mainly in 120fps games. The Sonic Utopia iOS port is the only sideloaded game that I know of where this isn't present, and most 60fps sideloaded games don't have this at all.

If anything in the setup process is broken, I wouldn't know, I had the game setup from before this update and I'm not willing to reset my app data and risk breaking it.

DevZer0D4Y pushed a commit to DevZer0D4Y/UnleashedRecompilediOS that referenced this pull request Sep 25, 2026
iOS limits apps to 60 Hz on iPhones with ProMotion displays unless they
declare CADisableMinimumFrameDurationOnPhone, so frame rates above 60 FPS
had no effect there. iPads don't need it.

Based on upstream pull request hedge-dev#1767 by Çağan Özcan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBtL1JSminRPnypJpP2HHW
DevZer0D4Y added a commit to DevZer0D4Y/UnleashedRecompilediOS that referenced this pull request Sep 25, 2026
iOS limits apps to 60 Hz on iPhones with ProMotion displays unless they
declare CADisableMinimumFrameDurationOnPhone, so frame rates above 60 FPS
had no effect there. iPads don't need it.

Based on upstream pull request hedge-dev#1767 by Çağan Özcan.
DevZer0D4Y added a commit to DevZer0D4Y/UnleashedRecompilediOS that referenced this pull request Sep 25, 2026
iOS limits apps to 60 Hz on iPhones with ProMotion displays unless they
declare CADisableMinimumFrameDurationOnPhone, so frame rates above 60 FPS
had no effect there. iPads don't need it.

Based on upstream pull request hedge-dev#1767 by Çağan Özcan.

This branch is waiting to be deployed

1 waiting deployment
external — b03baccb Waiting Sep 26, 2026 by Markos-Th09 via authorize #453
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants