NTSC-U and NTSC-J support (#326)

* add ntsc-u and ntsc-j support

Co-authored-by: DarthMDev <mgracer48@yahoo.com>
Co-authored-by: rooklz <rookieb@muzo-works.com>

* hash inc files and require project for shards

* port remaining stale rr us hooks

* drop map report generation

* drop one-off region porters

---------

Co-authored-by: DarthMDev <mgracer48@yahoo.com>
Co-authored-by: rooklz <rookieb@muzo-works.com>
This commit is contained in:
patchzyy
2026-10-21 17:37:44 +02:00
committed by GitHub
parent ad95c5c7da
commit e9f1fdd77c
95 changed files with 64733 additions and 646 deletions
+7
View File
@@ -4,3 +4,10 @@
# Patch files must stay LF: git apply matches context bytes against LF upstream sources
*.patch -text
translator/tests/Translator.Tests/TestAssets/**/*.bin binary
# Region tables reproduced by tools/region/
projects/mkwii-ntsc-*/MAP.txt linguist-generated=true
projects/mkwii-ntsc-*/region_port.json linguist-generated=true
projects/mkwii-ntsc-j/data_addresses.txt linguist-generated=true
runtime/include/guest_abi/guest_address_table.inc linguist-generated=true
runtime/include/guest_function_imports.inc linguist-generated=true
+3 -1
View File
@@ -66,7 +66,9 @@ project.lock.json
/.vs/
/.vscode/
/.idea/
/tools/
/tools/*
!/tools/region/
__pycache__/
/TEMP/
/tmp/
/test_output/
+23 -12
View File
@@ -23,12 +23,23 @@ $publish = Join-Path $workRoot 'publish'
$payloadRoot = Join-Path $workRoot 'payload'
$setupProject = Join-Path $PSScriptRoot 'WiiCompiled.Setup.Windows\WiiCompiled.Setup.Windows.csproj'
$translatorProject = Join-Path $repoRoot 'translator\src\Translator.Cli\Translator.Cli.csproj'
$projectFile = Join-Path $repoRoot 'projects\mkwii\recomp.yml'
# Everything Mario-Kart-specific in the manifest below is read from the project file rather than
# restated here, and Test-PinnedFacts.ps1 checks the copies that cannot read it (the C++ entry
# address, the C# endpoint constant, the lists this script and the installed host both carry).
$pins = Get-MkwProjectPins $projectFile
# Every supported release is one projects/<name>/recomp.yml. Everything Mario-Kart-specific in the
# manifest below is read from those files rather than restated here, and Test-PinnedFacts.ps1
# checks the copies that cannot read them (the C++ entry address, the C# Retro-WFC table, the
# lists this script and the installed host both carry).
$releaseProjects = @(Get-ChildItem -LiteralPath (Join-Path $repoRoot 'projects') -Directory |
Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName 'recomp.yml') -PathType Leaf } |
Sort-Object Name)
if ($releaseProjects.Count -eq 0) { throw 'No translation project defines a supported release.' }
$supportedReleases = @(foreach ($releaseProject in $releaseProjects) {
$pins = Get-MkwProjectPins (Join-Path $releaseProject.FullName 'recomp.yml')
[ordered]@{ GameId = $pins.GameId; DolSha256 = $pins.DolSha256; RelSha256 = $pins.RelSha256 }
})
foreach ($key in @('GameId', 'DolSha256')) {
$duplicates = @($supportedReleases | Group-Object { $_[$key] } | Where-Object Count -gt 1)
if ($duplicates.Count -gt 0) { throw "Two translation projects pin the same $($key): $($duplicates[0].Name)." }
}
# The audit reports by throwing, which aborts this run; nothing native has been invoked yet, so
# there is no exit code to inspect.
& (Join-Path $PSScriptRoot 'Test-PinnedFacts.ps1') -RepositoryRoot $repoRoot
@@ -243,7 +254,9 @@ if (Test-Path -LiteralPath $runtimeDeveloperBuild) {
}
$auroraExtern = Join-Path $workspace 'aurora-main\extern'
Get-ChildItem -LiteralPath $auroraExtern -Directory -Force | Remove-Item -Recurse -Force
Copy-Directory (Join-Path $repoRoot 'projects\mkwii') (Join-Path $workspace 'projects\mkwii')
foreach ($releaseProject in $releaseProjects) {
Copy-Directory $releaseProject.FullName (Join-Path $workspace "projects\$($releaseProject.Name)")
}
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'LocalBuild.ps1') -Destination (Join-Path $workspace 'LocalBuild.ps1')
# LocalBuild.ps1 dot-sources the canonical configure flags from this sibling.
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1') -Destination (Join-Path $workspace 'NativeBuildFlags.ps1')
@@ -326,22 +339,20 @@ foreach ($required in @('ToolkitFingerprint','TranslationFingerprint','NativeToo
$productVersion = ((& $setupHost --version) -join '').Trim()
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($productVersion)) { throw 'The setup host did not report its version.' }
# Schema 3 replaced schema 2's single Expected* release fields with SupportedReleases.
$manifest = [ordered]@{
SchemaVersion = 2
SchemaVersion = 3
ProductVersion = $productVersion
ExpectedGameId = $pins.GameId
ExpectedDolSha256 = $pins.DolSha256
ExpectedRelSha256 = $pins.RelSha256
SupportedReleases = $supportedReleases
ToolkitReleaseTag = if ($ToolkitReleaseTag) { $ToolkitReleaseTag } else { '' }
BuildModel = 'local-translation-and-compilation'
RetroWfcPayloadUri = $pins.RetroWfcPayloadUri
ToolkitFingerprint = $identities.ToolkitFingerprint
ToolkitPackageFingerprint = $identities.ToolkitPackageFingerprint
RuntimeAssetsFingerprint = $identities.RuntimeAssetsFingerprint
TranslationFingerprint = $identities.TranslationFingerprint
NativeToolchainFingerprint = $identities.NativeToolchainFingerprint
}
$manifest | ConvertTo-Json | Set-Content (Join-Path $payloadRoot 'payload-manifest.json') -Encoding UTF8
$manifest | ConvertTo-Json -Depth 4 | Set-Content (Join-Path $payloadRoot 'payload-manifest.json') -Encoding UTF8
Write-Host '[4/6] Enforcing the copyright and generated-code boundary...'
# Both audits are PowerShell scripts that throw directly on failure (Test-PayloadBoundary.ps1
+15 -11
View File
@@ -166,7 +166,6 @@ $toolchainBin = Join-Path $toolchain 'llvm-mingw\bin'
$cc = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang.exe'
$cxx = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang++.exe'
$windres = Join-Path $toolchainBin 'x86_64-w64-mingw32-windres.exe'
$project = Join-Path $Workspace 'projects\mkwii\recomp.yml'
$assets = Join-Path $Workspace 'Assets'
$generated = Join-Path $Workspace 'generated'
$functions = Join-Path $generated 'functions'
@@ -183,14 +182,18 @@ $retroRoot = if ([string]::IsNullOrWhiteSpace($RetroRewindPackageDirectory)) {
foreach ($required in @(
@($translator, 'Bundled translator'), @($cmake, 'Bundled CMake'), @($ninja, 'Bundled Ninja'),
@($cc, 'Bundled C compiler'), @($cxx, 'Bundled C++ compiler'), @($windres, 'Bundled resource compiler'), @($project, 'Translation project'),
@($cc, 'Bundled C compiler'), @($cxx, 'Bundled C++ compiler'), @($windres, 'Bundled resource compiler'),
@((Join-Path $assets 'main.dol'), 'Extracted main.dol'), @((Join-Path $assets 'StaticR.rel'), 'Extracted StaticR.rel')
)) { Assert-File $required[0] $required[1] }
# Everything Mario-Kart-specific this script needs is read from the project file rather than
# restated here: the translation entry point below, and (through the retro-rewind profile the
# translator loads itself) the module guest/link bases the mod translation uses.
$pins = Get-MkwProjectPins $project
# translator loads itself) the module guest/link bases the mod translation uses. The project is
# the one pinned to the extracted main.dol, which decides the region.
$selectedProject = Resolve-MkwProjectForAssets $Workspace $assets
$project = $selectedProject.Path
$pins = $selectedProject.Pins
$retroWfcPayloadFile = "binary\payload.$(($pins.RetroWfcPayloadUri -split 'g=')[-1]).bin"
# Three independent knobs: $translatorThreads (the translator's own worker threads; peak memory is
# one heap that barely moves with thread count, so clamping it only slows translation), $translatedJobs
@@ -243,7 +246,8 @@ try {
# outputs plus --prune-stale mean Ninja only recompiles shards whose bytes actually moved.
$reuseBase = $false
if (-not $ForceCleanBuild -and -not [string]::IsNullOrWhiteSpace($TranslationFingerprint) -and
(Get-RecordedFingerprint $translationProvenance 'TranslationFingerprint') -eq $TranslationFingerprint) {
(Get-RecordedFingerprint $translationProvenance 'TranslationFingerprint') -eq $TranslationFingerprint -and
(Get-RecordedFingerprint $translationProvenance 'DolSha256') -eq $pins.DolSha256) {
$artifacts = @($baseMetadata, $baseManifest, (Join-Path $generated 'base_translation_sources.bin'),
(Join-Path $generated 'base_translation_mod_awareness.json'))
$reuseBase = -not ($artifacts | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) })
@@ -312,11 +316,11 @@ try {
Invoke-Checked $translator @(
'emit-base-manifest', '--project', $project, '--out', $baseManifestDir,
'--functions-dir', $functions, '--translation-output-metadata', $baseMetadata, '--region', 'P'
'--functions-dir', $functions, '--translation-output-metadata', $baseMetadata, '--region', $pins.Region
) 'Creating the local base translation manifest' -StepId 'emit-base-manifest'
if (-not [string]::IsNullOrWhiteSpace($TranslationFingerprint)) {
[ordered]@{ SchemaVersion = 1; TranslationFingerprint = $TranslationFingerprint } |
[ordered]@{ SchemaVersion = 1; TranslationFingerprint = $TranslationFingerprint; DolSha256 = $pins.DolSha256 } |
ConvertTo-Json | Set-Content -LiteralPath $translationProvenance -Encoding UTF8
}
}
@@ -329,14 +333,14 @@ try {
'translate-mod', '--project', $project, '--profile', 'retro-rewind',
'--base-manifest', $baseManifest, '--base-translation-output-metadata', $baseMetadata,
'--code-pul', $codePul, '--mod-root', $retroRoot, '--mod-name', 'Retro Rewind',
'--region', 'P', '--out', $retroOut, '--prefer-cached-inputs', '--emit-cpp',
'--region', $pins.Region, '--out', $retroOut, '--prefer-cached-inputs', '--emit-cpp',
'--threads', $translatorThreads
)
if ($SkipRetroWfcPayload) {
$translateModArguments += '--skip-retro-wfc'
} elseif (-not [string]::IsNullOrWhiteSpace($RetroWfcOfflineDirectory)) {
$offlineRoot = [IO.Path]::GetFullPath($RetroWfcOfflineDirectory)
$offlinePayload = Join-Path $offlineRoot 'binary\payload.RMCPD00.bin'
$offlinePayload = Join-Path $offlineRoot $retroWfcPayloadFile
Assert-File $offlinePayload 'Offline Retro-WFC shared payload'
$translateModArguments += @('--retro-wfc-payload', $offlinePayload)
} else {
@@ -447,8 +451,8 @@ try {
RelSha256 = $relSha
CodePulSha256 = if ($isRetro) { Get-MkwFileSha256 (Join-Path $retroRoot 'Binaries\Code.pul') } else { $null }
RetroWfcPayloadMode = if (-not $isRetro) { $null } elseif ($SkipRetroWfcPayload) { 'skipped' } else { $RetroWfcPayloadOrigin }
RetroWfcPayloadSha256 = if ($isRetro -and -not $SkipRetroWfcPayload) { Get-MkwFileSha256 (Join-Path $RetroWfcOfflineDirectory 'binary\payload.RMCPD00.bin') } else { $null }
RetroWfcPayloadLength = if ($isRetro -and -not $SkipRetroWfcPayload) { (Get-Item -LiteralPath (Join-Path $RetroWfcOfflineDirectory 'binary\payload.RMCPD00.bin')).Length } else { $null }
RetroWfcPayloadSha256 = if ($isRetro -and -not $SkipRetroWfcPayload) { Get-MkwFileSha256 (Join-Path $RetroWfcOfflineDirectory $retroWfcPayloadFile) } else { $null }
RetroWfcPayloadLength = if ($isRetro -and -not $SkipRetroWfcPayload) { (Get-Item -LiteralPath (Join-Path $RetroWfcOfflineDirectory $retroWfcPayloadFile)).Length } else { $null }
Compiler = "llvm-mingw clang-22 sha256:$compilerSha"
}
$provenance | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $Destination 'local-build.json') -Encoding UTF8
+28 -2
View File
@@ -93,7 +93,7 @@ function Get-MkwBuildSafePath([string]$Path, [string]$Kind, [string]$MarkerFile)
function Get-MkwProjectPins([string]$ProjectFile) {
<#
The Mario Kart Wii facts pinned by projects/mkwii/recomp.yml (game identity, clean input
The Mario Kart Wii facts pinned by a projects/*/recomp.yml (game identity, clean input
digests, translation entry point, Retro-WFC endpoint), read from one place instead of being
restated; Test-PinnedFacts.ps1 checks the copies that can't read YAML (C++ header, C#
constants) against these values. Parsing is literal, not a YAML dependency, since the file is
@@ -101,7 +101,7 @@ function Get-MkwProjectPins([string]$ProjectFile) {
#>
Assert-File $ProjectFile 'Translation project file'
$pins = [ordered]@{
GameId = ''; DolSha256 = ''; RelSha256 = ''; EntryPoint = ''; RetroWfcPayloadUri = ''
GameId = ''; Region = ''; DolSha256 = ''; RelSha256 = ''; EntryPoint = ''; RetroWfcPayloadUri = ''
}
$section = ''
$inputKey = ''
@@ -112,6 +112,8 @@ function Get-MkwProjectPins([string]$ProjectFile) {
if ($section -eq 'inputs' -and $line -match '^\s{2}([A-Za-z0-9_]+):\s*$') { $inputKey = $Matches[1]; continue }
if ($section -eq 'project' -and $line -match '^\s*game_id:\s*(\S+)\s*$') {
$pins.GameId = $Matches[1]
} elseif ($section -eq 'project' -and $line -match '^\s*region:\s*(\S+)\s*$') {
$pins.Region = $Matches[1]
} elseif ($section -eq 'inputs' -and $line -match '^\s*sha256:\s*([0-9a-fA-F]{64})\s*$') {
if ($inputKey -eq 'dol') { $pins.DolSha256 = $Matches[1].ToLowerInvariant() }
elseif ($inputKey -eq 'rel') { $pins.RelSha256 = $Matches[1].ToLowerInvariant() }
@@ -130,6 +132,30 @@ function Get-MkwProjectPins([string]$ProjectFile) {
return $pins
}
function Resolve-MkwProjectForAssets([string]$Workspace, [string]$AssetsDirectory) {
<#
The translation project pinned to the main.dol in AssetsDirectory, after checking that
StaticR.rel and any extracted disc data belong to the same release.
#>
$dolSha = Get-MkwFileSha256 (Join-Path $AssetsDirectory 'main.dol')
foreach ($file in Get-ChildItem -LiteralPath (Join-Path $Workspace 'projects') -Filter 'recomp.yml' -Recurse -File) {
$pins = Get-MkwProjectPins $file.FullName
if ($pins.DolSha256 -ne $dolSha) { continue }
if ((Get-MkwFileSha256 (Join-Path $AssetsDirectory 'StaticR.rel')) -ne $pins.RelSha256) {
throw "StaticR.rel does not match the clean $($pins.GameId) release of main.dol."
}
$bootBin = Join-Path $AssetsDirectory 'DATA\sys\boot.bin'
if (Test-Path -LiteralPath $bootBin -PathType Leaf) {
$discId = [Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($bootBin), 0, 6)
if ($discId -ne $pins.GameId) {
throw "The extracted game data is $discId but main.dol is $($pins.GameId); extract the game again."
}
}
return [pscustomobject]@{ Path = $file.FullName; Pins = $pins }
}
throw "main.dol (sha256 $dolSha) is not a supported clean Mario Kart Wii release."
}
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '', [bool]$WaitForProcessTree = $true) {
<#
+36 -29
View File
@@ -1,7 +1,8 @@
# 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
# 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.
# that owns it (each projects/*/recomp.yml). Scripts read pinned facts through Get-MkwProjectPins
# or build-cache.sh's mkw_project_pin, but some consumers can't read YAML (the C++ runtime header,
# the C# Retro-WFC table, and hand-written lists on both sides of the C#/PowerShell boundary), so
# those are checked here instead.
[CmdletBinding()]
param([string]$RepositoryRoot)
@@ -46,32 +47,36 @@ function Compare-Set([string[]]$Expected, [string[]]$Actual, [string]$ExpectedNa
if ($extra.Count -gt 0) { Add-Failure "$ActualName has entries $ExpectedName does not: $($extra -join ', ')." }
}
$pins = Get-MkwProjectPins (Join-Path $repoRoot 'projects\mkwii\recomp.yml')
# Every supported release is one projects/<name>/recomp.yml, the same set Build-Installer.ps1 ships.
$releases = @(Get-ChildItem -LiteralPath (Join-Path $repoRoot 'projects') -Directory | Sort-Object Name |
ForEach-Object { Join-Path $_.FullName 'recomp.yml' } | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } |
ForEach-Object { $pins = Get-MkwProjectPins $_; $pins.Project = $_; [pscustomobject]$pins })
if ($releases.Count -eq 0) { throw 'No projects/*/recomp.yml was found; update Test-PinnedFacts.ps1.' }
# --- The Retro-WFC endpoint: recomp.yml owns it; the installer host pins the same string so a
# --- redirected or rewritten endpoint cannot be fetched from. The literal lives in
# --- WiiCompiled.Setup.Common (shared with WiiCompiled.Setup.Linux) - InputValidation.cs only
# --- re-exports it as `= RetroWfcPayload.CurrentRetroWfcPayloadUri;`, no literal to capture there.
# --- The Retro-WFC endpoints: each recomp.yml owns its release's; the host derives the same URL
# --- from its game table so a redirected or rewritten endpoint cannot be fetched from.
$retroWfcPayload = Read-SourceFile (Join-Path $common 'RetroWfcPayload.cs') 'RetroWfcPayload.cs'
$hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"([^"]+)"' `
'The host Retro-WFC endpoint constant'
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
$payloadGames = @{}
foreach ($entry in [regex]::Matches((Get-CapturedValue $retroWfcPayload `
'(?s)PayloadGames\s*=\s*new\([^)]*\)\s*\{(.*?)\};' 'The host Retro-WFC game table'),
'\["([^"]+)"\]\s*=\s*"([^"]+)"')) {
$payloadGames[$entry.Groups[1].Value] = $entry.Groups[2].Value
}
$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)'."
$uriTemplate = Get-CapturedValue $retroWfcPayload 'PayloadUri\(string gameId\)\s*=>\s*\$"([^"]+)"' `
'The host Retro-WFC endpoint template'
Compare-Set ([string[]]@($releases.GameId)) ([string[]]@($payloadGames.Keys)) `
'projects/*/recomp.yml' 'RetroWfcPayload.PayloadGames'
foreach ($release in $releases) {
if (-not $payloadGames.ContainsKey($release.GameId)) { continue }
$hostUri = $uriTemplate.Replace('{PayloadGame(gameId)}', $payloadGames[$release.GameId])
if ($hostUri -cne $release.RetroWfcPayloadUri) {
Add-Failure "RetroWfcPayload.PayloadUri($($release.GameId)) is '$hostUri' but $($release.Project) pins '$($release.RetroWfcPayloadUri)'."
}
}
# --- 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.
$models = Read-SourceFile (Join-Path $setup 'Models.cs') 'Models.cs'
$hostGameId = Get-CapturedValue $models 'ExpectedGameId\s*\{\s*get;\s*set;\s*\}\s*=\s*"([^"]+)"' `
'The host expected game id'
if ($hostGameId -cne $pins.GameId) {
Add-Failure "PayloadManifest.ExpectedGameId defaults to '$hostGameId' but recomp.yml pins '$($pins.GameId)'."
foreach ($key in @('GameId', 'DolSha256')) {
foreach ($duplicate in @($releases | Group-Object $key | Where-Object Count -gt 1)) {
Add-Failure "Several projects pin the same $($key) $($duplicate.Name)."
}
}
# --- The translation entry point: the translator is told it by LocalBuild.ps1 (which reads
@@ -79,8 +84,10 @@ if ($hostGameId -cne $pins.GameId) {
$systemBridge = Read-SourceFile (Join-Path $repoRoot 'runtime\include\system_bridge.h') 'system_bridge.h'
$runtimeEntry = (Get-CapturedValue $systemBridge 'kDefaultEntryAddress\s*=\s*(0[xX][0-9a-fA-F]+)' `
'The runtime default entry address').ToLowerInvariant()
if ($runtimeEntry -ne $pins.EntryPoint) {
Add-Failure "system_bridge.h enters at $runtimeEntry but recomp.yml translates from $($pins.EntryPoint)."
foreach ($release in $releases) {
if ($runtimeEntry -ne $release.EntryPoint) {
Add-Failure "system_bridge.h enters at $runtimeEntry but $($release.Project) translates from $($release.EntryPoint)."
}
}
# --- The pinned dependency set: Build-Installer.ps1 stages it, the installed host requires it.
@@ -137,6 +144,6 @@ Compare-Set $knownSteps $emittedSteps 'BuildStepIds' 'the steps LocalBuild.ps1 e
if ($failures.Count -gt 0) {
throw "Pinned facts disagree between their copies:`n $($failures -join "`n ")"
}
Write-Host ("Pinned-fact audit passed: game {0}, entry {1}, endpoint {2}; dependency, runtime-asset, " -f
$pins.GameId, $pins.EntryPoint, $pins.RetroWfcPayloadUri) -NoNewline
Write-Host ("Pinned-fact audit passed: games {0}, entry {1}; Retro-WFC endpoint, dependency, runtime-asset, " -f
($releases.GameId -join ', '), $runtimeEntry) -NoNewline
Write-Host 'build-step, and build-script parameter contracts all agree.'
@@ -11,22 +11,39 @@ namespace WiiCompiled.Setup.Common;
public sealed record RetroWfcPayloadSnapshot(string Directory, string Sha256, long ByteLength);
/// <summary>
/// Downloads and verifies the Retro-WFC payload (a small, RSA-signed blob served from a single
/// fixed endpoint). Shared by both installers - moved here from WiiCompiled.Setup.Windows's
/// InputValidation.cs, which keeps every one of these method names as thin forwarding wrappers so
/// its many existing call sites (ProductRepairService.cs, LocalBuildService.cs, Installation.cs,
/// SelfTests.cs) needed no changes.
/// Downloads and verifies the Retro-WFC payload (a small, RSA-signed blob served from one fixed
/// endpoint per game release). Shared by both installers and the translator.
/// </summary>
public static class RetroWfcPayload
{
private const long MaximumRetroWfcPayloadBytes = 16L * 1024 * 1024;
// The payload is tens of kilobytes from a single fixed endpoint 30s is good.
// The payload is tens of kilobytes from a fixed regional endpoint; allow 30 seconds.
private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30);
private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1);
public const string CurrentRetroWfcPayloadUri = "https://rwfc.net/api/wfc/payload?g=RMCPD00";
private static readonly string RetroWfcOfflinePayloadFile =
Path.Combine("binary", "payload.RMCPD00.bin");
/// <summary>
/// Base game id to Retro-WFC payload game. Test-PinnedFacts.ps1 checks this table against the
/// retro_wfc_payload every projects/*/recomp.yml pins.
/// </summary>
private static readonly Dictionary<string, string> PayloadGames = new(StringComparer.Ordinal)
{
["RMCP01"] = "RMCPD00",
["RMCE01"] = "RMCED00",
["RMCJ01"] = "RMCJD00",
};
public static IReadOnlyCollection<string> SupportedGameIds => PayloadGames.Keys;
public static string PayloadGame(string gameId) =>
PayloadGames.TryGetValue(gameId, out var payloadGame)
? payloadGame
: throw new InvalidDataException($"Unsupported Retro-WFC base game '{gameId}'.");
public static string PayloadRelativePath(string gameId) =>
Path.Combine("binary", $"payload.{PayloadGame(gameId)}.bin");
public static string PayloadUri(string gameId) =>
$"https://rwfc.net/api/wfc/payload?g={PayloadGame(gameId)}";
/// <summary>Retro-WFC production payload signing key (PROD PayloadPublicKey). Verifies
/// the payload past the 0x110 header; on-console stage1 pins the same key, so
@@ -45,47 +62,48 @@ public static class RetroWfcPayload
private const int RetroWfcPayloadSignatureOffset = 0x10;
private const int RetroWfcPayloadMinimumBytes = 0x130;
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory,
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory, string gameId,
RSAParameters? signingKey = null)
{
if (string.IsNullOrWhiteSpace(stagedDirectory))
throw new InvalidDataException("The staged Retro-WFC payload directory is missing.");
var root = Path.GetFullPath(stagedDirectory);
var payload = Path.Combine(root, RetroWfcOfflinePayloadFile);
var relativePath = PayloadRelativePath(gameId);
var payload = Path.Combine(root, relativePath);
try
{
if ((File.GetAttributes(payload) & FileAttributes.Directory) != 0)
throw new InvalidDataException(
"The staged Retro-WFC payload directory does not contain binary\\payload.RMCPD00.bin.");
$"The staged Retro-WFC payload directory does not contain {relativePath}.");
}
catch (FileNotFoundException)
{
throw new InvalidDataException(
"The staged Retro-WFC payload directory does not contain binary\\payload.RMCPD00.bin.");
$"The staged Retro-WFC payload directory does not contain {relativePath}.");
}
catch (DirectoryNotFoundException)
{
throw new InvalidDataException(
"The staged Retro-WFC payload directory does not contain binary\\payload.RMCPD00.bin.");
$"The staged Retro-WFC payload directory does not contain {relativePath}.");
}
ValidateRetroWfcPayloadFile(payload, signingKey);
ValidateRetroWfcPayloadFile(payload, PayloadGame(gameId), signingKey);
return root;
}
public static string ResolveRetroWfcPayloadFile(string stagedDirectory,
public static string ResolveRetroWfcPayloadFile(string stagedDirectory, string gameId,
RSAParameters? signingKey = null) =>
Path.Combine(ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, signingKey),
RetroWfcOfflinePayloadFile);
Path.Combine(ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, gameId, signingKey),
PayloadRelativePath(gameId));
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory,
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory, string gameId,
RSAParameters? signingKey = null) =>
Sha256File(ResolveRetroWfcPayloadFile(stagedDirectory, signingKey));
Sha256File(ResolveRetroWfcPayloadFile(stagedDirectory, gameId, signingKey));
public static void ValidateRetroWfcPayloadUri(string uriText)
{
if (!string.Equals(uriText, CurrentRetroWfcPayloadUri, StringComparison.Ordinal))
throw new InvalidDataException("The installer does not define the fixed Retro-WFC payload endpoint.");
if (!PayloadGames.Keys.Any(gameId => string.Equals(uriText, PayloadUri(gameId), StringComparison.Ordinal)))
throw new InvalidDataException("The installer does not define this fixed Retro-WFC payload endpoint.");
}
public static async Task<RetroWfcPayloadSnapshot> DownloadRetroWfcPayloadAsync(string uriText,
@@ -96,7 +114,8 @@ public static class RetroWfcPayload
var uri = new Uri(uriText, UriKind.Absolute);
var root = Path.GetFullPath(destinationDirectory);
var destination = Path.Combine(root, RetroWfcOfflinePayloadFile);
var payloadGame = uri.Query[3..]; // ValidateRetroWfcPayloadUri accepts only the exact ?g= form.
var destination = Path.Combine(root, "binary", $"payload.{payloadGame}.bin");
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
@@ -154,7 +173,7 @@ public static class RetroWfcPayload
}
actualSha256 = Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
}
ValidateRetroWfcPayloadFile(temporary);
ValidateRetroWfcPayloadFile(temporary, uri.Query[3..]);
File.Move(temporary, destination, overwrite: true);
return new RetroWfcPayloadSnapshot(root, actualSha256, total);
}
@@ -181,7 +200,8 @@ public static class RetroWfcPayload
request.StatusCode is { } status && (int)status >= 500;
}
private static void ValidateRetroWfcPayloadFile(string payload, RSAParameters? signingKey = null)
private static void ValidateRetroWfcPayloadFile(string payload, string expectedGame,
RSAParameters? signingKey = null)
{
byte[] image;
using (var stream = File.OpenRead(payload))
@@ -201,6 +221,10 @@ public static class RetroWfcPayload
throw new InvalidDataException(
$"The Retro-WFC payload declares {declaredSize} bytes but contains {image.Length}.");
if (image.Length < 0x144 ||
System.Text.Encoding.ASCII.GetString(image, 0x138, 12).TrimEnd('\0') != expectedGame)
throw new InvalidDataException($"The Retro-WFC payload is not for {expectedGame}.");
using var rsa = RSA.Create();
rsa.ImportParameters(signingKey ?? new RSAParameters
{
+32 -15
View File
@@ -11,29 +11,30 @@ namespace WiiCompiled.Setup.Linux;
/// </summary>
internal static class DiscTool
{
public static async Task ValidateAndExtractAsync(
string isoPath, ProjectManifest manifest, string assetsDirectory, string workspace,
/// <summary>Validates and extracts the disc, returning the supported release it is.</summary>
public static async Task<ProjectManifest> ValidateAndExtractAsync(
string isoPath, IReadOnlyList<ProjectManifest> releases, string assetsDirectory, string workspace,
string? nodToolBin, IInstallReporter reporter, CancellationToken cancellationToken)
{
if (IsAlreadyExtracted(manifest, assetsDirectory))
{
reporter.Progress(InstallStages.ExtractDisc, "Reusing the verified extracted disc", 6);
return;
}
var nodTool = nodToolBin ?? await NodToolProvider.ResolveAsync(workspace, cancellationToken);
// `nodtool info` only decodes the disc/partition headers (milliseconds); `nodtool extract`
// copies the whole data partition to disk (tens of seconds for a custom-track-heavy MKWii
// ISO). Checking the game ID first, before extracting, means a wrong disc fails fast -
// matching the original dolphin-tool `header` step this replaces.
// ISO). The game ID picks the release, so a wrong disc fails fast and an existing
// extraction is only reused for the same release.
reporter.Progress(InstallStages.ExtractDisc, "Reading the disc header", 2);
var info = NodToolInfoParser.Parse(await RunInfoAsync(nodTool, isoPath, cancellationToken));
if (!string.Equals(info.GameId, manifest.GameId, StringComparison.Ordinal))
var manifest = releases.FirstOrDefault(release =>
string.Equals(info.GameId, release.GameId, StringComparison.Ordinal))
?? throw new InvalidOperationException(
$"This disc is '{info.GameId}', which is not a supported Mario Kart Wii release " +
$"({ProjectManifest.Describe(releases)}). " +
"Only your own legally-owned copy of a supported game/region can be used.");
if (IsAlreadyExtracted(manifest, assetsDirectory))
{
throw new InvalidOperationException(
$"This disc is '{info.GameId}', not the expected '{manifest.GameId}' (Mario Kart Wii, region {manifest.Region}). " +
"Only your own legally-owned copy of that exact game/region can be used.");
reporter.Progress(InstallStages.ExtractDisc, "Reusing the verified extracted disc", 6);
return manifest;
}
// Extracted straight into Assets/DATA (kept, not a scratch dir) - the runtime reads course/
@@ -71,6 +72,22 @@ internal static class DiscTool
File.Copy(relPath, Path.Combine(assetsDirectory, "StaticR.rel"), overwrite: true);
File.WriteAllText(Path.Combine(assetsDirectory, ExtractionMarker), ExtractionRecord(dolSha, dataDir));
reporter.Progress(InstallStages.ExtractDisc, "Disc validated and extracted", 6);
return manifest;
}
/// <summary>The release already extracted into Assets, chosen by its main.dol like local-build.sh.</summary>
public static ProjectManifest SelectExtracted(IReadOnlyList<ProjectManifest> releases, string assetsDirectory)
{
var dolSha = Sha256Of(Path.Combine(assetsDirectory, "main.dol"));
var manifest = releases.FirstOrDefault(release => string.Equals(dolSha, release.DolSha256, StringComparison.Ordinal))
?? throw new InvalidOperationException(
$"Assets/main.dol (sha256 {dolSha}) is not a supported clean Mario Kart Wii release " +
$"({ProjectManifest.Describe(releases)}).");
var relSha = Sha256Of(Path.Combine(assetsDirectory, "StaticR.rel"));
if (!string.Equals(relSha, manifest.RelSha256, StringComparison.Ordinal))
throw new InvalidOperationException(
$"Assets/StaticR.rel does not match the clean {manifest.GameId} release of Assets/main.dol.");
return manifest;
}
private static async Task<string> RunInfoAsync(string nodTool, string isoPath, CancellationToken cancellationToken)
@@ -97,7 +114,7 @@ internal static class DiscTool
private const string ExtractionMarker = ".disc-extracted";
// Only one disc revision is accepted, so a completed, verified extraction is identical to a fresh one.
// Each release has one accepted revision, so a completed, verified extraction is identical to a fresh one.
private static bool IsAlreadyExtracted(ProjectManifest manifest, string assetsDirectory)
{
var marker = Path.Combine(assetsDirectory, ExtractionMarker);
+8 -6
View File
@@ -95,13 +95,14 @@ internal static class Program
if (installsRetro) retroDir = RetroRewindSource.ResolveRetroRewind6(retroDir!);
var workspace = flags.GetValueOrDefault("workspace") ?? WorkspaceLocator.FindFrom(AppContext.BaseDirectory);
var manifest = ProjectManifest.Load(Path.Combine(workspace, "projects", "mkwii", "recomp.yml"));
var releases = ProjectManifest.LoadAll(workspace);
var assetsDir = Path.Combine(workspace, "Assets");
reporter.Progress(InstallStages.Validate, "Checking prerequisites", 1);
ProjectManifest release;
if (flags.TryGetValue("game", out var isoPath) && !string.IsNullOrEmpty(isoPath))
{
await DiscTool.ValidateAndExtractAsync(isoPath, manifest, assetsDir, workspace,
release = await DiscTool.ValidateAndExtractAsync(isoPath, releases, assetsDir, workspace,
flags.GetValueOrDefault("disc-tool-bin"), reporter, token);
}
else
@@ -114,6 +115,7 @@ internal static class Program
"No --game ISO was given and Assets/main.dol + Assets/StaticR.rel are not already present. " +
"Either pass --game <path-to-iso>, or extract them yourself first (see translator/README.md).");
}
release = DiscTool.SelectExtracted(releases, assetsDir);
}
var state = JsonState.TryRead<InstallState>(StatePath) ?? new InstallState { Workspace = workspace };
@@ -133,10 +135,10 @@ internal static class Program
try
{
// A valid signature authenticates a payload, but does not prove it is the latest
// signed revision. Always ask the fixed endpoint for the current snapshot; the
// downloader verifies it before atomically replacing the cache.
// signed revision. Always ask the release's fixed endpoint for the current snapshot;
// the downloader verifies it before atomically replacing the cache.
await RetroWfcPayload.DownloadRetroWfcPayloadAsync(
RetroWfcPayload.CurrentRetroWfcPayloadUri, cacheDir, token);
RetroWfcPayload.PayloadUri(release.GameId), cacheDir, token);
}
catch (Exception downloadFailure) when (!token.IsCancellationRequested &&
downloadFailure is HttpRequestException or TimeoutException
@@ -147,7 +149,7 @@ internal static class Program
// that must remain a hard failure instead of hiding possible endpoint tampering.
try
{
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(cacheDir);
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(cacheDir, gameId: release.GameId);
}
catch (Exception cacheFailure) when (cacheFailure is IOException or
UnauthorizedAccessException or InvalidDataException)
@@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
namespace WiiCompiled.Setup.Linux;
/// <summary>
/// The handful of facts this tool needs out of projects/mkwii/recomp.yml. Parsed literally line by
/// The handful of facts this tool needs out of a projects/*/recomp.yml. Parsed literally line by
/// line - the same approach Launcher/NativeBuildFlags.ps1's Get-MkwProjectPins and
/// Launcher/local-build.sh already use - rather than pulling in a YAML library, since the manifest
/// is machine-written with a fixed shape.
@@ -67,4 +67,21 @@ internal sealed class ProjectManifest
return new ProjectManifest { GameId = gameId, Region = region, DolSha256 = dolSha, RelSha256 = relSha };
}
/// <summary>Every supported release: one per projects/*/recomp.yml, as local-build.sh selects them.</summary>
public static IReadOnlyList<ProjectManifest> LoadAll(string workspace)
{
var projects = Directory.EnumerateDirectories(Path.Combine(workspace, "projects"))
.Select(directory => Path.Combine(directory, "recomp.yml"))
.Where(File.Exists)
.Order(StringComparer.Ordinal)
.Select(Load)
.ToList();
if (projects.Count == 0)
throw new InvalidOperationException($"No projects/*/recomp.yml was found under {workspace}.");
return projects;
}
public static string Describe(IEnumerable<ProjectManifest> projects) =>
string.Join(", ", projects.Select(project => $"{project.GameId} (region {project.Region})"));
}
@@ -64,9 +64,7 @@ internal static class ConsoleCommands
{
using var payload = PayloadArchive.OpenCurrent();
var manifest = payload.ReadManifest();
var tool = Path.Combine(temp, "nodtool.exe");
payload.ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "nodtool.exe", tool);
payload.ExtractDirectory(InstalledLayout.ToolkitEntryPrefix + "Redist", temp);
var tool = payload.ExtractDiscTool(temp);
reporter?.Progress(InstallStages.Validate, "Checking the Wii disc image...", 10);
var header = InputValidation.ReadDiscHeaderAsync(tool, command.GamePath!).GetAwaiter().GetResult();
InputValidation.EnsureCompatibleDisc(header, manifest);
@@ -54,33 +54,29 @@ internal static class InputValidation
}
: "Unknown";
public static void EnsureCompatibleDisc(DiscHeader header, PayloadManifest manifest)
{
if (!header.GameId.Equals(manifest.ExpectedGameId, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidDataException(
$"This build supports Mario Kart Wii PAL ({manifest.ExpectedGameId}). " +
$"The selected image is {header.GameId} ({header.InternalName}, {header.Region}).");
}
}
/// <summary>The supported release this disc is, or a clear rejection naming every supported one.</summary>
public static GameRelease EnsureCompatibleDisc(DiscHeader header, PayloadManifest manifest) =>
manifest.SupportedReleases.FirstOrDefault(release =>
release.GameId.Equals(header.GameId, StringComparison.OrdinalIgnoreCase))
?? throw new InvalidDataException(
"This build supports Mario Kart Wii " +
string.Join(", ", manifest.SupportedReleases.Select(release =>
$"{RegionFromGameId(release.GameId)} ({release.GameId})")) +
$". The selected image is {header.GameId} ({header.InternalName}, {header.Region}).");
// Thin forwarding wrappers: the actual download/RSA-verification logic lives in
// WiiCompiled.Setup.Common.RetroWfcPayload (shared with WiiCompiled.Setup.Linux) so there's one
// copy of it, not two. Kept under these names so every existing call site here
// (ProductRepairService.cs, LocalBuildService.cs, Installation.cs, SelfTests.cs) is unchanged.
public const string CurrentRetroWfcPayloadUri = RetroWfcPayload.CurrentRetroWfcPayloadUri;
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory,
// Forwarding wrappers over WiiCompiled.Setup.Common.RetroWfcPayload that require the game id,
// so no caller here can silently fall back to one region's payload.
public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory, string gameId,
RSAParameters? signingKey = null) =>
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, signingKey);
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, gameId, signingKey);
public static string ResolveRetroWfcPayloadFile(string stagedDirectory,
public static string ResolveRetroWfcPayloadFile(string stagedDirectory, string gameId,
RSAParameters? signingKey = null) =>
RetroWfcPayload.ResolveRetroWfcPayloadFile(stagedDirectory, signingKey);
RetroWfcPayload.ResolveRetroWfcPayloadFile(stagedDirectory, gameId, signingKey);
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory,
public static string ComputeRetroWfcPayloadSha256(string stagedDirectory, string gameId,
RSAParameters? signingKey = null) =>
RetroWfcPayload.ComputeRetroWfcPayloadSha256(stagedDirectory, signingKey);
RetroWfcPayload.ComputeRetroWfcPayloadSha256(stagedDirectory, gameId, signingKey);
public static void ValidateRetroWfcPayloadUri(string uriText) =>
RetroWfcPayload.ValidateRetroWfcPayloadUri(uriText);
@@ -131,6 +131,8 @@ internal sealed class Installation
Workspace + "LocalBuild.ps1",
Workspace + "NativeBuildFlags.ps1",
Workspace + @"projects\mkwii\recomp.yml",
Workspace + @"projects\mkwii-ntsc-u\recomp.yml",
Workspace + @"projects\mkwii-ntsc-j\recomp.yml",
Workspace + @"runtime\CMakeLists.txt",
Workspace + @"runtime\assets\dsp\dsp_coef.bin"
];
@@ -168,6 +170,26 @@ internal sealed class Installation
public InstallState? ReadInstallState() => JsonState.TryRead<InstallState>(InstallStatePath);
/// <summary>The game id of the installed disc, which selects its region's Retro-WFC payload.</summary>
public string ReadGameId()
{
try { return ReadDiscGameId(GameDataDirectory); }
catch (IOException ex)
{
throw new InvalidDataException(
"The installed game data has no readable disc header. Apply setup again with the disc image.", ex);
}
}
/// <summary>The six-character game id in an extracted disc's <c>sys/boot.bin</c>.</summary>
internal static string ReadDiscGameId(string dataRoot)
{
using var stream = File.OpenRead(Path.Combine(dataRoot, "sys", "boot.bin"));
var id = new byte[6];
stream.ReadExactly(id);
return System.Text.Encoding.ASCII.GetString(id);
}
internal ToolkitState? ReadToolkitState() => JsonState.TryRead<ToolkitState>(ToolkitStatePath);
/// <summary>
@@ -396,7 +418,7 @@ internal sealed class Installation
try
{
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload);
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload, ReadGameId());
if (!InputValidation.Sha256File(payload).Equals(fingerprint.RetroWfcPayloadSha256,
StringComparison.OrdinalIgnoreCase) ||
new FileInfo(payload).Length != fingerprint.RetroWfcPayloadLength)
@@ -581,7 +603,7 @@ internal sealed class Installation
{
try
{
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload);
var payload = InputValidation.ResolveRetroWfcPayloadFile(WorkspaceRetroWfcPayload, ReadGameId());
return new FileInfo(payload).Length == snapshot.ByteLength &&
InputValidation.Sha256File(payload).Equals(snapshot.Sha256, StringComparison.Ordinal);
}
@@ -40,6 +40,28 @@ internal sealed class InstallerEngine
var toolkit = InstalledLayout.Toolkit(staging);
var workspace = InstalledLayout.Workspace(staging);
// The disc decides which release's inputs everything below expects, so it is identified
// before any reuse decision or toolkit extraction.
_reporter.Progress(InstallStages.Validate, "Checking the Wii disc image...", 1);
var nodTool = payload.ExtractDiscTool(Path.Combine(staging, "disc-tool"));
var header = await InputValidation.ReadDiscHeaderAsync(nodTool, options.GamePath,
cancellationToken);
var release = InputValidation.EnsureCompatibleDisc(header, manifest);
var canonicalRetroRoot = options.RetroDirectoryPath is null
? null
: RetroRewindSource.ResolveRetroRewind6(options.RetroDirectoryPath);
ValidateRetroOptions(canonicalRetroRoot, options.RetroWfcPayloadMode);
var retroCompileInputs = canonicalRetroRoot is null
? null
: SnapshotRetroRewindCompileInputs(canonicalRetroRoot,
Path.Combine(staging, "compile-inputs"), cancellationToken);
if (options.Portable)
{
var portableRoot = PortableRoot.Create(parent);
_reporter.Diagnostic($"Installing as a portable installation under {portableRoot}.");
}
var candidateToolkitFingerprint = manifest.ToolkitFingerprint;
var candidateToolkitPackageFingerprint = manifest.ToolkitPackageFingerprint;
var candidateRuntimeAssetsFingerprint = manifest.RuntimeAssetsFingerprint;
@@ -49,7 +71,7 @@ internal sealed class InstallerEngine
var installedToolkitPackageFingerprint = existing.IsPresent
? existing.ResolveToolkitPackageFingerprint()
: "";
var reusableGameAssets = FindReusableGameAssets(existing, manifest);
var reusableGameAssets = FindReusableGameAssets(existing, release);
var sameToolkit = existing.IsPresent && reusableGameAssets is not null &&
installedToolkitFingerprint.Equals(candidateToolkitFingerprint,
@@ -68,7 +90,7 @@ internal sealed class InstallerEngine
var extractWorkspace = !sameToolkit || !runtimeAssetsCurrent;
_reporter.Progress(InstallStages.ExtractToolkit,
"Inspecting the release's local recompilation toolkit...", 1);
"Inspecting the release's local recompilation toolkit...", 4);
if (extractToolkit) payload.ExtractDirectory(InstalledLayout.ToolkitDirectoryName, toolkit);
if (extractWorkspace)
{
@@ -82,30 +104,10 @@ internal sealed class InstallerEngine
payload.ExtractEntry(InstalledLayout.PayloadManifestFileName,
Path.Combine(staging, InstalledLayout.PayloadManifestFileName));
var nodTool = extractToolkit ? Path.Combine(toolkit, "nodtool.exe") : installedNodTool;
_reporter.Progress(InstallStages.Validate, "Checking the Wii disc image...", 2);
var header = await InputValidation.ReadDiscHeaderAsync(nodTool, options.GamePath,
cancellationToken);
InputValidation.EnsureCompatibleDisc(header, manifest);
var canonicalRetroRoot = options.RetroDirectoryPath is null
? null
: RetroRewindSource.ResolveRetroRewind6(options.RetroDirectoryPath);
ValidateRetroOptions(canonicalRetroRoot, options.RetroWfcPayloadMode, manifest);
var retroCompileInputs = canonicalRetroRoot is null
? null
: SnapshotRetroRewindCompileInputs(canonicalRetroRoot,
Path.Combine(staging, "compile-inputs"), cancellationToken);
if (options.Portable)
{
var portableRoot = PortableRoot.Create(parent);
_reporter.Diagnostic($"Installing as a portable installation under {portableRoot}.");
}
if (sameToolkit)
{
var desiredInputDrift = ProductRepairService.FindDesiredInputDrift(existing,
candidateToolkitFingerprint, manifest.ExpectedDolSha256, manifest.ExpectedRelSha256);
candidateToolkitFingerprint, release.DolSha256, release.RelSha256);
if (desiredInputDrift.Retro && retroCompileInputs is null)
throw new InvalidOperationException(
"This release changes the clean game inputs used by the installed Retro Rewind product. " +
@@ -113,7 +115,7 @@ internal sealed class InstallerEngine
var reconciliation = await ReconcileSameToolkitAsync(existing, retroCompileInputs,
canonicalRetroRoot, options.RetroWfcPayloadMode, Path.Combine(staging, "repair"),
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256, cancellationToken);
release.DolSha256, release.RelSha256, cancellationToken);
var remainingCancellation = reconciliation.PublicationCommitted
? CancellationToken.None
: cancellationToken;
@@ -125,11 +127,10 @@ internal sealed class InstallerEngine
remainingCancellation.ThrowIfCancellationRequested();
EnsureReconciliationCurrent(existing, candidateToolkitFingerprint, retroCompileInputs,
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256,
reconciliation.CachedRetroWfcPayloadMatches);
release.DolSha256, release.RelSha256, reconciliation.CachedRetroWfcPayloadMatches);
var updatedState = BuildInstallState(existing.ReadInstallState(), installDirectory, manifest,
retroCompileInputs, canonicalRetroRoot, options.RetroWfcPayloadMode);
release, retroCompileInputs, canonicalRetroRoot, options.RetroWfcPayloadMode);
PrepareMetadata(staging, manifest, updatedState);
remainingCancellation.ThrowIfCancellationRequested();
@@ -159,11 +160,11 @@ internal sealed class InstallerEngine
if (reusableGameAssets is null)
{
await ExtractGameAssetsAsync(nodTool, options.GamePath,
Path.Combine(staging, "GameAssets"), manifest, cancellationToken);
Path.Combine(staging, "GameAssets"), release, cancellationToken);
}
await PublishToolkitAndReconcileProductsAsync(existing, staging, workspace, manifest,
previousState, options, canonicalRetroRoot, retroCompileInputs,
release, previousState, options, canonicalRetroRoot, retroCompileInputs,
publishGameAssets: reusableGameAssets is null, createShortcuts: firstInstall,
cancellationToken: cancellationToken);
}
@@ -187,7 +188,7 @@ internal sealed class InstallerEngine
["generated", "build", "native-build", "Assets", "PulsarPacks"];
private async Task PublishToolkitAndReconcileProductsAsync(Installation existing, string staging,
string stagedWorkspace, PayloadManifest manifest, InstallState? previousState,
string stagedWorkspace, PayloadManifest manifest, GameRelease release, InstallState? previousState,
InstallOptions options, string? canonicalRetroRoot,
RetroRewindCompileInputs? retroCompileInputs,
bool publishGameAssets, bool createShortcuts, CancellationToken cancellationToken)
@@ -206,7 +207,7 @@ internal sealed class InstallerEngine
}
var state = BuildInstallState(previousState, installDirectory, manifest,
var state = BuildInstallState(previousState, installDirectory, manifest, release,
compileInputs: null, canonicalRetroRoot, options.RetroWfcPayloadMode);
PrepareMetadata(staging, manifest, state);
@@ -226,10 +227,9 @@ internal sealed class InstallerEngine
var reconciliation = await ReconcileSameToolkitAsync(existing, retroCompileInputs,
canonicalRetroRoot, options.RetroWfcPayloadMode, repairScratch: null,
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256, cancellationToken);
release.DolSha256, release.RelSha256, cancellationToken);
EnsureReconciliationCurrent(existing, manifest.ToolkitFingerprint, retroCompileInputs,
manifest.ExpectedDolSha256, manifest.ExpectedRelSha256,
reconciliation.CachedRetroWfcPayloadMatches);
release.DolSha256, release.RelSha256, reconciliation.CachedRetroWfcPayloadMatches);
}
private Task<ProductRepairService.ReconciliationResult> ReconcileSameToolkitAsync(Installation existing,
@@ -354,7 +354,8 @@ internal sealed class InstallerEngine
}
private static InstallState BuildInstallState(InstallState? previousState, string installDirectory,
PayloadManifest manifest, RetroRewindCompileInputs? compileInputs, string? canonicalRetroRoot,
PayloadManifest manifest, GameRelease release, RetroRewindCompileInputs? compileInputs,
string? canonicalRetroRoot,
RetroWfcPayloadMode payloadMode,
RetroWfcPayloadSnapshot? retroWfcPayloadSnapshot = null)
{
@@ -368,8 +369,8 @@ internal sealed class InstallerEngine
InstalledUtc = previousState?.InstalledUtc ?? DateTime.UtcNow,
RetroRewindInstalled = previousState?.RetroRewindInstalled ?? false,
ToolkitReleaseTag = manifest.ToolkitReleaseTag,
DolSha256 = manifest.ExpectedDolSha256,
RelSha256 = manifest.ExpectedRelSha256,
DolSha256 = release.DolSha256,
RelSha256 = release.RelSha256,
RetroRewindCodePulSha256 = previousState?.RetroRewindCodePulSha256 ?? "",
RetroRewindCompileInputsSha256 = previousState?.RetroRewindCompileInputsSha256 ?? "",
RetroWfcPayloadMode = previousState?.RetroWfcPayloadMode ?? "",
@@ -444,8 +445,7 @@ internal sealed class InstallerEngine
Path.Combine(InstalledLayout.Workspace(installDirectory), "runtime", "assets")));
}
private static void ValidateRetroOptions(string? canonicalRetroRoot, RetroWfcPayloadMode mode,
PayloadManifest manifest)
private static void ValidateRetroOptions(string? canonicalRetroRoot, RetroWfcPayloadMode mode)
{
if (canonicalRetroRoot is null && mode != RetroWfcPayloadMode.NotApplicable)
throw new InvalidOperationException(
@@ -453,11 +453,9 @@ internal sealed class InstallerEngine
if (canonicalRetroRoot is not null && mode == RetroWfcPayloadMode.NotApplicable)
throw new InvalidOperationException(
"Download the Retro-WFC payload, or explicitly skip the optional payload.");
if (canonicalRetroRoot is not null && mode == RetroWfcPayloadMode.Online)
InputValidation.ValidateRetroWfcPayloadUri(manifest.RetroWfcPayloadUri);
}
private string? FindReusableGameAssets(Installation existing, PayloadManifest manifest)
private string? FindReusableGameAssets(Installation existing, GameRelease release)
{
var dataRoot = existing.GameDataDirectory;
// No fst.bin simply means nothing was extracted here before; anything past that gate is an
@@ -466,7 +464,7 @@ internal sealed class InstallerEngine
if (!File.Exists(Path.Combine(dataRoot, "sys", "fst.bin"))) return null;
try
{
ValidateExtractedGame(dataRoot, manifest);
ValidateExtractedGame(dataRoot, release);
return dataRoot;
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidDataException)
@@ -478,7 +476,7 @@ internal sealed class InstallerEngine
}
private async Task ExtractGameAssetsAsync(string nodTool, string gamePath, string destination,
PayloadManifest manifest, CancellationToken cancellationToken)
GameRelease release, CancellationToken cancellationToken)
{
_reporter.Progress(InstallStages.ExtractDisc,
"Extracting the game disc. This is the longest preparation step...", 6);
@@ -493,15 +491,16 @@ internal sealed class InstallerEngine
cancellationToken);
if (extraction.ExitCode != 0)
throw new InvalidDataException("Game extraction failed. " + extraction.CombinedOutput.Trim());
ValidateExtractedGame(dataRoot, manifest);
ValidateExtractedGame(dataRoot, release);
}
private static void ValidateExtractedGame(string dataRoot, PayloadManifest manifest)
internal static void ValidateExtractedGame(string dataRoot, GameRelease release)
{
var dol = Path.Combine(dataRoot, "sys", "main.dol");
var rel = Path.Combine(dataRoot, "files", "rel", "StaticR.rel");
var fst = Path.Combine(dataRoot, "sys", "fst.bin");
var missing = new[] { dol, rel, fst }.Where(path => !File.Exists(path))
var boot = Path.Combine(dataRoot, "sys", "boot.bin");
var missing = new[] { dol, rel, fst, boot }.Where(path => !File.Exists(path))
.Select(path => Path.GetRelativePath(dataRoot, path)).ToList();
if (missing.Count > 0)
throw new InvalidDataException(
@@ -509,10 +508,13 @@ internal sealed class InstallerEngine
string.Join(", ", missing) + ".");
var mismatches = new List<string>();
var discGameId = Installation.ReadDiscGameId(dataRoot);
if (!discGameId.Equals(release.GameId, StringComparison.Ordinal))
mismatches.Add($"sys/boot.bin names {discGameId}");
foreach (var (name, path, expected) in new[]
{
("main.dol", dol, manifest.ExpectedDolSha256),
("StaticR.rel", rel, manifest.ExpectedRelSha256)
("main.dol", dol, release.DolSha256),
("StaticR.rel", rel, release.RelSha256)
})
{
var actual = InputValidation.Sha256File(path);
@@ -523,7 +525,7 @@ internal sealed class InstallerEngine
if (mismatches.Count > 0)
throw new InvalidDataException(
"The disc is RMCP01 but its game code does not match the supported clean PAL revision: " +
$"The disc is {release.GameId} but its game data does not match the supported clean revision: " +
string.Join("; ", mismatches) + ". " +
"Patched or otherwise modified game code cannot be installed safely.");
}
@@ -23,7 +23,8 @@ internal sealed class LocalBuildService
/// </summary>
public async Task BuildAsync(string installStaging, BuildProfile profile, string outputDirectory,
RetroWfcPayloadMode retroWfcPayloadMode, string? retroWfcOfflinePayloadDirectory,
CancellationToken cancellationToken, ToolkitFingerprintComponents? toolkitComponents = null,
string? retroWfcGameId, CancellationToken cancellationToken,
ToolkitFingerprintComponents? toolkitComponents = null,
bool forceCleanBuild = false, BuildProgressWindow? progress = null,
string? retroRewindPackageDirectory = null, string? baseOutputDirectory = null)
{
@@ -45,8 +46,9 @@ internal sealed class LocalBuildService
retroRewindPackageDirectory = RetroRewindSource.ResolveRetroRewind6(
retroRewindPackageDirectory);
if (retroWfcPayloadMode == RetroWfcPayloadMode.Online)
retroWfcOfflinePayloadDirectory =
InputValidation.ValidateStagedRetroWfcPayloadDirectory(retroWfcOfflinePayloadDirectory!);
retroWfcOfflinePayloadDirectory = InputValidation.ValidateStagedRetroWfcPayloadDirectory(
retroWfcOfflinePayloadDirectory!,
retroWfcGameId ?? throw new ArgumentException("The Retro-WFC payload needs its game id."));
var arguments = new List<string>
{
+11 -4
View File
@@ -33,16 +33,23 @@ internal sealed class InstallOptions
public bool HasRetroRewind => RetroDirectoryPath is not null;
}
/// <summary>A clean retail release the payload installs, as pinned by its projects/*/recomp.yml.</summary>
internal sealed class GameRelease
{
public string GameId { get; set; } = "";
public string DolSha256 { get; set; } = "";
public string RelSha256 { get; set; } = "";
}
internal sealed class PayloadManifest
{
public const int CurrentSchemaVersion = 3;
public int SchemaVersion { get; set; }
public string ProductVersion { get; set; } = "";
public string ExpectedGameId { get; set; } = "RMCP01";
public string ExpectedDolSha256 { get; set; } = "";
public string ExpectedRelSha256 { get; set; } = "";
public List<GameRelease> SupportedReleases { get; set; } = [];
public string ToolkitReleaseTag { get; set; } = "";
public string BuildModel { get; set; } = "";
public string RetroWfcPayloadUri { get; set; } = "";
/// <summary>
/// Content identities computed once at release-build time (<c>Build-Installer.ps1 --emit-payload-identities</c>)
@@ -54,10 +54,21 @@ internal sealed class PayloadArchive : IDisposable
var entry = FindEntry(InstalledLayout.PayloadManifestFileName)
?? throw new InvalidDataException("The payload manifest is missing.");
using var stream = entry.Open();
return ParseManifest(stream);
}
internal static PayloadManifest ParseManifest(Stream stream)
{
var manifest = JsonSerializer.Deserialize<PayloadManifest>(stream,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
?? throw new InvalidDataException("The payload manifest is invalid.");
if (manifest.SchemaVersion != 2)
ValidateManifest(manifest);
return manifest;
}
internal static void ValidateManifest(PayloadManifest manifest)
{
if (manifest.SchemaVersion != PayloadManifest.CurrentSchemaVersion)
throw new InvalidDataException($"Unsupported payload schema {manifest.SchemaVersion}.");
// The payload identities are the release-computed replacement for hashing the whole
// toolkit on the user's machine; a payload without them was packaged incorrectly.
@@ -65,8 +76,30 @@ internal sealed class PayloadArchive : IDisposable
string.IsNullOrWhiteSpace(manifest.ToolkitPackageFingerprint) ||
string.IsNullOrWhiteSpace(manifest.RuntimeAssetsFingerprint))
throw new InvalidDataException("The payload manifest is missing its content identities.");
InputValidation.ValidateRetroWfcPayloadUri(manifest.RetroWfcPayloadUri);
return manifest;
if (manifest.SupportedReleases is not { Count: > 0 })
throw new InvalidDataException("The payload manifest names no supported game release.");
foreach (var release in manifest.SupportedReleases)
{
if (!RetroWfcPayload.SupportedGameIds.Contains(release.GameId) ||
!IsSha256(release.DolSha256) || !IsSha256(release.RelSha256))
throw new InvalidDataException(
$"The payload manifest describes an unsupported or incomplete release '{release.GameId}'.");
}
if (manifest.SupportedReleases.DistinctBy(release => release.GameId).Count() !=
manifest.SupportedReleases.Count)
throw new InvalidDataException("The payload manifest lists a game release twice.");
}
private static bool IsSha256(string value) =>
value.Length == 64 && value.All(char.IsAsciiHexDigit);
/// <summary>Extracts nodtool and the runtime DLLs it loads; returns the nodtool path.</summary>
public string ExtractDiscTool(string directory)
{
var tool = Path.Combine(directory, "nodtool.exe");
ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "nodtool.exe", tool);
ExtractDirectory(InstalledLayout.ToolkitEntryPrefix + "Redist", directory);
return tool;
}
public void ExtractEntry(string entryName, string destination)
@@ -99,7 +99,11 @@ internal sealed class ProductRepairService
Directory.CreateDirectory(scratchRoot);
RetroWfcPayloadSnapshot? payloadSnapshot = null;
if (requestedPayloadMode == RetroWfcPayloadMode.Online)
// The installed disc's region decides which Retro-WFC payload the product embeds.
var payloadGameId = requestedPayloadMode == RetroWfcPayloadMode.Online
? _installation.ReadGameId()
: null;
if (payloadGameId is not null)
{
_reporter.Progress(InstallStages.PrepareRetro,
"Downloading the current shared Retro-WFC payload snapshot...", 3);
@@ -107,12 +111,12 @@ internal sealed class ProductRepairService
try
{
payloadSnapshot = await InputValidation.DownloadRetroWfcPayloadAsync(
InputValidation.CurrentRetroWfcPayloadUri, payloadScratch, cancellationToken);
RetroWfcPayload.PayloadUri(payloadGameId), payloadScratch, cancellationToken);
}
catch (Exception ex) when (!cancellationToken.IsCancellationRequested &&
ex is HttpRequestException or TimeoutException or IOException)
{
payloadSnapshot = RecoverInstalledRetroWfcPayload(toolkitFingerprint,
payloadSnapshot = RecoverInstalledRetroWfcPayload(toolkitFingerprint, payloadGameId,
Path.Combine(scratchRoot, "retro-wfc-payload-recovered"), ex, cancellationToken);
}
}
@@ -236,7 +240,7 @@ internal sealed class ProductRepairService
"Recompiling Mario Kart Wii and Retro Rewind with the installed toolkit...", 5);
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
builder.BuildAsync(_installation.Root, BuildProfile.Both, retroOutput,
requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken,
requestedPayloadMode, payloadSnapshot?.Directory, payloadGameId, cancellationToken,
toolkitComponents, clean,
progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92),
retroRewindPackageDirectory: compileInputs.RetroRewindRoot,
@@ -255,7 +259,7 @@ internal sealed class ProductRepairService
"Recompiling Mario Kart Wii with the installed toolkit...", 5);
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
builder.BuildAsync(_installation.Root, BuildProfile.Base, baseOutput,
RetroWfcPayloadMode.NotApplicable, null, cancellationToken,
RetroWfcPayloadMode.NotApplicable, null, null, cancellationToken,
toolkitComponents, clean,
progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92)));
LocalBuildService.WriteFingerprint(baseOutput, BuildProfile.Base, toolkitFingerprint,
@@ -270,7 +274,7 @@ internal sealed class ProductRepairService
"Recompiling Retro Rewind for the canonical Code.pul...", 5);
await BuildWithCleanRetryAsync(forceCleanBuild, clean =>
builder.BuildAsync(_installation.Root, BuildProfile.RetroRewind, retroOutput,
requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken,
requestedPayloadMode, payloadSnapshot?.Directory, payloadGameId, cancellationToken,
toolkitComponents, clean,
progress: new BuildProgressWindow(_reporter, InstallStages.BuildRetro, 5, 92),
retroRewindPackageDirectory: compileInputs.RetroRewindRoot));
@@ -310,7 +314,8 @@ internal sealed class ProductRepairService
private RetroWfcPayloadSnapshot RecoverInstalledRetroWfcPayload(string toolkitFingerprint,
string destinationDirectory, Exception downloadFailure, CancellationToken cancellationToken)
string gameId, string destinationDirectory, Exception downloadFailure,
CancellationToken cancellationToken)
{
InvalidOperationException Unavailable(string reason) => new(
"The Retro-WFC payload service is unavailable" +
@@ -334,7 +339,7 @@ internal sealed class ProductRepairService
try
{
cachedFile = InputValidation.ResolveRetroWfcPayloadFile(
_installation.WorkspaceRetroWfcPayload);
_installation.WorkspaceRetroWfcPayload, gameId);
cancellationToken.ThrowIfCancellationRequested();
if (new FileInfo(cachedFile).Length != fingerprint.RetroWfcPayloadLength ||
!InputValidation.Sha256File(cachedFile).Equals(fingerprint.RetroWfcPayloadSha256,
@@ -343,11 +348,11 @@ internal sealed class ProductRepairService
throw Unavailable("the installed payload snapshot no longer matches its recorded identity.");
}
var destination = Path.Combine(Path.GetFullPath(destinationDirectory), "binary",
"payload.RMCPD00.bin");
var destination = Path.Combine(Path.GetFullPath(destinationDirectory),
RetroWfcPayload.PayloadRelativePath(gameId));
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
File.Copy(cachedFile, destination, overwrite: true);
var root = InputValidation.ValidateStagedRetroWfcPayloadDirectory(destinationDirectory);
var root = InputValidation.ValidateStagedRetroWfcPayloadDirectory(destinationDirectory, gameId);
_reporter.Diagnostic(
"The Retro-WFC payload service is unavailable; reusing the installation's verified " +
$"payload snapshot (sha256 {fingerprint.RetroWfcPayloadSha256[..12]}..., " +
+222 -35
View File
@@ -28,19 +28,9 @@ internal static class SelfTests
}
finally { File.Delete(path); }
}, failures);
Test("Disc compatibility", () =>
InputValidation.EnsureCompatibleDisc(new DiscHeader { GameId = "RMCP01" },
new PayloadManifest { ExpectedGameId = "RMCP01" }), failures);
Test("Disc rejection", () =>
{
try
{
InputValidation.EnsureCompatibleDisc(new DiscHeader { GameId = "RMCJ01" },
new PayloadManifest { ExpectedGameId = "RMCP01" });
throw new Exception("Wrong region was accepted.");
}
catch (InvalidDataException) { }
}, failures);
Test("Disc compatibility", TestDiscCompatibility, failures);
Test("Payload manifest releases", TestPayloadManifestReleases, failures);
Test("Extracted game release validation", TestExtractedGameRelease, failures);
Test("Payload timestamp normalization", TestPayloadTimestampNormalization, failures);
Test("Workspace timestamp reconciliation", TestWorkspaceTimestampReconciliation, failures);
Test("Toolkit accompanies the workspace", TestToolkitRefreshDecision, failures);
@@ -77,6 +67,152 @@ internal static class SelfTests
return failures.Count == 0 ? 0 : 1;
}
private static PayloadManifest MultiReleaseManifest() => new()
{
SchemaVersion = PayloadManifest.CurrentSchemaVersion,
ToolkitFingerprint = "toolkit", ToolkitPackageFingerprint = "package", RuntimeAssetsFingerprint = "assets",
SupportedReleases =
[
new GameRelease { GameId = "RMCP01", DolSha256 = new string('1', 64), RelSha256 = new string('2', 64) },
new GameRelease { GameId = "RMCE01", DolSha256 = new string('3', 64), RelSha256 = new string('4', 64) },
new GameRelease { GameId = "RMCJ01", DolSha256 = new string('5', 64), RelSha256 = new string('6', 64) }
]
};
private static void TestDiscCompatibility()
{
var manifest = MultiReleaseManifest();
foreach (var release in manifest.SupportedReleases)
{
var selected = InputValidation.EnsureCompatibleDisc(
new DiscHeader { GameId = release.GameId.ToLowerInvariant() }, manifest);
if (!ReferenceEquals(selected, release))
throw new Exception($"{release.GameId} did not select its own release.");
}
foreach (var unsupported in new[] { "RMCK01", "RMCP02", "RSBE01" })
{
try
{
InputValidation.EnsureCompatibleDisc(new DiscHeader { GameId = unsupported }, manifest);
throw new Exception($"{unsupported} was accepted.");
}
catch (InvalidDataException ex) when (ex.Message.Contains("NTSC-U (RMCE01)", StringComparison.Ordinal) &&
ex.Message.Contains(unsupported, StringComparison.Ordinal)) { }
}
}
private static void TestPayloadManifestReleases()
{
PayloadArchive.ValidateManifest(MultiReleaseManifest());
// The shape Build-Installer.ps1 writes (PowerShell's ConvertTo-Json of the ordered manifest).
var dol = new string('a', 64);
var rel = new string('b', 64);
var json = $$"""
{
"SchemaVersion": 3,
"ProductVersion": "1.0.0",
"SupportedReleases": [
{ "GameId": "RMCP01", "DolSha256": "{{dol}}", "RelSha256": "{{rel}}" },
{ "GameId": "RMCE01", "DolSha256": "{{rel}}", "RelSha256": "{{dol}}" }
],
"ToolkitReleaseTag": "",
"BuildModel": "local-translation-and-compilation",
"ToolkitFingerprint": "toolkit",
"ToolkitPackageFingerprint": "package",
"RuntimeAssetsFingerprint": "assets",
"TranslationFingerprint": "translation",
"NativeToolchainFingerprint": "native"
}
""";
using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)))
{
var parsed = PayloadArchive.ParseManifest(stream);
if (parsed.SupportedReleases.Count != 2 || parsed.SupportedReleases[1].GameId != "RMCE01" ||
parsed.SupportedReleases[1].DolSha256 != rel)
throw new Exception("The packaged release list was not read back exactly.");
}
void Reject(string description, Action<PayloadManifest> corrupt)
{
var manifest = MultiReleaseManifest();
corrupt(manifest);
try
{
PayloadArchive.ValidateManifest(manifest);
throw new Exception($"A manifest with {description} was accepted.");
}
catch (InvalidDataException) { }
}
Reject("the previous single-release schema", manifest => manifest.SchemaVersion = 2);
Reject("no releases", manifest => manifest.SupportedReleases.Clear());
Reject("an unsupported release", manifest => manifest.SupportedReleases[0].GameId = "RMCK01");
Reject("a missing input digest", manifest => manifest.SupportedReleases[1].RelSha256 = "");
Reject("a duplicated release", manifest =>
manifest.SupportedReleases.Add(new GameRelease
{
GameId = "RMCE01", DolSha256 = new string('7', 64), RelSha256 = new string('8', 64)
}));
}
private static void TestExtractedGameRelease()
{
var root = Path.Combine(Path.GetTempPath(), "mkwc-release-" + Guid.NewGuid().ToString("N"));
try
{
var data = Path.Combine(root, "GameAssets", "DATA");
Directory.CreateDirectory(Path.Combine(data, "sys"));
Directory.CreateDirectory(Path.Combine(data, "files", "rel"));
File.WriteAllText(Path.Combine(data, "sys", "main.dol"), "ntsc-u dol");
File.WriteAllText(Path.Combine(data, "sys", "fst.bin"), "fst");
File.WriteAllText(Path.Combine(data, "files", "rel", "StaticR.rel"), "ntsc-u rel");
File.WriteAllText(Path.Combine(data, "sys", "boot.bin"), "RMCE01" + new string('\0', 26));
var release = new GameRelease
{
GameId = "RMCE01",
DolSha256 = InputValidation.Sha256File(Path.Combine(data, "sys", "main.dol")),
RelSha256 = InputValidation.Sha256File(Path.Combine(data, "files", "rel", "StaticR.rel"))
};
InstallerEngine.ValidateExtractedGame(data, release);
if (new Installation(root).ReadGameId() != "RMCE01")
throw new Exception("The installed game id was not read from the extracted disc header.");
void Reject(string description, GameRelease expected)
{
try
{
InstallerEngine.ValidateExtractedGame(data, expected);
throw new Exception($"{description} was accepted.");
}
catch (InvalidDataException) { }
}
Reject("Another release's digests", new GameRelease
{
GameId = "RMCE01", DolSha256 = new string('1', 64), RelSha256 = release.RelSha256
});
Reject("A disc header of another region", new GameRelease
{
GameId = "RMCJ01", DolSha256 = release.DolSha256, RelSha256 = release.RelSha256
});
File.Delete(Path.Combine(data, "sys", "boot.bin"));
Reject("An extraction without a disc header", release);
try
{
_ = new Installation(root).ReadGameId();
throw new Exception("A missing disc header produced a game id.");
}
catch (InvalidDataException) { }
}
finally
{
if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
}
}
private static void TestAcceptedExtension(string extension)
{
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + extension);
@@ -694,42 +830,91 @@ internal static class SelfTests
private static void TestRetroWfcPayloadSnapshotReconciliation()
{
// The endpoint is pinned in exactly one place; this test must not restate it.
const string endpoint = InputValidation.CurrentRetroWfcPayloadUri;
const string shaB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
var root = Path.Combine(Path.GetTempPath(), "mkwc-wfc-snapshot-" + Guid.NewGuid().ToString("N"));
try
{
InputValidation.ValidateRetroWfcPayloadUri(endpoint);
using var signingRsa = RSA.Create(2048);
var signingKey = signingRsa.ExportParameters(includePrivateParameters: false);
byte[] SignedPayload(string payloadGame)
{
var image = new byte[0x150];
"WWFC/Payload"u8.CopyTo(image);
BinaryPrimitives.WriteUInt32BigEndian(image.AsSpan(0x0C), (uint)image.Length);
System.Text.Encoding.ASCII.GetBytes(payloadGame).CopyTo(image, 0x138);
image[^1] = 0x5A;
signingRsa.SignData(image.AsSpan(0x110), HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1).CopyTo(image.AsSpan(0x10));
return image;
}
// Every supported release has its own endpoint and staged file, and a payload staged for
// one region never validates as another region's.
var endpoints = new HashSet<string>(StringComparer.Ordinal);
foreach (var gameId in RetroWfcPayload.SupportedGameIds)
{
var endpoint = RetroWfcPayload.PayloadUri(gameId);
InputValidation.ValidateRetroWfcPayloadUri(endpoint);
if (!endpoints.Add(endpoint))
throw new Exception($"{gameId} shares another release's Retro-WFC endpoint.");
try
{
InputValidation.ValidateRetroWfcPayloadUri(endpoint + "&extra=1");
throw new Exception("A non-canonical Retro-WFC endpoint was accepted.");
}
catch (InvalidDataException) { }
var regionalRoot = Path.Combine(root, "regional-" + gameId);
var regionalPath = Path.Combine(regionalRoot, RetroWfcPayload.PayloadRelativePath(gameId));
Directory.CreateDirectory(Path.GetDirectoryName(regionalPath)!);
File.WriteAllBytes(regionalPath, SignedPayload(RetroWfcPayload.PayloadGame(gameId)));
if (InputValidation.ResolveRetroWfcPayloadFile(regionalRoot, gameId, signingKey) != regionalPath)
throw new Exception($"{gameId} did not resolve its own staged payload file.");
foreach (var otherGameId in RetroWfcPayload.SupportedGameIds.Where(other => other != gameId))
{
var foreignRoot = Path.Combine(root, $"foreign-{gameId}-{otherGameId}");
var foreignPath = Path.Combine(foreignRoot, RetroWfcPayload.PayloadRelativePath(otherGameId));
Directory.CreateDirectory(Path.GetDirectoryName(foreignPath)!);
File.WriteAllBytes(foreignPath, SignedPayload(RetroWfcPayload.PayloadGame(gameId)));
try
{
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(foreignRoot, otherGameId,
signingKey);
throw new Exception($"A {gameId} payload was accepted for {otherGameId}.");
}
catch (InvalidDataException) { }
try
{
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(regionalRoot, otherGameId,
signingKey);
throw new Exception($"{otherGameId} accepted a directory staged only for {gameId}.");
}
catch (InvalidDataException) { }
}
}
try
{
InputValidation.ValidateRetroWfcPayloadUri(endpoint + "&extra=1");
throw new Exception("A non-canonical Retro-WFC endpoint was accepted.");
_ = RetroWfcPayload.PayloadUri("RMCK01");
throw new Exception("An unsupported release has a Retro-WFC endpoint.");
}
catch (InvalidDataException) { }
const string gameUnderTest = "RMCE01";
var installation = new Installation(root);
Directory.CreateDirectory(installation.RetroDirectory);
var payloadRoot = Path.Combine(root, "download");
var payloadPath = Path.Combine(payloadRoot, "binary", "payload.RMCPD00.bin");
var payloadPath = Path.Combine(payloadRoot, RetroWfcPayload.PayloadRelativePath(gameUnderTest));
Directory.CreateDirectory(Path.GetDirectoryName(payloadPath)!);
using var signingRsa = RSA.Create(2048);
var signingKey = signingRsa.ExportParameters(includePrivateParameters: false);
var bytes = new byte[0x140];
"WWFC/Payload"u8.CopyTo(bytes);
BinaryPrimitives.WriteUInt32BigEndian(bytes.AsSpan(0x0C), (uint)bytes.Length);
bytes[^1] = 0x5A;
signingRsa.SignData(bytes.AsSpan(0x110), HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1).CopyTo(bytes.AsSpan(0x10));
var bytes = SignedPayload(RetroWfcPayload.PayloadGame(gameUnderTest));
File.WriteAllBytes(payloadPath, bytes);
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
try
{
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot);
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest);
throw new Exception("A payload signed by a foreign key passed the pinned production key.");
}
catch (InvalidDataException) { }
var shaA = InputValidation.ComputeRetroWfcPayloadSha256(payloadRoot, signingKey);
var shaA = InputValidation.ComputeRetroWfcPayloadSha256(payloadRoot, gameUnderTest, signingKey);
var snapshot = new RetroWfcPayloadSnapshot(payloadRoot, shaA, bytes.Length);
JsonState.Write(Path.Combine(installation.RetroDirectory, ProductFingerprint.FileName),
new ProductFingerprint
@@ -764,7 +949,7 @@ internal static class SelfTests
File.WriteAllBytes(payloadPath, bytes);
try
{
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
throw new Exception("A payload with a malformed declared length was accepted.");
}
catch (InvalidDataException) { }
@@ -774,7 +959,7 @@ internal static class SelfTests
File.WriteAllBytes(payloadPath, bytes);
try
{
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
throw new Exception("A payload with tampered signed content was accepted.");
}
catch (InvalidDataException) { }
@@ -784,7 +969,7 @@ internal static class SelfTests
File.WriteAllBytes(payloadPath, bytes);
try
{
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, signingKey);
_ = InputValidation.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameUnderTest, signingKey);
throw new Exception("A payload with a malformed header was accepted.");
}
catch (InvalidDataException) { }
@@ -1261,7 +1446,8 @@ internal static class SelfTests
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "CMake", "bin"));
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "Ninja"));
Directory.CreateDirectory(Path.Combine(root, "Toolkit", "llvm-mingw", "bin"));
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "projects", "mkwii"));
foreach (var project in new[] { "mkwii", "mkwii-ntsc-u", "mkwii-ntsc-j" })
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "projects", project));
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "src"));
Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2",
"wc24"));
@@ -1288,7 +1474,8 @@ internal static class SelfTests
File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "tool");
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "LocalBuild.ps1"), "# build");
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "NativeBuildFlags.ps1"), "# flags");
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", "mkwii", "recomp.yml"), "profiles: {}");
foreach (var project in new[] { "mkwii", "mkwii-ntsc-u", "mkwii-ntsc-j" })
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", project, "recomp.yml"), "profiles: {}");
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "CMakeLists.txt"), "cmake_minimum_required(VERSION 3.20)");
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "src", "main.cpp"), "int main();");
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2", "wc24",
@@ -16,15 +16,15 @@ internal static class ToolkitFingerprint
// v6/native-toolchain v2 forces one clean rebuild past a Ninja stale-object bug.
// translation v2 added runtime/src, which the translator regex-scans for native overrides;
// v3 replaced it with the native index extracted from it, whenever that index is current.
private const string Version = "mkwc-toolkit-compile-v6";
private const string TranslationVersion = "mkwc-toolkit-translation-v3";
private const string Version = "mkwc-toolkit-compile-v7";
private const string TranslationVersion = "mkwc-toolkit-translation-v4";
private const string NativeToolchainVersion = "mkwc-toolkit-native-toolchain-v2";
private const string PackageVersion = "mkwc-toolkit-package-v1";
private const string RuntimeAssetsVersion = "mkwc-product-runtime-assets-v3";
private const string RuntimeAssetsDescription = "The product runtime assets";
private static readonly string[] SourceExtensions =
[".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".inl", ".s", ".asm",
[".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".inl", ".inc", ".s", ".asm",
".cmake", ".txt", ".yml", ".yaml", ".toml", ".json", ".ps1", ".in", ".def", ".patch"];
/// <summary>
@@ -40,7 +40,11 @@ internal static class ToolkitFingerprint
];
private static readonly string[] TranslationFiles =
[InstalledLayout.WorkspaceEntryPrefix + "LocalBuild.ps1"];
[
InstalledLayout.WorkspaceEntryPrefix + "LocalBuild.ps1",
// Maps the PAL-keyed native index to the installed release's addresses.
InstalledLayout.WorkspaceEntryPrefix + "runtime/include/guest_abi/guest_address_table.inc"
];
private const string NativeSourcePrefix = InstalledLayout.WorkspaceEntryPrefix + "runtime/src/";
private const string NativeIndexEntry = InstalledLayout.WorkspaceEntryPrefix + "runtime/native-index.json";
+65 -2
View File
@@ -1,5 +1,6 @@
# Shared incremental-build logic for the Unix setups (Linux AppImage and macOS), sourced by
# local-build.sh, local-build-macos.command, the AppImage's AppRun and macos/setup.command.
# Shared incremental-build and release-selection logic for the Unix setups (Linux AppImage and
# macOS), sourced by local-build.sh, local-build-macos.command, macos/extract-disc.command, the
# AppImage's AppRun and macos/setup.command.
# Mirrors the Windows installer's translation/toolchain identities and timestamp preservation.
# Must stay compatible with macOS's bash 3.2.
@@ -76,6 +77,7 @@ mkw_translation_identity() {
printf 'rel %s\n' "$(mkw_digest_path "$workspace/Assets/StaticR.rel")"
printf 'translator %s\n' "$(mkw_digest_path "$translator")"
printf 'script %s\n' "$(mkw_digest_path "$script")"
printf 'regions %s\n' "$(mkw_digest_path "$workspace/runtime/include/guest_abi/guest_address_table.inc")"
mkw_digest_tree "$workspace" projects
mkw_native_index_identity "$workspace"
} | mkw_sha256
@@ -99,6 +101,67 @@ mkw_toolchain_identity() {
} | mkw_sha256
}
# Prints one fact a projects/*/recomp.yml pins: game_id, region, dol, rel or retro_wfc_payload.
# Literal line matching against the fixed shape, like NativeBuildFlags.ps1's Get-MkwProjectPins.
# $1 = recomp.yml, $2 = fact.
mkw_project_pin() {
awk -v want="$2" '
{ sub(/\r$/, ""); sub(/#.*/, "") }
/^[A-Za-z0-9_]+:/ { section = $1; sub(/:.*/, "", section); input = ""; next }
section == "inputs" && /^ [A-Za-z0-9_]+:[[:space:]]*$/ { input = $1; sub(/:$/, "", input); next }
section == "project" && $1 == (want ":") { print $2; exit }
section == "inputs" && input == want && $1 == "sha256:" { print tolower($2); exit }
want == "retro_wfc_payload" && $1 == "retro_wfc_payload:" { print $2; exit }
' "$1"
}
# Prints the recomp.yml of the supported release whose game id is $2. $1 = workspace.
mkw_project_for_game() {
local project
for project in "$1"/projects/*/recomp.yml; do
if [[ -f "$project" && "$(mkw_project_pin "$project" game_id)" == "$2" ]]; then
printf '%s\n' "$project"
return 0
fi
done
return 1
}
# Prints the recomp.yml pinned to $2/main.dol after checking StaticR.rel and any extracted disc
# header against it, like NativeBuildFlags.ps1's Resolve-MkwProjectForAssets.
# $1 = workspace, $2 = assets directory.
mkw_resolve_project() {
local project dol_sha game_id disc_id
dol_sha=$(mkw_sha256 "$2/main.dol")
for project in "$1"/projects/*/recomp.yml; do
[[ -f "$project" && "$(mkw_project_pin "$project" dol)" == "$dol_sha" ]] || continue
game_id=$(mkw_project_pin "$project" game_id)
if [[ "$(mkw_sha256 "$2/StaticR.rel")" != "$(mkw_project_pin "$project" rel)" ]]; then
printf 'StaticR.rel does not match the clean %s release of main.dol.\n' "$game_id" >&2
return 1
fi
if [[ -f "$2/DATA/sys/boot.bin" ]]; then
disc_id=$(head -c 6 "$2/DATA/sys/boot.bin")
if [[ "$disc_id" != "$game_id" ]]; then
printf 'The extracted game data is %s but main.dol is %s; extract the game again.\n' "$disc_id" "$game_id" >&2
return 1
fi
fi
printf '%s\n' "$project"
return 0
done
printf 'main.dol (sha256 %s) is not a supported clean Mario Kart Wii release.\n' "$dol_sha" >&2
return 1
}
# The offline file name the translator expects for a release's Retro-WFC payload. $1 = recomp.yml.
mkw_retro_wfc_payload_file() {
local uri
uri=$(mkw_project_pin "$1" retro_wfc_payload)
[[ -n "$uri" ]] || return 1
printf 'binary/payload.%s.bin\n' "${uri##*g=}"
}
# $1 = JSON file, $2 = key. Prints the recorded string value, or nothing.
mkw_read_identity() {
[[ -f "$1" ]] || return 0
+10 -8
View File
@@ -19,9 +19,9 @@ Usage: local-build-macos.command --output-dir DIR [options]
--profile {base|retro-rewind|both} Product to build (default: base)
--output-dir DIR Output .app directory (required; Retro Rewind for both)
--base-output-dir DIR Base .app directory (required with --profile both)
--game IMAGE --nodtool PATH Extract and verify a clean PAL RMCP01 disc image first
--game IMAGE --nodtool PATH Extract and verify a clean supported disc image first
--retro-rewind-package-dir DIR RetroRewind6 directory (required for Retro Rewind)
--retro-wfc-offline-dir DIR Directory containing binary/payload.RMCPD00.bin
--retro-wfc-offline-dir DIR Directory containing the release's binary/payload.<game>.bin
--skip-retro-wfc-payload Build Retro Rewind without the shared Retro-WFC payload
--force-clean-build Delete local generated and current-architecture native build caches
--parallel N Pin translation and build parallelism
@@ -82,13 +82,15 @@ if (( builds_retro )); then
fi
for tool in "$cmake_bin" "$ninja_bin" clang clang++ shasum; do command -v "$tool" >/dev/null || fail "required tool not found: $tool"; done
project="$workspace/projects/mkwii/recomp.yml"; assets="$workspace/Assets"; generated="$workspace/generated"
assets="$workspace/Assets"; generated="$workspace/generated"
functions="$generated/functions"; metadata="$generated/base_translation_output.json"; manifest_dir="$workspace/build/base"
manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards"
native_build="$workspace/native-build-macos-$macos_arch"
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" --workspace "$workspace" --game "$game" --assets-dir "$assets" --nodtool "$nodtool"; fi
assert_file "$assets/main.dol" 'extracted main.dol'; assert_file "$assets/StaticR.rel" 'extracted StaticR.rel'
# The translation project is the one pinned to the extracted main.dol, which decides the region.
project=$(mkw_resolve_project "$workspace" "$assets") || fail 'the extracted game cannot be translated'
region=$(mkw_project_pin "$project" region); [[ -n "$region" ]] || fail "could not find the region of $project"
if (( force_clean )); then
step force-clean 'Discarding translation and native build caches'
@@ -148,12 +150,12 @@ else
step translate-base 'Translating the user-owned base game'
translator translate-recursive "$entry_point" --project "$project" --outdir "$functions" --output-metadata "$metadata" --production-source-bundle "$generated/base_translation_sources.bin" --no-function-files --prune-stale --threads "$translator_threads"
step emit-base-manifest 'Creating the local base translation manifest'
translator emit-base-manifest --project "$project" --out "$manifest_dir" --functions-dir "$functions" --translation-output-metadata "$metadata" --region P
translator emit-base-manifest --project "$project" --out "$manifest_dir" --functions-dir "$functions" --translation-output-metadata "$metadata" --region "$region"
mkw_write_identity "$translation_provenance" TranslationFingerprint "$translation_fingerprint"
fi
if (( builds_retro )); then
mod_out="$workspace/build/mods/retro_rewind_full_cpp"; args=(translate-mod --project "$project" --profile retro-rewind --base-manifest "$manifest" --base-translation-output-metadata "$metadata" --code-pul "$retro_root/Binaries/Code.pul" --mod-root "$retro_root" --mod-name 'Retro Rewind' --region P --out "$mod_out" --prefer-cached-inputs --emit-cpp --threads "$translator_threads")
if (( skip_retro_wfc )); then args+=(--skip-retro-wfc); else offline_payload="$retro_wfc/binary/payload.RMCPD00.bin"; assert_file "$offline_payload" 'Offline Retro-WFC payload'; args+=(--retro-wfc-payload "$offline_payload"); fi
mod_out="$workspace/build/mods/retro_rewind_full_cpp"; args=(translate-mod --project "$project" --profile retro-rewind --base-manifest "$manifest" --base-translation-output-metadata "$metadata" --code-pul "$retro_root/Binaries/Code.pul" --mod-root "$retro_root" --mod-name 'Retro Rewind' --region "$region" --out "$mod_out" --prefer-cached-inputs --emit-cpp --threads "$translator_threads")
if (( skip_retro_wfc )); then args+=(--skip-retro-wfc); else payload_file=$(mkw_retro_wfc_payload_file "$project") || fail "$project pins no Retro-WFC payload"; offline_payload="$retro_wfc/$payload_file"; assert_file "$offline_payload" 'Offline Retro-WFC payload'; args+=(--retro-wfc-payload "$offline_payload"); fi
step translate-mod 'Translating Retro Rewind'; translator "${args[@]}"
fi
step generate-data-init 'Generating local game data initialization'; translator generate-data-init --project "$project"
+8 -5
View File
@@ -208,7 +208,6 @@ if [[ "$x86_cpu_profile" == "auto" ]]; then
fi
fi
project=$workspace/projects/mkwii/recomp.yml
assets=$workspace/Assets
generated=$workspace/generated
functions=$generated/functions
@@ -221,9 +220,12 @@ translation_provenance=$generated/translation-provenance.json
toolchain_provenance=$build/toolchain-provenance.json
retro_root=${retro_rewind_package_dir:-$workspace/PulsarPacks/completed/RetroRewind/RetroRewind6}
assert_file "$project" "Translation project"
assert_file "$assets/main.dol" "Extracted main.dol (see translator/README.md - owning the game is required)"
assert_file "$assets/StaticR.rel" "Extracted StaticR.rel (see translator/README.md - owning the game is required)"
# The translation project is the one pinned to the extracted main.dol, which decides the region.
project=$(mkw_resolve_project "$workspace" "$assets") || fail "the extracted game cannot be translated"
region=$(mkw_project_pin "$project" region)
[[ -n "$region" ]] || fail "Could not find the region of $project"
# The AppImage bundles Clang, but Linux startup objects and the C/C++ link runtimes
# still come from the host. Check them before the expensive translation so a missing
@@ -362,7 +364,7 @@ else
log_step emit-base-manifest "Creating the local base translation manifest"
translator emit-base-manifest --project "$project" --out "$base_manifest_dir" \
--functions-dir "$functions" --translation-output-metadata "$base_metadata" --region P
--functions-dir "$functions" --translation-output-metadata "$base_metadata" --region "$region"
mkw_write_identity "$translation_provenance" TranslationFingerprint "$translation_fingerprint"
fi
@@ -374,12 +376,13 @@ if (( builds_retro )); then
translate_mod_args=(translate-mod --project "$project" --profile retro-rewind
--base-manifest "$base_manifest" --base-translation-output-metadata "$base_metadata"
--code-pul "$code_pul" --mod-root "$retro_root" --mod-name "Retro Rewind"
--region P --out "$retro_out" --prefer-cached-inputs --emit-cpp
--region "$region" --out "$retro_out" --prefer-cached-inputs --emit-cpp
--threads "$translator_threads")
if (( skip_retro_wfc_payload )); then
translate_mod_args+=(--skip-retro-wfc)
else
offline_payload=$retro_wfc_offline_dir/binary/payload.RMCPD00.bin
payload_file=$(mkw_retro_wfc_payload_file "$project") || fail "$project pins no Retro-WFC payload"
offline_payload=$retro_wfc_offline_dir/$payload_file
assert_file "$offline_payload" "Offline Retro-WFC shared payload"
translate_mod_args+=(--retro-wfc-payload "$offline_payload")
fi
+26 -13
View File
@@ -1,30 +1,35 @@
#!/usr/bin/env bash
# Extract a user-owned Mario Kart Wii PAL (RMCP01) disc image for a local build.
# Extract a user-owned Mario Kart Wii disc image of a supported release for a local build.
# This script deliberately contains no game data and is intended for the macOS
# setup application and for maintainers testing that setup path.
set -euo pipefail
readonly EXPECTED_DOL_SHA256=80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05
readonly EXPECTED_REL_SHA256=16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../build-cache.sh
source "$script_dir/../build-cache.sh"
fail() { printf 'extract-disc.command: error: %s\n' "$*" >&2; exit 1; }
sha256() { shasum -a 256 "$1" | awk '{ print $1 }'; }
usage() {
cat <<'EOF'
Usage: extract-disc.command --game IMAGE --assets-dir DIR --nodtool PATH
Usage: extract-disc.command --game IMAGE --assets-dir DIR --nodtool PATH [--workspace DIR]
Extracts a user-owned Mario Kart Wii PAL RMCP01 image into DIR. The final
layout is DIR/main.dol, DIR/StaticR.rel, and DIR/DATA. Existing data is left
untouched unless the complete new extraction passes both content hash checks.
Extracts a user-owned Mario Kart Wii image into DIR. The image must be a release
a projects/*/recomp.yml in the workspace (default: this script's repository)
pins. The final layout is DIR/main.dol, DIR/StaticR.rel, and DIR/DATA. Existing
data is left untouched unless the complete new extraction passes both content
hash checks.
EOF
}
game=""
assets_dir=""
nodtool=""
workspace=$(cd "$script_dir/../.." && pwd)
while (($#)); do
case "$1" in
--workspace) workspace=${2:-}; shift 2 ;;
--game) game=${2:-}; shift 2 ;;
--assets-dir) assets_dir=${2:-}; shift 2 ;;
--nodtool) nodtool=${2:-}; shift 2 ;;
@@ -39,11 +44,21 @@ done
assets_dir=$(mkdir -p "$assets_dir" && cd "$assets_dir" && pwd)
# The disc's game ID selects the release, so an existing extraction is only reused for the same one.
printf 'MKWCBUILD:STEP:validate-disc Checking the selected disc image\n'
disc_info=$("$nodtool" info "$game") || fail 'nodtool could not read the disc image'
game_id=$(awk '/^Game ID: / { print $3; exit }' <<<"$disc_info")
[[ -n "$game_id" ]] || fail 'nodtool did not report the disc game ID'
project=$(mkw_project_for_game "$workspace" "$game_id") || fail "disc $game_id is not a supported Mario Kart Wii release"
expected_dol_sha256=$(mkw_project_pin "$project" dol)
expected_rel_sha256=$(mkw_project_pin "$project" rel)
[[ -n "$expected_dol_sha256" && -n "$expected_rel_sha256" ]] || fail "$project does not pin the clean $game_id digests"
# Sorted "<size> <path>" of every extracted file; recorded at extraction, compared on reuse.
data_manifest() { (cd "$1" && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 stat -f '%z %N'); }
if [[ -d "$assets_dir/DATA/files" && -f "$assets_dir/.data-manifest" && -f "$assets_dir/main.dol" && -f "$assets_dir/StaticR.rel" ]] && \
[[ $(sha256 "$assets_dir/main.dol") == "$EXPECTED_DOL_SHA256" && $(sha256 "$assets_dir/StaticR.rel") == "$EXPECTED_REL_SHA256" ]] && \
[[ $(sha256 "$assets_dir/main.dol") == "$expected_dol_sha256" && $(sha256 "$assets_dir/StaticR.rel") == "$expected_rel_sha256" ]] && \
[[ "$(data_manifest "$assets_dir/DATA")" == "$(cat "$assets_dir/.data-manifest")" ]]; then
printf 'MKWCBUILD:STEP:disc-ready Reusing the verified extracted game assets\n'
exit 0
@@ -52,8 +67,6 @@ scratch=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-disc.XXXXXX")
cleanup() { rm -rf "$scratch"; }
trap cleanup EXIT
printf 'MKWCBUILD:STEP:validate-disc Checking the selected disc image\n'
"$nodtool" info "$game" >/dev/null
printf 'MKWCBUILD:STEP:extract-disc Extracting the user-owned disc image\n'
"$nodtool" extract "$game" "$scratch/extracted"
@@ -63,8 +76,8 @@ rel=$(find "$scratch/extracted" -type f -path '*/files/rel/StaticR.rel' -print -
[[ -n "$rel" ]] || fail 'nodtool extraction did not contain files/rel/StaticR.rel'
dol_sha=$(sha256 "$dol")
rel_sha=$(sha256 "$rel")
[[ $dol_sha == "$EXPECTED_DOL_SHA256" ]] || fail "main.dol does not match the clean PAL RMCP01 image (expected $EXPECTED_DOL_SHA256, got $dol_sha)"
[[ $rel_sha == "$EXPECTED_REL_SHA256" ]] || fail "StaticR.rel does not match the clean PAL RMCP01 image (expected $EXPECTED_REL_SHA256, got $rel_sha)"
[[ $dol_sha == "$expected_dol_sha256" ]] || fail "main.dol does not match the clean $game_id image (expected $expected_dol_sha256, got $dol_sha)"
[[ $rel_sha == "$expected_rel_sha256" ]] || fail "StaticR.rel does not match the clean $game_id image (expected $expected_rel_sha256, got $rel_sha)"
data_root=$(dirname "$(dirname "$dol")")
[[ -d "$data_root/files" ]] || fail 'nodtool extraction did not contain the Wii files directory'
@@ -92,4 +105,4 @@ mv "$stage/StaticR.rel" "$assets_dir/StaticR.rel"
rmdir "$stage"
rm -rf "$backup"
printf 'MKWCBUILD:STEP:disc-ready Verified and extracted clean PAL RMCP01 game assets\n'
printf 'MKWCBUILD:STEP:disc-ready Verified and extracted clean %s game assets\n' "$game_id"
+18 -9
View File
@@ -48,7 +48,7 @@ ninja_bin="$host_tools/ninja"
if [[ -z "$game" ]]; then
game=$(/usr/bin/osascript <<'APPLESCRIPT'
set selectedFile to choose file with prompt "Choose your clean Mario Kart Wii PAL (RMCP01) disc image"
set selectedFile to choose file with prompt "Choose your clean Mario Kart Wii disc image"
POSIX path of selectedFile
APPLESCRIPT
) || exit 0
@@ -100,18 +100,28 @@ if (( needs_workspace_refresh )); then
fi
fi
# The extracted disc decides the release, and with it the region's Retro-WFC payload.
# shellcheck source=../build-cache.sh
source "$workspace/Launcher/build-cache.sh"
"$workspace/Launcher/macos/extract-disc.command" --workspace "$workspace" --game "$game" \
--assets-dir "$workspace/Assets" --nodtool "$nodtool"
project=$(mkw_resolve_project "$workspace" "$workspace/Assets") || fail 'the extracted game cannot be built'
profile=base
build_args=(--workspace "$workspace" --game "$game" --nodtool "$nodtool" --output-dir "$products")
build_args=(--workspace "$workspace" --output-dir "$products")
if [[ -n "$retro_dir" ]]; then
profile=both
# Online play needs the shared Retro-WFC payload. Keep it in the per-user
# support directory rather than the packaged app or build workspace, then
# verify its pinned signature before publishing it into the local cache.
game_id=$(mkw_project_pin "$project" game_id)
payload_uri=$(mkw_project_pin "$project" retro_wfc_payload)
payload_file=$(mkw_retro_wfc_payload_file "$project") || fail "$project pins no Retro-WFC payload"
retro_wfc_dir="$support_root/RetroWfcPayload"
retro_wfc_payload="$retro_wfc_dir/binary/payload.RMCPD00.bin"
retro_wfc_payload="$retro_wfc_dir/$payload_file"
cached_payload_valid=0
if [[ -f "$retro_wfc_payload" ]]; then
if "$translator" validate-retro-wfc-payload --directory "$retro_wfc_dir"; then
if "$translator" validate-retro-wfc-payload --directory "$retro_wfc_dir" --game-id "$game_id"; then
cached_payload_valid=1
else
printf 'Discarding an invalid cached Retro-WFC payload...\n' >&2
@@ -125,15 +135,14 @@ if [[ -n "$retro_dir" ]]; then
printf 'Downloading the current Retro-WFC payload needed for online play...\n'
mkdir -p "$retro_wfc_dir"
payload_stage=$(mktemp -d "$retro_wfc_dir/.payload-download.XXXXXX")
temporary_payload="$payload_stage/binary/payload.RMCPD00.bin"
temporary_payload="$payload_stage/$payload_file"
mkdir -p "$(dirname "$temporary_payload")"
trap 'rm -rf "$payload_stage"' EXIT
if /usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
--retry 1 --output "$temporary_payload" \
'https://rwfc.net/api/wfc/payload?g=RMCPD00'; then
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
--retry 1 --output "$temporary_payload" "$payload_uri"; then
"$translator" validate-retro-wfc-payload --directory "$payload_stage" --game-id "$game_id" || \
fail 'downloaded Retro-WFC payload failed signature validation'
mkdir -p "$retro_wfc_dir/binary"
mkdir -p "$(dirname "$retro_wfc_payload")"
mv "$temporary_payload" "$retro_wfc_payload"
rmdir "$payload_stage/binary" "$payload_stage"
trap - EXIT
+29432
View File
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
# RMCJ01 addresses for the PAL (RMCP01) data globals named by the WiiCompiled runtime.
# Derived from projects/mkwii-ntsc-u/data_addresses.txt and confirmed against the RMCJ01 binaries.
# Format: <PAL hex> <target hex> <verdict and evidence>
# PROVEN - the instruction at the ported reference site forms exactly this address,
# and the independent chunk-table port of the PAL address agrees.
# CALIBRATED - the reference site reproduces the known reference-disc address for this
# global, so its decode in this region is trusted even where the chunk
# table has no mapping.
# MATCHED - the reference site's instruction window, with the fields that legitimately
# differ between regions masked out, occurs exactly once in this region's
# code; the displacement was read from that instruction. Used where the
# chunk table is not instruction-exact inside a function.
# FIELD - uniquely matched PAL base and field instructions, with the base register
# preserved and the target's own displacement decoded (including REL relocations).
# RECORD - a verified table base, target indexing stride and matching immutable records.
# SECTION - a section boundary, taken from the region section table.
# CHUNK-ONLY - chunk-table port only; no reference site formed exactly this address.
# Small-data bases: RMCJ01 r13=0x8038C580 r2=0x8038E920.
# Totals: 115 confirmed, 16 chunk-only, 0 unresolved.
80344090 80343A10 PROVEN @80172D38 (calibrated on P@80172E18) forms 0x80343A10 (chunk port agrees)
80344094 80343A14 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
80344098 80343A18 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
803440A4 80343A24 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
803440A8 80343A28 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
803440AC 80343A2C CHUNK-ONLY (no reference site confirmed it: no-reference-site)
80344110 80343A90 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
80344710 80344090 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
803867D8 80386158 PROVEN @8016EA44 (calibrated on P@8016EB24) forms 0x80386158 (chunk port agrees)
803886C8 80388048 PROVEN @8016B794 (calibrated on P@8016B874) forms 0x80388048 (chunk port agrees)
80343230 80342BB0 PROVEN @801634E0 (calibrated on P@801635C0) forms 0x80342BB0 (chunk port agrees)
803434E0 80342E60 PROVEN @801648A0 (calibrated on P@80164980) forms 0x80342E60 (chunk port agrees)
80386664 80385FE4 PROVEN @80162B34 (calibrated on P@80162C14) forms 0x80385FE4 (chunk port agrees)
80386668 80385FE8 PROVEN @80162B78 (calibrated on P@80162C58) forms 0x80385FE8 (chunk port agrees)
80386670 80385FF0 PROVEN @801633B0 (calibrated on P@80163490) forms 0x80385FF0 (chunk port agrees)
8038667C 80385FFC PROVEN @80163018 (calibrated on P@801630F8) forms 0x80385FFC (chunk port agrees)
803866A8 80386028 PROVEN @801633A0 (calibrated on P@80163480) forms 0x80386028 (chunk port agrees)
803866F0 80386070 PROVEN @80162B2C (calibrated on P@80162C0C) forms 0x80386070 (chunk port agrees)
80386720 803860A0 PROVEN @80164908 (calibrated on P@801649E8) forms 0x803860A0 (chunk port agrees)
80386724 803860A4 PROVEN @80164890 (calibrated on P@80164970) forms 0x803860A4 (chunk port agrees)
80386725 803860A5 PROVEN @80164784 (calibrated on P@80164864) forms 0x803860A5 (chunk port agrees)
803866A0 80386020 PROVEN @8015E950 (calibrated on P@8015EA30) forms 0x80386020 (chunk port agrees)
80346D20 803466A0 PROVEN @8019E794 (calibrated on P@8019E874) forms 0x803466A0 (chunk port agrees)
80386848 803861C8 PROVEN @8019E0CC (calibrated on P@8019E1AC) forms 0x803861C8 (chunk port agrees)
80350864 803501E4 CHUNK-ONLY (no reference site confirmed it: 801B95EC forms 0x803501E0)
80350866 803501E6 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
80350872 803501F2 CHUNK-ONLY (no reference site confirmed it: 801B96CC forms 0x803501E0)
8035087C 803501FC CHUNK-ONLY (no reference site confirmed it: no-reference-site)
80350890 80350210 CHUNK-ONLY (no reference site confirmed it: 801BA9F4 forms 0x803501E0)
80386B38 803864B8 PROVEN @801B93D8 (calibrated on P@801B94B8) forms 0x803864B8 (chunk port agrees)
80386BA8 80386528 PROVEN @801BAC0C (calibrated on P@801BACEC) forms 0x80386528 (chunk port agrees)
80386BE4 80386564 PROVEN @801B9928 (calibrated on P@801B9A08) forms 0x80386564 (chunk port agrees)
80386B44 803864C4 PROVEN @801B98C4 (calibrated on P@801B99A4) forms 0x803864C4 (chunk port agrees)
80386BB8 80386538 PROVEN @801B902C (calibrated on P@801B910C) forms 0x80386538 (chunk port agrees)
80386BB4 80386534 PROVEN @801B9070 (calibrated on P@801B9150) forms 0x80386534 (chunk port agrees)
80386BA0 80386520 PROVEN @801BAA44 (calibrated on P@801BAB24) forms 0x80386520 (chunk port agrees)
80386BC0 80386540 PROVEN @801B992C (calibrated on P@801B9A0C) forms 0x80386540 (chunk port agrees)
80386F60 803868E0 PROVEN @80008E74 (calibrated on P@80008F18) forms 0x803868E0 (chunk port agrees)
80244DE0 80244D00 SECTION .ctors start (from the region section table)
80244EA0 80244DC0 SECTION .dtors start (from the region section table)
8088F400 8088EA6C SECTION .ctors start (from the region section table)
8088F704 8088ED70 SECTION .dtors start (from the region section table)
80343740 803430C0 CHUNK-ONLY (no reference site confirmed it: no-reference-site)
803437C0 80343140 CHUNK-ONLY (no reference site confirmed it: 8016B794 forms 0x80388048)
80343DC0 80343740 PROVEN @8016D0EC (calibrated on P@8016D1CC) forms 0x80343740 (chunk port agrees)
80343DE4 80343764 PROVEN @8016D0D4 (calibrated on P@8016D1B4) forms 0x80343764 (chunk port agrees)
803867B0 80386130 PROVEN @8016D100 (calibrated on P@8016D1E0) forms 0x80386130 (chunk port agrees)
803867B1 80386131 PROVEN @8016D104 (calibrated on P@8016D1E4) forms 0x80386131 (chunk port agrees)
803867C0 80386140 PROVEN @8016D0DC (calibrated on P@8016D1BC) forms 0x80386140 (chunk port agrees)
803867C4 80386144 PROVEN @8016D0D0 (calibrated on P@8016D1B0) forms 0x80386144 (chunk port agrees)
803867D0 80386150 PROVEN @8016ED60 (calibrated on P@8016EE40) forms 0x80386150 (chunk port agrees)
803477B0 80347130 PROVEN @801A9970 (calibrated on P@801A9A50) forms 0x80347130 (chunk port agrees)
80386918 80386298 PROVEN @801A97A0 (calibrated on P@801A9880) forms 0x80386298 (chunk port agrees)
8038691C 8038629C PROVEN @801A96D8 (calibrated on P@801A97B8) forms 0x8038629C (chunk port agrees)
80386920 803862A0 PROVEN @801A96D0 (calibrated on P@801A97B0) forms 0x803862A0 (chunk port agrees)
803869E0 80386360 PROVEN @801B2350 (calibrated on P@801B2430) forms 0x80386360 (chunk port agrees)
80386000 80385980 PROVEN @8000A540 (calibrated on P@8000A5E4) forms 0x80385980 (chunk port agrees)
80386F14 80386894 PROVEN @8023E470 (calibrated on P@8023E550) forms 0x80386894 (chunk port agrees)
80386F20 803868A0 PROVEN @8023E624 (calibrated on P@8023E704) forms 0x803868A0 (chunk port agrees)
80386F24 803868A4 CHUNK-ONLY (no reference site confirmed it: 8023E624 forms 0x803868A0)
80388B80 80388500 PROVEN @8021B448 (calibrated on P@8021B528) forms 0x80388500 (chunk port agrees)
80388BA0 80388520 PROVEN @8021B4D8 (calibrated on P@8021B5B8) forms 0x80388520 (chunk port agrees)
80388BC0 80388540 PROVEN @8021B548 (calibrated on P@8021B628) forms 0x80388540 (chunk port agrees)
80388BE0 80388560 PROVEN @8021B540 (calibrated on P@8021B620) forms 0x80388560 (chunk port agrees)
802574A0 80256E40 FIELD base @8021B230 -> 0x80256E20; field @8021B298 word 389F0020 (unique PAL instruction matches; register r31 preserved)
802574E0 80256E80 FIELD base @8021B230 -> 0x80256E20; field @8021B2A8 word 389F0060 (unique PAL instruction matches; register r31 preserved)
80257520 80256EC0 FIELD base @8021B230 -> 0x80256E20; field @8021B434 word 389F00A0 (unique PAL instruction matches; register r31 preserved)
80257540 80256EE0 FIELD base @8021B230 -> 0x80256E20; field @8021B4C4 word 389F00C0 (unique PAL instruction matches; register r31 preserved)
802A3EE8 802A3888 CALIBRATED @8023E494 (calibrated on P@8023E574) forms 0x802A3888 (chunk port has no mapping)
802A3EF4 802A3894 RECORD table @8023E494 -> 0x802A3888; stride @8023E48C mulli r4,r0,12; both 12-byte records equal PAL
802A3F0C 802A38AC CALIBRATED @8023D250 (calibrated on P@8023D330) forms 0x802A38AC (chunk port has no mapping)
808B4C20 808B3D80 CALIBRATED @805651D0 (calibrated on P@80565850) forms 0x808B3D80 (chunk port has no mapping)
809C1830 809C0890 PROVEN @80564A9C (calibrated on P@8056511C) forms 0x809C0890 (chunk port agrees)
809C183C 809C089C FIELD base @80564BF0 -> 0x809C0890; field @80564C08 word 3865000C (unique PAL instruction matches; register r5 preserved)
809C2898 809C18F8 CALIBRATED @807164A0 (calibrated on P@80716E34) forms 0x809C18F8 (chunk port has no mapping)
8027F820 8027F1C0 CALIBRATED @80126920 (calibrated on P@80126A00) forms 0x8027F1C0 (chunk port has no mapping)
802F81A0 802F7B20 PROVEN @80126910 (calibrated on P@801269F0) forms 0x802F7B20 (chunk port agrees)
802F8200 802F7B80 PROVEN @801268E4 (calibrated on P@801269C4) forms 0x802F7B80 (chunk port agrees)
80386608 80385F88 PROVEN @8015D390 (calibrated on P@8015D470) forms 0x80385F88 (chunk port agrees)
80386610 80385F90 PROVEN @8015D4D0 (calibrated on P@8015D5B0) forms 0x80385F90 (chunk port agrees)
80386614 80385F94 PROVEN @8015D4CC (calibrated on P@8015D5AC) forms 0x80385F94 (chunk port agrees)
80386618 80385F98 PROVEN @8015D3F8 (calibrated on P@8015D4D8) forms 0x80385F98 (chunk port agrees)
8038661C 80385F9C PROVEN @8015D400 (calibrated on P@8015D4E0) forms 0x80385F9C (chunk port agrees)
80386620 80385FA0 PROVEN @8015D404 (calibrated on P@8015D4E4) forms 0x80385FA0 (chunk port agrees)
80386624 80385FA4 PROVEN @8015D3FC (calibrated on P@8015D4DC) forms 0x80385FA4 (chunk port agrees)
80388588 80387F08 PROVEN @8012B7DC (calibrated on P@8012B8BC) forms 0x80387F08 (chunk port agrees)
8038858C 80387F0C PROVEN @8012B7EC (calibrated on P@8012B8CC) forms 0x80387F0C (chunk port agrees)
80386448 80385DC8 PROVEN @80123FEC (calibrated on P@801240CC) forms 0x80385DC8 (chunk port agrees)
8038644C 80385DCC PROVEN @80124198 (calibrated on P@80124278) forms 0x80385DCC (chunk port agrees)
8038647C 80385DFC PROVEN @8012411C (calibrated on P@801241FC) forms 0x80385DFC (chunk port agrees)
80386480 80385E00 PROVEN @80123EC0 (calibrated on P@80123FA0) forms 0x80385E00 (chunk port agrees)
80347130 80346AB0 CHUNK-ONLY (no reference site confirmed it: 801A9E68 forms 0x80346A70)
80347498 80346E18 PROVEN @801A9560 (calibrated on P@801A9640) forms 0x80346E18 (chunk port agrees)
803478B0 80347230 PROVEN @801A9C60 (calibrated on P@801A9D40) forms 0x80347230 (chunk port agrees)
80385AA8 80385428 PROVEN @801A9EA0 (calibrated on P@801A9F80) forms 0x80385428 (chunk port agrees)
80385AE0 80385460 PROVEN @801A94EC (calibrated on P@801A95CC) forms 0x80385460 (chunk port agrees)
803868F8 80386278 PROVEN @801A6518 (calibrated on P@801A65F8) forms 0x80386278 (chunk port agrees)
80386298 80385C18 PROVEN @800BC390 (calibrated on P@800BC470) forms 0x80385C18 (chunk port agrees)
80336278 80335BF8 PROVEN @8013A220 (calibrated on P@8013A300) forms 0x80335BF8 (chunk port agrees)
809BEBA0 809BDC00 FIELD base @80550654 -> 0x809BDB60; field @8055067C word 801E00A0 (unique PAL instruction matches; register r30 preserved)
809BEBA4 809BDC04 FIELD base @80550654 -> 0x809BDB60; field @80550894 word 989E00A4 (unique PAL instruction matches; register r30 preserved)
809BEBA5 809BDC05 FIELD base @80550654 -> 0x809BDB60; field @8055088C word 989E00A5 (unique PAL instruction matches; register r30 preserved)
809BEBA6 809BDC06 FIELD base @80550654 -> 0x809BDB60; field @80550898 word 989E00A6 (unique PAL instruction matches; register r30 preserved)
809BEBA7 809BDC07 FIELD base @80550654 -> 0x809BDB60; field @8055079C word 981E00A7 (unique PAL instruction matches; register r30 preserved)
809BECF0 809BDD50 CALIBRATED @80550620 (calibrated on P@80550CA0) forms 0x809BDD50 (chunk port has no mapping)
8088FDB8 8088F418 CALIBRATED @805296A8 (calibrated on P@80529D28) forms 0x8088F418 (chunk port has no mapping)
80386934 803862B4 PROVEN @801AB7D0 (calibrated on P@801AB8B0) forms 0x803862B4 (chunk port agrees)
80386938 803862B8 PROVEN @801AB7C8 (calibrated on P@801AB8A8) forms 0x803862B8 (chunk port agrees)
8038693C 803862BC PROVEN @801AB7F0 (calibrated on P@801AB8D0) forms 0x803862BC (chunk port agrees)
80386940 803862C0 PROVEN @801AB6C0 (calibrated on P@801AB7A0) forms 0x803862C0 (chunk port agrees)
80386948 803862C8 PROVEN @801AB6A0 (calibrated on P@801AB780) forms 0x803862C8 (chunk port agrees)
80386770 803860F0 PROVEN @80169B04 (calibrated on P@80169BE4) forms 0x803860F0 (chunk port agrees)
80386774 803860F4 PROVEN @80169B34 (calibrated on P@80169C14) forms 0x803860F4 (chunk port agrees)
8038677C 803860FC PROVEN @80169B14 (calibrated on P@80169BF4) forms 0x803860FC (chunk port agrees)
80386780 80386100 PROVEN @80169B1C (calibrated on P@80169BFC) forms 0x80386100 (chunk port agrees)
80386784 80386104 PROVEN @80169BEC (calibrated on P@80169CCC) forms 0x80386104 (chunk port agrees)
803867EC 8038616C PROVEN @80192ED0 (calibrated on P@80192FB0) forms 0x8038616C (chunk port agrees)
803867F0 80386170 PROVEN @80192ECC (calibrated on P@80192FAC) forms 0x80386170 (chunk port agrees)
80385920 803852A0 PROVEN @80169B7C (calibrated on P@80169C5C) forms 0x803852A0 (chunk port agrees)
80385928 803852A8 PROVEN @80169B64 (calibrated on P@80169C44) forms 0x803852A8 (chunk port agrees)
803868A0 80386220 PROVEN @801A0500 (calibrated on P@801A05E0) forms 0x80386220 (chunk port agrees)
80386528 80385EA8 PROVEN @8012686C (calibrated on P@8012694C) forms 0x80385EA8 (chunk port agrees)
80386524 80385EA4 PROVEN @801267C4 (calibrated on P@801268A4) forms 0x80385EA4 (chunk port agrees)
80385800 80385180 PROVEN @80126934 (calibrated on P@80126A14) forms 0x80385180 (chunk port agrees)
80385802 80385182 PROVEN @8012693C (calibrated on P@80126A1C) forms 0x80385182 (chunk port agrees)
80385804 80385184 PROVEN @8012692C (calibrated on P@80126A0C) forms 0x80385184 (chunk port agrees)
80388DD0 80388750 PROVEN @8022D694 (calibrated on P@8022D774) forms 0x80388750 (chunk port agrees)
80388DE0 80388760 PROVEN @8022DDC8 (calibrated on P@8022DEA8) forms 0x80388760 (chunk port agrees)
80388DF4 80388774 PROVEN @8022E120 (calibrated on P@8022E200) forms 0x80388774 (chunk port agrees)
8029CEB0 8029C850 CALIBRATED @801B22E0 (calibrated on P@801B23C0) forms 0x8029C850 (chunk port has no mapping)
8029CEF8 8029C898 CALIBRATED @801B2408 (calibrated on P@801B24E8) forms 0x8029C898 (chunk port has no mapping)
+72
View File
@@ -0,0 +1,72 @@
schema_version: 1
workspace_root: ../..
# Mario Kart Wii NTSC-J (RMCJ01). The same translation surface as projects/mkwii/recomp.yml, with
# the facts that differ per region: the small-data bases the DOL's __init_registers installs, the
# StaticR.rel load address and the clean-input digests. The function map is the PAL map ported
# through the community region table and validated against these binaries.
project:
id: mkwii-rmcj01
display_name: Mario Kart Wii NTSC-J
game_id: RMCJ01
region: J
base_manifest_format: mkwii-recomp-base-manifest
base_manifest_stem: mkwii_base
memory:
base: 0x80000000
size: 0x01A00000
sda_base: 0x8038C580
sda2_base: 0x8038E920
inputs:
dol:
path: Assets/main.dol
sha256: 1b9621ef7c5d97dada103e50e5389730e67f3c2545dda592edd4b5843655af91
rel:
path: Assets/StaticR.rel
load_address: 0x8050FC60
sha256: 88539012d357a1420724e51dc7e351192ce696da4b0045994895518a3fad6fae
translation:
entry_points:
- 0x800060A4
function_map:
path: projects/mkwii-ntsc-j/MAP.txt
allow_unsupported_instructions: false
runtime:
native_registration_root: runtime/src
native_abi_directories:
- runtime/src/hle/gx
output:
root: generated
functions: functions
runtime_config: RuntimeConfig.h
data_initializer: data_sections_init.cpp
base_manifest: ../build/base/mkwii_base_manifest.json
# Code.pul carries a separate chunk for this region. The regional link base determines
# the branch bytes checked at startup; execution is remapped to module_guest_base.
profiles:
retro-rewind:
enabled: true
requires_game_id: RMCJ01
requires_dol_sha256: 1b9621ef7c5d97dada103e50e5389730e67f3c2545dda592edd4b5843655af91
code_pul: PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul
mod_root: PulsarPacks/completed/RetroRewind/RetroRewind6
region: J
module_guest_base: 0x81800000
module_link_base: 0x80398C60
output: build/mods/retro_rewind_full_cpp
enable_retro_wfc: true
retro_wfc_payload: https://rwfc.net/api/wfc/payload?g=RMCJD00
retro_wfc_legacy_bootstrap_hook: 0x800ED608
riivolution:
xml: xml/RetroRewind6.xml
options:
- section: Retro Rewind
option: Pack
choice: 1
+591
View File
@@ -0,0 +1,591 @@
{
"source_region": "P",
"target_region": "J",
"attribution": "chunk table and section layouts vendored from the mkw-sp project (https://github.com/mkw-sp/mkw-sp, port.py)",
"license": "MIT. Copyright (c) 2021-2023 Pablo Stebler. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
"port_rule": "if src_start <= a < src_end then a - src_start + dst_start",
"address_format": "hex strings",
"chunks": [
[
"0x80004000",
"0x80008024",
"0x80004000"
],
[
"0x800080E8",
"0x8000ADC0",
"0x80008044"
],
[
"0x8000AF08",
"0x80021BAC",
"0x8000AE2C"
],
[
"0x80021BAC",
"0x80244DE0",
"0x80021ACC"
],
[
"0x802A4080",
"0x8038917C",
"0x802A3A00"
],
[
"0x805103B4",
"0x805CC0A8",
"0x8050FD34"
],
[
"0x805CC1B4",
"0x805FA33C",
"0x805CBA90"
],
[
"0x805FA344",
"0x805FF6E8",
"0x805F9C20"
],
[
"0x805FFD70",
"0x806003E8",
"0x805FF528"
],
[
"0x80600C78",
"0x80620CB4",
"0x806003EC"
],
[
"0x80620D7C",
"0x80637A24",
"0x806204C8"
],
[
"0x80637A80",
"0x8063BCF8",
"0x8063716C"
],
[
"0x8063BE40",
"0x8088F400",
"0x8063B4AC"
],
[
"0x808913C0",
"0x808913C4",
"0x80890A10"
],
[
"0x80895238",
"0x808952B8",
"0x80894888"
],
[
"0x808AD3C4",
"0x808AD3C8",
"0x808AC524"
],
[
"0x808B3984",
"0x808B3988",
"0x808B2AE4"
],
[
"0x808B5B1C",
"0x808B5B20",
"0x808B4C7C"
],
[
"0x808B5C78",
"0x808B5C7C",
"0x808B4DD8"
],
[
"0x808BB034",
"0x808BB098",
"0x808BA184"
],
[
"0x808CB550",
"0x808CB554",
"0x808CA6A0"
],
[
"0x808D3698",
"0x808D369C",
"0x808D27E8"
],
[
"0x808D36CC",
"0x808D36D0",
"0x808D281C"
],
[
"0x808D36D4",
"0x808D36D8",
"0x808D2824"
],
[
"0x808D3744",
"0x808D3748",
"0x808D2894"
],
[
"0x808D374C",
"0x808D3750",
"0x808D289C"
],
[
"0x808D3E14",
"0x808D3E18",
"0x808D2F64"
],
[
"0x808DA070",
"0x808DA080",
"0x808D91C0"
],
[
"0x808DA318",
"0x808DA368",
"0x808D9468"
],
[
"0x809BD6E8",
"0x809BD74C",
"0x809BC748"
],
[
"0x809C1830",
"0x809C1834",
"0x809C0890"
],
[
"0x809C1874",
"0x809C1878",
"0x809C08D4"
],
[
"0x809C18F8",
"0x809C18FC",
"0x809C0958"
],
[
"0x809C1988",
"0x809C198C",
"0x809C09E8"
],
[
"0x809C19A0",
"0x809C19BC",
"0x809C0A00"
],
[
"0x809C1E38",
"0x809C1E3C",
"0x809C0E98"
],
[
"0x809C21D0",
"0x809C21D4",
"0x809C1230"
],
[
"0x809C21D8",
"0x809C21DC",
"0x809C1238"
],
[
"0x809C2328",
"0x809C232C",
"0x809C1388"
],
[
"0x809C27F0",
"0x809C27FC",
"0x809C1850"
],
[
"0x809C282C",
"0x809C2854",
"0x809C188C"
],
[
"0x809C28B8",
"0x809C28BC",
"0x809C1918"
],
[
"0x809C2BE8",
"0x809C2BEC",
"0x809C1C48"
],
[
"0x809C2EF0",
"0x809C2EF4",
"0x809C1F50"
],
[
"0x809C2F38",
"0x809C2F40",
"0x809C1F98"
],
[
"0x809C2F44",
"0x809C2F48",
"0x809C1FA4"
],
[
"0x809C3618",
"0x809C361C",
"0x809C2678"
],
[
"0x809C38B8",
"0x809C38BC",
"0x809C2918"
],
[
"0x809C4330",
"0x809C4334",
"0x809C3390"
],
[
"0x809C4680",
"0x809C4684",
"0x809C36E0"
],
[
"0x809C4740",
"0x809C474C",
"0x809C37A0"
],
[
"0x809C496C",
"0x809C4970",
"0x809C39CC"
]
],
"rel_load_address": {
"P": "0x805102E0",
"E": "0x8050BF60",
"J": "0x8050FC60"
},
"dol_sections": {
"P": [
{
"name": "init",
"start": "0x80004000",
"end": "0x80006460"
},
{
"name": "extab",
"start": "0x80006460",
"end": "0x80006A20"
},
{
"name": "extabindex",
"start": "0x80006A20",
"end": "0x800072C0"
},
{
"name": "text",
"start": "0x800072C0",
"end": "0x80244DE0"
},
{
"name": "ctors",
"start": "0x80244DE0",
"end": "0x80244EA0"
},
{
"name": "dtors",
"start": "0x80244EA0",
"end": "0x80244EC0"
},
{
"name": "rodata",
"start": "0x80244EC0",
"end": "0x80258580"
},
{
"name": "data",
"start": "0x80258580",
"end": "0x802A4040"
},
{
"name": "bss",
"start": "0x802A4080",
"end": "0x80384C00"
},
{
"name": "sdata",
"start": "0x80384C00",
"end": "0x80385FC0"
},
{
"name": "sbss",
"start": "0x80385FC0",
"end": "0x80386FA0"
},
{
"name": "sdata2",
"start": "0x80386FA0",
"end": "0x80389140"
},
{
"name": "sbss2",
"start": "0x80389140",
"end": "0x8038917C"
}
],
"E": [
{
"name": "init",
"start": "0x80004000",
"end": "0x80006460"
},
{
"name": "extab",
"start": "0x80006460",
"end": "0x80006A20"
},
{
"name": "extabindex",
"start": "0x80006A20",
"end": "0x800072C0"
},
{
"name": "text",
"start": "0x800072C0",
"end": "0x80244D40"
},
{
"name": "ctors",
"start": "0x80244D40",
"end": "0x80244E00"
},
{
"name": "dtors",
"start": "0x80244E00",
"end": "0x80244E20"
},
{
"name": "rodata",
"start": "0x80244E40",
"end": "0x80258260"
},
{
"name": "data",
"start": "0x80258260",
"end": "0x8029FD00"
},
{
"name": "bss",
"start": "0x8029FD00",
"end": "0x80380880"
},
{
"name": "sdata",
"start": "0x80380880",
"end": "0x80381C40"
},
{
"name": "sbss",
"start": "0x80381C40",
"end": "0x80382C20"
},
{
"name": "sdata2",
"start": "0x80382C20",
"end": "0x80384DC0"
},
{
"name": "sbss2",
"start": "0x80384DC0",
"end": "0x80384DFC"
}
],
"J": [
{
"name": "init",
"start": "0x80004000",
"end": "0x80006460"
},
{
"name": "extab",
"start": "0x80006460",
"end": "0x80006A20"
},
{
"name": "extabindex",
"start": "0x80006A20",
"end": "0x800072C0"
},
{
"name": "text",
"start": "0x800072C0",
"end": "0x80244D00"
},
{
"name": "ctors",
"start": "0x80244D00",
"end": "0x80244DC0"
},
{
"name": "dtors",
"start": "0x80244DC0",
"end": "0x80244DE0"
},
{
"name": "rodata",
"start": "0x80244E00",
"end": "0x80257F20"
},
{
"name": "data",
"start": "0x80257F20",
"end": "0x802A39E0"
},
{
"name": "bss",
"start": "0x802A3A00",
"end": "0x80384580"
},
{
"name": "sdata",
"start": "0x80384580",
"end": "0x80385940"
},
{
"name": "sbss",
"start": "0x80385940",
"end": "0x80386920"
},
{
"name": "sdata2",
"start": "0x80386920",
"end": "0x80388AC0"
},
{
"name": "sbss2",
"start": "0x80388AC0",
"end": "0x80388AFC"
}
]
},
"rel_sections": {
"P": [
{
"name": "text",
"start": "0x805103B4",
"end": "0x8088F400"
},
{
"name": "ctors",
"start": "0x8088F400",
"end": "0x8088F704"
},
{
"name": "dtors",
"start": "0x8088F704",
"end": "0x8088F710"
},
{
"name": "rodata",
"start": "0x8088F720",
"end": "0x808B2BD0"
},
{
"name": "data",
"start": "0x808B2BD0",
"end": "0x808DD3D4"
},
{
"name": "bss",
"start": "0x809BD6E0",
"end": "0x809C4F90"
}
],
"E": [
{
"name": "text",
"start": "0x8050C034",
"end": "0x8088AFD0"
},
{
"name": "ctors",
"start": "0x8088AFD0",
"end": "0x8088B2D4"
},
{
"name": "dtors",
"start": "0x8088B2D4",
"end": "0x8088B2E0"
},
{
"name": "rodata",
"start": "0x8088B2E0",
"end": "0x808AE520"
},
{
"name": "data",
"start": "0x808AE520",
"end": "0x808D8C7C"
},
{
"name": "bss",
"start": "0x809B8F20",
"end": "0x809C07D0"
}
],
"J": [
{
"name": "text",
"start": "0x8050FD34",
"end": "0x8088EA6C"
},
{
"name": "ctors",
"start": "0x8088EA6C",
"end": "0x8088ED70"
},
{
"name": "dtors",
"start": "0x8088ED70",
"end": "0x8088ED7C"
},
{
"name": "rodata",
"start": "0x8088ED80",
"end": "0x808B1D30"
},
{
"name": "data",
"start": "0x808B1D30",
"end": "0x808DC524"
},
{
"name": "bss",
"start": "0x809BC740",
"end": "0x809C3FF0"
}
]
},
"validated_against": {
"dol": {
"path": "main.dol",
"sha256": "1b9621ef7c5d97dada103e50e5389730e67f3c2545dda592edd4b5843655af91",
"entry": "0x800060A4"
},
"rel": {
"path": "StaticR.rel",
"sha256": "88539012d357a1420724e51dc7e351192ce696da4b0045994895518a3fad6fae",
"module_id": 1,
"prolog": "0x80554C9C",
"epilog": "0x80554CE8",
"unresolved": "0x80554D30"
}
}
}
+29412
View File
File diff suppressed because it is too large Load Diff
+243
View File
@@ -0,0 +1,243 @@
# NTSC-U (RMCE01) addresses for the PAL (RMCP01) data globals named by the WiiCompiled runtime.
# Format: <PAL hex> <NTSC hex> <evidence: PALfuncname@ntscaddr instr ...>
# Every cited instruction was disassembled from the RMCE01 main.dol (or StaticR.rel) at the
# stated NTSC-U address; the function names are the PAL MAP.txt names of the function that the
# validated PAL->NTSC-U port table (CHUNKS_E) maps onto that NTSC-U code.
# Small-data bases: NTSC-U r13=0x80388880 r2=0x8038AC20; PAL r13=0x8038CC00 r2=0x8038EFA0.
# Deltas observed: all .data/.bss objects, .sdata2 and every .sbss global up to (at least) PAL 0x80386F27
# are PAL-0x4380; .sbss from PAL 0x80386F60 onward is PAL-0x4388 (r13 offset 8 less). See notes at end.
# ---------------------------------------------------------------- GX
# Display-list fifo object (GXBeginDisplayList/GXEndDisplayList): NTSC base 0x8033FD10 = PAL 0x80344090 - 0x4380
80344090 8033FD10 GX::BeginDisplayList@80172d78 lis r31,0x8034; @80172d7c addi r31,r31,-0x2f0 (=0x8033FD10 dlFifo); @80172d8c addi r29,r31,0; @80172dc4 stw r27,0x0(r29) (dlFifo.base=list); @80172df0 addi r3,r31,0 -> GXSetCPUFifo(&dlFifo); GX::EndDisplayList@80172e2c addi r30,r30,-0x2f0; @80172e34 addi r3,r30,0 -> GXGetCPUFifo(&dlFifo)
80344094 8033FD14 GX::BeginDisplayList@80172dcc stw r4,0x4(r29) (dlFifo.end = list+size-4, r29=0x8033FD10; r4 from @80172db8 add r4,r27,r28 / @80172dc0 addi r4,r4,-4)
80344098 8033FD18 GX::BeginDisplayList@80172dd0 stw r28,0x8(r29) (dlFifo.size = size)
803440A4 8033FD24 GX::BeginDisplayList@80172dd8 stw r27,0x14(r29) (dlFifo.rdPtr = list)
803440A8 8033FD28 GX::BeginDisplayList@80172ddc stw r27,0x18(r29) (dlFifo.wrPtr = list)
803440AC 8033FD2C GX::BeginDisplayList@80172dd4 stw r3,0x1c(r29) (dlFifo.count = 0, li r3,0 @80172dbc)
80344110 8033FD90 GX::BeginDisplayList@80172dac addi r3,r31,0x80 -> memcpy(0x8033FD90, gx, 0x600) (li r5,0x600 @80172db0, saved GXData copy); GX::EndDisplayList@80172e6c addi r4,r30,0x80 (memcpy src)
80344710 80340390 GX::BeginDisplayList@80172de8 addi r3,r31,0x680 -> GXGetCPUFifo(0x80340390) (saved CPU fifo); GX::EndDisplayList@80172e48 addi r3,r30,0x680 -> GXSetCPUFifo(0x80340390)
803867D8 80382458 GX::DrawDone@8016ea84 stb r31,-0x6428(r13); GX::DrawDone@8016eaa4 lbz r0,-0x6428(r13); GX::FinishInterruptHandler@8016ed20 stb r3,-0x6428(r13) # r13-0x6428 in both regions (PAL 0x8038CC00-0x6428=0x803867D8)
803886C8 80384348 GX::Init@8016b7d4 lwz r31,-0x68d8(r2); GX::SetCPUFifo@8016c978 lwz r5,-0x68d8(r2); GX::DrawDone@8016ea3c lwz r4,-0x68d8(r2); GX::BeginDisplayList@80172d74 lwz r30,-0x68d8(r2) # .sdata2 `gx` (__GXData ptr), r2-0x68d8 in both regions (PAL 0x8038EFA0-0x68D8=0x803886C8)
# ---------------------------------------------------------------- DVD
80343230 8033EEB0 __DVDClearWaitingQueue@80163520 lis r6,0x8034; @80163524 addi r6,r6,-0x1150 (=0x8033EEB0); @8016352c stw r6,0x0(r6); @80163538 stw r6,0x4(r6) (4 self-linked queues of 8 bytes: +0..+0x1c); __DVDPushWaitingQueue@80163580 addi r4,r4,-0x1150; __DVDPopWaitingQueue@801635e0 addi r4,r4,-0x1150
803434E0 8033F160 DVD::LowInit@801648e0 lis r4,0x8034; @801648e4 stwu r5,-0xea0(r4) (r4=0x8033F160=ctx[0], ctx[0]+0=0); @801648e8 lis r3,0xfeec; @801648f8 addi r3,r3,-0x2513 (=0xFEEBDAED); @80164900 stw r3,0xc(r4) (ctx[0]+0xC=magic); @80164904 stw r5,0x10(r4) (ctx[0]+0x10=0); @80164918 stw r0,0x30(r4) (ctx[1]+0x10=1); @8016492c stw r6,0x50(r4) (ctx[2]+0x10=2); @80164944 stw r6,0x70(r4) (ctx[3]+0x10=3)
80386664 803822E4 DVD::Pause@80162b74 stw r4,-0x659c(r13); DVD::Resume@80162bb4 stw r4,-0x659c(r13); DVD::CancelAll@80163070 stw r4,-0x659c(r13); DVD::InquiryAsync@801629b4 lwz r0,-0x659c(r13) # r13-0x659c both regions
80386668 803822E8 DVD::Resume@80162bb8 lwz r0,-0x6598(r13); DVD::Resume@80162bc4 stw r4,-0x6598(r13); DVD::Pause@80162b80 stw r4,-0x6598(r13); DVD::GetDriveStatus@80162ae0 lwz r0,-0x6598(r13)
80386670 803822F0 __DVDPrepareReset@801633f0 lwz r0,-0x6590(r13); __DVDPrepareResetAsync@801632c0 lwz r0,-0x6590(r13)
8038667C 803822FC DVD::CancelAll@80163058 stw r0,-0x6584(r13); DVD::CancelAll@801630d4 stw r0,-0x6584(r13); DVD::CancelAll@80163128 lwz r0,-0x6584(r13)
803866A8 80382328 __DVDPrepareReset@801633e0 stw r31,-0x6558(r13); __DVDPrepareReset@80163488 stw r0,-0x6558(r13); __DVDPrepareReset@801634cc lwz r0,-0x6558(r13)
803866F0 80382370 DVD::Pause@80162b6c lwz r0,-0x6510(r13); DVD::CancelAll@80163068 lwz r0,-0x6510(r13); __DVDPrepareReset@8016340c lwz r3,-0x6510(r13); DVD::ReadAbsAsyncPrio@801628c8 lwz r0,-0x6510(r13) # `executing` command block
80386720 803823A0 DVD::LowInit@80164948 stw r5,-0x64e0(r13) (r5=0, current context index, right after the 4-context init); DVD::LowReadDiskID@80164a3c lwz r9,-0x64e0(r13)
80386724 803823A4 DVD::LowInit@801648d0 lbz r0,-0x64dc(r13) (skip context init if already set); DVD::LowInit@8016494c stb r0,-0x64dc(r13) (=1)
80386725 803823A5 DVD::LowInit@801647c4 lbz r0,-0x64db(r13); DVD::LowInit@801647d4 stb r0,-0x64db(r13) (=1); DVD::LowFinalize@80164794 stb r0,-0x64db(r13) (=0)
803866A0 80382320 DVD::Init@8015e990 lwz r0,-0x6560(r13) (return if already initialized); DVD::Init@8015e9a8 stw r0,-0x6560(r13) (=1)
# ---------------------------------------------------------------- NAND
80346D20 803429A0 nandGetHomeDir@8019e7d4 lis r3,0x8034; @8019e7d8 addi r3,r3,0x29a0 (returns &NANDHomeDir=0x803429A0); NAND::Init@8019e178 lis r5,0x8034; @8019e184 addi r5,r5,0x29a0 (ESP_GetDataDir out buffer); NAND::Init@8019e198 lis r4,0x8034; @8019e1a0 addi r4,r4,0x29a0
80386848 803824C8 NAND::Init@8019e10c lwz r0,-0x63b8(r13) (cmpwi 1 / cmpwi 2 = init state); NAND::Init@8019e13c stw r0,-0x63b8(r13) (=1); NAND::Init@8019e1d0 stw r0,-0x63b8(r13) (=2 on success); NAND::GetCurrentDir@8019e308 lwz r0,-0x63b8(r13)
# ---------------------------------------------------------------- VI
# VI HorVer struct (.bss): NTSC 0x8034C4E0 = PAL 0x80350860 - 0x4380; fields +4 DispSizeX, +6 DispSizeY, +0x12 FBSizeX, +0x1C PanSizeY, +0x30 bufAddr
80350864 8034C4E4 VIInit@801b962c lis r30,0x8035; @801b9630 addi r31,r30,-0x3b20 (=0x8034C4E0 HorVer); @801b965c li r7,640; @801b969c sth r7,0x4(r31) (HorVer.DispSizeX=640)
80350866 8034C4E6 VIInit@801b96b0 sth r0,0x6(r31) (HorVer.DispSizeY = timing->acv*2; r31=0x8034C4E0)
80350872 8034C4F2 VIInit@801b970c lis r4,0x8035; @801b9714 addi r4,r4,-0x3b20 (=0x8034C4E0); @801b9718 li r9,640; @801b97b4 sth r9,0x12(r4) (HorVer.FBSizeX=640)
8035087C 8034C4FC VIInit@801b9804 sth r5,0x1c(r4) (HorVer.PanSizeY; r4=0x8034C4E0)
80350890 8034C510 VISetNextFrameBuffer@801baa34 lis r7,0x8035; @801baa3c addi r7,r7,-0x3b20 (=0x8034C4E0); @801baa44 stw r30,0x30(r7) (HorVer.bufAddr=fb); @801baa4c addi r4,r7,0x34; @801baa50 addi r5,r7,0x38 (setFbbRegs(&HorVer,&tfbb,&bfbb,...))
80386B38 803827B8 VIInit@801b9418 lwz r0,-0x60c8(r13) (already-initialised check); VIInit@801b9434 stw r0,-0x60c8(r13) # r13-0x60c8 both regions
80386BA8 80382828 VIGetTvFormat@801bac4c lwz r31,-0x6058(r13); VIInit@801b9690 stw r8,-0x6058(r13) (CurrTvMode=HorVer.tv); __VIRetraceHandler@801b8b10 stw r3,-0x6058(r13)
80386BE4 80382864 VIWaitForRetrace@801b9968 lwz r30,-0x601c(r13); VIWaitForRetrace@801b9974 lwz r0,-0x601c(r13); VIGetRetraceCount@801bab04 lwz r3,-0x601c(r13); __VIRetraceHandler@801b8974 stw r0,-0x601c(r13) (retraceCount++)
80386B44 803827C4 VIInit@801b9904 stw r0,-0x60bc(r13) (=1, VIInit tail); __VIRetraceHandler@801b8da4 lwz r0,-0x60bc(r13); __VIRetraceHandler@801b8dcc stw r0,-0x60bc(r13)
80386BB8 80382838 VISetPreRetraceCallback@801b906c lwz r31,-0x6048(r13); VISetPreRetraceCallback@801b9074 stw r30,-0x6048(r13); __VIRetraceHandler@801b8984 lwz r12,-0x6048(r13); VIInit@801b984c stw r29,-0x6048(r13)
80386BB4 80382834 VISetPostRetraceCallback@801b90b0 lwz r31,-0x604c(r13); VISetPostRetraceCallback@801b90b8 stw r30,-0x604c(r13); __VIRetraceHandler@801b8d7c lwz r12,-0x604c(r13); VIInit@801b9850 stw r29,-0x604c(r13)
80386BA0 80382820 VIGetNextFrameBuffer@801baa84 lwz r3,-0x6060(r13); __VIRetraceHandler@801b8b14 stw r0,-0x6060(r13)
80386BC0 80382840 VIWaitForRetrace@801b996c addi r3,r13,-0x6040 -> OSSleepThread(&retraceQueue); VIInit@801b97f8 addi r3,r13,-0x6040 -> OSInitThreadQueue; __VIRetraceHandler@801b8d8c addi r3,r13,-0x6040 -> OSWakeupThread
# ---------------------------------------------------------------- EGG
# NOTE: past the .sbss gap: PAL r13-0x5ca0, NTSC r13-0x5ca8 (delta 0x4388)
80386F60 80382BD8 RKSystem::Main@80008ec0 lis r31,0x802a; @80008ec4 addi r31,r31,-0x300 (=0x8029FD00 system instance, .bss start); @80008ed8 stw r31,-0x5ca8(r13) (BaseSystem::sSystem = instance, then virtual call via vtable+0x38); EGG::AsyncDisplay::beginFrame@8020fe74 lwz r3,-0x5ca8(r13) (sSystem->vtable[2] call); EGG::Display::beginFrame@80219e50 lwz r3,-0x5ca8(r13); EGG::SceneManager::createScene@8023ad94 lwz r4,-0x5ca8(r13); EGG::DrawDoneCallback@8020fc48 lwz r3,-0x5ca8(r13); EGG::StateGX::InitFrame@80240054 lwz r3,-0x5ca8(r13)
# ---------------------------------------------------------------- Range constants (section tables)
# NTSC-U DOL header: section 9 = .ctors file off 0x240E40 addr 0x80244D40 size 0xC0 (end 0x80244E00)
# section 10 = .dtors file off 0x240F00 addr 0x80244E00 size 0x20 (end 0x80244E20)
# .ctors contents: 80244D40 __init_cpp_exceptions(0x80020684), 80244D44 __sinit_\RKSystem_cpp(0x8000998C), ... 80244DE8 __sinit__eggVector_cpp(0x80242D2C), 80244DEC..80244DFC zero
# .dtors contents: 80244E00 __destroy__global_chain(0x800207F0), 80244E04 __fini_cpp_exceptions(0x800206C0), rest zero
80244DE0 80244D40 DOL section table: .ctors start (sec9 addr), delta 0xA0 like the surrounding text
80244EA0 80244E00 DOL section table: .ctors end = .dtors start (sec9 addr+size = sec10 addr)
# NTSC-U StaticR.rel section table (load base 0x8050BF60): section 2 = .ctors file off 0x37F070 size 0x304 -> 0x8088AFD0..0x8088B2D4 (193 words: 192 R_PPC_ADDR32 self-relocations + null terminator);
# section 3 = .dtors file off 0x37F374 size 0xC -> 0x8088B2D4..0x8088B2E0 (2 ADDR32 relocations); section 4 (.rodata) starts 0x8088B2E0
8088F400 8088AFD0 REL section table: .ctors start (sec2 off 0x37F070 + base 0x8050BF60)
8088F704 8088B2D4 REL section table: .ctors end (0x8088AFD0 + 0x304) = .dtors (sec3) start
# ================================================================ Batch 2
# ---------------------------------------------------------------- GX (DOL .bss/.sbss) - GXInit / __GXFifoInit / __GXPEInit objects
80343740 8033F3C0 GX::Init@8016b824 lis r28,0x8034; @8016b830 addi r3,r28,-0xc40 (=0x8033F3C0 FifoObj -> GXInitFifoBase); @8016b838 addi r3,r28,-0xc40 (GXSetCPUFifo); @8016b840 addi r3,r28,-0xc40 (GXSetGPFifo); @8016bc9c addi r3,r3,-0xc40 (return &FifoObj)
803437C0 8033F440 .sdata2 cell: the `gx` pointer at NTSC 0x80384348 (r2-0x68d8, see 803886C8 above) contains 0x8033F440 in the DOL image (sec14 file data) = &gxData; PAL cell 0x803886C8 holds 0x803437C0. GX::Init@8016b7d4 (ctx) lwz r31,-0x68d8(r2) loads that cell.
80343DC0 8033FA40 __GX::FifoInit@8016d12c lis r3,0x8034; @8016d134 addi r3,r3,-0x5c0 (=0x8033FA40); @8016d138 li r5,0x24 -> memset(&GPFifo,0,0x24)
80343DE4 8033FA64 __GX::FifoInit@8016d114 lis r3,0x8034; @8016d120 addi r3,r3,-0x59c (=0x8033FA64); @8016d124 li r5,0x24 -> memset(&CPUFifo,0,0x24); GX::SetCPUFifo@8016c8d4 lis r4,0x8034; @8016c8dc addi r4,r4,-0x59c; @8016c8e8 stb r0,0x22(r4)
803867B0 80382430 __GX::FifoInit@8016d140 stb r31,-0x6450(r13) (=0); GX::SetCPUFifo@8016c8e0 stb r0,-0x6450(r13); GX::GetCPUFifo@8016ce84 lbz r0,-0x6450(r13)
803867B1 80382431 __GX::FifoInit@8016d144 stb r31,-0x644f(r13) (=0)
803867C0 80382440 __GX::FifoInit@8016d11c stw r31,-0x6440(r13) (r31=0: thread queue head cleared)
803867C4 80382444 __GX::FifoInit@8016d110 stw r3,-0x643c(r13) (r3 = OSGetCurrentThread()); GX::GetCurrentGXThread@8016d2fc lwz r3,-0x643c(r13)
803867D0 80382450 __GX::PEInit@8016eda0 addi r3,r13,-0x6430 -> OSInitThreadQueue; GX::DrawDone@8016ea9c addi r3,r13,-0x6430 -> OSSleepThread; GX::FinishInterruptHandler@8016ed58 addi r3,r13,-0x6430 -> OSWakeupThread
# ---------------------------------------------------------------- OS scheduler (DOL .bss/.sbss)
803477B0 80343430 SetEffectivePriority@801a99b0 lis r3,0x8034; @801a99b8 addi r3,r3,0x3430 (=0x80343430 RunQueue[32], indexed by prio*8: @801a99b4 slwi r0,r31,3); SelectThread@801a9bec lis r3,0x8034; @801a9bf4 addi r3,r3,0x3430; OS::ResumeThread@801aa580 lis r3,0x8034; @801aa588 addi r3,r3,0x3430
80386918 80382598 OS::__ThreadInit@801a97e0 stw r29,-0x62e8(r13) (=0); OS::DisableScheduler@801a985c lwz r31,-0x62e8(r13); @801a9864 stw r0,-0x62e8(r13) (count+1); SelectThread@801a9b80 lwz r0,-0x62e8(r13) (early exit while >0)
8038691C 8038259C OS::__ThreadInit@801a9718 stw r3,-0x62e4(r13)
80386920 803825A0 OS::__ThreadInit@801a9710 stw r3,-0x62e0(r13); UnsetRun@801a9908 lwz r5,-0x62e0(r13); @801a9918 stw r0,-0x62e0(r13)
# ---------------------------------------------------------------- SC / game system (DOL .sbss)
803869E0 80382660 SCGetProductCode@801b2390 addi r4,r13,-0x6220 (6-byte CODE output buffer: @801b2398 li r5,6); @801b23ac addi r3,r13,-0x6220 (returned pointer)
80386000 80381C80 SystemManager::create@8000a5a4 lwz r0,-0x6c00(r13) (s_instance null check); @8000a5c8 stw r3,-0x6c00(r13) (s_instance = new SystemManager); @8000a5d4 lwz r3,-0x6c00(r13); also REL GameScreen::__ct@80561520 lis r3 + @80561524 lwz r3,0(r3) carry module-0 relocations to 0x80381C80
# ---------------------------------------------------------------- EGG::Screen statics (DOL .sbss) - delta 0x4380 still holds here
80386F14 80382B94 EGG::Screen::SetTVModeDefault@8023e1cc lwz r6,-0x5cec(r13) (active screen, cmpwi 0 -> skip); EGG::Screen::SetTVMode@8023e114 lwz r6,-0x5cec(r13); EGG::Screen::CreateDummy@8023cf08 stw r3,-0x5cec(r13)
80386F20 80382BA0 EGG_Screen_cpp_sinit_@8023e37c (ctx) lfs f1,-0x5f84(r2) (=1.0f constant at 0x80384C9C); @8023e380 addi r4,r13,-0x5ce0; @8023e38c stfs f1,-0x5ce0(r13) (gScreenScale.x = 1.0); EGG::Screen::GetPosSizeInEFB@8023d62c lfs f0,-0x5ce0(r13); EGG::Screen::LoadDirectly@8023d1c4 lfs f0,-0x5ce0(r13)
80386F24 80382BA4 EGG_Screen_cpp_sinit_@8023e390 stfs f1,0x4(r4) with r4 = r13-0x5ce0 (=0x80382BA0) from @8023e380 (ctx) addi r4,r13,-0x5ce0 (gScreenScale.y = 1.0; second float of the Vector2f at 0x80382BA0)
# ---------------------------------------------------------------- EGG::DrawGX::SetVtxState vertex arrays (DOL .sdata2 / .rodata)
# .sdata2 arrays keep their r2 offsets (delta 0x4380); the .rodata arrays sit at PAL-0x320 (PAL .rodata carries 0x280 more bytes before them than NTSC; .rodata starts at PAL-0xA0).
80388B80 80384800 EGG::DrawGX::SetVtxState@8021b484 li r3,10(GX_VA_NRM); @8021b488 addi r4,r2,-0x6420; @8021b48c li r5,6 -> GXSetArray (states 7/8)
80388BA0 80384820 EGG::DrawGX::SetVtxState@8021b514 li r3,10; @8021b518 addi r4,r2,-0x6400; @8021b51c li r5,6 -> GXSetArray (state 9)
80388BC0 80384840 EGG::DrawGX::SetVtxState@8021b588 addi r4,r2,-0x63e0 (POS array, states 10/12); @8021b58c li r3,9; @8021b590 li r5,2
80388BE0 80384860 EGG::DrawGX::SetVtxState@8021b580 addi r4,r2,-0x63c0 (POS array, states 11/13); @8021b5a0 li r3,13(GX_VA_TEX0); @8021b5a4 addi r4,r2,-0x63c0; @8021b5a8 li r5,2
802574A0 80257180 EGG::DrawGX::SetVtxState@8021b270 lis r31,0x8025; @8021b274 addi r31,r31,0x7160 (=0x80257160); @8021b2d8 addi r4,r31,0x20 (=0x80257180); @8021b2dc li r3,9(GX_VA_POS); @8021b2e0 li r5,6 (state 0)
802574E0 802571C0 EGG::DrawGX::SetVtxState@8021b2e8 addi r4,r31,0x60 (=0x802571C0); @8021b2ec li r3,10(GX_VA_NRM); @8021b2f0 li r5,6 (state 0)
80257520 80257200 EGG::DrawGX::SetVtxState@8021b474 addi r4,r31,0xa0 (=0x80257200); @8021b478 li r3,9; @8021b47c li r5,4 (states 7/8)
80257540 80257220 EGG::DrawGX::SetVtxState@8021b504 addi r4,r31,0xc0 (=0x80257220); @8021b508 li r3,9; @8021b50c li r5,6 (state 9)
# ---------------------------------------------------------------- EGG::Screen canvas records + vtable (DOL .data) - these sit at PAL-0x4368 (not 0x4380)
# NTSC record table 0x8029FB80: {608,480,1.0526,1.0} then {832,480,0.7692,1.0} (12-byte records, matching the runtime's 608/832 widths); vtable 0x8029FBA4 = {EGG::Screen::__dt, EGG::Screen::LoadDirectly, EGG::Screen::CopyToG3D, ...}
802A3EE8 8029FB80 EGG::Screen::SetTVModeDefault@8023e1c4 bl SCGetAspectRatio; @8023e1e8 mulli r4,r0,12; @8023e1f0 lis r5,0x802a; @8023e1fc addi r5,r5,-0x480 (=0x8029FB80 record[0]); @8023e200 lhzx r4,r5,r4 (record[aspect].width); EGG::Screen::__ct@8023cf38 lis r5,0x802a; @8023cf48 addi r5,r5,-0x480; REL GameScreen::__ct@80561558 lis r4 / @80561568 addi r4,r4,0 carry module-0 relocations to 0x8029FB80
802A3EF4 8029FB8C record[1] = 0x8029FB80 + 12 (SetTVModeDefault index*12 above; 16:9 record {832,480,...} at 0x8029FB8C in the DOL image)
802A3F0C 8029FBA4 EGG::Screen::__ct@8023cfac lis r3,0x802a; @8023cfb0 addi r3,r3,-0x45c (=0x8029FBA4); @8023cfb8 stw r3,0x38(r31) (vtable at this+0x38); EGG::Screen::__ct(args)@8023d058 addi r3,r3,-0x45c
# ---------------------------------------------------------------- StaticR.rel (.data / .bss) - resolved through the NTSC-U REL relocation table
# Method: the REL text carries pre-relocation immediates (lis/addi = 0), so each cited instruction's R_PPC_ADDR16_HA/LO entry (module 1) was
# looked up at instruction+2 in the import table; target = section address + addend. NTSC-U section addresses: .data (sec5) = 0x808AE520,
# .bss (sec6) = 0x809B8F20 = 0x8050BF60 + alignUp(filesize 0x4ACF94 + 0x20 heap header, 0x20), which is exactly where the translator
# (RelFile.CalculateBssOffset) and the NTSC base manifest (build/base/mkwii_base_manifest.json: StaticR.rel:6 = 0x809b8f20..0x809c07d0) place it.
# PAL cross-check: the runtime's PAL bss identities imply PAL bss base 0x809BD6E0 (0x809C1830 - 0x4150; THP flag 0x809BECF0 = +0x1610 is
# referenced by NTSC THP::PlayerQuit/PlayerDrawDone at sec6+0x1610; Race::JugemManager 0x809C28B8 = +0x51D8 referenced by NTSC RaceScene::OnEnterEnd).
# The PAL bss layout is NOT identical everywhere: the audio singleton cluster sits 0x480 higher in PAL (mkw-sp PAL RaceSoundManager 0x809C27F8 =
# +0x5118 vs NTSC Audio::RaceMgr::CreateInstance -> +0x4C98; ItemMusicManager 0x809C27F0 = +0x5110 vs Audio::ItemAlterationMgr -> +0x4C90), so
# REL bss identities were matched by function, never by delta.
808B4C20 808B0570 GameScreen::__ct@805614cc bl -> EGG::Screen::__ct (module-0 REL24 reloc to 0x8023cf30; GameScreen is the only REL caller of an EGG::Screen ctor); @805614d0 lis r3 (ADDR16_HA -> sec5+0x2050); @805614d8 addi r3,r3 (ADDR16_LO -> sec5+0x2050 = 0x808B0570); @805614dc stw r3,0x38(r30) (vtable at this+0x38, same slot as the EGG::Screen vtable). Vtable contents via ADDR32 relocs: +8 GameScreen::__dt, +0xC EGG::Screen::LoadDirectly, +0x10 EGG::Screen::CopyToG3D (same layout as the EGG::Screen vtable at 0x8029FBA4).
809C1830 809BD070 Renderer::Register(listIdx)@80560d9c lis r5 (ADDR16_HA -> sec6+0x4150); @80560da8 addi r5,r5 (ADDR16_LO -> sec6+0x4150 = 0x809BD070 = Render::DrawList instance); @80560da4 mulli r0,r4,12; @80560dc0 add r3,r5,r0 (&lists[listIdx], 12-byte nw4r::ut::List); 38 sites reference sec6+0x4150 (Renderer::Register2ndList, RemoveFrom1stList/2ndList, ...)
809C183C 809BD07C Renderer::Register2ndList(NoAppend)@80560ef0 lis r5 (-> sec6+0x4150); @80560ef8 addi r5,r5 (-> 0x809BD070); @80560f08 addi r3,r5,0xc (second list = DrawList+0xC = 0x809BD07C); Renderer::RemoveFrom2ndList@80560f54 lis r5; @80560f5c addi r5,r5 (-> 0x809BD070); @80560f60 addi r3,r5,0xc
809C2898 809BDC58 Audio::Manager::CreateInstance@8070f390 lis r3 (ADDR16_HA -> sec6+0x4d38); @8070f3ac lwz r0,0(r3) (ADDR16_LO -> sec6+0x4d38 = 0x809BDC58, null check); @8070f440 lis r3; @8070f444 stw r30,0(r3) (-> sec6+0x4d38: spInstance = new Audio::Manager); the object is EGG::ExpAudioMgr-derived: Audio::Manager::StartSound@8070fcc0 lwz r3,0x5bc(r3) (archive player pointer) and DOL EGG::ExpAudioMgr::Initialize@802114e4 lwz r3,0x5bc(r27), matching the runtime's kArchivePlayerOffset 0x5BC. PAL offset 0x51B8 = NTSC 0x4D38 + 0x480, the same shift as the mkw-sp audio singletons.
# ---------------------------------------------------------------- .ctors/.dtors confirmation (batch 2 request) - unchanged from above
# DOL: PAL .ctors 80244DE0..80244EA0 -> NTSC 80244D40..80244E00 (sec9); .dtors NTSC 80244E00..80244E20 (sec10). REL: PAL .ctors 8088F400..8088F704 -> NTSC 8088AFD0..8088B2D4 (sec2); .dtors 8088B2D4..8088B2E0 (sec3).
# The NTSC base manifest agrees: .ctors 0x80244d40-0x80244e00, .dtors 0x80244e00-0x80244e20, StaticR.rel:2 0x8088afd0-0x8088b2d4, StaticR.rel:3 0x8088b2d4-0x8088b2e0.
# ================================================================ Batch 3
# ---------------------------------------------------------------- AX / DSP (DOL .data/.bss/.sbss/.sdata2)
# __AXOutInitDSP fills the DSPTaskInfo __AXDSPTask (NTSC 0x802F3E20): +0xC iram_mmem_addr, +0x10 iram_length, +0x18 dram_mmem_addr, +0x1C dram_length(64), +0x20 dram_addr(3282), +0x28.. callbacks 0x801268a8/0x801268b4/0x80126908/0x80126918 (= PAL 80126948/54/9A8/9B8 - 0xA0)
8027F820 8027B4E0 __AXOutInitDSP@80126960 lis r29,0x8028; @80126964 addi r29,r29,-0x4b20 (=0x8027B4E0, DSP ucode image: vector table 029f 0f3c 029f 0f48 ... at that address); @80126990 stw r29,0xc(r30) (task.iram_mmem_addr; r30 = __AXDSPTask) # .data delta 0x4340 for this object (not 0x4380)
802F81A0 802F3E20 __AXOutInitDSP@80126950 lis r30,0x802f; @80126954 addi r30,r30,0x3e20 (=0x802F3E20 __AXDSPTask); @801269d8 lis r3,0x802f; @801269dc addi r3,r3,0x3e20 -> DSPAddTask(&__AXDSPTask); __AXOutAiCallback@80126830 lis r3,0x802f; @80126838 addi r3,r3,0x3e20
802F8200 802F3E80 __AXOutInitDSP@80126924 lis r11,0x802f; @80126944 addi r11,r11,0x3e80 (=0x802F3E80 DRAM image); @8012699c stw r11,0x18(r30) (task.dram_mmem_addr); @80126978 li r10,64; @801269a0 stw r10,0x1c(r30) (dram_length = 64); @80126980 li r9,3282; @801269a4 stw r9,0x20(r30) (dram_addr)
80386608 80382288 DSPInit@8015d3d0 lwz r0,-0x65f8(r13) (already-initialised check); DSPInit@8015d448 stw r0,-0x65f8(r13) (=1); DSPCheckInit@8015d464 lwz r3,-0x65f8(r13)
80386610 80382290 DSPAssertTask@8015d510 stw r0,-0x65f0(r13) (=1); DSPAssertTask@8015d538 stw r0,-0x65f0(r13); __DSPHandler@8015d6d8 lwz r0,-0x65f0(r13)
80386614 80382294 DSPAssertTask@8015d50c stw r30,-0x65ec(r13) (= task being asserted); DSPAssertTask@8015d534 stw r30,-0x65ec(r13)
80386618 80382298 DSPInit@8015d438 stw r4,-0x65e8(r13) (=0, cleared with the task pointers below)
8038661C 8038229C DSPInit@8015d440 stw r4,-0x65e4(r13) (=0); __DSPHandler@8015d96c lwz r3,-0x65e4(r13); __DSP_insert_task@8015dd60 stw r3,-0x65e4(r13)
80386620 803822A0 DSPInit@8015d444 stw r4,-0x65e0(r13) (=0); DSPAddTask@8015d4ac lwz r0,-0x65e0(r13); __DSPHandler@8015d968 lwz r0,-0x65e0(r13)
80386624 803822A4 DSPInit@8015d43c stw r4,-0x65dc(r13) (=0); DSPAssertTask@8015d4f8 lwz r5,-0x65dc(r13); __DSPHandler@8015d634 lwz r5,-0x65dc(r13)
80388588 80384208 AXFXReverbStdExpCallback@8012b81c lfs f2,-0x6a18(r2) (cell 0x80384208 = 1.0f in the DOL image); AXFXReverbStdExp__InitParams@8012becc lfs f1,-0x6a18(r2) # r2-0x6a18 in both regions (PAL 0x8038EFA0-0x6A18=0x80388588)
8038858C 8038420C AXFXReverbStdExpCallback@8012b82c lfs f3,-0x6a14(r2) (cell 0x8038420C = 0.6f in the DOL image)
# ---------------------------------------------------------------- AI (DOL .sbss)
80386448 803820C8 AIInit@8012402c lwz r0,-0x67b8(r13) (already-initialised check); AIInit@80124170 stw r0,-0x67b8(r13) (=1); AICheckInit@80123ff4 lwz r3,-0x67b8(r13)
8038644C 803820CC __AIDHandler@801241d8 lwz r0,-0x67b4(r13) (callback-busy check); __AIDHandler@801241ec stw r4,-0x67b4(r13) (=1)
8038647C 803820FC AIInit@8012415c stw r31,-0x6784(r13); __AIDHandler@801241e4 lwz r0,-0x6784(r13); __AICallbackStackSwitch@8012425c lis r5,0x8038; @80124260 addi r5,r5,0x20fc (=0x803820FC)
80386480 80382100 AIRegisterDMACallback@80123f00 lwz r31,-0x6780(r13) (old callback); AIRegisterDMACallback@80123f08 stw r30,-0x6780(r13) (new callback); AIInit@80124150 stw r0,-0x6780(r13) (=0); __AIDHandler@801241cc lwz r3,-0x6780(r13)
# ---------------------------------------------------------------- OS thread/interrupt globals (DOL .bss/.sdata/.sbss)
80347130 80342DB0 OS::CreateThread@801a9ea8 lis r4,0x8034; @801a9eac addi r4,r4,0x2d70; @801a9eb0 lwz r0,0x40(r4) (=0x80342DB0); @801a9eb4 cmpwi r0,0 -> skip the FP/PS context init; when set: @801a9ecc ori r5,r5,0x900 (srr1), @801a9ed4 ori r4,r4,1 (mode), fpscr from the .sdata word below (exactly the sequence os_thread.cpp mirrors)
80347498 80343118 OS::__ThreadInit@801a95a0 lis r31,0x8034; @801a95a4 addi r31,r31,0x3118 (=0x80343118 DefaultThread; RunQueue[32] at 0x80343430 = +0x318 follows it)
803478B0 80343530 SelectThread@801a9ca0 lis r3,0x8034; @801a9ca8 addi r3,r3,0x3530 (=0x80343530 IdleContext -> OSLoadContext); SelectThread@801a9cd0 lis r3,0x8034; @801a9cd4 addi r3,r3,0x3530
80385AA8 80381728 OS::CreateThread@801a9ee0 lwz r4,-0x7158(r13) (__OSFpscrEnableBits); @801a9ee4 rlwinm r4,r4,0,24,28 (&0xF8); @801a9ee8 ori r4,r4,4; @801a9eec stw r4,0x194(r31) (thread->context.fpscr); OS::SetErrorHandler@801a28ac lwz r4,-0x7158(r13) # .sdata, r13-0x7158 both regions
80385AE0 80381760 OS::SetSwitchThreadCallback@801a952c lwz r31,-0x7120(r13) (old callback); @801a9540 stw r30,-0x7120(r13) (new); SelectThread@801a9c84 lwz r12,-0x7120(r13) (called on switch); OS::__ThreadInit@801a9620 lwz r12,-0x7120(r13) # .sdata, r13-0x7120 both regions
803868F8 80382578 OS::__SetInterruptHandler@801a6558 lwz r5,-0x6308(r13) (InterruptHandlerTable ptr, then stwx handler); OS::__GetInterruptHandler@801a656c lwz r4,-0x6308(r13); OS::__InterruptInit@801a65a4 stw r3,-0x6308(r13); OS::__DispatchInterrupt@801a6bc0 lwz r3,-0x6308(r13)
80386298 80381F18 RFL::iGetManager@800bc3d0 lwz r3,-0x6968(r13) (returns the RFL manager pointer); RFL::Init@800bbb34 lwz r0,-0x6968(r13)
80336278 80331EF8 BTM_IsDeviceUp@8013a260 lis r3,0x8033; @8013a264 addi r3,r3,0x1ef8 (=0x80331EF8 btm_cb); @8013a268 lbz r3,0x64e(r3) (device state; compared with 5, matching os_init.cpp's +0x64E/5); btm_acl_init@80137bc4 addi r3,r3,0x1ef8
# ---------------------------------------------------------------- StaticR.rel THP player state and movie mode table (relocation method, bss base 0x809B8F20)
# THP ActivePlayer struct = REL bss+0x1420 in both regions (PAL 0x809BEB00 = 0x809BD6E0+0x1420; NTSC 0x809BA340); the runtime's 809BEBA0..A7 are its +0xA0 (open) and +0xA4..+0xA7 byte fields.
809BEBA0 809BA3E0 THP::PlayerOpen@8054ad7c lis r29 (ADDR16_HA -> sec6+0x1420); @8054ad84 addi r29,r29 (ADDR16_LO -> 0x809BA340 ActivePlayer); @8054ada0 addi r30,r29,0; @8054ada4 lwz r0,0xa0(r30) (already-open check); @8054afc8 stw r0,0xa0(r30) (open = 1); THP::PlayerClose@8054aff8 lis r3; @8054b000 addi r3,r3 (-> 0x809BA340); @8054b020 stw r0,0xa0(r3) (open = 0) [reloc-base 0x809BA340 + 0xA0]
809BEBA4 809BA3E4 THP::PlayerOpen@8054afbc stb r4,0xa4(r30) (r30 = ActivePlayer 0x809BA340 from @8054ad7c / @8054ad84 / @8054ada0 [3bdd0000]) [reloc-base 0x809BA340 + 0xA4]
809BEBA5 809BA3E5 THP::PlayerOpen@8054afb4 stb r4,0xa5(r30) (same base: @8054ad7c / @8054ad84 / @8054ada0 [3bdd0000]) [reloc-base 0x809BA340 + 0xA5]
809BEBA6 809BA3E6 THP::PlayerOpen@8054afc0 stb r4,0xa6(r30) (same base: @8054ad7c / @8054ad84 / @8054ada0 [3bdd0000]) [reloc-base 0x809BA340 + 0xA6]
809BEBA7 809BA3E7 THP::PlayerOpen@8054aec4 stb r0,0xa7(r30) (same base: @8054ad7c / @8054ad84 / @8054ada0 [3bdd0000]) [reloc-base 0x809BA340 + 0xA7]
809BECF0 809BA530 THP::PlayerQuit@8054ad48 lis r3 (ADDR16_HA -> sec6+0x1610); @8054ad50 stw r0,0(r3) (ADDR16_LO -> sec6+0x1610 = 0x809BA530, Initialized = 0); THP::PlayerDrawDone@8054bee0 lis r3; @8054bee4 lwz r0,0(r3) (-> 0x809BA530) # PAL +0x1610 under base 0x809BD6E0, same offset
8088FDB8 8088B960 MovieManager::Prepare@805258b0 lwz r0,0xa8(r31) (mode); @805258b4 lis r4 (ADDR16_HA -> sec4+0x680); @805258b8 addi r4,r4 (ADDR16_LO -> sec4+0x680 = 0x8088B960); @805258c0 slwi r0,r0,2; @805258c8 lwzx r4,r4,r0 (audioSystem = table[mode]) -> THP::PlayerPrepare; MovieManager::PrepareAsync@8052594c lis r4; @80525950 addi r4,r4 (-> 0x8088B960); @80525960 lwzx r4,r4,r0 # REL .rodata: PAL 0x8088FDB8 is .rodata+0x698 from 0x8088F720 but the NTSC table is .rodata+0x680; matched by function, not by delta
# ---------------------------------------------------------------- Notes
# * No PAL binary is available on this machine (both MKW images found are RMCE01), so PAL-side offsets are
# PAL_addr - PAL_r13/r2 by definition; each NTSC-U instruction above was matched by function identity (port table +
# PAL MAP name), by the instruction's role in the SDK/EGG function, and by consistency with sibling globals.
# * .sbss gap: delta 0x4380 is directly witnessed up to PAL 0x80386F27 (EGG::Screen gScreenScale Vector2f at
# PAL 0x80386F20/24 -> NTSC 0x80382BA0/A4: EGG_Screen_cpp_sinit_@8023e38c stfs f1,-0x5ce0(r13),
# @8023e390 stfs f1,4(r4) with @8023e380 addi r4,r13,-0x5ce0; EGG::Screen::SetTVModeDefault@8023e1cc lwz r6,-0x5cec(r13)
# = PAL 0x80386F14 active-screen ptr), and delta 0x4388 at PAL 0x80386F60 (sSystem). The extra 8 PAL bytes therefore
# lie in PAL 0x80386F28..0x80386F5F (NTSC 0x80382BA8..0x80382BD7: EGG::Screen/StateGX statics); which object was
# added/removed could not be identified without the PAL binary. None of the identities above fall inside that window.
# * The runtime's comment labels for some DVD .sbss globals do not match the SDK roles visible in the code
# (0x80386664 is the flag DVDPause sets / DVDResume clears, 0x80386668 the one DVDResume tests-and-clears,
# 0x80386670 the one __DVDPrepareReset tests); the address identities are unaffected.
# STM (State Transition Manager) globals. __OSInitSTM stores the two /dev/stm handles and its
# initialized flag into the small-data block; the runtime's stub writes the same three words so
# OSResetSystem's zero-checks pass. Their SDA offsets are NOT the same in every region (RMCK01
# puts them 0x20 lower, where PAL/NTSC-U/NTSC-J keep the OSDisableScheduler nesting count), so
# they are named as identities here rather than as a hardcoded r13 offset.
80386934 803825B4 __OSInitSTM@801AB810 stw r31,-0x62cc(r13) (STM initialized flag)
80386938 803825B8 __OSInitSTM@801AB808 stw r3,-0x62c8(r13) (/dev/stm/immediate handle)
8038693C 803825BC __OSInitSTM@801AB830 stw r3,-0x62c4(r13) (/dev/stm/eventhook handle)
80386940 803825C0 OS::SetPowerCallback@801AB700 lwz r0,-0x62c0(r13); @801AB750 stw r0,-0x62c0(r13); @801AB75C stw r0,-0x62c0(r13) (STM event handler registered flag)
80386948 803825C8 OS::SetPowerCallback@801AB6E0 lwz r29,-0x62b8(r13); @801AB6EC stw r31,-0x62b8(r13); @801AB6FC stw r3,-0x62b8(r13) (registered power callback pointer)
# ISFS / IPC globals. The runtime's ISFS_OpenLib stub brings the filesystem client up itself:
# it writes the "/dev/fs" path string and the device fd, then creates the ISFS heap out of the
# IPC buffer window. Like the STM globals above, these do NOT sit at the same r13 offsets in
# every region - RMCK01 keeps the .sbss cluster 0x20 lower and __ISFS_fd/__ISFS_path 0x8 lower -
# so they are named as identities rather than as hardcoded r13 offsets. Addresses below are the
# NTSC-U ones, each confirmed by the RMCE01 instructions cited; PAL is uniformly +0x4380 here,
# matching the .sbss delta documented at the top of this file.
80386770 803823F0 ISFS@80169B44 lwz r0,-0x6490(r13); ISFS@80169B64 lwz r0,-0x6490(r13); ISFS@80169BCC lwz r4,-0x6490(r13); ISFS@80169C18 stw r0,-0x6490(r13) # isfsHeapInitialized, 4 sites in RMCE01
80386774 803823F4 ISFS@80169B74 stw r3,-0x648C(r13); ISFS@80169BAC lwz r3,-0x648C(r13); ISFS@80169BD0 lwz r30,-0x648C(r13) # isfsHeapBase, 3 sites in RMCE01
8038677C 803823FC ISFS@80169B54 stw r3,-0x6484(r13); ISFS@80169B60 lwz r3,-0x6484(r13) # __IPCArenaLo, 2 sites in RMCE01
80386780 80382400 ISFS@80169B5C stw r3,-0x6480(r13); ISFS@80169B7C lwz r0,-0x6480(r13); ISFS@80169BDC lwz r0,-0x6480(r13) # __IPCArenaHi, 3 sites in RMCE01
80386784 80382404 ISFS@80169C2C stw r3,-0x647C(r13); ISFS@80169DAC lwz r3,-0x647C(r13); ISFS@80169E30 lwz r3,-0x647C(r13); ISFS@80169EA0 lwz r3,-0x647C(r13) # isfsHeapHandle, 36 sites in RMCE01
803867EC 8038246C ISFS@80192F10 stw r3,-0x6414(r13); ISFS@80192F58 stw r3,-0x6414(r13); ISFS@80192F98 lwz r3,-0x6414(r13); ISFS@80192FA0 stw r3,-0x6414(r13) # ipcBufferLo, 4 sites in RMCE01
803867F0 80382470 ISFS@80192F0C stw r4,-0x6410(r13); ISFS@80192F54 stw r4,-0x6410(r13); ISFS@80192F90 lwz r3,-0x6410(r13) # ipcBufferHi, 3 sites in RMCE01
80385920 803815A0 ISFS@80169BBC stw r3,-0x72E0(r13); ISFS@80169E08 lwz r0,-0x72E0(r13); ISFS@80169E88 lwz r3,-0x72E0(r13); ISFS@80169F00 lwz r0,-0x72E0(r13) # __ISFS_fd, 33 sites in RMCE01
80385928 803815A8 ISFS@80169BA4 addi r4,r13,-0x72D8 # __ISFS_path, 1 sites in RMCE01
# OS alarm queue and the two AX state flags. Same story as the STM and ISFS globals above: the
# runtime's alarm stubs walk OSAlarmQueue and the AX stub sets two flags, and RMCK01 keeps this
# part of .sbss 0x20 lower than PAL/NTSC-U/NTSC-J (OSAlarmQueue is r13-0x6360 in PAL but
# r13-0x6340 in RMCK01), so a hardcoded offset walks a string table there instead of the queue.
803868A0 80382520 OSAlarmQueue@801A0540 addi r3,r13,-0x6360; OSAlarmQueue@801A054C stw r0,-0x6360(r13); OSAlarmQueue@801A064C lwz r7,-0x6360(r13); OSAlarmQueue@801A0698 stw r30,-0x6360(r13) # OSAlarmQueue, 13 sites in RMCE01
80386528 803821A8 AX@801268AC stw r0,-0x66D8(r13); AX@8012698C stw r12,-0x66D8(r13); AX@801269E4 lwz r0,-0x66D8(r13) # AX flag 0x66d8, 3 sites in RMCE01
80386524 803821A4 AX@80126804 lwz r0,-0x66DC(r13); AX@80126910 stw r0,-0x66DC(r13); AX@801269C0 stw r12,-0x66DC(r13) # AX flag 0x66dc, 3 sites in RMCE01
# The three .sdata halfwords __AXOutInitDSP copies into the AX DSPTaskInfo: the ucode length
# (task+0x10) and the DSP init/resume vectors (task+0x24/+0x26). The runtime's AX stub reads
# the same three when it builds the task.
80385800 80381480 __AXOutInitDSP@80126974 lhz r8,-0x7400(r13) -> sth r8,0x24(task) # dsp_init_vector
80385802 80381482 __AXOutInitDSP@8012697C lhz r0,-0x73fe(r13) -> sth r0,0x26(task) # dsp_resume_vector
80385804 80381484 __AXOutInitDSP@8012696C lhz r31,-0x73fc(r13) -> stw r31,0x10(task) # iram_length (ucode length)
# EGG fog constants in .sdata2, read through r2 by the EGG::StateGX fog-quad code. Unlike the
# .sbss globals above, the r2 offset of these three is 8 LARGER in NTSC-U than in PAL, while
# NTSC-J and NTSC-K keep PAL's offset. The values settle it: at the addresses below all four
# regions read 0.0 / 0.99 / 0.5, whereas NTSC-U at PAL's offset reads 176.0 / 176.0 / 1.0 -
# so the runtime's old hardcoded PAL r2 offsets were reading the wrong constants on NTSC-U only.
80388DD0 80384A48 EGG@8022D3F0 lfs r0,-0x61D8(r2); EGG@8022D71C lfs r2,-0x61D8(r2); EGG@8022D7A0 lfs r5,-0x61D8(r2); EGG@8022DB0C lfs r31,-0x61D8(r2) # EGG fog origin (m!=2), 9 sites in RMCE01
80388DE0 80384A58 EGG@8022DB24 lfs r0,-0x61C8(r2); EGG@8022DEBC lfs r0,-0x61C8(r2); EGG@8022E018 lfs r0,-0x61C8(r2) # EGG fog span, 3 sites in RMCE01
80388DF4 80384A6C EGG@8022DE7C lfs r31,-0x61B4(r2); EGG@8022DE90 lfs r30,-0x61B4(r2); EGG@8022DFD8 lfs r31,-0x61B4(r2); EGG@8022DFEC lfs r30,-0x61B4(r2) # EGG fog origin (m==2), 4 sites in RMCE01
8029CEB0 80298B70 SCGetProductArea@801b2320 lis r30,0x802a; @801b2324 addi r30,r30,-0x7490 (=0x80298B70, SC area table; 13 records identical to PAL)
8029CEF8 80298BB8 SCGetProductGameRegion@801b2448 lis r30,0x802a; @801b244c addi r30,r30,-0x7448 (=0x80298BB8, SC game region table; 4 records identical to PAL)
+72
View File
@@ -0,0 +1,72 @@
schema_version: 1
workspace_root: ../..
# Mario Kart Wii NTSC-U (RMCE01). The same translation surface as projects/mkwii/recomp.yml, with
# the facts that differ per region: the small-data bases the DOL's __init_registers installs, the
# StaticR.rel load address and the clean-input digests. The function map is the PAL map ported
# through the community region table and validated against these binaries.
project:
id: mkwii-rmce01
display_name: Mario Kart Wii NTSC-U
game_id: RMCE01
region: E
base_manifest_format: mkwii-recomp-base-manifest
base_manifest_stem: mkwii_base
memory:
base: 0x80000000
size: 0x01A00000
sda_base: 0x80388880
sda2_base: 0x8038AC20
inputs:
dol:
path: Assets/main.dol
sha256: d2beec1b1645fcd134efe9e7e63774b546667764ed8d431029daccd725995694
rel:
path: Assets/StaticR.rel
load_address: 0x8050BF60
sha256: 1168107f8fdef27a356df76036db55afe4fbf7752606dbac991726701133a617
translation:
entry_points:
- 0x800060A4
function_map:
path: projects/mkwii-ntsc-u/MAP.txt
allow_unsupported_instructions: false
runtime:
native_registration_root: runtime/src
native_abi_directories:
- runtime/src/hle/gx
output:
root: generated
functions: functions
runtime_config: RuntimeConfig.h
data_initializer: data_sections_init.cpp
base_manifest: ../build/base/mkwii_base_manifest.json
# Code.pul carries a separate chunk for this region. The regional link base determines
# the branch bytes checked at startup; execution is remapped to module_guest_base.
profiles:
retro-rewind:
enabled: true
requires_game_id: RMCE01
requires_dol_sha256: d2beec1b1645fcd134efe9e7e63774b546667764ed8d431029daccd725995694
code_pul: PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul
mod_root: PulsarPacks/completed/RetroRewind/RetroRewind6
region: E
module_guest_base: 0x81800000
module_link_base: 0x80394F60
output: build/mods/retro_rewind_full_cpp
enable_retro_wfc: true
retro_wfc_payload: https://rwfc.net/api/wfc/payload?g=RMCED00
retro_wfc_legacy_bootstrap_hook: 0x800ED648
riivolution:
xml: xml/RetroRewind6.xml
options:
- section: Retro Rewind
option: Pack
choice: 1
File diff suppressed because it is too large Load Diff
+7
View File
@@ -55,8 +55,14 @@ function(mkw_bound_translated_compiles target)
endif()
endfunction()
# The region the translator built for; translated code and runtime/game bind addresses to it.
if(NOT DEFINED MKW_TRANSLATED_GUEST_REGION)
message(FATAL_ERROR "The translator shard manifest names no guest region; re-run emit-build-shards.")
endif()
function(mkw_configure_translated_target target)
mkw_configure_object_target(${target})
target_compile_definitions(${target} PRIVATE MKW_TRANSLATED_GUEST_REGION=${MKW_TRANSLATED_GUEST_REGION})
mkw_apply_translated_compile_options(${target})
mkw_bound_translated_compiles(${target})
endfunction()
@@ -75,6 +81,7 @@ add_library(mkw_game_data OBJECT ${MKW_GAME_DATA_SOURCES})
mkw_configure_object_target(mkw_game_data)
target_compile_features(mkw_game_data PRIVATE cxx_std_20)
target_compile_definitions(mkw_game_data PRIVATE
MKW_TRANSLATED_GUEST_REGION=${MKW_TRANSLATED_GUEST_REGION}
SDL_MAIN_HANDLED
_DISABLE_STRING_ANNOTATION _DISABLE_VECTOR_ANNOTATION)
if(MKW_SETUP_VERSION)
+26
View File
@@ -0,0 +1,26 @@
#include "guest_abi/guest_region.h"
#include "guest_function_imports.h"
#if !defined(MKW_TRANSLATED_GUEST_REGION)
#error "MKW_TRANSLATED_GUEST_REGION comes from the translator's shard manifest (emit-build-shards)"
#endif
static_assert(MKW_TRANSLATED_GUEST_REGION >= 0 && MKW_TRANSLATED_GUEST_REGION < GuestRegion::kCount);
extern "C" const uint8_t kMkwGuestRegion = MKW_TRANSLATED_GUEST_REGION;
#define MKW_DEFINE_GUEST_FUNCTION_IMPORT(pal, symbol) \
extern "C" void func_##symbol(CpuContext*); \
extern "C" void (*const mkw_guest_function_##pal)(CpuContext*) = &func_##symbol; \
static_assert(MKW_GADDR_CT(pal) == 0x##symbol##u, "guest_function_imports.inc disagrees with " \
"guest_address_table.inc");
#if MKW_TRANSLATED_GUEST_REGION == 0
#define MKW_GUEST_FUNCTION_IMPORT(pal, ntscU, ntscJ) MKW_DEFINE_GUEST_FUNCTION_IMPORT(pal, pal)
#elif MKW_TRANSLATED_GUEST_REGION == 1
#define MKW_GUEST_FUNCTION_IMPORT(pal, ntscU, ntscJ) MKW_DEFINE_GUEST_FUNCTION_IMPORT(pal, ntscU)
#else
#define MKW_GUEST_FUNCTION_IMPORT(pal, ntscU, ntscJ) MKW_DEFINE_GUEST_FUNCTION_IMPORT(pal, ntscJ)
#endif
#include "guest_function_imports.inc"
#undef MKW_GUEST_FUNCTION_IMPORT
+6 -6
View File
@@ -157,14 +157,14 @@ public:
#define MKW_DETAIL_CAT(a, b) a##b
#define MKW_DETAIL_MAKE_UNIQUE(a, b) MKW_DETAIL_CAT(a, b)
// Three hand-written registration macros. Generated code never registers per function; every
// translated function reaches the registry via the bulk BulkTranslatedFunctionRecord tables
// in the translator's *_registration TUs.
// Three hand-written registration macros, given the PAL address. Generated code never registers
// per function; every translated function reaches the registry via the bulk
// BulkTranslatedFunctionRecord tables in the translator's *_registration TUs.
#define REGISTER_TRANSLATED_FUNCTION(address, fn) \
static AbiTrampoline<decltype(fn)> MKW_DETAIL_MAKE_UNIQUE(_abi_trampoline_, __COUNTER__)(address, #fn, fn, FunctionKind::BaseTranslated, false, kPpcAllNonvolatileFprMask, 0, 0, nullptr, KnownTranslatedCpuCall<address>::kMustRemainDynamicallyDispatchable)
static AbiTrampoline<decltype(fn)> MKW_DETAIL_MAKE_UNIQUE(_abi_trampoline_, __COUNTER__)(::GuestRegion::Translate(address), #fn, fn, FunctionKind::BaseTranslated, false, kPpcAllNonvolatileFprMask, 0, 0, nullptr, KnownTranslatedCpuCall<address>::kMustRemainDynamicallyDispatchable)
#define REGISTER_NATIVE_FUNCTION(address, fn) \
static AbiTrampoline<decltype(fn)> MKW_DETAIL_MAKE_UNIQUE(_abi_native_trampoline_, __COUNTER__)(address, #fn, fn, FunctionKind::Native, false, kPpcAllNonvolatileFprMask, 0, 0, &AbiRawCpuThunk<&fn>::Invoke)
static AbiTrampoline<decltype(fn)> MKW_DETAIL_MAKE_UNIQUE(_abi_native_trampoline_, __COUNTER__)(::GuestRegion::Translate(address), #fn, fn, FunctionKind::Native, false, kPpcAllNonvolatileFprMask, 0, 0, &AbiRawCpuThunk<&fn>::Invoke)
#define REGISTER_NATIVE_FUNCTION_AS(address, fn, pretty_name) \
static AbiTrampoline<decltype(fn)> MKW_DETAIL_MAKE_UNIQUE(_abi_native_trampoline_named_, __COUNTER__)(address, pretty_name, fn, FunctionKind::Native, false, kPpcAllNonvolatileFprMask, 0, 0, &AbiRawCpuThunk<&fn>::Invoke)
static AbiTrampoline<decltype(fn)> MKW_DETAIL_MAKE_UNIQUE(_abi_native_trampoline_named_, __COUNTER__)(::GuestRegion::Translate(address), pretty_name, fn, FunctionKind::Native, false, kPpcAllNonvolatileFprMask, 0, 0, &AbiRawCpuThunk<&fn>::Invoke)
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once
// Bump whenever guest_abi/, isa/ or ppc_isa_memory.h changes what translated code compiles against.
#define MKW_GUEST_ABI_VERSION 1
#define MKW_GUEST_ABI_VERSION 2
+6 -7
View File
@@ -2,6 +2,7 @@
#include "guest_abi/cpu_context.h"
#include "guest_abi/exec_guard.h"
#include "guest_abi/guest_region.h"
#include "isa/ppc_isa_config.h"
#include "isa/ppc_isa_cr.h"
@@ -24,7 +25,7 @@ uint32_t FilterScnRendererPathMaskOption(uint32_t pathMask) noexcept;
// (used for the path mask filtering in ScnRenderer::createPath: depth of
// field is always removed, bloom when the user disabled it)
inline void ApplyRuntimeCallOptions(uint32_t target, CpuContext* ctx) {
if (target == 0x8023BD38u) {
if (::GuestRegion::Is<0x8023BD38u>(target)) {
// ScnRenderer::createPath receives the post-processing path mask in r4.
ctx->gpr[4] = FilterScnRendererPathMaskOption(ctx->gpr[4]);
}
@@ -242,13 +243,11 @@ private:
std::array<uint32_t, 18> saved_;
};
// Guest address of OSLoadContext, which models rfi-style context restoration
// and intentionally replaces the full guest register file instead of returning
// like a normal ABI call. It can never be guarded.
inline constexpr uint32_t kOSLoadContextAddress = 0x801A1F58u;
// OSLoadContext models rfi-style context restoration and intentionally replaces
// the full guest register file instead of returning like a normal ABI call. It
// can never be guarded.
inline bool ShouldPreserveNonvolatileGprsForRawCpuCall(const TranslatedFunctionInfo* info) noexcept {
return info->kind == FunctionKind::Native && info->address != kOSLoadContextAddress;
return info->kind == FunctionKind::Native && info->address != MKW_GADDR(801A1F58);
}
inline uint32_t NonvolatileFprGuardMaskFor(const TranslatedFunctionInfo* info) noexcept {
+744
View File
@@ -0,0 +1,744 @@
// AUTO-GENERATED by tools/region/gen_guest_address_table.py - DO NOT EDIT.
// PAL identity -> address in each region: MKW_GUEST_ADDRESS(PAL, NTSC-U, NTSC-J).
// Sorted by PAL address; guest_abi/guest_region.h relies on that.
MKW_GUEST_ADDRESS(80006348, 80006348, 80006348) // __init_hardware
MKW_GUEST_ADDRESS(800077C8, 800077C8, 800077C8) // StrapScene::CheckInput
MKW_GUEST_ADDRESS(8000A000, 80009FC0, 80009F5C)
MKW_GUEST_ADDRESS(8000A400, 8000A3C0, 8000A35C)
MKW_GUEST_ADDRESS(80015500, 800149A0, 80015424) // __write_console
MKW_GUEST_ADDRESS(8002001C, 8001F4BC, 8001FF40)
MKW_GUEST_ADDRESS(80044B30, 80044A90, 80044A50) // GXEnd
MKW_GUEST_ADDRESS(80048C30, 80048B90, 80048B50) // GXEnd
MKW_GUEST_ADDRESS(800847C0, 80084720, 800846E0) // nw4r::lyt::detail::DrawQuad
MKW_GUEST_ADDRESS(80084D20, 80084C80, 80084C40) // nw4r::lyt::detail::DrawQuad
MKW_GUEST_ADDRESS(800A35E0, 800A3540, 800A3500) // nw4r::snd::SoundPlayer::SetVolume
MKW_GUEST_ADDRESS(800BD860, 800BD7C0, 800BD780) // thunk_0x800bc440
MKW_GUEST_ADDRESS(80123F88, 80123EE8, 80123EA8) // AIRegisterDMACallback
MKW_GUEST_ADDRESS(80123FCC, 80123F2C, 80123EEC) // AIInitDMA
MKW_GUEST_ADDRESS(80124048, 80123FA8, 80123F68) // AIStartDMA
MKW_GUEST_ADDRESS(8012405C, 80123FBC, 80123F7C) // AIGetDMABytesLeft
MKW_GUEST_ADDRESS(8012406C, 80123FCC, 80123F8C) // AIGetDMAStartAddr
MKW_GUEST_ADDRESS(80124084, 80123FE4, 80123FA4) // AIGetDMALength
MKW_GUEST_ADDRESS(80124094, 80123FF4, 80123FB4) // AICheckInit
MKW_GUEST_ADDRESS(8012409C, 80123FFC, 80123FBC) // AIGetDSPSampleRate
MKW_GUEST_ADDRESS(801240B0, 80124010, 80123FD0) // AIInit
MKW_GUEST_ADDRESS(80126948, 801268A8, 80126868) // __AXDSPInitCallback
MKW_GUEST_ADDRESS(80126954, 801268B4, 80126874) // __AXDSPResumeCallback
MKW_GUEST_ADDRESS(801269A8, 80126908, 801268C8) // __AXDSPRequestCallback
MKW_GUEST_ADDRESS(801269B8, 80126918, 801268D8)
MKW_GUEST_ADDRESS(801269BC, 8012691C, 801268DC) // __AXOutInitDSP
MKW_GUEST_ADDRESS(8012B830, 8012B790, 8012B750) // AXFXReverbStdExpCallback
MKW_GUEST_ADDRESS(8012E574, 8012E4D4, 8012E494) // PPCMfhid0
MKW_GUEST_ADDRESS(8012E57C, 8012E4DC, 8012E49C) // PPCMthid0
MKW_GUEST_ADDRESS(8012E594, 8012E4F4, 8012E4B4) // PPCMtdec
MKW_GUEST_ADDRESS(8012E59C, 8012E4FC, 8012E4BC) // PPCSync
MKW_GUEST_ADDRESS(8012E5A4, 8012E504, 8012E4C4) // PPCHalt
MKW_GUEST_ADDRESS(8012E5B8, 8012E518, 8012E4D8) // PPCMtmmcr0
MKW_GUEST_ADDRESS(8012E5C0, 8012E520, 8012E4E0) // PPCMtmmcr1
MKW_GUEST_ADDRESS(8012E5C8, 8012E528, 8012E4E8) // PPCMtpmc1
MKW_GUEST_ADDRESS(8012E5D0, 8012E530, 8012E4F0) // PPCMtpmc2
MKW_GUEST_ADDRESS(8012E5D8, 8012E538, 8012E4F8) // PPCMtpmc3
MKW_GUEST_ADDRESS(8012E5E0, 8012E540, 8012E500) // PPCMtpmc4
MKW_GUEST_ADDRESS(8012E630, 8012E590, 8012E550) // PPCMfhid2
MKW_GUEST_ADDRESS(8012E638, 8012E598, 8012E558) // PPCMthid2
MKW_GUEST_ADDRESS(8012E640, 8012E5A0, 8012E560) // PPCMfwpar
MKW_GUEST_ADDRESS(8012E64C, 8012E5AC, 8012E56C) // PPCMtwpar
MKW_GUEST_ADDRESS(8012E654, 8012E5B4, 8012E574) // PPCDisableSpeculation
MKW_GUEST_ADDRESS(8012E684, 8012E5E4, 8012E5A4) // PPCMthid4
MKW_GUEST_ADDRESS(801301B4, 80130114, 801300D4) // GKI_delay
MKW_GUEST_ADDRESS(8015BC80, 8015BBE0, 8015BBA0) // contentFastOpenNAND
MKW_GUEST_ADDRESS(8015BCF8, 8015BC58, 8015BC18) // contentReadNAND
MKW_GUEST_ADDRESS(8015D3FC, 8015D35C, 8015D31C) // DSPCheckMailToDSP
MKW_GUEST_ADDRESS(8015D40C, 8015D36C, 8015D32C) // RVL::DSPCheckMailFromDSP
MKW_GUEST_ADDRESS(8015D41C, 8015D37C, 8015D33C) // RVL::DSPReadMailFromDSP
MKW_GUEST_ADDRESS(8015D430, 8015D390, 8015D350) // DSPSendMailToDSP
MKW_GUEST_ADDRESS(8015D444, 8015D3A4, 8015D364) // DSPInit
MKW_GUEST_ADDRESS(8015D504, 8015D464, 8015D424) // DSPCheckInit
MKW_GUEST_ADDRESS(8015D50C, 8015D46C, 8015D42C) // DSPAddTask
MKW_GUEST_ADDRESS(8015D57C, 8015D4DC, 8015D49C) // DSPAssertTask
MKW_GUEST_ADDRESS(8015DC60, 8015DBC0, 8015DB80) // __DSP_boot_task
MKW_GUEST_ADDRESS(8015DF1C, 8015DE7C, 8015DE3C) // __DVDFSInit
MKW_GUEST_ADDRESS(8015E74C, 8015E6AC, 8015E66C) // DVD::ReadAsyncPrio
MKW_GUEST_ADDRESS(8015E834, 8015E794, 8015E754) // DVDReadPrio
MKW_GUEST_ADDRESS(8015EA1C, 8015E97C, 8015E93C) // DVD::Init
MKW_GUEST_ADDRESS(801628CC, 8016282C, 801627EC) // DVD::ReadAbsAsyncPrio
MKW_GUEST_ADDRESS(801643FC, 8016435C, 8016431C) // __DVDCheckDevice
MKW_GUEST_ADDRESS(80164848, 801647A8, 80164768) // DVD::LowInit
MKW_GUEST_ADDRESS(80164AAC, 80164A0C, 801649CC) // DVD::LowReadDiskID
MKW_GUEST_ADDRESS(80165708, 80165668, 80165628) // DVD::LowUnencryptedRead
MKW_GUEST_ADDRESS(80165A30, 80165990, 80165950) // DVD::LowInquiry
MKW_GUEST_ADDRESS(80166330, 80166290, 80166250) // DVD::LowRead
MKW_GUEST_ADDRESS(80166964, 801668C4, 80166884) // DVD::LowClearCoverInterrupt
MKW_GUEST_ADDRESS(801671D0, 80167130, 801670F0) // ESP_InitLib
MKW_GUEST_ADDRESS(80167224, 80167184, 80167144) // ESP_CloseLib
MKW_GUEST_ADDRESS(80167904, 80167864, 80167824) // ESP_GetDataDir
MKW_GUEST_ADDRESS(8016799C, 801678FC, 801678BC) // ESP_GetTitleId
MKW_GUEST_ADDRESS(80167E78, 80167DD8, 80167D98) // SetExiInterruptMask
MKW_GUEST_ADDRESS(80167F68, 80167EC8, 80167E88) // RVL::EXIImm
MKW_GUEST_ADDRESS(80168288, 801681E8, 801681A8) // RVL::EXIDma
MKW_GUEST_ADDRESS(80168380, 801682E0, 801682A0) // RVL::EXISync
MKW_GUEST_ADDRESS(801689D0, 80168930, 801688F0) // RVL::EXISelect
MKW_GUEST_ADDRESS(80168B00, 80168A60, 80168A20) // RVL::EXIDeselect
MKW_GUEST_ADDRESS(80168FA0, 80168F00, 80168EC0) // EXIInit
MKW_GUEST_ADDRESS(80169260, 801691C0, 80169180) // RVL::EXIUnlock
MKW_GUEST_ADDRESS(80169BCC, 80169B2C, 80169AEC) // ISFS::OpenLib
MKW_GUEST_ADDRESS(8016B49C, 8016B3FC, 8016B3BC) // __GX::DefaultTexRegionCallback
MKW_GUEST_ADDRESS(8016B590, 8016B4F0, 8016B4B0) // __GX::DefaultTlutRegionCallback
MKW_GUEST_ADDRESS(8016B5B4, 8016B514, 8016B4D4) // __GX::Shutdown
MKW_GUEST_ADDRESS(8016B720, 8016B680, 8016B640) // __GX::InitRevisionBits
MKW_GUEST_ADDRESS(8016B850, 8016B7B0, 8016B770) // GX::Init
MKW_GUEST_ADDRESS(8016C668, 8016C5C8, 8016C588) // GX::CPInterruptHandler
MKW_GUEST_ADDRESS(8016C94C, 8016C8AC, 8016C86C) // GX::SetCPUFifo
MKW_GUEST_ADDRESS(8016CB2C, 8016CA8C, 8016CA4C) // GX::SetGPFifo
MKW_GUEST_ADDRESS(8016CDBC, 8016CD1C, 8016CCDC) // __GX::SaveFifo
MKW_GUEST_ADDRESS(8016CF10, 8016CE70, 8016CE30) // GX::GetCPUFifo
MKW_GUEST_ADDRESS(8016D054, 8016CFB4, 8016CF74) // GX::SetBreakPtCallback
MKW_GUEST_ADDRESS(8016D098, 8016CFF8, 8016CFB8) // GX::EnableBreakPt
MKW_GUEST_ADDRESS(8016D138, 8016D098, 8016D058) // GX::DisableBreakPt
MKW_GUEST_ADDRESS(8016D1FC, 8016D15C, 8016D11C) // __GX::CleanGPFifo
MKW_GUEST_ADDRESS(8016D3A4, 8016D304, 8016D2C4) // GX::SetVtxDesc
MKW_GUEST_ADDRESS(8016D3C0, 8016D320, 8016D2E0) // GX::SetVtxDesc_switch
MKW_GUEST_ADDRESS(8016D3C4, 8016D324, 8016D2E4) // GX::SetVtxDesc_caseD_0
MKW_GUEST_ADDRESS(8016D3D8, 8016D338, 8016D2F8) // GX::SetVtxDesc_caseD_1
MKW_GUEST_ADDRESS(8016D3EC, 8016D34C, 8016D30C) // GX::SetVtxDesc_caseD_2
MKW_GUEST_ADDRESS(8016D400, 8016D360, 8016D320) // GX::SetVtxDesc_caseD_3
MKW_GUEST_ADDRESS(8016D414, 8016D374, 8016D334) // GX::SetVtxDesc_caseD_4
MKW_GUEST_ADDRESS(8016D428, 8016D388, 8016D348) // GX::SetVtxDesc_caseD_5
MKW_GUEST_ADDRESS(8016D43C, 8016D39C, 8016D35C) // GX::SetVtxDesc_caseD_6
MKW_GUEST_ADDRESS(8016D450, 8016D3B0, 8016D370) // GX::SetVtxDesc_caseD_7
MKW_GUEST_ADDRESS(8016D464, 8016D3C4, 8016D384) // GX::SetVtxDesc_caseD_8
MKW_GUEST_ADDRESS(8016D478, 8016D3D8, 8016D398) // GX::SetVtxDesc_caseD_9
MKW_GUEST_ADDRESS(8016D48C, 8016D3EC, 8016D3AC) // GX::SetVtxDesc_caseD_a
MKW_GUEST_ADDRESS(8016D4C0, 8016D420, 8016D3E0) // GX::SetVtxDesc_caseD_19
MKW_GUEST_ADDRESS(8016D4F4, 8016D454, 8016D414) // GX::SetVtxDesc_caseD_b
MKW_GUEST_ADDRESS(8016D508, 8016D468, 8016D428) // GX::SetVtxDesc_caseD_c
MKW_GUEST_ADDRESS(8016D51C, 8016D47C, 8016D43C) // GX::SetVtxDesc_caseD_d
MKW_GUEST_ADDRESS(8016D530, 8016D490, 8016D450) // GX::SetVtxDesc_caseD_e
MKW_GUEST_ADDRESS(8016D544, 8016D4A4, 8016D464) // GX::SetVtxDesc_caseD_f
MKW_GUEST_ADDRESS(8016D558, 8016D4B8, 8016D478) // GX::SetVtxDesc_caseD_10
MKW_GUEST_ADDRESS(8016D56C, 8016D4CC, 8016D48C) // GX::SetVtxDesc_caseD_11
MKW_GUEST_ADDRESS(8016D580, 8016D4E0, 8016D4A0) // GX::SetVtxDesc_caseD_12
MKW_GUEST_ADDRESS(8016D594, 8016D4F4, 8016D4B4) // GX::SetVtxDesc_caseD_13
MKW_GUEST_ADDRESS(8016D5A8, 8016D508, 8016D4C8) // GX::SetVtxDesc_caseD_14
MKW_GUEST_ADDRESS(8016D5B8, 8016D518, 8016D4D8) // GX::SetVtxDesc_caseD_15
MKW_GUEST_ADDRESS(8016D608, 8016D568, 8016D528) // GX::SetVtxDescv
MKW_GUEST_ADDRESS(8016D814, 8016D774, 8016D734) // __GX::SetVCD
MKW_GUEST_ADDRESS(8016D8C4, 8016D824, 8016D7E4) // __GX::CalculateVLim
MKW_GUEST_ADDRESS(8016D9F0, 8016D950, 8016D910) // GX::GetVtxDesc
MKW_GUEST_ADDRESS(8016DA0C, 8016D96C, 8016D92C) // GX::GetVtxDesc_switch
MKW_GUEST_ADDRESS(8016DA10, 8016D970, 8016D930) // GX::GetVtxDesc_caseD_0
MKW_GUEST_ADDRESS(8016DA20, 8016D980, 8016D940) // GX::GetVtxDesc_caseD_1
MKW_GUEST_ADDRESS(8016DA30, 8016D990, 8016D950) // GX::GetVtxDesc_caseD_2
MKW_GUEST_ADDRESS(8016DA40, 8016D9A0, 8016D960) // GX::GetVtxDesc_caseD_3
MKW_GUEST_ADDRESS(8016DA50, 8016D9B0, 8016D970) // GX::GetVtxDesc_caseD_4
MKW_GUEST_ADDRESS(8016DA60, 8016D9C0, 8016D980) // GX::GetVtxDesc_caseD_5
MKW_GUEST_ADDRESS(8016DA70, 8016D9D0, 8016D990) // GX::GetVtxDesc_caseD_6
MKW_GUEST_ADDRESS(8016DA80, 8016D9E0, 8016D9A0) // GX::GetVtxDesc_caseD_7
MKW_GUEST_ADDRESS(8016DA90, 8016D9F0, 8016D9B0) // GX::GetVtxDesc_caseD_8
MKW_GUEST_ADDRESS(8016DAA0, 8016DA00, 8016D9C0) // GX::GetVtxDesc_caseD_9
MKW_GUEST_ADDRESS(8016DAB0, 8016DA10, 8016D9D0) // GX::GetVtxDesc_caseD_a
MKW_GUEST_ADDRESS(8016DAD4, 8016DA34, 8016D9F4) // GX::GetVtxDesc_caseD_19
MKW_GUEST_ADDRESS(8016DAF8, 8016DA58, 8016DA18) // GX::GetVtxDesc_caseD_b
MKW_GUEST_ADDRESS(8016DB08, 8016DA68, 8016DA28) // GX::GetVtxDesc_caseD_c
MKW_GUEST_ADDRESS(8016DB18, 8016DA78, 8016DA38) // GX::GetVtxDesc_caseD_d
MKW_GUEST_ADDRESS(8016DB28, 8016DA88, 8016DA48) // GX::GetVtxDesc_caseD_e
MKW_GUEST_ADDRESS(8016DB38, 8016DA98, 8016DA58) // GX::GetVtxDesc_caseD_f
MKW_GUEST_ADDRESS(8016DB48, 8016DAA8, 8016DA68) // GX::GetVtxDesc_caseD_10
MKW_GUEST_ADDRESS(8016DB58, 8016DAB8, 8016DA78) // GX::GetVtxDesc_caseD_11
MKW_GUEST_ADDRESS(8016DB68, 8016DAC8, 8016DA88) // GX::GetVtxDesc_caseD_12
MKW_GUEST_ADDRESS(8016DB78, 8016DAD8, 8016DA98) // GX::GetVtxDesc_caseD_13
MKW_GUEST_ADDRESS(8016DB88, 8016DAE8, 8016DAA8) // GX::GetVtxDesc_caseD_14
MKW_GUEST_ADDRESS(8016DB98, 8016DAF8, 8016DAB8) // GX::GetVtxDesc_caseD_15
MKW_GUEST_ADDRESS(8016DBA4, 8016DB04, 8016DAC4) // GX::GetVtxDescv
MKW_GUEST_ADDRESS(8016DC34, 8016DB94, 8016DB54) // GX::ClearVtxDesc
MKW_GUEST_ADDRESS(8016DC68, 8016DBC8, 8016DB88) // GX::SetVtxAttrFmt
MKW_GUEST_ADDRESS(8016DC94, 8016DBF4, 8016DBB4) // GX::SetVtxAttrFmt_switch
MKW_GUEST_ADDRESS(8016DC98, 8016DBF8, 8016DBB8) // GX::SetVtxAttrFmt_caseD_9
MKW_GUEST_ADDRESS(8016DCB0, 8016DC10, 8016DBD0) // GX::SetVtxAttrFmt_caseD_19
MKW_GUEST_ADDRESS(8016DCEC, 8016DC4C, 8016DC0C) // GX::SetVtxAttrFmt_caseD_b
MKW_GUEST_ADDRESS(8016DD00, 8016DC60, 8016DC20) // GX::SetVtxAttrFmt_caseD_c
MKW_GUEST_ADDRESS(8016DD14, 8016DC74, 8016DC34) // GX::SetVtxAttrFmt_caseD_d
MKW_GUEST_ADDRESS(8016DD2C, 8016DC8C, 8016DC4C) // GX::SetVtxAttrFmt_caseD_e
MKW_GUEST_ADDRESS(8016DD44, 8016DCA4, 8016DC64) // GX::SetVtxAttrFmt_caseD_f
MKW_GUEST_ADDRESS(8016DD5C, 8016DCBC, 8016DC7C) // GX::SetVtxAttrFmt_caseD_10
MKW_GUEST_ADDRESS(8016DD74, 8016DCD4, 8016DC94) // GX::SetVtxAttrFmt_caseD_11
MKW_GUEST_ADDRESS(8016DD94, 8016DCF4, 8016DCB4) // GX::SetVtxAttrFmt_caseD_12
MKW_GUEST_ADDRESS(8016DDAC, 8016DD0C, 8016DCCC) // GX::SetVtxAttrFmt_caseD_13
MKW_GUEST_ADDRESS(8016DDC4, 8016DD24, 8016DCE4) // GX::SetVtxAttrFmt_caseD_14
MKW_GUEST_ADDRESS(8016DDD8, 8016DD38, 8016DCF8) // GX::SetVtxAttrFmt_caseD_15
MKW_GUEST_ADDRESS(8016DE08, 8016DD68, 8016DD28) // GX::SetVtxAttrFmtv
MKW_GUEST_ADDRESS(8016DE48, 8016DDA8, 8016DD68) // GX::SetVtxAttrFmtv_switch
MKW_GUEST_ADDRESS(8016DE4C, 8016DDAC, 8016DD6C) // GX::SetVtxAttrFmtv_caseD_9
MKW_GUEST_ADDRESS(8016DE64, 8016DDC4, 8016DD84) // GX::SetVtxAttrFmtv_caseD_19
MKW_GUEST_ADDRESS(8016DEA0, 8016DE00, 8016DDC0) // GX::SetVtxAttrFmtv_caseD_b
MKW_GUEST_ADDRESS(8016DEB4, 8016DE14, 8016DDD4) // GX::SetVtxAttrFmtv_caseD_c
MKW_GUEST_ADDRESS(8016DEC8, 8016DE28, 8016DDE8) // GX::SetVtxAttrFmtv_caseD_d
MKW_GUEST_ADDRESS(8016DEE0, 8016DE40, 8016DE00) // GX::SetVtxAttrFmtv_caseD_e
MKW_GUEST_ADDRESS(8016DEF8, 8016DE58, 8016DE18) // GX::SetVtxAttrFmtv_caseD_f
MKW_GUEST_ADDRESS(8016DF10, 8016DE70, 8016DE30) // GX::SetVtxAttrFmtv_caseD_10
MKW_GUEST_ADDRESS(8016DF28, 8016DE88, 8016DE48) // GX::SetVtxAttrFmtv_caseD_11
MKW_GUEST_ADDRESS(8016DF48, 8016DEA8, 8016DE68) // GX::SetVtxAttrFmtv_caseD_12
MKW_GUEST_ADDRESS(8016DF60, 8016DEC0, 8016DE80) // GX::SetVtxAttrFmtv_caseD_13
MKW_GUEST_ADDRESS(8016DF78, 8016DED8, 8016DE98) // GX::SetVtxAttrFmtv_caseD_14
MKW_GUEST_ADDRESS(8016DF8C, 8016DEEC, 8016DEAC) // GX::SetVtxAttrFmtv_caseD_15
MKW_GUEST_ADDRESS(8016DFCC, 8016DF2C, 8016DEEC) // __GX::SetVAT
MKW_GUEST_ADDRESS(8016E04C, 8016DFAC, 8016DF6C) // GX::GetVtxAttrFmt
MKW_GUEST_ADDRESS(8016E078, 8016DFD8, 8016DF98) // GX::GetVtxAttrFmt_switch
MKW_GUEST_ADDRESS(8016E07C, 8016DFDC, 8016DF9C) // GX::GetVtxAttrFmt_caseD_9
MKW_GUEST_ADDRESS(8016E0A4, 8016E004, 8016DFC4) // GX::GetVtxAttrFmt_caseD_19
MKW_GUEST_ADDRESS(8016E118, 8016E078, 8016E038) // GX::GetVtxAttrFmt_caseD_b
MKW_GUEST_ADDRESS(8016E13C, 8016E09C, 8016E05C) // GX::GetVtxAttrFmt_caseD_c
MKW_GUEST_ADDRESS(8016E160, 8016E0C0, 8016E080) // GX::GetVtxAttrFmt_caseD_d
MKW_GUEST_ADDRESS(8016E188, 8016E0E8, 8016E0A8) // GX::GetVtxAttrFmt_caseD_e
MKW_GUEST_ADDRESS(8016E1B0, 8016E110, 8016E0D0) // GX::GetVtxAttrFmt_caseD_f
MKW_GUEST_ADDRESS(8016E1C4, 8016E124, 8016E0E4)
MKW_GUEST_ADDRESS(8016E1D8, 8016E138, 8016E0F8) // GX::GetVtxAttrFmt_caseD_10
MKW_GUEST_ADDRESS(8016E200, 8016E160, 8016E120) // GX::GetVtxAttrFmt_caseD_11
MKW_GUEST_ADDRESS(8016E228, 8016E188, 8016E148) // GX::GetVtxAttrFmt_caseD_12
MKW_GUEST_ADDRESS(8016E250, 8016E1B0, 8016E170) // GX::GetVtxAttrFmt_caseD_13
MKW_GUEST_ADDRESS(8016E278, 8016E1D8, 8016E198) // GX::GetVtxAttrFmt_caseD_14
MKW_GUEST_ADDRESS(8016E2A0, 8016E200, 8016E1C0) // GX::GetVtxAttrFmt_caseD_15
MKW_GUEST_ADDRESS(8016E2B8, 8016E218, 8016E1D8) // GX::GetVtxAttrFmtv
MKW_GUEST_ADDRESS(8016E32C, 8016E28C, 8016E24C) // GX::SetArray
MKW_GUEST_ADDRESS(8016E37C, 8016E2DC, 8016E29C) // GX::SetTexCoordGen2
MKW_GUEST_ADDRESS(8016E3A4, 8016E304, 8016E2C4) // GX::SetTexCoordGen2_switch
MKW_GUEST_ADDRESS(8016E3A8, 8016E308, 8016E2C8) // GX::SetTexCoordGen2_caseD_0
MKW_GUEST_ADDRESS(8016E3B4, 8016E314, 8016E2D4) // GX::SetTexCoordGen2_caseD_1
MKW_GUEST_ADDRESS(8016E3C0, 8016E320, 8016E2E0) // GX::SetTexCoordGen2_caseD_2
MKW_GUEST_ADDRESS(8016E3CC, 8016E32C, 8016E2EC) // GX::SetTexCoordGen2_caseD_3
MKW_GUEST_ADDRESS(8016E3D8, 8016E338, 8016E2F8) // GX::SetTexCoordGen2_caseD_13
MKW_GUEST_ADDRESS(8016E3E0, 8016E340, 8016E300) // GX::SetTexCoordGen2_caseD_14
MKW_GUEST_ADDRESS(8016E3E8, 8016E348, 8016E308) // GX::SetTexCoordGen2_caseD_4
MKW_GUEST_ADDRESS(8016E3F0, 8016E350, 8016E310) // GX::SetTexCoordGen2_caseD_5
MKW_GUEST_ADDRESS(8016E3F8, 8016E358, 8016E318) // GX::SetTexCoordGen2_caseD_6
MKW_GUEST_ADDRESS(8016E400, 8016E360, 8016E320) // GX::SetTexCoordGen2_caseD_7
MKW_GUEST_ADDRESS(8016E408, 8016E368, 8016E328) // GX::SetTexCoordGen2_caseD_8
MKW_GUEST_ADDRESS(8016E410, 8016E370, 8016E330) // GX::SetTexCoordGen2_caseD_9
MKW_GUEST_ADDRESS(8016E418, 8016E378, 8016E338) // GX::SetTexCoordGen2_caseD_a
MKW_GUEST_ADDRESS(8016E420, 8016E380, 8016E340) // GX::SetTexCoordGen2_caseD_b
MKW_GUEST_ADDRESS(8016E424, 8016E384, 8016E344) // GX::SetTexCoordGen2_caseD_10
MKW_GUEST_ADDRESS(8016E514, 8016E474, 8016E434) // GX::SetTexCoordGen2_switch
MKW_GUEST_ADDRESS(8016E518, 8016E478, 8016E438) // GX::SetTexCoordGen2_caseD_0
MKW_GUEST_ADDRESS(8016E528, 8016E488, 8016E448) // GX::SetTexCoordGen2_caseD_1
MKW_GUEST_ADDRESS(8016E538, 8016E498, 8016E458) // GX::SetTexCoordGen2_caseD_2
MKW_GUEST_ADDRESS(8016E548, 8016E4A8, 8016E468) // GX::SetTexCoordGen2_caseD_3
MKW_GUEST_ADDRESS(8016E558, 8016E4B8, 8016E478) // GX::SetTexCoordGen2_caseD_4
MKW_GUEST_ADDRESS(8016E568, 8016E4C8, 8016E488) // GX::SetTexCoordGen2_caseD_5
MKW_GUEST_ADDRESS(8016E578, 8016E4D8, 8016E498) // GX::SetTexCoordGen2_caseD_6
MKW_GUEST_ADDRESS(8016E588, 8016E4E8, 8016E4A8)
MKW_GUEST_ADDRESS(8016E5A4, 8016E504, 8016E4C4) // GX::SetNumTexGens
MKW_GUEST_ADDRESS(8016E654, 8016E5B4, 8016E574) // GX::Flush
MKW_GUEST_ADDRESS(8016E6B0, 8016E610, 8016E5D0) // GX::ResetWriteGatherPipe
MKW_GUEST_ADDRESS(8016E6E4, 8016E644, 8016E604) // __GX::Abort
MKW_GUEST_ADDRESS(8016E848, 8016E7A8, 8016E768) // GX::AbortFrame
MKW_GUEST_ADDRESS(8016E9FC, 8016E95C, 8016E91C) // GX::SetDrawSync
MKW_GUEST_ADDRESS(8016EAB0, 8016EA10, 8016E9D0) // GX::DrawDone
MKW_GUEST_ADDRESS(8016EB70, 8016EAD0, 8016EA90) // GX::PixModeSync
MKW_GUEST_ADDRESS(8016EB94, 8016EAF4, 8016EAB4) // GX::PokeAlphaMode
MKW_GUEST_ADDRESS(8016EBA4, 8016EB04, 8016EAC4) // GX::PokeAlphaUpdate
MKW_GUEST_ADDRESS(8016EBBC, 8016EB1C, 8016EADC) // GX::PokeAlphaUpdate
MKW_GUEST_ADDRESS(8016EBD0, 8016EB30, 8016EAF0) // GX::PokeBlendMode
MKW_GUEST_ADDRESS(8016EC2C, 8016EB8C, 8016EB4C) // GX::PokeColorUpdate
MKW_GUEST_ADDRESS(8016EC40, 8016EBA0, 8016EB60) // GX::PokeDstAlpha
MKW_GUEST_ADDRESS(8016EC58, 8016EBB8, 8016EB78) // GX::PokeDither
MKW_GUEST_ADDRESS(8016EC6C, 8016EBCC, 8016EB8C) // GX::PokeZMode
MKW_GUEST_ADDRESS(8016ECCC, 8016EC2C, 8016EBEC) // GX::TokenInterruptHandler
MKW_GUEST_ADDRESS(8016ED50, 8016ECB0, 8016EC70) // GX::SetDrawDoneCallback
MKW_GUEST_ADDRESS(8016ED94, 8016ECF4, 8016ECB4) // GX::FinishInterruptHandler
MKW_GUEST_ADDRESS(8016EE14, 8016ED74, 8016ED34) // __GX::PEInit
MKW_GUEST_ADDRESS(8016EE78, 8016EDD8, 8016ED98) // __GX::SetDirtyState
MKW_GUEST_ADDRESS(8016F0F0, 8016F050, 8016F010) // GX::Begin
MKW_GUEST_ADDRESS(8016F23C, 8016F19C, 8016F15C) // __GX::SendFlushPrim
MKW_GUEST_ADDRESS(8016F314, 8016F274, 8016F234) // GX::SetLineWidth
MKW_GUEST_ADDRESS(8016F348, 8016F2A8, 8016F268) // GX::SetPointSize
MKW_GUEST_ADDRESS(8016F37C, 8016F2DC, 8016F29C) // GX::EnableTexOffsets
MKW_GUEST_ADDRESS(8016F3B8, 8016F318, 8016F2D8) // GX::SetCullMode
MKW_GUEST_ADDRESS(8016F3E0, 8016F340, 8016F300) // GX::SetCoPlanar
MKW_GUEST_ADDRESS(8016F414, 8016F374, 8016F334) // __GX::SetGenMode
MKW_GUEST_ADDRESS(8016F438, 8016F398, 8016F358) // GX::SetDispCopySrc
MKW_GUEST_ADDRESS(8016F478, 8016F3D8, 8016F398) // GX::SetTexCopySrc
MKW_GUEST_ADDRESS(8016F4B8, 8016F418, 8016F3D8) // GX::SetDispCopyDst
MKW_GUEST_ADDRESS(8016F4DC, 8016F43C, 8016F3FC) // GX::SetTexCopyDst
MKW_GUEST_ADDRESS(8016F5F8, 8016F558, 8016F518) // GX::SetDispCopyFrame2Field
MKW_GUEST_ADDRESS(8016F618, 8016F578, 8016F538) // GX::SetCopyClamp
MKW_GUEST_ADDRESS(8016FA40, 8016F9A0, 8016F960) // GX::SetCopyFilter
MKW_GUEST_ADDRESS(8016FC24, 8016FB84, 8016FB44) // GX::SetDispCopyGamma
MKW_GUEST_ADDRESS(8016FC38, 8016FB98, 8016FB58) // GX::CopyDisp
MKW_GUEST_ADDRESS(8016FD74, 8016FCD4, 8016FC94) // GX::CopyTex
MKW_GUEST_ADDRESS(8016FECC, 8016FE2C, 8016FDEC) // GX::ClearBoundingBox
MKW_GUEST_ADDRESS(80170320, 80170280, 80170240) // GX::LoadLightObjImm
MKW_GUEST_ADDRESS(8017039C, 801702FC, 801702BC) // GX::SetChanAmbColor
MKW_GUEST_ADDRESS(80170474, 801703D4, 80170394) // GX::SetChanMatColor
MKW_GUEST_ADDRESS(8017054C, 801704AC, 8017046C) // GX::SetNumChans
MKW_GUEST_ADDRESS(80170570, 801704D0, 80170490) // GX::SetChanCtrl
MKW_GUEST_ADDRESS(801707F8, 80170758, 80170718) // GX::InitTexObj
MKW_GUEST_ADDRESS(80170938, 80170898, 80170858) // GX::InitTexObj_switch
MKW_GUEST_ADDRESS(8017093C, 8017089C, 8017085C) // GX::InitTexObj_caseD_0
MKW_GUEST_ADDRESS(80170950, 801708B0, 80170870) // GX::InitTexObj_caseD_1
MKW_GUEST_ADDRESS(80170964, 801708C4, 80170884) // GX::InitTexObj_caseD_3
MKW_GUEST_ADDRESS(80170978, 801708D8, 80170898) // GX::InitTexObj_caseD_6
MKW_GUEST_ADDRESS(8017098C, 801708EC, 801708AC) // GX::InitTexObj_caseD_e
MKW_GUEST_ADDRESS(801709A0, 80170900, 801708C0) // GX::InitTexObj_caseD_7
MKW_GUEST_ADDRESS(80170A04, 80170964, 80170924) // GX::InitTexObjCI
MKW_GUEST_ADDRESS(80170A4C, 801709AC, 8017096C) // GX::InitTexObjLOD
MKW_GUEST_ADDRESS(80170B50, 80170AB0, 80170A70) // GX::InitTexObjWrapMode
MKW_GUEST_ADDRESS(80170B64, 80170AC4, 80170A84) // GX::InitTexObjTlut
MKW_GUEST_ADDRESS(80170B6C, 80170ACC, 80170A8C) // GX::InitTexObjFilter
MKW_GUEST_ADDRESS(80170B94, 80170AF4, 80170AB4) // GX::InitTexObjLODBias
MKW_GUEST_ADDRESS(80170BE8, 80170B48, 80170B08) // GX::InitTexObjUserData
MKW_GUEST_ADDRESS(80170BF8, 80170B58, 80170B18) // GX::GetTexObjAll
MKW_GUEST_ADDRESS(80170CBC, 80170C1C, 80170BDC) // GX::GetTexObjLODAll
MKW_GUEST_ADDRESS(80170DC8, 80170D28, 80170CE8) // GX::LoadTexObjPreLoaded
MKW_GUEST_ADDRESS(80170F2C, 80170E8C, 80170E4C) // GX::LoadTexObj
MKW_GUEST_ADDRESS(80170F80, 80170EE0, 80170EA0) // GX::InitTlutObj
MKW_GUEST_ADDRESS(80170FA8, 80170F08, 80170EC8) // GX::LoadTlut
MKW_GUEST_ADDRESS(80171110, 80171070, 80171030) // GX::InvalidateTexAll
MKW_GUEST_ADDRESS(80171180, 801710E0, 801710A0) // GX::SetTexCoordScaleManually
MKW_GUEST_ADDRESS(801711FC, 8017115C, 8017111C) // GX::SetTexCoordBias
MKW_GUEST_ADDRESS(801712F0, 80171250, 80171210) // __GX::SetSUTexRegs
MKW_GUEST_ADDRESS(80171458, 801713B8, 80171378) // __GX::SetTmemConfig
MKW_GUEST_ADDRESS(801717AC, 8017170C, 801716CC) // GX::SetTevIndirect
MKW_GUEST_ADDRESS(80171814, 80171774, 80171734) // GX::SetIndTexMtx
MKW_GUEST_ADDRESS(80171968, 801718C8, 80171888) // GX::SetIndTexCoordScale
MKW_GUEST_ADDRESS(80171A6C, 801719CC, 8017198C) // GX::SetIndTexOrder
MKW_GUEST_ADDRESS(80171B38, 80171A98, 80171A58) // GX::SetNumIndStages
MKW_GUEST_ADDRESS(80171B58, 80171AB8, 80171A78) // GX::SetTevDirect
MKW_GUEST_ADDRESS(80171BA0, 80171B00, 80171AC0) // GX::SetTevIndWarp
MKW_GUEST_ADDRESS(80171C28, 80171B88, 80171B48) // __GX::FlushTextureState
MKW_GUEST_ADDRESS(80171C4C, 80171BAC, 80171B6C) // GX::SetTevOp
MKW_GUEST_ADDRESS(80171CE0, 80171C40, 80171C00) // GX::SetTevColorIn
MKW_GUEST_ADDRESS(80171D20, 80171C80, 80171C40) // GX::SetTevAlphaIn
MKW_GUEST_ADDRESS(80171D60, 80171CC0, 80171C80) // GX::SetTevColorOp
MKW_GUEST_ADDRESS(80171DB8, 80171D18, 80171CD8) // GX::SetTevAlphaOp
MKW_GUEST_ADDRESS(80171E10, 80171D70, 80171D30) // GX::SetTevColor
MKW_GUEST_ADDRESS(80171E70, 80171DD0, 80171D90) // GX::SetTevColorS10
MKW_GUEST_ADDRESS(80171ED4, 80171E34, 80171DF4) // GX::SetTevKColor
MKW_GUEST_ADDRESS(80171F30, 80171E90, 80171E50) // GX::SetTevKColorSel
MKW_GUEST_ADDRESS(80171F80, 80171EE0, 80171EA0) // GX::SetTevKAlphaSel
MKW_GUEST_ADDRESS(80171FD0, 80171F30, 80171EF0) // GX::SetTevSwapMode
MKW_GUEST_ADDRESS(8017200C, 80171F6C, 80171F2C) // GX::SetTevSwapModeTable
MKW_GUEST_ADDRESS(80172088, 80171FE8, 80171FA8) // GX::SetAlphaCompare
MKW_GUEST_ADDRESS(801720C0, 80172020, 80171FE0) // GX::SetZTexture
MKW_GUEST_ADDRESS(8017214C, 801720AC, 8017206C) // GX::SetTevOrder
MKW_GUEST_ADDRESS(801722A8, 80172208, 801721C8) // GX::SetNumTevStages
MKW_GUEST_ADDRESS(801722CC, 8017222C, 801721EC) // GX::SetFog
MKW_GUEST_ADDRESS(801724F8, 80172458, 80172418) // GX::InitFogAdjTable
MKW_GUEST_ADDRESS(8017277C, 801726DC, 8017269C) // GX::SetBlendMode
MKW_GUEST_ADDRESS(801727CC, 8017272C, 801726EC) // GX::SetColorUpdate
MKW_GUEST_ADDRESS(801727F8, 80172758, 80172718) // GX::SetAlphaUpdate
MKW_GUEST_ADDRESS(80172824, 80172784, 80172744) // GX::SetZMode
MKW_GUEST_ADDRESS(80172858, 801727B8, 80172778) // GX::SetZCompLoc
MKW_GUEST_ADDRESS(80172888, 801727E8, 801727A8) // GX::SetPixelFmt
MKW_GUEST_ADDRESS(80172930, 80172890, 80172850) // GX::SetDither
MKW_GUEST_ADDRESS(8017295C, 801728BC, 8017287C) // GX::SetDstAlpha
MKW_GUEST_ADDRESS(80172A30, 80172990, 80172950) // GX::DrawSphere
MKW_GUEST_ADDRESS(80172E00, 80172D60, 80172D20) // GX::BeginDisplayList
MKW_GUEST_ADDRESS(80172EB4, 80172E14, 80172DD4) // GX::EndDisplayList
MKW_GUEST_ADDRESS(80172F64, 80172EC4, 80172E84) // GX::CallDisplayList
MKW_GUEST_ADDRESS(80172FD8, 80172F38, 80172EF8) // __GX::SetProjection
MKW_GUEST_ADDRESS(8017301C, 80172F7C, 80172F3C) // GX::SetProjection
MKW_GUEST_ADDRESS(80173080, 80172FE0, 80172FA0) // GX::SetProjectionv
MKW_GUEST_ADDRESS(801730CC, 8017302C, 80172FEC) // GX::GetProjectionv
MKW_GUEST_ADDRESS(8017310C, 8017306C, 8017302C) // GX::LoadPosMtxImm
MKW_GUEST_ADDRESS(8017315C, 801730BC, 8017307C) // GX::LoadPosMtxIndx
MKW_GUEST_ADDRESS(80173188, 801730E8, 801730A8) // GX::LoadNrmMtxImm
MKW_GUEST_ADDRESS(801731E0, 80173140, 80173100) // GX::LoadNrmMtxIndx3x3
MKW_GUEST_ADDRESS(80173214, 80173174, 80173134) // GX::SetCurrentMtx
MKW_GUEST_ADDRESS(80173234, 80173194, 80173154) // GX::LoadTexMtxImm
MKW_GUEST_ADDRESS(801732E8, 80173248, 80173208) // __GX::SetViewport
MKW_GUEST_ADDRESS(80173378, 801732D8, 80173298) // GX::SetViewportJitter
MKW_GUEST_ADDRESS(801733B4, 80173314, 801732D4) // GX::SetViewport
MKW_GUEST_ADDRESS(801733E0, 80173340, 80173300) // GX::GetViewportv
MKW_GUEST_ADDRESS(80173400, 80173360, 80173320) // GX::SetZScaleOffset
MKW_GUEST_ADDRESS(80173430, 80173390, 80173350) // GX::SetScissor
MKW_GUEST_ADDRESS(801734E0, 80173440, 80173400) // GX::SetScissorBoxOffset
MKW_GUEST_ADDRESS(8017351C, 8017347C, 8017343C) // GX::SetClipMode
MKW_GUEST_ADDRESS(80173544, 801734A4, 80173464) // __GX::SetMatrixIndex
MKW_GUEST_ADDRESS(801735CC, 8017352C, 801734EC) // GX::SetGPMetric
MKW_GUEST_ADDRESS(801736F4, 80173654, 80173614) // GX::SetGPMetric_switch
MKW_GUEST_ADDRESS(801736F8, 80173658, 80173618) // GX::SetGPMetric_caseD_0
MKW_GUEST_ADDRESS(80173718, 80173678, 80173638) // GX::SetGPMetric_caseD_1
MKW_GUEST_ADDRESS(80173738, 80173698, 80173658) // GX::SetGPMetric_caseD_2
MKW_GUEST_ADDRESS(80173758, 801736B8, 80173678) // GX::SetGPMetric_caseD_3
MKW_GUEST_ADDRESS(80173778, 801736D8, 80173698) // GX::SetGPMetric_caseD_4
MKW_GUEST_ADDRESS(80173798, 801736F8, 801736B8) // GX::SetGPMetric_caseD_5
MKW_GUEST_ADDRESS(801737B8, 80173718, 801736D8) // GX::SetGPMetric_caseD_6
MKW_GUEST_ADDRESS(801737D8, 80173738, 801736F8) // GX::SetGPMetric_caseD_7
MKW_GUEST_ADDRESS(801737F8, 80173758, 80173718) // GX::SetGPMetric_caseD_8
MKW_GUEST_ADDRESS(80173818, 80173778, 80173738) // GX::SetGPMetric_caseD_9
MKW_GUEST_ADDRESS(80173838, 80173798, 80173758) // GX::SetGPMetric_caseD_22
MKW_GUEST_ADDRESS(80173858, 801737B8, 80173778) // GX::SetGPMetric_caseD_a
MKW_GUEST_ADDRESS(80173878, 801737D8, 80173798) // GX::SetGPMetric_caseD_b
MKW_GUEST_ADDRESS(80173894, 801737F4, 801737B4) // GX::SetGPMetric_caseD_c
MKW_GUEST_ADDRESS(801738B0, 80173810, 801737D0) // GX::SetGPMetric_caseD_d
MKW_GUEST_ADDRESS(801738CC, 8017382C, 801737EC) // GX::SetGPMetric_caseD_e
MKW_GUEST_ADDRESS(801738E8, 80173848, 80173808) // GX::SetGPMetric_caseD_f
MKW_GUEST_ADDRESS(80173904, 80173864, 80173824) // GX::SetGPMetric_caseD_10
MKW_GUEST_ADDRESS(80173920, 80173880, 80173840) // GX::SetGPMetric_caseD_11
MKW_GUEST_ADDRESS(8017393C, 8017389C, 8017385C) // GX::SetGPMetric_caseD_12
MKW_GUEST_ADDRESS(80173958, 801738B8, 80173878) // GX::SetGPMetric_caseD_13
MKW_GUEST_ADDRESS(80173974, 801738D4, 80173894) // GX::SetGPMetric_caseD_14
MKW_GUEST_ADDRESS(80173990, 801738F0, 801738B0) // GX::SetGPMetric_caseD_15
MKW_GUEST_ADDRESS(801739AC, 8017390C, 801738CC) // GX::SetGPMetric_caseD_16
MKW_GUEST_ADDRESS(801739C8, 80173928, 801738E8) // GX::SetGPMetric_caseD_17
MKW_GUEST_ADDRESS(801739E4, 80173944, 80173904) // GX::SetGPMetric_caseD_18
MKW_GUEST_ADDRESS(80173A00, 80173960, 80173920) // GX::SetGPMetric_caseD_19
MKW_GUEST_ADDRESS(80173A1C, 8017397C, 8017393C) // GX::SetGPMetric_caseD_1a
MKW_GUEST_ADDRESS(80173A38, 80173998, 80173958) // GX::SetGPMetric_caseD_1b
MKW_GUEST_ADDRESS(80173A54, 801739B4, 80173974) // GX::SetGPMetric_caseD_1c
MKW_GUEST_ADDRESS(80173A70, 801739D0, 80173990) // GX::SetGPMetric_caseD_1d
MKW_GUEST_ADDRESS(80173A8C, 801739EC, 801739AC) // GX::SetGPMetric_caseD_1e
MKW_GUEST_ADDRESS(80173AA8, 80173A08, 801739C8) // GX::SetGPMetric_caseD_1f
MKW_GUEST_ADDRESS(80173AC4, 80173A24, 801739E4) // GX::SetGPMetric_caseD_20
MKW_GUEST_ADDRESS(80173AE0, 80173A40, 80173A00) // GX::SetGPMetric_caseD_21
MKW_GUEST_ADDRESS(80173AF8, 80173A58, 80173A18) // GX::SetGPMetric_caseD_23
MKW_GUEST_ADDRESS(80173B18, 80173A78, 80173A38) // GX::SetGPMetric_switch
MKW_GUEST_ADDRESS(80173B1C, 80173A7C, 80173A3C) // GX::SetGPMetric_caseD_0
MKW_GUEST_ADDRESS(80173B38, 80173A98, 80173A58) // GX::SetGPMetric_caseD_1
MKW_GUEST_ADDRESS(80173B54, 80173AB4, 80173A74) // GX::SetGPMetric_caseD_2
MKW_GUEST_ADDRESS(80173B70, 80173AD0, 80173A90) // GX::SetGPMetric_caseD_3
MKW_GUEST_ADDRESS(80173B8C, 80173AEC, 80173AAC) // GX::SetGPMetric_caseD_8
MKW_GUEST_ADDRESS(80173BA8, 80173B08, 80173AC8) // GX::SetGPMetric_caseD_15
MKW_GUEST_ADDRESS(80173BC4, 80173B24, 80173AE4) // GX::SetGPMetric_caseD_4
MKW_GUEST_ADDRESS(80173BE0, 80173B40, 80173B00) // GX::SetGPMetric_caseD_5
MKW_GUEST_ADDRESS(80173BFC, 80173B5C, 80173B1C) // GX::SetGPMetric_caseD_6
MKW_GUEST_ADDRESS(80173C18, 80173B78, 80173B38) // GX::SetGPMetric_caseD_7
MKW_GUEST_ADDRESS(80173C34, 80173B94, 80173B54) // GX::SetGPMetric_caseD_9
MKW_GUEST_ADDRESS(80173C64, 80173BC4, 80173B84) // GX::SetGPMetric_caseD_a
MKW_GUEST_ADDRESS(80173C94, 80173BF4, 80173BB4) // GX::SetGPMetric_caseD_b
MKW_GUEST_ADDRESS(80173CC4, 80173C24, 80173BE4) // GX::SetGPMetric_caseD_c
MKW_GUEST_ADDRESS(80173CF4, 80173C54, 80173C14) // GX::SetGPMetric_caseD_d
MKW_GUEST_ADDRESS(80173D24, 80173C84, 80173C44) // GX::SetGPMetric_caseD_e
MKW_GUEST_ADDRESS(80173D54, 80173CB4, 80173C74) // GX::SetGPMetric_caseD_f
MKW_GUEST_ADDRESS(80173D84, 80173CE4, 80173CA4) // GX::SetGPMetric_caseD_10
MKW_GUEST_ADDRESS(80173DB0, 80173D10, 80173CD0) // GX::SetGPMetric_caseD_11
MKW_GUEST_ADDRESS(80173DC0, 80173D20, 80173CE0) // GX::SetGPMetric_caseD_12
MKW_GUEST_ADDRESS(80173DD0, 80173D30, 80173CF0) // GX::SetGPMetric_caseD_13
MKW_GUEST_ADDRESS(80173DE0, 80173D40, 80173D00) // GX::SetGPMetric_caseD_14
MKW_GUEST_ADDRESS(80173DEC, 80173D4C, 80173D0C) // GX::SetGPMetric_caseD_16
MKW_GUEST_ADDRESS(80173DF8, 80173D58, 80173D18) // GX::ClearGPMetric
MKW_GUEST_ADDRESS(80192F7C, 80192EDC, 80192E9C) // IPCInit
MKW_GUEST_ADDRESS(80193010, 80192F70, 80192F30) // IPCReadReg
MKW_GUEST_ADDRESS(80193020, 80192F80, 80192F40) // IPCWriteReg
MKW_GUEST_ADDRESS(80193040, 80192FA0, 80192F60) // IPCSetBufferLo
MKW_GUEST_ADDRESS(80193478, 801933D8, 80193398) // IPCCltInit
MKW_GUEST_ADDRESS(801937E0, 80193740, 80193700) // IOS::OpenAsync
MKW_GUEST_ADDRESS(801938F8, 80193858, 80193818) // IOS::Open
MKW_GUEST_ADDRESS(801938FC, 8019385C, 8019381C)
MKW_GUEST_ADDRESS(80193A18, 80193978, 80193938) // IOS::CloseAsync
MKW_GUEST_ADDRESS(80193AD8, 80193A38, 801939F8) // IOS::Close
MKW_GUEST_ADDRESS(80193B80, 80193AE0, 80193AA0) // IOS::ReadAsync
MKW_GUEST_ADDRESS(80193C80, 80193BE0, 80193BA0) // IOS::Read
MKW_GUEST_ADDRESS(80193D88, 80193CE8, 80193CA8) // IOS::WriteAsync
MKW_GUEST_ADDRESS(80193E88, 80193DE8, 80193DA8) // IOS::Write
MKW_GUEST_ADDRESS(80193F90, 80193EF0, 80193EB0) // IOS::SeekAsync
MKW_GUEST_ADDRESS(80194070, 80193FD0, 80193F90) // IOS::Seek
MKW_GUEST_ADDRESS(80194158, 801940B8, 80194078) // IOS::IoctlAsync
MKW_GUEST_ADDRESS(80194290, 801941F0, 801941B0) // IOS::Ioctl
MKW_GUEST_ADDRESS(801944FC, 8019445C, 8019441C) // IOS::IoctlvAsync
MKW_GUEST_ADDRESS(801945E0, 80194540, 80194500) // IOS::Ioctlv
MKW_GUEST_ADDRESS(801949B8, 80194918, 801948D8) // iosCreateHeap
MKW_GUEST_ADDRESS(8019B43C, 8019B39C, 8019B35C) // NAND::Create
MKW_GUEST_ADDRESS(8019B524, 8019B484, 8019B444) // NAND::Private::CreateAsync
MKW_GUEST_ADDRESS(8019B59C, 8019B4FC, 8019B4BC) // NAND::Delete
MKW_GUEST_ADDRESS(8019B6E4, 8019B644, 8019B604) // NAND::Private::DeleteAsync
MKW_GUEST_ADDRESS(8019B7A4, 8019B704, 8019B6C4) // NAND::Read
MKW_GUEST_ADDRESS(8019B80C, 8019B76C, 8019B72C) // NAND::ReadAsync
MKW_GUEST_ADDRESS(8019B884, 8019B7E4, 8019B7A4) // NAND::Write
MKW_GUEST_ADDRESS(8019B8EC, 8019B84C, 8019B80C) // NAND::WriteAsync
MKW_GUEST_ADDRESS(8019B964, 8019B8C4, 8019B884) // NAND::Seek
MKW_GUEST_ADDRESS(8019BA04, 8019B964, 8019B924) // NAND::SeekAsync
MKW_GUEST_ADDRESS(8019BBE0, 8019BB40, 8019BB00) // NAND::CreateDir
MKW_GUEST_ADDRESS(8019BCC8, 8019BC28, 8019BBE8) // NAND::Private::CreateDirAsync
MKW_GUEST_ADDRESS(8019BEE8, 8019BE48, 8019BE08) // NAND::Move
MKW_GUEST_ADDRESS(8019BF4C, 8019BEAC, 8019BE6C) // NAND::GetLength
MKW_GUEST_ADDRESS(8019C048, 8019BFA8, 8019BF68) // NAND::GetLengthAsync
MKW_GUEST_ADDRESS(8019C380, 8019C2E0, 8019C2A0) // NAND::GetStatus
MKW_GUEST_ADDRESS(8019C448, 8019C3A8, 8019C368) // NAND::Private::GetStatusAsync
MKW_GUEST_ADDRESS(8019C800, 8019C760, 8019C720) // NAND::Open
MKW_GUEST_ADDRESS(8019C918, 8019C878, 8019C838) // NAND::OpenAsync
MKW_GUEST_ADDRESS(8019C990, 8019C8F0, 8019C8B0) // NAND::Private::OpenAsync
MKW_GUEST_ADDRESS(8019CA80, 8019C9E0, 8019C9A0) // NAND::Close
MKW_GUEST_ADDRESS(8019CAEC, 8019CA4C, 8019CA0C) // NAND::CloseAsync
MKW_GUEST_ADDRESS(8019CB74, 8019CAD4, 8019CA94) // NAND::SafeOpen
MKW_GUEST_ADDRESS(8019CF28, 8019CE88, 8019CE48) // NAND::SafeClose
MKW_GUEST_ADDRESS(8019D104, 8019D064, 8019D024) // NAND::Private::SafeOpenAsync
MKW_GUEST_ADDRESS(8019D720, 8019D680, 8019D640) // NAND::SafeCloseAsync
MKW_GUEST_ADDRESS(8019E18C, 8019E0EC, 8019E0AC) // NAND::Init
MKW_GUEST_ADDRESS(8019E390, 8019E2F0, 8019E2B0) // NAND::GetCurrentDir
MKW_GUEST_ADDRESS(8019E770, 8019E6D0, 8019E690) // NAND::GetType
MKW_GUEST_ADDRESS(8019E7B4, 8019E714, 8019E6D4) // NAND::Private::GetTypeAsync
MKW_GUEST_ADDRESS(8019EAD0, 8019EA30, 8019E9F0) // NAND::Check
MKW_GUEST_ADDRESS(8019F33C, 8019F29C, 8019F25C) // OS::GetConsoleType
MKW_GUEST_ADDRESS(801A00E0, 801A0040, 801A0000) // OS::ExceptionInit
MKW_GUEST_ADDRESS(801A04A0, 801A0400, 801A03C0) // OS::__PSInit
MKW_GUEST_ADDRESS(801A0620, 801A0580, 801A0540) // InsertAlarm
MKW_GUEST_ADDRESS(801A0870, 801A07D0, 801A0790) // OS::SetAlarm
MKW_GUEST_ADDRESS(801A08E0, 801A0840, 801A0800) // OS::SetPeriodicAlarm
MKW_GUEST_ADDRESS(801A1138, 801A1098, 801A1058) // __AIClockInit
MKW_GUEST_ADDRESS(801A1358, 801A12B8, 801A1278) // OS::__InitAudioSystem
MKW_GUEST_ADDRESS(801A1520, 801A1480, 801A1440) // OS::__StopAudioSystem
MKW_GUEST_ADDRESS(801A15EC, 801A154C, 801A150C) // DCEnable
MKW_GUEST_ADDRESS(801A1600, 801A1560, 801A1520) // DCInvalidateRange
MKW_GUEST_ADDRESS(801A162C, 801A158C, 801A154C) // DCFlushRange
MKW_GUEST_ADDRESS(801A165C, 801A15BC, 801A157C) // DCStoreRange
MKW_GUEST_ADDRESS(801A168C, 801A15EC, 801A15AC) // DCFlushRangeNoSync
MKW_GUEST_ADDRESS(801A16B8, 801A1618, 801A15D8) // DCStoreRangeNoSync
MKW_GUEST_ADDRESS(801A16E4, 801A1644, 801A1604) // DCZeroRange
MKW_GUEST_ADDRESS(801A1710, 801A1670, 801A1630) // ICInvalidateRange
MKW_GUEST_ADDRESS(801A1744, 801A16A4, 801A1664) // ICFlashInvalidate
MKW_GUEST_ADDRESS(801A1754, 801A16B4, 801A1674) // ICEnable
MKW_GUEST_ADDRESS(801A1768, 801A16C8, 801A1688) // __LCEnable
MKW_GUEST_ADDRESS(801A1834, 801A1794, 801A1754) // LCEnable
MKW_GUEST_ADDRESS(801A186C, 801A17CC, 801A178C) // LCDisable
MKW_GUEST_ADDRESS(801A1894, 801A17F4, 801A17B4) // LCLoadBlocks
MKW_GUEST_ADDRESS(801A18B8, 801A1818, 801A17D8) // LCStoreBlocks
MKW_GUEST_ADDRESS(801A18DC, 801A183C, 801A17FC) // LCStoreData
MKW_GUEST_ADDRESS(801A197C, 801A18DC, 801A189C) // LCQueueLength
MKW_GUEST_ADDRESS(801A1988, 801A18E8, 801A18A8) // LCQueueWait
MKW_GUEST_ADDRESS(801A1AE4, 801A1A44, 801A1A04) // OS::__CacheInit
MKW_GUEST_ADDRESS(801A1E70, 801A1DD0, 801A1D90) // OS::SetCurrentContext
MKW_GUEST_ADDRESS(801A1ED8, 801A1E38, 801A1DF8) // OS::SaveContext
MKW_GUEST_ADDRESS(801A1F58, 801A1EB8, 801A1E78) // OS::LoadContext
MKW_GUEST_ADDRESS(801A2098, 801A1FF8, 801A1FB8) // OS::ClearContext
MKW_GUEST_ADDRESS(801A20BC, 801A201C, 801A1FDC) // OS::InitContext
MKW_GUEST_ADDRESS(801A25D0, 801A2530, 801A24F0) // OS::Report
MKW_GUEST_ADDRESS(801A2660, 801A25C0, 801A2580) // OS::Panic
MKW_GUEST_ADDRESS(801A4EC4, 801A4E24, 801A4DE4) // OS::Fatal
MKW_GUEST_ADDRESS(801A6000, 801A5F60, 801A5F20)
MKW_GUEST_ADDRESS(801A65AC, 801A650C, 801A64CC) // OS::DisableInterrupts
MKW_GUEST_ADDRESS(801A65C0, 801A6520, 801A64E0) // OS::EnableInterrupts
MKW_GUEST_ADDRESS(801A65D4, 801A6534, 801A64F4) // OS::RestoreInterrupts
MKW_GUEST_ADDRESS(801A65F8, 801A6558, 801A6518) // OS::__SetInterruptHandler
MKW_GUEST_ADDRESS(801A661C, 801A657C, 801A653C) // OS::__InterruptInit
MKW_GUEST_ADDRESS(801A66E0, 801A6640, 801A6600) // SetInterruptMask
MKW_GUEST_ADDRESS(801A693C, 801A689C, 801A685C) // OS::__MaskInterrupts
MKW_GUEST_ADDRESS(801A69BC, 801A691C, 801A68DC) // OS::__UnmaskInterrupts
MKW_GUEST_ADDRESS(801A7000, 801A6F60, 801A6F20)
MKW_GUEST_ADDRESS(801A72FC, 801A725C, 801A721C) // OS::InitMessageQueue
MKW_GUEST_ADDRESS(801A735C, 801A72BC, 801A727C) // OS::SendMessage
MKW_GUEST_ADDRESS(801A7424, 801A7384, 801A7344) // OS::ReceiveMessage
MKW_GUEST_ADDRESS(801A7500, 801A7460, 801A7420) // OS::JamMessage
MKW_GUEST_ADDRESS(801A7DFC, 801A7D5C, 801A7D1C) // OS::__InitMemoryProtection
MKW_GUEST_ADDRESS(801A7EE4, 801A7E44, 801A7E04) // OS::LockMutex
MKW_GUEST_ADDRESS(801A7FC0, 801A7F20, 801A7EE0) // OS::UnlockMutex
MKW_GUEST_ADDRESS(801A8088, 801A7FE8, 801A7FA8) // OS::__UnlockAllMutex
MKW_GUEST_ADDRESS(801A8A50, 801A89B0, 801A8970) // OS::GetResetCode
MKW_GUEST_ADDRESS(801A8A80, 801A89E0, 801A89A0) // OS::ResetSystem
MKW_GUEST_ADDRESS(801A961C, 801A957C, 801A953C) // OS::__ThreadInit
MKW_GUEST_ADDRESS(801A98B0, 801A9810, 801A97D0) // OS::GetCurrentThread
MKW_GUEST_ADDRESS(801A9C08, 801A9B68, 801A9B28) // SelectThread
MKW_GUEST_ADDRESS(801A9E84, 801A9DE4, 801A9DA4) // OS::CreateThread
MKW_GUEST_ADDRESS(801AA0F0, 801AA050, 801AA010) // RVL::OS::ExitThread
MKW_GUEST_ADDRESS(801AA1D4, 801AA134, 801AA0F4) // OS::CancelThread
MKW_GUEST_ADDRESS(801AA3AC, 801AA30C, 801AA2CC) // OS::JoinThread
MKW_GUEST_ADDRESS(801AA4EC, 801AA44C, 801AA40C) // OS::DetachThread
MKW_GUEST_ADDRESS(801AA58C, 801AA4EC, 801AA4AC) // OS::ResumeThread
MKW_GUEST_ADDRESS(801AA6A8, 801AA608, 801AA5C8)
MKW_GUEST_ADDRESS(801AA9B8, 801AA918, 801AA8D8) // OS::SleepThread
MKW_GUEST_ADDRESS(801AAAA4, 801AAA04, 801AA9C4) // OS::WakeupThread
MKW_GUEST_ADDRESS(801AACA8, 801AAC08, 801AABC8) // OS::SleepTicks
MKW_GUEST_ADDRESS(801AAD5C, 801AACBC, 801AAC7C) // OS::GetTime
MKW_GUEST_ADDRESS(801AAD7C, 801AACDC, 801AAC9C) // OS::__GetSystemTime
MKW_GUEST_ADDRESS(801AADE0, 801AAD40, 801AAD00) // OS::__TimeToSystemTime
MKW_GUEST_ADDRESS(801AB75C, 801AB6BC, 801AB67C) // OS::SetPowerCallback
MKW_GUEST_ADDRESS(801AB848, 801AB7A8, 801AB768) // OS::__InitSTM
MKW_GUEST_ADDRESS(801ABC0C, 801ABB6C, 801ABB2C) // OS::__DefaultPowerCallback
MKW_GUEST_ADDRESS(801AD1D4, 801AD134, 801AD0F4) // OS::CheckInstall
MKW_GUEST_ADDRESS(801AE4A0, 801AE400, 801AE3C0) // OS::IsTitleInstalled
MKW_GUEST_ADDRESS(801AE58C, 801AE4EC, 801AE4AC) // exit
MKW_GUEST_ADDRESS(801AF0DC, 801AF03C, 801AEFFC) // PAD::Reset
MKW_GUEST_ADDRESS(801AF1E4, 801AF144, 801AF104) // PAD::Recalibrate
MKW_GUEST_ADDRESS(801AF2F0, 801AF250, 801AF210) // PAD::Init
MKW_GUEST_ADDRESS(801AF44C, 801AF3AC, 801AF36C) // PAD::Read
MKW_GUEST_ADDRESS(801AF908, 801AF868, 801AF828) // PAD::ControlMotor
MKW_GUEST_ADDRESS(801B0220, 801B0180, 801B0140) // SCCheckStatus
MKW_GUEST_ADDRESS(801B1BE4, 801B1B44, 801B1B04) // SCGetAspectRatio
MKW_GUEST_ADDRESS(801B1CAC, 801B1C0C, 801B1BCC) // SCGetEuRgb60Mode
MKW_GUEST_ADDRESS(801B1ED0, 801B1E30, 801B1DF0)
MKW_GUEST_ADDRESS(801B1EDC, 801B1E3C, 801B1DFC)
MKW_GUEST_ADDRESS(801B1EE8, 801B1E48, 801B1E08)
MKW_GUEST_ADDRESS(801B1EF4, 801B1E54, 801B1E14) // SCSetBtCmpDevInfoArray
MKW_GUEST_ADDRESS(801B1F00, 801B1E60, 801B1E20) // SCGetBtDpdSensibility
MKW_GUEST_ADDRESS(801B1F68, 801B1EC8, 801B1E88) // SCGetWpadMotorMode
MKW_GUEST_ADDRESS(801B1FBC, 801B1F1C, 801B1EDC) // SCSetWpadMotorMode
MKW_GUEST_ADDRESS(801B1FC4, 801B1F24, 801B1EE4) // SCGetWpadSensorBarPosition
MKW_GUEST_ADDRESS(801B2018, 801B1F78, 801B1F38) // SCGetWpadSpeakerVolume
MKW_GUEST_ADDRESS(801B206C, 801B1FCC, 801B1F8C) // SCSetWpadSpeakerVolume
MKW_GUEST_ADDRESS(801B23A0, 801B2300, 801B22C0) // SCGetProductArea
MKW_GUEST_ADDRESS(801B2424, 801B2384, 801B2344) // SCGetProductCode
MKW_GUEST_ADDRESS(801B2460, 801B23C0, 801B2380) // SCGetProductSN
MKW_GUEST_ADDRESS(801B24C8, 801B2428, 801B23E8) // SCGetProductGameRegion
MKW_GUEST_ADDRESS(801B2DE0, 801B2D40, 801B2D00) // SIInit
MKW_GUEST_ADDRESS(801B3ACC, 801B3A2C, 801B39EC) // SISetSamplingRate
MKW_GUEST_ADDRESS(801B90F4, 801B9054, 801B9014) // VISetPreRetraceCallback
MKW_GUEST_ADDRESS(801B9138, 801B9098, 801B9058) // VISetPostRetraceCallback
MKW_GUEST_ADDRESS(801B9294, 801B91F4, 801B91B4) // __VIInit
MKW_GUEST_ADDRESS(801B94A4, 801B9404, 801B93C4) // VIInit
MKW_GUEST_ADDRESS(801B9934, 801B9894, 801B9854) // VIInit_caseD_0
MKW_GUEST_ADDRESS(801B993C, 801B989C, 801B985C) // VIInit_caseD_1
MKW_GUEST_ADDRESS(801B9944, 801B98A4, 801B9864) // VIInit_caseD_2
MKW_GUEST_ADDRESS(801B99EC, 801B994C, 801B990C) // VIWaitForRetrace
MKW_GUEST_ADDRESS(801B9F6C, 801B9ECC, 801B9E8C) // VIConfigure
MKW_GUEST_ADDRESS(801BA9A4, 801BA904, 801BA8C4) // VIFlush
MKW_GUEST_ADDRESS(801BAAB8, 801BAA18, 801BA9D8) // VISetNextFrameBuffer
MKW_GUEST_ADDRESS(801BAB24, 801BAA84, 801BAA44) // VIGetNextFrameBuffer
MKW_GUEST_ADDRESS(801BAB2C, 801BAA8C, 801BAA4C) // VISetBlack
MKW_GUEST_ADDRESS(801BABA4, 801BAB04, 801BAAC4) // VIGetRetraceCount
MKW_GUEST_ADDRESS(801BABAC, 801BAB0C, 801BAACC) // VIGetNextField
MKW_GUEST_ADDRESS(801BAC48, 801BABA8, 801BAB68) // VIGetCurrentLine
MKW_GUEST_ADDRESS(801BAD38, 801BAC98, 801BAC58) // VIGetDTVStatus
MKW_GUEST_ADDRESS(801D8D30, 801D8C90, 801D8C50) // NHTTPStartup
MKW_GUEST_ADDRESS(801D9E94, 801D9DF4, 801D9DB4) // NHTTPi_GetSystemInfoP
MKW_GUEST_ADDRESS(8020FF9C, 8020FEFC, 8020FEBC) // EGG::AsyncDisplay::endRender
MKW_GUEST_ADDRESS(80218C2C, 80218B8C, 80218B4C) // EGG::Decomp::decodeSZS
MKW_GUEST_ADDRESS(80219FB4, 80219F14, 80219ED4) // EGG::Display::copyEFBtoXFB
MKW_GUEST_ADDRESS(8021B344, 8021B2A4, 8021B264) // EGG::DrawGX::SetVtxState_switch
MKW_GUEST_ADDRESS(8021B688, 8021B5E8, 8021B5A8)
MKW_GUEST_ADDRESS(8022E2BC, 8022DF38, 8022E1DC) // EGG::LightTexture::SetupTevFinish
MKW_GUEST_ADDRESS(8023BD38, 8023B9B4, 8023BC58) // EGG::ScnRenderer::CreatePath
MKW_GUEST_ADDRESS(8023E53C, 8023E1B8, 8023E45C) // EGG::Screen::SetTVModeDefault
MKW_GUEST_ADDRESS(80242D7C, 80241EC0, 80242C9C) // EGG::TaskThread::Run
MKW_GUEST_ADDRESS(8024373C, 80242880, 8024365C) // EGG::Thread::Run
MKW_GUEST_ADDRESS(80244DE0, 80244D40, 80244D00)
MKW_GUEST_ADDRESS(80244EA0, 80244E00, 80244DC0)
MKW_GUEST_ADDRESS(802574A0, 80257180, 80256E40)
MKW_GUEST_ADDRESS(802574E0, 802571C0, 80256E80)
MKW_GUEST_ADDRESS(80257520, 80257200, 80256EC0)
MKW_GUEST_ADDRESS(80257540, 80257220, 80256EE0)
MKW_GUEST_ADDRESS(8027F820, 8027B4E0, 8027F1C0)
MKW_GUEST_ADDRESS(8029CEB0, 80298B70, 8029C850)
MKW_GUEST_ADDRESS(8029CEF8, 80298BB8, 8029C898)
MKW_GUEST_ADDRESS(802A3EE8, 8029FB80, 802A3888)
MKW_GUEST_ADDRESS(802A3EF4, 8029FB8C, 802A3894)
MKW_GUEST_ADDRESS(802A3F0C, 8029FBA4, 802A38AC)
MKW_GUEST_ADDRESS(802F81A0, 802F3E20, 802F7B20)
MKW_GUEST_ADDRESS(802F8200, 802F3E80, 802F7B80)
MKW_GUEST_ADDRESS(80343230, 8033EEB0, 80342BB0)
MKW_GUEST_ADDRESS(803434E0, 8033F160, 80342E60)
MKW_GUEST_ADDRESS(80343740, 8033F3C0, 803430C0)
MKW_GUEST_ADDRESS(803437C0, 8033F440, 80343140)
MKW_GUEST_ADDRESS(80343DC0, 8033FA40, 80343740)
MKW_GUEST_ADDRESS(80343DE4, 8033FA64, 80343764)
MKW_GUEST_ADDRESS(80344090, 8033FD10, 80343A10)
MKW_GUEST_ADDRESS(80344094, 8033FD14, 80343A14)
MKW_GUEST_ADDRESS(80344098, 8033FD18, 80343A18)
MKW_GUEST_ADDRESS(803440A4, 8033FD24, 80343A24)
MKW_GUEST_ADDRESS(803440A8, 8033FD28, 80343A28)
MKW_GUEST_ADDRESS(803440AC, 8033FD2C, 80343A2C)
MKW_GUEST_ADDRESS(80344110, 8033FD90, 80343A90)
MKW_GUEST_ADDRESS(80344710, 80340390, 80344090)
MKW_GUEST_ADDRESS(80346D20, 803429A0, 803466A0)
MKW_GUEST_ADDRESS(80347130, 80342DB0, 80346AB0)
MKW_GUEST_ADDRESS(80347498, 80343118, 80346E18)
MKW_GUEST_ADDRESS(803477B0, 80343430, 80347130)
MKW_GUEST_ADDRESS(803478B0, 80343530, 80347230)
MKW_GUEST_ADDRESS(80350864, 8034C4E4, 803501E4)
MKW_GUEST_ADDRESS(80350866, 8034C4E6, 803501E6)
MKW_GUEST_ADDRESS(80350872, 8034C4F2, 803501F2)
MKW_GUEST_ADDRESS(8035087C, 8034C4FC, 803501FC)
MKW_GUEST_ADDRESS(80350890, 8034C510, 80350210)
MKW_GUEST_ADDRESS(80385800, 80381480, 80385180)
MKW_GUEST_ADDRESS(80385802, 80381482, 80385182)
MKW_GUEST_ADDRESS(80385804, 80381484, 80385184)
MKW_GUEST_ADDRESS(80385920, 803815A0, 803852A0) // dev/fs_fd
MKW_GUEST_ADDRESS(80385928, 803815A8, 803852A8)
MKW_GUEST_ADDRESS(80385AA8, 80381728, 80385428)
MKW_GUEST_ADDRESS(80385AE0, 80381760, 80385460)
MKW_GUEST_ADDRESS(80386000, 80381C80, 80385980) // SystemManager
MKW_GUEST_ADDRESS(80386298, 80381F18, 80385C18)
MKW_GUEST_ADDRESS(80386448, 803820C8, 80385DC8)
MKW_GUEST_ADDRESS(8038644C, 803820CC, 80385DCC)
MKW_GUEST_ADDRESS(8038647C, 803820FC, 80385DFC)
MKW_GUEST_ADDRESS(80386480, 80382100, 80385E00)
MKW_GUEST_ADDRESS(80386524, 803821A4, 80385EA4)
MKW_GUEST_ADDRESS(80386528, 803821A8, 80385EA8)
MKW_GUEST_ADDRESS(80386608, 80382288, 80385F88)
MKW_GUEST_ADDRESS(80386610, 80382290, 80385F90)
MKW_GUEST_ADDRESS(80386614, 80382294, 80385F94)
MKW_GUEST_ADDRESS(80386618, 80382298, 80385F98)
MKW_GUEST_ADDRESS(8038661C, 8038229C, 80385F9C)
MKW_GUEST_ADDRESS(80386620, 803822A0, 80385FA0)
MKW_GUEST_ADDRESS(80386624, 803822A4, 80385FA4)
MKW_GUEST_ADDRESS(80386664, 803822E4, 80385FE4)
MKW_GUEST_ADDRESS(80386668, 803822E8, 80385FE8)
MKW_GUEST_ADDRESS(80386670, 803822F0, 80385FF0)
MKW_GUEST_ADDRESS(8038667C, 803822FC, 80385FFC)
MKW_GUEST_ADDRESS(803866A0, 80382320, 80386020)
MKW_GUEST_ADDRESS(803866A8, 80382328, 80386028)
MKW_GUEST_ADDRESS(803866F0, 80382370, 80386070)
MKW_GUEST_ADDRESS(80386720, 803823A0, 803860A0)
MKW_GUEST_ADDRESS(80386724, 803823A4, 803860A4)
MKW_GUEST_ADDRESS(80386725, 803823A5, 803860A5)
MKW_GUEST_ADDRESS(80386770, 803823F0, 803860F0)
MKW_GUEST_ADDRESS(80386774, 803823F4, 803860F4)
MKW_GUEST_ADDRESS(8038677C, 803823FC, 803860FC)
MKW_GUEST_ADDRESS(80386780, 80382400, 80386100)
MKW_GUEST_ADDRESS(80386784, 80382404, 80386104)
MKW_GUEST_ADDRESS(803867B0, 80382430, 80386130)
MKW_GUEST_ADDRESS(803867B1, 80382431, 80386131)
MKW_GUEST_ADDRESS(803867C0, 80382440, 80386140)
MKW_GUEST_ADDRESS(803867C4, 80382444, 80386144)
MKW_GUEST_ADDRESS(803867D0, 80382450, 80386150)
MKW_GUEST_ADDRESS(803867D8, 80382458, 80386158)
MKW_GUEST_ADDRESS(803867EC, 8038246C, 8038616C)
MKW_GUEST_ADDRESS(803867F0, 80382470, 80386170)
MKW_GUEST_ADDRESS(80386848, 803824C8, 803861C8)
MKW_GUEST_ADDRESS(803868A0, 80382520, 80386220)
MKW_GUEST_ADDRESS(803868F8, 80382578, 80386278)
MKW_GUEST_ADDRESS(80386918, 80382598, 80386298)
MKW_GUEST_ADDRESS(8038691C, 8038259C, 8038629C)
MKW_GUEST_ADDRESS(80386920, 803825A0, 803862A0)
MKW_GUEST_ADDRESS(80386934, 803825B4, 803862B4)
MKW_GUEST_ADDRESS(80386938, 803825B8, 803862B8)
MKW_GUEST_ADDRESS(8038693C, 803825BC, 803862BC)
MKW_GUEST_ADDRESS(80386940, 803825C0, 803862C0)
MKW_GUEST_ADDRESS(80386948, 803825C8, 803862C8)
MKW_GUEST_ADDRESS(803869E0, 80382660, 80386360)
MKW_GUEST_ADDRESS(80386B38, 803827B8, 803864B8)
MKW_GUEST_ADDRESS(80386B44, 803827C4, 803864C4)
MKW_GUEST_ADDRESS(80386BA0, 80382820, 80386520)
MKW_GUEST_ADDRESS(80386BA8, 80382828, 80386528)
MKW_GUEST_ADDRESS(80386BB4, 80382834, 80386534)
MKW_GUEST_ADDRESS(80386BB8, 80382838, 80386538)
MKW_GUEST_ADDRESS(80386BC0, 80382840, 80386540)
MKW_GUEST_ADDRESS(80386BE4, 80382864, 80386564)
MKW_GUEST_ADDRESS(80386F14, 80382B94, 80386894)
MKW_GUEST_ADDRESS(80386F20, 80382BA0, 803868A0)
MKW_GUEST_ADDRESS(80386F24, 80382BA4, 803868A4)
MKW_GUEST_ADDRESS(80386F60, 80382BD8, 803868E0)
MKW_GUEST_ADDRESS(80388588, 80384208, 80387F08)
MKW_GUEST_ADDRESS(8038858C, 8038420C, 80387F0C)
MKW_GUEST_ADDRESS(803886C8, 80384348, 80388048)
MKW_GUEST_ADDRESS(80388B80, 80384800, 80388500)
MKW_GUEST_ADDRESS(80388BA0, 80384820, 80388520)
MKW_GUEST_ADDRESS(80388BC0, 80384840, 80388540)
MKW_GUEST_ADDRESS(80388BE0, 80384860, 80388560)
MKW_GUEST_ADDRESS(80388DD0, 80384A48, 80388750)
MKW_GUEST_ADDRESS(80388DE0, 80384A58, 80388760)
MKW_GUEST_ADDRESS(80388DF4, 80384A6C, 80388774)
MKW_GUEST_ADDRESS(80529D68, 805258F4, 805296E8) // MovieManager::PrepareAsync
MKW_GUEST_ADDRESS(80550CC0, 8054AD68, 80550640) // THP::PlayerOpen
MKW_GUEST_ADDRESS(80550F48, 8054AFF0, 805508C8) // THP::PlayerClose
MKW_GUEST_ADDRESS(80550F9C, 8054B044, 8055091C) // THP::PlayerCalcNeedMemory
MKW_GUEST_ADDRESS(80551054, 8054B0FC, 805509D4) // THP::PlayerSetBuffer
MKW_GUEST_ADDRESS(80551378, 8054B420, 80550CF8) // THP::PlayerPrepare
MKW_GUEST_ADDRESS(80551658, 8054B700, 80550FD8) // THP::PlayerStop
MKW_GUEST_ADDRESS(80551D38, 8054BDE0, 805516B8) // THP::PlayerGetVideoInfo
MKW_GUEST_ADDRESS(805529A8, 8054CA50, 80552328) // THP::VideoDecoder
MKW_GUEST_ADDRESS(80552A74, 8054CB1C, 805523F4) // THP::VideoDecoderOnMemory
MKW_GUEST_ADDRESS(8055531C, 8054F2FC, 80554C9C) // RelProlog
MKW_GUEST_ADDRESS(805653D0, 80561050, 80564D50)
MKW_GUEST_ADDRESS(8088F400, 8088AFD0, 8088EA6C)
MKW_GUEST_ADDRESS(8088F704, 8088B2D4, 8088ED70)
MKW_GUEST_ADDRESS(8088FDB8, 8088B960, 8088F418)
MKW_GUEST_ADDRESS(808B4C20, 808B0570, 808B3D80)
MKW_GUEST_ADDRESS(809BEBA0, 809BA3E0, 809BDC00)
MKW_GUEST_ADDRESS(809BEBA4, 809BA3E4, 809BDC04)
MKW_GUEST_ADDRESS(809BEBA5, 809BA3E5, 809BDC05)
MKW_GUEST_ADDRESS(809BEBA6, 809BA3E6, 809BDC06)
MKW_GUEST_ADDRESS(809BEBA7, 809BA3E7, 809BDC07)
MKW_GUEST_ADDRESS(809BECF0, 809BA530, 809BDD50)
MKW_GUEST_ADDRESS(809C1830, 809BD070, 809C0890)
MKW_GUEST_ADDRESS(809C183C, 809BD07C, 809C089C)
MKW_GUEST_ADDRESS(809C2898, 809BDC58, 809C18F8)
+137
View File
@@ -0,0 +1,137 @@
#pragma once
// Guest addresses are named by their PAL (RMCP01) address. Which address that identity has
// depends on the region the game was translated from, which runtime/game supplies at link
// time, so one compiled runtime serves every region.
//
// MKW_GADDR(801A9E84) address of that identity in the running region
// MKW_GADDR_CT(801A9E84) same, as a constant expression; translated code only
//
// A PAL address missing from guest_address_table.inc does not compile; regenerate the table
// with tools/region/gen_guest_address_table.py.
#include <cstddef>
#include <cstdint>
namespace GuestRegion {
enum class Id : uint8_t {
Pal = 0,
NtscU = 1,
NtscJ = 2,
};
inline constexpr size_t kCount = 3;
struct Row {
uint32_t address[kCount];
constexpr uint32_t In(Id region) const { return address[static_cast<size_t>(region)]; }
};
inline constexpr Row kRows[] = {
#define MKW_GUEST_ADDRESS(pal, ntscU, ntscJ) {{0x##pal##u, 0x##ntscU##u, 0x##ntscJ##u}},
#include "guest_abi/guest_address_table.inc"
#undef MKW_GUEST_ADDRESS
};
inline constexpr size_t kRowCount = sizeof(kRows) / sizeof(kRows[0]);
constexpr bool RowsAreSorted() {
for (size_t i = 1; i < kRowCount; ++i) {
if (kRows[i - 1].address[0] >= kRows[i].address[0]) {
return false;
}
}
return true;
}
static_assert(RowsAreSorted(), "guest_address_table.inc must be sorted by PAL address without duplicates");
constexpr const Row* Find(uint32_t pal) {
size_t lo = 0;
size_t hi = kRowCount;
while (lo < hi) {
const size_t mid = lo + (hi - lo) / 2;
if (kRows[mid].address[0] < pal) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo < kRowCount && kRows[lo].address[0] == pal ? &kRows[lo] : nullptr;
}
constexpr const Row& RowOf(uint32_t pal) {
return Find(pal) ? *Find(pal) : throw "PAL address missing from guest_abi/guest_address_table.inc";
}
} // namespace GuestRegion
// Defined by runtime/game for the translated region.
extern "C" const uint8_t kMkwGuestRegion;
namespace GuestRegion {
inline Id Current() noexcept { return static_cast<Id>(kMkwGuestRegion); }
template <uint32_t Pal>
inline uint32_t Address() noexcept {
constexpr Row row = RowOf(Pal);
if constexpr (row.address[1] == Pal && row.address[2] == Pal) {
return Pal;
} else {
return row.In(Current());
}
}
// Folds to false at compile time for a constant target that no region uses for Pal.
template <uint32_t Pal>
inline bool Is(uint32_t target) noexcept {
constexpr Row row = RowOf(Pal);
return (target == row.address[0] || target == row.address[1] || target == row.address[2]) &&
target == row.In(Current());
}
[[noreturn]] void FatalUnknownAddress(uint32_t pal) noexcept;
// For hand-written registrations, which name the PAL address.
inline uint32_t Translate(uint32_t pal) noexcept {
const Id region = Current();
if (region == Id::Pal) {
return pal;
}
const Row* row = Find(pal);
if (!row) {
FatalUnknownAddress(pal);
}
return row->In(region);
}
struct Facts {
const char* gameId;
char letter;
uint32_t gameCode;
uint32_t viTvFormat;
uint32_t mem1ArenaLo;
// setting.txt defaults for a new NAND
const char* area;
const char* model;
const char* productCode;
const char* video;
const char* gameRegion;
};
inline constexpr Facts kFacts[kCount] = {
{"RMCP01", 'P', 0x524D4350u, 1u, 0x80399180u, "EUR", "RVL-001(EUR)", "LEH", "PAL", "EU"},
{"RMCE01", 'E', 0x524D4345u, 0u, 0x80394E00u, "USA", "RVL-001(USA)", "LU", "NTSC", "US"},
{"RMCJ01", 'J', 0x524D434Au, 0u, 0x80398B00u, "JPN", "RVL-001(JPN)", "LJ", "NTSC", "JP"},
};
inline const Facts& CurrentFacts() noexcept { return kFacts[static_cast<size_t>(Current())]; }
} // namespace GuestRegion
#define MKW_GADDR(pal) (::GuestRegion::Address<0x##pal##u>())
#if defined(MKW_TRANSLATED_GUEST_REGION)
#define MKW_GADDR_CT(pal) \
(::GuestRegion::RowOf(0x##pal##u).In(static_cast<::GuestRegion::Id>(MKW_TRANSLATED_GUEST_REGION)))
#endif
+43 -37
View File
@@ -44,56 +44,62 @@ extern "C" void GX__SetBlendMode_8017277c(uint32_t t, uint32_t s, uint32_t d, ui
extern "C" void GX__LoadPosMtxImm_8017310c(uint32_t ma, uint32_t id);
extern "C" void GX__SetCurrentMtx_80173214(uint32_t id);
// Keyed by the translated region's addresses, so only translated code sees them;
// runtime calls resolve through the registry.
#if defined(MKW_TRANSLATED_GUEST_REGION)
#define MKW_KNOWN_TYPED_NATIVE(addr, fn, ...) \
template <> struct KnownTypedNativeCpuCall<addr> { \
template <> struct KnownTypedNativeCpuCall<MKW_GADDR_CT(addr)> { \
static constexpr bool kAvailable = true; \
static void Invoke(CpuContext* cpu) { (void)cpu; fn(__VA_ARGS__); } \
}
MKW_KNOWN_TYPED_NATIVE(0x8016D3A4u, GX__SetVtxDesc_8016d3a4, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x8016DC34u, GX__ClearVtxDesc_8016dc34);
MKW_KNOWN_TYPED_NATIVE(0x8016DC68u, GX__SetVtxAttrFmt_8016dc68, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(0x8016E37Cu, GX__SetTexCoordGen2_8016e37c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(0x8016E5A4u, GX__SetNumTexGens_8016e5a4, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(0x8016EAB0u, GX__DrawDone_8016eab0);
MKW_KNOWN_TYPED_NATIVE(0x8016F0F0u, GX__Begin_8016f0f0, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5]);
MKW_KNOWN_TYPED_NATIVE(0x8016F314u, GX__SetLineWidth_8016f314, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x8016F348u, GX__SetPointSize_8016f348, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x8016F3B8u, GX__SetCullMode_8016f3b8, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(0x8017054Cu, GX__SetNumChans_8017054c, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(0x80170570u, GX__SetChanCtrl_80170570, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8], cpu->gpr[9]);
MKW_KNOWN_TYPED_NATIVE(0x801707F8u, GX__InitTexObj_801707f8, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8], cpu->gpr[9], cpu->gpr[10]);
MKW_KNOWN_TYPED_NATIVE(0x80170A4Cu, GX__InitTexObjLOD_80170a4c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], static_cast<float>(cpu->fpr[1].d), static_cast<float>(cpu->fpr[2].d), static_cast<float>(cpu->fpr[3].d), cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(0x80171110u, GX__InvalidateTexAll_80171110);
MKW_KNOWN_TYPED_NATIVE(0x80170F2Cu, GX__LoadTexObj_80170f2c, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x80171B38u, GX__SetNumIndStages_80171b38, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(0x80171B58u, GX__SetTevDirect_80171b58, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(0x80171CE0u, GX__SetTevColorIn_80171ce0, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(0x80171D20u, GX__SetTevAlphaIn_80171d20, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(0x80171D60u, GX__SetTevColorOp_80171d60, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(0x80171DB8u, GX__SetTevAlphaOp_80171db8, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(0x80171E70u, GX__SetTevColorS10_80171e70, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x80171ED4u, GX__SetTevKColor_80171ed4, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x80171F30u, GX__SetTevKColorSel_80171f30, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x80171F80u, GX__SetTevKAlphaSel_80171f80, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x80171FD0u, GX__SetTevSwapMode_80171fd0, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5]);
MKW_KNOWN_TYPED_NATIVE(0x8017200Cu, GX__SetTevSwapModeTable_8017200c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(0x8017214Cu, GX__SetTevOrder_8017214c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6]);
MKW_KNOWN_TYPED_NATIVE(0x801722A8u, GX__SetNumTevStages_801722a8, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(0x8017277Cu, GX__SetBlendMode_8017277c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6]);
MKW_KNOWN_TYPED_NATIVE(0x8017310Cu, GX__LoadPosMtxImm_8017310c, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(0x80173214u, GX__SetCurrentMtx_80173214, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(8016D3A4, GX__SetVtxDesc_8016d3a4, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(8016DC34, GX__ClearVtxDesc_8016dc34);
MKW_KNOWN_TYPED_NATIVE(8016DC68, GX__SetVtxAttrFmt_8016dc68, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(8016E37C, GX__SetTexCoordGen2_8016e37c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(8016E5A4, GX__SetNumTexGens_8016e5a4, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(8016EAB0, GX__DrawDone_8016eab0);
MKW_KNOWN_TYPED_NATIVE(8016F0F0, GX__Begin_8016f0f0, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5]);
MKW_KNOWN_TYPED_NATIVE(8016F314, GX__SetLineWidth_8016f314, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(8016F348, GX__SetPointSize_8016f348, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(8016F3B8, GX__SetCullMode_8016f3b8, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(8017054C, GX__SetNumChans_8017054c, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(80170570, GX__SetChanCtrl_80170570, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8], cpu->gpr[9]);
MKW_KNOWN_TYPED_NATIVE(801707F8, GX__InitTexObj_801707f8, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8], cpu->gpr[9], cpu->gpr[10]);
MKW_KNOWN_TYPED_NATIVE(80170A4C, GX__InitTexObjLOD_80170a4c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], static_cast<float>(cpu->fpr[1].d), static_cast<float>(cpu->fpr[2].d), static_cast<float>(cpu->fpr[3].d), cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(80171110, GX__InvalidateTexAll_80171110);
MKW_KNOWN_TYPED_NATIVE(80170F2C, GX__LoadTexObj_80170f2c, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(80171B38, GX__SetNumIndStages_80171b38, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(80171B58, GX__SetTevDirect_80171b58, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(80171CE0, GX__SetTevColorIn_80171ce0, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(80171D20, GX__SetTevAlphaIn_80171d20, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(80171D60, GX__SetTevColorOp_80171d60, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(80171DB8, GX__SetTevAlphaOp_80171db8, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7], cpu->gpr[8]);
MKW_KNOWN_TYPED_NATIVE(80171E70, GX__SetTevColorS10_80171e70, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(80171ED4, GX__SetTevKColor_80171ed4, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(80171F30, GX__SetTevKColorSel_80171f30, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(80171F80, GX__SetTevKAlphaSel_80171f80, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(80171FD0, GX__SetTevSwapMode_80171fd0, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5]);
MKW_KNOWN_TYPED_NATIVE(8017200C, GX__SetTevSwapModeTable_8017200c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6], cpu->gpr[7]);
MKW_KNOWN_TYPED_NATIVE(8017214C, GX__SetTevOrder_8017214c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6]);
MKW_KNOWN_TYPED_NATIVE(801722A8, GX__SetNumTevStages_801722a8, cpu->gpr[3]);
MKW_KNOWN_TYPED_NATIVE(8017277C, GX__SetBlendMode_8017277c, cpu->gpr[3], cpu->gpr[4], cpu->gpr[5], cpu->gpr[6]);
MKW_KNOWN_TYPED_NATIVE(8017310C, GX__LoadPosMtxImm_8017310c, cpu->gpr[3], cpu->gpr[4]);
MKW_KNOWN_TYPED_NATIVE(80173214, GX__SetCurrentMtx_80173214, cpu->gpr[3]);
#undef MKW_KNOWN_TYPED_NATIVE
#define MKW_KNOWN_NATIVE_CPU_CALL(addr, fn) \
template <> struct KnownNativeCpuCall<addr> { \
template <> struct KnownNativeCpuCall<MKW_GADDR_CT(addr)> { \
static constexpr bool kAvailable = true; \
static constexpr uint32_t kNonvolatileFprWriteMask = 0; \
static constexpr void (*Entry)(CpuContext*) = &fn; \
}
MKW_KNOWN_NATIVE_CPU_CALL(0x800847C0u, nw4r__lyt__detail__DrawQuad_800847c0);
MKW_KNOWN_NATIVE_CPU_CALL(0x80084D20u, nw4r__lyt__detail__DrawQuad_80084d20);
MKW_KNOWN_NATIVE_CPU_CALL(800847C0, nw4r__lyt__detail__DrawQuad_800847c0);
MKW_KNOWN_NATIVE_CPU_CALL(80084D20, nw4r__lyt__detail__DrawQuad_80084d20);
#undef MKW_KNOWN_NATIVE_CPU_CALL
#endif
+13
View File
@@ -0,0 +1,13 @@
#pragma once
// Base translations the runtime calls directly, named by PAL identity. runtime/game binds each
// to the translated region's symbol.
// MKW_GUEST_FUNC(801A0620)(ctx);
#include "guest_abi/cpu_context.h"
#define MKW_GUEST_FUNC(pal) (*::mkw_guest_function_##pal)
#define MKW_GUEST_FUNCTION_IMPORT(pal, ntscU, ntscJ) extern "C" void (*const mkw_guest_function_##pal)(CpuContext*);
#include "guest_function_imports.inc"
#undef MKW_GUEST_FUNCTION_IMPORT
+10
View File
@@ -0,0 +1,10 @@
// AUTO-GENERATED by tools/region/gen_guest_address_table.py - DO NOT EDIT.
// Base translations the runtime calls directly: MKW_GUEST_FUNCTION_IMPORT(PAL, NTSC-U, NTSC-J).
MKW_GUEST_FUNCTION_IMPORT(8012B830, 8012B790, 8012B750) // AXFXReverbStdExpCallback
MKW_GUEST_FUNCTION_IMPORT(801A0620, 801A0580, 801A0540) // InsertAlarm
MKW_GUEST_FUNCTION_IMPORT(801A1ED8, 801A1E38, 801A1DF8) // OS::SaveContext
MKW_GUEST_FUNCTION_IMPORT(801A961C, 801A957C, 801A953C) // OS::__ThreadInit
MKW_GUEST_FUNCTION_IMPORT(801AADE0, 801AAD40, 801AAD00) // OS::__TimeToSystemTime
MKW_GUEST_FUNCTION_IMPORT(801D8D30, 801D8C90, 801D8C50) // NHTTPStartup
MKW_GUEST_FUNCTION_IMPORT(801D9E94, 801D9DF4, 801D9DB4) // NHTTPi_GetSystemInfoP
MKW_GUEST_FUNCTION_IMPORT(8055531C, 8054F2FC, 80554C9C) // RelProlog
+4 -2
View File
@@ -38,10 +38,12 @@ extern "C" void OS_HLE_EndDeferredGuestCallbacks();
// renaming requires updating Translator.Cli/Program.cs, RuntimeNativeGuestEffectAnalyzer.cs,
// TranslatedBuildShardEmitter.cs and RuntimeNativeFunctionAbiProvider.cs together.
// addr_hex is the PAL address (see guest_abi/guest_region.h). The translator reads arg_list as
// the guest ABI, so the unused wrapper keeps it checked against the implementation.
#define PPC_NATIVE_OVERRIDE(addr_hex, name, ret_type, arg_list, call_list) \
extern "C" ret_type func_##addr_hex arg_list { return name call_list; } \
[[maybe_unused]] static ret_type native_signature_##addr_hex arg_list { return name call_list; } \
REGISTER_NATIVE_FUNCTION(0x##addr_hex, name)
#define PPC_NATIVE_OVERRIDE_VOID(addr_hex, name, arg_list, call_list) \
extern "C" void func_##addr_hex arg_list { name call_list; } \
[[maybe_unused]] static void native_signature_##addr_hex arg_list { name call_list; } \
REGISTER_NATIVE_FUNCTION(0x##addr_hex, name)
+4 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include "runtime_config.h"
#include "guest_abi/guest_region.h"
#include "nand_settings.h"
#include "runtime_log.h"
#include "system_bridge.h"
@@ -184,7 +185,9 @@ inline std::filesystem::path DiscoverNandRootPath() {
static const auto root = [] {
const auto resolved = ResolveNandRootPath();
std::string error;
if (!RuntimeNandSettings::Ensure(resolved, error)) {
const auto& facts = ::GuestRegion::CurrentFacts();
if (!RuntimeNandSettings::Ensure(resolved, error,
{facts.area, facts.model, facts.productCode, facts.video, facts.gameRegion})) {
FailNandRoot(error.c_str(), RuntimeNandSettings::FilePath(resolved));
}
return resolved;
+25 -8
View File
@@ -88,10 +88,20 @@ inline std::string GenerateSerial(std::time_t now) {
return std::string(9 - digits.size(), '0') + digits;
}
// This recompilation targets the European disc. These are Dolphin's PAL boot
// defaults; an existing setting.txt always takes precedence, in every region.
inline std::optional<std::array<uint8_t, 256>> EncodeNew(const std::string& serial) {
const Settings identity{{"SERNO", serial}, {"CODE", "LEH"}, {"AREA", "EUR"}, {"GAME", "EU"}};
// Console identity for a new setting.txt; Dolphin's boot defaults for the game's region.
struct BootDefaults {
std::string area = "EUR";
std::string model = "RVL-001(EUR)";
std::string code = "LEH";
std::string video = "PAL";
std::string game = "EU";
};
// An existing setting.txt always takes precedence, in every region.
inline std::optional<std::array<uint8_t, 256>> EncodeNew(
const std::string& serial,
const BootDefaults& defaults = BootDefaults{}) {
const Settings identity{{"SERNO", serial}, {"CODE", defaults.code}, {"AREA", defaults.area}, {"GAME", defaults.game}};
if (!HasIdentity(identity)) {
return std::nullopt;
}
@@ -102,9 +112,15 @@ inline std::optional<std::array<uint8_t, 256>> EncodeNew(const std::string& seri
bytes[position++] = static_cast<uint8_t>(value) ^ static_cast<uint8_t>(key);
key = (key << 1) | (key >> 31);
};
for (const std::string& line : {std::string("AREA=EUR\r\n"), std::string("MODEL=RVL-001(EUR)\r\n"),
std::string("DVD=0\r\n"), std::string("MPCH=0x7FFE\r\n"), std::string("CODE=LEH\r\n"),
"SERNO=" + serial + "\r\n", std::string("VIDEO=PAL\r\n"), std::string("GAME=EU\r\n")}) {
for (const std::string& line : {
"AREA=" + defaults.area + "\r\n",
"MODEL=" + defaults.model + "\r\n",
std::string("DVD=0\r\n"),
std::string("MPCH=0x7FFE\r\n"),
"CODE=" + defaults.code + "\r\n",
"SERNO=" + serial + "\r\n",
"VIDEO=" + defaults.video + "\r\n",
"GAME=" + defaults.game + "\r\n"}) {
for (;;) {
if (position + line.size() > bytes.size()) {
return std::nullopt;
@@ -146,6 +162,7 @@ inline std::optional<std::filesystem::path> CreateScratchDirectory(
// Never replace an existing file, including an unreadable or damaged one.
// Publish a complete file atomically so simultaneous launches use one identity.
inline bool Ensure(const std::filesystem::path& root, std::string& error,
const BootDefaults& defaults,
std::time_t now = std::time(nullptr)) {
const auto path = FilePath(root);
std::error_code ec;
@@ -163,7 +180,7 @@ inline bool Ensure(const std::filesystem::path& root, std::string& error,
return false;
}
const auto bytes = EncodeNew(GenerateSerial(now));
const auto bytes = EncodeNew(GenerateSerial(now), defaults);
if (!bytes) {
error = "Cannot initialize NAND settings: invalid system clock";
return false;
+12 -12
View File
@@ -24,29 +24,29 @@ uint32_t g_lastEggWidth169 = 0;
// EGG::Screen's static canvas records (PAL). See gx_dynamic_aspect.h for the
// record layout and the anamorphic presentation model.
constexpr uint32_t kEggScreenRecord43 = 0x802A3EE8u;
constexpr uint32_t kEggScreenRecord169 = 0x802A3EF4u;
constexpr uint32_t kEggActiveScreenPtr = 0x80386F14u;
const uint32_t kEggScreenRecord43 = MKW_GADDR(802A3EE8);
const uint32_t kEggScreenRecord169 = MKW_GADDR(802A3EF4);
const uint32_t kEggActiveScreenPtr = MKW_GADDR(80386F14);
constexpr uint32_t kEggScreenProjScaleX = 0x80386F20u;
constexpr uint32_t kEggScreenProjScaleY = 0x80386F24u;
const uint32_t kEggScreenProjScaleX = MKW_GADDR(80386F20);
const uint32_t kEggScreenProjScaleY = MKW_GADDR(80386F24);
constexpr uint32_t kEggScreenAspectHandler = 0x8023E53Cu;
const uint32_t kEggScreenAspectHandler = MKW_GADDR(8023E53C);
constexpr uint32_t kMkwUpdateAllScreens = 0x805653D0u;
constexpr uint32_t kMkwGfxDrawList = 0x809C1830u;
constexpr uint32_t kSystemManagerInstance = 0x80386000u;
const uint32_t kMkwUpdateAllScreens = MKW_GADDR(805653D0);
const uint32_t kMkwGfxDrawList = MKW_GADDR(809C1830);
const uint32_t kSystemManagerInstance = MKW_GADDR(80386000);
// The draw list is an nw4r::ut::List: +0x00 head, +0x04 tail, +0x08 u16 count,
// +0x0A u16 link offset, with next = *(node + linkOffset + 4) (List_GetNext,
// 0x800AF180).
constexpr uint32_t kMkwGfxDrawListLinkOffset = 0x0Au;
constexpr uint32_t kMkwGfxOffscreenList = 0x809C183Cu;
const uint32_t kMkwGfxOffscreenList = MKW_GADDR(809C183C);
constexpr uint32_t kMkwGfxOffscreenNodeScreenSlot = 0x10u;
constexpr uint32_t kEggScreenVTableOffset = 0x38u;
constexpr uint32_t kEggScreenVTable = 0x802A3F0Cu;
constexpr uint32_t kMkwScreenVTable = 0x808B4C20u;
const uint32_t kEggScreenVTable = MKW_GADDR(802A3F0C);
const uint32_t kMkwScreenVTable = MKW_GADDR(808B4C20);
constexpr uint32_t kEggScreenFlagsOffset = 0x34u;
constexpr uint16_t kEggScreenFlagFramebufferCanvas = 0x0008u;
constexpr uint16_t kEggScreenFlagKeepFrustumScale = 0x0040u;
+5 -5
View File
@@ -46,10 +46,10 @@ std::atomic<uint32_t> g_viRetracePendingCount{0};
namespace {
constexpr uint32_t kOSCurrentContextAddr = 0x800000d4u;
constexpr uint32_t kOSRunningContextAddr = 0x800000e4u; // OSGetCurrentThread reads this
constexpr uint32_t kThreadQueueArrayAddr = 0x803477b0u;
constexpr uint32_t kSchedulerPendingFlagAddr = 0x80386920u;
constexpr uint32_t kSchedulerReschedCounterAddr = 0x8038691cu;
constexpr uint32_t kSchedulerIdleFlagAddr = 0x80386918u;
const uint32_t kThreadQueueArrayAddr = MKW_GADDR(803477b0);
const uint32_t kSchedulerPendingFlagAddr = MKW_GADDR(80386920);
const uint32_t kSchedulerReschedCounterAddr = MKW_GADDR(8038691c);
const uint32_t kSchedulerIdleFlagAddr = MKW_GADDR(80386918);
// OSThread structure offsets
constexpr uint32_t kThreadStateOffset = 0x2C8u;
@@ -584,7 +584,7 @@ void GuestFiberManager::FiberProc(void* param)
CpuContextScope scope(cpu);
int startDeferAttempts = 0;
while (entryPoint == 0x8024373c) { // EGG::Thread::start
while (entryPoint == MKW_GADDR(8024373c)) { // EGG::Thread::start
uint32_t vtable = 0;
uint32_t startFn = 0;
try {
+15
View File
@@ -0,0 +1,15 @@
#include "guest_abi/guest_region.h"
#include <cstdio>
#include <cstdlib>
namespace GuestRegion {
void FatalUnknownAddress(uint32_t pal) noexcept {
std::fprintf(stderr, "Guest region: PAL address 0x%08X has no %s address in guest_address_table.inc\n",
pal, CurrentFacts().gameId);
std::fflush(stderr);
std::abort();
}
} // namespace GuestRegion
+4 -4
View File
@@ -21,10 +21,10 @@ namespace {
constexpr uint32_t kDefaultSampleRate = 32000u;
constexpr uint32_t kAudioChannels = 2u;
constexpr uint32_t kBytesPerSample = 2u;
constexpr uint32_t kAIInitializedAddr = 0x80386448u;
constexpr uint32_t kAICallbackBusyAddr = 0x8038644Cu;
constexpr uint32_t kAICallbackStackSwitchAddr = 0x8038647Cu;
constexpr uint32_t kAIDmaCallbackAddr = 0x80386480u;
const uint32_t kAIInitializedAddr = MKW_GADDR(80386448);
const uint32_t kAICallbackBusyAddr = MKW_GADDR(8038644C);
const uint32_t kAICallbackStackSwitchAddr = MKW_GADDR(8038647C);
const uint32_t kAIDmaCallbackAddr = MKW_GADDR(80386480);
// Max completed 3 ms DMA blocks delivered per tick. Draining several at once catches up
// backlog from a long frame without letting a large stall spiral into an unbounded loop.
+5 -6
View File
@@ -3,6 +3,7 @@
#include "hle_stubs.h"
#include "memory.h"
#include "ppc_runtime.h"
#include "guest_function_imports.h"
#include <cmath>
#include <cstddef>
@@ -10,8 +11,6 @@
#include <cstring>
extern "C" void func_8012B830(CpuContext* ctx);
#if defined(__clang__)
// PowerPC uses discrete fmuls/fadds; a fused multiply-add would change sample rounding.
#pragma clang fp contract(off)
@@ -24,8 +23,8 @@ constexpr uint32_t kSamplesPerFrame = 96;
constexpr uint32_t kChannels = 3;
// .sdata2 constants the guest function loads through r2.
constexpr uint32_t kOneConstantAddr = 0x80388588u; // 1.0f
constexpr uint32_t kScaleConstantAddr = 0x8038858Cu; // 0.6f send pre-scale
const uint32_t kOneConstantAddr = MKW_GADDR(80388588); // 1.0f
const uint32_t kScaleConstantAddr = MKW_GADDR(8038858C); // 0.6f send pre-scale
// AXFX_REVERBSTD_EXP field offsets (byte offsets into the struct in r4).
constexpr uint32_t kFieldPreDelayCoef = 0x18;
@@ -346,7 +345,7 @@ extern "C" void AXFXReverbStdExpCallback_8012b830(CpuContext* ctx) {
try {
flags = Memory::Read32(stateAddr + ReverbStd::kFieldFlags);
} catch (const Memory::AccessViolation&) {
func_8012B830(ctx);
MKW_GUEST_FUNC(8012B830)(ctx);
return;
}
if (flags != 0) {
@@ -364,7 +363,7 @@ extern "C" void AXFXReverbStdExpCallback_8012b830(CpuContext* ctx) {
built = false;
}
if (!built) {
func_8012B830(ctx);
MKW_GUEST_FUNC(8012B830)(ctx);
return;
}
+14 -13
View File
@@ -4,6 +4,7 @@
// Hot accessors stay header-inline because the shipped build links runtime shards without LTO.
#include "isa/big_endian.h"
#include "guest_abi/guest_region.h"
#include "memory.h"
#include <algorithm>
@@ -31,21 +32,21 @@ constexpr uint32_t kMailReset = kTaskMailToDsp | 0x0002u;
constexpr size_t kResamplingCoefficientCount = 0x800;
constexpr uint32_t kMailContinue = kTaskMailToDsp | 0x0003u;
constexpr uint32_t kAxSamplesPerFrame = 96u;
constexpr uint32_t kAxDspTaskAddr = 0x802F81A0u;
constexpr uint32_t kDspInitializedAddr = 0x80386608u;
constexpr uint32_t kDspAssertPendingAddr = 0x80386610u;
constexpr uint32_t kDspAssertTaskAddr = 0x80386614u;
constexpr uint32_t kDspCurrentTaskAddr = 0x8038661Cu;
constexpr uint32_t kDspFirstTaskAddr = 0x80386620u;
constexpr uint32_t kDspRunningTaskAddr = 0x80386624u;
constexpr uint32_t kAxIramMmemAddr = 0x8027F820u;
constexpr uint32_t kAxDramMmemAddr = 0x802F8200u;
const uint32_t kAxDspTaskAddr = MKW_GADDR(802F81A0);
const uint32_t kDspInitializedAddr = MKW_GADDR(80386608);
const uint32_t kDspAssertPendingAddr = MKW_GADDR(80386610);
const uint32_t kDspAssertTaskAddr = MKW_GADDR(80386614);
const uint32_t kDspCurrentTaskAddr = MKW_GADDR(8038661C);
const uint32_t kDspFirstTaskAddr = MKW_GADDR(80386620);
const uint32_t kDspRunningTaskAddr = MKW_GADDR(80386624);
const uint32_t kAxIramMmemAddr = MKW_GADDR(8027F820);
const uint32_t kAxDramMmemAddr = MKW_GADDR(802F8200);
constexpr uint32_t kAxDramLength = 64u;
constexpr uint32_t kAxDramDspAddr = 3282u;
constexpr uint32_t kAxInitCallback = 0x80126948u;
constexpr uint32_t kAxResumeCallback = 0x80126954u;
constexpr uint32_t kAxDoneCallback = 0x801269A8u;
constexpr uint32_t kAxRequestCallback = 0x801269B8u;
const uint32_t kAxInitCallback = MKW_GADDR(80126948);
const uint32_t kAxResumeCallback = MKW_GADDR(80126954);
const uint32_t kAxDoneCallback = MKW_GADDR(801269A8);
const uint32_t kAxRequestCallback = MKW_GADDR(801269B8);
extern uint32_t g_axTaskPtr;
+1 -1
View File
@@ -68,7 +68,7 @@ void MarkDspInitialized() {
void ResetDspTaskGlobals() {
Memory::TryWrite32(kDspAssertPendingAddr, 0);
Memory::TryWrite32(kDspAssertTaskAddr, 0);
Memory::TryWrite32(0x80386618u, 0);
Memory::TryWrite32(MKW_GADDR(80386618), 0);
Memory::TryWrite32(kDspCurrentTaskAddr, 0);
Memory::TryWrite32(kDspFirstTaskAddr, 0);
Memory::TryWrite32(kDspRunningTaskAddr, 0);
+7 -11
View File
@@ -1475,19 +1475,15 @@ void InitForAXOut(CpuContext* ctx) {
Memory::Write32(g_axTaskPtr + 0x2cu, kAxResumeCallback);
Memory::Write32(g_axTaskPtr + 0x30u, kAxDoneCallback);
Memory::Write32(g_axTaskPtr + 0x34u, kAxRequestCallback);
if (ctx) {
const uint32_t r13 = ctx->gpr[13];
Memory::Write32(g_axTaskPtr + 0x10u, Memory::Read16(r13 - 0x73fcu));
Memory::Write16(g_axTaskPtr + 0x24u, Memory::Read16(r13 - 0x7400u));
Memory::Write16(g_axTaskPtr + 0x26u, Memory::Read16(r13 - 0x73feu));
}
// __AXOutInitDSP's ucode length and DSP init/resume vectors, then its two flags.
(void)ctx;
Memory::Write32(g_axTaskPtr + 0x10u, Memory::Read16(MKW_GADDR(80385804)));
Memory::Write16(g_axTaskPtr + 0x24u, Memory::Read16(MKW_GADDR(80385800)));
Memory::Write16(g_axTaskPtr + 0x26u, Memory::Read16(MKW_GADDR(80385802)));
Instance().ConfigureFromTask(g_axTaskPtr);
LinkSingleDspTask(g_axTaskPtr);
if (ctx) {
const uint32_t r13 = ctx->gpr[13];
Memory::Write32(r13 - 0x66d8u, 1);
Memory::Write32(r13 - 0x66dcu, 0);
}
Memory::Write32(MKW_GADDR(80386528), 1);
Memory::Write32(MKW_GADDR(80386524), 0);
}
void Stop() {
+1 -2
View File
@@ -12,7 +12,6 @@
#include <cctype>
static constexpr uint32_t MKW_TITLE_ID_HI = 0x00010004;
static constexpr uint32_t MKW_TITLE_ID_LO = 0x524D4350; // "RMCP" fallback
static bool IsValidEspTitleCode(uint32_t code) {
for (int shift = 24; shift >= 0; shift -= 8) {
@@ -32,7 +31,7 @@ static uint32_t CurrentTitleIdLo() {
}
}
return MKW_TITLE_ID_LO;
return ::GuestRegion::CurrentFacts().gameCode;
}
// The guest asked for its title ID or data directory through a pointer we cannot
+15 -11
View File
@@ -22,8 +22,8 @@ static void EGG__DrawGX__SetVtxState_HLE(uint32_t state) {
case 0:
setFmt(GX_VA_POS, GX_POS_XYZ, GX_S16, 0xE);
setFmt(GX_VA_NRM, GX_NRM_XYZ, GX_S16, 0xE);
setArray(GX_VA_POS, 0x802574a0u, 6);
setArray(GX_VA_NRM, 0x802574e0u, 6);
setArray(GX_VA_POS, MKW_GADDR(802574a0), 6);
setArray(GX_VA_NRM, MKW_GADDR(802574e0), 6);
setDesc(GX_VA_POS, GX_INDEX8);
setDesc(GX_VA_NRM, GX_INDEX8);
break;
@@ -45,22 +45,22 @@ static void EGG__DrawGX__SetVtxState_HLE(uint32_t state) {
if (state == 7) setDesc(GX_VA_TEX0, GX_DIRECT);
setFmt(GX_VA_POS, GX_POS_XY, GX_S16, 0xE); setFmt(GX_VA_NRM, GX_NRM_XYZ, GX_S16, 0xE);
if (state == 7) setFmt(GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
setArray(GX_VA_POS, 0x80257520u, 4); setArray(GX_VA_NRM, 0x80388b80u, 6);
setArray(GX_VA_POS, MKW_GADDR(80257520), 4); setArray(GX_VA_NRM, MKW_GADDR(80388b80), 6);
break;
case 9:
setDesc(GX_VA_POS, GX_INDEX8); setDesc(GX_VA_NRM, GX_INDEX8); setDesc(GX_VA_TEX0, GX_DIRECT);
setFmt(GX_VA_POS, GX_POS_XYZ, GX_S16, 0xE); setFmt(GX_VA_NRM, GX_NRM_XYZ, GX_S16, 0xE);
setFmt(GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
setArray(GX_VA_POS, 0x80257540u, 6); setArray(GX_VA_NRM, 0x80388ba0u, 6);
setArray(GX_VA_POS, MKW_GADDR(80257540), 6); setArray(GX_VA_NRM, MKW_GADDR(80388ba0), 6);
break;
case 10: case 11: case 12: case 13: {
const bool useAltPos = (state == 11 || state == 13);
const bool useTex = (state == 10 || state == 11);
setDesc(GX_VA_POS, GX_INDEX8);
if (useTex) setDesc(GX_VA_TEX0, GX_INDEX8);
const uint32_t posBase = useAltPos ? 0x80388be0u : 0x80388bc0u;
const uint32_t posBase = useAltPos ? MKW_GADDR(80388be0) : MKW_GADDR(80388bc0);
setArray(GX_VA_POS, posBase, 2);
if (useTex) setArray(GX_VA_TEX0, 0x80388be0u, 2);
if (useTex) setArray(GX_VA_TEX0, MKW_GADDR(80388be0), 2);
setFmt(GX_VA_POS, GX_POS_XY, GX_U8, 0);
if (useTex) setFmt(GX_VA_TEX0, GX_TEX_ST, GX_U8, 0);
break;
@@ -88,9 +88,13 @@ extern "C" void EGG__LightTexture__SetupTevFinish_HLE_8022e2bc(CpuContext* ctx)
++remainder;
}
const uint32_t kFogOriginDefault = MKW_GADDR(80388DD0);
const uint32_t kFogOriginMode2 = MKW_GADDR(80388DF4);
const uint32_t kFogSpan = MKW_GADDR(80388DE0);
const uint32_t mode = Memory::Read32(self + 0x44);
const float origin = Memory::ReadFloat32(ctx->gpr[2] + ((mode == 2) ? -25004 : -25040));
const float span = Memory::ReadFloat32(ctx->gpr[2] + -25024);
const float origin =
Memory::ReadFloat32((mode == 2) ? kFogOriginMode2 : kFogOriginDefault);
const float span = Memory::ReadFloat32(kFogSpan);
const float end = static_cast<float>(origin + span);
const float lower = static_cast<float>(origin - span);
@@ -117,8 +121,8 @@ PPC_NATIVE_OVERRIDE_VOID(8022e2bc, EGG__LightTexture__SetupTevFinish_HLE_8022e2b
// ============================================================================
extern "C" void EGG__AsyncDisplay__endRender_HLE_8020ff9c(CpuContext* ctx) {
uint32_t p = ctx->gpr[3]; ctx->gpr[3] = p; ctx->lr = 0x8020FF9C;
InvokeIndirectCpu(0x80219FB4u, ctx);
InvokeIndirectCpu(0x8016ED50u, ctx);
uint32_t p = ctx->gpr[3]; ctx->gpr[3] = p; ctx->lr = MKW_GADDR(8020FF9C);
InvokeIndirectCpu(MKW_GADDR(80219FB4), ctx);
InvokeIndirectCpu(MKW_GADDR(8016ED50), ctx);
}
PPC_NATIVE_OVERRIDE_VOID(8020FF9C, EGG__AsyncDisplay__endRender_HLE_8020ff9c, (CpuContext* ctx), (ctx));
+1 -1
View File
@@ -28,7 +28,7 @@ namespace {
// index sees the literal PPC_NATIVE_OVERRIDE_VOID invocation. Hiding it inside
// this macro would leave the index unable to associate an address with the stub.
#define GX_FATAL_STUB(addr, sym) \
extern "C" void gx_stub_##addr(CpuContext* ctx) { (void)ctx; HaltGX(0x##addr, sym); }
extern "C" void gx_stub_##addr(CpuContext* ctx) { (void)ctx; HaltGX(MKW_GADDR(addr), sym); }
GX_FATAL_STUB(8016b49c, "__GX__DefaultTexRegionCallback_8016b49c") PPC_NATIVE_OVERRIDE_VOID(8016b49c, gx_stub_8016b49c, (CpuContext* ctx), (ctx));
GX_FATAL_STUB(8016b590, "__GX__DefaultTlutRegionCallback_8016b590") PPC_NATIVE_OVERRIDE_VOID(8016b590, gx_stub_8016b590, (CpuContext* ctx), (ctx));
+24 -25
View File
@@ -25,8 +25,8 @@ extern "C" GXFifoObj* GXInit(void* base, u32 size);
extern "C" uint32_t GX__Init_8016b850(uint32_t fifoBase, uint32_t fifoSize)
{
GX_SYNC_INLINE();
constexpr uint32_t kFifoObjAddr = 0x80343740u;
constexpr uint32_t kGXDataAddr = 0x803437C0u;
const uint32_t kFifoObjAddr = MKW_GADDR(80343740);
const uint32_t kGXDataAddr = MKW_GADDR(803437C0);
constexpr uint32_t kGXDataSize = 0x600u;
GXInit(GuestToHostPtr(fifoBase, fifoSize), fifoSize);
@@ -78,7 +78,6 @@ extern "C" uint32_t GX__Init_8016b850(uint32_t fifoBase, uint32_t fifoSize)
} catch (...) {}
__GX__SetTmemConfig_80171458(2);
RT_LOGF(RT_TAG_GX, "GX initialized, FIFO at 0x%08X (base=0x%08X size=0x%08X)\n",
kFifoObjAddr, fifoBase, fifoSize);
@@ -108,14 +107,14 @@ extern "C" void __GX__FifoInit_8016d180()
{
constexpr uint32_t kCpInterruptId = 0x11u;
constexpr uint32_t kCpInterruptMask = 0x4000u;
constexpr uint32_t kCpInterruptHandlerAddr = 0x8016c668u;
constexpr uint32_t kGxCurrentThreadPtrAddr = 0x803867c4u;
constexpr uint32_t kGxThreadQueueAddr = 0x803867c0u;
constexpr uint32_t kCpuFifoObjAddr = 0x80343de4u;
constexpr uint32_t kGpFifoObjAddr = 0x80343dc0u;
const uint32_t kCpInterruptHandlerAddr = MKW_GADDR(8016c668);
const uint32_t kGxCurrentThreadPtrAddr = MKW_GADDR(803867c4);
const uint32_t kGxThreadQueueAddr = MKW_GADDR(803867c0);
const uint32_t kCpuFifoObjAddr = MKW_GADDR(80343de4);
const uint32_t kGpFifoObjAddr = MKW_GADDR(80343dc0);
constexpr size_t kFifoObjSize = 0x24u;
constexpr uint32_t kFifoWrapFlagAddr = 0x803867b0u;
constexpr uint32_t kFifoWrapFlag2Addr = 0x803867b1u;
const uint32_t kFifoWrapFlagAddr = MKW_GADDR(803867b0);
const uint32_t kFifoWrapFlag2Addr = MKW_GADDR(803867b1);
__OSSetInterruptHandler_801a65f8_hle(kCpInterruptId, kCpInterruptHandlerAddr);
__OSUnmaskInterrupts_801a69bc_hle(kCpInterruptMask);
@@ -143,9 +142,9 @@ extern "C" void __GX__PEInit_8016ee14()
constexpr uint32_t kPeFinishInterruptId = 0x13u;
constexpr uint32_t kPeTokenInterruptMask = 0x1000u;
constexpr uint32_t kPeFinishInterruptMask = 0x2000u;
constexpr uint32_t kPeTokenHandlerAddr = 0x8016eccc;
constexpr uint32_t kPeFinishHandlerAddr = 0x8016ed94;
constexpr uint32_t kPeThreadQueueAddr = 0x803867d0u;
const uint32_t kPeTokenHandlerAddr = MKW_GADDR(8016eccc);
const uint32_t kPeFinishHandlerAddr = MKW_GADDR(8016ed94);
const uint32_t kPeThreadQueueAddr = MKW_GADDR(803867d0);
__OSSetInterruptHandler_801a65f8_hle(kPeTokenInterruptId, kPeTokenHandlerAddr);
__OSSetInterruptHandler_801a65f8_hle(kPeFinishInterruptId, kPeFinishHandlerAddr);
@@ -181,20 +180,20 @@ extern "C" void GX__BeginDisplayList_80172e00(uint32_t la, uint32_t s) {
uint32_t gd = Memory::Read32(kGXDataPtrAddr);
if (!gd) return;
if (Memory::Read32(gd + 0x5FCu)) GX__SetDirtyState_8016ee78();
if (Memory::Read8(gd + 0x5F9u)) std::memcpy(Memory::GetPointer(0x80344110, 0x600), Memory::GetPointer(gd, 0x600), 0x600);
Memory::Write32(0x80344094, la + s - 4u);
Memory::Write32(0x803440AC, 0);
Memory::Write32(0x80344090, la);
Memory::Write32(0x80344098, s);
Memory::Write32(0x803440A4, la);
Memory::Write32(0x803440A8, la);
if (Memory::Read8(gd + 0x5F9u)) std::memcpy(Memory::GetPointer(MKW_GADDR(80344110), 0x600), Memory::GetPointer(gd, 0x600), 0x600);
Memory::Write32(MKW_GADDR(80344094), la + s - 4u);
Memory::Write32(MKW_GADDR(803440AC), 0);
Memory::Write32(MKW_GADDR(80344090), la);
Memory::Write32(MKW_GADDR(80344098), s);
Memory::Write32(MKW_GADDR(803440A4), la);
Memory::Write32(MKW_GADDR(803440A8), la);
Memory::Write8(gd + 0x5F8u, 1u);
// Mirror the guest fifo-object fields the FIFO write path consumes so
// HleFifoWrite never has to read them back out of guest memory.
BeginDisplayListRecording(la, s);
GXFlush();
GX__GetCPUFifo_8016cf10(0x80344710);
GX__SetCPUFifo_8016c94c(0x80344090);
GX__GetCPUFifo_8016cf10(MKW_GADDR(80344710));
GX__SetCPUFifo_8016c94c(MKW_GADDR(80344090));
} catch (...) {}
}
PPC_NATIVE_OVERRIDE_VOID(80172e00, GX__BeginDisplayList_80172e00, (uint32_t la, uint32_t s), (la, s));
@@ -209,9 +208,9 @@ static void EndDisplayListInline() {
extern "C" uint32_t GX__EndDisplayList_80172eb4() {
try {
GXFlush();
GX__GetCPUFifo_8016cf10(0x80344090);
GX__GetCPUFifo_8016cf10(MKW_GADDR(80344090));
const uint8_t wrapped = Memory::Read8(kDlFifoAddr + kDlWrapFlagOffset);
GX__SetCPUFifo_8016c94c(0x80344710);
GX__SetCPUFifo_8016c94c(MKW_GADDR(80344710));
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
if (gd) {
@@ -219,7 +218,7 @@ extern "C" uint32_t GX__EndDisplayList_80172eb4() {
const int32_t interruptLevel = OS__DisableInterrupts_801a65ac();
const uint32_t savedWord8 = Memory::Read32(gd + 0x08u);
std::memcpy(Memory::GetPointer(gd, 0x600),
Memory::GetPointer(0x80344110, 0x600),
Memory::GetPointer(MKW_GADDR(80344110), 0x600),
0x600);
Memory::Write32(gd + 0x08u, savedWord8);
OS__RestoreInterrupts_801a65d4(interruptLevel);
+6 -5
View File
@@ -1,6 +1,7 @@
#pragma once
#include "hle_stubs.h"
#include "guest_abi/guest_region.h"
#include "memory.h"
#include "gx_guest_write.h"
#include "ppc_runtime.h"
@@ -55,13 +56,13 @@ bool is_offscreen() noexcept;
}
// --- Constants ---
constexpr uint32_t kGXDataPtrAddr = 0x803886C8;
constexpr uint32_t kDlFifoAddr = 0x80344090;
constexpr uint32_t kDlWritePtrAddr = 0x803440A4;
constexpr uint32_t kDlCountAddr = 0x803440AC;
const uint32_t kGXDataPtrAddr = MKW_GADDR(803886C8);
const uint32_t kDlFifoAddr = MKW_GADDR(80344090);
const uint32_t kDlWritePtrAddr = MKW_GADDR(803440A4);
const uint32_t kDlCountAddr = MKW_GADDR(803440AC);
constexpr uint32_t kDlWrapFlagOffset = 0x20;
constexpr uint32_t kMaxTluts = 20;
constexpr uint32_t kGxDrawDoneFlagAddr = 0x803867d8;
const uint32_t kGxDrawDoneFlagAddr = MKW_GADDR(803867d8);
// --- External Declarations ---
extern "C" void GXInitTexObjTlut(GXTexObj* obj, u32 tlut);
+3 -5
View File
@@ -2,17 +2,15 @@
#include "abi_bridge.h"
#include "ppc_runtime.h"
#include "guest_function_imports.h"
namespace NetworkHle {
extern "C" void func_801D8D30(CpuContext* ctx);
extern "C" void func_801D9E94(CpuContext* ctx);
static uint32_t GetNHttpSystemInfo(CpuContext* ctx, uint32_t& savedR3, uint32_t& savedR4, uint32_t& savedR5) {
savedR3 = ctx->gpr[3];
savedR4 = ctx->gpr[4];
savedR5 = ctx->gpr[5];
func_801D9E94(ctx);
MKW_GUEST_FUNC(801D9E94)(ctx);
const uint32_t systemInfo = ctx->gpr[3];
ctx->gpr[3] = savedR3;
ctx->gpr[4] = savedR4;
@@ -47,7 +45,7 @@ extern "C" void NHTTPStartup_Reentrant_HLE_801d8d30(CpuContext* ctx) {
ctx->gpr[3] = savedR3;
ctx->gpr[4] = savedR4;
ctx->gpr[5] = savedR5;
func_801D8D30(ctx);
MKW_GUEST_FUNC(801D8D30)(ctx);
const int32_t result = static_cast<int32_t>(ctx->gpr[3]);
if (result < 0) {
+2 -1
View File
@@ -1,4 +1,5 @@
#include "network_internal.h"
#include "guest_abi/guest_region.h"
#include "fiber_manager.h"
#include "hle/net/network.h"
@@ -627,7 +628,7 @@ static bool InitializeDeferredSyncRoute(DeferredNetworkRoute& route, uint32_t wa
// early scheduler setup. Capture its identity before yielding so a late
// resolver result cannot wake a stale/reused stack queue after cancellation.
constexpr uint32_t kOSRunningContextAddr = 0x800000E4u;
constexpr uint32_t kDefaultThreadContextAddr = 0x80347498u;
const uint32_t kDefaultThreadContextAddr = MKW_GADDR(80347498);
uint32_t expectedThread = Memory::Read32(kOSRunningContextAddr);
if (expectedThread == 0) {
expectedThread = kDefaultThreadContextAddr;
+8 -10
View File
@@ -14,9 +14,7 @@
#include "hle/net/network.h"
#include "guest_small_data.h"
#include "os_internal.h"
extern "C" void func_801AADE0(CpuContext* ctx);
extern "C" void func_801A0620(CpuContext* ctx);
#include "guest_function_imports.h"
// ============================================================================
// Alarm queue helpers
@@ -100,7 +98,7 @@ void SanitizeAlarmQueue(CpuContext* cpu)
return;
}
const uint32_t queueBase = cpu->gpr[13] - kAlarmQueueOffsetFromR13;
const uint32_t queueBase = kAlarmQueueAddr;
try {
const uint32_t head = ::Memory::Read32(queueBase);
const uint32_t tail = ::Memory::Read32(queueBase + 4u);
@@ -169,7 +167,7 @@ bool ProcessAlarmQueue(CpuContext* cpu, int maxToProcess)
{
AlarmProcessScope alarmProcessScope;
SanitizeAlarmQueue(cpu);
const uint32_t queueBase = cpu->gpr[13] - kAlarmQueueOffsetFromR13;
const uint32_t queueBase = kAlarmQueueAddr;
try {
for (int i = 0; i < maxToProcess; ++i) {
@@ -209,7 +207,7 @@ bool ProcessAlarmQueue(CpuContext* cpu, int maxToProcess)
cpu->gpr[5] = 0;
cpu->gpr[6] = 0;
cpu->gpr[7] = handler;
func_801A0620(cpu);
MKW_GUEST_FUNC(801A0620)(cpu);
}
if (handler != 0) {
@@ -309,7 +307,7 @@ extern "C" void OSSetAlarm_HLE_801a0870(CpuContext* ctx)
return;
}
const uint32_t queueBase = cpu->gpr[13] - kAlarmQueueOffsetFromR13;
const uint32_t queueBase = kAlarmQueueAddr;
try {
const uint32_t head = ::Memory::Read32(queueBase);
if (head == 0) {
@@ -427,7 +425,7 @@ extern "C" void OS__SetPeriodicAlarm_801a08e0(CpuContext* ctx)
cpu->gpr[3] = startHi;
cpu->gpr[4] = startLo;
func_801AADE0(cpu);
MKW_GUEST_FUNC(801AADE0)(cpu);
Memory::Write32(alarm + 0x20u, cpu->gpr[3]);
Memory::Write32(alarm + 0x24u, cpu->gpr[4]);
@@ -435,7 +433,7 @@ extern "C" void OS__SetPeriodicAlarm_801a08e0(CpuContext* ctx)
cpu->gpr[5] = 0;
cpu->gpr[6] = 0;
cpu->gpr[7] = handler;
func_801A0620(cpu);
MKW_GUEST_FUNC(801A0620)(cpu);
cpu->gpr[3] = static_cast<uint32_t>(OS__RestoreInterrupts_801a65d4(level));
}
@@ -464,7 +462,7 @@ extern "C" uint32_t RFLiIsWorking_HLE_800bd860()
DecrementSchedulerDisableCount();
// Now return the actual "working" status
constexpr uint32_t kRflManagerPtrAddr = 0x80386298u;
const uint32_t kRflManagerPtrAddr = MKW_GADDR(80386298);
constexpr uint32_t kWorkingFlagOffset = 0x1b34u;
try {
+15 -20
View File
@@ -17,19 +17,17 @@
#include "recomp_mod_loader.h"
#include "runtime_log.h"
#include "system_bridge.h"
extern "C" void func_801A961C(CpuContext* ctx);
extern "C" void func_8055531C(CpuContext* ctx);
#include "guest_function_imports.h"
extern "C" void OSInitAlarm_RecompModLateInit_801a961c(CpuContext* ctx) {
func_801A961C(ctx);
MKW_GUEST_FUNC(801A961C)(ctx);
}
REGISTER_NATIVE_FUNCTION_AS(0x801A961C, OSInitAlarm_RecompModLateInit_801a961c, "OSInitAlarm_RecompModLateInit_801a961c");
extern "C" void StaticRProlog_RecompModInit_8055531c(CpuContext* ctx) {
RecompMod::RunMemoryInitializers();
func_8055531C(ctx);
MKW_GUEST_FUNC(8055531C)(ctx);
RecompMod::RunPostRelInitializers();
}
@@ -107,13 +105,14 @@ extern "C" void HLE_SISetSamplingRate_801b3acc(uint32_t msec)
// Video Interface (VI) - TV output.
// VIGetTvFormat (0x801bacd8): CRITICAL, must return 1 (VI_PAL) not 0, or PAL builds
// misbehave/panic.
// VIGetTvFormat (0x801bacd8): CRITICAL, must report the game's own TV format or it
// misbehaves/panics.
extern "C" uint32_t HLE_VIGetTvFormat_801bacd8()
{
// VI_NTSC = 0, VI_PAL = 1, VI_MPAL = 2
RT_LOG(RT_TAG_OS) << "HLE_VIGetTvFormat_801bacd8 called: returning VI_PAL (1)" << std::endl;
return 1;
RT_LOG(RT_TAG_OS) << "HLE_VIGetTvFormat_801bacd8 called: returning " << ::GuestRegion::CurrentFacts().viTvFormat
<< std::endl;
return ::GuestRegion::CurrentFacts().viTvFormat;
}
REGISTER_NATIVE_FUNCTION(0x801B2DE0, SIInit_801b2de0);
@@ -176,22 +175,18 @@ extern "C" uint32_t __OSInitSTM_HLE_801ab848(CpuContext* ctx)
RT_LOG(RT_TAG_OS) << "__OSInitSTM_HLE_801ab848 called: initializing STM state" << std::endl;
// R13 (SDA2) holds the base for small data variables
const uint32_t r13 = cpu->gpr[13];
if (r13 == 0) {
RT_LOG(RT_TAG_OS) << "__OSInitSTM: Warning - R13 is 0, cannot write state." << std::endl;
return 0;
}
// STM_Initialized, /dev/stm/immediate and /dev/stm/eventhook.
const uint32_t kStmInitializedAddr = MKW_GADDR(80386934);
const uint32_t kStmImmediateHandleAddr = MKW_GADDR(80386938);
const uint32_t kStmEventHookHandleAddr = MKW_GADDR(8038693C);
// Offsets from disassembly: r13-0x62cc=STM_Initialized, r13-0x62c8=/dev/stm/immediate,
// r13-0x62c4=/dev/stm/eventhook.
try {
// Mark STM as initialized
::Memory::Write32(r13 - 0x62ccu, 1);
::Memory::Write32(kStmInitializedAddr, 1);
// Fake non-zero handles so callers' zero-checks pass.
::Memory::Write32(r13 - 0x62c8u, 0x00535401); // "ST\x01"
::Memory::Write32(r13 - 0x62c4u, 0x00535402); // "ST\x02"
::Memory::Write32(kStmImmediateHandleAddr, 0x00535401); // "ST\x01"
::Memory::Write32(kStmEventHookHandleAddr, 0x00535402); // "ST\x02"
// Default Power/Reset callback pointers are left unset; safe since we never fire
// the STM hardware interrupt that would invoke them.
+9 -9
View File
@@ -24,7 +24,7 @@ extern std::atomic<bool> g_interrupts_enabled;
// observable state, not just bookkeeping.
extern std::atomic<uint32_t> g_interrupt_mask;
constexpr uint32_t kInterruptHandlerTablePtrAddr = 0x803868f8u;
const uint32_t kInterruptHandlerTablePtrAddr = MKW_GADDR(803868f8);
constexpr uint32_t kInterruptHandlerTableAddr = 0x80003040u;
constexpr size_t kInterruptHandlerTableBytes = 0x80u;
constexpr uint32_t kInterruptMaskLoAddr = 0x800000c4u;
@@ -36,17 +36,17 @@ constexpr uint32_t kThreadListHeadAddr = 0x800000dcu; // First thread in thre
constexpr uint32_t kThreadListTailAddr = 0x800000e0u; // Last added thread (tail of thread list)
constexpr uint32_t kOSRunningContextAddr = 0x800000e4u; // Currently running thread context
constexpr uint32_t kDefaultThreadContextAddr = 0x80347498u;
constexpr uint32_t kIdleThreadContextAddr = 0x803478b0u;
constexpr uint32_t kThreadQueueArrayAddr = 0x803477b0u;
const uint32_t kDefaultThreadContextAddr = MKW_GADDR(80347498);
const uint32_t kIdleThreadContextAddr = MKW_GADDR(803478b0);
const uint32_t kThreadQueueArrayAddr = MKW_GADDR(803477b0);
constexpr size_t kThreadQueueArrayBytes = 0x100u;
constexpr uint32_t kSwitchThreadCallbackPtrAddr = 0x80385ae0u;
constexpr uint32_t kSchedulerReschedCounterAddr = 0x8038691cu;
constexpr uint32_t kSchedulerPendingFlagAddr = 0x80386920u;
const uint32_t kSwitchThreadCallbackPtrAddr = MKW_GADDR(80385ae0);
const uint32_t kSchedulerReschedCounterAddr = MKW_GADDR(8038691c);
const uint32_t kSchedulerPendingFlagAddr = MKW_GADDR(80386920);
// RVL OS uses this as the OSDisableScheduler/OSEnableScheduler nesting count.
// SelectThread exits early while the count is non-zero.
constexpr uint32_t kSchedulerIdleFlagAddr = 0x80386918u;
constexpr uint32_t kAlarmQueueOffsetFromR13 = 0x6360u;
const uint32_t kSchedulerIdleFlagAddr = MKW_GADDR(80386918);
const uint32_t kAlarmQueueAddr = MKW_GADDR(803868A0);
constexpr uint32_t kThreadStateOffset = 0x2C8u;
constexpr uint32_t kThreadAttrOffset = 0x2CAu;
+9 -10
View File
@@ -391,14 +391,12 @@ extern "C" uint32_t OSSetPowerCallback_801ab75c(CpuContext* ctx)
if (!cpu) return 0;
const uint32_t newCallback = cpu->gpr[3];
const uint32_t r13 = cpu->gpr[13];
// Assembly defines the default callback address as (0x801b0000 - 0x43f4)
constexpr uint32_t kDefaultCallbackAddr = 0x801b0000u - 0x43f4u; // 0x801abc0c
// Offsets from R13 (SDA2)
const uint32_t kCallbackPtrAddr = r13 - 0x62b8u;
const uint32_t kHandlerActiveAddr = r13 - 0x62c0u;
// The SDK's default power callback, the registered callback pointer and the
// "event handler registered" flag.
const uint32_t kDefaultCallbackAddr = MKW_GADDR(801ABC0C);
const uint32_t kCallbackPtrAddr = MKW_GADDR(80386948);
const uint32_t kHandlerActiveAddr = MKW_GADDR(80386940);
RT_LOG(RT_TAG_OS) << "OSSetPowerCallback_801ab75c called: newCB=0x"
<< std::hex << newCallback << std::dec << std::endl;
@@ -489,12 +487,13 @@ extern "C" int32_t IPCCltInit_80193478(CpuContext* ctx)
RT_LOG(RT_TAG_OS) << "IPCCltInit_80193478 called: calling IPCInit for buffer setup" << std::endl;
// Sets 0x803867EC/F0/E8 (IPC buffer lo/hi + init flag) from __OSGetIPCBufferLo/Hi.
InvokeIndirectCpu(0x80192F7Cu, ctx);
InvokeIndirectCpu(MKW_GADDR(80192F7C), ctx);
// Advance the buffer lo pointer by 0x1000 (iosHeap size), matching real IPCCltInit.
uint32_t bufferLo = Memory::Read32(ctx->gpr[13] + -25620); // 0x803867EC at r13-0x6414
const uint32_t kIpcBufferLoGlobal = MKW_GADDR(803867EC);
uint32_t bufferLo = Memory::Read32(kIpcBufferLoGlobal);
uint32_t newBufLo = bufferLo + 0x1000; // Advance by 4KB for iosHeap
Memory::Write32(ctx->gpr[13] + -25620, newBufLo);
Memory::Write32(kIpcBufferLoGlobal, newBufLo);
RT_LOG(RT_TAG_OS) << "IPCCltInit: IPC buffer lo advanced from 0x" << std::hex << bufferLo
<< " to 0x" << newBufLo << std::dec << std::endl;
+2 -2
View File
@@ -10,6 +10,7 @@
#include "fiber_manager.h"
#include "runtime_log.h"
#include "os_internal.h"
#include "guest_function_imports.h"
namespace {
void LinkMutexToThread(uint32_t threadPtr, uint32_t mutexPtr)
@@ -119,7 +120,6 @@ static void TryInvokeSwitchCallback(uint32_t oldCtx, uint32_t newCtx, CpuContext
// Forward declare for SelectThread to use
extern "C" void OS__SetCurrentContext_801a1e70(uint32_t contextAddr);
extern "C" void func_801A1ED8(CpuContext* ctx);
// ============================================================================
// SelectThread HLE - Thread Scheduler (Fiber-Aware)
@@ -200,7 +200,7 @@ extern "C" void SelectThread_801a9c08(CpuContext* ctx)
const uint16_t modeFlags = ::Memory::Read16(runningContext + 0x1A2u);
if ((modeFlags & 0x0002u) == 0) {
cpu->gpr[3] = runningContext;
func_801A1ED8(cpu);
MKW_GUEST_FUNC(801A1ED8)(cpu);
if (cpu->gpr[3] != 0) {
cpu->gpr[3] = 0;
return;
+6 -12
View File
@@ -73,13 +73,7 @@ int32_t ComputeThreadEffectivePriority(uint32_t threadPtr)
bool IsThpVideoDecoderEntry(uint32_t entryFunc)
{
switch (entryFunc) {
case 0x805529A8u:
case 0x80552A74u:
return true;
default:
return false;
}
return entryFunc == MKW_GADDR(805529A8) || entryFunc == MKW_GADDR(80552A74);
}
void InsertThreadIntoQueueByPriority(uint32_t queuePtr, uint32_t threadPtr, int32_t priority)
@@ -216,7 +210,7 @@ void UnlockAllThreadMutexes(CpuContext* cpu, uint32_t threadPtr)
}
CpuContextScope scope(cpu);
cpu->gpr[3] = threadPtr;
InvokeIndirectCpu(0x801A8088u, cpu); // __OSUnlockAllMutex
InvokeIndirectCpu(MKW_GADDR(801A8088), cpu); // __OSUnlockAllMutex
}
// Shared tail of OSExitThread/OSCancelThread: clears context, delists if detached, marks
@@ -311,7 +305,7 @@ extern "C" void OSCreateThread_HLE_801a9e84(CpuContext* ctx)
cpu->gpr[3] = threadPtr;
cpu->gpr[4] = entryFunc;
cpu->gpr[5] = alignedStack - 8;
InvokeIndirectCpu(0x801A20BCu, cpu); // OSInitContext
InvokeIndirectCpu(MKW_GADDR(801A20BC), cpu); // OSInitContext
}
if (IsThpVideoDecoderEntry(entryFunc)) {
@@ -325,7 +319,7 @@ extern "C" void OSCreateThread_HLE_801a9e84(CpuContext* ctx)
}
::Memory::Write32(threadPtr + 0x84u, 0x801AA0F0u); // LR = OSExitThread
::Memory::Write32(threadPtr + 0x84u, MKW_GADDR(801AA0F0)); // LR = OSExitThread
::Memory::Write32(threadPtr + 0x0Cu, entryArg); // r3 = argument
// Stack info
@@ -341,8 +335,8 @@ extern "C" void OSCreateThread_HLE_801a9e84(CpuContext* ctx)
// Match the original OSCreateThread slow-path initialization that runs
// once scheduler globals are live. THP worker threads depend on these
// queue/list blocks being fully zeroed.
constexpr uint32_t kSchedulerInitFlagAddr = 0x80347130u;
constexpr uint32_t kThreadAttrSourceAddr = 0x80385AA8u;
const uint32_t kSchedulerInitFlagAddr = MKW_GADDR(80347130);
const uint32_t kThreadAttrSourceAddr = MKW_GADDR(80385AA8);
if (Memory::Contains(kSchedulerInitFlagAddr, 4) &&
::Memory::Read32(kSchedulerInitFlagAddr) != 0) {
uint32_t srr1 = ::Memory::Read32(threadPtr + 0x19Cu);
+4 -4
View File
@@ -74,7 +74,7 @@ PPC_NATIVE_OVERRIDE(801B1CAC, SCGetEuRgb60Mode_HLE, uint32_t, (), ());
extern "C" uint32_t SCGetProductArea_HLE()
{
return LookupProductRegion(0x8029CEB0u, 5, 13,
return LookupProductRegion(MKW_GADDR(8029CEB0), 5, 13,
RuntimeConsoleIdentity::Current().area);
}
@@ -82,8 +82,8 @@ PPC_NATIVE_OVERRIDE(801B23A0, SCGetProductArea_HLE, uint32_t, (), ());
extern "C" uint32_t SCGetProductCode_HLE()
{
// Original PAL SC storage for the six-byte CODE value.
constexpr uint32_t kProductCodeAddress = 0x803869E0u;
// Original SC storage for the six-byte CODE value.
const uint32_t kProductCodeAddress = MKW_GADDR(803869E0);
const std::string& productCode = RuntimeConsoleIdentity::Current().productCode;
const size_t size = productCode.size() + 1;
if (!Memory::Contains(kProductCodeAddress, size)) {
@@ -108,7 +108,7 @@ PPC_NATIVE_OVERRIDE(801B2460, SCGetProductSN_HLE, uint32_t, (uint32_t serialAddr
extern "C" uint32_t SCGetProductGameRegion_HLE()
{
return LookupProductRegion(0x8029CEF8u, 4, 4,
return LookupProductRegion(MKW_GADDR(8029CEF8), 4, 4,
RuntimeConsoleIdentity::Current().gameRegion);
}
+30 -15
View File
@@ -1,4 +1,4 @@
#include "hle_stubs.h"
#include "hle_stubs.h"
#include "isa/big_endian.h"
#include "hle/dvd_contract.h"
#include "hle/runtime_parse_helpers.h"
@@ -44,7 +44,7 @@ static fs::path g_dvdRoot;
static std::once_flag g_dvdRootOnce;
static uint32_t CurrentDiscGameCode() {
return RuntimeHle::CurrentGameCode(0x524D4350u); // RMCP
return RuntimeHle::CurrentGameCode(::GuestRegion::CurrentFacts().gameCode);
}
// ============================================================================
@@ -162,6 +162,21 @@ static const fs::path& GetDvdRoot() {
if (!IsDvdDataRoot(path)) {
FailDvdRoot("Configured DVD root is not an extracted DATA directory", path);
}
std::error_code ec;
const auto bootBin = path / "sys" / "boot.bin";
if (fs::is_regular_file(bootBin, ec)) {
std::ifstream file(bootBin, std::ios::binary);
char id[7] = {};
if (!file.read(id, 6)) {
FailDvdRoot("Configured DVD root has an unreadable or truncated sys/boot.bin", bootBin);
}
if (std::memcmp(id, "RMC", 3) == 0 && id[3] != ::GuestRegion::CurrentFacts().letter) {
FailDvd("dvd_region_mismatch", "DVD region mismatch",
std::string("Configured DVD root has disc ID '") + id +
"', but this build was compiled for region " + ::GuestRegion::CurrentFacts().gameId +
".\nThe extracted game assets must match the region of the compiled executable.");
}
}
g_dvdRoot = path;
});
@@ -749,7 +764,7 @@ extern "C" const char* DVDResolveHostPathForTest(const char* dvdPath)
static void InitDvdWaitingQueues()
{
constexpr uint32_t kQueueBase = 0x80343230;
const uint32_t kQueueBase = MKW_GADDR(80343230);
for (uint32_t i = 0; i < 4; ++i) {
const uint32_t queue = kQueueBase + (i * 8);
Memory::Write32(queue + 0, queue);
@@ -763,12 +778,12 @@ static void CompleteDvdCancelState()
// These are the SDK DVD globals used by DVDCancelAll/__DVDPrepareReset.
// The actual drive work is HLE'd, so complete pending cancel/reset waits.
Memory::Write32(0x80386664, 0); // Canceling
Memory::Write32(0x80386668, 0); // ResumeFromHere
Memory::Write32(0x80386670, 0); // PausingFlag
Memory::Write32(0x8038667C, 1); // CancelAllSync complete
Memory::Write32(0x803866A8, 1); // PrepareReset complete
Memory::Write32(0x803866F0, 0); // executing command block
Memory::Write32(MKW_GADDR(80386664), 0); // Canceling
Memory::Write32(MKW_GADDR(80386668), 0); // ResumeFromHere
Memory::Write32(MKW_GADDR(80386670), 0); // PausingFlag
Memory::Write32(MKW_GADDR(8038667C), 1); // CancelAllSync complete
Memory::Write32(MKW_GADDR(803866A8), 1); // PrepareReset complete
Memory::Write32(MKW_GADDR(803866F0), 0); // executing command block
}
// 0x8015EA1C -> DVDInit
@@ -785,14 +800,14 @@ extern "C" void DVDInit_8015EA1C()
// 1. Initialize Global Flags (Emulate OS state)
// These addresses are standard OS globals for DVD context
Memory::Write8(0x80386724, 1); // Contexts initialized
Memory::Write8(0x80386725, 1); // LowInit called
Memory::Write32(0x80386720, 0); // Current context index
Memory::Write8(0x803866a0, 1); // DVDInit called flag
Memory::Write8(MKW_GADDR(80386724), 1); // Contexts initialized
Memory::Write8(MKW_GADDR(80386725), 1); // LowInit called
Memory::Write32(MKW_GADDR(80386720), 0); // Current context index
Memory::Write8(MKW_GADDR(803866a0), 1); // DVDInit called flag
CompleteDvdCancelState();
// 2. Initialize DVD Context structures (prevent crashes in callbacks)
constexpr uint32_t kContextBase = 0x803434e0;
const uint32_t kContextBase = MKW_GADDR(803434e0);
constexpr uint32_t kMagicValue = 0xFEEBDAED;
for (int i = 0; i < 4; i++) {
uint32_t ctx = kContextBase + i * 0x20;
@@ -840,7 +855,7 @@ extern "C" void DVDInit_8015EA1C()
BuildAndPublishRuntimeFst();
// Initialize the translated DVD filesystem so it can use the published FST.
constexpr uint32_t kDvdFsInitAddress = 0x8015DF1C;
const uint32_t kDvdFsInitAddress = MKW_GADDR(8015DF1C);
if (TranslatedFunctionRegistry::FindByAddressPtr(kDvdFsInitAddress)) {
InvokeIndirectCpu(kDvdFsInitAddress, &GetPersistentCpuContext());
}
+2 -2
View File
@@ -83,10 +83,10 @@ extern "C" int32_t NANDInit_HLE(void) {
ISFS_OpenLib_Initialize(&GetPersistentCpuContext());
// NANDHomeDir is at 0x80346D20 (from ESP_GetDataDir output)
WriteGuestNandPath(0x80346D20, CurrentNandDataDir());
WriteGuestNandPath(MKW_GADDR(80346D20), CurrentNandDataDir());
// Mark NAND as initialized (0x80386848 = 2)
Memory::Write32(0x80386848, 2);
Memory::Write32(MKW_GADDR(80386848), 2);
return NAND_RESULT_OK;
}
+1 -1
View File
@@ -78,7 +78,7 @@ void CloseFd(int32_t fd) {
// ============================================================================
uint32_t CurrentMkwTitleIdLo() {
return RuntimeHle::CurrentGameCode(kNandTitleIdLo);
return RuntimeHle::CurrentGameCode(NandTitleIdLo());
}
std::string CurrentNandDataDir() {
+1 -1
View File
@@ -45,7 +45,7 @@
// Mario Kart Wii Title ID
namespace {
constexpr uint32_t kNandTitleIdHi = 0x00010004;
constexpr uint32_t kNandTitleIdLo = 0x524D4350; // "RMCP" fallback
inline uint32_t NandTitleIdLo() { return ::GuestRegion::CurrentFacts().gameCode; }
} // anonymous namespace
// ============================================================================
+29 -43
View File
@@ -187,7 +187,7 @@ static int32_t HandleDolphinIoctlv(uint32_t cmd, uint32_t numIn, uint32_t numOut
return ISFS_EINVAL;
}
char productCode[8] = {};
uint32_t discId = Memory::Contains(0x80000000u, 4u) ? Memory::Read32(0x80000000u) : kNandTitleIdLo;
uint32_t discId = Memory::Contains(0x80000000u, 4u) ? Memory::Read32(0x80000000u) : NandTitleIdLo();
productCode[0] = static_cast<char>((discId >> 24) & 0xffu);
productCode[1] = static_cast<char>((discId >> 16) & 0xffu);
productCode[2] = static_cast<char>((discId >> 8) & 0xffu);
@@ -841,15 +841,16 @@ static bool g_isfsInitialized = false;
// load-bearing and must not be "tidied". Names come from the RVL IPC/ISFS
// sources; only the naming changed here, never a value.
namespace {
constexpr uint32_t kIsfsFdSda1Offset = 29408u; // __ISFS_fd
constexpr uint32_t kIsfsPathSda1Offset = 29400u; // __ISFS_path ("/dev/fs")
constexpr uint32_t kIpcBufferLoSda1Offset = 25620u; // IPC buffer window, low
constexpr uint32_t kIpcBufferHiSda1Offset = 25616u; // IPC buffer window, high
constexpr uint32_t kIpcArenaLoSda1Offset = 25732u; // __IPCArenaLo
constexpr uint32_t kIpcArenaHiSda1Offset = 25728u; // __IPCArenaHi
constexpr uint32_t kIsfsHeapHandleSda1Offset = 25724u; // ISFS heap handle
constexpr uint32_t kIsfsHeapBaseSda1Offset = 25740u; // ISFS heap base address
constexpr uint32_t kIsfsHeapInitializedSda1Offset = 25744u; // ISFS heap created flag
// The ISFS client's small-data globals.
const uint32_t kIsfsFdGlobal = MKW_GADDR(80385920); // __ISFS_fd
const uint32_t kIsfsPathGlobal = MKW_GADDR(80385928); // __ISFS_path ("/dev/fs")
const uint32_t kIpcBufferLoGlobal = MKW_GADDR(803867EC); // IPC buffer window, low
const uint32_t kIpcBufferHiGlobal = MKW_GADDR(803867F0); // IPC buffer window, high
const uint32_t kIpcArenaLoGlobal = MKW_GADDR(8038677C); // __IPCArenaLo
const uint32_t kIpcArenaHiGlobal = MKW_GADDR(80386780); // __IPCArenaHi
const uint32_t kIsfsHeapHandleGlobal = MKW_GADDR(80386784); // ISFS heap handle
const uint32_t kIsfsHeapBaseGlobal = MKW_GADDR(80386774); // ISFS heap base address
const uint32_t kIsfsHeapInitializedGlobal = MKW_GADDR(80386770); // ISFS heap created flag
} // namespace
static void WriteGuestString(uint32_t address, const char* value) {
@@ -879,51 +880,36 @@ int32_t ISFS_OpenLib_Initialize(CpuContext* ctx) {
return ISFS_OK;
}
const uint32_t r13 = ctx->gpr[13];
if (r13 == 0) {
return ISFS_OK;
}
WriteGuestString(kIsfsPathGlobal, "/dev/fs");
const uint32_t isfsFdGlobal = r13 - kIsfsFdSda1Offset;
const uint32_t isfsPathGlobal = r13 - kIsfsPathSda1Offset;
const uint32_t ipcBufferLoGlobal = r13 - kIpcBufferLoSda1Offset;
const uint32_t ipcBufferHiGlobal = r13 - kIpcBufferHiSda1Offset;
const uint32_t ipcArenaLoGlobal = r13 - kIpcArenaLoSda1Offset;
const uint32_t ipcArenaHiGlobal = r13 - kIpcArenaHiSda1Offset;
const uint32_t isfsHeapGlobal = r13 - kIsfsHeapHandleSda1Offset;
const uint32_t isfsHeapBaseGlobal = r13 - kIsfsHeapBaseSda1Offset;
const uint32_t isfsHeapInitializedGlobal = r13 - kIsfsHeapInitializedSda1Offset;
WriteGuestString(isfsPathGlobal, "/dev/fs");
if (Memory::Contains(isfsFdGlobal, 4)) {
Memory::Write32(isfsFdGlobal, static_cast<uint32_t>(ISFS_DEV_FD));
if (Memory::Contains(kIsfsFdGlobal, 4)) {
Memory::Write32(kIsfsFdGlobal, static_cast<uint32_t>(ISFS_DEV_FD));
}
// The heap bring-up below reads and writes all seven IPC globals, so it only
// runs when every one of them is inside guest memory.
for (const uint32_t global : {ipcBufferLoGlobal, ipcBufferHiGlobal, ipcArenaLoGlobal,
ipcArenaHiGlobal, isfsHeapGlobal, isfsHeapBaseGlobal,
isfsHeapInitializedGlobal}) {
for (const uint32_t global : {kIpcBufferLoGlobal, kIpcBufferHiGlobal, kIpcArenaLoGlobal,
kIpcArenaHiGlobal, kIsfsHeapHandleGlobal, kIsfsHeapBaseGlobal,
kIsfsHeapInitializedGlobal}) {
if (!Memory::Contains(global, 4)) {
return ISFS_OK;
}
}
uint32_t ipcLo = Memory::Read32(ipcBufferLoGlobal);
uint32_t ipcHi = Memory::Read32(ipcBufferHiGlobal);
uint32_t ipcLo = Memory::Read32(kIpcBufferLoGlobal);
uint32_t ipcHi = Memory::Read32(kIpcBufferHiGlobal);
if (ipcLo == 0 || ipcHi == 0 || ipcLo >= ipcHi) {
return ISFS_OK;
}
if (Memory::Read32(isfsHeapInitializedGlobal) == 0) {
Memory::Write32(ipcArenaLoGlobal, ipcLo);
Memory::Write32(ipcArenaHiGlobal, ipcHi);
if (Memory::Read32(kIsfsHeapInitializedGlobal) == 0) {
Memory::Write32(kIpcArenaLoGlobal, ipcLo);
Memory::Write32(kIpcArenaHiGlobal, ipcHi);
const uint32_t heapBase = (ipcLo + 31u) & ~31u;
const uint32_t heapSize = 5440u;
if (heapBase + heapSize <= ipcHi) {
Memory::Write32(isfsHeapBaseGlobal, heapBase);
Memory::Write32(kIsfsHeapBaseGlobal, heapBase);
const uint32_t savedR3 = ctx->gpr[3];
const uint32_t savedR4 = ctx->gpr[4];
@@ -932,21 +918,21 @@ int32_t ISFS_OpenLib_Initialize(CpuContext* ctx) {
ctx->gpr[3] = heapBase;
ctx->gpr[4] = heapSize;
ctx->lr = 0x80169BCCu;
InvokeDirectCpu<0x801949B8u>(ctx);
ctx->lr = MKW_GADDR(80169BCC);
InvokeIndirectCpu(MKW_GADDR(801949B8), ctx);
const uint32_t heapHandle = ctx->gpr[3];
ctx->gpr[3] = heapBase + heapSize;
ctx->lr = 0x80169BCCu;
InvokeDirectCpu<0x80193040u>(ctx);
ctx->lr = MKW_GADDR(80169BCC);
InvokeIndirectCpu(MKW_GADDR(80193040), ctx);
ctx->gpr[3] = savedR3;
ctx->gpr[4] = savedR4;
ctx->gpr[5] = savedR5;
ctx->lr = savedLr;
Memory::Write32(isfsHeapGlobal, heapHandle);
Memory::Write32(isfsHeapInitializedGlobal, 1u);
Memory::Write32(kIsfsHeapHandleGlobal, heapHandle);
Memory::Write32(kIsfsHeapInitializedGlobal, 1u);
}
}
+3 -4
View File
@@ -6,6 +6,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "hle/storage/riivolution.h"
#include "guest_abi/guest_region.h"
#include "hle/riivolution_contract.h"
#include "hle/runtime_parse_helpers.h"
@@ -46,11 +47,9 @@ std::optional<std::string> RiivoReadFile(const fs::path& path) {
return buffer.str();
}
// Six-character game ID ("RMCP01") from guest low memory, with the same RMCP
// fallback the rest of the storage HLE uses for boots that have not written
// the disc header yet.
// Six-character game ID from guest low memory, falling back to the translated game's.
std::string RiivoGameId() {
const uint32_t code = RuntimeHle::CurrentGameCode(0x524D4350u); // "RMCP"
const uint32_t code = RuntimeHle::CurrentGameCode(::GuestRegion::CurrentFacts().gameCode);
std::string id(6, '\0');
id[0] = static_cast<char>((code >> 24) & 0xffu);
id[1] = static_cast<char>((code >> 16) & 0xffu);
+22 -22
View File
@@ -20,7 +20,7 @@ constexpr uint32_t kJobSize = 0x18u;
bool ShouldRetryThpPrepareAfterClose(uint32_t callback, uint32_t arg, uint32_t result)
{
if (callback != 0x80529D68u || result != 0) {
if (callback != MKW_GADDR(80529D68) || result != 0) {
return false;
}
if (arg == 0 || !Memory::Contains(arg + 0xACu, 4)) {
@@ -30,11 +30,11 @@ bool ShouldRetryThpPrepareAfterClose(uint32_t callback, uint32_t arg, uint32_t r
if (state != 0) {
return false;
}
if (!Memory::Contains(0x809BECF0u, 4) || !Memory::Contains(0x809BEBA0u, 4)) {
if (!Memory::Contains(MKW_GADDR(809BECF0), 4) || !Memory::Contains(MKW_GADDR(809BEBA0), 4)) {
return false;
}
const uint32_t thpInit = Memory::Read32(0x809BECF0u);
const uint32_t thpOpen = Memory::Read32(0x809BEBA0u);
const uint32_t thpInit = Memory::Read32(MKW_GADDR(809BECF0));
const uint32_t thpOpen = Memory::Read32(MKW_GADDR(809BEBA0));
return thpInit != 0 && thpOpen != 0;
}
@@ -52,23 +52,23 @@ uint32_t RunMovieManagerPrepareAsync(uint32_t manager, CpuContext* cpu)
CpuContextScope scope(cpu);
cpu->gpr[3] = manager + 0x28u;
cpu->gpr[4] = 0;
InvokeIndirectCpu(0x80550CC0u, cpu);
InvokeIndirectCpu(MKW_GADDR(80550CC0), cpu);
return cpu->gpr[3];
};
uint32_t openResult = callThpPlayerOpen();
if (openResult == 0 && ShouldRetryThpPrepareAfterClose(0x80529D68u, manager, 0)) {
if (openResult == 0 && ShouldRetryThpPrepareAfterClose(MKW_GADDR(80529D68), manager, 0)) {
{
CpuContextScope scope(cpu);
InvokeIndirectCpu(0x80551658u, cpu); // THP__PlayerStop
InvokeIndirectCpu(0x80550F48u, cpu); // THP__PlayerClose
InvokeIndirectCpu(MKW_GADDR(80551658), cpu); // THP__PlayerStop
InvokeIndirectCpu(MKW_GADDR(80550F48), cpu); // THP__PlayerClose
}
if (Memory::Contains(0x809BEBA0u, 8) && Memory::Read32(0x809BEBA0u) != 0) {
Memory::Write32(0x809BEBA0u, 0);
Memory::Write8(0x809BEBA4u, 0);
Memory::Write8(0x809BEBA5u, 0);
Memory::Write8(0x809BEBA6u, 0);
Memory::Write8(0x809BEBA7u, 0);
if (Memory::Contains(MKW_GADDR(809BEBA0), 8) && Memory::Read32(MKW_GADDR(809BEBA0)) != 0) {
Memory::Write32(MKW_GADDR(809BEBA0), 0);
Memory::Write8(MKW_GADDR(809BEBA4), 0);
Memory::Write8(MKW_GADDR(809BEBA5), 0);
Memory::Write8(MKW_GADDR(809BEBA6), 0);
Memory::Write8(MKW_GADDR(809BEBA7), 0);
}
openResult = callThpPlayerOpen();
}
@@ -81,8 +81,8 @@ uint32_t RunMovieManagerPrepareAsync(uint32_t manager, CpuContext* cpu)
{
CpuContextScope scope(cpu);
cpu->gpr[3] = manager + 0x10u;
InvokeIndirectCpu(0x80551D38u, cpu); // THP__PlayerGetVideoInfo
InvokeIndirectCpu(0x80550F9Cu, cpu); // THP__PlayerCalcNeedMemory
InvokeIndirectCpu(MKW_GADDR(80551D38), cpu); // THP__PlayerGetVideoInfo
InvokeIndirectCpu(MKW_GADDR(80550F9C), cpu); // THP__PlayerCalcNeedMemory
}
const uint32_t needMemory = cpu->gpr[3];
Memory::Write32(manager + 0x20u, needMemory);
@@ -92,18 +92,18 @@ uint32_t RunMovieManagerPrepareAsync(uint32_t manager, CpuContext* cpu)
{
CpuContextScope scope(cpu);
cpu->gpr[3] = setBufferArg;
InvokeIndirectCpu(0x80551054u, cpu); // THP__PlayerSetBuffer
InvokeIndirectCpu(MKW_GADDR(80551054), cpu); // THP__PlayerSetBuffer
}
const uint32_t mode = Memory::Read32(manager + 0xA8u);
const uint32_t audioSystem = Memory::Read32(0x8088FDB8u + (mode << 2));
const uint32_t audioSystem = Memory::Read32(MKW_GADDR(8088FDB8) + (mode << 2));
uint32_t prepareResult = 0;
{
CpuContextScope scope(cpu);
cpu->gpr[3] = Memory::Read32(manager + 0x24u);
cpu->gpr[4] = audioSystem;
cpu->gpr[5] = 0;
InvokeIndirectCpu(0x80551378u, cpu); // THP__PlayerPrepare
InvokeIndirectCpu(MKW_GADDR(80551378), cpu); // THP__PlayerPrepare
prepareResult = cpu->gpr[3];
}
if (prepareResult != 0) {
@@ -162,7 +162,7 @@ extern "C" void TaskThread_run_HLE_80242d7c(CpuContext* ctx) {
cpu->gpr[3] = taskThread + kTaskMessageQueueOffset;
cpu->gpr[4] = outMsgPtr;
cpu->gpr[5] = 1; // OS_MESSAGE_BLOCK
InvokeIndirectCpu(0x801A7424u, cpu); // OSReceiveMessage
InvokeIndirectCpu(MKW_GADDR(801A7424), cpu); // OSReceiveMessage
}
const uint32_t job = Memory::Read32(outMsgPtr);
@@ -176,7 +176,7 @@ extern "C" void TaskThread_run_HLE_80242d7c(CpuContext* ctx) {
if (callback != 0) {
CpuContextScope scope(cpu);
if (callback == 0x80529D68u) {
if (callback == MKW_GADDR(80529D68)) {
RunMovieManagerPrepareAsync(arg, cpu);
} else {
cpu->gpr[3] = arg;
@@ -198,7 +198,7 @@ extern "C" void TaskThread_run_HLE_80242d7c(CpuContext* ctx) {
cpu->gpr[3] = doneQueue;
cpu->gpr[4] = token;
cpu->gpr[5] = 0; // OS_MESSAGE_NOBLOCK
InvokeIndirectCpu(0x801A735Cu, cpu); // OSSendMessage
InvokeIndirectCpu(MKW_GADDR(801A735C), cpu); // OSSendMessage
}
}
}
+14 -14
View File
@@ -133,22 +133,22 @@ ViState g_vi;
// Guest-side state addresses used by the SDK's VI globals.
constexpr uint32_t kViInitializedFlagAddr = 0x80386b38;
constexpr uint32_t kViTvFormatAddr = 0x80386ba8;
constexpr uint32_t kViRenderWidthAddr = 0x80350864;
constexpr uint32_t kViRenderHeightAddr = 0x80350866;
constexpr uint32_t kViXfbWidthAddr = 0x80350872;
constexpr uint32_t kViXfbHeightAddr = 0x8035087c;
constexpr uint32_t kViRetraceCountAddr = 0x80386be4; // matches VIWaitForRetrace/handler
constexpr uint32_t kViTimingGuardAddr = 0x80386b44;
constexpr uint32_t kViPreRetraceCallback = 0x80386bb8;
constexpr uint32_t kViPostRetraceCallback = 0x80386bb4;
constexpr uint32_t kViNextFrameBufferAddr = 0x80386ba0;
constexpr uint32_t kViNextFrameBufferHwAddr = 0x80350890;
constexpr uint32_t kViRetraceQueueAddr = 0x80386bc0; // Thread queue for VIWaitForRetrace
const uint32_t kViInitializedFlagAddr = MKW_GADDR(80386b38);
const uint32_t kViTvFormatAddr = MKW_GADDR(80386ba8);
const uint32_t kViRenderWidthAddr = MKW_GADDR(80350864);
const uint32_t kViRenderHeightAddr = MKW_GADDR(80350866);
const uint32_t kViXfbWidthAddr = MKW_GADDR(80350872);
const uint32_t kViXfbHeightAddr = MKW_GADDR(8035087c);
const uint32_t kViRetraceCountAddr = MKW_GADDR(80386be4); // matches VIWaitForRetrace/handler
const uint32_t kViTimingGuardAddr = MKW_GADDR(80386b44);
const uint32_t kViPreRetraceCallback = MKW_GADDR(80386bb8);
const uint32_t kViPostRetraceCallback = MKW_GADDR(80386bb4);
const uint32_t kViNextFrameBufferAddr = MKW_GADDR(80386ba0);
const uint32_t kViNextFrameBufferHwAddr = MKW_GADDR(80350890);
const uint32_t kViRetraceQueueAddr = MKW_GADDR(80386bc0); // Thread queue for VIWaitForRetrace
// EGG::BaseSystem::sSystem pointer - must be non-null before post-retrace callback is valid
constexpr uint32_t kEggSSystemAddr = 0x80386F60;
const uint32_t kEggSSystemAddr = MKW_GADDR(80386F60);
std::chrono::microseconds IntervalForFormat(uint32_t tvFormat) {
// NTSC-ish defaults to 60 Hz; PAL uses 50 Hz.
+2 -1
View File
@@ -1,4 +1,5 @@
#include "music_attenuation.h"
#include "guest_abi/guest_region.h"
#include "memory.h"
@@ -28,7 +29,7 @@
namespace MusicAttenuation {
namespace {
constexpr uint32_t kSoundManagerPointer = 0x809C2898u;
const uint32_t kSoundManagerPointer = MKW_GADDR(809C2898);
constexpr uint32_t kArchivePlayerOffset = 0x5BCu;
constexpr uint32_t kSoundPlayersOffset = 0x34u;
constexpr uint32_t kSoundPlayerVolumeOffset = 0x2Cu;
+3 -2
View File
@@ -1,4 +1,5 @@
#include "recomp_mod_loader.h"
#include "guest_abi/guest_region.h"
#include <algorithm>
#include <cstdlib>
@@ -198,8 +199,8 @@ bool IsKnownBaseRelLoaderWrite(const ExecutableRange& range) {
// The game copies and relocates REL sections before executing them. Those
// writes are normal loading/linking, not runtime code patches.
return (cpu->lr >= 0x8000A000u && cpu->lr < 0x8000A400u) ||
(cpu->lr >= 0x801A6000u && cpu->lr < 0x801A7000u);
return (cpu->lr >= MKW_GADDR(8000A000) && cpu->lr < MKW_GADDR(8000A400)) ||
(cpu->lr >= MKW_GADDR(801A6000) && cpu->lr < MKW_GADDR(801A7000));
}
} // namespace
+8 -8
View File
@@ -334,8 +334,8 @@ void SystemBridge::Initialize() {
// These set up vtables and other critical infrastructure
RT_LOG(RT_TAG_RUNTIME) << "Running static constructors for main DOL..." << std::endl;
const uint32_t dolCtorStart = 0x80244DE0;
const uint32_t dolCtorEnd = 0x80244EA0;
const uint32_t dolCtorStart = MKW_GADDR(80244DE0);
const uint32_t dolCtorEnd = MKW_GADDR(80244EA0);
int dolCount = 0;
for (uint32_t addr = dolCtorStart; addr < dolCtorEnd; addr += 4) {
@@ -374,8 +374,8 @@ void SystemBridge::Initialize() {
RT_LOG(RT_TAG_RUNTIME) << "Running static constructors for StaticR.rel..." << std::endl;
// Define range for StaticR.rel .ctors
const uint32_t ctorStart = 0x8088f400;
const uint32_t ctorEnd = 0x8088f704;
const uint32_t ctorStart = MKW_GADDR(8088f400);
const uint32_t ctorEnd = MKW_GADDR(8088f704);
int count = 0;
for (uint32_t addr = ctorStart; addr < ctorEnd; addr += 4) {
@@ -484,7 +484,7 @@ void SystemBridge::SeedLowMemDefaults(const Memory::Config& config) {
};
std::vector<SeedEntry> entries;
constexpr uint32_t kMem1ArenaLoDefault = 0x80399180u;
const uint32_t kMem1ArenaLoDefault = ::GuestRegion::CurrentFacts().mem1ArenaLo;
uint32_t mem1ArenaHiDefault = 0x817f0520u;
constexpr uint32_t kBusClockHz =
static_cast<uint32_t>(TimeBaseContract::kBusClockHz);
@@ -508,10 +508,10 @@ void SystemBridge::SeedLowMemDefaults(const Memory::Config& config) {
// Retro Rewind reads the region byte directly from here while building its
// Retro-WFC payload URL, and OSGetAppGamename reads the app code mirrors
// at 0x80003180/0x80003194 while building NAS auth fields.
entries.push_back({0x80000000u, 0x524D4350u, "Disc game code"}); // RMCP
entries.push_back({0x80000000u, ::GuestRegion::CurrentFacts().gameCode, "Disc game code"});
entries.push_back({0x80000004u, 0x30310100u, "Disc maker/id"}); // 01 + disc 1
entries.push_back({0x80003180u, 0x524D4350u, "OS app game code"}); // RMCP
entries.push_back({0x80003194u, 0x524D4350u, "OS app gamename"}); // RMCP
entries.push_back({0x80003180u, ::GuestRegion::CurrentFacts().gameCode, "OS app game code"});
entries.push_back({0x80003194u, ::GuestRegion::CurrentFacts().gameCode, "OS app gamename"});
if (RuntimeProduct::IsRetroRewind()) {
entries.push_back({0x800017D8u, 0x00000001u, "Retro Rewind recomp runtime marker", true});
}
+18 -8
View File
@@ -91,7 +91,7 @@ int main() {
Require(settings && RuntimeNandSettings::HasIdentity(*settings));
Require(settings->at("SERNO") == "987654321" && settings->at("CODE") == "LU");
Require(settings->at("AREA") == "USA" && settings->at("GAME") == "US");
Require(Ensure(root, error, 1800000123), "Existing imported NAND must work");
Require(Ensure(root, error, {}, 1800000123), "Existing imported NAND must work");
std::array<uint8_t, 256> after{};
{
std::ifstream input(path, std::ios::binary);
@@ -112,11 +112,11 @@ int main() {
std::filesystem::resize_file(path, 128);
Require(!RuntimeNandSettings::Read(root));
const auto damaged = ReadBytes(path);
Require(!Ensure(root, error, 1800000123), "Do not replace a truncated identity");
Require(!Ensure(root, error, {}, 1800000123), "Do not replace a truncated identity");
Require(ReadBytes(path) == damaged, "Damaged file must remain untouched");
const auto fresh = root / "fresh";
Require(Ensure(fresh, error, 1800000123), "Missing setting.txt must initialize");
Require(Ensure(fresh, error, {}, 1800000123), "Missing setting.txt must initialize");
const auto generated = Read(fresh);
Require(generated && HasIdentity(*generated), "Generated file must be readable");
Require(generated->at("SERNO") == "800000123", "Persist Dolphin-generated serial");
@@ -127,13 +127,23 @@ int main() {
"Complete Dolphin boot settings");
const auto firstBoot = ReadBytes(FilePath(fresh));
Require(firstBoot.size() == 256 && firstBoot.back() == 0, "Dolphin buffer size and raw zero padding");
Require(Ensure(fresh, error, 1900000999), "Second boot");
Require(Ensure(fresh, error, {}, 1900000999), "Second boot");
Require(ReadBytes(FilePath(fresh)) == firstBoot, "Second boot must not change any bytes");
const auto freshNtsc = root / "fresh-ntsc";
const BootDefaults ntscDefaults{"USA", "RVL-001(USA)", "LU", "NTSC", "US"};
Require(Ensure(freshNtsc, error, ntscDefaults, 1800000123), "Regional setting.txt must initialize");
const auto generatedNtsc = Read(freshNtsc);
Require(generatedNtsc && HasIdentity(*generatedNtsc), "Generated NTSC file must be readable");
Require(generatedNtsc->at("CODE") == "LU" && generatedNtsc->at("AREA") == "USA" &&
generatedNtsc->at("GAME") == "US", "NTSC first-boot fields");
Require(generatedNtsc->at("MODEL") == "RVL-001(USA)" && generatedNtsc->at("VIDEO") == "NTSC",
"Complete NTSC boot settings");
const auto blocked = root / "blocked";
{ std::ofstream output(blocked); output << "file obstructing NAND directory"; }
Require(!Ensure(blocked, error, 1800000123), "Write failure must not return an ephemeral identity");
Require(!Ensure(root / "bad-clock", error, -1), "Clock failure must not initialize");
Require(!Ensure(blocked, error, {}, 1800000123), "Write failure must not return an ephemeral identity");
Require(!Ensure(root / "bad-clock", error, {}, -1), "Clock failure must not initialize");
const auto concurrent = root / "concurrent";
std::array<bool, 16> results{};
@@ -141,14 +151,14 @@ int main() {
for (size_t i = 0; i < results.size(); ++i) {
workers.emplace_back([&, i] {
std::string detail;
results[i] = Ensure(concurrent, detail, 1800000001 + i);
results[i] = Ensure(concurrent, detail, {}, 1800000001 + i);
});
}
for (auto& worker : workers) worker.join();
for (const bool result : results) Require(result, "Concurrent boot must read the persisted winner");
const auto winner = ReadBytes(FilePath(concurrent));
Require(Read(concurrent) && HasIdentity(*Read(concurrent)), "Concurrent boot must persist valid settings");
Require(Ensure(concurrent, error, 1900000999), "Boot after concurrent initialization");
Require(Ensure(concurrent, error, {}, 1900000999), "Boot after concurrent initialization");
Require(ReadBytes(FilePath(concurrent)) == winner, "Concurrent winner must remain stable");
// Independently decode as Nintendo does: stop at the first encoded NUL.
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Generate runtime/include/guest_abi/guest_address_table.inc and runtime/include/guest_function_imports.inc.
The runtime names every guest address by its PAL (RMCP01) address. This scans the runtime for
those identities and writes one row per identity with its NTSC-U and NTSC-J address:
MKW_GUEST_ADDRESS(<PAL>, <NTSC-U>, <NTSC-J>)
Identities the runtime calls through MKW_GUEST_FUNC also get a MKW_GUEST_FUNCTION_IMPORT row.
Code addresses go through each region's chunk table (projects/<region>/region_port.json) and must
be a function entry in that region's validated MAP.txt, or sit at the same offset inside the same
validated function. Data addresses come from projects/<region>/data_addresses.txt. An identity
that does not resolve aborts the generation.
Usage: python -I tools/region/gen_guest_address_table.py
"""
import bisect
import json
import os
import re
import sys
ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
RUNTIME = os.path.join(ROOT, "runtime")
OUT = os.path.join(RUNTIME, "include", "guest_abi", "guest_address_table.inc")
IMPORTS_OUT = os.path.join(RUNTIME, "include", "guest_function_imports.inc")
PAL_TEXT = [(0x80004000, 0x80006460), (0x800072C0, 0x80244DE0), (0x805103B4, 0x8088F400)]
# Column order is the GuestRegion::Id order in guest_abi/guest_region.h.
REGIONS = ["mkwii-ntsc-u", "mkwii-ntsc-j"]
HEX = r"([0-9A-Fa-f]{8})(?![0-9A-Fa-f])"
TOKEN_PATTERNS = [re.compile(p) for p in (
r"PPC_NATIVE_OVERRIDE(?:_VOID)?\s*\(\s*" + HEX,
r"GX_FATAL_STUB\s*\(\s*" + HEX,
r"MKW_GADDR\s*\(\s*" + HEX,
r"MKW_GADDR_CT\s*\(\s*" + HEX,
r"MKW_GUEST_FUNC\s*\(\s*" + HEX,
r"MKW_KNOWN_TYPED_NATIVE\s*\(\s*" + HEX,
r"MKW_KNOWN_NATIVE_CPU_CALL\s*\(\s*" + HEX,
r"REGISTER_NATIVE_FUNCTION(?:_AS)?\s*\(\s*0x" + HEX,
r"REGISTER_TRANSLATED_FUNCTION\s*\(\s*0x" + HEX,
r"GuestRegion::(?:Is|Address)<0x" + HEX,
)]
IMPORT_PATTERN = re.compile(r"MKW_GUEST_FUNC\s*\(\s*" + HEX)
COMMENTS = re.compile(r"//[^\n]*|/\*.*?\*/", re.S)
LABEL_NAME = re.compile(r"(_switch$|_caseD_|^0x)")
def scan_identities():
found, imports = set(), set()
for directory, _, files in os.walk(RUNTIME):
if directory.startswith(os.path.join(RUNTIME, "third_party")):
continue
for name in files:
path = os.path.join(directory, name)
if not name.endswith((".cpp", ".h", ".hpp", ".inc")) or path in (OUT, IMPORTS_OUT):
continue
with open(path, encoding="utf-8", errors="replace") as f:
text = COMMENTS.sub("", f.read())
for pattern in TOKEN_PATTERNS:
found.update(int(m.group(1), 16) for m in pattern.finditer(text))
imports.update(int(m.group(1), 16) for m in IMPORT_PATTERN.finditer(text))
return sorted(found), sorted(imports)
def is_text(address):
return any(start <= address < end for start, end in PAL_TEXT)
def load_map(path):
entries = []
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
address, _, name = line.partition(" ")
entries.append((int(address, 16), name.strip()))
entries.sort()
return entries
def load_chunks(project):
with open(os.path.join(ROOT, "projects", project, "region_port.json"), encoding="utf-8") as f:
return [tuple(int(v, 16) for v in chunk) for chunk in json.load(f)["chunks"]]
def load_data_table(project):
table = {}
with open(os.path.join(ROOT, "projects", project, "data_addresses.txt"), encoding="utf-8") as f:
for line in f:
parts = line.split("#", 1)[0].split()
if len(parts) >= 2 and parts[1] != "?":
table[int(parts[0], 16)] = int(parts[1], 16)
return table
def containing_function(entries, addresses, address):
i = bisect.bisect_right(addresses, address) - 1
while i >= 0 and LABEL_NAME.search(entries[i][1]):
i -= 1
if i < 0:
return None, None
return entries[i][1], address - entries[i][0]
def port_region(project, identities, pal_entries):
chunks = load_chunks(project)
data = load_data_table(project)
target_entries = load_map(os.path.join(ROOT, "projects", project, "MAP.txt"))
target_starts = {a for a, _ in target_entries}
target_by_name = {}
for address, name in target_entries:
target_by_name.setdefault(name, address)
pal_addresses = [a for a, _ in pal_entries]
mapping, problems = {}, []
for pal in identities:
if not is_text(pal):
if pal in data:
mapping[pal] = data[pal]
else:
problems.append(f"{pal:08X}: data address missing from projects/{project}/data_addresses.txt")
continue
ported = next((pal - s + d for s, e, d in chunks if s <= pal < e), None)
if ported is None:
problems.append(f"{pal:08X}: code address outside every chunk")
continue
if ported not in target_starts:
name, offset = containing_function(pal_entries, pal_addresses, pal)
start = target_by_name.get(name) if name else None
if start is None or start + offset != ported:
problems.append(f"{pal:08X} -> {ported:08X}: not a validated entry of {project}/MAP.txt "
f"(containing function {name} not confirmed at the same offset)")
continue
mapping[pal] = ported
return mapping, problems
def write(path, macro, header, rows, columns, names):
lines = ["// AUTO-GENERATED by tools/region/gen_guest_address_table.py - DO NOT EDIT."] + header
for pal in rows:
row = ", ".join([f"{pal:08X}"] + [f"{column[pal]:08X}" for column in columns])
name = names.get(pal)
lines.append(f"{macro}({row})" + (f" // {name}" if name and not name.startswith("0x") else ""))
with open(path, "w", encoding="utf-8", newline="\n") as f:
f.write("\n".join(lines) + "\n")
def main():
identities, imports = scan_identities()
pal_entries = load_map(os.path.join(ROOT, "projects", "mkwii", "MAP.txt"))
names = dict(pal_entries)
columns, failed = [], False
for project in REGIONS:
mapping, problems = port_region(project, identities, pal_entries)
if problems:
failed = True
print(f"{project}: {len(problems)} identities did not resolve:")
for problem in problems:
print(" " + problem)
columns.append(mapping)
if failed:
return 1
write(OUT, "MKW_GUEST_ADDRESS", [
"// PAL identity -> address in each region: MKW_GUEST_ADDRESS(PAL, NTSC-U, NTSC-J).",
"// Sorted by PAL address; guest_abi/guest_region.h relies on that.",
], identities, columns, names)
write(IMPORTS_OUT, "MKW_GUEST_FUNCTION_IMPORT", [
"// Base translations the runtime calls directly: MKW_GUEST_FUNCTION_IMPORT(PAL, NTSC-U, NTSC-J).",
], imports, columns, names)
code = sum(1 for a in identities if is_text(a))
print(f"wrote {len(identities)} identities ({code} code, {len(identities) - code} data), "
f"{len(imports)} function imports")
return 0
if __name__ == "__main__":
sys.exit(main())
+45 -6
View File
@@ -68,8 +68,8 @@ var canonicalIrStore = new CanonicalIrStore();
// recursive ABI cache in one stable pass, exactly like translate-mod does.
var inferredGuestAbi = new Lazy<InferredGuestFunctionAbiProvider>(() =>
new InferredGuestFunctionAbiProvider(image.Value, canonicalIrStore));
var runtimeNativeIndex = new Lazy<RuntimeNativeIndex>(() => RuntimeNativeIndexBuilder.Resolve(
RequireProject().Runtime.NativeRegistrationRoot, RequireProject().Runtime.NativeIndex, Console.Error));
var runtimeNativeIndex = new Lazy<RuntimeNativeIndex>(() => ResolveRegionalNativeIndex(
RequireProject().Runtime.NativeRegistrationRoot, RequireProject().Runtime.NativeIndex));
// Void-stub signatures are only trusted as declared guest ABIs when the stub
// lives under runtime.native_abi_directories (the vetted set); everywhere else
// the C++ signature is documentation and the inferred ABI stays authoritative.
@@ -217,8 +217,26 @@ int RunCheckBaseModAwareness(string[] argsTail)
return 0;
}
// The runtime registers natives by PAL address; translated code needs the project region's.
RuntimeNativeIndex ResolveRegionalNativeIndex(string nativeSourceDirectory, string? indexPath)
{
var index = RuntimeNativeIndexBuilder.Resolve(nativeSourceDirectory, indexPath, Console.Error);
var region = GuestRegions.FromGameId(RequireProject().Identity.GameId);
var tablePath = GuestAddressTable.DefaultPath(nativeSourceDirectory);
// A runtime without a table can only be built for PAL.
if (region == GuestRegion.Pal && !File.Exists(tablePath))
return index;
return index.ForRegion(GuestAddressTable.Load(tablePath), region);
}
int RunEmitBuildShards(string[] argsTail)
{
// The project decides the guest region the shards are built for.
if (project is null)
{
Console.Error.WriteLine("emit-build-shards requires --project <path>.");
return 1;
}
var baseMetadata = OptionValue(argsTail, "--base-metadata") ?? Path.Combine(root, "generated", "base_translation_output.json");
var baseFunctions = OptionValue(argsTail, "--base-functions-dir") ?? Path.Combine(root, "generated", "functions");
var output = OptionValue(argsTail, "--out") ?? Path.Combine(root, "generated", "build_shards");
@@ -237,7 +255,7 @@ int RunEmitBuildShards(string[] argsTail)
: Path.GetFullPath(nativeIndexOption);
if (!Directory.Exists(nativeSourcePath) && !File.Exists(nativeIndexPath))
throw new DirectoryNotFoundException(nativeSourcePath);
var nativeIndex = RuntimeNativeIndexBuilder.Resolve(nativeSourcePath, nativeIndexPath, Console.Error);
var nativeIndex = ResolveRegionalNativeIndex(nativeSourcePath, nativeIndexPath);
var result = TranslatedBuildShardEmitter.Emit(new TranslatedBuildShardOptions(
Path.GetFullPath(baseMetadata),
Path.GetFullPath(baseFunctions),
@@ -245,7 +263,8 @@ int RunEmitBuildShards(string[] argsTail)
nativeSourcePath,
string.IsNullOrWhiteSpace(resolvedProfile) ? null : Path.GetFullPath(resolvedProfile),
string.IsNullOrWhiteSpace(retroCpp) ? null : Path.GetFullPath(retroCpp),
NativeIndex: nativeIndex));
NativeIndex: nativeIndex,
Region: GuestRegions.FromGameId(project.Identity.GameId)));
Console.WriteLine("[translator] emitted stable translated build graph");
Console.WriteLine($" base functions: {result.BaseFunctionCount:N0}");
Console.WriteLine($" shared base functions: {result.SharedBaseFunctionCount:N0}");
@@ -402,7 +421,8 @@ int RunValidateRetroWfcPayload(string[] argsTail)
try
{
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(directory);
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(directory,
gameId: OptionValue(argsTail, "--game-id")!);
Console.WriteLine("[translator] Retro WFC payload signature validated.");
return 0;
}
@@ -1743,6 +1763,12 @@ int RunTranslateModCore(string[] argsTail, string? outputDirectoryOverride)
var modName = OptionValue(argsTail, "--mod-name")
?? (!string.IsNullOrWhiteSpace(modRoot) ? Path.GetFileName(Path.GetFullPath(modRoot)) : "Pulsar Mod");
var region = OptionValue(argsTail, "--region") ?? profile?.Region ?? "P";
if (project?.Identity.Region is { } projectRegion &&
!string.Equals(region, projectRegion, StringComparison.OrdinalIgnoreCase))
{
Console.Error.WriteLine($"[translator] --region {region} does not match the project's region {projectRegion}.");
return 1;
}
var moduleGuestBase = OptionValue(argsTail, "--module-guest-base") is { } moduleBaseText
? ParseAddress(moduleBaseText)
: profile?.ModuleGuestBase ?? 0x81700000u;
@@ -1754,6 +1780,18 @@ int RunTranslateModCore(string[] argsTail, string? outputDirectoryOverride)
var pul = KamekPulFile.Load(codePul);
var selected = pul.SelectRegion(region);
if (string.Equals(profile?.Name, "retro-rewind", StringComparison.OrdinalIgnoreCase))
{
var corrected = pul.IsCombined && !string.Equals(region, "P", StringComparison.OrdinalIgnoreCase)
? RetroRewindRegionPatches.CorrectUnportedUsHooks(selected, pul.SelectRegion("P"), project?.Identity.GameId, image.Value)
: selected;
if (!ReferenceEquals(corrected, selected))
Console.WriteLine("[translator] Moved Retro Rewind's unported PAL vtable hooks to their NTSC-U slots.");
var esrb = RetroRewindRegionPatches.ApplyUsEsrbSkip(corrected, project?.Identity.GameId, image.Value);
if (!ReferenceEquals(esrb, corrected))
Console.WriteLine("[translator] Applied Retro Rewind's NTSC-U ESRB skip statically at 0x80604094.");
selected = esrb;
}
if (requestedRetroWfc && profile?.RetroWfcLegacyBootstrapHook is { } legacyBootstrapHook)
{
selected = RetroWfcBootstrapSuppressor.Suppress(selected, legacyBootstrapHook);
@@ -3656,7 +3694,8 @@ static (string? Positional, CommandOption[] Options)? CommandSpec(string command
}),
"validate-retro-wfc-payload" => (null, new CommandOption[]
{
new("--directory", "directory", Required: true)
new("--directory", "directory", Required: true),
new("--game-id", "id", Required: true)
}),
"emit-base-manifest" => (null, new CommandOption[]
{
@@ -18,7 +18,8 @@ public sealed record TranslatedBuildShardOptions(
int BaseShardCount = 72,
int ModShardCount = 48,
int RegistrationShardCount = 16,
RuntimeNativeIndex? NativeIndex = null);
RuntimeNativeIndex? NativeIndex = null,
GuestRegion Region = GuestRegion.Pal);
/// <summary>
/// Per-shard compile-cost spread for one partition. Frozen membership does not
@@ -247,6 +248,7 @@ public static partial class TranslatedBuildShardEmitter
WriteCMakeManifest(
cmakeManifestPath,
outputRoot,
options.Region,
commonBase.Length,
portableCommonBase.Length,
activeBase.Length,
@@ -1039,6 +1041,7 @@ public static partial class TranslatedBuildShardEmitter
private static void WriteCMakeManifest(
string path,
string outputRoot,
GuestRegion region,
int sharedBaseCount,
int portableSharedBaseCount,
int activeBaseCount,
@@ -1053,6 +1056,7 @@ public static partial class TranslatedBuildShardEmitter
var output = new StringBuilder();
output.AppendLine("# Translator-owned stable shard graph; do not edit.");
output.AppendLine($"set(MKW_TRANSLATED_SHARD_ROOT \"{CMakePath(outputRoot)}\")");
output.AppendLine($"set(MKW_TRANSLATED_GUEST_REGION {(int)region})");
output.AppendLine($"set(MKW_BASE_FUNCTION_COUNT {activeBaseCount})");
output.AppendLine($"set(MKW_SHARED_BASE_FUNCTION_COUNT {sharedBaseCount})");
output.AppendLine($"set(MKW_PORTABLE_SHARED_BASE_FUNCTION_COUNT {portableSharedBaseCount})");
@@ -0,0 +1,80 @@
using System.Text.RegularExpressions;
namespace Translator.Core;
/// <summary>Column order of runtime/include/guest_abi/guest_address_table.inc.</summary>
public enum GuestRegion
{
Pal = 0,
NtscU = 1,
NtscJ = 2,
}
public static class GuestRegions
{
/// <summary>
/// The runtime region for a project's game id. Mario Kart Wii releases without a column in
/// the address table are rejected; any other game has no region-specific runtime addresses.
/// </summary>
public static GuestRegion FromGameId(string? gameId) => gameId?.ToUpperInvariant() switch
{
"RMCE01" => GuestRegion.NtscU,
"RMCJ01" => GuestRegion.NtscJ,
"RMCP01" => GuestRegion.Pal,
{ } id when id.StartsWith("RMC", StringComparison.Ordinal) =>
throw new InvalidDataException($"Mario Kart Wii release '{gameId}' is not supported."),
_ => GuestRegion.Pal,
};
}
/// <summary>
/// The runtime names guest addresses by their PAL address. This is the table it resolves them
/// through, read from the same file the C++ includes.
/// </summary>
public sealed partial class GuestAddressTable
{
private readonly Dictionary<uint, uint[]> _rows;
private GuestAddressTable(Dictionary<uint, uint[]> rows, string sourcePath)
{
_rows = rows;
SourcePath = sourcePath;
}
public string SourcePath { get; }
public int Count => _rows.Count;
/// <summary>The table beside a runtime whose native sources live in <paramref name="nativeSourceDirectory"/>.</summary>
public static string DefaultPath(string nativeSourceDirectory) =>
Path.GetFullPath(Path.Combine(nativeSourceDirectory, "..", "include", "guest_abi", "guest_address_table.inc"));
public static GuestAddressTable Load(string path)
{
if (!File.Exists(path))
throw new FileNotFoundException("The runtime's guest address table was not found.", path);
var rows = new Dictionary<uint, uint[]>();
foreach (var line in File.ReadLines(path))
{
var match = RowPattern().Match(line);
if (!match.Success) continue;
var pal = Convert.ToUInt32(match.Groups["pal"].Value, 16);
if (!rows.TryAdd(pal, [pal, Convert.ToUInt32(match.Groups["u"].Value, 16), Convert.ToUInt32(match.Groups["j"].Value, 16)]))
throw new InvalidDataException($"{path} lists PAL address 0x{pal:X8} twice.");
}
if (rows.Count == 0)
throw new InvalidDataException($"{path} has no MKW_GUEST_ADDRESS rows.");
return new GuestAddressTable(rows, path);
}
public uint Map(uint pal, GuestRegion region)
{
if (_rows.TryGetValue(pal, out var row))
return row[(int)region];
throw new InvalidDataException(
$"PAL address 0x{pal:X8} is missing from {SourcePath}; regenerate it with tools/region/gen_guest_address_table.py.");
}
[GeneratedRegex(@"^\s*MKW_GUEST_ADDRESS\(\s*(?<pal>[0-9A-Fa-f]{8})\s*,\s*(?<u>[0-9A-Fa-f]{8})\s*,\s*(?<j>[0-9A-Fa-f]{8})\s*\)", RegexOptions.CultureInvariant)]
private static partial Regex RowPattern();
}
@@ -0,0 +1,103 @@
using System.Buffers.Binary;
using Translator.Core.Loading;
using Translator.Core.Parsing.Kamek;
namespace Translator.Core.Mods.Mkwii;
public static class RetroRewindRegionPatches
{
private const uint PalRelStart = 0x805102E0;
// Hooks the pack writes at PAL vtable slots that its NTSC-U port map leaves unported:
// PAL slot, NTSC-U slot, and the function that NTSC-U slot holds in the clean StaticR.rel.
private static readonly (uint Pal, uint Us, uint UsTarget)[] UnportedUsHooks =
[
(0x808B4D48, 0x808B0698, 0x80563BE8), // Kart::Damage::ApplyBananaDamage
(0x808B4DFC, 0x808B074C, 0x80563CD8), // Kart::Damage::ApplyZapperDamage
(0x808B4E14, 0x808B0764, 0x80563CCC), // Kart::Damage::ApplyShockDamage
(0x808CB02C, 0x808C5D24, 0x8072E068), // AI::EnemyRouteController::Update
(0x808DBCD8, 0x808D7580, 0x806FAB44), // Audio::RaceActor::HoldSoundLimited
];
/// <summary>
/// Moves the NTSC-U chunk's unported PAL vtable hooks to their NTSC-U slots. A StaticR.rel
/// address the NTSC-U chunk shares with the PAL chunk is always unported, so an unknown one fails.
/// </summary>
public static KamekChunk CorrectUnportedUsHooks(KamekChunk usChunk, KamekChunk palChunk, string? gameId,
ProgramImage image)
{
if (!string.Equals(gameId, "RMCE01", StringComparison.OrdinalIgnoreCase))
return usChunk;
if (usChunk.Commands.Count != palChunk.Commands.Count)
throw new InvalidDataException("Retro Rewind's PAL and NTSC-U chunks have different command lists.");
var commands = usChunk.Commands.ToArray();
var changed = false;
for (var i = 0; i < commands.Length; i++)
{
var us = commands[i];
var pal = palChunk.Commands[i];
if (!us.AddressIsAbsolute || !pal.AddressIsAbsolute || us.Address != pal.Address || us.Address < PalRelStart)
continue;
var hook = UnportedUsHooks.FirstOrDefault(h => h.Pal == us.Address);
if (hook.Pal == 0)
throw new InvalidDataException($"Retro Rewind's NTSC-U chunk patches unported PAL address 0x{us.Address:X8}.");
if (us.Id != KamekCommandId.Addr32 || us.Arguments.Count != 1 || us.Arguments[0] >= usChunk.CodeSize)
throw new InvalidDataException($"Unexpected Retro Rewind command at PAL address 0x{us.Address:X8}.");
if (!image.Contains(hook.Us, 4) ||
BinaryPrimitives.ReadUInt32BigEndian(image.Memory.AsSpan(image.GetOffset(hook.Us, 4), 4)) != hook.UsTarget)
throw new InvalidDataException($"NTSC-U vtable slot check failed at 0x{hook.Us:X8}.");
commands[i] = us with { Address = hook.Us };
changed = true;
}
return changed
? new KamekChunk(usChunk.Index, usChunk.FileOffset, usChunk.BssSize, usChunk.CodeSize,
usChunk.CtorStart, usChunk.CtorEnd, usChunk.ChunkSize, usChunk.CodeBlob, commands)
: usChunk;
}
private const uint EsrbBranchSite = 0x80604094;
private const uint EsrbOriginalInstruction = 0x3C608038;
private const uint EsrbSkipBranch = 0x4800001C;
private const int EsrbStoreIndex = 7;
// PulsarSystem.cpp's removeESRB boot hook: if (region == 'E') *(u32*)0x80604094 = 0x4800001C.
private static readonly uint[] EsrbBootHook =
[
0x3C608000, 0x88030003, 0x2C000045, 0x4C820020, 0x3C804800, 0x3C608060, 0x3804001C, 0x90034094, 0x4E800020
];
/// <summary>
/// Translated code cannot be rewritten at run time, so the pack's NTSC-U ESRB skip becomes a
/// static patch and its run-time store a nop.
/// </summary>
public static KamekChunk ApplyUsEsrbSkip(KamekChunk chunk, string? gameId, ProgramImage image)
{
if (!string.Equals(gameId, "RMCE01", StringComparison.OrdinalIgnoreCase))
return chunk;
var sites = new List<int>();
for (var offset = 0; offset + EsrbBootHook.Length * 4 <= chunk.CodeBlob.Length; offset += 4)
{
var match = true;
for (var i = 0; i < EsrbBootHook.Length && match; i++)
match = BinaryPrimitives.ReadUInt32BigEndian(chunk.CodeBlob.AsSpan(offset + i * 4, 4)) == EsrbBootHook[i];
if (match)
sites.Add(offset);
}
if (sites.Count == 0)
return chunk;
if (sites.Count != 1 || chunk.Commands.Any(c => c.AddressIsAbsolute && c.Address == EsrbBranchSite))
throw new InvalidDataException("Unexpected Retro Rewind ESRB skip; cannot apply it statically.");
if (!image.Contains(EsrbBranchSite, 4) ||
BinaryPrimitives.ReadUInt32BigEndian(image.Memory.AsSpan(image.GetOffset(EsrbBranchSite, 4), 4)) != EsrbOriginalInstruction)
throw new InvalidDataException($"Retro Rewind ESRB skip target check failed at 0x{EsrbBranchSite:X8}.");
var code = chunk.CodeBlob.ToArray();
BinaryPrimitives.WriteUInt32BigEndian(code.AsSpan(sites[0] + EsrbStoreIndex * 4, 4), 0x60000000);
var branch = new KamekCommand(-1, ((uint)KamekCommandId.Write32 << 24) | 0xFFFFFE, KamekCommandId.Write32,
true, EsrbBranchSite, [EsrbSkipBranch]);
return new KamekChunk(chunk.Index, chunk.FileOffset, chunk.BssSize, chunk.CodeSize,
chunk.CtorStart, chunk.CtorEnd, chunk.ChunkSize, code, chunk.Commands.Append(branch).ToArray());
}
}
@@ -43,6 +43,22 @@ public sealed record RuntimeNativeIndex(
.Select(static entry => entry.Address).ToHashSet();
return new RuntimeNativeGuestEffectSet(contracts, precise, conservative);
}
/// <summary>
/// The index is keyed by the PAL addresses the runtime sources name; this is the same index
/// keyed by <paramref name="region"/>'s addresses. Every address must be in the table, PAL too.
/// </summary>
public RuntimeNativeIndex ForRegion(GuestAddressTable table, GuestRegion region) => new(
Registrations.Select(entry => entry with { Address = table.Map(entry.Address, region) }).ToArray(),
VoidStubAbis.Select(entry => entry with { Address = table.Map(entry.Address, region) }).ToArray(),
Effects.Select(entry => entry with
{
Address = table.Map(entry.Address, region),
Contract = entry.Contract with
{
DirectCallTargets = entry.Contract.DirectCallTargets.Select(target => table.Map(target, region)).ToArray()
}
}).ToArray());
}
public static class RuntimeNativeIndexBuilder
@@ -0,0 +1,119 @@
using Translator.Core;
using Translator.Core.Analysis;
using Xunit;
namespace Translator.Tests;
public sealed class GuestAddressTableTests
{
[Theory]
[InlineData("RMCP01", GuestRegion.Pal)]
[InlineData("RMCE01", GuestRegion.NtscU)]
[InlineData("rmcj01", GuestRegion.NtscJ)]
[InlineData("RECOMPGEN", GuestRegion.Pal)]
[InlineData(null, GuestRegion.Pal)]
public void RegionFollowsTheGameId(string? gameId, GuestRegion expected) =>
Assert.Equal(expected, GuestRegions.FromGameId(gameId));
[Theory]
[InlineData("RMCK01")]
[InlineData("RMCP02")]
public void UnsupportedMarioKartReleasesAreRejected(string gameId) =>
Assert.Throws<InvalidDataException>(() => GuestRegions.FromGameId(gameId));
[Fact]
public void MapsEachColumnAndRejectsUnknownAndDuplicateRows()
{
WithTable("""
// comment
MKW_GUEST_ADDRESS(801A9E84, 801A9DE4, 801A9DA4) // OSCreateThread
MKW_GUEST_ADDRESS(80386664, 80382304, 80385FE4)
""", table =>
{
Assert.Equal(2, table.Count);
Assert.Equal(0x801A9E84u, table.Map(0x801A9E84, GuestRegion.Pal));
Assert.Equal(0x801A9DE4u, table.Map(0x801A9E84, GuestRegion.NtscU));
Assert.Equal(0x80385FE4u, table.Map(0x80386664, GuestRegion.NtscJ));
Assert.Throws<InvalidDataException>(() => table.Map(0x80000000, GuestRegion.Pal));
});
WithTable("""
MKW_GUEST_ADDRESS(801A9E84, 801A9DE4, 801A9DA4)
MKW_GUEST_ADDRESS(801a9e84, 801A9DE4, 801A9DA4)
""", _ => { }, expectFailure: true);
WithTable("// no rows\n", _ => { }, expectFailure: true);
}
[Fact]
public void IndexForRegionRekeysEveryEntry()
{
var contract = new GuestAbiContract(0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, false, false,
GuestCallBoundaryFlags.None, [0x80386664u]);
var index = new RuntimeNativeIndex(
[new RuntimeNativeRegistration(0x801A9E84, "OSCreateThread_HLE", "os.cpp", false, true)],
[new RuntimeNativeAbiEntry(0x801A9E84, ["r3"], [])],
[new RuntimeNativeEffectEntry(0x801A9E84, contract, true)]);
WithTable("""
MKW_GUEST_ADDRESS(801A9E84, 801A9DE4, 801A9DA4)
MKW_GUEST_ADDRESS(80386664, 80382304, 80385FE4)
""", table =>
{
var ntscU = index.ForRegion(table, GuestRegion.NtscU);
Assert.Equal(0x801A9DE4u, ntscU.Registrations.Single().Address);
Assert.Equal("OSCreateThread_HLE", ntscU.Registrations.Single().Symbol);
Assert.Equal(0x801A9DE4u, ntscU.VoidStubAbis.Single().Address);
Assert.Equal(0x801A9DE4u, ntscU.Effects.Single().Address);
Assert.Equal([0x80382304u], ntscU.Effects.Single().Contract.DirectCallTargets);
Assert.Equal(0x801A9E84u, index.ForRegion(table, GuestRegion.Pal).Registrations.Single().Address);
});
}
// Every address the runtime registers must have a row, or a regional build cannot place it.
[Fact]
public void RepositoryTableCoversEveryRuntimeRegistration()
{
var root = ProjectPathsForTests.FindRepositoryRoot();
var sources = Path.Combine(root, "runtime", "src");
var table = GuestAddressTable.Load(GuestAddressTable.DefaultPath(sources));
var index = RuntimeNativeIndexBuilder.Build(sources);
Assert.NotEmpty(index.Registrations);
foreach (var region in Enum.GetValues<GuestRegion>())
Assert.Equal(index.Registrations.Length, index.ForRegion(table, region).Registrations.Length);
}
// Distinct PAL identities must stay distinct and in the same order in every region.
[Fact]
public void RepositoryTableIsInjectiveAndOrderedPerRegion()
{
var root = ProjectPathsForTests.FindRepositoryRoot();
var path = Path.Combine(root, "runtime", "include", "guest_abi", "guest_address_table.inc");
var rows = File.ReadLines(path)
.Where(static line => line.StartsWith("MKW_GUEST_ADDRESS(", StringComparison.Ordinal))
.Select(static line => line["MKW_GUEST_ADDRESS(".Length..line.IndexOf(')')]
.Split(',').Select(static value => Convert.ToUInt32(value.Trim(), 16)).ToArray())
.ToArray();
Assert.NotEmpty(rows);
for (var column = 0; column < 3; column++)
{
for (var i = 1; i < rows.Length; i++)
Assert.True(rows[i - 1][column] < rows[i][column],
$"column {column} row {i}: 0x{rows[i - 1][column]:X8} >= 0x{rows[i][column]:X8}");
}
}
private static void WithTable(string contents, Action<GuestAddressTable> check, bool expectFailure = false)
{
var path = Path.Combine(Path.GetTempPath(), $"mkw-guest-table-{Guid.NewGuid():N}.inc");
File.WriteAllText(path, contents);
try
{
if (expectFailure)
Assert.Throws<InvalidDataException>(() => GuestAddressTable.Load(path));
else
check(GuestAddressTable.Load(path));
}
finally
{
File.Delete(path);
}
}
}
@@ -1,4 +1,5 @@
using System.Buffers.Binary;
using Translator.Core.Mods.Mkwii;
using Translator.Core.Parsing.Kamek;
using Xunit;
@@ -78,8 +79,11 @@ public class KamekPulFileTests
Assert.Throws<ArgumentOutOfRangeException>(() => KamekPpcEncoding.EncodeBranch(0x8053369C, 0x8053369D, link: false));
}
[Fact]
public void ParsesBundledRetroRewindCodePulWhenPresent()
[Theory]
[InlineData("P", 0x800ED6E8u)]
[InlineData("E", 0x800ED648u)]
[InlineData("J", 0x800ED608u)]
public void ParsesBundledRetroRewindCodePulWhenPresent(string region, uint bootstrapHook)
{
var repoRoot = ProjectPathsForTests.FindRepositoryRoot();
var codePul = Path.Combine(repoRoot, "PulsarPacks", "completed", "RetroRewind", "RetroRewind6", "Binaries", "Code.pul");
@@ -89,7 +93,7 @@ public class KamekPulFileTests
}
var pul = KamekPulFile.Load(codePul);
var chunk = pul.SelectRegion("P");
var chunk = pul.SelectRegion(region);
Assert.True(pul.IsCombined);
Assert.Equal(3, pul.Chunks.Count);
@@ -102,6 +106,9 @@ public class KamekPulFileTests
Assert.NotEmpty(chunk.Commands);
Assert.Equal(chunk.Commands.Count, chunk.RelativeCommandCount + chunk.AbsoluteCommandCount);
Assert.Equal(chunk.Commands.Count, chunk.CommandCounts.Values.Sum());
var lowered = RetroWfcBootstrapSuppressor.Suppress(chunk, bootstrapHook);
Assert.Equal(chunk.Commands.Count - 1, lowered.Commands.Count);
Assert.DoesNotContain(lowered.Commands, command => command.AddressIsAbsolute && command.Address == bootstrapHook);
}
private static byte[] BuildChunk(uint bssSize, byte[] code, byte[][] commands,
@@ -0,0 +1,114 @@
using System.Buffers.Binary;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Translator.Cli.Configuration;
using Translator.Core.Mods.Mkwii;
using Translator.Core.Parsing.Kamek;
using PayloadFiles = WiiCompiled.Setup.Common.RetroWfcPayload;
namespace Translator.Tests;
public sealed class RegionalRetroRewindTests
{
[Theory]
[InlineData("mkwii", "RMCP01", "P", 0x800ED6E8u, 0x80601C60u, 0x4BD977A4u)]
[InlineData("mkwii-ntsc-u", "RMCE01", "E", 0x800ED648u, 0x805DD380u, 0x4BDB7D04u)]
[InlineData("mkwii-ntsc-j", "RMCJ01", "J", 0x800ED608u, 0x806013D4u, 0x4BD979B0u)]
public void RetailProfileUsesMatchingRegionAndSuppressesItsBootstrap(
string directory, string gameId, string region, uint hook, uint integritySite, uint expectedBranch)
{
var root = ProjectPathsForTests.FindRepositoryRoot();
var source = File.ReadAllText(Path.Combine(root, "projects", directory, "recomp.yml"));
var expectedDolHash = Regex.Match(source, @"sha256: ([0-9a-f]{64})").Groups[1].Value;
var scratch = Path.Combine(Path.GetTempPath(), $"retro-profile-{Guid.NewGuid():N}");
Directory.CreateDirectory(scratch);
TranslationProjectConfig project;
try
{
// Keep this suite binary-free: substitute only the retail input paths and hashes.
// The profile block, including its original compatibility pins, is read unchanged.
var input = Path.Combine(scratch, "input.bin");
File.WriteAllBytes(input, new byte[0x100]);
var hash = Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(input))).ToLowerInvariant();
source = source.Replace("workspace_root: ../..", $"workspace_root: {root}")
.Replace("path: Assets/main.dol", $"path: {input}")
.Replace("path: Assets/StaticR.rel", $"path: {input}");
source = Regex.Replace(source, @"(?m)^ sha256: [0-9a-f]{64}\r?$", $" sha256: {hash}");
var manifest = Path.Combine(scratch, "recomp.yml");
File.WriteAllText(manifest, source);
project = TranslationProjectConfig.Load(manifest);
}
finally { Directory.Delete(scratch, true); }
var profile = project.Profiles["retro-rewind"];
Assert.True(profile.Enabled);
Assert.Equal(gameId, profile.RequiresGameId);
Assert.Equal(expectedDolHash, profile.RequiresDolSha256);
Assert.Equal(region, profile.Region);
Assert.Equal(PayloadFiles.PayloadUri(gameId), profile.RetroWfcPayload);
Assert.Equal(hook, profile.RetroWfcLegacyBootstrapHook);
// The pack checks the static branch bytes at Page::IncrementDuration + 0x18.
// Its hook is module offset 0x124. A PAL link base in an NTSC profile translates
// successfully but fails this check at boot, so pin the actual expected opcode.
Assert.Equal(expectedBranch, KamekPpcEncoding.EncodeBranch(
integritySite, profile.ModuleLinkBase!.Value + 0x124u, link: false));
// A combined pack has a different absolute hook in each regional chunk.
// Selecting a region and suppressing a PAL hook in it must not succeed.
var bootstrap = new KamekCommand(0, 0, KamekCommandId.Branch, true, hook, [0x100u]);
var chunk = new KamekChunk(0, 0, 0, 4, 0, 0, 4, new byte[4], [bootstrap]);
Assert.Empty(RetroWfcBootstrapSuppressor.Suppress(chunk, profile.RetroWfcLegacyBootstrapHook!.Value).Commands);
if (region != "P")
Assert.Throws<InvalidDataException>(() => RetroWfcBootstrapSuppressor.Suppress(chunk, 0x800ED6E8u));
}
[Theory]
[InlineData("RMCP01")]
[InlineData("RMCE01")]
[InlineData("RMCJ01")]
public void StagedPayloadRequiresValidSignatureAndMatchingGame(string gameId)
{
var root = Path.Combine(Path.GetTempPath(), $"retro-region-{Guid.NewGuid():N}");
try
{
var path = Path.Combine(root, PayloadFiles.PayloadRelativePath(gameId));
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
using var rsa = RSA.Create(2048);
var key = rsa.ExportParameters(false);
var image = new byte[0x150];
"WWFC/Payload"u8.CopyTo(image);
BinaryPrimitives.WriteUInt32BigEndian(image.AsSpan(0x0C), (uint)image.Length);
Encoding.ASCII.GetBytes(PayloadFiles.PayloadGame(gameId)).CopyTo(image, 0x138);
Sign();
Assert.Equal(path, PayloadFiles.ResolveRetroWfcPayloadFile(root, gameId, key));
image[^1] ^= 1;
File.WriteAllBytes(path, image);
Assert.Throws<InvalidDataException>(() => PayloadFiles.ResolveRetroWfcPayloadFile(root, gameId, key));
// Even a correctly signed payload renamed to this region's filename is rejected.
Encoding.ASCII.GetBytes(gameId == "RMCP01" ? "RMCED00" : "RMCPD00").CopyTo(image, 0x138);
Sign();
Assert.Throws<InvalidDataException>(() => PayloadFiles.ResolveRetroWfcPayloadFile(root, gameId, key));
void Sign()
{
rsa.SignData(image.AsSpan(0x110), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
.CopyTo(image.AsSpan(0x10));
File.WriteAllBytes(path, image);
}
}
finally { if (Directory.Exists(root)) Directory.Delete(root, true); }
}
[Theory]
[InlineData("https://rwfc.net/api/wfc/payload?g=RMCED00")]
[InlineData("https://rwfc.net/api/wfc/payload?g=RMCJD00")]
public void OnlyExactRegionalDownloadEndpointsAreAccepted(string uri)
{
PayloadFiles.ValidateRetroWfcPayloadUri(uri);
Assert.Throws<InvalidDataException>(() => PayloadFiles.ValidateRetroWfcPayloadUri(uri + "&extra=1"));
Assert.Throws<InvalidDataException>(() => PayloadFiles.ValidateRetroWfcPayloadUri(uri.Replace("https:", "http:")));
}
}
@@ -0,0 +1,162 @@
using System.Buffers.Binary;
using Translator.Core.Loading;
using Translator.Core.Mods.Mkwii;
using Translator.Core.Parsing.Kamek;
namespace Translator.Tests;
public sealed class RetroRewindRegionPatchesTests
{
private static readonly (uint Pal, uint Us, uint Target)[] UnportedHooks =
[
(0x808B4D48, 0x808B0698, 0x80563BE8),
(0x808B4DFC, 0x808B074C, 0x80563CD8),
(0x808B4E14, 0x808B0764, 0x80563CCC),
(0x808CB02C, 0x808C5D24, 0x8072E068),
(0x808DBCD8, 0x808D7580, 0x806FAB44),
];
[Fact]
public void MovesEveryUnportedHookToItsUsSlotAndKeepsPortedOnes()
{
var pal = Chunk([.. UnportedHooks.Select(h => Command(KamekCommandId.Addr32, h.Pal)),
Command(KamekCommandId.Branch, 0x807C8994)]);
var us = Chunk([.. UnportedHooks.Select(h => Command(KamekCommandId.Addr32, h.Pal)),
Command(KamekCommandId.Branch, 0x807B9F34)]);
var corrected = RetroRewindRegionPatches.CorrectUnportedUsHooks(us, pal, "RMCE01", SlotImage());
for (var i = 0; i < UnportedHooks.Length; i++)
{
Assert.Equal(UnportedHooks[i].Us, corrected.Commands[i].Address);
Assert.Equal(us.Commands[i].Arguments, corrected.Commands[i].Arguments);
}
Assert.Same(us.Commands[^1], corrected.Commands[^1]);
Assert.Same(us.CodeBlob, corrected.CodeBlob);
}
[Theory]
[InlineData("RMCP01")]
[InlineData("RMCJ01")]
public void LeavesOtherRegionsUntouched(string gameId)
{
var chunk = Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8));
Assert.Same(chunk, RetroRewindRegionPatches.CorrectUnportedUsHooks(chunk, chunk, gameId, SlotImage()));
}
[Fact]
public void AcceptsAPackWhosePortMapCoversTheHooks()
{
var pal = Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8));
var us = Chunk(Command(KamekCommandId.Addr32, 0x808D7580));
Assert.Same(us, RetroRewindRegionPatches.CorrectUnportedUsHooks(us, pal, "RMCE01", SlotImage()));
}
[Fact]
public void RejectsAnUnknownUnportedRelAddress()
{
var chunk = Chunk(Command(KamekCommandId.Addr32, 0x808B4D60));
Assert.Throws<InvalidDataException>(() =>
RetroRewindRegionPatches.CorrectUnportedUsHooks(chunk, chunk, "RMCE01", SlotImage()));
}
[Fact]
public void IgnoresSharedAddressesOutsideStaticR()
{
var chunk = Chunk(Command(KamekCommandId.Write32, 0x80001A80));
Assert.Same(chunk, RetroRewindRegionPatches.CorrectUnportedUsHooks(chunk, chunk, "RMCE01", SlotImage()));
}
[Fact]
public void RejectsAMismatchedSlotOrCommand()
{
var hook = Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8));
var image = SlotImage();
image.Memory[image.GetOffset(0x808D7580)] ^= 1;
Assert.Throws<InvalidDataException>(() =>
RetroRewindRegionPatches.CorrectUnportedUsHooks(hook, hook, "RMCE01", image));
var write = Chunk(Command(KamekCommandId.Write32, 0x808DBCD8));
Assert.Throws<InvalidDataException>(() =>
RetroRewindRegionPatches.CorrectUnportedUsHooks(write, write, "RMCE01", SlotImage()));
Assert.Throws<InvalidDataException>(() => RetroRewindRegionPatches.CorrectUnportedUsHooks(
hook, Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8), Command(KamekCommandId.Branch, 0x80000100)),
"RMCE01", SlotImage()));
}
private static readonly uint[] EsrbHook =
[0x3C608000, 0x88030003, 0x2C000045, 0x4C820020, 0x3C804800, 0x3C608060, 0x3804001C, 0x90034094, 0x4E800020];
[Fact]
public void AppliesTheUsEsrbSkipStaticallyAndDropsTheStore()
{
var chunk = EsrbChunk(0x40);
var patched = RetroRewindRegionPatches.ApplyUsEsrbSkip(chunk, "RMCE01", EsrbImage(0x3C608038));
var write = Assert.Single(patched.Commands);
Assert.Equal(KamekCommandId.Write32, write.Id);
Assert.True(write.AddressIsAbsolute);
Assert.Equal(0x80604094u, write.Address);
Assert.Equal([0x4800001Cu], write.Arguments);
Assert.Equal(0x60000000u, BinaryPrimitives.ReadUInt32BigEndian(patched.CodeBlob.AsSpan(0x40 + 7 * 4, 4)));
Assert.Equal(0x90034094u, BinaryPrimitives.ReadUInt32BigEndian(chunk.CodeBlob.AsSpan(0x40 + 7 * 4, 4)));
}
[Theory]
[InlineData("RMCP01")]
[InlineData("RMCJ01")]
public void LeavesTheEsrbHookAloneOutsideNtscU(string gameId)
{
var chunk = EsrbChunk(0x40);
Assert.Same(chunk, RetroRewindRegionPatches.ApplyUsEsrbSkip(chunk, gameId, EsrbImage(0x3C608038)));
}
[Fact]
public void LeavesAPackWithoutTheEsrbHookAlone()
{
var chunk = Chunk();
Assert.Same(chunk, RetroRewindRegionPatches.ApplyUsEsrbSkip(chunk, "RMCE01", EsrbImage(0x3C608038)));
}
[Fact]
public void RejectsAnUnexpectedEsrbTargetOrDuplicateHook()
{
Assert.Throws<InvalidDataException>(() =>
RetroRewindRegionPatches.ApplyUsEsrbSkip(EsrbChunk(0x40), "RMCE01", EsrbImage(0x4800001C)));
var twice = EsrbChunk(0x40);
for (var i = 0; i < EsrbHook.Length; i++)
BinaryPrimitives.WriteUInt32BigEndian(twice.CodeBlob.AsSpan(0x100 + i * 4, 4), EsrbHook[i]);
Assert.Throws<InvalidDataException>(() =>
RetroRewindRegionPatches.ApplyUsEsrbSkip(twice, "RMCE01", EsrbImage(0x3C608038)));
}
private static KamekChunk EsrbChunk(int offset)
{
var chunk = Chunk();
for (var i = 0; i < EsrbHook.Length; i++)
BinaryPrimitives.WriteUInt32BigEndian(chunk.CodeBlob.AsSpan(offset + i * 4, 4), EsrbHook[i]);
return chunk;
}
private static ProgramImage EsrbImage(uint instruction)
{
var memory = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(memory, instruction);
var range = AddressRange.FromStartAndSize(0x80604094, 4);
return new ProgramImage(memory, range, default, range, "fixture", range.Start);
}
private static KamekCommand Command(KamekCommandId id, uint address) =>
new(0, 0, id, true, address, [0x100u]);
private static KamekChunk Chunk(params KamekCommand[] commands) =>
new(1, 0, 0x20, 0x200, 0, 0, 0x220, new byte[0x200], commands);
private static ProgramImage SlotImage()
{
const uint start = 0x808B0000;
var memory = new byte[0x28000];
foreach (var (_, us, target) in UnportedHooks)
BinaryPrimitives.WriteUInt32BigEndian(memory.AsSpan((int)(us - start), 4), target);
var range = AddressRange.FromStartAndSize(start, (uint)memory.Length);
return new ProgramImage(memory, range, default, range, "fixture", range.Start);
}
}
@@ -8,6 +8,25 @@ namespace Translator.Tests;
public class RetroWfcPayloadLoweringTests
{
[Theory]
[InlineData("RMCP01", "P", "RMCPD00")]
[InlineData("RMCE01", "E", "RMCED00")]
[InlineData("RMCJ01", "J", "RMCJD00")]
public void PayloadMustMatchBaseDiscRegion(string gameId, string region, string payloadGame)
{
var payload = BuildSharedPayloadFixture();
payload.AsSpan(0x138, 12).Clear();
Encoding.ASCII.GetBytes(payloadGame).CopyTo(payload, 0x138);
var manifest = TestManifest() with { GameId = gameId, Region = region };
var result = RetroWfcPayload.Parse(payload, manifest, 0x81800000u, 0x2000u);
Assert.Equal(payloadGame, result.Summary.Game);
Assert.True(result.LoweringPlan.IsPlannable);
var wrongRegion = manifest with { GameId = gameId == "RMCP01" ? "RMCE01" : "RMCP01" };
Assert.Throws<InvalidDataException>(() =>
RetroWfcPayload.Parse(payload, wrongRegion, 0x81800000u, 0x2000u));
}
[Fact]
public void SharedPayloadDescriptorDrivesRelocationAndStaticLowering()
{
@@ -35,28 +54,32 @@ public class RetroWfcPayloadLoweringTests
Assert.Equal("moduleFunction", pointer.TargetKind);
}
[Fact]
public void ProductionPayloadValidatesAndTranslatesEverySupportedPatch()
[Theory]
[InlineData("RMCP01", "P", "RMCPD00")]
[InlineData("RMCE01", "E", "RMCED00")]
[InlineData("RMCJ01", "J", "RMCJD00")]
public void ProductionPayloadValidatesAndTranslatesEverySupportedPatch(
string gameId, string region, string payloadGame)
{
var payloadRoot = Path.Combine(
AppContext.BaseDirectory,
"TestAssets",
"RetroWfcPayload");
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(payloadRoot);
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(payloadRoot, gameId: gameId);
var payloadPath = Path.Combine(
payloadRoot,
"binary",
"payload.RMCPD00.bin");
$"payload.{payloadGame}.bin");
var payload = File.ReadAllBytes(payloadPath);
var result = RetroWfcPayload.Parse(
payload,
ProductionPayloadManifest(),
ProductionPayloadManifest() with { GameId = gameId, Region = region },
0x81800000u,
0x00200000u,
"TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin");
$"TestAssets/RetroWfcPayload/binary/payload.{payloadGame}.bin");
Assert.Equal("RMCPD00", result.Summary.Game);
Assert.Equal(payloadGame, result.Summary.Game);
Assert.Equal(payload.Length, result.Summary.PayloadImageSize);
Assert.True(result.LoweringPlan.IsPlannable);
Assert.Empty(result.LoweringPlan.Issues);
@@ -100,8 +100,10 @@ public sealed class TranslatedBuildShardEmitterTests
var options = new TranslatedBuildShardOptions(
metadataPath, functions, output, native,
resolvedPath, mod, BaseShardCount: 2, ModShardCount: 1, RegistrationShardCount: 1);
resolvedPath, mod, BaseShardCount: 2, ModShardCount: 1, RegistrationShardCount: 1,
Region: GuestRegion.NtscJ);
var first = TranslatedBuildShardEmitter.Emit(options);
Assert.Contains("set(MKW_TRANSLATED_GUEST_REGION 2)", File.ReadAllText(first.CMakeManifestPath));
Assert.Equal(1, first.SharedBaseFunctionCount);
Assert.Equal(1, first.ProfileSensitiveTargetCount);
Assert.Equal(1, first.ProfileSensitiveCallerCount);
@@ -30,7 +30,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="TestAssets\RetroWfcPayload\binary\payload.RMCPD00.bin" CopyToOutputDirectory="PreserveNewest" />
<Content Include="TestAssets\RetroWfcPayload\binary\payload.RMC?D00.bin" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<!--