15 Commits

Author SHA1 Message Date
patchzyy 3d2c1fd8b8 extra safety 2026-09-06 10:49:01 +02:00
patchzyy 0e647b3dbb Merge commit '28cbea46ab44105fde928653507add5d9d4873f4' into fix/pr168-save-safety 2026-09-06 10:13:19 +02:00
patchzyy 28cbea46ab import setting.txt 2026-09-06 10:04:07 +02:00
JGM01 55ea5dfbda I dont really want to change this to be honest. 2026-09-05 22:55:59 -04:00
JGM01 4a7f592b8a shorten 2026-09-05 22:51:58 -04:00
JGM01 c95f1c95b7 treat empty mkw save as missing
first-run format zero-fills rksys.dat before any real save; a quit before
the first save left an all-zero file that read back as corrupt and trapped
the user in a delete/recreate loop. read opens now treat an all-zero
rksys.dat as absent (a real save always begins with the RKSD0006 header),
so the game recreates it from scratch. also ignore native build output.
2026-09-05 22:48:05 -04:00
patchzyy 8e57cc162f version 2026-09-06 00:48:41 +02:00
patchzyy 1c0a3edee9 Add synthetic recompilation CI workflow (#161) 2026-09-05 23:00:47 +02:00
patchzyy d1d80613cc Load console identity from NAND setting.txt (#164) 2026-09-05 23:00:23 +02:00
patchzyy 5d67b229f6 Update settings_overlay.cpp 2026-09-05 16:54:24 +02:00
patchzyy 56db6ba641 v0.2.28 2026-09-05 16:09:34 +02:00
Michael G a67069afd3 fix(macos): increase guest fiber stack size (#154) 2026-09-05 15:45:29 +02:00
patchzyy 009697fb97 Update network_socket.cpp 2026-09-05 14:31:53 +02:00
Cristian Boehm 3f7fed48c9 Fix GC Pocket+ rumble stop handling (#148) 2026-09-05 10:20:15 +02:00
theofficialgman 6eba523d70 Switch back to LLVM 22 so build can succeed on at least some systems (#141)
two upstream LLVM bugs currently prevent building on some of the newest distros. There is no current LLVM release that works on them so we are pending fixes from LLVM
https://github.com/patchzyy/Wiicompiled/issues/136
2026-09-05 10:17:21 +02:00
37 changed files with 160 additions and 806 deletions
-35
View File
@@ -38,38 +38,3 @@ jobs:
- name: Test - name: Test
run: dotnet test translator/Translator.sln -c Release --no-build --verbosity normal run: dotnet test translator/Translator.sln -c Release --no-build --verbosity normal
macos_substrate:
name: macOS arm64 (configure + substrate tests)
runs-on: macos-14
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Configure native runtime
shell: bash
run: |
test "$(uname -m)" = arm64
cmake -S runtime -B build-macos -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DMKW_BUILD_PRODUCTS=OFF
grep -qx 'CMAKE_OSX_DEPLOYMENT_TARGET:STRING=12.0' \
build-macos/CMakeCache.txt
- name: Build macOS portability targets
shell: bash
run: |
cmake --build build-macos --target \
mkw_platform_paths_tests \
mkw_nand_save_tests \
mkw_nand_settings_tests \
mkw_sc_serial_tests \
mkw_input_expr_tests \
mkw_macos_native_compile \
mkw_macos_context_abi_tests \
mkw_macos_host_context_tests \
mkw_macos_guest_flat_memory_tests
- name: Test execution substrate
shell: bash
run: ctest --test-dir build-macos --output-on-failure
+6 -127
View File
@@ -1,8 +1,8 @@
name: Package installers name: Package installers
# Builds the per-platform installer/setup tool (WiiCompiled-Setup.exe / # Builds the per-platform installer/setup tool (WiiCompiled-Setup.exe /
# WiiCompiled-Setup-x86_64.AppImage / WiiCompiled-Setup.pkg) via the platform packaging scripts - # WiiCompiled-Setup-x86_64.AppImage) via Launcher/Build-Installer.ps1 and
# the same scripts a maintainer runs by hand today to # Launcher/build-appimage.sh respectively - the same scripts a maintainer runs by hand today to
# produce a GitHub Release asset. This does NOT build the actual translated game executable: # produce a GitHub Release asset. This does NOT build the actual translated game executable:
# that step requires the end user's own Mario Kart Wii dump (Assets/main.dol, Assets/StaticR.rel), # that step requires the end user's own Mario Kart Wii dump (Assets/main.dol, Assets/StaticR.rel),
# which is proprietary and not present in this repository or in CI. # which is proprietary and not present in this repository or in CI.
@@ -11,11 +11,6 @@ on:
tags: tags:
- '*' - '*'
workflow_dispatch: workflow_dispatch:
inputs:
version:
description: Package version
required: true
type: string
permissions: permissions:
contents: read contents: read
@@ -95,130 +90,14 @@ jobs:
if-no-files-found: error if-no-files-found: error
archive: false archive: false
macos-setup-package:
name: macOS (universal Setup.pkg)
runs-on: macos-14
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
- name: Verify Apple Silicon runner
shell: bash
run: |
test "$(uname -m)" = arm64
xcode-select -p
- name: Download pinned Nod tools
shell: bash
run: |
mkdir -p Launcher/artifacts/macos
nodtool_version=v2.0.0-alpha.10
nodtool_arm64_asset=nodtool-macos-arm64
nodtool_arm64_sha256=e23ca466999b720c55e6d29c9683fce8cc74451ba64ead2e543d50129f24528a
nodtool_x86_64_asset=nodtool-macos-x86_64
nodtool_x86_64_sha256=f68f504dc2b72694b468ca78b6a24142c7aa5c8800f77564297f4143682e6575
curl -fsSL --retry 3 \
"https://github.com/encounter/nod/releases/download/${nodtool_version}/${nodtool_arm64_asset}" \
-o Launcher/artifacts/macos/nodtool-arm64
curl -fsSL --retry 3 \
"https://github.com/encounter/nod/releases/download/${nodtool_version}/${nodtool_x86_64_asset}" \
-o Launcher/artifacts/macos/nodtool-x86_64
printf '%s %s\n' "$nodtool_arm64_sha256" Launcher/artifacts/macos/nodtool-arm64 | shasum -a 256 -c -
printf '%s %s\n' "$nodtool_x86_64_sha256" Launcher/artifacts/macos/nodtool-x86_64 | shasum -a 256 -c -
chmod +x Launcher/artifacts/macos/nodtool-arm64 Launcher/artifacts/macos/nodtool-x86_64
- name: Publish self-contained Translator tools
shell: bash
run: |
dotnet publish translator/src/Translator.Cli/Translator.Cli.csproj \
-c Release -r osx-arm64 --self-contained true \
-p:PublishSingleFile=true \
-o Launcher/artifacts/macos/translator-arm64
dotnet publish translator/src/Translator.Cli/Translator.Cli.csproj \
-c Release -r osx-x64 --self-contained true \
-p:PublishSingleFile=true \
-o Launcher/artifacts/macos/translator-x86_64
- name: Download pinned universal Ninja
shell: bash
run: |
ninja_version=1.13.2
ninja_sha256=c99048673aa765960a99cf10c6ddb9f1fad506099ff0a0e137ad8960a88f321b
curl -fsSL --retry 3 "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-mac.zip" -o ninja-mac.zip
printf '%s %s\n' "$ninja_sha256" ninja-mac.zip | shasum -a 256 -c -
unzip -q ninja-mac.zip -d Launcher/artifacts/macos/ninja
chmod +x Launcher/artifacts/macos/ninja/ninja
- name: Download pinned portable CMake
shell: bash
run: |
cmake_version=4.4.3
archive="cmake-${cmake_version}-macos-universal.tar.gz"
base_url="https://github.com/Kitware/CMake/releases/download/v${cmake_version}"
expected_sha256=0c5d65251c14cc884bfa16bdbed3c263ce5bffe2e21c0d0d00962cb0610464fa
curl -fsSL --retry 3 "$base_url/$archive" -o "$archive"
printf '%s %s\n' "$expected_sha256" "$archive" | shasum -a 256 -c -
tar -xzf "$archive"
mv "cmake-${cmake_version}-macos-universal/CMake.app/Contents" Launcher/artifacts/macos/cmake
- name: Build Setup.pkg
env:
PACKAGE_VERSION: ${{ inputs.version }}
TAG_VERSION: ${{ github.ref_name }}
shell: bash
run: |
package_version="$PACKAGE_VERSION"
if [[ -z "$package_version" ]]; then package_version="${TAG_VERSION#v}"; fi
mkdir -p Launcher/dist
Launcher/macos/build-setup-pkg.command \
--nodtool-arm64 Launcher/artifacts/macos/nodtool-arm64 \
--nodtool-x86_64 Launcher/artifacts/macos/nodtool-x86_64 \
--translator-arm64 Launcher/artifacts/macos/translator-arm64/Translator.Cli \
--translator-x86_64 Launcher/artifacts/macos/translator-x86_64/Translator.Cli \
--cmake-root Launcher/artifacts/macos/cmake \
--ninja-arm64 Launcher/artifacts/macos/ninja/ninja \
--ninja-x86_64 Launcher/artifacts/macos/ninja/ninja \
--output Launcher/dist/WiiCompiled-Setup.pkg \
--version "$package_version"
- name: Verify package layout and architecture-specific tools
shell: bash
run: |
pkgutil --check-signature Launcher/dist/WiiCompiled-Setup.pkg
! pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \
grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)'
expanded="$RUNNER_TEMP/wiicompiled-setup-expanded"
pkgutil --expand-full Launcher/dist/WiiCompiled-Setup.pkg "$expanded"
resources="$expanded/Payload/Applications/WiiCompiled Setup.app/Contents/Resources"
for arch in arm64 x86_64; do
for tool in nodtool Translator.Cli ninja; do
lipo "$resources/tools/$arch/$tool" -verify_arch "$arch"
done
done
lipo "$resources/tools/cmake/bin/cmake" -verify_arch arm64 x86_64
bash "$resources/setup.command" --help
/usr/bin/arch -x86_64 /bin/bash "$resources/setup.command" --help
- uses: actions/upload-artifact@v7
with:
name: WiiCompiled-Setup-macos-universal
path: Launcher/dist/WiiCompiled-Setup.pkg
if-no-files-found: error
archive: false
# Publishes the packaged installers as a GitHub Release whenever a v* tag is pushed. Wheel Wizard # Publishes the packaged installers as a GitHub Release whenever a v* tag is pushed. Wheel Wizard
# discovers updates from these releases, so the contract it relies on is enforced here: a full # discovers updates from these releases, so the contract it relies on is enforced here: a full
# (non-prerelease) release whose tag is v<semver>, carrying the expected platform assets, # (non-prerelease) release whose tag is v<semver>, carrying an asset named exactly
# produced by setup hosts that report that same version. # WiiCompiled-Setup.exe, produced by a setup host that reports that same version.
release: release:
name: Publish GitHub Release name: Publish GitHub Release
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
needs: [linux-appimage, windows-installer, macos-setup-package, recompilation] needs: [linux-appimage, windows-installer, recompilation]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
@@ -264,7 +143,7 @@ jobs:
set -euo pipefail set -euo pipefail
ls -lR artifacts ls -lR artifacts
assets=() assets=()
for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage WiiCompiled-Setup.pkg; do for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage; do
found="$(find artifacts -type f -name "$name" | head -n 1)" found="$(find artifacts -type f -name "$name" | head -n 1)"
[ -n "$found" ] && [ -s "$found" ] || { echo "::error::missing release asset $name"; exit 1; } [ -n "$found" ] && [ -s "$found" ] || { echo "::error::missing release asset $name"; exit 1; }
assets+=("$found") assets+=("$found")
+1 -1
View File
@@ -281,7 +281,7 @@ foreach ($required in @('ToolkitFingerprint','TranslationFingerprint','NativeToo
$manifest = [ordered]@{ $manifest = [ordered]@{
SchemaVersion = 2 SchemaVersion = 2
ProductVersion = '0.2.31' ProductVersion = '0.2.29'
ExpectedGameId = $pins.GameId ExpectedGameId = $pins.GameId
ExpectedDolSha256 = $pins.DolSha256 ExpectedDolSha256 = $pins.DolSha256
ExpectedRelSha256 = $pins.RelSha256 ExpectedRelSha256 = $pins.RelSha256
@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>WiiCompiled.Setup.Common.Cli</RootNamespace> <RootNamespace>WiiCompiled.Setup.Common.Cli</RootNamespace>
<AssemblyName>WiiCompiled.Setup.Common.Cli</AssemblyName> <AssemblyName>WiiCompiled.Setup.Common.Cli</AssemblyName>
<Version>0.2.31</Version> <Version>0.2.29</Version>
<Authors>patchzy</Authors> <Authors>patchzy</Authors>
<Product>WiiCompiled</Product> <Product>WiiCompiled</Product>
<Description>Packaging-time helper: resolves (downloading if needed) the nodtool binary bundled by build-appimage.sh and Build-Installer.ps1</Description> <Description>Packaging-time helper: resolves (downloading if needed) the nodtool binary bundled by build-appimage.sh and Build-Installer.ps1</Description>
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>WiiCompiled.Setup.Common</RootNamespace> <RootNamespace>WiiCompiled.Setup.Common</RootNamespace>
<AssemblyName>WiiCompiled.Setup.Common</AssemblyName> <AssemblyName>WiiCompiled.Setup.Common</AssemblyName>
<Version>0.2.31</Version> <Version>0.2.29</Version>
<Authors>patchzy</Authors> <Authors>patchzy</Authors>
<Product>WiiCompiled</Product> <Product>WiiCompiled</Product>
<Description>Shared nodtool/Retro-WFC-payload logic used by both the Windows and Linux installers</Description> <Description>Shared nodtool/Retro-WFC-payload logic used by both the Windows and Linux installers</Description>
+1 -1
View File
@@ -3,7 +3,7 @@ namespace WiiCompiled.Setup.Linux;
internal static class ProductInfo internal static class ProductInfo
{ {
public const string Name = "WiiCompiled"; public const string Name = "WiiCompiled";
public const string Version = "0.2.31"; public const string Version = "0.2.29";
} }
/// <summary>One installed product's record inside install-state.json.</summary> /// <summary>One installed product's record inside install-state.json.</summary>
@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AssemblyName>WiiCompiled.Setup.Linux</AssemblyName> <AssemblyName>WiiCompiled.Setup.Linux</AssemblyName>
<RootNamespace>WiiCompiled.Setup.Linux</RootNamespace> <RootNamespace>WiiCompiled.Setup.Linux</RootNamespace>
<Version>0.2.31</Version> <Version>0.2.29</Version>
<Authors>patchzy</Authors> <Authors>patchzy</Authors>
<Product>WiiCompiled</Product> <Product>WiiCompiled</Product>
<Description>Command-line installer and launcher for WiiCompiled on Linux</Description> <Description>Command-line installer and launcher for WiiCompiled on Linux</Description>
@@ -121,7 +121,7 @@ internal static class PlatformChecks
internal static class ProductInfo internal static class ProductInfo
{ {
public const string Name = "WiiCompiled"; public const string Name = "WiiCompiled";
public const string Version = "0.2.31"; public const string Version = "0.2.29";
/// <summary> /// <summary>
/// The setup executable is copied into the installation under this name. It is the launcher and /// The setup executable is copied into the installation under this name. It is the launcher and
@@ -7,7 +7,7 @@
<AssemblyName>WiiCompiled.Setup</AssemblyName> <AssemblyName>WiiCompiled.Setup</AssemblyName>
<RootNamespace>WiiCompiled.Setup.Windows</RootNamespace> <RootNamespace>WiiCompiled.Setup.Windows</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.2.31</Version> <Version>0.2.29</Version>
<Authors>patchzy</Authors> <Authors>patchzy</Authors>
<Product>WiiCompiled</Product> <Product>WiiCompiled</Product>
<Description>Command-line installer and launcher for WiiCompiled</Description> <Description>Command-line installer and launcher for WiiCompiled</Description>
+6 -9
View File
@@ -22,7 +22,7 @@ Usage: local-build-macos.command --output-dir DIR [options]
--retro-rewind-package-dir DIR RetroRewind6 directory (required for Retro Rewind) --retro-rewind-package-dir DIR RetroRewind6 directory (required for Retro Rewind)
--retro-wfc-offline-dir DIR Directory containing binary/payload.RMCPD00.bin --retro-wfc-offline-dir DIR Directory containing binary/payload.RMCPD00.bin
--skip-retro-wfc-payload Build Retro Rewind without the shared Retro-WFC payload --skip-retro-wfc-payload Build Retro Rewind without the shared Retro-WFC payload
--force-clean-build Delete local generated and current-architecture native build caches --force-clean-build Delete local generated and native-build-macos caches
--parallel N Pin translation and build parallelism --parallel N Pin translation and build parallelism
--cmake PATH --ninja PATH Override build tools --cmake PATH --ninja PATH Override build tools
--dotnet PATH Override dotnet --dotnet PATH Override dotnet
@@ -56,9 +56,7 @@ while (($#)); do
done done
[[ $(uname -s) == Darwin ]] || fail 'this build script is for macOS only' [[ $(uname -s) == Darwin ]] || fail 'this build script is for macOS only'
macos_arch=$(uname -m) [[ $(uname -m) == arm64 ]] || fail 'the current macOS product target is Apple Silicon only'
case "$macos_arch" in arm64|x86_64) ;; *) fail "unsupported macOS architecture: $macos_arch" ;; esac
macos_deployment_target=12.0
workspace=$(cd "$workspace" && pwd) workspace=$(cd "$workspace" && pwd)
[[ -n "$output_dir" ]] || fail '--output-dir is required' [[ -n "$output_dir" ]] || fail '--output-dir is required'
case "$profile" in base|retro-rewind|both) ;; *) fail '--profile must be base, retro-rewind, or both' ;; esac case "$profile" in base|retro-rewind|both) ;; *) fail '--profile must be base, retro-rewind, or both' ;; esac
@@ -75,8 +73,7 @@ for tool in "$cmake_bin" "$ninja_bin" clang clang++ shasum; do command -v "$tool
project="$workspace/projects/mkwii/recomp.yml"; assets="$workspace/Assets"; generated="$workspace/generated" project="$workspace/projects/mkwii/recomp.yml"; assets="$workspace/Assets"; generated="$workspace/generated"
functions="$generated/functions"; metadata="$generated/base_translation_output.json"; manifest_dir="$workspace/build/base" functions="$generated/functions"; metadata="$generated/base_translation_output.json"; manifest_dir="$workspace/build/base"
manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards" manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards"; native_build="$workspace/native-build-macos"
native_build="$workspace/native-build-macos-$macos_arch"
assert_file "$project" 'translation project' assert_file "$project" 'translation project'
if [[ -n "$game" ]]; then "$script_dir/macos/extract-disc.command" --game "$game" --assets-dir "$assets" --nodtool "$nodtool"; fi if [[ -n "$game" ]]; then "$script_dir/macos/extract-disc.command" --game "$game" --assets-dir "$assets" --nodtool "$nodtool"; fi
assert_file "$assets/main.dol" 'extracted main.dol'; assert_file "$assets/StaticR.rel" 'extracted StaticR.rel' assert_file "$assets/main.dol" 'extracted main.dol'; assert_file "$assets/StaticR.rel" 'extracted StaticR.rel'
@@ -134,9 +131,9 @@ if (( builds_retro )); then args+=(--resolved-profile "$mod_out/resolved_dispatc
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}" step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
step configure-native 'Configuring the native toolchain' step configure-native 'Configuring the native toolchain'
"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DCMAKE_OSX_ARCHITECTURES="$macos_arch" -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target" -DAURORA_SDL3_PROVIDER=vendor -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" "$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs"
targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind) targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind)
step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs" step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs"
if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}"; fi
if (( builds_retro )); then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product RetroRewind --output-dir "$output_dir" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi if (( builds_retro )); then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product RetroRewind --output-dir "$output_dir"; fi
printf 'MKWCBUILD:OUTPUT=%s\n' "$output_dir" printf 'MKWCBUILD:OUTPUT=%s\n' "$output_dir"
+18 -66
View File
@@ -9,15 +9,12 @@ fail() { printf 'build-setup-pkg.command: error: %s\n' "$*" >&2; exit 1; }
copy_clean() { DITTONORSRC=1 ditto --norsrc --noqtn "$@"; } copy_clean() { DITTONORSRC=1 ditto --norsrc --noqtn "$@"; }
usage() { usage() {
cat <<'EOF' cat <<'EOF'
Usage: build-setup-pkg.command --nodtool-arm64 PATH --nodtool-x86_64 PATH --translator-arm64 PATH --translator-x86_64 PATH --cmake-root DIR --ninja-arm64 PATH --ninja-x86_64 PATH --output PKG [options] Usage: build-setup-pkg.command --nodtool PATH --translator PATH --cmake-root DIR --ninja PATH --output PKG [options]
Creates a game-code-free WiiCompiled Setup.pkg. The supplied tools must be Creates a game-code-free WiiCompiled Setup.pkg. The supplied tools must be
maintainer-verified, redistributable macOS artifacts for both arm64 and maintainer-verified, redistributable macOS arm64 artifacts. The resulting pkg
x86_64. The setup package selects native tools for its host while the game is is unsigned unless --installer-identity is supplied; releases should sign and
compiled locally for that host architecture. CMake must be universal2. The notarize it with a Developer ID Installer certificate.
resulting pkg is unsigned unless
--installer-identity is supplied; releases should sign and notarize it with a
Developer ID Installer certificate.
--workspace DIR Repository root (default: script's grandparent) --workspace DIR Repository root (default: script's grandparent)
--version VERSION Bundle/package version (default: 0.1.0) --version VERSION Bundle/package version (default: 0.1.0)
@@ -26,17 +23,14 @@ EOF
} }
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
workspace=$(cd "$script_dir/../.." && pwd); nodtool_arm64=""; nodtool_x86_64=""; translator_arm64=""; translator_x86_64=""; cmake_root=""; ninja_arm64=""; ninja_x86_64=""; output=""; version=0.1.0; identity="" workspace=$(cd "$script_dir/../.." && pwd); nodtool=""; translator=""; cmake_root=""; ninja=""; output=""; version=0.1.0; identity=""
while (($#)); do while (($#)); do
case "$1" in case "$1" in
--workspace) workspace=${2:-}; shift 2 ;; --workspace) workspace=${2:-}; shift 2 ;;
--nodtool-arm64) nodtool_arm64=${2:-}; shift 2 ;; --nodtool) nodtool=${2:-}; shift 2 ;;
--nodtool-x86_64) nodtool_x86_64=${2:-}; shift 2 ;; --translator) translator=${2:-}; shift 2 ;;
--translator-arm64) translator_arm64=${2:-}; shift 2 ;;
--translator-x86_64) translator_x86_64=${2:-}; shift 2 ;;
--cmake-root) cmake_root=${2:-}; shift 2 ;; --cmake-root) cmake_root=${2:-}; shift 2 ;;
--ninja-arm64) ninja_arm64=${2:-}; shift 2 ;; --ninja) ninja=${2:-}; shift 2 ;;
--ninja-x86_64) ninja_x86_64=${2:-}; shift 2 ;;
--output) output=${2:-}; shift 2 ;; --output) output=${2:-}; shift 2 ;;
--version) version=${2:-}; shift 2 ;; --version) version=${2:-}; shift 2 ;;
--installer-identity) identity=${2:-}; shift 2 ;; --installer-identity) identity=${2:-}; shift 2 ;;
@@ -48,48 +42,12 @@ version=${version#v}
[[ "$version" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || fail '--version must contain one to three period-separated integers' [[ "$version" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || fail '--version must contain one to three period-separated integers'
IFS=. read -r version_major version_minor version_patch <<< "$version" IFS=. read -r version_major version_minor version_patch <<< "$version"
short_version="$version_major.${version_minor:-0}.${version_patch:-0}" short_version="$version_major.${version_minor:-0}.${version_patch:-0}"
for tool in pkgbuild productbuild ditto codesign lipo; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done for tool in pkgbuild productbuild ditto codesign; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done
for tool_path in "$nodtool_arm64" "$nodtool_x86_64" "$translator_arm64" "$translator_x86_64" "$ninja_arm64" "$ninja_x86_64"; do [[ -x "$tool_path" ]] || fail 'each architecture-specific tool must name an executable'; done [[ -x "$nodtool" ]] || fail '--nodtool must name an executable'
[[ -x "$translator" ]] || fail '--translator must name an executable'
[[ -x "$cmake_root/bin/cmake" ]] || fail '--cmake-root must contain bin/cmake' [[ -x "$cmake_root/bin/cmake" ]] || fail '--cmake-root must contain bin/cmake'
require_arch() { [[ -x "$ninja" ]] || fail '--ninja must name an executable'
local artifact=$1 arch=$2 label=$3 "$nodtool" --version >/dev/null || fail '--nodtool did not run successfully'
lipo "$artifact" -verify_arch "$arch" >/dev/null 2>&1 || fail "$label must contain a $arch slice: $artifact"
}
require_arch "$nodtool_arm64" arm64 '--nodtool-arm64'; require_arch "$nodtool_x86_64" x86_64 '--nodtool-x86_64'
require_arch "$translator_arm64" arm64 '--translator-arm64'; require_arch "$translator_x86_64" x86_64 '--translator-x86_64'
require_arch "$ninja_arm64" arm64 '--ninja-arm64'; require_arch "$ninja_x86_64" x86_64 '--ninja-x86_64'
lipo "$cmake_root/bin/cmake" -verify_arch arm64 x86_64 >/dev/null 2>&1 || fail '--cmake-root/bin/cmake must be universal2'
# Slice checks above prevent accidental cross-architecture packaging. Exercise
# each supplied executable as well: an incorrectly bundled runtime can have a
# valid Mach-O header but still fail before the setup app can use it. Apple
# Silicon maintainers validate Intel tools through Rosetta when it is present.
host_arch=$(uname -m)
run_for_arch() {
local arch=$1 label=$2
shift 2
if [[ "$arch" == "$host_arch" ]]; then
"$@" >/dev/null || fail "$label did not run successfully"
elif [[ "$host_arch" == arm64 && "$arch" == x86_64 ]] && /usr/bin/arch -x86_64 /usr/bin/true >/dev/null 2>&1; then
/usr/bin/arch -x86_64 "$@" >/dev/null || fail "$label did not run successfully under Rosetta"
else
# Intel hosts cannot execute arm64 binaries. The slice remains checked
# above; CI or an Apple Silicon maintainer must execute that tool set.
printf 'build-setup-pkg.command: warning: unable to execute %s on %s; architecture slice was verified, but run it in %s CI before release\n' \
"$label" "$host_arch" "$arch" >&2
fi
}
for arch in arm64 x86_64; do
if [[ "$arch" == arm64 ]]; then
nodtool=$nodtool_arm64; translator=$translator_arm64; ninja=$ninja_arm64
else
nodtool=$nodtool_x86_64; translator=$translator_x86_64; ninja=$ninja_x86_64
fi
run_for_arch "$arch" "--nodtool-$arch" "$nodtool" --version
run_for_arch "$arch" "--translator-$arch" "$translator" --help
run_for_arch "$arch" "--ninja-$arch" "$ninja" --version
done
run_for_arch "$host_arch" '--cmake-root/bin/cmake' "$cmake_root/bin/cmake" --version
workspace=$(cd "$workspace" && pwd); output=$(cd "$(dirname "$output")" && pwd)/$(basename "$output") workspace=$(cd "$workspace" && pwd); output=$(cd "$(dirname "$output")" && pwd)/$(basename "$output")
stage=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-pkg.XXXXXX") stage=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-pkg.XXXXXX")
trap 'rm -rf "$stage"' EXIT trap 'rm -rf "$stage"' EXIT
@@ -106,7 +64,7 @@ cat > "$app/Contents/Info.plist" <<EOF
<key>CFBundlePackageType</key><string>APPL</string> <key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>$short_version</string> <key>CFBundleShortVersionString</key><string>$short_version</string>
<key>CFBundleVersion</key><string>$version</string> <key>CFBundleVersion</key><string>$version</string>
<key>LSMinimumSystemVersion</key><string>12.0</string> <key>LSMinimumSystemVersion</key><string>14.0</string>
</dict></plist> </dict></plist>
EOF EOF
cat > "$app/Contents/MacOS/WiiCompiledSetup" <<'EOF' cat > "$app/Contents/MacOS/WiiCompiledSetup" <<'EOF'
@@ -145,17 +103,11 @@ copy_clean "$workspace/Launcher/local-build-macos.command" "$resources/workspace
copy_clean "$workspace/Launcher/macos/extract-disc.command" "$resources/workspace/Launcher/macos/extract-disc.command" copy_clean "$workspace/Launcher/macos/extract-disc.command" "$resources/workspace/Launcher/macos/extract-disc.command"
copy_clean "$workspace/Launcher/macos/publish-app.command" "$resources/workspace/Launcher/macos/publish-app.command" copy_clean "$workspace/Launcher/macos/publish-app.command" "$resources/workspace/Launcher/macos/publish-app.command"
chmod +x "$resources/workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/macos/"*.command chmod +x "$resources/workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/macos/"*.command
# setup.command uses this marker to refresh source inputs in an existing user mkdir -p "$resources/tools/cmake"
# workspace without replacing extracted game assets or Retro Rewind files. copy_clean "$nodtool" "$resources/tools/nodtool"; chmod +x "$resources/tools/nodtool"
printf '%s\n' "$version" > "$resources/workspace/.bundle-version" copy_clean "$translator" "$resources/tools/Translator.Cli"; chmod +x "$resources/tools/Translator.Cli"
mkdir -p "$resources/tools/cmake" "$resources/tools/arm64" "$resources/tools/x86_64"
copy_clean "$nodtool_arm64" "$resources/tools/arm64/nodtool"; chmod +x "$resources/tools/arm64/nodtool"
copy_clean "$nodtool_x86_64" "$resources/tools/x86_64/nodtool"; chmod +x "$resources/tools/x86_64/nodtool"
copy_clean "$translator_arm64" "$resources/tools/arm64/Translator.Cli"; chmod +x "$resources/tools/arm64/Translator.Cli"
copy_clean "$translator_x86_64" "$resources/tools/x86_64/Translator.Cli"; chmod +x "$resources/tools/x86_64/Translator.Cli"
copy_clean "$cmake_root" "$resources/tools/cmake" copy_clean "$cmake_root" "$resources/tools/cmake"
copy_clean "$ninja_arm64" "$resources/tools/arm64/ninja"; chmod +x "$resources/tools/arm64/ninja" copy_clean "$ninja" "$resources/tools/ninja"; chmod +x "$resources/tools/ninja"
copy_clean "$ninja_x86_64" "$resources/tools/x86_64/ninja"; chmod +x "$resources/tools/x86_64/ninja"
copy_clean "$workspace/LICENSE" "$resources/LICENSE" copy_clean "$workspace/LICENSE" "$resources/LICENSE"
copy_clean "$workspace/THIRD-PARTY-NOTICES.md" "$resources/THIRD-PARTY-NOTICES.md" copy_clean "$workspace/THIRD-PARTY-NOTICES.md" "$resources/THIRD-PARTY-NOTICES.md"
codesign --force --deep --sign - "$app" codesign --force --deep --sign - "$app"
@@ -1,11 +0,0 @@
# Cross-compile a thin x86_64 macOS build from an Apple Silicon Mac.
# Pass this file on the first configure with:
# -DCMAKE_TOOLCHAIN_FILE=/absolute/path/to/macos-x86_64-toolchain.cmake
#
# CMAKE_SYSTEM_PROCESSOR is deliberately declared here rather than inferred
# from CMAKE_OSX_ARCHITECTURES, so target-aware CMake dependencies select their
# x86_64 artifacts.
set(CMAKE_SYSTEM_NAME Darwin)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING
"Target macOS architectures" FORCE)
+10 -55
View File
@@ -5,36 +5,28 @@ set -euo pipefail
fail() { printf 'publish-app.command: error: %s\n' "$*" >&2; exit 1; } fail() { printf 'publish-app.command: error: %s\n' "$*" >&2; exit 1; }
usage() { usage() {
cat <<'EOF' cat <<'EOF'
Usage: publish-app.command --build-dir DIR --product {WiiCompiled|RetroRewind} --output-dir DIR [options] Usage: publish-app.command --build-dir DIR --product {WiiCompiled|RetroRewind} --output-dir DIR
Copies a locally built product and its runtime assets into OUTPUT-DIR/<product>.app. Copies a locally built product and its runtime assets into OUTPUT-DIR/<product>.app.
It bundles non-system dylibs, rewrites their install names, and ad-hoc signs the It bundles non-system dylibs, rewrites their install names, and ad-hoc signs the
result. This is suitable for local use; a release must replace ad-hoc signing result. This is suitable for local use; a release must replace ad-hoc signing
with the project's Developer ID signing and notarization process. with the project's Developer ID signing and notarization process.
--architecture {arm64|x86_64} Required architecture of the compiled product (default: host)
--minimum-system-version VERSION App bundle minimum macOS version (default: 12.0)
EOF EOF
} }
build_dir=""; product=""; output_dir=""; architecture=$(uname -m); minimum_system_version=12.0 build_dir=""; product=""; output_dir=""
while (($#)); do while (($#)); do
case "$1" in case "$1" in
--build-dir) build_dir=${2:-}; shift 2 ;; --build-dir) build_dir=${2:-}; shift 2 ;;
--product) product=${2:-}; shift 2 ;; --product) product=${2:-}; shift 2 ;;
--output-dir) output_dir=${2:-}; shift 2 ;; --output-dir) output_dir=${2:-}; shift 2 ;;
--architecture) architecture=${2:-}; shift 2 ;;
--minimum-system-version) minimum_system_version=${2:-}; shift 2 ;;
-h|--help) usage; exit 0 ;; -h|--help) usage; exit 0 ;;
*) fail "unknown option: $1" ;; *) fail "unknown option: $1" ;;
esac esac
done done
[[ "$product" == WiiCompiled || "$product" == RetroRewind ]] || fail '--product must be WiiCompiled or RetroRewind' [[ "$product" == WiiCompiled || "$product" == RetroRewind ]] || fail '--product must be WiiCompiled or RetroRewind'
[[ "$architecture" == arm64 || "$architecture" == x86_64 ]] || fail '--architecture must be arm64 or x86_64' for tool in codesign ditto install_name_tool otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
[[ "$minimum_system_version" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]] || fail '--minimum-system-version must contain two or three period-separated integers'
for tool in codesign ditto install_name_tool lipo otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
[[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product" [[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product"
lipo "$build_dir/$product" -verify_arch "$architecture" || fail "compiled product is not $architecture: $build_dir/$product"
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done
app="$output_dir/$product.app" app="$output_dir/$product.app"
@@ -55,7 +47,7 @@ cat > "$app/Contents/Info.plist" <<EOF
<key>CFBundlePackageType</key><string>APPL</string> <key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>0.1.0</string> <key>CFBundleShortVersionString</key><string>0.1.0</string>
<key>CFBundleVersion</key><string>1</string> <key>CFBundleVersion</key><string>1</string>
<key>LSMinimumSystemVersion</key><string>$minimum_system_version</string> <key>LSMinimumSystemVersion</key><string>14.0</string>
<key>NSHighResolutionCapable</key><true/> <key>NSHighResolutionCapable</key><true/>
</dict></plist> </dict></plist>
EOF EOF
@@ -65,62 +57,25 @@ for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do
ln -s "../Resources/$asset" "$macos/$asset" ln -s "../Resources/$asset" "$macos/$asset"
done done
# Resolve a non-system dependency from the build product's rpaths. This covers # Build a closure of Homebrew dylibs. System libraries remain system references.
# both traditional Homebrew dylibs and the vendored dylibs CMake emits under
# the local build directory for a cross-architecture build.
dependency_path() {
local current=$1 dependency=$2 name rpath candidate
case "$dependency" in
/opt/homebrew/*|/usr/local/*)
[[ -f "$dependency" ]] && { printf '%s\n' "$dependency"; return 0; }
;;
@rpath/*)
name=${dependency##*/}
while IFS= read -r rpath; do
case "$rpath" in
@loader_path/*) rpath="$(dirname "$current")/${rpath#@loader_path/}" ;;
@executable_path/*) rpath="$macos/${rpath#@executable_path/}" ;;
esac
candidate="$rpath/$name"
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
done < <(otool -l "$current" | awk '
/LC_RPATH/ { rpath = 1; next }
rpath && /^[[:space:]]*path / {
sub(/^[[:space:]]*path /, "")
sub(/ \(offset [0-9]+\)$/, "")
print
rpath = 0
}
')
;;
@loader_path/*)
candidate="$(dirname "$current")/${dependency#@loader_path/}"
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
;;
esac
return 1
}
# Build a closure of non-system dylibs. System libraries remain system
# references, while every resolved dependency is copied beside the executable.
queue=("$macos/$product") queue=("$macos/$product")
while ((${#queue[@]})); do while ((${#queue[@]})); do
current=${queue[0]} current=${queue[0]}
queue=("${queue[@]:1}") queue=("${queue[@]:1}")
while IFS= read -r dependency; do while IFS= read -r dependency; do
dependency_path=$(dependency_path "$current" "$dependency") || continue [[ "$dependency" == /opt/homebrew/* || "$dependency" == /usr/local/* ]] || continue
[[ -f "$dependency" ]] || continue
name=$(basename "$dependency") name=$(basename "$dependency")
if [[ ! -f "$frameworks/$name" ]]; then if [[ ! -f "$frameworks/$name" ]]; then
ditto "$dependency_path" "$frameworks/$name" ditto "$dependency" "$frameworks/$name"
install_name_tool -id "@rpath/$name" "$frameworks/$name" install_name_tool -id "@rpath/$name" "$frameworks/$name"
# Resolve transitive @loader_path dependencies relative to the queue+=("$frameworks/$name")
# original dylib, not its copied Frameworks location.
queue+=("$dependency_path")
fi fi
done < <(otool -L "$current" | tail -n +2 | awk '{print $1}') done < <(otool -L "$current" | tail -n +2 | awk '{print $1}')
done done
while IFS= read -r binary; do while IFS= read -r binary; do
while IFS= read -r old; do while IFS= read -r old; do
[[ "$old" == /opt/homebrew/* || "$old" == /usr/local/* ]] || continue
name=$(basename "$old") name=$(basename "$old")
[[ -f "$frameworks/$name" ]] || continue [[ -f "$frameworks/$name" ]] || continue
if [[ "$binary" == "$macos/$product" ]]; then if [[ "$binary" == "$macos/$product" ]]; then
+6 -34
View File
@@ -5,7 +5,10 @@ set -euo pipefail
resources=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) resources=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
workspace_source="$resources/workspace" workspace_source="$resources/workspace"
nodtool="$resources/tools/nodtool"
translator="$resources/tools/Translator.Cli"
cmake_bin="$resources/tools/cmake/bin/cmake" cmake_bin="$resources/tools/cmake/bin/cmake"
ninja_bin="$resources/tools/ninja"
support_root="$HOME/Library/Application Support/WiiCompiled" support_root="$HOME/Library/Application Support/WiiCompiled"
workspace="$support_root/BuildWorkspace" workspace="$support_root/BuildWorkspace"
products="$support_root/Products" products="$support_root/Products"
@@ -33,13 +36,6 @@ while (($#)); do
done done
[[ "$install_location" == user || "$install_location" == applications ]] || fail '--install-location must be user or applications' [[ "$install_location" == user || "$install_location" == applications ]] || fail '--install-location must be user or applications'
host_arch=$(uname -m)
case "$host_arch" in arm64|x86_64) ;; *) fail "unsupported macOS architecture: $host_arch" ;; esac
host_tools="$resources/tools/$host_arch"
nodtool="$host_tools/nodtool"
translator="$host_tools/Translator.Cli"
ninja_bin="$host_tools/ninja"
if [[ -z "$game" ]]; then if [[ -z "$game" ]]; then
game=$(/usr/bin/osascript <<'APPLESCRIPT' game=$(/usr/bin/osascript <<'APPLESCRIPT'
set selectedFile to choose file with prompt "Choose your clean Mario Kart Wii PAL (RMCP01) disc image" set selectedFile to choose file with prompt "Choose your clean Mario Kart Wii PAL (RMCP01) disc image"
@@ -64,36 +60,12 @@ if ! /usr/bin/xcode-select -p >/dev/null 2>&1; then
/usr/bin/xcode-select --install || true /usr/bin/xcode-select --install || true
exit 1 exit 1
fi fi
for tool in "$nodtool" "$translator" "$cmake_bin" "$ninja_bin"; do
/usr/bin/lipo "$tool" -verify_arch "$host_arch" >/dev/null 2>&1 || \
fail "the packaged $(basename "$tool") does not support $host_arch"
done
mkdir -p "$support_root" "$products" mkdir -p "$support_root" "$products"
source_bundle_version="$workspace_source/.bundle-version" if [[ ! -d "$workspace/.git" && ! -f "$workspace/projects/mkwii/recomp.yml" ]]; then
workspace_bundle_version="$workspace/.bundle-version"
needs_workspace_refresh=0
if [[ ! -f "$workspace/projects/mkwii/recomp.yml" ]]; then
needs_workspace_refresh=1
elif [[ -f "$source_bundle_version" ]] && [[ ! -f "$workspace_bundle_version" || "$(<"$source_bundle_version")" != "$(<"$workspace_bundle_version")" ]]; then
needs_workspace_refresh=1
fi
if (( needs_workspace_refresh )); then
printf 'Preparing the local build workspace...\n' printf 'Preparing the local build workspace...\n'
if [[ ! -d "$workspace" ]]; then rm -rf "$workspace"
/usr/bin/ditto "$workspace_source" "$workspace" /usr/bin/ditto "$workspace_source" "$workspace"
else
# Refresh only packaged source inputs. Assets and the staged Retro
# Rewind package belong to the user and stay in place.
for source in aurora-main projects runtime translator Launcher; do
/usr/bin/ditto "$workspace_source/$source" "$workspace/$source"
done
/usr/bin/ditto "$source_bundle_version" "$workspace_bundle_version"
# A dependency provider can be cached in this directory, so make the
# refreshed sources configure from a clean native build tree.
rm -rf "$workspace/native-build-macos-arm64" "$workspace/native-build-macos-x86_64"
fi
fi fi
profile=base profile=base
+2 -17
View File
@@ -89,8 +89,8 @@ Known limitations of the Wii Remote path:
- GPU: GTX 1650 / RX 6400 / Arc A310 or higher - GPU: GTX 1650 / RX 6400 / Arc A310 or higher
- CPU: Intel Core i5-8400 / AMD Ryzen 5 2600 (4c/6c, ~3.5GHz+) or higher - CPU: Intel Core i5-8400 / AMD Ryzen 5 2600 (4c/6c, ~3.5GHz+) or higher
- About 20 GB of free disk space during installation (Final game size ~5 GB) - About 20 GB of free disk space during installation (Final game size ~5 GB)
- macOS 12 (Monterey) or later on Apple Silicon (`arm64`) or Intel (`x86_64-v3`); pre-Haswell Intel CPUs are unsupported - macOS 14 (Sonoma) or later on Apple Silicon
- On macOS, a Metal-capable GPU and Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing) - On macOS, Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing)
- A clean, unmodified **PAL `RMCP01`** disc image of Mario Kart Wii, dumped by you. ISO, GCM, - A clean, unmodified **PAL `RMCP01`** disc image of Mario Kart Wii, dumped by you. ISO, GCM,
GCZ, CISO, WBFS, WIA and RVZ are accepted. GCZ, CISO, WBFS, WIA and RVZ are accepted.
@@ -111,21 +111,6 @@ image under Settings, turn on **WiiCompiled (beta)**, and hit install from the H
Wheel Wizard downloads the setup tool from this repo and walks you through install, updates and Wheel Wizard downloads the setup tool from this repo and walks you through install, updates and
launching. The backend itself is deliberately command-line only, Wheel Wizard is a wrapper around it. launching. The backend itself is deliberately command-line only, Wheel Wizard is a wrapper around it.
### macOS
Download `WiiCompiled-Setup.pkg` from this repository's Releases page and open it. It requires an
Apple Silicon Mac because its bundled nodtool and Translator.Cli executables are arm64. It installs
**WiiCompiled Setup** in Applications; open that app, choose your clean PAL `RMCP01` disc image,
and select either the base game or Retro Rewind. For Retro Rewind, choose the `RetroRewind6` folder
or its parent folder.
Setup verifies and extracts the image locally, then translates and compiles the native app on your
Mac. On a first run it may ask macOS to install Xcode Command Line Tools; complete Apple's installer,
then open Setup again. When the build completes, Setup asks for administrator approval once to install
`WiiCompiled.app` (and, if selected, `RetroRewind.app`) in `/Applications`.
Setup opens Terminal while it works, so the extraction and build progress—and any error that needs
reporting—remain visible.
> [!CAUTION] > [!CAUTION]
> Only take builds from this repository's > Only take builds from this repository's
+2 -5
View File
@@ -41,10 +41,7 @@ if (_aurora_sdl3_provider STREQUAL "auto")
set(_aurora_sdl3_provider "package") set(_aurora_sdl3_provider "package")
else () else ()
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
# Aurora uses APIs from the SDL version pinned by AURORA_SDL3_VERSION. find_package(SDL3 QUIET)
# Do not silently select an older system package and fail later while
# compiling its headers.
find_package(SDL3 ${AURORA_SDL3_VERSION} QUIET)
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
if (SDL3_FOUND) if (SDL3_FOUND)
set(_aurora_sdl3_provider "system") set(_aurora_sdl3_provider "system")
@@ -61,7 +58,7 @@ if (_aurora_sdl3_provider STREQUAL "system")
message(STATUS "aurora: Using system SDL3 (provider=system)") message(STATUS "aurora: Using system SDL3 (provider=system)")
if (NOT SDL3_FOUND) if (NOT SDL3_FOUND)
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
find_package(SDL3 ${AURORA_SDL3_VERSION} REQUIRED) find_package(SDL3 REQUIRED)
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
endif () endif ()
_aurora_sdl3_select_target() _aurora_sdl3_select_target()
+1 -3
View File
@@ -41,9 +41,7 @@ if (AURORA_ENABLE_GX)
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_METAL) target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_METAL)
target_sources(aurora_core PRIVATE lib/dawn/MetalBinding.mm) target_sources(aurora_core PRIVATE lib/dawn/MetalBinding.mm)
set_source_files_properties(lib/dawn/MetalBinding.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(lib/dawn/MetalBinding.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_options(aurora_core PUBLIC target_link_options(aurora_core PUBLIC "LINKER:-weak_framework,Metal")
"LINKER:-weak_framework,Metal"
"LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor")
endif () endif ()
if (DAWN_ENABLE_D3D11) if (DAWN_ENABLE_D3D11)
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_D3D11) target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_D3D11)
-50
View File
@@ -16,60 +16,10 @@ function(aurora_find_package_global)
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ${_PREV_FIND_PACKAGE_TARGETS_GLOBAL}) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ${_PREV_FIND_PACKAGE_TARGETS_GLOBAL})
endfunction() endfunction()
# A macOS build for another architecture must not discover Homebrew packages
# built for its physical host. CMAKE_CROSSCOMPILING is not sufficient here:
# Apple Clang can target another architecture through CMAKE_OSX_ARCHITECTURES
# without CMake considering the configure a cross-build.
set(_AURORA_EXCLUDE_HOST_HOMEBREW FALSE)
if (APPLE AND CMAKE_OSX_ARCHITECTURES)
list(LENGTH CMAKE_OSX_ARCHITECTURES _AURORA_OSX_ARCH_COUNT)
if (_AURORA_OSX_ARCH_COUNT EQUAL 1)
list(GET CMAKE_OSX_ARCHITECTURES 0 _AURORA_TARGET_ARCH)
string(TOLOWER "${_AURORA_TARGET_ARCH}" _AURORA_TARGET_ARCH)
# hw.optional.arm64 identifies Apple Silicon even when CMake itself runs
# through Rosetta, where CMAKE_HOST_SYSTEM_PROCESSOR reports x86_64.
execute_process(
COMMAND /usr/sbin/sysctl -n hw.optional.arm64
RESULT_VARIABLE _AURORA_ARM64_PROBE_RESULT
OUTPUT_VARIABLE _AURORA_ARM64_PROBE
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if (_AURORA_ARM64_PROBE_RESULT EQUAL 0 AND _AURORA_ARM64_PROBE STREQUAL "1")
set(_AURORA_HOST_ARCH arm64)
else ()
execute_process(
COMMAND /usr/bin/uname -m
OUTPUT_VARIABLE _AURORA_HOST_ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(TOLOWER "${_AURORA_HOST_ARCH}" _AURORA_HOST_ARCH)
endif ()
if (_AURORA_TARGET_ARCH STREQUAL "x86_64" AND _AURORA_HOST_ARCH MATCHES "^(arm64|aarch64)$")
list(APPEND CMAKE_IGNORE_PREFIX_PATH "/opt/homebrew")
set(_AURORA_EXCLUDE_HOST_HOMEBREW TRUE)
elseif (_AURORA_TARGET_ARCH MATCHES "^(arm64|aarch64)$" AND _AURORA_HOST_ARCH MATCHES "^(x86_64|amd64)$")
list(APPEND CMAKE_IGNORE_PREFIX_PATH "/usr/local")
set(_AURORA_EXCLUDE_HOST_HOMEBREW TRUE)
endif ()
endif ()
endif ()
if (_AURORA_EXCLUDE_HOST_HOMEBREW)
list(REMOVE_DUPLICATES CMAKE_IGNORE_PREFIX_PATH)
message(STATUS "aurora: cross-architecture macOS build; ignoring host Homebrew prefixes")
endif ()
if (AURORA_ENABLE_GX) if (AURORA_ENABLE_GX)
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraDawnProvider.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraDawnProvider.cmake)
endif () endif ()
# SDL's pkg-config probe can bypass CMake's prefix exclusion. macOS does not
# need libusb for the supported SDL input paths, so keep that host-only library
# out of a cross-architecture configure.
if (_AURORA_EXCLUDE_HOST_HOMEBREW)
set(SDL_HIDAPI_LIBUSB OFF CACHE BOOL "" FORCE)
set(SDL_HIDAPI_LIBUSB_SHARED OFF CACHE BOOL "" FORCE)
endif ()
# Abseil is needed for core libraries. It normally comes via Dawn's vendor build. # Abseil is needed for core libraries. It normally comes via Dawn's vendor build.
# Otherwise prefer a system package and only fetch it as a last resort. # Otherwise prefer a system package and only fetch it as a last resort.
if (NOT TARGET absl::flat_hash_map OR NOT TARGET absl::btree) if (NOT TARGET absl::flat_hash_map OR NOT TARGET absl::btree)
+24 -30
View File
@@ -6,10 +6,10 @@
#include <SDL3/SDL_mouse.h> #include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_joystick.h> #include <SDL3/SDL_joystick.h>
#include <algorithm>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <sys/stat.h> #include <sys/stat.h>
#include <ranges>
namespace { namespace {
constexpr int32_t k_mappingsFileVersion = 3; constexpr int32_t k_mappingsFileVersion = 3;
@@ -342,7 +342,7 @@ BOOL PADInit() {
} }
g_initialized = true; g_initialized = true;
std::for_each(g_keyboardBindings.begin(), g_keyboardBindings.end(), [](auto& state) { std::ranges::for_each(g_keyboardBindings, [](auto& state) {
state.m_buttonMapping = g_defaultKeys; state.m_buttonMapping = g_defaultKeys;
state.m_axisMapping = g_defaultKeyAxis; state.m_axisMapping = g_defaultKeyAxis;
}); });
@@ -634,8 +634,8 @@ static void EnsureMappingLoaded(aurora::input::GameController* controller) {
static Sint16 _get_axis_value(const aurora::input::GameController* controller, // NOLINT(*-reserved-identifier) static Sint16 _get_axis_value(const aurora::input::GameController* controller, // NOLINT(*-reserved-identifier)
PADAxis axis) { PADAxis axis) {
const auto iter = std::find_if(controller->m_axisMapping.begin(), controller->m_axisMapping.end(), const auto iter =
[axis](const auto& pair) { return pair.padAxis == axis; }); std::ranges::find_if(controller->m_axisMapping, [axis](const auto& pair) { return pair.padAxis == axis; });
if (iter == controller->m_axisMapping.end()) { if (iter == controller->m_axisMapping.end()) {
return 0; return 0;
} }
@@ -725,8 +725,8 @@ u32 PADRead(PADStatus* status) {
status[i].err = PAD_ERR_NONE; status[i].err = PAD_ERR_NONE;
if (g_keyboardBindings[i].m_mappingsSet) { if (g_keyboardBindings[i].m_mappingsSet) {
std::for_each(g_keyboardBindings[i].m_buttonMapping.begin(), g_keyboardBindings[i].m_buttonMapping.end(), std::ranges::for_each(
[&kbState, &i, &status](const PADKeyButtonBinding& mapping) { g_keyboardBindings[i].m_buttonMapping, [&kbState, &i, &status](const PADKeyButtonBinding& mapping) {
if (mapping.scancode > PAD_KEY_INVALID && kbState[mapping.scancode]) { if (mapping.scancode > PAD_KEY_INVALID && kbState[mapping.scancode]) {
status[i].button |= mapping.padButton; status[i].button |= mapping.padButton;
} else if (is_mouse_scancode(mapping.scancode) && is_mouse_button_pressed(mapping.scancode)) { } else if (is_mouse_scancode(mapping.scancode) && is_mouse_button_pressed(mapping.scancode)) {
@@ -833,8 +833,8 @@ u32 PADRead(PADStatus* status) {
bool leftTriggerSet = false; bool leftTriggerSet = false;
bool rightTriggerSet = false; bool rightTriggerSet = false;
std::for_each(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(), std::ranges::for_each(controller->m_buttonMapping, [&controller, &i, &status, &leftTriggerSet,
[&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) { &rightTriggerSet](const auto& mapping) {
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(mapping.nativeButton))) { if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(mapping.nativeButton))) {
status[i].button |= mapping.padButton; status[i].button |= mapping.padButton;
} }
@@ -847,8 +847,8 @@ u32 PADRead(PADStatus* status) {
} }
}); });
std::for_each(controller->m_altButtonMapping.begin(), controller->m_altButtonMapping.end(), std::ranges::for_each(controller->m_altButtonMapping, [&controller, &i, &status, &leftTriggerSet,
[&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) { &rightTriggerSet](const auto& mapping) {
if (mapping.nativeButton == PAD_NATIVE_BUTTON_INVALID) { if (mapping.nativeButton == PAD_NATIVE_BUTTON_INVALID) {
return; return;
} }
@@ -1165,8 +1165,8 @@ void PADSetButtonMapping(const u32 port, const PADButtonMapping mapping) {
return; return;
} }
const auto iter = std::find_if(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(), const auto iter = std::ranges::find_if(controller->m_buttonMapping,
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; }); [mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
if (iter == controller->m_buttonMapping.end()) { if (iter == controller->m_buttonMapping.end()) {
return; return;
} }
@@ -1199,8 +1199,8 @@ void PADSetAltButtonMapping(const u32 port, const PADButtonMapping mapping) {
return; return;
} }
const auto iter = std::find_if(controller->m_altButtonMapping.begin(), controller->m_altButtonMapping.end(), const auto iter = std::ranges::find_if(controller->m_altButtonMapping,
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; }); [mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
if (iter == controller->m_altButtonMapping.end()) { if (iter == controller->m_altButtonMapping.end()) {
return; return;
} }
@@ -1226,8 +1226,8 @@ void PADSetAxisMapping(const u32 port, const PADAxisMapping mapping) {
return; return;
} }
const auto iter = std::find_if(controller->m_axisMapping.begin(), controller->m_axisMapping.end(), const auto iter = std::ranges::find_if(controller->m_axisMapping,
[mapping](const auto& pair) { return mapping.padAxis == pair.padAxis; }); [mapping](const auto& pair) { return mapping.padAxis == pair.padAxis; });
if (iter == controller->m_axisMapping.end()) { if (iter == controller->m_axisMapping.end()) {
return; return;
} }
@@ -1396,10 +1396,9 @@ static void load_keyboard_bindings() {
if (mappingsSet) { if (mappingsSet) {
const bool anyBound = const bool anyBound =
std::any_of(buttonMapping.begin(), buttonMapping.end(), std::ranges::any_of(buttonMapping,
[](const PADKeyButtonBinding& b) { return b.scancode != PAD_KEY_INVALID; }) || [](const PADKeyButtonBinding& b) { return b.scancode != PAD_KEY_INVALID; }) ||
std::any_of(axisMapping.begin(), axisMapping.end(), std::ranges::any_of(axisMapping, [](const PADKeyAxisBinding& b) { return b.scancode != PAD_KEY_INVALID; });
[](const PADKeyAxisBinding& b) { return b.scancode != PAD_KEY_INVALID; });
if (!anyBound) { if (!anyBound) {
mappingsSet = false; mappingsSet = false;
} }
@@ -1439,10 +1438,7 @@ void __PADWriteDeadZones(SDL_IOStream* file, // NOLINT(*-reserved-identifier)
void PADSerializeMappings() { void PADSerializeMappings() {
const std::filesystem::path basePath = fs_path_from_string(aurora::g_config.userPath); const std::filesystem::path basePath = fs_path_from_string(aurora::g_config.userPath);
// Avoid std::views::values here: older Apple libc++ releases implement the for (auto& controller : aurora::input::g_GameControllers | std::views::values) {
// C++20 ranges algorithms we use but not this adaptor.
for (auto& entry : aurora::input::g_GameControllers) {
auto& controller = entry.second;
EnsureMappingLoaded(&controller); EnsureMappingLoaded(&controller);
const auto filePath = const auto filePath =
basePath / fmt::format("{}_{:04X}_{:04X}.controller", aurora::input::controller_name(controller.m_index), basePath / fmt::format("{}_{:04X}_{:04X}.controller", aurora::input::controller_name(controller.m_index),
@@ -1544,8 +1540,8 @@ static constexpr std::array<std::pair<PADButton, std::string_view>, PAD_AXIS_COU
const char* PADGetButtonName(const PADButton button) { const char* PADGetButtonName(const PADButton button) {
if (const auto iter = std::find_if(skButtonNames.begin(), skButtonNames.end(), if (const auto iter =
[&button](const auto& pair) { return button == pair.first; }); std::ranges::find_if(skButtonNames, [&button](const auto& pair) { return button == pair.first; });
iter != skButtonNames.end()) { iter != skButtonNames.end()) {
return iter->second.data(); return iter->second.data();
} }
@@ -1558,8 +1554,7 @@ const char* PADGetNativeButtonName(u32 button) {
} }
const char* PADGetAxisName(const PADAxis axis) { const char* PADGetAxisName(const PADAxis axis) {
if (const auto it = std::find_if(skAxisNames.begin(), skAxisNames.end(), if (const auto it = std::ranges::find_if(skAxisNames, [&axis](const auto& pair) { return axis == pair.first; });
[&axis](const auto& pair) { return axis == pair.first; });
it != skAxisNames.end()) { it != skAxisNames.end()) {
return it->second.data(); return it->second.data();
} }
@@ -1568,8 +1563,7 @@ const char* PADGetAxisName(const PADAxis axis) {
} }
const char* PADGetAxisDirectionLabel(const PADAxis axis) { const char* PADGetAxisDirectionLabel(const PADAxis axis) {
if (const auto it = std::find_if(skAxisDirLabels.begin(), skAxisDirLabels.end(), if (const auto it = std::ranges::find_if(skAxisDirLabels, [&axis](const auto& pair) { return axis == pair.first; });
[&axis](const auto& pair) { return axis == pair.first; });
it != skAxisDirLabels.end()) { it != skAxisDirLabels.end()) {
return it->second.data(); return it->second.data();
} }
+3 -2
View File
@@ -22,6 +22,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <ranges>
#include <absl/container/flat_hash_map.h> #include <absl/container/flat_hash_map.h>
#include <magic_enum.hpp> #include <magic_enum.hpp>
@@ -1327,8 +1328,8 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
#if defined(AURORA_GFX_DEBUG_GROUPS) #if defined(AURORA_GFX_DEBUG_GROUPS)
if (finalize && !g_debugGroupStack.empty()) { if (finalize && !g_debugGroupStack.empty()) {
for (auto it = g_debugGroupStack.rbegin(); it != g_debugGroupStack.rend(); ++it) { for (auto& it : std::ranges::reverse_view(g_debugGroupStack)) {
Log.warn("Debug group was not popped at end of frame: {}", *it); Log.warn("Debug group was not popped at end of frame: {}", it);
} }
g_debugGroupStack.clear(); g_debugGroupStack.clear();
} }
+1 -1
View File
@@ -553,7 +553,7 @@ void build_index() noexcept {
continue; continue;
} }
s_replacementIndex.try_emplace(*parsed, ReplacementIndexEntry{path}); s_replacementIndex.try_emplace(*parsed, path);
} }
Log.info("Indexed {} texture replacements", s_replacementIndex.size()); Log.info("Indexed {} texture replacements", s_replacementIndex.size());
+1 -1
View File
@@ -235,7 +235,7 @@ std::string GetOSVersion() {
constexpr auto name = "iOS"; constexpr auto name = "iOS";
#elif TARGET_OS_TV #elif TARGET_OS_TV
constexpr auto name = "tvOS"; constexpr auto name = "tvOS";
#else #elif
constexpr auto name = Unknown; constexpr auto name = Unknown;
#endif #endif
+31 -79
View File
@@ -1,44 +1,22 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
# Dawn's pinned macOS artifacts target 12.0. Set the same floor before project()
# initializes the Apple toolchain so direct developer CMake invocations cannot
# accidentally inherit the running SDK's deployment version. This cache entry
# is harmless on non-Apple platforms and remains overridable by a caller.
if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING
"Minimum macOS version supported by WiiCompiled" FORCE)
endif()
project(mkw_recompiled) project(mkw_recompiled)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8) if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "WiiCompiled requires a 64-bit Clang toolchain") message(FATAL_ERROR "WiiCompiled requires a 64-bit Clang toolchain")
endif() endif()
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
list(LENGTH CMAKE_OSX_ARCHITECTURES MKW_OSX_ARCHITECTURE_COUNT)
if(MKW_OSX_ARCHITECTURE_COUNT GREATER 1)
message(FATAL_ERROR
"WiiCompiled supports one macOS architecture per build directory; "
"configure separate arm64 and x86_64 build directories")
endif()
endif()
if(WIN32 AND MINGW AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$") if(WIN32 AND MINGW AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
set(MKW_PLATFORM_WINDOWS TRUE) set(MKW_PLATFORM_WINDOWS TRUE)
elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|arm64|ARM64)$") elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|ARM64)$")
# The first native macOS target is Apple Silicon. Intel and universal
# binaries remain future compatibility work; do not silently claim them.
set(MKW_PLATFORM_MACOS TRUE) set(MKW_PLATFORM_MACOS TRUE)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
set(MKW_PLATFORM_MACOS_X86_64 TRUE)
else()
set(MKW_PLATFORM_MACOS_ARM64 TRUE)
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|aarch64|arm64|ARM64)$") elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|aarch64|arm64|ARM64)$")
set(MKW_PLATFORM_LINUX TRUE) set(MKW_PLATFORM_LINUX TRUE)
else() else()
message(FATAL_ERROR message(FATAL_ERROR
"WiiCompiled supports 64-bit LLVM-MinGW Clang on Windows, native Linux x86_64/aarch64, or Apple Clang on macOS x86_64/arm64") "WiiCompiled supports 64-bit LLVM-MinGW Clang on Windows, native Linux x86_64/aarch64, or Apple Clang on macOS arm64")
endif() endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Release") if(NOT CMAKE_BUILD_TYPE STREQUAL "Release")
message(FATAL_ERROR "WiiCompiled only supports Release builds") message(FATAL_ERROR "WiiCompiled only supports Release builds")
endif() endif()
@@ -81,15 +59,14 @@ target_include_directories(mkw_pugixml PUBLIC third_party/pugixml)
target_compile_features(mkw_pugixml PUBLIC cxx_std_17) target_compile_features(mkw_pugixml PUBLIC cxx_std_17)
set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF) set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF)
# POSIX x86-64 guest-fiber scheduling (runtime/src/host_context.cpp) needs a symmetric # Linux guest-fiber scheduling (runtime/src/host_context.cpp) needs a symmetric
# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers # stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers
# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike # directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike
# asymmetric resume/yield coroutine libraries, which would need every call site restructured). # asymmetric resume/yield coroutine libraries, which would need every call site restructured).
# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is # Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is
# separately BSD-style licensed, see third_party/libco/LICENSE). Windows keeps native Fibers; # separately BSD-style licensed, see third_party/libco/LICENSE). Windows keeps native Fibers
# Apple Silicon uses the project's x18-safe AArch64 assembly backend, while Intel macOS uses # and macOS uses the project's x18-safe AArch64 assembly backend, so this target is Linux-only.
# libco's existing System V AMD64 backend. if(MKW_PLATFORM_LINUX)
if(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64)
add_library(mkw_libco STATIC third_party/libco/libco.c) add_library(mkw_libco STATIC third_party/libco/libco.c)
add_library(mkw::libco ALIAS mkw_libco) add_library(mkw::libco ALIAS mkw_libco)
target_include_directories(mkw_libco PUBLIC third_party/libco) target_include_directories(mkw_libco PUBLIC third_party/libco)
@@ -266,14 +243,12 @@ endif()
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp") file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
if(MKW_PLATFORM_MACOS) if(MKW_PLATFORM_MACOS)
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory.cpp") list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory.cpp")
if(MKW_PLATFORM_MACOS_ARM64) # HostContext's Apple Silicon backend is implemented in a small assembly
# HostContext's Apple Silicon backend is implemented in a small assembly # companion. It must be part of the product runtime as well as the
# companion. It must be part of the product runtime as well as the # standalone context test; otherwise the final executable is missing
# standalone context test; otherwise the final executable is missing # mkw_co_init/mkw_co_switch at link time.
# mkw_co_init/mkw_co_switch at link time. enable_language(ASM)
enable_language(ASM) list(APPEND SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
list(APPEND SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
endif()
else() else()
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory_macos.cpp") list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory_macos.cpp")
endif() endif()
@@ -302,11 +277,6 @@ target_link_libraries(mkw_platform_paths_tests PRIVATE mkw_platform)
target_compile_features(mkw_platform_paths_tests PRIVATE cxx_std_17) target_compile_features(mkw_platform_paths_tests PRIVATE cxx_std_17)
add_test(NAME mkw_platform_paths_tests COMMAND mkw_platform_paths_tests) add_test(NAME mkw_platform_paths_tests COMMAND mkw_platform_paths_tests)
add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp")
target_include_directories(mkw_nand_save_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17)
add_test(NAME mkw_nand_save_tests COMMAND mkw_nand_save_tests)
add_executable(mkw_nand_settings_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_settings_tests.cpp") add_executable(mkw_nand_settings_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_settings_tests.cpp")
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
target_link_libraries(mkw_nand_settings_tests PRIVATE Threads::Threads) target_link_libraries(mkw_nand_settings_tests PRIVATE Threads::Threads)
@@ -314,10 +284,10 @@ target_include_directories(mkw_nand_settings_tests PRIVATE "${CMAKE_CURRENT_LIST
target_compile_features(mkw_nand_settings_tests PRIVATE cxx_std_17) target_compile_features(mkw_nand_settings_tests PRIVATE cxx_std_17)
add_test(NAME mkw_nand_settings_tests COMMAND mkw_nand_settings_tests) add_test(NAME mkw_nand_settings_tests COMMAND mkw_nand_settings_tests)
add_executable(mkw_sc_serial_tests "${CMAKE_CURRENT_LIST_DIR}/tests/sc_serial_tests.cpp") add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp")
target_include_directories(mkw_sc_serial_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") target_include_directories(mkw_nand_save_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
target_compile_features(mkw_sc_serial_tests PRIVATE cxx_std_17) target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17)
add_test(NAME mkw_sc_serial_tests COMMAND mkw_sc_serial_tests) add_test(NAME mkw_nand_save_tests COMMAND mkw_nand_save_tests)
# The input expression engine is self-contained, so it can be exercised without # The input expression engine is self-contained, so it can be exercised without
# linking the runtime or SDL. # linking the runtime or SDL.
@@ -344,31 +314,20 @@ if(MKW_PLATFORM_LINUX)
endif() endif()
if(MKW_PLATFORM_MACOS) if(MKW_PLATFORM_MACOS)
# Exercise the public host-memory contracts separately from translated products. # Exercise the Apple Silicon context ABI and the public host-memory
if(MKW_PLATFORM_MACOS_ARM64) # contracts separately from translated products.
# Apple Silicon's context ABI is implemented by the local assembly backend. enable_language(ASM)
enable_language(ASM) add_executable(mkw_macos_context_abi_tests
add_executable(mkw_macos_context_abi_tests "${CMAKE_CURRENT_LIST_DIR}/tests/macos_context_abi_tests.cpp"
"${CMAKE_CURRENT_LIST_DIR}/tests/macos_context_abi_tests.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
"${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S") target_compile_features(mkw_macos_context_abi_tests PRIVATE cxx_std_17)
target_compile_features(mkw_macos_context_abi_tests PRIVATE cxx_std_17) add_test(NAME mkw_macos_context_abi_tests COMMAND mkw_macos_context_abi_tests)
add_test(NAME mkw_macos_context_abi_tests COMMAND mkw_macos_context_abi_tests)
add_executable(mkw_macos_host_context_tests add_executable(mkw_macos_host_context_tests
"${CMAKE_CURRENT_LIST_DIR}/tests/host_context_tests.cpp" "${CMAKE_CURRENT_LIST_DIR}/tests/host_context_tests.cpp"
"${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp"
"${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S") "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
target_include_directories(mkw_macos_host_context_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") target_include_directories(mkw_macos_host_context_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
else()
# Intel macOS follows the same System V AMD64 libco path as Linux.
add_executable(mkw_macos_host_context_tests
"${CMAKE_CURRENT_LIST_DIR}/tests/host_context_tests.cpp"
"${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp")
target_include_directories(mkw_macos_host_context_tests PRIVATE
"${CMAKE_CURRENT_LIST_DIR}/include"
"${CMAKE_CURRENT_LIST_DIR}/third_party/libco")
target_link_libraries(mkw_macos_host_context_tests PRIVATE mkw::libco)
endif()
target_compile_features(mkw_macos_host_context_tests PRIVATE cxx_std_17) target_compile_features(mkw_macos_host_context_tests PRIVATE cxx_std_17)
add_test(NAME mkw_macos_host_context_tests COMMAND mkw_macos_host_context_tests) add_test(NAME mkw_macos_host_context_tests COMMAND mkw_macos_host_context_tests)
@@ -427,13 +386,6 @@ else()
target_link_libraries(mkw_macos_native_compile PRIVATE target_link_libraries(mkw_macos_native_compile PRIVATE
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx
mkw::pugixml mkw::toml11 mkw::cryptopp) mkw::pugixml mkw::toml11 mkw::cryptopp)
if(MKW_PLATFORM_MACOS_X86_64)
target_link_libraries(mkw_macos_native_compile PRIVATE mkw::libco)
# Keep this compile-only audit on the same Haswell-era x86-64-v3
# baseline as the translated product. PPC paired FMA helpers use
# FMA intrinsics and intentionally cannot compile for plain x86-64.
target_compile_options(mkw_macos_native_compile PRIVATE -march=x86-64-v3)
endif()
set_target_properties(mkw_macos_native_compile PROPERTIES UNITY_BUILD OFF) set_target_properties(mkw_macos_native_compile PROPERTIES UNITY_BUILD OFF)
endif() endif()
add_custom_target(mkw_platform_paths_check DEPENDS mkw_platform) add_custom_target(mkw_platform_paths_check DEPENDS mkw_platform)
+4 -6
View File
@@ -1,4 +1,4 @@
# Public WiiCompiled product graph. # Public WiiCompiled product graph.
# #
# The translator owns the translated build graph. Mario Kart's profile-neutral # The translator owns the translated build graph. Mario Kart's profile-neutral
# functions are compiled once into mkw_base_shared; only callers whose direct # functions are compiled once into mkw_base_shared; only callers whose direct
@@ -84,9 +84,9 @@ target_link_libraries(mkw_runtime_common PRIVATE
target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp) target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp)
if(MKW_PLATFORM_WINDOWS) if(MKW_PLATFORM_WINDOWS)
target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp) target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp)
elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64) elseif(MKW_PLATFORM_LINUX)
# ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS # ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS
# media monitoring). Empty on platforms where dl* is already in libc/libSystem. # media monitoring). Empty string on glibc >= 2.34 where dl* is in libc.
target_link_libraries(mkw_runtime_common PRIVATE mkw::libco ${CMAKE_DL_LIBS}) target_link_libraries(mkw_runtime_common PRIVATE mkw::libco ${CMAKE_DL_LIBS})
endif() endif()
if(MKW_CPPWINRT_INCLUDE_DIR) if(MKW_CPPWINRT_INCLUDE_DIR)
@@ -206,8 +206,6 @@ function(mkw_configure_product target)
if(MKW_PLATFORM_MACOS) if(MKW_PLATFORM_MACOS)
target_link_libraries(${target} PRIVATE target_link_libraries(${target} PRIVATE
"${MKW_IOKIT_FRAMEWORK}" "${MKW_COREFOUNDATION_FRAMEWORK}") "${MKW_IOKIT_FRAMEWORK}" "${MKW_COREFOUNDATION_FRAMEWORK}")
target_link_options(${target} PRIVATE
"LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor")
endif() endif()
if(EXISTS "${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake") if(EXISTS "${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake")
include("${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake") include("${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake")
@@ -228,7 +226,7 @@ function(mkw_configure_product target)
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp) dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp)
set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE) set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64) elseif(MKW_PLATFORM_LINUX)
# mkw_runtime_common is an OBJECT library: WiiCompiled/RetroRewind only pull in its .o # mkw_runtime_common is an OBJECT library: WiiCompiled/RetroRewind only pull in its .o
# files via $<TARGET_OBJECTS:>, which does not propagate mkw_runtime_common's own # files via $<TARGET_OBJECTS:>, which does not propagate mkw_runtime_common's own
# target_link_libraries (object libraries don't carry usage requirements to a consumer # target_link_libraries (object libraries don't carry usage requirements to a consumer
+5 -5
View File
@@ -3,11 +3,11 @@
#include <cstddef> #include <cstddef>
// HostContext is the deliberately small boundary between the guest scheduler // HostContext is the deliberately small boundary between the guest scheduler
// and the host's cooperative-context facility. Windows uses native Fibers; // and the host's cooperative-context facility. Windows uses native Fibers and
// Linux and Intel macOS use libco's System V x86-64 backend. macOS AArch64 uses // Linux uses libco; macOS AArch64 uses the local assembly backend because it
// the local assembly backend because it must preserve Darwin's platform-reserved // must preserve Darwin's platform-reserved x18 register, which libco's AArch64
// x18 register, which libco's AArch64 backend does not save. Its handles are // backend does not save. Its handles are only valid on the thread that
// only valid on the thread that initialized the scheduler. // initialized the scheduler.
namespace HostContext { namespace HostContext {
using Handle = void*; using Handle = void*;
+5 -26
View File
@@ -129,20 +129,6 @@ inline std::optional<std::array<uint8_t, 256>> EncodeNew(const std::string& seri
return bytes; // The unused tail stays raw zero, as in Dolphin. return bytes; // The unused tail stays raw zero, as in Dolphin.
} }
// Atomically claim our own scratch directory. A collision belongs to another
// launch (or a previous crashed launch); leave it untouched and try another name.
inline std::optional<std::filesystem::path> CreateScratchDirectory(
const std::filesystem::path& parent, const std::string& token, std::error_code& ec) {
for (unsigned attempt = 0; attempt < 128; ++attempt) {
const auto candidate = parent / (".setting-init-" + token + "-" + std::to_string(attempt));
ec.clear();
if (std::filesystem::create_directory(candidate, ec)) return candidate;
if (ec && ec != std::errc::file_exists) return std::nullopt;
}
ec = std::make_error_code(std::errc::file_exists);
return std::nullopt;
}
// Never replace an existing file, including an unreadable or damaged one. // Never replace an existing file, including an unreadable or damaged one.
// Publish a complete file atomically so simultaneous launches use one identity. // Publish a complete file atomically so simultaneous launches use one identity.
inline bool Ensure(const std::filesystem::path& root, std::string& error, inline bool Ensure(const std::filesystem::path& root, std::string& error,
@@ -175,20 +161,13 @@ inline bool Ensure(const std::filesystem::path& root, std::string& error,
return false; return false;
} }
static std::atomic<unsigned> sequence{0}; static std::atomic<unsigned> sequence{0};
#ifdef _WIN32 const auto scratch = path.parent_path() / (".setting-init-" + std::to_string(
const auto processId = GetCurrentProcessId(); std::chrono::steady_clock::now().time_since_epoch().count()) + "-" + std::to_string(sequence++));
#else if (!std::filesystem::create_directory(scratch, ec)) {
const auto processId = getpid();
#endif
const auto scratch = CreateScratchDirectory(path.parent_path(),
std::to_string(processId) + "-" + std::to_string(
std::chrono::steady_clock::now().time_since_epoch().count()) + "-" +
std::to_string(sequence++), ec);
if (!scratch) {
error = "Cannot create temporary NAND settings directory: " + ec.message(); error = "Cannot create temporary NAND settings directory: " + ec.message();
return false; return false;
} }
const auto temporary = *scratch / "setting.txt"; const auto temporary = scratch / "setting.txt";
bool written = false; bool written = false;
{ {
std::ofstream output(temporary, std::ios::binary); std::ofstream output(temporary, std::ios::binary);
@@ -205,7 +184,7 @@ inline bool Ensure(const std::filesystem::path& root, std::string& error,
#endif #endif
} }
std::filesystem::remove(temporary, ec); std::filesystem::remove(temporary, ec);
std::filesystem::remove(*scratch, ec); std::filesystem::remove(scratch, ec);
// A competing launcher may have published its settings first. Always read // A competing launcher may have published its settings first. Always read
// the winner from NAND rather than using our unpersisted candidate serial. // the winner from NAND rather than using our unpersisted candidate serial.
const auto persisted = Read(root); const auto persisted = Read(root);
-26
View File
@@ -1,26 +0,0 @@
#pragma once
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include <system_error>
namespace RuntimeScSerial {
// SCGetProductSN's output is a u32, not a character buffer. DWC loads
// that word and formats it with the product code to construct csnum.
template <typename RangeValidator, typename WordWriter>
uint32_t Write(std::string_view serial, uint32_t address,
RangeValidator&& contains, WordWriter&& write32) {
if (serial.empty() || serial.size() > 9 ||
serial.find_first_not_of("0123456789") != std::string_view::npos) return 0;
uint32_t number = 0;
const auto parsed = std::from_chars(serial.data(), serial.data() + serial.size(), number);
if (parsed.ec != std::errc{} || parsed.ptr != serial.data() + serial.size() ||
!address || !contains(address, sizeof(uint32_t))) return 0;
write32(address, number);
return 1;
}
} // namespace RuntimeScSerial
+6 -4
View File
@@ -1,7 +1,6 @@
#include "hle_stubs.h" #include "hle_stubs.h"
#include "console_identity.h" #include "console_identity.h"
#include "sc_serial_contract.h"
#include <cstdlib> #include <cstdlib>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@@ -98,9 +97,12 @@ PPC_NATIVE_OVERRIDE(801B2424, SCGetProductCode_HLE, uint32_t, (), ());
extern "C" uint32_t SCGetProductSN_HLE(uint32_t serialAddress) extern "C" uint32_t SCGetProductSN_HLE(uint32_t serialAddress)
{ {
const std::string& serial = RuntimeConsoleIdentity::Current().serial; const std::string& serial = RuntimeConsoleIdentity::Current().serial;
return RuntimeScSerial::Write(serial, serialAddress, if (!serialAddress || !Memory::Contains(serialAddress, serial.size() + 1)) {
[](uint32_t address, size_t size) { return Memory::Contains(address, size); }, return 0;
[](uint32_t address, uint32_t value) { Memory::Write32(address, value); }); }
std::memcpy(Memory::GetPointer(serialAddress, serial.size() + 1),
serial.c_str(), serial.size() + 1);
return 1;
} }
PPC_NATIVE_OVERRIDE(801B2460, SCGetProductSN_HLE, uint32_t, (uint32_t serialAddress), (serialAddress)); PPC_NATIVE_OVERRIDE(801B2460, SCGetProductSN_HLE, uint32_t, (uint32_t serialAddress), (serialAddress));
+2 -2
View File
@@ -14,7 +14,7 @@
extern "C" void mkw_co_switch(void** targetSp, void** sourceSp); extern "C" void mkw_co_switch(void** targetSp, void** sourceSp);
extern "C" void* mkw_co_init(void* stackTop, void (*entry)(void*), void* argument); extern "C" void* mkw_co_init(void* stackTop, void (*entry)(void*), void* argument);
#elif defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__)) #elif defined(__linux__)
#include <libco.h> #include <libco.h>
#include <cstdlib> #include <cstdlib>
@@ -158,7 +158,7 @@ void Switch(Handle target)
g_current = source; g_current = source;
} }
#elif defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__)) #elif defined(__linux__)
namespace { namespace {
struct Context { struct Context {
+2 -11
View File
@@ -44,15 +44,10 @@
#include <signal.h> #include <signal.h>
#if defined(__x86_64__) #if defined(__x86_64__)
// Only the x86 POSIX fault path inspects ucontext_t to recover the page-fault // Only the x86 POSIX fault path inspects ucontext_t to recover the page-fault
// write bit. macOS exposes the signal-handler context through sys/ucontext.h; // write bit. macOS deprecates ucontext and requires _XOPEN_SOURCE just to
// avoid ucontext.h itself because its deprecated user-context APIs require // include the header, while the arm64 handler does not use it at all.
// _XOPEN_SOURCE. The arm64 handler does not inspect a host context at all.
#if defined(__APPLE__)
#include <sys/ucontext.h>
#else
#include <ucontext.h> #include <ucontext.h>
#endif #endif
#endif
#include <unistd.h> #include <unistd.h>
#endif #endif
@@ -1133,11 +1128,7 @@ void PosixMemoryFaultHandler(int sig, siginfo_t* info, void* ucontextVoid) {
// error code x86 pushes on a page fault records whether it was a write. // error code x86 pushes on a page fault records whether it was a write.
if (ucontextVoid != nullptr) { if (ucontextVoid != nullptr) {
auto* uc = static_cast<ucontext_t*>(ucontextVoid); auto* uc = static_cast<ucontext_t*>(ucontextVoid);
#if defined(__APPLE__)
isWrite = uc->uc_mcontext != nullptr && (uc->uc_mcontext->__es.__err & 0x2) != 0;
#else
isWrite = (uc->uc_mcontext.gregs[REG_ERR] & 0x2) != 0; isWrite = (uc->uc_mcontext.gregs[REG_ERR] & 0x2) != 0;
#endif
} }
#endif #endif
+4 -18
View File
@@ -4,9 +4,9 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <bit>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <cstring>
#include <iostream> #include <iostream>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
@@ -52,20 +52,6 @@ std::array<float, kSoundPlayerCount> g_requestedSoundPlayerVolumes{};
std::array<float, kSoundPlayerCount> g_lastAppliedSoundPlayerVolumes{}; std::array<float, kSoundPlayerCount> g_lastAppliedSoundPlayerVolumes{};
std::array<bool, kSoundPlayerCount> g_haveSoundPlayerVolumes{}; std::array<bool, kSoundPlayerCount> g_haveSoundPlayerVolumes{};
uint32_t FloatBits(float value) noexcept {
static_assert(sizeof(float) == sizeof(uint32_t));
uint32_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
return bits;
}
float BitsFloat(uint32_t bits) noexcept {
static_assert(sizeof(float) == sizeof(uint32_t));
float value = 0.0f;
std::memcpy(&value, &bits, sizeof(value));
return value;
}
float ClampSoundPlayerVolume(float volume) noexcept { float ClampSoundPlayerVolume(float volume) noexcept {
// Match nw4r::snd::SoundPlayer::SetVolume at 0x800A35E0 exactly, // Match nw4r::snd::SoundPlayer::SetVolume at 0x800A35E0 exactly,
// including its NaN behavior (unordered compares select the upper bound). // including its NaN behavior (unordered compares select the upper bound).
@@ -77,7 +63,7 @@ float ClampSoundPlayerVolume(float volume) noexcept {
bool WriteGuestFloat(uint32_t address, float value) noexcept { bool WriteGuestFloat(uint32_t address, float value) noexcept {
try { try {
Memory::Write32(address, FloatBits(value)); Memory::Write32(address, std::bit_cast<uint32_t>(value));
return true; return true;
} catch (const Memory::AccessViolation&) { } catch (const Memory::AccessViolation&) {
return false; return false;
@@ -89,7 +75,7 @@ bool ReadGuestFloat(uint32_t address, float& value) noexcept {
if (!Memory::TryRead32(address, bits)) { if (!Memory::TryRead32(address, bits)) {
return false; return false;
} }
value = BitsFloat(bits); value = std::bit_cast<float>(bits);
return true; return true;
} }
@@ -572,7 +558,7 @@ void SetSoundPlayerVolume(uint32_t soundPlayer, float requestedVolume) {
} }
// Preserve the original function's access semantics. An invalid player is // Preserve the original function's access semantics. An invalid player is
// a guest bug and must not be converted into a silent successful call. // a guest bug and must not be converted into a silent successful call.
Memory::Write32(soundPlayer + kSoundPlayerVolumeOffset, FloatBits(applied)); Memory::Write32(soundPlayer + kSoundPlayerVolumeOffset, std::bit_cast<uint32_t>(applied));
} }
} // namespace MusicAttenuation } // namespace MusicAttenuation
+6 -10
View File
@@ -1,4 +1,5 @@
#include "nand_save_probe.h" #include "nand_save_probe.h"
#include "nand_settings.h"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@@ -6,10 +7,6 @@
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#ifdef _WIN32
#include <windows.h>
#endif
namespace fs = std::filesystem; namespace fs = std::filesystem;
using RuntimeNandSave::ReadAction; using RuntimeNandSave::ReadAction;
using RuntimeNandSave::Contents; using RuntimeNandSave::Contents;
@@ -50,11 +47,9 @@ int main() {
try { try {
const auto save = root / "title/00010004/524d4350/data/rksys.dat"; const auto save = root / "title/00010004/524d4350/data/rksys.dat";
const auto shadow = fs::path(save.native() + fs::path(".nandsafe.tmp").native()); const auto shadow = fs::path(save.native() + fs::path(".nandsafe.tmp").native());
// Save inspection must leave unrelated NAND data alone. Settings std::string error;
// initialization is covered separately by nand_settings_tests. Require(RuntimeNandSettings::Ensure(root, error, 1800000123), "New profile settings bootstrap");
const auto settingsPath = root / "title/00000001/00000002/data/setting.txt"; const auto identity = Read(RuntimeNandSettings::FilePath(root));
const std::string identity(256, '\x5a');
Write(settingsPath, identity);
Require(RuntimeNandSave::CheckRead(save, 1) == ReadAction::Proceed, "Fresh profile follows normal missing-file handling"); Require(RuntimeNandSave::CheckRead(save, 1) == ReadAction::Proceed, "Fresh profile follows normal missing-file handling");
Require(!fs::exists(save), "Probing fresh profile must not create a save"); Require(!fs::exists(save), "Probing fresh profile must not create a save");
@@ -131,7 +126,8 @@ int main() {
SetFileAttributesW(save.c_str(), FILE_ATTRIBUTE_NORMAL); SetFileAttributesW(save.c_str(), FILE_ATTRIBUTE_NORMAL);
Require(readOnlyResult == ReadAction::Proceed && Read(save) == existing, "Readable read-only save remains available"); Require(readOnlyResult == ReadAction::Proceed && Read(save) == existing, "Readable read-only save remains available");
#endif #endif
Require(Read(settingsPath) == identity, "Save inspection must not change NAND settings"); Require(RuntimeNandSettings::Ensure(root, error, 1900000123), "Existing profile settings bootstrap");
Require(Read(RuntimeNandSettings::FilePath(root)) == identity, "Save recovery must not change console identity");
fs::remove_all(root); fs::remove_all(root);
std::cout << "NAND save startup, preservation, interrupted-write and I/O failure scenarios passed\n"; std::cout << "NAND save startup, preservation, interrupted-write and I/O failure scenarios passed\n";
return 0; return 0;
-32
View File
@@ -43,38 +43,6 @@ int main() {
Require(!RuntimeNandSettings::Read(root)); Require(!RuntimeNandSettings::Read(root));
Require(!std::filesystem::exists(root)); Require(!std::filesystem::exists(root));
std::filesystem::create_directories(path.parent_path()); std::filesystem::create_directories(path.parent_path());
const auto scratchParent = root / "scratch-collisions";
std::filesystem::create_directories(scratchParent / ".setting-init-fixed-0");
const auto sentinel = scratchParent / ".setting-init-fixed-0" / "setting.txt";
{ std::ofstream output(sentinel); output << "another launch owns this"; }
const auto occupiedFile = scratchParent / ".setting-init-fixed-1";
{ std::ofstream output(occupiedFile); output << "leave this file alone"; }
std::error_code scratchError;
const auto claimed = CreateScratchDirectory(scratchParent, "fixed", scratchError);
Require(claimed && *claimed == scratchParent / ".setting-init-fixed-2" && !scratchError,
"Retry collisions with both existing directories and files");
Require(ReadBytes(sentinel) == "another launch owns this" &&
ReadBytes(occupiedFile) == "leave this file alone", "Never modify another launch's scratch data");
Require(!CreateScratchDirectory(occupiedFile / "not-a-directory", "fixed", scratchError) && scratchError,
"Real filesystem errors must fail rather than retry indefinitely");
// Force all claimants to use the same token; this deterministically
// exercises the collision path even when host clock precision is high.
std::array<std::optional<std::filesystem::path>, 16> claims;
std::vector<std::thread> claimants;
for (size_t i = 0; i < claims.size(); ++i) {
claimants.emplace_back([&, i] {
std::error_code ec;
claims[i] = CreateScratchDirectory(scratchParent, "shared", ec);
});
}
for (auto& claimant : claimants) claimant.join();
for (size_t i = 0; i < claims.size(); ++i) {
Require(claims[i].has_value(), "Every concurrent claimant must acquire a scratch directory");
for (size_t j = 0; j < i; ++j) {
Require(claims[i] != claims[j], "Concurrent claimants must own different scratch directories");
}
}
const std::string plain = "AREA=USA\r\n\nCODE=LU\r\nSERNO=987654321\r\nGAME=US\r\n"; const std::string plain = "AREA=USA\r\n\nCODE=LU\r\nSERNO=987654321\r\nGAME=US\r\n";
std::array<uint8_t, 256> fixture{}; std::array<uint8_t, 256> fixture{};
for (size_t i = 0; i < fixture.size(); ++i) { for (size_t i = 0; i < fixture.size(); ++i) {
-81
View File
@@ -1,81 +0,0 @@
#include "sc_serial_contract.h"
#include "nand_settings.h"
#include <algorithm>
#include <array>
#include <iostream>
#include <iomanip>
#include <stdexcept>
#include <string>
static void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
static uint32_t ReadWord(const unsigned char* bytes) {
return (uint32_t(bytes[0]) << 24) | (uint32_t(bytes[1]) << 16) |
(uint32_t(bytes[2]) << 8) | uint32_t(bytes[3]);
}
int main(int argc, char** argv) {
try {
// Feed real generator + SC ABI outputs to the upstream bot decoder.
// Usage: mkw_sc_serial_tests --timestamp-vectors <unix-seconds> ...
if (argc > 1 && std::string(argv[1]) == "--timestamp-vectors") {
for (int i = 2; i < argc; ++i) {
const auto timestamp = std::stoll(argv[i]);
const auto serial = RuntimeNandSettings::GenerateSerial(static_cast<std::time_t>(timestamp));
std::array<unsigned char, 4> output{};
Require(RuntimeScSerial::Write(serial, 4,
[](uint32_t address, size_t size) { return address == 4 && size == 4; },
[&](uint32_t, uint32_t value) {
for (unsigned j = 0; j < 4; ++j)
output[j] = static_cast<unsigned char>(value >> (24 - 8 * j));
}) == 1, "Generated serial must pass SC ABI");
std::cout << timestamp << '\t' << serial << "\tLEH"
<< std::setfill('0') << std::setw(9) << ReadWord(output.data()) << '\n';
}
return 0;
}
// Reproduce the reported csnums from the old string-writing override.
const unsigned char old7886[] = {'7', '8', '8', '6'};
const unsigned char old7618[] = {'7', '6', '1', '8'};
Require(ReadWord(old7886) == 926431286, "Reproduce shared LEH926431286");
Require(ReadWord(old7618) == 926298424, "Reproduce shared LEH926298424");
std::array<unsigned char, 16> memory;
size_t available = 4;
unsigned writes = 0;
const auto contains = [&](uint32_t address, size_t size) {
return address == 4 && size <= available;
};
const auto write32 = [&](uint32_t address, uint32_t value) {
++writes;
for (unsigned i = 0; i < 4; ++i)
memory[address + i] = static_cast<unsigned char>(value >> (24 - 8 * i));
};
for (const auto& pair : {std::pair{"788600001", 788600001u}, {"788699999", 788699999u},
{"761800001", 761800001u}, {"761899999", 761899999u},
{"012345678", 12345678u}, {"000000001", 1u}, {"999999999", 999999999u}}) {
memory.fill(0xa5);
writes = 0;
Require(RuntimeScSerial::Write(pair.first, 4, contains, write32) == 1, "Accept an exactly four-byte output buffer");
Require(writes == 1 && ReadWord(memory.data() + 4) == pair.second, "Return full numeric serial, including digits after common prefix");
for (size_t i = 0; i < memory.size(); ++i)
if (i < 4 || i >= 8) Require(memory[i] == 0xa5, "Do not overwrite adjacent guest stack data");
}
for (const char* serial : {"", "1234567890", "7886x1234", "-12345678", "+12345678"}) {
writes = 0;
Require(RuntimeScSerial::Write(serial, 4, contains, write32) == 0 && writes == 0, "Reject malformed serial without a write");
}
writes = 0;
Require(RuntimeScSerial::Write("788600001", 0, contains, write32) == 0 && writes == 0, "Reject null output");
available = 3;
Require(RuntimeScSerial::Write("788600001", 4, contains, write32) == 0 && writes == 0, "Reject undersized output");
std::cout << "SC serial collision reproduction, numeric output and memory-boundary tests passed\n";
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
+5 -19
View File
@@ -97,29 +97,15 @@ int main() {
Check(!Pressed(pulse), "pulse expires"); Check(!Pressed(pulse), "pulse expires");
// The timing-window idiom seen in shared Dolphin configs. // The timing-window idiom seen in shared Dolphin configs.
auto window = Compile("!pulse(`W`, 0.05) & pulse(`W`, 0.35)"); auto window = Compile("!pulse(`W`, 0.05) & pulse(`W`, 0.15)");
g_inputs["W"] = 0.0; g_inputs["W"] = 0.0;
window.Evaluate(Source()); window.Evaluate(Source());
g_inputs["W"] = 1.0; g_inputs["W"] = 1.0;
Check(!Pressed(window), "window closed before its start"); Check(!Pressed(window), "window closed before its start");
bool windowOpened = false; Sleep(90);
for (int i = 0; i < 40; ++i) { Check(Pressed(window), "window open between the two pulses");
Sleep(10); Sleep(90);
if (Pressed(window)) { Check(!Pressed(window), "window closed after its end");
windowOpened = true;
break;
}
}
Check(windowOpened, "window open between the two pulses");
bool windowClosed = false;
for (int i = 0; i < 50; ++i) {
Sleep(10);
if (!Pressed(window)) {
windowClosed = true;
break;
}
}
Check(windowClosed, "window closed after its end");
// timer ramps 0..1 and wraps, so a threshold turns it into a square wave. // timer ramps 0..1 and wraps, so a threshold turns it into a square wave.
auto timer = Compile("`X` & timer(0.1)"); auto timer = Compile("`X` & timer(0.1)");
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -378,7 +378,7 @@ public sealed partial class CxxLinearCodeGenerator
instructionContinuationLabels.TryGetValue(trace.Address, out var continuationLabel)) instructionContinuationLabels.TryGetValue(trace.Address, out var continuationLabel))
{ {
RecordEmittedLocalLabel(continuationLabel); RecordEmittedLocalLabel(continuationLabel);
body.AppendLine($"{continuationLabel}: ;"); body.AppendLine($"{continuationLabel}:");
} }
var localFallthroughLr = TryGetLocalFallthroughLr(block.Instructions, i, nonReturningCallTargets, lrContinuationCallTargets); var localFallthroughLr = TryGetLocalFallthroughLr(block.Instructions, i, nonReturningCallTargets, lrContinuationCallTargets);
// State-free bodies are cloned after register caching and lose their // State-free bodies are cloned after register caching and lose their
@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Translator.Core.Analysis.Representation; using Translator.Core.Analysis.Representation;
using Translator.Core.Analysis.Ssa; using Translator.Core.Analysis.Ssa;
using Translator.Core.CodeGen; using Translator.Core.CodeGen;
@@ -153,35 +153,4 @@ public class EmittedOutputShapeTests
Assert.Contains("f3.d = MemoryInline::FlatReadFloat32((r4 + 16));", code, StringComparison.Ordinal); Assert.Contains("f3.d = MemoryInline::FlatReadFloat32((r4 + 16));", code, StringComparison.Ordinal);
Assert.Contains("f4.d = MemoryInline::FlatReadFloat64((r4 + 24));", code, StringComparison.Ordinal); Assert.Contains("f4.d = MemoryInline::FlatReadFloat64((r4 + 24));", code, StringComparison.Ordinal);
} }
[Fact]
public void ContinuationLabelAtBlockEndEmitsValidCxx17Statement()
{
var function = new IrFunction("continuation_at_block_end", "0x800E7798", new[]
{
new IrBasicBlock("0x800E7798", new IrInstruction[]
{
new IrCall(string.Empty, "0x8179B000", System.Array.Empty<IrValue>()),
new IrTracePpc(0x800E77A0u, "nop", "0x60000000"),
new IrJump("0x800E77A4")
}),
new IrBasicBlock("0x800E77A4", new IrInstruction[]
{
new IrReturn(null)
})
});
var types = new RepresentationEnvironment(new Dictionary<string, ValueRepresentation>());
var code = new CxxLinearCodeGenerator().Emit(
0x800E7798,
new SsaTransformer().Convert(function),
new FunctionAbiClassification(function.Name, ValueRepresentation.Void),
types,
lrContinuationCallTargets: new HashSet<uint> { 0x8179B000u });
// In C++17, a label before a closing brace is invalid without an intervening statement.
Assert.Contains("loc_800E77A0: ;", code, StringComparison.Ordinal);
Assert.DoesNotContain("loc_800E77A0:\n}", code.Replace("\r\n", "\n"), StringComparison.Ordinal);
}
} }