mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-13 09:59:47 -04:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 352c4ca02e | |||
| cc286f3060 | |||
| 8769cf6dea | |||
| 452b478bb3 | |||
| 407f8a7190 | |||
| c2289e4ba4 | |||
| b034f7c4cc | |||
| a135beb201 | |||
| a51fc25daf | |||
| f41d1f1393 | |||
| 6242926af9 | |||
| 88b990b060 | |||
| e0e362bd99 | |||
| 5654d8f21b | |||
| 730e3122d5 | |||
| f424536d3b | |||
| 2d9dc4e0f2 | |||
| 8e57cc162f | |||
| 1c0a3edee9 | |||
| d1d80613cc | |||
| 88643d6884 | |||
| 5381c4ecf2 | |||
| e5258af5ec | |||
| c57ed5f69b | |||
| 5d67b229f6 | |||
| 56db6ba641 | |||
| a67069afd3 | |||
| 009697fb97 | |||
| 3f7fed48c9 | |||
| 6eba523d70 |
@@ -3,3 +3,4 @@
|
|||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -30,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
|
||||||
|
|
||||||
@@ -38,38 +46,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test translator/Translator.sln -c Release --no-build --verbosity normal
|
run: dotnet test translator/Translator.sln -c Release --no-build --verbosity normal
|
||||||
|
|
||||||
macos_substrate:
|
|
||||||
name: macOS arm64 (configure + substrate tests)
|
|
||||||
runs-on: macos-14
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Configure native runtime
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
test "$(uname -m)" = arm64
|
|
||||||
cmake -S runtime -B build-macos -G Ninja \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release -DMKW_BUILD_PRODUCTS=OFF
|
|
||||||
grep -qx 'CMAKE_OSX_DEPLOYMENT_TARGET:STRING=12.0' \
|
|
||||||
build-macos/CMakeCache.txt
|
|
||||||
|
|
||||||
- name: Build macOS portability targets
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cmake --build build-macos --target \
|
|
||||||
mkw_platform_paths_tests \
|
|
||||||
mkw_nand_save_tests \
|
|
||||||
mkw_nand_settings_tests \
|
|
||||||
mkw_sc_serial_tests \
|
|
||||||
mkw_input_expr_tests \
|
|
||||||
mkw_macos_native_compile \
|
|
||||||
mkw_macos_context_abi_tests \
|
|
||||||
mkw_macos_host_context_tests \
|
|
||||||
mkw_macos_guest_flat_memory_tests
|
|
||||||
|
|
||||||
- name: Test execution substrate
|
|
||||||
shell: bash
|
|
||||||
run: ctest --test-dir build-macos --output-on-failure
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
name: Package installers
|
name: Package installers
|
||||||
|
|
||||||
# Builds the per-platform installer/setup tool (WiiCompiled-Setup.exe /
|
# Builds the per-platform installer/setup tool (WiiCompiled-Setup.exe /
|
||||||
# WiiCompiled-Setup-x86_64.AppImage / WiiCompiled-Setup.pkg) via the platform packaging scripts -
|
# WiiCompiled-Setup-x86_64.AppImage) via Launcher/Build-Installer.ps1 and
|
||||||
# the same scripts a maintainer runs by hand today to
|
# Launcher/build-appimage.sh respectively - the same scripts a maintainer runs by hand today to
|
||||||
# produce a GitHub Release asset. This does NOT build the actual translated game executable:
|
# produce a GitHub Release asset. This does NOT build the actual translated game executable:
|
||||||
# that step requires the end user's own Mario Kart Wii dump (Assets/main.dol, Assets/StaticR.rel),
|
# that step requires the end user's own Mario Kart Wii dump (Assets/main.dol, Assets/StaticR.rel),
|
||||||
# which is proprietary and not present in this repository or in CI.
|
# which is proprietary and not present in this repository or in CI.
|
||||||
@@ -11,11 +11,6 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: Package version
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -95,130 +90,14 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
archive: false
|
archive: false
|
||||||
|
|
||||||
macos-setup-package:
|
|
||||||
name: macOS (universal Setup.pkg)
|
|
||||||
runs-on: macos-14
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v6
|
|
||||||
with:
|
|
||||||
dotnet-version: '8.0.x'
|
|
||||||
|
|
||||||
- name: Verify Apple Silicon runner
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
test "$(uname -m)" = arm64
|
|
||||||
xcode-select -p
|
|
||||||
|
|
||||||
- name: Download pinned Nod tools
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir -p Launcher/artifacts/macos
|
|
||||||
nodtool_version=v2.0.0-alpha.10
|
|
||||||
nodtool_arm64_asset=nodtool-macos-arm64
|
|
||||||
nodtool_arm64_sha256=e23ca466999b720c55e6d29c9683fce8cc74451ba64ead2e543d50129f24528a
|
|
||||||
nodtool_x86_64_asset=nodtool-macos-x86_64
|
|
||||||
nodtool_x86_64_sha256=f68f504dc2b72694b468ca78b6a24142c7aa5c8800f77564297f4143682e6575
|
|
||||||
curl -fsSL --retry 3 \
|
|
||||||
"https://github.com/encounter/nod/releases/download/${nodtool_version}/${nodtool_arm64_asset}" \
|
|
||||||
-o Launcher/artifacts/macos/nodtool-arm64
|
|
||||||
curl -fsSL --retry 3 \
|
|
||||||
"https://github.com/encounter/nod/releases/download/${nodtool_version}/${nodtool_x86_64_asset}" \
|
|
||||||
-o Launcher/artifacts/macos/nodtool-x86_64
|
|
||||||
printf '%s %s\n' "$nodtool_arm64_sha256" Launcher/artifacts/macos/nodtool-arm64 | shasum -a 256 -c -
|
|
||||||
printf '%s %s\n' "$nodtool_x86_64_sha256" Launcher/artifacts/macos/nodtool-x86_64 | shasum -a 256 -c -
|
|
||||||
chmod +x Launcher/artifacts/macos/nodtool-arm64 Launcher/artifacts/macos/nodtool-x86_64
|
|
||||||
|
|
||||||
- name: Publish self-contained Translator tools
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
dotnet publish translator/src/Translator.Cli/Translator.Cli.csproj \
|
|
||||||
-c Release -r osx-arm64 --self-contained true \
|
|
||||||
-p:PublishSingleFile=true \
|
|
||||||
-o Launcher/artifacts/macos/translator-arm64
|
|
||||||
dotnet publish translator/src/Translator.Cli/Translator.Cli.csproj \
|
|
||||||
-c Release -r osx-x64 --self-contained true \
|
|
||||||
-p:PublishSingleFile=true \
|
|
||||||
-o Launcher/artifacts/macos/translator-x86_64
|
|
||||||
|
|
||||||
- name: Download pinned universal Ninja
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
ninja_version=1.13.2
|
|
||||||
ninja_sha256=c99048673aa765960a99cf10c6ddb9f1fad506099ff0a0e137ad8960a88f321b
|
|
||||||
curl -fsSL --retry 3 "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-mac.zip" -o ninja-mac.zip
|
|
||||||
printf '%s %s\n' "$ninja_sha256" ninja-mac.zip | shasum -a 256 -c -
|
|
||||||
unzip -q ninja-mac.zip -d Launcher/artifacts/macos/ninja
|
|
||||||
chmod +x Launcher/artifacts/macos/ninja/ninja
|
|
||||||
|
|
||||||
- name: Download pinned portable CMake
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cmake_version=4.4.3
|
|
||||||
archive="cmake-${cmake_version}-macos-universal.tar.gz"
|
|
||||||
base_url="https://github.com/Kitware/CMake/releases/download/v${cmake_version}"
|
|
||||||
expected_sha256=0c5d65251c14cc884bfa16bdbed3c263ce5bffe2e21c0d0d00962cb0610464fa
|
|
||||||
curl -fsSL --retry 3 "$base_url/$archive" -o "$archive"
|
|
||||||
printf '%s %s\n' "$expected_sha256" "$archive" | shasum -a 256 -c -
|
|
||||||
tar -xzf "$archive"
|
|
||||||
mv "cmake-${cmake_version}-macos-universal/CMake.app/Contents" Launcher/artifacts/macos/cmake
|
|
||||||
|
|
||||||
- name: Build Setup.pkg
|
|
||||||
env:
|
|
||||||
PACKAGE_VERSION: ${{ inputs.version }}
|
|
||||||
TAG_VERSION: ${{ github.ref_name }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
package_version="$PACKAGE_VERSION"
|
|
||||||
if [[ -z "$package_version" ]]; then package_version="${TAG_VERSION#v}"; fi
|
|
||||||
mkdir -p Launcher/dist
|
|
||||||
Launcher/macos/build-setup-pkg.command \
|
|
||||||
--nodtool-arm64 Launcher/artifacts/macos/nodtool-arm64 \
|
|
||||||
--nodtool-x86_64 Launcher/artifacts/macos/nodtool-x86_64 \
|
|
||||||
--translator-arm64 Launcher/artifacts/macos/translator-arm64/Translator.Cli \
|
|
||||||
--translator-x86_64 Launcher/artifacts/macos/translator-x86_64/Translator.Cli \
|
|
||||||
--cmake-root Launcher/artifacts/macos/cmake \
|
|
||||||
--ninja-arm64 Launcher/artifacts/macos/ninja/ninja \
|
|
||||||
--ninja-x86_64 Launcher/artifacts/macos/ninja/ninja \
|
|
||||||
--output Launcher/dist/WiiCompiled-Setup.pkg \
|
|
||||||
--version "$package_version"
|
|
||||||
|
|
||||||
- name: Verify package layout and architecture-specific tools
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
pkgutil --check-signature Launcher/dist/WiiCompiled-Setup.pkg
|
|
||||||
! pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \
|
|
||||||
grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)'
|
|
||||||
expanded="$RUNNER_TEMP/wiicompiled-setup-expanded"
|
|
||||||
pkgutil --expand-full Launcher/dist/WiiCompiled-Setup.pkg "$expanded"
|
|
||||||
resources="$expanded/Payload/Applications/WiiCompiled Setup.app/Contents/Resources"
|
|
||||||
for arch in arm64 x86_64; do
|
|
||||||
for tool in nodtool Translator.Cli ninja; do
|
|
||||||
lipo "$resources/tools/$arch/$tool" -verify_arch "$arch"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
lipo "$resources/tools/cmake/bin/cmake" -verify_arch arm64 x86_64
|
|
||||||
bash "$resources/setup.command" --help
|
|
||||||
/usr/bin/arch -x86_64 /bin/bash "$resources/setup.command" --help
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: WiiCompiled-Setup-macos-universal
|
|
||||||
path: Launcher/dist/WiiCompiled-Setup.pkg
|
|
||||||
if-no-files-found: error
|
|
||||||
archive: false
|
|
||||||
|
|
||||||
# Publishes the packaged installers as a GitHub Release whenever a v* tag is pushed. Wheel Wizard
|
# Publishes the packaged installers as a GitHub Release whenever a v* tag is pushed. Wheel Wizard
|
||||||
# discovers updates from these releases, so the contract it relies on is enforced here: a full
|
# discovers updates from these releases, so the contract it relies on is enforced here: a full
|
||||||
# (non-prerelease) release whose tag is v<semver>, carrying the expected platform assets,
|
# (non-prerelease) release whose tag is v<semver>, carrying an asset named exactly
|
||||||
# produced by setup hosts that report that same version.
|
# WiiCompiled-Setup.exe, produced by a setup host that reports that same version.
|
||||||
release:
|
release:
|
||||||
name: Publish GitHub Release
|
name: Publish GitHub Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
needs: [linux-appimage, windows-installer, macos-setup-package, recompilation]
|
needs: [linux-appimage, windows-installer, recompilation]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -264,7 +143,7 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ls -lR artifacts
|
ls -lR artifacts
|
||||||
assets=()
|
assets=()
|
||||||
for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage WiiCompiled-Setup.pkg; do
|
for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage; do
|
||||||
found="$(find artifacts -type f -name "$name" | head -n 1)"
|
found="$(find artifacts -type f -name "$name" | head -n 1)"
|
||||||
[ -n "$found" ] && [ -s "$found" ] || { echo "::error::missing release asset $name"; exit 1; }
|
[ -n "$found" ] && [ -s "$found" ] || { echo "::error::missing release asset $name"; exit 1; }
|
||||||
assets+=("$found")
|
assets+=("$found")
|
||||||
|
|||||||
@@ -28,6 +28,19 @@ jobs:
|
|||||||
path: Launcher/artifacts/downloads
|
path: Launcher/artifacts/downloads
|
||||||
key: windows-recomp-downloads-${{ hashFiles('Launcher/Prepare-PortableTools.ps1', 'Launcher/Prepare-Dependencies.ps1') }}
|
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
|
- name: Prepare the shipped Windows toolchain
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: ./Launcher/Prepare-PortableTools.ps1
|
run: ./Launcher/Prepare-PortableTools.ps1
|
||||||
@@ -36,6 +49,13 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: ./Launcher/Prepare-Dependencies.ps1
|
run: ./Launcher/Prepare-Dependencies.ps1
|
||||||
|
|
||||||
|
# Expose cache token context to the build script.
|
||||||
- name: Translate, compile the full runtime, and link
|
- name: Translate, compile the full runtime, and link
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: ./Launcher/Test-Recompilation.ps1 -Parallel 3
|
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
|
||||||
|
|||||||
@@ -72,3 +72,5 @@ project.lock.json
|
|||||||
*.log
|
*.log
|
||||||
output.txt
|
output.txt
|
||||||
|
|
||||||
|
# Operating System
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -117,12 +117,13 @@ function Get-MkwProjectPins([string]$ProjectFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
|
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
|
||||||
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '') {
|
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '', [bool]$WaitForProcessTree = $true) {
|
||||||
<#
|
<#
|
||||||
Runs a build tool and turns a non-zero exit code into a described failure. Start-Process -Wait
|
Runs a build tool and turns a non-zero exit code into a described failure. By default,
|
||||||
is deliberate: it waits for the whole process tree, since a .NET single-file bundle host may
|
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. Start-Process
|
hand off to an extracted child that PowerShell's call operator would not wait for. Callers that
|
||||||
doesn't publish $LASTEXITCODE, so this sets it manually for callers that check it.
|
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.
|
||||||
#>
|
#>
|
||||||
@@ -132,9 +133,14 @@ function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Descri
|
|||||||
if ($_.Contains('"')) { throw "A native build argument contains an unsupported quote: $_" }
|
if ($_.Contains('"')) { throw "A native build argument contains an unsupported quote: $_" }
|
||||||
'"' + $_ + '"'
|
'"' + $_ + '"'
|
||||||
})
|
})
|
||||||
|
if ($WaitForProcessTree) {
|
||||||
$process = Start-Process -FilePath $FilePath -ArgumentList $quotedArguments `
|
$process = Start-Process -FilePath $FilePath -ArgumentList $quotedArguments `
|
||||||
-NoNewWindow -Wait -PassThru
|
-NoNewWindow -Wait -PassThru
|
||||||
$exitCode = $process.ExitCode
|
$exitCode = $process.ExitCode
|
||||||
|
} else {
|
||||||
|
& $FilePath @Arguments
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
}
|
||||||
$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." }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 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 (recomp.yml). Scripts read pinned facts through Get-MkwProjectPins, but three
|
||||||
# consumers can't read YAML (the C++ runtime header, the C# constants, hand-written lists on
|
# consumers can't read YAML (the C++ runtime header, the C# constants, shell scripts, and hand-written lists on
|
||||||
# both sides of the C#/PowerShell boundary), so those are checked here instead.
|
# both sides of the C#/PowerShell boundary), so those are checked here instead.
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param([string]$RepositoryRoot)
|
param([string]$RepositoryRoot)
|
||||||
@@ -58,6 +58,12 @@ $hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"
|
|||||||
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
|
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
|
||||||
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
||||||
}
|
}
|
||||||
|
$macosSetup = Read-SourceFile (Join-Path $launcher 'macos\setup.command') 'macOS setup.command'
|
||||||
|
$macosUri = Get-CapturedValue $macosSetup "'([^']*/api/wfc/payload\?g=RMCPD00)'" `
|
||||||
|
'The macOS Retro-WFC endpoint'
|
||||||
|
if ($macosUri -cne $pins.RetroWfcPayloadUri) {
|
||||||
|
Add-Failure "macOS setup.command downloads '$macosUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
||||||
|
}
|
||||||
|
|
||||||
# --- The game identity: the manifest carries it, but the host also compiles a fallback for a
|
# --- The game identity: the manifest carries it, but the host also compiles a fallback for a
|
||||||
# --- manifest that predates the field, and that fallback decides which disc is accepted.
|
# --- manifest that predates the field, and that fallback decides which disc is accepted.
|
||||||
|
|||||||
@@ -136,9 +136,11 @@ try {
|
|||||||
-CxxCompiler (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') `
|
-ResourceCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-windres.exe') `
|
||||||
-DependenciesDirectory $dependencies -AdditionalArguments @('-DMKW_BUILD_PRODUCTS=ON')
|
-DependenciesDirectory $dependencies -AdditionalArguments @('-DMKW_BUILD_PRODUCTS=ON')
|
||||||
Invoke-Checked $cmake $configure 'Configuring the production Windows runtime'
|
Invoke-Checked $cmake $configure 'Configuring the production Windows runtime' `
|
||||||
|
-WaitForProcessTree $false
|
||||||
Invoke-Checked $cmake @('--build', $nativeBuild, '--target', 'WiiCompiled', '--parallel', "$Parallel") `
|
Invoke-Checked $cmake @('--build', $nativeBuild, '--target', 'WiiCompiled', '--parallel', "$Parallel") `
|
||||||
'Compiling and linking the synthetic product with the full runtime'
|
'Compiling and linking the synthetic product with the full runtime' `
|
||||||
|
-WaitForProcessTree $false
|
||||||
Assert-File (Join-Path $nativeBuild 'WiiCompiled.exe') 'Linked synthetic product'
|
Assert-File (Join-Path $nativeBuild 'WiiCompiled.exe') 'Linked synthetic product'
|
||||||
} finally {
|
} finally {
|
||||||
$env:PATH = $oldPath
|
$env:PATH = $oldPath
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public static class RetroWfcPayload
|
|||||||
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";
|
public const string CurrentRetroWfcPayloadUri = "https://rwfc.net/api/wfc/payload?g=RMCPD00";
|
||||||
private static readonly string RetroWfcOfflinePayloadFile =
|
private static readonly string RetroWfcOfflinePayloadFile =
|
||||||
Path.Combine("binary", "payload.RMCPD00.bin");
|
Path.Combine("binary", "payload.RMCPD00.bin");
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Usage: local-build-macos.command --output-dir DIR [options]
|
|||||||
--retro-rewind-package-dir DIR RetroRewind6 directory (required for Retro Rewind)
|
--retro-rewind-package-dir DIR RetroRewind6 directory (required for Retro Rewind)
|
||||||
--retro-wfc-offline-dir DIR Directory containing binary/payload.RMCPD00.bin
|
--retro-wfc-offline-dir DIR Directory containing binary/payload.RMCPD00.bin
|
||||||
--skip-retro-wfc-payload Build Retro Rewind without the shared Retro-WFC payload
|
--skip-retro-wfc-payload Build Retro Rewind without the shared Retro-WFC payload
|
||||||
--force-clean-build Delete local generated and current-architecture native build caches
|
--force-clean-build Delete local generated and native-build-macos caches
|
||||||
--parallel N Pin translation and build parallelism
|
--parallel N Pin translation and build parallelism
|
||||||
--cmake PATH --ninja PATH Override build tools
|
--cmake PATH --ninja PATH Override build tools
|
||||||
--dotnet PATH Override dotnet
|
--dotnet PATH Override dotnet
|
||||||
@@ -56,9 +56,7 @@ while (($#)); do
|
|||||||
done
|
done
|
||||||
|
|
||||||
[[ $(uname -s) == Darwin ]] || fail 'this build script is for macOS only'
|
[[ $(uname -s) == Darwin ]] || fail 'this build script is for macOS only'
|
||||||
macos_arch=$(uname -m)
|
[[ $(uname -m) == arm64 ]] || fail 'the current macOS product target is Apple Silicon only'
|
||||||
case "$macos_arch" in arm64|x86_64) ;; *) fail "unsupported macOS architecture: $macos_arch" ;; esac
|
|
||||||
macos_deployment_target=12.0
|
|
||||||
workspace=$(cd "$workspace" && pwd)
|
workspace=$(cd "$workspace" && pwd)
|
||||||
[[ -n "$output_dir" ]] || fail '--output-dir is required'
|
[[ -n "$output_dir" ]] || fail '--output-dir is required'
|
||||||
case "$profile" in base|retro-rewind|both) ;; *) fail '--profile must be base, retro-rewind, or both' ;; esac
|
case "$profile" in base|retro-rewind|both) ;; *) fail '--profile must be base, retro-rewind, or both' ;; esac
|
||||||
@@ -75,8 +73,7 @@ for tool in "$cmake_bin" "$ninja_bin" clang clang++ shasum; do command -v "$tool
|
|||||||
|
|
||||||
project="$workspace/projects/mkwii/recomp.yml"; assets="$workspace/Assets"; generated="$workspace/generated"
|
project="$workspace/projects/mkwii/recomp.yml"; assets="$workspace/Assets"; generated="$workspace/generated"
|
||||||
functions="$generated/functions"; metadata="$generated/base_translation_output.json"; manifest_dir="$workspace/build/base"
|
functions="$generated/functions"; metadata="$generated/base_translation_output.json"; manifest_dir="$workspace/build/base"
|
||||||
manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards"
|
manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards"; native_build="$workspace/native-build-macos"
|
||||||
native_build="$workspace/native-build-macos-$macos_arch"
|
|
||||||
assert_file "$project" 'translation project'
|
assert_file "$project" 'translation project'
|
||||||
if [[ -n "$game" ]]; then "$script_dir/macos/extract-disc.command" --game "$game" --assets-dir "$assets" --nodtool "$nodtool"; fi
|
if [[ -n "$game" ]]; then "$script_dir/macos/extract-disc.command" --game "$game" --assets-dir "$assets" --nodtool "$nodtool"; fi
|
||||||
assert_file "$assets/main.dol" 'extracted main.dol'; assert_file "$assets/StaticR.rel" 'extracted StaticR.rel'
|
assert_file "$assets/main.dol" 'extracted main.dol'; assert_file "$assets/StaticR.rel" 'extracted StaticR.rel'
|
||||||
@@ -134,9 +131,9 @@ if (( builds_retro )); then args+=(--resolved-profile "$mod_out/resolved_dispatc
|
|||||||
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
|
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
|
||||||
|
|
||||||
step configure-native 'Configuring the native toolchain'
|
step configure-native 'Configuring the native toolchain'
|
||||||
"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DCMAKE_OSX_ARCHITECTURES="$macos_arch" -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target" -DAURORA_SDL3_PROVIDER=vendor -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs"
|
"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs"
|
||||||
targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind)
|
targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind)
|
||||||
step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs"
|
step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs"
|
||||||
if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi
|
if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}"; fi
|
||||||
if (( builds_retro )); then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product RetroRewind --output-dir "$output_dir" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi
|
if (( builds_retro )); then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product RetroRewind --output-dir "$output_dir"; fi
|
||||||
printf 'MKWCBUILD:OUTPUT=%s\n' "$output_dir"
|
printf 'MKWCBUILD:OUTPUT=%s\n' "$output_dir"
|
||||||
|
|||||||
@@ -9,15 +9,12 @@ fail() { printf 'build-setup-pkg.command: error: %s\n' "$*" >&2; exit 1; }
|
|||||||
copy_clean() { DITTONORSRC=1 ditto --norsrc --noqtn "$@"; }
|
copy_clean() { DITTONORSRC=1 ditto --norsrc --noqtn "$@"; }
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: build-setup-pkg.command --nodtool-arm64 PATH --nodtool-x86_64 PATH --translator-arm64 PATH --translator-x86_64 PATH --cmake-root DIR --ninja-arm64 PATH --ninja-x86_64 PATH --output PKG [options]
|
Usage: build-setup-pkg.command --nodtool PATH --translator PATH --cmake-root DIR --ninja PATH --output PKG [options]
|
||||||
|
|
||||||
Creates a game-code-free WiiCompiled Setup.pkg. The supplied tools must be
|
Creates a game-code-free WiiCompiled Setup.pkg. The supplied tools must be
|
||||||
maintainer-verified, redistributable macOS artifacts for both arm64 and
|
maintainer-verified, redistributable macOS arm64 artifacts. The resulting pkg
|
||||||
x86_64. The setup package selects native tools for its host while the game is
|
is unsigned unless --installer-identity is supplied; releases should sign and
|
||||||
compiled locally for that host architecture. CMake must be universal2. The
|
notarize it with a Developer ID Installer certificate.
|
||||||
resulting pkg is unsigned unless
|
|
||||||
--installer-identity is supplied; releases should sign and notarize it with a
|
|
||||||
Developer ID Installer certificate.
|
|
||||||
|
|
||||||
--workspace DIR Repository root (default: script's grandparent)
|
--workspace DIR Repository root (default: script's grandparent)
|
||||||
--version VERSION Bundle/package version (default: 0.1.0)
|
--version VERSION Bundle/package version (default: 0.1.0)
|
||||||
@@ -26,17 +23,14 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
workspace=$(cd "$script_dir/../.." && pwd); nodtool_arm64=""; nodtool_x86_64=""; translator_arm64=""; translator_x86_64=""; cmake_root=""; ninja_arm64=""; ninja_x86_64=""; output=""; version=0.1.0; identity=""
|
workspace=$(cd "$script_dir/../.." && pwd); nodtool=""; translator=""; cmake_root=""; ninja=""; output=""; version=0.1.0; identity=""
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--workspace) workspace=${2:-}; shift 2 ;;
|
--workspace) workspace=${2:-}; shift 2 ;;
|
||||||
--nodtool-arm64) nodtool_arm64=${2:-}; shift 2 ;;
|
--nodtool) nodtool=${2:-}; shift 2 ;;
|
||||||
--nodtool-x86_64) nodtool_x86_64=${2:-}; shift 2 ;;
|
--translator) translator=${2:-}; shift 2 ;;
|
||||||
--translator-arm64) translator_arm64=${2:-}; shift 2 ;;
|
|
||||||
--translator-x86_64) translator_x86_64=${2:-}; shift 2 ;;
|
|
||||||
--cmake-root) cmake_root=${2:-}; shift 2 ;;
|
--cmake-root) cmake_root=${2:-}; shift 2 ;;
|
||||||
--ninja-arm64) ninja_arm64=${2:-}; shift 2 ;;
|
--ninja) ninja=${2:-}; shift 2 ;;
|
||||||
--ninja-x86_64) ninja_x86_64=${2:-}; shift 2 ;;
|
|
||||||
--output) output=${2:-}; shift 2 ;;
|
--output) output=${2:-}; shift 2 ;;
|
||||||
--version) version=${2:-}; shift 2 ;;
|
--version) version=${2:-}; shift 2 ;;
|
||||||
--installer-identity) identity=${2:-}; shift 2 ;;
|
--installer-identity) identity=${2:-}; shift 2 ;;
|
||||||
@@ -48,48 +42,12 @@ version=${version#v}
|
|||||||
[[ "$version" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || fail '--version must contain one to three period-separated integers'
|
[[ "$version" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || fail '--version must contain one to three period-separated integers'
|
||||||
IFS=. read -r version_major version_minor version_patch <<< "$version"
|
IFS=. read -r version_major version_minor version_patch <<< "$version"
|
||||||
short_version="$version_major.${version_minor:-0}.${version_patch:-0}"
|
short_version="$version_major.${version_minor:-0}.${version_patch:-0}"
|
||||||
for tool in pkgbuild productbuild ditto codesign lipo; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done
|
for tool in pkgbuild productbuild ditto codesign; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done
|
||||||
for tool_path in "$nodtool_arm64" "$nodtool_x86_64" "$translator_arm64" "$translator_x86_64" "$ninja_arm64" "$ninja_x86_64"; do [[ -x "$tool_path" ]] || fail 'each architecture-specific tool must name an executable'; done
|
[[ -x "$nodtool" ]] || fail '--nodtool must name an executable'
|
||||||
|
[[ -x "$translator" ]] || fail '--translator must name an executable'
|
||||||
[[ -x "$cmake_root/bin/cmake" ]] || fail '--cmake-root must contain bin/cmake'
|
[[ -x "$cmake_root/bin/cmake" ]] || fail '--cmake-root must contain bin/cmake'
|
||||||
require_arch() {
|
[[ -x "$ninja" ]] || fail '--ninja must name an executable'
|
||||||
local artifact=$1 arch=$2 label=$3
|
"$nodtool" --version >/dev/null || fail '--nodtool did not run successfully'
|
||||||
lipo "$artifact" -verify_arch "$arch" >/dev/null 2>&1 || fail "$label must contain a $arch slice: $artifact"
|
|
||||||
}
|
|
||||||
require_arch "$nodtool_arm64" arm64 '--nodtool-arm64'; require_arch "$nodtool_x86_64" x86_64 '--nodtool-x86_64'
|
|
||||||
require_arch "$translator_arm64" arm64 '--translator-arm64'; require_arch "$translator_x86_64" x86_64 '--translator-x86_64'
|
|
||||||
require_arch "$ninja_arm64" arm64 '--ninja-arm64'; require_arch "$ninja_x86_64" x86_64 '--ninja-x86_64'
|
|
||||||
lipo "$cmake_root/bin/cmake" -verify_arch arm64 x86_64 >/dev/null 2>&1 || fail '--cmake-root/bin/cmake must be universal2'
|
|
||||||
|
|
||||||
# Slice checks above prevent accidental cross-architecture packaging. Exercise
|
|
||||||
# each supplied executable as well: an incorrectly bundled runtime can have a
|
|
||||||
# valid Mach-O header but still fail before the setup app can use it. Apple
|
|
||||||
# Silicon maintainers validate Intel tools through Rosetta when it is present.
|
|
||||||
host_arch=$(uname -m)
|
|
||||||
run_for_arch() {
|
|
||||||
local arch=$1 label=$2
|
|
||||||
shift 2
|
|
||||||
if [[ "$arch" == "$host_arch" ]]; then
|
|
||||||
"$@" >/dev/null || fail "$label did not run successfully"
|
|
||||||
elif [[ "$host_arch" == arm64 && "$arch" == x86_64 ]] && /usr/bin/arch -x86_64 /usr/bin/true >/dev/null 2>&1; then
|
|
||||||
/usr/bin/arch -x86_64 "$@" >/dev/null || fail "$label did not run successfully under Rosetta"
|
|
||||||
else
|
|
||||||
# Intel hosts cannot execute arm64 binaries. The slice remains checked
|
|
||||||
# above; CI or an Apple Silicon maintainer must execute that tool set.
|
|
||||||
printf 'build-setup-pkg.command: warning: unable to execute %s on %s; architecture slice was verified, but run it in %s CI before release\n' \
|
|
||||||
"$label" "$host_arch" "$arch" >&2
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
for arch in arm64 x86_64; do
|
|
||||||
if [[ "$arch" == arm64 ]]; then
|
|
||||||
nodtool=$nodtool_arm64; translator=$translator_arm64; ninja=$ninja_arm64
|
|
||||||
else
|
|
||||||
nodtool=$nodtool_x86_64; translator=$translator_x86_64; ninja=$ninja_x86_64
|
|
||||||
fi
|
|
||||||
run_for_arch "$arch" "--nodtool-$arch" "$nodtool" --version
|
|
||||||
run_for_arch "$arch" "--translator-$arch" "$translator" --help
|
|
||||||
run_for_arch "$arch" "--ninja-$arch" "$ninja" --version
|
|
||||||
done
|
|
||||||
run_for_arch "$host_arch" '--cmake-root/bin/cmake' "$cmake_root/bin/cmake" --version
|
|
||||||
workspace=$(cd "$workspace" && pwd); output=$(cd "$(dirname "$output")" && pwd)/$(basename "$output")
|
workspace=$(cd "$workspace" && pwd); output=$(cd "$(dirname "$output")" && pwd)/$(basename "$output")
|
||||||
stage=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-pkg.XXXXXX")
|
stage=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-pkg.XXXXXX")
|
||||||
trap 'rm -rf "$stage"' EXIT
|
trap 'rm -rf "$stage"' EXIT
|
||||||
@@ -106,7 +64,7 @@ cat > "$app/Contents/Info.plist" <<EOF
|
|||||||
<key>CFBundlePackageType</key><string>APPL</string>
|
<key>CFBundlePackageType</key><string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key><string>$short_version</string>
|
<key>CFBundleShortVersionString</key><string>$short_version</string>
|
||||||
<key>CFBundleVersion</key><string>$version</string>
|
<key>CFBundleVersion</key><string>$version</string>
|
||||||
<key>LSMinimumSystemVersion</key><string>12.0</string>
|
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
||||||
</dict></plist>
|
</dict></plist>
|
||||||
EOF
|
EOF
|
||||||
cat > "$app/Contents/MacOS/WiiCompiledSetup" <<'EOF'
|
cat > "$app/Contents/MacOS/WiiCompiledSetup" <<'EOF'
|
||||||
@@ -145,17 +103,11 @@ copy_clean "$workspace/Launcher/local-build-macos.command" "$resources/workspace
|
|||||||
copy_clean "$workspace/Launcher/macos/extract-disc.command" "$resources/workspace/Launcher/macos/extract-disc.command"
|
copy_clean "$workspace/Launcher/macos/extract-disc.command" "$resources/workspace/Launcher/macos/extract-disc.command"
|
||||||
copy_clean "$workspace/Launcher/macos/publish-app.command" "$resources/workspace/Launcher/macos/publish-app.command"
|
copy_clean "$workspace/Launcher/macos/publish-app.command" "$resources/workspace/Launcher/macos/publish-app.command"
|
||||||
chmod +x "$resources/workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/macos/"*.command
|
chmod +x "$resources/workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/macos/"*.command
|
||||||
# setup.command uses this marker to refresh source inputs in an existing user
|
mkdir -p "$resources/tools/cmake"
|
||||||
# workspace without replacing extracted game assets or Retro Rewind files.
|
copy_clean "$nodtool" "$resources/tools/nodtool"; chmod +x "$resources/tools/nodtool"
|
||||||
printf '%s\n' "$version" > "$resources/workspace/.bundle-version"
|
copy_clean "$translator" "$resources/tools/Translator.Cli"; chmod +x "$resources/tools/Translator.Cli"
|
||||||
mkdir -p "$resources/tools/cmake" "$resources/tools/arm64" "$resources/tools/x86_64"
|
|
||||||
copy_clean "$nodtool_arm64" "$resources/tools/arm64/nodtool"; chmod +x "$resources/tools/arm64/nodtool"
|
|
||||||
copy_clean "$nodtool_x86_64" "$resources/tools/x86_64/nodtool"; chmod +x "$resources/tools/x86_64/nodtool"
|
|
||||||
copy_clean "$translator_arm64" "$resources/tools/arm64/Translator.Cli"; chmod +x "$resources/tools/arm64/Translator.Cli"
|
|
||||||
copy_clean "$translator_x86_64" "$resources/tools/x86_64/Translator.Cli"; chmod +x "$resources/tools/x86_64/Translator.Cli"
|
|
||||||
copy_clean "$cmake_root" "$resources/tools/cmake"
|
copy_clean "$cmake_root" "$resources/tools/cmake"
|
||||||
copy_clean "$ninja_arm64" "$resources/tools/arm64/ninja"; chmod +x "$resources/tools/arm64/ninja"
|
copy_clean "$ninja" "$resources/tools/ninja"; chmod +x "$resources/tools/ninja"
|
||||||
copy_clean "$ninja_x86_64" "$resources/tools/x86_64/ninja"; chmod +x "$resources/tools/x86_64/ninja"
|
|
||||||
copy_clean "$workspace/LICENSE" "$resources/LICENSE"
|
copy_clean "$workspace/LICENSE" "$resources/LICENSE"
|
||||||
copy_clean "$workspace/THIRD-PARTY-NOTICES.md" "$resources/THIRD-PARTY-NOTICES.md"
|
copy_clean "$workspace/THIRD-PARTY-NOTICES.md" "$resources/THIRD-PARTY-NOTICES.md"
|
||||||
codesign --force --deep --sign - "$app"
|
codesign --force --deep --sign - "$app"
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Cross-compile a thin x86_64 macOS build from an Apple Silicon Mac.
|
|
||||||
# Pass this file on the first configure with:
|
|
||||||
# -DCMAKE_TOOLCHAIN_FILE=/absolute/path/to/macos-x86_64-toolchain.cmake
|
|
||||||
#
|
|
||||||
# CMAKE_SYSTEM_PROCESSOR is deliberately declared here rather than inferred
|
|
||||||
# from CMAKE_OSX_ARCHITECTURES, so target-aware CMake dependencies select their
|
|
||||||
# x86_64 artifacts.
|
|
||||||
set(CMAKE_SYSTEM_NAME Darwin)
|
|
||||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
|
||||||
set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING
|
|
||||||
"Target macOS architectures" FORCE)
|
|
||||||
@@ -5,36 +5,28 @@ set -euo pipefail
|
|||||||
fail() { printf 'publish-app.command: error: %s\n' "$*" >&2; exit 1; }
|
fail() { printf 'publish-app.command: error: %s\n' "$*" >&2; exit 1; }
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: publish-app.command --build-dir DIR --product {WiiCompiled|RetroRewind} --output-dir DIR [options]
|
Usage: publish-app.command --build-dir DIR --product {WiiCompiled|RetroRewind} --output-dir DIR
|
||||||
|
|
||||||
Copies a locally built product and its runtime assets into OUTPUT-DIR/<product>.app.
|
Copies a locally built product and its runtime assets into OUTPUT-DIR/<product>.app.
|
||||||
It bundles non-system dylibs, rewrites their install names, and ad-hoc signs the
|
It bundles non-system dylibs, rewrites their install names, and ad-hoc signs the
|
||||||
result. This is suitable for local use; a release must replace ad-hoc signing
|
result. This is suitable for local use; a release must replace ad-hoc signing
|
||||||
with the project's Developer ID signing and notarization process.
|
with the project's Developer ID signing and notarization process.
|
||||||
|
|
||||||
--architecture {arm64|x86_64} Required architecture of the compiled product (default: host)
|
|
||||||
--minimum-system-version VERSION App bundle minimum macOS version (default: 12.0)
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
build_dir=""; product=""; output_dir=""; architecture=$(uname -m); minimum_system_version=12.0
|
build_dir=""; product=""; output_dir=""
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--build-dir) build_dir=${2:-}; shift 2 ;;
|
--build-dir) build_dir=${2:-}; shift 2 ;;
|
||||||
--product) product=${2:-}; shift 2 ;;
|
--product) product=${2:-}; shift 2 ;;
|
||||||
--output-dir) output_dir=${2:-}; shift 2 ;;
|
--output-dir) output_dir=${2:-}; shift 2 ;;
|
||||||
--architecture) architecture=${2:-}; shift 2 ;;
|
|
||||||
--minimum-system-version) minimum_system_version=${2:-}; shift 2 ;;
|
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) fail "unknown option: $1" ;;
|
*) fail "unknown option: $1" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
[[ "$product" == WiiCompiled || "$product" == RetroRewind ]] || fail '--product must be WiiCompiled or RetroRewind'
|
[[ "$product" == WiiCompiled || "$product" == RetroRewind ]] || fail '--product must be WiiCompiled or RetroRewind'
|
||||||
[[ "$architecture" == arm64 || "$architecture" == x86_64 ]] || fail '--architecture must be arm64 or x86_64'
|
for tool in codesign ditto install_name_tool otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
|
||||||
[[ "$minimum_system_version" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]] || fail '--minimum-system-version must contain two or three period-separated integers'
|
|
||||||
for tool in codesign ditto install_name_tool lipo otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
|
|
||||||
[[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product"
|
[[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product"
|
||||||
lipo "$build_dir/$product" -verify_arch "$architecture" || fail "compiled product is not $architecture: $build_dir/$product"
|
|
||||||
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done
|
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done
|
||||||
|
|
||||||
app="$output_dir/$product.app"
|
app="$output_dir/$product.app"
|
||||||
@@ -55,7 +47,7 @@ cat > "$app/Contents/Info.plist" <<EOF
|
|||||||
<key>CFBundlePackageType</key><string>APPL</string>
|
<key>CFBundlePackageType</key><string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key><string>0.1.0</string>
|
<key>CFBundleShortVersionString</key><string>0.1.0</string>
|
||||||
<key>CFBundleVersion</key><string>1</string>
|
<key>CFBundleVersion</key><string>1</string>
|
||||||
<key>LSMinimumSystemVersion</key><string>$minimum_system_version</string>
|
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
||||||
<key>NSHighResolutionCapable</key><true/>
|
<key>NSHighResolutionCapable</key><true/>
|
||||||
</dict></plist>
|
</dict></plist>
|
||||||
EOF
|
EOF
|
||||||
@@ -65,62 +57,25 @@ for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do
|
|||||||
ln -s "../Resources/$asset" "$macos/$asset"
|
ln -s "../Resources/$asset" "$macos/$asset"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Resolve a non-system dependency from the build product's rpaths. This covers
|
# Build a closure of Homebrew dylibs. System libraries remain system references.
|
||||||
# both traditional Homebrew dylibs and the vendored dylibs CMake emits under
|
|
||||||
# the local build directory for a cross-architecture build.
|
|
||||||
dependency_path() {
|
|
||||||
local current=$1 dependency=$2 name rpath candidate
|
|
||||||
case "$dependency" in
|
|
||||||
/opt/homebrew/*|/usr/local/*)
|
|
||||||
[[ -f "$dependency" ]] && { printf '%s\n' "$dependency"; return 0; }
|
|
||||||
;;
|
|
||||||
@rpath/*)
|
|
||||||
name=${dependency##*/}
|
|
||||||
while IFS= read -r rpath; do
|
|
||||||
case "$rpath" in
|
|
||||||
@loader_path/*) rpath="$(dirname "$current")/${rpath#@loader_path/}" ;;
|
|
||||||
@executable_path/*) rpath="$macos/${rpath#@executable_path/}" ;;
|
|
||||||
esac
|
|
||||||
candidate="$rpath/$name"
|
|
||||||
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
|
||||||
done < <(otool -l "$current" | awk '
|
|
||||||
/LC_RPATH/ { rpath = 1; next }
|
|
||||||
rpath && /^[[:space:]]*path / {
|
|
||||||
sub(/^[[:space:]]*path /, "")
|
|
||||||
sub(/ \(offset [0-9]+\)$/, "")
|
|
||||||
print
|
|
||||||
rpath = 0
|
|
||||||
}
|
|
||||||
')
|
|
||||||
;;
|
|
||||||
@loader_path/*)
|
|
||||||
candidate="$(dirname "$current")/${dependency#@loader_path/}"
|
|
||||||
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Build a closure of non-system dylibs. System libraries remain system
|
|
||||||
# references, while every resolved dependency is copied beside the executable.
|
|
||||||
queue=("$macos/$product")
|
queue=("$macos/$product")
|
||||||
while ((${#queue[@]})); do
|
while ((${#queue[@]})); do
|
||||||
current=${queue[0]}
|
current=${queue[0]}
|
||||||
queue=("${queue[@]:1}")
|
queue=("${queue[@]:1}")
|
||||||
while IFS= read -r dependency; do
|
while IFS= read -r dependency; do
|
||||||
dependency_path=$(dependency_path "$current" "$dependency") || continue
|
[[ "$dependency" == /opt/homebrew/* || "$dependency" == /usr/local/* ]] || continue
|
||||||
|
[[ -f "$dependency" ]] || continue
|
||||||
name=$(basename "$dependency")
|
name=$(basename "$dependency")
|
||||||
if [[ ! -f "$frameworks/$name" ]]; then
|
if [[ ! -f "$frameworks/$name" ]]; then
|
||||||
ditto "$dependency_path" "$frameworks/$name"
|
ditto "$dependency" "$frameworks/$name"
|
||||||
install_name_tool -id "@rpath/$name" "$frameworks/$name"
|
install_name_tool -id "@rpath/$name" "$frameworks/$name"
|
||||||
# Resolve transitive @loader_path dependencies relative to the
|
queue+=("$frameworks/$name")
|
||||||
# original dylib, not its copied Frameworks location.
|
|
||||||
queue+=("$dependency_path")
|
|
||||||
fi
|
fi
|
||||||
done < <(otool -L "$current" | tail -n +2 | awk '{print $1}')
|
done < <(otool -L "$current" | tail -n +2 | awk '{print $1}')
|
||||||
done
|
done
|
||||||
while IFS= read -r binary; do
|
while IFS= read -r binary; do
|
||||||
while IFS= read -r old; do
|
while IFS= read -r old; do
|
||||||
|
[[ "$old" == /opt/homebrew/* || "$old" == /usr/local/* ]] || continue
|
||||||
name=$(basename "$old")
|
name=$(basename "$old")
|
||||||
[[ -f "$frameworks/$name" ]] || continue
|
[[ -f "$frameworks/$name" ]] || continue
|
||||||
if [[ "$binary" == "$macos/$product" ]]; then
|
if [[ "$binary" == "$macos/$product" ]]; then
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ set -euo pipefail
|
|||||||
|
|
||||||
resources=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
resources=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
workspace_source="$resources/workspace"
|
workspace_source="$resources/workspace"
|
||||||
|
nodtool="$resources/tools/nodtool"
|
||||||
|
translator="$resources/tools/Translator.Cli"
|
||||||
cmake_bin="$resources/tools/cmake/bin/cmake"
|
cmake_bin="$resources/tools/cmake/bin/cmake"
|
||||||
|
ninja_bin="$resources/tools/ninja"
|
||||||
support_root="$HOME/Library/Application Support/WiiCompiled"
|
support_root="$HOME/Library/Application Support/WiiCompiled"
|
||||||
workspace="$support_root/BuildWorkspace"
|
workspace="$support_root/BuildWorkspace"
|
||||||
products="$support_root/Products"
|
products="$support_root/Products"
|
||||||
@@ -33,13 +36,6 @@ while (($#)); do
|
|||||||
done
|
done
|
||||||
[[ "$install_location" == user || "$install_location" == applications ]] || fail '--install-location must be user or applications'
|
[[ "$install_location" == user || "$install_location" == applications ]] || fail '--install-location must be user or applications'
|
||||||
|
|
||||||
host_arch=$(uname -m)
|
|
||||||
case "$host_arch" in arm64|x86_64) ;; *) fail "unsupported macOS architecture: $host_arch" ;; esac
|
|
||||||
host_tools="$resources/tools/$host_arch"
|
|
||||||
nodtool="$host_tools/nodtool"
|
|
||||||
translator="$host_tools/Translator.Cli"
|
|
||||||
ninja_bin="$host_tools/ninja"
|
|
||||||
|
|
||||||
if [[ -z "$game" ]]; then
|
if [[ -z "$game" ]]; then
|
||||||
game=$(/usr/bin/osascript <<'APPLESCRIPT'
|
game=$(/usr/bin/osascript <<'APPLESCRIPT'
|
||||||
set selectedFile to choose file with prompt "Choose your clean Mario Kart Wii PAL (RMCP01) disc image"
|
set selectedFile to choose file with prompt "Choose your clean Mario Kart Wii PAL (RMCP01) disc image"
|
||||||
@@ -64,36 +60,12 @@ if ! /usr/bin/xcode-select -p >/dev/null 2>&1; then
|
|||||||
/usr/bin/xcode-select --install || true
|
/usr/bin/xcode-select --install || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
for tool in "$nodtool" "$translator" "$cmake_bin" "$ninja_bin"; do
|
|
||||||
/usr/bin/lipo "$tool" -verify_arch "$host_arch" >/dev/null 2>&1 || \
|
|
||||||
fail "the packaged $(basename "$tool") does not support $host_arch"
|
|
||||||
done
|
|
||||||
|
|
||||||
mkdir -p "$support_root" "$products"
|
mkdir -p "$support_root" "$products"
|
||||||
source_bundle_version="$workspace_source/.bundle-version"
|
if [[ ! -d "$workspace/.git" && ! -f "$workspace/projects/mkwii/recomp.yml" ]]; then
|
||||||
workspace_bundle_version="$workspace/.bundle-version"
|
|
||||||
needs_workspace_refresh=0
|
|
||||||
if [[ ! -f "$workspace/projects/mkwii/recomp.yml" ]]; then
|
|
||||||
needs_workspace_refresh=1
|
|
||||||
elif [[ -f "$source_bundle_version" ]] && [[ ! -f "$workspace_bundle_version" || "$(<"$source_bundle_version")" != "$(<"$workspace_bundle_version")" ]]; then
|
|
||||||
needs_workspace_refresh=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( needs_workspace_refresh )); then
|
|
||||||
printf 'Preparing the local build workspace...\n'
|
printf 'Preparing the local build workspace...\n'
|
||||||
if [[ ! -d "$workspace" ]]; then
|
rm -rf "$workspace"
|
||||||
/usr/bin/ditto "$workspace_source" "$workspace"
|
/usr/bin/ditto "$workspace_source" "$workspace"
|
||||||
else
|
|
||||||
# Refresh only packaged source inputs. Assets and the staged Retro
|
|
||||||
# Rewind package belong to the user and stay in place.
|
|
||||||
for source in aurora-main projects runtime translator Launcher; do
|
|
||||||
/usr/bin/ditto "$workspace_source/$source" "$workspace/$source"
|
|
||||||
done
|
|
||||||
/usr/bin/ditto "$source_bundle_version" "$workspace_bundle_version"
|
|
||||||
# A dependency provider can be cached in this directory, so make the
|
|
||||||
# refreshed sources configure from a clean native build tree.
|
|
||||||
rm -rf "$workspace/native-build-macos-arm64" "$workspace/native-build-macos-x86_64"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
profile=base
|
profile=base
|
||||||
@@ -118,7 +90,7 @@ if [[ -n "$retro_dir" ]]; then
|
|||||||
trap 'rm -rf "$payload_stage"' EXIT
|
trap 'rm -rf "$payload_stage"' EXIT
|
||||||
/usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
|
/usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
|
||||||
--retry 1 --output "$temporary_payload" \
|
--retry 1 --output "$temporary_payload" \
|
||||||
'http://nas.play.rwfc.net/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
|
'https://rwfc.net/api/wfc/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
|
||||||
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
|
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
|
||||||
fail 'downloaded Retro-WFC payload failed signature validation'
|
fail 'downloaded Retro-WFC payload failed signature validation'
|
||||||
mkdir -p "$retro_wfc_dir/binary"
|
mkdir -p "$retro_wfc_dir/binary"
|
||||||
|
|||||||
@@ -233,5 +233,6 @@ EOF
|
|||||||
rm -rf "$test_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))"
|
||||||
|
|||||||
@@ -54,10 +54,24 @@ Press **F10** while the game window has focus:
|
|||||||
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.**
|
**Real controller support.**
|
||||||
|
|
||||||
Controllers are fed to the game as a GameCube controller.
|
Controllers are fed to the game as a GameCube controller.
|
||||||
Mappings are positional (`south`, `east`, `west`, `north`) rather than Xbox-labelled, so the
|
Mappings are positional (`south`, `east`, `west`, `north`) rather than Xbox-labelled, so the
|
||||||
same config makes sense on Xbox, PlayStation, Nintendo and generic SDL pads alike, and extra
|
same config makes sense on Xbox, PlayStation, Nintendo and generic SDL pads alike, and extra
|
||||||
inputs like paddles, touchpads and share buttons show up when the hardware reports them.
|
inputs like paddles, touchpads and share buttons show up when the hardware reports them.
|
||||||
|
Both button-binding slots also accept SDL triggers and stick directions. Selecting an analog
|
||||||
|
input shows a threshold slider beneath it (1–100%, default 50%); reaching that amount of travel
|
||||||
|
holds the chosen digital button. Each binding's threshold is saved independently in `Config.toml`
|
||||||
|
(for example, `a = "right_trigger@35,south"`).
|
||||||
|
|
||||||
|
**Keyboard and Mouse support.**
|
||||||
|
|
||||||
|
Keyboard and mouse are also available through **F10 > Controller settings > Keyboard and mouse**
|
||||||
|
for each port. Enabling this replaces that port's gamepad input. The default preset uses WASD
|
||||||
|
for the main stick, left mouse for A (accelerate), Space for B (brake), right mouse for R
|
||||||
|
(drift), middle mouse for Z (item), arrow keys for the D-pad (tricks), and Enter for Start.
|
||||||
|
Keys and mouse buttons can be remapped, including both sticks and triggers; mouse movement
|
||||||
|
is not used. These settings are saved in `keyboard_bindings.dat` and restored next launch.
|
||||||
|
|
||||||
**Dolphin-compatible input expressions.**
|
**Dolphin-compatible input expressions.**
|
||||||
Each GameCube control can carry an expression in Dolphin's input syntax, with the same operators
|
Each GameCube control can carry an expression in Dolphin's input syntax, with the same operators
|
||||||
@@ -89,8 +103,8 @@ Known limitations of the Wii Remote path:
|
|||||||
- GPU: GTX 1650 / RX 6400 / Arc A310 or higher
|
- GPU: GTX 1650 / RX 6400 / Arc A310 or higher
|
||||||
- CPU: Intel Core i5-8400 / AMD Ryzen 5 2600 (4c/6c, ~3.5GHz+) or higher
|
- CPU: Intel Core i5-8400 / AMD Ryzen 5 2600 (4c/6c, ~3.5GHz+) or higher
|
||||||
- About 20 GB of free disk space during installation (Final game size ~5 GB)
|
- About 20 GB of free disk space during installation (Final game size ~5 GB)
|
||||||
- macOS 12 (Monterey) or later on Apple Silicon (`arm64`) or Intel (`x86_64-v3`); pre-Haswell Intel CPUs are unsupported
|
- macOS 14 (Sonoma) or later on Apple Silicon
|
||||||
- On macOS, a Metal-capable GPU and Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing)
|
- On macOS, Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing)
|
||||||
- A clean, unmodified **PAL `RMCP01`** disc image of Mario Kart Wii, dumped by you. ISO, GCM,
|
- A clean, unmodified **PAL `RMCP01`** disc image of Mario Kart Wii, dumped by you. ISO, GCM,
|
||||||
GCZ, CISO, WBFS, WIA and RVZ are accepted.
|
GCZ, CISO, WBFS, WIA and RVZ are accepted.
|
||||||
|
|
||||||
@@ -111,21 +125,6 @@ image under Settings, turn on **WiiCompiled (beta)**, and hit install from the H
|
|||||||
Wheel Wizard downloads the setup tool from this repo and walks you through install, updates and
|
Wheel Wizard downloads the setup tool from this repo and walks you through install, updates and
|
||||||
launching. The backend itself is deliberately command-line only, Wheel Wizard is a wrapper around it.
|
launching. The backend itself is deliberately command-line only, Wheel Wizard is a wrapper around it.
|
||||||
|
|
||||||
### macOS
|
|
||||||
|
|
||||||
Download `WiiCompiled-Setup.pkg` from this repository's Releases page and open it. It requires an
|
|
||||||
Apple Silicon Mac because its bundled nodtool and Translator.Cli executables are arm64. It installs
|
|
||||||
**WiiCompiled Setup** in Applications; open that app, choose your clean PAL `RMCP01` disc image,
|
|
||||||
and select either the base game or Retro Rewind. For Retro Rewind, choose the `RetroRewind6` folder
|
|
||||||
or its parent folder.
|
|
||||||
|
|
||||||
Setup verifies and extracts the image locally, then translates and compiles the native app on your
|
|
||||||
Mac. On a first run it may ask macOS to install Xcode Command Line Tools; complete Apple's installer,
|
|
||||||
then open Setup again. When the build completes, Setup asks for administrator approval once to install
|
|
||||||
`WiiCompiled.app` (and, if selected, `RetroRewind.app`) in `/Applications`.
|
|
||||||
|
|
||||||
Setup opens Terminal while it works, so the extraction and build progress—and any error that needs
|
|
||||||
reporting—remain visible.
|
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Only take builds from this repository's
|
> Only take builds from this repository's
|
||||||
@@ -165,7 +164,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
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,7 @@ if (_aurora_sdl3_provider STREQUAL "auto")
|
|||||||
set(_aurora_sdl3_provider "package")
|
set(_aurora_sdl3_provider "package")
|
||||||
else ()
|
else ()
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
||||||
# Aurora uses APIs from the SDL version pinned by AURORA_SDL3_VERSION.
|
find_package(SDL3 QUIET)
|
||||||
# Do not silently select an older system package and fail later while
|
|
||||||
# compiling its headers.
|
|
||||||
find_package(SDL3 ${AURORA_SDL3_VERSION} QUIET)
|
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
||||||
if (SDL3_FOUND)
|
if (SDL3_FOUND)
|
||||||
set(_aurora_sdl3_provider "system")
|
set(_aurora_sdl3_provider "system")
|
||||||
@@ -61,7 +58,7 @@ if (_aurora_sdl3_provider STREQUAL "system")
|
|||||||
message(STATUS "aurora: Using system SDL3 (provider=system)")
|
message(STATUS "aurora: Using system SDL3 (provider=system)")
|
||||||
if (NOT SDL3_FOUND)
|
if (NOT SDL3_FOUND)
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
||||||
find_package(SDL3 ${AURORA_SDL3_VERSION} REQUIRED)
|
find_package(SDL3 REQUIRED)
|
||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF)
|
||||||
endif ()
|
endif ()
|
||||||
_aurora_sdl3_select_target()
|
_aurora_sdl3_select_target()
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ if (AURORA_ENABLE_GX)
|
|||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_METAL)
|
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_METAL)
|
||||||
target_sources(aurora_core PRIVATE lib/dawn/MetalBinding.mm)
|
target_sources(aurora_core PRIVATE lib/dawn/MetalBinding.mm)
|
||||||
set_source_files_properties(lib/dawn/MetalBinding.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
set_source_files_properties(lib/dawn/MetalBinding.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||||
target_link_options(aurora_core PUBLIC
|
target_link_options(aurora_core PUBLIC "LINKER:-weak_framework,Metal")
|
||||||
"LINKER:-weak_framework,Metal"
|
|
||||||
"LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor")
|
|
||||||
endif ()
|
endif ()
|
||||||
if (DAWN_ENABLE_D3D11)
|
if (DAWN_ENABLE_D3D11)
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_D3D11)
|
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_D3D11)
|
||||||
|
|||||||
Vendored
-50
@@ -16,60 +16,10 @@ function(aurora_find_package_global)
|
|||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ${_PREV_FIND_PACKAGE_TARGETS_GLOBAL})
|
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ${_PREV_FIND_PACKAGE_TARGETS_GLOBAL})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# A macOS build for another architecture must not discover Homebrew packages
|
|
||||||
# built for its physical host. CMAKE_CROSSCOMPILING is not sufficient here:
|
|
||||||
# Apple Clang can target another architecture through CMAKE_OSX_ARCHITECTURES
|
|
||||||
# without CMake considering the configure a cross-build.
|
|
||||||
set(_AURORA_EXCLUDE_HOST_HOMEBREW FALSE)
|
|
||||||
if (APPLE AND CMAKE_OSX_ARCHITECTURES)
|
|
||||||
list(LENGTH CMAKE_OSX_ARCHITECTURES _AURORA_OSX_ARCH_COUNT)
|
|
||||||
if (_AURORA_OSX_ARCH_COUNT EQUAL 1)
|
|
||||||
list(GET CMAKE_OSX_ARCHITECTURES 0 _AURORA_TARGET_ARCH)
|
|
||||||
string(TOLOWER "${_AURORA_TARGET_ARCH}" _AURORA_TARGET_ARCH)
|
|
||||||
# hw.optional.arm64 identifies Apple Silicon even when CMake itself runs
|
|
||||||
# through Rosetta, where CMAKE_HOST_SYSTEM_PROCESSOR reports x86_64.
|
|
||||||
execute_process(
|
|
||||||
COMMAND /usr/sbin/sysctl -n hw.optional.arm64
|
|
||||||
RESULT_VARIABLE _AURORA_ARM64_PROBE_RESULT
|
|
||||||
OUTPUT_VARIABLE _AURORA_ARM64_PROBE
|
|
||||||
ERROR_QUIET
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
||||||
if (_AURORA_ARM64_PROBE_RESULT EQUAL 0 AND _AURORA_ARM64_PROBE STREQUAL "1")
|
|
||||||
set(_AURORA_HOST_ARCH arm64)
|
|
||||||
else ()
|
|
||||||
execute_process(
|
|
||||||
COMMAND /usr/bin/uname -m
|
|
||||||
OUTPUT_VARIABLE _AURORA_HOST_ARCH
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
||||||
string(TOLOWER "${_AURORA_HOST_ARCH}" _AURORA_HOST_ARCH)
|
|
||||||
endif ()
|
|
||||||
if (_AURORA_TARGET_ARCH STREQUAL "x86_64" AND _AURORA_HOST_ARCH MATCHES "^(arm64|aarch64)$")
|
|
||||||
list(APPEND CMAKE_IGNORE_PREFIX_PATH "/opt/homebrew")
|
|
||||||
set(_AURORA_EXCLUDE_HOST_HOMEBREW TRUE)
|
|
||||||
elseif (_AURORA_TARGET_ARCH MATCHES "^(arm64|aarch64)$" AND _AURORA_HOST_ARCH MATCHES "^(x86_64|amd64)$")
|
|
||||||
list(APPEND CMAKE_IGNORE_PREFIX_PATH "/usr/local")
|
|
||||||
set(_AURORA_EXCLUDE_HOST_HOMEBREW TRUE)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (_AURORA_EXCLUDE_HOST_HOMEBREW)
|
|
||||||
list(REMOVE_DUPLICATES CMAKE_IGNORE_PREFIX_PATH)
|
|
||||||
message(STATUS "aurora: cross-architecture macOS build; ignoring host Homebrew prefixes")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (AURORA_ENABLE_GX)
|
if (AURORA_ENABLE_GX)
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraDawnProvider.cmake)
|
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraDawnProvider.cmake)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# SDL's pkg-config probe can bypass CMake's prefix exclusion. macOS does not
|
|
||||||
# need libusb for the supported SDL input paths, so keep that host-only library
|
|
||||||
# out of a cross-architecture configure.
|
|
||||||
if (_AURORA_EXCLUDE_HOST_HOMEBREW)
|
|
||||||
set(SDL_HIDAPI_LIBUSB OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_HIDAPI_LIBUSB_SHARED OFF CACHE BOOL "" FORCE)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Abseil is needed for core libraries. It normally comes via Dawn's vendor build.
|
# Abseil is needed for core libraries. It normally comes via Dawn's vendor build.
|
||||||
# Otherwise prefer a system package and only fetch it as a last resort.
|
# Otherwise prefer a system package and only fetch it as a last resort.
|
||||||
if (NOT TARGET absl::flat_hash_map OR NOT TARGET absl::btree)
|
if (NOT TARGET absl::flat_hash_map OR NOT TARGET absl::btree)
|
||||||
|
|||||||
@@ -171,6 +171,22 @@ typedef struct PADButtonMapping {
|
|||||||
PADButton padButton;
|
PADButton padButton;
|
||||||
} PADButtonMapping;
|
} PADButtonMapping;
|
||||||
|
|
||||||
|
// Explicitly disabled, unlike INVALID which permits default L/R trigger input.
|
||||||
|
#define PAD_NATIVE_BUTTON_DISABLED 0xfffffffeu
|
||||||
|
|
||||||
|
// Axis-to-button bindings share the persisted nativeButton field without
|
||||||
|
// changing the binary layout of existing controller mapping files.
|
||||||
|
constexpr u32 PADEncodeAxisButton(u32 axis, bool negative, u32 threshold = 50) {
|
||||||
|
return 0x10000u | axis | (negative ? 0x80u : 0u) | (threshold << 8);
|
||||||
|
}
|
||||||
|
constexpr bool PADIsAxisButton(u32 binding) { return (binding & 0xffff0000u) == 0x10000u; }
|
||||||
|
constexpr u32 PADAxisButtonThreshold(u32 binding) { return (binding >> 8) & 0xffu; }
|
||||||
|
constexpr u32 PADAxisButtonAxis(u32 binding) { return binding & 0x7fu; }
|
||||||
|
constexpr bool PADAxisButtonNegative(u32 binding) { return (binding & 0x80u) != 0; }
|
||||||
|
constexpr u32 PADAxisButtonIdentity(u32 binding) {
|
||||||
|
return PADIsAxisButton(binding) ? (binding & ~0xff00u) : binding;
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct PADAxisMapping {
|
typedef struct PADAxisMapping {
|
||||||
PADSignedNativeAxis nativeAxis;
|
PADSignedNativeAxis nativeAxis;
|
||||||
s32 nativeButton;
|
s32 nativeButton;
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
#include <SDL3/SDL_mouse.h>
|
#include <SDL3/SDL_mouse.h>
|
||||||
#include <SDL3/SDL_joystick.h>
|
#include <SDL3/SDL_joystick.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int32_t k_mappingsFileVersion = 3;
|
constexpr int32_t k_mappingsFileVersion = 3;
|
||||||
@@ -319,6 +319,18 @@ std::array<bool, PAD_CHANMAX> g_suppressLeftTrigger{};
|
|||||||
std::array<bool, PAD_CHANMAX> g_suppressRightTrigger{};
|
std::array<bool, PAD_CHANMAX> g_suppressRightTrigger{};
|
||||||
|
|
||||||
bool is_mouse_scancode(const s32 scancode) { return scancode < PAD_KEY_INVALID; }
|
bool is_mouse_scancode(const s32 scancode) { return scancode < PAD_KEY_INVALID; }
|
||||||
|
bool is_native_binding_pressed(SDL_Gamepad* gamepad, u32 binding) {
|
||||||
|
if (PADIsAxisButton(binding)) {
|
||||||
|
const u32 axis = PADAxisButtonAxis(binding);
|
||||||
|
const u32 threshold = PADAxisButtonThreshold(binding);
|
||||||
|
if (axis >= SDL_GAMEPAD_AXIS_COUNT || threshold < 1 || threshold > 100) return false;
|
||||||
|
int value = SDL_GetGamepadAxis(gamepad, static_cast<SDL_GamepadAxis>(axis));
|
||||||
|
if (PADAxisButtonNegative(binding)) value = -value;
|
||||||
|
return value > 0 && value * 100 >= static_cast<int>(threshold) * 32767;
|
||||||
|
}
|
||||||
|
return binding < SDL_GAMEPAD_BUTTON_COUNT &&
|
||||||
|
SDL_GetGamepadButton(gamepad, static_cast<SDL_GamepadButton>(binding));
|
||||||
|
}
|
||||||
bool is_mouse_button_pressed(const s32 scancode) {
|
bool is_mouse_button_pressed(const s32 scancode) {
|
||||||
const int32_t buttonNum = -(scancode + 1);
|
const int32_t buttonNum = -(scancode + 1);
|
||||||
if (buttonNum < 1 || buttonNum > 5) {
|
if (buttonNum < 1 || buttonNum > 5) {
|
||||||
@@ -342,7 +354,7 @@ BOOL PADInit() {
|
|||||||
}
|
}
|
||||||
g_initialized = true;
|
g_initialized = true;
|
||||||
|
|
||||||
std::for_each(g_keyboardBindings.begin(), g_keyboardBindings.end(), [](auto& state) {
|
std::ranges::for_each(g_keyboardBindings, [](auto& state) {
|
||||||
state.m_buttonMapping = g_defaultKeys;
|
state.m_buttonMapping = g_defaultKeys;
|
||||||
state.m_axisMapping = g_defaultKeyAxis;
|
state.m_axisMapping = g_defaultKeyAxis;
|
||||||
});
|
});
|
||||||
@@ -634,8 +646,8 @@ static void EnsureMappingLoaded(aurora::input::GameController* controller) {
|
|||||||
|
|
||||||
static Sint16 _get_axis_value(const aurora::input::GameController* controller, // NOLINT(*-reserved-identifier)
|
static Sint16 _get_axis_value(const aurora::input::GameController* controller, // NOLINT(*-reserved-identifier)
|
||||||
PADAxis axis) {
|
PADAxis axis) {
|
||||||
const auto iter = std::find_if(controller->m_axisMapping.begin(), controller->m_axisMapping.end(),
|
const auto iter =
|
||||||
[axis](const auto& pair) { return pair.padAxis == axis; });
|
std::ranges::find_if(controller->m_axisMapping, [axis](const auto& pair) { return pair.padAxis == axis; });
|
||||||
if (iter == controller->m_axisMapping.end()) {
|
if (iter == controller->m_axisMapping.end()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -724,10 +736,10 @@ u32 PADRead(PADStatus* status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status[i].err = PAD_ERR_NONE;
|
status[i].err = PAD_ERR_NONE;
|
||||||
if (g_keyboardBindings[i].m_mappingsSet) {
|
if (g_keyboardBindings[i].m_mappingsSet && SDL_GetKeyboardFocus() != nullptr) {
|
||||||
std::for_each(g_keyboardBindings[i].m_buttonMapping.begin(), g_keyboardBindings[i].m_buttonMapping.end(),
|
std::ranges::for_each(
|
||||||
[&kbState, &i, &status](const PADKeyButtonBinding& mapping) {
|
g_keyboardBindings[i].m_buttonMapping, [&kbState, &numKeys, &i, &status](const PADKeyButtonBinding& mapping) {
|
||||||
if (mapping.scancode > PAD_KEY_INVALID && kbState[mapping.scancode]) {
|
if (mapping.scancode > PAD_KEY_INVALID && mapping.scancode < numKeys && kbState[mapping.scancode]) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
} else if (is_mouse_scancode(mapping.scancode) && is_mouse_button_pressed(mapping.scancode)) {
|
} else if (is_mouse_scancode(mapping.scancode) && is_mouse_button_pressed(mapping.scancode)) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
@@ -788,7 +800,7 @@ u32 PADRead(PADStatus* status) {
|
|||||||
status[i].triggerRight = static_cast<u8>(std::min(static_cast<int>(status[i].triggerRight) + tr, 255));
|
status[i].triggerRight = static_cast<u8>(std::min(static_cast<int>(status[i].triggerRight) + tr, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller) {
|
if (controller && !g_keyboardBindings[i].m_mappingsSet) {
|
||||||
EnsureMappingLoaded(controller);
|
EnsureMappingLoaded(controller);
|
||||||
|
|
||||||
// Wii U Pro Controller raw D-pad fallback. SDL's HIDAPI Wii driver posts
|
// Wii U Pro Controller raw D-pad fallback. SDL's HIDAPI Wii driver posts
|
||||||
@@ -833,9 +845,9 @@ u32 PADRead(PADStatus* status) {
|
|||||||
|
|
||||||
bool leftTriggerSet = false;
|
bool leftTriggerSet = false;
|
||||||
bool rightTriggerSet = false;
|
bool rightTriggerSet = false;
|
||||||
std::for_each(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(),
|
std::ranges::for_each(controller->m_buttonMapping, [&controller, &i, &status, &leftTriggerSet,
|
||||||
[&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) {
|
&rightTriggerSet](const auto& mapping) {
|
||||||
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(mapping.nativeButton))) {
|
if (is_native_binding_pressed(controller->m_controller, mapping.nativeButton)) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -847,12 +859,12 @@ u32 PADRead(PADStatus* status) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
std::for_each(controller->m_altButtonMapping.begin(), controller->m_altButtonMapping.end(),
|
std::ranges::for_each(controller->m_altButtonMapping, [&controller, &i, &status, &leftTriggerSet,
|
||||||
[&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) {
|
&rightTriggerSet](const auto& mapping) {
|
||||||
if (mapping.nativeButton == PAD_NATIVE_BUTTON_INVALID) {
|
if (mapping.nativeButton == PAD_NATIVE_BUTTON_INVALID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(mapping.nativeButton))) {
|
if (is_native_binding_pressed(controller->m_controller, mapping.nativeButton)) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,6 +958,17 @@ u32 PADRead(PADStatus* status) {
|
|||||||
Sint16 tl = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_L));
|
Sint16 tl = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_L));
|
||||||
Sint16 tr = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_R));
|
Sint16 tr = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_R));
|
||||||
|
|
||||||
|
// Games can read either the digital L/R bits or their analog pressure.
|
||||||
|
// An explicit button binding must drive both, otherwise the original
|
||||||
|
// L2/R2 axis still activates L/R even when it was rebound to L1/R1.
|
||||||
|
// Real GC pads retain independent analog travel and end-stop clicks.
|
||||||
|
if (!(controller->m_isGameCube ||
|
||||||
|
(SDL_GetGamepadType(controller->m_controller) == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO &&
|
||||||
|
controller->m_pid == 0x2073))) {
|
||||||
|
if (leftTriggerSet) tl = (status[i].button & PAD_TRIGGER_L) != 0 ? 32767 : 0;
|
||||||
|
if (rightTriggerSet) tr = (status[i].button & PAD_TRIGGER_R) != 0 ? 32767 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (controller->m_deadZones.emulateTriggers) {
|
if (controller->m_deadZones.emulateTriggers) {
|
||||||
if (!leftTriggerSet && tl > controller->m_deadZones.leftTriggerActivationZone) {
|
if (!leftTriggerSet && tl > controller->m_deadZones.leftTriggerActivationZone) {
|
||||||
status[i].button |= PAD_TRIGGER_L;
|
status[i].button |= PAD_TRIGGER_L;
|
||||||
@@ -990,12 +1013,13 @@ void PADControlMotor(const u32 chan, const u32 cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (controller->m_isGameCube) {
|
if (controller->m_isGameCube) {
|
||||||
if (cmd == PAD_MOTOR_STOP) {
|
if (cmd == PAD_MOTOR_STOP || cmd == PAD_MOTOR_STOP_HARD) {
|
||||||
aurora::input::controller_rumble(instance, 0, 1, 0);
|
// Use an unambiguous motor-off request. The (0, 1) coast encoding
|
||||||
|
// requires SDL's GameCube brake mode; other backends or an overridden
|
||||||
|
// hint interpret it as rumble and can leave the controller vibrating.
|
||||||
|
aurora::input::controller_rumble(instance, 0, 0, 0);
|
||||||
} else if (cmd == PAD_MOTOR_RUMBLE) {
|
} else if (cmd == PAD_MOTOR_RUMBLE) {
|
||||||
aurora::input::controller_rumble(instance, 1, 1, 0);
|
aurora::input::controller_rumble(instance, 1, 1, 0);
|
||||||
} else if (cmd == PAD_MOTOR_STOP_HARD) {
|
|
||||||
aurora::input::controller_rumble(instance, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (cmd == PAD_MOTOR_STOP) {
|
if (cmd == PAD_MOTOR_STOP) {
|
||||||
@@ -1165,7 +1189,7 @@ void PADSetButtonMapping(const u32 port, const PADButtonMapping mapping) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto iter = std::find_if(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(),
|
const auto iter = std::ranges::find_if(controller->m_buttonMapping,
|
||||||
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
|
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
|
||||||
if (iter == controller->m_buttonMapping.end()) {
|
if (iter == controller->m_buttonMapping.end()) {
|
||||||
return;
|
return;
|
||||||
@@ -1199,7 +1223,7 @@ void PADSetAltButtonMapping(const u32 port, const PADButtonMapping mapping) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto iter = std::find_if(controller->m_altButtonMapping.begin(), controller->m_altButtonMapping.end(),
|
const auto iter = std::ranges::find_if(controller->m_altButtonMapping,
|
||||||
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
|
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
|
||||||
if (iter == controller->m_altButtonMapping.end()) {
|
if (iter == controller->m_altButtonMapping.end()) {
|
||||||
return;
|
return;
|
||||||
@@ -1226,7 +1250,7 @@ void PADSetAxisMapping(const u32 port, const PADAxisMapping mapping) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto iter = std::find_if(controller->m_axisMapping.begin(), controller->m_axisMapping.end(),
|
const auto iter = std::ranges::find_if(controller->m_axisMapping,
|
||||||
[mapping](const auto& pair) { return mapping.padAxis == pair.padAxis; });
|
[mapping](const auto& pair) { return mapping.padAxis == pair.padAxis; });
|
||||||
if (iter == controller->m_axisMapping.end()) {
|
if (iter == controller->m_axisMapping.end()) {
|
||||||
return;
|
return;
|
||||||
@@ -1278,6 +1302,11 @@ BOOL PADSetKeyButtonBindings(const u32 port, PADKeyButtonBinding bindings[PAD_BU
|
|||||||
}
|
}
|
||||||
|
|
||||||
PADKeyButtonBinding* PADGetKeyButtonBindings(const u32 port, u32* buttonCount) {
|
PADKeyButtonBinding* PADGetKeyButtonBindings(const u32 port, u32* buttonCount) {
|
||||||
|
PADInit();
|
||||||
|
if (!g_keyboardBindingsLoaded) {
|
||||||
|
g_keyboardBindingsLoaded = true;
|
||||||
|
load_keyboard_bindings();
|
||||||
|
}
|
||||||
if (port >= PAD_MAX_CONTROLLERS || !g_keyboardBindings[port].m_mappingsSet) {
|
if (port >= PAD_MAX_CONTROLLERS || !g_keyboardBindings[port].m_mappingsSet) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -1396,10 +1425,9 @@ static void load_keyboard_bindings() {
|
|||||||
|
|
||||||
if (mappingsSet) {
|
if (mappingsSet) {
|
||||||
const bool anyBound =
|
const bool anyBound =
|
||||||
std::any_of(buttonMapping.begin(), buttonMapping.end(),
|
std::ranges::any_of(buttonMapping,
|
||||||
[](const PADKeyButtonBinding& b) { return b.scancode != PAD_KEY_INVALID; }) ||
|
[](const PADKeyButtonBinding& b) { return b.scancode != PAD_KEY_INVALID; }) ||
|
||||||
std::any_of(axisMapping.begin(), axisMapping.end(),
|
std::ranges::any_of(axisMapping, [](const PADKeyAxisBinding& b) { return b.scancode != PAD_KEY_INVALID; });
|
||||||
[](const PADKeyAxisBinding& b) { return b.scancode != PAD_KEY_INVALID; });
|
|
||||||
if (!anyBound) {
|
if (!anyBound) {
|
||||||
mappingsSet = false;
|
mappingsSet = false;
|
||||||
}
|
}
|
||||||
@@ -1439,10 +1467,7 @@ void __PADWriteDeadZones(SDL_IOStream* file, // NOLINT(*-reserved-identifier)
|
|||||||
void PADSerializeMappings() {
|
void PADSerializeMappings() {
|
||||||
const std::filesystem::path basePath = fs_path_from_string(aurora::g_config.userPath);
|
const std::filesystem::path basePath = fs_path_from_string(aurora::g_config.userPath);
|
||||||
|
|
||||||
// Avoid std::views::values here: older Apple libc++ releases implement the
|
for (auto& controller : aurora::input::g_GameControllers | std::views::values) {
|
||||||
// C++20 ranges algorithms we use but not this adaptor.
|
|
||||||
for (auto& entry : aurora::input::g_GameControllers) {
|
|
||||||
auto& controller = entry.second;
|
|
||||||
EnsureMappingLoaded(&controller);
|
EnsureMappingLoaded(&controller);
|
||||||
const auto filePath =
|
const auto filePath =
|
||||||
basePath / fmt::format("{}_{:04X}_{:04X}.controller", aurora::input::controller_name(controller.m_index),
|
basePath / fmt::format("{}_{:04X}_{:04X}.controller", aurora::input::controller_name(controller.m_index),
|
||||||
@@ -1544,8 +1569,8 @@ static constexpr std::array<std::pair<PADButton, std::string_view>, PAD_AXIS_COU
|
|||||||
|
|
||||||
const char* PADGetButtonName(const PADButton button) {
|
const char* PADGetButtonName(const PADButton button) {
|
||||||
|
|
||||||
if (const auto iter = std::find_if(skButtonNames.begin(), skButtonNames.end(),
|
if (const auto iter =
|
||||||
[&button](const auto& pair) { return button == pair.first; });
|
std::ranges::find_if(skButtonNames, [&button](const auto& pair) { return button == pair.first; });
|
||||||
iter != skButtonNames.end()) {
|
iter != skButtonNames.end()) {
|
||||||
return iter->second.data();
|
return iter->second.data();
|
||||||
}
|
}
|
||||||
@@ -1558,8 +1583,7 @@ const char* PADGetNativeButtonName(u32 button) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const char* PADGetAxisName(const PADAxis axis) {
|
const char* PADGetAxisName(const PADAxis axis) {
|
||||||
if (const auto it = std::find_if(skAxisNames.begin(), skAxisNames.end(),
|
if (const auto it = std::ranges::find_if(skAxisNames, [&axis](const auto& pair) { return axis == pair.first; });
|
||||||
[&axis](const auto& pair) { return axis == pair.first; });
|
|
||||||
it != skAxisNames.end()) {
|
it != skAxisNames.end()) {
|
||||||
return it->second.data();
|
return it->second.data();
|
||||||
}
|
}
|
||||||
@@ -1568,8 +1592,7 @@ const char* PADGetAxisName(const PADAxis axis) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const char* PADGetAxisDirectionLabel(const PADAxis axis) {
|
const char* PADGetAxisDirectionLabel(const PADAxis axis) {
|
||||||
if (const auto it = std::find_if(skAxisDirLabels.begin(), skAxisDirLabels.end(),
|
if (const auto it = std::ranges::find_if(skAxisDirLabels, [&axis](const auto& pair) { return axis == pair.first; });
|
||||||
[&axis](const auto& pair) { return axis == pair.first; });
|
|
||||||
it != skAxisDirLabels.end()) {
|
it != skAxisDirLabels.end()) {
|
||||||
return it->second.data();
|
return it->second.data();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
#include <absl/container/flat_hash_map.h>
|
#include <absl/container/flat_hash_map.h>
|
||||||
#include <magic_enum.hpp>
|
#include <magic_enum.hpp>
|
||||||
@@ -167,7 +168,12 @@ struct RenderPass {
|
|||||||
Range resolveUniformRange;
|
Range resolveUniformRange;
|
||||||
std::array<u32, 3> resolveCopyFilterCoefficients{0, 64, 0};
|
std::array<u32, 3> resolveCopyFilterCoefficients{0, 64, 0};
|
||||||
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
||||||
float clearDepthValue = 1.f;
|
// 1.f is the forward-Z "farthest" clear value; under UseReversedZ farthest is 0.f instead (see
|
||||||
|
// gx::clear_depth_value(), which the main render pass explicitly overrides this default with -
|
||||||
|
// any OTHER pass that keeps this default, e.g. an offscreen render-to-texture pass composited
|
||||||
|
// later, needs the same reversed-Z-aware value or its depth buffer starts "already nearest",
|
||||||
|
// failing every subsequent depth test and making whatever's drawn into it vanish).
|
||||||
|
float clearDepthValue = gx::UseReversedZ ? 0.f : 1.f;
|
||||||
CommandList commands;
|
CommandList commands;
|
||||||
bool clearColor = true;
|
bool clearColor = true;
|
||||||
bool clearDepth = true;
|
bool clearDepth = true;
|
||||||
@@ -756,7 +762,9 @@ void begin_offscreen(uint32_t width, uint32_t height) {
|
|||||||
.targetSize = {width, height, 1},
|
.targetSize = {width, height, 1},
|
||||||
.msaaSamples = 1,
|
.msaaSamples = 1,
|
||||||
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
|
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
|
||||||
.clearDepthValue = 1.f,
|
// See the RenderPass::clearDepthValue default's comment: this offscreen pass gets its own
|
||||||
|
// depth buffer, and the farthest clear value is 0.f, not 1.f, under UseReversedZ.
|
||||||
|
.clearDepthValue = gx::UseReversedZ ? 0.f : 1.f,
|
||||||
.clearColor = true,
|
.clearColor = true,
|
||||||
.clearDepth = true,
|
.clearDepth = true,
|
||||||
};
|
};
|
||||||
@@ -1327,8 +1335,8 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
|
|||||||
|
|
||||||
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
||||||
if (finalize && !g_debugGroupStack.empty()) {
|
if (finalize && !g_debugGroupStack.empty()) {
|
||||||
for (auto it = g_debugGroupStack.rbegin(); it != g_debugGroupStack.rend(); ++it) {
|
for (auto& it : std::ranges::reverse_view(g_debugGroupStack)) {
|
||||||
Log.warn("Debug group was not popped at end of frame: {}", *it);
|
Log.warn("Debug group was not popped at end of frame: {}", it);
|
||||||
}
|
}
|
||||||
g_debugGroupStack.clear();
|
g_debugGroupStack.clear();
|
||||||
}
|
}
|
||||||
@@ -1409,10 +1417,19 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
|
|||||||
switch (cmd.type) {
|
switch (cmd.type) {
|
||||||
case CommandType::SetViewport: {
|
case CommandType::SetViewport: {
|
||||||
const auto& vp = cmd.data.setViewport;
|
const auto& vp = cmd.data.setViewport;
|
||||||
// WebGPU requires 0 <= minDepth <= maxDepth <= 1, and the guest's (near, far) order is already
|
// WebGPU requires 0 <= minDepth <= maxDepth <= 1. vp.znear/vp.zfar are in GX's own distance
|
||||||
// reproduced in clip space. Passing the raw swapped pair diverged per backend in release builds.
|
// terms (0 = near); under UseReversedZ the host depth-buffer storage direction is flipped
|
||||||
const float minDepth = std::clamp(std::min(vp.znear, vp.zfar), 0.0f, 1.0f);
|
// (near = 1, far = 0), so this range has to be remapped through 1-x the same way the
|
||||||
const float maxDepth = std::clamp(std::max(vp.znear, vp.zfar), 0.0f, 1.0f);
|
// projection matrix, depth compare function, and clear value all are - a plain min/max clamp
|
||||||
|
// (the previous code here) maps a *restricted* range (e.g. a viewport deliberately narrowed
|
||||||
|
// to force something to draw "in front of everything") to the wrong end of the buffer: what
|
||||||
|
// should land near the near-storage-extreme (1.0) instead lands near the far-storage-extreme
|
||||||
|
// (0.0), so anything else drawn afterward at its true depth wins the compare test and the
|
||||||
|
// "in front" geometry silently vanishes. A full [0,1] viewport is unaffected either way,
|
||||||
|
// which is why this only broke specific elements, not the whole scene. Matches upstream
|
||||||
|
// aurora's apply_viewport (lib/gfx/encoding.cpp) exactly.
|
||||||
|
const float minDepth = gx::UseReversedZ ? 1.0f - vp.zfar : vp.znear;
|
||||||
|
const float maxDepth = gx::UseReversedZ ? 1.0f - vp.znear : vp.zfar;
|
||||||
pass.SetViewport(vp.left, vp.top, vp.width, vp.height, minDepth, maxDepth);
|
pass.SetViewport(vp.left, vp.top, vp.width, vp.height, minDepth, maxDepth);
|
||||||
} break;
|
} break;
|
||||||
case CommandType::SetScissor: {
|
case CommandType::SetScissor: {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ struct Params {
|
|||||||
|
|
||||||
constexpr std::string_view ReversedZBody = R"(
|
constexpr std::string_view ReversedZBody = R"(
|
||||||
fn gx_z24(depth: f32) -> u32 {
|
fn gx_z24(depth: f32) -> u32 {
|
||||||
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
|
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
|
||||||
}
|
}
|
||||||
)"sv;
|
)"sv;
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ fn gx_z24_at_coord(unclamped_coord: vec2i) -> u32 {
|
|||||||
let tex_size = vec2i(textureDimensions(src));
|
let tex_size = vec2i(textureDimensions(src));
|
||||||
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
|
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
|
||||||
let depth = textureLoad(src, coord, 0);
|
let depth = textureLoad(src, coord, 0);
|
||||||
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
|
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
|
||||||
}
|
}
|
||||||
)"s
|
)"s
|
||||||
: R"(
|
: R"(
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ void build_index() noexcept {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_replacementIndex.try_emplace(*parsed, ReplacementIndexEntry{path});
|
s_replacementIndex.try_emplace(*parsed, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("Indexed {} texture replacements", s_replacementIndex.size());
|
Log.info("Indexed {} texture replacements", s_replacementIndex.size());
|
||||||
|
|||||||
@@ -1416,23 +1416,32 @@ static inline GXBlendFactor remove_dst_alpha_usage(GXBlendFactor fac) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GX_LEQUAL etc. describe "pass if this pixel is closer than/equal to what's stored" in GX's own
|
||||||
|
// distance terms, independent of how that distance is encoded as a host depth value. Under
|
||||||
|
// UseReversedZ the encoding is flipped (near=1, far=0), so "closer" now corresponds to a *larger*
|
||||||
|
// stored value, not a smaller one - the ordered compare functions (LESS/LEQUAL/GREATER/GEQUAL)
|
||||||
|
// must invert to match, or the depth test silently runs backwards (verified directly: this was
|
||||||
|
// the actual cause of a bug report after the projection/shader half of the reverse-Z fix
|
||||||
|
// eliminated the double-negation that used to accidentally keep the unreversed comparisons
|
||||||
|
// correct - LEQUAL now needs GreaterEqual, not LessEqual, once the encoding it's testing against
|
||||||
|
// is genuinely reversed). Matches upstream aurora's to_compare_function exactly.
|
||||||
static inline wgpu::CompareFunction to_compare_function(GXCompare func) {
|
static inline wgpu::CompareFunction to_compare_function(GXCompare func) {
|
||||||
switch (func) {
|
switch (func) {
|
||||||
DEFAULT_FATAL("invalid depth fn {}", underlying(func));
|
DEFAULT_FATAL("invalid depth fn {}", underlying(func));
|
||||||
case GX_NEVER:
|
case GX_NEVER:
|
||||||
return wgpu::CompareFunction::Never;
|
return wgpu::CompareFunction::Never;
|
||||||
case GX_LESS:
|
case GX_LESS:
|
||||||
return wgpu::CompareFunction::Less;
|
return UseReversedZ ? wgpu::CompareFunction::Greater : wgpu::CompareFunction::Less;
|
||||||
case GX_EQUAL:
|
case GX_EQUAL:
|
||||||
return wgpu::CompareFunction::Equal;
|
return wgpu::CompareFunction::Equal;
|
||||||
case GX_LEQUAL:
|
case GX_LEQUAL:
|
||||||
return wgpu::CompareFunction::LessEqual;
|
return UseReversedZ ? wgpu::CompareFunction::GreaterEqual : wgpu::CompareFunction::LessEqual;
|
||||||
case GX_GREATER:
|
case GX_GREATER:
|
||||||
return wgpu::CompareFunction::Greater;
|
return UseReversedZ ? wgpu::CompareFunction::Less : wgpu::CompareFunction::Greater;
|
||||||
case GX_NEQUAL:
|
case GX_NEQUAL:
|
||||||
return wgpu::CompareFunction::NotEqual;
|
return wgpu::CompareFunction::NotEqual;
|
||||||
case GX_GEQUAL:
|
case GX_GEQUAL:
|
||||||
return wgpu::CompareFunction::GreaterEqual;
|
return UseReversedZ ? wgpu::CompareFunction::LessEqual : wgpu::CompareFunction::GreaterEqual;
|
||||||
case GX_ALWAYS:
|
case GX_ALWAYS:
|
||||||
return wgpu::CompareFunction::Always;
|
return wgpu::CompareFunction::Always;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -485,7 +485,14 @@ const gfx::TextureBind& get_texture(GXTexMapID id) noexcept;
|
|||||||
void resolve_sampled_textures(const ShaderInfo& info) noexcept;
|
void resolve_sampled_textures(const ShaderInfo& info) noexcept;
|
||||||
|
|
||||||
inline float clear_depth_value() {
|
inline float clear_depth_value() {
|
||||||
return std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
|
// g_gxState.clearDepth is in GX's own distance terms (0 = near, larger = farther), independent of
|
||||||
|
// how UseReversedZ encodes that as a host depth value - it must be re-mapped the same way the
|
||||||
|
// projection matrix and depth compare function are, or the buffer clears to the wrong extreme
|
||||||
|
// (verified directly: matches upstream aurora's clear_depth_value, which does this same inversion
|
||||||
|
// and was the second missing piece alongside to_compare_function's compare-op inversion).
|
||||||
|
const float normalizedDepth =
|
||||||
|
std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
|
||||||
|
return UseReversedZ ? (1.f - normalizedDepth) : normalizedDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool render_target_has_alpha(GXPixelFmt pixelFmt) noexcept { return pixelFmt == GX_PF_RGBA6_Z24; }
|
inline bool render_target_has_alpha(GXPixelFmt pixelFmt) noexcept { return pixelFmt == GX_PF_RGBA6_Z24; }
|
||||||
|
|||||||
@@ -993,11 +993,13 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
"\n let clip_base = select(clip_a, clip_b, use_b);"
|
"\n let clip_base = select(clip_a, clip_b, use_b);"
|
||||||
"\n out.pos = vec4f(clip_base.xy + offset_ndc * clip_base.w, clip_base.zw);";
|
"\n out.pos = vec4f(clip_base.xy + offset_ndc * clip_base.w, clip_base.zw);";
|
||||||
}
|
}
|
||||||
if constexpr (UseReversedZ) {
|
// The near/far depth correction used to be applied here per-vertex (out.pos.z = -out.pos.z for
|
||||||
vtxXfrAttrsPre += "\n out.pos.z = -out.pos.z;";
|
// reversed, or += out.pos.w for forward), redundantly on top of the same correction already
|
||||||
} else {
|
// folded into ubuf.proj by effective_projection() (shader_info.cpp) - applying it twice canceled
|
||||||
vtxXfrAttrsPre += "\n out.pos.z += out.pos.w;";
|
// out for the common case (any draw where effective_projection() decides to flip), silently
|
||||||
}
|
// making "reversed" Z behave identically to forward Z. It is now applied exactly once, in the
|
||||||
|
// projection matrix alone (matching upstream aurora commit 1dde08fa: "Move depth correction to
|
||||||
|
// projection matrix"), so nothing needs to happen to out.pos.z here.
|
||||||
// GX rasterizes at a 7/12 pixel center when antialiasing is disabled, while WebGPU rasterizes at 1/2.
|
// GX rasterizes at a 7/12 pixel center when antialiasing is disabled, while WebGPU rasterizes at 1/2.
|
||||||
vtxXfrAttrsPre +=
|
vtxXfrAttrsPre +=
|
||||||
"\n let gx_pixel_center_correction = "
|
"\n let gx_pixel_center_correction = "
|
||||||
@@ -1465,7 +1467,14 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
textureDependency.texMapId, uvIn);
|
textureDependency.texMapId, uvIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string fogDepthExpr = UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)";
|
// in.pos.z is the host NDC z (forward: 0=near/1=far; reversed: 1=near/0=far post-fix), but this
|
||||||
|
// expression needs to produce GX's own native distance term (always 0=near/1=far, matching how
|
||||||
|
// g_gxState.clearDepth/clear_depth_value() are interpreted before their own UseReversedZ
|
||||||
|
// inversion) - forward already matches directly; reversed needs the same 1-x flip everything
|
||||||
|
// else reversed-Z-aware uses. This was backwards (verified directly against upstream aurora's
|
||||||
|
// identical expression in build_shader_source), which fed both fog density and the GX_ZT_ADD
|
||||||
|
// z-texture path the wrong distance value.
|
||||||
|
std::string fogDepthExpr = UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z";
|
||||||
std::string fogZCoordExpr =
|
std::string fogZCoordExpr =
|
||||||
fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
|
fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
|
||||||
if (usesZTextureDepth) {
|
if (usesZTextureDepth) {
|
||||||
@@ -1498,7 +1507,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
fragmentFn += fmt::format(
|
fragmentFn += fmt::format(
|
||||||
"\n let oldZ = u32(round(clamp({0}, 0.0, 1.0) * 16777216.0));"
|
"\n let oldZ = u32(round(clamp({0}, 0.0, 1.0) * 16777216.0));"
|
||||||
"\n ztexCoord = (ztexCoord + oldZ) & 0x00ffffffu;",
|
"\n ztexCoord = (ztexCoord + oldZ) & 0x00ffffffu;",
|
||||||
UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)");
|
UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z");
|
||||||
}
|
}
|
||||||
fragmentFn += "\n let ztexDepth = f32(ztexCoord) / 16777216.0;";
|
fragmentFn += "\n let ztexDepth = f32(ztexCoord) / 16777216.0;";
|
||||||
fogZCoordExpr = "ztexCoord";
|
fogZCoordExpr = "ztexCoord";
|
||||||
@@ -1639,7 +1648,13 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
" @builtin(frag_depth) depth: f32,\n"
|
" @builtin(frag_depth) depth: f32,\n"
|
||||||
"};";
|
"};";
|
||||||
|
|
||||||
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "" : "1.0 - ");
|
// ztexDepth is in GX's native distance terms (0=near/1=far, see fogDepthExpr's comment above),
|
||||||
|
// but frag_depth must be written in the same host NDC-z convention in.pos.z itself uses -
|
||||||
|
// forward matches directly (no change), reversed needs the same 1-x flip. This was backwards
|
||||||
|
// the same way fogDepthExpr was (verified by the same derivation, since aurora upstream has no
|
||||||
|
// directly equivalent line here to cross-check against - this z-texture-depth-output path
|
||||||
|
// appears to be specific to this fork).
|
||||||
|
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "1.0 - " : "");
|
||||||
fragmentReturnType = "FragmentOutput";
|
fragmentReturnType = "FragmentOutput";
|
||||||
fragmentReturn =
|
fragmentReturn =
|
||||||
" var out: FragmentOutput;\n"
|
" var out: FragmentOutput;\n"
|
||||||
|
|||||||
@@ -548,14 +548,22 @@ constexpr size_t kStagedUniformBytes =
|
|||||||
96 + sizeof(Mat4x4<float>) + sizeof(Mat3x4<float>) * (MaxPostexMtx + MaxPnMtx);
|
96 + sizeof(Mat4x4<float>) + sizeof(Mat3x4<float>) * (MaxPostexMtx + MaxPnMtx);
|
||||||
|
|
||||||
// The host viewport always receives the normalized GX depth window (render_pass_impl clamps to minDepth <= maxDepth).
|
// The host viewport always receives the normalized GX depth window (render_pass_impl clamps to minDepth <= maxDepth).
|
||||||
|
//
|
||||||
|
// Folds the near/far depth correction the vertex shader used to apply per-vertex directly into the
|
||||||
|
// projection matrix instead (matching upstream aurora commit 1dde08fa, "Move depth correction to
|
||||||
|
// projection matrix") - valid because the correction is a linear combination of the z/w rows, so
|
||||||
|
// applying it once here to the row is equivalent to applying it once per-vertex to the dot product,
|
||||||
|
// and it must be applied exactly once: doing it here AND in the shader (the previous bug) canceled
|
||||||
|
// the negation out for `flip`, silently making "reversed" Z behave identically to forward Z.
|
||||||
|
// `flip` decides which of the two single-application forms this draw needs: true bakes in the
|
||||||
|
// reversed-Z inversion (z' = -z), false bakes in the forward-Z near/far combination (z' = z + w) -
|
||||||
|
// exactly one always applies, never both, and never neither.
|
||||||
static Mat4x4<float> effective_projection() noexcept {
|
static Mat4x4<float> effective_projection() noexcept {
|
||||||
const auto& vp = g_gxState.renderViewport;
|
const auto& vp = g_gxState.renderViewport;
|
||||||
const bool flip = (vp.znear <= vp.zfar) == UseReversedZ;
|
const bool flip = (vp.znear <= vp.zfar) == UseReversedZ;
|
||||||
Mat4x4<float> proj = g_gxState.proj;
|
Mat4x4<float> proj = g_gxState.proj;
|
||||||
if (flip) {
|
|
||||||
for (size_t i = 0; i < 4; ++i) {
|
for (size_t i = 0; i < 4; ++i) {
|
||||||
proj.m2.m[i] = -(proj.m2.m[i] + proj.m3.m[i]);
|
proj.m2.m[i] = flip ? -proj.m2.m[i] : (proj.m2.m[i] + proj.m3.m[i]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return proj;
|
return proj;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ std::string GetOSVersion() {
|
|||||||
constexpr auto name = "iOS";
|
constexpr auto name = "iOS";
|
||||||
#elif TARGET_OS_TV
|
#elif TARGET_OS_TV
|
||||||
constexpr auto name = "tvOS";
|
constexpr auto name = "tvOS";
|
||||||
#else
|
#elif
|
||||||
constexpr auto name = Unknown;
|
constexpr auto name = Unknown;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,349 @@
|
|||||||
|
# Building WiiCompiled and Retro Rewind on macOS
|
||||||
|
|
||||||
|
This guide covers building **WiiCompiled** (base game) and **Retro Rewind** from source on macOS for Apple Silicon (`arm64`). Follow these instructions to compile the native executables directly.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If you only want to build the base game (**WiiCompiled**), look for sections marked **`(Skip if only building WiiCompiled)`** to bypass Retro Rewind and online payload steps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Prerequisites
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
- **Hardware**: Apple Silicon Mac (M1/M2/M3/M4)
|
||||||
|
- **Operating System**: macOS 14 (Sonoma) or later
|
||||||
|
- **Xcode Command Line Tools**:
|
||||||
|
```bash
|
||||||
|
xcode-select --install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Toolchain Dependencies
|
||||||
|
Install the required tools using [Homebrew](https://brew.sh):
|
||||||
|
```bash
|
||||||
|
brew install cmake ninja
|
||||||
|
brew install --cask dotnet-sdk@8
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify that Clang, CMake, Ninja, and the .NET 8 runtime are available:
|
||||||
|
```bash
|
||||||
|
clang --version
|
||||||
|
cmake --version
|
||||||
|
ninja --version
|
||||||
|
dotnet --list-runtimes # Must list Microsoft.NETCore.App 8.x
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Required Game and Mod Assets
|
||||||
|
|
||||||
|
Due to legal requirements, no proprietary Nintendo assets or code are included in this repository. You must provide your own legally dumped game files.
|
||||||
|
|
||||||
|
1. **Mario Kart Wii PAL (`RMCP01`) Disc Image** *(Required)*:
|
||||||
|
- Supported formats: `.iso`, `.wbfs`, `.ciso`, `.rvz`, `.gcm`, `.gcz`.
|
||||||
|
2. **nodtool** *(Required for disc extraction)*:
|
||||||
|
- Download the macOS Apple Silicon binary of [nodtool](https://github.com/encounter/nod/releases):
|
||||||
|
```bash
|
||||||
|
curl -fsSL "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.10/nodtool-macos-arm64" -o nodtool
|
||||||
|
chmod +x nodtool
|
||||||
|
```
|
||||||
|
3. **Retro Rewind Distribution** *(Skip if only building WiiCompiled)*:
|
||||||
|
- Download the [Retro Rewind](https://wiki.tockdom.com/wiki/Retro_Rewind) release package. You will need the `RetroRewind6` folder (which contains `Binaries/Code.pul`).
|
||||||
|
4. **Retro-WFC Payload** *(Skip if only building WiiCompiled or building offline)*:
|
||||||
|
- Required for online multiplayer on Retro Rewind. Downloaded during setup from `http://nas.play.rwfc.net/payload?g=RMCPD00`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Step 1: Extract Disc Assets
|
||||||
|
|
||||||
|
Extract your clean PAL `RMCP01` disc into the `Assets/` directory of the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using nodtool directly into a temporary scratch directory
|
||||||
|
mkdir -p /tmp/mkw-extract
|
||||||
|
./nodtool extract /path/to/RMCP01.iso /tmp/mkw-extract
|
||||||
|
|
||||||
|
# Copy extracted assets into the repository Assets directory
|
||||||
|
rm -rf Assets/DATA/files Assets/DATA/sys
|
||||||
|
mkdir -p Assets/DATA
|
||||||
|
cp /tmp/mkw-extract/*/sys/main.dol Assets/main.dol
|
||||||
|
cp /tmp/mkw-extract/*/files/rel/StaticR.rel Assets/StaticR.rel
|
||||||
|
cp -R /tmp/mkw-extract/*/files Assets/DATA/files
|
||||||
|
cp -R /tmp/mkw-extract/*/sys Assets/DATA/sys
|
||||||
|
|
||||||
|
# Clean up temporary files
|
||||||
|
rm -rf /tmp/mkw-extract
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Alternatively, you can use the repository's helper script:
|
||||||
|
> ```bash
|
||||||
|
> Launcher/macos/extract-disc.command --game /path/to/RMCP01.iso --assets-dir Assets --nodtool ./nodtool
|
||||||
|
> ```
|
||||||
|
|
||||||
|
### Verify Extracted Asset Hashes
|
||||||
|
Confirm that the extracted files match the expected clean PAL revision:
|
||||||
|
```bash
|
||||||
|
shasum -a 256 Assets/main.dol Assets/StaticR.rel
|
||||||
|
```
|
||||||
|
- `Assets/main.dol`: `80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05`
|
||||||
|
- `Assets/StaticR.rel`: `16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Step 2: Build the Translator CLI
|
||||||
|
|
||||||
|
Compile the static recompiler CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet build translator/src/Translator.Cli/Translator.Cli.csproj -c Release
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a shell function to invoke the translator (ensuring paths with spaces are handled safely):
|
||||||
|
```bash
|
||||||
|
translator() {
|
||||||
|
dotnet "$(pwd)/translator/src/Translator.Cli/bin/Release/net8.0/Translator.Cli.dll" "$@"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Step 3: Translation
|
||||||
|
|
||||||
|
### A. Translate Base Game Functions
|
||||||
|
```bash
|
||||||
|
mkdir -p generated/functions build/base
|
||||||
|
|
||||||
|
translator translate-recursive 0x8000629c \
|
||||||
|
--project projects/mkwii/recomp.yml \
|
||||||
|
--outdir generated/functions \
|
||||||
|
--output-metadata generated/base_translation_output.json \
|
||||||
|
--production-source-bundle generated/base_translation_sources.bin \
|
||||||
|
--no-function-files \
|
||||||
|
--prune-stale \
|
||||||
|
--threads $(sysctl -n hw.ncpu)
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. Emit Base Manifest
|
||||||
|
```bash
|
||||||
|
translator emit-base-manifest \
|
||||||
|
--project projects/mkwii/recomp.yml \
|
||||||
|
--out build/base \
|
||||||
|
--functions-dir generated/functions \
|
||||||
|
--translation-output-metadata generated/base_translation_output.json \
|
||||||
|
--region P
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### C. Stage and Translate Retro Rewind *(Skip this step if you only want to build WiiCompiled)*
|
||||||
|
|
||||||
|
1. Stage `Code.pul`:
|
||||||
|
```bash
|
||||||
|
RETRO_DIR="/path/to/RetroRewind6"
|
||||||
|
mkdir -p PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries
|
||||||
|
cp "$RETRO_DIR/Binaries/Code.pul" PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Retro-WFC Payload Setup (for Online Multiplayer)**:
|
||||||
|
Online play in Retro Rewind requires the shared Retro-WFC payload. Download and validate it:
|
||||||
|
```bash
|
||||||
|
mkdir -p build/retro-wfc/binary
|
||||||
|
curl -fsSL --retry 3 "https://nas.play.rwfc.net/payload?g=RMCPD00" \
|
||||||
|
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
||||||
|
|
||||||
|
# Validate payload signature and integrity
|
||||||
|
translator validate-retro-wfc-payload --directory build/retro-wfc
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run Retro Rewind translation:
|
||||||
|
```bash
|
||||||
|
mkdir -p build/mods/retro_rewind_full_cpp
|
||||||
|
|
||||||
|
translator translate-mod \
|
||||||
|
--project projects/mkwii/recomp.yml \
|
||||||
|
--profile retro-rewind \
|
||||||
|
--base-manifest build/base/mkwii_base_manifest.json \
|
||||||
|
--base-translation-output-metadata generated/base_translation_output.json \
|
||||||
|
--code-pul "$RETRO_DIR/Binaries/Code.pul" \
|
||||||
|
--mod-root "$RETRO_DIR" \
|
||||||
|
--mod-name "Retro Rewind" \
|
||||||
|
--region P \
|
||||||
|
--out build/mods/retro_rewind_full_cpp \
|
||||||
|
--prefer-cached-inputs \
|
||||||
|
--emit-cpp \
|
||||||
|
--threads $(sysctl -n hw.ncpu) \
|
||||||
|
--retro-wfc-payload build/retro-wfc/binary/payload.RMCPD00.bin
|
||||||
|
```
|
||||||
|
> [!TIP]
|
||||||
|
> If you do not want online play or do not have an internet connection, replace `--retro-wfc-payload ...` with `--skip-retro-wfc`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### D. Generate Data Initialization and Build Shards
|
||||||
|
|
||||||
|
First, generate the embedded game data initializer:
|
||||||
|
```bash
|
||||||
|
translator generate-data-init --project projects/mkwii/recomp.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, generate the CMake build shards using **one** of the following options:
|
||||||
|
|
||||||
|
#### Option 1: Base Game Only (WiiCompiled)
|
||||||
|
```bash
|
||||||
|
mkdir -p generated/build_shards
|
||||||
|
translator emit-build-shards \
|
||||||
|
--project projects/mkwii/recomp.yml \
|
||||||
|
--base-metadata generated/base_translation_output.json \
|
||||||
|
--base-functions-dir generated/functions \
|
||||||
|
--native-source-dir runtime/src \
|
||||||
|
--out generated/build_shards
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 2: Base Game + Retro Rewind
|
||||||
|
```bash
|
||||||
|
mkdir -p generated/build_shards
|
||||||
|
translator emit-build-shards \
|
||||||
|
--project projects/mkwii/recomp.yml \
|
||||||
|
--base-metadata generated/base_translation_output.json \
|
||||||
|
--base-functions-dir generated/functions \
|
||||||
|
--native-source-dir runtime/src \
|
||||||
|
--out generated/build_shards \
|
||||||
|
--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json \
|
||||||
|
--retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Step 4: Configure and Compile with CMake & Ninja
|
||||||
|
|
||||||
|
Configure the native C++ build targeting Apple Silicon:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S runtime -B build-macos -G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_C_COMPILER=clang \
|
||||||
|
-DCMAKE_CXX_COMPILER=clang++ \
|
||||||
|
-DAURORA_SDL3_PROVIDER=vendor
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile the desired target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# To build WiiCompiled only:
|
||||||
|
cmake --build build-macos --target WiiCompiled --parallel $(sysctl -n hw.ncpu)
|
||||||
|
|
||||||
|
# OR to build both WiiCompiled and Retro Rewind:
|
||||||
|
cmake --build build-macos --target WiiCompiled RetroRewind --parallel $(sysctl -n hw.ncpu)
|
||||||
|
```
|
||||||
|
|
||||||
|
Once compilation completes, the executables are ready in your build directory:
|
||||||
|
- `build-macos/WiiCompiled`
|
||||||
|
- `build-macos/RetroRewind` (if built)
|
||||||
|
|
||||||
|
During the build, CMake automatically copies the required runtime assets into `build-macos/`:
|
||||||
|
- `build-macos/dsp_coef.bin`
|
||||||
|
- `build-macos/initial_pipeline_cache.db`
|
||||||
|
- `build-macos/wii_bootstrap/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Step 5: Running Executables from the Build Folder
|
||||||
|
|
||||||
|
### Configure `Config.toml`
|
||||||
|
The runtime reads configuration from `~/Library/Application Support/WiiCompiled/Config.toml`.
|
||||||
|
|
||||||
|
Create the directory and configuration file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p "$HOME/Library/Application Support/WiiCompiled"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For Base Game Only (WiiCompiled):
|
||||||
|
```toml
|
||||||
|
# ~/Library/Application Support/WiiCompiled/Config.toml
|
||||||
|
[video]
|
||||||
|
widescreen = true
|
||||||
|
resolution_multiplier = 1.0
|
||||||
|
graphics_api = "metal"
|
||||||
|
|
||||||
|
[paths]
|
||||||
|
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For Base Game and Retro Rewind:
|
||||||
|
```toml
|
||||||
|
# ~/Library/Application Support/WiiCompiled/Config.toml
|
||||||
|
[video]
|
||||||
|
widescreen = true
|
||||||
|
resolution_multiplier = 1.0
|
||||||
|
graphics_api = "metal"
|
||||||
|
|
||||||
|
[paths]
|
||||||
|
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
|
||||||
|
retro_rewind_root = "/path/to/RetroRewind6"
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Ensure `dvd_root` points to the directory containing `files` and `sys/fst.bin`.
|
||||||
|
|
||||||
|
### Launching the Game
|
||||||
|
Run the compiled binaries directly from your terminal or by double clicking:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run base WiiCompiled
|
||||||
|
./build-macos/WiiCompiled
|
||||||
|
|
||||||
|
# Run Retro Rewind
|
||||||
|
./build-macos/RetroRewind
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Press **F10** in-game at any time to open the configuration bar (controls, resolution, display settings, audio).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference: Automated Helper Script
|
||||||
|
|
||||||
|
The repository provides a script (`Launcher/local-build-macos.command`) that handles extraction, translation, and compilation in a single command.
|
||||||
|
|
||||||
|
### Building Base Game Only:
|
||||||
|
```bash
|
||||||
|
Launcher/local-build-macos.command \
|
||||||
|
--profile base \
|
||||||
|
--output-dir build-macos/Products \
|
||||||
|
--game /path/to/RMCP01.iso \
|
||||||
|
--nodtool ./nodtool
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building Both (with Online Retro-WFC Payload):
|
||||||
|
```bash
|
||||||
|
# 1. Download Retro-WFC payload into a staging directory:
|
||||||
|
mkdir -p build/retro-wfc/binary
|
||||||
|
curl -fsSL --retry 3 "http://nas.play.rwfc.net/payload?g=RMCPD00" \
|
||||||
|
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
||||||
|
|
||||||
|
# 2. Run the automated build with the payload directory:
|
||||||
|
Launcher/local-build-macos.command \
|
||||||
|
--profile both \
|
||||||
|
--output-dir build-macos/Products \
|
||||||
|
--base-output-dir build-macos/Products \
|
||||||
|
--game /path/to/RMCP01.iso \
|
||||||
|
--nodtool ./nodtool \
|
||||||
|
--retro-rewind-package-dir /path/to/RetroRewind6 \
|
||||||
|
--retro-wfc-offline-dir build/retro-wfc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building Both (Offline, Skipping Payload):
|
||||||
|
```bash
|
||||||
|
Launcher/local-build-macos.command \
|
||||||
|
--profile both \
|
||||||
|
--output-dir build-macos/Products \
|
||||||
|
--base-output-dir build-macos/Products \
|
||||||
|
--game /path/to/RMCP01.iso \
|
||||||
|
--nodtool ./nodtool \
|
||||||
|
--retro-rewind-package-dir /path/to/RetroRewind6 \
|
||||||
|
--skip-retro-wfc-payload
|
||||||
|
```
|
||||||
|
|
||||||
|
When finished, the compiled executables reside in `native-build-macos/` and the bundled `.app` packages are placed in `build-macos/Products/`.
|
||||||
@@ -58,7 +58,7 @@ profiles:
|
|||||||
module_link_base: 0x803992E0
|
module_link_base: 0x803992E0
|
||||||
output: build/mods/retro_rewind_full_cpp
|
output: build/mods/retro_rewind_full_cpp
|
||||||
enable_retro_wfc: true
|
enable_retro_wfc: true
|
||||||
retro_wfc_payload: http://nas.play.rwfc.net/payload?g=RMCPD00
|
retro_wfc_payload: https://rwfc.net/api/wfc/payload?g=RMCPD00
|
||||||
retro_wfc_legacy_bootstrap_hook: 0x800ED6E8
|
retro_wfc_legacy_bootstrap_hook: 0x800ED6E8
|
||||||
riivolution:
|
riivolution:
|
||||||
xml: xml/RetroRewind6.xml
|
xml: xml/RetroRewind6.xml
|
||||||
|
|||||||
+10
-53
@@ -1,44 +1,22 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# Dawn's pinned macOS artifacts target 12.0. Set the same floor before project()
|
|
||||||
# initializes the Apple toolchain so direct developer CMake invocations cannot
|
|
||||||
# accidentally inherit the running SDK's deployment version. This cache entry
|
|
||||||
# is harmless on non-Apple platforms and remains overridable by a caller.
|
|
||||||
if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
|
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING
|
|
||||||
"Minimum macOS version supported by WiiCompiled" FORCE)
|
|
||||||
endif()
|
|
||||||
project(mkw_recompiled)
|
project(mkw_recompiled)
|
||||||
|
|
||||||
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
message(FATAL_ERROR "WiiCompiled requires a 64-bit Clang toolchain")
|
message(FATAL_ERROR "WiiCompiled requires a 64-bit Clang toolchain")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
|
|
||||||
list(LENGTH CMAKE_OSX_ARCHITECTURES MKW_OSX_ARCHITECTURE_COUNT)
|
|
||||||
if(MKW_OSX_ARCHITECTURE_COUNT GREATER 1)
|
|
||||||
message(FATAL_ERROR
|
|
||||||
"WiiCompiled supports one macOS architecture per build directory; "
|
|
||||||
"configure separate arm64 and x86_64 build directories")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32 AND MINGW AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
|
if(WIN32 AND MINGW AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
|
||||||
set(MKW_PLATFORM_WINDOWS TRUE)
|
set(MKW_PLATFORM_WINDOWS TRUE)
|
||||||
elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|arm64|ARM64)$")
|
elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|ARM64)$")
|
||||||
|
# The first native macOS target is Apple Silicon. Intel and universal
|
||||||
|
# binaries remain future compatibility work; do not silently claim them.
|
||||||
set(MKW_PLATFORM_MACOS TRUE)
|
set(MKW_PLATFORM_MACOS TRUE)
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
|
|
||||||
set(MKW_PLATFORM_MACOS_X86_64 TRUE)
|
|
||||||
else()
|
|
||||||
set(MKW_PLATFORM_MACOS_ARM64 TRUE)
|
|
||||||
endif()
|
|
||||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|aarch64|arm64|ARM64)$")
|
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|aarch64|arm64|ARM64)$")
|
||||||
set(MKW_PLATFORM_LINUX TRUE)
|
set(MKW_PLATFORM_LINUX TRUE)
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR
|
message(FATAL_ERROR
|
||||||
"WiiCompiled supports 64-bit LLVM-MinGW Clang on Windows, native Linux x86_64/aarch64, or Apple Clang on macOS x86_64/arm64")
|
"WiiCompiled supports 64-bit LLVM-MinGW Clang on Windows, native Linux x86_64/aarch64, or Apple Clang on macOS arm64")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Release")
|
if(NOT CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
message(FATAL_ERROR "WiiCompiled only supports Release builds")
|
message(FATAL_ERROR "WiiCompiled only supports Release builds")
|
||||||
endif()
|
endif()
|
||||||
@@ -81,15 +59,14 @@ target_include_directories(mkw_pugixml PUBLIC third_party/pugixml)
|
|||||||
target_compile_features(mkw_pugixml PUBLIC cxx_std_17)
|
target_compile_features(mkw_pugixml PUBLIC cxx_std_17)
|
||||||
set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF)
|
set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF)
|
||||||
|
|
||||||
# POSIX x86-64 guest-fiber scheduling (runtime/src/host_context.cpp) needs a symmetric
|
# Linux guest-fiber scheduling (runtime/src/host_context.cpp) needs a symmetric
|
||||||
# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers
|
# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers
|
||||||
# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike
|
# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike
|
||||||
# asymmetric resume/yield coroutine libraries, which would need every call site restructured).
|
# asymmetric resume/yield coroutine libraries, which would need every call site restructured).
|
||||||
# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is
|
# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is
|
||||||
# separately BSD-style licensed, see third_party/libco/LICENSE). Windows keeps native Fibers;
|
# separately BSD-style licensed, see third_party/libco/LICENSE). Windows keeps native Fibers
|
||||||
# Apple Silicon uses the project's x18-safe AArch64 assembly backend, while Intel macOS uses
|
# and macOS uses the project's x18-safe AArch64 assembly backend, so this target is Linux-only.
|
||||||
# libco's existing System V AMD64 backend.
|
if(MKW_PLATFORM_LINUX)
|
||||||
if(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64)
|
|
||||||
add_library(mkw_libco STATIC third_party/libco/libco.c)
|
add_library(mkw_libco STATIC third_party/libco/libco.c)
|
||||||
add_library(mkw::libco ALIAS mkw_libco)
|
add_library(mkw::libco ALIAS mkw_libco)
|
||||||
target_include_directories(mkw_libco PUBLIC third_party/libco)
|
target_include_directories(mkw_libco PUBLIC third_party/libco)
|
||||||
@@ -266,14 +243,12 @@ endif()
|
|||||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
||||||
if(MKW_PLATFORM_MACOS)
|
if(MKW_PLATFORM_MACOS)
|
||||||
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory.cpp")
|
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory.cpp")
|
||||||
if(MKW_PLATFORM_MACOS_ARM64)
|
|
||||||
# HostContext's Apple Silicon backend is implemented in a small assembly
|
# HostContext's Apple Silicon backend is implemented in a small assembly
|
||||||
# companion. It must be part of the product runtime as well as the
|
# companion. It must be part of the product runtime as well as the
|
||||||
# standalone context test; otherwise the final executable is missing
|
# standalone context test; otherwise the final executable is missing
|
||||||
# mkw_co_init/mkw_co_switch at link time.
|
# mkw_co_init/mkw_co_switch at link time.
|
||||||
enable_language(ASM)
|
enable_language(ASM)
|
||||||
list(APPEND SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
|
list(APPEND SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
|
||||||
endif()
|
|
||||||
else()
|
else()
|
||||||
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory_macos.cpp")
|
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory_macos.cpp")
|
||||||
endif()
|
endif()
|
||||||
@@ -344,9 +319,8 @@ if(MKW_PLATFORM_LINUX)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MKW_PLATFORM_MACOS)
|
if(MKW_PLATFORM_MACOS)
|
||||||
# Exercise the public host-memory contracts separately from translated products.
|
# Exercise the Apple Silicon context ABI and the public host-memory
|
||||||
if(MKW_PLATFORM_MACOS_ARM64)
|
# contracts separately from translated products.
|
||||||
# Apple Silicon's context ABI is implemented by the local assembly backend.
|
|
||||||
enable_language(ASM)
|
enable_language(ASM)
|
||||||
add_executable(mkw_macos_context_abi_tests
|
add_executable(mkw_macos_context_abi_tests
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/tests/macos_context_abi_tests.cpp"
|
"${CMAKE_CURRENT_LIST_DIR}/tests/macos_context_abi_tests.cpp"
|
||||||
@@ -359,16 +333,6 @@ if(MKW_PLATFORM_MACOS)
|
|||||||
"${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp"
|
"${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp"
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
|
"${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S")
|
||||||
target_include_directories(mkw_macos_host_context_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
target_include_directories(mkw_macos_host_context_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||||
else()
|
|
||||||
# Intel macOS follows the same System V AMD64 libco path as Linux.
|
|
||||||
add_executable(mkw_macos_host_context_tests
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/tests/host_context_tests.cpp"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp")
|
|
||||||
target_include_directories(mkw_macos_host_context_tests PRIVATE
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/third_party/libco")
|
|
||||||
target_link_libraries(mkw_macos_host_context_tests PRIVATE mkw::libco)
|
|
||||||
endif()
|
|
||||||
target_compile_features(mkw_macos_host_context_tests PRIVATE cxx_std_17)
|
target_compile_features(mkw_macos_host_context_tests PRIVATE cxx_std_17)
|
||||||
add_test(NAME mkw_macos_host_context_tests COMMAND mkw_macos_host_context_tests)
|
add_test(NAME mkw_macos_host_context_tests COMMAND mkw_macos_host_context_tests)
|
||||||
|
|
||||||
@@ -427,13 +391,6 @@ else()
|
|||||||
target_link_libraries(mkw_macos_native_compile PRIVATE
|
target_link_libraries(mkw_macos_native_compile PRIVATE
|
||||||
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx
|
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx
|
||||||
mkw::pugixml mkw::toml11 mkw::cryptopp)
|
mkw::pugixml mkw::toml11 mkw::cryptopp)
|
||||||
if(MKW_PLATFORM_MACOS_X86_64)
|
|
||||||
target_link_libraries(mkw_macos_native_compile PRIVATE mkw::libco)
|
|
||||||
# Keep this compile-only audit on the same Haswell-era x86-64-v3
|
|
||||||
# baseline as the translated product. PPC paired FMA helpers use
|
|
||||||
# FMA intrinsics and intentionally cannot compile for plain x86-64.
|
|
||||||
target_compile_options(mkw_macos_native_compile PRIVATE -march=x86-64-v3)
|
|
||||||
endif()
|
|
||||||
set_target_properties(mkw_macos_native_compile PROPERTIES UNITY_BUILD OFF)
|
set_target_properties(mkw_macos_native_compile PROPERTIES UNITY_BUILD OFF)
|
||||||
endif()
|
endif()
|
||||||
add_custom_target(mkw_platform_paths_check DEPENDS mkw_platform)
|
add_custom_target(mkw_platform_paths_check DEPENDS mkw_platform)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Public WiiCompiled product graph.
|
# Public WiiCompiled product graph.
|
||||||
#
|
#
|
||||||
# The translator owns the translated build graph. Mario Kart's profile-neutral
|
# The translator owns the translated build graph. Mario Kart's profile-neutral
|
||||||
# functions are compiled once into mkw_base_shared; only callers whose direct
|
# functions are compiled once into mkw_base_shared; only callers whose direct
|
||||||
@@ -84,9 +84,9 @@ target_link_libraries(mkw_runtime_common PRIVATE
|
|||||||
target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp)
|
target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp)
|
||||||
if(MKW_PLATFORM_WINDOWS)
|
if(MKW_PLATFORM_WINDOWS)
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp)
|
target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp)
|
||||||
elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64)
|
elseif(MKW_PLATFORM_LINUX)
|
||||||
# ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS
|
# ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS
|
||||||
# media monitoring). Empty on platforms where dl* is already in libc/libSystem.
|
# media monitoring). Empty string on glibc >= 2.34 where dl* is in libc.
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE mkw::libco ${CMAKE_DL_LIBS})
|
target_link_libraries(mkw_runtime_common PRIVATE mkw::libco ${CMAKE_DL_LIBS})
|
||||||
endif()
|
endif()
|
||||||
if(MKW_CPPWINRT_INCLUDE_DIR)
|
if(MKW_CPPWINRT_INCLUDE_DIR)
|
||||||
@@ -206,8 +206,6 @@ function(mkw_configure_product target)
|
|||||||
if(MKW_PLATFORM_MACOS)
|
if(MKW_PLATFORM_MACOS)
|
||||||
target_link_libraries(${target} PRIVATE
|
target_link_libraries(${target} PRIVATE
|
||||||
"${MKW_IOKIT_FRAMEWORK}" "${MKW_COREFOUNDATION_FRAMEWORK}")
|
"${MKW_IOKIT_FRAMEWORK}" "${MKW_COREFOUNDATION_FRAMEWORK}")
|
||||||
target_link_options(${target} PRIVATE
|
|
||||||
"LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor")
|
|
||||||
endif()
|
endif()
|
||||||
if(EXISTS "${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake")
|
if(EXISTS "${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake")
|
||||||
include("${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake")
|
include("${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake")
|
||||||
@@ -228,7 +226,7 @@ function(mkw_configure_product target)
|
|||||||
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp)
|
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp)
|
||||||
|
|
||||||
set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
|
set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||||
elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64)
|
elseif(MKW_PLATFORM_LINUX)
|
||||||
# mkw_runtime_common is an OBJECT library: WiiCompiled/RetroRewind only pull in its .o
|
# mkw_runtime_common is an OBJECT library: WiiCompiled/RetroRewind only pull in its .o
|
||||||
# files via $<TARGET_OBJECTS:>, which does not propagate mkw_runtime_common's own
|
# files via $<TARGET_OBJECTS:>, which does not propagate mkw_runtime_common's own
|
||||||
# target_link_libraries (object libraries don't carry usage requirements to a consumer
|
# target_link_libraries (object libraries don't carry usage requirements to a consumer
|
||||||
|
|||||||
@@ -48,8 +48,19 @@ struct NativeButtonItem {
|
|||||||
uint32_t nativeButton;
|
uint32_t nativeButton;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr std::array<NativeButtonItem, SDL_GAMEPAD_BUTTON_COUNT + 1> kNativeButtons = {{
|
inline constexpr auto kNativeButtons = std::to_array<NativeButtonItem>({
|
||||||
{"unmapped", "Unmapped / analog trigger", PAD_NATIVE_BUTTON_INVALID},
|
{"disabled", "Unmapped", PAD_NATIVE_BUTTON_DISABLED},
|
||||||
|
{"left_trigger", "Left trigger (LT / L2)", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFT_TRIGGER, false)},
|
||||||
|
{"right_trigger", "Right trigger (RT / R2)", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, false)},
|
||||||
|
{"left_stick_left", "Left stick left", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTX, true)},
|
||||||
|
{"left_stick_right", "Left stick right", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTX, false)},
|
||||||
|
{"left_stick_up", "Left stick up", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTY, true)},
|
||||||
|
{"left_stick_down", "Left stick down", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTY, false)},
|
||||||
|
{"right_stick_left", "Right stick left", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTX, true)},
|
||||||
|
{"right_stick_right", "Right stick right", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTX, false)},
|
||||||
|
{"right_stick_up", "Right stick up", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTY, true)},
|
||||||
|
{"right_stick_down", "Right stick down", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTY, false)},
|
||||||
|
{"unmapped", "Default", PAD_NATIVE_BUTTON_INVALID},
|
||||||
{"south", "South (A / Cross)", SDL_GAMEPAD_BUTTON_SOUTH},
|
{"south", "South (A / Cross)", SDL_GAMEPAD_BUTTON_SOUTH},
|
||||||
{"east", "East (B / Circle)", SDL_GAMEPAD_BUTTON_EAST},
|
{"east", "East (B / Circle)", SDL_GAMEPAD_BUTTON_EAST},
|
||||||
{"west", "West (X / Square)", SDL_GAMEPAD_BUTTON_WEST},
|
{"west", "West (X / Square)", SDL_GAMEPAD_BUTTON_WEST},
|
||||||
@@ -76,7 +87,7 @@ inline constexpr std::array<NativeButtonItem, SDL_GAMEPAD_BUTTON_COUNT + 1> kNat
|
|||||||
{"misc4", "Misc 4 / GC R click", SDL_GAMEPAD_BUTTON_MISC4},
|
{"misc4", "Misc 4 / GC R click", SDL_GAMEPAD_BUTTON_MISC4},
|
||||||
{"misc5", "Misc 5", SDL_GAMEPAD_BUTTON_MISC5},
|
{"misc5", "Misc 5", SDL_GAMEPAD_BUTTON_MISC5},
|
||||||
{"misc6", "Misc 6", SDL_GAMEPAD_BUTTON_MISC6},
|
{"misc6", "Misc 6", SDL_GAMEPAD_BUTTON_MISC6},
|
||||||
}};
|
});
|
||||||
|
|
||||||
inline std::string TrimToken(std::string_view token) {
|
inline std::string TrimToken(std::string_view token) {
|
||||||
const size_t begin = token.find_first_not_of(" \t");
|
const size_t begin = token.find_first_not_of(" \t");
|
||||||
@@ -88,7 +99,7 @@ inline std::string TrimToken(std::string_view token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline const NativeButtonItem* FindNativeButton(std::string_view configName) {
|
inline const NativeButtonItem* FindNativeButton(std::string_view configName) {
|
||||||
const std::string name = TrimToken(configName);
|
const std::string name = TrimToken(configName.substr(0, configName.find('@')));
|
||||||
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
||||||
[&](const NativeButtonItem& item) { return name == item.configName; });
|
[&](const NativeButtonItem& item) { return name == item.configName; });
|
||||||
return it == kNativeButtons.end() ? nullptr : &*it;
|
return it == kNativeButtons.end() ? nullptr : &*it;
|
||||||
@@ -97,8 +108,8 @@ inline const NativeButtonItem* FindNativeButton(std::string_view configName) {
|
|||||||
// Falls back to the "unmapped" entry so callers always have a label to draw.
|
// Falls back to the "unmapped" entry so callers always have a label to draw.
|
||||||
inline const NativeButtonItem& NativeButtonForValue(uint32_t nativeButton) {
|
inline const NativeButtonItem& NativeButtonForValue(uint32_t nativeButton) {
|
||||||
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
||||||
[&](const NativeButtonItem& item) { return nativeButton == item.nativeButton; });
|
[&](const NativeButtonItem& item) { return PADAxisButtonIdentity(nativeButton) == PADAxisButtonIdentity(item.nativeButton); });
|
||||||
return it == kNativeButtons.end() ? kNativeButtons.front() : *it;
|
return it == kNativeButtons.end() ? *FindNativeButton("unmapped") : *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const GameCubeButtonItem* FindGameCubeButton(std::string_view configKey) {
|
inline const GameCubeButtonItem* FindGameCubeButton(std::string_view configKey) {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
// HostContext is the deliberately small boundary between the guest scheduler
|
// HostContext is the deliberately small boundary between the guest scheduler
|
||||||
// and the host's cooperative-context facility. Windows uses native Fibers;
|
// and the host's cooperative-context facility. Windows uses native Fibers and
|
||||||
// Linux and Intel macOS use libco's System V x86-64 backend. macOS AArch64 uses
|
// Linux uses libco; macOS AArch64 uses the local assembly backend because it
|
||||||
// the local assembly backend because it must preserve Darwin's platform-reserved
|
// must preserve Darwin's platform-reserved x18 register, which libco's AArch64
|
||||||
// x18 register, which libco's AArch64 backend does not save. Its handles are
|
// backend does not save. Its handles are only valid on the thread that
|
||||||
// only valid on the thread that initialized the scheduler.
|
// initialized the scheduler.
|
||||||
namespace HostContext {
|
namespace HostContext {
|
||||||
|
|
||||||
using Handle = void*;
|
using Handle = void*;
|
||||||
|
|||||||
@@ -78,22 +78,38 @@ bool ProcessSleepTimers(CpuContext* cpu)
|
|||||||
{
|
{
|
||||||
using Clock = std::chrono::steady_clock;
|
using Clock = std::chrono::steady_clock;
|
||||||
|
|
||||||
std::vector<SleepTimerEntry> dueTimers;
|
// Pop and process ONE due timer at a time, straight from the shared table. Resuming a
|
||||||
|
// sleeper re-enters the scheduler (OSResumeThread -> SelectThread) and can switch fibers
|
||||||
|
// away from this call. Timers that had already been popped into a private list would then
|
||||||
|
// sit on the suspended fiber's stack with their threads parked and no entry in the table:
|
||||||
|
// exactly the "park-shaped with no pending wake timer" strand the reconciler below heals
|
||||||
|
// 100ms late, followed by a "sleep-timer stale" drop when this fiber finally resumes.
|
||||||
|
// Leaving unprocessed timers in the table keeps them visible to every other pump (idle
|
||||||
|
// loop, other threads' SelectThread) while this one is switched away.
|
||||||
|
bool processedAny = false;
|
||||||
|
constexpr size_t kMaxTimersPerCall = 64;
|
||||||
|
size_t processedCount = 0;
|
||||||
const auto now = Clock::now();
|
const auto now = Clock::now();
|
||||||
|
while (processedCount < kMaxTimersPerCall) {
|
||||||
|
SleepTimerEntry timer{0, {}};
|
||||||
|
bool found = false;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(gSleepTimerMutex);
|
std::lock_guard<std::mutex> lock(gSleepTimerMutex);
|
||||||
auto it = gSleepTimers.begin();
|
for (auto it = gSleepTimers.begin(); it != gSleepTimers.end(); ++it) {
|
||||||
while (it != gSleepTimers.end()) {
|
if (it->deadline <= now) {
|
||||||
if (it->deadline > now) {
|
timer = *it;
|
||||||
++it;
|
gSleepTimers.erase(it);
|
||||||
continue;
|
found = true;
|
||||||
}
|
break;
|
||||||
dueTimers.push_back(*it);
|
|
||||||
it = gSleepTimers.erase(it);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++processedCount;
|
||||||
|
processedAny = true;
|
||||||
|
|
||||||
for (const SleepTimerEntry& timer : dueTimers) {
|
|
||||||
const uint32_t threadPtr = timer.threadPtr;
|
const uint32_t threadPtr = timer.threadPtr;
|
||||||
if (threadPtr == 0 ||
|
if (threadPtr == 0 ||
|
||||||
!Memory::Contains(threadPtr + kThreadSuspendOffset, sizeof(uint32_t))) {
|
!Memory::Contains(threadPtr + kThreadSuspendOffset, sizeof(uint32_t))) {
|
||||||
@@ -219,7 +235,7 @@ bool ProcessSleepTimers(CpuContext* cpu)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !dueTimers.empty();
|
return processedAny;
|
||||||
}
|
}
|
||||||
} // namespace OsHleInternal
|
} // namespace OsHleInternal
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
extern "C" void mkw_co_switch(void** targetSp, void** sourceSp);
|
extern "C" void mkw_co_switch(void** targetSp, void** sourceSp);
|
||||||
extern "C" void* mkw_co_init(void* stackTop, void (*entry)(void*), void* argument);
|
extern "C" void* mkw_co_init(void* stackTop, void (*entry)(void*), void* argument);
|
||||||
#elif defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__))
|
#elif defined(__linux__)
|
||||||
#include <libco.h>
|
#include <libco.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -158,7 +158,7 @@ void Switch(Handle target)
|
|||||||
g_current = source;
|
g_current = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__))
|
#elif defined(__linux__)
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct Context {
|
struct Context {
|
||||||
|
|||||||
@@ -94,7 +94,12 @@ double ReadInput(SDL_Gamepad* gamepad, const std::string& name) {
|
|||||||
// Fall back to this project's own positional names, so a binding written
|
// Fall back to this project's own positional names, so a binding written
|
||||||
// here does not have to use Dolphin vocabulary.
|
// here does not have to use Dolphin vocabulary.
|
||||||
if (const auto* native = ControllerNames::FindNativeButton(name)) {
|
if (const auto* native = ControllerNames::FindNativeButton(name)) {
|
||||||
if (native->nativeButton != PAD_NATIVE_BUTTON_INVALID) {
|
if (PADIsAxisButton(native->nativeButton)) {
|
||||||
|
const auto axis = static_cast<SDL_GamepadAxis>(PADAxisButtonAxis(native->nativeButton));
|
||||||
|
const double sign = PADAxisButtonNegative(native->nativeButton) ? -1.0 : 1.0;
|
||||||
|
return std::clamp(SDL_GetGamepadAxis(gamepad, axis) / 32767.0 * sign, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
if (native->nativeButton < SDL_GAMEPAD_BUTTON_COUNT) {
|
||||||
return SDL_GetGamepadButton(gamepad, static_cast<SDL_GamepadButton>(native->nativeButton)) ? 1.0
|
return SDL_GetGamepadButton(gamepad, static_cast<SDL_GamepadButton>(native->nativeButton)) ? 1.0
|
||||||
: 0.0;
|
: 0.0;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-11
@@ -44,15 +44,10 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#if defined(__x86_64__)
|
#if defined(__x86_64__)
|
||||||
// Only the x86 POSIX fault path inspects ucontext_t to recover the page-fault
|
// Only the x86 POSIX fault path inspects ucontext_t to recover the page-fault
|
||||||
// write bit. macOS exposes the signal-handler context through sys/ucontext.h;
|
// write bit. macOS deprecates ucontext and requires _XOPEN_SOURCE just to
|
||||||
// avoid ucontext.h itself because its deprecated user-context APIs require
|
// include the header, while the arm64 handler does not use it at all.
|
||||||
// _XOPEN_SOURCE. The arm64 handler does not inspect a host context at all.
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
#include <sys/ucontext.h>
|
|
||||||
#else
|
|
||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1133,11 +1128,7 @@ void PosixMemoryFaultHandler(int sig, siginfo_t* info, void* ucontextVoid) {
|
|||||||
// error code x86 pushes on a page fault records whether it was a write.
|
// error code x86 pushes on a page fault records whether it was a write.
|
||||||
if (ucontextVoid != nullptr) {
|
if (ucontextVoid != nullptr) {
|
||||||
auto* uc = static_cast<ucontext_t*>(ucontextVoid);
|
auto* uc = static_cast<ucontext_t*>(ucontextVoid);
|
||||||
#if defined(__APPLE__)
|
|
||||||
isWrite = uc->uc_mcontext != nullptr && (uc->uc_mcontext->__es.__err & 0x2) != 0;
|
|
||||||
#else
|
|
||||||
isWrite = (uc->uc_mcontext.gregs[REG_ERR] & 0x2) != 0;
|
isWrite = (uc->uc_mcontext.gregs[REG_ERR] & 0x2) != 0;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <bit>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -52,20 +52,6 @@ std::array<float, kSoundPlayerCount> g_requestedSoundPlayerVolumes{};
|
|||||||
std::array<float, kSoundPlayerCount> g_lastAppliedSoundPlayerVolumes{};
|
std::array<float, kSoundPlayerCount> g_lastAppliedSoundPlayerVolumes{};
|
||||||
std::array<bool, kSoundPlayerCount> g_haveSoundPlayerVolumes{};
|
std::array<bool, kSoundPlayerCount> g_haveSoundPlayerVolumes{};
|
||||||
|
|
||||||
uint32_t FloatBits(float value) noexcept {
|
|
||||||
static_assert(sizeof(float) == sizeof(uint32_t));
|
|
||||||
uint32_t bits = 0;
|
|
||||||
std::memcpy(&bits, &value, sizeof(bits));
|
|
||||||
return bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
float BitsFloat(uint32_t bits) noexcept {
|
|
||||||
static_assert(sizeof(float) == sizeof(uint32_t));
|
|
||||||
float value = 0.0f;
|
|
||||||
std::memcpy(&value, &bits, sizeof(value));
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
float ClampSoundPlayerVolume(float volume) noexcept {
|
float ClampSoundPlayerVolume(float volume) noexcept {
|
||||||
// Match nw4r::snd::SoundPlayer::SetVolume at 0x800A35E0 exactly,
|
// Match nw4r::snd::SoundPlayer::SetVolume at 0x800A35E0 exactly,
|
||||||
// including its NaN behavior (unordered compares select the upper bound).
|
// including its NaN behavior (unordered compares select the upper bound).
|
||||||
@@ -77,7 +63,7 @@ float ClampSoundPlayerVolume(float volume) noexcept {
|
|||||||
|
|
||||||
bool WriteGuestFloat(uint32_t address, float value) noexcept {
|
bool WriteGuestFloat(uint32_t address, float value) noexcept {
|
||||||
try {
|
try {
|
||||||
Memory::Write32(address, FloatBits(value));
|
Memory::Write32(address, std::bit_cast<uint32_t>(value));
|
||||||
return true;
|
return true;
|
||||||
} catch (const Memory::AccessViolation&) {
|
} catch (const Memory::AccessViolation&) {
|
||||||
return false;
|
return false;
|
||||||
@@ -89,7 +75,7 @@ bool ReadGuestFloat(uint32_t address, float& value) noexcept {
|
|||||||
if (!Memory::TryRead32(address, bits)) {
|
if (!Memory::TryRead32(address, bits)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
value = BitsFloat(bits);
|
value = std::bit_cast<float>(bits);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +558,7 @@ void SetSoundPlayerVolume(uint32_t soundPlayer, float requestedVolume) {
|
|||||||
}
|
}
|
||||||
// Preserve the original function's access semantics. An invalid player is
|
// Preserve the original function's access semantics. An invalid player is
|
||||||
// a guest bug and must not be converted into a silent successful call.
|
// a guest bug and must not be converted into a silent successful call.
|
||||||
Memory::Write32(soundPlayer + kSoundPlayerVolumeOffset, FloatBits(applied));
|
Memory::Write32(soundPlayer + kSoundPlayerVolumeOffset, std::bit_cast<uint32_t>(applied));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace MusicAttenuation
|
} // namespace MusicAttenuation
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <charconv>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -186,6 +187,18 @@ void LimitResolutionForFrameRate() {
|
|||||||
|
|
||||||
using ControllerNames::FindNativeButton;
|
using ControllerNames::FindNativeButton;
|
||||||
|
|
||||||
|
uint32_t ConfiguredNativeButton(const NativeButtonItem& item, const std::string& token) {
|
||||||
|
if (!PADIsAxisButton(item.nativeButton)) return item.nativeButton;
|
||||||
|
const size_t separator = token.find('@');
|
||||||
|
if (separator == std::string::npos) return item.nativeButton;
|
||||||
|
uint32_t threshold = 0;
|
||||||
|
const char* end = token.data() + token.size();
|
||||||
|
const auto parsed = std::from_chars(token.data() + separator + 1, end, threshold);
|
||||||
|
if (parsed.ec != std::errc{} || parsed.ptr != end || threshold < 1 || threshold > 100)
|
||||||
|
return item.nativeButton;
|
||||||
|
return PADAxisButtonIdentity(item.nativeButton) | (threshold << 8);
|
||||||
|
}
|
||||||
|
|
||||||
struct ControllerBindingPair {
|
struct ControllerBindingPair {
|
||||||
std::string primary;
|
std::string primary;
|
||||||
std::string secondary;
|
std::string secondary;
|
||||||
@@ -204,6 +217,13 @@ ControllerBindingPair SplitControllerBinding(const std::string& value) {
|
|||||||
|
|
||||||
using ControllerNames::NativeButtonForValue;
|
using ControllerNames::NativeButtonForValue;
|
||||||
|
|
||||||
|
std::string NativeBindingConfig(uint32_t binding) {
|
||||||
|
std::string value = NativeButtonForValue(binding).configName;
|
||||||
|
if (PADIsAxisButton(binding)) value += '@' + std::to_string(PADAxisButtonThreshold(binding));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void SetTopBarVisible(bool visible) {
|
void SetTopBarVisible(bool visible) {
|
||||||
if (g_topBarVisible == visible) {
|
if (g_topBarVisible == visible) {
|
||||||
return;
|
return;
|
||||||
@@ -242,7 +262,7 @@ void ApplyConfiguredMappings() {
|
|||||||
}
|
}
|
||||||
const ControllerBindingPair binding = SplitControllerBinding(*configured);
|
const ControllerBindingPair binding = SplitControllerBinding(*configured);
|
||||||
if (const NativeButtonItem* native = FindNativeButton(binding.primary)) {
|
if (const NativeButtonItem* native = FindNativeButton(binding.primary)) {
|
||||||
PADSetButtonMapping(port, PADButtonMapping{native->nativeButton, kControllerButtons[i].padButton});
|
PADSetButtonMapping(port, PADButtonMapping{ConfiguredNativeButton(*native, binding.primary), kControllerButtons[i].padButton});
|
||||||
} else {
|
} else {
|
||||||
RT_LOG(RT_TAG_CONFIG) << "Unknown controller." << kControllerButtons[i].configKey
|
RT_LOG(RT_TAG_CONFIG) << "Unknown controller." << kControllerButtons[i].configKey
|
||||||
<< " button '" << binding.primary << "'" << std::endl;
|
<< " button '" << binding.primary << "'" << std::endl;
|
||||||
@@ -250,7 +270,7 @@ void ApplyConfiguredMappings() {
|
|||||||
uint32_t altNative = PAD_NATIVE_BUTTON_INVALID;
|
uint32_t altNative = PAD_NATIVE_BUTTON_INVALID;
|
||||||
if (!binding.secondary.empty()) {
|
if (!binding.secondary.empty()) {
|
||||||
if (const NativeButtonItem* native = FindNativeButton(binding.secondary)) {
|
if (const NativeButtonItem* native = FindNativeButton(binding.secondary)) {
|
||||||
altNative = native->nativeButton;
|
altNative = ConfiguredNativeButton(*native, binding.secondary);
|
||||||
} else {
|
} else {
|
||||||
RT_LOG(RT_TAG_CONFIG) << "Unknown controller." << kControllerButtons[i].configKey
|
RT_LOG(RT_TAG_CONFIG) << "Unknown controller." << kControllerButtons[i].configKey
|
||||||
<< " secondary button '" << binding.secondary << "'" << std::endl;
|
<< " secondary button '" << binding.secondary << "'" << std::endl;
|
||||||
@@ -395,6 +415,219 @@ void DrawWiiRemoteSettings(uint32_t selectedGamePort) {
|
|||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* KeyBindingName(int scancode) {
|
||||||
|
switch (scancode) {
|
||||||
|
case PAD_KEY_MOUSE_LEFT: return "Mouse left";
|
||||||
|
case PAD_KEY_MOUSE_RIGHT: return "Mouse right";
|
||||||
|
case PAD_KEY_MOUSE_MIDDLE: return "Mouse middle";
|
||||||
|
case PAD_KEY_MOUSE_X1: return "Mouse side 1";
|
||||||
|
case PAD_KEY_MOUSE_X2: return "Mouse side 2";
|
||||||
|
case PAD_KEY_INVALID: return "Unmapped";
|
||||||
|
default:
|
||||||
|
return scancode >= 0 && scancode < SDL_SCANCODE_COUNT
|
||||||
|
? SDL_GetScancodeName(static_cast<SDL_Scancode>(scancode)) : "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class RebindKind { KeyboardButton, KeyboardAxis, Controller };
|
||||||
|
struct RebindState {
|
||||||
|
bool active = false;
|
||||||
|
bool openPopup = false;
|
||||||
|
RebindKind kind{};
|
||||||
|
uint32_t port = 0;
|
||||||
|
uint16_t target = 0;
|
||||||
|
bool secondary = false;
|
||||||
|
SDL_JoystickID instance = 0;
|
||||||
|
Clock::time_point deadline{};
|
||||||
|
std::string label;
|
||||||
|
std::array<bool, SDL_SCANCODE_COUNT> keys{};
|
||||||
|
uint32_t mouse = 0;
|
||||||
|
std::array<bool, SDL_GAMEPAD_BUTTON_COUNT> buttons{};
|
||||||
|
std::array<bool, SDL_GAMEPAD_AXIS_COUNT> axesReady{};
|
||||||
|
} g_rebind;
|
||||||
|
|
||||||
|
void BeginRebind(RebindKind kind, uint16_t target, const char* label, bool secondary = false) {
|
||||||
|
g_rebind = {};
|
||||||
|
g_rebind.active = true;
|
||||||
|
g_rebind.openPopup = true;
|
||||||
|
g_rebind.kind = kind;
|
||||||
|
g_rebind.port = static_cast<uint32_t>(g_controllerPort);
|
||||||
|
g_rebind.target = target;
|
||||||
|
g_rebind.secondary = secondary;
|
||||||
|
g_rebind.label = label;
|
||||||
|
g_rebind.deadline = Clock::now() + std::chrono::seconds(10);
|
||||||
|
int count = 0;
|
||||||
|
const bool* keys = SDL_GetKeyboardState(&count);
|
||||||
|
std::copy_n(keys, std::min(count, static_cast<int>(g_rebind.keys.size())), g_rebind.keys.begin());
|
||||||
|
g_rebind.mouse = SDL_GetMouseState(nullptr, nullptr);
|
||||||
|
const int index = PADGetIndexForPort(g_rebind.port);
|
||||||
|
if (kind == RebindKind::Controller && index >= 0) {
|
||||||
|
if (auto* pad = PADGetSDLGamepadForIndex(index)) {
|
||||||
|
g_rebind.instance = SDL_GetGamepadID(pad);
|
||||||
|
for (int i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i)
|
||||||
|
g_rebind.buttons[i] = SDL_GetGamepadButton(pad, static_cast<SDL_GamepadButton>(i));
|
||||||
|
for (int i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i)
|
||||||
|
g_rebind.axesReady[i] = std::abs(static_cast<int>(SDL_GetGamepadAxis(pad, static_cast<SDL_GamepadAxis>(i)))) < 8000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompleteRebind(uint32_t value) {
|
||||||
|
const auto& capture = g_rebind;
|
||||||
|
if (capture.kind == RebindKind::Controller) {
|
||||||
|
const int index = PADGetIndexForPort(capture.port);
|
||||||
|
auto* pad = index >= 0 ? PADGetSDLGamepadForIndex(index) : nullptr;
|
||||||
|
if (pad == nullptr || SDL_GetGamepadID(pad) != capture.instance) {
|
||||||
|
g_rebind.active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (capture.secondary) PADSetAltButtonMapping(capture.port, {value, capture.target});
|
||||||
|
else PADSetButtonMapping(capture.port, {value, capture.target});
|
||||||
|
uint32_t count = 0, altCount = 0;
|
||||||
|
auto* primary = PADGetButtonMappings(capture.port, &count);
|
||||||
|
auto* alternate = PADGetAltButtonMappings(capture.port, &altCount);
|
||||||
|
uint32_t primaryValue = PAD_NATIVE_BUTTON_INVALID, alternateValue = PAD_NATIVE_BUTTON_INVALID;
|
||||||
|
for (uint32_t i = 0; i < count; ++i)
|
||||||
|
if (primary[i].padButton == capture.target) primaryValue = primary[i].nativeButton;
|
||||||
|
for (uint32_t i = 0; i < altCount; ++i)
|
||||||
|
if (alternate[i].padButton == capture.target) alternateValue = alternate[i].nativeButton;
|
||||||
|
std::string config = NativeBindingConfig(primaryValue);
|
||||||
|
if (alternateValue != PAD_NATIVE_BUTTON_INVALID) config += ',' + NativeBindingConfig(alternateValue);
|
||||||
|
for (size_t i = 0; i < kControllerButtons.size(); ++i)
|
||||||
|
if (kControllerButtons[i].padButton == capture.target) RuntimeConfigFile::SetControllerButton(i, config);
|
||||||
|
} else if (capture.kind == RebindKind::KeyboardButton) {
|
||||||
|
PADSetKeyButtonBinding(capture.port, {static_cast<int32_t>(value), capture.target});
|
||||||
|
} else {
|
||||||
|
PADSetKeyAxisBinding(capture.port, {static_cast<int32_t>(value), capture.target, 1});
|
||||||
|
}
|
||||||
|
PADSerializeMappings();
|
||||||
|
g_rebind.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawRebindPrompt() {
|
||||||
|
if (g_rebind.openPopup) {
|
||||||
|
ImGui::OpenPopup("Rebind input");
|
||||||
|
g_rebind.openPopup = false;
|
||||||
|
}
|
||||||
|
if (!ImGui::BeginPopupModal("Rebind input", &g_rebind.active, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
g_rebind.active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (g_rebind.active) {
|
||||||
|
ImGui::Text("Rebind: %s", g_rebind.label.c_str());
|
||||||
|
ImGui::TextUnformatted(g_rebind.kind == RebindKind::Controller
|
||||||
|
? "Press a controller button, pull a trigger, or move a stick."
|
||||||
|
: "Press a keyboard key or click a mouse button.");
|
||||||
|
ImGui::TextUnformatted("Release any held input first. Backspace or Delete clears the mapping.");
|
||||||
|
ImGui::TextUnformatted("Escape can be bound. F10 is reserved for settings.");
|
||||||
|
const float remaining = std::chrono::duration<float>(g_rebind.deadline - Clock::now()).count();
|
||||||
|
ImGui::Text("Unmapped in %d seconds", std::max(0, static_cast<int>(std::ceil(remaining))));
|
||||||
|
const bool clear = ImGui::Button("Clear mapping");
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel")) g_rebind.active = false;
|
||||||
|
// UI clicks must not become mouse bindings (buttons activate on release).
|
||||||
|
const bool overControl = ImGui::IsAnyItemHovered();
|
||||||
|
if (g_rebind.active && (clear || remaining <= 0.0f)) {
|
||||||
|
CompleteRebind(g_rebind.kind == RebindKind::Controller ? PAD_NATIVE_BUTTON_DISABLED
|
||||||
|
: static_cast<uint32_t>(PAD_KEY_INVALID));
|
||||||
|
} else if (g_rebind.active && SDL_GetKeyboardFocus() != nullptr && g_rebind.kind != RebindKind::Controller) {
|
||||||
|
int count = 0;
|
||||||
|
const bool* keys = SDL_GetKeyboardState(&count);
|
||||||
|
for (int i = 1; i < std::min(count, static_cast<int>(SDL_SCANCODE_COUNT)) && g_rebind.active; ++i) {
|
||||||
|
if (keys[i] && !g_rebind.keys[i] && i != SDL_SCANCODE_F10) CompleteRebind(i);
|
||||||
|
g_rebind.keys[i] = keys[i];
|
||||||
|
}
|
||||||
|
const uint32_t mouse = SDL_GetMouseState(nullptr, nullptr);
|
||||||
|
for (int i = 1; i <= 5 && g_rebind.active; ++i)
|
||||||
|
if (!overControl && (mouse & ~g_rebind.mouse & (1u << (i - 1))) != 0) CompleteRebind(static_cast<uint32_t>(-i - 1));
|
||||||
|
g_rebind.mouse = mouse;
|
||||||
|
} else if (g_rebind.active && SDL_GetKeyboardFocus() != nullptr && g_rebind.kind == RebindKind::Controller) {
|
||||||
|
auto* pad = SDL_GetGamepadFromID(g_rebind.instance);
|
||||||
|
if (pad != nullptr) {
|
||||||
|
for (int i = 0; i < SDL_GAMEPAD_BUTTON_COUNT && g_rebind.active; ++i) {
|
||||||
|
const bool pressed = SDL_GetGamepadButton(pad, static_cast<SDL_GamepadButton>(i));
|
||||||
|
if (pressed && !g_rebind.buttons[i]) CompleteRebind(i);
|
||||||
|
g_rebind.buttons[i] = pressed;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < SDL_GAMEPAD_AXIS_COUNT && g_rebind.active; ++i) {
|
||||||
|
const int value = SDL_GetGamepadAxis(pad, static_cast<SDL_GamepadAxis>(i));
|
||||||
|
if (std::abs(value) < 8000) g_rebind.axesReady[i] = true;
|
||||||
|
if (g_rebind.axesReady[i] && std::abs(value) >= 16384)
|
||||||
|
CompleteRebind(PADEncodeAxisButton(i, value < 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!g_rebind.active) ImGui::CloseCurrentPopup();
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawKeyBinding(const char* label, int scancode, RebindKind kind, uint16_t target) {
|
||||||
|
const std::string caption = std::string(KeyBindingName(scancode)) + "##binding";
|
||||||
|
if (ImGui::Button(caption.c_str(), ImVec2(220.0f, 0.0f))) BeginRebind(kind, target, label);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextUnformatted(label);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawKeyboardSettings(uint32_t port) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
auto* buttons = PADGetKeyButtonBindings(port, &count);
|
||||||
|
bool enabled = buttons != nullptr;
|
||||||
|
bool usePreset = false;
|
||||||
|
if (ImGui::Checkbox("Keyboard and mouse", &enabled)) {
|
||||||
|
PADSetKeyboardActive(port, enabled);
|
||||||
|
PADSerializeMappings();
|
||||||
|
buttons = PADGetKeyButtonBindings(port, &count);
|
||||||
|
usePreset = enabled && std::all_of(buttons, buttons + count, [](const auto& binding) {
|
||||||
|
return binding.scancode == PAD_KEY_INVALID;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!enabled) return false;
|
||||||
|
ImGui::TextDisabled("Replaces the gamepad on this port. F10 opens settings.");
|
||||||
|
if (ImGui::Button("Use WASD + mouse preset") || usePreset) {
|
||||||
|
const std::array<int, PAD_BUTTON_COUNT> keys = {
|
||||||
|
PAD_KEY_MOUSE_LEFT, SDL_SCANCODE_SPACE, SDL_SCANCODE_E, SDL_SCANCODE_Q,
|
||||||
|
SDL_SCANCODE_RETURN, PAD_KEY_MOUSE_MIDDLE, SDL_SCANCODE_LSHIFT, PAD_KEY_MOUSE_RIGHT,
|
||||||
|
SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT,
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < keys.size(); ++i)
|
||||||
|
PADSetKeyButtonBinding(port, {keys[i], kControllerButtons[i].padButton});
|
||||||
|
const std::array<int, PAD_AXIS_COUNT> axes = {
|
||||||
|
SDL_SCANCODE_D, SDL_SCANCODE_A, SDL_SCANCODE_W, SDL_SCANCODE_S,
|
||||||
|
SDL_SCANCODE_L, SDL_SCANCODE_J, SDL_SCANCODE_I, SDL_SCANCODE_K,
|
||||||
|
SDL_SCANCODE_LSHIFT, PAD_KEY_MOUSE_RIGHT,
|
||||||
|
};
|
||||||
|
uint32_t axisCount = 0;
|
||||||
|
auto* mappings = PADGetKeyAxisBindings(port, &axisCount);
|
||||||
|
for (uint32_t i = 0; i < axisCount; ++i)
|
||||||
|
PADSetKeyAxisBinding(port, {axes[i], mappings[i].padAxis, 1});
|
||||||
|
PADSerializeMappings();
|
||||||
|
}
|
||||||
|
ImGui::SeparatorText("Button mapping");
|
||||||
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
|
int key = buttons[i].scancode;
|
||||||
|
ImGui::PushID(static_cast<int>(i));
|
||||||
|
ImGui::SetNextItemWidth(220.0f);
|
||||||
|
DrawKeyBinding(PADGetButtonName(buttons[i].padButton), key, RebindKind::KeyboardButton, buttons[i].padButton);
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
ImGui::SeparatorText("Stick and trigger mapping");
|
||||||
|
uint32_t axisCount = 0;
|
||||||
|
auto* axes = PADGetKeyAxisBindings(port, &axisCount);
|
||||||
|
for (uint32_t i = 0; i < axisCount; ++i) {
|
||||||
|
int key = axes[i].scancode;
|
||||||
|
ImGui::PushID(static_cast<int>(count + i));
|
||||||
|
const char* direction = PADGetAxisDirectionLabel(axes[i].padAxis);
|
||||||
|
const std::string label = std::string(PADGetAxisName(axes[i].padAxis)) + " " +
|
||||||
|
(direction != nullptr ? direction : "");
|
||||||
|
ImGui::SetNextItemWidth(220.0f);
|
||||||
|
DrawKeyBinding(label.c_str(), key, RebindKind::KeyboardAxis, axes[i].padAxis);
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Controller settings menu: port selection, controller assignment and button mapping.
|
// Controller settings menu: port selection, controller assignment and button mapping.
|
||||||
int ExpressionResizeCallback(ImGuiInputTextCallbackData* data) {
|
int ExpressionResizeCallback(ImGuiInputTextCallbackData* data) {
|
||||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
|
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
|
||||||
@@ -501,6 +734,10 @@ void DrawControllerSettings() {
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
const uint32_t selectedGamePort = static_cast<uint32_t>(g_controllerPort);
|
const uint32_t selectedGamePort = static_cast<uint32_t>(g_controllerPort);
|
||||||
|
if (DrawKeyboardSettings(selectedGamePort)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
const char* currentName = PADGetName(selectedGamePort);
|
const char* currentName = PADGetName(selectedGamePort);
|
||||||
ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None");
|
ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None");
|
||||||
if (ImGui::MenuItem("Unassign controller")) {
|
if (ImGui::MenuItem("Unassign controller")) {
|
||||||
@@ -542,10 +779,10 @@ void DrawControllerSettings() {
|
|||||||
PADGetAltButtonMappings(static_cast<uint32_t>(g_controllerPort), &altMappingCount);
|
PADGetAltButtonMappings(static_cast<uint32_t>(g_controllerPort), &altMappingCount);
|
||||||
|
|
||||||
const auto writeBinding = [](size_t index, uint32_t primaryNative, uint32_t altNative) {
|
const auto writeBinding = [](size_t index, uint32_t primaryNative, uint32_t altNative) {
|
||||||
std::string value = NativeButtonForValue(primaryNative).configName;
|
std::string value = NativeBindingConfig(primaryNative);
|
||||||
if (altNative != PAD_NATIVE_BUTTON_INVALID) {
|
if (altNative != PAD_NATIVE_BUTTON_INVALID) {
|
||||||
value += ',';
|
value += ',';
|
||||||
value += NativeButtonForValue(altNative).configName;
|
value += NativeBindingConfig(altNative);
|
||||||
}
|
}
|
||||||
RuntimeConfigFile::SetControllerButton(index, value);
|
RuntimeConfigFile::SetControllerButton(index, value);
|
||||||
};
|
};
|
||||||
@@ -603,6 +840,11 @@ void DrawControllerSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SeparatorText("Button mapping");
|
ImGui::SeparatorText("Button mapping");
|
||||||
|
ImGui::TextDisabled("LT / L2 = left trigger. RT / R2 = right trigger.");
|
||||||
|
ImGui::TextDisabled("LB / L1 = left shoulder. RB / R1 = right shoulder.");
|
||||||
|
ImGui::TextDisabled("Click a binding, then press an input. No input for 10 seconds clears it.");
|
||||||
|
const float bindingWidth = ImGui::CalcTextSize("Right shoulder (RB / R1)").x +
|
||||||
|
ImGui::GetFrameHeight() + ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||||
for (size_t i = 0; i < kControllerButtons.size(); ++i) {
|
for (size_t i = 0; i < kControllerButtons.size(); ++i) {
|
||||||
auto mappingIt = std::find_if(mappings, mappings + mappingCount, [&](const PADButtonMapping& mapping) {
|
auto mappingIt = std::find_if(mappings, mappings + mappingCount, [&](const PADButtonMapping& mapping) {
|
||||||
return mapping.padButton == kControllerButtons[i].padButton;
|
return mapping.padButton == kControllerButtons[i].padButton;
|
||||||
@@ -622,30 +864,40 @@ void DrawControllerSettings() {
|
|||||||
|
|
||||||
const NativeButtonItem& current = NativeButtonForValue(mappingIt->nativeButton);
|
const NativeButtonItem& current = NativeButtonForValue(mappingIt->nativeButton);
|
||||||
ImGui::PushID(static_cast<int>(i));
|
ImGui::PushID(static_cast<int>(i));
|
||||||
ImGui::SetNextItemWidth(190.0f);
|
const auto drawThreshold = [&](PADButtonMapping* mapping, bool secondary) {
|
||||||
if (ImGui::BeginCombo("##primary", current.label)) {
|
if (!PADIsAxisButton(mapping->nativeButton)) return;
|
||||||
for (const auto& candidate : kNativeButtons) {
|
int threshold = static_cast<int>(PADAxisButtonThreshold(mapping->nativeButton));
|
||||||
const bool selected = candidate.nativeButton == mappingIt->nativeButton;
|
ImGui::SetNextItemWidth(bindingWidth);
|
||||||
if (ImGui::Selectable(candidate.label, selected)) {
|
if (ImGui::SliderInt(secondary ? "##altThreshold" : "##primaryThreshold", &threshold,
|
||||||
const uint32_t port = static_cast<uint32_t>(g_controllerPort);
|
1, 100, "Threshold: %d%%", ImGuiSliderFlags_AlwaysClamp)) {
|
||||||
PADSetButtonMapping(port, PADButtonMapping{candidate.nativeButton, kControllerButtons[i].padButton});
|
const PADButtonMapping updated = {
|
||||||
writeBinding(i, candidate.nativeButton,
|
PADAxisButtonIdentity(mapping->nativeButton) | (static_cast<uint32_t>(threshold) << 8),
|
||||||
|
mapping->padButton,
|
||||||
|
};
|
||||||
|
if (secondary) PADSetAltButtonMapping(selectedGamePort, updated);
|
||||||
|
else PADSetButtonMapping(selectedGamePort, updated);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
|
writeBinding(i, mappingIt->nativeButton,
|
||||||
altIt != nullptr ? altIt->nativeButton : PAD_NATIVE_BUTTON_INVALID);
|
altIt != nullptr ? altIt->nativeButton : PAD_NATIVE_BUTTON_INVALID);
|
||||||
PADSerializeMappings();
|
PADSerializeMappings();
|
||||||
mappings = PADGetButtonMappings(port, &mappingCount);
|
|
||||||
}
|
}
|
||||||
if (selected) {
|
};
|
||||||
ImGui::SetItemDefaultFocus();
|
ImGui::BeginGroup();
|
||||||
}
|
ImGui::SetNextItemWidth(bindingWidth);
|
||||||
}
|
const std::string primaryCaption = std::string(current.label) + "##primary";
|
||||||
ImGui::EndCombo();
|
if (ImGui::Button(primaryCaption.c_str(), ImVec2(bindingWidth, 0.0f))) {
|
||||||
|
BeginRebind(RebindKind::Controller, kControllerButtons[i].padButton, kControllerButtons[i].label);
|
||||||
}
|
}
|
||||||
|
drawThreshold(mappingIt, false);
|
||||||
|
ImGui::EndGroup();
|
||||||
if (altIt != nullptr) {
|
if (altIt != nullptr) {
|
||||||
const bool altBound = altIt->nativeButton != PAD_NATIVE_BUTTON_INVALID;
|
const bool altBound = altIt->nativeButton != PAD_NATIVE_BUTTON_INVALID;
|
||||||
if (!altBound && !altRowExpanded[i]) {
|
if (!altBound && !altRowExpanded[i]) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::SmallButton("+")) {
|
if (ImGui::SmallButton("+")) {
|
||||||
altRowExpanded[i] = true;
|
altRowExpanded[i] = true;
|
||||||
|
BeginRebind(RebindKind::Controller, kControllerButtons[i].padButton, kControllerButtons[i].label, true);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Add a second binding; pressing either one works");
|
ImGui::SetTooltip("Add a second binding; pressing either one works");
|
||||||
@@ -654,27 +906,15 @@ void DrawControllerSettings() {
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::TextUnformatted("or");
|
ImGui::TextUnformatted("or");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginGroup();
|
||||||
const char* altLabel = altBound ? NativeButtonForValue(altIt->nativeButton).label : "None";
|
const char* altLabel = altBound ? NativeButtonForValue(altIt->nativeButton).label : "None";
|
||||||
ImGui::SetNextItemWidth(190.0f);
|
ImGui::SetNextItemWidth(bindingWidth);
|
||||||
if (ImGui::BeginCombo("##alt", altLabel)) {
|
const std::string altCaption = std::string(altLabel) + "##alt";
|
||||||
for (const auto& candidate : kNativeButtons) {
|
if (ImGui::Button(altCaption.c_str(), ImVec2(bindingWidth, 0.0f))) {
|
||||||
const bool isNone = candidate.nativeButton == PAD_NATIVE_BUTTON_INVALID;
|
BeginRebind(RebindKind::Controller, kControllerButtons[i].padButton, kControllerButtons[i].label, true);
|
||||||
const bool selected = candidate.nativeButton == altIt->nativeButton;
|
|
||||||
if (ImGui::Selectable(isNone ? "None" : candidate.label, selected)) {
|
|
||||||
const uint32_t port = static_cast<uint32_t>(g_controllerPort);
|
|
||||||
PADSetAltButtonMapping(
|
|
||||||
port, PADButtonMapping{candidate.nativeButton, kControllerButtons[i].padButton});
|
|
||||||
writeBinding(i, mappingIt->nativeButton, candidate.nativeButton);
|
|
||||||
if (isNone) {
|
|
||||||
altRowExpanded[i] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
ImGui::SetItemDefaultFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
}
|
||||||
|
drawThreshold(altIt, true);
|
||||||
|
ImGui::EndGroup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
@@ -936,10 +1176,27 @@ void DrawStartupScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DrawTopBar() {
|
void DrawTopBar() {
|
||||||
if (!g_topBarVisible || !ImGui::BeginMainMenuBar()) {
|
if (!g_topBarVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||||
|
ImGui::GetBackgroundDrawList()->AddRectFilled(viewport->Pos,
|
||||||
|
ImVec2(viewport->Pos.x + viewport->Size.x, viewport->Pos.y + viewport->Size.y),
|
||||||
|
IM_COL32(0, 0, 0, 70));
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + viewport->Size.x * 0.5f,
|
||||||
|
viewport->Pos.y + viewport->Size.y - 24.0f),
|
||||||
|
ImGuiCond_Always, ImVec2(0.5f, 1.0f));
|
||||||
|
ImGui::SetNextWindowBgAlpha(0.85f);
|
||||||
|
if (ImGui::Begin("Settings input hint", nullptr,
|
||||||
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
|
||||||
|
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings |
|
||||||
|
ImGuiWindowFlags_NoFocusOnAppearing)) {
|
||||||
|
ImGui::TextUnformatted("Settings open - game controls disabled. Press F10 to return to the game.");
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
if (!ImGui::BeginMainMenuBar()) return;
|
||||||
|
|
||||||
ImGui::TextUnformatted("WiiCompiled");
|
ImGui::TextUnformatted("WiiCompiled");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
const auto resolutionIt = std::find_if(kResolutions.begin(), kResolutions.end(), [](const ResolutionItem& item) {
|
const auto resolutionIt = std::find_if(kResolutions.begin(), kResolutions.end(), [](const ResolutionItem& item) {
|
||||||
@@ -968,6 +1225,9 @@ void DrawTopBar() {
|
|||||||
|
|
||||||
if (ImGui::BeginMenu("Controller settings")) {
|
if (ImGui::BeginMenu("Controller settings")) {
|
||||||
DrawControllerSettings();
|
DrawControllerSettings();
|
||||||
|
// Nest capture under this menu so opening/closing the modal preserves
|
||||||
|
// the settings popup and its current port and scroll position.
|
||||||
|
DrawRebindPrompt();
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,7 +1334,12 @@ void HandleEvents(const AuroraEvent* events) noexcept {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
controller_mapping_wizard::HandleSdlEvent(ev->sdl);
|
controller_mapping_wizard::HandleSdlEvent(ev->sdl);
|
||||||
if (IsToggleKey(ev->sdl, SDL_SCANCODE_F10)) {
|
if (g_rebind.active && (IsToggleKey(ev->sdl, SDL_SCANCODE_BACKSPACE) ||
|
||||||
|
IsToggleKey(ev->sdl, SDL_SCANCODE_DELETE))) {
|
||||||
|
CompleteRebind(g_rebind.kind == RebindKind::Controller ? PAD_NATIVE_BUTTON_DISABLED
|
||||||
|
: static_cast<uint32_t>(PAD_KEY_INVALID));
|
||||||
|
}
|
||||||
|
if (!g_rebind.active && IsToggleKey(ev->sdl, SDL_SCANCODE_F10)) {
|
||||||
SetTopBarVisible(!g_topBarVisible);
|
SetTopBarVisible(!g_topBarVisible);
|
||||||
}
|
}
|
||||||
if (IsMouseActivity(ev->sdl)) {
|
if (IsMouseActivity(ev->sdl)) {
|
||||||
@@ -1102,7 +1367,7 @@ void Draw() noexcept {
|
|||||||
DrawTopBar();
|
DrawTopBar();
|
||||||
controller_mapping_wizard::Draw();
|
controller_mapping_wizard::Draw();
|
||||||
// The wizard captures raw presses; keep them out of the game.
|
// The wizard captures raw presses; keep them out of the game.
|
||||||
const bool inputBlocked = controller_mapping_wizard::IsActive();
|
const bool inputBlocked = controller_mapping_wizard::IsActive() || g_rebind.active;
|
||||||
PADBlockInput(inputBlocked);
|
PADBlockInput(inputBlocked);
|
||||||
InputBindings::SetInputBlocked(inputBlocked);
|
InputBindings::SetInputBlocked(inputBlocked);
|
||||||
DrawStartupScreen();
|
DrawStartupScreen();
|
||||||
|
|||||||
@@ -97,29 +97,15 @@ int main() {
|
|||||||
Check(!Pressed(pulse), "pulse expires");
|
Check(!Pressed(pulse), "pulse expires");
|
||||||
|
|
||||||
// The timing-window idiom seen in shared Dolphin configs.
|
// The timing-window idiom seen in shared Dolphin configs.
|
||||||
auto window = Compile("!pulse(`W`, 0.05) & pulse(`W`, 0.35)");
|
auto window = Compile("!pulse(`W`, 0.05) & pulse(`W`, 0.15)");
|
||||||
g_inputs["W"] = 0.0;
|
g_inputs["W"] = 0.0;
|
||||||
window.Evaluate(Source());
|
window.Evaluate(Source());
|
||||||
g_inputs["W"] = 1.0;
|
g_inputs["W"] = 1.0;
|
||||||
Check(!Pressed(window), "window closed before its start");
|
Check(!Pressed(window), "window closed before its start");
|
||||||
bool windowOpened = false;
|
Sleep(90);
|
||||||
for (int i = 0; i < 40; ++i) {
|
Check(Pressed(window), "window open between the two pulses");
|
||||||
Sleep(10);
|
Sleep(90);
|
||||||
if (Pressed(window)) {
|
Check(!Pressed(window), "window closed after its end");
|
||||||
windowOpened = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Check(windowOpened, "window open between the two pulses");
|
|
||||||
bool windowClosed = false;
|
|
||||||
for (int i = 0; i < 50; ++i) {
|
|
||||||
Sleep(10);
|
|
||||||
if (!Pressed(window)) {
|
|
||||||
windowClosed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Check(windowClosed, "window closed after its end");
|
|
||||||
|
|
||||||
// timer ramps 0..1 and wraps, so a threshold turns it into a square wave.
|
// timer ramps 0..1 and wraps, so a threshold turns it into a square wave.
|
||||||
auto timer = Compile("`X` & timer(0.1)");
|
auto timer = Compile("`X` & timer(0.1)");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -378,7 +378,7 @@ public sealed partial class CxxLinearCodeGenerator
|
|||||||
instructionContinuationLabels.TryGetValue(trace.Address, out var continuationLabel))
|
instructionContinuationLabels.TryGetValue(trace.Address, out var continuationLabel))
|
||||||
{
|
{
|
||||||
RecordEmittedLocalLabel(continuationLabel);
|
RecordEmittedLocalLabel(continuationLabel);
|
||||||
body.AppendLine($"{continuationLabel}: ;");
|
body.AppendLine($"{continuationLabel}:");
|
||||||
}
|
}
|
||||||
var localFallthroughLr = TryGetLocalFallthroughLr(block.Instructions, i, nonReturningCallTargets, lrContinuationCallTargets);
|
var localFallthroughLr = TryGetLocalFallthroughLr(block.Instructions, i, nonReturningCallTargets, lrContinuationCallTargets);
|
||||||
// State-free bodies are cloned after register caching and lose their
|
// State-free bodies are cloned after register caching and lose their
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Translator.Core.Analysis.Representation;
|
using Translator.Core.Analysis.Representation;
|
||||||
using Translator.Core.Analysis.Ssa;
|
using Translator.Core.Analysis.Ssa;
|
||||||
using Translator.Core.CodeGen;
|
using Translator.Core.CodeGen;
|
||||||
@@ -153,35 +153,4 @@ public class EmittedOutputShapeTests
|
|||||||
Assert.Contains("f3.d = MemoryInline::FlatReadFloat32((r4 + 16));", code, StringComparison.Ordinal);
|
Assert.Contains("f3.d = MemoryInline::FlatReadFloat32((r4 + 16));", code, StringComparison.Ordinal);
|
||||||
Assert.Contains("f4.d = MemoryInline::FlatReadFloat64((r4 + 24));", code, StringComparison.Ordinal);
|
Assert.Contains("f4.d = MemoryInline::FlatReadFloat64((r4 + 24));", code, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ContinuationLabelAtBlockEndEmitsValidCxx17Statement()
|
|
||||||
{
|
|
||||||
var function = new IrFunction("continuation_at_block_end", "0x800E7798", new[]
|
|
||||||
{
|
|
||||||
new IrBasicBlock("0x800E7798", new IrInstruction[]
|
|
||||||
{
|
|
||||||
new IrCall(string.Empty, "0x8179B000", System.Array.Empty<IrValue>()),
|
|
||||||
new IrTracePpc(0x800E77A0u, "nop", "0x60000000"),
|
|
||||||
new IrJump("0x800E77A4")
|
|
||||||
}),
|
|
||||||
new IrBasicBlock("0x800E77A4", new IrInstruction[]
|
|
||||||
{
|
|
||||||
new IrReturn(null)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
var types = new RepresentationEnvironment(new Dictionary<string, ValueRepresentation>());
|
|
||||||
var code = new CxxLinearCodeGenerator().Emit(
|
|
||||||
0x800E7798,
|
|
||||||
new SsaTransformer().Convert(function),
|
|
||||||
new FunctionAbiClassification(function.Name, ValueRepresentation.Void),
|
|
||||||
types,
|
|
||||||
lrContinuationCallTargets: new HashSet<uint> { 0x8179B000u });
|
|
||||||
|
|
||||||
// In C++17, a label before a closing brace is invalid without an intervening statement.
|
|
||||||
Assert.Contains("loc_800E77A0: ;", code, StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain("loc_800E77A0:\n}", code.Replace("\r\n", "\n"), StringComparison.Ordinal);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,39 @@ public class RetroWfcPayloadLoweringTests
|
|||||||
Assert.Equal("moduleFunction", pointer.TargetKind);
|
Assert.Equal("moduleFunction", pointer.TargetKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProductionPayloadValidatesAndTranslatesEverySupportedPatch()
|
||||||
|
{
|
||||||
|
var payloadRoot = Path.Combine(
|
||||||
|
AppContext.BaseDirectory,
|
||||||
|
"TestAssets",
|
||||||
|
"RetroWfcPayload");
|
||||||
|
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(payloadRoot);
|
||||||
|
var payloadPath = Path.Combine(
|
||||||
|
payloadRoot,
|
||||||
|
"binary",
|
||||||
|
"payload.RMCPD00.bin");
|
||||||
|
var payload = File.ReadAllBytes(payloadPath);
|
||||||
|
|
||||||
|
var result = RetroWfcPayload.Parse(
|
||||||
|
payload,
|
||||||
|
ProductionPayloadManifest(),
|
||||||
|
0x81800000u,
|
||||||
|
0x00200000u,
|
||||||
|
"TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin");
|
||||||
|
|
||||||
|
Assert.Equal("RMCPD00", result.Summary.Game);
|
||||||
|
Assert.Equal(payload.Length, result.Summary.PayloadImageSize);
|
||||||
|
Assert.True(result.LoweringPlan.IsPlannable);
|
||||||
|
Assert.Empty(result.LoweringPlan.Issues);
|
||||||
|
Assert.NotEmpty(result.LoweringPlan.StaticBytePatches);
|
||||||
|
Assert.NotEmpty(result.LoweringPlan.ExecutableHooks);
|
||||||
|
Assert.NotEmpty(result.LoweringPlan.StaticPointers);
|
||||||
|
Assert.All(result.LoweringPlan.ExecutableHooks, hook => Assert.NotNull(hook.TargetAddress));
|
||||||
|
Assert.All(result.LoweringPlan.StaticPointers, pointer => Assert.NotNull(pointer.TargetAddress));
|
||||||
|
Assert.NotEmpty(result.Summary.InitializationCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
private static BaseManifest TestManifest() =>
|
private static BaseManifest TestManifest() =>
|
||||||
new(
|
new(
|
||||||
"test",
|
"test",
|
||||||
@@ -52,6 +85,51 @@ public class RetroWfcPayloadLoweringTests
|
|||||||
],
|
],
|
||||||
"ranges.json");
|
"ranges.json");
|
||||||
|
|
||||||
|
// The payload parser needs the base image's address classes and containing
|
||||||
|
// function ranges to prove every patch can be lowered. A single synthetic
|
||||||
|
// executable and writable ranges are sufficient here: the assertions above
|
||||||
|
// test the real production payload without checking proprietary game bytes
|
||||||
|
// into CI. The split also proves pointer patches lower as data writes.
|
||||||
|
private static BaseManifest ProductionPayloadManifest() =>
|
||||||
|
new(
|
||||||
|
"test",
|
||||||
|
1,
|
||||||
|
"RMCP01",
|
||||||
|
"P",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
[
|
||||||
|
new BaseSectionMetadata(
|
||||||
|
".synthetic-text",
|
||||||
|
"synthetic.dol",
|
||||||
|
0x80000000u,
|
||||||
|
0x80800000u,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"synthetic_text.bin",
|
||||||
|
0),
|
||||||
|
new BaseSectionMetadata(
|
||||||
|
".synthetic-data",
|
||||||
|
"synthetic.dol",
|
||||||
|
0x80800000u,
|
||||||
|
0x81000000u,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"synthetic_data.bin",
|
||||||
|
0)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new BaseFunctionRangeMetadata(
|
||||||
|
0x80000000u,
|
||||||
|
0x80800000u,
|
||||||
|
"synthetic_base",
|
||||||
|
".synthetic-text",
|
||||||
|
0,
|
||||||
|
"test",
|
||||||
|
["Executable"])
|
||||||
|
],
|
||||||
|
"ranges.json");
|
||||||
|
|
||||||
private static byte[] BuildSharedPayloadFixture()
|
private static byte[] BuildSharedPayloadFixture()
|
||||||
{
|
{
|
||||||
var payload = new byte[0x240];
|
var payload = new byte[0x240];
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -26,6 +26,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Translator.Core\Translator.Core.csproj" />
|
<ProjectReference Include="..\..\src\Translator.Core\Translator.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Translator.Cli\Translator.Cli.csproj" />
|
<ProjectReference Include="..\..\src\Translator.Cli\Translator.Cli.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\Launcher\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="TestAssets\RetroWfcPayload\binary\payload.RMCPD00.bin" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
Reference in New Issue
Block a user