Jesseb342

Untitled

Oct 9th, 2026
218
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.16 KB | None | 0 0
  1. # Play Wind Waker on iPhone Without a Mac: Build BlueWake with GitHub Actions
  2.  
  3. 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.
  4.  
  5. ## Read this first
  6.  
  7. - **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.
  8. - **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.
  9. - **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).
  10. - **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).
  11.  
  12. ## What you need
  13.  
  14. - An iPhone or iPad with an A13 chip or newer, running iOS/iPadOS 17 or later, with Developer Mode turned on
  15. - Your disc image (`.ciso`, `.iso` or `.gcm`). The app itself only imports `.iso`, which is why the workflow below converts it
  16. - A Google account (for Drive) and a free [GitHub account](https://github.com/signup)
  17. - 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
  18. - Patience: the first build takes roughly 6 hours of runner time, spread over a couple of runs
  19.  
  20. ## Step 1: Upload your disc image to Google Drive
  21.  
  22. 1. Go to [drive.google.com](https://drive.google.com), tap **New > File upload**, and choose your disc image.
  23. 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.
  24. 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`.
  25. 4. Build your direct download link by putting your ID into this:
  26. `https://drive.usercontent.google.com/download?id=FILE_ID&export=download&confirm=t`
  27. 5. Paste that into a browser to check that a download starts. Keep this link private.
  28.  
  29. 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.
  30.  
  31. ## Step 2: Create a GitHub account
  32.  
  33. Sign up at [github.com/signup](https://github.com/signup).
  34.  
  35. ## Step 3: Create a public repository
  36.  
  37. 1. Go to [github.com/new](https://github.com/new).
  38. 2. Name it anything (for example `bluewake-build`).
  39. 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.
  40. 4. Tick **Add a README file** and tap **Create repository**.
  41.  
  42. ## Step 4: Store your download link as a secret
  43.  
  44. Because the repo is public, anyone can read your workflow file. Keep your Drive link out of it.
  45.  
  46. 1. In your repo, go to **Settings > Secrets and variables > Actions**.
  47. 2. Tap **New repository secret**.
  48. 3. Name: `WIND_WAKER_URL`. Value: the direct download link from Step 1.
  49. 4. Tap **Add secret**.
  50.  
  51. ## Step 5: Add the workflow file
  52.  
  53. 1. On the repo's main page, tap **Add file > Create new file**.
  54. 2. In the name box, type exactly `.github/workflows/build.yml`. Typing the slashes creates the folders.
  55. 3. Paste the full file below.
  56. 4. Tap **Commit changes** and commit to the main branch.
  57.  
  58. 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.
  59.  
  60. ```yaml
  61. name: Build BlueWake IPA
  62.  
  63. on:
  64. workflow_dispatch:
  65. inputs:
  66. minutes:
  67. description: "Stop the build after this many minutes (15 = test, 320 = real)"
  68. default: "15"
  69. required: true
  70. iso_only:
  71. description: "Only convert and upload the ISO (skip the build)"
  72. type: boolean
  73. default: false
  74.  
  75. jobs:
  76. build:
  77. runs-on: macos-26
  78. timeout-minutes: 360
  79.  
  80. steps:
  81. - name: Show runner information
  82. run: |
  83. sw_vers
  84. sysctl -n hw.ncpu
  85. sysctl -n machdep.cpu.brand_string
  86. sysctl -n hw.memsize
  87. df -h /
  88.  
  89. - name: Download Wind Waker disc image
  90. env:
  91. WIND_WAKER_URL: ${{ secrets.WIND_WAKER_URL }}
  92. run: |
  93. set -euo pipefail
  94. mkdir -p "$RUNNER_TEMP/wind-waker"
  95. cd "$RUNNER_TEMP/wind-waker"
  96. curl -L --fail --retry 3 --retry-all-errors \
  97. -A "Mozilla/5.0" \
  98. -o "test build.ciso" \
  99. "$WIND_WAKER_URL"
  100. ls -lh "test build.ciso"
  101. file "test build.ciso"
  102.  
  103. - name: Download Wiimms ISO Tools
  104. run: |
  105. set -euo pipefail
  106. cd "$RUNNER_TEMP/wind-waker"
  107. curl -L --fail --retry 3 --retry-all-errors \
  108. -o wit.tar.gz \
  109. "https://wit.wiimm.de/download/wit-v3.05a-r8638-mac.tar.gz"
  110. tar -xzf wit.tar.gz
  111. WIT_DIR="$(find . -maxdepth 1 -type d -name 'wit-*' -print -quit)"
  112. if [ -z "$WIT_DIR" ]; then
  113. echo "::error::Wiimms ISO Tools directory was not found."
  114. exit 1
  115. fi
  116. chmod +x "$WIT_DIR/bin/wit"
  117. "$WIT_DIR/bin/wit" --version
  118.  
  119. - name: Convert to ISO
  120. run: |
  121. set -euo pipefail
  122. cd "$RUNNER_TEMP/wind-waker"
  123. WIT_DIR="$(find . -maxdepth 1 -type d -name 'wit-*' -print -quit)"
  124. "$WIT_DIR/bin/wit" FILES "test build.ciso"
  125. "$WIT_DIR/bin/wit" copy \
  126. "test build.ciso" \
  127. --iso \
  128. -d WindWaker.iso
  129. ls -lh WindWaker.iso
  130. file WindWaker.iso
  131.  
  132. - name: Upload ISO
  133. if: ${{ inputs.iso_only }}
  134. uses: actions/upload-artifact@v4
  135. with:
  136. name: WindWaker-ISO
  137. path: ${{ runner.temp }}/wind-waker/WindWaker.iso
  138. if-no-files-found: error
  139. compression-level: 0
  140. retention-days: 1
  141.  
  142. - name: Install build tools
  143. if: ${{ !inputs.iso_only }}
  144. run: brew install cmake ninja || true
  145.  
  146. - name: Restore build cache
  147. if: ${{ !inputs.iso_only }}
  148. id: cache
  149. uses: actions/cache/restore@v4
  150. with:
  151. path: ${{ runner.temp }}/bluewake
  152. key: bw-build-${{ github.run_id }}-${{ github.run_attempt }}
  153. restore-keys: |
  154. bw-build-
  155.  
  156. - name: Cache status
  157. if: ${{ !inputs.iso_only }}
  158. run: |
  159. echo "Exact hit: ${{ steps.cache.outputs.cache-hit }}"
  160. echo "Matched key: '${{ steps.cache.outputs.cache-matched-key }}'"
  161. if [ -d "$RUNNER_TEMP/bluewake" ]; then
  162. du -sh "$RUNNER_TEMP/bluewake"
  163. else
  164. echo "No restored folder (fresh start)"
  165. fi
  166.  
  167. - name: Clone BlueWake
  168. if: ${{ !inputs.iso_only && steps.cache.outputs.cache-matched-key == '' }}
  169. run: |
  170. set -euo pipefail
  171. git clone --depth 1 \
  172. https://github.com/chrissotraidis/bluewake.git \
  173. "$RUNNER_TEMP/bluewake"
  174.  
  175. - name: Show BlueWake commit
  176. if: ${{ !inputs.iso_only }}
  177. run: |
  178. cd "$RUNNER_TEMP/bluewake"
  179. git log -1 --format='%H %s'
  180.  
  181. - name: Build IPA (with watchdog)
  182. if: ${{ !inputs.iso_only }}
  183. id: build
  184. env:
  185. LIMIT_MIN: ${{ inputs.minutes }}
  186. run: |
  187. set -u
  188. set -m
  189.  
  190. cd "$RUNNER_TEMP/bluewake"
  191. chmod +x scripts/builder/build.sh
  192.  
  193. FLAGS=(--ipa "$RUNNER_TEMP/BlueWake.ipa" --no-train)
  194. if grep -q -- '--jobs' scripts/builder/build.sh; then
  195. FLAGS+=(--jobs 3)
  196. fi
  197. if grep -q -- '--no-mods' scripts/builder/build.sh; then
  198. FLAGS+=(--no-mods)
  199. fi
  200. echo "Flags: ${FLAGS[*]}"
  201.  
  202. LIMIT=$(( LIMIT_MIN * 60 ))
  203. echo "Watchdog limit: ${LIMIT_MIN} minutes"
  204.  
  205. ./scripts/builder/build.sh \
  206. "$RUNNER_TEMP/wind-waker/WindWaker.iso" \
  207. "${FLAGS[@]}" &
  208. BUILD_PID=$!
  209.  
  210. (
  211. sleep "$LIMIT"
  212. echo "::warning::Time limit reached, stopping build to save cache"
  213. kill -TERM -- -"$BUILD_PID" 2>/dev/null
  214. sleep 20
  215. kill -KILL -- -"$BUILD_PID" 2>/dev/null
  216. pkill -KILL -x xcodebuild clang clang++ ninja make 2>/dev/null
  217. ) &
  218. WATCHDOG_PID=$!
  219.  
  220. wait "$BUILD_PID"
  221. RC=$?
  222. kill "$WATCHDOG_PID" 2>/dev/null
  223.  
  224. if [ "$RC" -eq 0 ]; then
  225. echo "Build finished successfully"
  226. echo "done=true" >> "$GITHUB_OUTPUT"
  227. else
  228. echo "Build stopped or failed (exit $RC). Cache will be saved; run again to resume."
  229. echo "done=false" >> "$GITHUB_OUTPUT"
  230. fi
  231. exit 0
  232.  
  233. - name: Save build cache
  234. if: ${{ always() && !inputs.iso_only }}
  235. uses: actions/cache/save@v4
  236. with:
  237. path: ${{ runner.temp }}/bluewake
  238. key: bw-build-${{ github.run_id }}-${{ github.run_attempt }}
  239.  
  240. - name: Check IPA
  241. if: steps.build.outputs.done == 'true'
  242. run: |
  243. ls -lh "$RUNNER_TEMP/BlueWake.ipa"
  244. file "$RUNNER_TEMP/BlueWake.ipa"
  245. df -h /
  246.  
  247. - name: Upload IPA
  248. if: steps.build.outputs.done == 'true'
  249. uses: actions/upload-artifact@v4
  250. with:
  251. name: BlueWake-iOS
  252. path: ${{ runner.temp }}/BlueWake.ipa
  253. if-no-files-found: error
  254. retention-days: 1
  255. ```
  256.  
  257. ### Why the build is split into stops and restarts
  258.  
  259. 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.
  260.  
  261. ## Step 6: Test the download and conversion (ISO only)
  262.  
  263. 1. Open your repo's **Actions** tab. If GitHub asks you to enable workflows, enable them.
  264. 2. Select **Build BlueWake IPA** on the left, tap **Run workflow**, and tick **ISO only**.
  265. 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.
  266. 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.
  267.  
  268. ## Step 7: Test resuming with two 15-minute runs
  269.  
  270. Before spending 6 hours, check that progress carries over.
  271.  
  272. 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.
  273. 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.
  274. 3. If the second run starts over from nothing, the cache isn't resuming. See Troubleshooting.
  275.  
  276. ## Step 8: The real build
  277.  
  278. 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.
  279. 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.
  280. 3. Don't cancel runs manually. A cancelled run may not save its cache.
  281. 4. Caches that aren't used for 7 days get deleted, so don't leave long gaps between runs.
  282.  
  283. ## Step 9: Download the IPA
  284.  
  285. 1. Open the finished run and scroll to **Artifacts**.
  286. 2. Tap **BlueWake-iOS** to download the zip, and unzip it in the Files app to get `BlueWake.ipa`.
  287. 3. Delete the artifact from the run page right after downloading, using the trash icon. It is set to expire after 1 day.
  288.  
  289. ## Step 10: Download the uncompressed ISO
  290.  
  291. The app only accepts `.iso`, not `.ciso`.
  292.  
  293. 1. Run the workflow with **ISO only** ticked.
  294. 2. When it finishes, download **WindWaker-ISO** from the Artifacts section. It's about 1.4 GB, so use Wi-Fi.
  295. 3. Unzip it in the Files app to get `WindWaker.iso`.
  296. 4. Delete the artifact from the run page.
  297.  
  298. ## Step 11: Install the app
  299.  
  300. 1. Turn on Developer Mode: **Settings > Privacy & Security > Developer Mode**, then restart.
  301. 2. Sideload `BlueWake.ipa` with your tool of choice ([AltStore](https://altstore.io), [SideStore](https://sidestore.io) or [Sideloadly](https://sideloadly.io)).
  302. 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.
  303.  
  304. ## Step 12: Import the disc
  305.  
  306. 1. Open **Files > On My iPhone > BlueWake**. The folder appears once the app is installed.
  307. 2. Copy `WindWaker.iso` into that folder.
  308. 3. Open BlueWake and import the disc image. On first launch the app checks it and prepares the game files.
  309.  
  310. ## Step 13: Clean up
  311.  
  312. - In Google Drive, set the file's sharing back to **Restricted**, or delete the file.
  313. - Delete any leftover artifacts on the Actions run pages.
  314. - Optionally delete old caches under **Actions > Caches**, or delete the whole repo.
  315.  
  316. ## Troubleshooting
  317.  
  318. - **"Waiting for a runner" for a long time:** macOS runners can queue. Wait about 15 minutes, then try `macos-15` instead of `macos-26`.
  319. - **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.
  320. - **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.
  321. - **"CISO isn't supported":** import the `.iso` from Step 10 instead.
  322. - **Build step shows a red X after hitting the time limit:** expected. The step is stopped on purpose, and the cache still saves.
  323. - **The app stops opening after a week:** free-account sideloads expire after 7 days. Reinstall over it.
  324.  
  325. ## Links
  326.  
  327. - BlueWake: [github.com/chrissotraidis/bluewake](https://github.com/chrissotraidis/bluewake)
  328. - Build your own BlueWake: [docs/BUILD_YOUR_OWN.md](https://github.com/chrissotraidis/bluewake/blob/main/docs/BUILD_YOUR_OWN.md)
  329. - BlueWake Discord: [discord.gg/xwHfUD2bxW](https://discord.gg/xwHfUD2bxW)
  330. - Wiimms ISO Tools: [wit.wiimm.de](https://wit.wiimm.de)
  331. - GitHub Actions docs: [docs.github.com/actions](https://docs.github.com/actions)
  332. - Sideloading: [AltStore](https://altstore.io), [SideStore](https://sidestore.io), [Sideloadly](https://sideloadly.io)
  333.  
Advertisement
Add Comment
Please, Sign In to add comment