diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 810993c680..f6684aa2bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build IPA on: push: - branches: [main, 'codex/pr-build-*'] + branches: [main, 'codex/pr-build-*', 'claude/**'] pull_request: workflow_dispatch: @@ -15,11 +15,13 @@ 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 }} @@ -27,6 +29,8 @@ jobs: 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 @@ -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 @@ -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 @@ -123,7 +131,7 @@ 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 @@ -131,6 +139,8 @@ jobs: 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 @@ -163,7 +173,7 @@ 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 @@ -171,10 +181,12 @@ jobs: 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: @@ -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 @@ -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 @@ -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 }} @@ -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 diff --git a/.github/workflows/runner-probe.yml b/.github/workflows/runner-probe.yml new file mode 100644 index 0000000000..0e557b2ab8 --- /dev/null +++ b/.github/workflows/runner-probe.yml @@ -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 diff --git a/BUILDING.md b/BUILDING.md index 717b210ac7..9c6c8e3f12 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -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 diff --git a/README.md b/README.md index d9d9ff551e..d0112022c6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 1dc719fca0..b70d05d23d 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -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`. | diff --git a/app/Madeira.xcodeproj/project.pbxproj b/app/Madeira.xcodeproj/project.pbxproj index 5d6c310ed8..7f11390663 100644 --- a/app/Madeira.xcodeproj/project.pbxproj +++ b/app/Madeira.xcodeproj/project.pbxproj @@ -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 */; }; @@ -404,6 +405,7 @@ A200001F /* WineServerBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WineServerBridge.h; sourceTree = ""; }; A2000020 /* libwineserver.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libwineserver.a; sourceTree = ""; }; A2000021 /* nls */ = {isa = PBXFileReference; lastKnownFileType = folder; path = nls; sourceTree = ""; }; + A20009A1 /* fonts-ja */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "fonts-ja"; sourceTree = ""; }; A2000030 /* WineProcessBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WineProcessBridge.m; sourceTree = ""; }; A2000031 /* WineProcessBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WineProcessBridge.h; sourceTree = ""; }; A2000032 /* libntdll_unix.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libntdll_unix.a; sourceTree = ""; }; @@ -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 */, @@ -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 */, diff --git a/app/Madeira/ConfigCatalog.generated.swift b/app/Madeira/ConfigCatalog.generated.swift index 138d0ca34b..b796a39b73 100644 --- a/app/Madeira/ConfigCatalog.generated.swift +++ b/app/Madeira/ConfigCatalog.generated.swift @@ -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"]), diff --git a/app/Madeira/Library.swift b/app/Madeira/Library.swift index 35cb4f128e..c19d3b2199 100644 --- a/app/Madeira/Library.swift +++ b/app/Madeira/Library.swift @@ -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) })) { @@ -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 = "" @@ -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 } diff --git a/app/Madeira/LibraryBCD.swift b/app/Madeira/LibraryBCD.swift index baef3aa0c6..ff5bdbbc45 100644 --- a/app/Madeira/LibraryBCD.swift +++ b/app/Madeira/LibraryBCD.swift @@ -19,6 +19,7 @@ enum BCDLaunch { static func applyExtras(avx: Bool, wineVCRT: Bool, nvidia: Bool, olderNvidia: Bool = false, card: GPUCard? = nil, profile: GameProfile?) { if avx { setenv("MADEIRA_FEX_AVX", "1", 1) } else { unsetenv("MADEIRA_FEX_AVX") } + applyJapanese(false) // applyLibrary turns it on for a game that has it if wineVCRT { setenv("MADEIRA_WINE_VCRT", "1", 1) } else { unsetenv("MADEIRA_WINE_VCRT") } var dxmtExtra: [String] = [] if nvidia { @@ -65,6 +66,23 @@ enum BCDLaunch { else { setenv("MADEIRA_DXMT_EXTRA", dxmtExtra.joined(separator: ";"), 1) /* DXMT splits on ";" */ } } + /// "Japanese locale" (Game details › Compatibility): MADEIRA_JAPANESE, read by + /// WineProcessBridge.m before the wineserver starts (the font and the Japanese font + /// names go into the prefix) and when Wine starts (LC_ALL = ja_JP.UTF-8, so code page + /// 932, and the extra code page tables). Off: a Japanese locale an earlier launch + /// exported is taken back; madeira.cfg's and the game file's own env lines are + /// exported again after this, so a line there still applies. + static func applyJapanese(_ on: Bool) { + if on { + setenv("MADEIRA_JAPANESE", "1", 1) + } else { + unsetenv("MADEIRA_JAPANESE") + if let lc = getenv("LC_ALL"), String(cString: lc).hasPrefix("ja") { + unsetenv("LC_ALL"); unsetenv("LANG"); unsetenv("MADEIRA_NLS_EXTRA") + } + } + } + /// A start from upstream's library: this game's madeira-bcd options. The /// FPS limit is the entry's (upstream's FPS picker, which offers 40 FPS too). static func applyLibrary(_ entry: LibraryEntry) { @@ -80,6 +98,7 @@ enum BCDLaunch { let olderNvidia = card == nil && LibraryPrefs.olderNvidia(path) applyExtras(avx: LibraryPrefs.avx(path), wineVCRT: LibraryPrefs.wineVCRT(path), nvidia: nvidia, olderNvidia: olderNvidia, card: card, profile: profile) + applyJapanese(LibraryPrefs.japanese(path)) if let appID = entry.steamAppID { if let card { D3D9Identity.syncGamePrefs(appID: appID, card: card) } else { D3D9Identity.syncGamePrefs(appID: appID, olderNvidia: olderNvidia) } @@ -89,6 +108,7 @@ enum BCDLaunch { LogStore.shared.log("[bcd] library launch \(program): avx=\(LibraryPrefs.avx(path) ? 1 : 0) " + "wine-vcrt=\(LibraryPrefs.wineVCRT(path) ? 1 : 0) nvidia=\(nvidia ? 1 : 0) " + "older-nvidia=\(olderNvidia ? 1 : 0) gpu-card=\(cardLog) " + + "japanese=\(LibraryPrefs.japanese(path) ? 1 : 0) " + "game-config=\(profile.hasSettings ? 1 : 0)") } diff --git a/app/Madeira/LibraryPrefs.swift b/app/Madeira/LibraryPrefs.swift index 932aa6652d..7d9b4c2b03 100644 --- a/app/Madeira/LibraryPrefs.swift +++ b/app/Madeira/LibraryPrefs.swift @@ -18,6 +18,7 @@ enum LibraryPrefs { private static let olderNvidiaKey = "madeira.library.olderNvidia" private static let realSuspendKey = "madeira.library.realSuspend" private static let gpuCardKey = "madeira.library.gpuCard" + private static let japaneseKey = "madeira.library.japanese" private static func dict(_ key: String) -> [String: T] { (UserDefaults.standard.dictionary(forKey: key) as? [String: T]) ?? [:] @@ -42,6 +43,11 @@ enum LibraryPrefs { static func olderNvidia(_ windowsPath: String) -> Bool { (dict(olderNvidiaKey) as [String: Bool])[windowsPath] ?? false } static func setOlderNvidia(_ on: Bool, for windowsPath: String) { store(on ? true : nil, olderNvidiaKey, windowsPath) } + /// "Japanese locale": Wine runs the game with a Japanese system locale (code page 932) + /// and a Japanese font (MADEIRA_JAPANESE, WineProcessBridge.m madeira_japanese_prefix). + static func japanese(_ windowsPath: String) -> Bool { (dict(japaneseKey) as [String: Bool])[windowsPath] ?? false } + static func setJapanese(_ on: Bool, for windowsPath: String) { store(on ? true : nil, japaneseKey, windowsPath) } + /// "Graphics card the game sees": a GPUCards id, nil for no choice. static func gpuCard(_ windowsPath: String) -> String? { (dict(gpuCardKey) as [String: String])[windowsPath] } static func setGPUCard(_ id: String?, for windowsPath: String) { store(id.flatMap { $0.isEmpty ? nil : $0 }, gpuCardKey, windowsPath) } @@ -58,6 +64,7 @@ enum LibraryPrefs { if wineVCRT(windowsPath) { out["wineVCRT"] = true } if nvidia(windowsPath) { out["nvidia"] = true } if olderNvidia(windowsPath) { out["olderNvidia"] = true } + if japanese(windowsPath) { out["japanese"] = true } if let suspend = realSuspend(windowsPath) { out["realSuspend"] = suspend } return out } @@ -68,6 +75,7 @@ enum LibraryPrefs { setWineVCRT(switches["wineVCRT"] as? Bool ?? false, for: windowsPath) setNvidia(switches["nvidia"] as? Bool ?? false, for: windowsPath) setOlderNvidia(switches["olderNvidia"] as? Bool ?? false, for: windowsPath) + setJapanese(switches["japanese"] as? Bool ?? false, for: windowsPath) setRealSuspend(switches["realSuspend"] as? Bool, for: windowsPath) } diff --git a/app/Madeira/WineProcessBridge.m b/app/Madeira/WineProcessBridge.m index 968071f2ff..d528ab5e69 100644 --- a/app/Madeira/WineProcessBridge.m +++ b/app/Madeira/WineProcessBridge.m @@ -517,6 +517,124 @@ static void madeira_ensure_locallow(NSString *prefix) ws_log( "[profile] users/%s/AppData/LocalLow %s", name, made ? "created" : "could NOT be created" ); } +/* Japanese locale (Game details › Compatibility › Japanese locale, or + * env.MADEIRA_JAPANESE = 1 in madeira.cfg or the game's own config). + * + * Japanese games -- visual novels above all -- keep their text in Shift-JIS + * (code page 932) and ask GDI for MS Gothic / MS Mincho / MS UI Gothic. On a + * phone set to another language Wine runs them with an English or European + * ANSI code page, so their text turns into mojibake or squares, and some stop + * at start-up because a file name or a script does not convert. This mode: + * - gives Wine a Japanese system and user locale (LC_ALL = ja_JP.UTF-8, so + * ANSI and OEM code page 932), exported in wine_process_thread; + * - turns on env.MADEIRA_NLS_EXTRA, which supplies c_932.nls (and the other + * CJK tables) from the bundle's nls/extra folder; + * - installs a Japanese font (the bundle's fonts-ja/NotoSansJP-Regular.otf, + * SIL OFL 1.1) into C:\windows\Fonts, and maps the Windows Japanese font + * names onto it with HKCU\Software\Wine\Fonts\Replacements. A replacement + * only applies while no real font of that name is installed, so a game that + * ships or installs the real MS Gothic keeps it. + * The font and the registry values are written here, before the wineserver + * reads the registry (wineserver_start → madeira_seed_prefix_if_needed). They + * stay in the prefix afterwards; other games only see them when they ask for + * one of those Japanese names or draw Japanese text. Log tag [japanese]. */ +static int madeira_japanese_on(void) +{ + const char *e = getenv("MADEIRA_JAPANESE"); + char v[16]; + if (e && *e) return e[0] == '1'; + if (madeira_cfg_get("env.MADEIRA_JAPANESE", v, sizeof v) && v[0]) return v[0] == '1'; + return 0; +} + +static NSString *madeira_reg_escape(NSString *s) +{ + /* Wine's registry text format: \ and " are escaped, characters above 0x7f as \xNNNN */ + NSMutableString *out = [NSMutableString string]; + for (NSUInteger i = 0; i < s.length; i++) { + unichar c = [s characterAtIndex:i]; + if (c == '\\') [out appendString:@"\\\\"]; + else if (c == '"') [out appendString:@"\\\""]; + else if (c < 0x20 || c > 0x7e) [out appendFormat:@"\\x%04x", c]; + else [out appendFormat:@"%C", c]; + } + return out; +} + +static void madeira_japanese_prefix(NSString *prefix) +{ + if (!madeira_japanese_on()) return; + extern void ws_log(const char *fmt, ...); + NSFileManager *fm = [NSFileManager defaultManager]; + + /* 1. the font, into C:\windows\Fonts (copied again when the bundled file changed size) */ + static NSString *const fontFile = @"NotoSansJP-Regular.otf"; + static NSString *const family = @"Noto Sans JP"; + NSString *src = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: + [@"fonts-ja" stringByAppendingPathComponent:fontFile]]; + NSString *fontsDir = [prefix stringByAppendingPathComponent:@"drive_c/windows/Fonts"]; + NSString *dst = [fontsDir stringByAppendingPathComponent:fontFile]; + unsigned long long srcSize = [[fm attributesOfItemAtPath:src error:nil] fileSize]; + if (!srcSize) { + ws_log("[japanese] fonts-ja/%s missing from the bundle; Japanese text needs a font the game brings", fontFile.UTF8String); + } else if ([[fm attributesOfItemAtPath:dst error:nil] fileSize] != srcSize) { + [fm createDirectoryAtPath:fontsDir withIntermediateDirectories:YES attributes:nil error:nil]; + [fm removeItemAtPath:dst error:nil]; + NSError *err = nil; + BOOL ok = [fm copyItemAtPath:src toPath:dst error:&err]; + ws_log("[japanese] %s %s C:\\windows\\Fonts", fontFile.UTF8String, ok ? "copied to" : "could NOT be copied to"); + } + + /* 2. the Windows Japanese font names → the bundled family (user.reg, HKCU) */ + NSArray *names = @[ + @"MS Gothic", @"MS PGothic", @"MS UI Gothic", @"MS Mincho", @"MS PMincho", + @"\uFF2D\uFF33 \u30B4\u30B7\u30C3\u30AF", /* MS ゴシック */ + @"\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF", /* MS Pゴシック */ + @"\uFF2D\uFF33 \u660E\u671D", /* MS 明朝 */ + @"\uFF2D\uFF33 \uFF30\u660E\u671D", /* MS P明朝 */ + @"Meiryo", @"Meiryo UI", @"\u30E1\u30A4\u30EA\u30AA", /* メイリオ */ + @"Yu Gothic", @"Yu Gothic UI", @"Yu Mincho", + @"\u6E38\u30B4\u30B7\u30C3\u30AF", @"\u6E38\u660E\u671D", /* 游ゴシック, 游明朝 */ + ]; + NSString *regPath = [prefix stringByAppendingPathComponent:@"user.reg"]; + NSString *reg = [NSString stringWithContentsOfFile:regPath encoding:NSUTF8StringEncoding error:nil]; + if (!reg.length) { ws_log("[japanese] user.reg not readable yet; font names not mapped this time"); return; } + NSMutableArray *lines = [[reg componentsSeparatedByString:@"\n"] mutableCopy]; + NSString *header = @"[software\\\\wine\\\\fonts\\\\replacements]"; + NSInteger start = -1; + for (NSUInteger i = 0; i < lines.count; i++) + if ([[lines[i] lowercaseString] hasPrefix:header]) { start = (NSInteger)i; break; } + if (start < 0) { + if (lines.count && [lines.lastObject isEqualToString:@""]) [lines removeLastObject]; + [lines addObject:@""]; + [lines addObject:[NSString stringWithFormat:@"[Software\\\\Wine\\\\Fonts\\\\Replacements] %ld", (long)time(NULL)]]; + start = (NSInteger)lines.count - 1; + [lines addObject:@""]; + } + NSUInteger end = (NSUInteger)start + 1; + while (end < lines.count && ![lines[end] hasPrefix:@"["]) end++; + NSUInteger insertAt = (NSUInteger)start + 1; + if (insertAt < lines.count && [lines[insertAt] hasPrefix:@"#time="]) insertAt++; + unsigned added = 0; + for (NSString *name in names) { + NSString *key = [NSString stringWithFormat:@"\"%@\"=", madeira_reg_escape(name)]; + BOOL present = NO; + for (NSUInteger i = (NSUInteger)start + 1; i < end; i++) + if ([[lines[i] lowercaseString] hasPrefix:[key lowercaseString]]) { present = YES; break; } + if (present) continue; + [lines insertObject:[NSString stringWithFormat:@"%@\"%@\"", key, family] atIndex:insertAt++]; + end++; added++; + } + if (!added) return; + NSString *backup = [regPath stringByAppendingPathExtension:@"madeira-bak"]; + if (![fm fileExistsAtPath:backup]) [fm copyItemAtPath:regPath toPath:backup error:nil]; + NSString *text = [lines componentsJoinedByString:@"\n"]; + if (![text hasSuffix:@"\n"]) text = [text stringByAppendingString:@"\n"]; + BOOL ok = [text writeToFile:regPath atomically:YES encoding:NSUTF8StringEncoding error:nil]; + ws_log("[japanese] %u Japanese font names %s %s in user.reg", added, ok ? "mapped to" : "could NOT be mapped to", + family.UTF8String); +} + /*********************************************************************** * madeira_seed_prefix_if_needed * @@ -572,6 +690,7 @@ void madeira_seed_prefix_if_needed(const char *prefix_path) { /* ml581: see madeira_undo_appdata_skeleton() above. */ madeira_undo_appdata_skeleton( prefix ); madeira_ensure_locallow( prefix ); + madeira_japanese_prefix( prefix ); } } @@ -1878,6 +1997,17 @@ static int madeira_split_launch_args(const char *input, char *storage, size_t st LOG("game config env: %{public}s=%{public}s", k.UTF8String, v.UTF8String); fprintf(stderr, "[madeira-env] game %s=%s\n", k.UTF8String, v.UTF8String); } + /* Japanese locale (madeira_japanese_prefix above): Wine's system and user + * locale ja-JP (ANSI/OEM code page 932) and the extra code page tables. A + * Japanese LC_ALL the config already chose (ja_JP.SJIS, ...) is kept. */ + if (madeira_japanese_on()) { + const char *lc = getenv("LC_ALL"); + if (!lc || strncmp(lc, "ja", 2)) setenv("LC_ALL", "ja_JP.UTF-8", 1); + setenv("LANG", getenv("LC_ALL"), 1); + setenv("MADEIRA_NLS_EXTRA", "1", 1); + fprintf(stderr, "[japanese] LC_ALL=%s MADEIRA_NLS_EXTRA=1: Japanese system locale, code page 932\n", + getenv("LC_ALL")); + } { /* madeira-bcd: the server's per-launch switches (fd_ios.c, request_ios.c) */ const char *ef = getenv("MADEIRA_SRV_ECO_FOLLOW"); /* 1: the server thread runs at utility QoS while ECO is on */ const char *dr = getenv("MADEIRA_SRV_DIRECT"); /* 1 or 2: listed requests run on the calling thread */ diff --git a/app/Madeira/fonts-ja/LICENSE-NotoSansJP-OFL-1.1.txt b/app/Madeira/fonts-ja/LICENSE-NotoSansJP-OFL-1.1.txt new file mode 100644 index 0000000000..d952d62c06 --- /dev/null +++ b/app/Madeira/fonts-ja/LICENSE-NotoSansJP-OFL-1.1.txt @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/Madeira/fonts-ja/NotoSansJP-Regular.otf b/app/Madeira/fonts-ja/NotoSansJP-Regular.otf new file mode 100644 index 0000000000..4af556abb9 Binary files /dev/null and b/app/Madeira/fonts-ja/NotoSansJP-Regular.otf differ diff --git a/app/Madeira/legal/THIRD-PARTY-NOTICES.md b/app/Madeira/legal/THIRD-PARTY-NOTICES.md index 8021141589..c114aefc08 100644 --- a/app/Madeira/legal/THIRD-PARTY-NOTICES.md +++ b/app/Madeira/legal/THIRD-PARTY-NOTICES.md @@ -36,6 +36,7 @@ dependency license texts are in `LICENSES/`. See "Why GPL-3.0-or-later" below. | **On-device pairing, signing refresh and Apple ID sign-in** (`libmadeira_rppairing.a`) | **idevice** 0.1.68, Copyright 2026 Jackson Coxson, MIT; **isideload** 0.4.4, Copyright (c) 2025 nab138, MIT; their Rust dependencies under MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Zlib, Unicode-3.0 or CDLA-Permissive-2.0, and one, **apple-codesign-quick** 0.1.0 (Dadoum/apple-crates, which isideload signs apps with), under LGPL-2.1-or-later | Madeira's wrapper (`build/rppairing-ios`) **GPL-3.0-or-later**; dependencies unmodified, static in the app | iOS 27 on-device remote pairing for Built-in StikJIT (`app/Madeira/JITPairing.swift`), and installing a new provisioning profile with the pairing file (`app/Madeira/SigningRefresh.swift`) and the Apple ID sign-in that asks Apple for one (`app/Madeira/AppleSignIn.swift`, isideload); docs/JIT.md. Built from crates.io at the versions pinned in `build/rppairing-ios/Cargo.lock`. Every crate in the binary, with its licence files, is listed in `LICENSES-rppairing-crates.txt`, regenerated by `build/rppairing-ios/build.sh`. idevice's licence text: `LICENSE-idevice-MIT.txt`. isideload's: `LICENSE-isideload-MIT.txt`. apple-codesign-quick's: `LICENSES/LGPL-2.1.txt`; Madeira calls none of its code, and its source is the crate at the pinned version. | | **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`. | +| **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 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. | | **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`. | diff --git a/app/Madeira/nls/extra/c_10000.nls b/app/Madeira/nls/extra/c_10000.nls new file mode 100644 index 0000000000..80f8055857 Binary files /dev/null and b/app/Madeira/nls/extra/c_10000.nls differ diff --git a/app/Madeira/nls/extra/c_932.nls b/app/Madeira/nls/extra/c_932.nls new file mode 100644 index 0000000000..44ce6cefb6 Binary files /dev/null and b/app/Madeira/nls/extra/c_932.nls differ diff --git a/app/Madeira/nls/extra/c_936.nls b/app/Madeira/nls/extra/c_936.nls new file mode 100644 index 0000000000..ecbacb15ee Binary files /dev/null and b/app/Madeira/nls/extra/c_936.nls differ diff --git a/app/Madeira/nls/extra/c_949.nls b/app/Madeira/nls/extra/c_949.nls new file mode 100644 index 0000000000..f3dd71c850 Binary files /dev/null and b/app/Madeira/nls/extra/c_949.nls differ diff --git a/app/Madeira/nls/extra/c_950.nls b/app/Madeira/nls/extra/c_950.nls new file mode 100644 index 0000000000..33a016566f Binary files /dev/null and b/app/Madeira/nls/extra/c_950.nls differ diff --git a/build/ntdll-unix/build.sh b/build/ntdll-unix/build.sh index 2a43bd437a..a22418b4e4 100755 --- a/build/ntdll-unix/build.sh +++ b/build/ntdll-unix/build.sh @@ -11,6 +11,9 @@ APP_LIB="$REPO_ROOT/app/Madeira/libntdll_unix.a" mkdir -p "$OBJ_DIR" +# Madeira's wine patches (32-bit OpenGL among them) before anything compiles from the tree. +"$REPO_ROOT/tools/build/apply-wine-patches.sh" + # Optional Vulkan route; do not change the normal native build. VULKAN_FLAGS=() if [ "${MADEIRA_MOLTENVK:-0}" = 1 ]; then diff --git a/build/tools/gen-config-catalog.py b/build/tools/gen-config-catalog.py index 3b490192b7..05657d27bc 100644 --- a/build/tools/gen-config-catalog.py +++ b/build/tools/gen-config-catalog.py @@ -1155,6 +1155,8 @@ "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."}, + "env.MADEIRA_JAPANESE": {"category": "Wine core (ntdll)", "title": "Japanese locale and fonts", "kind": "bool", "default": "0", + "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."}, "env.MADEIRA_ALIAS_RELEASE": {"category": "Memory & JIT pool", "title": "Drop a released arena's code copies", "kind": "bool", "default": "0", "note": "Default off. 1: anonymous memory the game made executable (copied into the JIT pool) follows the game's " diff --git a/build/wine-i386/build.sh b/build/wine-i386/build.sh index 733b2a1ceb..e0029f0d9a 100755 --- a/build/wine-i386/build.sh +++ b/build/wine-i386/build.sh @@ -42,6 +42,9 @@ LOG="$B/madeira-i386-build.log" [ -x "$STRIP" ] || { echo "llvm-mingw not found at $TC (docs/BUILDING.md)" >&2; exit 1; } +# Madeira's wine patches (32-bit OpenGL among them) before anything compiles from the tree. +"$R/tools/build/apply-wine-patches.sh" + # ---------------------------------------------------------------- configure # Its own tree, like build-arm64ec: reconfiguring build-macos with an extra # arch would regenerate every module Makefile the ntdll-unix build relies on. diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 3f446d719c..7939e97ee1 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -93,14 +93,14 @@ git-ignored and consumed by the app project. 6. App: `xcodebuild -project app/Madeira.xcodeproj -scheme Madeira -destination 'generic/platform=iOS' -allowProvisioningUpdates build` (Debug is the configuration that runs the games; Release builds have crashed the guest), then zip `Payload/Madeira.app` into an IPA and sideload. Verified this session on the development machine. 7. WoW64 (32-bit programs, optional): `build/wine-i386/build.sh` (i386 Wine farm 6. OpenGL and LOVE games: - - Before step 3, apply the Wine patch: `git -C wine apply ../patches/wine-opengl-winios.patch`. Then rebuild the tracked `opengl32.dll` with `build/wine-pe/build-modules.sh opengl32`. + - The build scripts apply the Wine patch themselves (`tools/build/apply-wine-patches.sh`, run by `tools/build/build-wine.sh`, `build/ntdll-unix/build.sh` and `build/wine-i386/build.sh`; by hand: `git -C wine apply ../patches/wine-opengl-winios.patch`). Then rebuild the tracked `opengl32.dll` with `build/wine-pe/build-modules.sh opengl32`. - `build/luajit-x64/build.sh` -> `app/Madeira/compat/love/lua51.dll`, needed for LOVE games. - `build/moltenvk-ios/build.sh`, then `build/mesa-ios/build.sh` -> `app/Madeira/gl/`, the desktop OpenGL backend. Optional: without it the app uses OpenGL ES. The script applies every file in `build/mesa-ios/patches` once (a `.madeira-applied-*` marker in the Mesa source records it), so an existing Mesa tree picks up a new patch on the next run. - 32-bit OpenGL games (id Tech 3: Jedi Academy, Jedi Outcast, Quake III) need all of: the regenerated wow64 thunks in `wine-opengl-winios.patch` (from `dlls/opengl32/make_opengl`; to regenerate, run `perl make_opengl` in `wine/dlls/opengl32`, which downloads the pinned Khronos registry files into `~/.cache/wine`), `build/ntdll-unix` rebuilt, and Mesa patch `0004` (Zink on MoltenVK: fragment inputs Metal can link, and no crash when a pipeline fails). They need the desktop OpenGL (Zink) backend; fixed-function OpenGL does not exist in OpenGL ES. A 32-bit process gets `GL_EXTENSIONS` cut to 3072 bytes, because these engines copy it into a fixed buffer; `env.MADEIRA_GL_EXTENSIONS_MAX` in madeira.cfg sets another limit (0 = none). - Each script says what source to download if it is missing. Verified on the development machine. `faultrep.dll` (Windows Error Reporting stubs; Wine's faultrep lacks `WerReportHang`, and Ghost of Tsushima imports it): `build/faultrep/build.sh`. 6. OpenGL games: - - Before step 3, apply the Wine patch: `git -C wine apply ../patches/wine-opengl-winios.patch`. Then rebuild the tracked `opengl32.dll` with `build/wine-pe/build-modules.sh opengl32`. + - The build scripts apply the Wine patch themselves (`tools/build/apply-wine-patches.sh`, run by `tools/build/build-wine.sh`, `build/ntdll-unix/build.sh` and `build/wine-i386/build.sh`; by hand: `git -C wine apply ../patches/wine-opengl-winios.patch`). Then rebuild the tracked `opengl32.dll` with `build/wine-pe/build-modules.sh opengl32`. - `build/moltenvk-ios/build.sh`, then `build/mesa-ios/build.sh` -> `app/Madeira/gl/`, the desktop OpenGL backend. Optional: without it the app uses OpenGL ES. The script applies every file in `build/mesa-ios/patches` once (a `.madeira-applied-*` marker in the Mesa source records it), so an existing Mesa tree picks up a new patch on the next run. - 32-bit OpenGL games (id Tech 3: Jedi Academy, Jedi Outcast, Quake III) need all of: the regenerated wow64 thunks in `wine-opengl-winios.patch` (from `dlls/opengl32/make_opengl`; to regenerate, run `perl make_opengl` in `wine/dlls/opengl32`, which downloads the pinned Khronos registry files into `~/.cache/wine`), `build/ntdll-unix` rebuilt, and Mesa patch `0004` (Zink on MoltenVK: fragment inputs Metal can link, and no crash when a pipeline fails). They need the desktop OpenGL (Zink) backend; fixed-function OpenGL does not exist in OpenGL ES. A 32-bit process gets `GL_EXTENSIONS` cut to 3072 bytes, because these engines copy it into a fixed buffer; `env.MADEIRA_GL_EXTENSIONS_MAX` in madeira.cfg sets another limit (0 = none). - Each script says what source to download if it is missing. Verified on the development machine. diff --git a/docs/JAPANESE.md b/docs/JAPANESE.md new file mode 100644 index 0000000000..1673d9d08e --- /dev/null +++ b/docs/JAPANESE.md @@ -0,0 +1,47 @@ +# Japanese games and visual novels + +Many Japanese PC games, visual novels above all, keep their text in Shift-JIS +(Windows code page 932) and draw it with MS Gothic, MS Mincho or MS UI Gothic. +When the iPhone is set to another language, Wine runs them with an English or +European code page and none of those fonts exist, so: + +- text shows as gibberish (mojibake) or as empty squares, or does not show at all; +- some games do not start at all, because a file name, a script or a resource + name cannot be converted. + +## Turning it on + +**Game details › Compatibility › Japanese locale**, for each game that needs it. +It applies the next time the game starts. + +The same switch can be written by hand, for one game in **Game details › This +game's config** or for every game in `Documents/madeira.cfg`: + +```text +env.MADEIRA_JAPANESE = 1 +``` + +## What it does + +| Part | Effect | +|---|---| +| Locale | `LC_ALL = ja_JP.UTF-8`: Wine's system and user locale are Japanese (ja-JP), so the ANSI and OEM code pages are 932 (Shift-JIS). A Japanese `env.LC_ALL` already in the game's config is kept. | +| Code page tables | `env.MADEIRA_NLS_EXTRA = 1`: Wine's `c_932.nls` (and the other CJK tables, plus `c_10000.nls`, Mac Roman, which Wine's font code asks for when it reads font names) are loaded from the app's `nls/extra` folder. Without them code page 932 does not exist in Madeira. | +| Font | The bundled **Noto Sans JP** (SIL Open Font License 1.1) is copied into the prefix's `C:\windows\Fonts`. | +| Font names | `HKCU\Software\Wine\Fonts\Replacements` maps MS Gothic, MS PGothic, MS UI Gothic, MS Mincho, MS PMincho, Meiryo, Yu Gothic, Yu Mincho and their Japanese names (MS ゴシック, MS 明朝 …) onto Noto Sans JP. A replacement only applies while no real font of that name is installed, so a game that brings its own fonts keeps them. | + +The font and the font names stay in the prefix once written. Other games only +see them when they ask for one of those Japanese font names or draw Japanese +text. The locale and code page are set only for games that have the switch on. + +The log shows `[japanese]` lines for each step. + +## Known limits + +- Mincho (serif) names are mapped to the same sans-serif font; the text is + readable but does not look like Mincho. +- MS Gothic is a fixed-pitch font and Noto Sans JP is not. A game that lays out + half-width Latin text in fixed columns can show it slightly misaligned. If a + game needs the real metrics, installing a fixed-pitch Japanese font (for + example IPAGothic) into `C:\windows\Fonts` takes precedence over the mapping. +- Text inside the game's own images is not affected. diff --git a/tools/build/apply-wine-patches.sh b/tools/build/apply-wine-patches.sh new file mode 100755 index 0000000000..c0fcb09842 --- /dev/null +++ b/tools/build/apply-wine-patches.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Applies Madeira's patches to the wine submodule: patches/wine-*.patch. +# +# patches/wine-opengl-winios.patch carries opengl32's 32-bit (WoW64) support on +# iOS: the regenerated wow64 thunks convert every embedded guest pointer through +# the process's 4 GB window, get_teb64() included, plus the winios OpenGL ES +# contexts. docs/BUILDING.md asked for it to be applied by hand before building; +# a build that skipped it (every CI build) left 32-bit programs that load +# opengl32.dll dying in its thread attach ("opengl32.dll failed to initialize", +# c0000142) -- also programs that only probe DirectX through dxdiagn/ddraw -> +# wined3d -> opengl32, which then report that DirectX 9.0c is missing. +# +# Idempotent: a patch that is already applied is skipped, one that applies is +# applied, and one that does neither stops the build. +set -euo pipefail +ROOT="${ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +WINE="$ROOT/wine" +[[ -f "$WINE/configure" ]] || { echo "apply-wine-patches: wine submodule is missing" >&2; exit 1; } + +shopt -s nullglob +for p in "$ROOT"/patches/wine-*.patch; do + name=$(basename "$p") + if git -C "$WINE" apply --reverse --check "$p" >/dev/null 2>&1; then + echo "wine patch $name: already applied" + elif git -C "$WINE" apply --check "$p" >/dev/null 2>&1; then + git -C "$WINE" apply "$p" + echo "wine patch $name: applied" + else + echo "ERROR: wine patch $name neither applies nor is already applied:" >&2 + git -C "$WINE" apply --check "$p" >&2 || true + exit 1 + fi +done diff --git a/tools/build/build-runtime-component.sh b/tools/build/build-runtime-component.sh index 8ce9d06539..c17ec587b6 100755 --- a/tools/build/build-runtime-component.sh +++ b/tools/build/build-runtime-component.sh @@ -22,6 +22,7 @@ esac case "${1:-}" in windows) git -C "$ROOT" submodule update --init --recursive wine dxmt FEX + "$ROOT/tools/build/apply-wine-patches.sh" xcodebuild -downloadComponent MetalToolchain "$ROOT/tools/build/build-shader-headers.sh" ;; diff --git a/tools/build/build-shader-headers.sh b/tools/build/build-shader-headers.sh index 3287399d80..d353e7eb29 100755 --- a/tools/build/build-shader-headers.sh +++ b/tools/build/build-shader-headers.sh @@ -10,19 +10,24 @@ OUT="$ROOT/build/dxmt-ios/shader-headers" SHADERS=(air_msad air_samplepos air_tessellation) # Metal toolchain 32023 (Xcode 27) gave __metal_atomic_fetch_add_explicit a fifth -# memory-flags argument. Pass it when the toolchain defines it. +# memory-flags argument. Xcode 26.x's toolchain already defines +# __METAL_MEMORY_FLAGS_NONE__ but its builtin still takes four, so the macro +# cannot choose the form: MADEIRA_METAL_ATOMIC_FLAGS does, and the compile below +# tries the five-argument form first and falls back to four. python3 - "$SRC/air_tessellation.metal" <<'PY' import sys from pathlib import Path p = Path(sys.argv[1]) s = p.read_text() old = " return __metal_atomic_fetch_add_explicit(out_count, 1, int(memory_order_relaxed), __METAL_MEMORY_SCOPE_THREADGROUP__);\n" -if "__METAL_MEMORY_FLAGS_NONE__" not in s: +five = old.replace("__);", "__, __METAL_MEMORY_FLAGS_NONE__);") +earlier = "#ifdef __METAL_MEMORY_FLAGS_NONE__\n" + five + "#else\n" + old + "#endif\n" +if "MADEIRA_METAL_ATOMIC_FLAGS" not in s: + if earlier in s: # a checkout patched by the earlier version of this script + s = s.replace(earlier, old, 1) if old not in s: raise SystemExit(f"expected atomic call not found in {p}") - new = ("#ifdef __METAL_MEMORY_FLAGS_NONE__\n" - + old.replace("__);", "__, __METAL_MEMORY_FLAGS_NONE__);") - + "#else\n" + old + "#endif\n") + new = "#if MADEIRA_METAL_ATOMIC_FLAGS\n" + five + "#else\n" + old + "#endif\n" p.write_text(s.replace(old, new, 1)) PY @@ -30,7 +35,16 @@ for name in "${SHADERS[@]}"; do [[ -f "$SRC/$name.metal" ]] || { echo "ERROR: missing shader source $SRC/$name.metal (is the dxmt submodule checked out?)" >&2; exit 1; } echo "shader $name: compiling" mkdir -p "$OUT" - xcrun -sdk macosx metal -o "$OUT/$name.air" -c "$SRC/$name.metal" \ - -std=metal3.1 --target=air64-apple-macos14.0 + if ! xcrun -sdk macosx metal -o "$OUT/$name.air" -c "$SRC/$name.metal" \ + -std=metal3.1 --target=air64-apple-macos14.0 -DMADEIRA_METAL_ATOMIC_FLAGS=1 2>"$OUT/$name.err"; then + if [[ "$name" == air_tessellation ]] && grep -q "too many arguments" "$OUT/$name.err"; then + echo "shader $name: this Metal toolchain's atomic builtin takes four arguments" + xcrun -sdk macosx metal -o "$OUT/$name.air" -c "$SRC/$name.metal" \ + -std=metal3.1 --target=air64-apple-macos14.0 -DMADEIRA_METAL_ATOMIC_FLAGS=0 + else + cat "$OUT/$name.err" >&2; exit 1 + fi + fi + rm -f "$OUT/$name.err" xxd -n "$name" -i "$OUT/$name.air" "$OUT/$name.h" done diff --git a/tools/build/build-wine.sh b/tools/build/build-wine.sh index 514067cb2d..9cd9b69897 100755 --- a/tools/build/build-wine.sh +++ b/tools/build/build-wine.sh @@ -8,6 +8,9 @@ export PATH="$MINGW:$PATH" [[ -x "$WINE/configure" ]] || { echo "ERROR: wine submodule is missing" >&2; exit 1; } +# Madeira's wine patches (32-bit OpenGL among them) before anything compiles from the tree. +"$ROOT/tools/build/apply-wine-patches.sh" + # Native macOS build tree. Madeira's iOS unix-side libraries consume config.h, # generated headers, and host build outputs from here. aarch64 is the native # PE architecture used by the tracked Wine/DXMT side. diff --git a/tools/build/ci-select-xcode.sh b/tools/build/ci-select-xcode.sh new file mode 100755 index 0000000000..105922e791 --- /dev/null +++ b/tools/build/ci-select-xcode.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Selects the newest Xcode 27 on a GitHub-hosted macOS runner, so a fork without +# the upstream `xcode-27` runner label can build the IPA. Without an Xcode 27 +# (GitHub's macos-26 image ships 26.x only) it takes the newest Xcode 26: the +# one iOS 27 SDK field the ntdll unix side reads (rusage_info_v6 +# .ri_page_wait_time_mach) is detected by build/ntdll-unix/build.sh and left out +# when the SDK lacks it. Every job runs this, so all of them pick the same Xcode +# and agree on ci-input-key.sh's fingerprint. +# +# MADEIRA_XCODE=/Applications/Xcode_27.1.app use exactly this one +# +# A runner whose selected Xcode is already 27 or later is left as it is. +set -euo pipefail + +major() { xcodebuild -version 2>/dev/null | awk '/^Xcode/ { split($2, v, "."); print v[1]; exit }'; } + +if [[ -n "${MADEIRA_XCODE:-}" ]]; then + pick="$MADEIRA_XCODE" +elif [[ "$(major)" -ge 27 ]] 2>/dev/null; then + echo "Xcode already selected: $(xcode-select -p)" + xcodebuild -version + exit 0 +else + # Newest release build first; betas (Xcode_27.0_beta*.app) only when nothing else exists. + pick=$(ls -d /Applications/Xcode_27*.app 2>/dev/null | grep -vi beta | sort -V | tail -1 || true) + [[ -n "$pick" ]] || pick=$(ls -d /Applications/Xcode_27*.app /Applications/Xcode-27*.app 2>/dev/null | sort -V | tail -1 || true) + if [[ -z "$pick" ]]; then + pick=$(ls -d /Applications/Xcode_26*.app 2>/dev/null | grep -vi beta | sort -V | tail -1 || true) + [[ -n "$pick" ]] && echo "::warning::No Xcode 27 on this runner; building with $(basename "$pick") (iOS 26 SDK)" + fi +fi + +if [[ -z "${pick:-}" || ! -d "$pick" ]]; then + echo "ERROR: no Xcode 27 or 26 on this runner. Installed:" >&2 + ls -d /Applications/Xcode*.app >&2 || true + echo "Set the repository variable MADEIRA_RUNNER to a runner image that has Xcode 27" >&2 + echo "(Settings > Secrets and variables > Actions > Variables), e.g. macos-26 once it ships Xcode 27," >&2 + echo "or MADEIRA_XCODE to the Xcode app to use." >&2 + exit 1 +fi + +sudo xcode-select -s "$pick/Contents/Developer" +xcodebuild -version +xcrun --sdk iphoneos --show-sdk-version