Files
gen1recomp/mobile/ios/README.md
T
Myles Resnick 902f0d73d4 iOS: native document-picker + Apple Health bridges, working device builds
Makes the iOS build a first-class citizen: ROM/mod/save import through
the system document picker (the README's missing "UIDocumentPicker
handoff"), Files-app drop-in support, and an opt-in Apple Health
step-sync seam consumed by a new gallery mod (Pokewalker).

Native layer (mobile/ios/native/, wired by mobile/ios/patch_love_src.py
on every build, so the fetched love-src tree stays pristine + re-patchable):
- GRPickerBridge.swift: love.system.pickFile("rom"|"mod"|"sav") and
  love.system.createFile on iOS with the same contract as love-android's
  SAF picker (picked_rom.gb / picked_mod.zip / picked_save.sav /
  export_done.flag in the save dir). Reached from wrap_System.cpp via the
  ObjC runtime, so liblove needs no Swift interop.
- GRBootstrap.m: sweeps .gb/.gbc/.zip/.sav dropped in Documents (Files
  app / Finder sharing) into the save dir on every activation;
  UIFileSharingEnabled + LSSupportsOpeningDocumentsInPlace in the plist
  overlay. Drop a ROM, open the app, it imports with zero taps.
- GRHealthBridge.swift: love.system.syncHealthSteps() -> read-only
  HealthKit step query anchored to the last sync, delivered as
  steps_pending.json (merge-not-overwrite). HealthKit entitlement +
  usage description included.

Lua:
- RomImporter: iOS rides the Android mobile flows; a 0.5s poll consumes
  picker deliveries (iOS pickers are in-process modals, so the Android
  refocus rescan never fires); failed pick copies surface as an
  on-screen notice via pick_error.txt.
- main.lua: on iOS, stop forwarding touchpressed to the Importer - LOVE
  already synthesizes a mousepressed for the primary touch, and the
  resulting same-frame double-present made the document picker
  auto-dismiss with zero documents (silent import failure).
- mods/pokewalker: opt-in Pokewalker mod (manifest v2, MECHANIC,
  permissions declared, mod.card, CHANGELOG, headless test suite 9/9,
  modkit validate --base imported + lint clean). Fused into iOS
  game.love only; loads dormant anywhere without the bridge.

Build (scripts/build_ios.sh):
- Fix Xcode 26: the global PRODUCT_NAME override also renamed liblove.a
  and broke the app link; the app bundle is renamed after the build
  instead.
- Fix nondeterministic pack failures: grep -q + pipefail races SIGPIPE
  on the game.love content checks.
- Simulator builds sign ad-hoc so entitlements embed (HealthKit works in
  the simulator).
- Device builds: signing team auto-detected from the keychain,
  CODE_SIGN_STYLE=Automatic + -allowProvisioningUpdates for CLI-only
  provisioning, per-team derived bundle ID (explicit App IDs are
  globally unique, so third parties can't sign the project default),
  gitignored mobile/ios/bundle_id.local pin, and --install to push to a
  connected iPhone.
- docs/ios-install.md: a zero-knowledge walkthrough from bare Mac to
  playing on an iPhone.

Backward compatibility: no behavior change on desktop or Android. The
new love.system functions exist only under LOVE_IOS; RomImporter's
mobile flag simply includes iOS alongside Android; the main.lua change
is iOS-gated; the Pokewalker mod is packed only by the iOS build script
and its option defaults off.

Verified on an iPhone 17 Pro simulator and an iPhone 16 Pro device:
scripted ROM import to title screen, Files-drop zero-tap import,
picker-driven mod install and save import/export, HealthKit permission
sheet + step credit (4000 steps -> +200 EXP at the default rate through
the engine growth curve).
2026-07-30 09:46:48 -04:00

152 lines
6.1 KiB
Markdown

# iOS build (LÖVE 11.5)
> **Native ROM/mod/save import.** The iOS build ships a Swift
> document-picker bridge (`native/GRPickerBridge.swift` + `GRBootstrap.m`)
> that `patch_love_src.py` wires into the LÖVE tree on every build:
>
> - `love.system.pickFile("rom"|"mod"|"sav")` and `love.system.createFile`
> are exposed to Lua on iOS (same contract as love-android's SAF picker:
> picks land in the save dir as `picked_rom.gb` / `picked_mod.zip` /
> `picked_save.sav`; exports signal via `export_done.flag`).
> - The Info.plist overlay enables `UIFileSharingEnabled` +
> `LSSupportsOpeningDocumentsInPlace`, and `GRBootstrap.m` sweeps
> `.gb/.gbc/.zip/.sav` files dropped in Documents (Files app / Finder)
> into the LÖVE save dir on every activation — drop a ROM, open the app,
> and it imports with no taps.
> - `src/import/RomImporter.lua` treats iOS as a mobile platform and polls
> for picker results (iOS pickers are in-process modals, so Android's
> refocus rescan never fires).
>
> The note below about a missing "UIDocumentPicker handoff" is
> resolved by this bridge.
macOS + Xcode only. Pins the official **LÖVE 11.5** iOS Xcode tree
(`love-11.5-ios-source.zip` from [love2d/love releases](https://github.com/love2d/love/releases/tag/11.5)),
matching `conf.lua`'s `t.version = "11.5"`.
There is no separate `love2d/love-ios` GitHub repo for 11.5; the release zip
**is** the vendored iOS project (Xcode project under
`love-src/platform/xcode/love.xcodeproj`, target `love-ios`).
Pin file: [`LOVE_VERSION`](./LOVE_VERSION) → `11.5`.
## Quick start (simulator)
```bash
# Fetch LÖVE 11.5 iOS sources (once) + build for Simulator
scripts/build_ios.sh --fetch
```
The embedded `game.love` contains no ROM or generated game data. The current
first-boot importer has desktop file pickers only, so a production iOS release
still needs a UIDocumentPicker handoff that passes the selected ROM to LÖVE.
Default output: an unsigned Simulator `.app` under `mobile/ios/build/`
(no Apple Developer account required). A convenience copy also lands under
`dist/ios/<Config>-<sdk>/`.
Install on a booted simulator (example):
```bash
xcrun simctl install booted mobile/ios/build/Build/Products/Debug-iphonesimulator/PokemonRed.app
xcrun simctl launch booted com.theboisclub.pokemonred
```
Or open `mobile/ios/love-src/platform/xcode/love.xcodeproj` in Xcode,
select the `love-ios` target, and Run on a Simulator after
`scripts/build_ios.sh --package-only` (or a full build) has placed `game.love`.
## Device / Release
```bash
scripts/build_ios.sh --device # Debug, physical device SDK
scripts/build_ios.sh --device --release # Release configuration
```
Device builds need a signing identity and provisioning profile configured in
Xcode (or via `DEVELOPMENT_TEAM` / `CODE_SIGN_IDENTITY` env vars). This repo
does **not** store certificates, profiles, or App Store Connect secrets.
Manual out-of-band steps:
1. Apple Developer account + App ID for `com.theboisclub.pokemonred`
2. Development or Distribution certificate + provisioning profile
3. In Xcode: open `love.xcodeproj` → target `love-ios` → Signing & Capabilities
→ select your Team (or set `DEVELOPMENT_TEAM=XXXXXXXXXX` when invoking
`scripts/build_ios.sh --device`)
4. Archive / export an `.ipa` from Xcode Organizer for TestFlight / Ad Hoc
## Layout
| Path | Role |
|------|------|
| `LOVE_VERSION` | Engine pin (`11.5`) |
| `overlays/love-ios.plist` | Portrait-only Info.plist + display name **Pokemon Red** (copied over the upstream plist every build) |
| `love-src/` | Downloaded `love-11.5-ios-source` tree (**gitignored**, do not commit) |
| `cache/` | Downloaded zips (**gitignored**) |
| `build/` | `xcodebuild` derived data (**gitignored**) |
Game payload lands at:
`love-src/platform/xcode/ios/resources/game.love`
and is fused into the built `.app` (LÖVE auto-runs any bundled `*.love`).
## Apple libraries dependency
The official `love-11.5-ios-source.zip` already ships prebuilt iOS
xcframeworks under `platform/xcode/ios/libraries/` (SDL2, LuaJIT, freetype,
ogg, vorbis, theora, modplug).
If that folder is missing or incomplete (e.g. you cloned sources without
libs), download the matching prebuilts and install them:
```bash
curl -fL -o mobile/ios/cache/love-11.5-apple-libraries.zip \
https://github.com/love2d/love/releases/download/11.5/love-11.5-apple-libraries.zip
unzip -q mobile/ios/cache/love-11.5-apple-libraries.zip -d mobile/ios/cache
rm -rf mobile/ios/love-src/platform/xcode/ios/libraries
cp -R mobile/ios/cache/love-apple-dependencies/iOS/libraries \
mobile/ios/love-src/platform/xcode/ios/libraries
```
`scripts/build_ios.sh` checks for `libraries/SDL2.xcframework` and fails with
these instructions if it is absent.
Upstream also documents
[love-apple-dependencies](https://github.com/love2d/love-apple-dependencies)
as an alternate source of the same libraries.
## App identity
| Field | Value |
|-------|--------|
| Display name | Pokemon Red |
| `PRODUCT_NAME` | PokemonRed |
| Bundle ID | `com.theboisclub.pokemonred` |
| Orientations | Portrait only (`UIInterfaceOrientationPortrait`) |
Overrides are applied by the build script (`xcodebuild` settings + plist overlay)
so refreshing `love-src/` does not lose branding.
## Flags (`scripts/build_ios.sh`)
| Flag | Meaning |
|------|---------|
| *(default)* | Simulator, Debug, no signing |
| `--fetch` | Download/extract `love-11.5-ios-source.zip` if `love-src/` is missing |
| `--device` | Build against `iphoneos` instead of `iphonesimulator` |
| `--release` | `Release` configuration instead of `Debug` |
| `--package-only` | Zip `game.love` + apply plist overlay; skip `xcodebuild` |
Also: `scripts/build.sh ios` delegates here (`--release` is forwarded).
## Preconditions
- macOS (Darwin) with Xcode + `xcodebuild` on `PATH`
- iOS platform installed in Xcode (Settings → Platforms). `xcodebuild -showsdks`
should list `iphonesimulator` / `iphoneos`. A partial install can fail IB/xib
compiles with `iOS … Platform Not Installed` even when the SDK name appears.
- `love-src/` present (`--fetch` or manual unzip of `love-11.5-ios-source.zip`)
- iOS libraries under `love-src/platform/xcode/ios/libraries/` (see above)