iOS XCFrameworks for SRB2
  • C 77%
  • C++ 13.2%
  • Objective-C 2.3%
  • Shell 1.6%
  • CMake 1.6%
  • Other 3.7%
Find a file
PAS 9e7b0210f6 Stop embedding headers in xcframeworks (dead weight, never used)
make_xcframework's -headers flag baked a full copy of each library's
headers into EVERY architecture slice (device AND simulator) of the
xcframework itself, on top of the separate loose install/<tag>/<lib>/
include copy this script already produces and that both consuming
repos actually reference in their CMake setup. Neither repo's CMake
ever points at headers embedded in an xcframework -- both explicitly
set their own include dirs at the loose copy -- so the embedded ones
were pure duplication, multiplying an already-duplicated-across-repos
header set by another 2x for nothing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 23:08:31 -04:00
sources iOS static libs: switch to plain SDL2, backport UIScene support 2026-09-12 21:57:22 -04:00
toolchain iOS static libs: SDL3 + sdl2-compat working build 2026-09-12 21:55:59 -04:00
.gitignore iOS static libs: SDL3 + sdl2-compat working build 2026-09-12 21:55:59 -04:00
build.sh Stop embedding headers in xcframeworks (dead weight, never used) 2026-09-21 23:08:31 -04:00
README.md Update SDL2 backport verification status 2026-09-12 22:07:43 -04:00

SRB2 Legacy iOS static libraries

Build area for the static libraries the iOS port needs (see IOS_PORT.md in the srb2-legacy repo). Built successfully with Xcode 27 (iOS 27 SDK) — output/ contains all static libraries as .xcframeworks, including SDL2 and SDL2main.

This directory is self-contained: all library sources are vendored locally under sources/ as fresh upstream downloads, not copies of the (older) versions vendored inside the srb2-legacy repo itself.

Status

Library Version Source
zlib 1.3.2 github.com/madler/zlib
libpng 1.6.58 (stock, no APNG patch) github.com/pnggroup/libpng
libgme 0.6.5 github.com/libgme/game-music-emu
SDL2 2.32.10 (patched — see below) github.com/libsdl-org/SDL
SDL2_mixer 2.8.2 (+ vendored ogg/vorbis/opus/opusfile/libxmp/wavpack) github.com/libsdl-org/SDL_mixer
libopenmpt 0.8.9 github.com/OpenMPT/openmpt
  • toolchain/ios.toolchain.cmake — the leetal/ios-cmake toolchain file used to cross-compile the CMake-based libraries with CMake's Xcode generator.
  • build.sh — builds everything for both arm64 device and arm64 simulator, and packs the static libs into an .xcframework under output/.

SDL2: backported UIScene life cycle support

Plain SDL2's iOS backend only ever implemented the classic pre-iOS-13 UIApplicationDelegate lifecycle. Starting with the iOS 27 SDK/runtime, Apple hard-crashes any UIKit app at launch (UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption) if it hasn't adopted the UIScene lifecycle. SDL3 fixed this properly (3.4.0+); SDL2 itself never will, and won't going forward — SDL2 is in maintenance mode upstream.

An earlier version of this build switched the whole stack to SDL3 + sdl2-compat (libsdl-org's shim exposing the classic SDL2 API/ABI on top of the real SDL3 engine) to pick up the fix for free. It worked (confirmed full rendered gameplay on an iOS 27 simulator), but sdl2-compat has no static-link mode on any platform — it always dlopen()s the real SDL3 library at runtime — and its iOS support turned out to be quite undertested upstream (six distinct pre-existing bugs needed patching just to get it building and running). That's a lot of permanent architectural complexity (dylib embedding, ad-hoc codesigning at build time, an @rpath link setup, a stack of upstream patches to carry forever) to buy one bugfix. Reverted as not worth it.

Instead, SDL3's actual fix is backported directly onto our vendored plain SDL2 2.32.10, keeping everything statically linked like every other dependency here. The fix is upstream SDL3 commit b46e26e65a ("Added support for the UIScene life cycle on Apple platforms", fixes libsdl-org/SDL#12680), plus its small deprecation-warning-suppression follow-up 016a1b87a. Patched into sources/SDL2-2.32.10/src/video/uikit/{SDL_uikitappdelegate.h,.m,SDL_uikitwindow.m} — grep those files for "Backported from SDL3" to find every patch site.

The port is adapted, not a verbatim copy of the SDL3 diff:

  • SDL2 naming conventions are preserved (SDL_bool/SDL_TRUE/SDL_FALSE instead of bool/true/false, SDL_iPhoneSetEventPump instead of SDL_SetiOSEventPump, TARGET_OS_TV instead of SDL_PLATFORM_TVOS, the 2-argument SDL_SendDropFile(NULL, path) instead of SDL3's 3-argument version).
  • The new SDLUIKitSceneDelegate (used on iOS/tvOS 13+, alongside the existing SDLUIKitDelegate as a pre-13 fallback) deliberately does not implement scene:didActivate:/sceneWillResignActive:/ sceneWillEnterForeground:/sceneDidEnterBackground:: SDL2's existing SDL_uikitevents.m already observes UIApplicationDidBecomeActiveNotification and friends via NSNotificationCenter, which fire app-wide regardless of UIScene adoption. Adding scene-level equivalents would just call SDL_OnApplication*() a second time per transition.
  • The new scene delegate registers the SDL_HINT_IDLE_TIMER_DISABLED hint callback (SDL_AddHintCallback) in willConnectToSession, matching what the old SDLUIKitDelegate already does in application:didFinishLaunchingWithOptions: — omitted from a literal port of the SDL3 diff, but needed here to avoid silently regressing that feature once scenes are adopted.
  • SDL_uikitwindow.m gained a GetActiveWindowScene() helper and now creates the UIWindow via initWithWindowScene: when a scene is available (falling back to the old initWithFrame: path otherwise) — a UIWindow must be tied to a scene once the app has adopted one, or it won't actually display anything.

Verified working end-to-end: rebuilt and retested against an iPhone 18 Pro (iOS 27) simulator — boots cleanly to full initialization, WAD/asset loading, and gameplay (server start, level load), no UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption crash.

libpng: no APNG patch, by instruction

This is stock libpng 1.6.58 — no APNG patch applied. Note for later, when actually wiring this into the SRB2 iOS CMake target: srb2-legacy's src/apng.c calls png_set_acTL/png_get_acTL, which only exist in an APNG-patched libpng. With stock libpng those symbols don't exist, so apng.c (the in-game APNG movie/recording feature) will fail to link as-is on iOS — it'll need to be excluded from the iOS target or reworked. Regular PNG screenshot saving (the non-animated path) is unaffected.

SDL2_mixer codec configuration

build.sh sets SDL2MIXER_VENDORED=ON (build codecs from bundled source, since there's no system-installed libopus/libvorbis/libxmp/etc. to find on iOS) and SDL2MIXER_DEPS_SHARED=OFF (link them in statically):

  • OGG Vorbis → SDL2MIXER_VORBIS=VORBISFILE (standard libogg + libvorbis + libvorbisfile), per instruction — stb_vorbis (the default) wasn't sufficient.
  • MOD/tracker music → SDL2MIXER_MOD_XMP=ON (libxmp), MOD_MODPLUG=OFF.
  • FLAC and MP3 still default to SDL2_mixer's inline single-header vendored decoders (dr_flac, minimp3) — no external source needed for those.
  • Opus/Opusfile, OGG/Vorbis, libxmp, and WavPack all need real cloned source, so build.sh clones exactly those six submodules — ogg, vorbis, opus, opusfile, libxmp, wavpack — from libsdl-org's pinned forks/branches, straight into sources/SDL2_mixer-2.8.2/external/. (The release tarball ships .gitmodules but not the submodule contents.)
  • Our own separate gme.xcframework and libopenmpt.xcframework are unrelated to SDL2_mixer's own optional SDL2MIXER_GME/module options, which stay off inside SDL2_mixer itself.

libopenmpt: built via its own Makefile, not its Xcode project

libopenmpt ships a checked-in build/xcode-ios/libopenmpt.xcodeproj, but inspecting it (via its project.pbxproj, since Xcode isn't available here to open it) showed it's not what it looks like:

  • Its one target is productType = com.apple.product-type.library.dynamic — a dynamic library, not static like the four libs above.
  • ARCHS = "$(NATIVE_ARCH_ACTUAL)" — pinned to whatever the host machine's native architecture is, not properly parameterized per device/simulator.
  • No shared scheme is checked in.
  • Its ext/{ogg,vorbis,mpg123}.xcodeproj sub-projects expect upstream source manually dropped into include/ogg, include/vorbis, include/mpg123 — there's no submodule or download script that populates them (build/download_externals.sh only fetches unrelated Windows/DOS tooling: 7-Zip, Winamp/XMPlay SDKs, InnoSetup, etc.)

In short: this project is set up for a human opening it in Xcode and hand-configuring a destination, not for scripted device+simulator static builds — matching a 2020 OpenMPT forum thread describing the same manual massaging being necessary. Regenerating it properly requires premake/genie, which is its own yak-shave.

Instead, build.sh uses libopenmpt's plain GNU Makefile (CONFIG=macos as the base, since it's the closest existing config to an Apple/Clang target, already builds a static .a with DYNLINK=0), with our own cross flags applied by baking them directly into CC/CXX (resolved via xcrun --sdk iphoneos/iphonesimulator) rather than passing CFLAGS/CXXFLAGS/LDFLAGS on the make command line — GNU Make locks command-line-assigned variables, so the Makefile's own CXXFLAGS += ... (which auto-detects and adds -std=c++20/c++23) silently no-ops against them, leaving the build compiling pre-C++17 and failing on constexpr if/static_assert. Baking -isysroot and -mios-version-min/-mios-simulator-version-min into the compiler paths themselves sidesteps that precedence trap. All optional codec dependencies are disabled via libopenmpt's own NO_* flags (NO_ZLIB NO_MPG123 NO_OGG NO_VORBIS NO_VORBISFILE NO_FLAC NO_SNDFILE NO_PORTAUDIO NO_PORTAUDIOCPP NO_PULSEAUDIO NO_SDL2), giving a minimal, dependency-free static libopenmpt.a with core MOD/S3M/XM/IT playback.

Device and simulator are each built in their own fresh copy of the source tree (build/<tag>/openmpt-src/, rsynced from sources/openmpt) rather than reusing one checkout with make clean in between: the vendored plain-C objects (miniz/minimp3/stb_vorbis) aren't covered by libopenmpt's clean target, so a leftover device-platform .o survived into the simulator build (and vice versa) the first time around, producing an archive that mixed IOS and IOSSIMULATOR object files — which -create-xcframework correctly refused ("binaries with multiple platforms are not supported"). Confirmed fixed by checking vtool -show on an extracted object from each platform's final .a.

Verified working: built clean end-to-end against Xcode 27 / iOS 27 SDK.

To run

cd ~/builds/srb2-legacy-ioslibs
./build.sh

Output:

  • output/{zlib,libpng,gme,SDL2,SDL2main,SDL2_mixer,ogg,opus,opusfile,vorbis,vorbisfile,wavpack,xmp,libopenmpt}.xcframework — static libraries, each with device + simulator arm64 slices.

Already wired into srb2-legacy-ios-slopported's thirdparty/ and CMake iOS branch (see IOS_PORT.md); re-run this script and re-copy into that repo's thirdparty/ if you need to rebuild from scratch.

Note: DEPLOYMENT_TARGET in build.sh is 15.0 — Xcode 27's iOS 27 SDK only supports deployment targets from 15.0 up (13.0, used in an earlier draft of this script, is rejected at CMake's compiler-sanity-check step).

Deliberately not built

  • curl — investigated and it turned out to be dead code in srb2-legacy: SRB2_CONFIG_HAVE_CURL is referenced in src/CMakeLists.txt but never declared as an actual option (unlike HAVE_PNG/HAVE_ZLIB/HAVE_GME, which are), so that if() block never triggers, and no .c/.h file in src/ includes <curl/curl.h> or calls curl_easy_*. There's nothing in the actual game to link a curl build into. If networked HTTP is ever wanted on iOS (e.g. master-server queries), NSURLSession is the natural iOS-native replacement rather than reviving curl.