Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build IPA

on:
push:
branches: [main, 'codex/pr-build-*']
branches: [main, 'codex/pr-build-*', 'claude/**']
pull_request:
workflow_dispatch:

Expand All @@ -15,18 +15,22 @@ concurrency:

# A complete IPA cache is scoped by the checked-out tree and build environment.
# Native libraries and runtime components have separate source/SDK caches.
# xcode-27: the ntdll unix side uses iOS 27 SDK fields (rusage_info_v6.ri_page_wait_time_mach).
# Xcode 27: the ntdll unix side uses iOS 27 SDK fields (rusage_info_v6.ri_page_wait_time_mach).
# Runner: the repository variable MADEIRA_RUNNER (upstream sets it to its `xcode-27` label);
# without it, GitHub's hosted macos-26 image, on which tools/build/ci-select-xcode.sh picks Xcode 27.

jobs:
plan:
runs-on: xcode-27
runs-on: ${{ vars.MADEIRA_RUNNER || 'macos-26' }}
outputs:
ipa: ${{ steps.key.outputs.ipa }}
reuse: ${{ steps.ipa.outputs.cache-hit }}
sdk: ${{ steps.sdk.outputs.key }}
ipaenv: ${{ steps.sdk.outputs.ipa }}
steps:
- uses: actions/checkout@v4
- name: Select Xcode 27
run: bash tools/build/ci-select-xcode.sh
- name: Check build reuse rules
run: |
python3 tests/host/check-build-inputs.py
Expand Down Expand Up @@ -54,12 +58,14 @@ jobs:
reuse:
needs: plan
if: needs.plan.outputs.reuse == 'true'
runs-on: xcode-27
runs-on: ${{ vars.MADEIRA_RUNNER || 'macos-26' }}
env:
MADEIRA_PLANNED_SDK: ${{ needs.plan.outputs.sdk }}
MADEIRA_RUNTIME_SDK: ${{ needs.plan.outputs.sdk }}
steps:
- uses: actions/checkout@v4
- name: Select Xcode 27
run: bash tools/build/ci-select-xcode.sh
- id: sdk
run: bash tools/build/ci-input-key.sh
- uses: actions/cache/restore@v4
Expand All @@ -83,12 +89,14 @@ jobs:
llvm:
needs: plan
if: needs.plan.outputs.reuse != 'true'
runs-on: xcode-27
runs-on: ${{ vars.MADEIRA_RUNNER || 'macos-26' }}
env:
MADEIRA_PLANNED_SDK: ${{ needs.plan.outputs.sdk }}
timeout-minutes: 360
steps:
- uses: actions/checkout@v4
- name: Select Xcode 27
run: bash tools/build/ci-select-xcode.sh
- id: sdk
run: bash tools/build/ci-input-key.sh
- id: cache
Expand Down Expand Up @@ -123,14 +131,16 @@ jobs:
fex:
needs: plan
if: needs.plan.outputs.reuse != 'true'
runs-on: xcode-27
runs-on: ${{ vars.MADEIRA_RUNNER || 'macos-26' }}
env:
MADEIRA_PLANNED_SDK: ${{ needs.plan.outputs.sdk }}
timeout-minutes: 360
outputs:
key: ${{ steps.key.outputs.key }}
steps:
- uses: actions/checkout@v4
- name: Select Xcode 27
run: bash tools/build/ci-select-xcode.sh
- id: sdk
run: bash tools/build/ci-input-key.sh
- id: key
Expand Down Expand Up @@ -163,18 +173,20 @@ jobs:
i386:
needs: plan
if: needs.plan.outputs.reuse != 'true'
runs-on: xcode-27
runs-on: ${{ vars.MADEIRA_RUNNER || 'macos-26' }}
env:
MADEIRA_PLANNED_SDK: ${{ needs.plan.outputs.sdk }}
timeout-minutes: 360
outputs:
key: ${{ steps.key.outputs.key }}
steps:
- uses: actions/checkout@v4
- name: Select Xcode 27
run: bash tools/build/ci-select-xcode.sh
- id: sdk
run: bash tools/build/ci-input-key.sh
- id: key
run: echo "key=i386-windows-$(git rev-parse HEAD:wine)-$(git rev-parse HEAD:dxmt)-${{ hashFiles('build/wine-i386/**', 'build/dxmt-patches/**', 'tools/build/setup-llvm-mingw.sh', 'tools/build/build-shader-headers.sh') }}" >> "$GITHUB_OUTPUT"
run: echo "key=i386-windows-$(git rev-parse HEAD:wine)-$(git rev-parse HEAD:dxmt)-${{ hashFiles('build/wine-i386/**', 'build/dxmt-patches/**', 'tools/build/setup-llvm-mingw.sh', 'tools/build/build-shader-headers.sh', 'patches/wine-*.patch', 'tools/build/apply-wine-patches.sh') }}" >> "$GITHUB_OUTPUT"
- id: cache
uses: actions/cache@v4
with:
Expand Down Expand Up @@ -213,7 +225,7 @@ jobs:
runtime:
needs: [plan, llvm]
if: needs.plan.outputs.reuse != 'true'
runs-on: xcode-27
runs-on: ${{ vars.MADEIRA_RUNNER || 'macos-26' }}
timeout-minutes: 360
strategy:
fail-fast: false
Expand All @@ -225,6 +237,8 @@ jobs:
MADEIRA_RUNTIME_REUSE: '1'
steps:
- uses: actions/checkout@v4
- name: Select Xcode 27
run: bash tools/build/ci-select-xcode.sh
- id: sdk
run: bash tools/build/ci-input-key.sh
- id: key
Expand Down Expand Up @@ -258,7 +272,7 @@ jobs:
app:
needs: [plan, llvm, fex, i386, runtime]
if: needs.plan.outputs.reuse != 'true'
runs-on: xcode-27
runs-on: ${{ vars.MADEIRA_RUNNER || 'macos-26' }}
env:
MADEIRA_PLANNED_SDK: ${{ needs.plan.outputs.sdk }}
MADEIRA_PLANNED_IPA: ${{ needs.plan.outputs.ipaenv }}
Expand All @@ -269,6 +283,8 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Select Xcode 27
run: bash tools/build/ci-select-xcode.sh
- id: sdk
run: bash tools/build/ci-input-key.sh
- id: cache-llvm
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/runner-probe.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Runner probe

# Which GitHub-hosted macOS images start for this repository, and which Xcode
# versions they carry (build.yml needs Xcode 27). Manual only.
on:
workflow_dispatch:
push:
branches: ['claude/**']
paths: ['.github/workflows/runner-probe.yml']

permissions:
contents: read

jobs:
probe:
strategy:
fail-fast: false
matrix:
image: [ubuntu-latest, macos-26, macos-15, macos-latest]
runs-on: ${{ matrix.image }}
timeout-minutes: 10
steps:
- run: |
uname -a
df -h /
if [ "$(uname)" = Darwin ]; then sw_vers; xcodebuild -version; ls -d /Applications/Xcode*.app; fi
6 changes: 6 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ requests. Documentation changes also start a run. A newer run cancels an older
run for the same PR or branch. Build copies under `codex/pr-build-*` also run on
push, so a PR head with merge conflicts can still produce an IPA.

The jobs run on the runner named by the repository variable `MADEIRA_RUNNER`
(Settings › Secrets and variables › Actions › Variables); without it they use
GitHub's hosted `macos-26` image, and `tools/build/ci-select-xcode.sh` selects
Xcode 27 there (`MADEIRA_XCODE` names a specific Xcode app). Branches under
`claude/` also build on push.

CI can reuse a completed IPA when the checked-out Git tree, runner platform,
Xcode/SDK/compiler identity, runner image and build flags match. The Git tree
includes component pins and build scripts. A reused IPA must pass the ZIP and
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Wine's server runs as a thread instead of a separate program.
on-screen touch controls.
- **Keyboard, mouse and trackpad** passed through to games as real input.
- **Video and audio** for cutscenes and music, through FFmpeg, VideoToolbox and AudioToolbox.
- **Japanese locale** per game, for Japanese games and visual novels whose text
shows as squares or gibberish or that do not start: Shift-JIS (code page 932)
and a bundled Japanese font ([details](docs/JAPANESE.md)).

## Requirements

Expand Down
1 change: 1 addition & 0 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependency license texts are in `LICENSES/`. See "Why GPL-3.0-or-later" below.
| **libxml2** 2.12.10 and **libxslt** 1.1.45 | MIT (Daniel Veillard and contributors) | Unmodified, statically linked into Wine's `msxml3.dll` (`arm64ec-windows`) | Wine's bundled copies (`wine/libs/xml2`, `wine/libs/xslt`), built with the module by `build/wine-pe/build-modules.sh`. Licence texts in `LICENSES/MIT-libxml2.txt` and `LICENSES/MIT-libxslt.txt`. |
| **NVAPI SDK headers** | MIT (NVIDIA Corporation & affiliates) | Unmodified headers, compiled into DXMT's `nvapi64.dll` (`arm64ec-windows`) | `dxmt/external/nvapi`. DXMT's own NVAPI implementation answers only with Madeira's opt-in "Report an NVIDIA GPU" switch; no NVIDIA binary is shipped. Licence text in `LICENSES/MIT-NVAPI.txt`. |
| **Wine Mono** 11.0.0 | Mono: LGPL or MIT X11 per part; FNA: Ms-PL and MIT; FAudio, FNA3D, MojoShader, SDL3: zlib; winforms, wpf, monoDX, System.Speech and Wine Mono's own code: MIT (`build/wine-mono/COPYING`) | Prebuilt runtime, unmodified except `lib/mono/4.5/mscorlib.dll` (4 IL bytes in `GC.Collect`, `build/wine-mono/bundle.sh`), bundled as `wine-mono/` with that COPYING only in a development build made after `build/wine-mono/fetch.sh`; **release builds do not include it** (the packaging step removes `wine-mono/`). Instead the app downloads it from WineHQ when the user asks (setup or Settings › .NET Framework, `app/Madeira/WineMono.swift`), checks it against the same SHA-256, unpacks it without `lib/mono/*-api` and applies the same mscorlib patch on the device | Runs .NET Framework programs; Wine's mscoree loads it. The x86 tarball from `dl.winehq.org`, SHA-256 pinned in `build/wine-mono/pin.sh`. The compile-time reference assemblies (`lib/mono/*-api`, which include the GPL-licensed ICSharpCode.SharpZipLib) are not bundled. Exact corresponding source: `wine-mono-11.0.0-src.tar.xz` from the same folder; see "Corresponding source" below. |
| **Noto Sans JP** (Noto Sans CJK, SubsetOTF Regular) | SIL OFL 1.1 | Unmodified font file `app/Madeira/fonts-ja/NotoSansJP-Regular.otf` from [`notofonts/noto-cjk`](https://github.com/notofonts/noto-cjk) commit `f8d1575`, SHA-256 `dff723ba59d57d136764a04b9b2d03205544f7cd785a711442d6d2d085ac5073` | Copied into the prefix's `C:\windows\Fonts` only for a game with **Japanese locale** on, so Japanese text has a font. Licence text in `app/Madeira/fonts-ja/LICENSE-NotoSansJP-OFL-1.1.txt`. Wine's code page tables `c_932/936/949/950.nls` (`app/Madeira/nls/extra`) come unmodified from the Wine fork. |
| **Wine Gecko** 2.47.4 | MPL-2.0 | **Not bundled.** The app downloads Wine's own installers, `wine-gecko-2.47.4-x86.msi` and `wine-gecko-2.47.4-x86_64.msi`, from WineHQ (`app/Madeira/WineGecko.swift`), checks them against the SHA-256s in `wine/dlls/appwiz.cpl/addons.c`, and Wine installs them unmodified into the prefix | Wine's mshtml (Internet Explorer's web view) for 32-bit and 64-bit programs. Source: `wine-gecko-2.47.4-src.tar.xz` at `https://dl.winehq.org/wine/wine-gecko/2.47.4/`. |
| **Mesa** 26.2.4 — `opengl32.dll`, `libgallium_wgl.dll` (`x86_64-opengl`, when built) | MIT (most files; each file's SPDX identifier governs), with BSL-1.0 (`src/c11/impl/threads_win32.c`), BLAKE3 (Apache-2.0 selected of CC0-1.0 / Apache-2.0), Berkeley SoftFloat 3e (BSD-3-Clause), zlib 1.3.1 (Zlib) and DirectX-Headers v1.619.1 (MIT) | upstream licences + **Madeira's two patches GPL-3.0-or-later** (`build/mesa-d3d12/patches/`) | Windows x64 OpenGL driver on D3D12 (OpenGL → D3D12 → `madeira_d3d12` → Metal). Built from the pinned release tarball by `build/mesa-d3d12/build.sh`; not committed as a binary. Licence texts in `LICENSES/MIT-Mesa.txt`, `BSL-1.0-Mesa.txt`, `Apache-2.0-BLAKE3.txt`, `BSD-3-SoftFloat-3e.txt`, `Zlib-zlib.txt`, `MIT-DirectX-Headers.txt`. |

Expand Down
4 changes: 4 additions & 0 deletions app/Madeira.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
A1000020 /* WineServerBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = A200001E /* WineServerBridge.m */; };
A1000021 /* libwineserver.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A2000020 /* libwineserver.a */; };
A1000022 /* nls in Resources */ = {isa = PBXBuildFile; fileRef = A2000021 /* nls */; };
A10009A1 /* fonts-ja in Resources */ = {isa = PBXBuildFile; fileRef = A20009A1 /* fonts-ja */; };
A1000030 /* WineProcessBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = A2000030 /* WineProcessBridge.m */; };
A1000031 /* libntdll_unix.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A2000032 /* libntdll_unix.a */; };
A1000032 /* aarch64-windows in Resources */ = {isa = PBXBuildFile; fileRef = A2000033 /* aarch64-windows */; };
Expand Down Expand Up @@ -404,6 +405,7 @@
A200001F /* WineServerBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WineServerBridge.h; sourceTree = "<group>"; };
A2000020 /* libwineserver.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libwineserver.a; sourceTree = "<group>"; };
A2000021 /* nls */ = {isa = PBXFileReference; lastKnownFileType = folder; path = nls; sourceTree = "<group>"; };
A20009A1 /* fonts-ja */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "fonts-ja"; sourceTree = "<group>"; };
A2000030 /* WineProcessBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WineProcessBridge.m; sourceTree = "<group>"; };
A2000031 /* WineProcessBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WineProcessBridge.h; sourceTree = "<group>"; };
A2000032 /* libntdll_unix.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libntdll_unix.a; sourceTree = "<group>"; };
Expand Down Expand Up @@ -776,6 +778,7 @@
B200CA02 /* Give-to-Claude-with-log-and-cfg.txt */,
B2000051 /* Madeira JIT.shortcut */,
A2000021 /* nls */,
A20009A1 /* fonts-ja */,
A2000033 /* aarch64-windows */,
A20000F1 /* legal */,
A2000080 /* arm64ec-windows */,
Expand Down Expand Up @@ -995,6 +998,7 @@
buildActionMask = 2147483647;
files = (
A1000022 /* nls in Resources */,
A10009A1 /* fonts-ja in Resources */,
A1000032 /* aarch64-windows in Resources */,
A10000F1 /* legal in Resources */,
A1000080 /* arm64ec-windows in Resources */,
Expand Down
1 change: 1 addition & 0 deletions app/Madeira/ConfigCatalog.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ extension ConfigCatalog {
ConfigOption(key: "env.MADEIRA_LOCK_ORPHAN", title: "Orphan-lock reaper mode", kind: .choice, defaultValue: "", category: "Wine core (ntdll)", note: "Default and stamped: only a lock seen held by FEX can be released, after three monitor cycles with the same lock word and no live owner stamp. Guest locks are never released. 0: no orphan-lock releases. The dead-thread reaper is unchanged. Restart the session after changing it.", choices: [("", "Only stamped locks (default)"), ("stamped", "Only stamped locks"), ("0", "Off")], sources: ["wine/dlls/ntdll/unix/sync.c"]),
ConfigOption(key: "env.MADEIRA_LOG_THROTTLE", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "", choices: [], sources: ["build/ntdll-unix/loader_ios.c"]),
ConfigOption(key: "env.MADEIRA_NLS_EXTRA", title: "Extra code page tables", kind: .bool, defaultValue: "0", category: "Wine core (ntdll)", note: "Default off. 1: a code page table that is not loaded for every game (932, 936, 949, 950, 874, 1250, 1251, 1253-1258) is read from the app's nls/extra folder when the game asks for it; without the line such a request fails (GetCPInfo and MultiByteToWideChar return an error), as before. For a game that converts its text in one of those pages. Logged as [nls-extra]; [nls-missing] names a table that was not found. Read at every request.", choices: [], sources: ["build/ntdll-unix/env_ios.c"]),
ConfigOption(key: "env.MADEIRA_JAPANESE", title: "Japanese locale and fonts", kind: .bool, defaultValue: "0", category: "Wine core (ntdll)", note: "Default off. 1: the game runs with a Japanese system and user locale (LC_ALL = ja_JP.UTF-8, ANSI and OEM code page 932) and the extra code page tables (env.MADEIRA_NLS_EXTRA), and a Japanese font (Noto Sans JP) is installed in C:\\windows\\Fonts with MS Gothic, MS PGothic, MS UI Gothic, MS Mincho, Meiryo and Yu Gothic mapped onto it. For Japanese games and visual novels whose text shows as squares or gibberish, or that do not start. Game details › Compatibility › Japanese locale sets it for one game. Logged as [japanese]. Read at session start.", choices: [], sources: ["app/Madeira/WineProcessBridge.m"]),
ConfigOption(key: "env.MADEIRA_NO_UNIXCALL_DIRECT", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "iOS-Madeira Round 7 (2026-07-04): de-fault the unix-call path. Pool-executing EC callers read this slot and `blr` its value. With the baseline PE VA they exec-fault (~100 Mach round trips/present, ~0.5ms wall each = the bulk of the gamep...", choices: [], sources: ["build/ntdll-unix/loader_ios.c"]),
ConfigOption(key: "env.MADEIRA_NSI_CACHE_MS", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "On by default (500 ms): an identical interface, address or route table read within this many milliseconds reuses the last result. 0 reads the host every time; at most 5000.", choices: [], sources: ["build/ntdll-unix/nsi_network_ios.c"]),
ConfigOption(key: "env.MADEIRA_NSI_NETWORK_TABLES", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "On by default: programs see this device's network interfaces, IP addresses and routes. 0 serves none of them, as before.", choices: [], sources: ["build/ntdll-unix/nsi_network_ios.c"]),
Expand Down
7 changes: 6 additions & 1 deletion app/Madeira/Library.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4365,6 +4365,10 @@ struct LibraryDetail: View {
Toggle(isOn: Binding(get: { bcd.wineVCRT }, set: { on in bcd.wineVCRT = on; LibraryPrefs.setWineVCRT(on, for: entry.windowsPath) })) {
compatLabel(L("Wine's C++ runtime"), L("Instead of the game's msvcp/vcruntime"))
}
// Japanese locale (LibraryPrefs.japanese, BCDLaunch.applyExtras, WineProcessBridge.m).
Toggle(isOn: Binding(get: { bcd.japanese }, set: { on in bcd.japanese = on; LibraryPrefs.setJapanese(on, for: entry.windowsPath) })) {
compatLabel(L("Japanese locale"), L("For Japanese games and visual novels whose text shows as squares or gibberish, or that do not start"))
}
// madeira-bcd: MADEIRA_REAL_SUSPEND for this game (BCDLaunch.realSuspend).
Picker(selection: Binding(get: { bcd.realSuspend.map { $0 ? 1 : 2 } ?? 0 },
set: { v in bcd.realSuspend = v == 0 ? nil : v == 1; LibraryPrefs.setRealSuspend(bcd.realSuspend, for: entry.windowsPath) })) {
Expand Down Expand Up @@ -4634,7 +4638,7 @@ struct LibraryDetail: View {
/// madeira-bcd: a game's own options as the fork stores them (LibraryDetail):
/// LibraryPrefs' switches and the GameProfiles.swift file's lines.
struct BCDGameValues {
var avx = false, nvidia = false, olderNvidia = false, wineVCRT = false
var avx = false, nvidia = false, olderNvidia = false, wineVCRT = false, japanese = false
var gpuCard = ""
var realSuspend: Bool? = nil, unityIL2CPP = false
var configText = ""
Expand All @@ -4645,6 +4649,7 @@ struct BCDGameValues {
olderNvidia = LibraryPrefs.olderNvidia(windowsPath)
gpuCard = LibraryPrefs.gpuCard(windowsPath) ?? ""
wineVCRT = LibraryPrefs.wineVCRT(windowsPath)
japanese = LibraryPrefs.japanese(windowsPath)
realSuspend = LibraryPrefs.realSuspend(windowsPath)
configText = GameProfile(windowsPath: windowsPath).text
}
Expand Down
Loading