Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Play Wind Waker on iPhone Without a Mac: Build BlueWake with GitHub Actions
- A free, step-by-step walkthrough for building your own BlueWake IPA using GitHub's Apple silicon runners, with no Mac required. Written from my own experience getting it working; it is not official.
- ## Read this first
- - **Bring your own disc.** You need your own legally obtained copy of *The Legend of Zelda: The Wind Waker* for GameCube, USA version (GZLE01, revision 0). This guide includes no game files and won't point you to any.
- - **Never share your build.** The IPA contains code translated from your disc, and the ISO is the game itself. Don't post either one, and don't post your Google Drive link.
- - **Public repos expose artifacts.** Anyone signed into GitHub can download the artifacts from a public repo's runs. Download yours right away, then delete them (Step 9).
- - **Official docs come first.** BlueWake's maintainers haven't reviewed this guide. See the [BlueWake README](https://github.com/chrissotraidis/bluewake) and [Build your own BlueWake](https://github.com/chrissotraidis/bluewake/blob/main/docs/BUILD_YOUR_OWN.md). Questions about the app itself go to the project's [Discord](https://discord.gg/xwHfUD2bxW).
- ## What you need
- - An iPhone or iPad with an A13 chip or newer, running iOS/iPadOS 17 or later, with Developer Mode turned on
- - Your disc image (`.ciso`, `.iso` or `.gcm`). The app itself only imports `.iso`, which is why the workflow below converts it
- - A Google account (for Drive) and a free [GitHub account](https://github.com/signup)
- - A way to sideload an IPA, such as [AltStore](https://altstore.io), [SideStore](https://sidestore.io) or [Sideloadly](https://sideloadly.io). Most of these need a computer for first-time setup, but not a Mac
- - Patience: the first build takes roughly 6 hours of runner time, spread over a couple of runs
- ## Step 1: Upload your disc image to Google Drive
- 1. Go to [drive.google.com](https://drive.google.com), tap **New > File upload**, and choose your disc image.
- 2. When it finishes, open the file's **Share** menu and set **General access** to **Anyone with the link** (Viewer). GitHub's runner needs this to download the file without signing in. You'll turn it off again in Step 13.
- 3. Copy the link. It looks like `https://drive.google.com/file/d/FILE_ID/view?usp=sharing`. The `FILE_ID` is the long string between `/d/` and `/view`.
- 4. Build your direct download link by putting your ID into this:
- `https://drive.usercontent.google.com/download?id=FILE_ID&export=download&confirm=t`
- 5. Paste that into a browser to check that a download starts. Keep this link private.
- Notes: Google can temporarily block files that get downloaded many times, so don't share the link. The workflow below expects a CISO, and Wiimms ISO Tools should also accept a plain ISO, but I only tested a CISO.
- ## Step 2: Create a GitHub account
- Sign up at [github.com/signup](https://github.com/signup).
- ## Step 3: Create a public repository
- 1. Go to [github.com/new](https://github.com/new).
- 2. Name it anything (for example `bluewake-build`).
- 3. Choose **Public**. Public repos get free, unmetered use of standard GitHub Actions runners, including Apple silicon Macs. Private repos use a monthly quota, and macOS minutes count 10 times.
- 4. Tick **Add a README file** and tap **Create repository**.
- ## Step 4: Store your download link as a secret
- Because the repo is public, anyone can read your workflow file. Keep your Drive link out of it.
- 1. In your repo, go to **Settings > Secrets and variables > Actions**.
- 2. Tap **New repository secret**.
- 3. Name: `WIND_WAKER_URL`. Value: the direct download link from Step 1.
- 4. Tap **Add secret**.
- ## Step 5: Add the workflow file
- 1. On the repo's main page, tap **Add file > Create new file**.
- 2. In the name box, type exactly `.github/workflows/build.yml`. Typing the slashes creates the folders.
- 3. Paste the full file below.
- 4. Tap **Commit changes** and commit to the main branch.
- Tip on a phone: use the browser, not the GitHub app, and if the editor is awkward, switch to the desktop site. To replace an existing file, open it, tap the pencil icon, tap inside the text, **Select All**, and paste.
- ```yaml
- name: Build BlueWake IPA
- on:
- workflow_dispatch:
- inputs:
- minutes:
- description: "Stop the build after this many minutes (15 = test, 320 = real)"
- default: "15"
- required: true
- iso_only:
- description: "Only convert and upload the ISO (skip the build)"
- type: boolean
- default: false
- jobs:
- build:
- runs-on: macos-26
- timeout-minutes: 360
- steps:
- - name: Show runner information
- run: |
- sw_vers
- sysctl -n hw.ncpu
- sysctl -n machdep.cpu.brand_string
- sysctl -n hw.memsize
- df -h /
- - name: Download Wind Waker disc image
- env:
- WIND_WAKER_URL: ${{ secrets.WIND_WAKER_URL }}
- run: |
- set -euo pipefail
- mkdir -p "$RUNNER_TEMP/wind-waker"
- cd "$RUNNER_TEMP/wind-waker"
- curl -L --fail --retry 3 --retry-all-errors \
- -A "Mozilla/5.0" \
- -o "test build.ciso" \
- "$WIND_WAKER_URL"
- ls -lh "test build.ciso"
- file "test build.ciso"
- - name: Download Wiimms ISO Tools
- run: |
- set -euo pipefail
- cd "$RUNNER_TEMP/wind-waker"
- curl -L --fail --retry 3 --retry-all-errors \
- -o wit.tar.gz \
- "https://wit.wiimm.de/download/wit-v3.05a-r8638-mac.tar.gz"
- tar -xzf wit.tar.gz
- WIT_DIR="$(find . -maxdepth 1 -type d -name 'wit-*' -print -quit)"
- if [ -z "$WIT_DIR" ]; then
- echo "::error::Wiimms ISO Tools directory was not found."
- exit 1
- fi
- chmod +x "$WIT_DIR/bin/wit"
- "$WIT_DIR/bin/wit" --version
- - name: Convert to ISO
- run: |
- set -euo pipefail
- cd "$RUNNER_TEMP/wind-waker"
- WIT_DIR="$(find . -maxdepth 1 -type d -name 'wit-*' -print -quit)"
- "$WIT_DIR/bin/wit" FILES "test build.ciso"
- "$WIT_DIR/bin/wit" copy \
- "test build.ciso" \
- --iso \
- -d WindWaker.iso
- ls -lh WindWaker.iso
- file WindWaker.iso
- - name: Upload ISO
- if: ${{ inputs.iso_only }}
- uses: actions/upload-artifact@v4
- with:
- name: WindWaker-ISO
- path: ${{ runner.temp }}/wind-waker/WindWaker.iso
- if-no-files-found: error
- compression-level: 0
- retention-days: 1
- - name: Install build tools
- if: ${{ !inputs.iso_only }}
- run: brew install cmake ninja || true
- - name: Restore build cache
- if: ${{ !inputs.iso_only }}
- id: cache
- uses: actions/cache/restore@v4
- with:
- path: ${{ runner.temp }}/bluewake
- key: bw-build-${{ github.run_id }}-${{ github.run_attempt }}
- restore-keys: |
- bw-build-
- - name: Cache status
- if: ${{ !inputs.iso_only }}
- run: |
- echo "Exact hit: ${{ steps.cache.outputs.cache-hit }}"
- echo "Matched key: '${{ steps.cache.outputs.cache-matched-key }}'"
- if [ -d "$RUNNER_TEMP/bluewake" ]; then
- du -sh "$RUNNER_TEMP/bluewake"
- else
- echo "No restored folder (fresh start)"
- fi
- - name: Clone BlueWake
- if: ${{ !inputs.iso_only && steps.cache.outputs.cache-matched-key == '' }}
- run: |
- set -euo pipefail
- git clone --depth 1 \
- https://github.com/chrissotraidis/bluewake.git \
- "$RUNNER_TEMP/bluewake"
- - name: Show BlueWake commit
- if: ${{ !inputs.iso_only }}
- run: |
- cd "$RUNNER_TEMP/bluewake"
- git log -1 --format='%H %s'
- - name: Build IPA (with watchdog)
- if: ${{ !inputs.iso_only }}
- id: build
- env:
- LIMIT_MIN: ${{ inputs.minutes }}
- run: |
- set -u
- set -m
- cd "$RUNNER_TEMP/bluewake"
- chmod +x scripts/builder/build.sh
- FLAGS=(--ipa "$RUNNER_TEMP/BlueWake.ipa" --no-train)
- if grep -q -- '--jobs' scripts/builder/build.sh; then
- FLAGS+=(--jobs 3)
- fi
- if grep -q -- '--no-mods' scripts/builder/build.sh; then
- FLAGS+=(--no-mods)
- fi
- echo "Flags: ${FLAGS[*]}"
- LIMIT=$(( LIMIT_MIN * 60 ))
- echo "Watchdog limit: ${LIMIT_MIN} minutes"
- ./scripts/builder/build.sh \
- "$RUNNER_TEMP/wind-waker/WindWaker.iso" \
- "${FLAGS[@]}" &
- BUILD_PID=$!
- (
- sleep "$LIMIT"
- echo "::warning::Time limit reached, stopping build to save cache"
- kill -TERM -- -"$BUILD_PID" 2>/dev/null
- sleep 20
- kill -KILL -- -"$BUILD_PID" 2>/dev/null
- pkill -KILL -x xcodebuild clang clang++ ninja make 2>/dev/null
- ) &
- WATCHDOG_PID=$!
- wait "$BUILD_PID"
- RC=$?
- kill "$WATCHDOG_PID" 2>/dev/null
- if [ "$RC" -eq 0 ]; then
- echo "Build finished successfully"
- echo "done=true" >> "$GITHUB_OUTPUT"
- else
- echo "Build stopped or failed (exit $RC). Cache will be saved; run again to resume."
- echo "done=false" >> "$GITHUB_OUTPUT"
- fi
- exit 0
- - name: Save build cache
- if: ${{ always() && !inputs.iso_only }}
- uses: actions/cache/save@v4
- with:
- path: ${{ runner.temp }}/bluewake
- key: bw-build-${{ github.run_id }}-${{ github.run_attempt }}
- - name: Check IPA
- if: steps.build.outputs.done == 'true'
- run: |
- ls -lh "$RUNNER_TEMP/BlueWake.ipa"
- file "$RUNNER_TEMP/BlueWake.ipa"
- df -h /
- - name: Upload IPA
- if: steps.build.outputs.done == 'true'
- uses: actions/upload-artifact@v4
- with:
- name: BlueWake-iOS
- path: ${{ runner.temp }}/BlueWake.ipa
- if-no-files-found: error
- retention-days: 1
- ```
- ### Why the build is split into stops and restarts
- GitHub's free runners cancel any job at 6 hours, and a cancelled job loses everything it compiled. This workflow stops the build itself after the number of minutes you choose, then saves the build folder to GitHub's cache before the job can be cancelled. The next run restores that cache and carries on. The first compile takes close to 6 hours, so expect to run it more than once.
- ## Step 6: Test the download and conversion (ISO only)
- 1. Open your repo's **Actions** tab. If GitHub asks you to enable workflows, enable them.
- 2. Select **Build BlueWake IPA** on the left, tap **Run workflow**, and tick **ISO only**.
- 3. Start it. It should finish in a few minutes. If it fails at the download step, recheck your secret and your Drive sharing setting.
- 4. macOS runners sometimes queue for 10 minutes or more. If a job waits over about 15 minutes, cancel it and change `macos-26` to `macos-15` in the file.
- ## Step 7: Test resuming with two 15-minute runs
- Before spending 6 hours, check that progress carries over.
- 1. Run the workflow with **minutes = 15** and **ISO only unticked**. The build step stops itself after 15 minutes, which is expected, and "Save build cache" should report a size.
- 2. Run it again with 15 minutes. In the **Cache status** step you should see a matched key starting with `bw-build-` and a folder size. In the build log, the early stages should be skipped or finish much faster than the first time.
- 3. If the second run starts over from nothing, the cache isn't resuming. See Troubleshooting.
- ## Step 8: The real build
- 1. Run the workflow with **minutes = 320**. It stops itself at 5 hours 20 minutes and saves the cache, leaving margin before GitHub's 6-hour limit.
- 2. If it didn't finish, run it again with 320. Each run resumes from the last. Once the IPA is built, the **Upload IPA** step runs automatically.
- 3. Don't cancel runs manually. A cancelled run may not save its cache.
- 4. Caches that aren't used for 7 days get deleted, so don't leave long gaps between runs.
- ## Step 9: Download the IPA
- 1. Open the finished run and scroll to **Artifacts**.
- 2. Tap **BlueWake-iOS** to download the zip, and unzip it in the Files app to get `BlueWake.ipa`.
- 3. Delete the artifact from the run page right after downloading, using the trash icon. It is set to expire after 1 day.
- ## Step 10: Download the uncompressed ISO
- The app only accepts `.iso`, not `.ciso`.
- 1. Run the workflow with **ISO only** ticked.
- 2. When it finishes, download **WindWaker-ISO** from the Artifacts section. It's about 1.4 GB, so use Wi-Fi.
- 3. Unzip it in the Files app to get `WindWaker.iso`.
- 4. Delete the artifact from the run page.
- ## Step 11: Install the app
- 1. Turn on Developer Mode: **Settings > Privacy & Security > Developer Mode**, then restart.
- 2. Sideload `BlueWake.ipa` with your tool of choice ([AltStore](https://altstore.io), [SideStore](https://sidestore.io) or [Sideloadly](https://sideloadly.io)).
- 3. Apps signed with a free Apple ID expire after 7 days. Reinstall over the existing app to keep your saves, and never delete the app to update it.
- ## Step 12: Import the disc
- 1. Open **Files > On My iPhone > BlueWake**. The folder appears once the app is installed.
- 2. Copy `WindWaker.iso` into that folder.
- 3. Open BlueWake and import the disc image. On first launch the app checks it and prepares the game files.
- ## Step 13: Clean up
- - In Google Drive, set the file's sharing back to **Restricted**, or delete the file.
- - Delete any leftover artifacts on the Actions run pages.
- - Optionally delete old caches under **Actions > Caches**, or delete the whole repo.
- ## Troubleshooting
- - **"Waiting for a runner" for a long time:** macOS runners can queue. Wait about 15 minutes, then try `macos-15` instead of `macos-26`.
- - **Download step fails:** check the `WIND_WAKER_URL` secret, that Drive sharing is "Anyone with the link", and that Drive hasn't rate-limited the file.
- - **The second 15-minute run restarts from the beginning:** the builder's work isn't stored in the cached folder. Open an issue or ask on the BlueWake Discord with your log.
- - **"CISO isn't supported":** import the `.iso` from Step 10 instead.
- - **Build step shows a red X after hitting the time limit:** expected. The step is stopped on purpose, and the cache still saves.
- - **The app stops opening after a week:** free-account sideloads expire after 7 days. Reinstall over it.
- ## Links
- - BlueWake: [github.com/chrissotraidis/bluewake](https://github.com/chrissotraidis/bluewake)
- - Build your own BlueWake: [docs/BUILD_YOUR_OWN.md](https://github.com/chrissotraidis/bluewake/blob/main/docs/BUILD_YOUR_OWN.md)
- - BlueWake Discord: [discord.gg/xwHfUD2bxW](https://discord.gg/xwHfUD2bxW)
- - Wiimms ISO Tools: [wit.wiimm.de](https://wit.wiimm.de)
- - GitHub Actions docs: [docs.github.com/actions](https://docs.github.com/actions)
- - Sideloading: [AltStore](https://altstore.io), [SideStore](https://sidestore.io), [Sideloadly](https://sideloadly.io)
Advertisement
Add Comment
Please, Sign In to add comment