mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-28 08:06:19 -04:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d7fe7450c | |||
| 0ae0d6391c | |||
| e9f1fdd77c | |||
| ad95c5c7da | |||
| dfe32f61e1 | |||
| e3f5e3a3ec | |||
| 0321842569 | |||
| 35b552782f | |||
| 2f30417d61 | |||
| 46eb6b46f5 | |||
| 4f166e1403 | |||
| 6b4de9fbdb | |||
| 16bfe7b095 | |||
| efedf42035 | |||
| 3ff14738bd | |||
| 8676d1ab14 | |||
| c255df5136 | |||
| 2ee48fc880 | |||
| d7b64361e7 | |||
| 5ee158918f | |||
| bd12087cc3 | |||
| d5296779ec | |||
| a93eaf582f | |||
| ed6fea0efb | |||
| 95a49f3c89 | |||
| 60197f2e75 | |||
| b87186c96b | |||
| 0b12fc864a | |||
| 90589131b5 | |||
| bd219e38a3 | |||
| 196b48528e | |||
| 54c608ef9c | |||
| f3df63b529 | |||
| dd6f596b45 | |||
| 6fa24737d3 | |||
| 279ce8328f | |||
| 9d182f8316 | |||
| 75886669bc | |||
| a50bad2970 | |||
| 9b7b9913e4 | |||
| a88b7b502b | |||
| d0d58072d1 | |||
| 6b853ca371 | |||
| 77a8623416 | |||
| 10ab54adee | |||
| a05c89739d | |||
| 82991ec337 | |||
| e164af9ff4 | |||
| e409d9f99b | |||
| 85f250155f | |||
| a9c9c0de26 | |||
| c92e45c2e7 | |||
| 583e702547 | |||
| b59e035b87 | |||
| 6f14bde26a | |||
| 83463764b8 | |||
| 8008d885ad | |||
| 7e6604c415 | |||
| 6fb593749e | |||
| 8ec3a1b752 | |||
| 8705e957c7 | |||
| 8e0cc96898 | |||
| 6458ec6abe | |||
| 209405dfb7 | |||
| 4bdaff01fc | |||
| b555ede2d3 | |||
| 53d8f71c68 | |||
| 149cfef608 | |||
| 25c69ae28e | |||
| 0bb15f0a44 | |||
| 466d06d7db | |||
| 8769cf6dea | |||
| 452b478bb3 | |||
| 407f8a7190 | |||
| c2289e4ba4 | |||
| a135beb201 | |||
| 88b990b060 | |||
| e0e362bd99 | |||
| 5654d8f21b | |||
| 730e3122d5 | |||
| f424536d3b | |||
| 2d9dc4e0f2 | |||
| 8e57cc162f | |||
| 1c0a3edee9 | |||
| d1d80613cc | |||
| 5d67b229f6 | |||
| 56db6ba641 | |||
| a67069afd3 | |||
| 009697fb97 | |||
| 3f7fed48c9 | |||
| 6eba523d70 | |||
| 8c6c177857 | |||
| be153e0fa0 |
@@ -3,3 +3,10 @@
|
|||||||
|
|
||||||
# Patch files must stay LF: git apply matches context bytes against LF upstream sources
|
# Patch files must stay LF: git apply matches context bytes against LF upstream sources
|
||||||
*.patch -text
|
*.patch -text
|
||||||
|
translator/tests/Translator.Tests/TestAssets/**/*.bin binary
|
||||||
|
|
||||||
|
# Region tables
|
||||||
|
projects/mkwii-ntsc-*/MAP.txt linguist-generated=true
|
||||||
|
projects/mkwii-ntsc-*/region_port.json linguist-generated=true
|
||||||
|
projects/mkwii-ntsc-j/data_addresses.txt linguist-generated=true
|
||||||
|
runtime/include/guest_abi/guest_versions.inc linguist-generated=true
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
recompilation:
|
||||||
|
name: Recompilation test
|
||||||
|
uses: ./.github/workflows/recomp-test.yml
|
||||||
|
|
||||||
translator:
|
translator:
|
||||||
name: Translator (build + test)
|
name: Translator (build + test)
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
@@ -26,6 +30,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: Cache NuGet packages
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: ~/.nuget/packages
|
||||||
|
key: ${{ runner.os }}-nuget-${{ hashFiles('translator/Translator.sln', '**/*.csproj', '**/*.props', '**/*.targets', '**/packages.lock.json', 'global.json', 'NuGet.config', 'nuget.config') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-nuget-
|
||||||
|
|
||||||
- name: Restore
|
- name: Restore
|
||||||
run: dotnet restore translator/Translator.sln
|
run: dotnet restore translator/Translator.sln
|
||||||
|
|
||||||
@@ -34,3 +46,69 @@ 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
|
||||||
|
|
||||||
|
platform_tests:
|
||||||
|
name: Host platform (${{ matrix.runner }})
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
runner: [ubuntu-24.04, macos-14, macos-15-intel]
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Configure without graphics dependencies or game assets
|
||||||
|
run: >-
|
||||||
|
cmake -S runtime -B build-platform -G Ninja
|
||||||
|
-DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||||
|
-DMKW_BUILD_PRODUCTS=OFF -DMKW_PLATFORM_TESTS_ONLY=ON
|
||||||
|
|
||||||
|
- name: Build and run host-platform tests
|
||||||
|
run: |
|
||||||
|
cmake --build build-platform --parallel 3
|
||||||
|
ctest --test-dir build-platform --output-on-failure
|
||||||
|
|
||||||
|
macos_substrate:
|
||||||
|
name: macOS arm64 (configure + substrate tests)
|
||||||
|
runs-on: macos-14
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Test macOS app dependency packaging
|
||||||
|
shell: bash
|
||||||
|
run: bash Launcher/macos/test-publish-app.command
|
||||||
|
|
||||||
|
- 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_runtime_config_tests \
|
||||||
|
mkw_nand_save_tests \
|
||||||
|
mkw_nand_settings_tests \
|
||||||
|
mkw_sc_serial_tests \
|
||||||
|
mkw_input_expr_tests \
|
||||||
|
mkw_bluetooth_controller_tests \
|
||||||
|
mkw_macos_native_compile \
|
||||||
|
mkw_macos_context_abi_tests \
|
||||||
|
mkw_macos_host_context_tests \
|
||||||
|
mkw_macos_guest_flat_memory_tests \
|
||||||
|
mkw_macos_external_audio_tests
|
||||||
|
|
||||||
|
- name: Test execution substrate
|
||||||
|
shell: bash
|
||||||
|
run: ctest --test-dir build-macos --output-on-failure
|
||||||
|
|||||||
+146
-10
@@ -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) via Launcher/Build-Installer.ps1 and
|
# WiiCompiled-Setup-x86_64.AppImage / WiiCompiled-Setup.pkg) via the platform packaging scripts -
|
||||||
# Launcher/build-appimage.sh respectively - the same scripts a maintainer runs by hand today to
|
# 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,6 +11,11 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Package version
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -20,6 +25,10 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
recompilation:
|
||||||
|
name: Recompilation test
|
||||||
|
uses: ./.github/workflows/recomp-test.yml
|
||||||
|
|
||||||
linux-appimage:
|
linux-appimage:
|
||||||
name: Linux (AppImage, ${{ matrix.arch }})
|
name: Linux (AppImage, ${{ matrix.arch }})
|
||||||
strategy:
|
strategy:
|
||||||
@@ -35,6 +44,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v6
|
- uses: actions/setup-dotnet@v6
|
||||||
with:
|
with:
|
||||||
@@ -70,6 +80,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v6
|
- uses: actions/setup-dotnet@v6
|
||||||
with:
|
with:
|
||||||
@@ -86,14 +97,140 @@ 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
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- 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
|
||||||
|
if [[ "${TAG_VERSION:-}" =~ ^v?[0-9] ]]; then
|
||||||
|
package_version="${TAG_VERSION#v}"
|
||||||
|
elif [[ -f "Launcher/Directory.Build.props" ]]; then
|
||||||
|
package_version=$(grep -m1 '<Version>' Launcher/Directory.Build.props | sed -E 's/.*<Version>([^<]+)<\/Version>.*/\1/')
|
||||||
|
fi
|
||||||
|
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 \
|
||||||
|
${package_version:+--version "$package_version"}
|
||||||
|
|
||||||
|
- name: Verify package layout and architecture-specific tools
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pkgutil --check-signature Launcher/dist/WiiCompiled-Setup.pkg || true
|
||||||
|
if pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \
|
||||||
|
grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)'; then
|
||||||
|
echo "::error::Setup.pkg contains a forbidden payload"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
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 an asset named exactly
|
# (non-prerelease) release whose tag is v<semver>, carrying the expected platform assets,
|
||||||
# WiiCompiled-Setup.exe, produced by a setup host that reports that same version.
|
# produced by setup hosts that report 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]
|
needs: [linux-appimage, windows-installer, macos-setup-package, recompilation]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -101,9 +238,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
# Wheel Wizard runs the downloaded setup with --version and refuses it when the reported
|
# Wheel Wizard runs the downloaded setup with --version and refuses it when the reported
|
||||||
# version differs from the release tag, so a tag that was pushed without bumping every pinned
|
# version differs from the release tag, so a tag that was pushed without bumping the pinned
|
||||||
# version would ship an update nobody can install. Catch that before anything is published.
|
# version would ship an update nobody can install. Catch that before anything is published.
|
||||||
- name: Verify the tag matches the pinned setup version
|
- name: Verify the tag matches the pinned setup version
|
||||||
env:
|
env:
|
||||||
@@ -118,9 +256,7 @@ jobs:
|
|||||||
status=1
|
status=1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
check Launcher/WiiCompiled.Setup.Windows/Program.cs "public const string Version = \"$version\";"
|
check Launcher/Directory.Build.props "<Version>$version</Version>"
|
||||||
check Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj "<Version>$version</Version>"
|
|
||||||
check Launcher/Build-Installer.ps1 "ProductVersion = '$version'"
|
|
||||||
exit $status
|
exit $status
|
||||||
|
|
||||||
# The build jobs upload with `archive: false`, which stores each installer as a raw file
|
# The build jobs upload with `archive: false`, which stores each installer as a raw file
|
||||||
@@ -139,7 +275,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; do
|
for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage WiiCompiled-Setup.pkg; 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")
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Synthetic recompilation
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows:
|
||||||
|
name: Windows runtime (synthetic DOL)
|
||||||
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-dotnet@v6
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
# Cache downloads only. Preparation still validates pins, and every run
|
||||||
|
# compiles current Aurora, runtime, and generated sources from scratch.
|
||||||
|
- uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: Launcher/artifacts/downloads
|
||||||
|
key: windows-recomp-downloads-${{ hashFiles('Launcher/Prepare-PortableTools.ps1', 'Launcher/Prepare-Dependencies.ps1') }}
|
||||||
|
|
||||||
|
# Install sccache.
|
||||||
|
- name: Run sccache-action
|
||||||
|
uses: mozilla/sccache-action@v0.0.11
|
||||||
|
|
||||||
|
# Tell CMake to use sccache and use GitHub's API.
|
||||||
|
- name: Configure sccache environment
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
"SCCACHE_GHA_ENABLED=true" | Add-Content -Path $env:GITHUB_ENV
|
||||||
|
"ACTIONS_CACHE_SERVICE_V2=on" | Add-Content -Path $env:GITHUB_ENV
|
||||||
|
"CMAKE_C_COMPILER_LAUNCHER=$env:SCCACHE_PATH" | Add-Content -Path $env:GITHUB_ENV
|
||||||
|
"CMAKE_CXX_COMPILER_LAUNCHER=$env:SCCACHE_PATH" | Add-Content -Path $env:GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Prepare the shipped Windows toolchain
|
||||||
|
shell: pwsh
|
||||||
|
run: ./Launcher/Prepare-PortableTools.ps1
|
||||||
|
|
||||||
|
- name: Build and run Windows host-platform tests
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$tools = Join-Path $PWD 'Launcher/artifacts/portable-tools'
|
||||||
|
$env:PATH = "$tools/llvm-mingw/bin;$tools/CMake/bin;$tools/Ninja;$env:PATH"
|
||||||
|
cmake -S runtime -B build-platform -G Ninja `
|
||||||
|
-DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ `
|
||||||
|
-DMKW_BUILD_PRODUCTS=OFF -DMKW_PLATFORM_TESTS_ONLY=ON
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
cmake --build build-platform --parallel 3
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
ctest --test-dir build-platform --output-on-failure
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
|
||||||
|
- name: Prepare pinned native dependencies
|
||||||
|
shell: pwsh
|
||||||
|
run: ./Launcher/Prepare-Dependencies.ps1
|
||||||
|
|
||||||
|
# Expose cache token context to the build script.
|
||||||
|
- name: Translate, compile the full runtime, and link
|
||||||
|
shell: pwsh
|
||||||
|
run: ./Launcher/Test-Recompilation.ps1 -Parallel 4
|
||||||
|
|
||||||
|
# Print cache results (even if the build fails)
|
||||||
|
- name: Show sccache stats
|
||||||
|
if: always()
|
||||||
|
shell: pwsh
|
||||||
|
run: sccache --show-stats
|
||||||
+7
-1
@@ -1,4 +1,5 @@
|
|||||||
/generated/
|
/generated/
|
||||||
|
/runtime/native-index.json
|
||||||
/Assets/
|
/Assets/
|
||||||
/assets/
|
/assets/
|
||||||
/PulsarPacks/
|
/PulsarPacks/
|
||||||
@@ -26,6 +27,8 @@ Code.pul
|
|||||||
/build/
|
/build/
|
||||||
/build-*/
|
/build-*/
|
||||||
/native-build/
|
/native-build/
|
||||||
|
/native-build-macos/
|
||||||
|
/local-products/
|
||||||
/dist/
|
/dist/
|
||||||
/out/
|
/out/
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
@@ -63,10 +66,13 @@ project.lock.json
|
|||||||
/.vs/
|
/.vs/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/.idea/
|
/.idea/
|
||||||
/tools/
|
/tools/*
|
||||||
|
__pycache__/
|
||||||
/TEMP/
|
/TEMP/
|
||||||
/tmp/
|
/tmp/
|
||||||
/test_output/
|
/test_output/
|
||||||
*.log
|
*.log
|
||||||
output.txt
|
output.txt
|
||||||
|
|
||||||
|
# Operating System
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "aurora-main"]
|
||||||
|
path = aurora-main
|
||||||
|
url = https://github.com/patchzyy/aurora-wiicompiled.git
|
||||||
|
branch = main
|
||||||
@@ -23,12 +23,23 @@ $publish = Join-Path $workRoot 'publish'
|
|||||||
$payloadRoot = Join-Path $workRoot 'payload'
|
$payloadRoot = Join-Path $workRoot 'payload'
|
||||||
$setupProject = Join-Path $PSScriptRoot 'WiiCompiled.Setup.Windows\WiiCompiled.Setup.Windows.csproj'
|
$setupProject = Join-Path $PSScriptRoot 'WiiCompiled.Setup.Windows\WiiCompiled.Setup.Windows.csproj'
|
||||||
$translatorProject = Join-Path $repoRoot 'translator\src\Translator.Cli\Translator.Cli.csproj'
|
$translatorProject = Join-Path $repoRoot 'translator\src\Translator.Cli\Translator.Cli.csproj'
|
||||||
$projectFile = Join-Path $repoRoot 'projects\mkwii\recomp.yml'
|
|
||||||
|
|
||||||
# Everything Mario-Kart-specific in the manifest below is read from the project file rather than
|
# Every supported release is one projects/<name>/recomp.yml. Everything Mario-Kart-specific in the
|
||||||
# restated here, and Test-PinnedFacts.ps1 checks the copies that cannot read it (the C++ entry
|
# manifest below is read from those files rather than restated here, and Test-PinnedFacts.ps1
|
||||||
# address, the C# endpoint constant, the lists this script and the installed host both carry).
|
# checks the copies that cannot read them (the C++ entry address, the C# Retro-WFC table, the
|
||||||
$pins = Get-MkwProjectPins $projectFile
|
# lists this script and the installed host both carry).
|
||||||
|
$releaseProjects = @(Get-ChildItem -LiteralPath (Join-Path $repoRoot 'projects') -Directory |
|
||||||
|
Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName 'recomp.yml') -PathType Leaf } |
|
||||||
|
Sort-Object Name)
|
||||||
|
if ($releaseProjects.Count -eq 0) { throw 'No translation project defines a supported release.' }
|
||||||
|
$supportedReleases = @(foreach ($releaseProject in $releaseProjects) {
|
||||||
|
$pins = Get-MkwProjectPins (Join-Path $releaseProject.FullName 'recomp.yml')
|
||||||
|
[ordered]@{ GameId = $pins.GameId; DolSha256 = $pins.DolSha256; RelSha256 = $pins.RelSha256 }
|
||||||
|
})
|
||||||
|
foreach ($key in @('GameId', 'DolSha256')) {
|
||||||
|
$duplicates = @($supportedReleases | Group-Object { $_[$key] } | Where-Object Count -gt 1)
|
||||||
|
if ($duplicates.Count -gt 0) { throw "Two translation projects pin the same $($key): $($duplicates[0].Name)." }
|
||||||
|
}
|
||||||
# The audit reports by throwing, which aborts this run; nothing native has been invoked yet, so
|
# The audit reports by throwing, which aborts this run; nothing native has been invoked yet, so
|
||||||
# there is no exit code to inspect.
|
# there is no exit code to inspect.
|
||||||
& (Join-Path $PSScriptRoot 'Test-PinnedFacts.ps1') -RepositoryRoot $repoRoot
|
& (Join-Path $PSScriptRoot 'Test-PinnedFacts.ps1') -RepositoryRoot $repoRoot
|
||||||
@@ -73,7 +84,16 @@ function Resolve-VcRuntimeDirectory([string]$RequestedPath) {
|
|||||||
function Copy-Directory([string]$Source, [string]$Destination) {
|
function Copy-Directory([string]$Source, [string]$Destination) {
|
||||||
Assert-Directory $Source "Source directory"
|
Assert-Directory $Source "Source directory"
|
||||||
[IO.Directory]::CreateDirectory($Destination) | Out-Null
|
[IO.Directory]::CreateDirectory($Destination) | Out-Null
|
||||||
Copy-Item -Path (Join-Path $Source '*') -Destination $Destination -Recurse -Force
|
# Source payloads are materialized trees, independent of Git checkout metadata.
|
||||||
|
foreach ($entry in Get-ChildItem -LiteralPath $Source -Force) {
|
||||||
|
if ($entry.Name -eq '.git') { continue }
|
||||||
|
$target = Join-Path $Destination $entry.Name
|
||||||
|
if ($entry.PSIsContainer) {
|
||||||
|
Copy-Directory $entry.FullName $target
|
||||||
|
} else {
|
||||||
|
Copy-Item -LiteralPath $entry.FullName -Destination $target -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function Compress-Zip([string]$Source, [string]$Destination, [string[]]$Entries) {
|
function Compress-Zip([string]$Source, [string]$Destination, [string[]]$Entries) {
|
||||||
$tar = Join-Path $env:SystemRoot 'System32\tar.exe'
|
$tar = Join-Path $env:SystemRoot 'System32\tar.exe'
|
||||||
@@ -112,6 +132,20 @@ Assert-Directory $dependencySources 'Pinned offline dependency sources'
|
|||||||
# back to compiling aurora from source, which is what this package exists to
|
# back to compiling aurora from source, which is what this package exists to
|
||||||
# avoid - and a mismatched one would be an ABI hazard.
|
# avoid - and a mismatched one would be an ABI hazard.
|
||||||
$portableClangHash = Get-MkwFileSha256 (Join-Path $portableTools 'llvm-mingw\bin\clang-22.exe')
|
$portableClangHash = Get-MkwFileSha256 (Join-Path $portableTools 'llvm-mingw\bin\clang-22.exe')
|
||||||
|
# The identity the runtime build compares against the package's runtime archive.
|
||||||
|
function Get-RuntimeSourceFingerprint([string]$RuntimeDirectory, [string]$DependenciesDirectory) {
|
||||||
|
$arguments = @("-DMKW_RUNTIME_DIR=$(ConvertTo-MkwCMakePath $RuntimeDirectory)")
|
||||||
|
$cppWinRt = Join-Path $DependenciesDirectory 'cppwinrt'
|
||||||
|
if (Test-Path -LiteralPath (Join-Path $cppWinRt 'winrt\base.h') -PathType Leaf) {
|
||||||
|
$arguments += "-DMKW_CPPWINRT_INCLUDE_DIR=$(ConvertTo-MkwCMakePath $cppWinRt)"
|
||||||
|
}
|
||||||
|
$arguments += @('-P', (ConvertTo-MkwCMakePath (Join-Path $RuntimeDirectory 'cmake\RuntimeFingerprint.cmake')))
|
||||||
|
$output = @(& (Join-Path $portableTools 'CMake\bin\cmake.exe') @arguments)
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Runtime fingerprinting failed with exit code $LASTEXITCODE." }
|
||||||
|
$line = $output | Where-Object { "$_" -match '^runtime_fingerprint=[0-9a-f]{64}$' } | Select-Object -First 1
|
||||||
|
if (-not $line) { throw "RuntimeFingerprint.cmake printed no fingerprint: $($output -join ' ')" }
|
||||||
|
return "$line".Substring('runtime_fingerprint='.Length)
|
||||||
|
}
|
||||||
function Test-NativePrebuiltCurrent([string]$PackageDirectory) {
|
function Test-NativePrebuiltCurrent([string]$PackageDirectory) {
|
||||||
$provenancePath = Join-Path $PackageDirectory 'provenance.json'
|
$provenancePath = Join-Path $PackageDirectory 'provenance.json'
|
||||||
if (-not (Test-Path -LiteralPath $provenancePath -PathType Leaf)) { return $false }
|
if (-not (Test-Path -LiteralPath $provenancePath -PathType Leaf)) { return $false }
|
||||||
@@ -129,6 +163,9 @@ function Test-NativePrebuiltCurrent([string]$PackageDirectory) {
|
|||||||
# release ships runtime/third_party sources that first-party TUs compile
|
# release ships runtime/third_party sources that first-party TUs compile
|
||||||
# against, so archives harvested before a vendored change are stale.
|
# against, so archives harvested before a vendored change are stale.
|
||||||
$recordedThirdParty = $provenance.PSObject.Properties['ThirdPartySourceFingerprint']
|
$recordedThirdParty = $provenance.PSObject.Properties['ThirdPartySourceFingerprint']
|
||||||
|
# The precompiled runtime archive must be from these runtime sources; a package without one
|
||||||
|
# would make every installation compile the runtime itself.
|
||||||
|
$recordedRuntime = $provenance.PSObject.Properties['RuntimeSourceFingerprint']
|
||||||
return ($provenance.CompilerSha256 -eq $portableClangHash) -and
|
return ($provenance.CompilerSha256 -eq $portableClangHash) -and
|
||||||
($provenance.FlagFingerprint -eq (Get-MkwNativeFlagFingerprint)) -and
|
($provenance.FlagFingerprint -eq (Get-MkwNativeFlagFingerprint)) -and
|
||||||
($null -ne $recordedDawn) -and
|
($null -ne $recordedDawn) -and
|
||||||
@@ -136,11 +173,13 @@ function Test-NativePrebuiltCurrent([string]$PackageDirectory) {
|
|||||||
($null -ne $recordedAurora) -and
|
($null -ne $recordedAurora) -and
|
||||||
($recordedAurora.Value -eq (Get-MkwAuroraSourceFingerprint (Join-Path $repoRoot 'aurora-main'))) -and
|
($recordedAurora.Value -eq (Get-MkwAuroraSourceFingerprint (Join-Path $repoRoot 'aurora-main'))) -and
|
||||||
($null -ne $recordedThirdParty) -and
|
($null -ne $recordedThirdParty) -and
|
||||||
($recordedThirdParty.Value -eq (Get-MkwThirdPartySourceFingerprint (Join-Path $repoRoot 'runtime\third_party')))
|
($recordedThirdParty.Value -eq (Get-MkwThirdPartySourceFingerprint (Join-Path $repoRoot 'runtime\third_party'))) -and
|
||||||
|
($null -ne $recordedRuntime) -and
|
||||||
|
($recordedRuntime.Value -eq (Get-RuntimeSourceFingerprint (Join-Path $repoRoot 'runtime') $dependencySources))
|
||||||
}
|
}
|
||||||
$nativePrebuiltPackage = Join-Path $dependencySources 'native_prebuilt'
|
$nativePrebuiltPackage = Join-Path $dependencySources 'native_prebuilt'
|
||||||
if (-not (Test-NativePrebuiltCurrent $nativePrebuiltPackage)) {
|
if (-not (Test-NativePrebuiltCurrent $nativePrebuiltPackage)) {
|
||||||
Write-Host '[0/6] Building the precompiled aurora and third-party package...'
|
Write-Host '[0/6] Building the precompiled aurora, third-party and runtime package...'
|
||||||
& (Join-Path $PSScriptRoot 'Prepare-NativePrebuilt.ps1') `
|
& (Join-Path $PSScriptRoot 'Prepare-NativePrebuilt.ps1') `
|
||||||
-PortableToolsDirectory $portableTools -DependencySourceDirectory $dependencySources
|
-PortableToolsDirectory $portableTools -DependencySourceDirectory $dependencySources
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'Precompiled aurora package preparation failed.' }
|
if ($LASTEXITCODE -ne 0) { throw 'Precompiled aurora package preparation failed.' }
|
||||||
@@ -215,13 +254,31 @@ if (Test-Path -LiteralPath $runtimeDeveloperBuild) {
|
|||||||
}
|
}
|
||||||
$auroraExtern = Join-Path $workspace 'aurora-main\extern'
|
$auroraExtern = Join-Path $workspace 'aurora-main\extern'
|
||||||
Get-ChildItem -LiteralPath $auroraExtern -Directory -Force | Remove-Item -Recurse -Force
|
Get-ChildItem -LiteralPath $auroraExtern -Directory -Force | Remove-Item -Recurse -Force
|
||||||
Copy-Directory (Join-Path $repoRoot 'projects\mkwii') (Join-Path $workspace 'projects\mkwii')
|
foreach ($releaseProject in $releaseProjects) {
|
||||||
|
Copy-Directory $releaseProject.FullName (Join-Path $workspace "projects\$($releaseProject.Name)")
|
||||||
|
}
|
||||||
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'LocalBuild.ps1') -Destination (Join-Path $workspace 'LocalBuild.ps1')
|
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'LocalBuild.ps1') -Destination (Join-Path $workspace 'LocalBuild.ps1')
|
||||||
# LocalBuild.ps1 dot-sources the canonical configure flags from this sibling.
|
# LocalBuild.ps1 dot-sources the canonical configure flags from this sibling.
|
||||||
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1') -Destination (Join-Path $workspace 'NativeBuildFlags.ps1')
|
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1') -Destination (Join-Path $workspace 'NativeBuildFlags.ps1')
|
||||||
[IO.Directory]::CreateDirectory((Join-Path $workspace 'Dependencies')) | Out-Null
|
[IO.Directory]::CreateDirectory((Join-Path $workspace 'Dependencies')) | Out-Null
|
||||||
foreach ($name in $requiredDependencies) { Copy-Directory (Join-Path $dependencySources $name) (Join-Path $workspace "Dependencies\$name") }
|
foreach ($name in $requiredDependencies) { Copy-Directory (Join-Path $dependencySources $name) (Join-Path $workspace "Dependencies\$name") }
|
||||||
|
|
||||||
|
# The translation identity covers the runtime's native registrations through this index (see
|
||||||
|
# ToolkitFingerprint), so it is extracted from exactly the staged runtime sources.
|
||||||
|
$nativeIndex = Join-Path $workspace 'runtime\native-index.json'
|
||||||
|
# The payload has no game inputs, so the project file (which validates them) cannot be loaded here.
|
||||||
|
& $translator emit-native-index --native-source-dir (Join-Path $workspace 'runtime\src') --out $nativeIndex
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Native index extraction failed with exit code $LASTEXITCODE." }
|
||||||
|
Assert-File $nativeIndex 'Runtime native index'
|
||||||
|
# A staged runtime that fingerprints differently from the shipped archive would make every
|
||||||
|
# installation silently compile the runtime from source.
|
||||||
|
$stagedRuntimeFingerprint = Get-RuntimeSourceFingerprint (Join-Path $workspace 'runtime') (Join-Path $workspace 'Dependencies')
|
||||||
|
$packagedRuntimeFingerprint = (Get-Content -LiteralPath (Join-Path $workspace 'Dependencies\native_prebuilt\provenance.json') -Raw |
|
||||||
|
ConvertFrom-Json).RuntimeSourceFingerprint
|
||||||
|
if ($stagedRuntimeFingerprint -ne $packagedRuntimeFingerprint) {
|
||||||
|
throw "The staged runtime ($stagedRuntimeFingerprint) does not match the precompiled runtime archive ($packagedRuntimeFingerprint)."
|
||||||
|
}
|
||||||
|
|
||||||
[IO.Directory]::CreateDirectory((Join-Path $payloadRoot 'host')) | Out-Null
|
[IO.Directory]::CreateDirectory((Join-Path $payloadRoot 'host')) | Out-Null
|
||||||
Copy-Item $setupHost (Join-Path $payloadRoot 'host\WiiCompiled-Setup.exe')
|
Copy-Item $setupHost (Join-Path $payloadRoot 'host\WiiCompiled-Setup.exe')
|
||||||
|
|
||||||
@@ -279,22 +336,23 @@ foreach ($required in @('ToolkitFingerprint','TranslationFingerprint','NativeToo
|
|||||||
if ([string]::IsNullOrWhiteSpace($identities.$required)) { throw "Payload identity output is missing $required." }
|
if ([string]::IsNullOrWhiteSpace($identities.$required)) { throw "Payload identity output is missing $required." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$productVersion = ((& $setupHost --version) -join '').Trim()
|
||||||
|
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($productVersion)) { throw 'The setup host did not report its version.' }
|
||||||
|
|
||||||
|
# Schema 3 replaced schema 2's single Expected* release fields with SupportedReleases.
|
||||||
$manifest = [ordered]@{
|
$manifest = [ordered]@{
|
||||||
SchemaVersion = 2
|
SchemaVersion = 3
|
||||||
ProductVersion = '0.2.27'
|
ProductVersion = $productVersion
|
||||||
ExpectedGameId = $pins.GameId
|
SupportedReleases = $supportedReleases
|
||||||
ExpectedDolSha256 = $pins.DolSha256
|
|
||||||
ExpectedRelSha256 = $pins.RelSha256
|
|
||||||
ToolkitReleaseTag = if ($ToolkitReleaseTag) { $ToolkitReleaseTag } else { '' }
|
ToolkitReleaseTag = if ($ToolkitReleaseTag) { $ToolkitReleaseTag } else { '' }
|
||||||
BuildModel = 'local-translation-and-compilation'
|
BuildModel = 'local-translation-and-compilation'
|
||||||
RetroWfcPayloadUri = $pins.RetroWfcPayloadUri
|
|
||||||
ToolkitFingerprint = $identities.ToolkitFingerprint
|
ToolkitFingerprint = $identities.ToolkitFingerprint
|
||||||
ToolkitPackageFingerprint = $identities.ToolkitPackageFingerprint
|
ToolkitPackageFingerprint = $identities.ToolkitPackageFingerprint
|
||||||
RuntimeAssetsFingerprint = $identities.RuntimeAssetsFingerprint
|
RuntimeAssetsFingerprint = $identities.RuntimeAssetsFingerprint
|
||||||
TranslationFingerprint = $identities.TranslationFingerprint
|
TranslationFingerprint = $identities.TranslationFingerprint
|
||||||
NativeToolchainFingerprint = $identities.NativeToolchainFingerprint
|
NativeToolchainFingerprint = $identities.NativeToolchainFingerprint
|
||||||
}
|
}
|
||||||
$manifest | ConvertTo-Json | Set-Content (Join-Path $payloadRoot 'payload-manifest.json') -Encoding UTF8
|
$manifest | ConvertTo-Json -Depth 4 | Set-Content (Join-Path $payloadRoot 'payload-manifest.json') -Encoding UTF8
|
||||||
|
|
||||||
Write-Host '[4/6] Enforcing the copyright and generated-code boundary...'
|
Write-Host '[4/6] Enforcing the copyright and generated-code boundary...'
|
||||||
# Both audits are PowerShell scripts that throw directly on failure (Test-PayloadBoundary.ps1
|
# Both audits are PowerShell scripts that throw directly on failure (Test-PayloadBoundary.ps1
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- The one product version. Release tags must match it. -->
|
||||||
|
<Version>0.2.35</Version>
|
||||||
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
+28
-15
@@ -103,7 +103,7 @@ function Write-MkwBuildStep([string]$StepId, [string]$Message) {
|
|||||||
function Reset-LocalDirectory([string]$Path) {
|
function Reset-LocalDirectory([string]$Path) {
|
||||||
$full = [IO.Path]::GetFullPath($Path)
|
$full = [IO.Path]::GetFullPath($Path)
|
||||||
$root = [IO.Path]::GetFullPath($Workspace).TrimEnd('\') + '\'
|
$root = [IO.Path]::GetFullPath($Workspace).TrimEnd('\') + '\'
|
||||||
$installRoot = [IO.Path]::GetFullPath((Split-Path -Parent $Workspace)).TrimEnd('\') + '\'
|
$installRoot = [IO.Path]::GetFullPath((Split-Path -Parent $realWorkspace)).TrimEnd('\') + '\'
|
||||||
# The caller-supplied output destinations are legitimate reset targets by
|
# The caller-supplied output destinations are legitimate reset targets by
|
||||||
# definition, wherever the caller placed them: a fresh install's operation
|
# definition, wherever the caller placed them: a fresh install's operation
|
||||||
# scratch lives beside the installation directory rather than inside it.
|
# scratch lives beside the installation directory rather than inside it.
|
||||||
@@ -150,15 +150,22 @@ if ($Profile -eq 'both' -and [string]::IsNullOrWhiteSpace($BaseOutputDirectory))
|
|||||||
if ($Profile -ne 'both' -and -not [string]::IsNullOrWhiteSpace($BaseOutputDirectory)) {
|
if ($Profile -ne 'both' -and -not [string]::IsNullOrWhiteSpace($BaseOutputDirectory)) {
|
||||||
throw '-BaseOutputDirectory is valid only with -Profile both.'
|
throw '-BaseOutputDirectory is valid only with -Profile both.'
|
||||||
}
|
}
|
||||||
|
$realWorkspace = $Workspace.TrimEnd('\')
|
||||||
|
$Workspace = Get-MkwBuildSafePath $realWorkspace 'workspace' 'runtime\CMakeLists.txt'
|
||||||
|
# A selected package inside the install may also name the same Code.pul through the real path.
|
||||||
|
# Give it the workspace spelling before comparing it with the staged copy.
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($RetroRewindPackageDirectory) -and
|
||||||
|
$RetroRewindPackageDirectory.StartsWith($realWorkspace + '\', [StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
$RetroRewindPackageDirectory = $Workspace + $RetroRewindPackageDirectory.Substring($realWorkspace.Length)
|
||||||
|
}
|
||||||
$translator = Join-Path $Toolkit 'Translator\Translator.Cli.exe'
|
$translator = Join-Path $Toolkit 'Translator\Translator.Cli.exe'
|
||||||
$toolchain = Get-MkwShellSafeToolchainRoot $Toolkit
|
$toolchain = Get-MkwBuildSafePath $Toolkit 'toolchain' 'CMake\bin\cmake.exe'
|
||||||
$cmake = Join-Path $toolchain 'CMake\bin\cmake.exe'
|
$cmake = Join-Path $toolchain 'CMake\bin\cmake.exe'
|
||||||
$ninja = Join-Path $toolchain 'Ninja\ninja.exe'
|
$ninja = Join-Path $toolchain 'Ninja\ninja.exe'
|
||||||
$toolchainBin = Join-Path $toolchain 'llvm-mingw\bin'
|
$toolchainBin = Join-Path $toolchain 'llvm-mingw\bin'
|
||||||
$cc = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang.exe'
|
$cc = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang.exe'
|
||||||
$cxx = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang++.exe'
|
$cxx = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang++.exe'
|
||||||
$windres = Join-Path $toolchainBin 'x86_64-w64-mingw32-windres.exe'
|
$windres = Join-Path $toolchainBin 'x86_64-w64-mingw32-windres.exe'
|
||||||
$project = Join-Path $Workspace 'projects\mkwii\recomp.yml'
|
|
||||||
$assets = Join-Path $Workspace 'Assets'
|
$assets = Join-Path $Workspace 'Assets'
|
||||||
$generated = Join-Path $Workspace 'generated'
|
$generated = Join-Path $Workspace 'generated'
|
||||||
$functions = Join-Path $generated 'functions'
|
$functions = Join-Path $generated 'functions'
|
||||||
@@ -175,14 +182,18 @@ $retroRoot = if ([string]::IsNullOrWhiteSpace($RetroRewindPackageDirectory)) {
|
|||||||
|
|
||||||
foreach ($required in @(
|
foreach ($required in @(
|
||||||
@($translator, 'Bundled translator'), @($cmake, 'Bundled CMake'), @($ninja, 'Bundled Ninja'),
|
@($translator, 'Bundled translator'), @($cmake, 'Bundled CMake'), @($ninja, 'Bundled Ninja'),
|
||||||
@($cc, 'Bundled C compiler'), @($cxx, 'Bundled C++ compiler'), @($windres, 'Bundled resource compiler'), @($project, 'Translation project'),
|
@($cc, 'Bundled C compiler'), @($cxx, 'Bundled C++ compiler'), @($windres, 'Bundled resource compiler'),
|
||||||
@((Join-Path $assets 'main.dol'), 'Extracted main.dol'), @((Join-Path $assets 'StaticR.rel'), 'Extracted StaticR.rel')
|
@((Join-Path $assets 'main.dol'), 'Extracted main.dol'), @((Join-Path $assets 'StaticR.rel'), 'Extracted StaticR.rel')
|
||||||
)) { Assert-File $required[0] $required[1] }
|
)) { Assert-File $required[0] $required[1] }
|
||||||
|
|
||||||
# Everything Mario-Kart-specific this script needs is read from the project file rather than
|
# Everything Mario-Kart-specific this script needs is read from the project file rather than
|
||||||
# restated here: the translation entry point below, and (through the retro-rewind profile the
|
# restated here: the translation entry point below, and (through the retro-rewind profile the
|
||||||
# translator loads itself) the module guest/link bases the mod translation uses.
|
# translator loads itself) the module guest/link bases the mod translation uses. The project is
|
||||||
$pins = Get-MkwProjectPins $project
|
# the one pinned to the extracted main.dol, which decides the region.
|
||||||
|
$selectedProject = Resolve-MkwProjectForAssets $Workspace $assets
|
||||||
|
$project = $selectedProject.Path
|
||||||
|
$pins = $selectedProject.Pins
|
||||||
|
$retroWfcPayloadFile = "binary\payload.$(($pins.RetroWfcPayloadUri -split 'g=')[-1]).bin"
|
||||||
|
|
||||||
# Three independent knobs: $translatorThreads (the translator's own worker threads; peak memory is
|
# Three independent knobs: $translatorThreads (the translator's own worker threads; peak memory is
|
||||||
# one heap that barely moves with thread count, so clamping it only slows translation), $translatedJobs
|
# one heap that barely moves with thread count, so clamping it only slows translation), $translatedJobs
|
||||||
@@ -235,7 +246,8 @@ try {
|
|||||||
# outputs plus --prune-stale mean Ninja only recompiles shards whose bytes actually moved.
|
# outputs plus --prune-stale mean Ninja only recompiles shards whose bytes actually moved.
|
||||||
$reuseBase = $false
|
$reuseBase = $false
|
||||||
if (-not $ForceCleanBuild -and -not [string]::IsNullOrWhiteSpace($TranslationFingerprint) -and
|
if (-not $ForceCleanBuild -and -not [string]::IsNullOrWhiteSpace($TranslationFingerprint) -and
|
||||||
(Get-RecordedFingerprint $translationProvenance 'TranslationFingerprint') -eq $TranslationFingerprint) {
|
(Get-RecordedFingerprint $translationProvenance 'TranslationFingerprint') -eq $TranslationFingerprint -and
|
||||||
|
(Get-RecordedFingerprint $translationProvenance 'DolSha256') -eq $pins.DolSha256) {
|
||||||
$artifacts = @($baseMetadata, $baseManifest, (Join-Path $generated 'base_translation_sources.bin'),
|
$artifacts = @($baseMetadata, $baseManifest, (Join-Path $generated 'base_translation_sources.bin'),
|
||||||
(Join-Path $generated 'base_translation_mod_awareness.json'))
|
(Join-Path $generated 'base_translation_mod_awareness.json'))
|
||||||
$reuseBase = -not ($artifacts | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) })
|
$reuseBase = -not ($artifacts | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) })
|
||||||
@@ -304,11 +316,11 @@ try {
|
|||||||
|
|
||||||
Invoke-Checked $translator @(
|
Invoke-Checked $translator @(
|
||||||
'emit-base-manifest', '--project', $project, '--out', $baseManifestDir,
|
'emit-base-manifest', '--project', $project, '--out', $baseManifestDir,
|
||||||
'--functions-dir', $functions, '--translation-output-metadata', $baseMetadata, '--region', 'P'
|
'--functions-dir', $functions, '--translation-output-metadata', $baseMetadata, '--region', $pins.Region
|
||||||
) 'Creating the local base translation manifest' -StepId 'emit-base-manifest'
|
) 'Creating the local base translation manifest' -StepId 'emit-base-manifest'
|
||||||
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace($TranslationFingerprint)) {
|
if (-not [string]::IsNullOrWhiteSpace($TranslationFingerprint)) {
|
||||||
[ordered]@{ SchemaVersion = 1; TranslationFingerprint = $TranslationFingerprint } |
|
[ordered]@{ SchemaVersion = 1; TranslationFingerprint = $TranslationFingerprint; DolSha256 = $pins.DolSha256 } |
|
||||||
ConvertTo-Json | Set-Content -LiteralPath $translationProvenance -Encoding UTF8
|
ConvertTo-Json | Set-Content -LiteralPath $translationProvenance -Encoding UTF8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,14 +333,14 @@ try {
|
|||||||
'translate-mod', '--project', $project, '--profile', 'retro-rewind',
|
'translate-mod', '--project', $project, '--profile', 'retro-rewind',
|
||||||
'--base-manifest', $baseManifest, '--base-translation-output-metadata', $baseMetadata,
|
'--base-manifest', $baseManifest, '--base-translation-output-metadata', $baseMetadata,
|
||||||
'--code-pul', $codePul, '--mod-root', $retroRoot, '--mod-name', 'Retro Rewind',
|
'--code-pul', $codePul, '--mod-root', $retroRoot, '--mod-name', 'Retro Rewind',
|
||||||
'--region', 'P', '--out', $retroOut, '--prefer-cached-inputs', '--emit-cpp',
|
'--region', $pins.Region, '--out', $retroOut, '--prefer-cached-inputs', '--emit-cpp',
|
||||||
'--threads', $translatorThreads
|
'--threads', $translatorThreads
|
||||||
)
|
)
|
||||||
if ($SkipRetroWfcPayload) {
|
if ($SkipRetroWfcPayload) {
|
||||||
$translateModArguments += '--skip-retro-wfc'
|
$translateModArguments += '--skip-retro-wfc'
|
||||||
} elseif (-not [string]::IsNullOrWhiteSpace($RetroWfcOfflineDirectory)) {
|
} elseif (-not [string]::IsNullOrWhiteSpace($RetroWfcOfflineDirectory)) {
|
||||||
$offlineRoot = [IO.Path]::GetFullPath($RetroWfcOfflineDirectory)
|
$offlineRoot = [IO.Path]::GetFullPath($RetroWfcOfflineDirectory)
|
||||||
$offlinePayload = Join-Path $offlineRoot 'binary\payload.RMCPD00.bin'
|
$offlinePayload = Join-Path $offlineRoot $retroWfcPayloadFile
|
||||||
Assert-File $offlinePayload 'Offline Retro-WFC shared payload'
|
Assert-File $offlinePayload 'Offline Retro-WFC shared payload'
|
||||||
$translateModArguments += @('--retro-wfc-payload', $offlinePayload)
|
$translateModArguments += @('--retro-wfc-payload', $offlinePayload)
|
||||||
} else {
|
} else {
|
||||||
@@ -415,13 +427,14 @@ try {
|
|||||||
$exe = Join-Path $build "$Target.exe"
|
$exe = Join-Path $build "$Target.exe"
|
||||||
Assert-File $exe 'Locally compiled game executable'
|
Assert-File $exe 'Locally compiled game executable'
|
||||||
Copy-Item -LiteralPath $exe -Destination (Join-Path $Destination "$Target.exe")
|
Copy-Item -LiteralPath $exe -Destination (Join-Path $Destination "$Target.exe")
|
||||||
foreach ($name in @('dxcompiler.dll','dxil.dll','libc++.dll','libpng16.dll','libunwind.dll','libz.dll','SDL3.dll','sqlite3.dll','webgpu_dawn.dll','z.dll','noshaders.zip','dsp_coef.bin','initial_pipeline_cache.db')) {
|
foreach ($name in @('dxcompiler.dll','dxil.dll','libc++.dll','libpng16.dll','libunwind.dll','libz.dll','libzlib1.dll','SDL3.dll','sqlite3.dll','webgpu_dawn.dll','z.dll','noshaders.zip','dsp_coef.bin','initial_pipeline_cache.db')) {
|
||||||
$file = Join-Path $build $name
|
$file = Join-Path $build $name
|
||||||
if (Test-Path -LiteralPath $file -PathType Leaf) { Copy-Item -LiteralPath $file -Destination $Destination }
|
if (Test-Path -LiteralPath $file -PathType Leaf) { Copy-Item -LiteralPath $file -Destination $Destination }
|
||||||
}
|
}
|
||||||
$redist = Join-Path $Toolkit 'Redist'
|
$redist = Join-Path $Toolkit 'Redist'
|
||||||
if (Test-Path -LiteralPath $redist -PathType Container) {
|
if (Test-Path -LiteralPath $redist -PathType Container) {
|
||||||
Copy-Item -Path (Join-Path $redist '*.dll') -Destination $Destination -Force
|
Get-ChildItem -LiteralPath $redist -Filter '*.dll' -File |
|
||||||
|
Copy-Item -Destination $Destination -Force
|
||||||
}
|
}
|
||||||
foreach ($name in @('config','wii_bootstrap')) {
|
foreach ($name in @('config','wii_bootstrap')) {
|
||||||
$directory = Join-Path $build $name
|
$directory = Join-Path $build $name
|
||||||
@@ -438,8 +451,8 @@ try {
|
|||||||
RelSha256 = $relSha
|
RelSha256 = $relSha
|
||||||
CodePulSha256 = if ($isRetro) { Get-MkwFileSha256 (Join-Path $retroRoot 'Binaries\Code.pul') } else { $null }
|
CodePulSha256 = if ($isRetro) { Get-MkwFileSha256 (Join-Path $retroRoot 'Binaries\Code.pul') } else { $null }
|
||||||
RetroWfcPayloadMode = if (-not $isRetro) { $null } elseif ($SkipRetroWfcPayload) { 'skipped' } else { $RetroWfcPayloadOrigin }
|
RetroWfcPayloadMode = if (-not $isRetro) { $null } elseif ($SkipRetroWfcPayload) { 'skipped' } else { $RetroWfcPayloadOrigin }
|
||||||
RetroWfcPayloadSha256 = if ($isRetro -and -not $SkipRetroWfcPayload) { Get-MkwFileSha256 (Join-Path $RetroWfcOfflineDirectory 'binary\payload.RMCPD00.bin') } else { $null }
|
RetroWfcPayloadSha256 = if ($isRetro -and -not $SkipRetroWfcPayload) { Get-MkwFileSha256 (Join-Path $RetroWfcOfflineDirectory $retroWfcPayloadFile) } else { $null }
|
||||||
RetroWfcPayloadLength = if ($isRetro -and -not $SkipRetroWfcPayload) { (Get-Item -LiteralPath (Join-Path $RetroWfcOfflineDirectory 'binary\payload.RMCPD00.bin')).Length } else { $null }
|
RetroWfcPayloadLength = if ($isRetro -and -not $SkipRetroWfcPayload) { (Get-Item -LiteralPath (Join-Path $RetroWfcOfflineDirectory $retroWfcPayloadFile)).Length } else { $null }
|
||||||
Compiler = "llvm-mingw clang-22 sha256:$compilerSha"
|
Compiler = "llvm-mingw clang-22 sha256:$compilerSha"
|
||||||
}
|
}
|
||||||
$provenance | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $Destination 'local-build.json') -Encoding UTF8
|
$provenance | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $Destination 'local-build.json') -Encoding UTF8
|
||||||
|
|||||||
@@ -40,46 +40,60 @@ function Get-MkwToolchainPath([string]$ToolchainRoot) {
|
|||||||
) -join ';')
|
) -join ';')
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-MkwShellSafeToolchainRoot([string]$ToolchainRoot) {
|
function Get-MkwBuildSafePath([string]$Path, [string]$Kind, [string]$MarkerFile) {
|
||||||
if ([string]::IsNullOrWhiteSpace($ToolchainRoot)) { throw 'A toolchain root is required.' }
|
<#
|
||||||
$full = [IO.Path]::GetFullPath($ToolchainRoot)
|
The Windows native build passes workspace paths through CMake, Ninja response files and
|
||||||
|
clang, which do not all interpret quotes the same way. Keep those paths plain even when the
|
||||||
|
user's install directory contains an apostrophe, ampersand or other punctuation.
|
||||||
|
#>
|
||||||
|
if ([string]::IsNullOrWhiteSpace($Path)) { throw "A $Kind path is required." }
|
||||||
|
$full = [IO.Path]::GetFullPath($Path)
|
||||||
# A drive root keeps its separator: "C:" is relative to the current directory on that drive.
|
# A drive root keeps its separator: "C:" is relative to the current directory on that drive.
|
||||||
if ($full -ne [IO.Path]::GetPathRoot($full)) { $full = $full.TrimEnd('\') }
|
if ($full -ne [IO.Path]::GetPathRoot($full)) { $full = $full.TrimEnd('\') }
|
||||||
if ($full -notmatch '[()&^%!]') { return $full }
|
if ($full -cmatch '^[A-Za-z0-9 ._\\:-]+$') { return $full }
|
||||||
|
|
||||||
|
Assert-File (Join-Path $full $MarkerFile) "$Kind marker"
|
||||||
|
|
||||||
$sha = [Security.Cryptography.SHA256]::Create()
|
$sha = [Security.Cryptography.SHA256]::Create()
|
||||||
try {
|
try {
|
||||||
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($full.ToLowerInvariant()))
|
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($full.ToLowerInvariant()))
|
||||||
} finally { $sha.Dispose() }
|
} finally { $sha.Dispose() }
|
||||||
$linkName = 'toolchain-' + ((($bytes[0..7]) | ForEach-Object { $_.ToString('x2') }) -join '')
|
$linkName = "$Kind-" + ((($bytes[0..7]) | ForEach-Object { $_.ToString('x2') }) -join '')
|
||||||
|
|
||||||
$failures = @()
|
$failures = @()
|
||||||
foreach ($base in @($env:ProgramData, $env:PUBLIC)) {
|
foreach ($base in @($env:ProgramData, $env:PUBLIC)) {
|
||||||
if ([string]::IsNullOrWhiteSpace($base) -or $base -match '[()&^%! ]') { continue }
|
if ([string]::IsNullOrWhiteSpace($base) -or $base -cnotmatch '^[A-Za-z0-9._\\:-]+$') { continue }
|
||||||
$link = Join-Path (Join-Path $base 'WiiCompiled') $linkName
|
$link = Join-Path (Join-Path $base 'WiiCompiled') $linkName
|
||||||
try {
|
try {
|
||||||
[IO.Directory]::CreateDirectory((Split-Path -Parent $link)) | Out-Null
|
[IO.Directory]::CreateDirectory((Split-Path -Parent $link)) | Out-Null
|
||||||
# The name already identifies the target, so an existing junction that still resolves is
|
$existing = Get-Item -LiteralPath $link -Force -ErrorAction SilentlyContinue
|
||||||
# this one; only a broken leftover is replaced. Directory.Delete removes the reparse
|
if ($null -ne $existing) {
|
||||||
# point itself, where Remove-Item -Recurse would delete the toolchain it points at.
|
# Never trust a directory just because it has the marker: it could point at a
|
||||||
if (-not (Test-Path -LiteralPath (Join-Path $link 'CMake\bin\cmake.exe') -PathType Leaf)) {
|
# different installation. Nor may we remove a directory we did not create.
|
||||||
if (Test-Path -LiteralPath $link) { [IO.Directory]::Delete($link) }
|
if ($existing.LinkType -ne 'Junction' -or
|
||||||
|
@($existing.Target).Count -ne 1 -or
|
||||||
|
-not [string]::Equals([IO.Path]::GetFullPath(@($existing.Target)[0]),
|
||||||
|
$full, [StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
throw "An existing path is not the expected junction: $link"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
New-Item -ItemType Junction -Path $link -Target $full -ErrorAction Stop | Out-Null
|
New-Item -ItemType Junction -Path $link -Target $full -ErrorAction Stop | Out-Null
|
||||||
}
|
}
|
||||||
Write-Host "MKWCBUILD: Building through $link, because $full contains characters cmd.exe cannot parse"
|
Assert-File (Join-Path $link $MarkerFile) "$Kind junction marker"
|
||||||
|
Write-Host "MKWCBUILD: Building the $Kind through $link, because $full contains characters the native build cannot quote reliably"
|
||||||
return $link
|
return $link
|
||||||
} catch {
|
} catch {
|
||||||
$failures += "$link ($($_.Exception.Message))"
|
$failures += "$link ($($_.Exception.Message))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw ("The toolchain path $full contains a character (one of ( ) & ^ % !) that the compiler " +
|
throw ("The $Kind path $full cannot be passed safely to the native build, and no junction " +
|
||||||
'cannot be invoked through, and no junction to it could be created: ' + ($failures -join '; ') +
|
'to it could be created: ' + ($failures -join '; ') + '. Install to a path of plain ' +
|
||||||
'. Install to a path without those characters.')
|
'letters, digits and spaces, or make a safe junction location available.')
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-MkwProjectPins([string]$ProjectFile) {
|
function Get-MkwProjectPins([string]$ProjectFile) {
|
||||||
<#
|
<#
|
||||||
The Mario Kart Wii facts pinned by projects/mkwii/recomp.yml (game identity, clean input
|
The Mario Kart Wii facts pinned by a projects/*/recomp.yml (game identity, clean input
|
||||||
digests, translation entry point, Retro-WFC endpoint), read from one place instead of being
|
digests, translation entry point, Retro-WFC endpoint), read from one place instead of being
|
||||||
restated; Test-PinnedFacts.ps1 checks the copies that can't read YAML (C++ header, C#
|
restated; Test-PinnedFacts.ps1 checks the copies that can't read YAML (C++ header, C#
|
||||||
constants) against these values. Parsing is literal, not a YAML dependency, since the file is
|
constants) against these values. Parsing is literal, not a YAML dependency, since the file is
|
||||||
@@ -87,7 +101,7 @@ function Get-MkwProjectPins([string]$ProjectFile) {
|
|||||||
#>
|
#>
|
||||||
Assert-File $ProjectFile 'Translation project file'
|
Assert-File $ProjectFile 'Translation project file'
|
||||||
$pins = [ordered]@{
|
$pins = [ordered]@{
|
||||||
GameId = ''; DolSha256 = ''; RelSha256 = ''; EntryPoint = ''; RetroWfcPayloadUri = ''
|
GameId = ''; Region = ''; DolSha256 = ''; RelSha256 = ''; EntryPoint = ''; RetroWfcPayloadUri = ''
|
||||||
}
|
}
|
||||||
$section = ''
|
$section = ''
|
||||||
$inputKey = ''
|
$inputKey = ''
|
||||||
@@ -98,6 +112,8 @@ function Get-MkwProjectPins([string]$ProjectFile) {
|
|||||||
if ($section -eq 'inputs' -and $line -match '^\s{2}([A-Za-z0-9_]+):\s*$') { $inputKey = $Matches[1]; continue }
|
if ($section -eq 'inputs' -and $line -match '^\s{2}([A-Za-z0-9_]+):\s*$') { $inputKey = $Matches[1]; continue }
|
||||||
if ($section -eq 'project' -and $line -match '^\s*game_id:\s*(\S+)\s*$') {
|
if ($section -eq 'project' -and $line -match '^\s*game_id:\s*(\S+)\s*$') {
|
||||||
$pins.GameId = $Matches[1]
|
$pins.GameId = $Matches[1]
|
||||||
|
} elseif ($section -eq 'project' -and $line -match '^\s*region:\s*(\S+)\s*$') {
|
||||||
|
$pins.Region = $Matches[1]
|
||||||
} elseif ($section -eq 'inputs' -and $line -match '^\s*sha256:\s*([0-9a-fA-F]{64})\s*$') {
|
} elseif ($section -eq 'inputs' -and $line -match '^\s*sha256:\s*([0-9a-fA-F]{64})\s*$') {
|
||||||
if ($inputKey -eq 'dol') { $pins.DolSha256 = $Matches[1].ToLowerInvariant() }
|
if ($inputKey -eq 'dol') { $pins.DolSha256 = $Matches[1].ToLowerInvariant() }
|
||||||
elseif ($inputKey -eq 'rel') { $pins.RelSha256 = $Matches[1].ToLowerInvariant() }
|
elseif ($inputKey -eq 'rel') { $pins.RelSha256 = $Matches[1].ToLowerInvariant() }
|
||||||
@@ -116,13 +132,38 @@ function Get-MkwProjectPins([string]$ProjectFile) {
|
|||||||
return $pins
|
return $pins
|
||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
|
function Resolve-MkwProjectForAssets([string]$Workspace, [string]$AssetsDirectory) {
|
||||||
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '') {
|
|
||||||
<#
|
<#
|
||||||
Runs a build tool and turns a non-zero exit code into a described failure. Start-Process -Wait
|
The translation project pinned to the main.dol in AssetsDirectory, after checking that
|
||||||
is deliberate: it waits for the whole process tree, since a .NET single-file bundle host may
|
StaticR.rel and any extracted disc data belong to the same release.
|
||||||
hand off to an extracted child that PowerShell's call operator would not wait for. Start-Process
|
#>
|
||||||
doesn't publish $LASTEXITCODE, so this sets it manually for callers that check it.
|
$dolSha = Get-MkwFileSha256 (Join-Path $AssetsDirectory 'main.dol')
|
||||||
|
foreach ($file in Get-ChildItem -LiteralPath (Join-Path $Workspace 'projects') -Filter 'recomp.yml' -Recurse -File) {
|
||||||
|
$pins = Get-MkwProjectPins $file.FullName
|
||||||
|
if ($pins.DolSha256 -ne $dolSha) { continue }
|
||||||
|
if ((Get-MkwFileSha256 (Join-Path $AssetsDirectory 'StaticR.rel')) -ne $pins.RelSha256) {
|
||||||
|
throw "StaticR.rel does not match the clean $($pins.GameId) release of main.dol."
|
||||||
|
}
|
||||||
|
$bootBin = Join-Path $AssetsDirectory 'DATA\sys\boot.bin'
|
||||||
|
if (Test-Path -LiteralPath $bootBin -PathType Leaf) {
|
||||||
|
$discId = [Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($bootBin), 0, 6)
|
||||||
|
if ($discId -ne $pins.GameId) {
|
||||||
|
throw "The extracted game data is $discId but main.dol is $($pins.GameId); extract the game again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [pscustomobject]@{ Path = $file.FullName; Pins = $pins }
|
||||||
|
}
|
||||||
|
throw "main.dol (sha256 $dolSha) is not a supported clean Mario Kart Wii release."
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
|
||||||
|
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '', [bool]$WaitForProcessTree = $true) {
|
||||||
|
<#
|
||||||
|
Runs a build tool and turns a non-zero exit code into a described failure. By default,
|
||||||
|
Start-Process -Wait waits for the whole process tree, since a .NET single-file bundle host may
|
||||||
|
hand off to an extracted child that PowerShell's call operator would not wait for. Callers that
|
||||||
|
need to avoid waiting on unrelated descendants can opt into the call-operator path.
|
||||||
|
Start-Process doesn't publish $LASTEXITCODE, so this sets it manually for callers that check it.
|
||||||
-StepId emits the machine-readable form the installer's progress bar consumes (BuildStepIds in
|
-StepId emits the machine-readable form the installer's progress bar consumes (BuildStepIds in
|
||||||
WiiCompiled.Setup/InstallProgress.cs); the human sentence stays on the same log line.
|
WiiCompiled.Setup/InstallProgress.cs); the human sentence stays on the same log line.
|
||||||
#>
|
#>
|
||||||
@@ -130,11 +171,24 @@ function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Descri
|
|||||||
else { Write-Host "${LogPrefix}: $Description" }
|
else { Write-Host "${LogPrefix}: $Description" }
|
||||||
$quotedArguments = @($Arguments | ForEach-Object {
|
$quotedArguments = @($Arguments | ForEach-Object {
|
||||||
if ($_.Contains('"')) { throw "A native build argument contains an unsupported quote: $_" }
|
if ($_.Contains('"')) { throw "A native build argument contains an unsupported quote: $_" }
|
||||||
'"' + $_ + '"'
|
# Backslashes directly before the closing quote would otherwise escape it.
|
||||||
|
'"' + ($_ -replace '(\\+)$', '$1$1') + '"'
|
||||||
})
|
})
|
||||||
$process = Start-Process -FilePath $FilePath -ArgumentList $quotedArguments `
|
if ($WaitForProcessTree) {
|
||||||
-NoNewWindow -Wait -PassThru
|
$process = Start-Process -FilePath $FilePath -ArgumentList $quotedArguments `
|
||||||
$exitCode = $process.ExitCode
|
-NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
} else {
|
||||||
|
# Tools report progress on stderr (CMake message()); only the exit code decides failure.
|
||||||
|
$previousPreference = $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
try {
|
||||||
|
& $FilePath @Arguments
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
} finally {
|
||||||
|
$ErrorActionPreference = $previousPreference
|
||||||
|
}
|
||||||
|
}
|
||||||
$global:LASTEXITCODE = $exitCode
|
$global:LASTEXITCODE = $exitCode
|
||||||
if ($exitCode -ne 0) { throw "$Description failed with exit code $exitCode." }
|
if ($exitCode -ne 0) { throw "$Description failed with exit code $exitCode." }
|
||||||
}
|
}
|
||||||
@@ -187,13 +241,14 @@ function Get-MkwThirdPartySourceFingerprint([string]$ThirdPartyDirectory) {
|
|||||||
SHA-256 over runtime/third_party, the vendored sources behind the prebuilt package. The
|
SHA-256 over runtime/third_party, the vendored sources behind the prebuilt package. The
|
||||||
precompiled Crypto++ archive is linked while first-party TUs compile against the same headers,
|
precompiled Crypto++ archive is linked while first-party TUs compile against the same headers,
|
||||||
so a workspace whose vendored tree moved on must not consume archives harvested from an older
|
so a workspace whose vendored tree moved on must not consume archives harvested from an older
|
||||||
one (same reasoning as the aurora fingerprint below). Unlike that scan this hashes hidden files
|
one (same reasoning as the aurora fingerprint below), skipping dot-paths by name the same way.
|
||||||
too, since Build-Installer stages this tree with Copy-Item -Force. Returns '' when absent.
|
Returns '' when absent.
|
||||||
#>
|
#>
|
||||||
if ([string]::IsNullOrWhiteSpace($ThirdPartyDirectory)) { return '' }
|
if ([string]::IsNullOrWhiteSpace($ThirdPartyDirectory)) { return '' }
|
||||||
if (-not (Test-Path -LiteralPath $ThirdPartyDirectory -PathType Container)) { return '' }
|
if (-not (Test-Path -LiteralPath $ThirdPartyDirectory -PathType Container)) { return '' }
|
||||||
$root = [IO.Path]::GetFullPath($ThirdPartyDirectory).TrimEnd('\')
|
$root = [IO.Path]::GetFullPath($ThirdPartyDirectory).TrimEnd('\')
|
||||||
$files = Get-ChildItem -LiteralPath $root -Recurse -File |
|
$files = Get-ChildItem -LiteralPath $root -Recurse -File -Force |
|
||||||
|
Where-Object { -not ($_.FullName.Substring($root.Length + 1) -split '\\' | Where-Object { $_.StartsWith('.') }) } |
|
||||||
Sort-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
|
Sort-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
|
||||||
$sha = [Security.Cryptography.SHA256]::Create()
|
$sha = [Security.Cryptography.SHA256]::Create()
|
||||||
try {
|
try {
|
||||||
@@ -224,13 +279,13 @@ function Get-MkwAuroraSourceFingerprint([string]$AuroraDirectory) {
|
|||||||
(Join-Path $root 'extern') + '\',
|
(Join-Path $root 'extern') + '\',
|
||||||
(Join-Path $root 'build') + '\'
|
(Join-Path $root 'build') + '\'
|
||||||
)
|
)
|
||||||
# Hidden files are skipped deliberately: Build-Installer.ps1 stages this tree
|
# Dot-paths (the submodule's .git pointer) are skipped by name, not by the hidden attribute:
|
||||||
# with Copy-Item -Recurse (no -Force), so they never reach the payload and
|
# the payload zip drops that attribute, so the installed side would otherwise hash them.
|
||||||
# hashing them would make the two sides disagree by construction.
|
$files = Get-ChildItem -LiteralPath $root -Recurse -File -Force |
|
||||||
$files = Get-ChildItem -LiteralPath $root -Recurse -File |
|
|
||||||
Where-Object {
|
Where-Object {
|
||||||
$full = $_.FullName
|
$full = $_.FullName
|
||||||
-not ($excludedPrefixes | Where-Object { $full.StartsWith($_, [StringComparison]::OrdinalIgnoreCase) })
|
-not ($excludedPrefixes | Where-Object { $full.StartsWith($_, [StringComparison]::OrdinalIgnoreCase) }) -and
|
||||||
|
-not ($full.Substring($root.Length + 1) -split '\\' | Where-Object { $_.StartsWith('.') })
|
||||||
} |
|
} |
|
||||||
Sort-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
|
Sort-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
|
||||||
$sha = [Security.Cryptography.SHA256]::Create()
|
$sha = [Security.Cryptography.SHA256]::Create()
|
||||||
|
|||||||
@@ -21,16 +21,21 @@ $auroraExtern = Join-Path $repoRoot 'aurora-main\extern\CMakeLists.txt'
|
|||||||
$auroraDawn = Join-Path $repoRoot 'aurora-main\cmake\AuroraDawnProvider.cmake'
|
$auroraDawn = Join-Path $repoRoot 'aurora-main\cmake\AuroraDawnProvider.cmake'
|
||||||
$auroraLibUsb = Join-Path $repoRoot 'aurora-main\cmake\AuroraLibUSB.cmake'
|
$auroraLibUsb = Join-Path $repoRoot 'aurora-main\cmake\AuroraLibUSB.cmake'
|
||||||
$auroraSdl = Join-Path $repoRoot 'aurora-main\cmake\AuroraSDL3Provider.cmake'
|
$auroraSdl = Join-Path $repoRoot 'aurora-main\cmake\AuroraSDL3Provider.cmake'
|
||||||
|
$auroraVersions = Join-Path $repoRoot 'aurora-main\cmake\AuroraDependencyVersions.cmake'
|
||||||
|
$auroraImgui = Join-Path $repoRoot 'aurora-main\cmake\AuroraDearImguiProvider.cmake'
|
||||||
|
if (-not (Test-Path -LiteralPath $auroraCMake -PathType Leaf)) {
|
||||||
|
throw 'Aurora sources are missing. Run git submodule update --init --recursive from the repository root first.'
|
||||||
|
}
|
||||||
|
|
||||||
# Name = directory under the destination; FetchContent maps it back through
|
# Name = directory under the destination; FetchContent maps it back through
|
||||||
# FETCHCONTENT_SOURCE_DIR_<UPPERCASE NAME> (NativeBuildFlags.ps1), so the names are the declared
|
# FETCHCONTENT_SOURCE_DIR_<UPPERCASE NAME> (NativeBuildFlags.ps1), so the names are the declared
|
||||||
# FetchContent names, not the upstream project names.
|
# FetchContent names, not the upstream project names.
|
||||||
$packages = @(
|
$packages = @(
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'SDL'; File = 'SDL3-3.4.4.tar.gz'
|
Name = 'SDL'; File = 'SDL3-3.4.10.tar.gz'
|
||||||
Uris = @('https://github.com/libsdl-org/SDL/releases/download/release-3.4.4/SDL3-3.4.4.tar.gz')
|
Uris = @('https://github.com/libsdl-org/SDL/releases/download/release-3.4.10/SDL3-3.4.10.tar.gz')
|
||||||
Pins = @(@{ File = $auroraCMake; Text = 'set(AURORA_SDL3_VERSION "3.4.4"' },
|
Pins = @(@{ File = $auroraVersions; Text = '_aurora_dependency_version(AURORA_SDL3_REF "refs/tags/release-3.4.10"' },
|
||||||
@{ File = $auroraSdl; Text = 'releases/download/release-${AURORA_SDL3_VERSION}/SDL3-${AURORA_SDL3_VERSION}.tar.gz' })
|
@{ File = $auroraSdl; Text = 'URL "https://github.com/libsdl-org/SDL/archive/${AURORA_SDL3_REF}.tar.gz"' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'abseil-cpp'; File = 'abseil-cpp-20240722.0.tar.gz'
|
Name = 'abseil-cpp'; File = 'abseil-cpp-20240722.0.tar.gz'
|
||||||
@@ -38,15 +43,15 @@ $packages = @(
|
|||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/abseil/abseil-cpp/archive/refs/tags/20240722.0.tar.gz' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/abseil/abseil-cpp/archive/refs/tags/20240722.0.tar.gz' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'dawn_prebuilt'; File = 'dawn-v20260603.191052-windows-amd64.tar.gz'
|
Name = 'dawn_prebuilt'; File = 'dawn-v20260807.225922-windows-amd64.tar.gz'
|
||||||
Uris = @('https://github.com/encounter/dawn-build/releases/download/v20260603.191052/dawn-windows-amd64.tar.gz')
|
Uris = @('https://github.com/encounter/dawn/releases/download/v20260807.225922/dawn-windows-amd64.tar.gz')
|
||||||
Pins = @(@{ File = $auroraCMake; Text = 'set(AURORA_DAWN_VERSION "v20260603.191052"' },
|
Pins = @(@{ File = $auroraVersions; Text = '_aurora_dependency_version(AURORA_DAWN_VERSION "v20260807.225922"' },
|
||||||
@{ File = $auroraDawn; Text = 'SHA256=7785373d569b3b0237918ec9c523239f7d0667857c5ea8242e3cdfde95e6aeab' })
|
@{ File = $auroraDawn; Text = 'SHA256=42a7a295c87b2e8a36312e2f84a5823aac63325e8e1d271bc7ced6f83bfb075f' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'fmt'; File = 'fmt-11.1.4.tar.gz'
|
Name = 'fmt'; File = 'fmt-12.1.0.tar.gz'
|
||||||
Uris = @('https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz')
|
Uris = @('https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz')
|
||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'freetype'; File = 'freetype-2.14.3.tar.gz'
|
Name = 'freetype'; File = 'freetype-2.14.3.tar.gz'
|
||||||
@@ -58,12 +63,12 @@ $packages = @(
|
|||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'imgui'; File = 'imgui-1.91.9b-docking.tar.gz'
|
Name = 'imgui'; File = 'imgui-1.91.9b-docking.tar.gz'
|
||||||
Uris = @('https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz')
|
Uris = @('https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz')
|
||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz' })
|
Pins = @(@{ File = $auroraImgui; Text = 'https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'libusb'; File = 'libusb-1.0.30.tar.bz2'
|
Name = 'libusb'; File = 'libusb-1.0.30.tar.bz2'
|
||||||
Uris = @('https://github.com/libusb/libusb/releases/download/v1.0.30/libusb-1.0.30.tar.bz2')
|
Uris = @('https://github.com/libusb/libusb/releases/download/v1.0.30/libusb-1.0.30.tar.bz2')
|
||||||
Pins = @(@{ File = $auroraCMake; Text = 'set(AURORA_LIBUSB_VERSION "1.0.30"' },
|
Pins = @(@{ File = $auroraVersions; Text = '_aurora_dependency_version(AURORA_LIBUSB_VERSION "1.0.30"' },
|
||||||
@{ File = $auroraLibUsb; Text = 'releases/download/v${AURORA_LIBUSB_VERSION}/libusb-${AURORA_LIBUSB_VERSION}.tar.bz2' })
|
@{ File = $auroraLibUsb; Text = 'releases/download/v${AURORA_LIBUSB_VERSION}/libusb-${AURORA_LIBUSB_VERSION}.tar.bz2' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
@@ -77,19 +82,19 @@ $packages = @(
|
|||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://sqlite.org/2026/sqlite-amalgamation-3510300.zip' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://sqlite.org/2026/sqlite-amalgamation-3510300.zip' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'tracy'; File = 'tracy-a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz'
|
Name = 'tracy'; File = 'tracy-0.14.1.zip'
|
||||||
Uris = @('https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz')
|
Uris = @('https://github.com/wolfpld/tracy/archive/refs/tags/v0.14.1.zip')
|
||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/wolfpld/tracy/archive/refs/tags/v0.14.1.zip' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'xxhash'; File = 'xxHash-0.8.3.tar.gz'
|
Name = 'xxhash'; File = 'xxHash-0.8.4.tar.gz'
|
||||||
Uris = @('https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz')
|
Uris = @('https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.4.tar.gz')
|
||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.4.tar.gz' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'zlib'; File = 'zlib-1.3.2.tar.gz'
|
Name = 'zlib'; File = 'zlib-ng-2.3.3.tar.gz'
|
||||||
Uris = @('https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz')
|
Uris = @('https://github.com/zlib-ng/zlib-ng/archive/refs/tags/2.3.3.tar.gz')
|
||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/zlib-ng/zlib-ng/archive/refs/tags/2.3.3.tar.gz' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'zstd'; File = 'zstd-1.5.7.tar.gz'
|
Name = 'zstd'; File = 'zstd-1.5.7.tar.gz'
|
||||||
@@ -168,8 +173,12 @@ function Expand-Package([string]$Archive, [string]$Target) {
|
|||||||
foreach ($package in $packages) {
|
foreach ($package in $packages) {
|
||||||
Assert-Pinned $package
|
Assert-Pinned $package
|
||||||
$target = Join-Path $Destination $package.Name
|
$target = Join-Path $Destination $package.Name
|
||||||
if (Test-Path -LiteralPath $target -PathType Container) { continue }
|
# Records which archive a tree came from, so a pin change replaces a stale tree.
|
||||||
|
$stamp = $target + '.source'
|
||||||
|
$current = (Test-Path -LiteralPath $stamp -PathType Leaf) -and ([IO.File]::ReadAllText($stamp) -eq $package.File)
|
||||||
|
if ($current -and (Test-Path -LiteralPath $target -PathType Container)) { continue }
|
||||||
Expand-Package (Get-Archive $package) $target
|
Expand-Package (Get-Archive $package) $target
|
||||||
|
[IO.File]::WriteAllText($stamp, $package.File)
|
||||||
Write-Host "Prepared $($package.Name)"
|
Write-Host "Prepared $($package.Name)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Builds the redistributable precompiled aurora + third-party package: aurora (~43% of local build
|
# Builds the redistributable precompiled aurora + third-party package: aurora (~43% of local build
|
||||||
# CPU time) and vendored Crypto++ are identical for every user, so this configures runtime/ like
|
# CPU time) and vendored Crypto++ are identical for every user, so this configures runtime/ like
|
||||||
# LocalBuild.ps1 (both dot-source NativeBuildFlags.ps1), builds just that closure, and harvests the
|
# LocalBuild.ps1 (both dot-source NativeBuildFlags.ps1), builds just that closure plus the
|
||||||
# archives plus a generated CMake description into launcher/artifacts/dependencies/native_prebuilt.
|
# game-independent runtime archive, and harvests the archives and a generated CMake description
|
||||||
|
# into launcher/artifacts/dependencies/native_prebuilt.
|
||||||
[CmdletBinding(PositionalBinding = $false)]
|
[CmdletBinding(PositionalBinding = $false)]
|
||||||
param(
|
param(
|
||||||
[string]$PortableToolsDirectory = 'launcher/artifacts/portable-tools',
|
[string]$PortableToolsDirectory = 'launcher/artifacts/portable-tools',
|
||||||
@@ -87,24 +88,29 @@ try {
|
|||||||
-AdditionalArguments @("-DMKW_NATIVE_PREBUILT_EXPORT_DIR=$(ConvertTo-MkwCMakePath $exportDirectory)")
|
-AdditionalArguments @("-DMKW_NATIVE_PREBUILT_EXPORT_DIR=$(ConvertTo-MkwCMakePath $exportDirectory)")
|
||||||
Invoke-Checked $cmake $configure 'Configuring the aurora/third-party staging build' -LogPrefix 'MKWNP'
|
Invoke-Checked $cmake $configure 'Configuring the aurora/third-party staging build' -LogPrefix 'MKWNP'
|
||||||
|
|
||||||
|
$metaPath = Join-Path $exportDirectory 'meta.txt'
|
||||||
|
Assert-File $metaPath 'Native prebuilt export metadata'
|
||||||
|
$meta = @{}
|
||||||
|
foreach ($line in Get-Content -LiteralPath $metaPath) {
|
||||||
|
if ($line -match '^([a-z0-9_]+)=(.*)$') { $meta[$Matches[1]] = $Matches[2] }
|
||||||
|
}
|
||||||
|
# The game-independent runtime ships precompiled too; it is outside the probe closure.
|
||||||
|
if (-not $meta.ContainsKey('runtime_fingerprint')) {
|
||||||
|
throw 'The staging configure exported no runtime_fingerprint; the x86_64 package must carry the runtime archive.'
|
||||||
|
}
|
||||||
|
|
||||||
# mkw_np_probe links exactly what mkw_runtime_common and the public products
|
# mkw_np_probe links exactly what mkw_runtime_common and the public products
|
||||||
# link, so building it compiles the whole redistributable closure and nothing
|
# link, so building it compiles the whole redistributable closure and nothing
|
||||||
# else - and a successful link proves the harvested archives are complete.
|
# else - and a successful link proves the harvested archives are complete.
|
||||||
Invoke-Checked $cmake @('--build', $stage, '--target', 'mkw_np_probe', '--parallel', "$Parallel") `
|
Invoke-Checked $cmake @('--build', $stage, '--target', 'mkw_np_probe', '--target', 'mkw_runtime',
|
||||||
'Building the aurora/third-party closure' -LogPrefix 'MKWNP'
|
'--parallel', "$Parallel") 'Building the aurora/third-party closure and the runtime' -LogPrefix 'MKWNP'
|
||||||
} finally {
|
} finally {
|
||||||
$env:PATH = $oldPath
|
$env:PATH = $oldPath
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Read the description CMake wrote, plus the resolved command lines Ninja holds.
|
# Read the resolved command lines Ninja holds.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
$metaPath = Join-Path $exportDirectory 'meta.txt'
|
|
||||||
Assert-File $metaPath 'Native prebuilt export metadata'
|
|
||||||
$meta = @{}
|
|
||||||
foreach ($line in Get-Content -LiteralPath $metaPath) {
|
|
||||||
if ($line -match '^([a-z0-9_]+)=(.*)$') { $meta[$Matches[1]] = $Matches[2] }
|
|
||||||
}
|
|
||||||
|
|
||||||
$buildNinja = Join-Path $stage 'build.ninja'
|
$buildNinja = Join-Path $stage 'build.ninja'
|
||||||
Assert-File $buildNinja 'Generated build.ninja'
|
Assert-File $buildNinja 'Generated build.ninja'
|
||||||
@@ -234,6 +240,12 @@ foreach ($name in ($targets.Keys | Sort-Object)) {
|
|||||||
}
|
}
|
||||||
if ($sharedImports.Count -eq 0) { throw 'No shared libraries were harvested; the SDL/zlib/libpng DLLs would be missing.' }
|
if ($sharedImports.Count -eq 0) { throw 'No shared libraries were harvested; the SDL/zlib/libpng DLLs would be missing.' }
|
||||||
if ($dawnLinkerFile) { $linkerFileToReference[$dawnLinkerFile] = 'dawn::webgpu_dawn' }
|
if ($dawnLinkerFile) { $linkerFileToReference[$dawnLinkerFile] = 'dawn::webgpu_dawn' }
|
||||||
|
$runtimeTarget = $targets['mkw_runtime']
|
||||||
|
if ($null -eq $runtimeTarget -or $runtimeTarget.Type -ne 'STATIC_LIBRARY' -or
|
||||||
|
-not $linkerFileToReference.ContainsKey($runtimeTarget.LinkerFile)) {
|
||||||
|
throw 'The runtime archive (mkw_runtime) was not built and harvested.'
|
||||||
|
}
|
||||||
|
$runtimeArchiveReference = $linkerFileToReference[$runtimeTarget.LinkerFile]
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Rewrite the compile/link interface into workspace-relative tokens.
|
# Rewrite the compile/link interface into workspace-relative tokens.
|
||||||
@@ -297,6 +309,9 @@ $packageLinkItems = @($linkItems | ForEach-Object {
|
|||||||
if (StartsWithPath $absolute $dependencyRoot) { return "`@DEPS`@/$(RelativeTo $absolute $dependencyRoot)" }
|
if (StartsWithPath $absolute $dependencyRoot) { return "`@DEPS`@/$(RelativeTo $absolute $dependencyRoot)" }
|
||||||
throw "Link item is neither a system library nor a harvested archive: $item"
|
throw "Link item is neither a system library nor a harvested archive: $item"
|
||||||
})
|
})
|
||||||
|
if ($packageLinkItems -contains $runtimeArchiveReference) {
|
||||||
|
throw 'The runtime archive leaked into the aurora link interface.'
|
||||||
|
}
|
||||||
|
|
||||||
# fmt's `-include cstdlib` (and anything like it) is recorded from a C++ compile
|
# fmt's `-include cstdlib` (and anything like it) is recorded from a C++ compile
|
||||||
# line, so re-scope it to C++. The runtime also assembles generated .S blobs
|
# line, so re-scope it to C++. The runtime also assembles generated .S blobs
|
||||||
@@ -333,6 +348,11 @@ $generated.Add('')
|
|||||||
$generated.Add("set(MKW_NP_DAWN_CONFIG_DIR `"$(ConvertTo-Token $meta['dawn_config_dir'] 'dawn')`")")
|
$generated.Add("set(MKW_NP_DAWN_CONFIG_DIR `"$(ConvertTo-Token $meta['dawn_config_dir'] 'dawn')`")")
|
||||||
$generated.Add("set(MKW_NP_DAWN_VERSION `"$($meta['aurora_dawn_version'])`")")
|
$generated.Add("set(MKW_NP_DAWN_VERSION `"$($meta['aurora_dawn_version'])`")")
|
||||||
$generated.Add('')
|
$generated.Add('')
|
||||||
|
$generated.Add('# The precompiled game-independent runtime; RuntimeLibrary.cmake decides whether it applies.')
|
||||||
|
$generated.Add("set(MKW_NP_RUNTIME_ARCHIVE `"$runtimeArchiveReference`")")
|
||||||
|
$generated.Add("set(MKW_NP_RUNTIME_FINGERPRINT `"$($meta['runtime_fingerprint'])`")")
|
||||||
|
$generated.Add("set(MKW_NP_RUNTIME_CPU_PROFILE `"$($meta['runtime_cpu_profile'])`")")
|
||||||
|
$generated.Add('')
|
||||||
$generated.Add('# Shared third-party libraries keep imported SHARED targets so that')
|
$generated.Add('# Shared third-party libraries keep imported SHARED targets so that')
|
||||||
$generated.Add('# $<TARGET_RUNTIME_DLLS> still copies them next to the game executable.')
|
$generated.Add('# $<TARGET_RUNTIME_DLLS> still copies them next to the game executable.')
|
||||||
foreach ($import in $sharedImports) {
|
foreach ($import in $sharedImports) {
|
||||||
@@ -393,6 +413,9 @@ $provenance = [ordered]@{
|
|||||||
# release build and the local build can refuse a package aurora has outgrown.
|
# release build and the local build can refuse a package aurora has outgrown.
|
||||||
AuroraSourceFingerprint = $auroraSourceFingerprint
|
AuroraSourceFingerprint = $auroraSourceFingerprint
|
||||||
ThirdPartySourceFingerprint = $thirdPartySourceFingerprint
|
ThirdPartySourceFingerprint = $thirdPartySourceFingerprint
|
||||||
|
# runtime/cmake/RuntimeFingerprint.cmake over the runtime sources the archive was built from.
|
||||||
|
RuntimeSourceFingerprint = $meta['runtime_fingerprint']
|
||||||
|
RuntimeCpuProfile = $meta['runtime_cpu_profile']
|
||||||
Sdl3Target = $meta['aurora_sdl3_target']
|
Sdl3Target = $meta['aurora_sdl3_target']
|
||||||
HarvestedLibraryCount = @($linkerFileToReference.Keys).Count
|
HarvestedLibraryCount = @($linkerFileToReference.Keys).Count
|
||||||
Contents = $contents
|
Contents = $contents
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Builds the redistributable precompiled aurora + third-party package for native Linux: aurora
|
# Builds the redistributable precompiled aurora + third-party package for native Linux: aurora
|
||||||
# (~43% of local build CPU time per Prepare-NativePrebuilt.ps1) and vendored Crypto++ are identical
|
# (~43% of local build CPU time per Prepare-NativePrebuilt.ps1), Crypto++ and mbed TLS are identical
|
||||||
# for every user under the pinned toolchain prepare-portable-tools.sh bundles, so this configures
|
# for every user under the pinned toolchain prepare-portable-tools.sh bundles, so this configures
|
||||||
# runtime/ against that toolchain, builds just that closure, and harvests the archives plus a
|
# runtime/ against that toolchain, builds just that closure (plus, on x86_64, the game-independent
|
||||||
# generated CMake description into an output package - the Linux counterpart to
|
# runtime archive), and harvests the archives plus a generated CMake description into an output
|
||||||
|
# package - the Linux counterpart to
|
||||||
# Launcher/Prepare-NativePrebuilt.ps1, consumed by the same platform-agnostic
|
# Launcher/Prepare-NativePrebuilt.ps1, consumed by the same platform-agnostic
|
||||||
# runtime/cmake/NativePrebuilt.cmake either script's package works with unmodified.
|
# runtime/cmake/NativePrebuilt.cmake either script's package works with unmodified.
|
||||||
#
|
#
|
||||||
@@ -22,11 +23,17 @@ workspace=$(cd "$script_dir/.." && pwd)
|
|||||||
|
|
||||||
arch=""
|
arch=""
|
||||||
output_dir=""
|
output_dir=""
|
||||||
|
toolchain_dir=""
|
||||||
|
ninja_bin=""
|
||||||
|
cmake_bin=""
|
||||||
|
llvm_dir=""
|
||||||
|
sysroot=""
|
||||||
stage_dir="$workspace/build/native-prebuilt-stage"
|
stage_dir="$workspace/build/native-prebuilt-stage"
|
||||||
keep_stage=0
|
keep_stage=0
|
||||||
reuse_stage=0
|
reuse_stage=0
|
||||||
parallel=0
|
parallel=0
|
||||||
print_fingerprint_only=0
|
print_fingerprint_only=0
|
||||||
|
disconnected=0
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -39,8 +46,7 @@ Usage: Prepare-NativePrebuilt.sh --arch {x86_64|aarch64} [options]
|
|||||||
--reuse-stage Reuse an existing staging build directory (maintainer iteration aid: a
|
--reuse-stage Reuse an existing staging build directory (maintainer iteration aid: a
|
||||||
re-harvest does not recompile aurora from scratch)
|
re-harvest does not recompile aurora from scratch)
|
||||||
--parallel N Ninja build parallelism (default: nproc)
|
--parallel N Ninja build parallelism (default: nproc)
|
||||||
--print-fingerprint-only Print the four provenance inputs (compiler_sha256, flag_fingerprint,
|
--print-fingerprint-only Print the provenance inputs as "key=value" lines and
|
||||||
aurora_fingerprint, third_party_fingerprint) as "key=value" lines and
|
|
||||||
exit, without configuring/building/harvesting anything - lets a caller
|
exit, without configuring/building/harvesting anything - lets a caller
|
||||||
(build-appimage.sh) decide whether an existing package is still current
|
(build-appimage.sh) decide whether an existing package is still current
|
||||||
without paying for a full aurora rebuild just to find out.
|
without paying for a full aurora rebuild just to find out.
|
||||||
@@ -56,6 +62,12 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--reuse-stage) reuse_stage=1; shift ;;
|
--reuse-stage) reuse_stage=1; shift ;;
|
||||||
--parallel) parallel=$2; shift 2 ;;
|
--parallel) parallel=$2; shift 2 ;;
|
||||||
--print-fingerprint-only) print_fingerprint_only=1; shift ;;
|
--print-fingerprint-only) print_fingerprint_only=1; shift ;;
|
||||||
|
--toolchain-dir) toolchain_dir=$2; shift 2 ;;
|
||||||
|
--ninja-bin) ninja_bin=$2; shift 2 ;;
|
||||||
|
--cmake-bin) cmake_bin=$2; shift 2 ;;
|
||||||
|
--llvm-dir) llvm_dir=$2; shift 2 ;;
|
||||||
|
--sysroot) sysroot=$2; shift 2 ;;
|
||||||
|
--disconnected) disconnected=1; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) echo "Prepare-NativePrebuilt.sh: unknown argument: $1" >&2; exit 1 ;;
|
*) echo "Prepare-NativePrebuilt.sh: unknown argument: $1" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -72,14 +84,22 @@ assert_dir() { [[ -d "$1" ]] || fail "$2 is missing: $1"; }
|
|||||||
sha256_of() { sha256sum "$1" | awk '{print $1}'; }
|
sha256_of() { sha256sum "$1" | awk '{print $1}'; }
|
||||||
normalize() { readlink -f "$1"; }
|
normalize() { readlink -f "$1"; }
|
||||||
|
|
||||||
|
# Environment check: Ensure the `SOURCE_DATE_EPOCH` value is not invalid
|
||||||
|
# before trying to build anything (fail-fast)
|
||||||
|
if [[ -n "${SOURCE_DATE_EPOCH:-}" ]] && ! python3 -c \
|
||||||
|
'import datetime, os; datetime.datetime.fromtimestamp(int(os.environ.get("SOURCE_DATE_EPOCH")), datetime.timezone.utc)'
|
||||||
|
then
|
||||||
|
fail "SOURCE_DATE_EPOCH must be a valid UNIX timestamp"
|
||||||
|
fi
|
||||||
|
|
||||||
[[ -n "$output_dir" ]] || output_dir="$script_dir/artifacts/native-prebuilt-$arch"
|
[[ -n "$output_dir" ]] || output_dir="$script_dir/artifacts/native-prebuilt-$arch"
|
||||||
[[ "$stage_dir" = /* ]] || stage_dir="$workspace/$stage_dir"
|
[[ "$stage_dir" = /* ]] || stage_dir="$workspace/$stage_dir"
|
||||||
|
|
||||||
toolchain_dir="$script_dir/artifacts/portable-tools/toolchain-$arch"
|
toolchain_dir="${toolchain_dir:-$script_dir/artifacts/portable-tools/toolchain-$arch}"
|
||||||
cc="$toolchain_dir/bin/clang"
|
cc="${llvm_dir:-$toolchain_dir}/bin/clang"
|
||||||
cxx="$toolchain_dir/bin/clang++"
|
cxx="${llvm_dir:-$toolchain_dir}/bin/clang++"
|
||||||
cmake_bin="$toolchain_dir/bin/cmake"
|
cmake_bin="${cmake_bin:-$toolchain_dir/bin/cmake}"
|
||||||
ninja_bin="$toolchain_dir/bin/ninja"
|
ninja_bin="${ninja_bin:-$toolchain_dir/bin/ninja}"
|
||||||
runtime_source="$workspace/runtime"
|
runtime_source="$workspace/runtime"
|
||||||
aurora_source="$workspace/aurora-main"
|
aurora_source="$workspace/aurora-main"
|
||||||
|
|
||||||
@@ -88,7 +108,7 @@ assert_file "$ninja_bin" "Portable Ninja"
|
|||||||
assert_file "$cc" "Portable C compiler"
|
assert_file "$cc" "Portable C compiler"
|
||||||
assert_file "$cxx" "Portable C++ compiler"
|
assert_file "$cxx" "Portable C++ compiler"
|
||||||
assert_dir "$aurora_source" "aurora-main source tree"
|
assert_dir "$aurora_source" "aurora-main source tree"
|
||||||
clang_binary=$(normalize "$toolchain_dir/bin/clang-23")
|
clang_binary=$(normalize "${llvm_dir:-$toolchain_dir}/bin/clang-22")
|
||||||
assert_file "$clang_binary" "Portable clang driver binary"
|
assert_file "$clang_binary" "Portable clang driver binary"
|
||||||
|
|
||||||
(( parallel > 0 )) || parallel=$(nproc)
|
(( parallel > 0 )) || parallel=$(nproc)
|
||||||
@@ -98,7 +118,7 @@ fingerprint_tree() {
|
|||||||
local root=$1; shift
|
local root=$1; shift
|
||||||
root=$(normalize "$root")
|
root=$(normalize "$root")
|
||||||
[[ -d "$root" ]] || return 0
|
[[ -d "$root" ]] || return 0
|
||||||
local find_args=("$root")
|
local find_args=("$root" -name .git -prune -o)
|
||||||
local ex
|
local ex
|
||||||
for ex in "$@"; do find_args+=(-path "$root/$ex" -prune -o); done
|
for ex in "$@"; do find_args+=(-path "$root/$ex" -prune -o); done
|
||||||
find_args+=(-type f -print)
|
find_args+=(-type f -print)
|
||||||
@@ -131,6 +151,8 @@ fixed_configure_flags=(
|
|||||||
-DCMAKE_DISABLE_FIND_PACKAGE_absl=ON
|
-DCMAKE_DISABLE_FIND_PACKAGE_absl=ON
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_PNG=ON
|
-DCMAKE_DISABLE_FIND_PACKAGE_PNG=ON
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_Freetype=ON
|
-DCMAKE_DISABLE_FIND_PACKAGE_Freetype=ON
|
||||||
|
-DUSE_STATIC_MBEDTLS_LIBRARY=ON
|
||||||
|
-DUSE_SHARED_MBEDTLS_LIBRARY=OFF
|
||||||
# Freetype's own vendored CMakeLists.txt separately probes for system BZip2 (optional
|
# Freetype's own vendored CMakeLists.txt separately probes for system BZip2 (optional
|
||||||
# bzip2-compressed-font support aurora-main never asked for) regardless of the Freetype
|
# bzip2-compressed-font support aurora-main never asked for) regardless of the Freetype
|
||||||
# find_package disable above, since that only stops aurora's own outer find_package(Freetype)
|
# find_package disable above, since that only stops aurora's own outer find_package(Freetype)
|
||||||
@@ -141,6 +163,16 @@ fixed_configure_flags=(
|
|||||||
-DFT_DISABLE_BZIP2=ON
|
-DFT_DISABLE_BZIP2=ON
|
||||||
-DCMAKE_POLICY_DEFAULT_CMP0168=NEW
|
-DCMAKE_POLICY_DEFAULT_CMP0168=NEW
|
||||||
)
|
)
|
||||||
|
if [[ "$disconnected" -eq 1 ]]; then
|
||||||
|
fixed_configure_flags+=(
|
||||||
|
-DFETCHCONTENT_FULLY_DISCONNECTED=ON
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
if [[ -n "$sysroot" ]]; then
|
||||||
|
fixed_configure_flags+=(
|
||||||
|
-DCMAKE_SYSROOT="$sysroot"
|
||||||
|
)
|
||||||
|
fi
|
||||||
flag_fingerprint=$(printf '%s\n' "${fixed_configure_flags[@]}" | sha256sum | awk '{print $1}')
|
flag_fingerprint=$(printf '%s\n' "${fixed_configure_flags[@]}" | sha256sum | awk '{print $1}')
|
||||||
|
|
||||||
# extern/ is excluded because the payload ships that tree separately (aurora-main/extern is bundled
|
# extern/ is excluded because the payload ships that tree separately (aurora-main/extern is bundled
|
||||||
@@ -148,19 +180,35 @@ flag_fingerprint=$(printf '%s\n' "${fixed_configure_flags[@]}" | sha256sum | awk
|
|||||||
aurora_fingerprint=$(fingerprint_tree "$aurora_source" extern build)
|
aurora_fingerprint=$(fingerprint_tree "$aurora_source" extern build)
|
||||||
[[ -n "$aurora_fingerprint" ]] || fail "The aurora source tree could not be fingerprinted: $aurora_source"
|
[[ -n "$aurora_fingerprint" ]] || fail "The aurora source tree could not be fingerprinted: $aurora_source"
|
||||||
|
|
||||||
# The harvested Crypto++ archive is consumed against this tree's headers, so it is fingerprinted
|
# The harvested Crypto++ archive is consumed against this tree's headers, so it is fingerprinted.
|
||||||
# for the same reason as aurora above. No exclusions: unlike aurora's extern/, nothing under
|
|
||||||
# runtime/third_party is shipped separately.
|
|
||||||
third_party_fingerprint=$(fingerprint_tree "$runtime_source/third_party")
|
third_party_fingerprint=$(fingerprint_tree "$runtime_source/third_party")
|
||||||
[[ -n "$third_party_fingerprint" ]] || fail "The vendored third-party tree could not be fingerprinted: $runtime_source/third_party"
|
[[ -n "$third_party_fingerprint" ]] || fail "The vendored third-party tree could not be fingerprinted: $runtime_source/third_party"
|
||||||
|
mbedtls_fingerprint=$(sha256_of "$runtime_source/cmake/MbedTLSPin.cmake")
|
||||||
|
|
||||||
compiler_sha256=$(sha256_of "$clang_binary")
|
compiler_sha256=$(sha256_of "$cc")
|
||||||
|
cxx_sha256=$(sha256_of "$cxx")
|
||||||
|
# Append the CXX SHA256 if it doesn't match the hash of CC
|
||||||
|
if [[ "${cxx_sha256}" != "${compiler_sha256}" ]]; then
|
||||||
|
compiler_sha256="${compiler_sha256}:${cxx_sha256}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The game-independent runtime ships precompiled on x86_64 only; aarch64 compiles it locally.
|
||||||
|
runtime_fingerprint=""
|
||||||
|
if [[ "$arch" == x86_64 ]]; then
|
||||||
|
runtime_fingerprint=$("$cmake_bin" -DMKW_RUNTIME_DIR="$runtime_source" \
|
||||||
|
-P "$runtime_source/cmake/RuntimeFingerprint.cmake" | sed -n 's/^runtime_fingerprint=\([0-9a-f]\{64\}\)$/\1/p')
|
||||||
|
[[ -n "$runtime_fingerprint" ]] || fail "RuntimeFingerprint.cmake printed no runtime fingerprint"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$print_fingerprint_only" -eq 1 ]]; then
|
if [[ "$print_fingerprint_only" -eq 1 ]]; then
|
||||||
printf 'compiler_sha256=%s\n' "$compiler_sha256"
|
printf 'compiler_sha256=%s\n' "$compiler_sha256"
|
||||||
printf 'flag_fingerprint=%s\n' "$flag_fingerprint"
|
printf 'flag_fingerprint=%s\n' "$flag_fingerprint"
|
||||||
printf 'aurora_fingerprint=%s\n' "$aurora_fingerprint"
|
printf 'aurora_fingerprint=%s\n' "$aurora_fingerprint"
|
||||||
printf 'third_party_fingerprint=%s\n' "$third_party_fingerprint"
|
printf 'third_party_fingerprint=%s\n' "$third_party_fingerprint"
|
||||||
|
printf 'mbedtls_fingerprint=%s\n' "$mbedtls_fingerprint"
|
||||||
|
if [[ -n "$runtime_fingerprint" ]]; then
|
||||||
|
printf 'runtime_fingerprint=%s\n' "$runtime_fingerprint"
|
||||||
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -184,20 +232,30 @@ echo "Prepare-NativePrebuilt.sh: configuring the aurora/third-party staging buil
|
|||||||
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \
|
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \
|
||||||
-DMKW_NATIVE_PREBUILT_EXPORT_DIR="$export_dir"
|
-DMKW_NATIVE_PREBUILT_EXPORT_DIR="$export_dir"
|
||||||
|
|
||||||
# mkw_np_probe links exactly what mkw_runtime_common and the public products link, so building it
|
|
||||||
# compiles the whole redistributable closure and nothing else - and a successful link proves the
|
|
||||||
# harvested archives are complete.
|
|
||||||
echo "Prepare-NativePrebuilt.sh: building the aurora/third-party closure..."
|
|
||||||
"$cmake_bin" --build "$stage_dir" --target mkw_np_probe --parallel "$parallel"
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Read the description CMake wrote, plus the resolved command lines Ninja holds.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
meta_path="$export_dir/meta.txt"
|
meta_path="$export_dir/meta.txt"
|
||||||
assert_file "$meta_path" "Native prebuilt export metadata"
|
assert_file "$meta_path" "Native prebuilt export metadata"
|
||||||
get_meta() { awk -F= -v k="$1" '$0 ~ "^" k "=" { sub(/^[^=]*=/, ""); print; exit }' "$meta_path"; }
|
get_meta() { awk -F= -v k="$1" '$0 ~ "^" k "=" { sub(/^[^=]*=/, ""); print; exit }' "$meta_path"; }
|
||||||
|
|
||||||
|
# mkw_np_probe links exactly what mkw_runtime_common and the public products link, so building it
|
||||||
|
# compiles the whole redistributable closure and nothing else - and a successful link proves the
|
||||||
|
# harvested archives are complete. The runtime archive is outside that closure and built alongside.
|
||||||
|
build_targets=(--target mkw_np_probe)
|
||||||
|
meta_runtime_fingerprint=$(get_meta runtime_fingerprint)
|
||||||
|
if [[ -n "$meta_runtime_fingerprint" ]]; then
|
||||||
|
if [[ -n "$runtime_fingerprint" && "$meta_runtime_fingerprint" != "$runtime_fingerprint" ]]; then
|
||||||
|
fail "The configure's runtime fingerprint ($meta_runtime_fingerprint) differs from RuntimeFingerprint.cmake's ($runtime_fingerprint)"
|
||||||
|
fi
|
||||||
|
build_targets+=(--target mkw_runtime)
|
||||||
|
elif [[ -n "$runtime_fingerprint" ]]; then
|
||||||
|
fail "The staging configure exported no runtime_fingerprint; the $arch package must carry the runtime archive"
|
||||||
|
fi
|
||||||
|
echo "Prepare-NativePrebuilt.sh: building the aurora/third-party closure..."
|
||||||
|
"$cmake_bin" --build "$stage_dir" "${build_targets[@]}" --parallel "$parallel"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Read the resolved command lines Ninja holds.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
build_ninja="$stage_dir/build.ninja"
|
build_ninja="$stage_dir/build.ninja"
|
||||||
assert_file "$build_ninja" "Generated build.ninja"
|
assert_file "$build_ninja" "Generated build.ninja"
|
||||||
|
|
||||||
@@ -361,6 +419,28 @@ while IFS='|' read -r name type file linkerfile; do
|
|||||||
linker_file_to_reference["$linkerfile"]="@PKG@/$relative_linker"
|
linker_file_to_reference["$linkerfile"]="@PKG@/$relative_linker"
|
||||||
fi
|
fi
|
||||||
done < "$targets_txt"
|
done < "$targets_txt"
|
||||||
|
runtime_archive_ref=""
|
||||||
|
if [[ -n "$meta_runtime_fingerprint" ]]; then
|
||||||
|
runtime_linker=$(awk -F'|' '$1 == "mkw_runtime" && $2 == "STATIC_LIBRARY" { print $4; exit }' "$targets_txt")
|
||||||
|
[[ -n "$runtime_linker" && -f "$runtime_linker" ]] || fail "The runtime archive (mkw_runtime) was not built"
|
||||||
|
runtime_archive_ref=${linker_file_to_reference["$(normalize "$runtime_linker")"]:-}
|
||||||
|
[[ "$runtime_archive_ref" == "@PKG@/lib/"* ]] || fail "The runtime archive (mkw_runtime) was not harvested"
|
||||||
|
fi
|
||||||
|
mbedtls_refs=()
|
||||||
|
mbedtls_txt="$export_dir/mbedtls.txt"
|
||||||
|
assert_file "$mbedtls_txt" "Mbed TLS export targets list"
|
||||||
|
while IFS='|' read -r name linkerfile; do
|
||||||
|
[[ -n "$name" ]] || continue
|
||||||
|
linkerfile=$(normalize "$linkerfile")
|
||||||
|
ref=${linker_file_to_reference["$linkerfile"]:-}
|
||||||
|
[[ -n "$ref" ]] || fail "Mbed TLS archive was not harvested: $name ($linkerfile)"
|
||||||
|
mbedtls_refs+=("$ref")
|
||||||
|
done < "$mbedtls_txt"
|
||||||
|
[[ ${#mbedtls_refs[@]} -ge 3 ]] || fail "Expected at least three Mbed TLS archives, got ${#mbedtls_refs[@]}"
|
||||||
|
mbedtls_source_dir=$(get_meta mbedtls_source_dir)
|
||||||
|
assert_dir "$mbedtls_source_dir/include/mbedtls" "Mbed TLS headers"
|
||||||
|
mkdir -p "$output_dir/include/mbedtls"
|
||||||
|
cp -a "$mbedtls_source_dir/include/." "$output_dir/include/mbedtls/"
|
||||||
# Unlike Windows (SDL/zlib/libpng ship as DLLs by default), everything here was forced static above
|
# Unlike Windows (SDL/zlib/libpng ship as DLLs by default), everything here was forced static above
|
||||||
# and Dawn's own Linux package (verified directly) ships libwebgpu_dawn.a, also static - so zero
|
# and Dawn's own Linux package (verified directly) ships libwebgpu_dawn.a, also static - so zero
|
||||||
# shared imports is the expected, normal outcome, not a failure.
|
# shared imports is the expected, normal outcome, not a failure.
|
||||||
@@ -426,6 +506,9 @@ for item in "${link_items[@]}"; do
|
|||||||
if [[ "$item" = /* ]]; then item_abs=$(normalize "$item"); else item_abs=$(normalize "$stage_dir/$item"); fi
|
if [[ "$item" = /* ]]; then item_abs=$(normalize "$item"); else item_abs=$(normalize "$stage_dir/$item"); fi
|
||||||
ref=${linker_file_to_reference["$item_abs"]:-}
|
ref=${linker_file_to_reference["$item_abs"]:-}
|
||||||
if [[ -n "$ref" ]]; then
|
if [[ -n "$ref" ]]; then
|
||||||
|
for mbedtls_ref in "${mbedtls_refs[@]}"; do
|
||||||
|
[[ "$ref" == "$mbedtls_ref" ]] && continue 2
|
||||||
|
done
|
||||||
package_link_items+=("$ref")
|
package_link_items+=("$ref")
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -444,6 +527,11 @@ for item in "${link_items[@]}"; do
|
|||||||
esac
|
esac
|
||||||
fail "Link item is neither a system library nor a harvested archive: $item"
|
fail "Link item is neither a system library nor a harvested archive: $item"
|
||||||
done
|
done
|
||||||
|
if [[ -n "$runtime_archive_ref" ]]; then
|
||||||
|
for item in "${package_link_items[@]}"; do
|
||||||
|
[[ "$item" != "$runtime_archive_ref" ]] || fail "The runtime archive leaked into the aurora link interface"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Emit the consumer-facing CMake description.
|
# Emit the consumer-facing CMake description.
|
||||||
@@ -505,6 +593,15 @@ generated_cmake="$output_dir/native_prebuilt.cmake"
|
|||||||
format_cmake_block MKW_NP_COMPILE_DEFINITIONS "${package_definitions[@]}"
|
format_cmake_block MKW_NP_COMPILE_DEFINITIONS "${package_definitions[@]}"
|
||||||
format_cmake_block MKW_NP_COMPILE_OPTIONS "${package_compile_options[@]}"
|
format_cmake_block MKW_NP_COMPILE_OPTIONS "${package_compile_options[@]}"
|
||||||
format_cmake_block MKW_NP_LINK_LIBRARIES "${package_link_items[@]}"
|
format_cmake_block MKW_NP_LINK_LIBRARIES "${package_link_items[@]}"
|
||||||
|
format_cmake_block MKW_NP_MBEDTLS_LIBRARIES "${mbedtls_refs[@]}"
|
||||||
|
echo 'set(MKW_NP_MBEDTLS_INCLUDE_DIR "@PKG@/include/mbedtls")'
|
||||||
|
printf 'set(MKW_NP_MBEDTLS_FINGERPRINT "%s")\n' "$mbedtls_fingerprint"
|
||||||
|
if [[ -n "$runtime_archive_ref" ]]; then
|
||||||
|
# The precompiled game-independent runtime; RuntimeLibrary.cmake decides whether it applies.
|
||||||
|
printf 'set(MKW_NP_RUNTIME_ARCHIVE "%s")\n' "$runtime_archive_ref"
|
||||||
|
printf 'set(MKW_NP_RUNTIME_FINGERPRINT "%s")\n' "$meta_runtime_fingerprint"
|
||||||
|
printf 'set(MKW_NP_RUNTIME_CPU_PROFILE "%s")\n' "$(get_meta runtime_cpu_profile)"
|
||||||
|
fi
|
||||||
format_cmake_block MKW_NP_AURORA_TARGETS "aurora::gx" "aurora::pad" "aurora::si" "aurora::vi" "aurora::mtx"
|
format_cmake_block MKW_NP_AURORA_TARGETS "aurora::gx" "aurora::pad" "aurora::si" "aurora::vi" "aurora::mtx"
|
||||||
echo ""
|
echo ""
|
||||||
printf 'set(MKW_NP_DAWN_CONFIG_DIR "%s")\n' "$dawn_config_token"
|
printf 'set(MKW_NP_DAWN_CONFIG_DIR "%s")\n' "$dawn_config_token"
|
||||||
@@ -540,12 +637,13 @@ harvested_count=${#linker_file_to_reference[@]}
|
|||||||
|
|
||||||
python3 - "$output_dir" "$compiler_sha256" "$compiler_version" "$flag_fingerprint" \
|
python3 - "$output_dir" "$compiler_sha256" "$compiler_version" "$flag_fingerprint" \
|
||||||
"$dawn_version" "$dawn_runtime_sha256" "$aurora_fingerprint" "$third_party_fingerprint" \
|
"$dawn_version" "$dawn_runtime_sha256" "$aurora_fingerprint" "$third_party_fingerprint" \
|
||||||
"$sdl3_target" "$harvested_count" <<'PY'
|
"$sdl3_target" "$harvested_count" "$mbedtls_fingerprint" \
|
||||||
|
"${runtime_archive_ref:+$meta_runtime_fingerprint}" "${runtime_archive_ref:+$(get_meta runtime_cpu_profile)}" <<'PY'
|
||||||
import hashlib, json, os, sys, datetime
|
import hashlib, json, os, sys, datetime
|
||||||
|
|
||||||
(output_dir, compiler_sha256, compiler_version, flag_fingerprint, dawn_version,
|
(output_dir, compiler_sha256, compiler_version, flag_fingerprint, dawn_version,
|
||||||
dawn_runtime_sha256, aurora_fingerprint, third_party_fingerprint, sdl3_target,
|
dawn_runtime_sha256, aurora_fingerprint, third_party_fingerprint, sdl3_target,
|
||||||
harvested_count) = sys.argv[1:]
|
harvested_count, mbedtls_fingerprint, runtime_fingerprint, runtime_cpu_profile) = sys.argv[1:]
|
||||||
|
|
||||||
contents = []
|
contents = []
|
||||||
for root, dirs, files in os.walk(output_dir):
|
for root, dirs, files in os.walk(output_dir):
|
||||||
@@ -560,9 +658,21 @@ for root, dirs, files in os.walk(output_dir):
|
|||||||
contents.append({"Path": rel, "Bytes": os.path.getsize(path), "Sha256": digest})
|
contents.append({"Path": rel, "Bytes": os.path.getsize(path), "Sha256": digest})
|
||||||
contents.sort(key=lambda c: c["Path"])
|
contents.sort(key=lambda c: c["Path"])
|
||||||
|
|
||||||
|
source_date_epoch = os.environ.get("SOURCE_DATE_EPOCH") or None
|
||||||
|
utc = datetime.timezone.utc
|
||||||
|
if source_date_epoch is not None:
|
||||||
|
built_utc = datetime.datetime.fromtimestamp(
|
||||||
|
int(source_date_epoch),
|
||||||
|
utc
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
built_utc = datetime.datetime.now(utc)
|
||||||
|
|
||||||
|
built_utc = built_utc.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
||||||
|
|
||||||
provenance = {
|
provenance = {
|
||||||
"SchemaVersion": 1,
|
"SchemaVersion": 1,
|
||||||
"BuiltUtc": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
|
"BuiltUtc": built_utc,
|
||||||
"CompilerSha256": compiler_sha256,
|
"CompilerSha256": compiler_sha256,
|
||||||
"CompilerVersion": compiler_version,
|
"CompilerVersion": compiler_version,
|
||||||
"FlagFingerprint": flag_fingerprint,
|
"FlagFingerprint": flag_fingerprint,
|
||||||
@@ -570,10 +680,14 @@ provenance = {
|
|||||||
"DawnRuntimeSha256": dawn_runtime_sha256,
|
"DawnRuntimeSha256": dawn_runtime_sha256,
|
||||||
"AuroraSourceFingerprint": aurora_fingerprint,
|
"AuroraSourceFingerprint": aurora_fingerprint,
|
||||||
"ThirdPartySourceFingerprint": third_party_fingerprint,
|
"ThirdPartySourceFingerprint": third_party_fingerprint,
|
||||||
|
"MbedTlsFingerprint": mbedtls_fingerprint,
|
||||||
"Sdl3Target": sdl3_target,
|
"Sdl3Target": sdl3_target,
|
||||||
"HarvestedLibraryCount": int(harvested_count),
|
"HarvestedLibraryCount": int(harvested_count),
|
||||||
"Contents": contents,
|
"Contents": contents,
|
||||||
}
|
}
|
||||||
|
if runtime_fingerprint:
|
||||||
|
provenance["RuntimeSourceFingerprint"] = runtime_fingerprint
|
||||||
|
provenance["RuntimeCpuProfile"] = runtime_cpu_profile
|
||||||
with open(os.path.join(output_dir, "provenance.json"), "w", encoding="utf-8") as fh:
|
with open(os.path.join(output_dir, "provenance.json"), "w", encoding="utf-8") as fh:
|
||||||
json.dump(provenance, fh, indent=2)
|
json.dump(provenance, fh, indent=2)
|
||||||
fh.write("\n")
|
fh.write("\n")
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Fails the release build when a fact duplicated across the repo stops agreeing with the copy
|
# Fails the release build when a fact duplicated across the repo stops agreeing with the copy
|
||||||
# that owns it (recomp.yml). Scripts read pinned facts through Get-MkwProjectPins, but three
|
# that owns it (each projects/*/recomp.yml). Scripts read pinned facts through Get-MkwProjectPins
|
||||||
# consumers can't read YAML (the C++ runtime header, the C# constants, hand-written lists on
|
# or build-cache.sh's mkw_project_pin, but some consumers can't read YAML (the C++ runtime header,
|
||||||
# both sides of the C#/PowerShell boundary), so those are checked here instead.
|
# the C# Retro-WFC table, and hand-written lists on both sides of the C#/PowerShell boundary), so
|
||||||
|
# those are checked here instead.
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param([string]$RepositoryRoot)
|
param([string]$RepositoryRoot)
|
||||||
|
|
||||||
@@ -46,26 +47,36 @@ function Compare-Set([string[]]$Expected, [string[]]$Actual, [string]$ExpectedNa
|
|||||||
if ($extra.Count -gt 0) { Add-Failure "$ActualName has entries $ExpectedName does not: $($extra -join ', ')." }
|
if ($extra.Count -gt 0) { Add-Failure "$ActualName has entries $ExpectedName does not: $($extra -join ', ')." }
|
||||||
}
|
}
|
||||||
|
|
||||||
$pins = Get-MkwProjectPins (Join-Path $repoRoot 'projects\mkwii\recomp.yml')
|
# Every supported release is one projects/<name>/recomp.yml, the same set Build-Installer.ps1 ships.
|
||||||
|
$releases = @(Get-ChildItem -LiteralPath (Join-Path $repoRoot 'projects') -Directory | Sort-Object Name |
|
||||||
|
ForEach-Object { Join-Path $_.FullName 'recomp.yml' } | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } |
|
||||||
|
ForEach-Object { $pins = Get-MkwProjectPins $_; $pins.Project = $_; [pscustomobject]$pins })
|
||||||
|
if ($releases.Count -eq 0) { throw 'No projects/*/recomp.yml was found; update Test-PinnedFacts.ps1.' }
|
||||||
|
|
||||||
# --- The Retro-WFC endpoint: recomp.yml owns it; the installer host pins the same string so a
|
# --- The Retro-WFC endpoints: each recomp.yml owns its release's; the host derives the same URL
|
||||||
# --- redirected or rewritten endpoint cannot be fetched from. The literal lives in
|
# --- from its game table so a redirected or rewritten endpoint cannot be fetched from.
|
||||||
# --- WiiCompiled.Setup.Common (shared with WiiCompiled.Setup.Linux) - InputValidation.cs only
|
|
||||||
# --- re-exports it as `= RetroWfcPayload.CurrentRetroWfcPayloadUri;`, no literal to capture there.
|
|
||||||
$retroWfcPayload = Read-SourceFile (Join-Path $common 'RetroWfcPayload.cs') 'RetroWfcPayload.cs'
|
$retroWfcPayload = Read-SourceFile (Join-Path $common 'RetroWfcPayload.cs') 'RetroWfcPayload.cs'
|
||||||
$hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"([^"]+)"' `
|
$payloadGames = @{}
|
||||||
'The host Retro-WFC endpoint constant'
|
foreach ($entry in [regex]::Matches((Get-CapturedValue $retroWfcPayload `
|
||||||
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
|
'(?s)PayloadGames\s*=\s*new\([^)]*\)\s*\{(.*?)\};' 'The host Retro-WFC game table'),
|
||||||
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
'\["([^"]+)"\]\s*=\s*"([^"]+)"')) {
|
||||||
|
$payloadGames[$entry.Groups[1].Value] = $entry.Groups[2].Value
|
||||||
}
|
}
|
||||||
|
$uriTemplate = Get-CapturedValue $retroWfcPayload 'PayloadUri\(string gameId\)\s*=>\s*\$"([^"]+)"' `
|
||||||
# --- The game identity: the manifest carries it, but the host also compiles a fallback for a
|
'The host Retro-WFC endpoint template'
|
||||||
# --- manifest that predates the field, and that fallback decides which disc is accepted.
|
Compare-Set ([string[]]@($releases.GameId)) ([string[]]@($payloadGames.Keys)) `
|
||||||
$models = Read-SourceFile (Join-Path $setup 'Models.cs') 'Models.cs'
|
'projects/*/recomp.yml' 'RetroWfcPayload.PayloadGames'
|
||||||
$hostGameId = Get-CapturedValue $models 'ExpectedGameId\s*\{\s*get;\s*set;\s*\}\s*=\s*"([^"]+)"' `
|
foreach ($release in $releases) {
|
||||||
'The host expected game id'
|
if (-not $payloadGames.ContainsKey($release.GameId)) { continue }
|
||||||
if ($hostGameId -cne $pins.GameId) {
|
$hostUri = $uriTemplate.Replace('{PayloadGame(gameId)}', $payloadGames[$release.GameId])
|
||||||
Add-Failure "PayloadManifest.ExpectedGameId defaults to '$hostGameId' but recomp.yml pins '$($pins.GameId)'."
|
if ($hostUri -cne $release.RetroWfcPayloadUri) {
|
||||||
|
Add-Failure "RetroWfcPayload.PayloadUri($($release.GameId)) is '$hostUri' but $($release.Project) pins '$($release.RetroWfcPayloadUri)'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($key in @('GameId', 'DolSha256')) {
|
||||||
|
foreach ($duplicate in @($releases | Group-Object $key | Where-Object Count -gt 1)) {
|
||||||
|
Add-Failure "Several projects pin the same $($key) $($duplicate.Name)."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- The translation entry point: the translator is told it by LocalBuild.ps1 (which reads
|
# --- The translation entry point: the translator is told it by LocalBuild.ps1 (which reads
|
||||||
@@ -73,8 +84,10 @@ if ($hostGameId -cne $pins.GameId) {
|
|||||||
$systemBridge = Read-SourceFile (Join-Path $repoRoot 'runtime\include\system_bridge.h') 'system_bridge.h'
|
$systemBridge = Read-SourceFile (Join-Path $repoRoot 'runtime\include\system_bridge.h') 'system_bridge.h'
|
||||||
$runtimeEntry = (Get-CapturedValue $systemBridge 'kDefaultEntryAddress\s*=\s*(0[xX][0-9a-fA-F]+)' `
|
$runtimeEntry = (Get-CapturedValue $systemBridge 'kDefaultEntryAddress\s*=\s*(0[xX][0-9a-fA-F]+)' `
|
||||||
'The runtime default entry address').ToLowerInvariant()
|
'The runtime default entry address').ToLowerInvariant()
|
||||||
if ($runtimeEntry -ne $pins.EntryPoint) {
|
foreach ($release in $releases) {
|
||||||
Add-Failure "system_bridge.h enters at $runtimeEntry but recomp.yml translates from $($pins.EntryPoint)."
|
if ($runtimeEntry -ne $release.EntryPoint) {
|
||||||
|
Add-Failure "system_bridge.h enters at $runtimeEntry but $($release.Project) translates from $($release.EntryPoint)."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- The pinned dependency set: Build-Installer.ps1 stages it, the installed host requires it.
|
# --- The pinned dependency set: Build-Installer.ps1 stages it, the installed host requires it.
|
||||||
@@ -121,7 +134,7 @@ foreach ($mandatory in ([regex]::Matches($parameterBlock,
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- The build steps the installer's progress bar acts on.
|
# --- The build steps the installer's progress bar acts on.
|
||||||
$installProgress = Read-SourceFile (Join-Path $setup 'InstallProgress.cs') 'InstallProgress.cs'
|
$installProgress = Read-SourceFile (Join-Path $common 'InstallProgress.cs') 'InstallProgress.cs'
|
||||||
$knownSteps = Get-QuotedList $installProgress '(?s)class BuildStepIds\s*\{(.*?)\n\}' `
|
$knownSteps = Get-QuotedList $installProgress '(?s)class BuildStepIds\s*\{(.*?)\n\}' `
|
||||||
'const string \w+ = "([^"]+)";' 'BuildStepIds'
|
'const string \w+ = "([^"]+)";' 'BuildStepIds'
|
||||||
$emittedSteps = @([regex]::Matches($localBuild, "(?:-StepId|Write-MkwBuildStep)\s+'([^']+)'") |
|
$emittedSteps = @([regex]::Matches($localBuild, "(?:-StepId|Write-MkwBuildStep)\s+'([^']+)'") |
|
||||||
@@ -131,6 +144,6 @@ Compare-Set $knownSteps $emittedSteps 'BuildStepIds' 'the steps LocalBuild.ps1 e
|
|||||||
if ($failures.Count -gt 0) {
|
if ($failures.Count -gt 0) {
|
||||||
throw "Pinned facts disagree between their copies:`n $($failures -join "`n ")"
|
throw "Pinned facts disagree between their copies:`n $($failures -join "`n ")"
|
||||||
}
|
}
|
||||||
Write-Host ("Pinned-fact audit passed: game {0}, entry {1}, endpoint {2}; dependency, runtime-asset, " -f
|
Write-Host ("Pinned-fact audit passed: games {0}, entry {1}; Retro-WFC endpoint, dependency, runtime-asset, " -f
|
||||||
$pins.GameId, $pins.EntryPoint, $pins.RetroWfcPayloadUri) -NoNewline
|
($releases.GameId -join ', '), $runtimeEntry) -NoNewline
|
||||||
Write-Host 'build-step, and build-script parameter contracts all agree.'
|
Write-Host 'build-step, and build-script parameter contracts all agree.'
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# Build the real Windows runtime with translated, entirely synthetic PowerPC code.
|
||||||
|
# No game dump, game symbol map, REL, mod download, or existing generated/ output is used.
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$PortableToolsDirectory = 'Launcher/artifacts/portable-tools',
|
||||||
|
[string]$DependencySourceDirectory = 'Launcher/artifacts/dependencies',
|
||||||
|
[string]$StageDirectory = 'build/recomp-test',
|
||||||
|
[ValidateRange(1, 64)] [int]$Parallel = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
Set-StrictMode -Version 3.0
|
||||||
|
. (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1')
|
||||||
|
|
||||||
|
$repoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||||
|
function Full([string]$Path) {
|
||||||
|
if ([IO.Path]::IsPathRooted($Path)) { return [IO.Path]::GetFullPath($Path) }
|
||||||
|
return [IO.Path]::GetFullPath((Join-Path $repoRoot $Path))
|
||||||
|
}
|
||||||
|
$portableTools = Full $PortableToolsDirectory
|
||||||
|
$dependencies = Full $DependencySourceDirectory
|
||||||
|
$stage = Full $StageDirectory
|
||||||
|
$dotnet = (Get-Command dotnet -CommandType Application).Source
|
||||||
|
$cmake = Join-Path $portableTools 'CMake/bin/cmake.exe'
|
||||||
|
$ninja = Join-Path $portableTools 'Ninja/ninja.exe'
|
||||||
|
$compilerBin = Join-Path $portableTools 'llvm-mingw/bin'
|
||||||
|
Assert-File $cmake 'Pinned CMake (run Prepare-PortableTools.ps1 first)'
|
||||||
|
Assert-File $ninja 'Pinned Ninja'
|
||||||
|
Assert-File (Join-Path $dependencies 'cppwinrt/winrt/base.h') 'Pinned dependencies (run Prepare-Dependencies.ps1 first)'
|
||||||
|
|
||||||
|
# Refuse reuse so a developer's game translation or an earlier build cannot make
|
||||||
|
# the test pass. Keep the staging tree after the run for diagnostics.
|
||||||
|
if (Test-Path -LiteralPath $stage) { throw "Test stage already exists; choose a fresh -StageDirectory: $stage" }
|
||||||
|
[IO.Directory]::CreateDirectory($stage) | Out-Null
|
||||||
|
Write-Host "Synthetic recompilation workspace: $stage"
|
||||||
|
|
||||||
|
# Copy current sources, including uncommitted edits, but no ignored build output.
|
||||||
|
# An isolated workspace preserves the developer's real generated/ directory.
|
||||||
|
$auroraRoot = Join-Path $repoRoot 'aurora-main'
|
||||||
|
if (-not (Test-Path -LiteralPath (Join-Path $auroraRoot '.git'))) {
|
||||||
|
throw 'Aurora submodule is not initialized. Run git submodule update --init --recursive first.'
|
||||||
|
}
|
||||||
|
# The parent tracks Aurora as one gitlink; enumerate its repository separately.
|
||||||
|
# Retain untracked source files so local development edits are still tested.
|
||||||
|
$sourceFiles = @(& git -C $repoRoot -c core.quotepath=false ls-files --cached --others --exclude-standard -- runtime)
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'Could not enumerate runtime sources.' }
|
||||||
|
$auroraFiles = @(& git -C $auroraRoot -c core.quotepath=false ls-files --cached --others --exclude-standard)
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'Could not enumerate Aurora sources.' }
|
||||||
|
$sourceFiles += @($auroraFiles | ForEach-Object { "aurora-main/$_" })
|
||||||
|
foreach ($relative in $sourceFiles | Sort-Object -Unique) {
|
||||||
|
$destination = Join-Path $stage $relative
|
||||||
|
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination)) | Out-Null
|
||||||
|
Copy-Item -LiteralPath (Join-Path $repoRoot $relative) -Destination $destination
|
||||||
|
}
|
||||||
|
|
||||||
|
# One synthetic text section: li r3,40; addi r3,r3,2; nop; blr.
|
||||||
|
# Guest functions the runtime calls directly resolve through the registry at run time, so the
|
||||||
|
# product links without any game code.
|
||||||
|
[uint32]$entry = 0x80001000L
|
||||||
|
$textSize = 0x10
|
||||||
|
$dataOffset = 0x100 + $textSize
|
||||||
|
$dol = [byte[]]::new($dataOffset + 4)
|
||||||
|
function Write-BigEndian32([int]$Offset, [uint32]$Value) {
|
||||||
|
$dol[$Offset] = [byte](($Value -shr 24) -band 255)
|
||||||
|
$dol[$Offset + 1] = [byte](($Value -shr 16) -band 255)
|
||||||
|
$dol[$Offset + 2] = [byte](($Value -shr 8) -band 255)
|
||||||
|
$dol[$Offset + 3] = [byte]($Value -band 255)
|
||||||
|
}
|
||||||
|
Write-BigEndian32 0x00 0x100 # text[0] file offset
|
||||||
|
Write-BigEndian32 0x48 $entry # text[0] guest address
|
||||||
|
Write-BigEndian32 0x90 $textSize # text[0] length
|
||||||
|
Write-BigEndian32 0x1C $dataOffset # data[0] file offset
|
||||||
|
Write-BigEndian32 0x64 0x80600000L # data[0] guest address
|
||||||
|
Write-BigEndian32 0xAC 4 # data[0] length
|
||||||
|
Write-BigEndian32 0xD8 0x80601000L # BSS address
|
||||||
|
Write-BigEndian32 0xDC 32 # BSS length
|
||||||
|
Write-BigEndian32 0xE0 $entry # entry point
|
||||||
|
Write-BigEndian32 0x100 0x38600028 # li r3,40
|
||||||
|
Write-BigEndian32 0x104 0x38630002 # addi r3,r3,2
|
||||||
|
Write-BigEndian32 0x108 0x60000000 # nop
|
||||||
|
Write-BigEndian32 0x10C 0x4E800020 # blr
|
||||||
|
Write-BigEndian32 $dataOffset 0x12345678
|
||||||
|
[IO.File]::WriteAllBytes((Join-Path $stage 'synthetic.dol'), $dol)
|
||||||
|
$entryPoints = '0x{0:X8}' -f $entry
|
||||||
|
$functionMap = '{0:X8} func_{0:X8}' -f $entry
|
||||||
|
[IO.File]::WriteAllText((Join-Path $stage 'synthetic-functions.txt'), $functionMap)
|
||||||
|
$manifest = Join-Path $stage 'recomp.yml'
|
||||||
|
[IO.File]::WriteAllText($manifest, @"
|
||||||
|
schema_version: 1
|
||||||
|
workspace_root: .
|
||||||
|
project:
|
||||||
|
id: ci-synthetic-dol
|
||||||
|
display_name: CI Synthetic DOL
|
||||||
|
memory:
|
||||||
|
base: 0x80000000
|
||||||
|
size: 0x01800000
|
||||||
|
sda_base: 0x80600000
|
||||||
|
sda2_base: 0x80600000
|
||||||
|
inputs:
|
||||||
|
dol:
|
||||||
|
path: synthetic.dol
|
||||||
|
translation:
|
||||||
|
entry_points: [$entryPoints]
|
||||||
|
function_map:
|
||||||
|
path: synthetic-functions.txt
|
||||||
|
allow_unsupported_instructions: false
|
||||||
|
runtime:
|
||||||
|
native_abi_directories: []
|
||||||
|
native_registration_root: runtime/src
|
||||||
|
output:
|
||||||
|
root: generated
|
||||||
|
"@)
|
||||||
|
|
||||||
|
$translatorProject = Join-Path $repoRoot 'translator/src/Translator.Cli/Translator.Cli.csproj'
|
||||||
|
Invoke-Checked $dotnet @('build', $translatorProject, '-c', 'Release', '--disable-build-servers') 'Building the translator'
|
||||||
|
$translator = Join-Path $repoRoot 'translator/src/Translator.Cli/bin/Release/net8.0/Translator.Cli.dll'
|
||||||
|
$metadata = Join-Path $stage 'generated/base_translation_output.json'
|
||||||
|
Invoke-Checked $dotnet @($translator, 'translate-recursive', '0x80001000', '--project', $manifest,
|
||||||
|
'--output-metadata', $metadata, '--threads', "$Parallel") `
|
||||||
|
'Translating the synthetic DOL'
|
||||||
|
# Function-map seeds can be skipped by discovery; do not accept a partial fixture.
|
||||||
|
$translated = Get-Content -LiteralPath $metadata -Raw | ConvertFrom-Json
|
||||||
|
foreach ($address in @($entry)) {
|
||||||
|
if ($address -notin $translated.functions.entryPoint) {
|
||||||
|
throw ('Synthetic function 0x{0:X8} was not translated.' -f $address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Invoke-Checked $dotnet @($translator, 'generate-data-init', '--project', $manifest) 'Generating synthetic data and runtime configuration'
|
||||||
|
Invoke-Checked $dotnet @($translator, 'emit-build-shards', '--project', $manifest) 'Emitting the production build graph'
|
||||||
|
|
||||||
|
$nativeBuild = Join-Path $stage 'native-build'
|
||||||
|
$oldPath = $env:PATH
|
||||||
|
try {
|
||||||
|
$env:PATH = Get-MkwToolchainPath $portableTools
|
||||||
|
$configure = Get-MkwNativeConfigureArguments -SourceDirectory (Join-Path $stage 'runtime') -BuildDirectory $nativeBuild `
|
||||||
|
-Ninja $ninja -CCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-clang.exe') `
|
||||||
|
-CxxCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-clang++.exe') `
|
||||||
|
-ResourceCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-windres.exe') `
|
||||||
|
-DependenciesDirectory $dependencies -AdditionalArguments @('-DMKW_BUILD_PRODUCTS=ON')
|
||||||
|
Invoke-Checked $cmake $configure 'Configuring the production Windows runtime' `
|
||||||
|
-WaitForProcessTree $false
|
||||||
|
Invoke-Checked $cmake @('--build', $nativeBuild, '--target', 'WiiCompiled', '--parallel', "$Parallel") `
|
||||||
|
'Compiling and linking the synthetic product with the full runtime' `
|
||||||
|
-WaitForProcessTree $false
|
||||||
|
Assert-File (Join-Path $nativeBuild 'WiiCompiled.exe') 'Linked synthetic product'
|
||||||
|
} finally {
|
||||||
|
$env:PATH = $oldPath
|
||||||
|
}
|
||||||
|
Write-Host 'Synthetic recompilation passed (translation, data generation, runtime compilation, and product link).'
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
<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.22</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>
|
||||||
|
|||||||
+39
-39
@@ -1,35 +1,19 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace WiiCompiled.Setup.Windows;
|
namespace WiiCompiled.Setup.Common;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stable stage identifiers reported by <c>--progress-json</c>. These are part of the public
|
|
||||||
/// WheelWizard contract (docs/WHEELWIZARD_CONTRACT.md): the strings may gain new members, but an
|
|
||||||
/// existing identifier never changes meaning.
|
|
||||||
/// </summary>
|
|
||||||
internal static class InstallStages
|
|
||||||
{
|
|
||||||
public const string Validate = "validate";
|
|
||||||
public const string ExtractToolkit = "extract-toolkit";
|
|
||||||
public const string ExtractDisc = "extract-disc";
|
|
||||||
public const string PrepareRetro = "prepare-retro";
|
|
||||||
public const string BuildBase = "build-base";
|
|
||||||
public const string BuildRetro = "build-retro";
|
|
||||||
public const string Publish = "publish";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where an installation reports what it is doing. Progress is coarse and monotonic; raw translator
|
/// Where an installation reports what it is doing. Progress is coarse and monotonic; raw translator
|
||||||
/// and compiler output is a diagnostic, never progress, because it is unbounded and machine-hostile.
|
/// and compiler output is a diagnostic, never progress, because it is unbounded and machine-hostile.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IInstallReporter
|
public interface IInstallReporter
|
||||||
{
|
{
|
||||||
void Progress(string stage, string message, int percent);
|
void Progress(string stage, string message, int percent);
|
||||||
void Diagnostic(string line);
|
void Diagnostic(string line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Reports to a caller-supplied sink; used by console and maintenance commands.</summary>
|
/// <summary>Reports to a caller-supplied sink; used by console and maintenance commands.</summary>
|
||||||
internal sealed class DelegatingInstallReporter : IInstallReporter
|
public sealed class DelegatingInstallReporter : IInstallReporter
|
||||||
{
|
{
|
||||||
private readonly Action<string> _sink;
|
private readonly Action<string> _sink;
|
||||||
|
|
||||||
@@ -43,15 +27,20 @@ internal sealed class DelegatingInstallReporter : IInstallReporter
|
|||||||
/// The <c>--progress-json</c> protocol: one JSON object per line on stdout, nothing else on stdout,
|
/// The <c>--progress-json</c> protocol: one JSON object per line on stdout, nothing else on stdout,
|
||||||
/// diagnostics on stderr. The terminal <c>result</c> line is written exactly once.
|
/// diagnostics on stderr. The terminal <c>result</c> line is written exactly once.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class NdjsonInstallReporter : IInstallReporter
|
public sealed class NdjsonInstallReporter : IInstallReporter
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions Options = new() { WriteIndented = false };
|
private static readonly JsonSerializerOptions Options = new() { WriteIndented = false };
|
||||||
|
private readonly string _version;
|
||||||
private readonly Action<string>? _log;
|
private readonly Action<string>? _log;
|
||||||
private readonly object _gate = new();
|
private readonly object _gate = new();
|
||||||
private int _lastPercent;
|
private int _lastPercent;
|
||||||
private bool _finished;
|
private bool _finished;
|
||||||
|
|
||||||
public NdjsonInstallReporter(Action<string>? log = null) => _log = log;
|
public NdjsonInstallReporter(string version, Action<string>? log = null)
|
||||||
|
{
|
||||||
|
_version = version;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
public void Progress(string stage, string message, int percent)
|
public void Progress(string stage, string message, int percent)
|
||||||
{
|
{
|
||||||
@@ -78,7 +67,7 @@ internal sealed class NdjsonInstallReporter : IInstallReporter
|
|||||||
{
|
{
|
||||||
if (_finished) return;
|
if (_finished) return;
|
||||||
_finished = true;
|
_finished = true;
|
||||||
WriteLine(new { type = "result", success = true, version = ProductInfo.Version, installDir = installDirectory });
|
WriteLine(new { type = "result", success = true, version = _version, installDir = installDirectory });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +95,8 @@ internal sealed class NdjsonInstallReporter : IInstallReporter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Plain-text console reporting for a silent install without <c>--progress-json</c>.</summary>
|
/// <summary>Plain-text console reporting for a run without <c>--progress-json</c>.</summary>
|
||||||
internal sealed class ConsoleInstallReporter : IInstallReporter
|
public sealed class ConsoleInstallReporter : IInstallReporter
|
||||||
{
|
{
|
||||||
private readonly Action<string>? _log;
|
private readonly Action<string>? _log;
|
||||||
|
|
||||||
@@ -126,10 +115,10 @@ internal sealed class ConsoleInstallReporter : IInstallReporter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Build step identifiers from the bundled script's <c>MKWCBUILD:STEP:<id></c> lines. The
|
/// <summary>Build step identifiers from the build scripts' <c>MKWCBUILD:STEP:<id></c> lines. The
|
||||||
/// id is the contract; LocalBuild.ps1 emits these exactly and Test-PinnedFacts.ps1 fails the release if
|
/// id is the contract; LocalBuild.ps1 emits these exactly and Test-PinnedFacts.ps1 fails the release if
|
||||||
/// the two sets disagree, so progress never depends on matching reworded English prose.</summary>
|
/// the two sets disagree, so progress never depends on matching reworded English prose.</summary>
|
||||||
internal static class BuildStepIds
|
public static class BuildStepIds
|
||||||
{
|
{
|
||||||
public const string ReuseBaseTranslation = "reuse-base-translation";
|
public const string ReuseBaseTranslation = "reuse-base-translation";
|
||||||
public const string RetranslateBase = "retranslate-base";
|
public const string RetranslateBase = "retranslate-base";
|
||||||
@@ -143,16 +132,16 @@ internal static class BuildStepIds
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps one local translate-and-compile run onto a slice of the overall percentage. The bundled
|
/// Maps one local translate-and-compile run onto a slice of the overall percentage. The build
|
||||||
/// build script announces every step it starts with an <c>MKWCBUILD:</c> prefix, so the slice can
|
/// scripts announce every step they start with an <c>MKWCBUILD:</c> prefix, so the slice can advance
|
||||||
/// advance on real events instead of on a timer.
|
/// on real events instead of on a timer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class BuildProgressWindow
|
public sealed class BuildProgressWindow
|
||||||
{
|
{
|
||||||
private const string Marker = "MKWCBUILD:";
|
private const string Marker = "MKWCBUILD:";
|
||||||
private const string StepMarker = "STEP:";
|
private const string StepMarker = "STEP:";
|
||||||
|
|
||||||
/// <summary>The fraction the compile step reaches; beyond it, compiler output is a heartbeat.</summary>
|
/// <summary>The fraction the compile step reaches; beyond it, Ninja's counter drives progress.</summary>
|
||||||
private const double CompileFraction = 0.58;
|
private const double CompileFraction = 0.58;
|
||||||
|
|
||||||
private static readonly (string Id, double Fraction, string Message)[] Steps =
|
private static readonly (string Id, double Fraction, string Message)[] Steps =
|
||||||
@@ -164,7 +153,7 @@ internal sealed class BuildProgressWindow
|
|||||||
(BuildStepIds.TranslateMod, 0.38, "Translating the Retro Rewind Code.pul"),
|
(BuildStepIds.TranslateMod, 0.38, "Translating the Retro Rewind Code.pul"),
|
||||||
(BuildStepIds.GenerateDataInit, 0.44, "Generating game data initialization"),
|
(BuildStepIds.GenerateDataInit, 0.44, "Generating game data initialization"),
|
||||||
(BuildStepIds.EmitBuildShards, 0.48, "Preparing the native build"),
|
(BuildStepIds.EmitBuildShards, 0.48, "Preparing the native build"),
|
||||||
(BuildStepIds.ConfigureNative, 0.52, "Configuring the bundled compiler"),
|
(BuildStepIds.ConfigureNative, 0.52, "Configuring the compiler"),
|
||||||
(BuildStepIds.Compile, CompileFraction, "Compiling the game. This is the longest step"),
|
(BuildStepIds.Compile, CompileFraction, "Compiling the game. This is the longest step"),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -209,19 +198,30 @@ internal sealed class BuildProgressWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anything else - an unknown step identifier from a newer script, a plain MKWCBUILD note, or
|
// Anything else - an unknown step identifier, a plain MKWCBUILD note, or raw tool output -
|
||||||
// raw tool output - stays a diagnostic and only feeds the heartbeat below.
|
// stays a diagnostic.
|
||||||
_reporter.Diagnostic(line);
|
_reporter.Diagnostic(line);
|
||||||
// Compilation announces itself once and then emits thousands of compiler lines. Treat that
|
// Ninja prefixes every edge with [done/total]; how many edges there are depends on how much
|
||||||
// output as a heartbeat so the slice keeps creeping forward, but only publish a progress
|
// of the build is already up to date, so follow the counter rather than counting lines.
|
||||||
// line when the rounded percentage actually changes.
|
if (_fraction >= CompileFraction && TryParseNinjaProgress(line, out var done, out var total))
|
||||||
if (_fraction >= CompileFraction)
|
|
||||||
{
|
{
|
||||||
_fraction = Math.Min(0.97, _fraction + 0.0015);
|
_fraction = Math.Max(_fraction, CompileFraction + (0.97 - CompileFraction) * done / total);
|
||||||
Emit();
|
Emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryParseNinjaProgress(string line, out int done, out int total)
|
||||||
|
{
|
||||||
|
done = total = 0;
|
||||||
|
if (!line.StartsWith('[')) return false;
|
||||||
|
var close = line.IndexOf(']');
|
||||||
|
var slash = line.IndexOf('/');
|
||||||
|
return close > 0 && slash > 0 && slash < close
|
||||||
|
&& int.TryParse(line.AsSpan(1, slash - 1), out done)
|
||||||
|
&& int.TryParse(line.AsSpan(slash + 1, close - slash - 1), out total)
|
||||||
|
&& total > 0 && done <= total;
|
||||||
|
}
|
||||||
|
|
||||||
private void Emit()
|
private void Emit()
|
||||||
{
|
{
|
||||||
var percent = Interpolate(_fraction);
|
var percent = Interpolate(_fraction);
|
||||||
@@ -11,22 +11,39 @@ namespace WiiCompiled.Setup.Common;
|
|||||||
public sealed record RetroWfcPayloadSnapshot(string Directory, string Sha256, long ByteLength);
|
public sealed record RetroWfcPayloadSnapshot(string Directory, string Sha256, long ByteLength);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downloads and verifies the Retro-WFC payload (a small, RSA-signed blob served from a single
|
/// Downloads and verifies the Retro-WFC payload (a small, RSA-signed blob served from one fixed
|
||||||
/// fixed endpoint). Shared by both installers - moved here from WiiCompiled.Setup.Windows's
|
/// endpoint per game release). Shared by both installers and the translator.
|
||||||
/// InputValidation.cs, which keeps every one of these method names as thin forwarding wrappers so
|
|
||||||
/// its many existing call sites (ProductRepairService.cs, LocalBuildService.cs, Installation.cs,
|
|
||||||
/// SelfTests.cs) needed no changes.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class RetroWfcPayload
|
public static class RetroWfcPayload
|
||||||
{
|
{
|
||||||
private const long MaximumRetroWfcPayloadBytes = 16L * 1024 * 1024;
|
private const long MaximumRetroWfcPayloadBytes = 16L * 1024 * 1024;
|
||||||
// The payload is tens of kilobytes from a single fixed endpoint 30s is good.
|
// The payload is tens of kilobytes from a fixed regional endpoint; allow 30 seconds.
|
||||||
private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30);
|
private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30);
|
||||||
private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1);
|
private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
public const string CurrentRetroWfcPayloadUri = "http://nas.play.rwfc.net/payload?g=RMCPD00";
|
/// <summary>
|
||||||
private static readonly string RetroWfcOfflinePayloadFile =
|
/// Base game id to Retro-WFC payload game. Test-PinnedFacts.ps1 checks this table against the
|
||||||
Path.Combine("binary", "payload.RMCPD00.bin");
|
/// retro_wfc_payload every projects/*/recomp.yml pins.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<string, string> PayloadGames = new(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["RMCP01"] = "RMCPD00",
|
||||||
|
["RMCE01"] = "RMCED00",
|
||||||
|
["RMCJ01"] = "RMCJD00",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IReadOnlyCollection<string> SupportedGameIds => PayloadGames.Keys;
|
||||||
|
|
||||||
|
public static string PayloadGame(string gameId) =>
|
||||||
|
PayloadGames.TryGetValue(gameId, out var payloadGame)
|
||||||
|
? payloadGame
|
||||||
|
: throw new InvalidDataException($"Unsupported Retro-WFC base game '{gameId}'.");
|
||||||
|
|
||||||
|
public static string PayloadRelativePath(string gameId) =>
|
||||||
|
Path.Combine("binary", $"payload.{PayloadGame(gameId)}.bin");
|
||||||
|
|
||||||
|
public static string PayloadUri(string gameId) =>
|
||||||
|
$"https://rwfc.net/api/wfc/payload?g={PayloadGame(gameId)}";
|
||||||
|
|
||||||
/// <summary>Retro-WFC production payload signing key (PROD PayloadPublicKey). Verifies
|
/// <summary>Retro-WFC production payload signing key (PROD PayloadPublicKey). Verifies
|
||||||
/// the payload past the 0x110 header; on-console stage1 pins the same key, so
|
/// the payload past the 0x110 header; on-console stage1 pins the same key, so
|
||||||
@@ -45,47 +62,48 @@ public static class RetroWfcPayload
|
|||||||
private const int RetroWfcPayloadSignatureOffset = 0x10;
|
private const int RetroWfcPayloadSignatureOffset = 0x10;
|
||||||
private const int RetroWfcPayloadMinimumBytes = 0x130;
|
private const int RetroWfcPayloadMinimumBytes = 0x130;
|
||||||
|
|
||||||
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory,
|
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory, string gameId,
|
||||||
RSAParameters? signingKey = null)
|
RSAParameters? signingKey = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(stagedDirectory))
|
if (string.IsNullOrWhiteSpace(stagedDirectory))
|
||||||
throw new InvalidDataException("The staged Retro-WFC payload directory is missing.");
|
throw new InvalidDataException("The staged Retro-WFC payload directory is missing.");
|
||||||
|
|
||||||
var root = Path.GetFullPath(stagedDirectory);
|
var root = Path.GetFullPath(stagedDirectory);
|
||||||
var payload = Path.Combine(root, RetroWfcOfflinePayloadFile);
|
var relativePath = PayloadRelativePath(gameId);
|
||||||
|
var payload = Path.Combine(root, relativePath);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if ((File.GetAttributes(payload) & FileAttributes.Directory) != 0)
|
if ((File.GetAttributes(payload) & FileAttributes.Directory) != 0)
|
||||||
throw new InvalidDataException(
|
throw new InvalidDataException(
|
||||||
"The staged Retro-WFC payload directory does not contain binary\\payload.RMCPD00.bin.");
|
$"The staged Retro-WFC payload directory does not contain {relativePath}.");
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException(
|
throw new InvalidDataException(
|
||||||
"The staged Retro-WFC payload directory does not contain binary\\payload.RMCPD00.bin.");
|
$"The staged Retro-WFC payload directory does not contain {relativePath}.");
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException(
|
throw new InvalidDataException(
|
||||||
"The staged Retro-WFC payload directory does not contain binary\\payload.RMCPD00.bin.");
|
$"The staged Retro-WFC payload directory does not contain {relativePath}.");
|
||||||
}
|
}
|
||||||
ValidateRetroWfcPayloadFile(payload, signingKey);
|
ValidateRetroWfcPayloadFile(payload, PayloadGame(gameId), signingKey);
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ResolveRetroWfcPayloadFile(string stagedDirectory,
|
public static string ResolveRetroWfcPayloadFile(string stagedDirectory, string gameId,
|
||||||
RSAParameters? signingKey = null) =>
|
RSAParameters? signingKey = null) =>
|
||||||
Path.Combine(ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, signingKey),
|
Path.Combine(ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, gameId, signingKey),
|
||||||
RetroWfcOfflinePayloadFile);
|
PayloadRelativePath(gameId));
|
||||||
|
|
||||||
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory,
|
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory, string gameId,
|
||||||
RSAParameters? signingKey = null) =>
|
RSAParameters? signingKey = null) =>
|
||||||
Sha256File(ResolveRetroWfcPayloadFile(stagedDirectory, signingKey));
|
Sha256File(ResolveRetroWfcPayloadFile(stagedDirectory, gameId, signingKey));
|
||||||
|
|
||||||
public static void ValidateRetroWfcPayloadUri(string uriText)
|
public static void ValidateRetroWfcPayloadUri(string uriText)
|
||||||
{
|
{
|
||||||
if (!string.Equals(uriText, CurrentRetroWfcPayloadUri, StringComparison.Ordinal))
|
if (!PayloadGames.Keys.Any(gameId => string.Equals(uriText, PayloadUri(gameId), StringComparison.Ordinal)))
|
||||||
throw new InvalidDataException("The installer does not define the fixed Retro-WFC payload endpoint.");
|
throw new InvalidDataException("The installer does not define this fixed Retro-WFC payload endpoint.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<RetroWfcPayloadSnapshot> DownloadRetroWfcPayloadAsync(string uriText,
|
public static async Task<RetroWfcPayloadSnapshot> DownloadRetroWfcPayloadAsync(string uriText,
|
||||||
@@ -96,7 +114,8 @@ public static class RetroWfcPayload
|
|||||||
var uri = new Uri(uriText, UriKind.Absolute);
|
var uri = new Uri(uriText, UriKind.Absolute);
|
||||||
|
|
||||||
var root = Path.GetFullPath(destinationDirectory);
|
var root = Path.GetFullPath(destinationDirectory);
|
||||||
var destination = Path.Combine(root, RetroWfcOfflinePayloadFile);
|
var payloadGame = uri.Query[3..]; // ValidateRetroWfcPayloadUri accepts only the exact ?g= form.
|
||||||
|
var destination = Path.Combine(root, "binary", $"payload.{payloadGame}.bin");
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
|
||||||
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
|
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
|
||||||
using var client = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
|
using var client = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
|
||||||
@@ -154,7 +173,7 @@ public static class RetroWfcPayload
|
|||||||
}
|
}
|
||||||
actualSha256 = Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
|
actualSha256 = Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
|
||||||
}
|
}
|
||||||
ValidateRetroWfcPayloadFile(temporary);
|
ValidateRetroWfcPayloadFile(temporary, uri.Query[3..]);
|
||||||
File.Move(temporary, destination, overwrite: true);
|
File.Move(temporary, destination, overwrite: true);
|
||||||
return new RetroWfcPayloadSnapshot(root, actualSha256, total);
|
return new RetroWfcPayloadSnapshot(root, actualSha256, total);
|
||||||
}
|
}
|
||||||
@@ -181,7 +200,8 @@ public static class RetroWfcPayload
|
|||||||
request.StatusCode is { } status && (int)status >= 500;
|
request.StatusCode is { } status && (int)status >= 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateRetroWfcPayloadFile(string payload, RSAParameters? signingKey = null)
|
private static void ValidateRetroWfcPayloadFile(string payload, string expectedGame,
|
||||||
|
RSAParameters? signingKey = null)
|
||||||
{
|
{
|
||||||
byte[] image;
|
byte[] image;
|
||||||
using (var stream = File.OpenRead(payload))
|
using (var stream = File.OpenRead(payload))
|
||||||
@@ -201,6 +221,10 @@ public static class RetroWfcPayload
|
|||||||
throw new InvalidDataException(
|
throw new InvalidDataException(
|
||||||
$"The Retro-WFC payload declares {declaredSize} bytes but contains {image.Length}.");
|
$"The Retro-WFC payload declares {declaredSize} bytes but contains {image.Length}.");
|
||||||
|
|
||||||
|
if (image.Length < 0x144 ||
|
||||||
|
System.Text.Encoding.ASCII.GetString(image, 0x138, 12).TrimEnd('\0') != expectedGame)
|
||||||
|
throw new InvalidDataException($"The Retro-WFC payload is not for {expectedGame}.");
|
||||||
|
|
||||||
using var rsa = RSA.Create();
|
using var rsa = RSA.Create();
|
||||||
rsa.ImportParameters(signingKey ?? new RSAParameters
|
rsa.ImportParameters(signingKey ?? new RSAParameters
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Tomlyn;
|
||||||
|
using Tomlyn.Parsing;
|
||||||
|
using Tomlyn.Syntax;
|
||||||
|
|
||||||
namespace WiiCompiled.Setup.Common;
|
namespace WiiCompiled.Setup.Common;
|
||||||
|
|
||||||
@@ -17,12 +20,6 @@ public static class RuntimeConfiguration
|
|||||||
{
|
{
|
||||||
public const string ConfigFileName = "Config.toml";
|
public const string ConfigFileName = "Config.toml";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ordinal, matching the runtime parser/writer (<c>runtime_config.h</c>) exactly. Case-insensitive
|
|
||||||
/// matching here would let setup rewrite a <c>[Paths]</c>/<c>Dvd_Root</c> spelling the runtime ignores.
|
|
||||||
/// </summary>
|
|
||||||
private const StringComparison KeyComparison = StringComparison.Ordinal;
|
|
||||||
|
|
||||||
/// <summary>The per-user configuration an ordinary (non-portable) installation shares.</summary>
|
/// <summary>The per-user configuration an ordinary (non-portable) installation shares.</summary>
|
||||||
public static string ApplicationDataConfigPath => Path.Combine(
|
public static string ApplicationDataConfigPath => Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
@@ -75,9 +72,11 @@ public static class RuntimeConfiguration
|
|||||||
public static void RemoveDvdRootIfOwned(string configPath, string dvdRoot) =>
|
public static void RemoveDvdRootIfOwned(string configPath, string dvdRoot) =>
|
||||||
RemovePathIfOwned(configPath, "dvd_root", dvdRoot);
|
RemovePathIfOwned(configPath, "dvd_root", dvdRoot);
|
||||||
|
|
||||||
/// <summary>The raw stored text of a <c>[paths]</c> key, exactly as the file holds it.</summary>
|
/// <summary>The stored string of a <c>[paths]</c> key, exactly as the file holds it.</summary>
|
||||||
public static string? GetPath(string configPath, string key) =>
|
public static string? GetPath(string configPath, string key) =>
|
||||||
TryUnquoteToml(GetRawValue(configPath, "paths", key) ?? "", out var value) ? value : null;
|
TryLoad(configPath) is { } document && Find(document, ["paths"], key)?.Item.Value is StringValueSyntax value
|
||||||
|
? value.Value!
|
||||||
|
: null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <c>[paths]</c> value as the runtime will actually use it. The runtime resolves every relative
|
/// A <c>[paths]</c> value as the runtime will actually use it. The runtime resolves every relative
|
||||||
@@ -99,9 +98,19 @@ public static class RuntimeConfiguration
|
|||||||
|
|
||||||
private static void SetPath(string configPath, string key, string value)
|
private static void SetPath(string configPath, string key, string value)
|
||||||
{
|
{
|
||||||
var lines = ReadLinesOrDefault(configPath);
|
var document = Parse(File.Exists(configPath) ? File.ReadAllText(configPath) : DefaultConfig);
|
||||||
SetSectionValue(lines, "paths", key, QuoteToml(FormatPathValue(configPath, value)));
|
var section = new[] { "paths" };
|
||||||
WriteLines(configPath, lines);
|
var newValue = new StringValueSyntax(FormatPathValue(configPath, value));
|
||||||
|
if (Find(document, section, key) is { } existing)
|
||||||
|
{
|
||||||
|
LastToken(newValue).TrailingTrivia = LastToken(existing.Item.Value!).TrailingTrivia;
|
||||||
|
existing.Item.Value = newValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Append(document, section, new KeyValueSyntax(key, newValue));
|
||||||
|
}
|
||||||
|
Save(configPath, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -123,176 +132,104 @@ public static class RuntimeConfiguration
|
|||||||
|
|
||||||
private static void RemovePathIfOwned(string configPath, string key, string expectedValue)
|
private static void RemovePathIfOwned(string configPath, string key, string expectedValue)
|
||||||
{
|
{
|
||||||
if (!File.Exists(configPath)) return;
|
if (TryLoad(configPath) is not { } document ||
|
||||||
var lines = File.ReadAllLines(configPath).ToList();
|
Find(document, ["paths"], key) is not { } entry ||
|
||||||
var inPaths = false;
|
entry.Item.Value is not StringValueSyntax configured)
|
||||||
var changed = false;
|
return;
|
||||||
for (var index = 0; index < lines.Count; index++)
|
// A portable installation stores this relative, so ownership is decided on the path the
|
||||||
{
|
// runtime would actually resolve, not on the stored text.
|
||||||
var trimmed = RemoveComment(lines[index]).Trim();
|
if (!ResolveAgainstConfig(configPath, configured.Value!)
|
||||||
if (IsSection(trimmed))
|
.Equals(Path.GetFullPath(expectedValue), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return;
|
||||||
inPaths = trimmed.Equals("[paths]", KeyComparison);
|
var previous = entry.Previous?.EndOfLineToken ?? entry.Table?.EndOfLineToken;
|
||||||
continue;
|
if (previous is not null && entry.Item.EndOfLineToken?.TrailingTrivia is { } trivia)
|
||||||
}
|
(previous.TrailingTrivia ??= []).AddRange(trivia);
|
||||||
if (!inPaths || trimmed.Length == 0) continue;
|
entry.Items.RemoveChild(entry.Item);
|
||||||
var equals = trimmed.IndexOf('=');
|
Save(configPath, document);
|
||||||
if (equals < 0 || !trimmed[..equals].Trim().Equals(key, KeyComparison))
|
|
||||||
continue;
|
|
||||||
if (!TryUnquoteToml(trimmed[(equals + 1)..].Trim(), out var configured)) return;
|
|
||||||
// A portable installation stores this relative, so ownership is decided on the path the
|
|
||||||
// runtime would actually resolve, not on the stored text.
|
|
||||||
if (!ResolveAgainstConfig(configPath, configured)
|
|
||||||
.Equals(Path.GetFullPath(expectedValue), StringComparison.OrdinalIgnoreCase))
|
|
||||||
return;
|
|
||||||
lines.RemoveAt(index);
|
|
||||||
changed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (changed) WriteLines(configPath, lines);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The raw TOML literal stored for a key, or null when the section or key is absent.</summary>
|
private const string DefaultConfig = "# WiiCompiled user configuration\n\n";
|
||||||
public static string? GetRawValue(string configPath, string section, string key)
|
|
||||||
|
private sealed record Entry(
|
||||||
|
KeyValueSyntax Item, SyntaxList<KeyValueSyntax> Items, TableSyntax? Table, KeyValueSyntax? Previous);
|
||||||
|
|
||||||
|
private static DocumentSyntax Parse(string text) => SyntaxParser.ParseStrict(text);
|
||||||
|
|
||||||
|
private static DocumentSyntax? TryLoad(string configPath)
|
||||||
{
|
{
|
||||||
if (!File.Exists(configPath)) return null;
|
if (!File.Exists(configPath)) return null;
|
||||||
var header = $"[{section}]";
|
try { return Parse(File.ReadAllText(configPath)); }
|
||||||
var inSection = false;
|
catch (TomlException) { return null; }
|
||||||
foreach (var line in File.ReadLines(configPath))
|
}
|
||||||
|
|
||||||
|
/// <summary>Re-parses the edited tree before replacing the file, so an invalid edit never reaches disk.</summary>
|
||||||
|
private static void Save(string configPath, DocumentSyntax document)
|
||||||
|
{
|
||||||
|
var text = document.ToString();
|
||||||
|
Parse(text);
|
||||||
|
FileSystemUtilities.WriteAtomic(configPath, new UTF8Encoding(false).GetBytes(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TableSyntax> Tables(DocumentSyntax document) =>
|
||||||
|
document.Tables.OfType<TableSyntax>();
|
||||||
|
|
||||||
|
private static string[] Parts(KeySyntax key) =>
|
||||||
|
[Part(key.Key!), .. key.DotKeys.Select(dotted => Part(dotted.Key!))];
|
||||||
|
|
||||||
|
private static string Part(BareKeyOrStringValueSyntax part) =>
|
||||||
|
part is StringValueSyntax quoted ? quoted.Value! : ((BareKeySyntax)part).Key!.Text!;
|
||||||
|
|
||||||
|
/// <summary>Matches by TOML meaning, so <c>"dvd_root"</c> and <c>dvd_root</c> are the same key.</summary>
|
||||||
|
private static Entry? Find(DocumentSyntax document, string[] section, string key)
|
||||||
|
{
|
||||||
|
string[] wanted = [.. section, key];
|
||||||
|
var scopes = Tables(document)
|
||||||
|
.Select(table => (Table: (TableSyntax?)table, Prefix: Parts(table.Name!), Items: table.Items))
|
||||||
|
.Prepend((Table: null, Prefix: [], Items: document.KeyValues));
|
||||||
|
foreach (var (table, prefix, items) in scopes)
|
||||||
{
|
{
|
||||||
var trimmed = RemoveComment(line).Trim();
|
KeyValueSyntax? previous = null;
|
||||||
if (IsSection(trimmed))
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
inSection = trimmed.Equals(header, KeyComparison);
|
if (prefix.Concat(Parts(item.Key!)).SequenceEqual(wanted))
|
||||||
continue;
|
return new Entry(item, items, table, previous);
|
||||||
|
previous = item;
|
||||||
}
|
}
|
||||||
if (!inSection || trimmed.Length == 0) continue;
|
|
||||||
var equals = trimmed.IndexOf('=');
|
|
||||||
if (equals < 0 || !trimmed[..equals].Trim().Equals(key, KeyComparison)) continue;
|
|
||||||
return trimmed[(equals + 1)..].Trim();
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static void Append(DocumentSyntax document, string[] section, KeyValueSyntax item)
|
||||||
/// Replaces one key in one section, preserving comments, ordering, and every unrelated setting -
|
|
||||||
/// this file belongs to the user and to the in-game settings bar as much as to setup. A
|
|
||||||
/// commented-out key is deliberately not matched, matching the runtime's own writer.
|
|
||||||
/// </summary>
|
|
||||||
private static void SetSectionValue(List<string> lines, string section, string key, string value)
|
|
||||||
{
|
{
|
||||||
var header = $"[{section}]";
|
var text = document.ToString();
|
||||||
var sectionStart = -1;
|
var newline = text.Contains("\r\n") ? "\r\n" : "\n";
|
||||||
var sectionEnd = lines.Count;
|
var table = Tables(document).FirstOrDefault(t => Parts(t.Name!).SequenceEqual(section));
|
||||||
for (var index = 0; index < lines.Count; index++)
|
if (table is null)
|
||||||
{
|
{
|
||||||
var trimmed = RemoveComment(lines[index]).Trim();
|
var name = new KeySyntax(section[0]);
|
||||||
if (!IsSection(trimmed)) continue;
|
foreach (var part in section.Skip(1)) name.DotKeys.Add(new DottedKeyItemSyntax(part));
|
||||||
if (sectionStart >= 0)
|
table = new TableSyntax(name) { EndOfLineToken = NewLine(newline) };
|
||||||
{
|
var blankLine = text.EndsWith("\n\n") || text.EndsWith("\n\r\n");
|
||||||
sectionEnd = index;
|
var gap = text.Length == 0 ? 0 : !text.EndsWith('\n') ? 2 : blankLine ? 0 : 1;
|
||||||
break;
|
for (var i = 0; i < gap; i++) (table.LeadingTrivia ??= []).Add(new SyntaxTrivia(TokenKind.NewLine, newline));
|
||||||
}
|
document.Tables.Add(table);
|
||||||
if (trimmed.Equals(header, KeyComparison)) sectionStart = index;
|
|
||||||
}
|
|
||||||
if (sectionStart < 0)
|
|
||||||
{
|
|
||||||
if (lines.Count > 0 && lines[^1].Length != 0) lines.Add("");
|
|
||||||
lines.Add(header);
|
|
||||||
lines.Add($"{key} = {value}");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index = sectionStart + 1; index < sectionEnd; index++)
|
// Blank lines and comments after the last entry stay after the new one.
|
||||||
{
|
var last = table.Items.LastOrDefault();
|
||||||
var trimmed = RemoveComment(lines[index]).Trim();
|
var before = last is null ? table.EndOfLineToken ??= NewLine(newline) : last.EndOfLineToken ??= NewLine(newline);
|
||||||
if (trimmed.Length == 0) continue;
|
item.EndOfLineToken = NewLine(newline);
|
||||||
var equals = trimmed.IndexOf('=');
|
item.EndOfLineToken.TrailingTrivia = before.TrailingTrivia;
|
||||||
if (equals >= 0 && trimmed[..equals].Trim().Equals(key, KeyComparison))
|
before.TrailingTrivia = null;
|
||||||
{
|
table.Items.Add(item);
|
||||||
lines[index] = $"{key} = {value}";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append after the section's last real line, not after the blank line that separates it from
|
|
||||||
// the next header, so a generated file stays readable for the user who also edits it by hand.
|
|
||||||
var insertAt = sectionEnd;
|
|
||||||
while (insertAt > sectionStart + 1 && lines[insertAt - 1].Trim().Length == 0) insertAt--;
|
|
||||||
lines.Insert(insertAt, $"{key} = {value}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> ReadLinesOrDefault(string configPath) =>
|
private static SyntaxToken NewLine(string text) => new(TokenKind.NewLine, text);
|
||||||
File.Exists(configPath) ? File.ReadAllLines(configPath).ToList() : DefaultConfigLines();
|
|
||||||
|
|
||||||
private static void WriteLines(string configPath, List<string> lines) =>
|
private static SyntaxToken LastToken(SyntaxNode node)
|
||||||
FileSystemUtilities.WriteAtomic(configPath,
|
|
||||||
Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, lines) + Environment.NewLine));
|
|
||||||
|
|
||||||
private static List<string> DefaultConfigLines() =>
|
|
||||||
[
|
|
||||||
"# WiiCompiled user configuration",
|
|
||||||
"",
|
|
||||||
"[paths]"
|
|
||||||
];
|
|
||||||
|
|
||||||
private static bool IsSection(string value) =>
|
|
||||||
value.Length >= 2 && value[0] == '[' && value[^1] == ']';
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Line-for-line port of <c>RemoveComment</c> in <c>runtime_config.h</c>, including its backslash
|
|
||||||
/// escaping inside basic strings: without it a Windows path ending in <c>\\"</c> reads as
|
|
||||||
/// re-opening the quote, and the two implementations disagree about which <c>#</c> starts a comment.
|
|
||||||
/// </summary>
|
|
||||||
private static string RemoveComment(string line)
|
|
||||||
{
|
{
|
||||||
var inSingle = false;
|
while (node is not SyntaxToken)
|
||||||
var inDouble = false;
|
node = node.GetChild(node.ChildrenCount - 1)!;
|
||||||
var escaped = false;
|
return (SyntaxToken)node;
|
||||||
for (var index = 0; index < line.Length; index++)
|
|
||||||
{
|
|
||||||
var character = line[index];
|
|
||||||
if (inDouble && character == '\\' && !escaped)
|
|
||||||
{
|
|
||||||
escaped = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (character == '\'' && !inDouble) inSingle = !inSingle;
|
|
||||||
else if (character == '"' && !inSingle && !escaped) inDouble = !inDouble;
|
|
||||||
else if (character == '#' && !inSingle && !inDouble) return line[..index];
|
|
||||||
escaped = false;
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string QuoteToml(string value) =>
|
|
||||||
"\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
|
|
||||||
|
|
||||||
public static bool TryUnquoteToml(string value, out string result)
|
|
||||||
{
|
|
||||||
result = "";
|
|
||||||
if (value.Length < 2) return false;
|
|
||||||
// Literal strings carry no escapes; the runtime's parser treats them the same way.
|
|
||||||
if (value[0] == '\'' && value[^1] == '\'')
|
|
||||||
{
|
|
||||||
result = value[1..^1];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (value[0] != '"' || value[^1] != '"') return false;
|
|
||||||
var builder = new StringBuilder(value.Length - 2);
|
|
||||||
for (var index = 1; index < value.Length - 1; index++)
|
|
||||||
{
|
|
||||||
if (value[index] == '\\' && index + 1 < value.Length - 1)
|
|
||||||
{
|
|
||||||
var escaped = value[++index];
|
|
||||||
builder.Append(escaped switch { '\\' => '\\', '"' => '"', _ => escaped });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.Append(value[index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = builder.ToString();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@
|
|||||||
<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.22</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>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Tomlyn" Version="2.10.1" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ internal static class BuildRunner
|
|||||||
string workspace, string profile, string outputDir, string? baseOutputDir,
|
string workspace, string profile, string outputDir, string? baseOutputDir,
|
||||||
string? retroDir, string? retroWfcOfflineDir, bool skipRetroWfcPayload,
|
string? retroDir, string? retroWfcOfflineDir, bool skipRetroWfcPayload,
|
||||||
bool forceCleanBuild, string? translatorBin, string? ccBin, string? cxxBin, string? fuseLd,
|
bool forceCleanBuild, string? translatorBin, string? ccBin, string? cxxBin, string? fuseLd,
|
||||||
string? cmakeBin, string? ninjaBin, string? nativePrebuiltDir, IInstallReporter reporter,
|
string? cmakeBin, string? ninjaBin, string? nativePrebuiltDir, string? sysroot,
|
||||||
|
IInstallReporter reporter,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var script = Path.Combine(workspace, "Launcher", "local-build.sh");
|
var script = Path.Combine(workspace, "Launcher", "local-build.sh");
|
||||||
@@ -77,6 +78,11 @@ internal static class BuildRunner
|
|||||||
{
|
{
|
||||||
startInfo.ArgumentList.Add("--native-prebuilt-dir"); startInfo.ArgumentList.Add(nativePrebuiltDir);
|
startInfo.ArgumentList.Add("--native-prebuilt-dir"); startInfo.ArgumentList.Add(nativePrebuiltDir);
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrEmpty(sysroot))
|
||||||
|
{
|
||||||
|
startInfo.ArgumentList.Add("--sysroot"); startInfo.ArgumentList.Add(sysroot);
|
||||||
|
}
|
||||||
|
startInfo.ArgumentList.Add("--setup-version"); startInfo.ArgumentList.Add(ProductInfo.Version);
|
||||||
|
|
||||||
using var process = new Process { StartInfo = startInfo };
|
using var process = new Process { StartInfo = startInfo };
|
||||||
var window = new BuildProgressWindow(reporter, InstallStages.Build, start: 6, end: 96);
|
var window = new BuildProgressWindow(reporter, InstallStages.Build, start: 6, end: 96);
|
||||||
|
|||||||
@@ -11,23 +11,30 @@ namespace WiiCompiled.Setup.Linux;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class DiscTool
|
internal static class DiscTool
|
||||||
{
|
{
|
||||||
public static async Task ValidateAndExtractAsync(
|
/// <summary>Validates and extracts the disc, returning the supported release it is.</summary>
|
||||||
string isoPath, ProjectManifest manifest, string assetsDirectory, string workspace,
|
public static async Task<ProjectManifest> ValidateAndExtractAsync(
|
||||||
|
string isoPath, IReadOnlyList<ProjectManifest> releases, string assetsDirectory, string workspace,
|
||||||
string? nodToolBin, IInstallReporter reporter, CancellationToken cancellationToken)
|
string? nodToolBin, IInstallReporter reporter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var nodTool = nodToolBin ?? await NodToolProvider.ResolveAsync(workspace, cancellationToken);
|
var nodTool = nodToolBin ?? await NodToolProvider.ResolveAsync(workspace, cancellationToken);
|
||||||
|
|
||||||
// `nodtool info` only decodes the disc/partition headers (milliseconds); `nodtool extract`
|
// `nodtool info` only decodes the disc/partition headers (milliseconds); `nodtool extract`
|
||||||
// copies the whole data partition to disk (tens of seconds for a custom-track-heavy MKWii
|
// copies the whole data partition to disk (tens of seconds for a custom-track-heavy MKWii
|
||||||
// ISO). Checking the game ID first, before extracting, means a wrong disc fails fast -
|
// ISO). The game ID picks the release, so a wrong disc fails fast and an existing
|
||||||
// matching the original dolphin-tool `header` step this replaces.
|
// extraction is only reused for the same release.
|
||||||
reporter.Progress(InstallStages.ExtractDisc, "Reading the disc header", 2);
|
reporter.Progress(InstallStages.ExtractDisc, "Reading the disc header", 2);
|
||||||
var info = NodToolInfoParser.Parse(await RunInfoAsync(nodTool, isoPath, cancellationToken));
|
var info = NodToolInfoParser.Parse(await RunInfoAsync(nodTool, isoPath, cancellationToken));
|
||||||
if (!string.Equals(info.GameId, manifest.GameId, StringComparison.Ordinal))
|
var manifest = releases.FirstOrDefault(release =>
|
||||||
|
string.Equals(info.GameId, release.GameId, StringComparison.Ordinal))
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"This disc is '{info.GameId}', which is not a supported Mario Kart Wii release " +
|
||||||
|
$"({ProjectManifest.Describe(releases)}). " +
|
||||||
|
"Only your own legally-owned copy of a supported game/region can be used.");
|
||||||
|
|
||||||
|
if (IsAlreadyExtracted(manifest, assetsDirectory))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
reporter.Progress(InstallStages.ExtractDisc, "Reusing the verified extracted disc", 6);
|
||||||
$"This disc is '{info.GameId}', not the expected '{manifest.GameId}' (Mario Kart Wii, region {manifest.Region}). " +
|
return manifest;
|
||||||
"Only your own legally-owned copy of that exact game/region can be used.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extracted straight into Assets/DATA (kept, not a scratch dir) - the runtime reads course/
|
// Extracted straight into Assets/DATA (kept, not a scratch dir) - the runtime reads course/
|
||||||
@@ -35,6 +42,8 @@ internal static class DiscTool
|
|||||||
// time, so it has to survive past this install (see Program.cs, which points dvd_root here).
|
// time, so it has to survive past this install (see Program.cs, which points dvd_root here).
|
||||||
reporter.Progress(InstallStages.ExtractDisc, "Extracting the disc image", 4);
|
reporter.Progress(InstallStages.ExtractDisc, "Extracting the disc image", 4);
|
||||||
var dataDir = Path.Combine(assetsDirectory, "DATA");
|
var dataDir = Path.Combine(assetsDirectory, "DATA");
|
||||||
|
Directory.CreateDirectory(assetsDirectory);
|
||||||
|
File.Delete(Path.Combine(assetsDirectory, ExtractionMarker));
|
||||||
if (Directory.Exists(dataDir)) Directory.Delete(dataDir, recursive: true);
|
if (Directory.Exists(dataDir)) Directory.Delete(dataDir, recursive: true);
|
||||||
await RunExtractAsync(nodTool, isoPath, dataDir, cancellationToken);
|
await RunExtractAsync(nodTool, isoPath, dataDir, cancellationToken);
|
||||||
|
|
||||||
@@ -61,7 +70,24 @@ internal static class DiscTool
|
|||||||
Directory.CreateDirectory(assetsDirectory);
|
Directory.CreateDirectory(assetsDirectory);
|
||||||
File.Copy(dolPath, Path.Combine(assetsDirectory, "main.dol"), overwrite: true);
|
File.Copy(dolPath, Path.Combine(assetsDirectory, "main.dol"), overwrite: true);
|
||||||
File.Copy(relPath, Path.Combine(assetsDirectory, "StaticR.rel"), overwrite: true);
|
File.Copy(relPath, Path.Combine(assetsDirectory, "StaticR.rel"), overwrite: true);
|
||||||
|
File.WriteAllText(Path.Combine(assetsDirectory, ExtractionMarker), ExtractionRecord(dolSha, dataDir));
|
||||||
reporter.Progress(InstallStages.ExtractDisc, "Disc validated and extracted", 6);
|
reporter.Progress(InstallStages.ExtractDisc, "Disc validated and extracted", 6);
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The release already extracted into Assets, chosen by its main.dol like local-build.sh.</summary>
|
||||||
|
public static ProjectManifest SelectExtracted(IReadOnlyList<ProjectManifest> releases, string assetsDirectory)
|
||||||
|
{
|
||||||
|
var dolSha = Sha256Of(Path.Combine(assetsDirectory, "main.dol"));
|
||||||
|
var manifest = releases.FirstOrDefault(release => string.Equals(dolSha, release.DolSha256, StringComparison.Ordinal))
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"Assets/main.dol (sha256 {dolSha}) is not a supported clean Mario Kart Wii release " +
|
||||||
|
$"({ProjectManifest.Describe(releases)}).");
|
||||||
|
var relSha = Sha256Of(Path.Combine(assetsDirectory, "StaticR.rel"));
|
||||||
|
if (!string.Equals(relSha, manifest.RelSha256, StringComparison.Ordinal))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Assets/StaticR.rel does not match the clean {manifest.GameId} release of Assets/main.dol.");
|
||||||
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<string> RunInfoAsync(string nodTool, string isoPath, CancellationToken cancellationToken)
|
private static async Task<string> RunInfoAsync(string nodTool, string isoPath, CancellationToken cancellationToken)
|
||||||
@@ -86,6 +112,36 @@ internal static class DiscTool
|
|||||||
return stdout;
|
return stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const string ExtractionMarker = ".disc-extracted";
|
||||||
|
|
||||||
|
// Each release has one accepted revision, so a completed, verified extraction is identical to a fresh one.
|
||||||
|
private static bool IsAlreadyExtracted(ProjectManifest manifest, string assetsDirectory)
|
||||||
|
{
|
||||||
|
var marker = Path.Combine(assetsDirectory, ExtractionMarker);
|
||||||
|
var dol = Path.Combine(assetsDirectory, "main.dol");
|
||||||
|
var rel = Path.Combine(assetsDirectory, "StaticR.rel");
|
||||||
|
var dataDir = Path.Combine(assetsDirectory, "DATA");
|
||||||
|
if (!File.Exists(marker) || !File.Exists(dol) || !File.Exists(rel) ||
|
||||||
|
!Directory.Exists(Path.Combine(dataDir, "files")))
|
||||||
|
return false;
|
||||||
|
return string.Equals(File.ReadAllText(marker), ExtractionRecord(manifest.DolSha256, dataDir),
|
||||||
|
StringComparison.Ordinal) &&
|
||||||
|
string.Equals(Sha256Of(dol), manifest.DolSha256, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(Sha256Of(rel), manifest.RelSha256, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The DOL hash plus every extracted file's path and size, so a later deletion or truncation inside
|
||||||
|
// DATA forces a fresh extraction.
|
||||||
|
private static string ExtractionRecord(string dolSha, string dataDir)
|
||||||
|
{
|
||||||
|
var record = new System.Text.StringBuilder(dolSha).Append('\n');
|
||||||
|
foreach (var path in Directory.EnumerateFiles(dataDir, "*", SearchOption.AllDirectories)
|
||||||
|
.Select(path => Path.GetRelativePath(dataDir, path).Replace('\\', '/'))
|
||||||
|
.Order(StringComparer.Ordinal))
|
||||||
|
record.Append(new FileInfo(Path.Combine(dataDir, path)).Length).Append(' ').Append(path).Append('\n');
|
||||||
|
return record.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private static string Sha256Of(string path)
|
private static string Sha256Of(string path)
|
||||||
{
|
{
|
||||||
using var stream = File.OpenRead(path);
|
using var stream = File.OpenRead(path);
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace WiiCompiled.Setup.Linux;
|
|
||||||
|
|
||||||
// Ported near-verbatim from Launcher/WiiCompiled.Setup/InstallProgress.cs: this whole file is
|
|
||||||
// platform-neutral (System.Text.Json + Console only), so the NDJSON --progress-json wire protocol
|
|
||||||
// stays byte-for-byte the same shape a future GUI already speaks on Windows.
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stable stage identifiers reported by <c>--progress-json</c>. Kept intentionally small for this
|
|
||||||
/// lean Linux installer (no toolkit-extraction/publish-transaction stages, since there is no
|
|
||||||
/// bundled toolkit or staged workspace copy here - see the plan's "operate on a git checkout"
|
|
||||||
/// scoping decision).
|
|
||||||
/// </summary>
|
|
||||||
internal static class InstallStages
|
|
||||||
{
|
|
||||||
public const string Validate = "validate";
|
|
||||||
public const string ExtractDisc = "extract-disc";
|
|
||||||
public const string Build = "build";
|
|
||||||
public const string Shortcuts = "shortcuts";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Where an installation reports what it is doing. Progress is coarse and monotonic; raw translator
|
|
||||||
/// and compiler output is a diagnostic, never progress, because it is unbounded and machine-hostile.
|
|
||||||
/// </summary>
|
|
||||||
internal interface IInstallReporter
|
|
||||||
{
|
|
||||||
void Progress(string stage, string message, int percent);
|
|
||||||
void Diagnostic(string line);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <c>--progress-json</c> protocol: one JSON object per line on stdout, nothing else on stdout,
|
|
||||||
/// diagnostics on stderr. The terminal <c>result</c> line is written exactly once.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class NdjsonInstallReporter : IInstallReporter
|
|
||||||
{
|
|
||||||
private static readonly JsonSerializerOptions Options = new() { WriteIndented = false };
|
|
||||||
private readonly object _gate = new();
|
|
||||||
private int _lastPercent;
|
|
||||||
private bool _finished;
|
|
||||||
|
|
||||||
public void Progress(string stage, string message, int percent)
|
|
||||||
{
|
|
||||||
lock (_gate)
|
|
||||||
{
|
|
||||||
if (_finished) return;
|
|
||||||
// Percentages are clamped monotonic: a caller's progress bar must never walk backwards
|
|
||||||
// because a later stage happened to estimate a lower number.
|
|
||||||
_lastPercent = Math.Clamp(Math.Max(percent, _lastPercent), 0, 99);
|
|
||||||
WriteLine(new { type = "progress", stage, message, percent = _lastPercent });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Diagnostic(string line) => Console.Error.WriteLine(line);
|
|
||||||
|
|
||||||
public void Success(string installDirectory)
|
|
||||||
{
|
|
||||||
lock (_gate)
|
|
||||||
{
|
|
||||||
if (_finished) return;
|
|
||||||
_finished = true;
|
|
||||||
WriteLine(new { type = "result", success = true, version = ProductInfo.Version, installDir = installDirectory });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Failure(string error)
|
|
||||||
{
|
|
||||||
lock (_gate)
|
|
||||||
{
|
|
||||||
if (_finished) return;
|
|
||||||
_finished = true;
|
|
||||||
WriteLine(new { type = "result", success = false, error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The terminal result line is the caller's only completion signal, so no exit path may skip it.
|
|
||||||
/// Callers invoke this from a finally block; it is a no-op once a result was already written.
|
|
||||||
/// </summary>
|
|
||||||
public void EnsureFinished(string errorIfUnfinished) => Failure(errorIfUnfinished);
|
|
||||||
|
|
||||||
private static void WriteLine(object value)
|
|
||||||
{
|
|
||||||
Console.Out.WriteLine(JsonSerializer.Serialize(value, Options));
|
|
||||||
Console.Out.Flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Plain-text console reporting for a run without <c>--progress-json</c>.</summary>
|
|
||||||
internal sealed class ConsoleInstallReporter : IInstallReporter
|
|
||||||
{
|
|
||||||
public void Progress(string stage, string message, int percent) =>
|
|
||||||
Console.Out.WriteLine($"[{percent,3}%] {message}");
|
|
||||||
|
|
||||||
public void Diagnostic(string line) => Console.Out.WriteLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Build step identifiers from local-build.sh's <c>MKWCBUILD:STEP:<id></c> lines - the id is
|
|
||||||
/// the contract, matched against Launcher/local-build.sh's log_step() call sites.
|
|
||||||
/// </summary>
|
|
||||||
internal static class BuildStepIds
|
|
||||||
{
|
|
||||||
public const string BuildTranslator = "build-translator";
|
|
||||||
public const string ReuseBaseTranslation = "reuse-base-translation";
|
|
||||||
public const string RetranslateBase = "retranslate-base";
|
|
||||||
public const string TranslateBase = "translate-base";
|
|
||||||
public const string EmitBaseManifest = "emit-base-manifest";
|
|
||||||
public const string TranslateMod = "translate-mod";
|
|
||||||
public const string GenerateDataInit = "generate-data-init";
|
|
||||||
public const string EmitBuildShards = "emit-build-shards";
|
|
||||||
public const string ConfigureNative = "configure-native";
|
|
||||||
public const string Compile = "compile";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maps one local-build.sh run onto a slice of the overall percentage. local-build.sh announces
|
|
||||||
/// every step it starts with an <c>MKWCBUILD:</c> prefix, so the slice can advance on real events
|
|
||||||
/// instead of on a timer.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class BuildProgressWindow
|
|
||||||
{
|
|
||||||
private const string Marker = "MKWCBUILD:";
|
|
||||||
private const string StepMarker = "STEP:";
|
|
||||||
|
|
||||||
/// <summary>The fraction the compile step reaches; beyond it, compiler output is a heartbeat.</summary>
|
|
||||||
private const double CompileFraction = 0.58;
|
|
||||||
|
|
||||||
private static readonly (string Id, double Fraction, string Message)[] Steps =
|
|
||||||
[
|
|
||||||
(BuildStepIds.BuildTranslator, 0.04, "Building the translator"),
|
|
||||||
(BuildStepIds.ReuseBaseTranslation, 0.30, "Reusing the completed base translation"),
|
|
||||||
(BuildStepIds.RetranslateBase, 0.08, "The base translation is stale; retranslating it"),
|
|
||||||
(BuildStepIds.TranslateBase, 0.10, "Translating Mario Kart Wii"),
|
|
||||||
(BuildStepIds.EmitBaseManifest, 0.34, "Creating the translation manifest"),
|
|
||||||
(BuildStepIds.TranslateMod, 0.38, "Translating the Retro Rewind Code.pul"),
|
|
||||||
(BuildStepIds.GenerateDataInit, 0.44, "Generating game data initialization"),
|
|
||||||
(BuildStepIds.EmitBuildShards, 0.48, "Preparing the native build"),
|
|
||||||
(BuildStepIds.ConfigureNative, 0.52, "Configuring the compiler"),
|
|
||||||
(BuildStepIds.Compile, CompileFraction, "Compiling the game. This is the longest step"),
|
|
||||||
];
|
|
||||||
|
|
||||||
private readonly IInstallReporter _reporter;
|
|
||||||
private readonly string _stage;
|
|
||||||
private readonly int _start;
|
|
||||||
private readonly int _end;
|
|
||||||
private double _fraction;
|
|
||||||
private string _message = "Preparing the local build";
|
|
||||||
private int _reportedPercent = -1;
|
|
||||||
|
|
||||||
public BuildProgressWindow(IInstallReporter reporter, string stage, int start, int end)
|
|
||||||
{
|
|
||||||
_reporter = reporter;
|
|
||||||
_stage = stage;
|
|
||||||
_start = start;
|
|
||||||
_end = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Observe(string line)
|
|
||||||
{
|
|
||||||
var index = line.IndexOf(Marker, StringComparison.Ordinal);
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
|
||||||
var text = line[(index + Marker.Length)..].Trim();
|
|
||||||
if (text.StartsWith(StepMarker, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
var identifier = text[StepMarker.Length..];
|
|
||||||
var end = identifier.IndexOf(' ');
|
|
||||||
if (end >= 0) identifier = identifier[..end];
|
|
||||||
foreach (var (id, fraction, message) in Steps)
|
|
||||||
{
|
|
||||||
if (!id.Equals(identifier, StringComparison.Ordinal)) continue;
|
|
||||||
if (fraction > _fraction)
|
|
||||||
{
|
|
||||||
_fraction = fraction;
|
|
||||||
_message = message;
|
|
||||||
Emit();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anything else - a plain MKWCBUILD note, or raw tool output - stays a diagnostic and only
|
|
||||||
// feeds the heartbeat below.
|
|
||||||
_reporter.Diagnostic(line);
|
|
||||||
// Compilation announces itself once and then emits thousands of compiler lines. Treat that
|
|
||||||
// output as a heartbeat so the slice keeps creeping forward, but only publish a progress
|
|
||||||
// line when the rounded percentage actually changes.
|
|
||||||
if (_fraction >= CompileFraction)
|
|
||||||
{
|
|
||||||
_fraction = Math.Min(0.97, _fraction + 0.0015);
|
|
||||||
Emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Emit()
|
|
||||||
{
|
|
||||||
var percent = Interpolate(_fraction);
|
|
||||||
if (percent == _reportedPercent) return;
|
|
||||||
_reportedPercent = percent;
|
|
||||||
_reporter.Progress(_stage, _message, percent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int Interpolate(double fraction) =>
|
|
||||||
(int)Math.Round(_start + (_end - _start) * Math.Clamp(fraction, 0, 1));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace WiiCompiled.Setup.Linux;
|
||||||
|
|
||||||
|
/// <summary>Stable stage identifiers reported by <c>--progress-json</c>.</summary>
|
||||||
|
internal static class InstallStages
|
||||||
|
{
|
||||||
|
public const string Validate = "validate";
|
||||||
|
public const string ExtractDisc = "extract-disc";
|
||||||
|
public const string Build = "build";
|
||||||
|
public const string Shortcuts = "shortcuts";
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace WiiCompiled.Setup.Linux;
|
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.27";
|
public static readonly string Version =
|
||||||
|
typeof(ProductInfo).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
|
||||||
|
.InformationalVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>One installed product's record inside install-state.json.</summary>
|
/// <summary>One installed product's record inside install-state.json.</summary>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ internal static class Program
|
|||||||
var (command, flags) = ParseArgs(args);
|
var (command, flags) = ParseArgs(args);
|
||||||
if (command is null) { PrintUsage(); return 1; }
|
if (command is null) { PrintUsage(); return 1; }
|
||||||
var progressJson = flags.ContainsKey("progress-json");
|
var progressJson = flags.ContainsKey("progress-json");
|
||||||
IInstallReporter reporter = progressJson ? new NdjsonInstallReporter() : new ConsoleInstallReporter();
|
IInstallReporter reporter = progressJson ? new NdjsonInstallReporter(ProductInfo.Version) : new ConsoleInstallReporter();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -95,13 +95,14 @@ internal static class Program
|
|||||||
if (installsRetro) retroDir = RetroRewindSource.ResolveRetroRewind6(retroDir!);
|
if (installsRetro) retroDir = RetroRewindSource.ResolveRetroRewind6(retroDir!);
|
||||||
|
|
||||||
var workspace = flags.GetValueOrDefault("workspace") ?? WorkspaceLocator.FindFrom(AppContext.BaseDirectory);
|
var workspace = flags.GetValueOrDefault("workspace") ?? WorkspaceLocator.FindFrom(AppContext.BaseDirectory);
|
||||||
var manifest = ProjectManifest.Load(Path.Combine(workspace, "projects", "mkwii", "recomp.yml"));
|
var releases = ProjectManifest.LoadAll(workspace);
|
||||||
var assetsDir = Path.Combine(workspace, "Assets");
|
var assetsDir = Path.Combine(workspace, "Assets");
|
||||||
|
|
||||||
reporter.Progress(InstallStages.Validate, "Checking prerequisites", 1);
|
reporter.Progress(InstallStages.Validate, "Checking prerequisites", 1);
|
||||||
|
ProjectManifest release;
|
||||||
if (flags.TryGetValue("game", out var isoPath) && !string.IsNullOrEmpty(isoPath))
|
if (flags.TryGetValue("game", out var isoPath) && !string.IsNullOrEmpty(isoPath))
|
||||||
{
|
{
|
||||||
await DiscTool.ValidateAndExtractAsync(isoPath, manifest, assetsDir, workspace,
|
release = await DiscTool.ValidateAndExtractAsync(isoPath, releases, assetsDir, workspace,
|
||||||
flags.GetValueOrDefault("disc-tool-bin"), reporter, token);
|
flags.GetValueOrDefault("disc-tool-bin"), reporter, token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -114,6 +115,7 @@ internal static class Program
|
|||||||
"No --game ISO was given and Assets/main.dol + Assets/StaticR.rel are not already present. " +
|
"No --game ISO was given and Assets/main.dol + Assets/StaticR.rel are not already present. " +
|
||||||
"Either pass --game <path-to-iso>, or extract them yourself first (see translator/README.md).");
|
"Either pass --game <path-to-iso>, or extract them yourself first (see translator/README.md).");
|
||||||
}
|
}
|
||||||
|
release = DiscTool.SelectExtracted(releases, assetsDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = JsonState.TryRead<InstallState>(StatePath) ?? new InstallState { Workspace = workspace };
|
var state = JsonState.TryRead<InstallState>(StatePath) ?? new InstallState { Workspace = workspace };
|
||||||
@@ -127,22 +129,52 @@ internal static class Program
|
|||||||
string? retroWfcOfflineDir = null;
|
string? retroWfcOfflineDir = null;
|
||||||
if (downloadPayload)
|
if (downloadPayload)
|
||||||
{
|
{
|
||||||
// Reused if a previous install already downloaded and it's still valid - matches
|
|
||||||
// Windows's own reuse-if-valid behavior instead of re-downloading on every install.
|
|
||||||
var cacheDir = Path.Combine(workspace, "generated", "retro-wfc-payload");
|
var cacheDir = Path.Combine(workspace, "generated", "retro-wfc-payload");
|
||||||
reporter.Progress(InstallStages.Validate, "Preparing the Retro-WFC payload", 1);
|
reporter.Progress(InstallStages.Validate,
|
||||||
|
"Downloading the current Retro-WFC payload", 1);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(cacheDir);
|
// A valid signature authenticates a payload, but does not prove it is the latest
|
||||||
}
|
// signed revision. Always ask the release's fixed endpoint for the current snapshot;
|
||||||
catch (InvalidDataException)
|
// the downloader verifies it before atomically replacing the cache.
|
||||||
{
|
|
||||||
await RetroWfcPayload.DownloadRetroWfcPayloadAsync(
|
await RetroWfcPayload.DownloadRetroWfcPayloadAsync(
|
||||||
RetroWfcPayload.CurrentRetroWfcPayloadUri, cacheDir, token);
|
RetroWfcPayload.PayloadUri(release.GameId), cacheDir, token);
|
||||||
|
}
|
||||||
|
catch (Exception downloadFailure) when (!token.IsCancellationRequested &&
|
||||||
|
downloadFailure is HttpRequestException or TimeoutException
|
||||||
|
or IOException)
|
||||||
|
{
|
||||||
|
// Offline installs may continue with a previously authenticated snapshot. Do not
|
||||||
|
// use this path for a newly downloaded payload that failed signature validation:
|
||||||
|
// that must remain a hard failure instead of hiding possible endpoint tampering.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(cacheDir, gameId: release.GameId);
|
||||||
|
}
|
||||||
|
catch (Exception cacheFailure) when (cacheFailure is IOException or
|
||||||
|
UnauthorizedAccessException or InvalidDataException)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"The current Retro-WFC payload could not be downloaded and no valid cached " +
|
||||||
|
$"payload is available ({cacheFailure.Message.TrimEnd('.')}).", downloadFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
reporter.Diagnostic(
|
||||||
|
"The current Retro-WFC payload could not be downloaded; using the previously " +
|
||||||
|
$"verified cached payload instead ({downloadFailure.Message.TrimEnd('.')}).");
|
||||||
}
|
}
|
||||||
retroWfcOfflineDir = cacheDir;
|
retroWfcOfflineDir = cacheDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sysroot = flags.GetValueOrDefault("sysroot");
|
||||||
|
// --sysroot explicitly provided (even as bare flag at end of argv, which ParseArgs
|
||||||
|
// stores as null) must carry a path; omitting --sysroot entirely is fine (local-build.sh
|
||||||
|
// adds -UCMAKE_SYSROOT to clear any stale cached value from a prior configure).
|
||||||
|
if (flags.ContainsKey("sysroot") && string.IsNullOrWhiteSpace(sysroot))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("--sysroot requires a non-empty directory path.");
|
||||||
|
}
|
||||||
|
|
||||||
await BuildRunner.RunAsync(
|
await BuildRunner.RunAsync(
|
||||||
workspace, profile, installDir, baseInstallDir,
|
workspace, profile, installDir, baseInstallDir,
|
||||||
retroDir,
|
retroDir,
|
||||||
@@ -156,6 +188,7 @@ internal static class Program
|
|||||||
flags.GetValueOrDefault("cmake"),
|
flags.GetValueOrDefault("cmake"),
|
||||||
flags.GetValueOrDefault("ninja"),
|
flags.GetValueOrDefault("ninja"),
|
||||||
flags.GetValueOrDefault("native-prebuilt-dir"),
|
flags.GetValueOrDefault("native-prebuilt-dir"),
|
||||||
|
sysroot,
|
||||||
reporter, token);
|
reporter, token);
|
||||||
|
|
||||||
reporter.Progress(InstallStages.Shortcuts, "Creating shortcuts", 98);
|
reporter.Progress(InstallStages.Shortcuts, "Creating shortcuts", 98);
|
||||||
@@ -324,7 +357,7 @@ internal static class Program
|
|||||||
{--download-retro-wfc-payload | --skip-retro-wfc-payload}]
|
{--download-retro-wfc-payload | --skip-retro-wfc-payload}]
|
||||||
[--force-clean-build] [--translator-bin PATH] [--disc-tool-bin PATH]
|
[--force-clean-build] [--translator-bin PATH] [--disc-tool-bin PATH]
|
||||||
[--cc PATH] [--cxx PATH] [--fuse-ld NAME_OR_PATH] [--cmake PATH] [--ninja PATH]
|
[--cc PATH] [--cxx PATH] [--fuse-ld NAME_OR_PATH] [--cmake PATH] [--ninja PATH]
|
||||||
[--native-prebuilt-dir DIR] [--progress-json] [--workspace DIR]
|
[--native-prebuilt-dir DIR] [--sysroot PATH] [--progress-json] [--workspace DIR]
|
||||||
uninstall
|
uninstall
|
||||||
launch-base
|
launch-base
|
||||||
launch-retro
|
launch-retro
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
|
|||||||
namespace WiiCompiled.Setup.Linux;
|
namespace WiiCompiled.Setup.Linux;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The handful of facts this tool needs out of projects/mkwii/recomp.yml. Parsed literally line by
|
/// The handful of facts this tool needs out of a projects/*/recomp.yml. Parsed literally line by
|
||||||
/// line - the same approach Launcher/NativeBuildFlags.ps1's Get-MkwProjectPins and
|
/// line - the same approach Launcher/NativeBuildFlags.ps1's Get-MkwProjectPins and
|
||||||
/// Launcher/local-build.sh already use - rather than pulling in a YAML library, since the manifest
|
/// Launcher/local-build.sh already use - rather than pulling in a YAML library, since the manifest
|
||||||
/// is machine-written with a fixed shape.
|
/// is machine-written with a fixed shape.
|
||||||
@@ -67,4 +67,21 @@ internal sealed class ProjectManifest
|
|||||||
|
|
||||||
return new ProjectManifest { GameId = gameId, Region = region, DolSha256 = dolSha, RelSha256 = relSha };
|
return new ProjectManifest { GameId = gameId, Region = region, DolSha256 = dolSha, RelSha256 = relSha };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Every supported release: one per projects/*/recomp.yml, as local-build.sh selects them.</summary>
|
||||||
|
public static IReadOnlyList<ProjectManifest> LoadAll(string workspace)
|
||||||
|
{
|
||||||
|
var projects = Directory.EnumerateDirectories(Path.Combine(workspace, "projects"))
|
||||||
|
.Select(directory => Path.Combine(directory, "recomp.yml"))
|
||||||
|
.Where(File.Exists)
|
||||||
|
.Order(StringComparer.Ordinal)
|
||||||
|
.Select(Load)
|
||||||
|
.ToList();
|
||||||
|
if (projects.Count == 0)
|
||||||
|
throw new InvalidOperationException($"No projects/*/recomp.yml was found under {workspace}.");
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Describe(IEnumerable<ProjectManifest> projects) =>
|
||||||
|
string.Join(", ", projects.Select(project => $"{project.GameId} (region {project.Region})"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
<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.22</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>
|
||||||
@@ -16,5 +15,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
|
<ProjectReference Include="..\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
|
||||||
|
<Using Include="WiiCompiled.Setup.Common" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -57,16 +57,14 @@ internal static class ConsoleCommands
|
|||||||
|
|
||||||
public static int VerifyInputs(CommandLine command)
|
public static int VerifyInputs(CommandLine command)
|
||||||
{
|
{
|
||||||
var reporter = command.ProgressJson ? new NdjsonInstallReporter() : null;
|
var reporter = command.ProgressJson ? new NdjsonInstallReporter(ProductInfo.Version) : null;
|
||||||
var temp = Path.Combine(Path.GetTempPath(), "mkwc-verify-" + Guid.NewGuid().ToString("N"));
|
var temp = Path.Combine(Path.GetTempPath(), "mkwc-verify-" + Guid.NewGuid().ToString("N"));
|
||||||
Directory.CreateDirectory(temp);
|
Directory.CreateDirectory(temp);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var payload = PayloadArchive.OpenCurrent();
|
using var payload = PayloadArchive.OpenCurrent();
|
||||||
var manifest = payload.ReadManifest();
|
var manifest = payload.ReadManifest();
|
||||||
var tool = Path.Combine(temp, "nodtool.exe");
|
var tool = payload.ExtractDiscTool(temp);
|
||||||
payload.ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "nodtool.exe", tool);
|
|
||||||
payload.ExtractDirectory(InstalledLayout.ToolkitEntryPrefix + "Redist", temp);
|
|
||||||
reporter?.Progress(InstallStages.Validate, "Checking the Wii disc image...", 10);
|
reporter?.Progress(InstallStages.Validate, "Checking the Wii disc image...", 10);
|
||||||
var header = InputValidation.ReadDiscHeaderAsync(tool, command.GamePath!).GetAwaiter().GetResult();
|
var header = InputValidation.ReadDiscHeaderAsync(tool, command.GamePath!).GetAwaiter().GetResult();
|
||||||
InputValidation.EnsureCompatibleDisc(header, manifest);
|
InputValidation.EnsureCompatibleDisc(header, manifest);
|
||||||
@@ -107,7 +105,7 @@ internal static class ConsoleCommands
|
|||||||
}
|
}
|
||||||
File.WriteAllText(logPath, $"{ProductInfo.Name} setup {ProductInfo.Version}{Environment.NewLine}");
|
File.WriteAllText(logPath, $"{ProductInfo.Name} setup {ProductInfo.Version}{Environment.NewLine}");
|
||||||
|
|
||||||
var ndjson = command.ProgressJson ? new NdjsonInstallReporter(Log) : null;
|
var ndjson = command.ProgressJson ? new NdjsonInstallReporter(ProductInfo.Version, Log) : null;
|
||||||
IInstallReporter reporter = ndjson ?? (IInstallReporter)new ConsoleInstallReporter(Log);
|
IInstallReporter reporter = ndjson ?? (IInstallReporter)new ConsoleInstallReporter(Log);
|
||||||
var installDirectory = Path.GetFullPath(command.InstallDirectory!);
|
var installDirectory = Path.GetFullPath(command.InstallDirectory!);
|
||||||
try
|
try
|
||||||
@@ -145,7 +143,7 @@ internal static class ConsoleCommands
|
|||||||
public static int CheckProducts(CommandLine command,
|
public static int CheckProducts(CommandLine command,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var reporter = command.ProgressJson ? new NdjsonInstallReporter() : null;
|
var reporter = command.ProgressJson ? new NdjsonInstallReporter(ProductInfo.Version) : null;
|
||||||
var installation = new Installation(string.IsNullOrWhiteSpace(command.InstallDirectory)
|
var installation = new Installation(string.IsNullOrWhiteSpace(command.InstallDirectory)
|
||||||
? AppContext.BaseDirectory
|
? AppContext.BaseDirectory
|
||||||
: Path.GetFullPath(command.InstallDirectory));
|
: Path.GetFullPath(command.InstallDirectory));
|
||||||
@@ -198,7 +196,7 @@ internal static class ConsoleCommands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ndjson = command.ProgressJson ? new NdjsonInstallReporter(Log) : null;
|
var ndjson = command.ProgressJson ? new NdjsonInstallReporter(ProductInfo.Version, Log) : null;
|
||||||
IInstallReporter reporter = ndjson is null ? new ConsoleInstallReporter(Log) : ndjson;
|
IInstallReporter reporter = ndjson is null ? new ConsoleInstallReporter(Log) : ndjson;
|
||||||
var installDirectory = Path.GetFullPath(command.InstallDirectory!);
|
var installDirectory = Path.GetFullPath(command.InstallDirectory!);
|
||||||
var installation = new Installation(installDirectory);
|
var installation = new Installation(installDirectory);
|
||||||
|
|||||||
@@ -54,33 +54,29 @@ internal static class InputValidation
|
|||||||
}
|
}
|
||||||
: "Unknown";
|
: "Unknown";
|
||||||
|
|
||||||
public static void EnsureCompatibleDisc(DiscHeader header, PayloadManifest manifest)
|
/// <summary>The supported release this disc is, or a clear rejection naming every supported one.</summary>
|
||||||
{
|
public static GameRelease EnsureCompatibleDisc(DiscHeader header, PayloadManifest manifest) =>
|
||||||
if (!header.GameId.Equals(manifest.ExpectedGameId, StringComparison.OrdinalIgnoreCase))
|
manifest.SupportedReleases.FirstOrDefault(release =>
|
||||||
{
|
release.GameId.Equals(header.GameId, StringComparison.OrdinalIgnoreCase))
|
||||||
throw new InvalidDataException(
|
?? throw new InvalidDataException(
|
||||||
$"This build supports Mario Kart Wii PAL ({manifest.ExpectedGameId}). " +
|
"This build supports Mario Kart Wii " +
|
||||||
$"The selected image is {header.GameId} ({header.InternalName}, {header.Region}).");
|
string.Join(", ", manifest.SupportedReleases.Select(release =>
|
||||||
}
|
$"{RegionFromGameId(release.GameId)} ({release.GameId})")) +
|
||||||
}
|
$". The selected image is {header.GameId} ({header.InternalName}, {header.Region}).");
|
||||||
|
|
||||||
// Thin forwarding wrappers: the actual download/RSA-verification logic lives in
|
// Forwarding wrappers over WiiCompiled.Setup.Common.RetroWfcPayload that require the game id,
|
||||||
// WiiCompiled.Setup.Common.RetroWfcPayload (shared with WiiCompiled.Setup.Linux) so there's one
|
// so no caller here can silently fall back to one region's payload.
|
||||||
// copy of it, not two. Kept under these names so every existing call site here
|
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory, string gameId,
|
||||||
// (ProductRepairService.cs, LocalBuildService.cs, Installation.cs, SelfTests.cs) is unchanged.
|
|
||||||
public const string CurrentRetroWfcPayloadUri = RetroWfcPayload.CurrentRetroWfcPayloadUri;
|
|
||||||
|
|
||||||
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory,
|
|
||||||
RSAParameters? signingKey = null) =>
|
RSAParameters? signingKey = null) =>
|
||||||
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, signingKey);
|
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, gameId, signingKey);
|
||||||
|
|
||||||
public static string ResolveRetroWfcPayloadFile(string stagedDirectory,
|
public static string ResolveRetroWfcPayloadFile(string stagedDirectory, string gameId,
|
||||||
RSAParameters? signingKey = null) =>
|
RSAParameters? signingKey = null) =>
|
||||||
RetroWfcPayload.ResolveRetroWfcPayloadFile(stagedDirectory, signingKey);
|
RetroWfcPayload.ResolveRetroWfcPayloadFile(stagedDirectory, gameId, signingKey);
|
||||||
|
|
||||||
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory,
|
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory, string gameId,
|
||||||
RSAParameters? signingKey = null) =>
|
RSAParameters? signingKey = null) =>
|
||||||
RetroWfcPayload.ComputeRetroWfcPayloadSha256(stagedDirectory, signingKey);
|
RetroWfcPayload.ComputeRetroWfcPayloadSha256(stagedDirectory, gameId, signingKey);
|
||||||
|
|
||||||
public static void ValidateRetroWfcPayloadUri(string uriText) =>
|
public static void ValidateRetroWfcPayloadUri(string uriText) =>
|
||||||
RetroWfcPayload.ValidateRetroWfcPayloadUri(uriText);
|
RetroWfcPayload.ValidateRetroWfcPayloadUri(uriText);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace WiiCompiled.Setup.Windows;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stable stage identifiers reported by <c>--progress-json</c>. These are part of the public
|
||||||
|
/// WheelWizard contract (docs/WHEELWIZARD_CONTRACT.md): the strings may gain new members, but an
|
||||||
|
/// existing identifier never changes meaning.
|
||||||
|
/// </summary>
|
||||||
|
internal static class InstallStages
|
||||||
|
{
|
||||||
|
public const string Validate = "validate";
|
||||||
|
public const string ExtractToolkit = "extract-toolkit";
|
||||||
|
public const string ExtractDisc = "extract-disc";
|
||||||
|
public const string PrepareRetro = "prepare-retro";
|
||||||
|
public const string BuildBase = "build-base";
|
||||||
|
public const string BuildRetro = "build-retro";
|
||||||
|
public const string Publish = "publish";
|
||||||
|
}
|
||||||
@@ -131,6 +131,8 @@ internal sealed class Installation
|
|||||||
Workspace + "LocalBuild.ps1",
|
Workspace + "LocalBuild.ps1",
|
||||||
Workspace + "NativeBuildFlags.ps1",
|
Workspace + "NativeBuildFlags.ps1",
|
||||||
Workspace + @"projects\mkwii\recomp.yml",
|
Workspace + @"projects\mkwii\recomp.yml",
|
||||||
|
Workspace + @"projects\mkwii-ntsc-u\recomp.yml",
|
||||||
|
Workspace + @"projects\mkwii-ntsc-j\recomp.yml",
|
||||||
Workspace + @"runtime\CMakeLists.txt",
|
Workspace + @"runtime\CMakeLists.txt",
|
||||||
Workspace + @"runtime\assets\dsp\dsp_coef.bin"
|
Workspace + @"runtime\assets\dsp\dsp_coef.bin"
|
||||||
];
|
];
|
||||||
@@ -168,6 +170,26 @@ internal sealed class Installation
|
|||||||
|
|
||||||
public InstallState? ReadInstallState() => JsonState.TryRead<InstallState>(InstallStatePath);
|
public InstallState? ReadInstallState() => JsonState.TryRead<InstallState>(InstallStatePath);
|
||||||
|
|
||||||
|
/// <summary>The game id of the installed disc, which selects its region's Retro-WFC payload.</summary>
|
||||||
|
public string ReadGameId()
|
||||||
|
{
|
||||||
|
try { return ReadDiscGameId(GameDataDirectory); }
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException(
|
||||||
|
"The installed game data has no readable disc header. Apply setup again with the disc image.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The six-character game id in an extracted disc's <c>sys/boot.bin</c>.</summary>
|
||||||
|
internal static string ReadDiscGameId(string dataRoot)
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(Path.Combine(dataRoot, "sys", "boot.bin"));
|
||||||
|
var id = new byte[6];
|
||||||
|
stream.ReadExactly(id);
|
||||||
|
return System.Text.Encoding.ASCII.GetString(id);
|
||||||
|
}
|
||||||
|
|
||||||
internal ToolkitState? ReadToolkitState() => JsonState.TryRead<ToolkitState>(ToolkitStatePath);
|
internal ToolkitState? ReadToolkitState() => JsonState.TryRead<ToolkitState>(ToolkitStatePath);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -396,7 +418,7 @@ internal sealed class Installation
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload);
|
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload, ReadGameId());
|
||||||
if (!InputValidation.Sha256File(payload).Equals(fingerprint.RetroWfcPayloadSha256,
|
if (!InputValidation.Sha256File(payload).Equals(fingerprint.RetroWfcPayloadSha256,
|
||||||
StringComparison.OrdinalIgnoreCase) ||
|
StringComparison.OrdinalIgnoreCase) ||
|
||||||
new FileInfo(payload).Length != fingerprint.RetroWfcPayloadLength)
|
new FileInfo(payload).Length != fingerprint.RetroWfcPayloadLength)
|
||||||
@@ -581,7 +603,7 @@ internal sealed class Installation
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload);
|
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload, ReadGameId());
|
||||||
return new FileInfo(payload).Length == snapshot.ByteLength &&
|
return new FileInfo(payload).Length == snapshot.ByteLength &&
|
||||||
InputValidation.Sha256File(payload).Equals(snapshot.Sha256, StringComparison.Ordinal);
|
InputValidation.Sha256File(payload).Equals(snapshot.Sha256, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,28 @@ internal sealed class InstallerEngine
|
|||||||
var toolkit = InstalledLayout.Toolkit(staging);
|
var toolkit = InstalledLayout.Toolkit(staging);
|
||||||
var workspace = InstalledLayout.Workspace(staging);
|
var workspace = InstalledLayout.Workspace(staging);
|
||||||
|
|
||||||
|
// The disc decides which release's inputs everything below expects, so it is identified
|
||||||
|
// before any reuse decision or toolkit extraction.
|
||||||
|
_reporter.Progress(InstallStages.Validate, "Checking the Wii disc image...", 1);
|
||||||
|
var nodTool = payload.ExtractDiscTool(Path.Combine(staging, "disc-tool"));
|
||||||
|
var header = await InputValidation.ReadDiscHeaderAsync(nodTool, options.GamePath,
|
||||||
|
cancellationToken);
|
||||||
|
var release = InputValidation.EnsureCompatibleDisc(header, manifest);
|
||||||
|
var canonicalRetroRoot = options.RetroDirectoryPath is null
|
||||||
|
? null
|
||||||
|
: RetroRewindSource.ResolveRetroRewind6(options.RetroDirectoryPath);
|
||||||
|
ValidateRetroOptions(canonicalRetroRoot, options.RetroWfcPayloadMode);
|
||||||
|
|
||||||
|
var retroCompileInputs = canonicalRetroRoot is null
|
||||||
|
? null
|
||||||
|
: SnapshotRetroRewindCompileInputs(canonicalRetroRoot,
|
||||||
|
Path.Combine(staging, "compile-inputs"), cancellationToken);
|
||||||
|
if (options.Portable)
|
||||||
|
{
|
||||||
|
var portableRoot = PortableRoot.Create(parent);
|
||||||
|
_reporter.Diagnostic($"Installing as a portable installation under {portableRoot}.");
|
||||||
|
}
|
||||||
|
|
||||||
var candidateToolkitFingerprint = manifest.ToolkitFingerprint;
|
var candidateToolkitFingerprint = manifest.ToolkitFingerprint;
|
||||||
var candidateToolkitPackageFingerprint = manifest.ToolkitPackageFingerprint;
|
var candidateToolkitPackageFingerprint = manifest.ToolkitPackageFingerprint;
|
||||||
var candidateRuntimeAssetsFingerprint = manifest.RuntimeAssetsFingerprint;
|
var candidateRuntimeAssetsFingerprint = manifest.RuntimeAssetsFingerprint;
|
||||||
@@ -49,7 +71,7 @@ internal sealed class InstallerEngine
|
|||||||
var installedToolkitPackageFingerprint = existing.IsPresent
|
var installedToolkitPackageFingerprint = existing.IsPresent
|
||||||
? existing.ResolveToolkitPackageFingerprint()
|
? existing.ResolveToolkitPackageFingerprint()
|
||||||
: "";
|
: "";
|
||||||
var reusableGameAssets = FindReusableGameAssets(existing, manifest);
|
var reusableGameAssets = FindReusableGameAssets(existing, release);
|
||||||
|
|
||||||
var sameToolkit = existing.IsPresent && reusableGameAssets is not null &&
|
var sameToolkit = existing.IsPresent && reusableGameAssets is not null &&
|
||||||
installedToolkitFingerprint.Equals(candidateToolkitFingerprint,
|
installedToolkitFingerprint.Equals(candidateToolkitFingerprint,
|
||||||
@@ -68,7 +90,7 @@ internal sealed class InstallerEngine
|
|||||||
var extractWorkspace = !sameToolkit || !runtimeAssetsCurrent;
|
var extractWorkspace = !sameToolkit || !runtimeAssetsCurrent;
|
||||||
|
|
||||||
_reporter.Progress(InstallStages.ExtractToolkit,
|
_reporter.Progress(InstallStages.ExtractToolkit,
|
||||||
"Inspecting the release's local recompilation toolkit...", 1);
|
"Inspecting the release's local recompilation toolkit...", 4);
|
||||||
if (extractToolkit) payload.ExtractDirectory(InstalledLayout.ToolkitDirectoryName, toolkit);
|
if (extractToolkit) payload.ExtractDirectory(InstalledLayout.ToolkitDirectoryName, toolkit);
|
||||||
if (extractWorkspace)
|
if (extractWorkspace)
|
||||||
{
|
{
|
||||||
@@ -82,30 +104,10 @@ internal sealed class InstallerEngine
|
|||||||
payload.ExtractEntry(InstalledLayout.PayloadManifestFileName,
|
payload.ExtractEntry(InstalledLayout.PayloadManifestFileName,
|
||||||
Path.Combine(staging, InstalledLayout.PayloadManifestFileName));
|
Path.Combine(staging, InstalledLayout.PayloadManifestFileName));
|
||||||
|
|
||||||
var nodTool = extractToolkit ? Path.Combine(toolkit, "nodtool.exe") : installedNodTool;
|
|
||||||
_reporter.Progress(InstallStages.Validate, "Checking the Wii disc image...", 2);
|
|
||||||
var header = await InputValidation.ReadDiscHeaderAsync(nodTool, options.GamePath,
|
|
||||||
cancellationToken);
|
|
||||||
InputValidation.EnsureCompatibleDisc(header, manifest);
|
|
||||||
var canonicalRetroRoot = options.RetroDirectoryPath is null
|
|
||||||
? null
|
|
||||||
: RetroRewindSource.ResolveRetroRewind6(options.RetroDirectoryPath);
|
|
||||||
ValidateRetroOptions(canonicalRetroRoot, options.RetroWfcPayloadMode, manifest);
|
|
||||||
|
|
||||||
var retroCompileInputs = canonicalRetroRoot is null
|
|
||||||
? null
|
|
||||||
: SnapshotRetroRewindCompileInputs(canonicalRetroRoot,
|
|
||||||
Path.Combine(staging, "compile-inputs"), cancellationToken);
|
|
||||||
if (options.Portable)
|
|
||||||
{
|
|
||||||
var portableRoot = PortableRoot.Create(parent);
|
|
||||||
_reporter.Diagnostic($"Installing as a portable installation under {portableRoot}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sameToolkit)
|
if (sameToolkit)
|
||||||
{
|
{
|
||||||
var desiredInputDrift = ProductRepairService.FindDesiredInputDrift(existing,
|
var desiredInputDrift = ProductRepairService.FindDesiredInputDrift(existing,
|
||||||
candidateToolkitFingerprint, manifest.ExpectedDolSha256, manifest.ExpectedRelSha256);
|
candidateToolkitFingerprint, release.DolSha256, release.RelSha256);
|
||||||
if (desiredInputDrift.Retro && retroCompileInputs is null)
|
if (desiredInputDrift.Retro && retroCompileInputs is null)
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"This release changes the clean game inputs used by the installed Retro Rewind product. " +
|
"This release changes the clean game inputs used by the installed Retro Rewind product. " +
|
||||||
@@ -113,7 +115,7 @@ internal sealed class InstallerEngine
|
|||||||
|
|
||||||
var reconciliation = await ReconcileSameToolkitAsync(existing, retroCompileInputs,
|
var reconciliation = await ReconcileSameToolkitAsync(existing, retroCompileInputs,
|
||||||
canonicalRetroRoot, options.RetroWfcPayloadMode, Path.Combine(staging, "repair"),
|
canonicalRetroRoot, options.RetroWfcPayloadMode, Path.Combine(staging, "repair"),
|
||||||
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256, cancellationToken);
|
release.DolSha256, release.RelSha256, cancellationToken);
|
||||||
var remainingCancellation = reconciliation.PublicationCommitted
|
var remainingCancellation = reconciliation.PublicationCommitted
|
||||||
? CancellationToken.None
|
? CancellationToken.None
|
||||||
: cancellationToken;
|
: cancellationToken;
|
||||||
@@ -125,11 +127,10 @@ internal sealed class InstallerEngine
|
|||||||
|
|
||||||
remainingCancellation.ThrowIfCancellationRequested();
|
remainingCancellation.ThrowIfCancellationRequested();
|
||||||
EnsureReconciliationCurrent(existing, candidateToolkitFingerprint, retroCompileInputs,
|
EnsureReconciliationCurrent(existing, candidateToolkitFingerprint, retroCompileInputs,
|
||||||
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256,
|
release.DolSha256, release.RelSha256, reconciliation.CachedRetroWfcPayloadMatches);
|
||||||
reconciliation.CachedRetroWfcPayloadMatches);
|
|
||||||
|
|
||||||
var updatedState = BuildInstallState(existing.ReadInstallState(), installDirectory, manifest,
|
var updatedState = BuildInstallState(existing.ReadInstallState(), installDirectory, manifest,
|
||||||
retroCompileInputs, canonicalRetroRoot, options.RetroWfcPayloadMode);
|
release, retroCompileInputs, canonicalRetroRoot, options.RetroWfcPayloadMode);
|
||||||
PrepareMetadata(staging, manifest, updatedState);
|
PrepareMetadata(staging, manifest, updatedState);
|
||||||
remainingCancellation.ThrowIfCancellationRequested();
|
remainingCancellation.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
@@ -159,11 +160,11 @@ internal sealed class InstallerEngine
|
|||||||
if (reusableGameAssets is null)
|
if (reusableGameAssets is null)
|
||||||
{
|
{
|
||||||
await ExtractGameAssetsAsync(nodTool, options.GamePath,
|
await ExtractGameAssetsAsync(nodTool, options.GamePath,
|
||||||
Path.Combine(staging, "GameAssets"), manifest, cancellationToken);
|
Path.Combine(staging, "GameAssets"), release, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
await PublishToolkitAndReconcileProductsAsync(existing, staging, workspace, manifest,
|
await PublishToolkitAndReconcileProductsAsync(existing, staging, workspace, manifest,
|
||||||
previousState, options, canonicalRetroRoot, retroCompileInputs,
|
release, previousState, options, canonicalRetroRoot, retroCompileInputs,
|
||||||
publishGameAssets: reusableGameAssets is null, createShortcuts: firstInstall,
|
publishGameAssets: reusableGameAssets is null, createShortcuts: firstInstall,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -187,7 +188,7 @@ internal sealed class InstallerEngine
|
|||||||
["generated", "build", "native-build", "Assets", "PulsarPacks"];
|
["generated", "build", "native-build", "Assets", "PulsarPacks"];
|
||||||
|
|
||||||
private async Task PublishToolkitAndReconcileProductsAsync(Installation existing, string staging,
|
private async Task PublishToolkitAndReconcileProductsAsync(Installation existing, string staging,
|
||||||
string stagedWorkspace, PayloadManifest manifest, InstallState? previousState,
|
string stagedWorkspace, PayloadManifest manifest, GameRelease release, InstallState? previousState,
|
||||||
InstallOptions options, string? canonicalRetroRoot,
|
InstallOptions options, string? canonicalRetroRoot,
|
||||||
RetroRewindCompileInputs? retroCompileInputs,
|
RetroRewindCompileInputs? retroCompileInputs,
|
||||||
bool publishGameAssets, bool createShortcuts, CancellationToken cancellationToken)
|
bool publishGameAssets, bool createShortcuts, CancellationToken cancellationToken)
|
||||||
@@ -206,7 +207,7 @@ internal sealed class InstallerEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var state = BuildInstallState(previousState, installDirectory, manifest,
|
var state = BuildInstallState(previousState, installDirectory, manifest, release,
|
||||||
compileInputs: null, canonicalRetroRoot, options.RetroWfcPayloadMode);
|
compileInputs: null, canonicalRetroRoot, options.RetroWfcPayloadMode);
|
||||||
PrepareMetadata(staging, manifest, state);
|
PrepareMetadata(staging, manifest, state);
|
||||||
|
|
||||||
@@ -226,10 +227,9 @@ internal sealed class InstallerEngine
|
|||||||
|
|
||||||
var reconciliation = await ReconcileSameToolkitAsync(existing, retroCompileInputs,
|
var reconciliation = await ReconcileSameToolkitAsync(existing, retroCompileInputs,
|
||||||
canonicalRetroRoot, options.RetroWfcPayloadMode, repairScratch: null,
|
canonicalRetroRoot, options.RetroWfcPayloadMode, repairScratch: null,
|
||||||
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256, cancellationToken);
|
release.DolSha256, release.RelSha256, cancellationToken);
|
||||||
EnsureReconciliationCurrent(existing, manifest.ToolkitFingerprint, retroCompileInputs,
|
EnsureReconciliationCurrent(existing, manifest.ToolkitFingerprint, retroCompileInputs,
|
||||||
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256,
|
release.DolSha256, release.RelSha256, reconciliation.CachedRetroWfcPayloadMatches);
|
||||||
reconciliation.CachedRetroWfcPayloadMatches);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<ProductRepairService.ReconciliationResult> ReconcileSameToolkitAsync(Installation existing,
|
private Task<ProductRepairService.ReconciliationResult> ReconcileSameToolkitAsync(Installation existing,
|
||||||
@@ -354,7 +354,8 @@ internal sealed class InstallerEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static InstallState BuildInstallState(InstallState? previousState, string installDirectory,
|
private static InstallState BuildInstallState(InstallState? previousState, string installDirectory,
|
||||||
PayloadManifest manifest, RetroRewindCompileInputs? compileInputs, string? canonicalRetroRoot,
|
PayloadManifest manifest, GameRelease release, RetroRewindCompileInputs? compileInputs,
|
||||||
|
string? canonicalRetroRoot,
|
||||||
RetroWfcPayloadMode payloadMode,
|
RetroWfcPayloadMode payloadMode,
|
||||||
RetroWfcPayloadSnapshot? retroWfcPayloadSnapshot = null)
|
RetroWfcPayloadSnapshot? retroWfcPayloadSnapshot = null)
|
||||||
{
|
{
|
||||||
@@ -368,8 +369,8 @@ internal sealed class InstallerEngine
|
|||||||
InstalledUtc = previousState?.InstalledUtc ?? DateTime.UtcNow,
|
InstalledUtc = previousState?.InstalledUtc ?? DateTime.UtcNow,
|
||||||
RetroRewindInstalled = previousState?.RetroRewindInstalled ?? false,
|
RetroRewindInstalled = previousState?.RetroRewindInstalled ?? false,
|
||||||
ToolkitReleaseTag = manifest.ToolkitReleaseTag,
|
ToolkitReleaseTag = manifest.ToolkitReleaseTag,
|
||||||
DolSha256 = manifest.ExpectedDolSha256,
|
DolSha256 = release.DolSha256,
|
||||||
RelSha256 = manifest.ExpectedRelSha256,
|
RelSha256 = release.RelSha256,
|
||||||
RetroRewindCodePulSha256 = previousState?.RetroRewindCodePulSha256 ?? "",
|
RetroRewindCodePulSha256 = previousState?.RetroRewindCodePulSha256 ?? "",
|
||||||
RetroRewindCompileInputsSha256 = previousState?.RetroRewindCompileInputsSha256 ?? "",
|
RetroRewindCompileInputsSha256 = previousState?.RetroRewindCompileInputsSha256 ?? "",
|
||||||
RetroWfcPayloadMode = previousState?.RetroWfcPayloadMode ?? "",
|
RetroWfcPayloadMode = previousState?.RetroWfcPayloadMode ?? "",
|
||||||
@@ -444,8 +445,7 @@ internal sealed class InstallerEngine
|
|||||||
Path.Combine(InstalledLayout.Workspace(installDirectory), "runtime", "assets")));
|
Path.Combine(InstalledLayout.Workspace(installDirectory), "runtime", "assets")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateRetroOptions(string? canonicalRetroRoot, RetroWfcPayloadMode mode,
|
private static void ValidateRetroOptions(string? canonicalRetroRoot, RetroWfcPayloadMode mode)
|
||||||
PayloadManifest manifest)
|
|
||||||
{
|
{
|
||||||
if (canonicalRetroRoot is null && mode != RetroWfcPayloadMode.NotApplicable)
|
if (canonicalRetroRoot is null && mode != RetroWfcPayloadMode.NotApplicable)
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
@@ -453,11 +453,9 @@ internal sealed class InstallerEngine
|
|||||||
if (canonicalRetroRoot is not null && mode == RetroWfcPayloadMode.NotApplicable)
|
if (canonicalRetroRoot is not null && mode == RetroWfcPayloadMode.NotApplicable)
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"Download the Retro-WFC payload, or explicitly skip the optional payload.");
|
"Download the Retro-WFC payload, or explicitly skip the optional payload.");
|
||||||
if (canonicalRetroRoot is not null && mode == RetroWfcPayloadMode.Online)
|
|
||||||
InputValidation.ValidateRetroWfcPayloadUri(manifest.RetroWfcPayloadUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? FindReusableGameAssets(Installation existing, PayloadManifest manifest)
|
private string? FindReusableGameAssets(Installation existing, GameRelease release)
|
||||||
{
|
{
|
||||||
var dataRoot = existing.GameDataDirectory;
|
var dataRoot = existing.GameDataDirectory;
|
||||||
// No fst.bin simply means nothing was extracted here before; anything past that gate is an
|
// No fst.bin simply means nothing was extracted here before; anything past that gate is an
|
||||||
@@ -466,7 +464,7 @@ internal sealed class InstallerEngine
|
|||||||
if (!File.Exists(Path.Combine(dataRoot, "sys", "fst.bin"))) return null;
|
if (!File.Exists(Path.Combine(dataRoot, "sys", "fst.bin"))) return null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ValidateExtractedGame(dataRoot, manifest);
|
ValidateExtractedGame(dataRoot, release);
|
||||||
return dataRoot;
|
return dataRoot;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidDataException)
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidDataException)
|
||||||
@@ -478,7 +476,7 @@ internal sealed class InstallerEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExtractGameAssetsAsync(string nodTool, string gamePath, string destination,
|
private async Task ExtractGameAssetsAsync(string nodTool, string gamePath, string destination,
|
||||||
PayloadManifest manifest, CancellationToken cancellationToken)
|
GameRelease release, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_reporter.Progress(InstallStages.ExtractDisc,
|
_reporter.Progress(InstallStages.ExtractDisc,
|
||||||
"Extracting the game disc. This is the longest preparation step...", 6);
|
"Extracting the game disc. This is the longest preparation step...", 6);
|
||||||
@@ -493,25 +491,43 @@ internal sealed class InstallerEngine
|
|||||||
cancellationToken);
|
cancellationToken);
|
||||||
if (extraction.ExitCode != 0)
|
if (extraction.ExitCode != 0)
|
||||||
throw new InvalidDataException("Game extraction failed. " + extraction.CombinedOutput.Trim());
|
throw new InvalidDataException("Game extraction failed. " + extraction.CombinedOutput.Trim());
|
||||||
ValidateExtractedGame(dataRoot, manifest);
|
ValidateExtractedGame(dataRoot, release);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateExtractedGame(string dataRoot, PayloadManifest manifest)
|
internal static void ValidateExtractedGame(string dataRoot, GameRelease release)
|
||||||
{
|
{
|
||||||
var dol = Path.Combine(dataRoot, "sys", "main.dol");
|
var dol = Path.Combine(dataRoot, "sys", "main.dol");
|
||||||
var rel = Path.Combine(dataRoot, "files", "rel", "StaticR.rel");
|
var rel = Path.Combine(dataRoot, "files", "rel", "StaticR.rel");
|
||||||
var fst = Path.Combine(dataRoot, "sys", "fst.bin");
|
var fst = Path.Combine(dataRoot, "sys", "fst.bin");
|
||||||
if (!File.Exists(dol) || !File.Exists(rel) || !File.Exists(fst))
|
var boot = Path.Combine(dataRoot, "sys", "boot.bin");
|
||||||
throw new InvalidDataException("The disc extraction is missing required Mario Kart Wii files.");
|
var missing = new[] { dol, rel, fst, boot }.Where(path => !File.Exists(path))
|
||||||
var dolHash = InputValidation.Sha256File(dol);
|
.Select(path => Path.GetRelativePath(dataRoot, path)).ToList();
|
||||||
var relHash = InputValidation.Sha256File(rel);
|
if (missing.Count > 0)
|
||||||
if (!dolHash.Equals(manifest.ExpectedDolSha256, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
!relHash.Equals(manifest.ExpectedRelSha256, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
throw new InvalidDataException(
|
throw new InvalidDataException(
|
||||||
"The disc is RMCP01 but does not match the supported clean PAL revision. " +
|
"The disc extraction is missing required Mario Kart Wii files: " +
|
||||||
"Patched or otherwise modified game code cannot be installed safely.");
|
string.Join(", ", missing) + ".");
|
||||||
|
|
||||||
|
var mismatches = new List<string>();
|
||||||
|
var discGameId = Installation.ReadDiscGameId(dataRoot);
|
||||||
|
if (!discGameId.Equals(release.GameId, StringComparison.Ordinal))
|
||||||
|
mismatches.Add($"sys/boot.bin names {discGameId}");
|
||||||
|
foreach (var (name, path, expected) in new[]
|
||||||
|
{
|
||||||
|
("main.dol", dol, release.DolSha256),
|
||||||
|
("StaticR.rel", rel, release.RelSha256)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
var actual = InputValidation.Sha256File(path);
|
||||||
|
if (!actual.Equals(expected, StringComparison.OrdinalIgnoreCase))
|
||||||
|
mismatches.Add($"{name} is {new FileInfo(path).Length:N0} bytes with SHA-256 {actual} " +
|
||||||
|
$"(expected {expected})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mismatches.Count > 0)
|
||||||
|
throw new InvalidDataException(
|
||||||
|
$"The disc is {release.GameId} but its game data does not match the supported clean revision: " +
|
||||||
|
string.Join("; ", mismatches) + ". " +
|
||||||
|
"Patched or otherwise modified game code cannot be installed safely.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateInstallDirectory(string path) =>
|
private static void ValidateInstallDirectory(string path) =>
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ internal sealed class LocalBuildService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task BuildAsync(string installStaging, BuildProfile profile, string outputDirectory,
|
public async Task BuildAsync(string installStaging, BuildProfile profile, string outputDirectory,
|
||||||
RetroWfcPayloadMode retroWfcPayloadMode, string? retroWfcOfflinePayloadDirectory,
|
RetroWfcPayloadMode retroWfcPayloadMode, string? retroWfcOfflinePayloadDirectory,
|
||||||
CancellationToken cancellationToken, ToolkitFingerprintComponents? toolkitComponents = null,
|
string? retroWfcGameId, CancellationToken cancellationToken,
|
||||||
|
ToolkitFingerprintComponents? toolkitComponents = null,
|
||||||
bool forceCleanBuild = false, BuildProgressWindow? progress = null,
|
bool forceCleanBuild = false, BuildProgressWindow? progress = null,
|
||||||
string? retroRewindPackageDirectory = null, string? baseOutputDirectory = null)
|
string? retroRewindPackageDirectory = null, string? baseOutputDirectory = null)
|
||||||
{
|
{
|
||||||
@@ -45,8 +46,9 @@ internal sealed class LocalBuildService
|
|||||||
retroRewindPackageDirectory = RetroRewindSource.ResolveRetroRewind6(
|
retroRewindPackageDirectory = RetroRewindSource.ResolveRetroRewind6(
|
||||||
retroRewindPackageDirectory);
|
retroRewindPackageDirectory);
|
||||||
if (retroWfcPayloadMode == RetroWfcPayloadMode.Online)
|
if (retroWfcPayloadMode == RetroWfcPayloadMode.Online)
|
||||||
retroWfcOfflinePayloadDirectory =
|
retroWfcOfflinePayloadDirectory = InputValidation.ValidateStagedRetroWfcPayloadDirectory(
|
||||||
InputValidation.ValidateStagedRetroWfcPayloadDirectory(retroWfcOfflinePayloadDirectory!);
|
retroWfcOfflinePayloadDirectory!,
|
||||||
|
retroWfcGameId ?? throw new ArgumentException("The Retro-WFC payload needs its game id."));
|
||||||
|
|
||||||
var arguments = new List<string>
|
var arguments = new List<string>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,16 +33,23 @@ internal sealed class InstallOptions
|
|||||||
public bool HasRetroRewind => RetroDirectoryPath is not null;
|
public bool HasRetroRewind => RetroDirectoryPath is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>A clean retail release the payload installs, as pinned by its projects/*/recomp.yml.</summary>
|
||||||
|
internal sealed class GameRelease
|
||||||
|
{
|
||||||
|
public string GameId { get; set; } = "";
|
||||||
|
public string DolSha256 { get; set; } = "";
|
||||||
|
public string RelSha256 { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class PayloadManifest
|
internal sealed class PayloadManifest
|
||||||
{
|
{
|
||||||
|
public const int CurrentSchemaVersion = 3;
|
||||||
|
|
||||||
public int SchemaVersion { get; set; }
|
public int SchemaVersion { get; set; }
|
||||||
public string ProductVersion { get; set; } = "";
|
public string ProductVersion { get; set; } = "";
|
||||||
public string ExpectedGameId { get; set; } = "RMCP01";
|
public List<GameRelease> SupportedReleases { get; set; } = [];
|
||||||
public string ExpectedDolSha256 { get; set; } = "";
|
|
||||||
public string ExpectedRelSha256 { get; set; } = "";
|
|
||||||
public string ToolkitReleaseTag { get; set; } = "";
|
public string ToolkitReleaseTag { get; set; } = "";
|
||||||
public string BuildModel { get; set; } = "";
|
public string BuildModel { get; set; } = "";
|
||||||
public string RetroWfcPayloadUri { get; set; } = "";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Content identities computed once at release-build time (<c>Build-Installer.ps1 --emit-payload-identities</c>)
|
/// Content identities computed once at release-build time (<c>Build-Installer.ps1 --emit-payload-identities</c>)
|
||||||
|
|||||||
@@ -54,10 +54,21 @@ internal sealed class PayloadArchive : IDisposable
|
|||||||
var entry = FindEntry(InstalledLayout.PayloadManifestFileName)
|
var entry = FindEntry(InstalledLayout.PayloadManifestFileName)
|
||||||
?? throw new InvalidDataException("The payload manifest is missing.");
|
?? throw new InvalidDataException("The payload manifest is missing.");
|
||||||
using var stream = entry.Open();
|
using var stream = entry.Open();
|
||||||
|
return ParseManifest(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static PayloadManifest ParseManifest(Stream stream)
|
||||||
|
{
|
||||||
var manifest = JsonSerializer.Deserialize<PayloadManifest>(stream,
|
var manifest = JsonSerializer.Deserialize<PayloadManifest>(stream,
|
||||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
|
||||||
?? throw new InvalidDataException("The payload manifest is invalid.");
|
?? throw new InvalidDataException("The payload manifest is invalid.");
|
||||||
if (manifest.SchemaVersion != 2)
|
ValidateManifest(manifest);
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ValidateManifest(PayloadManifest manifest)
|
||||||
|
{
|
||||||
|
if (manifest.SchemaVersion != PayloadManifest.CurrentSchemaVersion)
|
||||||
throw new InvalidDataException($"Unsupported payload schema {manifest.SchemaVersion}.");
|
throw new InvalidDataException($"Unsupported payload schema {manifest.SchemaVersion}.");
|
||||||
// The payload identities are the release-computed replacement for hashing the whole
|
// The payload identities are the release-computed replacement for hashing the whole
|
||||||
// toolkit on the user's machine; a payload without them was packaged incorrectly.
|
// toolkit on the user's machine; a payload without them was packaged incorrectly.
|
||||||
@@ -65,8 +76,30 @@ internal sealed class PayloadArchive : IDisposable
|
|||||||
string.IsNullOrWhiteSpace(manifest.ToolkitPackageFingerprint) ||
|
string.IsNullOrWhiteSpace(manifest.ToolkitPackageFingerprint) ||
|
||||||
string.IsNullOrWhiteSpace(manifest.RuntimeAssetsFingerprint))
|
string.IsNullOrWhiteSpace(manifest.RuntimeAssetsFingerprint))
|
||||||
throw new InvalidDataException("The payload manifest is missing its content identities.");
|
throw new InvalidDataException("The payload manifest is missing its content identities.");
|
||||||
InputValidation.ValidateRetroWfcPayloadUri(manifest.RetroWfcPayloadUri);
|
if (manifest.SupportedReleases is not { Count: > 0 })
|
||||||
return manifest;
|
throw new InvalidDataException("The payload manifest names no supported game release.");
|
||||||
|
foreach (var release in manifest.SupportedReleases)
|
||||||
|
{
|
||||||
|
if (!RetroWfcPayload.SupportedGameIds.Contains(release.GameId) ||
|
||||||
|
!IsSha256(release.DolSha256) || !IsSha256(release.RelSha256))
|
||||||
|
throw new InvalidDataException(
|
||||||
|
$"The payload manifest describes an unsupported or incomplete release '{release.GameId}'.");
|
||||||
|
}
|
||||||
|
if (manifest.SupportedReleases.DistinctBy(release => release.GameId).Count() !=
|
||||||
|
manifest.SupportedReleases.Count)
|
||||||
|
throw new InvalidDataException("The payload manifest lists a game release twice.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSha256(string value) =>
|
||||||
|
value.Length == 64 && value.All(char.IsAsciiHexDigit);
|
||||||
|
|
||||||
|
/// <summary>Extracts nodtool and the runtime DLLs it loads; returns the nodtool path.</summary>
|
||||||
|
public string ExtractDiscTool(string directory)
|
||||||
|
{
|
||||||
|
var tool = Path.Combine(directory, "nodtool.exe");
|
||||||
|
ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "nodtool.exe", tool);
|
||||||
|
ExtractDirectory(InstalledLayout.ToolkitEntryPrefix + "Redist", directory);
|
||||||
|
return tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExtractEntry(string entryName, string destination)
|
public void ExtractEntry(string entryName, string destination)
|
||||||
|
|||||||
@@ -99,7 +99,11 @@ internal sealed class ProductRepairService
|
|||||||
Directory.CreateDirectory(scratchRoot);
|
Directory.CreateDirectory(scratchRoot);
|
||||||
|
|
||||||
RetroWfcPayloadSnapshot? payloadSnapshot = null;
|
RetroWfcPayloadSnapshot? payloadSnapshot = null;
|
||||||
if (requestedPayloadMode == RetroWfcPayloadMode.Online)
|
// The installed disc's region decides which Retro-WFC payload the product embeds.
|
||||||
|
var payloadGameId = requestedPayloadMode == RetroWfcPayloadMode.Online
|
||||||
|
? _installation.ReadGameId()
|
||||||
|
: null;
|
||||||
|
if (payloadGameId is not null)
|
||||||
{
|
{
|
||||||
_reporter.Progress(InstallStages.PrepareRetro,
|
_reporter.Progress(InstallStages.PrepareRetro,
|
||||||
"Downloading the current shared Retro-WFC payload snapshot...", 3);
|
"Downloading the current shared Retro-WFC payload snapshot...", 3);
|
||||||
@@ -107,13 +111,12 @@ internal sealed class ProductRepairService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
payloadSnapshot = await InputValidation.DownloadRetroWfcPayloadAsync(
|
payloadSnapshot = await InputValidation.DownloadRetroWfcPayloadAsync(
|
||||||
InputValidation.CurrentRetroWfcPayloadUri, payloadScratch, cancellationToken);
|
RetroWfcPayload.PayloadUri(payloadGameId), payloadScratch, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (!cancellationToken.IsCancellationRequested &&
|
catch (Exception ex) when (!cancellationToken.IsCancellationRequested &&
|
||||||
ex is HttpRequestException or IOException or InvalidDataException
|
ex is HttpRequestException or TimeoutException or IOException)
|
||||||
or InvalidOperationException or OperationCanceledException)
|
|
||||||
{
|
{
|
||||||
payloadSnapshot = RecoverInstalledRetroWfcPayload(toolkitFingerprint,
|
payloadSnapshot = RecoverInstalledRetroWfcPayload(toolkitFingerprint, payloadGameId,
|
||||||
Path.Combine(scratchRoot, "retro-wfc-payload-recovered"), ex, cancellationToken);
|
Path.Combine(scratchRoot, "retro-wfc-payload-recovered"), ex, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +240,7 @@ internal sealed class ProductRepairService
|
|||||||
"Recompiling Mario Kart Wii and Retro Rewind with the installed toolkit...", 5);
|
"Recompiling Mario Kart Wii and Retro Rewind with the installed toolkit...", 5);
|
||||||
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
|
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
|
||||||
builder.BuildAsync(_installation.Root, BuildProfile.Both, retroOutput,
|
builder.BuildAsync(_installation.Root, BuildProfile.Both, retroOutput,
|
||||||
requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken,
|
requestedPayloadMode, payloadSnapshot?.Directory, payloadGameId, cancellationToken,
|
||||||
toolkitComponents, clean,
|
toolkitComponents, clean,
|
||||||
progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92),
|
progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92),
|
||||||
retroRewindPackageDirectory: compileInputs.RetroRewindRoot,
|
retroRewindPackageDirectory: compileInputs.RetroRewindRoot,
|
||||||
@@ -256,7 +259,7 @@ internal sealed class ProductRepairService
|
|||||||
"Recompiling Mario Kart Wii with the installed toolkit...", 5);
|
"Recompiling Mario Kart Wii with the installed toolkit...", 5);
|
||||||
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
|
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
|
||||||
builder.BuildAsync(_installation.Root, BuildProfile.Base, baseOutput,
|
builder.BuildAsync(_installation.Root, BuildProfile.Base, baseOutput,
|
||||||
RetroWfcPayloadMode.NotApplicable, null, cancellationToken,
|
RetroWfcPayloadMode.NotApplicable, null, null, cancellationToken,
|
||||||
toolkitComponents, clean,
|
toolkitComponents, clean,
|
||||||
progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92)));
|
progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92)));
|
||||||
LocalBuildService.WriteFingerprint(baseOutput, BuildProfile.Base, toolkitFingerprint,
|
LocalBuildService.WriteFingerprint(baseOutput, BuildProfile.Base, toolkitFingerprint,
|
||||||
@@ -271,7 +274,7 @@ internal sealed class ProductRepairService
|
|||||||
"Recompiling Retro Rewind for the canonical Code.pul...", 5);
|
"Recompiling Retro Rewind for the canonical Code.pul...", 5);
|
||||||
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
|
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
|
||||||
builder.BuildAsync(_installation.Root, BuildProfile.RetroRewind, retroOutput,
|
builder.BuildAsync(_installation.Root, BuildProfile.RetroRewind, retroOutput,
|
||||||
requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken,
|
requestedPayloadMode, payloadSnapshot?.Directory, payloadGameId, cancellationToken,
|
||||||
toolkitComponents, clean,
|
toolkitComponents, clean,
|
||||||
progress: new BuildProgressWindow(_reporter, InstallStages.BuildRetro, 5, 92),
|
progress: new BuildProgressWindow(_reporter, InstallStages.BuildRetro, 5, 92),
|
||||||
retroRewindPackageDirectory: compileInputs.RetroRewindRoot));
|
retroRewindPackageDirectory: compileInputs.RetroRewindRoot));
|
||||||
@@ -311,7 +314,8 @@ internal sealed class ProductRepairService
|
|||||||
|
|
||||||
|
|
||||||
private RetroWfcPayloadSnapshot RecoverInstalledRetroWfcPayload(string toolkitFingerprint,
|
private RetroWfcPayloadSnapshot RecoverInstalledRetroWfcPayload(string toolkitFingerprint,
|
||||||
string destinationDirectory, Exception downloadFailure, CancellationToken cancellationToken)
|
string gameId, string destinationDirectory, Exception downloadFailure,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
InvalidOperationException Unavailable(string reason) => new(
|
InvalidOperationException Unavailable(string reason) => new(
|
||||||
"The Retro-WFC payload service is unavailable" +
|
"The Retro-WFC payload service is unavailable" +
|
||||||
@@ -335,7 +339,7 @@ internal sealed class ProductRepairService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
cachedFile = InputValidation.ResolveRetroWfcPayloadFile(
|
cachedFile = InputValidation.ResolveRetroWfcPayloadFile(
|
||||||
_installation.WorkspaceRetroWfcPayload);
|
_installation.WorkspaceRetroWfcPayload, gameId);
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
if (new FileInfo(cachedFile).Length != fingerprint.RetroWfcPayloadLength ||
|
if (new FileInfo(cachedFile).Length != fingerprint.RetroWfcPayloadLength ||
|
||||||
!InputValidation.Sha256File(cachedFile).Equals(fingerprint.RetroWfcPayloadSha256,
|
!InputValidation.Sha256File(cachedFile).Equals(fingerprint.RetroWfcPayloadSha256,
|
||||||
@@ -344,11 +348,11 @@ internal sealed class ProductRepairService
|
|||||||
throw Unavailable("the installed payload snapshot no longer matches its recorded identity.");
|
throw Unavailable("the installed payload snapshot no longer matches its recorded identity.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var destination = Path.Combine(Path.GetFullPath(destinationDirectory), "binary",
|
var destination = Path.Combine(Path.GetFullPath(destinationDirectory),
|
||||||
"payload.RMCPD00.bin");
|
RetroWfcPayload.PayloadRelativePath(gameId));
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
|
||||||
File.Copy(cachedFile, destination, overwrite: true);
|
File.Copy(cachedFile, destination, overwrite: true);
|
||||||
var root = InputValidation.ValidateStagedRetroWfcPayloadDirectory(destinationDirectory);
|
var root = InputValidation.ValidateStagedRetroWfcPayloadDirectory(destinationDirectory, gameId);
|
||||||
_reporter.Diagnostic(
|
_reporter.Diagnostic(
|
||||||
"The Retro-WFC payload service is unavailable; reusing the installation's verified " +
|
"The Retro-WFC payload service is unavailable; reusing the installation's verified " +
|
||||||
$"payload snapshot (sha256 {fingerprint.RetroWfcPayloadSha256[..12]}..., " +
|
$"payload snapshot (sha256 {fingerprint.RetroWfcPayloadSha256[..12]}..., " +
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace WiiCompiled.Setup.Windows;
|
namespace WiiCompiled.Setup.Windows;
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
internal static class Program
|
internal static class Program
|
||||||
@@ -30,7 +31,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
// A command line was supplied, so a caller is driving this process. Rejecting the
|
// A command line was supplied, so a caller is driving this process. Rejecting the
|
||||||
// command with a modal dialog would hang that caller forever.
|
// command with a modal dialog would hang that caller forever.
|
||||||
if (progressJson) new NdjsonInstallReporter().Failure(ex.Message);
|
if (progressJson) new NdjsonInstallReporter(ProductInfo.Version).Failure(ex.Message);
|
||||||
else Console.Error.WriteLine(ex.Message);
|
else Console.Error.WriteLine(ex.Message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -90,7 +91,7 @@ internal static class Program
|
|||||||
// errors raised outside it.
|
// errors raised outside it.
|
||||||
if (progressJson)
|
if (progressJson)
|
||||||
{
|
{
|
||||||
new NdjsonInstallReporter().Failure(ex.Message);
|
new NdjsonInstallReporter(ProductInfo.Version).Failure(ex.Message);
|
||||||
Console.Error.WriteLine(ex);
|
Console.Error.WriteLine(ex);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -121,7 +122,9 @@ 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.27";
|
public static readonly string Version =
|
||||||
|
typeof(ProductInfo).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
|
||||||
|
.InformationalVersion;
|
||||||
|
|
||||||
/// <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
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using Tomlyn;
|
||||||
using WiiCompiled.Setup.Common;
|
using WiiCompiled.Setup.Common;
|
||||||
|
|
||||||
namespace WiiCompiled.Setup.Windows;
|
namespace WiiCompiled.Setup.Windows;
|
||||||
@@ -27,19 +28,9 @@ internal static class SelfTests
|
|||||||
}
|
}
|
||||||
finally { File.Delete(path); }
|
finally { File.Delete(path); }
|
||||||
}, failures);
|
}, failures);
|
||||||
Test("Disc compatibility", () =>
|
Test("Disc compatibility", TestDiscCompatibility, failures);
|
||||||
InputValidation.EnsureCompatibleDisc(new DiscHeader { GameId = "RMCP01" },
|
Test("Payload manifest releases", TestPayloadManifestReleases, failures);
|
||||||
new PayloadManifest { ExpectedGameId = "RMCP01" }), failures);
|
Test("Extracted game release validation", TestExtractedGameRelease, failures);
|
||||||
Test("Disc rejection", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InputValidation.EnsureCompatibleDisc(new DiscHeader { GameId = "RMCJ01" },
|
|
||||||
new PayloadManifest { ExpectedGameId = "RMCP01" });
|
|
||||||
throw new Exception("Wrong region was accepted.");
|
|
||||||
}
|
|
||||||
catch (InvalidDataException) { }
|
|
||||||
}, failures);
|
|
||||||
Test("Payload timestamp normalization", TestPayloadTimestampNormalization, failures);
|
Test("Payload timestamp normalization", TestPayloadTimestampNormalization, failures);
|
||||||
Test("Workspace timestamp reconciliation", TestWorkspaceTimestampReconciliation, failures);
|
Test("Workspace timestamp reconciliation", TestWorkspaceTimestampReconciliation, failures);
|
||||||
Test("Toolkit accompanies the workspace", TestToolkitRefreshDecision, failures);
|
Test("Toolkit accompanies the workspace", TestToolkitRefreshDecision, failures);
|
||||||
@@ -51,6 +42,7 @@ internal static class SelfTests
|
|||||||
RuntimeConfiguration.SetRetroRewindRoot, RuntimeConfiguration.RemoveRetroRewindRootIfOwned),
|
RuntimeConfiguration.SetRetroRewindRoot, RuntimeConfiguration.RemoveRetroRewindRootIfOwned),
|
||||||
failures);
|
failures);
|
||||||
Test("Runtime configuration key matching", TestRuntimeConfigurationKeyMatching, failures);
|
Test("Runtime configuration key matching", TestRuntimeConfigurationKeyMatching, failures);
|
||||||
|
Test("Runtime configuration quoted keys", TestRuntimeConfigurationQuotedKeys, failures);
|
||||||
Test("Install location validation", TestInstallLocationValidation, failures);
|
Test("Install location validation", TestInstallLocationValidation, failures);
|
||||||
Test("Command line contract", TestCommandLineContract, failures);
|
Test("Command line contract", TestCommandLineContract, failures);
|
||||||
Test("Portable root discovery", TestPortableRootDiscovery, failures);
|
Test("Portable root discovery", TestPortableRootDiscovery, failures);
|
||||||
@@ -75,6 +67,152 @@ internal static class SelfTests
|
|||||||
return failures.Count == 0 ? 0 : 1;
|
return failures.Count == 0 ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PayloadManifest MultiReleaseManifest() => new()
|
||||||
|
{
|
||||||
|
SchemaVersion = PayloadManifest.CurrentSchemaVersion,
|
||||||
|
ToolkitFingerprint = "toolkit", ToolkitPackageFingerprint = "package", RuntimeAssetsFingerprint = "assets",
|
||||||
|
SupportedReleases =
|
||||||
|
[
|
||||||
|
new GameRelease { GameId = "RMCP01", DolSha256 = new string('1', 64), RelSha256 = new string('2', 64) },
|
||||||
|
new GameRelease { GameId = "RMCE01", DolSha256 = new string('3', 64), RelSha256 = new string('4', 64) },
|
||||||
|
new GameRelease { GameId = "RMCJ01", DolSha256 = new string('5', 64), RelSha256 = new string('6', 64) }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
private static void TestDiscCompatibility()
|
||||||
|
{
|
||||||
|
var manifest = MultiReleaseManifest();
|
||||||
|
foreach (var release in manifest.SupportedReleases)
|
||||||
|
{
|
||||||
|
var selected = InputValidation.EnsureCompatibleDisc(
|
||||||
|
new DiscHeader { GameId = release.GameId.ToLowerInvariant() }, manifest);
|
||||||
|
if (!ReferenceEquals(selected, release))
|
||||||
|
throw new Exception($"{release.GameId} did not select its own release.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var unsupported in new[] { "RMCK01", "RMCP02", "RSBE01" })
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputValidation.EnsureCompatibleDisc(new DiscHeader { GameId = unsupported }, manifest);
|
||||||
|
throw new Exception($"{unsupported} was accepted.");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException ex) when (ex.Message.Contains("NTSC-U (RMCE01)", StringComparison.Ordinal) &&
|
||||||
|
ex.Message.Contains(unsupported, StringComparison.Ordinal)) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TestPayloadManifestReleases()
|
||||||
|
{
|
||||||
|
PayloadArchive.ValidateManifest(MultiReleaseManifest());
|
||||||
|
|
||||||
|
// The shape Build-Installer.ps1 writes (PowerShell's ConvertTo-Json of the ordered manifest).
|
||||||
|
var dol = new string('a', 64);
|
||||||
|
var rel = new string('b', 64);
|
||||||
|
var json = $$"""
|
||||||
|
{
|
||||||
|
"SchemaVersion": 3,
|
||||||
|
"ProductVersion": "1.0.0",
|
||||||
|
"SupportedReleases": [
|
||||||
|
{ "GameId": "RMCP01", "DolSha256": "{{dol}}", "RelSha256": "{{rel}}" },
|
||||||
|
{ "GameId": "RMCE01", "DolSha256": "{{rel}}", "RelSha256": "{{dol}}" }
|
||||||
|
],
|
||||||
|
"ToolkitReleaseTag": "",
|
||||||
|
"BuildModel": "local-translation-and-compilation",
|
||||||
|
"ToolkitFingerprint": "toolkit",
|
||||||
|
"ToolkitPackageFingerprint": "package",
|
||||||
|
"RuntimeAssetsFingerprint": "assets",
|
||||||
|
"TranslationFingerprint": "translation",
|
||||||
|
"NativeToolchainFingerprint": "native"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)))
|
||||||
|
{
|
||||||
|
var parsed = PayloadArchive.ParseManifest(stream);
|
||||||
|
if (parsed.SupportedReleases.Count != 2 || parsed.SupportedReleases[1].GameId != "RMCE01" ||
|
||||||
|
parsed.SupportedReleases[1].DolSha256 != rel)
|
||||||
|
throw new Exception("The packaged release list was not read back exactly.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reject(string description, Action<PayloadManifest> corrupt)
|
||||||
|
{
|
||||||
|
var manifest = MultiReleaseManifest();
|
||||||
|
corrupt(manifest);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PayloadArchive.ValidateManifest(manifest);
|
||||||
|
throw new Exception($"A manifest with {description} was accepted.");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
Reject("the previous single-release schema", manifest => manifest.SchemaVersion = 2);
|
||||||
|
Reject("no releases", manifest => manifest.SupportedReleases.Clear());
|
||||||
|
Reject("an unsupported release", manifest => manifest.SupportedReleases[0].GameId = "RMCK01");
|
||||||
|
Reject("a missing input digest", manifest => manifest.SupportedReleases[1].RelSha256 = "");
|
||||||
|
Reject("a duplicated release", manifest =>
|
||||||
|
manifest.SupportedReleases.Add(new GameRelease
|
||||||
|
{
|
||||||
|
GameId = "RMCE01", DolSha256 = new string('7', 64), RelSha256 = new string('8', 64)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TestExtractedGameRelease()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(Path.GetTempPath(), "mkwc-release-" + Guid.NewGuid().ToString("N"));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = Path.Combine(root, "GameAssets", "DATA");
|
||||||
|
Directory.CreateDirectory(Path.Combine(data, "sys"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(data, "files", "rel"));
|
||||||
|
File.WriteAllText(Path.Combine(data, "sys", "main.dol"), "ntsc-u dol");
|
||||||
|
File.WriteAllText(Path.Combine(data, "sys", "fst.bin"), "fst");
|
||||||
|
File.WriteAllText(Path.Combine(data, "files", "rel", "StaticR.rel"), "ntsc-u rel");
|
||||||
|
File.WriteAllText(Path.Combine(data, "sys", "boot.bin"), "RMCE01" + new string('\0', 26));
|
||||||
|
var release = new GameRelease
|
||||||
|
{
|
||||||
|
GameId = "RMCE01",
|
||||||
|
DolSha256 = InputValidation.Sha256File(Path.Combine(data, "sys", "main.dol")),
|
||||||
|
RelSha256 = InputValidation.Sha256File(Path.Combine(data, "files", "rel", "StaticR.rel"))
|
||||||
|
};
|
||||||
|
|
||||||
|
InstallerEngine.ValidateExtractedGame(data, release);
|
||||||
|
if (new Installation(root).ReadGameId() != "RMCE01")
|
||||||
|
throw new Exception("The installed game id was not read from the extracted disc header.");
|
||||||
|
|
||||||
|
void Reject(string description, GameRelease expected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InstallerEngine.ValidateExtractedGame(data, expected);
|
||||||
|
throw new Exception($"{description} was accepted.");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
Reject("Another release's digests", new GameRelease
|
||||||
|
{
|
||||||
|
GameId = "RMCE01", DolSha256 = new string('1', 64), RelSha256 = release.RelSha256
|
||||||
|
});
|
||||||
|
Reject("A disc header of another region", new GameRelease
|
||||||
|
{
|
||||||
|
GameId = "RMCJ01", DolSha256 = release.DolSha256, RelSha256 = release.RelSha256
|
||||||
|
});
|
||||||
|
File.Delete(Path.Combine(data, "sys", "boot.bin"));
|
||||||
|
Reject("An extraction without a disc header", release);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = new Installation(root).ReadGameId();
|
||||||
|
throw new Exception("A missing disc header produced a game id.");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException) { }
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void TestAcceptedExtension(string extension)
|
private static void TestAcceptedExtension(string extension)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + extension);
|
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + extension);
|
||||||
@@ -229,6 +367,70 @@ internal static class SelfTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quoted and bare spellings of a key or table are the same TOML key, so rewriting replaces the
|
||||||
|
/// entry in place, keeps everything else byte-identical, and never touches an unparseable file.
|
||||||
|
/// </summary>
|
||||||
|
private static void TestRuntimeConfigurationQuotedKeys()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(Path.GetTempPath(), "mkwc-tomlquoted-" + Guid.NewGuid().ToString("N"));
|
||||||
|
var config = Path.Combine(root, RuntimeConfiguration.ConfigFileName);
|
||||||
|
var owned = Path.Combine(root, "Owned Location");
|
||||||
|
var fullOwned = Path.GetFullPath(owned);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(owned);
|
||||||
|
foreach (var header in new[] { "[\"paths\"]", "[ 'paths' ]", "[paths]" })
|
||||||
|
foreach (var key in new[] { "\"dvd_root\"", "'dvd_root'", "\"dvd\\u005Froot\"", "dvd_root" })
|
||||||
|
{
|
||||||
|
File.WriteAllText(config,
|
||||||
|
$"# top\r\n[video]\r\nwidescreen = true # keep\r\n\r\n{header}\r\n {key}=\"D:\\\\Old\" # note\r\nother = 1\r\n");
|
||||||
|
RuntimeConfiguration.SetDvdRoot(config, owned);
|
||||||
|
var text = File.ReadAllText(config);
|
||||||
|
var expected = $"# top\r\n[video]\r\nwidescreen = true # keep\r\n\r\n{header}\r\n {key}=" +
|
||||||
|
$"\"{fullOwned.Replace("\\", "\\\\")}\" # note\r\nother = 1\r\n";
|
||||||
|
if (text != expected)
|
||||||
|
throw new Exception($"{header} {key} was not replaced in place:\r\n{text}");
|
||||||
|
if (RuntimeConfiguration.GetResolvedPath(config, "dvd_root") != fullOwned)
|
||||||
|
throw new Exception($"The replaced {key} could not be read back.");
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(config, "[paths]\r\nother = 1\r\n\r\n# next\r\n[video]\r\nx = 2");
|
||||||
|
RuntimeConfiguration.SetDvdRoot(config, owned);
|
||||||
|
var appended = File.ReadAllText(config);
|
||||||
|
if (!appended.StartsWith("[paths]\r\nother = 1\r\ndvd_root = ", StringComparison.Ordinal) ||
|
||||||
|
!appended.EndsWith("\r\n\r\n# next\r\n[video]\r\nx = 2", StringComparison.Ordinal))
|
||||||
|
throw new Exception("A missing key was not appended to its section:\r\n" + appended);
|
||||||
|
|
||||||
|
File.WriteAllText(config, "dvd_root = \"D:\\\\Root\"\r\n[video]\r\nx = 2\r\n");
|
||||||
|
RuntimeConfiguration.SetDvdRoot(config, owned);
|
||||||
|
var created = File.ReadAllText(config);
|
||||||
|
if (!created.StartsWith("dvd_root = \"D:\\\\Root\"\r\n[video]\r\nx = 2\r\n\r\n[paths]\r\n", StringComparison.Ordinal) ||
|
||||||
|
RuntimeConfiguration.GetResolvedPath(config, "dvd_root") != fullOwned)
|
||||||
|
throw new Exception("A missing section was not created without touching a same-named root key:\r\n" + created);
|
||||||
|
|
||||||
|
File.Delete(config);
|
||||||
|
RuntimeConfiguration.SetDvdRoot(config, owned);
|
||||||
|
if (RuntimeConfiguration.GetResolvedPath(config, "dvd_root") != fullOwned)
|
||||||
|
throw new Exception("A missing config file was not created.");
|
||||||
|
|
||||||
|
const string broken = "[paths]\r\ndvd_root = 1\r\ndvd_root = 2\r\n";
|
||||||
|
File.WriteAllText(config, broken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RuntimeConfiguration.SetDvdRoot(config, owned);
|
||||||
|
throw new Exception("An unparseable config was rewritten.");
|
||||||
|
}
|
||||||
|
catch (TomlException) { }
|
||||||
|
if (File.ReadAllText(config) != broken)
|
||||||
|
throw new Exception("An unparseable config was modified.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// One location rule set covers both an installation directory and a portable root, so the
|
/// One location rule set covers both an installation directory and a portable root, so the
|
||||||
/// rejections a portable install performs are the ones an ordinary install performs too.
|
/// rejections a portable install performs are the ones an ordinary install performs too.
|
||||||
@@ -628,42 +830,91 @@ internal static class SelfTests
|
|||||||
|
|
||||||
private static void TestRetroWfcPayloadSnapshotReconciliation()
|
private static void TestRetroWfcPayloadSnapshotReconciliation()
|
||||||
{
|
{
|
||||||
// The endpoint is pinned in exactly one place; this test must not restate it.
|
|
||||||
const string endpoint = InputValidation.CurrentRetroWfcPayloadUri;
|
|
||||||
const string shaB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
const string shaB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
||||||
var root = Path.Combine(Path.GetTempPath(), "mkwc-wfc-snapshot-" + Guid.NewGuid().ToString("N"));
|
var root = Path.Combine(Path.GetTempPath(), "mkwc-wfc-snapshot-" + Guid.NewGuid().ToString("N"));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
InputValidation.ValidateRetroWfcPayloadUri(endpoint);
|
using var signingRsa = RSA.Create(2048);
|
||||||
|
var signingKey = signingRsa.ExportParameters(includePrivateParameters: false);
|
||||||
|
byte[] SignedPayload(string payloadGame)
|
||||||
|
{
|
||||||
|
var image = new byte[0x150];
|
||||||
|
"WWFC/Payload"u8.CopyTo(image);
|
||||||
|
BinaryPrimitives.WriteUInt32BigEndian(image.AsSpan(0x0C), (uint)image.Length);
|
||||||
|
System.Text.Encoding.ASCII.GetBytes(payloadGame).CopyTo(image, 0x138);
|
||||||
|
image[^1] = 0x5A;
|
||||||
|
signingRsa.SignData(image.AsSpan(0x110), HashAlgorithmName.SHA256,
|
||||||
|
RSASignaturePadding.Pkcs1).CopyTo(image.AsSpan(0x10));
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every supported release has its own endpoint and staged file, and a payload staged for
|
||||||
|
// one region never validates as another region's.
|
||||||
|
var endpoints = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
foreach (var gameId in RetroWfcPayload.SupportedGameIds)
|
||||||
|
{
|
||||||
|
var endpoint = RetroWfcPayload.PayloadUri(gameId);
|
||||||
|
InputValidation.ValidateRetroWfcPayloadUri(endpoint);
|
||||||
|
if (!endpoints.Add(endpoint))
|
||||||
|
throw new Exception($"{gameId} shares another release's Retro-WFC endpoint.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputValidation.ValidateRetroWfcPayloadUri(endpoint + "&extra=1");
|
||||||
|
throw new Exception("A non-canonical Retro-WFC endpoint was accepted.");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException) { }
|
||||||
|
|
||||||
|
var regionalRoot = Path.Combine(root, "regional-" + gameId);
|
||||||
|
var regionalPath = Path.Combine(regionalRoot, RetroWfcPayload.PayloadRelativePath(gameId));
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(regionalPath)!);
|
||||||
|
File.WriteAllBytes(regionalPath, SignedPayload(RetroWfcPayload.PayloadGame(gameId)));
|
||||||
|
if (InputValidation.ResolveRetroWfcPayloadFile(regionalRoot, gameId, signingKey) != regionalPath)
|
||||||
|
throw new Exception($"{gameId} did not resolve its own staged payload file.");
|
||||||
|
foreach (var otherGameId in RetroWfcPayload.SupportedGameIds.Where(other => other != gameId))
|
||||||
|
{
|
||||||
|
var foreignRoot = Path.Combine(root, $"foreign-{gameId}-{otherGameId}");
|
||||||
|
var foreignPath = Path.Combine(foreignRoot, RetroWfcPayload.PayloadRelativePath(otherGameId));
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(foreignPath)!);
|
||||||
|
File.WriteAllBytes(foreignPath, SignedPayload(RetroWfcPayload.PayloadGame(gameId)));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(foreignRoot, otherGameId,
|
||||||
|
signingKey);
|
||||||
|
throw new Exception($"A {gameId} payload was accepted for {otherGameId}.");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException) { }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(regionalRoot, otherGameId,
|
||||||
|
signingKey);
|
||||||
|
throw new Exception($"{otherGameId} accepted a directory staged only for {gameId}.");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
InputValidation.ValidateRetroWfcPayloadUri(endpoint + "&extra=1");
|
_ = RetroWfcPayload.PayloadUri("RMCK01");
|
||||||
throw new Exception("A non-canonical Retro-WFC endpoint was accepted.");
|
throw new Exception("An unsupported release has a Retro-WFC endpoint.");
|
||||||
}
|
}
|
||||||
catch (InvalidDataException) { }
|
catch (InvalidDataException) { }
|
||||||
|
|
||||||
|
const string gameUnderTest = "RMCE01";
|
||||||
var installation = new Installation(root);
|
var installation = new Installation(root);
|
||||||
Directory.CreateDirectory(installation.RetroDirectory);
|
Directory.CreateDirectory(installation.RetroDirectory);
|
||||||
var payloadRoot = Path.Combine(root, "download");
|
var payloadRoot = Path.Combine(root, "download");
|
||||||
var payloadPath = Path.Combine(payloadRoot, "binary", "payload.RMCPD00.bin");
|
var payloadPath = Path.Combine(payloadRoot, RetroWfcPayload.PayloadRelativePath(gameUnderTest));
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(payloadPath)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(payloadPath)!);
|
||||||
using var signingRsa = RSA.Create(2048);
|
var bytes = SignedPayload(RetroWfcPayload.PayloadGame(gameUnderTest));
|
||||||
var signingKey = signingRsa.ExportParameters(includePrivateParameters: false);
|
|
||||||
var bytes = new byte[0x140];
|
|
||||||
"WWFC/Payload"u8.CopyTo(bytes);
|
|
||||||
BinaryPrimitives.WriteUInt32BigEndian(bytes.AsSpan(0x0C), (uint)bytes.Length);
|
|
||||||
bytes[^1] = 0x5A;
|
|
||||||
signingRsa.SignData(bytes.AsSpan(0x110), HashAlgorithmName.SHA256,
|
|
||||||
RSASignaturePadding.Pkcs1).CopyTo(bytes.AsSpan(0x10));
|
|
||||||
File.WriteAllBytes(payloadPath, bytes);
|
File.WriteAllBytes(payloadPath, bytes);
|
||||||
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
|
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot);
|
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest);
|
||||||
throw new Exception("A payload signed by a foreign key passed the pinned production key.");
|
throw new Exception("A payload signed by a foreign key passed the pinned production key.");
|
||||||
}
|
}
|
||||||
catch (InvalidDataException) { }
|
catch (InvalidDataException) { }
|
||||||
var shaA = InputValidation.ComputeRetroWfcPayloadSha256(payloadRoot, signingKey);
|
var shaA = InputValidation.ComputeRetroWfcPayloadSha256(payloadRoot, gameUnderTest, signingKey);
|
||||||
var snapshot = new RetroWfcPayloadSnapshot(payloadRoot, shaA, bytes.Length);
|
var snapshot = new RetroWfcPayloadSnapshot(payloadRoot, shaA, bytes.Length);
|
||||||
JsonState.Write(Path.Combine(installation.RetroDirectory, ProductFingerprint.FileName),
|
JsonState.Write(Path.Combine(installation.RetroDirectory, ProductFingerprint.FileName),
|
||||||
new ProductFingerprint
|
new ProductFingerprint
|
||||||
@@ -698,7 +949,7 @@ internal static class SelfTests
|
|||||||
File.WriteAllBytes(payloadPath, bytes);
|
File.WriteAllBytes(payloadPath, bytes);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
|
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
|
||||||
throw new Exception("A payload with a malformed declared length was accepted.");
|
throw new Exception("A payload with a malformed declared length was accepted.");
|
||||||
}
|
}
|
||||||
catch (InvalidDataException) { }
|
catch (InvalidDataException) { }
|
||||||
@@ -708,7 +959,7 @@ internal static class SelfTests
|
|||||||
File.WriteAllBytes(payloadPath, bytes);
|
File.WriteAllBytes(payloadPath, bytes);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
|
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
|
||||||
throw new Exception("A payload with tampered signed content was accepted.");
|
throw new Exception("A payload with tampered signed content was accepted.");
|
||||||
}
|
}
|
||||||
catch (InvalidDataException) { }
|
catch (InvalidDataException) { }
|
||||||
@@ -718,7 +969,7 @@ internal static class SelfTests
|
|||||||
File.WriteAllBytes(payloadPath, bytes);
|
File.WriteAllBytes(payloadPath, bytes);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
|
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
|
||||||
throw new Exception("A payload with a malformed header was accepted.");
|
throw new Exception("A payload with a malformed header was accepted.");
|
||||||
}
|
}
|
||||||
catch (InvalidDataException) { }
|
catch (InvalidDataException) { }
|
||||||
@@ -1073,10 +1324,10 @@ internal static class SelfTests
|
|||||||
if (topologyChangedRuntimeAssets == changedRuntimeAssets)
|
if (topologyChangedRuntimeAssets == changedRuntimeAssets)
|
||||||
throw new Exception("An empty copied runtime-asset directory was omitted from its identity.");
|
throw new Exception("An empty copied runtime-asset directory was omitted from its identity.");
|
||||||
|
|
||||||
// Cache-reuse subsets: translator or runtime/src changes (the translator regex-scans it
|
// Cache-reuse subsets: translator or native registration changes invalidate translation
|
||||||
// for native overrides and effect contracts) invalidate translation but not toolchain,
|
// but not toolchain, since CMake/Ninja rebuild exactly the affected objects; a compiler
|
||||||
// since CMake/Ninja rebuild exactly the affected objects; a compiler change invalidates
|
// change invalidates only toolchain; a native_prebuilt provenance rewrite invalidates
|
||||||
// only toolchain; a native_prebuilt provenance rewrite invalidates neither.
|
// neither. Without a current native index, runtime/src itself decides translation.
|
||||||
var components = ToolkitFingerprint.ComputeComponents(root);
|
var components = ToolkitFingerprint.ComputeComponents(root);
|
||||||
if (components.Compile != afterPatch)
|
if (components.Compile != afterPatch)
|
||||||
throw new Exception("ComputeComponents disagrees with the compile identity.");
|
throw new Exception("ComputeComponents disagrees with the compile identity.");
|
||||||
@@ -1087,15 +1338,36 @@ internal static class SelfTests
|
|||||||
throw new Exception("A new translator did not change the translation identity.");
|
throw new Exception("A new translator did not change the translation identity.");
|
||||||
if (afterNewTranslator.NativeToolchain != components.NativeToolchain)
|
if (afterNewTranslator.NativeToolchain != components.NativeToolchain)
|
||||||
throw new Exception("A new translator needlessly changed the native toolchain identity.");
|
throw new Exception("A new translator needlessly changed the native toolchain identity.");
|
||||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "src", "main.cpp"),
|
var runtimeMain = Path.Combine(root, "BuildWorkspace", "runtime", "src", "main.cpp");
|
||||||
"int main3();");
|
File.WriteAllText(runtimeMain, "int main3();");
|
||||||
|
var afterUnindexedRuntime = ToolkitFingerprint.ComputeComponents(root);
|
||||||
|
if (afterUnindexedRuntime.Translation == afterNewTranslator.Translation)
|
||||||
|
throw new Exception("A runtime source change without a native index did not change the translation identity.");
|
||||||
|
|
||||||
|
File.WriteAllText(runtimeMain, "int main();");
|
||||||
|
if (WriteFakeNativeIndex(root, "\"a\"") !=
|
||||||
|
"50b9779591f4b205751361c8899accd362f96ea5dfd32febed1bedca534c523a")
|
||||||
|
throw new Exception("The native index source digest no longer matches the translator's.");
|
||||||
|
var indexed = ToolkitFingerprint.ComputeComponents(root);
|
||||||
|
File.WriteAllText(runtimeMain, "int main3();");
|
||||||
|
WriteFakeNativeIndex(root, "\"a\"");
|
||||||
var afterNewRuntime = ToolkitFingerprint.ComputeComponents(root);
|
var afterNewRuntime = ToolkitFingerprint.ComputeComponents(root);
|
||||||
if (afterNewRuntime.Translation == afterNewTranslator.Translation)
|
if (afterNewRuntime.Translation != indexed.Translation)
|
||||||
throw new Exception("A runtime source change did not change the translation identity.");
|
throw new Exception("A runtime source change with unchanged registrations changed the translation identity.");
|
||||||
if (afterNewRuntime.NativeToolchain != afterNewTranslator.NativeToolchain)
|
if (afterNewRuntime.NativeToolchain != indexed.NativeToolchain)
|
||||||
throw new Exception("A runtime source change needlessly changed the native toolchain identity.");
|
throw new Exception("A runtime source change needlessly changed the native toolchain identity.");
|
||||||
if (afterNewRuntime.Compile == afterNewTranslator.Compile)
|
if (afterNewRuntime.Compile == indexed.Compile)
|
||||||
throw new Exception("A runtime source change did not change the compile identity.");
|
throw new Exception("A runtime source change did not change the compile identity.");
|
||||||
|
File.WriteAllText(runtimeMain, "int main4();");
|
||||||
|
if (ToolkitFingerprint.ComputeComponents(root).Translation == afterNewRuntime.Translation)
|
||||||
|
throw new Exception("A stale native index was trusted for the translation identity.");
|
||||||
|
File.WriteAllText(runtimeMain, "int main3();");
|
||||||
|
WriteFakeNativeIndex(root, "\"a\", \"b\"");
|
||||||
|
afterNewRuntime = ToolkitFingerprint.ComputeComponents(root);
|
||||||
|
if (afterNewRuntime.Translation == indexed.Translation)
|
||||||
|
throw new Exception("A native registration change did not change the translation identity.");
|
||||||
|
if (afterNewRuntime.NativeToolchain != indexed.NativeToolchain)
|
||||||
|
throw new Exception("A native registration change needlessly changed the native toolchain identity.");
|
||||||
File.WriteAllText(Path.Combine(root, "Toolkit", "llvm-mingw", "bin", "clang-22.exe"),
|
File.WriteAllText(Path.Combine(root, "Toolkit", "llvm-mingw", "bin", "clang-22.exe"),
|
||||||
"clang-v2");
|
"clang-v2");
|
||||||
var afterNewCompiler = ToolkitFingerprint.ComputeComponents(root);
|
var afterNewCompiler = ToolkitFingerprint.ComputeComponents(root);
|
||||||
@@ -1148,13 +1420,34 @@ internal static class SelfTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Writes a native index current for the fake runtime/src; returns its source digest.</summary>
|
||||||
|
private static string WriteFakeNativeIndex(string root, string registrations)
|
||||||
|
{
|
||||||
|
var source = Path.Combine(root, "BuildWorkspace", "runtime", "src");
|
||||||
|
var manifest = new System.Text.StringBuilder();
|
||||||
|
foreach (var (relative, path) in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)
|
||||||
|
.Where(path => path.EndsWith(".cpp", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
path.EndsWith(".h", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
path.EndsWith(".hpp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(path => (Path.GetRelativePath(source, path).Replace('\\', '/'), path))
|
||||||
|
.OrderBy(item => item.Item1, StringComparer.Ordinal))
|
||||||
|
manifest.Append(InputValidation.Sha256File(path)).Append(" ").Append(relative).Append('\n');
|
||||||
|
var digest = Convert.ToHexString(SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(manifest.ToString())))
|
||||||
|
.ToLowerInvariant();
|
||||||
|
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "native-index.json"),
|
||||||
|
"{\n \"format\": \"wiicompiled-runtime-native-index\",\n \"sourceDigest\": \"" + digest +
|
||||||
|
"\",\n \"registrations\": [" + registrations + "]\n}\n");
|
||||||
|
return digest;
|
||||||
|
}
|
||||||
|
|
||||||
private static void CreateFakeToolkit(string root)
|
private static void CreateFakeToolkit(string root)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "Translator"));
|
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "Translator"));
|
||||||
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "CMake", "bin"));
|
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "CMake", "bin"));
|
||||||
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "Ninja"));
|
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "Ninja"));
|
||||||
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "llvm-mingw", "bin"));
|
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "llvm-mingw", "bin"));
|
||||||
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "projects", "mkwii"));
|
foreach (var project in new[] { "mkwii", "mkwii-ntsc-u", "mkwii-ntsc-j" })
|
||||||
|
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "projects", project));
|
||||||
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "src"));
|
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "src"));
|
||||||
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2",
|
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2",
|
||||||
"wc24"));
|
"wc24"));
|
||||||
@@ -1181,7 +1474,8 @@ internal static class SelfTests
|
|||||||
File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "tool");
|
File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "tool");
|
||||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "LocalBuild.ps1"), "# build");
|
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "LocalBuild.ps1"), "# build");
|
||||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "NativeBuildFlags.ps1"), "# flags");
|
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "NativeBuildFlags.ps1"), "# flags");
|
||||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", "mkwii", "recomp.yml"), "profiles: {}");
|
foreach (var project in new[] { "mkwii", "mkwii-ntsc-u", "mkwii-ntsc-j" })
|
||||||
|
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", project, "recomp.yml"), "profiles: {}");
|
||||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "CMakeLists.txt"), "cmake_minimum_required(VERSION 3.20)");
|
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "CMakeLists.txt"), "cmake_minimum_required(VERSION 3.20)");
|
||||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "src", "main.cpp"), "int main();");
|
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "src", "main.cpp"), "int main();");
|
||||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2", "wc24",
|
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2", "wc24",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using WiiCompiled.Setup.Common;
|
using WiiCompiled.Setup.Common;
|
||||||
|
|
||||||
namespace WiiCompiled.Setup.Windows;
|
namespace WiiCompiled.Setup.Windows;
|
||||||
@@ -12,32 +14,42 @@ namespace WiiCompiled.Setup.Windows;
|
|||||||
internal static class ToolkitFingerprint
|
internal static class ToolkitFingerprint
|
||||||
{
|
{
|
||||||
// v6/native-toolchain v2 forces one clean rebuild past a Ninja stale-object bug.
|
// v6/native-toolchain v2 forces one clean rebuild past a Ninja stale-object bug.
|
||||||
// translation v2 added runtime/src, which the translator regex-scans for native overrides.
|
// translation v2 added runtime/src, which the translator regex-scans for native overrides;
|
||||||
private const string Version = "mkwc-toolkit-compile-v6";
|
// v3 replaced it with the native index extracted from it, whenever that index is current.
|
||||||
private const string TranslationVersion = "mkwc-toolkit-translation-v2";
|
private const string Version = "mkwc-toolkit-compile-v7";
|
||||||
|
private const string TranslationVersion = "mkwc-toolkit-translation-v4";
|
||||||
private const string NativeToolchainVersion = "mkwc-toolkit-native-toolchain-v2";
|
private const string NativeToolchainVersion = "mkwc-toolkit-native-toolchain-v2";
|
||||||
private const string PackageVersion = "mkwc-toolkit-package-v1";
|
private const string PackageVersion = "mkwc-toolkit-package-v1";
|
||||||
private const string RuntimeAssetsVersion = "mkwc-product-runtime-assets-v3";
|
private const string RuntimeAssetsVersion = "mkwc-product-runtime-assets-v3";
|
||||||
private const string RuntimeAssetsDescription = "The product runtime assets";
|
private const string RuntimeAssetsDescription = "The product runtime assets";
|
||||||
|
|
||||||
private static readonly string[] SourceExtensions =
|
private static readonly string[] SourceExtensions =
|
||||||
[".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".inl", ".s", ".asm",
|
[".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".inl", ".inc", ".s", ".asm",
|
||||||
".cmake", ".txt", ".yml", ".yaml", ".toml", ".json", ".ps1", ".in", ".def", ".patch"];
|
".cmake", ".txt", ".yml", ".yaml", ".toml", ".json", ".ps1", ".in", ".def", ".patch"];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inputs that decide translator output: translator, translation project, runtime sources it
|
/// Inputs that decide translator output: translator, translation project, the native override
|
||||||
/// scans for native override registrations/effect contracts, and the build script's translator
|
/// registrations/effect contracts it extracts from runtime/src (see
|
||||||
/// command lines. Unchanged means the completed base translation can be reused instead of re-run.
|
/// <see cref="AddNativeRegistrations"/>), and the build script's translator command lines.
|
||||||
|
/// Unchanged means the completed base translation can be reused instead of re-run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly string[] TranslationPrefixes =
|
private static readonly string[] TranslationPrefixes =
|
||||||
[
|
[
|
||||||
InstalledLayout.ToolkitEntryPrefix + "Translator/",
|
InstalledLayout.ToolkitEntryPrefix + "Translator/",
|
||||||
InstalledLayout.WorkspaceEntryPrefix + "projects/",
|
InstalledLayout.WorkspaceEntryPrefix + "projects/"
|
||||||
InstalledLayout.WorkspaceEntryPrefix + "runtime/src/"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private static readonly string[] TranslationFiles =
|
private static readonly string[] TranslationFiles =
|
||||||
[InstalledLayout.WorkspaceEntryPrefix + "LocalBuild.ps1"];
|
[
|
||||||
|
InstalledLayout.WorkspaceEntryPrefix + "LocalBuild.ps1",
|
||||||
|
// Maps the PAL-keyed native index to the installed release's addresses.
|
||||||
|
InstalledLayout.WorkspaceEntryPrefix + "runtime/include/guest_abi/guest_versions.inc"
|
||||||
|
];
|
||||||
|
|
||||||
|
private const string NativeSourcePrefix = InstalledLayout.WorkspaceEntryPrefix + "runtime/src/";
|
||||||
|
private const string NativeIndexEntry = InstalledLayout.WorkspaceEntryPrefix + "runtime/native-index.json";
|
||||||
|
// The file set the translator's native index source digest covers.
|
||||||
|
private static readonly string[] NativeIndexSourceExtensions = [".cpp", ".h", ".hpp"];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inputs that make compiled objects unsafe to reuse: the compiler/CMake/Ninja toolchain, the
|
/// Inputs that make compiled objects unsafe to reuse: the compiler/CMake/Ninja toolchain, the
|
||||||
@@ -108,14 +120,16 @@ internal static class ToolkitFingerprint
|
|||||||
if (entries.Count == 0)
|
if (entries.Count == 0)
|
||||||
throw new InvalidDataException($"No toolkit files were found under {root}; the installation is incomplete.");
|
throw new InvalidDataException($"No toolkit files were found under {root}; the installation is incomplete.");
|
||||||
|
|
||||||
|
var translation = SelectSubset(entries, TranslationPrefixes, TranslationFiles);
|
||||||
|
AddNativeRegistrations(translation, entries, root);
|
||||||
return new ToolkitFingerprintComponents(
|
return new ToolkitFingerprintComponents(
|
||||||
BuildIdentity(Version, entries),
|
BuildIdentity(Version, entries),
|
||||||
BuildSubsetIdentity(TranslationVersion, entries, TranslationPrefixes, TranslationFiles),
|
BuildIdentity(TranslationVersion, translation),
|
||||||
BuildSubsetIdentity(NativeToolchainVersion, entries, NativeToolchainPrefixes,
|
BuildIdentity(NativeToolchainVersion,
|
||||||
NativeToolchainFiles));
|
SelectSubset(entries, NativeToolchainPrefixes, NativeToolchainFiles)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildSubsetIdentity(string version, SortedDictionary<string, string> entries,
|
private static SortedDictionary<string, string> SelectSubset(SortedDictionary<string, string> entries,
|
||||||
string[] prefixes, string[] files)
|
string[] prefixes, string[] files)
|
||||||
{
|
{
|
||||||
var subset = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
var subset = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
||||||
@@ -125,9 +139,61 @@ internal static class ToolkitFingerprint
|
|||||||
files.Any(file => relative.Equals(file, StringComparison.Ordinal)))
|
files.Any(file => relative.Equals(file, StringComparison.Ordinal)))
|
||||||
subset[relative] = hash;
|
subset[relative] = hash;
|
||||||
}
|
}
|
||||||
return BuildIdentity(version, subset);
|
return subset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A native-index.json extracted from exactly the shipped runtime/src stands in for those
|
||||||
|
/// sources, minus its source digest, so a runtime change that leaves the registrations alone
|
||||||
|
/// keeps the base translation. A missing, unreadable or stale index keeps the sources instead.
|
||||||
|
/// </summary>
|
||||||
|
private static void AddNativeRegistrations(SortedDictionary<string, string> subset,
|
||||||
|
SortedDictionary<string, string> entries, string root)
|
||||||
|
{
|
||||||
|
var sources = entries.Where(entry => entry.Key.StartsWith(NativeSourcePrefix, StringComparison.Ordinal))
|
||||||
|
.ToList();
|
||||||
|
var registrations = TryHashCurrentNativeIndex(
|
||||||
|
Path.Combine(InstalledLayout.Workspace(root), "runtime", "native-index.json"), sources);
|
||||||
|
if (registrations is not null)
|
||||||
|
{
|
||||||
|
subset[NativeIndexEntry] = registrations;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (var (relative, hash) in sources) subset[relative] = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? TryHashCurrentNativeIndex(string path, List<KeyValuePair<string, string>> sources)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path)) return null;
|
||||||
|
JsonObject? document;
|
||||||
|
try { document = JsonNode.Parse(File.ReadAllText(path)) as JsonObject; }
|
||||||
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (document?["sourceDigest"] is not JsonValue recorded || !recorded.TryGetValue<string>(out var digest))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Mirrors the translator's RuntimeNativeIndexFile.ComputeSourceDigest.
|
||||||
|
var manifest = new StringBuilder();
|
||||||
|
foreach (var (relative, hash) in sources)
|
||||||
|
{
|
||||||
|
if (!NativeIndexSourceExtensions.Any(extension =>
|
||||||
|
relative.EndsWith(extension, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
continue;
|
||||||
|
manifest.Append(hash).Append(" ").Append(relative, NativeSourcePrefix.Length,
|
||||||
|
relative.Length - NativeSourcePrefix.Length).Append('\n');
|
||||||
|
}
|
||||||
|
if (!digest.Equals(Sha256Hex(manifest.ToString()), StringComparison.OrdinalIgnoreCase))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
document.Remove("sourceDigest");
|
||||||
|
return Sha256Hex(document.ToJsonString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Sha256Hex(string text) =>
|
||||||
|
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))).ToLowerInvariant();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Content identity of the source-owned files copied verbatim beside every product. This is
|
/// Content identity of the source-owned files copied verbatim beside every product. This is
|
||||||
/// intentionally independent of <see cref="Compute"/>: an asset-only release is published
|
/// intentionally independent of <see cref="Compute"/>: an asset-only release is published
|
||||||
@@ -273,8 +339,7 @@ internal static class ToolkitFingerprint
|
|||||||
var builder = new StringBuilder(version).Append('\n');
|
var builder = new StringBuilder(version).Append('\n');
|
||||||
foreach (var (relative, hash) in entries)
|
foreach (var (relative, hash) in entries)
|
||||||
builder.Append(relative).Append('|').Append(hash).Append('\n');
|
builder.Append(relative).Append('|').Append(hash).Append('\n');
|
||||||
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(builder.ToString())))
|
return Sha256Hex(builder.ToString());
|
||||||
.ToLowerInvariant();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
<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.27</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>
|
||||||
@@ -16,5 +15,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
|
<ProjectReference Include="..\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
|
||||||
|
<Using Include="WiiCompiled.Setup.Common" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+35
-11
@@ -127,10 +127,10 @@ mkdir -p "$appdir/usr/toolchain"
|
|||||||
cp -a "$workspace/Launcher/artifacts/portable-tools/toolchain-$appimagetool_arch"/. "$appdir/usr/toolchain/"
|
cp -a "$workspace/Launcher/artifacts/portable-tools/toolchain-$appimagetool_arch"/. "$appdir/usr/toolchain/"
|
||||||
|
|
||||||
# Precompiled aurora + third-party package (see Prepare-NativePrebuilt.sh) so a user's own
|
# Precompiled aurora + third-party package (see Prepare-NativePrebuilt.sh) so a user's own
|
||||||
# local-build.sh never has to compile aurora itself (~43% of local build CPU time). Re-harvesting
|
# local-build.sh never has to compile aurora itself (~43% of local build CPU time), nor on x86_64
|
||||||
# recompiles the whole aurora/Crypto++ closure with the toolchain above, so this is skipped unless
|
# the game-independent runtime. Re-harvesting recompiles that closure with the toolchain above, so
|
||||||
# --print-fingerprint-only (a fast, build-free check) says the existing package no longer matches
|
# this is skipped unless --print-fingerprint-only (a fast, build-free check) says the existing
|
||||||
# the current compiler/flags/aurora/third_party sources.
|
# package no longer matches the current compiler, flags, source trees or mbed TLS pin.
|
||||||
native_prebuilt_dir="$workspace/Launcher/artifacts/native-prebuilt-$appimagetool_arch"
|
native_prebuilt_dir="$workspace/Launcher/artifacts/native-prebuilt-$appimagetool_arch"
|
||||||
echo "Checking whether the precompiled aurora + third-party package ($appimagetool_arch) is current..."
|
echo "Checking whether the precompiled aurora + third-party package ($appimagetool_arch) is current..."
|
||||||
current_fingerprint=$(bash "$script_dir/Prepare-NativePrebuilt.sh" --arch "$appimagetool_arch" --print-fingerprint-only)
|
current_fingerprint=$(bash "$script_dir/Prepare-NativePrebuilt.sh" --arch "$appimagetool_arch" --print-fingerprint-only)
|
||||||
@@ -148,6 +148,9 @@ fields = {
|
|||||||
"flag_fingerprint": "FlagFingerprint",
|
"flag_fingerprint": "FlagFingerprint",
|
||||||
"aurora_fingerprint": "AuroraSourceFingerprint",
|
"aurora_fingerprint": "AuroraSourceFingerprint",
|
||||||
"third_party_fingerprint": "ThirdPartySourceFingerprint",
|
"third_party_fingerprint": "ThirdPartySourceFingerprint",
|
||||||
|
"mbedtls_fingerprint": "MbedTlsFingerprint",
|
||||||
|
# Only printed on x86_64, so a package without the runtime archive is stale there.
|
||||||
|
"runtime_fingerprint": "RuntimeSourceFingerprint",
|
||||||
}
|
}
|
||||||
print(1 if all(provenance.get(v) == current.get(k) for k, v in fields.items()) else 0)
|
print(1 if all(provenance.get(v) == current.get(k) for k, v in fields.items()) else 0)
|
||||||
PY
|
PY
|
||||||
@@ -166,6 +169,8 @@ echo "Staging the bundled workspace snapshot..."
|
|||||||
for dir in runtime aurora-main projects; do
|
for dir in runtime aurora-main projects; do
|
||||||
cp -r "$workspace/$dir" "$appdir/workspace/$dir"
|
cp -r "$workspace/$dir" "$appdir/workspace/$dir"
|
||||||
done
|
done
|
||||||
|
# Ship source bytes, never submodule checkout pointers or embedded Git databases.
|
||||||
|
find "$appdir/workspace" -name .git -prune -exec rm -rf {} +
|
||||||
# Mirrors Build-Installer.ps1's own staging exclusions exactly: aurora-main/extern/CMakeLists.txt
|
# Mirrors Build-Installer.ps1's own staging exclusions exactly: aurora-main/extern/CMakeLists.txt
|
||||||
# is the real FetchContent driver and must ship, but any already-fetched dependency *subdirectory*
|
# is the real FetchContent driver and must ship, but any already-fetched dependency *subdirectory*
|
||||||
# a developer's local checkout accumulated under extern/ is stale/large build output, not a
|
# a developer's local checkout accumulated under extern/ is stale/large build output, not a
|
||||||
@@ -173,7 +178,29 @@ done
|
|||||||
# is a plain developer build directory.
|
# is a plain developer build directory.
|
||||||
find "$appdir/workspace/aurora-main/extern" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
|
find "$appdir/workspace/aurora-main/extern" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
|
||||||
rm -rf "$appdir/workspace/runtime/build"
|
rm -rf "$appdir/workspace/runtime/build"
|
||||||
cp "$workspace/Launcher/local-build.sh" "$appdir/workspace/Launcher/local-build.sh"
|
cp "$workspace/Launcher/local-build.sh" "$workspace/Launcher/build-cache.sh" "$appdir/workspace/Launcher/"
|
||||||
|
|
||||||
|
# The translation identity (build-cache.sh) covers the runtime's native registrations through this
|
||||||
|
# index, so it is extracted from exactly the bundled runtime sources.
|
||||||
|
echo "Extracting the runtime native index..."
|
||||||
|
"$appdir/usr/bin/translator-cli" emit-native-index \
|
||||||
|
--native-source-dir "$appdir/workspace/runtime/src" \
|
||||||
|
--out "$appdir/workspace/runtime/native-index.json"
|
||||||
|
[[ -f "$appdir/workspace/runtime/native-index.json" ]] || { echo "build-appimage.sh: the native index was not written" >&2; exit 1; }
|
||||||
|
|
||||||
|
# A bundled runtime that fingerprints differently from the shipped archive would make every
|
||||||
|
# installation silently compile the runtime from source.
|
||||||
|
if [[ "$appimagetool_arch" == x86_64 ]]; then
|
||||||
|
staged_runtime_fingerprint=$("$workspace/Launcher/artifacts/portable-tools/toolchain-$appimagetool_arch/bin/cmake" \
|
||||||
|
-DMKW_RUNTIME_DIR="$appdir/workspace/runtime" -P "$appdir/workspace/runtime/cmake/RuntimeFingerprint.cmake" |
|
||||||
|
sed -n 's/^runtime_fingerprint=//p')
|
||||||
|
packaged_runtime_fingerprint=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1], encoding="utf-8")).get("RuntimeSourceFingerprint", ""))' \
|
||||||
|
"$appdir/native-prebuilt/provenance.json")
|
||||||
|
if [[ -z "$staged_runtime_fingerprint" || "$staged_runtime_fingerprint" != "$packaged_runtime_fingerprint" ]]; then
|
||||||
|
echo "build-appimage.sh: the bundled runtime ($staged_runtime_fingerprint) does not match the precompiled runtime archive ($packaged_runtime_fingerprint)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# AppRun re-syncs runtime/aurora-main/projects/local-build.sh into the writable cache only when
|
# AppRun re-syncs runtime/aurora-main/projects/local-build.sh into the writable cache only when
|
||||||
# this changes, so it must change whenever any of those bundled paths actually did - a bare commit
|
# this changes, so it must change whenever any of those bundled paths actually did - a bare commit
|
||||||
@@ -203,12 +230,9 @@ CACHE="${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled/workspace"
|
|||||||
mkdir -p "$CACHE"
|
mkdir -p "$CACHE"
|
||||||
if [ ! -f "$CACHE/.bundle-version" ] || \
|
if [ ! -f "$CACHE/.bundle-version" ] || \
|
||||||
[ "$(cat "$HERE/workspace/.bundle-version")" != "$(cat "$CACHE/.bundle-version")" ]; then
|
[ "$(cat "$HERE/workspace/.bundle-version")" != "$(cat "$CACHE/.bundle-version")" ]; then
|
||||||
mkdir -p "$CACHE/Launcher"
|
# Unchanged files keep their timestamps so the incremental build survives the update.
|
||||||
for dir in runtime aurora-main projects; do
|
source "$HERE/workspace/Launcher/build-cache.sh"
|
||||||
rm -rf "$CACHE/$dir"
|
mkw_sync_tree "$HERE/workspace" "$CACHE" runtime aurora-main projects Launcher >&2
|
||||||
cp -r "$HERE/workspace/$dir" "$CACHE/$dir"
|
|
||||||
done
|
|
||||||
cp "$HERE/workspace/Launcher/local-build.sh" "$CACHE/Launcher/local-build.sh"
|
|
||||||
cp "$HERE/workspace/.bundle-version" "$CACHE/.bundle-version"
|
cp "$HERE/workspace/.bundle-version" "$CACHE/.bundle-version"
|
||||||
fi
|
fi
|
||||||
# toolchain/ and native-prebuilt/ are NOT copied into the cache (they're large - ~500 MiB /
|
# toolchain/ and native-prebuilt/ are NOT copied into the cache (they're large - ~500 MiB /
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# Shared incremental-build and release-selection logic for the Unix setups (Linux AppImage and
|
||||||
|
# macOS), sourced by local-build.sh, local-build-macos.command, macos/extract-disc.command, the
|
||||||
|
# AppImage's AppRun and macos/setup.command.
|
||||||
|
# Mirrors the Windows installer's translation/toolchain identities and timestamp preservation.
|
||||||
|
# Must stay compatible with macOS's bash 3.2.
|
||||||
|
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
mkw_sha256_cmd=(sha256sum)
|
||||||
|
else
|
||||||
|
mkw_sha256_cmd=(shasum -a 256)
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkw_sha256() {
|
||||||
|
"${mkw_sha256_cmd[@]}" "$@" | awk '{print $1}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints "<sha> <relative path>" for every file under each directory, relative to $1.
|
||||||
|
mkw_digest_tree() {
|
||||||
|
local root=$1; shift
|
||||||
|
(
|
||||||
|
cd "$root" || exit 1
|
||||||
|
local dir
|
||||||
|
for dir in "$@"; do
|
||||||
|
if [[ -d "$dir" ]]; then
|
||||||
|
printf 'tree %s\n' "$dir"
|
||||||
|
find "$dir" -type f -print0 | LC_ALL=C sort -z | xargs -0 "${mkw_sha256_cmd[@]}" /dev/null
|
||||||
|
else
|
||||||
|
printf 'absent %s\n' "$dir"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Content-only digest of a file or directory, independent of where it lives (the AppImage mount
|
||||||
|
# path changes every launch).
|
||||||
|
mkw_digest_path() {
|
||||||
|
if [[ -d "$1" ]]; then
|
||||||
|
mkw_digest_tree "$1" . | mkw_sha256
|
||||||
|
elif [[ -f "$1" ]]; then
|
||||||
|
mkw_sha256 < "$1"
|
||||||
|
else
|
||||||
|
printf 'absent\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Same digest as the translator's RuntimeNativeIndexFile.ComputeSourceDigest. $1 = runtime/src.
|
||||||
|
mkw_native_source_digest() {
|
||||||
|
(
|
||||||
|
cd "$1" || exit 1
|
||||||
|
find . -type f \( -iname '*.cpp' -o -iname '*.h' -o -iname '*.hpp' \) -print0 | LC_ALL=C sort -z |
|
||||||
|
xargs -0 "${mkw_sha256_cmd[@]}" /dev/null
|
||||||
|
) | grep -v ' /dev/null$' | sed 's| \./| |' | mkw_sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
# The native registrations the translator reads from runtime/src. A current native-index.json
|
||||||
|
# stands in for the sources (minus its source digest), so runtime edits that leave the
|
||||||
|
# registrations alone keep the translation; a missing or stale index falls back to the sources.
|
||||||
|
# $1 = workspace.
|
||||||
|
mkw_native_index_identity() {
|
||||||
|
local index="$1/runtime/native-index.json" recorded
|
||||||
|
recorded=$(mkw_read_identity "$index" sourceDigest)
|
||||||
|
if [[ -n "$recorded" && -d "$1/runtime/src" && "$recorded" == "$(mkw_native_source_digest "$1/runtime/src")" ]]; then
|
||||||
|
printf 'native-index %s\n' "$(grep -v '^[[:space:]]*"sourceDigest"[[:space:]]*:' "$index" | mkw_sha256)"
|
||||||
|
else
|
||||||
|
mkw_digest_tree "$1" runtime/src
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Everything the base translation depends on: game inputs, project, native registrations, the
|
||||||
|
# translator itself and the build script that drives it.
|
||||||
|
# $1 = workspace, $2 = translator file or directory, $3 = build script.
|
||||||
|
mkw_translation_identity() {
|
||||||
|
local workspace=$1 translator=$2 script=$3
|
||||||
|
{
|
||||||
|
printf 'mkw-translation-v2\n'
|
||||||
|
printf 'dol %s\n' "$(mkw_digest_path "$workspace/Assets/main.dol")"
|
||||||
|
printf 'rel %s\n' "$(mkw_digest_path "$workspace/Assets/StaticR.rel")"
|
||||||
|
printf 'translator %s\n' "$(mkw_digest_path "$translator")"
|
||||||
|
printf 'script %s\n' "$(mkw_digest_path "$script")"
|
||||||
|
printf 'regions %s\n' "$(mkw_digest_path "$workspace/runtime/include/guest_abi/guest_versions.inc")"
|
||||||
|
mkw_digest_tree "$workspace" projects
|
||||||
|
mkw_native_index_identity "$workspace"
|
||||||
|
} | mkw_sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
# Inputs that make compiled objects unsafe to reuse: compiler/CMake/Ninja versions and the
|
||||||
|
# from-source dependency pins. Source changes are excluded; Ninja rebuilds exactly what they touch.
|
||||||
|
# $1 = workspace, remaining = tools (C compiler, C++ compiler, cmake, ninja).
|
||||||
|
mkw_toolchain_identity() {
|
||||||
|
local workspace=$1; shift
|
||||||
|
local tool
|
||||||
|
{
|
||||||
|
printf 'mkw-toolchain-v2\n'
|
||||||
|
for tool in "$@"; do
|
||||||
|
# First line only: clang appends its InstalledDir, which moves with the AppImage mount.
|
||||||
|
printf '%s\n' "$("$tool" --version 2>/dev/null | head -n 1)"
|
||||||
|
done
|
||||||
|
mkw_digest_tree "$workspace" aurora-main/extern aurora-main/cmake
|
||||||
|
printf 'aurora %s\n' "$(mkw_digest_path "$workspace/aurora-main/CMakeLists.txt")"
|
||||||
|
printf 'mbedtls %s\n' "$(mkw_digest_path "$workspace/runtime/cmake/MbedTLSPin.cmake")"
|
||||||
|
} | mkw_sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints one fact a projects/*/recomp.yml pins: game_id, region, dol, rel or retro_wfc_payload.
|
||||||
|
# Literal line matching against the fixed shape, like NativeBuildFlags.ps1's Get-MkwProjectPins.
|
||||||
|
# $1 = recomp.yml, $2 = fact.
|
||||||
|
mkw_project_pin() {
|
||||||
|
awk -v want="$2" '
|
||||||
|
{ sub(/\r$/, ""); sub(/#.*/, "") }
|
||||||
|
/^[A-Za-z0-9_]+:/ { section = $1; sub(/:.*/, "", section); input = ""; next }
|
||||||
|
section == "inputs" && /^ [A-Za-z0-9_]+:[[:space:]]*$/ { input = $1; sub(/:$/, "", input); next }
|
||||||
|
section == "project" && $1 == (want ":") { print $2; exit }
|
||||||
|
section == "inputs" && input == want && $1 == "sha256:" { print tolower($2); exit }
|
||||||
|
want == "retro_wfc_payload" && $1 == "retro_wfc_payload:" { print $2; exit }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints the recomp.yml of the supported release whose game id is $2. $1 = workspace.
|
||||||
|
mkw_project_for_game() {
|
||||||
|
local project
|
||||||
|
for project in "$1"/projects/*/recomp.yml; do
|
||||||
|
if [[ -f "$project" && "$(mkw_project_pin "$project" game_id)" == "$2" ]]; then
|
||||||
|
printf '%s\n' "$project"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints the recomp.yml pinned to $2/main.dol after checking StaticR.rel and any extracted disc
|
||||||
|
# header against it, like NativeBuildFlags.ps1's Resolve-MkwProjectForAssets.
|
||||||
|
# $1 = workspace, $2 = assets directory.
|
||||||
|
mkw_resolve_project() {
|
||||||
|
local project dol_sha game_id disc_id
|
||||||
|
dol_sha=$(mkw_sha256 "$2/main.dol")
|
||||||
|
for project in "$1"/projects/*/recomp.yml; do
|
||||||
|
[[ -f "$project" && "$(mkw_project_pin "$project" dol)" == "$dol_sha" ]] || continue
|
||||||
|
game_id=$(mkw_project_pin "$project" game_id)
|
||||||
|
if [[ "$(mkw_sha256 "$2/StaticR.rel")" != "$(mkw_project_pin "$project" rel)" ]]; then
|
||||||
|
printf 'StaticR.rel does not match the clean %s release of main.dol.\n' "$game_id" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ -f "$2/DATA/sys/boot.bin" ]]; then
|
||||||
|
disc_id=$(head -c 6 "$2/DATA/sys/boot.bin")
|
||||||
|
if [[ "$disc_id" != "$game_id" ]]; then
|
||||||
|
printf 'The extracted game data is %s but main.dol is %s; extract the game again.\n' "$disc_id" "$game_id" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$project"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
printf 'main.dol (sha256 %s) is not a supported clean Mario Kart Wii release.\n' "$dol_sha" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# The offline file name the translator expects for a release's Retro-WFC payload. $1 = recomp.yml.
|
||||||
|
mkw_retro_wfc_payload_file() {
|
||||||
|
local uri
|
||||||
|
uri=$(mkw_project_pin "$1" retro_wfc_payload)
|
||||||
|
[[ -n "$uri" ]] || return 1
|
||||||
|
printf 'binary/payload.%s.bin\n' "${uri##*g=}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1 = JSON file, $2 = key. Prints the recorded string value, or nothing.
|
||||||
|
mkw_read_identity() {
|
||||||
|
[[ -f "$1" ]] || return 0
|
||||||
|
grep -o "\"$2\" *: *\"[^\"]*\"" "$1" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
mkw_write_identity() {
|
||||||
|
printf '{"SchemaVersion":1,"%s":"%s"}\n' "$2" "$3" > "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mirrors each named directory from $1 into $2. Unchanged files are left untouched so their
|
||||||
|
# timestamps (which clang's PCH validation and Ninja compare) survive; changed and new files are
|
||||||
|
# copied fresh and files no longer shipped are removed.
|
||||||
|
mkw_sync_tree() {
|
||||||
|
local source=$1 destination=$2; shift 2
|
||||||
|
local dir relative changed=0 unchanged=0 removed=0
|
||||||
|
for dir in "$@"; do
|
||||||
|
if [[ ! -d "$source/$dir" ]]; then
|
||||||
|
rm -rf "$destination/$dir"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ -e "$destination/$dir" && ! -d "$destination/$dir" ]]; then
|
||||||
|
rm -f "$destination/$dir"
|
||||||
|
fi
|
||||||
|
mkdir -p "$destination/$dir"
|
||||||
|
while IFS= read -r -d '' relative; do
|
||||||
|
relative=${relative#./}
|
||||||
|
if [[ -d "$source/$dir/$relative" ]]; then
|
||||||
|
[[ -d "$destination/$dir/$relative" ]] || { rm -f "$destination/$dir/$relative"; mkdir -p "$destination/$dir/$relative"; }
|
||||||
|
elif [[ -f "$destination/$dir/$relative" ]] && cmp -s "$source/$dir/$relative" "$destination/$dir/$relative"; then
|
||||||
|
unchanged=$((unchanged + 1))
|
||||||
|
else
|
||||||
|
rm -rf "$destination/$dir/$relative"
|
||||||
|
cp "$source/$dir/$relative" "$destination/$dir/$relative"
|
||||||
|
changed=$((changed + 1))
|
||||||
|
fi
|
||||||
|
done < <(cd "$source/$dir" && find . -mindepth 1 -print0)
|
||||||
|
while IFS= read -r -d '' relative; do
|
||||||
|
relative=${relative#./}
|
||||||
|
if [[ ! -e "$source/$dir/$relative" && ( -e "$destination/$dir/$relative" || -L "$destination/$dir/$relative" ) ]]; then
|
||||||
|
[[ -d "$destination/$dir/$relative" ]] || removed=$((removed + 1))
|
||||||
|
rm -rf "$destination/$dir/$relative"
|
||||||
|
fi
|
||||||
|
done < <(cd "$destination/$dir" && find . -mindepth 1 -depth -print0)
|
||||||
|
done
|
||||||
|
printf 'Workspace refreshed: %d changed, %d unchanged, %d removed file(s).\n' "$changed" "$unchanged" "$removed"
|
||||||
|
}
|
||||||
@@ -4,11 +4,12 @@ set -euo pipefail
|
|||||||
|
|
||||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
default_workspace=$(cd "$script_dir/.." && pwd)
|
default_workspace=$(cd "$script_dir/.." && pwd)
|
||||||
|
# shellcheck source=build-cache.sh
|
||||||
|
source "$script_dir/build-cache.sh"
|
||||||
|
|
||||||
fail() { printf 'local-build-macos.command: error: %s\n' "$*" >&2; exit 1; }
|
fail() { printf 'local-build-macos.command: error: %s\n' "$*" >&2; exit 1; }
|
||||||
step() { printf 'MKWCBUILD:STEP:%s %s\n' "$1" "$2"; }
|
step() { printf 'MKWCBUILD:STEP:%s %s\n' "$1" "$2"; }
|
||||||
assert_file() { [[ -f "$1" ]] || fail "$2 is missing: $1"; }
|
assert_file() { [[ -f "$1" ]] || fail "$2 is missing: $1"; }
|
||||||
sha256() { shasum -a 256 "$1" | awk '{ print $1 }'; }
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -18,21 +19,24 @@ Usage: local-build-macos.command --output-dir DIR [options]
|
|||||||
--profile {base|retro-rewind|both} Product to build (default: base)
|
--profile {base|retro-rewind|both} Product to build (default: base)
|
||||||
--output-dir DIR Output .app directory (required; Retro Rewind for both)
|
--output-dir DIR Output .app directory (required; Retro Rewind for both)
|
||||||
--base-output-dir DIR Base .app directory (required with --profile both)
|
--base-output-dir DIR Base .app directory (required with --profile both)
|
||||||
--game IMAGE --nodtool PATH Extract and verify a clean PAL RMCP01 disc image first
|
--game IMAGE --nodtool PATH Extract and verify a clean supported disc image first
|
||||||
--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 the release's binary/payload.<game>.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 native-build-macos caches
|
--force-clean-build Delete local generated and current-architecture native build 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
|
||||||
--translator-bin PATH Use a self-contained Translator.Cli executable
|
--translator-bin PATH Use a self-contained Translator.Cli executable
|
||||||
|
--setup-version VERSION Version recorded in the runtime log (default: <Version> in
|
||||||
|
Launcher/Directory.Build.props when present)
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace="$default_workspace"; profile=base; output_dir=""; base_output_dir=""
|
workspace="$default_workspace"; profile=base; output_dir=""; base_output_dir=""
|
||||||
game=""; nodtool=""; retro_root=""; retro_wfc=""; skip_retro_wfc=0; force_clean=0
|
game=""; nodtool=""; retro_root=""; retro_wfc=""; skip_retro_wfc=0; force_clean=0
|
||||||
parallel=0; cmake_bin=cmake; ninja_bin=ninja; dotnet_bin=dotnet; translator_bin=""
|
parallel=0; cmake_bin=cmake; ninja_bin=ninja; dotnet_bin=dotnet; translator_bin=""
|
||||||
|
setup_version=""
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--workspace) workspace=${2:-}; shift 2 ;;
|
--workspace) workspace=${2:-}; shift 2 ;;
|
||||||
@@ -50,14 +54,21 @@ while (($#)); do
|
|||||||
--ninja) ninja_bin=${2:-}; shift 2 ;;
|
--ninja) ninja_bin=${2:-}; shift 2 ;;
|
||||||
--dotnet) dotnet_bin=${2:-}; shift 2 ;;
|
--dotnet) dotnet_bin=${2:-}; shift 2 ;;
|
||||||
--translator-bin) translator_bin=${2:-}; shift 2 ;;
|
--translator-bin) translator_bin=${2:-}; shift 2 ;;
|
||||||
|
--setup-version) setup_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
|
||||||
|
|
||||||
[[ $(uname -s) == Darwin ]] || fail 'this build script is for macOS only'
|
[[ $(uname -s) == Darwin ]] || fail 'this build script is for macOS only'
|
||||||
[[ $(uname -m) == arm64 ]] || fail 'the current macOS product target is Apple Silicon only'
|
macos_arch=$(uname -m)
|
||||||
|
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)
|
||||||
|
if [[ -z "$setup_version" && -f "$workspace/Launcher/Directory.Build.props" ]]; then
|
||||||
|
setup_version=$(sed -nE 's/.*<Version>([^<]+)<\/Version>.*/\1/p' "$workspace/Launcher/Directory.Build.props" | head -n 1)
|
||||||
|
fi
|
||||||
|
[[ "$setup_version" =~ ^[0-9A-Za-z.+-]*$ ]] || fail '--setup-version contains unsupported characters'
|
||||||
[[ -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
|
||||||
builds_retro=0; [[ "$profile" != base ]] && builds_retro=1
|
builds_retro=0; [[ "$profile" != base ]] && builds_retro=1
|
||||||
@@ -71,12 +82,15 @@ if (( builds_retro )); then
|
|||||||
fi
|
fi
|
||||||
for tool in "$cmake_bin" "$ninja_bin" clang clang++ shasum; do command -v "$tool" >/dev/null || fail "required tool not found: $tool"; done
|
for tool in "$cmake_bin" "$ninja_bin" clang clang++ shasum; do command -v "$tool" >/dev/null || fail "required tool not found: $tool"; done
|
||||||
|
|
||||||
project="$workspace/projects/mkwii/recomp.yml"; assets="$workspace/Assets"; generated="$workspace/generated"
|
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"; native_build="$workspace/native-build-macos"
|
manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards"
|
||||||
assert_file "$project" 'translation project'
|
native_build="$workspace/native-build-macos-$macos_arch"
|
||||||
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" --workspace "$workspace" --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'
|
||||||
|
# The translation project is the one pinned to the extracted main.dol, which decides the region.
|
||||||
|
project=$(mkw_resolve_project "$workspace" "$assets") || fail 'the extracted game cannot be translated'
|
||||||
|
region=$(mkw_project_pin "$project" region); [[ -n "$region" ]] || fail "could not find the region of $project"
|
||||||
|
|
||||||
if (( force_clean )); then
|
if (( force_clean )); then
|
||||||
step force-clean 'Discarding translation and native build caches'
|
step force-clean 'Discarding translation and native build caches'
|
||||||
@@ -115,14 +129,33 @@ if (( builds_retro )); then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
step translate-base 'Translating the user-owned base game'
|
translation_provenance="$generated/translation-provenance.json"
|
||||||
rm -rf "$functions" "$metadata" "$manifest_dir"; mkdir -p "$functions" "$manifest_dir"
|
translation_fingerprint=$(mkw_translation_identity "$workspace" "${translator_bin:-$(dirname "${translator_dll:-}")}" "$script_dir/local-build-macos.command")
|
||||||
translator translate-recursive "$entry_point" --project "$project" --outdir "$functions" --output-metadata "$metadata" --production-source-bundle "$generated/base_translation_sources.bin" --no-function-files --prune-stale --threads "$translator_threads"
|
reuse_base=0
|
||||||
step emit-base-manifest 'Creating the local base translation manifest'
|
if [[ "$(mkw_read_identity "$translation_provenance" TranslationFingerprint)" == "$translation_fingerprint" && -f "$metadata" && -f "$manifest" ]]; then
|
||||||
translator emit-base-manifest --project "$project" --out "$manifest_dir" --functions-dir "$functions" --translation-output-metadata "$metadata" --region P
|
reuse_base=1
|
||||||
|
fi
|
||||||
|
if (( reuse_base && builds_retro )); then
|
||||||
|
# A base tree that never saw this Code.pul would bake vanilla code into the modded product.
|
||||||
|
pul_sha=$(mkw_sha256 "$retro_root/Binaries/Code.pul")
|
||||||
|
if ! grep -q "\"codePulSha256\":\"$pul_sha\"" "$metadata" && \
|
||||||
|
! translator check-base-mod-awareness --project "$project" --profile retro-rewind --translation-output-metadata "$metadata" --code-pul "$retro_root/Binaries/Code.pul"; then
|
||||||
|
reuse_base=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if (( reuse_base )); then
|
||||||
|
step reuse-base-translation 'Reusing the completed base translation'
|
||||||
|
else
|
||||||
|
rm -f "$translation_provenance"; mkdir -p "$functions" "$manifest_dir"
|
||||||
|
step translate-base 'Translating the user-owned base game'
|
||||||
|
translator translate-recursive "$entry_point" --project "$project" --outdir "$functions" --output-metadata "$metadata" --production-source-bundle "$generated/base_translation_sources.bin" --no-function-files --prune-stale --threads "$translator_threads"
|
||||||
|
step emit-base-manifest 'Creating the local base translation manifest'
|
||||||
|
translator emit-base-manifest --project "$project" --out "$manifest_dir" --functions-dir "$functions" --translation-output-metadata "$metadata" --region "$region"
|
||||||
|
mkw_write_identity "$translation_provenance" TranslationFingerprint "$translation_fingerprint"
|
||||||
|
fi
|
||||||
if (( builds_retro )); then
|
if (( builds_retro )); then
|
||||||
mod_out="$workspace/build/mods/retro_rewind_full_cpp"; args=(translate-mod --project "$project" --profile retro-rewind --base-manifest "$manifest" --base-translation-output-metadata "$metadata" --code-pul "$retro_root/Binaries/Code.pul" --mod-root "$retro_root" --mod-name 'Retro Rewind' --region P --out "$mod_out" --prefer-cached-inputs --emit-cpp --threads "$translator_threads")
|
mod_out="$workspace/build/mods/retro_rewind_full_cpp"; args=(translate-mod --project "$project" --profile retro-rewind --base-manifest "$manifest" --base-translation-output-metadata "$metadata" --code-pul "$retro_root/Binaries/Code.pul" --mod-root "$retro_root" --mod-name 'Retro Rewind' --region "$region" --out "$mod_out" --prefer-cached-inputs --emit-cpp --threads "$translator_threads")
|
||||||
if (( skip_retro_wfc )); then args+=(--skip-retro-wfc); else offline_payload="$retro_wfc/binary/payload.RMCPD00.bin"; assert_file "$offline_payload" 'Offline Retro-WFC payload'; args+=(--retro-wfc-payload "$offline_payload"); fi
|
if (( skip_retro_wfc )); then args+=(--skip-retro-wfc); else payload_file=$(mkw_retro_wfc_payload_file "$project") || fail "$project pins no Retro-WFC payload"; offline_payload="$retro_wfc/$payload_file"; assert_file "$offline_payload" 'Offline Retro-WFC payload'; args+=(--retro-wfc-payload "$offline_payload"); fi
|
||||||
step translate-mod 'Translating Retro Rewind'; translator "${args[@]}"
|
step translate-mod 'Translating Retro Rewind'; translator "${args[@]}"
|
||||||
fi
|
fi
|
||||||
step generate-data-init 'Generating local game data initialization'; translator generate-data-init --project "$project"
|
step generate-data-init 'Generating local game data initialization'; translator generate-data-init --project "$project"
|
||||||
@@ -130,10 +163,19 @@ args=(emit-build-shards --project "$project" --base-metadata "$metadata" --base-
|
|||||||
if (( builds_retro )); then args+=(--resolved-profile "$mod_out/resolved_dispatch_profile.json" --retro-cpp-dir "$mod_out/cpp"); fi
|
if (( builds_retro )); then args+=(--resolved-profile "$mod_out/resolved_dispatch_profile.json" --retro-cpp-dir "$mod_out/cpp"); fi
|
||||||
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
|
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
|
||||||
|
|
||||||
|
toolchain_provenance="$native_build/toolchain-provenance.json"
|
||||||
|
toolchain_fingerprint=$(mkw_toolchain_identity "$workspace" clang clang++ "$cmake_bin" "$ninja_bin")
|
||||||
|
if [[ -d "$native_build" && "$(mkw_read_identity "$toolchain_provenance" ToolchainFingerprint)" != "$toolchain_fingerprint" ]]; then
|
||||||
|
printf 'MKWCBUILD: The toolchain or a pinned dependency changed; rebuilding the native build from scratch\n'
|
||||||
|
rm -rf "$native_build"
|
||||||
|
fi
|
||||||
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" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs"
|
# Use Aurora's pinned SDL3 source on macOS. A system SDL3 can be older than
|
||||||
|
# Aurora's required API even when find_package() succeeds.
|
||||||
|
"$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" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" -DAURORA_SDL3_PROVIDER=vendor -DMKW_SETUP_VERSION="$setup_version"
|
||||||
|
mkw_write_identity "$toolchain_provenance" ToolchainFingerprint "$toolchain_fingerprint"
|
||||||
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}"; 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}" --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
|
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
|
||||||
printf 'MKWCBUILD:OUTPUT=%s\n' "$output_dir"
|
printf 'MKWCBUILD:OUTPUT=%s\n' "$output_dir"
|
||||||
|
|||||||
+106
-21
@@ -39,7 +39,7 @@ require_command() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sha256_of() {
|
sha256_of() {
|
||||||
sha256sum "$1" | awk '{print $1}'
|
mkw_sha256 "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -47,6 +47,8 @@ sha256_of() {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
# shellcheck source=build-cache.sh
|
||||||
|
source "$script_dir/build-cache.sh"
|
||||||
workspace=$(cd "$script_dir/.." && pwd)
|
workspace=$(cd "$script_dir/.." && pwd)
|
||||||
profile=base
|
profile=base
|
||||||
output_dir=""
|
output_dir=""
|
||||||
@@ -65,6 +67,9 @@ translator_dll_override=""
|
|||||||
translator_bin_override=""
|
translator_bin_override=""
|
||||||
fuse_ld_override=""
|
fuse_ld_override=""
|
||||||
native_prebuilt_dir=""
|
native_prebuilt_dir=""
|
||||||
|
sysroot=""
|
||||||
|
setup_version=""
|
||||||
|
x86_cpu_profile=auto
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -87,6 +92,12 @@ Usage: local-build.sh --output-dir DIR [options]
|
|||||||
--translator-bin PATH Self-contained Translator.Cli executable (skips building AND needs no dotnet at all)
|
--translator-bin PATH Self-contained Translator.Cli executable (skips building AND needs no dotnet at all)
|
||||||
--native-prebuilt-dir DIR Precompiled aurora/third-party package (see Prepare-NativePrebuilt.sh);
|
--native-prebuilt-dir DIR Precompiled aurora/third-party package (see Prepare-NativePrebuilt.sh);
|
||||||
skips compiling aurora-main from source entirely
|
skips compiling aurora-main from source entirely
|
||||||
|
--sysroot PATH Passed to CMake as -DCMAKE_SYSROOT: where the compiler resolves
|
||||||
|
standard headers/startup files
|
||||||
|
--setup-version VERSION Version recorded in the runtime log (default: <Version> in
|
||||||
|
Launcher/Directory.Build.props when present)
|
||||||
|
--x86-cpu-profile {auto|v2|v3} x86-64 instruction set to build for (default: auto, v3 when
|
||||||
|
this machine supports it)
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,16 +121,28 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--translator-dll) translator_dll_override=$2; shift 2 ;;
|
--translator-dll) translator_dll_override=$2; shift 2 ;;
|
||||||
--translator-bin) translator_bin_override=$2; shift 2 ;;
|
--translator-bin) translator_bin_override=$2; shift 2 ;;
|
||||||
--native-prebuilt-dir) native_prebuilt_dir=$2; shift 2 ;;
|
--native-prebuilt-dir) native_prebuilt_dir=$2; shift 2 ;;
|
||||||
|
--sysroot) sysroot=$2; shift 2 ;;
|
||||||
|
--setup-version) setup_version=$2; shift 2 ;;
|
||||||
|
--x86-cpu-profile) x86_cpu_profile=$2; shift 2 ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) fail "unknown argument: $1" ;;
|
*) fail "unknown argument: $1" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [[ -z "$setup_version" && -f "$workspace/Launcher/Directory.Build.props" ]]; then
|
||||||
|
setup_version=$(sed -nE 's/.*<Version>([^<]+)<\/Version>.*/\1/p' "$workspace/Launcher/Directory.Build.props" | head -n 1)
|
||||||
|
fi
|
||||||
|
[[ "$setup_version" =~ ^[0-9A-Za-z.+-]*$ ]] || fail "--setup-version contains unsupported characters"
|
||||||
|
|
||||||
[[ -n "$output_dir" ]] || { usage; fail "--output-dir is required"; }
|
[[ -n "$output_dir" ]] || { usage; fail "--output-dir is required"; }
|
||||||
case "$profile" in
|
case "$profile" in
|
||||||
base|retro-rewind|both) ;;
|
base|retro-rewind|both) ;;
|
||||||
*) fail "--profile must be base, retro-rewind, or both" ;;
|
*) fail "--profile must be base, retro-rewind, or both" ;;
|
||||||
esac
|
esac
|
||||||
|
case "$x86_cpu_profile" in
|
||||||
|
auto|v2|v3) ;;
|
||||||
|
*) fail "--x86-cpu-profile must be auto, v2, or v3" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
builds_retro=0
|
builds_retro=0
|
||||||
[[ "$profile" == "retro-rewind" || "$profile" == "both" ]] && builds_retro=1
|
[[ "$profile" == "retro-rewind" || "$profile" == "both" ]] && builds_retro=1
|
||||||
@@ -169,7 +192,22 @@ if [[ -n "$native_prebuilt_dir" ]]; then
|
|||||||
assert_file "$native_prebuilt_dir/native_prebuilt.cmake" "Native prebuilt package"
|
assert_file "$native_prebuilt_dir/native_prebuilt.cmake" "Native prebuilt package"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
project=$workspace/projects/mkwii/recomp.yml
|
host_supports_x86_64_v3() {
|
||||||
|
local macros macro
|
||||||
|
macros=$("$cc_bin" -march=native -dM -E -x c /dev/null 2>/dev/null) || return 1
|
||||||
|
for macro in __AVX__ __AVX2__ __BMI__ __BMI2__ __F16C__ __FMA__ __LZCNT__ __MOVBE__; do
|
||||||
|
grep -q "^#define $macro " <<<"$macros" || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$x86_cpu_profile" == "auto" ]]; then
|
||||||
|
x86_cpu_profile=v3
|
||||||
|
if [[ "$(uname -m)" == "x86_64" ]] && ! host_supports_x86_64_v3; then
|
||||||
|
x86_cpu_profile=v2
|
||||||
|
echo "MKWCBUILD: This CPU does not support x86-64-v3; building for x86-64-v2"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
assets=$workspace/Assets
|
assets=$workspace/Assets
|
||||||
generated=$workspace/generated
|
generated=$workspace/generated
|
||||||
functions=$generated/functions
|
functions=$generated/functions
|
||||||
@@ -182,9 +220,36 @@ translation_provenance=$generated/translation-provenance.json
|
|||||||
toolchain_provenance=$build/toolchain-provenance.json
|
toolchain_provenance=$build/toolchain-provenance.json
|
||||||
retro_root=${retro_rewind_package_dir:-$workspace/PulsarPacks/completed/RetroRewind/RetroRewind6}
|
retro_root=${retro_rewind_package_dir:-$workspace/PulsarPacks/completed/RetroRewind/RetroRewind6}
|
||||||
|
|
||||||
assert_file "$project" "Translation project"
|
|
||||||
assert_file "$assets/main.dol" "Extracted main.dol (see translator/README.md - owning the game is required)"
|
assert_file "$assets/main.dol" "Extracted main.dol (see translator/README.md - owning the game is required)"
|
||||||
assert_file "$assets/StaticR.rel" "Extracted StaticR.rel (see translator/README.md - owning the game is required)"
|
assert_file "$assets/StaticR.rel" "Extracted StaticR.rel (see translator/README.md - owning the game is required)"
|
||||||
|
# The translation project is the one pinned to the extracted main.dol, which decides the region.
|
||||||
|
project=$(mkw_resolve_project "$workspace" "$assets") || fail "the extracted game cannot be translated"
|
||||||
|
region=$(mkw_project_pin "$project" region)
|
||||||
|
[[ -n "$region" ]] || fail "Could not find the region of $project"
|
||||||
|
|
||||||
|
# The AppImage bundles Clang, but Linux startup objects and the C/C++ link runtimes
|
||||||
|
# still come from the host. Check them before the expensive translation so a missing
|
||||||
|
# development package produces a useful error instead of CMake's generic exit 1.
|
||||||
|
link_probe_dir=$(mktemp -d)
|
||||||
|
link_probe_flags=()
|
||||||
|
[[ -z "$sysroot" ]] || link_probe_flags+=(--sysroot="$sysroot")
|
||||||
|
[[ -z "$fuse_ld_override" ]] || link_probe_flags+=(-fuse-ld="$fuse_ld_override")
|
||||||
|
printf 'int main(void) { return 0; }\n' > "$link_probe_dir/probe.c"
|
||||||
|
cat > "$link_probe_dir/probe.cpp" <<'EOF'
|
||||||
|
#include <vector>
|
||||||
|
int main() { std::vector<int> values{1}; return values.front() - 1; }
|
||||||
|
EOF
|
||||||
|
if ! "$cc_bin" "${link_probe_flags[@]}" "$link_probe_dir/probe.c" -o "$link_probe_dir/probe-c" > "$link_probe_dir/error" 2>&1; then
|
||||||
|
cat "$link_probe_dir/error" >&2
|
||||||
|
rm -rf "$link_probe_dir"
|
||||||
|
fail "The C compiler cannot link a test program. Linux needs C development files (glibc startup objects and a compiler runtime) in addition to bundled Clang. Install your distribution's development packages, or on SteamOS run WiiCompiled through the Wheel Wizard Flatpak."
|
||||||
|
fi
|
||||||
|
if ! "$cxx_bin" "${link_probe_flags[@]}" "$link_probe_dir/probe.cpp" -o "$link_probe_dir/probe-cxx" > "$link_probe_dir/error" 2>&1; then
|
||||||
|
cat "$link_probe_dir/error" >&2
|
||||||
|
rm -rf "$link_probe_dir"
|
||||||
|
fail "The C++ compiler cannot link a test program. Install your distribution's C++ development packages, or on SteamOS run WiiCompiled through the Wheel Wizard Flatpak."
|
||||||
|
fi
|
||||||
|
rm -rf "$link_probe_dir"
|
||||||
|
|
||||||
# Literal line matching against the manifest's fixed shape, not a YAML dependency - the same
|
# Literal line matching against the manifest's fixed shape, not a YAML dependency - the same
|
||||||
# approach NativeBuildFlags.ps1's Get-MkwProjectPins uses on Windows, kept here only for the one
|
# approach NativeBuildFlags.ps1's Get-MkwProjectPins uses on Windows, kept here only for the one
|
||||||
@@ -238,10 +303,9 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Translation cache: this script is the only owner of the reuse decision (unlike LocalBuild.ps1,
|
# Translation cache: this script is the only owner of the reuse decision. The identity (see
|
||||||
# which is handed caller-computed fingerprints by the Windows installer - there is no Linux
|
# build-cache.sh) covers the game inputs, project, native registrations, translator and this script,
|
||||||
# installer yet to supply anything). Hash the game inputs the translation actually depends on;
|
# matching the Windows installer's translation identity.
|
||||||
# a match plus every expected output file present means the previous translation is still good.
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
if (( force_clean_build )); then
|
if (( force_clean_build )); then
|
||||||
@@ -249,13 +313,12 @@ if (( force_clean_build )); then
|
|||||||
rm -rf "$generated" "$base_manifest_dir" "$build"
|
rm -rf "$generated" "$base_manifest_dir" "$build"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
translation_fingerprint=$(cat "$assets/main.dol" "$assets/StaticR.rel" "$project" | sha256sum | awk '{print $1}')
|
translator_identity_input=${translator_bin:-$(dirname "$translator_dll")}
|
||||||
|
translation_fingerprint=$(mkw_translation_identity "$workspace" "$translator_identity_input" "$script_dir/local-build.sh")
|
||||||
reuse_base=0
|
reuse_base=0
|
||||||
if [[ -f "$translation_provenance" ]]; then
|
if [[ "$(mkw_read_identity "$translation_provenance" TranslationFingerprint)" == "$translation_fingerprint" \
|
||||||
recorded=$(grep -o '"TranslationFingerprint" *: *"[^"]*"' "$translation_provenance" 2>/dev/null | sed 's/.*"\([0-9a-f]*\)"$/\1/' || true)
|
&& -f "$base_metadata" && -f "$base_manifest" ]]; then
|
||||||
if [[ "$recorded" == "$translation_fingerprint" && -f "$base_metadata" && -f "$base_manifest" ]]; then
|
reuse_base=1
|
||||||
reuse_base=1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if (( builds_retro )); then
|
if (( builds_retro )); then
|
||||||
@@ -301,10 +364,9 @@ else
|
|||||||
|
|
||||||
log_step emit-base-manifest "Creating the local base translation manifest"
|
log_step emit-base-manifest "Creating the local base translation manifest"
|
||||||
translator emit-base-manifest --project "$project" --out "$base_manifest_dir" \
|
translator emit-base-manifest --project "$project" --out "$base_manifest_dir" \
|
||||||
--functions-dir "$functions" --translation-output-metadata "$base_metadata" --region P
|
--functions-dir "$functions" --translation-output-metadata "$base_metadata" --region "$region"
|
||||||
|
|
||||||
printf '{"SchemaVersion":1,"TranslationFingerprint":"%s"}' "$translation_fingerprint" \
|
mkw_write_identity "$translation_provenance" TranslationFingerprint "$translation_fingerprint"
|
||||||
> "$translation_provenance"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if (( builds_retro )); then
|
if (( builds_retro )); then
|
||||||
@@ -314,12 +376,13 @@ if (( builds_retro )); then
|
|||||||
translate_mod_args=(translate-mod --project "$project" --profile retro-rewind
|
translate_mod_args=(translate-mod --project "$project" --profile retro-rewind
|
||||||
--base-manifest "$base_manifest" --base-translation-output-metadata "$base_metadata"
|
--base-manifest "$base_manifest" --base-translation-output-metadata "$base_metadata"
|
||||||
--code-pul "$code_pul" --mod-root "$retro_root" --mod-name "Retro Rewind"
|
--code-pul "$code_pul" --mod-root "$retro_root" --mod-name "Retro Rewind"
|
||||||
--region P --out "$retro_out" --prefer-cached-inputs --emit-cpp
|
--region "$region" --out "$retro_out" --prefer-cached-inputs --emit-cpp
|
||||||
--threads "$translator_threads")
|
--threads "$translator_threads")
|
||||||
if (( skip_retro_wfc_payload )); then
|
if (( skip_retro_wfc_payload )); then
|
||||||
translate_mod_args+=(--skip-retro-wfc)
|
translate_mod_args+=(--skip-retro-wfc)
|
||||||
else
|
else
|
||||||
offline_payload=$retro_wfc_offline_dir/binary/payload.RMCPD00.bin
|
payload_file=$(mkw_retro_wfc_payload_file "$project") || fail "$project pins no Retro-WFC payload"
|
||||||
|
offline_payload=$retro_wfc_offline_dir/$payload_file
|
||||||
assert_file "$offline_payload" "Offline Retro-WFC shared payload"
|
assert_file "$offline_payload" "Offline Retro-WFC shared payload"
|
||||||
translate_mod_args+=(--retro-wfc-payload "$offline_payload")
|
translate_mod_args+=(--retro-wfc-payload "$offline_payload")
|
||||||
fi
|
fi
|
||||||
@@ -379,6 +442,13 @@ if [[ -f "$build/CMakeCache.txt" ]]; then
|
|||||||
done < <(grep -o ':FILEPATH=.*' "$build/CMakeCache.txt" | sed 's/^:FILEPATH=//')
|
done < <(grep -o ':FILEPATH=.*' "$build/CMakeCache.txt" | sed 's/^:FILEPATH=//')
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# The AppImage keeps tool paths stable across updates, so a changed compiler is only visible here.
|
||||||
|
toolchain_fingerprint=$(mkw_toolchain_identity "$workspace" "$cc_bin" "$cxx_bin" "$cmake_bin" "$ninja_bin")
|
||||||
|
if [[ "$keep_native_build" -eq 1 && \
|
||||||
|
"$(mkw_read_identity "$toolchain_provenance" ToolchainFingerprint)" != "$toolchain_fingerprint" ]]; then
|
||||||
|
keep_native_build=0
|
||||||
|
stale_reason=" (the toolchain or a pinned dependency changed)"
|
||||||
|
fi
|
||||||
if [[ -d "$build" && "$keep_native_build" -eq 0 ]]; then
|
if [[ -d "$build" && "$keep_native_build" -eq 0 ]]; then
|
||||||
echo "MKWCBUILD: The native build cache does not belong to this workspace path or toolchain; rebuilding from scratch$stale_reason"
|
echo "MKWCBUILD: The native build cache does not belong to this workspace path or toolchain; rebuilding from scratch$stale_reason"
|
||||||
rm -rf "$build"
|
rm -rf "$build"
|
||||||
@@ -390,7 +460,9 @@ configure_args=(-S "$workspace/runtime" -B "$build" -G Ninja
|
|||||||
-DCMAKE_BUILD_TYPE=Release
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
-DCMAKE_C_COMPILER="$cc_bin" -DCMAKE_CXX_COMPILER="$cxx_bin"
|
-DCMAKE_C_COMPILER="$cc_bin" -DCMAKE_CXX_COMPILER="$cxx_bin"
|
||||||
-DCMAKE_MAKE_PROGRAM="$ninja_bin"
|
-DCMAKE_MAKE_PROGRAM="$ninja_bin"
|
||||||
-DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs")
|
-DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs"
|
||||||
|
-DMKW_SETUP_VERSION="$setup_version"
|
||||||
|
-DMKW_X86_CPU_PROFILE="$x86_cpu_profile")
|
||||||
# CMAKE_C_COMPILER/CXX_COMPILER stay stable across AppImage runs on their own (they're exactly
|
# CMAKE_C_COMPILER/CXX_COMPILER stay stable across AppImage runs on their own (they're exactly
|
||||||
# what's passed via -D above, and AppRun points --cc/--cxx at a symlink it re-targets at the
|
# what's passed via -D above, and AppRun points --cc/--cxx at a symlink it re-targets at the
|
||||||
# current mount every launch - see build-appimage.sh). CMAKE_AR/RANLIB/LINKER/ASM_COMPILER do NOT
|
# current mount every launch - see build-appimage.sh). CMAKE_AR/RANLIB/LINKER/ASM_COMPILER do NOT
|
||||||
@@ -409,14 +481,25 @@ if [[ "$cc_bin" == */* ]]; then
|
|||||||
configure_args+=(-DCMAKE_ASM_COMPILER="$cc_bin")
|
configure_args+=(-DCMAKE_ASM_COMPILER="$cc_bin")
|
||||||
fi
|
fi
|
||||||
if [[ -n "$fuse_ld_override" ]]; then
|
if [[ -n "$fuse_ld_override" ]]; then
|
||||||
configure_args+=(-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=$fuse_ld_override")
|
# CMAKE_EXE_LINKER_FLAGS is one string that CMake pastes into the shell link command.
|
||||||
|
printf -v fuse_ld_quoted '%q' "$fuse_ld_override"
|
||||||
|
configure_args+=(-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=$fuse_ld_quoted")
|
||||||
fi
|
fi
|
||||||
if [[ -n "$native_prebuilt_dir" ]]; then
|
if [[ -n "$native_prebuilt_dir" ]]; then
|
||||||
configure_args+=(-DMKW_NATIVE_PREBUILT_DIR="$native_prebuilt_dir")
|
configure_args+=(-DMKW_NATIVE_PREBUILT_DIR="$native_prebuilt_dir")
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "$sysroot" ]]; then
|
||||||
|
configure_args+=(-DCMAKE_SYSROOT="$sysroot")
|
||||||
|
else
|
||||||
|
# Explicitly clear any cached CMAKE_SYSROOT from a prior configure so an
|
||||||
|
# incremental build that transitions from one sysroot to none does not
|
||||||
|
# silently keep the stale cached path.
|
||||||
|
configure_args+=(-UCMAKE_SYSROOT)
|
||||||
|
fi
|
||||||
|
|
||||||
log_step configure-native "Configuring the native toolchain"
|
log_step configure-native "Configuring the native toolchain"
|
||||||
"$cmake_bin" "${configure_args[@]}"
|
"$cmake_bin" "${configure_args[@]}"
|
||||||
|
mkw_write_identity "$toolchain_provenance" ToolchainFingerprint "$toolchain_fingerprint"
|
||||||
|
|
||||||
case "$profile" in
|
case "$profile" in
|
||||||
base) targets=(WiiCompiled) ;;
|
base) targets=(WiiCompiled) ;;
|
||||||
@@ -445,7 +528,9 @@ publish_built_product() {
|
|||||||
local exe=$build/$target
|
local exe=$build/$target
|
||||||
assert_file "$exe" "Locally compiled game executable"
|
assert_file "$exe" "Locally compiled game executable"
|
||||||
cp -f "$exe" "$destination/$target"
|
cp -f "$exe" "$destination/$target"
|
||||||
for name in dsp_coef.bin initial_pipeline_cache.db; do
|
# cacert.pem is the TLS root bundle the mbed TLS path looks up beside the executable
|
||||||
|
# (runtime/src/hle/net/network_ssl.cpp); without it HTTPS fails at runtime.
|
||||||
|
for name in dsp_coef.bin initial_pipeline_cache.db cacert.pem; do
|
||||||
[[ -f "$build/$name" ]] && cp -f "$build/$name" "$destination/"
|
[[ -f "$build/$name" ]] && cp -f "$build/$name" "$destination/"
|
||||||
done
|
done
|
||||||
[[ -d "$build/wii_bootstrap" ]] && cp -rf "$build/wii_bootstrap" "$destination/"
|
[[ -d "$build/wii_bootstrap" ]] && cp -rf "$build/wii_bootstrap" "$destination/"
|
||||||
|
|||||||
@@ -9,45 +9,97 @@ 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 PATH --translator PATH --cmake-root DIR --ninja PATH --output PKG [options]
|
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]
|
||||||
|
|
||||||
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 arm64 artifacts. The resulting pkg
|
maintainer-verified, redistributable macOS artifacts for both arm64 and
|
||||||
is unsigned unless --installer-identity is supplied; releases should sign and
|
x86_64. The setup package selects native tools for its host while the game is
|
||||||
notarize it with a Developer ID Installer certificate.
|
compiled locally for that host architecture. CMake must be universal2. The
|
||||||
|
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: project version, or 0.1.0)
|
||||||
--installer-identity NAME Developer ID Installer identity for productbuild
|
--installer-identity NAME Developer ID Installer identity for productbuild
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
workspace=$(cd "$script_dir/../.." && pwd); nodtool=""; translator=""; cmake_root=""; ninja=""; output=""; version=0.1.0; identity=""
|
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=""
|
||||||
|
version_supplied=false
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--workspace) workspace=${2:-}; shift 2 ;;
|
--workspace) workspace=${2:-}; shift 2 ;;
|
||||||
--nodtool) nodtool=${2:-}; shift 2 ;;
|
--nodtool-arm64) nodtool_arm64=${2:-}; shift 2 ;;
|
||||||
--translator) translator=${2:-}; shift 2 ;;
|
--nodtool-x86_64) nodtool_x86_64=${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) ninja=${2:-}; shift 2 ;;
|
--ninja-arm64) ninja_arm64=${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:-}; version_supplied=true; shift 2 ;;
|
||||||
--installer-identity) identity=${2:-}; shift 2 ;;
|
--installer-identity) identity=${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
|
||||||
version=${version#v}
|
version=${version#v}
|
||||||
|
if [[ "$version_supplied" == false ]]; then
|
||||||
|
local_csproj="$workspace/Launcher/Directory.Build.props"
|
||||||
|
if [[ -f "$local_csproj" ]]; then
|
||||||
|
detected=$(grep -m1 '<Version>' "$local_csproj" | sed -E 's/.*<Version>([^<]+)<\/Version>.*/\1/' || true)
|
||||||
|
if [[ -n "$detected" ]]; then
|
||||||
|
version="$detected"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
[[ "$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; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done
|
for tool in pkgbuild productbuild ditto codesign lipo; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done
|
||||||
[[ -x "$nodtool" ]] || fail '--nodtool must name an executable'
|
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 "$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'
|
||||||
[[ -x "$ninja" ]] || fail '--ninja must name an executable'
|
require_arch() {
|
||||||
"$nodtool" --version >/dev/null || fail '--nodtool did not run successfully'
|
local artifact=$1 arch=$2 label=$3
|
||||||
|
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
|
||||||
@@ -64,7 +116,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>14.0</string>
|
<key>LSMinimumSystemVersion</key><string>12.0</string>
|
||||||
</dict></plist>
|
</dict></plist>
|
||||||
EOF
|
EOF
|
||||||
cat > "$app/Contents/MacOS/WiiCompiledSetup" <<'EOF'
|
cat > "$app/Contents/MacOS/WiiCompiledSetup" <<'EOF'
|
||||||
@@ -96,18 +148,36 @@ copy_clean "$script_dir/setup.command" "$resources/setup.command"; chmod +x "$re
|
|||||||
mkdir -p "$resources/workspace"
|
mkdir -p "$resources/workspace"
|
||||||
for source in aurora-main projects runtime translator; do
|
for source in aurora-main projects runtime translator; do
|
||||||
[[ -d "$workspace/$source" ]] || fail "required workspace directory is missing: $source"
|
[[ -d "$workspace/$source" ]] || fail "required workspace directory is missing: $source"
|
||||||
|
[[ "$source" != aurora-main || -f "$workspace/$source/CMakeLists.txt" ]] ||
|
||||||
|
fail "aurora-main is empty; run git submodule update --init --recursive"
|
||||||
copy_clean "$workspace/$source" "$resources/workspace/$source"
|
copy_clean "$workspace/$source" "$resources/workspace/$source"
|
||||||
done
|
done
|
||||||
|
# Setup bundles contain materialized sources and must work without Git.
|
||||||
|
find "$resources/workspace" -name .git -prune -exec rm -rf {} +
|
||||||
|
# The translation identity (build-cache.sh) covers the runtime's native registrations through this
|
||||||
|
# index, so it is extracted from exactly the packaged runtime sources.
|
||||||
|
if [[ "$host_arch" == arm64 ]]; then host_translator=$translator_arm64; else host_translator=$translator_x86_64; fi
|
||||||
|
"$host_translator" emit-native-index --native-source-dir "$resources/workspace/runtime/src" \
|
||||||
|
--out "$resources/workspace/runtime/native-index.json" || fail 'the runtime native index could not be extracted'
|
||||||
|
[[ -f "$resources/workspace/runtime/native-index.json" ]] || fail 'the runtime native index was not written'
|
||||||
mkdir -p "$resources/workspace/Launcher/macos"
|
mkdir -p "$resources/workspace/Launcher/macos"
|
||||||
|
copy_clean "$workspace/Launcher/Directory.Build.props" "$resources/workspace/Launcher/Directory.Build.props"
|
||||||
copy_clean "$workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/local-build-macos.command"
|
copy_clean "$workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/local-build-macos.command"
|
||||||
|
copy_clean "$workspace/Launcher/build-cache.sh" "$resources/workspace/Launcher/build-cache.sh"
|
||||||
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
|
||||||
mkdir -p "$resources/tools/cmake"
|
# setup.command uses this marker to update source inputs in an existing user
|
||||||
copy_clean "$nodtool" "$resources/tools/nodtool"; chmod +x "$resources/tools/nodtool"
|
# workspace without replacing extracted game assets or Retro Rewind files.
|
||||||
copy_clean "$translator" "$resources/tools/Translator.Cli"; chmod +x "$resources/tools/Translator.Cli"
|
printf '%s\n' "$version" > "$resources/workspace/.bundle-version"
|
||||||
|
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" "$resources/tools/ninja"; chmod +x "$resources/tools/ninja"
|
copy_clean "$ninja_arm64" "$resources/tools/arm64/ninja"; chmod +x "$resources/tools/arm64/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,30 +1,35 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Extract a user-owned Mario Kart Wii PAL (RMCP01) disc image for a local build.
|
# Extract a user-owned Mario Kart Wii disc image of a supported release for a local build.
|
||||||
# This script deliberately contains no game data and is intended for the macOS
|
# This script deliberately contains no game data and is intended for the macOS
|
||||||
# setup application and for maintainers testing that setup path.
|
# setup application and for maintainers testing that setup path.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
readonly EXPECTED_DOL_SHA256=80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
readonly EXPECTED_REL_SHA256=16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d
|
# shellcheck source=../build-cache.sh
|
||||||
|
source "$script_dir/../build-cache.sh"
|
||||||
|
|
||||||
fail() { printf 'extract-disc.command: error: %s\n' "$*" >&2; exit 1; }
|
fail() { printf 'extract-disc.command: error: %s\n' "$*" >&2; exit 1; }
|
||||||
sha256() { shasum -a 256 "$1" | awk '{ print $1 }'; }
|
sha256() { shasum -a 256 "$1" | awk '{ print $1 }'; }
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: extract-disc.command --game IMAGE --assets-dir DIR --nodtool PATH
|
Usage: extract-disc.command --game IMAGE --assets-dir DIR --nodtool PATH [--workspace DIR]
|
||||||
|
|
||||||
Extracts a user-owned Mario Kart Wii PAL RMCP01 image into DIR. The final
|
Extracts a user-owned Mario Kart Wii image into DIR. The image must be a release
|
||||||
layout is DIR/main.dol, DIR/StaticR.rel, and DIR/DATA. Existing data is left
|
a projects/*/recomp.yml in the workspace (default: this script's repository)
|
||||||
untouched unless the complete new extraction passes both content hash checks.
|
pins. The final layout is DIR/main.dol, DIR/StaticR.rel, and DIR/DATA. Existing
|
||||||
|
data is left untouched unless the complete new extraction passes both content
|
||||||
|
hash checks.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
game=""
|
game=""
|
||||||
assets_dir=""
|
assets_dir=""
|
||||||
nodtool=""
|
nodtool=""
|
||||||
|
workspace=$(cd "$script_dir/../.." && pwd)
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
--workspace) workspace=${2:-}; shift 2 ;;
|
||||||
--game) game=${2:-}; shift 2 ;;
|
--game) game=${2:-}; shift 2 ;;
|
||||||
--assets-dir) assets_dir=${2:-}; shift 2 ;;
|
--assets-dir) assets_dir=${2:-}; shift 2 ;;
|
||||||
--nodtool) nodtool=${2:-}; shift 2 ;;
|
--nodtool) nodtool=${2:-}; shift 2 ;;
|
||||||
@@ -38,12 +43,30 @@ done
|
|||||||
[[ -x "$nodtool" ]] || fail "nodtool is not executable: $nodtool"
|
[[ -x "$nodtool" ]] || fail "nodtool is not executable: $nodtool"
|
||||||
|
|
||||||
assets_dir=$(mkdir -p "$assets_dir" && cd "$assets_dir" && pwd)
|
assets_dir=$(mkdir -p "$assets_dir" && cd "$assets_dir" && pwd)
|
||||||
|
|
||||||
|
# The disc's game ID selects the release, so an existing extraction is only reused for the same one.
|
||||||
|
printf 'MKWCBUILD:STEP:validate-disc Checking the selected disc image\n'
|
||||||
|
disc_info=$("$nodtool" info "$game") || fail 'nodtool could not read the disc image'
|
||||||
|
game_id=$(awk '/^Game ID: / { print $3; exit }' <<<"$disc_info")
|
||||||
|
[[ -n "$game_id" ]] || fail 'nodtool did not report the disc game ID'
|
||||||
|
project=$(mkw_project_for_game "$workspace" "$game_id") || fail "disc $game_id is not a supported Mario Kart Wii release"
|
||||||
|
expected_dol_sha256=$(mkw_project_pin "$project" dol)
|
||||||
|
expected_rel_sha256=$(mkw_project_pin "$project" rel)
|
||||||
|
[[ -n "$expected_dol_sha256" && -n "$expected_rel_sha256" ]] || fail "$project does not pin the clean $game_id digests"
|
||||||
|
|
||||||
|
# Sorted "<size> <path>" of every extracted file; recorded at extraction, compared on reuse.
|
||||||
|
data_manifest() { (cd "$1" && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 stat -f '%z %N'); }
|
||||||
|
|
||||||
|
if [[ -d "$assets_dir/DATA/files" && -f "$assets_dir/.data-manifest" && -f "$assets_dir/main.dol" && -f "$assets_dir/StaticR.rel" ]] && \
|
||||||
|
[[ $(sha256 "$assets_dir/main.dol") == "$expected_dol_sha256" && $(sha256 "$assets_dir/StaticR.rel") == "$expected_rel_sha256" ]] && \
|
||||||
|
[[ "$(data_manifest "$assets_dir/DATA")" == "$(cat "$assets_dir/.data-manifest")" ]]; then
|
||||||
|
printf 'MKWCBUILD:STEP:disc-ready Reusing the verified extracted game assets\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
scratch=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-disc.XXXXXX")
|
scratch=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-disc.XXXXXX")
|
||||||
cleanup() { rm -rf "$scratch"; }
|
cleanup() { rm -rf "$scratch"; }
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
printf 'MKWCBUILD:STEP:validate-disc Checking the selected disc image\n'
|
|
||||||
"$nodtool" info "$game" >/dev/null
|
|
||||||
printf 'MKWCBUILD:STEP:extract-disc Extracting the user-owned disc image\n'
|
printf 'MKWCBUILD:STEP:extract-disc Extracting the user-owned disc image\n'
|
||||||
"$nodtool" extract "$game" "$scratch/extracted"
|
"$nodtool" extract "$game" "$scratch/extracted"
|
||||||
|
|
||||||
@@ -51,8 +74,10 @@ dol=$(find "$scratch/extracted" -type f -path '*/sys/main.dol' -print -quit)
|
|||||||
rel=$(find "$scratch/extracted" -type f -path '*/files/rel/StaticR.rel' -print -quit)
|
rel=$(find "$scratch/extracted" -type f -path '*/files/rel/StaticR.rel' -print -quit)
|
||||||
[[ -n "$dol" ]] || fail 'nodtool extraction did not contain sys/main.dol'
|
[[ -n "$dol" ]] || fail 'nodtool extraction did not contain sys/main.dol'
|
||||||
[[ -n "$rel" ]] || fail 'nodtool extraction did not contain files/rel/StaticR.rel'
|
[[ -n "$rel" ]] || fail 'nodtool extraction did not contain files/rel/StaticR.rel'
|
||||||
[[ $(sha256 "$dol") == "$EXPECTED_DOL_SHA256" ]] || fail 'disc is not the supported clean PAL RMCP01 Mario Kart Wii image'
|
dol_sha=$(sha256 "$dol")
|
||||||
[[ $(sha256 "$rel") == "$EXPECTED_REL_SHA256" ]] || fail 'disc has an unexpected StaticR.rel; use a clean PAL RMCP01 image'
|
rel_sha=$(sha256 "$rel")
|
||||||
|
[[ $dol_sha == "$expected_dol_sha256" ]] || fail "main.dol does not match the clean $game_id image (expected $expected_dol_sha256, got $dol_sha)"
|
||||||
|
[[ $rel_sha == "$expected_rel_sha256" ]] || fail "StaticR.rel does not match the clean $game_id image (expected $expected_rel_sha256, got $rel_sha)"
|
||||||
|
|
||||||
data_root=$(dirname "$(dirname "$dol")")
|
data_root=$(dirname "$(dirname "$dol")")
|
||||||
[[ -d "$data_root/files" ]] || fail 'nodtool extraction did not contain the Wii files directory'
|
[[ -d "$data_root/files" ]] || fail 'nodtool extraction did not contain the Wii files directory'
|
||||||
@@ -64,18 +89,20 @@ mkdir -p "$stage"
|
|||||||
ditto "$data_root" "$stage/DATA"
|
ditto "$data_root" "$stage/DATA"
|
||||||
ditto "$dol" "$stage/main.dol"
|
ditto "$dol" "$stage/main.dol"
|
||||||
ditto "$rel" "$stage/StaticR.rel"
|
ditto "$rel" "$stage/StaticR.rel"
|
||||||
|
data_manifest "$stage/DATA" > "$stage/.data-manifest"
|
||||||
|
|
||||||
backup="$assets_dir/.previous-extraction-$(date +%Y%m%d-%H%M%S)"
|
backup="$assets_dir/.previous-extraction-$(date +%Y%m%d-%H%M%S)"
|
||||||
if [[ -e "$assets_dir/DATA" || -e "$assets_dir/main.dol" || -e "$assets_dir/StaticR.rel" ]]; then
|
if [[ -e "$assets_dir/DATA" || -e "$assets_dir/.data-manifest" || -e "$assets_dir/main.dol" || -e "$assets_dir/StaticR.rel" ]]; then
|
||||||
mkdir -p "$backup"
|
mkdir -p "$backup"
|
||||||
for item in DATA main.dol StaticR.rel; do
|
for item in DATA .data-manifest main.dol StaticR.rel; do
|
||||||
[[ -e "$assets_dir/$item" ]] && mv "$assets_dir/$item" "$backup/$item"
|
[[ -e "$assets_dir/$item" ]] && mv "$assets_dir/$item" "$backup/$item"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
mv "$stage/DATA" "$assets_dir/DATA"
|
mv "$stage/DATA" "$assets_dir/DATA"
|
||||||
|
mv "$stage/.data-manifest" "$assets_dir/.data-manifest"
|
||||||
mv "$stage/main.dol" "$assets_dir/main.dol"
|
mv "$stage/main.dol" "$assets_dir/main.dol"
|
||||||
mv "$stage/StaticR.rel" "$assets_dir/StaticR.rel"
|
mv "$stage/StaticR.rel" "$assets_dir/StaticR.rel"
|
||||||
rmdir "$stage"
|
rmdir "$stage"
|
||||||
rm -rf "$backup"
|
rm -rf "$backup"
|
||||||
|
|
||||||
printf 'MKWCBUILD:STEP:disc-ready Verified and extracted clean PAL RMCP01 game assets\n'
|
printf 'MKWCBUILD:STEP:disc-ready Verified and extracted clean %s game assets\n' "$game_id"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# 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)
|
||||||
@@ -5,29 +5,37 @@ 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
|
Usage: publish-app.command --build-dir DIR --product {WiiCompiled|RetroRewind} --output-dir DIR [options]
|
||||||
|
|
||||||
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=""
|
build_dir=""; product=""; output_dir=""; architecture=$(uname -m); minimum_system_version=12.0
|
||||||
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'
|
||||||
for tool in codesign ditto install_name_tool otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
|
[[ "$architecture" == arm64 || "$architecture" == x86_64 ]] || fail '--architecture must be arm64 or x86_64'
|
||||||
|
[[ "$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"
|
||||||
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
|
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 cacert.pem 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"
|
||||||
macos="$app/Contents/MacOS"
|
macos="$app/Contents/MacOS"
|
||||||
@@ -47,35 +55,106 @@ 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>14.0</string>
|
<key>LSMinimumSystemVersion</key><string>$minimum_system_version</string>
|
||||||
<key>NSHighResolutionCapable</key><true/>
|
<key>NSHighResolutionCapable</key><true/>
|
||||||
</dict></plist>
|
</dict></plist>
|
||||||
EOF
|
EOF
|
||||||
ditto "$build_dir/$product" "$macos/$product"
|
ditto "$build_dir/$product" "$macos/$product"
|
||||||
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do
|
for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do
|
||||||
ditto "$build_dir/$asset" "$resources/$asset"
|
ditto "$build_dir/$asset" "$resources/$asset"
|
||||||
ln -s "../Resources/$asset" "$macos/$asset"
|
ln -s "../Resources/$asset" "$macos/$asset"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Build a closure of Homebrew dylibs. System libraries remain system references.
|
# Expand each image's rpaths before inheriting them, so @loader_path stays
|
||||||
queue=("$macos/$product")
|
# relative to the image that declared it rather than a descendant library.
|
||||||
|
expanded_rpaths() {
|
||||||
|
local target=$1 rpath
|
||||||
|
while IFS= read -r rpath; do
|
||||||
|
case "$rpath" in
|
||||||
|
@loader_path/*) rpath="$(dirname "$target")/${rpath#@loader_path/}" ;;
|
||||||
|
@loader_path) rpath="$(dirname "$target")" ;;
|
||||||
|
@executable_path/*) rpath="$build_dir/${rpath#@executable_path/}" ;;
|
||||||
|
@executable_path) rpath="$build_dir" ;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "$rpath"
|
||||||
|
done < <(otool -l "$target" | awk '
|
||||||
|
/LC_RPATH/ { rpath = 1; next }
|
||||||
|
rpath && /^[[:space:]]*path / {
|
||||||
|
sub(/^[[:space:]]*path[[:space:]]+/, "");
|
||||||
|
sub(/[[:space:]]+\(offset[[:space:]]+[0-9]+\)$/, "");
|
||||||
|
print;
|
||||||
|
rpath = 0;
|
||||||
|
}')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve a non-system dependency using the current image's rpaths followed
|
||||||
|
# by the inherited loader stack, matching dyld's dependency-chain search.
|
||||||
|
dependency_path() {
|
||||||
|
local current=$1 dependency=$2 search_rpaths=$3 rpath candidate
|
||||||
|
case "$dependency" in
|
||||||
|
/System/Library/*|/usr/lib/*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
/*)
|
||||||
|
[[ -f "$dependency" ]] && { printf '%s\n' "$dependency"; return 0; }
|
||||||
|
;;
|
||||||
|
@loader_path/*)
|
||||||
|
candidate="$(dirname "$current")/${dependency#@loader_path/}"
|
||||||
|
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
||||||
|
;;
|
||||||
|
@executable_path/*)
|
||||||
|
candidate="$build_dir/${dependency#@executable_path/}"
|
||||||
|
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
||||||
|
;;
|
||||||
|
@rpath/*|*.dylib)
|
||||||
|
local subpath
|
||||||
|
if [[ "$dependency" == @rpath/* ]]; then
|
||||||
|
subpath="${dependency#@rpath/}"
|
||||||
|
else
|
||||||
|
subpath="$dependency"
|
||||||
|
fi
|
||||||
|
while IFS= read -r rpath; do
|
||||||
|
[[ -n "$rpath" ]] || continue
|
||||||
|
candidate="$rpath/$subpath"
|
||||||
|
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
||||||
|
done <<< "$search_rpaths"
|
||||||
|
;;
|
||||||
|
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=("$build_dir/$product")
|
||||||
|
queue_rpaths=("")
|
||||||
while ((${#queue[@]})); do
|
while ((${#queue[@]})); do
|
||||||
current=${queue[0]}
|
current=${queue[0]}
|
||||||
|
inherited_rpaths=${queue_rpaths[0]}
|
||||||
queue=("${queue[@]:1}")
|
queue=("${queue[@]:1}")
|
||||||
|
queue_rpaths=("${queue_rpaths[@]:1}")
|
||||||
|
current_rpaths=$(expanded_rpaths "$current")
|
||||||
|
if [[ -n "$inherited_rpaths" ]]; then
|
||||||
|
current_rpaths="${current_rpaths}${current_rpaths:+$'\n'}$inherited_rpaths"
|
||||||
|
fi
|
||||||
|
self_id=$(otool -D "$current" 2>/dev/null | tail -n +2 | sed -nE 's/^[[:space:]]*(.*)[[:space:]]*$/\1/p' || true)
|
||||||
while IFS= read -r dependency; do
|
while IFS= read -r dependency; do
|
||||||
[[ "$dependency" == /opt/homebrew/* || "$dependency" == /usr/local/* ]] || continue
|
[[ -z "$dependency" ]] && continue
|
||||||
[[ -f "$dependency" ]] || continue
|
[[ -n "$self_id" && "$dependency" == "$self_id" ]] && continue
|
||||||
name=$(basename "$dependency")
|
case "$dependency" in
|
||||||
|
/System/Library/*|/usr/lib/*) continue ;;
|
||||||
|
esac
|
||||||
|
dep_path=$(dependency_path "$current" "$dependency" "$current_rpaths") || fail "unresolved non-system dependency: '$dependency' needed by '$current'"
|
||||||
|
name=$(basename "$dep_path")
|
||||||
if [[ ! -f "$frameworks/$name" ]]; then
|
if [[ ! -f "$frameworks/$name" ]]; then
|
||||||
ditto "$dependency" "$frameworks/$name"
|
ditto "$dep_path" "$frameworks/$name"
|
||||||
install_name_tool -id "@rpath/$name" "$frameworks/$name"
|
install_name_tool -id "@rpath/$name" "$frameworks/$name"
|
||||||
queue+=("$frameworks/$name")
|
queue+=("$dep_path")
|
||||||
|
queue_rpaths+=("$current_rpaths")
|
||||||
fi
|
fi
|
||||||
done < <(otool -L "$current" | tail -n +2 | awk '{print $1}')
|
done < <(otool -L "$current" | tail -n +2 | sed -nE 's/^[[:space:]]*(.*)[[:space:]]+\(compatibility version .*/\1/p')
|
||||||
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
|
||||||
@@ -83,7 +162,7 @@ while IFS= read -r binary; do
|
|||||||
else
|
else
|
||||||
install_name_tool -change "$old" "@loader_path/$name" "$binary"
|
install_name_tool -change "$old" "@loader_path/$name" "$binary"
|
||||||
fi
|
fi
|
||||||
done < <(otool -L "$binary" | tail -n +2 | awk '{print $1}')
|
done < <(otool -L "$binary" | tail -n +2 | sed -nE 's/^[[:space:]]*(.*)[[:space:]]+\(compatibility version .*/\1/p')
|
||||||
done < <(find "$frameworks" -type f -print; printf '%s\n' "$macos/$product")
|
done < <(find "$frameworks" -type f -print; printf '%s\n' "$macos/$product")
|
||||||
|
|
||||||
find "$frameworks" -type f -exec codesign --force --sign - {} +
|
find "$frameworks" -type f -exec codesign --force --sign - {} +
|
||||||
|
|||||||
@@ -5,16 +5,19 @@ 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"
|
||||||
|
|
||||||
fail() { printf 'WiiCompiled Setup: %s\n' "$*" >&2; exit 1; }
|
fail() { printf 'WiiCompiled Setup: %s\n' "$*" >&2; exit 1; }
|
||||||
notice() { /usr/bin/osascript -e "display dialog \"${1//\"/\\\"}\" buttons {\"OK\"} default button \"OK\" with icon caution" >/dev/null; }
|
notice() {
|
||||||
|
/usr/bin/osascript - "$1" >/dev/null <<'APPLESCRIPT'
|
||||||
|
on run argv
|
||||||
|
display dialog (item 1 of argv) buttons {"OK"} default button "OK" with icon caution
|
||||||
|
end run
|
||||||
|
APPLESCRIPT
|
||||||
|
}
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: setup.command --game IMAGE [--retro-dir DIR] [--install-location {user|applications}]
|
Usage: setup.command --game IMAGE [--retro-dir DIR] [--install-location {user|applications}]
|
||||||
@@ -36,9 +39,16 @@ 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 disc image"
|
||||||
POSIX path of selectedFile
|
POSIX path of selectedFile
|
||||||
APPLESCRIPT
|
APPLESCRIPT
|
||||||
) || exit 0
|
) || exit 0
|
||||||
@@ -60,50 +70,95 @@ 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"
|
||||||
if [[ ! -d "$workspace/.git" && ! -f "$workspace/projects/mkwii/recomp.yml" ]]; then
|
source_bundle_version="$workspace_source/.bundle-version"
|
||||||
printf 'Preparing the local build workspace...\n'
|
workspace_bundle_version="$workspace/.bundle-version"
|
||||||
rm -rf "$workspace"
|
needs_workspace_refresh=0
|
||||||
/usr/bin/ditto "$workspace_source" "$workspace"
|
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
|
fi
|
||||||
|
|
||||||
|
if (( needs_workspace_refresh )); then
|
||||||
|
printf 'Preparing the local build workspace...\n'
|
||||||
|
if [[ ! -d "$workspace" ]]; then
|
||||||
|
/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; unchanged files
|
||||||
|
# keep their timestamps so the incremental native build survives.
|
||||||
|
# shellcheck source=../build-cache.sh
|
||||||
|
source "$workspace_source/Launcher/build-cache.sh"
|
||||||
|
mkw_sync_tree "$workspace_source" "$workspace" aurora-main projects runtime translator Launcher
|
||||||
|
/usr/bin/ditto "$source_bundle_version" "$workspace_bundle_version"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The extracted disc decides the release, and with it the region's Retro-WFC payload.
|
||||||
|
# shellcheck source=../build-cache.sh
|
||||||
|
source "$workspace/Launcher/build-cache.sh"
|
||||||
|
"$workspace/Launcher/macos/extract-disc.command" --workspace "$workspace" --game "$game" \
|
||||||
|
--assets-dir "$workspace/Assets" --nodtool "$nodtool"
|
||||||
|
project=$(mkw_resolve_project "$workspace" "$workspace/Assets") || fail 'the extracted game cannot be built'
|
||||||
|
|
||||||
profile=base
|
profile=base
|
||||||
build_args=(--workspace "$workspace" --game "$game" --nodtool "$nodtool" --output-dir "$products")
|
build_args=(--workspace "$workspace" --output-dir "$products")
|
||||||
if [[ -n "$retro_dir" ]]; then
|
if [[ -n "$retro_dir" ]]; then
|
||||||
profile=both
|
profile=both
|
||||||
# Online play needs the shared Retro-WFC payload. Keep it in the per-user
|
# Online play needs the shared Retro-WFC payload. Keep it in the per-user
|
||||||
# support directory rather than the packaged app or build workspace, then
|
# support directory rather than the packaged app or build workspace, then
|
||||||
# verify its pinned signature before publishing it into the local cache.
|
# verify its pinned signature before publishing it into the local cache.
|
||||||
|
game_id=$(mkw_project_pin "$project" game_id)
|
||||||
|
payload_uri=$(mkw_project_pin "$project" retro_wfc_payload)
|
||||||
|
payload_file=$(mkw_retro_wfc_payload_file "$project") || fail "$project pins no Retro-WFC payload"
|
||||||
retro_wfc_dir="$support_root/RetroWfcPayload"
|
retro_wfc_dir="$support_root/RetroWfcPayload"
|
||||||
retro_wfc_payload="$retro_wfc_dir/binary/payload.RMCPD00.bin"
|
retro_wfc_payload="$retro_wfc_dir/$payload_file"
|
||||||
if [[ -f "$retro_wfc_payload" ]] && ! "$translator" validate-retro-wfc-payload --directory "$retro_wfc_dir"; then
|
cached_payload_valid=0
|
||||||
printf 'Discarding an invalid cached Retro-WFC payload...\n' >&2
|
if [[ -f "$retro_wfc_payload" ]]; then
|
||||||
rm -f "$retro_wfc_payload"
|
if "$translator" validate-retro-wfc-payload --directory "$retro_wfc_dir" --game-id "$game_id"; then
|
||||||
|
cached_payload_valid=1
|
||||||
|
else
|
||||||
|
printf 'Discarding an invalid cached Retro-WFC payload...\n' >&2
|
||||||
|
rm -f "$retro_wfc_payload"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ ! -f "$retro_wfc_payload" ]]; then
|
|
||||||
printf 'Downloading the Retro-WFC payload needed for online play...\n'
|
# A signed cache may still be an older vulnerable revision, so always attempt to replace it
|
||||||
mkdir -p "$retro_wfc_dir"
|
# with the current signed snapshot. A transport failure may fall back to the verified cache;
|
||||||
payload_stage=$(mktemp -d "$retro_wfc_dir/.payload-download.XXXXXX")
|
# a downloaded snapshot with an invalid signature remains a hard failure.
|
||||||
temporary_payload="$payload_stage/binary/payload.RMCPD00.bin"
|
printf 'Downloading the current Retro-WFC payload needed for online play...\n'
|
||||||
mkdir -p "$(dirname "$temporary_payload")"
|
mkdir -p "$retro_wfc_dir"
|
||||||
trap 'rm -rf "$payload_stage"' EXIT
|
payload_stage=$(mktemp -d "$retro_wfc_dir/.payload-download.XXXXXX")
|
||||||
/usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
|
temporary_payload="$payload_stage/$payload_file"
|
||||||
--retry 1 --output "$temporary_payload" \
|
mkdir -p "$(dirname "$temporary_payload")"
|
||||||
'http://nas.play.rwfc.net/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
|
trap 'rm -rf "$payload_stage"' EXIT
|
||||||
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
|
if /usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
|
||||||
|
--retry 1 --output "$temporary_payload" "$payload_uri"; then
|
||||||
|
"$translator" validate-retro-wfc-payload --directory "$payload_stage" --game-id "$game_id" || \
|
||||||
fail 'downloaded Retro-WFC payload failed signature validation'
|
fail 'downloaded Retro-WFC payload failed signature validation'
|
||||||
mkdir -p "$retro_wfc_dir/binary"
|
mkdir -p "$(dirname "$retro_wfc_payload")"
|
||||||
mv "$temporary_payload" "$retro_wfc_payload"
|
mv "$temporary_payload" "$retro_wfc_payload"
|
||||||
rmdir "$payload_stage/binary" "$payload_stage"
|
rmdir "$payload_stage/binary" "$payload_stage"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
|
elif (( cached_payload_valid )); then
|
||||||
|
printf 'Could not download the current Retro-WFC payload; using the previously verified cached payload.\n' >&2
|
||||||
|
rm -rf "$payload_stage"
|
||||||
|
trap - EXIT
|
||||||
|
else
|
||||||
|
fail 'could not download the current Retro-WFC payload and no valid cached payload is available'
|
||||||
fi
|
fi
|
||||||
build_args+=(--profile both --base-output-dir "$products" --retro-rewind-package-dir "$retro_dir" --retro-wfc-offline-dir "$retro_wfc_dir")
|
build_args+=(--profile both --base-output-dir "$products" --retro-rewind-package-dir "$retro_dir" --retro-wfc-offline-dir "$retro_wfc_dir")
|
||||||
fi
|
fi
|
||||||
"$workspace/Launcher/local-build-macos.command" "${build_args[@]}" --profile "$profile" --cmake "$cmake_bin" --ninja "$ninja_bin" --translator-bin "$translator"
|
"$workspace/Launcher/local-build-macos.command" "${build_args[@]}" --profile "$profile" --cmake "$cmake_bin" --ninja "$ninja_bin" --translator-bin "$translator"
|
||||||
|
|
||||||
config="$support_root/Config.toml"
|
config="$support_root/Config.toml"
|
||||||
toml_string() { printf '%s' "$1" | sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g'; }
|
toml_string() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'; }
|
||||||
set_path() {
|
set_path() {
|
||||||
local key=$1 value=$2 encoded line temporary
|
local key=$1 value=$2 encoded line temporary
|
||||||
encoded=$(toml_string "$value"); line="$key = \"$encoded\""; temporary="$config.tmp"
|
encoded=$(toml_string "$value"); line="$key = \"$encoded\""; temporary="$config.tmp"
|
||||||
@@ -129,8 +184,13 @@ install_app() {
|
|||||||
local app=$1
|
local app=$1
|
||||||
[[ -d "$products/$app" ]] || return 0
|
[[ -d "$products/$app" ]] || return 0
|
||||||
if [[ "$destination" == /Applications ]]; then
|
if [[ "$destination" == /Applications ]]; then
|
||||||
command="mkdir -p /Applications && rm -rf '/Applications/$app' && ditto '$products/$app' '/Applications/$app'"
|
/usr/bin/osascript - "$products/$app" "/Applications/$app" <<'APPLESCRIPT'
|
||||||
/usr/bin/osascript -e "do shell script \"$command\" with administrator privileges"
|
on run argv
|
||||||
|
set sourcePath to quoted form of (item 1 of argv)
|
||||||
|
set installedPath to quoted form of (item 2 of argv)
|
||||||
|
do shell script "mkdir -p /Applications && rm -rf " & installedPath & " && /usr/bin/ditto " & sourcePath & " " & installedPath with administrator privileges
|
||||||
|
end run
|
||||||
|
APPLESCRIPT
|
||||||
else
|
else
|
||||||
mkdir -p "$destination"; rm -rf "$destination/$app"; /usr/bin/ditto "$products/$app" "$destination/$app"
|
mkdir -p "$destination"; rm -rf "$destination/$app"; /usr/bin/ditto "$products/$app" "$destination/$app"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Exercise a real dyld dependency chain before and after app packaging.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
[[ $(uname -s) == Darwin ]] || { printf 'This test requires macOS.\n' >&2; exit 1; }
|
||||||
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
temp_root=$(cd "${TMPDIR:-/tmp}" && pwd)
|
||||||
|
test_root=$(mktemp -d "$temp_root/wiicompiled-publish-test.XXXXXX")
|
||||||
|
[[ "$test_root" == "$temp_root"/wiicompiled-publish-test.* ]] || exit 1
|
||||||
|
trap 'rm -rf "$test_root"' EXIT
|
||||||
|
|
||||||
|
build_dir="$test_root/build with spaces"
|
||||||
|
output_dir="$test_root/output with spaces"
|
||||||
|
mkdir -p "$build_dir/A/b" "$build_dir/A/c" "$build_dir/wii_bootstrap" "$output_dir"
|
||||||
|
cat > "$test_root/c.c" <<'EOF'
|
||||||
|
int value_c(void) { return 7; }
|
||||||
|
EOF
|
||||||
|
cat > "$test_root/b.c" <<'EOF'
|
||||||
|
extern int value_c(void);
|
||||||
|
int value_b(void) { return 2 * value_c(); }
|
||||||
|
EOF
|
||||||
|
cat > "$test_root/a.c" <<'EOF'
|
||||||
|
extern int value_b(void);
|
||||||
|
int value_a(void) { return 1 + value_b(); }
|
||||||
|
EOF
|
||||||
|
cat > "$test_root/main.c" <<'EOF'
|
||||||
|
#include <stdio.h>
|
||||||
|
extern int value_a(void);
|
||||||
|
int main(void) { printf("%d\n", value_a()); return 0; }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
clang -dynamiclib "$test_root/c.c" -o "$build_dir/A/c/libC.dylib" \
|
||||||
|
-Wl,-headerpad_max_install_names -Wl,-install_name,@rpath/libC.dylib
|
||||||
|
clang -dynamiclib "$test_root/b.c" -o "$build_dir/A/b/libB.dylib" \
|
||||||
|
-L "$build_dir/A/c" -lC \
|
||||||
|
-Wl,-headerpad_max_install_names -Wl,-install_name,@rpath/libB.dylib
|
||||||
|
clang -dynamiclib "$test_root/a.c" -o "$build_dir/A/libA.dylib" \
|
||||||
|
-L "$build_dir/A/b" -lB \
|
||||||
|
-Wl,-headerpad_max_install_names -Wl,-install_name,@rpath/libA.dylib \
|
||||||
|
-Wl,-rpath,@loader_path/b -Wl,-rpath,@loader_path/c
|
||||||
|
clang "$test_root/main.c" -o "$build_dir/WiiCompiled" \
|
||||||
|
-L "$build_dir/A" -lA \
|
||||||
|
-Wl,-headerpad_max_install_names -Wl,-rpath,@executable_path/A
|
||||||
|
|
||||||
|
# B has no rpaths: its C dependency must inherit A's loader-relative path.
|
||||||
|
[[ $("$build_dir/WiiCompiled") == 15 ]]
|
||||||
|
for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem; do
|
||||||
|
: > "$build_dir/$asset"
|
||||||
|
done
|
||||||
|
bash "$script_dir/publish-app.command" --build-dir "$build_dir" \
|
||||||
|
--product WiiCompiled --output-dir "$output_dir" --architecture "$(uname -m)"
|
||||||
|
for name in libA.dylib libB.dylib libC.dylib; do
|
||||||
|
[[ -f "$output_dir/WiiCompiled.app/Contents/Frameworks/$name" ]]
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove access to the original paths and relocate the app before executing.
|
||||||
|
mv "$build_dir" "$test_root/hidden build"
|
||||||
|
mkdir "$test_root/relocated app"
|
||||||
|
mv "$output_dir/WiiCompiled.app" "$test_root/relocated app/WiiCompiled.app"
|
||||||
|
[[ $("$test_root/relocated app/WiiCompiled.app/Contents/MacOS/WiiCompiled") == 15 ]]
|
||||||
|
printf 'publish-app dependency-chain test passed\n'
|
||||||
@@ -25,7 +25,7 @@ set -euo pipefail
|
|||||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
workspace=$(cd "$script_dir/.." && pwd)
|
workspace=$(cd "$script_dir/.." && pwd)
|
||||||
|
|
||||||
llvm_version=23.1.0
|
llvm_version=22.1.8
|
||||||
cmake_version=4.3.3
|
cmake_version=4.3.3
|
||||||
ninja_version=1.13.2
|
ninja_version=1.13.2
|
||||||
destination="$script_dir/artifacts/portable-tools"
|
destination="$script_dir/artifacts/portable-tools"
|
||||||
@@ -52,13 +52,13 @@ done
|
|||||||
|
|
||||||
case "$arch" in
|
case "$arch" in
|
||||||
x86_64) llvm_release_arch=X64; target_triple=x86_64-unknown-linux-gnu
|
x86_64) llvm_release_arch=X64; target_triple=x86_64-unknown-linux-gnu
|
||||||
llvm_release_sha256=18da30f77f475688a18f7704d23f9f155ae007ed9922dbed6850a9419d9fec8c
|
llvm_release_sha256=fccecb1906e7ddf5ec040aec5b646b650e2daaafa4423b41341c4717db5bdec0
|
||||||
cmake_release_arch=x86_64
|
cmake_release_arch=x86_64
|
||||||
cmake_sha256=927b2368a946c37269c3a66225ab00544e756459cdd0b5d0da438694fb9ff802
|
cmake_sha256=927b2368a946c37269c3a66225ab00544e756459cdd0b5d0da438694fb9ff802
|
||||||
ninja_asset=ninja-linux.zip
|
ninja_asset=ninja-linux.zip
|
||||||
ninja_sha256=5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6 ;;
|
ninja_sha256=5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6 ;;
|
||||||
aarch64) llvm_release_arch=ARM64; target_triple=aarch64-unknown-linux-gnu
|
aarch64) llvm_release_arch=ARM64; target_triple=aarch64-unknown-linux-gnu
|
||||||
llvm_release_sha256=cfb31bfc713ef453248bf5bd026312f838ad6c52c25623e987cb6a340f3050d4
|
llvm_release_sha256=d431eff9f064c86ee7c4c94af570a8f74fcccd1f74c6f0da3af32ce34a1e1b05
|
||||||
cmake_release_arch=aarch64
|
cmake_release_arch=aarch64
|
||||||
cmake_sha256=9ea38356dbd3e32e51029a3e09a0f2f8e117ef4fbcaad7a21ffb36409bbd5cb4
|
cmake_sha256=9ea38356dbd3e32e51029a3e09a0f2f8e117ef4fbcaad7a21ffb36409bbd5cb4
|
||||||
ninja_asset=ninja-linux-aarch64.zip
|
ninja_asset=ninja-linux-aarch64.zip
|
||||||
@@ -101,11 +101,14 @@ rm -rf "$work"
|
|||||||
mkdir -p "$work/bin" "$work/lib/$target_triple" "$work/include/$target_triple/c++/v1"
|
mkdir -p "$work/bin" "$work/lib/$target_triple" "$work/include/$target_triple/c++/v1"
|
||||||
|
|
||||||
# --- clang/lld/llvm-ar, pruned from the official LLVM release ---
|
# --- clang/lld/llvm-ar, pruned from the official LLVM release ---
|
||||||
|
# built from PR https://github.com/llvm/llvm-project/pull/222821 on official LLVM Github Actions Runner
|
||||||
llvm_archive_name="LLVM-$llvm_version-Linux-$llvm_release_arch.tar.xz"
|
# only switch to an official stable LLVM release again once:
|
||||||
|
# - this PR has merged https://github.com/llvm/llvm-project/pull/221365 and been backported to LLVM stable branch
|
||||||
|
# - this bug has been fixed with a workaround in the Wiicompiled translator https://github.com/patchzyy/Wiicompiled/issues/208 or in LLVM and been backported to LLVM stable branch
|
||||||
|
llvm_archive_name="LLVM-PR222821-5ae1c7c43a11b4cdc5ce4dd483c28357bab7dae2-Linux-$llvm_release_arch.tar.xz"
|
||||||
llvm_archive="$downloads/$llvm_archive_name"
|
llvm_archive="$downloads/$llvm_archive_name"
|
||||||
download_verified "$llvm_archive" \
|
download_verified "$llvm_archive" \
|
||||||
"https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvm_version/$llvm_archive_name" \
|
"https://github.com/theofficialgman/llvm-project/releases/download/llvmorg-22.1.8-patched/$llvm_archive_name" \
|
||||||
"$llvm_release_sha256"
|
"$llvm_release_sha256"
|
||||||
|
|
||||||
extract_root="$script_dir/artifacts/.extract-clang-$arch"
|
extract_root="$script_dir/artifacts/.extract-clang-$arch"
|
||||||
@@ -113,16 +116,16 @@ rm -rf "$extract_root"
|
|||||||
mkdir -p "$extract_root"
|
mkdir -p "$extract_root"
|
||||||
echo "prepare-portable-tools.sh: extracting $llvm_archive_name (this is the full ~1.9 GiB release; only a fraction is kept)..."
|
echo "prepare-portable-tools.sh: extracting $llvm_archive_name (this is the full ~1.9 GiB release; only a fraction is kept)..."
|
||||||
tar -xf "$llvm_archive" -C "$extract_root"
|
tar -xf "$llvm_archive" -C "$extract_root"
|
||||||
src="$extract_root/LLVM-$llvm_version-Linux-$llvm_release_arch"
|
src="$extract_root/${llvm_archive_name%.tar.xz}"
|
||||||
[[ -d "$src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $src" >&2; exit 1; }
|
[[ -d "$src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $src" >&2; exit 1; }
|
||||||
|
|
||||||
echo "prepare-portable-tools.sh: pruning to the minimal compile+link toolchain..."
|
echo "prepare-portable-tools.sh: pruning to the minimal compile+link toolchain..."
|
||||||
|
|
||||||
# clang: the real driver executable plus the clang/clang++ symlinks CMake/local-build.sh invoke.
|
# clang: the real driver executable plus the clang/clang++ symlinks CMake/local-build.sh invoke.
|
||||||
# Stripped: debug symbols are dead weight for a bundled compiler nobody will debug.
|
# Stripped: debug symbols are dead weight for a bundled compiler nobody will debug.
|
||||||
cp -a "$src/bin/clang-23" "$work/bin/"
|
cp -a "$src/bin/clang-22" "$work/bin/"
|
||||||
strip "$work/bin/clang-23"
|
strip "$work/bin/clang-22"
|
||||||
ln -s clang-23 "$work/bin/clang"
|
ln -s clang-22 "$work/bin/clang"
|
||||||
ln -s clang "$work/bin/clang++"
|
ln -s clang "$work/bin/clang++"
|
||||||
|
|
||||||
# lld: linked via -fuse-ld=lld, which clang resolves by looking for ld.lld next to itself first -
|
# lld: linked via -fuse-ld=lld, which clang resolves by looking for ld.lld next to itself first -
|
||||||
@@ -165,7 +168,8 @@ rm -rf "$cmake_extract_root"
|
|||||||
mkdir -p "$cmake_extract_root"
|
mkdir -p "$cmake_extract_root"
|
||||||
echo "prepare-portable-tools.sh: extracting $cmake_archive_name..."
|
echo "prepare-portable-tools.sh: extracting $cmake_archive_name..."
|
||||||
tar -xzf "$cmake_archive" -C "$cmake_extract_root"
|
tar -xzf "$cmake_archive" -C "$cmake_extract_root"
|
||||||
cmake_src="$cmake_extract_root/cmake-$cmake_version-linux-$cmake_release_arch"
|
|
||||||
|
cmake_src="$cmake_extract_root/${cmake_archive_name%.tar.gz}"
|
||||||
[[ -d "$cmake_src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $cmake_src" >&2; exit 1; }
|
[[ -d "$cmake_src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $cmake_src" >&2; exit 1; }
|
||||||
|
|
||||||
mkdir -p "$work/share/cmake-$cmake_share_version"
|
mkdir -p "$work/share/cmake-$cmake_share_version"
|
||||||
@@ -201,10 +205,10 @@ Ninja $ninja_version
|
|||||||
Apache License 2.0
|
Apache License 2.0
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "prepare-portable-tools.sh: smoke-testing the toolchain..."
|
echo "prepare-portable-tools.sh: testing the toolchain..."
|
||||||
smoke_dir=$(mktemp -d)
|
test_dir=$(mktemp -d)
|
||||||
trap 'rm -rf "$smoke_dir"' EXIT
|
trap 'rm -rf "$test_dir"' EXIT
|
||||||
cat > "$smoke_dir/t.cpp" <<'EOF'
|
cat > "$test_dir/t.cpp" <<'EOF'
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
int main() {
|
int main() {
|
||||||
@@ -214,24 +218,25 @@ int main() {
|
|||||||
return sum == 6 ? 0 : 1;
|
return sum == 6 ? 0 : 1;
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
"$work/bin/clang++" -std=c++20 -fuse-ld=lld "$smoke_dir/t.cpp" -o "$smoke_dir/t"
|
"$work/bin/clang++" -std=c++20 -fuse-ld=lld "$test_dir/t.cpp" -o "$test_dir/t"
|
||||||
"$smoke_dir/t"
|
"$test_dir/t"
|
||||||
|
|
||||||
# Also exercised together through CMake+Ninja, exactly how local-build.sh drives them - a plain
|
# Also exercised together through CMake+Ninja, exactly how local-build.sh drives them - a plain
|
||||||
# clang++ invocation above would not catch a broken CMAKE_ROOT (Modules/Templates) or a Ninja that
|
# clang++ invocation above would not catch a broken CMAKE_ROOT (Modules/Templates) or a Ninja that
|
||||||
# can't find the compiler.
|
# can't find the compiler.
|
||||||
cat > "$smoke_dir/CMakeLists.txt" <<'EOF'
|
cat > "$test_dir/CMakeLists.txt" <<'EOF'
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
project(smoke CXX)
|
project(test CXX)
|
||||||
add_executable(smoke t.cpp)
|
add_executable(test t.cpp)
|
||||||
EOF
|
EOF
|
||||||
"$work/bin/cmake" -S "$smoke_dir" -B "$smoke_dir/build" -G Ninja \
|
"$work/bin/cmake" -S "$test_dir" -B "$test_dir/build" -G Ninja \
|
||||||
-DCMAKE_MAKE_PROGRAM="$work/bin/ninja" -DCMAKE_CXX_COMPILER="$work/bin/clang++" >/dev/null
|
-DCMAKE_MAKE_PROGRAM="$work/bin/ninja" -DCMAKE_CXX_COMPILER="$work/bin/clang++" >/dev/null
|
||||||
"$work/bin/cmake" --build "$smoke_dir/build" >/dev/null
|
"$work/bin/cmake" --build "$test_dir/build" >/dev/null
|
||||||
"$smoke_dir/build/smoke"
|
"$test_dir/build/test"
|
||||||
|
|
||||||
rm -rf "$smoke_dir"
|
rm -rf "$test_dir"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
|
|
||||||
|
rm -rf "$toolchain_dir"
|
||||||
mv "$work" "$toolchain_dir"
|
mv "$work" "$toolchain_dir"
|
||||||
echo "prepare-portable-tools.sh: toolchain ready at $toolchain_dir ($(du -sh "$toolchain_dir" | cut -f1))"
|
echo "prepare-portable-tools.sh: toolchain ready at $toolchain_dir ($(du -sh "$toolchain_dir" | cut -f1))"
|
||||||
|
|||||||
@@ -1,6 +1,20 @@
|
|||||||
|
<img width="4190" height="1232" alt="wiicomplogofinalfinalfinalev2MADEBY_INKWRECK_plzcredit" src="https://github.com/user-attachments/assets/df7a3f2e-5336-479a-b4c0-968dd578726d" />
|
||||||
|
|
||||||
# WiiCompiled
|
# WiiCompiled
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="Windows 10 / 11, x64" src="https://img.shields.io/badge/Windows-10%20%2F%2011%20%C2%B7%20x64-0078D4"></a>
|
||||||
|
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="Linux, x64 / ARM64" src="https://img.shields.io/badge/Linux-x64%20%2F%20ARM64-FCC624?logo=linux&logoColor=white"></a>
|
||||||
|
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="macOS 14+, Apple Silicon" src="https://img.shields.io/badge/macOS-14%2B%20%C2%B7%20Apple%20Silicon-0A84FF?logo=apple&logoColor=white"></a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="#building-from-source"><img alt="PowerPC static recompilation" src="https://img.shields.io/badge/PowerPC-static%20recompilation-FF9F0A"></a>
|
||||||
|
<a href="#retro-rewind"><img alt="Retro Rewind supported" src="https://img.shields.io/badge/Retro%20Rewind-supported-FF375F"></a>
|
||||||
|
<a href="https://github.com/TeamWheelWizard/WheelWizard/releases"><img alt="Install with Wheel Wizard" src="https://img.shields.io/badge/install%20with-Wheel%20Wizard-8B5CF6"></a>
|
||||||
|
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="Total downloads" src="https://img.shields.io/github/downloads/patchzyy/Wiicompiled/total?color=00B8D9&logo=github"></a>
|
||||||
|
<a href="LICENSE"><img alt="License: GPLv3" src="https://img.shields.io/badge/license-GPLv3-2EA44F?logo=gnu&logoColor=white"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
A native PC port of Mario Kart Wii, made with static recompilation.
|
A native PC port of Mario Kart Wii, made with static recompilation.
|
||||||
|
|
||||||
There's no emulator in the loop, no interpreter, no JIT, no PowerPC
|
There's no emulator in the loop, no interpreter, no JIT, no PowerPC
|
||||||
@@ -39,42 +53,37 @@ Play at several times the console's resolution.
|
|||||||
**Music ducking.**
|
**Music ducking.**
|
||||||
Start playing something else, Spotify, a YouTube video, and
|
Start playing something else, Spotify, a YouTube video, and
|
||||||
the game automatically mutes its own music until the other audio stops. Optional, if you'd
|
the game automatically mutes its own music until the other audio stops. Optional, if you'd
|
||||||
rather it didn't. All audio that shows in your display media controls on your windows pc fall under this.
|
rather it didn't. Windows uses system media controls and Linux uses MPRIS players.
|
||||||
|
On macOS 14.2 or later, this detects other apps with active audio output and excludes
|
||||||
|
the game's own audio. Apps that keep an output stream running silently can keep
|
||||||
|
game music muted even when nothing is audible.
|
||||||
|
|
||||||
**An in-game settings bar.**
|
**An in-game settings bar.**
|
||||||
Press **F10** while the game window has focus:
|
Press **F10** while the game window has focus:
|
||||||
- Internal resolution
|
- Internal resolution
|
||||||
- FPS counter
|
- FPS counter
|
||||||
|
- MetalFX spatial upscaling on supported macOS GPUs
|
||||||
- Controller assignment for all four ports
|
- Controller assignment for all four ports
|
||||||
- Full per-controller button mapping
|
- Full per-controller button mapping, including the bumpers
|
||||||
|
- Dolphin-syntax input expressions and GCPadNew.ini import
|
||||||
|
- Controller vibration on/off
|
||||||
- Volume, instant mute, and the music ducking toggle
|
- Volume, instant mute, and the music ducking toggle
|
||||||
|
|
||||||
Everything you change is saved to `Config.toml` on the spot and restored next launch.
|
Everything you change is saved to `Config.toml` on the spot and restored next launch.
|
||||||
|
|
||||||
**Real controller support.**
|
**Dolphin-compatible input expressions.**
|
||||||
Controllers are fed to the game as a GameCube controller.
|
Each GameCube control can carry an expression in Dolphin's input syntax, with the same operators
|
||||||
Mappings are positional (`south`, `east`, `west`, `north`) rather than Xbox-labelled, so the
|
and the same functions.
|
||||||
same config makes sense on Xbox, PlayStation, Nintendo and generic SDL pads alike, and extra
|
A Dolphin `GCPadNew.ini` can be imported directly from the F10 bar.
|
||||||
inputs like paddles, touchpads and share buttons show up when the hardware reports them.
|
|
||||||
|
**Vibration toggle.**
|
||||||
|
Force feedback can be turned off for every port at once.
|
||||||
The official Wii U / Switch GameCube adapter (WUP-028) works too; as with Dolphin, on Windows the
|
The official Wii U / Switch GameCube adapter (WUP-028) works too; as with Dolphin, on Windows the
|
||||||
adapter must be switched to the WinUSB driver once (Zadig).
|
adapter must be switched to the WinUSB driver once (Zadig).
|
||||||
|
|
||||||
**Real Wii Remotes over Bluetooth.**
|
**Real Wii Remotes over Bluetooth.**
|
||||||
Pair a Wii Remote with Windows (Settings > Bluetooth > Add device, press 1+2 or SYNC, leave the
|
Pair a Wii Remote with Windows (Settings > Bluetooth > Add device, press 1+2 or SYNC, leave the
|
||||||
PIN empty) and the game reads it as an actual Wii Remote through KPAD: Wii Remote icons and
|
PIN empty)
|
||||||
prompts, Wii Wheel tilt steering, wheelies and tricks all come from the game's own motion code.
|
|
||||||
Nunchuk and Classic Controller are real Wii extensions too: the game gets the Nunchuk's stick,
|
|
||||||
C/Z and accelerometer, and the Classic Controller through `KPADGetUnifiedWpadStatus` with its own
|
|
||||||
layout and icons, so its buttons do what the game says they do and no mapping is involved. Plug an
|
|
||||||
extension in or pull it out mid-game and the game switches control scheme like on the console
|
|
||||||
(the runtime patches SDL's Wii driver, which otherwise loses the remote for good on an extension
|
|
||||||
change). Only the Wii U Pro Controller, which has no Wii-era equivalent, is fed to the game as a
|
|
||||||
GameCube pad with Nintendo's layout. If a remote drops out or was switched on after launch, the
|
|
||||||
runtime keeps rescanning Bluetooth until it comes back (F10 > Controller settings > Wii Remotes). SDL's read of
|
|
||||||
the remote's factory accelerometer calibration often times out over Bluetooth (`console.log`
|
|
||||||
then says "Using fallback accelerometer calibration") and it falls back to a nominal zero point,
|
|
||||||
so the same menu has a one-button calibration (remote flat, buttons up) that removes the small
|
|
||||||
tilt offset some remotes show.
|
|
||||||
|
|
||||||
Known limitations of the Wii Remote path:
|
Known limitations of the Wii Remote path:
|
||||||
- No IR pointer yet: menus are navigated with the D-pad and A (the game treats the remote as
|
- No IR pointer yet: menus are navigated with the D-pad and A (the game treats the remote as
|
||||||
@@ -92,8 +101,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 14 (Sonoma) or later on Apple Silicon
|
- macOS 12 (Monterey) or later on Apple Silicon (`arm64`) or Intel (`x86_64-v3`); pre-Haswell Intel CPUs are unsupported
|
||||||
- On macOS, Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing)
|
- On macOS, a Metal-capable GPU and 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.
|
||||||
|
|
||||||
@@ -114,6 +123,21 @@ 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. The universal
|
||||||
|
package selects the appropriate bundled tools for the host architecture, supporting both Apple Silicon (`arm64`)
|
||||||
|
and Intel (`x86_64`) Macs. 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
|
||||||
@@ -153,7 +177,9 @@ The default test suite needs no binaries and no host C++ compiler, so you can ha
|
|||||||
translator without any game data around.
|
translator without any game data around.
|
||||||
|
|
||||||
For everything beyond that, feeding in your own `main.dol`/`StaticR.rel`, running the
|
For everything beyond that, feeding in your own `main.dol`/`StaticR.rel`, running the
|
||||||
translation, generating the manifest and build graph, and compiling. see [`translator/README.md`](translator/README.md).
|
translation, generating the manifest and build graph, and compiling, see [`translator/README.md`](translator/README.md).
|
||||||
|
|
||||||
|
For a step-by-step guide on compiling both WiiCompiled and Retro Rewind from source on macOS (Apple Silicon), see the [macOS Build Guide](docs/building-macos.md).
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
@@ -202,7 +228,7 @@ AI coding tools were used during development of this project.
|
|||||||
All translated output is verified against real hardware behavior and most importantly, physics accuracy is proven synced across Wii, Dolphin, and WiiCompiled (see FAQ).
|
All translated output is verified against real hardware behavior and most importantly, physics accuracy is proven synced across Wii, Dolphin, and WiiCompiled (see FAQ).
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
- **inkwreck** - making the logo
|
||||||
- **[aurora](https://github.com/encounter/aurora)** - the GX rendering/windowing backend this
|
- **[aurora](https://github.com/encounter/aurora)** - the GX rendering/windowing backend this
|
||||||
project's whole graphics layer sits on. MIT licensed.
|
project's whole graphics layer sits on. MIT licensed.
|
||||||
- **[Dawn](https://dawn.googlesource.com/dawn)** - Google's WebGPU implementation, powering
|
- **[Dawn](https://dawn.googlesource.com/dawn)** - Google's WebGPU implementation, powering
|
||||||
|
|||||||
@@ -114,14 +114,16 @@ Source: <https://github.com/higan-emu/libco>. Full license text:
|
|||||||
|
|
||||||
## Fetched at build time and redistributed in release builds
|
## Fetched at build time and redistributed in release builds
|
||||||
|
|
||||||
These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt` and
|
These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt`,
|
||||||
`aurora-main/cmake/AuroraDawnProvider.cmake`. They are not stored in this repository; the build
|
`aurora-main/cmake/AuroraDawnProvider.cmake`, and (for Mbed TLS) `runtime/CMakeLists.txt`. They are
|
||||||
downloads them, and release installers carry the resulting binaries. Their license texts are
|
not stored in this repository; the build downloads them - each fetch is pinned to an exact version
|
||||||
|
with a checked SHA-256 - and links or redistributes the resulting binaries. Their license texts are
|
||||||
included in the installer's `licenses/` folder. The Windows installer bundles the pinned source
|
included in the installer's `licenses/` folder. The Windows installer bundles the pinned source
|
||||||
trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user builds run offline.
|
trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user builds run offline.
|
||||||
|
|
||||||
| Component | Version | License | Upstream |
|
| Component | Version | License | Upstream |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
|
| Mbed TLS | 3.6.7 | Apache-2.0 / GPL-2.0-or-later | <https://github.com/Mbed-TLS/mbedtls> |
|
||||||
| Dawn (WebGPU) | `v20260603.191052` prebuilt | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
| Dawn (WebGPU) | `v20260603.191052` prebuilt | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
||||||
| Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
| Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
||||||
| DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> |
|
| DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> |
|
||||||
@@ -130,7 +132,7 @@ trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user bu
|
|||||||
| Abseil | LTS 20240722.0 | Apache-2.0 | <https://github.com/abseil/abseil-cpp> |
|
| Abseil | LTS 20240722.0 | Apache-2.0 | <https://github.com/abseil/abseil-cpp> |
|
||||||
| Dear ImGui | 1.91.9b-docking | MIT | <https://github.com/ocornut/imgui> |
|
| Dear ImGui | 1.91.9b-docking | MIT | <https://github.com/ocornut/imgui> |
|
||||||
| {fmt} | 11.1.4 | MIT | <https://github.com/fmtlib/fmt> |
|
| {fmt} | 11.1.4 | MIT | <https://github.com/fmtlib/fmt> |
|
||||||
| xxHash | 0.8.3 | BSD-2-Clause | <https://github.com/Cyan4973/xxHash> |
|
| xxHash | 0.8.4 | BSD-2-Clause | <https://github.com/Cyan4973/xxHash> |
|
||||||
| zlib | 1.3.2 | zlib | <https://github.com/madler/zlib> |
|
| zlib | 1.3.2 | zlib | <https://github.com/madler/zlib> |
|
||||||
| libpng | 1.6.58 | PNG Reference Library License v2 | <https://github.com/pnggroup/libpng> |
|
| libpng | 1.6.58 | PNG Reference Library License v2 | <https://github.com/pnggroup/libpng> |
|
||||||
| FreeType | 2.14.3 | **FreeType License (FTL)** - see below | <https://freetype.org/> |
|
| FreeType | 2.14.3 | **FreeType License (FTL)** - see below | <https://freetype.org/> |
|
||||||
|
|||||||
Submodule
+1
Submodule aurora-main added at ff0b617a92
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
BasedOnStyle: LLVM
|
|
||||||
ColumnLimit: 120
|
|
||||||
UseTab: Never
|
|
||||||
TabWidth: 2
|
|
||||||
---
|
|
||||||
Language: Cpp
|
|
||||||
DerivePointerAlignment: false
|
|
||||||
PointerAlignment: Left
|
|
||||||
AlignAfterOpenBracket: Align
|
|
||||||
AlignConsecutiveAssignments: false
|
|
||||||
IndentCaseLabels: false
|
|
||||||
AllowShortBlocksOnASingleLine: Always
|
|
||||||
AlignOperands: true
|
|
||||||
AlignTrailingComments: true
|
|
||||||
AlwaysBreakBeforeMultilineStrings: true
|
|
||||||
AlwaysBreakTemplateDeclarations: Yes
|
|
||||||
BreakConstructorInitializersBeforeComma: true
|
|
||||||
AlwaysBreakAfterReturnType: None
|
|
||||||
AlwaysBreakAfterDefinitionReturnType: None
|
|
||||||
AllowShortFunctionsOnASingleLine: All
|
|
||||||
Cpp11BracedListStyle: true
|
|
||||||
NamespaceIndentation: None
|
|
||||||
BinPackArguments: true
|
|
||||||
BinPackParameters: true
|
|
||||||
SortIncludes: false
|
|
||||||
AccessModifierOffset: -2
|
|
||||||
ConstructorInitializerIndentWidth: 0
|
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
.buildcache/
|
|
||||||
.DS_Store
|
|
||||||
.idea/
|
|
||||||
.cache/
|
|
||||||
.vscode/
|
|
||||||
.vs/
|
|
||||||
build/
|
|
||||||
cmake-build-*/
|
|
||||||
CMakeUserPresets.json
|
|
||||||
out/
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.25)
|
|
||||||
project(aurora LANGUAGES C CXX)
|
|
||||||
if (APPLE)
|
|
||||||
enable_language(OBJC)
|
|
||||||
endif()
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
|
|
||||||
option(AURORA_ENABLE_GX "Enable GX implementation and WebGPU renderer" ON)
|
|
||||||
option(AURORA_ENABLE_DVD "Enable DVD implementation backed by nod" OFF) # TODO: need to fix tests
|
|
||||||
option(AURORA_ENABLE_CARD "Enable CARD implementation based on kabufuda" ON)
|
|
||||||
option(AURORA_CACHE_USE_ZSTD "Compress WebGPU cache entries with zstd" ON)
|
|
||||||
|
|
||||||
# Dependency versions
|
|
||||||
set(AURORA_DAWN_VERSION "v20260603.191052" CACHE STRING "Dawn version tag (https://github.com/encounter/dawn-build/releases)")
|
|
||||||
set(AURORA_SDL3_VERSION "3.4.4" CACHE STRING "SDL3 version tag (https://github.com/libsdl-org/SDL/releases)")
|
|
||||||
set(AURORA_NOD_VERSION "v2.0.0-alpha.8" CACHE STRING "nod version tag (https://github.com/encounter/nod/releases)")
|
|
||||||
set(AURORA_LIBUSB_VERSION "1.0.30" CACHE STRING "libusb version tag (https://github.com/libusb/libusb/releases)")
|
|
||||||
|
|
||||||
# Platform-specific defaults
|
|
||||||
if (CMAKE_CROSSCOMPILING)
|
|
||||||
# Cross-compiling: vendor everything, don't use system packages
|
|
||||||
set(_default_provider "vendor")
|
|
||||||
set(_default_linkage "static")
|
|
||||||
else ()
|
|
||||||
set(_default_provider "auto")
|
|
||||||
if (WIN32)
|
|
||||||
set(_default_linkage "shared")
|
|
||||||
else ()
|
|
||||||
set(_default_linkage "static")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Dawn/WebGPU (if AURORA_ENABLE_GX)
|
|
||||||
set(AURORA_DAWN_PROVIDER "${_default_provider}" CACHE STRING
|
|
||||||
"How to provide Dawn: auto, vendor (build from source), system (find_package/imported), package (download prebuilt)")
|
|
||||||
set_property(CACHE AURORA_DAWN_PROVIDER PROPERTY STRINGS auto vendor system package)
|
|
||||||
set(AURORA_DAWN_LINKAGE "${_default_linkage}" CACHE STRING "Dawn linkage type preference")
|
|
||||||
set_property(CACHE AURORA_DAWN_LINKAGE PROPERTY STRINGS shared static)
|
|
||||||
|
|
||||||
# SDL3
|
|
||||||
set(AURORA_SDL3_PROVIDER "${_default_provider}" CACHE STRING
|
|
||||||
"How to provide SDL3: auto, vendor (build from source), system (find_package/imported), or package (download prebuilt)")
|
|
||||||
set_property(CACHE AURORA_SDL3_PROVIDER PROPERTY STRINGS auto vendor system package)
|
|
||||||
set(AURORA_SDL3_LINKAGE "${_default_linkage}" CACHE STRING "SDL3 linkage type preference")
|
|
||||||
set_property(CACHE AURORA_SDL3_LINKAGE PROPERTY STRINGS shared static)
|
|
||||||
option(AURORA_SDL3_LIBUSB "Build the vendored SDL3 with libusb on Windows (official GameCube adapter support)" ON)
|
|
||||||
|
|
||||||
# nod (if AURORA_ENABLE_DVD)
|
|
||||||
set(AURORA_NOD_PROVIDER "${_default_provider}" CACHE STRING
|
|
||||||
"How to provide nod: auto, vendor (build from source), system (find_package/imported), package (download prebuilt)")
|
|
||||||
set_property(CACHE AURORA_NOD_PROVIDER PROPERTY STRINGS auto vendor system package)
|
|
||||||
set(AURORA_NOD_LINKAGE "${_default_linkage}" CACHE STRING "nod linkage type preference")
|
|
||||||
set_property(CACHE AURORA_NOD_LINKAGE PROPERTY STRINGS shared static)
|
|
||||||
|
|
||||||
add_subdirectory(extern)
|
|
||||||
|
|
||||||
include(cmake/aurora_core.cmake)
|
|
||||||
include(cmake/aurora_os.cmake)
|
|
||||||
if (AURORA_ENABLE_GX)
|
|
||||||
include(cmake/aurora_gx.cmake)
|
|
||||||
include(cmake/aurora_gd.cmake)
|
|
||||||
endif ()
|
|
||||||
include(cmake/aurora_pad.cmake)
|
|
||||||
include(cmake/aurora_ms.cmake)
|
|
||||||
include(cmake/aurora_si.cmake)
|
|
||||||
include(cmake/aurora_main.cmake)
|
|
||||||
include(cmake/aurora_mtx.cmake)
|
|
||||||
include(cmake/aurora_vi.cmake)
|
|
||||||
if(AURORA_ENABLE_CARD)
|
|
||||||
include(cmake/aurora_card.cmake)
|
|
||||||
endif()
|
|
||||||
if (AURORA_ENABLE_DVD)
|
|
||||||
include(cmake/aurora_dvd.cmake)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
||||||
add_subdirectory(examples)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND NOT CMAKE_CROSSCOMPILING)
|
|
||||||
enable_testing()
|
|
||||||
add_subdirectory(tests)
|
|
||||||
endif ()
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
The MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022 Luke Street
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<div align="center">
|
|
||||||
<img src="assets/aurora.png" alt="Logo" width="640">
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
Aurora is a source-level GameCube & Wii compatibility layer intended for use with game decompilation projects.
|
|
||||||
|
|
||||||
Originally developed for use in [Metaforce](https://github.com/AxioDL/metaforce), a Metroid Prime reverse engineering project.
|
|
||||||
It now powers several completed source ports, including [Dusklight](https://github.com/TwilitRealm/dusklight).
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Application layer using SDL3
|
|
||||||
- Runs on Windows, Linux, macOS, iOS, tvOS, Android
|
|
||||||
- GX compatibility layer
|
|
||||||
- Graphics API support: D3D12, Vulkan, Metal
|
|
||||||
- Highly accurate and performant GX implementation
|
|
||||||
- Robust pipeline cache system with "transferable" cache support for releases
|
|
||||||
- Dolphin-compatible texture pack support
|
|
||||||
- Widescreen & resolution scaling support
|
|
||||||
- Custom APIs for offscreen rendering
|
|
||||||
- PAD compatibility layer
|
|
||||||
- Utilizes `SDL_Gamepad` for wide controller support, including GameCube controller adapters
|
|
||||||
- Automatically saves and loads controller bindings and port mappings
|
|
||||||
- Gyro & mouse support
|
|
||||||
- DVD compatibility layer
|
|
||||||
- Utilizes [nod](https://github.com/encounter/nod) to support all GameCube/Wii disc image types, including RVZ
|
|
||||||
- CARD compatibility layer
|
|
||||||
- Full compatibility with Dolphin `.gci` and `.raw` for game saves
|
|
||||||
- [Dear ImGui](https://github.com/ocornut/imgui) built-in for simple debug UIs
|
|
||||||
|
|
||||||
### Graphics
|
|
||||||
|
|
||||||
The GX compatibility layer is built on top of [WebGPU](https://www.w3.org/TR/webgpu/), a cross-platform graphics API
|
|
||||||
abstraction layer. WebGPU allows targeting all major platforms simultaneously with minimal overhead. The WebGPU
|
|
||||||
implementation used is Chromium's [Dawn](https://dawn.googlesource.com/dawn/).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Building
|
|
||||||
|
|
||||||
See [docs/building.md](docs/building.md) for build instructions, CMake integration, and configuration options.
|
|
||||||
|
|
||||||
### License
|
|
||||||
|
|
||||||
Aurora is licensed under the [MIT License](LICENSE).
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,55 +0,0 @@
|
|||||||
include_guard(GLOBAL)
|
|
||||||
|
|
||||||
# aurora_copy_runtime_dlls(<executable_target> [<additional_targets>...])
|
|
||||||
#
|
|
||||||
# POST_BUILD command that copies all TARGET_RUNTIME_DLLS from the given targets
|
|
||||||
# next to the executable, plus any extra DLLs that Dawn loads dynamically
|
|
||||||
# (dxcompiler.dll, dxil.dll).
|
|
||||||
function(aurora_copy_runtime_dlls target)
|
|
||||||
if (NOT WIN32)
|
|
||||||
return()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Copy TARGET_RUNTIME_DLLS for each target
|
|
||||||
foreach (_t IN ITEMS ${target} ${ARGN})
|
|
||||||
add_custom_command(TARGET ${target} POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:${_t}> $<TARGET_FILE_DIR:${target}>
|
|
||||||
COMMAND_EXPAND_LISTS
|
|
||||||
)
|
|
||||||
endforeach ()
|
|
||||||
|
|
||||||
# Copy DXC DLLs that Dawn loads via LoadLibrary at runtime
|
|
||||||
if (TARGET dawn::webgpu_dawn)
|
|
||||||
get_target_property(_dawn_loc dawn::webgpu_dawn IMPORTED_LOCATION)
|
|
||||||
if (NOT _dawn_loc)
|
|
||||||
get_target_property(_dawn_loc dawn::webgpu_dawn IMPORTED_LOCATION_RELEASE)
|
|
||||||
endif ()
|
|
||||||
if (_dawn_loc)
|
|
||||||
cmake_path(GET _dawn_loc PARENT_PATH _dawn_bin_dir)
|
|
||||||
foreach (_dll IN ITEMS dxcompiler.dll dxil.dll)
|
|
||||||
if (EXISTS "${_dawn_bin_dir}/${_dll}")
|
|
||||||
add_custom_command(TARGET ${target} POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_dawn_bin_dir}/${_dll}" $<TARGET_FILE_DIR:${target}>
|
|
||||||
)
|
|
||||||
endif ()
|
|
||||||
endforeach ()
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# aurora_install_runtime_dlls(<executable_target> <install_destination>)
|
|
||||||
#
|
|
||||||
# INSTALL rule that copies all DLLs already placed next to the built target
|
|
||||||
# into the install destination.
|
|
||||||
function(aurora_install_runtime_dlls target install_destination)
|
|
||||||
if (NOT WIN32)
|
|
||||||
return()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
install(CODE
|
|
||||||
"file(GLOB _aurora_runtime_dlls \"$<TARGET_FILE_DIR:${target}>/*.dll\")\n\
|
|
||||||
if(_aurora_runtime_dlls)\n\
|
|
||||||
file(INSTALL DESTINATION \"${install_destination}\" TYPE FILE FILES \${_aurora_runtime_dlls})\n\
|
|
||||||
endif()"
|
|
||||||
)
|
|
||||||
endfunction()
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
include_guard(GLOBAL)
|
|
||||||
|
|
||||||
# Resolve Dawn/WebGPU dependency based on AURORA_DAWN_PROVIDER and AURORA_DAWN_LINKAGE.
|
|
||||||
#
|
|
||||||
# After this module runs:
|
|
||||||
# - dawn::webgpu_dawn target is available
|
|
||||||
# - dawn::dawncpp_headers target is available (for tests)
|
|
||||||
# - DAWN_ENABLE_* backend variables are set for the target platform
|
|
||||||
# - AURORA_DAWN_IS_SHARED is set to TRUE/FALSE
|
|
||||||
|
|
||||||
# When using a non-vendored Dawn, we don't get DAWN_ENABLE_* from its build.
|
|
||||||
# Infer from the target platform instead.
|
|
||||||
function(_aurora_dawn_set_platform_backends)
|
|
||||||
if (WIN32)
|
|
||||||
set(DAWN_ENABLE_D3D12 ON CACHE INTERNAL "")
|
|
||||||
# Disable D3D11 because this Aurora fork does not support it.
|
|
||||||
set(DAWN_ENABLE_D3D11 OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_VULKAN ON CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_METAL OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_DESKTOP_GL OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_OPENGLES OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_NULL ON CACHE INTERNAL "")
|
|
||||||
elseif (APPLE)
|
|
||||||
set(DAWN_ENABLE_D3D12 OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_D3D11 OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_VULKAN OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_METAL ON CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_DESKTOP_GL OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_OPENGLES OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_NULL ON CACHE INTERNAL "")
|
|
||||||
else () # Linux / other
|
|
||||||
set(DAWN_ENABLE_D3D12 OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_D3D11 OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_VULKAN ON CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_METAL OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_DESKTOP_GL OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_OPENGLES OFF CACHE INTERNAL "")
|
|
||||||
set(DAWN_ENABLE_NULL ON CACHE INTERNAL "")
|
|
||||||
endif ()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# ── Auto: resolve provider based on platform availability ──
|
|
||||||
set(_aurora_dawn_provider "${AURORA_DAWN_PROVIDER}")
|
|
||||||
if (_aurora_dawn_provider STREQUAL "auto")
|
|
||||||
# Prebuilt Dawn packages available for: windows-{amd64,arm64}, linux-{x86_64,aarch64}, darwin-{arm64,x86_64}
|
|
||||||
set(_has_package FALSE)
|
|
||||||
if (WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|x86_64|ARM64|aarch64)$")
|
|
||||||
set(_has_package TRUE)
|
|
||||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|aarch64)$")
|
|
||||||
set(_has_package TRUE)
|
|
||||||
elseif (APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|x86_64)$")
|
|
||||||
set(_has_package TRUE)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (_has_package)
|
|
||||||
set(_aurora_dawn_provider "package")
|
|
||||||
else ()
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
|
||||||
find_package(Dawn QUIET)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
|
||||||
if (Dawn_FOUND)
|
|
||||||
set(_aurora_dawn_provider "system")
|
|
||||||
else ()
|
|
||||||
set(_aurora_dawn_provider "vendor")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
set(AURORA_DAWN_PROVIDER "${_aurora_dawn_provider}" CACHE STRING "" FORCE)
|
|
||||||
message(STATUS "aurora: Dawn auto-resolved provider: ${_aurora_dawn_provider}")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (_aurora_dawn_provider STREQUAL "vendor")
|
|
||||||
# ── Vendor: FetchContent build from source ──
|
|
||||||
if (NOT TARGET webgpu_dawn)
|
|
||||||
message(STATUS "aurora: Fetching Dawn (provider=vendor, linkage=${AURORA_DAWN_LINKAGE})")
|
|
||||||
|
|
||||||
if (AURORA_DAWN_LINKAGE STREQUAL "shared")
|
|
||||||
set(DAWN_BUILD_MONOLITHIC_LIBRARY SHARED CACHE INTERNAL
|
|
||||||
"Bundle all dawn components into a single shared library.")
|
|
||||||
else ()
|
|
||||||
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC CACHE INTERNAL
|
|
||||||
"Bundle all dawn components into a single static library.")
|
|
||||||
endif ()
|
|
||||||
set(DAWN_BUILD_SAMPLES OFF CACHE INTERNAL "Disable Dawn sample applications")
|
|
||||||
set(DAWN_BUILD_BENCHMARKS OFF CACHE INTERNAL "Disable Dawn benchmarks")
|
|
||||||
set(DAWN_SUPPORTS_GLFW_FOR_WINDOWING OFF CACHE INTERNAL "Disable Dawn GLFW windowing")
|
|
||||||
set(DAWN_ENABLE_DESKTOP_GL OFF CACHE INTERNAL "Enable compilation of the OpenGL backend")
|
|
||||||
set(DAWN_ENABLE_OPENGLES OFF CACHE INTERNAL "Enable compilation of the OpenGL ES backend")
|
|
||||||
set(DAWN_FETCH_DEPENDENCIES ON CACHE INTERNAL
|
|
||||||
"Use fetch_dawn_dependencies.py as an alternative to using depot_tools")
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
|
||||||
set(DAWN_USE_WAYLAND ON CACHE INTERNAL "Enable support for Wayland surface")
|
|
||||||
endif ()
|
|
||||||
set(TINT_BUILD_TESTS OFF CACHE INTERNAL "Build tests")
|
|
||||||
set(TINT_BUILD_CMD_TOOLS OFF CACHE INTERNAL "Build the Tint command line tools")
|
|
||||||
if (NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY OR CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "DLL$")
|
|
||||||
set(ABSL_MSVC_STATIC_RUNTIME OFF CACHE INTERNAL "Link static runtime libraries")
|
|
||||||
else ()
|
|
||||||
set(ABSL_MSVC_STATIC_RUNTIME ON CACHE INTERNAL "Link static runtime libraries")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(dawn
|
|
||||||
URL "https://github.com/google/dawn/archive/refs/tags/${AURORA_DAWN_VERSION}.tar.gz"
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(dawn)
|
|
||||||
if (NOT TARGET webgpu_dawn)
|
|
||||||
message(FATAL_ERROR "Failed to make dawn available")
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing Dawn")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (AURORA_DAWN_LINKAGE STREQUAL "shared")
|
|
||||||
set(AURORA_DAWN_IS_SHARED TRUE PARENT_SCOPE)
|
|
||||||
else ()
|
|
||||||
set(AURORA_DAWN_IS_SHARED FALSE PARENT_SCOPE)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
elseif (_aurora_dawn_provider STREQUAL "system")
|
|
||||||
# ── System: find_package(Dawn) ──
|
|
||||||
message(STATUS "aurora: Using system Dawn (provider=system)")
|
|
||||||
if (NOT Dawn_FOUND)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
|
||||||
find_package(Dawn REQUIRED)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
|
||||||
endif ()
|
|
||||||
if (NOT TARGET dawn::webgpu_dawn)
|
|
||||||
message(FATAL_ERROR "find_package(Dawn) succeeded but dawn::webgpu_dawn target not found")
|
|
||||||
endif ()
|
|
||||||
_aurora_dawn_set_platform_backends()
|
|
||||||
|
|
||||||
get_target_property(_dawn_type dawn::webgpu_dawn TYPE)
|
|
||||||
if (_dawn_type STREQUAL "SHARED_LIBRARY")
|
|
||||||
set(AURORA_DAWN_IS_SHARED TRUE PARENT_SCOPE)
|
|
||||||
else ()
|
|
||||||
set(AURORA_DAWN_IS_SHARED FALSE PARENT_SCOPE)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
elseif (_aurora_dawn_provider STREQUAL "package")
|
|
||||||
# ── Package: download prebuilt Dawn install tree ──
|
|
||||||
if (NOT AURORA_DAWN_PACKAGE_URL)
|
|
||||||
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _dawn_system)
|
|
||||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _dawn_arch)
|
|
||||||
if (_dawn_system STREQUAL "windows")
|
|
||||||
if (_dawn_arch STREQUAL "x86_64")
|
|
||||||
set(_dawn_arch "amd64")
|
|
||||||
elseif (_dawn_arch STREQUAL "aarch64")
|
|
||||||
set(_dawn_arch "arm64")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL
|
|
||||||
"https://github.com/encounter/dawn-build/releases/download/${AURORA_DAWN_VERSION}/dawn-${_dawn_system}-${_dawn_arch}.tar.gz")
|
|
||||||
|
|
||||||
# A release asset is mutable: the same tag has already served two different windows-amd64 archives,
|
|
||||||
# and a cached extraction is never re-verified. Pin the digest for the combinations we ship.
|
|
||||||
if (NOT AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
AND AURORA_DAWN_VERSION STREQUAL "v20260603.191052"
|
|
||||||
AND _dawn_system STREQUAL "windows" AND _dawn_arch STREQUAL "amd64")
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=7785373d569b3b0237918ec9c523239f7d0667857c5ea8242e3cdfde95e6aeab")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
message(STATUS "aurora: Fetching prebuilt Dawn package from ${AURORA_DAWN_PACKAGE_URL}")
|
|
||||||
|
|
||||||
set(_dawn_prebuilt_hash_argument "")
|
|
||||||
if (AURORA_DAWN_PACKAGE_URL_HASH)
|
|
||||||
set(_dawn_prebuilt_hash_argument URL_HASH "${AURORA_DAWN_PACKAGE_URL_HASH}")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(dawn_prebuilt
|
|
||||||
URL "${AURORA_DAWN_PACKAGE_URL}"
|
|
||||||
${_dawn_prebuilt_hash_argument}
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(dawn_prebuilt)
|
|
||||||
|
|
||||||
# Detect package layout: may be nested in a subdirectory
|
|
||||||
set(_dawn_pkg_dir "${dawn_prebuilt_SOURCE_DIR}")
|
|
||||||
file(GLOB _dawn_subdirs
|
|
||||||
"${dawn_prebuilt_SOURCE_DIR}/dawn-*"
|
|
||||||
"${dawn_prebuilt_SOURCE_DIR}/Dawn-*"
|
|
||||||
)
|
|
||||||
foreach (_sd IN LISTS _dawn_subdirs)
|
|
||||||
if (IS_DIRECTORY "${_sd}")
|
|
||||||
set(_dawn_pkg_dir "${_sd}")
|
|
||||||
break()
|
|
||||||
endif ()
|
|
||||||
endforeach ()
|
|
||||||
if (IS_DIRECTORY "${dawn_prebuilt_SOURCE_DIR}/dawn-install")
|
|
||||||
set(_dawn_pkg_dir "${dawn_prebuilt_SOURCE_DIR}/dawn-install")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Static Dawn packages may link Threads::Threads
|
|
||||||
find_package(Threads QUIET)
|
|
||||||
|
|
||||||
# Find DawnConfig.cmake in the package
|
|
||||||
set(_dawn_cmake_found FALSE)
|
|
||||||
foreach (_cmake_path
|
|
||||||
"${_dawn_pkg_dir}/lib/cmake/Dawn"
|
|
||||||
"${_dawn_pkg_dir}/lib64/cmake/Dawn"
|
|
||||||
"${_dawn_pkg_dir}/cmake/Dawn"
|
|
||||||
"${_dawn_pkg_dir}/cmake"
|
|
||||||
)
|
|
||||||
if (EXISTS "${_cmake_path}/DawnConfig.cmake")
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
|
||||||
find_package(Dawn REQUIRED CONFIG PATHS "${_cmake_path}" NO_DEFAULT_PATH)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
|
||||||
set(_dawn_cmake_found TRUE)
|
|
||||||
break()
|
|
||||||
endif ()
|
|
||||||
endforeach ()
|
|
||||||
|
|
||||||
if (NOT _dawn_cmake_found OR NOT TARGET dawn::webgpu_dawn)
|
|
||||||
message(FATAL_ERROR
|
|
||||||
"Dawn package does not contain DawnConfig.cmake.\n"
|
|
||||||
"Searched: ${_dawn_pkg_dir}/{lib,lib64,cmake}/cmake/Dawn/\n"
|
|
||||||
"The package must be a Dawn install tree built with DAWN_ENABLE_INSTALL=ON.")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
_aurora_dawn_set_platform_backends()
|
|
||||||
|
|
||||||
get_target_property(_dawn_pkg_type dawn::webgpu_dawn TYPE)
|
|
||||||
if (_dawn_pkg_type STREQUAL "SHARED_LIBRARY")
|
|
||||||
set(AURORA_DAWN_IS_SHARED TRUE PARENT_SCOPE)
|
|
||||||
|
|
||||||
else ()
|
|
||||||
set(AURORA_DAWN_IS_SHARED FALSE PARENT_SCOPE)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
else ()
|
|
||||||
message(FATAL_ERROR "Invalid AURORA_DAWN_PROVIDER: ${AURORA_DAWN_PROVIDER} "
|
|
||||||
"(must be auto, vendor, system, or package)")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Ensure dawn::dawncpp_headers target exists (needed by tests).
|
|
||||||
# When Dawn is vendored, this target is created by Dawn's CMake.
|
|
||||||
# For package/system providers, create it from the same include dirs.
|
|
||||||
if (NOT TARGET dawn::dawncpp_headers)
|
|
||||||
add_library(dawn_dawncpp_headers INTERFACE IMPORTED GLOBAL)
|
|
||||||
get_target_property(_dawn_inc dawn::webgpu_dawn INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
if (NOT _dawn_inc)
|
|
||||||
if (TARGET dawn::dawn_public_config)
|
|
||||||
get_target_property(_dawn_inc dawn::dawn_public_config INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
if (_dawn_inc)
|
|
||||||
target_include_directories(dawn_dawncpp_headers INTERFACE ${_dawn_inc})
|
|
||||||
else ()
|
|
||||||
target_link_libraries(dawn_dawncpp_headers INTERFACE dawn::webgpu_dawn)
|
|
||||||
endif ()
|
|
||||||
add_library(dawn::dawncpp_headers ALIAS dawn_dawncpp_headers)
|
|
||||||
endif ()
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# libusb for SDL3's HIDAPI joystick drivers on Windows.
|
|
||||||
#
|
|
||||||
# The official GameCube adapter (WUP-028) is a vendor-specific USB device, not HID, so SDL3
|
|
||||||
# can only reach it through libusb - which the official SDL3 packages leave out. The vendored
|
|
||||||
# SDL3 build compiles libusb from the pinned upstream release and links it in statically.
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(libusb
|
|
||||||
URL "https://github.com/libusb/libusb/releases/download/v${AURORA_LIBUSB_VERSION}/libusb-${AURORA_LIBUSB_VERSION}.tar.bz2"
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
)
|
|
||||||
# Upstream ships no CMakeLists.txt, so this only populates the source tree.
|
|
||||||
FetchContent_MakeAvailable(libusb)
|
|
||||||
|
|
||||||
set(_libusb_root "${libusb_SOURCE_DIR}/libusb")
|
|
||||||
add_library(usb-1.0 STATIC
|
|
||||||
"${_libusb_root}/core.c"
|
|
||||||
"${_libusb_root}/descriptor.c"
|
|
||||||
"${_libusb_root}/hotplug.c"
|
|
||||||
"${_libusb_root}/io.c"
|
|
||||||
"${_libusb_root}/strerror.c"
|
|
||||||
"${_libusb_root}/sync.c"
|
|
||||||
"${_libusb_root}/os/events_windows.c"
|
|
||||||
"${_libusb_root}/os/threads_windows.c"
|
|
||||||
"${_libusb_root}/os/windows_common.c"
|
|
||||||
"${_libusb_root}/os/windows_usbdk.c"
|
|
||||||
"${_libusb_root}/os/windows_winusb.c"
|
|
||||||
)
|
|
||||||
target_include_directories(usb-1.0
|
|
||||||
PUBLIC "${_libusb_root}"
|
|
||||||
PRIVATE "${CMAKE_CURRENT_LIST_DIR}/libusb" "${_libusb_root}/os"
|
|
||||||
)
|
|
||||||
set_target_properties(usb-1.0 PROPERTIES UNITY_BUILD OFF)
|
|
||||||
add_library(LibUSB::LibUSB ALIAS usb-1.0)
|
|
||||||
|
|
||||||
# SDL's FindLibUSB expects an installed copy. Satisfy its presence checks with this target
|
|
||||||
# instead: the alias pre-empts the imported target it would otherwise create, and the link
|
|
||||||
# probe is answered up front because the archive does not exist until build time.
|
|
||||||
set(LibUSB_INCLUDE_PATH "${_libusb_root}" CACHE PATH "" FORCE)
|
|
||||||
set(LibUSB_LIBRARY "usb-1.0" CACHE STRING "" FORCE)
|
|
||||||
set(HAVE_LIBUSB_H 1 CACHE INTERNAL "" FORCE)
|
|
||||||
set(SDL_HIDAPI_LIBUSB ON CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_HIDAPI_LIBUSB_SHARED OFF CACHE BOOL "" FORCE)
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
include_guard(GLOBAL)
|
|
||||||
|
|
||||||
# Resolve nod dependency based on AURORA_NOD_PROVIDER.
|
|
||||||
#
|
|
||||||
# After this module runs:
|
|
||||||
# - nod::nod target is available
|
|
||||||
#
|
|
||||||
# AURORA_NOD_LINKAGE controls shared vs static linkage:
|
|
||||||
# - "shared" (default): prefer nod::nod_shared
|
|
||||||
# - "static": prefer nod::nod_static
|
|
||||||
|
|
||||||
# ── Auto: resolve provider based on platform availability ──
|
|
||||||
set(_aurora_nod_provider "${AURORA_NOD_PROVIDER}")
|
|
||||||
if (_aurora_nod_provider STREQUAL "auto")
|
|
||||||
# Prebuilt nod packages available for: windows-x86_64, linux-x86_64, macos-arm64
|
|
||||||
set(_has_package FALSE)
|
|
||||||
if (WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64)$")
|
|
||||||
set(_has_package TRUE)
|
|
||||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64)$")
|
|
||||||
set(_has_package TRUE)
|
|
||||||
elseif (APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64)$")
|
|
||||||
set(_has_package TRUE)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (_has_package)
|
|
||||||
set(_aurora_nod_provider "package")
|
|
||||||
else ()
|
|
||||||
find_package(nod QUIET CONFIG)
|
|
||||||
if (nod_FOUND)
|
|
||||||
set(_aurora_nod_provider "system")
|
|
||||||
else ()
|
|
||||||
set(_aurora_nod_provider "vendor")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
set(AURORA_NOD_PROVIDER "${_aurora_nod_provider}" CACHE STRING "" FORCE)
|
|
||||||
message(STATUS "aurora: nod auto-resolved provider: ${_aurora_nod_provider}")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (_aurora_nod_provider STREQUAL "vendor")
|
|
||||||
# ── Vendor: FetchContent + Corrosion build from source ──
|
|
||||||
if (NOT TARGET nod::nod)
|
|
||||||
message(STATUS "aurora: Building nod (provider=vendor, linkage=${AURORA_NOD_LINKAGE})")
|
|
||||||
|
|
||||||
# Corrosion may default to the host Rust target triple (commonly x86_64 on
|
|
||||||
# Windows). Force Rust to i686 for true Win32 builds so nod and its vendored
|
|
||||||
# compression libraries are built for the same architecture as the C/C++ build.
|
|
||||||
if (WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4)
|
|
||||||
set(Rust_CARGO_TARGET "i686-pc-windows-msvc" CACHE STRING "Rust target triple" FORCE)
|
|
||||||
set(Rust_RUSTUP_INSTALL_MISSING_TARGET ON CACHE BOOL "Allow Corrosion to install missing Rust targets" FORCE)
|
|
||||||
message(STATUS "aurora: forcing Rust_CARGO_TARGET=${Rust_CARGO_TARGET} for Win32 nod build")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Control whether nod builds shared, static, or both.
|
|
||||||
# Save/restore BUILD_SHARED_LIBS to avoid leaking into other subdirectories.
|
|
||||||
set(_aurora_nod_saved_bsl "${BUILD_SHARED_LIBS}")
|
|
||||||
if (AURORA_NOD_LINKAGE STREQUAL "static")
|
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
|
||||||
else ()
|
|
||||||
set(BUILD_SHARED_LIBS ON)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(aurora_nod
|
|
||||||
GIT_REPOSITORY "https://github.com/encounter/nod.git"
|
|
||||||
GIT_TAG "${AURORA_NOD_VERSION}"
|
|
||||||
GIT_SHALLOW TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_MakeAvailable(aurora_nod)
|
|
||||||
set(BUILD_SHARED_LIBS "${_aurora_nod_saved_bsl}")
|
|
||||||
unset(_aurora_nod_saved_bsl)
|
|
||||||
if (NOT TARGET nod::nod)
|
|
||||||
message(FATAL_ERROR "Failed to make nod available")
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing nod")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
elseif (_aurora_nod_provider STREQUAL "system")
|
|
||||||
# ── System: find preinstalled nod ──
|
|
||||||
message(STATUS "aurora: Using system nod (provider=system, linkage=${AURORA_NOD_LINKAGE})")
|
|
||||||
|
|
||||||
if (NOT nod_FOUND)
|
|
||||||
set(_aurora_nod_saved_bsl "${BUILD_SHARED_LIBS}")
|
|
||||||
if (AURORA_NOD_LINKAGE STREQUAL "static")
|
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
|
||||||
else ()
|
|
||||||
set(BUILD_SHARED_LIBS ON)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
find_package(nod REQUIRED CONFIG)
|
|
||||||
set(BUILD_SHARED_LIBS "${_aurora_nod_saved_bsl}")
|
|
||||||
unset(_aurora_nod_saved_bsl)
|
|
||||||
endif ()
|
|
||||||
if (NOT TARGET nod::nod)
|
|
||||||
message(FATAL_ERROR "find_package(nod) succeeded but nod::nod target not found")
|
|
||||||
endif ()
|
|
||||||
message(STATUS "aurora: Found nod via find_package(nod)")
|
|
||||||
|
|
||||||
elseif (_aurora_nod_provider STREQUAL "package")
|
|
||||||
# ── Package: download prebuilt nod library ──
|
|
||||||
if (NOT AURORA_NOD_PACKAGE_URL)
|
|
||||||
if (WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8)
|
|
||||||
set(_nod_platform "windows-x86_64")
|
|
||||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
|
||||||
set(_nod_platform "linux-x86_64")
|
|
||||||
elseif (APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
|
|
||||||
set(_nod_platform "macos-arm64")
|
|
||||||
else ()
|
|
||||||
message(FATAL_ERROR
|
|
||||||
"AURORA_NOD_PROVIDER=package requires AURORA_NOD_PACKAGE_URL on this platform.\n"
|
|
||||||
"Prebuilt packages: https://github.com/encounter/nod/releases")
|
|
||||||
endif ()
|
|
||||||
set(AURORA_NOD_PACKAGE_URL
|
|
||||||
"https://github.com/encounter/nod/releases/download/${AURORA_NOD_VERSION}/libnod-${_nod_platform}.tar.gz")
|
|
||||||
endif ()
|
|
||||||
message(STATUS "aurora: Fetching prebuilt nod package (provider=package, linkage=${AURORA_NOD_LINKAGE})")
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(nod_prebuilt
|
|
||||||
URL "${AURORA_NOD_PACKAGE_URL}"
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(nod_prebuilt)
|
|
||||||
|
|
||||||
# The prebuilt package ships a CMake config that creates nod::nod_shared,
|
|
||||||
# nod::nod_static, and a default nod::nod (based on BUILD_SHARED_LIBS).
|
|
||||||
# Set BUILD_SHARED_LIBS so the config selects the right default target.
|
|
||||||
set(_aurora_nod_saved_bsl "${BUILD_SHARED_LIBS}")
|
|
||||||
if (AURORA_NOD_LINKAGE STREQUAL "static")
|
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
|
||||||
else ()
|
|
||||||
set(BUILD_SHARED_LIBS ON)
|
|
||||||
endif ()
|
|
||||||
find_package(nod REQUIRED CONFIG
|
|
||||||
PATHS "${nod_prebuilt_SOURCE_DIR}"
|
|
||||||
NO_DEFAULT_PATH
|
|
||||||
)
|
|
||||||
set(BUILD_SHARED_LIBS "${_aurora_nod_saved_bsl}")
|
|
||||||
unset(_aurora_nod_saved_bsl)
|
|
||||||
if (NOT TARGET nod::nod)
|
|
||||||
message(FATAL_ERROR "Prebuilt nod package did not provide nod::nod target")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
else ()
|
|
||||||
message(FATAL_ERROR "Invalid AURORA_NOD_PROVIDER: ${AURORA_NOD_PROVIDER} "
|
|
||||||
"(must be auto, vendor, system, or package)")
|
|
||||||
endif ()
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
# Source-level fixes applied to the vendored SDL3 tree before it is built.
|
|
||||||
#
|
|
||||||
# Each patch is an exact string replacement, checked and idempotent: a tree that
|
|
||||||
# already carries the fix is left alone, a tree where the anchor text is missing
|
|
||||||
# (a different SDL version) stops the configure with a clear message instead of
|
|
||||||
# silently building without the fix.
|
|
||||||
#
|
|
||||||
# Used two ways:
|
|
||||||
# - included by AuroraSDL3Provider.cmake, which calls aurora_sdl3_apply_patches()
|
|
||||||
# on a pre-provided source tree (FETCHCONTENT_SOURCE_DIR_SDL);
|
|
||||||
# - run as `cmake -DSDL_SOURCE_DIR=<dir> -P AuroraSDL3Patches.cmake` from the
|
|
||||||
# FetchContent PATCH_COMMAND for a freshly extracted tarball.
|
|
||||||
|
|
||||||
function(_aurora_sdl3_replace file description old new)
|
|
||||||
file(READ "${file}" _content)
|
|
||||||
string(FIND "${_content}" "${new}" _already)
|
|
||||||
if (NOT _already EQUAL -1)
|
|
||||||
return()
|
|
||||||
endif ()
|
|
||||||
string(FIND "${_content}" "${old}" _anchor)
|
|
||||||
if (_anchor EQUAL -1)
|
|
||||||
message(FATAL_ERROR "aurora: SDL3 patch '${description}' does not apply to ${file}; "
|
|
||||||
"the vendored SDL version changed, review AuroraSDL3Patches.cmake")
|
|
||||||
endif ()
|
|
||||||
string(REPLACE "${old}" "${new}" _content "${_content}")
|
|
||||||
file(WRITE "${file}" "${_content}")
|
|
||||||
message(STATUS "aurora: SDL3 patch applied: ${description}")
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
function(aurora_sdl3_apply_patches sdl_source_dir)
|
|
||||||
set(_wii "${sdl_source_dir}/src/joystick/hidapi/SDL_hidapi_wii.c")
|
|
||||||
if (NOT EXISTS "${_wii}")
|
|
||||||
message(FATAL_ERROR "aurora: SDL3 source tree at ${sdl_source_dir} has no SDL_hidapi_wii.c")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Wii Remote: rebuild the joystick in place after an extension change.
|
|
||||||
#
|
|
||||||
# When a Nunchuk or Classic Controller is plugged in or pulled out, the driver
|
|
||||||
# flags the joystick as disconnected so it can come back with the new name and
|
|
||||||
# capabilities. But the HID device stays open, and HIDAPI only removes a
|
|
||||||
# joystick-less device once its handle is closed, so nothing ever re-creates
|
|
||||||
# the joystick: the remote is gone for good until the application toggles the
|
|
||||||
# driver hint (which closes and re-opens the Bluetooth HID handle, something
|
|
||||||
# some Windows Bluetooth stacks answer by dropping the link). Instead, when the
|
|
||||||
# device has no joystick, probe the extension again (two bounded attempts, at
|
|
||||||
# most once a second) and connect a new joystick on the still-open handle.
|
|
||||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (context field)"
|
|
||||||
[==[ Uint64 m_ulNextMotionPlusCheck;
|
|
||||||
bool m_bDisconnected;
|
|
||||||
]==]
|
|
||||||
[==[ Uint64 m_ulNextMotionPlusCheck;
|
|
||||||
bool m_bDisconnected;
|
|
||||||
Uint64 m_ulNextReconnect; /* WiiCompiled: see HIDAPI_DriverWii_UpdateDevice */
|
|
||||||
]==])
|
|
||||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (bounded extension probe)"
|
|
||||||
[==[static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device)
|
|
||||||
{
|
|
||||||
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
|
|
||||||
EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown;
|
|
||||||
const int MAX_ATTEMPTS = 20;
|
|
||||||
int attempts = 0;
|
|
||||||
]==]
|
|
||||||
[==[static EWiiExtensionControllerType ReadExtensionControllerTypeAttempts(SDL_HIDAPI_Device *device, int MAX_ATTEMPTS)
|
|
||||||
{
|
|
||||||
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
|
|
||||||
EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown;
|
|
||||||
int attempts = 0;
|
|
||||||
]==])
|
|
||||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (probe wrapper)"
|
|
||||||
[==[static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
|
|
||||||
{
|
|
||||||
]==]
|
|
||||||
[==[static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device)
|
|
||||||
{
|
|
||||||
return ReadExtensionControllerTypeAttempts(device, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
|
|
||||||
{
|
|
||||||
]==])
|
|
||||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (UpdateDevice)"
|
|
||||||
[==[ if (device->num_joysticks > 0) {
|
|
||||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
now = SDL_GetTicks();
|
|
||||||
]==]
|
|
||||||
[==[ if (device->num_joysticks > 0) {
|
|
||||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
|
||||||
} else {
|
|
||||||
/* WiiCompiled: the joystick was dropped (extension plugged or unplugged,
|
|
||||||
* or a failed read) but the HID device is still open, and the device
|
|
||||||
* list only removes a joystick-less device once its handle is closed.
|
|
||||||
* Rebuild the joystick in place, so an extension change behaves like it
|
|
||||||
* does on the console: the remote never goes away, it just changes
|
|
||||||
* type. If the remote does not answer, keep the device and retry. */
|
|
||||||
now = SDL_GetTicks();
|
|
||||||
if (ctx->m_ulNextReconnect != 0 && now < ctx->m_ulNextReconnect) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ctx->m_ulNextReconnect = now + 1000;
|
|
||||||
{
|
|
||||||
EWiiExtensionControllerType type = ReadExtensionControllerTypeAttempts(device, 2);
|
|
||||||
if (type == k_eWiiExtensionControllerType_Unknown) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ctx->m_bDisconnected = false;
|
|
||||||
ctx->m_ulLastInput = now;
|
|
||||||
ctx->m_ulNextReconnect = 0;
|
|
||||||
ctx->m_eExtensionControllerType = type;
|
|
||||||
UpdateDeviceIdentity(device);
|
|
||||||
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Reconnected joystick as %s", device->name);
|
|
||||||
return HIDAPI_JoystickConnected(device, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
now = SDL_GetTicks();
|
|
||||||
]==])
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# Script mode (FetchContent PATCH_COMMAND).
|
|
||||||
if (CMAKE_SCRIPT_MODE_FILE AND DEFINED SDL_SOURCE_DIR)
|
|
||||||
aurora_sdl3_apply_patches("${SDL_SOURCE_DIR}")
|
|
||||||
endif ()
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
include_guard(GLOBAL)
|
|
||||||
|
|
||||||
# Resolve SDL3 dependency based on AURORA_SDL3_PROVIDER and AURORA_SDL3_LINKAGE.
|
|
||||||
#
|
|
||||||
# After this module runs, the variable AURORA_SDL3_TARGET is set to the
|
|
||||||
# appropriate target name for use in target_link_libraries() calls.
|
|
||||||
|
|
||||||
# Select the SDL3 target matching the requested linkage.
|
|
||||||
function(_aurora_sdl3_select_target)
|
|
||||||
if (AURORA_SDL3_LINKAGE STREQUAL "static")
|
|
||||||
if (TARGET SDL3::SDL3-static)
|
|
||||||
set(AURORA_SDL3_TARGET SDL3::SDL3-static CACHE INTERNAL "")
|
|
||||||
message(STATUS "aurora: Using static SDL3")
|
|
||||||
return()
|
|
||||||
elseif (TARGET SDL3::SDL3)
|
|
||||||
message(WARNING "aurora: Static SDL3 requested but only shared is available")
|
|
||||||
set(AURORA_SDL3_TARGET SDL3::SDL3 CACHE INTERNAL "")
|
|
||||||
message(STATUS "aurora: Using shared SDL3")
|
|
||||||
return()
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
if (TARGET SDL3::SDL3)
|
|
||||||
set(AURORA_SDL3_TARGET SDL3::SDL3 CACHE INTERNAL "")
|
|
||||||
message(STATUS "aurora: Using shared SDL3")
|
|
||||||
return()
|
|
||||||
elseif (TARGET SDL3::SDL3-static)
|
|
||||||
message(WARNING "aurora: Shared SDL3 requested but only static is available")
|
|
||||||
set(AURORA_SDL3_TARGET SDL3::SDL3-static CACHE INTERNAL "")
|
|
||||||
message(STATUS "aurora: Using static SDL3")
|
|
||||||
return()
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
message(FATAL_ERROR "No SDL3 target found (looked for SDL3::SDL3 and SDL3::SDL3-static)")
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# ── Auto: resolve provider based on platform availability ──
|
|
||||||
set(_aurora_sdl3_provider "${AURORA_SDL3_PROVIDER}")
|
|
||||||
if (_aurora_sdl3_provider STREQUAL "auto")
|
|
||||||
# Prebuilt SDL3 packages are available for Windows (MSVC and MinGW)
|
|
||||||
if (WIN32)
|
|
||||||
set(_aurora_sdl3_provider "package")
|
|
||||||
else ()
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
|
||||||
find_package(SDL3 QUIET)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
|
||||||
if (SDL3_FOUND)
|
|
||||||
set(_aurora_sdl3_provider "system")
|
|
||||||
else ()
|
|
||||||
set(_aurora_sdl3_provider "vendor")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
set(AURORA_SDL3_PROVIDER "${_aurora_sdl3_provider}" CACHE STRING "" FORCE)
|
|
||||||
message(STATUS "aurora: SDL3 auto-resolved provider: ${_aurora_sdl3_provider}")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (_aurora_sdl3_provider STREQUAL "system")
|
|
||||||
# ── System: find_package(SDL3) ──
|
|
||||||
message(STATUS "aurora: Using system SDL3 (provider=system)")
|
|
||||||
if (NOT SDL3_FOUND)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
|
||||||
find_package(SDL3 REQUIRED)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
|
||||||
endif ()
|
|
||||||
_aurora_sdl3_select_target()
|
|
||||||
|
|
||||||
elseif (_aurora_sdl3_provider STREQUAL "package")
|
|
||||||
# ── Package: download custom build or official SDL3 development package ──
|
|
||||||
if (NOT AURORA_SDL3_PACKAGE_URL)
|
|
||||||
if (MINGW)
|
|
||||||
# WIN32 is also true for MinGW. Select the matching GNU-ABI package
|
|
||||||
# before considering Visual C++ packages.
|
|
||||||
set(AURORA_SDL3_PACKAGE_URL
|
|
||||||
"https://github.com/libsdl-org/SDL/releases/download/release-${AURORA_SDL3_VERSION}/SDL3-devel-${AURORA_SDL3_VERSION}-mingw.tar.gz")
|
|
||||||
elseif (WIN32)
|
|
||||||
# We have custom builds of SDL3 for Win32 x86/AMD64 with libusb support included
|
|
||||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|x86)$")
|
|
||||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _sdl3_arch)
|
|
||||||
set(AURORA_SDL3_PACKAGE_URL
|
|
||||||
"https://github.com/encounter/sdl3-build/releases/download/v${AURORA_SDL3_VERSION}/SDL3-windows-${_sdl3_arch}.tar.gz")
|
|
||||||
else ()
|
|
||||||
set(AURORA_SDL3_PACKAGE_URL
|
|
||||||
"https://github.com/libsdl-org/SDL/releases/download/release-${AURORA_SDL3_VERSION}/SDL3-devel-${AURORA_SDL3_VERSION}-VC.zip")
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
message(FATAL_ERROR
|
|
||||||
"AURORA_SDL3_PROVIDER=package requires AURORA_SDL3_PACKAGE_URL on non-Windows platforms.\n"
|
|
||||||
"Official SDL3 packages: https://github.com/libsdl-org/SDL/releases")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
message(STATUS "aurora: Fetching prebuilt SDL3 package from ${AURORA_SDL3_PACKAGE_URL}")
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(sdl3_prebuilt
|
|
||||||
URL "${AURORA_SDL3_PACKAGE_URL}"
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(sdl3_prebuilt)
|
|
||||||
|
|
||||||
set(_sdl3_search_paths
|
|
||||||
"${sdl3_prebuilt_SOURCE_DIR}/cmake"
|
|
||||||
"${sdl3_prebuilt_SOURCE_DIR}/lib/cmake/SDL3"
|
|
||||||
"${sdl3_prebuilt_SOURCE_DIR}"
|
|
||||||
)
|
|
||||||
file(GLOB _sdl3_subdirs "${sdl3_prebuilt_SOURCE_DIR}/SDL3-*")
|
|
||||||
foreach (_sd IN LISTS _sdl3_subdirs)
|
|
||||||
list(APPEND _sdl3_search_paths
|
|
||||||
"${_sd}/cmake"
|
|
||||||
"${_sd}/lib/cmake/SDL3"
|
|
||||||
"${_sd}"
|
|
||||||
)
|
|
||||||
endforeach ()
|
|
||||||
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
|
||||||
find_package(SDL3 REQUIRED CONFIG PATHS ${_sdl3_search_paths} NO_DEFAULT_PATH)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
|
||||||
_aurora_sdl3_select_target()
|
|
||||||
|
|
||||||
elseif (_aurora_sdl3_provider STREQUAL "vendor")
|
|
||||||
# ── Vendor: FetchContent build from source ──
|
|
||||||
if (NOT TARGET SDL3-static AND NOT TARGET SDL3-shared)
|
|
||||||
message(STATUS "aurora: Fetching SDL3 (provider=vendor)")
|
|
||||||
|
|
||||||
if (AURORA_SDL3_LINKAGE STREQUAL "static")
|
|
||||||
set(SDL_STATIC ON CACHE BOOL "Build SDL3 static library" FORCE)
|
|
||||||
set(SDL_SHARED OFF CACHE BOOL "Build SDL3 shared library" FORCE)
|
|
||||||
else ()
|
|
||||||
set(SDL_STATIC OFF CACHE BOOL "Build SDL3 static library" FORCE)
|
|
||||||
set(SDL_SHARED ON CACHE BOOL "Build SDL3 shared library" FORCE)
|
|
||||||
endif ()
|
|
||||||
if (WIN32)
|
|
||||||
set(SDL_LIBC ON CACHE BOOL "Use the system C library" FORCE)
|
|
||||||
if (AURORA_SDL3_LIBUSB)
|
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/AuroraLibUSB.cmake")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
# Source fixes for the vendored SDL (see AuroraSDL3Patches.cmake). A freshly
|
|
||||||
# downloaded tarball is patched by the PATCH_COMMAND; a tree handed in through
|
|
||||||
# FETCHCONTENT_SOURCE_DIR_SDL skips the download steps, so patch it here.
|
|
||||||
set(_aurora_sdl3_patches "${CMAKE_CURRENT_LIST_DIR}/AuroraSDL3Patches.cmake")
|
|
||||||
include("${_aurora_sdl3_patches}")
|
|
||||||
if (DEFINED FETCHCONTENT_SOURCE_DIR_SDL AND EXISTS "${FETCHCONTENT_SOURCE_DIR_SDL}")
|
|
||||||
aurora_sdl3_apply_patches("${FETCHCONTENT_SOURCE_DIR_SDL}")
|
|
||||||
endif ()
|
|
||||||
FetchContent_Declare(SDL
|
|
||||||
URL "https://github.com/libsdl-org/SDL/releases/download/release-${AURORA_SDL3_VERSION}/SDL3-${AURORA_SDL3_VERSION}.tar.gz"
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
PATCH_COMMAND "${CMAKE_COMMAND}" -DSDL_SOURCE_DIR=<SOURCE_DIR> -P "${_aurora_sdl3_patches}"
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(SDL)
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing SDL3")
|
|
||||||
endif ()
|
|
||||||
_aurora_sdl3_select_target()
|
|
||||||
|
|
||||||
else ()
|
|
||||||
message(FATAL_ERROR "Invalid AURORA_SDL3_PROVIDER: ${AURORA_SDL3_PROVIDER} "
|
|
||||||
"(must be auto, vendor, system, or package)")
|
|
||||||
endif ()
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
add_library(aurora_card STATIC
|
|
||||||
lib/card/BlockAllocationTable.cpp
|
|
||||||
lib/card/CardRawFile.cpp
|
|
||||||
lib/card/CardGciFolder.cpp
|
|
||||||
lib/card/Directory.cpp
|
|
||||||
lib/card/DolphinCardPath.cpp
|
|
||||||
lib/card/File.cpp
|
|
||||||
lib/card/FileIO.cpp
|
|
||||||
lib/card/SRAM.cpp
|
|
||||||
lib/card/Util.cpp
|
|
||||||
lib/dolphin/card.cpp
|
|
||||||
)
|
|
||||||
add_library(aurora::card ALIAS aurora_card)
|
|
||||||
set_target_properties(aurora_card PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_link_libraries(aurora_card PUBLIC aurora::core)
|
|
||||||
target_include_directories(aurora_card PRIVATE include)
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
add_library(aurora_core STATIC
|
|
||||||
lib/aurora.cpp
|
|
||||||
lib/input.cpp
|
|
||||||
lib/window.cpp
|
|
||||||
lib/logging.cpp
|
|
||||||
lib/system_info.cpp
|
|
||||||
lib/system_info.hpp
|
|
||||||
)
|
|
||||||
add_library(aurora::core ALIAS aurora_core)
|
|
||||||
set_target_properties(aurora_core PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_compile_definitions(aurora_core PUBLIC AURORA TARGET_PC)
|
|
||||||
target_include_directories(aurora_core PUBLIC include)
|
|
||||||
target_link_libraries(aurora_core PUBLIC fmt::fmt ${AURORA_SDL3_TARGET} xxhash)
|
|
||||||
target_link_libraries(aurora_core PRIVATE absl::btree absl::flat_hash_map sqlite3 TracyClient)
|
|
||||||
if (AURORA_ENABLE_GX AND AURORA_CACHE_USE_ZSTD)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE AURORA_CACHE_USE_ZSTD)
|
|
||||||
target_link_libraries(aurora_core PRIVATE libzstd_static)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
|
|
||||||
# stuff for fetching system info.
|
|
||||||
target_link_libraries(aurora_core PRIVATE ntdll dxgi advapi32 user32)
|
|
||||||
elseif (APPLE)
|
|
||||||
target_sources(aurora_core PRIVATE lib/system_info_mac.mm)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (AURORA_ENABLE_GX)
|
|
||||||
target_sources(aurora_core PRIVATE lib/imgui.cpp)
|
|
||||||
target_link_libraries(aurora_core PUBLIC imgui)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (AURORA_ENABLE_GX)
|
|
||||||
target_compile_definitions(aurora_core PUBLIC AURORA_ENABLE_GX WEBGPU_DAWN)
|
|
||||||
target_sources(aurora_core PRIVATE lib/webgpu/gpu.cpp lib/webgpu/gpu_cache.cpp lib/dawn/BackendBinding.cpp)
|
|
||||||
target_link_libraries(aurora_core PRIVATE dawn::webgpu_dawn)
|
|
||||||
if (DAWN_ENABLE_VULKAN)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_VULKAN)
|
|
||||||
endif ()
|
|
||||||
if (DAWN_ENABLE_METAL)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_METAL)
|
|
||||||
target_sources(aurora_core PRIVATE lib/dawn/MetalBinding.mm)
|
|
||||||
set_source_files_properties(lib/dawn/MetalBinding.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
|
||||||
target_link_options(aurora_core PUBLIC "LINKER:-weak_framework,Metal")
|
|
||||||
endif ()
|
|
||||||
if (DAWN_ENABLE_D3D11)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_D3D11)
|
|
||||||
endif ()
|
|
||||||
if (DAWN_ENABLE_D3D12)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_D3D12)
|
|
||||||
endif ()
|
|
||||||
if (DAWN_ENABLE_DESKTOP_GL OR DAWN_ENABLE_OPENGLES)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_OPENGL)
|
|
||||||
if (DAWN_ENABLE_DESKTOP_GL)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_DESKTOP_GL)
|
|
||||||
endif ()
|
|
||||||
if (DAWN_ENABLE_OPENGLES)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_OPENGLES)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
if (DAWN_ENABLE_NULL)
|
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_NULL)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AuroraNodProvider.cmake)
|
|
||||||
|
|
||||||
add_library(aurora_dvd STATIC lib/dolphin/dvd/dvd.cpp)
|
|
||||||
add_library(aurora::dvd ALIAS aurora_dvd)
|
|
||||||
set_target_properties(aurora_dvd PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_compile_definitions(aurora_dvd PUBLIC AURORA TARGET_PC)
|
|
||||||
target_include_directories(aurora_dvd PUBLIC include)
|
|
||||||
target_link_libraries(aurora_dvd PUBLIC nod::nod ${AURORA_SDL3_TARGET})
|
|
||||||
target_link_libraries(aurora_dvd PRIVATE fmt::fmt)
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
add_library(aurora_gd STATIC
|
|
||||||
lib/dolphin/gd/GDBase.cpp
|
|
||||||
lib/dolphin/gd/GDGeometry.cpp
|
|
||||||
lib/dolphin/gd/GDIndirect.cpp
|
|
||||||
lib/dolphin/gd/GDLight.cpp
|
|
||||||
lib/dolphin/gd/GDPixel.cpp
|
|
||||||
lib/dolphin/gd/GDTev.cpp
|
|
||||||
lib/dolphin/gd/GDTexture.cpp
|
|
||||||
lib/dolphin/gd/GDTransform.cpp
|
|
||||||
lib/dolphin/gd/gd.hpp
|
|
||||||
lib/dolphin/gd/GDAurora.cpp
|
|
||||||
)
|
|
||||||
add_library(aurora::gd ALIAS aurora_gd)
|
|
||||||
set_target_properties(aurora_gd PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_link_libraries(aurora_gd PUBLIC aurora::gx)
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
add_library(aurora_gx STATIC
|
|
||||||
lib/gfx/clear.cpp
|
|
||||||
lib/gfx/common.cpp
|
|
||||||
lib/gfx/depth_peek.cpp
|
|
||||||
lib/gfx/efb_ram_copy.cpp
|
|
||||||
lib/gfx/efb_ram_encoder.cpp
|
|
||||||
lib/gfx/pipeline_cache.cpp
|
|
||||||
lib/gfx/dds_io.cpp
|
|
||||||
lib/gfx/tex_copy_conv.cpp
|
|
||||||
lib/gfx/tex_palette_conv.cpp
|
|
||||||
lib/gfx/texture.cpp
|
|
||||||
lib/gfx/texture_convert.cpp
|
|
||||||
lib/gfx/texture_replacement.cpp
|
|
||||||
lib/gx/command_processor.cpp
|
|
||||||
lib/gx/fifo.cpp
|
|
||||||
lib/gx/frame_interpolation.cpp
|
|
||||||
lib/gx/gx.cpp
|
|
||||||
lib/gx/pipeline.cpp
|
|
||||||
lib/gx/shader.cpp
|
|
||||||
lib/gx/shader_info.cpp
|
|
||||||
lib/dolphin/gx/GXBump.cpp
|
|
||||||
lib/dolphin/gx/GXCull.cpp
|
|
||||||
lib/dolphin/gx/GXCpu2Efb.cpp
|
|
||||||
lib/dolphin/gx/GXDispList.cpp
|
|
||||||
lib/dolphin/gx/GXDraw.cpp
|
|
||||||
lib/dolphin/gx/GXExtra.cpp
|
|
||||||
lib/dolphin/gx/GXFifo.cpp
|
|
||||||
lib/dolphin/gx/GXFrameBuffer.cpp
|
|
||||||
lib/dolphin/gx/GXGeometry.cpp
|
|
||||||
lib/dolphin/gx/GXGet.cpp
|
|
||||||
lib/dolphin/gx/GXLighting.cpp
|
|
||||||
lib/dolphin/gx/GXManage.cpp
|
|
||||||
lib/dolphin/gx/GXPixel.cpp
|
|
||||||
lib/dolphin/gx/GXTev.cpp
|
|
||||||
lib/dolphin/gx/GXTexture.cpp
|
|
||||||
lib/dolphin/gx/GXTransform.cpp
|
|
||||||
lib/dolphin/gx/GXVert.cpp
|
|
||||||
lib/dolphin/gx/GXAurora.cpp
|
|
||||||
lib/gfx/png_io.cpp
|
|
||||||
lib/gfx/png_io.hpp
|
|
||||||
)
|
|
||||||
add_library(aurora::gx ALIAS aurora_gx)
|
|
||||||
set_target_properties(aurora_gx PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_link_libraries(aurora_gx PUBLIC aurora::core xxhash)
|
|
||||||
target_link_libraries(aurora_gx PRIVATE absl::btree absl::flat_hash_map dawn::webgpu_dawn sqlite3 TracyClient PNG::PNG)
|
|
||||||
target_compile_definitions(aurora_gx PRIVATE WEBGPU_DAWN)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
add_library(aurora_main STATIC lib/main.cpp)
|
|
||||||
add_library(aurora::main ALIAS aurora_main)
|
|
||||||
set_target_properties(aurora_main PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_include_directories(aurora_main PUBLIC include)
|
|
||||||
target_link_libraries(aurora_main PUBLIC ${AURORA_SDL3_TARGET})
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
add_library(aurora_ms STATIC lib/dolphin/ms/mouse.cpp)
|
|
||||||
add_library(aurora::ms ALIAS aurora_ms)
|
|
||||||
set_target_properties(aurora_ms PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_include_directories(aurora_ms PUBLIC include)
|
|
||||||
target_link_libraries(aurora_ms PUBLIC aurora::core)
|
|
||||||
target_link_libraries(aurora_ms PRIVATE absl::flat_hash_map)
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
add_library(aurora_mtx STATIC
|
|
||||||
lib/dolphin/mtx/mtx.c
|
|
||||||
lib/dolphin/mtx/mtxstack.c
|
|
||||||
lib/dolphin/mtx/mtxvec.c
|
|
||||||
lib/dolphin/mtx/mtx44.c
|
|
||||||
lib/dolphin/mtx/vec.c
|
|
||||||
lib/dolphin/mtx/quat.c
|
|
||||||
)
|
|
||||||
add_library(aurora::mtx ALIAS aurora_mtx)
|
|
||||||
set_target_properties(aurora_mtx PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_include_directories(aurora_mtx PUBLIC include)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
add_library(aurora_os STATIC lib/dolphin/os/OSInit.cpp
|
|
||||||
lib/dolphin/os/OSMemory.cpp
|
|
||||||
lib/dolphin/os/internal.hpp
|
|
||||||
lib/dolphin/os/OSBootInfo.cpp
|
|
||||||
lib/dolphin/os/OSTime.cpp
|
|
||||||
lib/dolphin/os/OSArena.cpp
|
|
||||||
lib/dolphin/os/OSAlloc.cpp
|
|
||||||
lib/dolphin/os/OSAddress.cpp
|
|
||||||
lib/dolphin/AR.cpp)
|
|
||||||
add_library(aurora::os ALIAS aurora_os)
|
|
||||||
set_target_properties(aurora_os PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_include_directories(aurora_os PUBLIC include)
|
|
||||||
target_link_libraries(aurora_os PRIVATE aurora::core)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
add_library(aurora_pad STATIC lib/dolphin/pad/pad.cpp)
|
|
||||||
add_library(aurora::pad ALIAS aurora_pad)
|
|
||||||
set_target_properties(aurora_pad PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_include_directories(aurora_pad PUBLIC include)
|
|
||||||
target_link_libraries(aurora_pad PUBLIC aurora::core aurora::si)
|
|
||||||
target_link_libraries(aurora_pad PRIVATE absl::flat_hash_map)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
add_library(aurora_si STATIC lib/dolphin/si/si.cpp)
|
|
||||||
add_library(aurora::si ALIAS aurora_si)
|
|
||||||
set_target_properties(aurora_si PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_include_directories(aurora_si PUBLIC include)
|
|
||||||
target_link_libraries(aurora_si PUBLIC aurora::core)
|
|
||||||
target_link_libraries(aurora_si PRIVATE absl::flat_hash_map)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
add_library(aurora_vi STATIC
|
|
||||||
lib/dolphin/vi/vi.cpp
|
|
||||||
)
|
|
||||||
add_library(aurora::vi ALIAS aurora_vi)
|
|
||||||
set_target_properties(aurora_vi PROPERTIES FOLDER "aurora")
|
|
||||||
|
|
||||||
target_link_libraries(aurora_vi PUBLIC aurora::core)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/* libusb build configuration for the Windows backend (MinGW/Clang). */
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#define PLATFORM_WINDOWS 1
|
|
||||||
#define ENABLE_LOGGING 1
|
|
||||||
#define DEFAULT_VISIBILITY
|
|
||||||
#define HAVE_STRUCT_TIMESPEC 1
|
|
||||||
#define PRINTF_FORMAT(a, b) __attribute__((__format__(__printf__, a, b)))
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
# Building
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- CMake 3.25 or later
|
|
||||||
- A C++20 compatible compiler (Clang, GCC, or MSVC)
|
|
||||||
- Git (for cloning)
|
|
||||||
|
|
||||||
## Building the Examples
|
|
||||||
|
|
||||||
To build Aurora's included examples:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/encounter/aurora.git
|
|
||||||
cd aurora
|
|
||||||
mkdir build && cd build
|
|
||||||
cmake ..
|
|
||||||
cmake --build . --target simple
|
|
||||||
```
|
|
||||||
|
|
||||||
The `simple` example demonstrates a minimal Aurora application with a blue screen. The built executable will be in `build/examples/simple`.
|
|
||||||
|
|
||||||
## Using Aurora in Your Project
|
|
||||||
|
|
||||||
Aurora is designed to be integrated as a library in GameCube/Wii decompilation projects.
|
|
||||||
|
|
||||||
**CMakeLists.txt example:**
|
|
||||||
|
|
||||||
```cmake
|
|
||||||
cmake_minimum_required(VERSION 3.25)
|
|
||||||
project(your_game)
|
|
||||||
|
|
||||||
# Add Aurora as a subdirectory
|
|
||||||
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
|
|
||||||
|
|
||||||
# Create your executable
|
|
||||||
add_executable(your_game src/main.c)
|
|
||||||
|
|
||||||
# Link against Aurora components
|
|
||||||
target_link_libraries(your_game PRIVATE
|
|
||||||
aurora::core
|
|
||||||
aurora::gx
|
|
||||||
aurora::main
|
|
||||||
aurora::vi
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
See [examples/simple.c](../examples/simple.c) for a minimal application template.
|
|
||||||
|
|
||||||
## CMake Options
|
|
||||||
|
|
||||||
- `AURORA_ENABLE_GX` (default: ON) - Enable GX implementation and WebGPU renderer
|
|
||||||
- `AURORA_ENABLE_DVD` (default: OFF) - Enable DVD implementation backed by nod
|
|
||||||
- `AURORA_ENABLE_CARD` (default: ON) - Enable CARD implementation based on kabufuda
|
|
||||||
- `AURORA_CACHE_USE_ZSTD` (default: ON) - Compress WebGPU cache entries with zstd
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
if (AURORA_ENABLE_GX)
|
|
||||||
add_executable(simple simple.c)
|
|
||||||
target_link_libraries(simple PRIVATE aurora::core aurora::gx aurora::main aurora::vi)
|
|
||||||
endif ()
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#include <aurora/aurora.h>
|
|
||||||
#include <aurora/event.h>
|
|
||||||
#include <aurora/main.h>
|
|
||||||
#include <dolphin/gx.h>
|
|
||||||
#include <dolphin/os.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
static void log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len) {
|
|
||||||
const char* levelStr;
|
|
||||||
FILE* out = stdout;
|
|
||||||
switch (level) {
|
|
||||||
case LOG_DEBUG:
|
|
||||||
levelStr = "DEBUG";
|
|
||||||
break;
|
|
||||||
case LOG_INFO:
|
|
||||||
levelStr = "INFO";
|
|
||||||
break;
|
|
||||||
case LOG_WARNING:
|
|
||||||
levelStr = "WARNING";
|
|
||||||
break;
|
|
||||||
case LOG_ERROR:
|
|
||||||
levelStr = "ERROR";
|
|
||||||
out = stderr;
|
|
||||||
break;
|
|
||||||
case LOG_FATAL:
|
|
||||||
levelStr = "FATAL";
|
|
||||||
out = stderr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fprintf(out, "[%s: %s;%s]\n", levelStr, module, message);
|
|
||||||
if (level == LOG_FATAL) {
|
|
||||||
fflush(out);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void draw() {
|
|
||||||
GXSetCopyClear(
|
|
||||||
(GXColor){
|
|
||||||
.r = 0,
|
|
||||||
.g = 0,
|
|
||||||
.b = 100,
|
|
||||||
.a = 255,
|
|
||||||
},
|
|
||||||
GX_MAX_Z24);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
const AuroraConfig config = {
|
|
||||||
.appName = "Demo",
|
|
||||||
.logCallback = &log_callback,
|
|
||||||
};
|
|
||||||
AuroraInfo initInfo = aurora_initialize(argc, argv, &config);
|
|
||||||
|
|
||||||
bool exiting = false;
|
|
||||||
bool paused = false;
|
|
||||||
while (!exiting) {
|
|
||||||
const AuroraEvent* event = aurora_update();
|
|
||||||
while (event != NULL && event->type != AURORA_NONE) {
|
|
||||||
switch (event->type) {
|
|
||||||
case AURORA_EXIT:
|
|
||||||
exiting = true;
|
|
||||||
break;
|
|
||||||
case AURORA_PAUSED:
|
|
||||||
paused = true;
|
|
||||||
break;
|
|
||||||
case AURORA_UNPAUSED:
|
|
||||||
paused = false;
|
|
||||||
break;
|
|
||||||
case AURORA_WINDOW_RESIZED:
|
|
||||||
initInfo.windowSize = event->windowSize;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++event;
|
|
||||||
}
|
|
||||||
if (exiting || paused || !aurora_begin_frame()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
draw();
|
|
||||||
aurora_end_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
aurora_shutdown();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Vendored
-310
@@ -1,310 +0,0 @@
|
|||||||
include(FetchContent)
|
|
||||||
|
|
||||||
# Don't build tests for third party dependencies
|
|
||||||
set(BUILD_TESTING OFF CACHE INTERNAL "Build tests")
|
|
||||||
set(SKIP_INSTALL_ALL ON)
|
|
||||||
if (NOT DEFINED BUILD_SHARED_LIBS)
|
|
||||||
set(_USE_SHARED ON)
|
|
||||||
else ()
|
|
||||||
set(_USE_SHARED OFF)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
function(aurora_find_package_global)
|
|
||||||
set(_PREV_FIND_PACKAGE_TARGETS_GLOBAL ${CMAKE_FIND_PACKAGE_TARGETS_GLOBAL})
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
|
||||||
find_package(${ARGV})
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ${_PREV_FIND_PACKAGE_TARGETS_GLOBAL})
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
if (AURORA_ENABLE_GX)
|
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraDawnProvider.cmake)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
if (NOT TARGET absl::flat_hash_map OR NOT TARGET absl::btree)
|
|
||||||
aurora_find_package_global(absl CONFIG QUIET GLOBAL)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (TARGET absl::flat_hash_map AND TARGET absl::btree)
|
|
||||||
message(STATUS "aurora: Using system/existing abseil")
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Fetching abseil-cpp (standalone)")
|
|
||||||
if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY OR CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "DLL$")
|
|
||||||
set(ABSL_MSVC_STATIC_RUNTIME OFF CACHE INTERNAL "Link static runtime libraries")
|
|
||||||
else()
|
|
||||||
set(ABSL_MSVC_STATIC_RUNTIME ON CACHE INTERNAL "Link static runtime libraries")
|
|
||||||
endif()
|
|
||||||
set(ABSL_PROPAGATE_CXX_STD ON)
|
|
||||||
FetchContent_Declare(abseil-cpp
|
|
||||||
URL https://github.com/abseil/abseil-cpp/archive/refs/tags/20240722.0.tar.gz
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(abseil-cpp)
|
|
||||||
if (NOT TARGET absl::flat_hash_map OR NOT TARGET absl::btree)
|
|
||||||
message(FATAL_ERROR "Failed to make abseil-cpp available")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraSDL3Provider.cmake)
|
|
||||||
|
|
||||||
if (NOT TARGET xxhash)
|
|
||||||
message(STATUS "aurora: Fetching xxhash")
|
|
||||||
FetchContent_Declare(xxhash
|
|
||||||
URL https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz
|
|
||||||
URL_HASH SHA256=aae608dfe8213dfd05d909a57718ef82f30722c392344583d3f39050c7f29a80
|
|
||||||
SOURCE_SUBDIR cmake_unofficial
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
set(XXHASH_BUILD_XXHSUM OFF CACHE INTERNAL "Build the xxhsum binary")
|
|
||||||
# xxhash uses CMAKE_DEPENDENT_OPTION(BUILD_SHARED_LIBS ...) which writes
|
|
||||||
# a sticky CACHE INTERNAL entry on reconfigure. Undo it afterward.
|
|
||||||
set(_aurora_xxhash_saved_bsl "${BUILD_SHARED_LIBS}")
|
|
||||||
FetchContent_MakeAvailable(xxhash)
|
|
||||||
unset(BUILD_SHARED_LIBS CACHE)
|
|
||||||
set(BUILD_SHARED_LIBS "${_aurora_xxhash_saved_bsl}")
|
|
||||||
unset(_aurora_xxhash_saved_bsl)
|
|
||||||
if (NOT TARGET xxhash)
|
|
||||||
message(FATAL_ERROR "Failed to make xxhash available")
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing xxhash")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (NOT TARGET fmt)
|
|
||||||
message(STATUS "aurora: Fetching fmt")
|
|
||||||
FetchContent_Declare(fmt
|
|
||||||
URL https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz
|
|
||||||
URL_HASH SHA256=ac366b7b4c2e9f0dde63a59b3feb5ee59b67974b14ee5dc9ea8ad78aa2c1ee1e
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(fmt)
|
|
||||||
if (NOT TARGET fmt)
|
|
||||||
message(FATAL_ERROR "Failed to make fmt available")
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing fmt")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# fmt 11.1 drops its libc++ transitive includes but still calls malloc/free without <cstdlib>.
|
|
||||||
# Force the missing header for fmt and its consumers until the pinned release is updated.
|
|
||||||
if (MINGW AND TARGET fmt)
|
|
||||||
target_compile_options(fmt PUBLIC
|
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-include>
|
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:cstdlib>)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (AURORA_ENABLE_GX)
|
|
||||||
if (NOT TARGET ZLIB::ZLIB)
|
|
||||||
aurora_find_package_global(ZLIB QUIET)
|
|
||||||
endif ()
|
|
||||||
if (NOT TARGET ZLIB::ZLIB)
|
|
||||||
message(STATUS "aurora: Fetching zlib")
|
|
||||||
set(_AURORA_ZLIB_SHARED ${_USE_SHARED})
|
|
||||||
# ClangCL injects Clang's C headers into the MSVC resource compiler path, and zlib's DLL version
|
|
||||||
# resource then hits syntax rc.exe cannot parse. Use the resource-free static target here.
|
|
||||||
if (MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
set(_AURORA_ZLIB_SHARED OFF)
|
|
||||||
endif ()
|
|
||||||
set(ZLIB_BUILD_SHARED ${_AURORA_ZLIB_SHARED} CACHE BOOL "Enable building zlib shared library" FORCE)
|
|
||||||
set(ZLIB_BUILD_TESTING OFF CACHE BOOL "Enable Zlib Examples as tests" FORCE)
|
|
||||||
set(ZLIB_INSTALL OFF CACHE BOOL "Enable installation of zlib" FORCE)
|
|
||||||
FetchContent_Declare(
|
|
||||||
ZLIB
|
|
||||||
URL https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz
|
|
||||||
URL_HASH SHA256=bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(ZLIB)
|
|
||||||
if (NOT TARGET ZLIB::ZLIB AND TARGET ZLIB::ZLIBSTATIC)
|
|
||||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
|
||||||
endif ()
|
|
||||||
if (NOT TARGET ZLIB::ZLIB)
|
|
||||||
message(FATAL_ERROR "Failed to make zlib available")
|
|
||||||
endif ()
|
|
||||||
# libpng calls find_package(ZLIB) even when zlib was already provided by FetchContent.
|
|
||||||
file(WRITE "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/ZLIBConfig.cmake"
|
|
||||||
"set(ZLIB_FOUND TRUE)\n"
|
|
||||||
"set(ZLIB_INCLUDE_DIR \"${zlib_SOURCE_DIR}\")\n"
|
|
||||||
"set(ZLIB_INCLUDE_DIRS \"${zlib_BINARY_DIR};${zlib_SOURCE_DIR}\")\n"
|
|
||||||
)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (NOT TARGET PNG::PNG)
|
|
||||||
if (TARGET png_shared AND _USE_SHARED)
|
|
||||||
add_library(PNG::PNG ALIAS png_shared)
|
|
||||||
elseif (TARGET png_static)
|
|
||||||
add_library(PNG::PNG ALIAS png_static)
|
|
||||||
else ()
|
|
||||||
aurora_find_package_global(PNG QUIET)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
if (NOT TARGET PNG::PNG)
|
|
||||||
message(STATUS "aurora: Fetching libpng")
|
|
||||||
set(PNG_SHARED ${_USE_SHARED} CACHE BOOL "Build libpng as a shared library" FORCE)
|
|
||||||
set(PNG_TESTS OFF CACHE BOOL "Build the libpng tests" FORCE)
|
|
||||||
set(PNG_TOOLS OFF CACHE BOOL "Build the libpng tools" FORCE)
|
|
||||||
FetchContent_Declare(
|
|
||||||
PNG
|
|
||||||
URL https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.58.tar.gz
|
|
||||||
URL_HASH SHA256=A9D4DF463D36A6E5F9C29BD6F4967312D17E996C1854F3511F833924EB1993CF
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(PNG)
|
|
||||||
if (TARGET png_shared AND _USE_SHARED)
|
|
||||||
add_library(PNG::PNG ALIAS png_shared)
|
|
||||||
elseif (TARGET png_static)
|
|
||||||
add_library(PNG::PNG ALIAS png_static)
|
|
||||||
else ()
|
|
||||||
message(FATAL_ERROR "Failed to make libpng available")
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing libpng")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
aurora_find_package_global(Freetype QUIET)
|
|
||||||
if (NOT TARGET Freetype::Freetype)
|
|
||||||
message(STATUS "aurora: Fetching Freetype")
|
|
||||||
set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE)
|
|
||||||
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE)
|
|
||||||
set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE)
|
|
||||||
FetchContent_Declare(Freetype
|
|
||||||
URL https://files.twilitrealm.dev/freetype-2.14.3.tar.gz
|
|
||||||
URL_HASH SHA256=e61b31ab26358b946e767ed7eb7f4bb2e507da1cfefeb7a8861ace7fd5c899a1
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(Freetype)
|
|
||||||
add_library(Freetype::Freetype ALIAS freetype)
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing Freetype")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (NOT TARGET imgui)
|
|
||||||
message(STATUS "aurora: Fetching imgui")
|
|
||||||
FetchContent_Declare(imgui
|
|
||||||
URL https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz
|
|
||||||
URL_HASH SHA256=466fdef9b18de15f0bb6e288e3d00ffa3d82200ec458ce5e4f724a161d9528a5
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(imgui)
|
|
||||||
|
|
||||||
add_library(imgui STATIC
|
|
||||||
${imgui_SOURCE_DIR}/imgui.cpp
|
|
||||||
${imgui_SOURCE_DIR}/imgui_demo.cpp
|
|
||||||
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
|
||||||
${imgui_SOURCE_DIR}/imgui_tables.cpp
|
|
||||||
${imgui_SOURCE_DIR}/imgui_widgets.cpp
|
|
||||||
${imgui_SOURCE_DIR}/misc/cpp/imgui_stdlib.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR})
|
|
||||||
|
|
||||||
add_library(imgui_backends STATIC
|
|
||||||
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl3.cpp
|
|
||||||
${imgui_SOURCE_DIR}/backends/imgui_impl_sdlrenderer3.cpp
|
|
||||||
${imgui_SOURCE_DIR}/backends/imgui_impl_wgpu.cpp
|
|
||||||
)
|
|
||||||
target_compile_definitions(imgui_backends PRIVATE IMGUI_IMPL_WEBGPU_BACKEND_DAWN)
|
|
||||||
target_link_libraries(imgui_backends PRIVATE imgui ${AURORA_SDL3_TARGET} dawn::webgpu_dawn)
|
|
||||||
target_link_libraries(imgui PUBLIC imgui_backends)
|
|
||||||
|
|
||||||
if (TARGET Freetype::Freetype)
|
|
||||||
target_sources(imgui PRIVATE ${imgui_SOURCE_DIR}/misc/freetype/imgui_freetype.cpp)
|
|
||||||
target_compile_definitions(imgui PUBLIC IMGUI_ENABLE_FREETYPE)
|
|
||||||
target_link_libraries(imgui PRIVATE Freetype::Freetype)
|
|
||||||
endif ()
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing imgui")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (TARGET imgui)
|
|
||||||
target_include_directories(imgui PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
|
||||||
target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="aurora/imgui_config.h")
|
|
||||||
target_sources(imgui PRIVATE ${PROJECT_SOURCE_DIR}/lib/imgui_config.cpp)
|
|
||||||
target_link_libraries(imgui PRIVATE ${AURORA_SDL3_TARGET})
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (NOT TARGET sqlite3)
|
|
||||||
message(STATUS "aurora: Fetching sqlite3")
|
|
||||||
FetchContent_Declare(sqlite3
|
|
||||||
URL https://sqlite.org/2026/sqlite-amalgamation-3510300.zip
|
|
||||||
URL_HASH SHA256=acb1e6f5d832484bf6d32b681e858c38add8b2acdfd42ac5df24b8afb46552b4
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(sqlite3)
|
|
||||||
|
|
||||||
add_library(sqlite3 STATIC ${sqlite3_SOURCE_DIR}/sqlite3.c)
|
|
||||||
target_include_directories(sqlite3 PUBLIC ${sqlite3_SOURCE_DIR})
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing sqlite3")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (AURORA_CACHE_USE_ZSTD)
|
|
||||||
if (NOT TARGET libzstd_static)
|
|
||||||
message(STATUS "aurora: Fetching zstd")
|
|
||||||
set(ZSTD_BUILD_STATIC ON)
|
|
||||||
set(ZSTD_BUILD_SHARED OFF)
|
|
||||||
set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "BUILD PROGRAMS" FORCE)
|
|
||||||
set(ZSTD_BUILD_TESTS OFF CACHE BOOL "BUILD TESTS" FORCE)
|
|
||||||
|
|
||||||
if (WIN32 AND CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
set(C_FLAG_WALL TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_WALL TRUE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_WEXTRA TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_WEXTRA TRUE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_WUNDEF TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_WUNDEF TRUE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_WSHADOW TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_WSHADOW TRUE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_WCAST_ALIGN TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_WCAST_ALIGN TRUE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_WCAST_QUAL TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_WCAST_QUAL TRUE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_WSTRICT_PROTOTYPES TRUE CACHE INTERNAL "")
|
|
||||||
set(LD_FLAG_WL_Z_NOEXECSTACK FALSE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_QUNUSED_ARGUMENTS TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_QUNUSED_ARGUMENTS TRUE CACHE INTERNAL "")
|
|
||||||
set(C_FLAG_WA_NOEXECSTACK TRUE CACHE INTERNAL "")
|
|
||||||
set(CXX_FLAG_WA_NOEXECSTACK TRUE CACHE INTERNAL "")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
FetchContent_Declare(zstd
|
|
||||||
URL https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz
|
|
||||||
URL_HASH SHA256=eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
SOURCE_SUBDIR build/cmake
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(zstd)
|
|
||||||
else ()
|
|
||||||
message(STATUS "aurora: Using existing zstd")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (NOT TARGET TracyClient)
|
|
||||||
message(STATUS "aurora: Fetching Tracy")
|
|
||||||
|
|
||||||
# Default to off, users can enable the cache variable if they want to use it.
|
|
||||||
set(TRACY_ENABLE OFF CACHE BOOL "Enable Tracy profiling")
|
|
||||||
|
|
||||||
FetchContent_Declare(
|
|
||||||
tracy
|
|
||||||
URL https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz
|
|
||||||
URL_HASH SHA256=24d342b5127d7f659dc3cf94f24347b348cc736f25b17a691fd7f69541937658
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(tracy)
|
|
||||||
|
|
||||||
if (NOT TARGET TracyClient)
|
|
||||||
message(FATAL_ERROR "Failed to make TracyClient available")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
#ifndef AURORA_AURORA_H
|
|
||||||
#define AURORA_AURORA_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#else
|
|
||||||
#include "stdbool.h"
|
|
||||||
#include "stddef.h"
|
|
||||||
#include "stdint.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
BACKEND_AUTO,
|
|
||||||
BACKEND_D3D11,
|
|
||||||
BACKEND_D3D12,
|
|
||||||
BACKEND_METAL,
|
|
||||||
BACKEND_VULKAN,
|
|
||||||
BACKEND_OPENGL,
|
|
||||||
BACKEND_OPENGLES,
|
|
||||||
BACKEND_WEBGPU,
|
|
||||||
BACKEND_NULL,
|
|
||||||
} AuroraBackend;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
LOG_DEBUG,
|
|
||||||
LOG_INFO,
|
|
||||||
LOG_WARNING,
|
|
||||||
LOG_ERROR,
|
|
||||||
LOG_FATAL,
|
|
||||||
} AuroraLogLevel;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
AURORA_DISPLAY_MODE_WINDOWED,
|
|
||||||
AURORA_DISPLAY_MODE_BORDERLESS,
|
|
||||||
AURORA_DISPLAY_MODE_EXCLUSIVE,
|
|
||||||
} AuroraDisplayMode;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t x;
|
|
||||||
int32_t y;
|
|
||||||
} AuroraWindowPos;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint32_t width;
|
|
||||||
uint32_t height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Width of the main GX framebuffer.
|
|
||||||
*/
|
|
||||||
uint32_t fb_width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Height of the main GX framebuffer.
|
|
||||||
*/
|
|
||||||
uint32_t fb_height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of the framebuffer used to present to the operating system.
|
|
||||||
* May differ from fb_width if Aurora is instructed to force an aspect ratio or resolution configuration.
|
|
||||||
*/
|
|
||||||
uint32_t native_fb_width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of the framebuffer used to present to the operating system.
|
|
||||||
* May differ from fb_height if Aurora is instructed to force an aspect ratio or resolution configuration.
|
|
||||||
*/
|
|
||||||
uint32_t native_fb_height;
|
|
||||||
float scale;
|
|
||||||
} AuroraWindowSize;
|
|
||||||
|
|
||||||
typedef struct SDL_Window SDL_Window;
|
|
||||||
typedef struct AuroraEvent AuroraEvent;
|
|
||||||
|
|
||||||
typedef void (*AuroraLogCallback)(AuroraLogLevel level, const char* module, const char* message, unsigned int len);
|
|
||||||
typedef void (*AuroraImGuiInitCallback)(const AuroraWindowSize* size);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const char* appName;
|
|
||||||
const char* userPath;
|
|
||||||
const char* cachePath;
|
|
||||||
// Read-only application resources. Defaults to SDL_GetBasePath(), which is
|
|
||||||
// where release builds place initial_pipeline_cache.db.
|
|
||||||
const char* resourcesPath;
|
|
||||||
AuroraBackend desiredBackend;
|
|
||||||
uint32_t msaa;
|
|
||||||
uint16_t maxTextureAnisotropy;
|
|
||||||
// No vsync knob exists: the swapchain is always configured for a
|
|
||||||
// non-blocking present mode (Immediate, else Mailbox). See best_present_mode.
|
|
||||||
bool startFullscreen;
|
|
||||||
bool allowJoystickBackgroundEvents;
|
|
||||||
bool pauseOnFocusLost;
|
|
||||||
bool allowTextureReplacements;
|
|
||||||
bool allowTextureDumps;
|
|
||||||
bool disableCopyFilter;
|
|
||||||
// When false, Aurora centers the first window. When true, windowPosX/Y are restored verbatim,
|
|
||||||
// including negative coordinates on monitors left of or above the primary display.
|
|
||||||
bool hasWindowPosition;
|
|
||||||
int32_t windowPosX;
|
|
||||||
int32_t windowPosY;
|
|
||||||
uint32_t windowWidth;
|
|
||||||
uint32_t windowHeight;
|
|
||||||
void* iconRGBA8;
|
|
||||||
uint32_t iconWidth;
|
|
||||||
uint32_t iconHeight;
|
|
||||||
AuroraLogCallback logCallback;
|
|
||||||
AuroraLogLevel logLevel;
|
|
||||||
AuroraImGuiInitCallback imGuiInitCallback;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The size of the GameCube's main memory, or MEM1 on the Wii.
|
|
||||||
* Note that it will not be allocated at the exact 0x80000000 address, as that cannot be guaranteed.
|
|
||||||
* This can be set to 0 to disable allocating this region.
|
|
||||||
*/
|
|
||||||
uint32_t mem1Size;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The size of the GameCube's ARAM, or MEM2 on the Wii.
|
|
||||||
* This can be set to 0 to disable allocating this region.
|
|
||||||
*/
|
|
||||||
uint32_t mem2Size;
|
|
||||||
|
|
||||||
// Optional directory for the portable GX pipeline database. When null, the
|
|
||||||
// database is stored in cachePath with Dawn's machine-specific cache.
|
|
||||||
const char* pipelineCachePath;
|
|
||||||
} AuroraConfig;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
AuroraBackend backend;
|
|
||||||
const char* userPath;
|
|
||||||
const char* cachePath;
|
|
||||||
SDL_Window* window;
|
|
||||||
AuroraWindowSize windowSize;
|
|
||||||
} AuroraInfo;
|
|
||||||
|
|
||||||
AuroraInfo aurora_initialize(int argc, char* argv[], const AuroraConfig* config);
|
|
||||||
void aurora_shutdown();
|
|
||||||
const AuroraEvent* aurora_update();
|
|
||||||
bool aurora_begin_frame();
|
|
||||||
void aurora_end_frame();
|
|
||||||
typedef void (*AuroraFrameWorkerWaitCallback)();
|
|
||||||
// Called from the producer thread at bounded intervals while Aurora waits for
|
|
||||||
// the asynchronous frame worker. The callback must not enter Aurora.
|
|
||||||
void aurora_set_frame_worker_wait_callback(AuroraFrameWorkerWaitCallback callback);
|
|
||||||
void aurora_wait_for_frame_worker();
|
|
||||||
bool aurora_wait_for_frame_worker_for(uint32_t timeoutMicros);
|
|
||||||
// Absolute schedule for the next sealed frame, on steady_clock: baseNanos anchors the group and
|
|
||||||
// intervalNanos is the period, so slot k of N+1 fires at base + k*interval/(N+1). Zeros clear it.
|
|
||||||
void aurora_set_present_schedule(uint64_t baseNanos, uint64_t intervalNanos);
|
|
||||||
// Reports whether the frame about to be sealed met its display boundary. Interpolation sizes its
|
|
||||||
// slot group from this, backing off after misses. Only paced presents may report.
|
|
||||||
void aurora_report_producer_paced(bool paced);
|
|
||||||
void aurora_request_frame_capture(uint32_t frame, const char* outputPath);
|
|
||||||
bool aurora_flush_efb_copies_to_ram();
|
|
||||||
bool aurora_flush_efb_copy_to_ram(void* dest);
|
|
||||||
|
|
||||||
void aurora_set_log_level(AuroraLogLevel level);
|
|
||||||
void aurora_set_pause_on_focus_lost(bool value);
|
|
||||||
void aurora_set_background_input(bool value);
|
|
||||||
void aurora_set_display_mode(AuroraDisplayMode mode);
|
|
||||||
AuroraDisplayMode aurora_get_display_mode();
|
|
||||||
|
|
||||||
AuroraBackend aurora_get_backend();
|
|
||||||
const AuroraBackend* aurora_get_available_backends(size_t* count);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#ifndef AURORA_DVD_H
|
|
||||||
#define AURORA_DVD_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <dolphin/types.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a GC/Wii disc image for use by the DVD API.
|
|
||||||
* Must be called before DVDInit().
|
|
||||||
* Returns true on success, false on failure.
|
|
||||||
*/
|
|
||||||
bool aurora_dvd_open(const char* disc_path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the disc image and free all resources.
|
|
||||||
*/
|
|
||||||
void aurora_dvd_close(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#ifndef AURORA_EVENT_H
|
|
||||||
#define AURORA_EVENT_H
|
|
||||||
|
|
||||||
#include "aurora.h"
|
|
||||||
|
|
||||||
#include <SDL3/SDL_events.h>
|
|
||||||
#include <SDL3/SDL_joystick.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#else
|
|
||||||
#include "stdint.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
AURORA_NONE,
|
|
||||||
AURORA_EXIT,
|
|
||||||
AURORA_SDL_EVENT,
|
|
||||||
AURORA_WINDOW_MOVED,
|
|
||||||
AURORA_WINDOW_RESIZED,
|
|
||||||
AURORA_CONTROLLER_ADDED,
|
|
||||||
AURORA_CONTROLLER_REMOVED,
|
|
||||||
AURORA_PAUSED,
|
|
||||||
AURORA_UNPAUSED,
|
|
||||||
AURORA_DISPLAY_SCALE_CHANGED,
|
|
||||||
} AuroraEventType;
|
|
||||||
|
|
||||||
struct AuroraEvent {
|
|
||||||
AuroraEventType type;
|
|
||||||
union {
|
|
||||||
SDL_Event sdl;
|
|
||||||
AuroraWindowPos windowPos;
|
|
||||||
AuroraWindowSize windowSize;
|
|
||||||
SDL_JoystickID controller;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
#ifndef AURORA_GFX_H
|
|
||||||
#define AURORA_GFX_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#else
|
|
||||||
#include "stddef.h"
|
|
||||||
#include "stdint.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define AURORA_GFX_DEBUG_GROUPS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void aurora_push_debug_group(const char* label);
|
|
||||||
void aurora_pop_debug_group();
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint32_t queuedPipelines;
|
|
||||||
uint32_t createdPipelines;
|
|
||||||
uint32_t drawCallCount;
|
|
||||||
uint32_t mergedDrawCallCount;
|
|
||||||
uint32_t lastVertSize;
|
|
||||||
uint32_t lastUniformSize;
|
|
||||||
uint32_t lastIndexSize;
|
|
||||||
uint32_t lastStorageSize;
|
|
||||||
uint32_t lastTextureUploadSize;
|
|
||||||
uint32_t presentedFrameCount;
|
|
||||||
uint32_t interpolatedFrameCount;
|
|
||||||
} AuroraStats;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint64_t totalPresentCount;
|
|
||||||
uint32_t sampleCount;
|
|
||||||
double framesPerSecond;
|
|
||||||
double averageFrameTimeMs;
|
|
||||||
double p95FrameTimeMs;
|
|
||||||
double jitterMs;
|
|
||||||
// framesPerSecond with duplicated presentation slots scaled out, so this is the rate of frames
|
|
||||||
// that carried new motion. Equal to framesPerSecond when every slot replayed real interpolation.
|
|
||||||
double effectiveFramesPerSecond;
|
|
||||||
} AuroraPresentTiming;
|
|
||||||
|
|
||||||
const AuroraStats* aurora_get_stats();
|
|
||||||
void aurora_get_present_timing(AuroraPresentTiming* timing);
|
|
||||||
|
|
||||||
// Interpolation health: the per-frame fields describe the last sealed frame, the counters
|
|
||||||
// accumulate since it was configured. This answers "output FPS dropped but the game held 60".
|
|
||||||
typedef struct {
|
|
||||||
uint32_t targetFps; // configured target, 0 when interpolation is off
|
|
||||||
uint32_t targetSamples; // slots the pacing controller currently aims for
|
|
||||||
uint32_t activeSamples; // slots latched for the latest sealed frame
|
|
||||||
uint32_t candidates; // perspective draws in the latest sealed frame
|
|
||||||
uint32_t matchable; // candidates whose identity also existed last frame
|
|
||||||
uint32_t matches; // draws matched to the previous frame
|
|
||||||
uint32_t eligible; // latest frame inserted interpolated slots
|
|
||||||
uint32_t replaySafe; // latest frame could replay its command stream
|
|
||||||
uint64_t framesSealed;
|
|
||||||
uint64_t framesLowMatch;
|
|
||||||
uint64_t framesReplayUnsafe;
|
|
||||||
uint64_t slotReductions;
|
|
||||||
uint64_t lateSealDrops;
|
|
||||||
} AuroraFrameInterpolationDiagnostics;
|
|
||||||
|
|
||||||
void aurora_get_frame_interpolation_diagnostics(AuroraFrameInterpolationDiagnostics* diagnostics);
|
|
||||||
|
|
||||||
// Generates transform-interpolated perspective frames between consecutive 60 Hz logical frames.
|
|
||||||
// Supported targets are 0 (off), 120, 180 and 240. Guest simulation and VI timing are unchanged.
|
|
||||||
void aurora_set_frame_interpolation_fps(uint32_t targetFps);
|
|
||||||
uint32_t aurora_get_frame_interpolation_fps();
|
|
||||||
|
|
||||||
// Newly encountered GX pipelines compile on the bounded worker queue. Draws whose pipeline is not
|
|
||||||
// ready are skipped rather than stalling submission, and pick it up once compilation finishes.
|
|
||||||
void aurora_set_skip_unready_pipelines(bool enabled);
|
|
||||||
bool aurora_get_skip_unready_pipelines();
|
|
||||||
uint32_t aurora_get_queued_pipeline_count();
|
|
||||||
|
|
||||||
// Controls whether display copies bypass the Wii's vertical copy filter.
|
|
||||||
void aurora_set_disable_copy_filter(bool disabled);
|
|
||||||
bool aurora_get_disable_copy_filter();
|
|
||||||
|
|
||||||
// Guest-RAM write tracking. `generation` changes whenever guest RAM covering a host range was
|
|
||||||
// written (or returns AURORA_GUEST_WRITE_UNTRACKED); `notify` reports writes aurora made itself.
|
|
||||||
#define AURORA_GUEST_WRITE_UNTRACKED UINT64_MAX
|
|
||||||
typedef uint64_t (*AuroraGuestWriteGenerationCallback)(const void* hostPtr, size_t size);
|
|
||||||
typedef void (*AuroraGuestWriteNotifyCallback)(const void* hostPtr, size_t size);
|
|
||||||
void aurora_set_guest_write_hooks(AuroraGuestWriteGenerationCallback generation,
|
|
||||||
AuroraGuestWriteNotifyCallback notify);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#ifndef AURORA_IMGUI_H
|
|
||||||
#define AURORA_IMGUI_H
|
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#else
|
|
||||||
#include "stdint.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ImTextureID aurora_imgui_add_texture(uint32_t width, uint32_t height, const void* rgba8);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS 1
|
|
||||||
|
|
||||||
typedef struct SDL_IOStream SDL_IOStream;
|
|
||||||
typedef SDL_IOStream* ImFileHandle;
|
|
||||||
typedef unsigned long long ImU64;
|
|
||||||
|
|
||||||
ImFileHandle ImFileOpen(const char* filename, const char* mode);
|
|
||||||
bool ImFileClose(ImFileHandle file);
|
|
||||||
ImU64 ImFileGetSize(ImFileHandle file);
|
|
||||||
ImU64 ImFileRead(void* data, ImU64 size, ImU64 count, ImFileHandle file);
|
|
||||||
ImU64 ImFileWrite(const void* data, ImU64 size, ImU64 count, ImFileHandle file);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#ifndef AURORA_MAIN_H
|
|
||||||
#define AURORA_MAIN_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int aurora_main(int argc, char* argv[]);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define main aurora_main
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#ifndef AURORA_VEC2_EXTRA
|
|
||||||
#define AURORA_VEC2_EXTRA
|
|
||||||
#endif
|
|
||||||
#ifndef AURORA_VEC3_EXTRA
|
|
||||||
#define AURORA_VEC3_EXTRA
|
|
||||||
#endif
|
|
||||||
#ifndef AURORA_VEC4_EXTRA
|
|
||||||
#define AURORA_VEC4_EXTRA
|
|
||||||
#endif
|
|
||||||
#ifndef AURORA_MAT4X4_EXTRA
|
|
||||||
#define AURORA_MAT4X4_EXTRA
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef __has_attribute
|
|
||||||
#define __has_attribute(x) 0
|
|
||||||
#endif
|
|
||||||
#ifndef __has_builtin
|
|
||||||
#define __has_builtin(x) 0
|
|
||||||
#endif
|
|
||||||
#if __has_attribute(vector_size) && !defined(__clang__)
|
|
||||||
#define USE_GCC_VECTOR_EXTENSIONS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace aurora {
|
|
||||||
template <typename T>
|
|
||||||
struct Vec2 {
|
|
||||||
T x{};
|
|
||||||
T y{};
|
|
||||||
|
|
||||||
constexpr Vec2() = default;
|
|
||||||
constexpr Vec2(T x, T y) : x(x), y(y) {}
|
|
||||||
AURORA_VEC2_EXTRA
|
|
||||||
|
|
||||||
bool operator==(const Vec2& rhs) const { return x == rhs.x && y == rhs.y; }
|
|
||||||
bool operator!=(const Vec2& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
template <typename T>
|
|
||||||
struct Vec3 {
|
|
||||||
T x{};
|
|
||||||
T y{};
|
|
||||||
T z{};
|
|
||||||
|
|
||||||
constexpr Vec3() = default;
|
|
||||||
constexpr Vec3(T x, T y, T z) : x(x), y(y), z(z) {}
|
|
||||||
AURORA_VEC3_EXTRA
|
|
||||||
|
|
||||||
bool operator==(const Vec3& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; }
|
|
||||||
bool operator!=(const Vec3& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
template <typename T>
|
|
||||||
struct Vec4 {
|
|
||||||
#ifdef USE_GCC_VECTOR_EXTENSIONS
|
|
||||||
typedef T Vt __attribute__((vector_size(sizeof(T) * 4)));
|
|
||||||
Vt m;
|
|
||||||
#else
|
|
||||||
using Vt = T[4];
|
|
||||||
Vt m;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
constexpr Vec4() = default;
|
|
||||||
constexpr Vec4(Vt m) : m(m) {}
|
|
||||||
constexpr Vec4(T x, T y, T z, T w) : m{x, y, z, w} {}
|
|
||||||
// For Vec3 with padding
|
|
||||||
constexpr Vec4(T x, T y, T z) : m{x, y, z, {}} {}
|
|
||||||
// For Vec3 -> Vec4
|
|
||||||
constexpr Vec4(Vec3<T> v, T w) : m{v.x, v.y, v.z, w} {}
|
|
||||||
AURORA_VEC4_EXTRA
|
|
||||||
|
|
||||||
inline Vec4& operator=(const Vec4& other) {
|
|
||||||
memcpy(&m, &other.m, sizeof(Vt));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] inline T& x() { return m[0]; }
|
|
||||||
[[nodiscard]] inline T x() const { return m[0]; }
|
|
||||||
[[nodiscard]] inline T& y() { return m[1]; }
|
|
||||||
[[nodiscard]] inline T y() const { return m[1]; }
|
|
||||||
[[nodiscard]] inline T& z() { return m[2]; }
|
|
||||||
[[nodiscard]] inline T z() const { return m[2]; }
|
|
||||||
[[nodiscard]] inline T& w() { return m[3]; }
|
|
||||||
[[nodiscard]] inline T w() const { return m[3]; }
|
|
||||||
[[nodiscard]] inline T& operator[](size_t i) { return m[i]; }
|
|
||||||
[[nodiscard]] inline T operator[](size_t i) const { return m[i]; }
|
|
||||||
|
|
||||||
template <size_t x, size_t y, size_t z, size_t w>
|
|
||||||
[[nodiscard]] constexpr Vec4 shuffle() const {
|
|
||||||
static_assert(x < 4 && y < 4 && z < 4 && w < 4);
|
|
||||||
#if defined(USE_GCC_VECTOR_EXTENSIONS) && __has_builtin(__builtin_shuffle)
|
|
||||||
typedef int Vi __attribute__((vector_size(16)));
|
|
||||||
return __builtin_shuffle(m, Vi{x, y, z, w});
|
|
||||||
#else
|
|
||||||
return {m[x], m[y], m[z], m[w]};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Vec4& rhs) const {
|
|
||||||
#if defined(USE_GCC_VECTOR_EXTENSIONS) && __has_builtin(__builtin_reduce_and)
|
|
||||||
return __builtin_reduce_and(m == rhs.m) != 0;
|
|
||||||
#else
|
|
||||||
return m[0] == rhs.m[0] && m[1] == rhs.m[1] && m[2] == rhs.m[2] && m[3] == rhs.m[3];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
bool operator!=(const Vec4& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] Vec4<T> operator+(const Vec4<T>& a, const Vec4<T>& b) {
|
|
||||||
#ifdef USE_GCC_VECTOR_EXTENSIONS
|
|
||||||
return a.m + b.m;
|
|
||||||
#else
|
|
||||||
return {a.m[0] + b.m[0], a.m[1] + b.m[1], a.m[2] + b.m[2], a.m[3] + b.m[3]};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] Vec4<T> operator*(const Vec4<T>& a, const Vec4<T>& b) {
|
|
||||||
#ifdef USE_GCC_VECTOR_EXTENSIONS
|
|
||||||
return a.m * b.m;
|
|
||||||
#else
|
|
||||||
return {a.m[0] * b.m[0], a.m[1] * b.m[1], a.m[2] * b.m[2], a.m[3] * b.m[3]};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
struct Mat3x2 {
|
|
||||||
Vec2<T> m0{};
|
|
||||||
Vec2<T> m1{};
|
|
||||||
Vec2<T> m2{};
|
|
||||||
|
|
||||||
constexpr Mat3x2() = default;
|
|
||||||
constexpr Mat3x2(const Vec2<T>& m0, const Vec2<T>& m1, const Vec2<T>& m2) : m0(m0), m1(m1), m2(m2) {}
|
|
||||||
|
|
||||||
bool operator==(const Mat3x2& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1 && m2 == rhs.m2; }
|
|
||||||
bool operator!=(const Mat3x2& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
template <typename T>
|
|
||||||
struct Mat4x2 {
|
|
||||||
Vec2<T> m0{};
|
|
||||||
Vec2<T> m1{};
|
|
||||||
Vec2<T> m2{};
|
|
||||||
Vec2<T> m3{};
|
|
||||||
|
|
||||||
constexpr Mat4x2() = default;
|
|
||||||
constexpr Mat4x2(const Vec2<T>& m0, const Vec2<T>& m1, const Vec2<T>& m2, const Vec2<T>& m3)
|
|
||||||
: m0(m0), m1(m1), m2(m2), m3(m3) {}
|
|
||||||
|
|
||||||
inline Mat4x2 transpose() const {
|
|
||||||
return {
|
|
||||||
{m0.x, m2.x},
|
|
||||||
{m0.y, m2.y},
|
|
||||||
{m1.x, m3.x},
|
|
||||||
{m1.y, m3.y},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Mat4x2& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1 && m2 == rhs.m2 && m3 == rhs.m3; }
|
|
||||||
bool operator!=(const Mat4x2& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
template <typename T>
|
|
||||||
struct Mat2x4 {
|
|
||||||
Vec4<T> m0{};
|
|
||||||
Vec4<T> m1{};
|
|
||||||
|
|
||||||
constexpr Mat2x4() = default;
|
|
||||||
constexpr Mat2x4(const Vec4<T>& m0, const Vec4<T>& m1, const Vec4<T>& m2) : m0(m0), m1(m1) {}
|
|
||||||
|
|
||||||
bool operator==(const Mat2x4& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1; }
|
|
||||||
bool operator!=(const Mat2x4& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
static_assert(sizeof(Mat2x4<float>) == 32);
|
|
||||||
template <typename T>
|
|
||||||
struct Mat4x4;
|
|
||||||
template <typename T>
|
|
||||||
struct Mat3x4 {
|
|
||||||
Vec4<T> m0{};
|
|
||||||
Vec4<T> m1{};
|
|
||||||
Vec4<T> m2{};
|
|
||||||
|
|
||||||
constexpr Mat3x4() = default;
|
|
||||||
constexpr Mat3x4(const Vec4<T>& m0, const Vec4<T>& m1, const Vec4<T>& m2) : m0(m0), m1(m1), m2(m2) {}
|
|
||||||
|
|
||||||
[[nodiscard]] Mat4x4<T> to4x4() const;
|
|
||||||
[[nodiscard]] Mat4x4<T> toTransposed4x4() const;
|
|
||||||
|
|
||||||
bool operator==(const Mat3x4& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1 && m2 == rhs.m2; }
|
|
||||||
bool operator!=(const Mat3x4& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
static_assert(sizeof(Mat3x4<float>) == 48);
|
|
||||||
template <typename T>
|
|
||||||
struct Mat4x4 {
|
|
||||||
Vec4<T> m0{};
|
|
||||||
Vec4<T> m1{};
|
|
||||||
Vec4<T> m2{};
|
|
||||||
Vec4<T> m3{};
|
|
||||||
|
|
||||||
constexpr Mat4x4() = default;
|
|
||||||
constexpr Mat4x4(const Vec4<T>& m0, const Vec4<T>& m1, const Vec4<T>& m2, const Vec4<T>& m3)
|
|
||||||
: m0(m0), m1(m1), m2(m2), m3(m3) {}
|
|
||||||
AURORA_MAT4X4_EXTRA
|
|
||||||
|
|
||||||
[[nodiscard]] Mat4x4 transpose() const {
|
|
||||||
return {
|
|
||||||
{m0[0], m1[0], m2[0], m3[0]},
|
|
||||||
{m0[1], m1[1], m2[1], m3[1]},
|
|
||||||
{m0[2], m1[2], m2[2], m3[2]},
|
|
||||||
{m0[3], m1[3], m2[3], m3[3]},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Mat4x4& operator=(const Mat4x4& other) = default;
|
|
||||||
|
|
||||||
Vec4<T>& operator[](size_t i) { return *(&m0 + i); }
|
|
||||||
const Vec4<T>& operator[](size_t i) const { return *(&m0 + i); }
|
|
||||||
|
|
||||||
bool operator==(const Mat4x4& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1 && m2 == rhs.m2 && m3 == rhs.m3; }
|
|
||||||
bool operator!=(const Mat4x4& rhs) const { return !(*this == rhs); }
|
|
||||||
};
|
|
||||||
static_assert(sizeof(Mat4x4<float>) == 64);
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] Mat4x4<T> operator*(const Mat4x4<T>& a, const Mat4x4<T>& b) {
|
|
||||||
Mat4x4<T> out;
|
|
||||||
for (size_t i = 0; i < 4; ++i) {
|
|
||||||
*(&out.m0 + i) = a.m0 * b[i].template shuffle<0, 0, 0, 0>() + a.m1 * b[i].template shuffle<1, 1, 1, 1>() +
|
|
||||||
a.m2 * b[i].template shuffle<2, 2, 2, 2>() + a.m3 * b[i].template shuffle<3, 3, 3, 3>();
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] Mat4x4<T> Mat3x4<T>::to4x4() const {
|
|
||||||
return {
|
|
||||||
{m0[0], m0[1], m0[2], 0.f},
|
|
||||||
{m1[0], m1[1], m1[2], 0.f},
|
|
||||||
{m2[0], m2[1], m2[2], 0.f},
|
|
||||||
{m0[3], m1[3], m2[3], 1.f},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] Mat4x4<T> Mat3x4<T>::toTransposed4x4() const {
|
|
||||||
return Mat4x4<T>{
|
|
||||||
{m0[0], m1[0], m2[0], 0.f},
|
|
||||||
{m0[1], m1[1], m2[1], 0.f},
|
|
||||||
{m0[2], m1[2], m2[2], 0.f},
|
|
||||||
{m0[3], m1[3], m2[3], 1.f},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
constexpr Mat4x4 Mat4x4_Identity{
|
|
||||||
Vec4{1.f, 0.f, 0.f, 0.f},
|
|
||||||
Vec4{0.f, 1.f, 0.f, 0.f},
|
|
||||||
Vec4{0.f, 0.f, 1.f, 0.f},
|
|
||||||
Vec4{0.f, 0.f, 0.f, 1.f},
|
|
||||||
};
|
|
||||||
} // namespace aurora
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
namespace aurora::render_size_limits {
|
|
||||||
|
|
||||||
struct Size {
|
|
||||||
uint32_t width;
|
|
||||||
uint32_t height;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The canvas follows the window aspect however wide it gets, since the presenter stretches it and
|
|
||||||
// a cap would distort the UI. Only the portrait side clamps, where the guest cannot fill taller.
|
|
||||||
inline constexpr float kMaxDynamicPortraitExpansion = 6.f;
|
|
||||||
inline constexpr float kMinDynamicAspect = (16.f / 9.f) / kMaxDynamicPortraitExpansion;
|
|
||||||
|
|
||||||
// The adapter limit is an API ceiling, not an allocation budget: a 16384-wide RGBA attachment is
|
|
||||||
// near 100 MiB. 8192 plus an 8K-UHD pixel budget keeps 8x 16:9 while avoiding device removal.
|
|
||||||
inline constexpr uint32_t kMaxFramebufferDimension = 8192;
|
|
||||||
inline constexpr uint64_t kMaxFramebufferPixels = 7680ull * 4320ull;
|
|
||||||
|
|
||||||
inline float clamp_dynamic_aspect(float aspect) noexcept {
|
|
||||||
if (!std::isfinite(aspect) || aspect <= 0.f) {
|
|
||||||
return 1.f;
|
|
||||||
}
|
|
||||||
return std::max(aspect, kMinDynamicAspect);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Size scale_framebuffer_to_aspect(uint32_t width, uint32_t height, float scale, float requestedAspect) noexcept {
|
|
||||||
if (width == 0 || height == 0 || !std::isfinite(scale) || scale <= 0.f) {
|
|
||||||
return {std::max(width, 1u), std::max(height, 1u)};
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto scaledWidth = std::max(1u, static_cast<uint32_t>(std::lround(static_cast<double>(width) * scale)));
|
|
||||||
const auto scaledHeight = std::max(1u, static_cast<uint32_t>(std::lround(static_cast<double>(height) * scale)));
|
|
||||||
const float aspect = clamp_dynamic_aspect(requestedAspect);
|
|
||||||
if (aspect >= static_cast<float>(width) / static_cast<float>(height)) {
|
|
||||||
return {
|
|
||||||
std::max(1u, static_cast<uint32_t>(std::lround(static_cast<double>(scaledHeight) * aspect))),
|
|
||||||
scaledHeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
scaledWidth,
|
|
||||||
std::max(1u, static_cast<uint32_t>(std::lround(static_cast<double>(scaledWidth) / aspect))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Size fit_framebuffer_to_budget(uint32_t width, uint32_t height, uint32_t adapterMaxDimension) noexcept {
|
|
||||||
if (width == 0 || height == 0) {
|
|
||||||
return {width, height};
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t dimensionLimit = kMaxFramebufferDimension;
|
|
||||||
if (adapterMaxDimension != 0 && adapterMaxDimension != std::numeric_limits<uint32_t>::max()) {
|
|
||||||
dimensionLimit = std::min(dimensionLimit, adapterMaxDimension);
|
|
||||||
}
|
|
||||||
dimensionLimit = std::max(dimensionLimit, 1u);
|
|
||||||
|
|
||||||
double fitScale = 1.0;
|
|
||||||
fitScale = std::min(fitScale, static_cast<double>(dimensionLimit) / static_cast<double>(width));
|
|
||||||
fitScale = std::min(fitScale, static_cast<double>(dimensionLimit) / static_cast<double>(height));
|
|
||||||
|
|
||||||
const double requestedPixels = static_cast<double>(width) * static_cast<double>(height);
|
|
||||||
if (requestedPixels > static_cast<double>(kMaxFramebufferPixels)) {
|
|
||||||
fitScale = std::min(fitScale, std::sqrt(static_cast<double>(kMaxFramebufferPixels) / requestedPixels));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fitScale >= 1.0) {
|
|
||||||
return {width, height};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
std::max(1u, static_cast<uint32_t>(std::floor(static_cast<double>(width) * fitScale))),
|
|
||||||
std::max(1u, static_cast<uint32_t>(std::floor(static_cast<double>(height) * fitScale))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace aurora::render_size_limits
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#ifndef _DOLPHIN_AI_H_
|
|
||||||
#define _DOLPHIN_AI_H_
|
|
||||||
|
|
||||||
#include <dolphin/types.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef void (*AISCallback)(u32 count);
|
|
||||||
typedef void (*AIDCallback)();
|
|
||||||
|
|
||||||
#define AI_STREAM_START 1
|
|
||||||
#define AI_STREAM_STOP 0
|
|
||||||
|
|
||||||
#define AI_SAMPLERATE_32KHZ 0
|
|
||||||
#define AI_SAMPLERATE_48KHZ 1
|
|
||||||
|
|
||||||
AIDCallback AIRegisterDMACallback(AIDCallback callback);
|
|
||||||
void AIInitDMA(u32 start_addr, u32 length);
|
|
||||||
BOOL AIGetDMAEnableFlag(void);
|
|
||||||
void AIStartDMA(void);
|
|
||||||
void AIStopDMA(void);
|
|
||||||
u32 AIGetDMABytesLeft(void);
|
|
||||||
u32 AIGetDMAStartAddr(void);
|
|
||||||
u32 AIGetDMALength(void);
|
|
||||||
BOOL AICheckInit(void);
|
|
||||||
AISCallback AIRegisterStreamCallback(AISCallback callback);
|
|
||||||
u32 AIGetStreamSampleCount(void);
|
|
||||||
void AIResetStreamSampleCount(void);
|
|
||||||
void AISetStreamTrigger(u32 trigger);
|
|
||||||
u32 AIGetStreamTrigger(void);
|
|
||||||
void AISetStreamPlayState(u32 state);
|
|
||||||
u32 AIGetStreamPlayState(void);
|
|
||||||
void AISetDSPSampleRate(u32 rate);
|
|
||||||
u32 AIGetDSPSampleRate(void);
|
|
||||||
void AISetStreamSampleRate(u32 rate);
|
|
||||||
u32 AIGetStreamSampleRate(void);
|
|
||||||
void AISetStreamVolLeft(u8 vol);
|
|
||||||
u8 AIGetStreamVolLeft(void);
|
|
||||||
void AISetStreamVolRight(u8 vol);
|
|
||||||
u8 AIGetStreamVolRight(void);
|
|
||||||
void AIInit(u8* stack);
|
|
||||||
void AIReset(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#ifndef _DOLPHIN_AR_H_
|
|
||||||
#define _DOLPHIN_AR_H_
|
|
||||||
|
|
||||||
#include <dolphin/types.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef void (*ARQCallback)(uintptr_t pointerToARQRequest);
|
|
||||||
|
|
||||||
struct ARQRequest {
|
|
||||||
/* 0x00 */ struct ARQRequest *next;
|
|
||||||
/* 0x04 */ u32 owner;
|
|
||||||
/* 0x08 */ u32 type;
|
|
||||||
/* 0x0C */ u32 priority;
|
|
||||||
/* 0x10 */ u32 source;
|
|
||||||
/* 0x14 */ u32 dest;
|
|
||||||
/* 0x18 */ u32 length;
|
|
||||||
/* 0x1C */ ARQCallback callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define ARQ_DMA_ALIGNMENT 32
|
|
||||||
|
|
||||||
#define ARAM_DIR_MRAM_TO_ARAM 0x00
|
|
||||||
#define ARAM_DIR_ARAM_TO_MRAM 0x01
|
|
||||||
|
|
||||||
#define ARStartDMARead(mmem, aram, len) \
|
|
||||||
ARStartDMA(ARAM_DIR_ARAM_TO_MRAM, mmem, aram, len)
|
|
||||||
#define ARStartDMAWrite(mmem, aram, len) \
|
|
||||||
ARStartDMA(ARAM_DIR_MRAM_TO_ARAM, mmem, aram, len)
|
|
||||||
|
|
||||||
typedef struct ARQRequest ARQRequest;
|
|
||||||
|
|
||||||
#define ARQ_TYPE_MRAM_TO_ARAM ARAM_DIR_MRAM_TO_ARAM
|
|
||||||
#define ARQ_TYPE_ARAM_TO_MRAM ARAM_DIR_ARAM_TO_MRAM
|
|
||||||
|
|
||||||
#define ARQ_PRIORITY_LOW 0
|
|
||||||
#define ARQ_PRIORITY_HIGH 1
|
|
||||||
|
|
||||||
// AR
|
|
||||||
ARQCallback ARRegisterDMACallback(ARQCallback callback);
|
|
||||||
u32 ARGetDMAStatus(void);
|
|
||||||
void ARStartDMA(u32 type, u32 mainmem_addr, u32 aram_addr, u32 length);
|
|
||||||
u32 ARAlloc(u32 length);
|
|
||||||
u32 ARFree(u32* length);
|
|
||||||
BOOL ARCheckInit(void);
|
|
||||||
u32 ARInit(u32* stack_index_addr, u32 num_entries);
|
|
||||||
void ARReset(void);
|
|
||||||
void ARSetSize(void);
|
|
||||||
u32 ARGetBaseAddress(void);
|
|
||||||
u32 ARGetSize(void);
|
|
||||||
u32 ARGetInternalSize(void);
|
|
||||||
void ARClear(u32 flag);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aurora extension: gets the memory address where ARAM is stored, for direct access.
|
|
||||||
*/
|
|
||||||
void* ARGetStorageAddress();
|
|
||||||
|
|
||||||
// ARQ
|
|
||||||
void ARQInit(void);
|
|
||||||
void ARQReset(void);
|
|
||||||
void ARQPostRequest(ARQRequest* request, u32 owner, u32 type, u32 priority, uintptr_t source, uintptr_t dest, u32 length, ARQCallback callback);
|
|
||||||
void ARQRemoveRequest(ARQRequest* request);
|
|
||||||
void ARQRemoveOwnerRequest(u32 owner);
|
|
||||||
void ARQFlushQueue(void);
|
|
||||||
void ARQSetChunkSize(u32 size);
|
|
||||||
u32 ARQGetChunkSize(void);
|
|
||||||
BOOL ARQCheckInit(void);
|
|
||||||
|
|
||||||
u16 __ARGetInterruptStatus(void);
|
|
||||||
void __ARClearInterrupt(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,542 +0,0 @@
|
|||||||
#ifndef _DOLPHIN_PPCARCH
|
|
||||||
#define _DOLPHIN_PPCARCH
|
|
||||||
|
|
||||||
#include <dolphin/types.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CTR 9
|
|
||||||
#define XER 1
|
|
||||||
#define LR 8
|
|
||||||
|
|
||||||
#define UPMC1 937
|
|
||||||
#define UPMC2 938
|
|
||||||
#define UPMC3 941
|
|
||||||
#define UPMC4 942
|
|
||||||
|
|
||||||
#define USIA 939
|
|
||||||
|
|
||||||
#define UMMCR0 936
|
|
||||||
#define UMMCR1 940
|
|
||||||
|
|
||||||
#define HID0 1008
|
|
||||||
#define HID1 1009
|
|
||||||
|
|
||||||
#define PVR 287
|
|
||||||
|
|
||||||
#define IBAT0U 528
|
|
||||||
#define IBAT0L 529
|
|
||||||
#define IBAT1U 530
|
|
||||||
#define IBAT1L 531
|
|
||||||
#define IBAT2U 532
|
|
||||||
#define IBAT2L 533
|
|
||||||
#define IBAT3U 534
|
|
||||||
#define IBAT3L 535
|
|
||||||
|
|
||||||
#define DBAT0U 536
|
|
||||||
#define DBAT0L 537
|
|
||||||
#define DBAT1U 538
|
|
||||||
#define DBAT1L 539
|
|
||||||
#define DBAT2U 540
|
|
||||||
#define DBAT2L 541
|
|
||||||
#define DBAT3U 542
|
|
||||||
#define DBAT3L 543
|
|
||||||
|
|
||||||
#define SDR1 25
|
|
||||||
|
|
||||||
#define SPRG0 272
|
|
||||||
#define SPRG1 273
|
|
||||||
#define SPRG2 274
|
|
||||||
#define SPRG3 275
|
|
||||||
|
|
||||||
#define DAR 19
|
|
||||||
#define DSISR 18
|
|
||||||
|
|
||||||
#define SRR0 26
|
|
||||||
#define SRR1 27
|
|
||||||
|
|
||||||
#define EAR 282
|
|
||||||
|
|
||||||
#define DABR 1013
|
|
||||||
|
|
||||||
#define TBL 284
|
|
||||||
#define TBU 285
|
|
||||||
|
|
||||||
#define L2CR 1017
|
|
||||||
|
|
||||||
#define DEC 22
|
|
||||||
|
|
||||||
#define IABR 1010
|
|
||||||
|
|
||||||
#define PMC1 953
|
|
||||||
#define PMC2 954
|
|
||||||
#define PMC3 957
|
|
||||||
#define PMC4 958
|
|
||||||
|
|
||||||
#define SIA 955
|
|
||||||
|
|
||||||
#define MMCR0 952
|
|
||||||
#define MMCR1 956
|
|
||||||
|
|
||||||
#define THRM1 1020
|
|
||||||
#define THRM2 1021
|
|
||||||
#define THRM3 1022
|
|
||||||
|
|
||||||
#define ICTC 1019
|
|
||||||
|
|
||||||
#define GQR0 912
|
|
||||||
#define GQR1 913
|
|
||||||
#define GQR2 914
|
|
||||||
#define GQR3 915
|
|
||||||
#define GQR4 916
|
|
||||||
#define GQR5 917
|
|
||||||
#define GQR6 918
|
|
||||||
#define GQR7 919
|
|
||||||
|
|
||||||
#define HID2 920
|
|
||||||
|
|
||||||
#define WPAR 921
|
|
||||||
|
|
||||||
#define DMA_U 922
|
|
||||||
#define DMA_L 923
|
|
||||||
|
|
||||||
#define MSR_POW 0x00040000 // Power Management
|
|
||||||
#define MSR_ILE 0x00010000 // Interrupt Little Endian
|
|
||||||
#define MSR_EE 0x00008000 // external interrupt
|
|
||||||
#define MSR_PR 0x00004000 // privilege level(should be 0)
|
|
||||||
#define MSR_FP 0x00002000 // floating point available
|
|
||||||
#define MSR_ME 0x00001000 // machine check enable
|
|
||||||
#define MSR_FE0 0x00000800 // floating point exception enable
|
|
||||||
#define MSR_SE 0x00000400 // single step trace enable
|
|
||||||
#define MSR_BE 0x00000200 // branch trace enable
|
|
||||||
#define MSR_FE1 0x00000100 // floating point exception enable
|
|
||||||
#define MSR_IP 0x00000040 // Exception prefix
|
|
||||||
#define MSR_IR 0x00000020 // instruction relocate
|
|
||||||
#define MSR_DR 0x00000010 // data relocate
|
|
||||||
#define MSR_PM 0x00000004 // Performance monitor marked mode
|
|
||||||
#define MSR_RI 0x00000002 // Recoverable interrupt
|
|
||||||
#define MSR_LE 0x00000001 // Little Endian
|
|
||||||
|
|
||||||
#define MSR_POW_BIT 13 // Power Management
|
|
||||||
#define MSR_ILE_BIT 15 // Interrupt Little Endian
|
|
||||||
#define MSR_EE_BIT 16 // external interrupt
|
|
||||||
#define MSR_PR_BIT 17 // privilege level (should be 0)
|
|
||||||
#define MSR_FP_BIT 18 // floating point available
|
|
||||||
#define MSR_ME_BIT 19 // machine check enable
|
|
||||||
#define MSR_FE0_BIT 20 // floating point exception enable
|
|
||||||
#define MSR_SE_BIT 21 // single step trace enable
|
|
||||||
#define MSR_BE_BIT 22 // branch trace enable
|
|
||||||
#define MSR_FE1_BIT 23 // floating point exception enable
|
|
||||||
#define MSR_IP_BIT 25 // Exception prefix
|
|
||||||
#define MSR_IR_BIT 26 // instruction relocate
|
|
||||||
#define MSR_DR_BIT 27 // data relocate
|
|
||||||
#define MSR_PM_BIT 29 // Performance monitor marked mode
|
|
||||||
#define MSR_RI_BIT 30 // Recoverable interrupt
|
|
||||||
#define MSR_LE_BIT 31 // Little Endian
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*
|
|
||||||
HID0 bits
|
|
||||||
*---------------------------------------------------------------------------*/
|
|
||||||
#define HID0_EMCP 0x80000000u // Enable MCP
|
|
||||||
#define HID0_DBP 0x40000000u // Enable 60x bus address and data parity chk
|
|
||||||
#define HID0_EBA 0x20000000u // Enable 60x address parity checking
|
|
||||||
#define HID0_EBD 0x10000000u // Enable 60x data parity checking
|
|
||||||
#define HID0_BCLK 0x08000000u // CLK_OUT output enable and clk selection
|
|
||||||
#define HID0_ECLK 0x02000000u // CLK_OUT output enable and clk selection
|
|
||||||
#define HID0_PAR 0x01000000u // Disable !ARTRY precharge
|
|
||||||
#define HID0_DOZE 0x00800000u // Doze mode enable
|
|
||||||
#define HID0_NAP 0x00400000u // Nap mode enable
|
|
||||||
#define HID0_SLEEP 0x00200000u // Sleep mode enable
|
|
||||||
#define HID0_DPM 0x00100000u // Dynamic power management enable
|
|
||||||
#define HID0_NHR 0x00010000u // Not hard reset (0 hard reset if s/w set it)
|
|
||||||
#define HID0_ICE 0x00008000u // Instruction cache enable
|
|
||||||
#define HID0_DCE 0x00004000u // Data cache enable
|
|
||||||
#define HID0_ILOCK 0x00002000u // ICache lock
|
|
||||||
#define HID0_DLOCK 0x00001000u // DCache lock
|
|
||||||
#define HID0_ICFI 0x00000800u // ICache flash invalidate
|
|
||||||
#define HID0_DCFI 0x00000400u // DCache flash invalidate
|
|
||||||
#define HID0_SPD 0x00000200u // Speculative cache access enable (0 enable)
|
|
||||||
#define HID0_IFEM 0x00000100u // Enable M bit on bus for Ifetch
|
|
||||||
#define HID0_SGE 0x00000080u // Store gathering enable
|
|
||||||
#define HID0_DCFA 0x00000040u // DCache flush assist - set before a flush
|
|
||||||
#define HID0_BTIC 0x00000020u // Branch target icache enable
|
|
||||||
#define HID0_ABE 0x00000008u // Address bcast enable
|
|
||||||
#define HID0_BHT 0x00000004u // Branch history table enable
|
|
||||||
#define HID0_NOOPTI 0x00000001u // No-op Dcache touch instructions
|
|
||||||
|
|
||||||
#define HID0_ICE_BIT 16 // Instruction cache enable
|
|
||||||
#define HID0_DCE_BIT 17 // Data cache enable
|
|
||||||
#define HID0_ILOCK_BIT 18 // ICache lock
|
|
||||||
#define HID0_DLOCK_BIT 19 // DCache lock
|
|
||||||
|
|
||||||
#define HID2_LSQE 0x80000000 // L/S quantize enable
|
|
||||||
#define HID2_WPE 0x40000000 // Write pipe enable
|
|
||||||
#define HID2_PSE 0x20000000 // Paired single enable
|
|
||||||
#define HID2_LCE 0x10000000 // Locked cache enable
|
|
||||||
|
|
||||||
#define HID2_DCHERR 0x00800000 // ERROR: dcbz_l cache hit
|
|
||||||
#define HID2_DNCERR 0x00400000 // ERROR: DMA access to normal cache
|
|
||||||
#define HID2_DCMERR 0x00200000 // ERROR: DMA cache miss error
|
|
||||||
#define HID2_DQOERR 0x00100000 // ERROR: DMA queue overflow
|
|
||||||
#define HID2_DCHEE 0x00080000 // dcbz_l cache hit error enable
|
|
||||||
#define HID2_DNCEE 0x00040000 // DMA access to normal cache error enable
|
|
||||||
#define HID2_DCMEE 0x00020000 // DMA cache miss error error enable
|
|
||||||
#define HID2_DQOEE 0x00010000 // DMA queue overflow error enable
|
|
||||||
|
|
||||||
#define HID2_DMAQL_MASK 0x0F000000 // DMA queue length mask
|
|
||||||
#define HID2_DMAQL_SHIFT 24 // DMA queue shift
|
|
||||||
|
|
||||||
#define HID2_LSQE_BIT 0
|
|
||||||
#define HID2_WPE_BIT 1
|
|
||||||
#define HID2_PSE_BIT 2
|
|
||||||
#define HID2_LCE_BIT 3
|
|
||||||
|
|
||||||
#define HID2_DCHERR_BIT 8
|
|
||||||
#define HID2_DNCERR_BIT 9
|
|
||||||
#define HID2_DCMERR_BIT 10
|
|
||||||
#define HID2_DQOERR_BIT 11
|
|
||||||
#define HID2_DCHEE_BIT 12
|
|
||||||
#define HID2_DNCEE_BIT 13
|
|
||||||
#define HID2_DCMEE_BIT 14
|
|
||||||
#define HID2_DQOEE_BIT 15
|
|
||||||
|
|
||||||
#define GQR_LOAD_SCALE_MASK 0x3F000000 // load scale field
|
|
||||||
#define GQR_LOAD_TYPE_MASK 0x00070000 // load type field
|
|
||||||
#define GQR_STORE_SCALE_MASK 0x00003F00 // store scale field
|
|
||||||
#define GQR_STORE_TYPE_MASK 0x00000007 // store type field
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
u32 _pad0 :2;
|
|
||||||
u32 loadScale :6;
|
|
||||||
u32 _pad1 :5;
|
|
||||||
u32 loadType :3;
|
|
||||||
u32 _pad2 :2;
|
|
||||||
u32 storeScale :6;
|
|
||||||
u32 _pad3 :5;
|
|
||||||
u32 storeType :3;
|
|
||||||
} PPC_GQR_t;
|
|
||||||
|
|
||||||
typedef union
|
|
||||||
{
|
|
||||||
u32 val;
|
|
||||||
PPC_GQR_t f;
|
|
||||||
} PPC_GQR_u;
|
|
||||||
|
|
||||||
|
|
||||||
#define DMA_U_ADDR_MASK 0xFFFFFFE0 // Start addr in memory
|
|
||||||
#define DMA_U_LEN_U_MASK 0x0000001F // lines to transfer (U)
|
|
||||||
|
|
||||||
#define DMA_L_LC_ADDR_MASK 0xFFFFFFE0 // Start addr in LC
|
|
||||||
#define DMA_L_LOAD 0x00000010 // 0 - store, 1 - load
|
|
||||||
#define DMA_L_STORE 0x00000000 // 0 - store, 1 - load
|
|
||||||
#define DMA_L_LEN_MASK 0x0000000C // lines to transfer (L)
|
|
||||||
#define DMA_L_TRIGGER 0x00000002 // 0 - cmd inactive, 1 - cmd rdy
|
|
||||||
#define DMA_L_FLUSH 0x00000001 // 1 - Flush DMA queue
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
u32 memAddr :27;
|
|
||||||
u32 dmaLenU :5;
|
|
||||||
} PPC_DMA_U_t;
|
|
||||||
|
|
||||||
typedef union
|
|
||||||
{
|
|
||||||
u32 val;
|
|
||||||
PPC_DMA_U_t f;
|
|
||||||
} PPC_DMA_U_u;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
u32 lcAddr :27;
|
|
||||||
u32 dmaLd :1;
|
|
||||||
u32 dmaLenL :2;
|
|
||||||
u32 dmaTrigger :1;
|
|
||||||
u32 dmaFlush :1;
|
|
||||||
} PPC_DMA_L_t;
|
|
||||||
|
|
||||||
|
|
||||||
typedef union
|
|
||||||
{
|
|
||||||
u32 val;
|
|
||||||
PPC_DMA_L_t f;
|
|
||||||
} PPC_DMA_L_u;
|
|
||||||
|
|
||||||
|
|
||||||
#define WPAR_ADDR 0xFFFFFFE0 // 32byte gather address
|
|
||||||
#define WPAR_BNE 0x00000001 // Buffer not empty (R)
|
|
||||||
|
|
||||||
#define SRR1_DMA_BIT 0x00200000
|
|
||||||
#define SRR1_L2DP_BIT 0x00100000
|
|
||||||
|
|
||||||
#define L2CR_L2E 0x80000000 // L2 Enable
|
|
||||||
#define L2CR_L2PE 0x40000000 // L2 data parity generation and checking enable
|
|
||||||
|
|
||||||
#define L2CR_L2SIZ_256K 0x10000000 // L2 size 256K
|
|
||||||
#define L2CR_L2SIZ_512K 0x20000000 // L2 size 512
|
|
||||||
#define L2CR_L2SIZ_1M 0x30000000 // L2 size 1M
|
|
||||||
|
|
||||||
#define L2CR_L2CLK_1_0 0x02000000 // L2 clock ratio 1
|
|
||||||
#define L2CR_L2CLK_1_5 0x04000000 // L2 clock ratio 1.5
|
|
||||||
#define L2CR_L2CLK_2_0 0x08000000 // L2 clock ratio 2
|
|
||||||
#define L2CR_L2CLK_2_5 0x0A000000 // L2 clock ratio 2.5
|
|
||||||
#define L2CR_L2CLK_3_0 0x0C000000 // L2 clock ratio 3
|
|
||||||
|
|
||||||
#define L2CR_RAM_FLOW_THRU_BURST 0x00000000 // L2 RAM type flow-through sync. burst SRAM
|
|
||||||
#define L2CR_RAM_PIPELINE_BURST 0x01000000 // L2 RAM type pipelined sync. burst SRAM
|
|
||||||
#define L2CR_RAM_PIPELINE_LATE 0x01800000 // L2 RAM type pipelined sync. late-write SRAM
|
|
||||||
|
|
||||||
#define L2CR_L2DO 0x00400000 // Data only
|
|
||||||
#define L2CR_L2I 0x00200000 // Global invalidate
|
|
||||||
#define L2CR_L2CTL 0x00100000 // ZZ enable
|
|
||||||
#define L2CR_L2WT 0x00080000 // L2 write through
|
|
||||||
#define L2CR_L2TS 0x00040000 // L2 test support
|
|
||||||
|
|
||||||
#define L2CR_L2OH_0_5 0x00000000 // L2 output hold 0.5 ns
|
|
||||||
#define L2CR_L2OH_1_0 0x00010000 // L2 output hold 1.0 ns
|
|
||||||
|
|
||||||
#define L2CR_L2SL 0x00008000 // L2 DLL slow
|
|
||||||
#define L2CR_L2DF 0x00004000 // L2 differential clock
|
|
||||||
#define L2CR_L2BYP 0x00002000 // L2 DLL bypass
|
|
||||||
#define L2CR_L2CS 0x00000200 // L2 clock stop
|
|
||||||
#define L2CR_L2DRO 0x00000100 // L2 DLL rollover checkstop enable
|
|
||||||
#define L2CR_L2CTR_MASK 0x000000FE // L2 counter value mask
|
|
||||||
#define L2CR_L2IP 0x00000001 // L2 global invalidate in progress
|
|
||||||
|
|
||||||
#define MMCR0_DIS 0x80000000 // Disables counting unconditionally
|
|
||||||
#define MMCR0_DP 0x40000000 // Disables counting while in supervisor mode
|
|
||||||
#define MMCR0_DU 0x20000000 // Disables counting while in user mode
|
|
||||||
#define MMCR0_DMS 0x10000000 // Disables counting while MSR[PM] is set
|
|
||||||
#define MMCR0_DMR 0x08000000 // Disables counting while MSR[PM] is zero
|
|
||||||
#define MMCR0_ENINT 0x04000000 // Enables performance monitor interrupt signaling
|
|
||||||
#define MMCR0_DISCOUNT 0x02000000 // Disables counting of PMCn when a performance monitor interrupt is signaled or...
|
|
||||||
#define MMCR0_RTCSELECT_MASK 0x01800000 // 64-bit time base, bit selection enable
|
|
||||||
#define MMCR0_RTCSELECT_63 0x00000000 // Pick bit 63 to count
|
|
||||||
#define MMCR0_RTCSELECT_55 0x00800000 // Pick bit 55 to count
|
|
||||||
#define MMCR0_RTCSELECT_51 0x01000000 // Pick bit 51 to count
|
|
||||||
#define MMCR0_RTCSELECT_47 0x01800000 // Pick bit 47 to count
|
|
||||||
#define MMCR0_INTONBITTRANS 0x00400000 // Causes interrupt signaling on bit transition from off to on
|
|
||||||
#define MMCR0_THRESHOLD_MASK 0x003F0000 // Threshold value
|
|
||||||
#define MMCR0_THRESHOLD(n) ((n) << 16) // Threshold value (0 - 63)
|
|
||||||
#define MMCR0_PMC1INTCONTROL 0x00008000 // Enables interrupt signaling due to PMC1 counter overflow
|
|
||||||
#define MMCR0_PMC2INTCONTROL 0x00004000 // Enables interrupt signaling due to PMC2-PMC4 counter overflow
|
|
||||||
#define MMCR0_PMCTRIGGER 0x00002000 // Can be used to trigger counting of PMC2-PMC4 after PMC1 has overflowed or...
|
|
||||||
#define MMCR0_PMC1SELECT_MASK 0x00001FC0 // PMC1 input selector
|
|
||||||
#define MMCR0_PMC2SELECT_MASK 0x0000003F // PMC2 input selector
|
|
||||||
|
|
||||||
#define MMCR1_PMC3SELECT_MASK 0xF8000000 // PMC3 input selector
|
|
||||||
#define MMCR1_PMC4SELECT_MASK 0x07C00000 // PMC4 input selector
|
|
||||||
|
|
||||||
#define PMC1_OV 0x80000000 // Overflow
|
|
||||||
#define PMC1_COUNTER 0x7FFFFFFF // Counter value
|
|
||||||
#define PMC2_OV 0x80000000 // Overflow
|
|
||||||
#define PMC2_COUNTER 0x7FFFFFFF // Counter value
|
|
||||||
#define PMC3_OV 0x80000000 // Overflow
|
|
||||||
#define PMC3_COUNTER 0x7FFFFFFF // Counter value
|
|
||||||
#define PMC4_OV 0x80000000 // Overflow
|
|
||||||
#define PMC4_COUNTER 0x7FFFFFFF // Counter value
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*
|
|
||||||
PMC1 Events
|
|
||||||
*---------------------------------------------------------------------------*/
|
|
||||||
#define MMCR0_PMC1_HOLD 0x00000000 // Register holds current value
|
|
||||||
#define MMCR0_PMC1_CYCLE 0x00000040 // Processor cycles
|
|
||||||
#define MMCR0_PMC1_INSTRUCTION 0x00000080 // # of instructions completed.
|
|
||||||
#define MMCR0_PMC1_TRANSITION 0x000000C0 // # of transitions for 0 to 1
|
|
||||||
#define MMCR0_PMC1_DISPATCHED 0x00000100 // # of instructions dispatched
|
|
||||||
#define MMCR0_PMC1_EIEIO 0x00000140 // # of eieio instructions completed
|
|
||||||
#define MMCR0_PMC1_ITLB_CYCLE 0x00000180 // # of cycles spent performing table search op. for the ITLB
|
|
||||||
#define MMCR0_PMC1_L2_HIT 0x000001C0 // # of access that hit the L2.
|
|
||||||
#define MMCR0_PMC1_EA 0x00000200 // # of valid instruction EAs delivered to the memory subsystem
|
|
||||||
#define MMCR0_PMC1_IABR 0x00000240 // # of time the address of an instruction matches the IABR
|
|
||||||
#define MMCR0_PMC1_L1_MISS 0x00000280 // # of loads that miss the L1
|
|
||||||
#define MMCR0_PMC1_Bx_UNRESOLVED 0x000002C0 // # of branches that are unresolved when processed
|
|
||||||
#define MMCR0_PMC1_Bx_STALL_CYCLE 0x00000300 // # of cycles that dispatcher stalls due to a second
|
|
||||||
// unresolved branch in the instruction stream
|
|
||||||
#define MMCR0_PMC1_IC_FETCH_MISS 0x00000340 // # of times an instruction fetch missed the L1 Icache
|
|
||||||
#define MMCR0_PMC2_HOLD 0x00000000 // Register holds current value
|
|
||||||
#define MMCR0_PMC2_CYCLE 0x00000001 // Processor cycles
|
|
||||||
#define MMCR0_PMC2_INSTRUCTION 0x00000002 // # of instructions completed
|
|
||||||
#define MMCR0_PMC2_TRANSITION 0x00000003 // # of time-base (lower) bit transitions
|
|
||||||
#define MMCR0_PMC2_DISPATCHED 0x00000004 // # of instructions dispatched
|
|
||||||
#define MMCR0_PMC2_IC_MISS 0x00000005 // # of L1 instruction cache misses
|
|
||||||
#define MMCR0_PMC2_ITLB_MISS 0x00000006 // # of ITLB misses
|
|
||||||
#define MMCR0_PMC2_L2_I_MISS 0x00000007 // # of L2 instruction misses
|
|
||||||
#define MMCR0_PMC2_Bx_FALL_TROUGH 0x00000008 // # of fall-through branches
|
|
||||||
#define MMCR0_PMC2_PR_SWITCH 0x00000009 // # of MSR[PR] bit toggles
|
|
||||||
#define MMCR0_PMC2_RESERVED_LOAD 0x0000000A // # of reserved loads completed
|
|
||||||
#define MMCR0_PMC2_LOAD_STORE 0x0000000B // # of completed loads and stores
|
|
||||||
#define MMCR0_PMC2_SNOOP 0x0000000C // # of snoops
|
|
||||||
#define MMCR0_PMC2_L1_CASTOUT 0x0000000D // # of L1 castouts to L2
|
|
||||||
#define MMCR0_PMC2_SYSTEM 0x0000000E // # of completed system unit instructions
|
|
||||||
#define MMCR0_PMC2_IC_FETCH_MISS 0x0000000F // # of instruction fetch misses in the L1
|
|
||||||
#define MMCR0_PMC2_Bx_OUT_OF_ORDER 0x00000010 // # of branches allowing out-of-order execution
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*
|
|
||||||
PMC3 Events
|
|
||||||
*---------------------------------------------------------------------------*/
|
|
||||||
#define MMCR1_PMC3_HOLD 0x00000000 // Register holds current value
|
|
||||||
#define MMCR1_PMC3_CYCLE 0x08000000 // Processor cycles
|
|
||||||
#define MMCR1_PMC3_INSTRUCTION 0x10000000 // # of instructions completed
|
|
||||||
#define MMCR1_PMC3_TRANSITION 0x18000000 // # of time-base (lower) bit transitions
|
|
||||||
#define MMCR1_PMC3_DISPATCHED 0x20000000 // # of instructions dispatched
|
|
||||||
#define MMCR1_PMC3_DC_MISS 0x28000000 // # of L1 data cache misses
|
|
||||||
#define MMCR1_PMC3_DTLB_MISS 0x30000000 // # of DTLB misses
|
|
||||||
#define MMCR1_PMC3_L2_D_MISS 0x38000000 // # of L2 data misses
|
|
||||||
#define MMCR1_PMC3_Bx_TAKEN 0x40000000 // # predicted branches that were taken
|
|
||||||
#define MMCR1_PMC3_PM_SWITCH 0x48000000 // # of transitions between marked and unmarked processes
|
|
||||||
#define MMCR1_PMC3_COND_STORE 0x50000000 // # of store conditional instructions completed
|
|
||||||
#define MMCR1_PMC3_FPU 0x58000000 // # of instructions completed from the FPU
|
|
||||||
#define MMCR1_PMC3_L2_SNOOP_CASTOUT 0x60000000 // # of L2 castout caused by snoops to modified lines
|
|
||||||
#define MMCR1_PMC3_L2_HIT 0x68000000 // # of cache operations that hit in the L2 cache
|
|
||||||
#define MMCR1_PMC3_L1_MISS_CYCLE 0x78000000 // # of cycles generated by L1 load misses
|
|
||||||
#define MMCR1_PMC3_Bx_SECOND 0x80000000 // # of branches in the second speculative branch
|
|
||||||
// resolved correctly
|
|
||||||
#define MMCR1_PMC3_BPU_LR_CR 0x88000000 // # of cycles the BPU stalls due to LR or CR unresolved
|
|
||||||
// dependencies
|
|
||||||
|
|
||||||
#define MMCR1_PMC4_HOLD 0x00000000 // Register holds current value
|
|
||||||
#define MMCR1_PMC4_CYCLE 0x00400000 // Processor cycles
|
|
||||||
#define MMCR1_PMC4_INSTRUCTION 0x00800000 // # of instructions completed
|
|
||||||
#define MMCR1_PMC4_TRANSITION 0x00C00000 // # of time-base (lower) bit transitions
|
|
||||||
#define MMCR1_PMC4_DISPATCHED 0x01000000 // # of instructions dispatched
|
|
||||||
#define MMCR1_PMC4_L2_CASTOUT 0x01400000 // # of L2 castouts
|
|
||||||
#define MMCR1_PMC4_DTLB_CYCLE 0x01800000 // # of cycles spent performing table searches for DTLB accesses
|
|
||||||
#define MMCR1_PMC4_Bx_MISSED 0x02000000 // # of mispredicted branches
|
|
||||||
#define MMCR1_PMC4_COND_STORE_INT 0x02800000 // # of store conditional instructions completed
|
|
||||||
// with reservation intact
|
|
||||||
#define MMCR1_PMC4_SYNC 0x02C00000 // # of completed sync instructions
|
|
||||||
#define MMCR1_PMC4_SNOOP_RETRY 0x03000000 // # of snoop request retries
|
|
||||||
#define MMCR1_PMC4_INTEGER 0x03400000 // # of completed integer operations
|
|
||||||
#define MMCR1_PMC4_BPU_THIRD 0x03800000 // # of cycles the BPU cannot process new branches
|
|
||||||
// due to having two unresolved branches
|
|
||||||
#define MMCR1_PMC4_DC_MISS 0x07C00000 // # of L1 data cache misses
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*
|
|
||||||
FPSCR bits
|
|
||||||
*---------------------------------------------------------------------------*/
|
|
||||||
#ifndef FPSCR_FX
|
|
||||||
#define FPSCR_FX 0x80000000 // Exception summary
|
|
||||||
#define FPSCR_FEX 0x40000000 // Enabled exception summary
|
|
||||||
#define FPSCR_VX 0x20000000 // Invalid operation
|
|
||||||
#define FPSCR_OX 0x10000000 // Overflow exception
|
|
||||||
#define FPSCR_UX 0x08000000 // Underflow exception
|
|
||||||
#define FPSCR_ZX 0x04000000 // Zero divide exception
|
|
||||||
#define FPSCR_XX 0x02000000 // Inexact exception
|
|
||||||
#define FPSCR_VXSNAN 0x01000000 // SNaN
|
|
||||||
#define FPSCR_VXISI 0x00800000 // Infinity - Infinity
|
|
||||||
#define FPSCR_VXIDI 0x00400000 // Infinity / Infinity
|
|
||||||
#define FPSCR_VXZDZ 0x00200000 // 0 / 0
|
|
||||||
#define FPSCR_VXIMZ 0x00100000 // Infinity * 0
|
|
||||||
#define FPSCR_VXVC 0x00080000 // Invalid compare
|
|
||||||
#define FPSCR_FR 0x00040000 // Fraction rounded
|
|
||||||
#define FPSCR_FI 0x00020000 // Fraction inexact
|
|
||||||
#define FPSCR_VXSOFT 0x00000400 // Software request
|
|
||||||
#define FPSCR_VXSQRT 0x00000200 // Invalid square root
|
|
||||||
#define FPSCR_VXCVI 0x00000100 // Invalid integer convert
|
|
||||||
#define FPSCR_VE 0x00000080 // Invalid operation exception enable
|
|
||||||
#define FPSCR_OE 0x00000040 // Overflow exception enable
|
|
||||||
#define FPSCR_UE 0x00000020 // Underflow exception enable
|
|
||||||
#define FPSCR_ZE 0x00000010 // Zero divide exception enable
|
|
||||||
#define FPSCR_XE 0x00000008 // Inexact exception enable
|
|
||||||
#define FPSCR_NI 0x00000004 // Non-IEEE mode
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef FPSCR_FX_BIT
|
|
||||||
#define FPSCR_FX_BIT 0 // Exception summary
|
|
||||||
#define FPSCR_FEX_BIT 1 // Enabled exception summary
|
|
||||||
#define FPSCR_VX_BIT 2 // Invalid operation
|
|
||||||
#define FPSCR_OX_BIT 3 // Overflow exception
|
|
||||||
#define FPSCR_UX_BIT 4 // Underflow exception
|
|
||||||
#define FPSCR_ZX_BIT 5 // Zero divide exception
|
|
||||||
#define FPSCR_XX_BIT 6 // Inexact exception
|
|
||||||
#define FPSCR_VXSNAN_BIT 7 // SNaN
|
|
||||||
#define FPSCR_VXISI_BIT 8 // Infinity - Infinity
|
|
||||||
#define FPSCR_VXIDI_BIT 9 // Infinity / Infinity
|
|
||||||
#define FPSCR_VXZDZ_BIT 10 // 0 / 0
|
|
||||||
#define FPSCR_VXIMZ_BIT 11 // Infinity * 0
|
|
||||||
#define FPSCR_VXVC_BIT 12 // Invalid compare
|
|
||||||
#define FPSCR_FR_BIT 13 // Fraction rounded
|
|
||||||
#define FPSCR_FI_BIT 14 // Fraction inexact
|
|
||||||
#define FPSCR_VXSOFT_BIT 21 // Software request
|
|
||||||
#define FPSCR_VXSQRT_BIT 22 // Invalid square root
|
|
||||||
#define FPSCR_VXCVI_BIT 23 // Invalid integer convert
|
|
||||||
#define FPSCR_VE_BIT 24 // Invalid operation exception enable
|
|
||||||
#define FPSCR_OE_BIT 25 // Overflow exception enable
|
|
||||||
#define FPSCR_UE_BIT 26 // Underflow exception enable
|
|
||||||
#define FPSCR_ZE_BIT 27 // Zero divide exception enable
|
|
||||||
#define FPSCR_XE_BIT 28 // Inexact exception enable
|
|
||||||
#define FPSCR_NI_BIT 29 // Non-IEEE mode
|
|
||||||
#endif
|
|
||||||
|
|
||||||
union FpscrUnion {
|
|
||||||
f64 f;
|
|
||||||
struct {
|
|
||||||
u32 fpscr_pad;
|
|
||||||
u32 fpscr;
|
|
||||||
} u;
|
|
||||||
};
|
|
||||||
|
|
||||||
// PPCArch
|
|
||||||
u32 PPCMfmsr();
|
|
||||||
void PPCMtmsr(u32 newMSR);
|
|
||||||
u32 PPCOrMsr(u32 value);
|
|
||||||
u32 PPCMfhid0();
|
|
||||||
void PPCMthid0(u32 newHID0);
|
|
||||||
u32 PPCMfl2cr();
|
|
||||||
void PPCMtl2cr(u32 newL2cr);
|
|
||||||
void PPCMtdec(u32 newDec);
|
|
||||||
void PPCSync();
|
|
||||||
void PPCHalt();
|
|
||||||
u32 PPCMffpscr();
|
|
||||||
void PPCMtfpscr(u32 newFPSCR);
|
|
||||||
u32 PPCMfhid2();
|
|
||||||
void PPCMthid2(u32 newhid2);
|
|
||||||
u32 PPCMfwpar();
|
|
||||||
void PPCMtwpar(u32 newwpar);
|
|
||||||
void PPCEnableSpeculation();
|
|
||||||
void PPCDisableSpeculation();
|
|
||||||
void PPCSetFpIEEEMode();
|
|
||||||
void PPCSetFpNonIEEEMode();
|
|
||||||
u32 PPCMfpmc4();
|
|
||||||
u32 PPCMfpmc3();
|
|
||||||
u32 PPCMfpmc1();
|
|
||||||
void PPCMtpmc1(u32 newPmc1);
|
|
||||||
void PPCMtpmc2(u32 newPmc1);
|
|
||||||
void PPCMtpmc3(u32 newPmc1);
|
|
||||||
void PPCMtpmc4(u32 newPmc1);
|
|
||||||
void PPCMtmmcr0(u32 newMmcr0);
|
|
||||||
void PPCMtmmcr1(u32 newMmcr0);
|
|
||||||
void PPCMtdmaU(u32 newdmau);
|
|
||||||
void PPCMtdmaL(u32 newdmal);
|
|
||||||
u32 PPCMfdec(void);
|
|
||||||
u32 PPCMfpmc2(void);
|
|
||||||
u32 PPCAndMsr(u32 value);
|
|
||||||
u32 PPCAndCMsr(u32 value);
|
|
||||||
u32 PPCMfhid1();
|
|
||||||
void PPCEieio();
|
|
||||||
u32 PPCMfmmcr0();
|
|
||||||
u32 PPCMfmmcr1();
|
|
||||||
u32 PPCMfpmc2();
|
|
||||||
u32 PPCMfsia();
|
|
||||||
void PPCMtsia(u32 newSia);
|
|
||||||
u32 PPCMfdmaL();
|
|
||||||
u32 PPCMfpvr();
|
|
||||||
u32 PPCMfdmaU();
|
|
||||||
|
|
||||||
// PPCPm
|
|
||||||
void PMBegin(void);
|
|
||||||
void PMEnd(void);
|
|
||||||
void PMCycles(void);
|
|
||||||
void PML1FetchMisses(void);
|
|
||||||
void PML1MissCycles(void);
|
|
||||||
void PMInstructions(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // _DOLPHIN_PPCARCH
|
|
||||||
@@ -1,340 +0,0 @@
|
|||||||
#ifndef _DOLPHIN_CARD_H_
|
|
||||||
#define _DOLPHIN_CARD_H_
|
|
||||||
|
|
||||||
#include <dolphin/os.h>
|
|
||||||
#include <dolphin/dsp.h>
|
|
||||||
#include <dolphin/dvd.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CARD_FILENAME_MAX 32
|
|
||||||
#define CARD_MAX_FILE 127
|
|
||||||
#define CARD_ICON_MAX 8
|
|
||||||
|
|
||||||
#if TARGET_PC
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
CARD_RAWIMAGE,
|
|
||||||
CARD_GCIFOLDER,
|
|
||||||
} CARDFileType;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef void (*CARDCallback)(s32 chan, s32 result);
|
|
||||||
|
|
||||||
typedef struct CARDFileInfo {
|
|
||||||
s32 chan;
|
|
||||||
s32 fileNo;
|
|
||||||
s32 offset;
|
|
||||||
s32 length;
|
|
||||||
u16 iBlock;
|
|
||||||
} CARDFileInfo;
|
|
||||||
|
|
||||||
typedef struct CARDDir {
|
|
||||||
u8 gameName[4];
|
|
||||||
u8 company[2];
|
|
||||||
u8 _padding0;
|
|
||||||
u8 bannerFormat;
|
|
||||||
u8 fileName[CARD_FILENAME_MAX];
|
|
||||||
u32 time; // seconds since 01/01/2000 midnight
|
|
||||||
u32 iconAddr; // 0xffffffff if not used
|
|
||||||
u16 iconFormat;
|
|
||||||
u16 iconSpeed;
|
|
||||||
u8 permission;
|
|
||||||
u8 copyTimes;
|
|
||||||
u16 startBlock;
|
|
||||||
u16 length;
|
|
||||||
u8 _padding1[2];
|
|
||||||
u32 commentAddr; // 0xffffffff if not used
|
|
||||||
} CARDDir;
|
|
||||||
|
|
||||||
typedef struct CARDControl {
|
|
||||||
/* 0x000 */ BOOL attached;
|
|
||||||
/* 0x004 */ s32 result;
|
|
||||||
/* 0x008 */ u16 size;
|
|
||||||
/* 0x00A */ u16 pageSize;
|
|
||||||
/* 0x00C */ s32 sectorSize;
|
|
||||||
/* 0x010 */ u16 cBlock;
|
|
||||||
/* 0x012 */ u16 vendorID;
|
|
||||||
/* 0x014 */ s32 latency;
|
|
||||||
/* 0x018 */ u8 id[12];
|
|
||||||
/* 0x024 */ int mountStep;
|
|
||||||
/* 0x028 */ int formatStep;
|
|
||||||
/* 0x028 */ u32 scramble;
|
|
||||||
/* 0x02C */ DSPTaskInfo task;
|
|
||||||
/* 0x080 */ void* workArea;
|
|
||||||
/* 0x084 */ CARDDir *currentDir;
|
|
||||||
/* 0x088 */ u16* currentFat;
|
|
||||||
/* 0x08C */ OSThreadQueue threadQueue;
|
|
||||||
/* 0x094 */ u8 cmd[9];
|
|
||||||
/* 0x0A0 */ s32 cmdlen;
|
|
||||||
/* 0x0A4 */ volatile u32 mode;
|
|
||||||
/* 0x0A8 */ int retry;
|
|
||||||
/* 0x0AC */ int repeat;
|
|
||||||
/* 0x0B0 */ u32 addr;
|
|
||||||
/* 0x0B4 */ void* buffer;
|
|
||||||
/* 0x0B8 */ s32 xferred;
|
|
||||||
/* 0x0BC */ u16 freeNo;
|
|
||||||
/* 0x0BE */ u16 startBlock;
|
|
||||||
/* 0x0C0 */ CARDFileInfo* fileInfo;
|
|
||||||
/* 0x0C4 */ CARDCallback extCallback;
|
|
||||||
/* 0x0C8 */ CARDCallback txCallback;
|
|
||||||
/* 0x0CC */ CARDCallback exiCallback;
|
|
||||||
/* 0x0D0 */ CARDCallback apiCallback;
|
|
||||||
/* 0x0D4 */ CARDCallback xferCallback;
|
|
||||||
/* 0x0D8 */ CARDCallback eraseCallback;
|
|
||||||
/* 0x0DC */ CARDCallback unlockCallback;
|
|
||||||
/* 0x0E0 */ OSAlarm alarm;
|
|
||||||
/* 0x108 */ u32 cid;
|
|
||||||
/* 0x10C */ const DVDDiskID* diskID;
|
|
||||||
} CARDControl;
|
|
||||||
|
|
||||||
typedef struct CARDDecParam {
|
|
||||||
/* 0x00 */ u8* inputAddr;
|
|
||||||
/* 0x04 */ u32 inputLength;
|
|
||||||
/* 0x08 */ u32 aramAddr;
|
|
||||||
/* 0x0C */ u8* outputAddr;
|
|
||||||
} CARDDecParam;
|
|
||||||
|
|
||||||
typedef struct CARDID {
|
|
||||||
/* 0x000 */ u8 serial[32];
|
|
||||||
/* 0x020 */ u16 deviceID;
|
|
||||||
/* 0x022 */ u16 size;
|
|
||||||
/* 0x024 */ u16 encode;
|
|
||||||
/* 0x026 */ u8 padding[470];
|
|
||||||
/* 0x1FC */ u16 checkSum;
|
|
||||||
/* 0x1FE */ u16 checkSumInv;
|
|
||||||
} CARDID;
|
|
||||||
|
|
||||||
typedef struct CARDDirCheck {
|
|
||||||
/* 0x00 */ u8 padding0[56];
|
|
||||||
/* 0x38 */ u16 padding1;
|
|
||||||
/* 0x3A */ s16 checkCode;
|
|
||||||
/* 0x3C */ u16 checkSum;
|
|
||||||
/* 0x3E */ u16 checkSumInv;
|
|
||||||
} CARDDirCheck;
|
|
||||||
|
|
||||||
typedef struct CARDStat {
|
|
||||||
/* 0x00 */ char fileName[CARD_FILENAME_MAX];
|
|
||||||
/* 0x20 */ u32 length;
|
|
||||||
/* 0x24 */ u32 time;
|
|
||||||
/* 0x28 */ u8 gameName[4];
|
|
||||||
/* 0x2C */ u8 company[2];
|
|
||||||
/* 0x2E */ u8 bannerFormat;
|
|
||||||
/* 0x30 */ u32 iconAddr;
|
|
||||||
/* 0x34 */ u16 iconFormat;
|
|
||||||
/* 0x36 */ u16 iconSpeed;
|
|
||||||
/* 0x38 */ u32 commentAddr;
|
|
||||||
/* 0x3C */ u32 offsetBanner;
|
|
||||||
/* 0x40 */ u32 offsetBannerTlut;
|
|
||||||
/* 0x44 */ u32 offsetIcon[CARD_ICON_MAX];
|
|
||||||
/* 0x64 */ u32 offsetIconTlut;
|
|
||||||
/* 0x68 */ u32 offsetData;
|
|
||||||
} CARDStat;
|
|
||||||
|
|
||||||
#define CARD_ATTR_PUBLIC 0x04u
|
|
||||||
#define CARD_ATTR_NO_COPY 0x08u
|
|
||||||
#define CARD_ATTR_NO_MOVE 0x10u
|
|
||||||
#define CARD_ATTR_GLOBAL 0x20u
|
|
||||||
#define CARD_ATTR_COMPANY 0x40u
|
|
||||||
|
|
||||||
#define CARD_FAT_AVAIL 0x0000u
|
|
||||||
#define CARD_FAT_CHECKSUM 0x0000u
|
|
||||||
#define CARD_FAT_CHECKSUMINV 0x0001u
|
|
||||||
#define CARD_FAT_CHECKCODE 0x0002u
|
|
||||||
#define CARD_FAT_FREEBLOCKS 0x0003u
|
|
||||||
#define CARD_FAT_LASTSLOT 0x0004u
|
|
||||||
|
|
||||||
#define CARD_WORKAREA_SIZE (5 * 8 * 1024)
|
|
||||||
|
|
||||||
#define CARD_SEG_SIZE 0x200u
|
|
||||||
#define CARD_PAGE_SIZE 0x80u
|
|
||||||
#define CARD_MAX_SIZE 0x01000000U
|
|
||||||
|
|
||||||
#define CARD_NUM_SYSTEM_BLOCK 5
|
|
||||||
#define CARD_SYSTEM_BLOCK_SIZE (8 * 1024u)
|
|
||||||
|
|
||||||
#define CARD_MAX_MOUNT_STEP (CARD_NUM_SYSTEM_BLOCK + 2)
|
|
||||||
|
|
||||||
#define CARD_STAT_SPEED_END 0
|
|
||||||
#define CARD_STAT_SPEED_FAST 1
|
|
||||||
#define CARD_STAT_SPEED_MIDDLE 2
|
|
||||||
#define CARD_STAT_SPEED_SLOW 3
|
|
||||||
#define CARD_STAT_SPEED_MASK 3
|
|
||||||
|
|
||||||
#define CARD_STAT_ANIM_LOOP 0
|
|
||||||
#define CARD_STAT_ANIM_BOUNCE 4
|
|
||||||
#define CARD_STAT_ANIM_MASK 0x4
|
|
||||||
|
|
||||||
#define CARD_RESULT_UNLOCKED 1
|
|
||||||
#define CARD_RESULT_READY 0
|
|
||||||
#define CARD_RESULT_BUSY -1
|
|
||||||
#define CARD_RESULT_WRONGDEVICE -2
|
|
||||||
#define CARD_RESULT_NOCARD -3
|
|
||||||
#define CARD_RESULT_NOFILE -4
|
|
||||||
#define CARD_RESULT_IOERROR -5
|
|
||||||
#define CARD_RESULT_BROKEN -6
|
|
||||||
#define CARD_RESULT_EXIST -7
|
|
||||||
#define CARD_RESULT_NOENT -8
|
|
||||||
#define CARD_RESULT_INSSPACE -9
|
|
||||||
#define CARD_RESULT_NOPERM -10
|
|
||||||
#define CARD_RESULT_LIMIT -11
|
|
||||||
#define CARD_RESULT_NAMETOOLONG -12
|
|
||||||
#define CARD_RESULT_ENCODING -13
|
|
||||||
#define CARD_RESULT_CANCELED -14
|
|
||||||
#define CARD_RESULT_FATAL_ERROR -128
|
|
||||||
|
|
||||||
#define CARDIsValidBlockNo(card, blockNo) ((blockNo) >= CARD_NUM_SYSTEM_BLOCK && (blockNo) < (card)->cBlock)
|
|
||||||
|
|
||||||
#define CARD_READ_SIZE 512
|
|
||||||
|
|
||||||
#define CARD_COMMENT_SIZE 64
|
|
||||||
|
|
||||||
#define CARD_ICON_WIDTH 32
|
|
||||||
#define CARD_ICON_HEIGHT 32
|
|
||||||
|
|
||||||
#define CARD_BANNER_WIDTH 96
|
|
||||||
#define CARD_BANNER_HEIGHT 32
|
|
||||||
|
|
||||||
#define CARD_STAT_ICON_NONE 0
|
|
||||||
#define CARD_STAT_ICON_C8 1
|
|
||||||
#define CARD_STAT_ICON_RGB5A3 2
|
|
||||||
#define CARD_STAT_ICON_MASK 3
|
|
||||||
|
|
||||||
#define CARD_STAT_BANNER_NONE 0
|
|
||||||
#define CARD_STAT_BANNER_C8 1
|
|
||||||
#define CARD_STAT_BANNER_RGB5A3 2
|
|
||||||
#define CARD_STAT_BANNER_MASK 3
|
|
||||||
|
|
||||||
#define CARD_ENCODE_ANSI 0
|
|
||||||
#define CARD_ENCODE_SJIS 1
|
|
||||||
|
|
||||||
#define CARDGetDirCheck(dir) ((CARDDirCheck*)&(dir)[CARD_MAX_FILE])
|
|
||||||
#define CARDGetBannerFormat(stat) (((stat)->bannerFormat) & CARD_STAT_BANNER_MASK)
|
|
||||||
#define CARDGetIconAnim(stat) (((stat)->bannerFormat) & CARD_STAT_ANIM_MASK)
|
|
||||||
#define CARDGetIconFormat(stat, n) (((stat)->iconFormat >> (2 * (n))) & CARD_STAT_ICON_MASK)
|
|
||||||
#define CARDGetIconSpeed(stat, n) (((stat)->iconSpeed >> (2 * (n))) & CARD_STAT_SPEED_MASK)
|
|
||||||
#define CARDSetBannerFormat(stat, f) \
|
|
||||||
((stat)->bannerFormat = (u8)(((stat)->bannerFormat & ~CARD_STAT_BANNER_MASK) | (f)))
|
|
||||||
#define CARDSetIconAnim(stat, f) \
|
|
||||||
((stat)->bannerFormat = (u8)(((stat)->bannerFormat & ~CARD_STAT_ANIM_MASK) | (f)))
|
|
||||||
#define CARDSetIconFormat(stat, n, f) \
|
|
||||||
((stat)->iconFormat = \
|
|
||||||
(u16)(((stat)->iconFormat & ~(CARD_STAT_ICON_MASK << (2 * (n)))) | ((f) << (2 * (n)))))
|
|
||||||
#define CARDSetIconSpeed(stat, n, f) \
|
|
||||||
((stat)->iconSpeed = \
|
|
||||||
(u16)(((stat)->iconSpeed & ~(CARD_STAT_SPEED_MASK << (2 * (n)))) | ((f) << (2 * (n)))))
|
|
||||||
#define CARDSetIconAddress(stat, addr) ((stat)->iconAddr = (u32)(addr))
|
|
||||||
#define CARDSetCommentAddress(stat, addr) ((stat)->commentAddr = (u32)(addr))
|
|
||||||
#define CARDGetFileNo(fileInfo) ((fileInfo)->fileNo)
|
|
||||||
|
|
||||||
extern u32 __CARDFreq;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
#define CARDFreq __CARDFreq
|
|
||||||
#else
|
|
||||||
#define CARDFreq EXI_FREQ_16M
|
|
||||||
#endif
|
|
||||||
|
|
||||||
s32 CARDRenameAsync(s32 chan, const char* oldName, const char* newName, CARDCallback callback);
|
|
||||||
|
|
||||||
// CARDBios
|
|
||||||
#if TARGET_PC
|
|
||||||
void CARDInit(const char* game, const char* maker);
|
|
||||||
void CARDSetGameAndMaker(const s32 chan, const char* game, const char* maker);
|
|
||||||
|
|
||||||
// pass -1 to set both
|
|
||||||
void CARDDetectDolphin(s32 chan);
|
|
||||||
// pass -1 to set both
|
|
||||||
void CARDSetBasePath(const char*, s32 chan);
|
|
||||||
void CARDSetLoadType(CARDFileType type);
|
|
||||||
|
|
||||||
#else
|
|
||||||
void CARDInit(void);
|
|
||||||
#endif
|
|
||||||
s32 CARDGetResultCode(s32 chan);
|
|
||||||
s32 CARDFreeBlocks(s32 chan, s32* byteNotUsed, s32* filesNotUsed);
|
|
||||||
s32 CARDGetEncoding(s32 chan, u16* encode);
|
|
||||||
s32 CARDGetMemSize(s32 chan, u16* size);
|
|
||||||
s32 CARDGetSectorSize(s32 chan, u32* size);
|
|
||||||
const DVDDiskID* CARDGetDiskID(s32 chan);
|
|
||||||
s32 CARDSetDiskID(s32 chan, const DVDDiskID* diskID);
|
|
||||||
BOOL CARDSetFastMode(BOOL enable);
|
|
||||||
BOOL CARDGetFastMode(void);
|
|
||||||
s32 CARDGetCurrentMode(s32 chan, u32* mode);
|
|
||||||
|
|
||||||
// CARDCheck
|
|
||||||
s32 CARDCheckExAsync(s32 chan, s32* xferBytes, CARDCallback callback);
|
|
||||||
s32 CARDCheckAsync(s32 chan, CARDCallback callback);
|
|
||||||
s32 CARDCheckEx(s32 chan, s32* xferBytes);
|
|
||||||
s32 CARDCheck(s32 chan);
|
|
||||||
|
|
||||||
// CARDCreate
|
|
||||||
s32 CARDCreateAsync(s32 chan, const char* fileName, u32 size, CARDFileInfo* fileInfo, CARDCallback callback);
|
|
||||||
s32 CARDCreate(s32 chan, const char* fileName, u32 size, CARDFileInfo* fileInfo);
|
|
||||||
|
|
||||||
// CARDDelete
|
|
||||||
s32 CARDFastDeleteAsync(s32 chan, s32 fileNo, CARDCallback callback);
|
|
||||||
s32 CARDFastDelete(s32 chan, s32 fileNo);
|
|
||||||
s32 CARDDeleteAsync(s32 chan, const char* fileName, CARDCallback callback);
|
|
||||||
s32 CARDDelete(s32 chan, const char* fileName);
|
|
||||||
|
|
||||||
// CARDErase
|
|
||||||
s32 CARDEraseAsync(CARDFileInfo* fileInfo, s32 length, s32 offset, CARDCallback callback);
|
|
||||||
s32 CARDErase(CARDFileInfo* fileInfo, s32 length, s32 offset);
|
|
||||||
|
|
||||||
// CARDFormat
|
|
||||||
s32 CARDFormat(s32 chan);
|
|
||||||
|
|
||||||
// CARDMount
|
|
||||||
int CARDProbe(s32 chan);
|
|
||||||
s32 CARDProbeEx(s32 chan, s32* memSize, s32* sectorSize);
|
|
||||||
s32 CARDMountAsync(s32 chan, void* workArea, CARDCallback detachCallback, CARDCallback attachCallback);
|
|
||||||
s32 CARDMount(s32 chan, void* workArea, CARDCallback detachCallback);
|
|
||||||
s32 CARDUnmount(s32 chan);
|
|
||||||
|
|
||||||
// CARDNet
|
|
||||||
u16 CARDSetVendorID(u16 vendorID);
|
|
||||||
u16 CARDGetVendorID();
|
|
||||||
s32 CARDGetSerialNo(s32 chan, u64* serialNo);
|
|
||||||
s32 CARDGetUniqueCode(s32 chan, u64* uniqueCode);
|
|
||||||
s32 CARDGetAttributes(s32 chan, s32 fileNo, u8* attr);
|
|
||||||
s32 CARDSetAttributesAsync(s32 chan, s32 fileNo, u8 attr, CARDCallback callback);
|
|
||||||
s32 CARDSetAttributes(s32 chan, s32 fileNo, u8 attr);
|
|
||||||
|
|
||||||
// CARDOpen
|
|
||||||
s32 CARDFastOpen(s32 chan, s32 fileNo, CARDFileInfo* fileInfo);
|
|
||||||
s32 CARDOpen(s32 chan, const char* fileName, CARDFileInfo* fileInfo);
|
|
||||||
s32 CARDClose(CARDFileInfo* fileInfo);
|
|
||||||
|
|
||||||
// CARDProgram
|
|
||||||
s32 CARDProgramAsync(CARDFileInfo* fileInfo, void* buf, s32 length, s32 offset, CARDCallback callback);
|
|
||||||
s32 CARDProgram(CARDFileInfo* fileInfo, void* buf, s32 length, s32 offset);
|
|
||||||
|
|
||||||
// CARDRdwr
|
|
||||||
s32 CARDGetXferredBytes(s32 chan);
|
|
||||||
|
|
||||||
// CARDRead
|
|
||||||
s32 CARDReadAsync(const CARDFileInfo* fileInfo, void* addr, s32 length, s32 offset, CARDCallback callback);
|
|
||||||
s32 CARDRead(const CARDFileInfo* fileInfo, void* addr, s32 length, s32 offset);
|
|
||||||
s32 CARDCancel(CARDFileInfo* fileInfo);
|
|
||||||
|
|
||||||
// CARDRename
|
|
||||||
s32 CARDRename(s32 chan, const char* oldName, const char* newName);
|
|
||||||
|
|
||||||
// CARDStat
|
|
||||||
s32 CARDGetStatus(s32 chan, s32 fileNo, CARDStat* stat);
|
|
||||||
s32 CARDSetStatusAsync(s32 chan, s32 fileNo, const CARDStat* stat, CARDCallback callback);
|
|
||||||
s32 CARDSetStatus(s32 chan, s32 fileNo, const CARDStat* stat);
|
|
||||||
|
|
||||||
// CARDWrite
|
|
||||||
s32 CARDWriteAsync(const CARDFileInfo* fileInfo, const void* addr, s32 length, s32 offset, CARDCallback callback);
|
|
||||||
s32 CARDWrite(const CARDFileInfo* fileInfo, const void* addr, s32 length, s32 offset);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#ifndef _DOLPHIN_DB_H_
|
|
||||||
#define _DOLPHIN_DB_H_
|
|
||||||
|
|
||||||
#include <dolphin/types.h>
|
|
||||||
#include <dolphin/db/DBInterface.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define OS_DBINTERFACE_ADDR 0x00000040
|
|
||||||
|
|
||||||
BOOL DBIsDebuggerPresent(void);
|
|
||||||
void DBPrintf(char* str, ...);
|
|
||||||
s32 DBQueryData(void);
|
|
||||||
u32 DBRead(u8*, u32);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // _DOLPHIN_DB_H_
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#ifndef _DOLPHIN_DBINTERFACE_H_
|
|
||||||
#define _DOLPHIN_DBINTERFACE_H_
|
|
||||||
|
|
||||||
#include <dolphin/os.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct DBInterface {
|
|
||||||
u32 bPresent;
|
|
||||||
u32 exceptionMask;
|
|
||||||
void (*ExceptionDestination)(void);
|
|
||||||
void* exceptionReturn;
|
|
||||||
} DBInterface;
|
|
||||||
|
|
||||||
extern DBInterface* __DBInterface;
|
|
||||||
|
|
||||||
void DBInit(void);
|
|
||||||
void DBInitComm(int* inputFlagPtr, int* mtrCallback);
|
|
||||||
void __DBExceptionDestination(void);
|
|
||||||
void __DBExceptionDestinationAux(void);
|
|
||||||
BOOL __DBIsExceptionMarked(__OSException exception);
|
|
||||||
void __DBMarkException(u8 exception, int value);
|
|
||||||
void __DBSetPresent(u32 value);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#ifndef _DOLPHIN_H_
|
|
||||||
#define _DOLPHIN_H_
|
|
||||||
|
|
||||||
#include <dolphin/types.h>
|
|
||||||
#include <dolphin/os.h>
|
|
||||||
#include <dolphin/dsp.h>
|
|
||||||
#include <dolphin/dvd.h>
|
|
||||||
#include <dolphin/gx.h>
|
|
||||||
#include <dolphin/hio.h>
|
|
||||||
#include <dolphin/mtx.h>
|
|
||||||
#include <dolphin/vi.h>
|
|
||||||
#include <dolphin/card.h>
|
|
||||||
#include <dolphin/perf.h>
|
|
||||||
#include <dolphin/ar.h>
|
|
||||||
#include <dolphin/base/PPCArch.h>
|
|
||||||
#include <dolphin/db.h>
|
|
||||||
#include <dolphin/pad.h>
|
|
||||||
#include <dolphin/dtk.h>
|
|
||||||
#include <dolphin/ai.h>
|
|
||||||
// #include <dolphin/demo.h>
|
|
||||||
#include <dolphin/exi.h>
|
|
||||||
#include <dolphin/si.h>
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user