mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-21 13:54:55 -04:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e73a51616 | |||
| 7db64ea00a | |||
| a222d02fbf | |||
| 9dfbef00f5 | |||
| 5e337e6ecf | |||
| ad95c5c7da | |||
| dfe32f61e1 | |||
| e3f5e3a3ec | |||
| 0321842569 |
@@ -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
|
||||
|
||||
@@ -102,6 +102,7 @@ jobs:
|
||||
mkw_nand_settings_tests \
|
||||
mkw_sc_serial_tests \
|
||||
mkw_input_expr_tests \
|
||||
mkw_bluetooth_controller_tests \
|
||||
mkw_macos_native_compile \
|
||||
mkw_macos_context_abi_tests \
|
||||
mkw_macos_host_context_tests \
|
||||
|
||||
+3
-1
@@ -66,7 +66,9 @@ project.lock.json
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/.idea/
|
||||
/tools/
|
||||
/tools/*
|
||||
!/tools/region/
|
||||
__pycache__/
|
||||
/TEMP/
|
||||
/tmp/
|
||||
/test_output/
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
<#
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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]}..., " +
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+34
-6
@@ -69,6 +69,7 @@ fuse_ld_override=""
|
||||
native_prebuilt_dir=""
|
||||
sysroot=""
|
||||
setup_version=""
|
||||
x86_cpu_profile=auto
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
@@ -95,6 +96,8 @@ Usage: local-build.sh --output-dir DIR [options]
|
||||
standard headers/startup files
|
||||
--setup-version VERSION Version recorded in the runtime log (default: <Version> in
|
||||
Launcher/Directory.Build.props when present)
|
||||
--x86-cpu-profile {auto|v2|v3} x86-64 instruction set to build for (default: auto, v3 when
|
||||
this machine supports it)
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -120,6 +123,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--native-prebuilt-dir) native_prebuilt_dir=$2; shift 2 ;;
|
||||
--sysroot) sysroot=$2; shift 2 ;;
|
||||
--setup-version) setup_version=$2; shift 2 ;;
|
||||
--x86-cpu-profile) x86_cpu_profile=$2; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) fail "unknown argument: $1" ;;
|
||||
esac
|
||||
@@ -135,6 +139,10 @@ case "$profile" in
|
||||
base|retro-rewind|both) ;;
|
||||
*) fail "--profile must be base, retro-rewind, or both" ;;
|
||||
esac
|
||||
case "$x86_cpu_profile" in
|
||||
auto|v2|v3) ;;
|
||||
*) fail "--x86-cpu-profile must be auto, v2, or v3" ;;
|
||||
esac
|
||||
|
||||
builds_retro=0
|
||||
[[ "$profile" == "retro-rewind" || "$profile" == "both" ]] && builds_retro=1
|
||||
@@ -184,7 +192,22 @@ if [[ -n "$native_prebuilt_dir" ]]; then
|
||||
assert_file "$native_prebuilt_dir/native_prebuilt.cmake" "Native prebuilt package"
|
||||
fi
|
||||
|
||||
project=$workspace/projects/mkwii/recomp.yml
|
||||
host_supports_x86_64_v3() {
|
||||
local macros macro
|
||||
macros=$("$cc_bin" -march=native -dM -E -x c /dev/null 2>/dev/null) || return 1
|
||||
for macro in __AVX__ __AVX2__ __BMI__ __BMI2__ __F16C__ __FMA__ __LZCNT__ __MOVBE__; do
|
||||
grep -q "^#define $macro " <<<"$macros" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
if [[ "$x86_cpu_profile" == "auto" ]]; then
|
||||
x86_cpu_profile=v3
|
||||
if [[ "$(uname -m)" == "x86_64" ]] && ! host_supports_x86_64_v3; then
|
||||
x86_cpu_profile=v2
|
||||
echo "MKWCBUILD: This CPU does not support x86-64-v3; building for x86-64-v2"
|
||||
fi
|
||||
fi
|
||||
|
||||
assets=$workspace/Assets
|
||||
generated=$workspace/generated
|
||||
functions=$generated/functions
|
||||
@@ -197,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
|
||||
@@ -338,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
|
||||
@@ -350,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
|
||||
@@ -434,7 +461,8 @@ configure_args=(-S "$workspace/runtime" -B "$build" -G Ninja
|
||||
-DCMAKE_C_COMPILER="$cc_bin" -DCMAKE_CXX_COMPILER="$cxx_bin"
|
||||
-DCMAKE_MAKE_PROGRAM="$ninja_bin"
|
||||
-DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs"
|
||||
-DMKW_SETUP_VERSION="$setup_version")
|
||||
-DMKW_SETUP_VERSION="$setup_version"
|
||||
-DMKW_X86_CPU_PROFILE="$x86_cpu_profile")
|
||||
# CMAKE_C_COMPILER/CXX_COMPILER stay stable across AppImage runs on their own (they're exactly
|
||||
# what's passed via -D above, and AppRun points --cc/--cxx at a symlink it re-targets at the
|
||||
# current mount every launch - see build-appimage.sh). CMAKE_AR/RANLIB/LINKER/ASM_COMPILER do NOT
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
Submodule aurora-main updated: 648e77d7e8...ff0b617a92
Generated
+29432
File diff suppressed because it is too large
Load Diff
Generated
+150
@@ -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)
|
||||
@@ -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
|
||||
Generated
+591
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+29412
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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
|
||||
Generated
+1826
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,16 @@ target_include_directories(mkw_input_expr_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}
|
||||
target_compile_features(mkw_input_expr_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_input_expr_tests COMMAND mkw_input_expr_tests)
|
||||
|
||||
# The emulated Bluetooth controller is pure host code.
|
||||
add_executable(mkw_bluetooth_controller_tests
|
||||
"${MKW_TEST_RUNTIME_DIR}/tests/bluetooth_controller_tests.cpp"
|
||||
"${MKW_TEST_RUNTIME_DIR}/src/hle/bluetooth/bt_controller.cpp"
|
||||
"${MKW_TEST_RUNTIME_DIR}/src/hle/bluetooth/bt_remote_link.cpp"
|
||||
"${MKW_TEST_RUNTIME_DIR}/src/hle/bluetooth/bt_sdp.cpp")
|
||||
target_include_directories(mkw_bluetooth_controller_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include")
|
||||
target_compile_features(mkw_bluetooth_controller_tests PRIVATE cxx_std_20)
|
||||
add_test(NAME mkw_bluetooth_controller_tests COMMAND mkw_bluetooth_controller_tests)
|
||||
|
||||
# HostContext deliberately keeps the platform-specific context primitive out
|
||||
# of fiber_manager.cpp. Exercise the Linux libco handoff directly so future
|
||||
# refactors cannot silently remove its headers, implementation, or link edge.
|
||||
|
||||
@@ -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)
|
||||
@@ -206,7 +213,7 @@ function(mkw_configure_product target)
|
||||
|
||||
if(MKW_PLATFORM_WINDOWS)
|
||||
target_link_libraries(${target} PRIVATE
|
||||
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp)
|
||||
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp bthprops)
|
||||
|
||||
set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64)
|
||||
|
||||
@@ -66,7 +66,7 @@ function(mkw_runtime_link_dependencies target scope)
|
||||
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx)
|
||||
target_link_libraries(${target} ${scope} mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls)
|
||||
if(MKW_PLATFORM_WINDOWS)
|
||||
target_link_libraries(${target} ${scope} shell32 windowsapp)
|
||||
target_link_libraries(${target} ${scope} shell32 windowsapp bthprops)
|
||||
elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64)
|
||||
# ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS
|
||||
# media monitoring). Empty on platforms where dl* is already in libc/libSystem.
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "settings_overlay.h"
|
||||
#include "runtime_config.h"
|
||||
#include "gx_worker.h"
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <aurora/event.h>
|
||||
@@ -131,14 +132,42 @@ inline void ApplyPendingMkwDynamicAspectSurface() {
|
||||
UpdateMkwDynamicAspectSurface(surfaceWidth, surfaceHeight);
|
||||
}
|
||||
|
||||
void GxWorkerBeginFrame();
|
||||
void GxWorkerEndFrame(AuroraImguiFrame* imgui);
|
||||
void SetAuroraFrameGxBegun(bool begun);
|
||||
|
||||
inline bool BeginAuroraFrame() {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
// The window half can touch the surface and render targets the worker draws into.
|
||||
GxWorker::Sync();
|
||||
if (!aurora_begin_frame_window()) {
|
||||
return false;
|
||||
}
|
||||
GxWorker::Submit([] { GxWorkerBeginFrame(); });
|
||||
ApplyPendingMkwDynamicAspectSurface();
|
||||
return true;
|
||||
}
|
||||
if (!aurora_begin_frame()) {
|
||||
return false;
|
||||
}
|
||||
SetAuroraFrameGxBegun(true);
|
||||
ApplyPendingMkwDynamicAspectSurface();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ends the frame; with the GX worker, the game thread joins it here so the next frame never
|
||||
// overlaps guest memory the worker may still read.
|
||||
inline void EndAuroraFrame() {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
AuroraImguiFrame* imgui = aurora_end_frame_window();
|
||||
GxWorker::Submit([imgui] { GxWorkerEndFrame(imgui); });
|
||||
GxWorker::Sync();
|
||||
return;
|
||||
}
|
||||
aurora_end_frame();
|
||||
SetAuroraFrameGxBegun(false);
|
||||
}
|
||||
|
||||
// Poll Aurora events and update cached window/framebuffer dimensions.
|
||||
inline void UpdateAuroraAndProcessEvents() {
|
||||
ProcessAuroraEvents(aurora_update());
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// HCI and L2CAP fields are little-endian.
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Bluetooth {
|
||||
|
||||
inline uint16_t Le16(const uint8_t* p) {
|
||||
return static_cast<uint16_t>(p[0] | (p[1] << 8));
|
||||
}
|
||||
|
||||
inline void PutLe16(std::vector<uint8_t>& out, uint16_t v) {
|
||||
out.push_back(static_cast<uint8_t>(v));
|
||||
out.push_back(static_cast<uint8_t>(v >> 8));
|
||||
}
|
||||
|
||||
} // namespace Bluetooth
|
||||
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
// Emulated Bluetooth controller behind the Wii's internal USB module
|
||||
// (/dev/usb/oh1/57e/305). The game's own Broadcom stack (BTE), WUD, WPAD and KPAD
|
||||
// talk HCI to it; each of the four virtual Wii Remotes it hosts is backed by a
|
||||
// physical remote on the PC (see RemoteHost), whose HID reports are passed
|
||||
// through unchanged. Pure host code: no guest memory, so it is unit-testable.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Bluetooth {
|
||||
|
||||
// Bluetooth device address in HCI (least significant byte first) order.
|
||||
using BdAddr = std::array<uint8_t, 6>;
|
||||
|
||||
inline constexpr size_t kRemoteSlots = 4;
|
||||
|
||||
enum class LinkState : uint8_t {
|
||||
Down, // no baseband connection to the game
|
||||
Connecting, // baseband up, HID channels being opened
|
||||
Ready, // HID control and interrupt channels open
|
||||
};
|
||||
|
||||
// The physical side of the virtual remotes. Called from the thread that drives
|
||||
// the Controller; implementations synchronize with their own I/O threads.
|
||||
class RemoteHost {
|
||||
public:
|
||||
virtual ~RemoteHost() = default;
|
||||
// A physical remote is attached to `slot` and can carry a connection.
|
||||
virtual bool IsAttached(size_t slot) const = 0;
|
||||
// True once each time the remote on `slot` asks to connect (it was just
|
||||
// attached, or a button was pressed while it had no connection).
|
||||
virtual bool ConsumeWake(size_t slot) = 0;
|
||||
// Next input report (report id first, no HID header), false when none.
|
||||
virtual bool PopInputReport(size_t slot, std::vector<uint8_t>& report) = 0;
|
||||
// Output report from the game (report id first, no HID header).
|
||||
virtual void SendOutputReport(size_t slot, const uint8_t* report, size_t size) = 0;
|
||||
virtual void OnLinkStateChanged(size_t slot, LinkState state) = 0;
|
||||
// The game ended a working connection; a real remote powers off here.
|
||||
virtual void OnGuestDisconnect(size_t slot) = 0;
|
||||
// The game is searching for remotes (its 1+2 / reconnect screens).
|
||||
virtual void OnGuestInquiry(bool active) = 0;
|
||||
// "Nintendo RVL-CNT-01" or "Nintendo RVL-CNT-01-TR".
|
||||
virtual const char* DeviceName(size_t slot) const = 0;
|
||||
};
|
||||
|
||||
class RemoteLink;
|
||||
|
||||
class Controller {
|
||||
public:
|
||||
explicit Controller(RemoteHost& host);
|
||||
~Controller();
|
||||
Controller(const Controller&) = delete;
|
||||
Controller& operator=(const Controller&) = delete;
|
||||
|
||||
// The guest opened / closed the USB device.
|
||||
void Open(uint64_t nowUs);
|
||||
void Close();
|
||||
bool IsOpen() const { return m_open; }
|
||||
|
||||
// Raw HCI command packet (opcode first, no H4 byte).
|
||||
void ExecuteCommand(const uint8_t* packet, size_t size, uint64_t nowUs);
|
||||
// Raw ACL packet from the guest (handle first).
|
||||
void ReceiveAcl(const uint8_t* packet, size_t size, uint64_t nowUs);
|
||||
// Advances timers, connection attempts, input forwarding and flow control.
|
||||
void Tick(uint64_t nowUs);
|
||||
|
||||
bool HasEvent() const { return !m_events.empty(); }
|
||||
bool PopEvent(std::vector<uint8_t>& event);
|
||||
// ACL data is only handed out once every queued event has been delivered,
|
||||
// so the guest always sees a connection before data on it.
|
||||
bool PopAcl(std::vector<uint8_t>& packet);
|
||||
size_t AclQueued() const { return m_acl.size(); }
|
||||
|
||||
LinkState SlotLinkState(size_t slot) const;
|
||||
bool PageScanEnabled() const { return (m_scanEnable & 0x02) != 0; }
|
||||
bool InquiryActive() const { return m_inquiry.active; }
|
||||
|
||||
static BdAddr LocalAddress();
|
||||
static BdAddr RemoteAddress(size_t slot);
|
||||
static uint16_t ConnectionHandle(size_t slot);
|
||||
|
||||
// Used by RemoteLink.
|
||||
void QueueEvent(std::vector<uint8_t> event);
|
||||
void QueueAcl(uint16_t handle, const std::vector<uint8_t>& l2capFrame);
|
||||
RemoteHost& Host() { return m_host; }
|
||||
uint32_t PageTimeoutUs() const { return static_cast<uint32_t>(m_pageTimeoutSlots) * 625u; }
|
||||
|
||||
private:
|
||||
struct StoredKey {
|
||||
BdAddr addr{};
|
||||
std::array<uint8_t, 16> key{};
|
||||
};
|
||||
struct Inquiry {
|
||||
bool active = false;
|
||||
uint64_t endUs = 0;
|
||||
uint8_t maxResponses = 0;
|
||||
uint8_t reported = 0;
|
||||
std::array<bool, kRemoteSlots> slotReported{};
|
||||
};
|
||||
struct Reassembly {
|
||||
std::vector<uint8_t> frame;
|
||||
size_t expected = 0;
|
||||
};
|
||||
// A reply the controller sends after a delay (page / name request timeouts).
|
||||
struct PendingEvent {
|
||||
uint64_t dueUs = 0;
|
||||
std::vector<uint8_t> event;
|
||||
};
|
||||
|
||||
void ResetState(bool keepStoredKeys);
|
||||
void CommandComplete(uint16_t opcode, const std::vector<uint8_t>& params);
|
||||
void CommandStatus(uint16_t opcode, uint8_t status);
|
||||
void TickInquiry(uint64_t nowUs);
|
||||
void FinishInquiry();
|
||||
void SendNumberOfCompletedPackets();
|
||||
int SlotForAddress(const BdAddr& addr) const;
|
||||
int SlotForHandle(uint16_t handle) const;
|
||||
const StoredKey* FindStoredKey(const BdAddr& addr) const;
|
||||
|
||||
void HandleLinkControl(uint16_t opcode, const uint8_t* p, size_t n, uint64_t nowUs);
|
||||
void HandleLinkPolicy(uint16_t opcode, const uint8_t* p, size_t n);
|
||||
void HandleControllerBaseband(uint16_t opcode, const uint8_t* p, size_t n, uint64_t nowUs);
|
||||
void HandleInformational(uint16_t opcode, const uint8_t* p, size_t n);
|
||||
void HandleStatusParameters(uint16_t opcode, const uint8_t* p, size_t n);
|
||||
void HandleVendor(uint16_t opcode, const uint8_t* p, size_t n);
|
||||
void UnknownCommand(uint16_t opcode);
|
||||
|
||||
RemoteHost& m_host;
|
||||
bool m_open = false;
|
||||
uint8_t m_scanEnable = 0;
|
||||
uint16_t m_pageTimeoutSlots = 0x2000;
|
||||
uint8_t m_inquiryMode = 0;
|
||||
std::array<uint8_t, 3> m_classOfDevice{};
|
||||
std::array<char, 248> m_localName{};
|
||||
Inquiry m_inquiry;
|
||||
bool m_hostInquiryNotified = false;
|
||||
std::vector<StoredKey> m_storedKeys;
|
||||
std::array<std::unique_ptr<RemoteLink>, kRemoteSlots> m_links;
|
||||
std::array<uint16_t, kRemoteSlots> m_completedPackets{};
|
||||
std::array<Reassembly, kRemoteSlots> m_reassembly;
|
||||
std::array<bool, kRemoteSlots> m_authPending{};
|
||||
std::vector<PendingEvent> m_pending;
|
||||
std::deque<std::vector<uint8_t>> m_events;
|
||||
std::deque<std::vector<uint8_t>> m_acl;
|
||||
};
|
||||
|
||||
} // namespace Bluetooth
|
||||
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
// One emulated Wii Remote as seen from the controller: its baseband connection,
|
||||
// L2CAP channels (SDP, HID control, HID interrupt) and the HID transaction layer
|
||||
// that carries the physical remote's reports.
|
||||
|
||||
#include "bluetooth/bt_controller.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Bluetooth {
|
||||
|
||||
class RemoteLink {
|
||||
public:
|
||||
enum class Baseband : uint8_t {
|
||||
Idle,
|
||||
Requesting, // Connection Request sent, waiting for the guest to accept
|
||||
Paging, // the guest paged this remote (Create Connection)
|
||||
Connected,
|
||||
};
|
||||
|
||||
RemoteLink(Controller& controller, size_t slot);
|
||||
|
||||
// Drops every connection without telling the guest (controller reset/close).
|
||||
void Reset();
|
||||
void Tick(uint64_t nowUs);
|
||||
|
||||
// Remote side wants a connection and should be offered to the guest's inquiry.
|
||||
bool Discoverable() const;
|
||||
bool ConnectWanted() const { return m_connectWanted; }
|
||||
|
||||
// Guest accepted / rejected this remote's Connection Request.
|
||||
bool IsRequesting() const { return m_baseband == Baseband::Requesting; }
|
||||
void OnAccepted(uint8_t role);
|
||||
void OnRejected(uint8_t reason, uint64_t nowUs);
|
||||
// Guest paged this remote. Returns false when the remote cannot answer.
|
||||
bool OnPaged(uint64_t nowUs);
|
||||
// Baseband went down. `byGuest` = the guest's HCI Disconnect.
|
||||
void OnDisconnected(bool byGuest, uint64_t nowUs);
|
||||
// The physical remote vanished while connected: tells the guest.
|
||||
void DropFromRemote(uint8_t reason);
|
||||
|
||||
void ReceiveL2cap(const uint8_t* frame, size_t size);
|
||||
|
||||
Baseband BasebandState() const { return m_baseband; }
|
||||
LinkState State() const { return m_state; }
|
||||
|
||||
private:
|
||||
struct Channel {
|
||||
uint16_t psm = 0;
|
||||
uint16_t localCid = 0;
|
||||
uint16_t remoteCid = 0;
|
||||
uint16_t remoteMtu = 672;
|
||||
bool weInitiated = false;
|
||||
bool connected = false; // connection response success seen/sent
|
||||
bool configSent = false; // our Configuration Request is out
|
||||
bool localConfigured = false; // the guest accepted our configuration
|
||||
bool remoteConfigured = false; // we accepted the guest's configuration
|
||||
uint8_t connectIdent = 0;
|
||||
uint8_t configIdent = 0;
|
||||
uint64_t requestUs = 0;
|
||||
std::vector<uint8_t> pendingOptions; // continued Configuration Request
|
||||
bool Open() const { return connected && localConfigured && remoteConfigured; }
|
||||
};
|
||||
|
||||
Channel* FindByLocal(uint16_t cid);
|
||||
Channel* FindByPsm(uint16_t psm);
|
||||
uint16_t AllocateCid() const;
|
||||
uint8_t NextIdent();
|
||||
|
||||
void SendSignal(uint8_t code, uint8_t ident, const std::vector<uint8_t>& data);
|
||||
void SendData(const Channel& channel, const std::vector<uint8_t>& payload);
|
||||
void RequestChannel(uint16_t psm, uint64_t nowUs);
|
||||
void SendConfigRequest(Channel& channel);
|
||||
void HandleSignalling(const uint8_t* data, size_t size);
|
||||
void HandleConnectionRequest(uint8_t ident, const uint8_t* p, size_t n);
|
||||
void HandleConnectionResponse(uint8_t ident, const uint8_t* p, size_t n);
|
||||
void HandleConfigurationRequest(uint8_t ident, const uint8_t* p, size_t n);
|
||||
void HandleConfigurationResponse(uint8_t ident, const uint8_t* p, size_t n);
|
||||
void HandleDisconnectionRequest(uint8_t ident, const uint8_t* p, size_t n);
|
||||
void HandleDisconnectionResponse(const uint8_t* p, size_t n);
|
||||
void HandleChannelData(Channel& channel, const uint8_t* data, size_t size);
|
||||
void HandleHidControl(Channel& channel, const uint8_t* data, size_t size);
|
||||
void UpdateState();
|
||||
void SetState(LinkState state);
|
||||
void ForwardInput();
|
||||
void SendInput(const Channel& interrupt, const std::vector<uint8_t>& report);
|
||||
void ClearConnection();
|
||||
|
||||
Controller& m_controller;
|
||||
size_t m_slot;
|
||||
Baseband m_baseband = Baseband::Idle;
|
||||
LinkState m_state = LinkState::Down;
|
||||
bool m_connectWanted = false;
|
||||
bool m_guestInitiated = false;
|
||||
bool m_everReady = false;
|
||||
uint8_t m_ident = 0;
|
||||
uint64_t m_stateSinceUs = 0;
|
||||
uint64_t m_retryAtUs = 0;
|
||||
uint64_t m_nowUs = 0;
|
||||
std::vector<Channel> m_channels;
|
||||
std::vector<uint8_t> m_report;
|
||||
};
|
||||
|
||||
} // namespace Bluetooth
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// SDP server of an emulated Wii Remote: the HID service record (0x00010000)
|
||||
// and the Device ID record (0x00010001) a real RVL-CNT-01 publishes.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Bluetooth::Sdp {
|
||||
|
||||
// The Wii Remote's HID report descriptor, as the game also embeds it.
|
||||
const std::vector<uint8_t>& WiiRemoteHidDescriptor();
|
||||
|
||||
// Answers one SDP request PDU. `maxResponse` bounds the PDU returned (the
|
||||
// guest's L2CAP MTU on the SDP channel).
|
||||
std::vector<uint8_t> HandleRequest(const uint8_t* pdu, size_t size, size_t maxResponse);
|
||||
|
||||
} // namespace Bluetooth::Sdp
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// Dedicated GX thread (Dolphin "dual core"): the game thread records GX HLE calls in order and
|
||||
// the worker executes them. While active, GX work runs on the worker except inside inline scopes,
|
||||
// which run on the game thread after the worker has caught up.
|
||||
namespace GxWorker {
|
||||
|
||||
namespace detail {
|
||||
inline constexpr size_t kAlign = 16;
|
||||
|
||||
extern std::atomic<bool> g_active;
|
||||
extern constinit thread_local bool t_onWorker;
|
||||
extern constinit thread_local int t_inlineDepth;
|
||||
extern bool g_fifoPending;
|
||||
|
||||
void* Reserve(size_t payloadSize, void (*run)(void*));
|
||||
void Commit();
|
||||
void FlushFifo();
|
||||
} // namespace detail
|
||||
|
||||
// Starts the worker unless [video] gx_worker disables it. Call once Aurora is up.
|
||||
void Start();
|
||||
void Stop();
|
||||
// Stops without running queued work; bounded, for fatal exits.
|
||||
void Abandon();
|
||||
|
||||
inline bool Active() noexcept { return detail::g_active.load(std::memory_order_relaxed); }
|
||||
inline bool OnWorker() noexcept { return detail::t_onWorker; }
|
||||
// True when a GX entry point on this thread must be recorded instead of executed.
|
||||
inline bool ShouldDefer() noexcept {
|
||||
return Active() && !detail::t_onWorker && detail::t_inlineDepth == 0;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void Submit(F&& fn) {
|
||||
using Fn = std::decay_t<F>;
|
||||
static_assert(alignof(Fn) <= detail::kAlign, "over-aligned GX command");
|
||||
if (detail::g_fifoPending) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
void* storage = detail::Reserve(sizeof(Fn), [](void* p) {
|
||||
struct Destroy {
|
||||
Fn* f;
|
||||
~Destroy() { f->~Fn(); }
|
||||
} guard{static_cast<Fn*>(p)};
|
||||
(*guard.f)();
|
||||
});
|
||||
new (storage) Fn(std::forward<F>(fn));
|
||||
detail::Commit();
|
||||
}
|
||||
|
||||
// Records fn when deferring, otherwise runs it now. Read call-time guest memory before calling.
|
||||
template <typename F>
|
||||
void Run(F&& fn) {
|
||||
if (ShouldDefer()) {
|
||||
Submit(std::forward<F>(fn));
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
// Gather-pipe writes, coalesced into one record per run of consecutive writes and replayed
|
||||
// with their original sizes.
|
||||
void FifoWrite(uint32_t value, uint32_t sizeBytes);
|
||||
void FifoBurst(const uint8_t* data, uint32_t sizeBytes);
|
||||
|
||||
// Called about every millisecond while the game thread waits in Sync.
|
||||
void SetWaitCallback(void (*callback)());
|
||||
|
||||
// Blocks until every recorded command has executed.
|
||||
void Sync();
|
||||
|
||||
// GX entry points run inline on the game thread while inside (after a Sync), for work whose
|
||||
// results the guest needs immediately.
|
||||
void BeginInline();
|
||||
void EndInline();
|
||||
|
||||
class InlineScope {
|
||||
public:
|
||||
InlineScope() { BeginInline(); }
|
||||
~InlineScope() { EndInline(); }
|
||||
InlineScope(const InlineScope&) = delete;
|
||||
InlineScope& operator=(const InlineScope&) = delete;
|
||||
};
|
||||
|
||||
} // namespace GxWorker
|
||||
|
||||
// Records the enclosing call for the GX worker and returns, or falls through when it should run
|
||||
// here (worker thread, inline scope, worker disabled).
|
||||
#define GX_DEFER_CALL(fn, ...) \
|
||||
do { \
|
||||
if (GxWorker::ShouldDefer()) { \
|
||||
GxWorker::Submit([=] { fn(__VA_ARGS__); }); \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define GX_DEFER_CALL0(fn) \
|
||||
do { \
|
||||
if (GxWorker::ShouldDefer()) { \
|
||||
GxWorker::Submit([] { fn(); }); \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
// Runs the rest of the enclosing function on this thread once the worker has caught up.
|
||||
#define GX_SYNC_INLINE() GxWorker::InlineScope gxInlineScope_
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
// IOS side of the emulated Bluetooth module (/dev/usb/oh1/57e/305): routes the
|
||||
// guest's USB control / bulk / interrupt transfers to Bluetooth::Controller and
|
||||
// completes the held event and ACL reads from the alarm pump.
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct CpuContext;
|
||||
|
||||
// Returns the fd for the Bluetooth device path, 0 when `path` is not ours.
|
||||
int32_t Bluetooth_HLE_OpenDevice(const char* path);
|
||||
bool Bluetooth_HLE_IsFd(uint32_t fd);
|
||||
// Cancels the held reads (their callbacks are queued) and closes the device.
|
||||
int32_t Bluetooth_HLE_Close(uint32_t fd);
|
||||
// True when the request completed now with `result`; false when it is held
|
||||
// until data arrives (its callback is queued later by the pump).
|
||||
bool Bluetooth_HLE_IoctlvAsync(uint32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr,
|
||||
uint32_t callback, uint32_t callbackArg, int32_t& result);
|
||||
int32_t Bluetooth_HLE_Ioctlv(uint32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr);
|
||||
// Runs the controller and completes held reads. Emulation thread only.
|
||||
bool Bluetooth_HLE_ProcessCompletions(CpuContext* cpu);
|
||||
@@ -41,42 +41,3 @@ inline GuestStatus Encode(const Fields& fields)
|
||||
}
|
||||
|
||||
} // namespace PadStatusContract
|
||||
|
||||
namespace WpadContract {
|
||||
|
||||
inline constexpr std::size_t kChannelCount = 4;
|
||||
inline constexpr int32_t kStatusDisabled = 0;
|
||||
inline constexpr int32_t kStatusReady = 3;
|
||||
inline constexpr int32_t kErrorNoController = -1;
|
||||
inline constexpr int32_t kErrorNotReady = -2;
|
||||
inline constexpr int32_t kErrorBadChannel = -6;
|
||||
inline constexpr int32_t kExtensionCore = 0;
|
||||
|
||||
class State {
|
||||
public:
|
||||
void Initialize() { m_initialized = true; }
|
||||
bool IsInitialized() const { return m_initialized; }
|
||||
int32_t GetLibraryStatus() const { return m_initialized ? kStatusReady : kStatusDisabled; }
|
||||
|
||||
int32_t GetDataFormat(uint32_t chan) const
|
||||
{
|
||||
if (chan >= kChannelCount) {
|
||||
return kErrorBadChannel;
|
||||
}
|
||||
return m_initialized ? 0 : kErrorNotReady;
|
||||
}
|
||||
|
||||
int32_t SetDataFormat(uint32_t chan, int32_t format) const
|
||||
{
|
||||
(void)format;
|
||||
if (chan >= kChannelCount) {
|
||||
return kErrorBadChannel;
|
||||
}
|
||||
return m_initialized ? kErrorNoController : kErrorNotReady;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
};
|
||||
|
||||
} // namespace WpadContract
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// HW_GPIOB_OUT, the GPIO outputs the PPC owns. WPAD sets bit 8 to power the
|
||||
// sensor bar; there is nothing behind it on a PC, so the register only latches.
|
||||
inline constexpr uint32_t kHollywoodGpioBOut = 0xCD0000C0u;
|
||||
|
||||
bool HollywoodGpio_Read32(uint32_t address, uint32_t& value);
|
||||
bool HollywoodGpio_Write32(uint32_t address, uint32_t value);
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -44,6 +44,7 @@ struct RuntimeUserConfig {
|
||||
std::optional<uint32_t> frameInterpolationFps;
|
||||
std::optional<bool> metalFxSpatialUpscaling;
|
||||
std::optional<bool> skipUnreadyPipelines;
|
||||
std::optional<bool> gxWorker;
|
||||
std::optional<bool> disableCopyFilter;
|
||||
std::optional<bool> textureReplacements;
|
||||
std::optional<bool> textureDumps;
|
||||
@@ -57,26 +58,16 @@ struct RuntimeUserConfig {
|
||||
std::optional<bool> audioMuted;
|
||||
std::optional<bool> audioMixWorker;
|
||||
std::optional<bool> attenuateMusicWhenMediaPlays;
|
||||
// Real Wii Remotes (with or without Nunchuk / Classic Controller) and Wii U Pro
|
||||
// Controllers paired over Bluetooth, driven by SDL's HIDAPI Wii driver. The driver
|
||||
// is opt-in on SDL's side, so this decides whether the runtime turns it on.
|
||||
// Real Wii Remotes passed through to the game's own Bluetooth stack. Off hands
|
||||
// Nintendo controllers (e.g. the Wii U Pro Controller) to SDL as plain gamepads.
|
||||
std::optional<bool> wiiRemotes;
|
||||
// Keep re-enumerating Bluetooth HID devices while no Wii controller is connected
|
||||
// (Dolphin's "continuous scanning"), so a remote that dropped or was switched on
|
||||
// after launch shows up without restarting.
|
||||
std::optional<bool> wiiContinuousScan;
|
||||
// Accelerometer zero-point correction for the Bluetooth Wii Remote, in g and in
|
||||
// SDL's sensor frame (x right, y out of the button face, z towards the user).
|
||||
// SDL's Wii driver falls back to a nominal zero point when its read of the
|
||||
// remote's calibration block times out (common over Bluetooth), so this is
|
||||
// measured in the overlay with the remote at rest. Stored per remote under
|
||||
// [controller.wii_accel_offsets]; the old shared
|
||||
// wii_accel_offset_* keys are only detected, to report that they are ignored.
|
||||
std::map<std::string, std::array<double, 3>> wiiAccelOffsets;
|
||||
bool hasLegacyWiiAccelOffset = false;
|
||||
// Debugging aid: append every KPAD sample of the Bluetooth remote (raw and
|
||||
// corrected accelerometer, buttons) to wii_accel_trace.csv next to Config.toml.
|
||||
std::optional<bool> wiiAccelTrace;
|
||||
// Look for remotes in pairing mode (1+2 / SYNC) while none is connected.
|
||||
std::optional<bool> wiiAutoPair;
|
||||
// The console's Wii Remote settings (SYSCONF BT.BAR, BT.SENS, BT.SPKV, BT.MOT).
|
||||
std::optional<bool> wiiSensorBarAbove;
|
||||
std::optional<int32_t> wiiIrSensitivity;
|
||||
std::optional<int32_t> wiiSpeakerVolume;
|
||||
std::optional<bool> wiiRumble;
|
||||
std::optional<bool> networkEnabled;
|
||||
std::optional<bool> discordPresenceEnabled;
|
||||
// The application ID of the WiiCompiled Discord application. This is only
|
||||
@@ -285,6 +276,8 @@ inline void EnsureConfigFile() {
|
||||
"display_mode = \"windowed\"\n"
|
||||
"graphics_api = \"auto\"\n"
|
||||
"skip_unready_pipelines = true\n"
|
||||
"# Runs GX command processing on its own thread. Read once, at startup.\n"
|
||||
"gx_worker = true\n"
|
||||
"disable_copy_filter = true\n"
|
||||
"show_fps = true\n"
|
||||
"# Dolphin-style custom textures. When enabled, the renderer indexes\n"
|
||||
@@ -382,28 +375,6 @@ inline void AppendOverlayRoots(RuntimeUserConfig& config, const std::string& roo
|
||||
}
|
||||
|
||||
// Reads every supported setting out of a parsed Config.toml document.
|
||||
// [x, y, z] of finite numbers (integers allowed), else nullopt.
|
||||
inline std::optional<std::array<double, 3>> ParseAccelOffset(const toml::value& value) {
|
||||
if (!value.is_array() || value.as_array().size() != 3) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::array<double, 3> offset{};
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
const toml::value& element = value.as_array()[i];
|
||||
if (element.is_floating()) {
|
||||
offset[i] = element.as_floating();
|
||||
} else if (element.is_integer()) {
|
||||
offset[i] = static_cast<double>(element.as_integer());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!std::isfinite(offset[i])) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
RuntimeUserConfig config;
|
||||
|
||||
@@ -427,13 +398,6 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
config.controllerExpressions[key] = value.as_string();
|
||||
}
|
||||
}
|
||||
if (section->contains("wii_accel_offsets") && section->at("wii_accel_offsets").is_table()) {
|
||||
for (const auto& [key, value] : section->at("wii_accel_offsets").as_table()) {
|
||||
if (const auto offset = ParseAccelOffset(value)) {
|
||||
config.wiiAccelOffsets[key] = *offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.widescreen = FindConfigValue<bool>(document, "video", "widescreen");
|
||||
@@ -471,6 +435,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
config.metalFxSpatialUpscaling =
|
||||
FindConfigValue<bool>(document, "video", "metalfx_spatial_upscaling");
|
||||
config.skipUnreadyPipelines = FindConfigValue<bool>(document, "video", "skip_unready_pipelines");
|
||||
config.gxWorker = FindConfigValue<bool>(document, "video", "gx_worker");
|
||||
config.disableCopyFilter = FindConfigValue<bool>(document, "video", "disable_copy_filter");
|
||||
config.showFps = FindConfigValue<bool>(document, "video", "show_fps");
|
||||
config.textureReplacements = FindConfigValue<bool>(document, "video", "texture_replacements");
|
||||
@@ -494,11 +459,17 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
config.attenuateMusicWhenMediaPlays =
|
||||
FindConfigValue<bool>(document, "audio", "attenuate_music_when_media_plays");
|
||||
config.wiiRemotes = FindConfigValue<bool>(document, "controller", "wii_remotes");
|
||||
config.wiiContinuousScan = FindConfigValue<bool>(document, "controller", "wii_continuous_scan");
|
||||
for (const char* key : {"wii_accel_offset_x", "wii_accel_offset_y", "wii_accel_offset_z"}) {
|
||||
config.hasLegacyWiiAccelOffset |= FindConfigValue<double>(document, "controller", key).value_or(0.0) != 0.0;
|
||||
config.wiiAutoPair = FindConfigValue<bool>(document, "controller", "wii_auto_pair");
|
||||
if (const auto bar = FindConfigValue<std::string>(document, "controller", "wii_sensor_bar")) {
|
||||
if (*bar == "above" || *bar == "below") config.wiiSensorBarAbove = *bar == "above";
|
||||
}
|
||||
config.wiiAccelTrace = FindConfigValue<bool>(document, "controller", "wii_accel_trace");
|
||||
if (const auto value = FindConfigInt(document, "controller", "wii_ir_sensitivity"); value && *value >= 1 && *value <= 5) {
|
||||
config.wiiIrSensitivity = *value;
|
||||
}
|
||||
if (const auto value = FindConfigInt(document, "controller", "wii_speaker_volume"); value && *value >= 0 && *value <= 127) {
|
||||
config.wiiSpeakerVolume = *value;
|
||||
}
|
||||
config.wiiRumble = FindConfigValue<bool>(document, "controller", "wii_rumble");
|
||||
config.networkEnabled = FindConfigValue<bool>(document, "network", "enabled");
|
||||
config.discordPresenceEnabled = FindConfigValue<bool>(document, "discord", "enabled");
|
||||
config.discordClientId = FindConfigValue<std::string>(document, "discord", "client_id");
|
||||
@@ -844,56 +815,70 @@ inline bool AudioMixWorkerEnabled(bool fallback = true) {
|
||||
return Get().audioMixWorker.value_or(fallback);
|
||||
}
|
||||
|
||||
inline bool GxWorkerEnabled(bool fallback = true) {
|
||||
return Get().gxWorker.value_or(fallback);
|
||||
}
|
||||
|
||||
// Whether background music should duck automatically for other media playback.
|
||||
inline bool AttenuateMusicWhenMediaPlays(bool fallback = false) {
|
||||
return Get().attenuateMusicWhenMediaPlays.value_or(fallback);
|
||||
}
|
||||
|
||||
// Bluetooth Wii Remotes / Wii U Pro Controllers. Read once before SDL's joystick
|
||||
// subsystem comes up, so a change only takes effect on the next launch.
|
||||
inline bool WiiRemotesEnabled(bool fallback = true) {
|
||||
// Bluetooth Wii Remotes. Read once before SDL's joystick subsystem comes up, so a
|
||||
// change only takes effect on the next launch.
|
||||
inline bool WiiRemotesEnabled(bool fallback = false) {
|
||||
return Get().wiiRemotes.value_or(fallback);
|
||||
}
|
||||
|
||||
// Persists the Bluetooth Wii Remote driver switch.
|
||||
inline bool SetWiiRemotesEnabled(bool value) {
|
||||
Mutable().wiiRemotes = value;
|
||||
return WriteSetting("controller", "wii_remotes", value ? "true" : "false");
|
||||
}
|
||||
|
||||
// Whether to keep rescanning Bluetooth while no Wii controller is connected.
|
||||
inline bool WiiContinuousScanEnabled(bool fallback = false) {
|
||||
return Get().wiiContinuousScan.value_or(fallback);
|
||||
inline bool WiiAutoPairEnabled(bool fallback = true) {
|
||||
return Get().wiiAutoPair.value_or(fallback);
|
||||
}
|
||||
|
||||
// Persists the continuous scanning switch.
|
||||
inline bool SetWiiContinuousScanEnabled(bool value) {
|
||||
Mutable().wiiContinuousScan = value;
|
||||
return WriteSetting("controller", "wii_continuous_scan", value ? "true" : "false");
|
||||
inline bool SetWiiAutoPairEnabled(bool value) {
|
||||
Mutable().wiiAutoPair = value;
|
||||
return WriteSetting("controller", "wii_auto_pair", value ? "true" : "false");
|
||||
}
|
||||
|
||||
// Stored correction of the remote identified by `remoteKey`, if it has one.
|
||||
inline std::optional<std::array<double, 3>> WiiAccelOffsetFor(const std::string& remoteKey) {
|
||||
const auto it = Get().wiiAccelOffsets.find(remoteKey);
|
||||
if (it == Get().wiiAccelOffsets.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->second;
|
||||
// Defaults are the console's.
|
||||
inline bool WiiSensorBarAbove(bool fallback = false) {
|
||||
return Get().wiiSensorBarAbove.value_or(fallback);
|
||||
}
|
||||
|
||||
// Whether to write the per-frame accelerometer trace (off unless asked for).
|
||||
inline bool WiiAccelTraceEnabled(bool fallback = false) {
|
||||
return Get().wiiAccelTrace.value_or(fallback);
|
||||
inline bool SetWiiSensorBarAbove(bool value) {
|
||||
Mutable().wiiSensorBarAbove = value;
|
||||
return WriteSetting("controller", "wii_sensor_bar", value ? "\"above\"" : "\"below\"");
|
||||
}
|
||||
|
||||
// Persists the accelerometer correction of one remote. `remoteKey` must be a bare TOML key.
|
||||
inline bool SetWiiAccelOffsetFor(const std::string& remoteKey, const std::array<double, 3>& offset) {
|
||||
Mutable().wiiAccelOffsets[remoteKey] = offset;
|
||||
// Always float literals, so a whole-number offset does not come back as a TOML integer.
|
||||
std::ostringstream formatted;
|
||||
formatted << std::fixed << std::setprecision(4) << '[' << offset[0] << ", " << offset[1] << ", " << offset[2]
|
||||
<< ']';
|
||||
return WriteSetting("controller.wii_accel_offsets", remoteKey, formatted.str());
|
||||
inline int32_t WiiIrSensitivity(int32_t fallback = 3) {
|
||||
return Get().wiiIrSensitivity.value_or(fallback);
|
||||
}
|
||||
|
||||
inline bool SetWiiIrSensitivity(int32_t value) {
|
||||
Mutable().wiiIrSensitivity = value;
|
||||
return WriteSetting("controller", "wii_ir_sensitivity", std::to_string(value));
|
||||
}
|
||||
|
||||
inline int32_t WiiSpeakerVolume(int32_t fallback = 0x59) {
|
||||
return Get().wiiSpeakerVolume.value_or(fallback);
|
||||
}
|
||||
|
||||
inline bool SetWiiSpeakerVolume(int32_t value) {
|
||||
Mutable().wiiSpeakerVolume = value;
|
||||
return WriteSetting("controller", "wii_speaker_volume", std::to_string(value));
|
||||
}
|
||||
|
||||
inline bool WiiRumbleEnabled(bool fallback = true) {
|
||||
return Get().wiiRumble.value_or(fallback);
|
||||
}
|
||||
|
||||
inline bool SetWiiRumbleEnabled(bool value) {
|
||||
Mutable().wiiRumble = value;
|
||||
return WriteSetting("controller", "wii_rumble", value ? "true" : "false");
|
||||
}
|
||||
|
||||
// Target frame rate for frame interpolation, or 0 to disable it.
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
struct PADStatus;
|
||||
|
||||
// Real Wii Remotes paired over Bluetooth.
|
||||
//
|
||||
// SDL 3 ships a HIDAPI driver for them (Wii Remote alone, with Nunchuk, with a
|
||||
// Classic Controller, and the Wii U Pro Controller) that exposes each as a
|
||||
// regular SDL gamepad; it is off by default on SDL's side and ConfigureSdlHints
|
||||
// turns it on. A remote, alone or with a Nunchuk or Classic Controller, is
|
||||
// handed to the game as a real Wii Remote with that extension: the KPAD/WPAD
|
||||
// HLE builds a KPADStatus (and the WPADCLStatus behind KPADGetUnifiedWpadStatus)
|
||||
// from it every frame (see ReadKpadSample), so the game's own code does wheelies,
|
||||
// tricks, Wii Wheel steering and the Classic Controller layout, and plugging an
|
||||
// extension in or out mid-game switches control scheme like on the console. Only
|
||||
// the Wii U Pro Controller, which has no Wii-era equivalent, goes through
|
||||
// aurora's PAD layer as a GameCube pad.
|
||||
namespace WiiRemoteInput {
|
||||
|
||||
enum class Kind : uint8_t {
|
||||
NotWii,
|
||||
Remote, // Wii Remote with no extension
|
||||
RemoteWithNunchuk,
|
||||
RemoteWithClassic,
|
||||
WiiUPro,
|
||||
};
|
||||
|
||||
// Must run before SDL's joystick subsystem is initialized (aurora does that
|
||||
// inside aurora_initialize); SDL only consults the hint on its first device scan.
|
||||
void ConfigureSdlHints(bool enabled);
|
||||
|
||||
// Classifies a gamepad by the name SDL's Wii driver reports for it.
|
||||
Kind KindForName(const char* gamepadName);
|
||||
// Classification of the SDL gamepad currently assigned to a game port.
|
||||
Kind KindForPort(uint32_t port);
|
||||
const char* KindLabel(Kind kind);
|
||||
|
||||
// One frame of a Wii Remote in the units KPAD uses.
|
||||
struct KpadSample {
|
||||
uint32_t hold = 0; // WPAD button bits (WPAD_BUTTON_*), Nunchuk C/Z included
|
||||
float acc[3] = {}; // remote accelerometer in g, KPAD frame (rest: y = -1)
|
||||
bool hasNunchuk = false;
|
||||
float stick[2] = {}; // Nunchuk stick, -1..1, +y up
|
||||
float nunchukAcc[3] = {};
|
||||
bool hasClassic = false;
|
||||
uint32_t clHold = 0; // WPAD_CL_BUTTON_* bits
|
||||
float clLStick[2] = {}; // Classic sticks, -1..1, +y up
|
||||
float clRStick[2] = {};
|
||||
int16_t clLStickRaw[2] = {}; // as WPADCLStatus reports them: -512..511, centre 0, +y up
|
||||
int16_t clRStickRaw[2] = {};
|
||||
uint8_t clTriggerL = 0; // 0..255; SDL only exposes the digital click
|
||||
uint8_t clTriggerR = 0;
|
||||
};
|
||||
|
||||
// What the game should see on `chan`: the controller SDL has there right now,
|
||||
// or, for a few seconds after a remote vanished, the kind it had. SDL's driver
|
||||
// destroys and re-creates the joystick when an extension is plugged in or out,
|
||||
// and the console never disconnects for that, so the gap is papered over with
|
||||
// neutral input instead of a "communications interrupted" prompt.
|
||||
Kind EffectiveKind(uint32_t chan);
|
||||
// True when the game reads `chan` through KPAD: a Wii Remote alone, with a
|
||||
// Nunchuk or with a Classic Controller (live or within the swap grace period).
|
||||
bool IsRemoteChannel(uint32_t chan);
|
||||
// Reads the current state of the remote on `chan`; false when IsRemoteChannel
|
||||
// is false. During the swap grace period the sample is neutral.
|
||||
bool ReadKpadSample(uint32_t chan, KpadSample& sample);
|
||||
|
||||
// Remote accelerometer for the overlay readout: the SDL sample in g after the
|
||||
// zero-point correction (x right across the face, y out of the button face, z
|
||||
// towards the user) and the KPAD vector built from it. False when the port has
|
||||
// no remote or SDL has not delivered a sample yet.
|
||||
bool ReadAccelDebug(uint32_t chan, float sdlG[3], float kpadAcc[3]);
|
||||
|
||||
// Residual zero-point trim on top of SDL's factory-calibrated decode, stored per
|
||||
// physical remote under [controller.wii_accel_offsets]. Expects the remote lying
|
||||
// still, buttons up; Poll() averages about a second and stores it for that remote.
|
||||
void StartAccelCalibration(uint32_t chan);
|
||||
// Forgets the stored correction of the remote on `chan`.
|
||||
void ClearAccelCalibration(uint32_t chan);
|
||||
bool IsAccelCalibrating(uint32_t chan);
|
||||
// 0..1 while a calibration is collecting samples.
|
||||
float AccelCalibrationProgress();
|
||||
// Correction in effect for the remote on `chan` (zeros when uncalibrated), or
|
||||
// nullopt when the port has no remote.
|
||||
std::optional<std::array<double, 3>> ReadAccelOffset(uint32_t chan);
|
||||
// Outcome of the last calibration attempt on `chan` for the overlay, or nullptr.
|
||||
const char* AccelCalibrationMessage(uint32_t chan);
|
||||
|
||||
// Reports "no controller" on the GameCube side for every port served through
|
||||
// KPAD, so the game never sees the same remote twice. Runs after aurora's PADRead.
|
||||
void HideRemotesFromPad(PADStatus* statuses, uint32_t count);
|
||||
|
||||
// Dolphin-style continuous scanning. SDL's HIDAPI Wii driver drops a remote on
|
||||
// a failed Bluetooth read or an extension change and never re-adds it on its
|
||||
// own. Poll() runs once per PADRead and, while no Wii controller is present,
|
||||
// periodically forces SDL to re-enumerate HIDAPI by toggling the driver hint.
|
||||
void Poll();
|
||||
// Forces one re-enumeration right now (settings overlay "Rescan now").
|
||||
void RescanNow();
|
||||
// True while Poll() is looking for a remote (no Wii controller connected).
|
||||
bool IsScanning();
|
||||
// True where looking means periodic rescans; elsewhere Poll() waits for hotplug.
|
||||
bool PeriodicRescanEnabled();
|
||||
// Rescans issued since a Wii controller was last seen.
|
||||
uint32_t ScanCount();
|
||||
|
||||
} // namespace WiiRemoteInput
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
// The Wii's extension controller encryption: the game writes a random key to
|
||||
// the extension, which then XORs and offsets every byte it reports.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace Wiimote::ExtCrypto {
|
||||
|
||||
struct Tables {
|
||||
std::array<uint8_t, 8> ft{};
|
||||
std::array<uint8_t, 8> sb{};
|
||||
void Encrypt(uint8_t* data, uint32_t address, size_t length) const;
|
||||
void Decrypt(uint8_t* data, uint32_t address, size_t length) const;
|
||||
};
|
||||
|
||||
// Tables for the key the game wrote to registers 0x40..0x4F, or nullopt when it
|
||||
// does not come from the first-party key schedule.
|
||||
std::optional<Tables> FromKey(const std::array<uint8_t, 16>& keyData);
|
||||
|
||||
} // namespace Wiimote::ExtCrypto
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
// Follows the game's conversation with a remote's extension (type, calibration,
|
||||
// encryption key) so its data can be put at rest while the overlay owns input.
|
||||
|
||||
#include "wiimote_ext_crypto.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Wiimote {
|
||||
|
||||
class ExtensionState {
|
||||
public:
|
||||
void ObserveOutput(const uint8_t* report, size_t size);
|
||||
void ObserveInput(const uint8_t* report, size_t size);
|
||||
// Centres the sticks and releases the buttons of the extension data in a
|
||||
// data report, leaving its motion data alone.
|
||||
void Neutralize(std::vector<uint8_t>& report) const;
|
||||
|
||||
private:
|
||||
enum class Type : uint8_t { Unknown, Nunchuk, Classic };
|
||||
|
||||
void Reset();
|
||||
uint8_t Calibration(size_t index, uint8_t fallback) const;
|
||||
|
||||
Type m_type = Type::Unknown;
|
||||
uint8_t m_format = 0;
|
||||
bool m_encrypted = false;
|
||||
std::array<uint8_t, 16> m_key{};
|
||||
std::optional<ExtCrypto::Tables> m_tables;
|
||||
std::array<uint8_t, 16> m_calibration{};
|
||||
bool m_haveCalibration = false;
|
||||
uint32_t m_readAddress = 0;
|
||||
};
|
||||
|
||||
} // namespace Wiimote
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
// Physical Wii Remotes on the PC. Each one is attached to one of the four
|
||||
// virtual remotes of the emulated Bluetooth controller and its HID reports are
|
||||
// passed through unchanged, so the game's own WPAD/KPAD drive it (IR, speaker,
|
||||
// rumble, extensions, battery).
|
||||
|
||||
#include "bluetooth/bt_controller.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace Wiimote {
|
||||
|
||||
struct SlotInfo {
|
||||
bool attached = false;
|
||||
// The game disconnected it and the platform could not power it off; any
|
||||
// button wakes it up again.
|
||||
bool asleep = false;
|
||||
Bluetooth::LinkState link = Bluetooth::LinkState::Down;
|
||||
std::string model; // "Wii Remote" / "Wii Remote Plus"
|
||||
std::string extension; // empty when none
|
||||
int battery = -1; // 0..4 like the game's battery bars, -1 unknown
|
||||
};
|
||||
|
||||
struct PairingInfo {
|
||||
bool supported = false; // this platform can discover and pair remotes itself
|
||||
bool searching = false;
|
||||
std::string status; // last pairing outcome, for the overlay
|
||||
};
|
||||
|
||||
// Must run before SDL's joystick subsystem starts (aurora_initialize).
|
||||
void ConfigureSdlHints(bool enabled);
|
||||
// Starts device scanning when Wii Remotes are enabled.
|
||||
void Initialize();
|
||||
// Powers the remotes off, like a console shutting down, and stops the threads.
|
||||
void Shutdown();
|
||||
|
||||
Bluetooth::RemoteHost& Host();
|
||||
|
||||
SlotInfo Slot(size_t slot);
|
||||
PairingInfo Pairing();
|
||||
// The console's SYNC button: look for remotes in pairing mode for a while.
|
||||
void StartPairing();
|
||||
// Ends the game's connection to the remote on `slot` and powers it off.
|
||||
void Disconnect(size_t slot);
|
||||
// Setup problems worth showing (kernel driver, missing permissions), else empty.
|
||||
std::string PlatformNotice();
|
||||
// Connected remotes waiting for a free player slot.
|
||||
size_t WaitingRemotes();
|
||||
|
||||
} // namespace Wiimote
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
// Host Bluetooth services the Wii Remote manager needs from the OS: finding and
|
||||
// pairing remotes in pairing mode, naming a HID device, and dropping a remote's
|
||||
// connection (which powers it off, as a console does).
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
struct SDL_hid_device_info;
|
||||
|
||||
namespace Wiimote::Platform {
|
||||
|
||||
struct Capabilities {
|
||||
bool pairing = false;
|
||||
bool powerOff = false;
|
||||
};
|
||||
|
||||
Capabilities Query();
|
||||
|
||||
// One search for remotes in pairing mode (red SYNC or 1+2); pairs and connects
|
||||
// each one found. Blocks for a few seconds. Returns the number connected.
|
||||
int DiscoverAndPair(const std::atomic<bool>& cancel, std::string& status);
|
||||
|
||||
// Disconnects the remote; it switches itself off. False when not possible.
|
||||
bool PowerOff(const std::string& address);
|
||||
|
||||
// Bluetooth address of a HID device as 12 lowercase hex digits, or empty.
|
||||
std::string AddressFor(const SDL_hid_device_info& info);
|
||||
|
||||
// Bluetooth name ("Nintendo RVL-CNT-01-TR", ...) or the HID product string.
|
||||
std::string NameFor(const std::string& address, const SDL_hid_device_info& info);
|
||||
|
||||
// False only when the OS knows the remote is not connected right now.
|
||||
bool MaybeConnected(const std::string& address);
|
||||
|
||||
// False when the OS driver would fight over the device (Linux hid-wiimote).
|
||||
bool Usable(const SDL_hid_device_info& info);
|
||||
|
||||
// Setup problems worth showing in the overlay, else empty.
|
||||
std::string Notice();
|
||||
|
||||
} // namespace Wiimote::Platform
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -21,14 +21,16 @@ 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.
|
||||
constexpr int kMaxBlocksPerTick = 4;
|
||||
// Backlog (in blocks) tolerated before a catch-up block waits on an in-flight mix.
|
||||
constexpr double kMaxDeferredBlocks = 2.0;
|
||||
|
||||
struct AIDmaState {
|
||||
std::mutex mutex;
|
||||
@@ -407,11 +409,15 @@ void Audio_HLE_Tick(CpuContext* ctx, uint32_t deltaMicros)
|
||||
|
||||
int blocksCompleted = 0;
|
||||
while (true) {
|
||||
// A block due while the previous mix is still running would block on it; leave it for the
|
||||
// next service point unless the backlog is getting long.
|
||||
const bool deferWhileMixing = AxDspHle::MixWorkerBusy();
|
||||
// Claim the block in one critical section; sample-then-consume separately gains
|
||||
// nothing since the callback below (the only reentrancy point) runs mutex-released.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_ai.mutex);
|
||||
if (g_ai.accumulatorSeconds < blockDuration) {
|
||||
if (g_ai.accumulatorSeconds < blockDuration ||
|
||||
(deferWhileMixing && g_ai.accumulatorSeconds < blockDuration * kMaxDeferredBlocks)) {
|
||||
break;
|
||||
}
|
||||
g_ai.accumulatorSeconds -= blockDuration;
|
||||
|
||||
@@ -25,6 +25,9 @@ void ServiceDeferredCallbacks();
|
||||
// Cheap when the mix already finished (the normal case).
|
||||
void JoinMixWorker();
|
||||
|
||||
// True while a deferred mix is queued or still running.
|
||||
bool MixWorkerBusy();
|
||||
|
||||
// Joins and tears down the mix worker thread. Call before the guest memory map
|
||||
// is destroyed or re-created.
|
||||
void ShutdownMixWorker();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -112,6 +112,14 @@ public:
|
||||
ConfigureFromTaskLocked(taskPtr);
|
||||
}
|
||||
|
||||
bool MixBusy() {
|
||||
if (!m_mixThread.joinable()) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(m_mixMutex);
|
||||
return m_mixPending || m_mixBusy;
|
||||
}
|
||||
|
||||
void JoinMix() {
|
||||
if (m_mixThread.joinable()) {
|
||||
std::unique_lock<std::mutex> lock(m_mixMutex);
|
||||
@@ -1467,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() {
|
||||
@@ -1560,6 +1564,10 @@ void JoinMixWorker() {
|
||||
Instance().JoinMix();
|
||||
}
|
||||
|
||||
bool MixWorkerBusy() {
|
||||
return Instance().MixBusy();
|
||||
}
|
||||
|
||||
void ShutdownMixWorker() {
|
||||
Instance().ShutdownMixWorker();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,951 @@
|
||||
#include "bluetooth/bt_controller.h"
|
||||
|
||||
#include "bluetooth/bt_bytes.h"
|
||||
#include "bluetooth/bt_remote_link.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
namespace Bluetooth {
|
||||
namespace HciDetail {
|
||||
|
||||
|
||||
constexpr uint16_t kOgfLinkControl = 0x01;
|
||||
constexpr uint16_t kOgfLinkPolicy = 0x02;
|
||||
constexpr uint16_t kOgfControllerBaseband = 0x03;
|
||||
constexpr uint16_t kOgfInformational = 0x04;
|
||||
constexpr uint16_t kOgfStatus = 0x05;
|
||||
constexpr uint16_t kOgfVendor = 0x3F;
|
||||
|
||||
constexpr uint8_t kEventInquiryComplete = 0x01;
|
||||
constexpr uint8_t kEventInquiryResult = 0x02;
|
||||
constexpr uint8_t kEventConnectionComplete = 0x03;
|
||||
constexpr uint8_t kEventDisconnectionComplete = 0x05;
|
||||
constexpr uint8_t kEventAuthenticationComplete = 0x06;
|
||||
constexpr uint8_t kEventRemoteNameComplete = 0x07;
|
||||
constexpr uint8_t kEventEncryptionChange = 0x08;
|
||||
constexpr uint8_t kEventRemoteFeatures = 0x0B;
|
||||
constexpr uint8_t kEventRemoteVersion = 0x0C;
|
||||
constexpr uint8_t kEventCommandComplete = 0x0E;
|
||||
constexpr uint8_t kEventCommandStatus = 0x0F;
|
||||
constexpr uint8_t kEventRoleChange = 0x12;
|
||||
constexpr uint8_t kEventNumberOfCompletedPackets = 0x13;
|
||||
constexpr uint8_t kEventModeChange = 0x14;
|
||||
constexpr uint8_t kEventReturnLinkKeys = 0x15;
|
||||
constexpr uint8_t kEventPinCodeRequest = 0x16;
|
||||
constexpr uint8_t kEventLinkKeyRequest = 0x17;
|
||||
constexpr uint8_t kEventLinkKeyNotification = 0x18;
|
||||
constexpr uint8_t kEventClockOffset = 0x1C;
|
||||
constexpr uint8_t kEventPacketTypeChanged = 0x1D;
|
||||
constexpr uint8_t kEventInquiryResultWithRssi = 0x22;
|
||||
constexpr uint8_t kEventExtendedInquiryResult = 0x2F;
|
||||
|
||||
constexpr uint8_t kStatusSuccess = 0x00;
|
||||
constexpr uint8_t kStatusUnknownCommand = 0x01;
|
||||
constexpr uint8_t kStatusUnknownConnection = 0x02;
|
||||
constexpr uint8_t kStatusPageTimeout = 0x04;
|
||||
constexpr uint8_t kStatusAuthenticationFailure = 0x05;
|
||||
constexpr uint8_t kStatusKeyMissing = 0x06;
|
||||
constexpr uint8_t kStatusUnsupportedFeature = 0x11;
|
||||
constexpr uint8_t kReasonLocalHostTerminated = 0x16;
|
||||
|
||||
// What a Wii's BCM2045 reports (the version makes WUD apply its usual patch).
|
||||
constexpr BdAddr kLocalAddress = {0x11, 0x02, 0x19, 0x79, 0x00, 0xFF};
|
||||
constexpr std::array<uint8_t, 8> kLocalFeatures = {0xFF, 0xFF, 0x8D, 0xFE, 0x9B, 0xF9, 0x00, 0x80};
|
||||
// Never advertise more than 10 ACL buffers: the stack keeps the count in a u8.
|
||||
constexpr uint16_t kAclBufferSize = 339;
|
||||
constexpr uint16_t kAclBufferCount = 10;
|
||||
constexpr uint8_t kScoBufferSize = 64;
|
||||
|
||||
// A real RVL-CNT-01.
|
||||
constexpr std::array<uint8_t, 8> kRemoteFeatures = {0xBC, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00};
|
||||
constexpr uint8_t kRemoteLmpVersion = 0x02;
|
||||
constexpr uint16_t kRemoteManufacturer = 0x000F;
|
||||
constexpr uint16_t kRemoteLmpSubversion = 0x0229;
|
||||
constexpr uint16_t kRemoteClockOffset = 0x3818;
|
||||
constexpr uint8_t kInquiryRssi = 0xD8; // -40 dBm
|
||||
constexpr uint64_t kInquiryUnitUs = 1'280'000;
|
||||
constexpr uint16_t kMaxStoredKeys = 0x00FF;
|
||||
constexpr uint8_t kKeysPerEvent = 11;
|
||||
|
||||
void PutAddr(std::vector<uint8_t>& out, const BdAddr& addr) {
|
||||
out.insert(out.end(), addr.begin(), addr.end());
|
||||
}
|
||||
|
||||
BdAddr ReadAddr(const uint8_t* p) {
|
||||
BdAddr addr{};
|
||||
std::memcpy(addr.data(), p, addr.size());
|
||||
return addr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Event(uint8_t code, const std::vector<uint8_t>& params) {
|
||||
std::vector<uint8_t> event;
|
||||
event.reserve(2 + params.size());
|
||||
event.push_back(code);
|
||||
event.push_back(static_cast<uint8_t>(params.size()));
|
||||
event.insert(event.end(), params.begin(), params.end());
|
||||
return event;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 16> SlotLinkKey(size_t slot) {
|
||||
std::array<uint8_t, 16> key{};
|
||||
for (size_t i = 0; i < key.size(); ++i) {
|
||||
key[i] = static_cast<uint8_t>(0xA0 + slot + i * 0x11);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace HciDetail
|
||||
|
||||
using namespace HciDetail;
|
||||
|
||||
Controller::Controller(RemoteHost& host) : m_host(host) {
|
||||
for (size_t slot = 0; slot < kRemoteSlots; ++slot) {
|
||||
m_links[slot] = std::make_unique<RemoteLink>(*this, slot);
|
||||
m_storedKeys.push_back({RemoteAddress(slot), SlotLinkKey(slot)});
|
||||
}
|
||||
ResetState(true);
|
||||
}
|
||||
|
||||
Controller::~Controller() = default;
|
||||
|
||||
BdAddr Controller::LocalAddress() {
|
||||
return kLocalAddress;
|
||||
}
|
||||
|
||||
BdAddr Controller::RemoteAddress(size_t slot) {
|
||||
return {0x11, 0x02, 0x19, 0x79, 0x00, static_cast<uint8_t>(slot)};
|
||||
}
|
||||
|
||||
uint16_t Controller::ConnectionHandle(size_t slot) {
|
||||
return static_cast<uint16_t>(0x0100 + slot);
|
||||
}
|
||||
|
||||
void Controller::Open(uint64_t nowUs) {
|
||||
(void)nowUs;
|
||||
ResetState(true);
|
||||
m_open = true;
|
||||
}
|
||||
|
||||
void Controller::Close() {
|
||||
ResetState(true);
|
||||
m_open = false;
|
||||
}
|
||||
|
||||
void Controller::ResetState(bool keepStoredKeys) {
|
||||
for (size_t slot = 0; slot < kRemoteSlots; ++slot) {
|
||||
RemoteLink& link = *m_links[slot];
|
||||
// A reset drops the radio links: the remotes lose their connection.
|
||||
if (link.State() == LinkState::Ready) {
|
||||
m_host.OnGuestDisconnect(slot);
|
||||
}
|
||||
link.Reset();
|
||||
}
|
||||
if (!keepStoredKeys) {
|
||||
m_storedKeys.clear();
|
||||
}
|
||||
if (m_hostInquiryNotified) {
|
||||
m_host.OnGuestInquiry(false);
|
||||
m_hostInquiryNotified = false;
|
||||
}
|
||||
m_inquiry = {};
|
||||
m_scanEnable = 0;
|
||||
m_pageTimeoutSlots = 0x2000;
|
||||
m_inquiryMode = 0;
|
||||
m_classOfDevice = {};
|
||||
m_localName = {};
|
||||
m_completedPackets = {};
|
||||
for (Reassembly& r : m_reassembly) r = {};
|
||||
m_events.clear();
|
||||
m_acl.clear();
|
||||
m_pending.clear();
|
||||
m_authPending = {};
|
||||
}
|
||||
|
||||
LinkState Controller::SlotLinkState(size_t slot) const {
|
||||
return slot < kRemoteSlots ? m_links[slot]->State() : LinkState::Down;
|
||||
}
|
||||
|
||||
int Controller::SlotForAddress(const BdAddr& addr) const {
|
||||
for (size_t slot = 0; slot < kRemoteSlots; ++slot) {
|
||||
if (RemoteAddress(slot) == addr) return static_cast<int>(slot);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Controller::SlotForHandle(uint16_t handle) const {
|
||||
for (size_t slot = 0; slot < kRemoteSlots; ++slot) {
|
||||
if (ConnectionHandle(slot) == (handle & 0x0FFF) &&
|
||||
m_links[slot]->BasebandState() == RemoteLink::Baseband::Connected) {
|
||||
return static_cast<int>(slot);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const Controller::StoredKey* Controller::FindStoredKey(const BdAddr& addr) const {
|
||||
for (const StoredKey& key : m_storedKeys) {
|
||||
if (key.addr == addr) return &key;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Controller::QueueEvent(std::vector<uint8_t> event) {
|
||||
m_events.push_back(std::move(event));
|
||||
}
|
||||
|
||||
void Controller::QueueAcl(uint16_t handle, const std::vector<uint8_t>& l2capFrame) {
|
||||
std::vector<uint8_t> packet;
|
||||
packet.reserve(4 + l2capFrame.size());
|
||||
// Packet boundary 0x2: first (and only) fragment, automatically flushable.
|
||||
PutLe16(packet, static_cast<uint16_t>((handle & 0x0FFF) | 0x2000));
|
||||
PutLe16(packet, static_cast<uint16_t>(l2capFrame.size()));
|
||||
packet.insert(packet.end(), l2capFrame.begin(), l2capFrame.end());
|
||||
m_acl.push_back(std::move(packet));
|
||||
}
|
||||
|
||||
bool Controller::PopEvent(std::vector<uint8_t>& event) {
|
||||
if (m_events.empty()) return false;
|
||||
event = std::move(m_events.front());
|
||||
m_events.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Controller::PopAcl(std::vector<uint8_t>& packet) {
|
||||
if (!m_events.empty() || m_acl.empty()) return false;
|
||||
packet = std::move(m_acl.front());
|
||||
m_acl.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Controller::CommandComplete(uint16_t opcode, const std::vector<uint8_t>& params) {
|
||||
std::vector<uint8_t> body = {0x01};
|
||||
PutLe16(body, opcode);
|
||||
body.insert(body.end(), params.begin(), params.end());
|
||||
QueueEvent(Event(kEventCommandComplete, body));
|
||||
}
|
||||
|
||||
void Controller::CommandStatus(uint16_t opcode, uint8_t status) {
|
||||
std::vector<uint8_t> body = {status, 0x01};
|
||||
PutLe16(body, opcode);
|
||||
QueueEvent(Event(kEventCommandStatus, body));
|
||||
}
|
||||
|
||||
void Controller::UnknownCommand(uint16_t opcode) {
|
||||
std::cerr << "[bluetooth] unsupported HCI command 0x" << std::hex << opcode << std::dec << std::endl;
|
||||
CommandStatus(opcode, kStatusUnknownCommand);
|
||||
}
|
||||
|
||||
void Controller::ExecuteCommand(const uint8_t* packet, size_t size, uint64_t nowUs) {
|
||||
if (size < 3) return;
|
||||
const uint16_t opcode = Le16(packet);
|
||||
const size_t length = packet[2];
|
||||
if (3 + length > size) {
|
||||
std::cerr << "[bluetooth] truncated HCI command 0x" << std::hex << opcode << std::dec << std::endl;
|
||||
return;
|
||||
}
|
||||
const uint8_t* p = packet + 3;
|
||||
switch (opcode >> 10) {
|
||||
case kOgfLinkControl: HandleLinkControl(opcode, p, length, nowUs); break;
|
||||
case kOgfLinkPolicy: HandleLinkPolicy(opcode, p, length); break;
|
||||
case kOgfControllerBaseband: HandleControllerBaseband(opcode, p, length, nowUs); break;
|
||||
case kOgfInformational: HandleInformational(opcode, p, length); break;
|
||||
case kOgfStatus: HandleStatusParameters(opcode, p, length); break;
|
||||
case kOgfVendor: HandleVendor(opcode, p, length); break;
|
||||
default: UnknownCommand(opcode); break;
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::HandleLinkControl(uint16_t opcode, const uint8_t* p, size_t n, uint64_t nowUs) {
|
||||
const auto handleSlot = [&](size_t offset) { return n >= offset + 2 ? SlotForHandle(Le16(p + offset)) : -1; };
|
||||
switch (opcode & 0x3FF) {
|
||||
case 0x0001: { // Inquiry
|
||||
if (n < 5) break;
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
m_inquiry = {};
|
||||
m_inquiry.active = true;
|
||||
m_inquiry.endUs = nowUs + std::max<uint8_t>(p[3], 1) * kInquiryUnitUs;
|
||||
m_inquiry.maxResponses = p[4];
|
||||
if (!m_hostInquiryNotified) {
|
||||
m_host.OnGuestInquiry(true);
|
||||
m_hostInquiryNotified = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x0002: // Inquiry Cancel
|
||||
m_inquiry = {};
|
||||
if (m_hostInquiryNotified) {
|
||||
m_host.OnGuestInquiry(false);
|
||||
m_hostInquiryNotified = false;
|
||||
}
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x0003: // Periodic Inquiry Mode
|
||||
case 0x0004: // Exit Periodic Inquiry Mode
|
||||
CommandComplete(opcode, {kStatusUnsupportedFeature});
|
||||
return;
|
||||
case 0x0005: { // Create Connection
|
||||
if (n < 6) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
const int slot = SlotForAddress(addr);
|
||||
if (slot >= 0 && m_links[slot]->OnPaged(nowUs)) {
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
PutAddr(body, addr);
|
||||
body.insert(body.end(), {0x01, 0x00});
|
||||
QueueEvent(Event(kEventConnectionComplete, body));
|
||||
} else {
|
||||
std::vector<uint8_t> body = {kStatusPageTimeout, 0x00, 0x00};
|
||||
PutAddr(body, addr);
|
||||
body.insert(body.end(), {0x01, 0x00});
|
||||
m_pending.push_back({nowUs + PageTimeoutUs(), Event(kEventConnectionComplete, body)});
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x0006: { // Disconnect
|
||||
const int slot = handleSlot(0);
|
||||
if (slot < 0 || n < 3) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
body.push_back(kReasonLocalHostTerminated);
|
||||
QueueEvent(Event(kEventDisconnectionComplete, body));
|
||||
m_completedPackets[slot] = 0;
|
||||
m_reassembly[slot] = {};
|
||||
m_authPending[slot] = false;
|
||||
m_links[slot]->OnDisconnected(true, nowUs);
|
||||
return;
|
||||
}
|
||||
case 0x0008: { // Create Connection Cancel
|
||||
if (n < 6) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
auto it = std::find_if(m_pending.begin(), m_pending.end(), [&](const PendingEvent& e) {
|
||||
return e.event[0] == kEventConnectionComplete && ReadAddr(&e.event[5]) == addr;
|
||||
});
|
||||
std::vector<uint8_t> body = {static_cast<uint8_t>(it == m_pending.end() ? kStatusUnknownConnection
|
||||
: kStatusSuccess)};
|
||||
PutAddr(body, addr);
|
||||
CommandComplete(opcode, body);
|
||||
if (it != m_pending.end()) {
|
||||
std::vector<uint8_t> complete = it->event;
|
||||
complete[2] = kStatusUnknownConnection;
|
||||
m_pending.erase(it);
|
||||
QueueEvent(std::move(complete));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x0009: { // Accept Connection Request
|
||||
if (n < 7) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
const uint8_t role = p[6];
|
||||
const int slot = SlotForAddress(addr);
|
||||
if (slot < 0 || !m_links[slot]->IsRequesting()) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
if (role == 0x00) {
|
||||
std::vector<uint8_t> change = {kStatusSuccess};
|
||||
PutAddr(change, addr);
|
||||
change.push_back(0x00);
|
||||
QueueEvent(Event(kEventRoleChange, change));
|
||||
}
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
PutAddr(body, addr);
|
||||
body.insert(body.end(), {0x01, 0x00});
|
||||
QueueEvent(Event(kEventConnectionComplete, body));
|
||||
m_completedPackets[slot] = 0;
|
||||
m_reassembly[slot] = {};
|
||||
m_links[slot]->OnAccepted(role);
|
||||
return;
|
||||
}
|
||||
case 0x000A: { // Reject Connection Request
|
||||
if (n < 7) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
const int slot = SlotForAddress(addr);
|
||||
if (slot < 0 || !m_links[slot]->IsRequesting()) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {p[6], 0x00, 0x00};
|
||||
PutAddr(body, addr);
|
||||
body.insert(body.end(), {0x01, 0x00});
|
||||
QueueEvent(Event(kEventConnectionComplete, body));
|
||||
m_links[slot]->OnRejected(p[6], nowUs);
|
||||
return;
|
||||
}
|
||||
case 0x000B: { // Link Key Request Reply
|
||||
if (n < 22) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutAddr(body, addr);
|
||||
CommandComplete(opcode, body);
|
||||
const int slot = SlotForAddress(addr);
|
||||
if (slot >= 0 && m_authPending[slot]) {
|
||||
m_authPending[slot] = false;
|
||||
const bool match = std::equal(p + 6, p + 22, SlotLinkKey(slot).begin());
|
||||
std::vector<uint8_t> auth = {static_cast<uint8_t>(match ? kStatusSuccess : kStatusKeyMissing)};
|
||||
PutLe16(auth, ConnectionHandle(slot));
|
||||
QueueEvent(Event(kEventAuthenticationComplete, auth));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x000C: { // Link Key Request Negative Reply
|
||||
if (n < 6) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutAddr(body, addr);
|
||||
CommandComplete(opcode, body);
|
||||
const int slot = SlotForAddress(addr);
|
||||
if (slot >= 0 && m_authPending[slot]) {
|
||||
std::vector<uint8_t> request;
|
||||
PutAddr(request, addr);
|
||||
QueueEvent(Event(kEventPinCodeRequest, request));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x000D: { // PIN Code Request Reply
|
||||
if (n < 23) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutAddr(body, addr);
|
||||
CommandComplete(opcode, body);
|
||||
const int slot = SlotForAddress(addr);
|
||||
if (slot < 0 || !m_authPending[slot]) return;
|
||||
m_authPending[slot] = false;
|
||||
// A remote takes the host's address (SYNC button) or its own (1+2) as PIN.
|
||||
const uint8_t pinLength = p[6];
|
||||
const uint8_t* pin = p + 7;
|
||||
const BdAddr local = LocalAddress();
|
||||
const bool valid = pinLength == 6 && (std::equal(pin, pin + 6, local.begin()) ||
|
||||
std::equal(pin, pin + 6, addr.begin()));
|
||||
if (valid) {
|
||||
std::vector<uint8_t> notification;
|
||||
PutAddr(notification, addr);
|
||||
const auto key = SlotLinkKey(slot);
|
||||
notification.insert(notification.end(), key.begin(), key.end());
|
||||
notification.push_back(0x00); // combination key
|
||||
QueueEvent(Event(kEventLinkKeyNotification, notification));
|
||||
}
|
||||
std::vector<uint8_t> auth = {static_cast<uint8_t>(valid ? kStatusSuccess : kStatusAuthenticationFailure)};
|
||||
PutLe16(auth, ConnectionHandle(slot));
|
||||
QueueEvent(Event(kEventAuthenticationComplete, auth));
|
||||
return;
|
||||
}
|
||||
case 0x000E: { // PIN Code Request Negative Reply
|
||||
if (n < 6) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutAddr(body, addr);
|
||||
CommandComplete(opcode, body);
|
||||
const int slot = SlotForAddress(addr);
|
||||
if (slot >= 0 && m_authPending[slot]) {
|
||||
m_authPending[slot] = false;
|
||||
std::vector<uint8_t> auth = {kStatusKeyMissing};
|
||||
PutLe16(auth, ConnectionHandle(slot));
|
||||
QueueEvent(Event(kEventAuthenticationComplete, auth));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x000F: { // Change Connection Packet Type
|
||||
const int slot = handleSlot(0);
|
||||
if (slot < 0 || n < 4) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
PutLe16(body, Le16(p + 2));
|
||||
QueueEvent(Event(kEventPacketTypeChanged, body));
|
||||
return;
|
||||
}
|
||||
case 0x0011: { // Authentication Requested
|
||||
const int slot = handleSlot(0);
|
||||
if (slot < 0) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
m_authPending[slot] = true;
|
||||
std::vector<uint8_t> request;
|
||||
PutAddr(request, RemoteAddress(slot));
|
||||
QueueEvent(Event(kEventLinkKeyRequest, request));
|
||||
return;
|
||||
}
|
||||
case 0x0013: { // Set Connection Encryption
|
||||
const int slot = handleSlot(0);
|
||||
if (slot < 0 || n < 3) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
body.push_back(p[2]);
|
||||
QueueEvent(Event(kEventEncryptionChange, body));
|
||||
return;
|
||||
}
|
||||
case 0x0019: { // Remote Name Request
|
||||
if (n < 6) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
const int slot = SlotForAddress(addr);
|
||||
const bool reachable = slot >= 0 && (m_links[slot]->BasebandState() == RemoteLink::Baseband::Connected ||
|
||||
m_links[slot]->Discoverable());
|
||||
std::vector<uint8_t> body = {static_cast<uint8_t>(reachable ? kStatusSuccess : kStatusPageTimeout)};
|
||||
PutAddr(body, addr);
|
||||
std::array<char, 248> name{};
|
||||
if (reachable) {
|
||||
std::strncpy(name.data(), m_host.DeviceName(slot), name.size() - 1);
|
||||
}
|
||||
body.insert(body.end(), name.begin(), name.end());
|
||||
if (reachable) {
|
||||
QueueEvent(Event(kEventRemoteNameComplete, body));
|
||||
} else {
|
||||
m_pending.push_back({nowUs + PageTimeoutUs(), Event(kEventRemoteNameComplete, body)});
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x001A: { // Remote Name Request Cancel
|
||||
if (n < 6) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
auto it = std::find_if(m_pending.begin(), m_pending.end(), [&](const PendingEvent& e) {
|
||||
return e.event[0] == kEventRemoteNameComplete && ReadAddr(&e.event[3]) == addr;
|
||||
});
|
||||
std::vector<uint8_t> body = {static_cast<uint8_t>(it == m_pending.end() ? kStatusUnknownConnection
|
||||
: kStatusSuccess)};
|
||||
PutAddr(body, addr);
|
||||
CommandComplete(opcode, body);
|
||||
if (it != m_pending.end()) {
|
||||
std::vector<uint8_t> complete = it->event;
|
||||
complete[2] = kStatusUnknownConnection;
|
||||
m_pending.erase(it);
|
||||
QueueEvent(std::move(complete));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x001B: { // Read Remote Supported Features
|
||||
const int slot = handleSlot(0);
|
||||
if (slot < 0) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
body.insert(body.end(), kRemoteFeatures.begin(), kRemoteFeatures.end());
|
||||
QueueEvent(Event(kEventRemoteFeatures, body));
|
||||
return;
|
||||
}
|
||||
case 0x001D: { // Read Remote Version Information
|
||||
const int slot = handleSlot(0);
|
||||
if (slot < 0) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
body.push_back(kRemoteLmpVersion);
|
||||
PutLe16(body, kRemoteManufacturer);
|
||||
PutLe16(body, kRemoteLmpSubversion);
|
||||
QueueEvent(Event(kEventRemoteVersion, body));
|
||||
return;
|
||||
}
|
||||
case 0x001F: { // Read Clock Offset
|
||||
const int slot = handleSlot(0);
|
||||
if (slot < 0) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
PutLe16(body, kRemoteClockOffset);
|
||||
QueueEvent(Event(kEventClockOffset, body));
|
||||
return;
|
||||
}
|
||||
case 0x0007: // Add SCO Connection
|
||||
case 0x0028: // Setup Synchronous Connection
|
||||
case 0x0029: // Accept Synchronous Connection Request
|
||||
case 0x002A: // Reject Synchronous Connection Request
|
||||
CommandStatus(opcode, kStatusUnsupportedFeature);
|
||||
return;
|
||||
default:
|
||||
UnknownCommand(opcode);
|
||||
return;
|
||||
}
|
||||
std::cerr << "[bluetooth] short HCI command 0x" << std::hex << opcode << std::dec << std::endl;
|
||||
CommandStatus(opcode, kStatusUnknownCommand);
|
||||
}
|
||||
|
||||
void Controller::HandleLinkPolicy(uint16_t opcode, const uint8_t* p, size_t n) {
|
||||
const int slot = n >= 2 ? SlotForHandle(Le16(p)) : -1;
|
||||
const auto modeChange = [&](uint8_t mode, uint16_t interval) {
|
||||
if (slot < 0) {
|
||||
CommandStatus(opcode, kStatusUnknownConnection);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, ConnectionHandle(static_cast<size_t>(slot)));
|
||||
body.push_back(mode);
|
||||
PutLe16(body, interval);
|
||||
QueueEvent(Event(kEventModeChange, body));
|
||||
};
|
||||
const auto handleReply = [&](std::vector<uint8_t> extra) {
|
||||
std::vector<uint8_t> body = {static_cast<uint8_t>(slot < 0 ? kStatusUnknownConnection : kStatusSuccess)};
|
||||
PutLe16(body, n >= 2 ? Le16(p) : 0);
|
||||
body.insert(body.end(), extra.begin(), extra.end());
|
||||
CommandComplete(opcode, body);
|
||||
};
|
||||
switch (opcode & 0x3FF) {
|
||||
case 0x0001: modeChange(0x01, n >= 4 ? Le16(p + 2) : 0); return; // Hold Mode
|
||||
case 0x0003: modeChange(0x02, n >= 4 ? Le16(p + 2) : 0); return; // Sniff Mode
|
||||
case 0x0004: modeChange(0x00, 0); return; // Exit Sniff Mode
|
||||
case 0x0005: modeChange(0x03, n >= 4 ? Le16(p + 2) : 0); return; // Park State
|
||||
case 0x0006: modeChange(0x00, 0); return; // Exit Park State
|
||||
case 0x0009: handleReply({0x00}); return; // Role Discovery
|
||||
case 0x000B: { // Switch Role
|
||||
if (n < 7) break;
|
||||
CommandStatus(opcode, kStatusSuccess);
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
body.insert(body.end(), p, p + 7);
|
||||
QueueEvent(Event(kEventRoleChange, body));
|
||||
return;
|
||||
}
|
||||
case 0x000C: handleReply({0x05, 0x00}); return; // Read Link Policy Settings
|
||||
case 0x000D: handleReply({}); return; // Write Link Policy Settings
|
||||
case 0x000E: CommandComplete(opcode, {kStatusSuccess, 0x05, 0x00}); return; // Read Default Link Policy
|
||||
case 0x000F: CommandComplete(opcode, {kStatusSuccess}); return; // Write Default Link Policy
|
||||
default:
|
||||
UnknownCommand(opcode);
|
||||
return;
|
||||
}
|
||||
CommandStatus(opcode, kStatusUnknownCommand);
|
||||
}
|
||||
|
||||
void Controller::HandleControllerBaseband(uint16_t opcode, const uint8_t* p, size_t n, uint64_t nowUs) {
|
||||
(void)nowUs;
|
||||
const auto handleReply = [&](std::vector<uint8_t> extra) {
|
||||
const int slot = n >= 2 ? SlotForHandle(Le16(p)) : -1;
|
||||
std::vector<uint8_t> body = {static_cast<uint8_t>(slot < 0 ? kStatusUnknownConnection : kStatusSuccess)};
|
||||
PutLe16(body, n >= 2 ? Le16(p) : 0);
|
||||
body.insert(body.end(), extra.begin(), extra.end());
|
||||
CommandComplete(opcode, body);
|
||||
};
|
||||
switch (opcode & 0x3FF) {
|
||||
case 0x0003: // Reset
|
||||
ResetState(true);
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x000D: { // Read Stored Link Key
|
||||
if (n < 7) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
const bool readAll = p[6] == 0x01;
|
||||
std::vector<StoredKey> keys;
|
||||
for (const StoredKey& key : m_storedKeys) {
|
||||
if (readAll || key.addr == addr) keys.push_back(key);
|
||||
}
|
||||
for (size_t start = 0; start < keys.size(); start += kKeysPerEvent) {
|
||||
const size_t count = std::min<size_t>(kKeysPerEvent, keys.size() - start);
|
||||
std::vector<uint8_t> body = {static_cast<uint8_t>(count)};
|
||||
for (size_t i = start; i < start + count; ++i) {
|
||||
PutAddr(body, keys[i].addr);
|
||||
body.insert(body.end(), keys[i].key.begin(), keys[i].key.end());
|
||||
}
|
||||
QueueEvent(Event(kEventReturnLinkKeys, body));
|
||||
}
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, kMaxStoredKeys);
|
||||
PutLe16(body, static_cast<uint16_t>(keys.size()));
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
}
|
||||
case 0x0011: { // Write Stored Link Key
|
||||
if (n < 1) break;
|
||||
const uint8_t count = p[0];
|
||||
if (n < 1 + count * size_t{22}) break;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
const uint8_t* entry = p + 1 + i * 22;
|
||||
StoredKey key{ReadAddr(entry), {}};
|
||||
std::copy(entry + 6, entry + 22, key.key.begin());
|
||||
auto existing = std::find_if(m_storedKeys.begin(), m_storedKeys.end(),
|
||||
[&](const StoredKey& k) { return k.addr == key.addr; });
|
||||
if (existing != m_storedKeys.end()) {
|
||||
*existing = key;
|
||||
} else if (m_storedKeys.size() < kMaxStoredKeys) {
|
||||
m_storedKeys.push_back(key);
|
||||
}
|
||||
}
|
||||
CommandComplete(opcode, {kStatusSuccess, count});
|
||||
return;
|
||||
}
|
||||
case 0x0012: { // Delete Stored Link Key
|
||||
if (n < 7) break;
|
||||
const BdAddr addr = ReadAddr(p);
|
||||
const bool deleteAll = p[6] == 0x01;
|
||||
const size_t before = m_storedKeys.size();
|
||||
m_storedKeys.erase(std::remove_if(m_storedKeys.begin(), m_storedKeys.end(),
|
||||
[&](const StoredKey& k) { return deleteAll || k.addr == addr; }),
|
||||
m_storedKeys.end());
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, static_cast<uint16_t>(before - m_storedKeys.size()));
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
}
|
||||
case 0x0013: // Change Local Name
|
||||
if (n < m_localName.size()) break;
|
||||
std::memcpy(m_localName.data(), p, m_localName.size());
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x0014: { // Read Local Name
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
body.insert(body.end(), m_localName.begin(), m_localName.end());
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
}
|
||||
case 0x0018: // Write Page Timeout
|
||||
if (n < 2) break;
|
||||
m_pageTimeoutSlots = Le16(p);
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x0019: // Read Scan Enable
|
||||
CommandComplete(opcode, {kStatusSuccess, m_scanEnable});
|
||||
return;
|
||||
case 0x001A: // Write Scan Enable
|
||||
if (n < 1) break;
|
||||
m_scanEnable = p[0];
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x0023: // Read Class of Device
|
||||
CommandComplete(opcode, {kStatusSuccess, m_classOfDevice[0], m_classOfDevice[1], m_classOfDevice[2]});
|
||||
return;
|
||||
case 0x0024: // Write Class of Device
|
||||
if (n < 3) break;
|
||||
std::copy(p, p + 3, m_classOfDevice.begin());
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x0045: // Write Inquiry Mode
|
||||
if (n < 1) break;
|
||||
m_inquiryMode = p[0];
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x0028: // Write Automatic Flush Timeout
|
||||
case 0x0037: // Write Link Supervision Timeout
|
||||
handleReply({});
|
||||
return;
|
||||
case 0x0036: // Read Link Supervision Timeout
|
||||
handleReply({0x80, 0x0C});
|
||||
return;
|
||||
case 0x0001: // Set Event Mask
|
||||
case 0x0005: // Set Event Filter
|
||||
case 0x000A: // Write PIN Type
|
||||
case 0x0016: // Write Connection Accept Timeout
|
||||
case 0x001C: // Write Page Scan Activity
|
||||
case 0x001E: // Write Inquiry Scan Activity
|
||||
case 0x0020: // Write Authentication Enable
|
||||
case 0x0022: // Write Encryption Mode
|
||||
case 0x0031: // Set Controller To Host Flow Control
|
||||
case 0x0033: // Host Buffer Size
|
||||
case 0x003A: // Write Current IAC LAP
|
||||
case 0x003F: // Set AFH Host Channel Classification
|
||||
case 0x0043: // Write Inquiry Scan Type
|
||||
case 0x0047: // Write Page Scan Type
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
return;
|
||||
case 0x0035: // Host Number Of Completed Packets: no event
|
||||
return;
|
||||
default:
|
||||
UnknownCommand(opcode);
|
||||
return;
|
||||
}
|
||||
std::cerr << "[bluetooth] short HCI command 0x" << std::hex << opcode << std::dec << std::endl;
|
||||
CommandComplete(opcode, {kStatusUnknownCommand});
|
||||
}
|
||||
|
||||
void Controller::HandleInformational(uint16_t opcode, const uint8_t* p, size_t n) {
|
||||
(void)p;
|
||||
(void)n;
|
||||
switch (opcode & 0x3FF) {
|
||||
case 0x0001: { // Read Local Version Information
|
||||
std::vector<uint8_t> body = {kStatusSuccess, 0x03};
|
||||
PutLe16(body, 0x40A7);
|
||||
body.push_back(0x03);
|
||||
PutLe16(body, 0x000F);
|
||||
PutLe16(body, 0x430E);
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
}
|
||||
case 0x0003: { // Read Local Supported Features
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
body.insert(body.end(), kLocalFeatures.begin(), kLocalFeatures.end());
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
}
|
||||
case 0x0005: { // Read Buffer Size
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutLe16(body, kAclBufferSize);
|
||||
body.push_back(kScoBufferSize);
|
||||
PutLe16(body, kAclBufferCount);
|
||||
PutLe16(body, 0);
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
}
|
||||
case 0x0009: { // Read BD_ADDR
|
||||
std::vector<uint8_t> body = {kStatusSuccess};
|
||||
PutAddr(body, LocalAddress());
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
UnknownCommand(opcode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::HandleStatusParameters(uint16_t opcode, const uint8_t* p, size_t n) {
|
||||
const int slot = n >= 2 ? SlotForHandle(Le16(p)) : -1;
|
||||
std::vector<uint8_t> body = {static_cast<uint8_t>(slot < 0 ? kStatusUnknownConnection : kStatusSuccess)};
|
||||
PutLe16(body, n >= 2 ? Le16(p) : 0);
|
||||
switch (opcode & 0x3FF) {
|
||||
case 0x0003: // Get Link Quality
|
||||
body.push_back(0xFF);
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
case 0x0005: // Read RSSI: 0 = inside the golden receive power range
|
||||
body.push_back(0x00);
|
||||
CommandComplete(opcode, body);
|
||||
return;
|
||||
default:
|
||||
UnknownCommand(opcode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::HandleVendor(uint16_t opcode, const uint8_t* p, size_t n) {
|
||||
(void)p;
|
||||
(void)n;
|
||||
// WUD's firmware patch upload (0xFC4C writes, 0xFC4F install) and friends.
|
||||
CommandComplete(opcode, {kStatusSuccess});
|
||||
}
|
||||
|
||||
void Controller::ReceiveAcl(const uint8_t* packet, size_t size, uint64_t nowUs) {
|
||||
(void)nowUs;
|
||||
if (size < 4) return;
|
||||
const uint16_t header = Le16(packet);
|
||||
const uint16_t length = Le16(packet + 2);
|
||||
if (length != size - 4) {
|
||||
std::cerr << "[bluetooth] dropped malformed ACL packet" << std::endl;
|
||||
return;
|
||||
}
|
||||
const int slot = SlotForHandle(header & 0x0FFF);
|
||||
if (slot < 0) return;
|
||||
++m_completedPackets[slot];
|
||||
const uint8_t boundary = static_cast<uint8_t>((header >> 12) & 0x3);
|
||||
Reassembly& r = m_reassembly[slot];
|
||||
const uint8_t* data = packet + 4;
|
||||
if (boundary == 0x1) {
|
||||
if (r.expected == 0) return;
|
||||
r.frame.insert(r.frame.end(), data, data + length);
|
||||
} else {
|
||||
r.frame.assign(data, data + length);
|
||||
r.expected = length >= 2 ? static_cast<size_t>(Le16(data)) + 4 : 0;
|
||||
}
|
||||
if (r.expected != 0 && r.frame.size() >= r.expected) {
|
||||
const std::vector<uint8_t> frame(r.frame.begin(), r.frame.begin() + static_cast<std::ptrdiff_t>(r.expected));
|
||||
r = {};
|
||||
m_links[slot]->ReceiveL2cap(frame.data(), frame.size());
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::Tick(uint64_t nowUs) {
|
||||
if (!m_open) return;
|
||||
for (size_t i = 0; i < m_pending.size();) {
|
||||
if (nowUs >= m_pending[i].dueUs) {
|
||||
QueueEvent(std::move(m_pending[i].event));
|
||||
m_pending.erase(m_pending.begin() + static_cast<std::ptrdiff_t>(i));
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
TickInquiry(nowUs);
|
||||
for (auto& link : m_links) {
|
||||
link->Tick(nowUs);
|
||||
}
|
||||
SendNumberOfCompletedPackets();
|
||||
}
|
||||
|
||||
void Controller::TickInquiry(uint64_t nowUs) {
|
||||
if (!m_inquiry.active) return;
|
||||
if (nowUs >= m_inquiry.endUs ||
|
||||
(m_inquiry.maxResponses != 0 && m_inquiry.reported >= m_inquiry.maxResponses)) {
|
||||
FinishInquiry();
|
||||
return;
|
||||
}
|
||||
// One result per tick, lowest slot first, so remotes keep their order.
|
||||
for (size_t slot = 0; slot < kRemoteSlots; ++slot) {
|
||||
if (m_inquiry.slotReported[slot] || !m_links[slot]->Discoverable()) continue;
|
||||
m_inquiry.slotReported[slot] = true;
|
||||
++m_inquiry.reported;
|
||||
const BdAddr addr = RemoteAddress(slot);
|
||||
std::vector<uint8_t> body = {0x01};
|
||||
PutAddr(body, addr);
|
||||
body.push_back(0x01); // page scan repetition mode R1
|
||||
if (m_inquiryMode == 0) {
|
||||
body.insert(body.end(), {0x00, 0x00, 0x04, 0x25, 0x00});
|
||||
PutLe16(body, kRemoteClockOffset);
|
||||
QueueEvent(Event(kEventInquiryResult, body));
|
||||
} else {
|
||||
body.insert(body.end(), {0x00, 0x04, 0x25, 0x00});
|
||||
PutLe16(body, kRemoteClockOffset);
|
||||
body.push_back(kInquiryRssi);
|
||||
if (m_inquiryMode == 2) {
|
||||
std::array<uint8_t, 240> eir{};
|
||||
const char* name = m_host.DeviceName(slot);
|
||||
const size_t nameLength = std::min<size_t>(std::strlen(name), eir.size() - 2);
|
||||
eir[0] = static_cast<uint8_t>(nameLength + 1);
|
||||
eir[1] = 0x09; // complete local name
|
||||
std::memcpy(&eir[2], name, nameLength);
|
||||
body.insert(body.end(), eir.begin(), eir.end());
|
||||
QueueEvent(Event(kEventExtendedInquiryResult, body));
|
||||
} else {
|
||||
QueueEvent(Event(kEventInquiryResultWithRssi, body));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::FinishInquiry() {
|
||||
m_inquiry = {};
|
||||
QueueEvent(Event(kEventInquiryComplete, {kStatusSuccess}));
|
||||
if (m_hostInquiryNotified) {
|
||||
m_host.OnGuestInquiry(false);
|
||||
m_hostInquiryNotified = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::SendNumberOfCompletedPackets() {
|
||||
std::vector<uint8_t> body = {0};
|
||||
for (size_t slot = 0; slot < kRemoteSlots; ++slot) {
|
||||
if (m_completedPackets[slot] == 0) continue;
|
||||
PutLe16(body, ConnectionHandle(slot));
|
||||
PutLe16(body, m_completedPackets[slot]);
|
||||
m_completedPackets[slot] = 0;
|
||||
++body[0];
|
||||
}
|
||||
if (body[0] != 0) {
|
||||
QueueEvent(Event(kEventNumberOfCompletedPackets, body));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Bluetooth
|
||||
@@ -0,0 +1,567 @@
|
||||
#include "bluetooth/bt_remote_link.h"
|
||||
|
||||
#include "bluetooth/bt_bytes.h"
|
||||
#include "bluetooth/bt_sdp.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
namespace Bluetooth {
|
||||
namespace L2capDetail {
|
||||
|
||||
constexpr uint16_t kSignallingCid = 0x0001;
|
||||
constexpr uint16_t kFirstDynamicCid = 0x0040;
|
||||
|
||||
constexpr uint16_t kPsmSdp = 0x0001;
|
||||
constexpr uint16_t kPsmHidControl = 0x0011;
|
||||
constexpr uint16_t kPsmHidInterrupt = 0x0013;
|
||||
|
||||
constexpr uint8_t kCommandReject = 0x01;
|
||||
constexpr uint8_t kConnectionRequest = 0x02;
|
||||
constexpr uint8_t kConnectionResponse = 0x03;
|
||||
constexpr uint8_t kConfigurationRequest = 0x04;
|
||||
constexpr uint8_t kConfigurationResponse = 0x05;
|
||||
constexpr uint8_t kDisconnectionRequest = 0x06;
|
||||
constexpr uint8_t kDisconnectionResponse = 0x07;
|
||||
constexpr uint8_t kEchoRequest = 0x08;
|
||||
constexpr uint8_t kEchoResponse = 0x09;
|
||||
constexpr uint8_t kInformationRequest = 0x0A;
|
||||
constexpr uint8_t kInformationResponse = 0x0B;
|
||||
|
||||
constexpr uint16_t kResultSuccess = 0x0000;
|
||||
constexpr uint16_t kResultPending = 0x0001;
|
||||
constexpr uint16_t kResultPsmNotSupported = 0x0002;
|
||||
constexpr uint16_t kResultNoResources = 0x0004;
|
||||
|
||||
constexpr uint8_t kOptionMtu = 0x01;
|
||||
constexpr uint8_t kOptionFlushTimeout = 0x02;
|
||||
// A real remote asks for this MTU on its own channels.
|
||||
constexpr uint16_t kRemoteMtu = 185;
|
||||
|
||||
constexpr uint8_t kHidHandshake = 0x0;
|
||||
constexpr uint8_t kHidControl = 0x1;
|
||||
constexpr uint8_t kHidSetReport = 0x5;
|
||||
constexpr uint8_t kHidData = 0xA;
|
||||
constexpr uint8_t kHidParamOutput = 0x2;
|
||||
constexpr uint8_t kHidHandshakeSuccess = 0x00;
|
||||
constexpr uint8_t kHidHandshakeUnsupported = 0x03;
|
||||
constexpr uint8_t kHidVirtualCableUnplug = 0x5;
|
||||
constexpr uint8_t kHidDataInput = 0xA1;
|
||||
|
||||
constexpr uint64_t kRequestTimeoutUs = 10'000'000;
|
||||
constexpr uint64_t kRetryDelayUs = 3'000'000;
|
||||
constexpr uint8_t kReasonRemoteUserTerminated = 0x13;
|
||||
// Output reports carry at most 21 payload bytes; anything longer is not a Wii Remote report.
|
||||
constexpr size_t kMaxReportSize = 22;
|
||||
constexpr size_t kInputBacklog = 8;
|
||||
|
||||
} // namespace L2capDetail
|
||||
|
||||
using namespace L2capDetail;
|
||||
|
||||
RemoteLink::RemoteLink(Controller& controller, size_t slot) : m_controller(controller), m_slot(slot) {}
|
||||
|
||||
void RemoteLink::Reset() {
|
||||
ClearConnection();
|
||||
m_baseband = Baseband::Idle;
|
||||
SetState(LinkState::Down);
|
||||
m_retryAtUs = 0;
|
||||
}
|
||||
|
||||
void RemoteLink::ClearConnection() {
|
||||
m_channels.clear();
|
||||
m_guestInitiated = false;
|
||||
m_everReady = false;
|
||||
}
|
||||
|
||||
bool RemoteLink::Discoverable() const {
|
||||
return m_baseband == Baseband::Idle && m_connectWanted && m_controller.Host().IsAttached(m_slot);
|
||||
}
|
||||
|
||||
void RemoteLink::Tick(uint64_t nowUs) {
|
||||
m_nowUs = nowUs;
|
||||
RemoteHost& host = m_controller.Host();
|
||||
if (m_baseband == Baseband::Idle) {
|
||||
if (host.ConsumeWake(m_slot)) {
|
||||
m_connectWanted = true;
|
||||
m_retryAtUs = 0;
|
||||
}
|
||||
if (!host.IsAttached(m_slot)) {
|
||||
m_connectWanted = false;
|
||||
}
|
||||
if (m_connectWanted && m_controller.PageScanEnabled() && nowUs >= m_retryAtUs) {
|
||||
std::vector<uint8_t> event = {0x04, 10};
|
||||
const BdAddr addr = Controller::RemoteAddress(m_slot);
|
||||
event.insert(event.end(), addr.begin(), addr.end());
|
||||
// Class of device 0x002504: peripheral, joystick.
|
||||
event.insert(event.end(), {0x04, 0x25, 0x00, 0x01});
|
||||
m_controller.QueueEvent(std::move(event));
|
||||
m_baseband = Baseband::Requesting;
|
||||
m_stateSinceUs = nowUs;
|
||||
}
|
||||
} else if (m_baseband == Baseband::Requesting) {
|
||||
if (nowUs - m_stateSinceUs > kRequestTimeoutUs) {
|
||||
m_baseband = Baseband::Idle;
|
||||
m_retryAtUs = nowUs + kRetryDelayUs;
|
||||
}
|
||||
} else if (m_baseband == Baseband::Connected) {
|
||||
if (!host.IsAttached(m_slot)) {
|
||||
DropFromRemote(kReasonRemoteUserTerminated);
|
||||
m_connectWanted = false;
|
||||
return;
|
||||
}
|
||||
if (!m_guestInitiated) {
|
||||
const Channel* control = FindByPsm(kPsmHidControl);
|
||||
if (control == nullptr) {
|
||||
RequestChannel(kPsmHidControl, nowUs);
|
||||
} else if (control->Open() && FindByPsm(kPsmHidInterrupt) == nullptr) {
|
||||
RequestChannel(kPsmHidInterrupt, nowUs);
|
||||
}
|
||||
}
|
||||
for (const Channel& channel : m_channels) {
|
||||
const bool waiting = (channel.weInitiated && !channel.connected) ||
|
||||
(channel.configSent && !channel.localConfigured);
|
||||
if (waiting && nowUs - channel.requestUs > kRequestTimeoutUs) {
|
||||
std::cerr << "[bluetooth] Wii Remote " << (m_slot + 1) << ": L2CAP channel 0x" << std::hex
|
||||
<< channel.psm << std::dec << " did not open, dropping the connection" << std::endl;
|
||||
DropFromRemote(kReasonRemoteUserTerminated);
|
||||
m_retryAtUs = nowUs + kRetryDelayUs;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
ForwardInput();
|
||||
}
|
||||
|
||||
void RemoteLink::ForwardInput() {
|
||||
RemoteHost& host = m_controller.Host();
|
||||
if (m_state != LinkState::Ready) {
|
||||
while (host.PopInputReport(m_slot, m_report)) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const Channel* interrupt = FindByPsm(kPsmHidInterrupt);
|
||||
while (m_controller.AclQueued() < kInputBacklog && host.PopInputReport(m_slot, m_report)) {
|
||||
if (m_report.empty() || m_report.size() > kMaxReportSize) continue;
|
||||
SendInput(*interrupt, m_report);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteLink::SendInput(const Channel& interrupt, const std::vector<uint8_t>& report) {
|
||||
std::vector<uint8_t> payload;
|
||||
payload.reserve(report.size() + 1);
|
||||
payload.push_back(kHidDataInput);
|
||||
payload.insert(payload.end(), report.begin(), report.end());
|
||||
if (payload.size() <= interrupt.remoteMtu) {
|
||||
SendData(interrupt, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RemoteLink::OnAccepted(uint8_t role) {
|
||||
(void)role;
|
||||
m_baseband = Baseband::Connected;
|
||||
m_guestInitiated = false;
|
||||
m_everReady = false;
|
||||
m_stateSinceUs = m_nowUs;
|
||||
SetState(LinkState::Connecting);
|
||||
}
|
||||
|
||||
void RemoteLink::OnRejected(uint8_t reason, uint64_t nowUs) {
|
||||
std::cerr << "[bluetooth] Wii Remote " << (m_slot + 1) << ": connection rejected (0x" << std::hex
|
||||
<< static_cast<int>(reason) << std::dec << ")" << std::endl;
|
||||
m_baseband = Baseband::Idle;
|
||||
m_retryAtUs = nowUs + kRetryDelayUs;
|
||||
}
|
||||
|
||||
bool RemoteLink::OnPaged(uint64_t nowUs) {
|
||||
if (!Discoverable()) {
|
||||
return false;
|
||||
}
|
||||
m_nowUs = nowUs;
|
||||
m_baseband = Baseband::Connected;
|
||||
m_guestInitiated = true;
|
||||
m_everReady = false;
|
||||
m_stateSinceUs = nowUs;
|
||||
SetState(LinkState::Connecting);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoteLink::OnDisconnected(bool byGuest, uint64_t nowUs) {
|
||||
const bool wasReady = m_everReady;
|
||||
ClearConnection();
|
||||
m_baseband = Baseband::Idle;
|
||||
SetState(LinkState::Down);
|
||||
if (byGuest && wasReady) {
|
||||
m_connectWanted = false;
|
||||
m_controller.Host().OnGuestDisconnect(m_slot);
|
||||
} else {
|
||||
m_retryAtUs = nowUs + kRetryDelayUs;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteLink::DropFromRemote(uint8_t reason) {
|
||||
std::vector<uint8_t> event = {0x05, 4, 0x00};
|
||||
PutLe16(event, Controller::ConnectionHandle(m_slot));
|
||||
event.push_back(reason);
|
||||
m_controller.QueueEvent(std::move(event));
|
||||
ClearConnection();
|
||||
m_baseband = Baseband::Idle;
|
||||
SetState(LinkState::Down);
|
||||
}
|
||||
|
||||
void RemoteLink::SetState(LinkState state) {
|
||||
if (m_state == state) return;
|
||||
m_state = state;
|
||||
if (state == LinkState::Ready) {
|
||||
m_everReady = true;
|
||||
m_connectWanted = false;
|
||||
}
|
||||
m_controller.Host().OnLinkStateChanged(m_slot, state);
|
||||
}
|
||||
|
||||
void RemoteLink::UpdateState() {
|
||||
if (m_baseband != Baseband::Connected) {
|
||||
SetState(LinkState::Down);
|
||||
return;
|
||||
}
|
||||
const Channel* control = FindByPsm(kPsmHidControl);
|
||||
const Channel* interrupt = FindByPsm(kPsmHidInterrupt);
|
||||
const bool ready = control != nullptr && interrupt != nullptr && control->Open() && interrupt->Open();
|
||||
SetState(ready ? LinkState::Ready : LinkState::Connecting);
|
||||
}
|
||||
|
||||
RemoteLink::Channel* RemoteLink::FindByLocal(uint16_t cid) {
|
||||
for (Channel& channel : m_channels) {
|
||||
if (channel.localCid == cid) return &channel;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RemoteLink::Channel* RemoteLink::FindByPsm(uint16_t psm) {
|
||||
for (Channel& channel : m_channels) {
|
||||
if (channel.psm == psm) return &channel;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint16_t RemoteLink::AllocateCid() const {
|
||||
uint16_t cid = kFirstDynamicCid;
|
||||
while (std::any_of(m_channels.begin(), m_channels.end(), [&](const Channel& c) { return c.localCid == cid; })) {
|
||||
++cid;
|
||||
}
|
||||
return cid;
|
||||
}
|
||||
|
||||
uint8_t RemoteLink::NextIdent() {
|
||||
m_ident = static_cast<uint8_t>(m_ident + 1);
|
||||
if (m_ident == 0) m_ident = 1;
|
||||
return m_ident;
|
||||
}
|
||||
|
||||
void RemoteLink::SendSignal(uint8_t code, uint8_t ident, const std::vector<uint8_t>& data) {
|
||||
std::vector<uint8_t> frame;
|
||||
frame.reserve(8 + data.size());
|
||||
PutLe16(frame, static_cast<uint16_t>(4 + data.size()));
|
||||
PutLe16(frame, kSignallingCid);
|
||||
frame.push_back(code);
|
||||
frame.push_back(ident);
|
||||
PutLe16(frame, static_cast<uint16_t>(data.size()));
|
||||
frame.insert(frame.end(), data.begin(), data.end());
|
||||
m_controller.QueueAcl(Controller::ConnectionHandle(m_slot), frame);
|
||||
}
|
||||
|
||||
void RemoteLink::SendData(const Channel& channel, const std::vector<uint8_t>& payload) {
|
||||
std::vector<uint8_t> frame;
|
||||
frame.reserve(4 + payload.size());
|
||||
PutLe16(frame, static_cast<uint16_t>(payload.size()));
|
||||
PutLe16(frame, channel.remoteCid);
|
||||
frame.insert(frame.end(), payload.begin(), payload.end());
|
||||
m_controller.QueueAcl(Controller::ConnectionHandle(m_slot), frame);
|
||||
}
|
||||
|
||||
void RemoteLink::RequestChannel(uint16_t psm, uint64_t nowUs) {
|
||||
Channel channel;
|
||||
channel.psm = psm;
|
||||
channel.localCid = AllocateCid();
|
||||
channel.weInitiated = true;
|
||||
channel.connectIdent = NextIdent();
|
||||
channel.requestUs = nowUs;
|
||||
std::vector<uint8_t> data;
|
||||
PutLe16(data, psm);
|
||||
PutLe16(data, channel.localCid);
|
||||
SendSignal(kConnectionRequest, channel.connectIdent, data);
|
||||
m_channels.push_back(channel);
|
||||
}
|
||||
|
||||
void RemoteLink::SendConfigRequest(Channel& channel) {
|
||||
channel.configSent = true;
|
||||
channel.configIdent = NextIdent();
|
||||
channel.requestUs = m_nowUs;
|
||||
std::vector<uint8_t> data;
|
||||
PutLe16(data, channel.remoteCid);
|
||||
PutLe16(data, 0);
|
||||
data.push_back(kOptionMtu);
|
||||
data.push_back(2);
|
||||
PutLe16(data, kRemoteMtu);
|
||||
SendSignal(kConfigurationRequest, channel.configIdent, data);
|
||||
}
|
||||
|
||||
void RemoteLink::ReceiveL2cap(const uint8_t* frame, size_t size) {
|
||||
if (m_baseband != Baseband::Connected || size < 4) return;
|
||||
const uint16_t length = Le16(frame);
|
||||
const uint16_t cid = Le16(frame + 2);
|
||||
if (length != size - 4) {
|
||||
std::cerr << "[bluetooth] Wii Remote " << (m_slot + 1) << ": dropped malformed L2CAP frame" << std::endl;
|
||||
return;
|
||||
}
|
||||
if (cid == kSignallingCid) {
|
||||
HandleSignalling(frame + 4, length);
|
||||
} else if (Channel* channel = FindByLocal(cid)) {
|
||||
HandleChannelData(*channel, frame + 4, length);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteLink::HandleSignalling(const uint8_t* data, size_t size) {
|
||||
while (size >= 4) {
|
||||
const uint8_t code = data[0];
|
||||
const uint8_t ident = data[1];
|
||||
const uint16_t length = Le16(data + 2);
|
||||
if (static_cast<size_t>(length) + 4 > size) {
|
||||
return;
|
||||
}
|
||||
const uint8_t* p = data + 4;
|
||||
switch (code) {
|
||||
case kConnectionRequest: HandleConnectionRequest(ident, p, length); break;
|
||||
case kConnectionResponse: HandleConnectionResponse(ident, p, length); break;
|
||||
case kConfigurationRequest: HandleConfigurationRequest(ident, p, length); break;
|
||||
case kConfigurationResponse: HandleConfigurationResponse(ident, p, length); break;
|
||||
case kDisconnectionRequest: HandleDisconnectionRequest(ident, p, length); break;
|
||||
case kDisconnectionResponse: HandleDisconnectionResponse(p, length); break;
|
||||
case kEchoRequest: SendSignal(kEchoResponse, ident, std::vector<uint8_t>(p, p + length)); break;
|
||||
case kInformationRequest: {
|
||||
if (length >= 2) {
|
||||
std::vector<uint8_t> reply;
|
||||
PutLe16(reply, Le16(p));
|
||||
PutLe16(reply, 0x0001); // not supported
|
||||
SendSignal(kInformationResponse, ident, reply);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kCommandReject:
|
||||
std::cerr << "[bluetooth] Wii Remote " << (m_slot + 1) << ": the game rejected L2CAP command "
|
||||
<< static_cast<int>(ident) << std::endl;
|
||||
break;
|
||||
case kEchoResponse:
|
||||
case kInformationResponse:
|
||||
break;
|
||||
default: {
|
||||
std::vector<uint8_t> reply;
|
||||
PutLe16(reply, 0x0000); // command not understood
|
||||
SendSignal(kCommandReject, ident, reply);
|
||||
break;
|
||||
}
|
||||
}
|
||||
data += 4 + length;
|
||||
size -= 4 + length;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteLink::HandleConnectionRequest(uint8_t ident, const uint8_t* p, size_t n) {
|
||||
if (n < 4) return;
|
||||
const uint16_t psm = Le16(p);
|
||||
const uint16_t scid = Le16(p + 2);
|
||||
uint16_t result = kResultSuccess;
|
||||
uint16_t dcid = 0;
|
||||
if (psm != kPsmSdp && psm != kPsmHidControl && psm != kPsmHidInterrupt) {
|
||||
result = kResultPsmNotSupported;
|
||||
} else if (FindByPsm(psm) != nullptr) {
|
||||
// A real remote refuses a second channel on the same PSM.
|
||||
result = kResultNoResources;
|
||||
} else {
|
||||
Channel channel;
|
||||
channel.psm = psm;
|
||||
channel.localCid = AllocateCid();
|
||||
channel.remoteCid = scid;
|
||||
channel.connected = true;
|
||||
dcid = channel.localCid;
|
||||
m_channels.push_back(channel);
|
||||
}
|
||||
std::vector<uint8_t> reply;
|
||||
PutLe16(reply, dcid);
|
||||
PutLe16(reply, scid);
|
||||
PutLe16(reply, result);
|
||||
PutLe16(reply, 0);
|
||||
SendSignal(kConnectionResponse, ident, reply);
|
||||
if (result == kResultSuccess) {
|
||||
SendConfigRequest(*FindByLocal(dcid));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteLink::HandleConnectionResponse(uint8_t ident, const uint8_t* p, size_t n) {
|
||||
if (n < 8) return;
|
||||
const uint16_t dcid = Le16(p);
|
||||
const uint16_t scid = Le16(p + 2);
|
||||
const uint16_t result = Le16(p + 4);
|
||||
Channel* channel = FindByLocal(scid);
|
||||
if (channel == nullptr || !channel->weInitiated || channel->connected || channel->connectIdent != ident) {
|
||||
return;
|
||||
}
|
||||
if (result == kResultSuccess) {
|
||||
channel->remoteCid = dcid;
|
||||
channel->connected = true;
|
||||
SendConfigRequest(*channel);
|
||||
} else if (result == kResultPending) {
|
||||
channel->requestUs = m_nowUs;
|
||||
} else {
|
||||
std::cerr << "[bluetooth] Wii Remote " << (m_slot + 1) << ": the game refused L2CAP PSM 0x" << std::hex
|
||||
<< channel->psm << " (result 0x" << result << ")" << std::dec << std::endl;
|
||||
DropFromRemote(kReasonRemoteUserTerminated);
|
||||
m_retryAtUs = m_nowUs + kRetryDelayUs;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteLink::HandleConfigurationRequest(uint8_t ident, const uint8_t* p, size_t n) {
|
||||
if (n < 4) return;
|
||||
const uint16_t dcid = Le16(p);
|
||||
const uint16_t flags = Le16(p + 2);
|
||||
Channel* channel = FindByLocal(dcid);
|
||||
if (channel == nullptr) {
|
||||
std::vector<uint8_t> reply;
|
||||
PutLe16(reply, 0x0002); // invalid CID
|
||||
PutLe16(reply, dcid);
|
||||
PutLe16(reply, 0);
|
||||
SendSignal(kCommandReject, ident, reply);
|
||||
return;
|
||||
}
|
||||
channel->pendingOptions.insert(channel->pendingOptions.end(), p + 4, p + n);
|
||||
std::vector<uint8_t> reply;
|
||||
PutLe16(reply, channel->remoteCid);
|
||||
if (flags & 1) {
|
||||
PutLe16(reply, 1);
|
||||
PutLe16(reply, kResultSuccess);
|
||||
SendSignal(kConfigurationResponse, ident, reply);
|
||||
return;
|
||||
}
|
||||
PutLe16(reply, 0);
|
||||
PutLe16(reply, kResultSuccess);
|
||||
const std::vector<uint8_t>& options = channel->pendingOptions;
|
||||
for (size_t i = 0; i + 2 <= options.size();) {
|
||||
const uint8_t type = options[i] & 0x7F;
|
||||
const uint8_t length = options[i + 1];
|
||||
if (i + 2 + length > options.size()) break;
|
||||
if (type == kOptionMtu && length == 2) {
|
||||
channel->remoteMtu = Le16(&options[i + 2]);
|
||||
reply.insert(reply.end(), options.begin() + static_cast<std::ptrdiff_t>(i),
|
||||
options.begin() + static_cast<std::ptrdiff_t>(i + 4));
|
||||
} else if (type == kOptionFlushTimeout && length == 2) {
|
||||
reply.insert(reply.end(), options.begin() + static_cast<std::ptrdiff_t>(i),
|
||||
options.begin() + static_cast<std::ptrdiff_t>(i + 4));
|
||||
}
|
||||
i += 2 + length;
|
||||
}
|
||||
channel->pendingOptions.clear();
|
||||
channel->remoteConfigured = true;
|
||||
SendSignal(kConfigurationResponse, ident, reply);
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void RemoteLink::HandleConfigurationResponse(uint8_t ident, const uint8_t* p, size_t n) {
|
||||
if (n < 6) return;
|
||||
const uint16_t scid = Le16(p);
|
||||
const uint16_t flags = Le16(p + 2);
|
||||
const uint16_t result = Le16(p + 4);
|
||||
Channel* channel = FindByLocal(scid);
|
||||
if (channel == nullptr || !channel->configSent || channel->configIdent != ident) return;
|
||||
if (flags & 1) {
|
||||
return;
|
||||
}
|
||||
if (result == kResultSuccess) {
|
||||
channel->localConfigured = true;
|
||||
UpdateState();
|
||||
return;
|
||||
}
|
||||
// Our only option is the MTU; offer the default instead.
|
||||
channel->configIdent = NextIdent();
|
||||
channel->requestUs = m_nowUs;
|
||||
std::vector<uint8_t> data;
|
||||
PutLe16(data, channel->remoteCid);
|
||||
PutLe16(data, 0);
|
||||
SendSignal(kConfigurationRequest, channel->configIdent, data);
|
||||
}
|
||||
|
||||
void RemoteLink::HandleDisconnectionRequest(uint8_t ident, const uint8_t* p, size_t n) {
|
||||
if (n < 4) return;
|
||||
const uint16_t dcid = Le16(p);
|
||||
const uint16_t scid = Le16(p + 2);
|
||||
std::vector<uint8_t> reply;
|
||||
PutLe16(reply, dcid);
|
||||
PutLe16(reply, scid);
|
||||
SendSignal(kDisconnectionResponse, ident, reply);
|
||||
m_channels.erase(std::remove_if(m_channels.begin(), m_channels.end(),
|
||||
[&](const Channel& c) { return c.localCid == dcid; }),
|
||||
m_channels.end());
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void RemoteLink::HandleDisconnectionResponse(const uint8_t* p, size_t n) {
|
||||
if (n < 4) return;
|
||||
const uint16_t scid = Le16(p + 2);
|
||||
m_channels.erase(std::remove_if(m_channels.begin(), m_channels.end(),
|
||||
[&](const Channel& c) { return c.localCid == scid; }),
|
||||
m_channels.end());
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void RemoteLink::HandleChannelData(Channel& channel, const uint8_t* data, size_t size) {
|
||||
if (size == 0) return;
|
||||
if (channel.psm == kPsmSdp) {
|
||||
const std::vector<uint8_t> response = Sdp::HandleRequest(data, size, channel.remoteMtu);
|
||||
SendData(channel, response);
|
||||
return;
|
||||
}
|
||||
if (channel.psm == kPsmHidControl) {
|
||||
HandleHidControl(channel, data, size);
|
||||
return;
|
||||
}
|
||||
const uint8_t type = data[0] >> 4;
|
||||
const uint8_t param = data[0] & 0x0F;
|
||||
if (type == kHidData && param == kHidParamOutput && size >= 2 && size - 1 <= kMaxReportSize) {
|
||||
m_controller.Host().SendOutputReport(m_slot, data + 1, size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteLink::HandleHidControl(Channel& channel, const uint8_t* data, size_t size) {
|
||||
const uint8_t type = data[0] >> 4;
|
||||
const uint8_t param = data[0] & 0x0F;
|
||||
switch (type) {
|
||||
case kHidHandshake:
|
||||
return;
|
||||
case kHidControl:
|
||||
if (param == kHidVirtualCableUnplug) {
|
||||
const bool wasReady = m_everReady;
|
||||
DropFromRemote(kReasonRemoteUserTerminated);
|
||||
if (wasReady) {
|
||||
m_connectWanted = false;
|
||||
m_controller.Host().OnGuestDisconnect(m_slot);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case kHidSetReport:
|
||||
SendData(channel, {kHidHandshakeSuccess});
|
||||
if ((param & 0x3) == kHidParamOutput && size >= 2 && size - 1 <= kMaxReportSize) {
|
||||
m_controller.Host().SendOutputReport(m_slot, data + 1, size - 1);
|
||||
}
|
||||
return;
|
||||
case kHidData:
|
||||
if (param == kHidParamOutput && size >= 2 && size - 1 <= kMaxReportSize) {
|
||||
m_controller.Host().SendOutputReport(m_slot, data + 1, size - 1);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
SendData(channel, {kHidHandshakeUnsupported});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Bluetooth
|
||||
@@ -0,0 +1,438 @@
|
||||
#include "bluetooth/bt_sdp.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace Bluetooth::Sdp {
|
||||
namespace {
|
||||
|
||||
using Bytes = std::vector<uint8_t>;
|
||||
|
||||
constexpr uint8_t kErrorResponse = 0x01;
|
||||
constexpr uint8_t kServiceSearchRequest = 0x02;
|
||||
constexpr uint8_t kServiceSearchResponse = 0x03;
|
||||
constexpr uint8_t kServiceAttributeRequest = 0x04;
|
||||
constexpr uint8_t kServiceAttributeResponse = 0x05;
|
||||
constexpr uint8_t kServiceSearchAttributeRequest = 0x06;
|
||||
constexpr uint8_t kServiceSearchAttributeResponse = 0x07;
|
||||
|
||||
constexpr uint16_t kErrInvalidHandle = 0x0002;
|
||||
constexpr uint16_t kErrInvalidSyntax = 0x0003;
|
||||
constexpr uint16_t kErrInvalidPduSize = 0x0004;
|
||||
constexpr uint16_t kErrInvalidContinuation = 0x0005;
|
||||
|
||||
void PutBe16(Bytes& out, uint16_t v) {
|
||||
out.push_back(static_cast<uint8_t>(v >> 8));
|
||||
out.push_back(static_cast<uint8_t>(v));
|
||||
}
|
||||
|
||||
void PutBe32(Bytes& out, uint32_t v) {
|
||||
PutBe16(out, static_cast<uint16_t>(v >> 16));
|
||||
PutBe16(out, static_cast<uint16_t>(v));
|
||||
}
|
||||
|
||||
uint16_t Be16(const uint8_t* p) {
|
||||
return static_cast<uint16_t>((p[0] << 8) | p[1]);
|
||||
}
|
||||
|
||||
uint32_t Be32(const uint8_t* p) {
|
||||
return (static_cast<uint32_t>(Be16(p)) << 16) | Be16(p + 2);
|
||||
}
|
||||
|
||||
// Data element constructors (Bluetooth Core, Vol 3 Part B, 3.2).
|
||||
Bytes U8(uint8_t v) { return {0x08, v}; }
|
||||
Bytes U16(uint16_t v) { Bytes b{0x09}; PutBe16(b, v); return b; }
|
||||
Bytes U32(uint32_t v) { Bytes b{0x0A}; PutBe32(b, v); return b; }
|
||||
Bytes Uuid16(uint16_t v) { Bytes b{0x19}; PutBe16(b, v); return b; }
|
||||
Bytes Bool(bool v) { return {0x28, static_cast<uint8_t>(v ? 1 : 0)}; }
|
||||
|
||||
Bytes WithLength(uint8_t type, size_t length, bool wide = false) {
|
||||
if (length <= 0xFF && !wide) {
|
||||
return {static_cast<uint8_t>((type << 3) | 5), static_cast<uint8_t>(length)};
|
||||
}
|
||||
Bytes b{static_cast<uint8_t>((type << 3) | 6)};
|
||||
PutBe16(b, static_cast<uint16_t>(length));
|
||||
return b;
|
||||
}
|
||||
|
||||
Bytes Text(const Bytes& raw) {
|
||||
Bytes b = WithLength(4, raw.size());
|
||||
b.insert(b.end(), raw.begin(), raw.end());
|
||||
return b;
|
||||
}
|
||||
|
||||
Bytes Text(std::string_view s) {
|
||||
return Text(Bytes(s.begin(), s.end()));
|
||||
}
|
||||
|
||||
Bytes Seq(std::initializer_list<Bytes> items) {
|
||||
size_t length = 0;
|
||||
for (const Bytes& item : items) length += item.size();
|
||||
Bytes b = WithLength(6, length);
|
||||
for (const Bytes& item : items) b.insert(b.end(), item.begin(), item.end());
|
||||
return b;
|
||||
}
|
||||
|
||||
using Record = std::vector<std::pair<uint16_t, Bytes>>;
|
||||
|
||||
const Bytes kHidDescriptor = {
|
||||
0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x10, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95,
|
||||
0x01, 0x06, 0x00, 0xFF, 0x09, 0x01, 0x91, 0x00, 0x85, 0x11, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
|
||||
0x85, 0x12, 0x95, 0x02, 0x09, 0x01, 0x91, 0x00, 0x85, 0x13, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
|
||||
0x85, 0x14, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x15, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
|
||||
0x85, 0x16, 0x95, 0x15, 0x09, 0x01, 0x91, 0x00, 0x85, 0x17, 0x95, 0x06, 0x09, 0x01, 0x91, 0x00,
|
||||
0x85, 0x18, 0x95, 0x15, 0x09, 0x01, 0x91, 0x00, 0x85, 0x19, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00,
|
||||
0x85, 0x1A, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x20, 0x95, 0x06, 0x09, 0x01, 0x81, 0x00,
|
||||
0x85, 0x21, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x22, 0x95, 0x04, 0x09, 0x01, 0x81, 0x00,
|
||||
0x85, 0x30, 0x95, 0x02, 0x09, 0x01, 0x81, 0x00, 0x85, 0x31, 0x95, 0x05, 0x09, 0x01, 0x81, 0x00,
|
||||
0x85, 0x32, 0x95, 0x0A, 0x09, 0x01, 0x81, 0x00, 0x85, 0x33, 0x95, 0x11, 0x09, 0x01, 0x81, 0x00,
|
||||
0x85, 0x34, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x35, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
|
||||
0x85, 0x36, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x37, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
|
||||
0x85, 0x3D, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x3E, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00,
|
||||
0x85, 0x3F, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0xC0,
|
||||
};
|
||||
|
||||
constexpr uint32_t kHidRecordHandle = 0x00010000;
|
||||
constexpr uint32_t kDeviceIdRecordHandle = 0x00010001;
|
||||
|
||||
const Record& HidRecord() {
|
||||
static const Record record = {
|
||||
{0x0000, U32(kHidRecordHandle)},
|
||||
{0x0001, Seq({Uuid16(0x1124)})},
|
||||
{0x0004, Seq({Seq({Uuid16(0x0100), U16(0x0011)}), Seq({Uuid16(0x0011)})})},
|
||||
{0x0005, Seq({Uuid16(0x1002)})},
|
||||
{0x0006, Seq({U16(0x656E), U16(0x006A), U16(0x0100)})},
|
||||
{0x0009, Seq({Seq({Uuid16(0x1124), U16(0x0100)})})},
|
||||
{0x000D, Seq({Seq({Seq({Uuid16(0x0100), U16(0x0013)}), Seq({Uuid16(0x0011)})})})},
|
||||
{0x0100, Text("Nintendo RVL-CNT-01")},
|
||||
{0x0101, Text("Nintendo RVL-CNT-01")},
|
||||
{0x0102, Text("Nintendo")},
|
||||
{0x0200, U16(0x0100)},
|
||||
{0x0201, U16(0x0111)},
|
||||
{0x0202, U8(0x04)},
|
||||
{0x0203, U8(0x33)},
|
||||
{0x0204, Bool(false)},
|
||||
{0x0205, Bool(true)},
|
||||
{0x0206, Seq({Seq({U8(0x22), Text(kHidDescriptor)})})},
|
||||
{0x0207, Seq({Seq({U16(0x0409), U16(0x0100)})})},
|
||||
{0x0208, Bool(false)},
|
||||
{0x0209, Bool(true)},
|
||||
{0x020A, Bool(true)},
|
||||
{0x020B, U16(0x0100)},
|
||||
{0x020C, U16(0x0C80)},
|
||||
{0x020D, Bool(false)},
|
||||
{0x020E, Bool(false)},
|
||||
};
|
||||
return record;
|
||||
}
|
||||
|
||||
const Record& DeviceIdRecord() {
|
||||
static const Record record = {
|
||||
{0x0000, U32(kDeviceIdRecordHandle)},
|
||||
{0x0001, Seq({Uuid16(0x1200)})},
|
||||
{0x0004, Seq({Seq({Uuid16(0x0100), U16(0x0001)}), Seq({Uuid16(0x0001)})})},
|
||||
{0x0005, Seq({Uuid16(0x1002)})},
|
||||
{0x0009, Seq({Seq({Uuid16(0x1200), U16(0x0100)})})},
|
||||
{0x0200, U16(0x0100)},
|
||||
{0x0201, U16(0x057E)},
|
||||
{0x0202, U16(0x0306)},
|
||||
{0x0203, U16(0x0600)},
|
||||
{0x0204, Bool(true)},
|
||||
{0x0205, U16(0x0002)},
|
||||
};
|
||||
return record;
|
||||
}
|
||||
|
||||
struct NamedRecord {
|
||||
uint32_t handle;
|
||||
const Record& (*get)();
|
||||
};
|
||||
constexpr std::array<NamedRecord, 2> kRecords = {{
|
||||
{kHidRecordHandle, HidRecord},
|
||||
{kDeviceIdRecordHandle, DeviceIdRecord},
|
||||
}};
|
||||
|
||||
using Uuid = std::array<uint8_t, 16>;
|
||||
|
||||
Uuid FromShort(uint32_t value) {
|
||||
// Bluetooth base UUID 00000000-0000-1000-8000-00805F9B34FB.
|
||||
Uuid u = {0, 0, 0, 0, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
|
||||
u[0] = static_cast<uint8_t>(value >> 24);
|
||||
u[1] = static_cast<uint8_t>(value >> 16);
|
||||
u[2] = static_cast<uint8_t>(value >> 8);
|
||||
u[3] = static_cast<uint8_t>(value);
|
||||
return u;
|
||||
}
|
||||
|
||||
// One parsed data element header: type, header size and payload size.
|
||||
struct ElementHeader {
|
||||
uint8_t type = 0;
|
||||
size_t headerSize = 0;
|
||||
size_t dataSize = 0;
|
||||
};
|
||||
|
||||
std::optional<ElementHeader> ParseHeader(const uint8_t* p, size_t n) {
|
||||
if (n == 0) return std::nullopt;
|
||||
ElementHeader h;
|
||||
h.type = static_cast<uint8_t>(p[0] >> 3);
|
||||
const uint8_t sizeIndex = p[0] & 7;
|
||||
if (h.type == 0) {
|
||||
h.headerSize = 1;
|
||||
h.dataSize = 0;
|
||||
} else if (sizeIndex <= 4) {
|
||||
h.headerSize = 1;
|
||||
h.dataSize = size_t{1} << sizeIndex;
|
||||
} else if (sizeIndex == 5) {
|
||||
if (n < 2) return std::nullopt;
|
||||
h.headerSize = 2;
|
||||
h.dataSize = p[1];
|
||||
} else if (sizeIndex == 6) {
|
||||
if (n < 3) return std::nullopt;
|
||||
h.headerSize = 3;
|
||||
h.dataSize = Be16(p + 1);
|
||||
} else {
|
||||
if (n < 5) return std::nullopt;
|
||||
h.headerSize = 5;
|
||||
h.dataSize = Be32(p + 1);
|
||||
}
|
||||
if (h.headerSize + h.dataSize > n) return std::nullopt;
|
||||
return h;
|
||||
}
|
||||
|
||||
std::optional<Uuid> ReadUuid(const uint8_t* p, const ElementHeader& h) {
|
||||
if (h.type != 3) return std::nullopt;
|
||||
const uint8_t* d = p + h.headerSize;
|
||||
if (h.dataSize == 2) return FromShort(Be16(d));
|
||||
if (h.dataSize == 4) return FromShort(Be32(d));
|
||||
if (h.dataSize == 16) {
|
||||
Uuid u{};
|
||||
std::copy(d, d + 16, u.begin());
|
||||
return u;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void CollectUuids(const uint8_t* p, size_t n, std::vector<Uuid>& out) {
|
||||
while (n > 0) {
|
||||
const auto h = ParseHeader(p, n);
|
||||
if (!h) return;
|
||||
if (h->type == 6 || h->type == 7) {
|
||||
CollectUuids(p + h->headerSize, h->dataSize, out);
|
||||
} else if (const auto uuid = ReadUuid(p, *h)) {
|
||||
out.push_back(*uuid);
|
||||
}
|
||||
const size_t total = h->headerSize + h->dataSize;
|
||||
p += total;
|
||||
n -= total;
|
||||
}
|
||||
}
|
||||
|
||||
bool RecordMatches(const Record& record, const std::vector<Uuid>& pattern) {
|
||||
std::vector<Uuid> present;
|
||||
for (const auto& [id, value] : record) CollectUuids(value.data(), value.size(), present);
|
||||
return std::all_of(pattern.begin(), pattern.end(), [&](const Uuid& wanted) {
|
||||
return std::find(present.begin(), present.end(), wanted) != present.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Parses a data element sequence at `p`; advances `p`/`n` past it.
|
||||
std::optional<std::pair<const uint8_t*, size_t>> TakeSequence(const uint8_t*& p, size_t& n) {
|
||||
const auto h = ParseHeader(p, n);
|
||||
if (!h || (h->type != 6)) return std::nullopt;
|
||||
const std::pair<const uint8_t*, size_t> body{p + h->headerSize, h->dataSize};
|
||||
p += h->headerSize + h->dataSize;
|
||||
n -= h->headerSize + h->dataSize;
|
||||
return body;
|
||||
}
|
||||
|
||||
std::optional<std::vector<Uuid>> ParsePattern(const uint8_t*& p, size_t& n) {
|
||||
const auto body = TakeSequence(p, n);
|
||||
if (!body) return std::nullopt;
|
||||
std::vector<Uuid> pattern;
|
||||
const uint8_t* q = body->first;
|
||||
size_t m = body->second;
|
||||
while (m > 0) {
|
||||
const auto h = ParseHeader(q, m);
|
||||
if (!h) return std::nullopt;
|
||||
const auto uuid = ReadUuid(q, *h);
|
||||
if (!uuid) return std::nullopt;
|
||||
pattern.push_back(*uuid);
|
||||
q += h->headerSize + h->dataSize;
|
||||
m -= h->headerSize + h->dataSize;
|
||||
}
|
||||
if (pattern.empty() || pattern.size() > 12) return std::nullopt;
|
||||
return pattern;
|
||||
}
|
||||
|
||||
using IdRange = std::pair<uint16_t, uint16_t>;
|
||||
|
||||
std::optional<std::vector<IdRange>> ParseAttributeIds(const uint8_t*& p, size_t& n) {
|
||||
const auto body = TakeSequence(p, n);
|
||||
if (!body) return std::nullopt;
|
||||
std::vector<IdRange> ranges;
|
||||
const uint8_t* q = body->first;
|
||||
size_t m = body->second;
|
||||
while (m > 0) {
|
||||
const auto h = ParseHeader(q, m);
|
||||
if (!h || h->type != 1) return std::nullopt;
|
||||
const uint8_t* d = q + h->headerSize;
|
||||
if (h->dataSize == 2) {
|
||||
ranges.emplace_back(Be16(d), Be16(d));
|
||||
} else if (h->dataSize == 4) {
|
||||
ranges.emplace_back(Be16(d), Be16(d + 2));
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
q += h->headerSize + h->dataSize;
|
||||
m -= h->headerSize + h->dataSize;
|
||||
}
|
||||
if (ranges.empty()) return std::nullopt;
|
||||
return ranges;
|
||||
}
|
||||
|
||||
// Continuation state is the byte offset into the complete response (2 bytes BE).
|
||||
std::optional<size_t> ParseContinuation(const uint8_t* p, size_t n) {
|
||||
if (n < 1 || p[0] + size_t{1} != n) return std::nullopt;
|
||||
if (p[0] == 0) return 0;
|
||||
if (p[0] != 2) return std::nullopt;
|
||||
return Be16(p + 1);
|
||||
}
|
||||
|
||||
Bytes AttributeList(const Record& record, const std::vector<IdRange>& ranges) {
|
||||
Bytes inner;
|
||||
for (const auto& [id, value] : record) {
|
||||
const bool wanted = std::any_of(ranges.begin(), ranges.end(),
|
||||
[id = id](const IdRange& r) { return id >= r.first && id <= r.second; });
|
||||
if (!wanted) continue;
|
||||
const Bytes idElement = U16(id);
|
||||
inner.insert(inner.end(), idElement.begin(), idElement.end());
|
||||
inner.insert(inner.end(), value.begin(), value.end());
|
||||
}
|
||||
// A real remote always sends attribute lists with a 16-bit length.
|
||||
Bytes list = WithLength(6, inner.size(), true);
|
||||
list.insert(list.end(), inner.begin(), inner.end());
|
||||
return list;
|
||||
}
|
||||
|
||||
Bytes Pdu(uint8_t id, uint16_t tid, const Bytes& params) {
|
||||
Bytes out{id};
|
||||
PutBe16(out, tid);
|
||||
PutBe16(out, static_cast<uint16_t>(params.size()));
|
||||
out.insert(out.end(), params.begin(), params.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
Bytes Error(uint16_t tid, uint16_t code) {
|
||||
Bytes params;
|
||||
PutBe16(params, code);
|
||||
return Pdu(kErrorResponse, tid, params);
|
||||
}
|
||||
|
||||
// Emits the slice of `full` starting at `offset` that fits, plus continuation.
|
||||
Bytes Chunked(uint8_t responseId, uint16_t tid, const Bytes& full, size_t offset, uint16_t maxBytes,
|
||||
size_t maxResponse) {
|
||||
// PDU header 5, byte count 2, continuation up to 3.
|
||||
const size_t room = maxResponse > 10 ? maxResponse - 10 : 1;
|
||||
const size_t limit = std::max<size_t>(1, std::min<size_t>(maxBytes, room));
|
||||
const size_t count = std::min(limit, full.size() - offset);
|
||||
Bytes params;
|
||||
PutBe16(params, static_cast<uint16_t>(count));
|
||||
params.insert(params.end(), full.begin() + static_cast<std::ptrdiff_t>(offset),
|
||||
full.begin() + static_cast<std::ptrdiff_t>(offset + count));
|
||||
const size_t next = offset + count;
|
||||
if (next < full.size()) {
|
||||
params.push_back(2);
|
||||
PutBe16(params, static_cast<uint16_t>(next));
|
||||
} else {
|
||||
params.push_back(0);
|
||||
}
|
||||
return Pdu(responseId, tid, params);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<uint8_t>& WiiRemoteHidDescriptor() {
|
||||
return kHidDescriptor;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> HandleRequest(const uint8_t* pdu, size_t size, size_t maxResponse) {
|
||||
if (size < 5) {
|
||||
return Error(0, kErrInvalidPduSize);
|
||||
}
|
||||
const uint8_t id = pdu[0];
|
||||
const uint16_t tid = Be16(pdu + 1);
|
||||
const uint16_t paramLength = Be16(pdu + 3);
|
||||
if (paramLength != size - 5) {
|
||||
return Error(tid, kErrInvalidPduSize);
|
||||
}
|
||||
const uint8_t* p = pdu + 5;
|
||||
size_t n = paramLength;
|
||||
|
||||
switch (id) {
|
||||
case kServiceSearchRequest: {
|
||||
const auto pattern = ParsePattern(p, n);
|
||||
if (!pattern || n < 2) return Error(tid, kErrInvalidSyntax);
|
||||
const uint16_t maxCount = Be16(p);
|
||||
if (!ParseContinuation(p + 2, n - 2)) return Error(tid, kErrInvalidContinuation);
|
||||
Bytes handles;
|
||||
uint16_t found = 0;
|
||||
for (const NamedRecord& record : kRecords) {
|
||||
if (found < maxCount && RecordMatches(record.get(), *pattern)) {
|
||||
PutBe32(handles, record.handle);
|
||||
++found;
|
||||
}
|
||||
}
|
||||
Bytes params;
|
||||
PutBe16(params, found);
|
||||
PutBe16(params, found);
|
||||
params.insert(params.end(), handles.begin(), handles.end());
|
||||
params.push_back(0);
|
||||
return Pdu(kServiceSearchResponse, tid, params);
|
||||
}
|
||||
case kServiceAttributeRequest: {
|
||||
if (n < 6) return Error(tid, kErrInvalidSyntax);
|
||||
const uint32_t handle = Be32(p);
|
||||
const uint16_t maxBytes = Be16(p + 4);
|
||||
p += 6;
|
||||
n -= 6;
|
||||
const auto ranges = ParseAttributeIds(p, n);
|
||||
if (!ranges || maxBytes < 7) return Error(tid, kErrInvalidSyntax);
|
||||
const auto record = std::find_if(kRecords.begin(), kRecords.end(),
|
||||
[&](const NamedRecord& r) { return r.handle == handle; });
|
||||
if (record == kRecords.end()) return Error(tid, kErrInvalidHandle);
|
||||
const Bytes full = AttributeList(record->get(), *ranges);
|
||||
const auto offset = ParseContinuation(p, n);
|
||||
if (!offset || *offset > full.size()) return Error(tid, kErrInvalidContinuation);
|
||||
return Chunked(kServiceAttributeResponse, tid, full, *offset, maxBytes, maxResponse);
|
||||
}
|
||||
case kServiceSearchAttributeRequest: {
|
||||
const auto pattern = ParsePattern(p, n);
|
||||
if (!pattern || n < 2) return Error(tid, kErrInvalidSyntax);
|
||||
const uint16_t maxBytes = Be16(p);
|
||||
p += 2;
|
||||
n -= 2;
|
||||
const auto ranges = ParseAttributeIds(p, n);
|
||||
if (!ranges || maxBytes < 7) return Error(tid, kErrInvalidSyntax);
|
||||
Bytes lists;
|
||||
for (const NamedRecord& record : kRecords) {
|
||||
if (RecordMatches(record.get(), *pattern)) {
|
||||
const Bytes list = AttributeList(record.get(), *ranges);
|
||||
lists.insert(lists.end(), list.begin(), list.end());
|
||||
}
|
||||
}
|
||||
Bytes full = WithLength(6, lists.size());
|
||||
full.insert(full.end(), lists.begin(), lists.end());
|
||||
const auto offset = ParseContinuation(p, n);
|
||||
if (!offset || *offset > full.size()) return Error(tid, kErrInvalidContinuation);
|
||||
return Chunked(kServiceSearchAttributeResponse, tid, full, *offset, maxBytes, maxResponse);
|
||||
}
|
||||
default:
|
||||
return Error(tid, kErrInvalidSyntax);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Bluetooth::Sdp
|
||||
@@ -0,0 +1,126 @@
|
||||
// SYSCONF items behind the Wii Remote stack. The runtime never parses a
|
||||
// SYSCONF (SCCheckStatus is HLE'd), so these accessors serve the Bluetooth items
|
||||
// directly: BT.DINF registers the four virtual remotes of the emulated
|
||||
// controller, and the console settings come from Config.toml.
|
||||
|
||||
#include "bluetooth/bt_controller.h"
|
||||
#include "hle_stubs.h"
|
||||
#include "memory.h"
|
||||
#include "runtime_config.h"
|
||||
#include "runtime_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace BluetoothSysconf {
|
||||
|
||||
constexpr uint32_t kDeviceInfoSize = 0x461;
|
||||
constexpr uint32_t kCmpDeviceInfoSize = 0x205;
|
||||
constexpr uint32_t kDeviceEntrySize = 0x46;
|
||||
// [0..9] registered remotes, [10..13] the remote last connected on each channel.
|
||||
constexpr uint32_t kActiveEntriesStart = 10;
|
||||
constexpr const char kRemoteName[] = "Nintendo RVL-CNT-01";
|
||||
|
||||
void Fill(uint32_t buffer, uint32_t size, uint8_t value) {
|
||||
std::memset(Memory::GetPointer(buffer, size), value, size);
|
||||
}
|
||||
|
||||
void WriteEntry(uint32_t buffer, uint32_t index, size_t slot) {
|
||||
const uint32_t entry = buffer + 1 + index * kDeviceEntrySize;
|
||||
// SYSCONF keeps addresses most significant byte first.
|
||||
const Bluetooth::BdAddr addr = Bluetooth::Controller::RemoteAddress(slot);
|
||||
for (size_t i = 0; i < addr.size(); ++i) {
|
||||
Memory::Write8(entry + static_cast<uint32_t>(i), addr[addr.size() - 1 - i]);
|
||||
}
|
||||
std::memcpy(Memory::GetPointer(entry + 6, sizeof(kRemoteName)), kRemoteName, sizeof(kRemoteName));
|
||||
}
|
||||
|
||||
void SetReturn(CpuContext* ctx, uint32_t value) {
|
||||
ctx->gpr[3] = value;
|
||||
}
|
||||
|
||||
} // namespace BluetoothSysconf
|
||||
|
||||
extern "C" void SCGetBtDeviceInfoArray_HLE(CpuContext* ctx) {
|
||||
const uint32_t buffer = ctx->gpr[3];
|
||||
try {
|
||||
BluetoothSysconf::Fill(buffer, BluetoothSysconf::kDeviceInfoSize, 0);
|
||||
Memory::Write8(buffer, static_cast<uint8_t>(Bluetooth::kRemoteSlots));
|
||||
for (size_t slot = 0; slot < Bluetooth::kRemoteSlots; ++slot) {
|
||||
BluetoothSysconf::WriteEntry(buffer, static_cast<uint32_t>(slot), slot);
|
||||
BluetoothSysconf::WriteEntry(buffer, BluetoothSysconf::kActiveEntriesStart + static_cast<uint32_t>(slot), slot);
|
||||
}
|
||||
BluetoothSysconf::SetReturn(ctx, 1);
|
||||
} catch (const Memory::AccessViolation& e) {
|
||||
LogMemoryError(RT_TAG_HLE, "SCGetBtDeviceInfoArray", e);
|
||||
BluetoothSysconf::SetReturn(ctx, 0);
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1ED0, SCGetBtDeviceInfoArray_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
// WUD writes the list back after reconciling it with the controller's link
|
||||
// keys, and WPAD whenever a channel's remote changes. The virtual remotes are
|
||||
// fixed, so the list is rebuilt on every boot.
|
||||
extern "C" void SCSetBtDeviceInfoArray_HLE(CpuContext* ctx) {
|
||||
BluetoothSysconf::SetReturn(ctx, 1);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1EDC, SCSetBtDeviceInfoArray_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
// Remotes paired with 1+2 on the game's own sync screens. The virtual remotes
|
||||
// are all registered, so the list is always empty.
|
||||
extern "C" void SCGetBtCmpDevInfoArray_HLE(CpuContext* ctx) {
|
||||
try {
|
||||
BluetoothSysconf::Fill(ctx->gpr[3], BluetoothSysconf::kCmpDeviceInfoSize, 0);
|
||||
BluetoothSysconf::SetReturn(ctx, 1);
|
||||
} catch (const Memory::AccessViolation& e) {
|
||||
LogMemoryError(RT_TAG_HLE, "SCGetBtCmpDevInfoArray", e);
|
||||
BluetoothSysconf::SetReturn(ctx, 0);
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1EE8, SCGetBtCmpDevInfoArray_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
extern "C" void SCSetBtCmpDevInfoArray_HLE(CpuContext* ctx) {
|
||||
BluetoothSysconf::SetReturn(ctx, 1);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1EF4, SCSetBtCmpDevInfoArray_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
extern "C" void SCGetBtDpdSensibility_HLE(CpuContext* ctx) {
|
||||
BluetoothSysconf::SetReturn(ctx, static_cast<uint32_t>(std::clamp(RuntimeConfigFile::WiiIrSensitivity(), 1, 5)));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1F00, SCGetBtDpdSensibility_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
extern "C" void SCGetWpadMotorMode_HLE(CpuContext* ctx) {
|
||||
BluetoothSysconf::SetReturn(ctx, RuntimeConfigFile::WiiRumbleEnabled() ? 1u : 0u);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1F68, SCGetWpadMotorMode_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
// The Home Menu's vibration switch, saved by WPADSaveConfig.
|
||||
extern "C" void SCSetWpadMotorMode_HLE(CpuContext* ctx) {
|
||||
const bool enabled = (ctx->gpr[3] & 0xFF) == 1;
|
||||
if (enabled != RuntimeConfigFile::WiiRumbleEnabled()) {
|
||||
RuntimeConfigFile::SetWiiRumbleEnabled(enabled);
|
||||
}
|
||||
BluetoothSysconf::SetReturn(ctx, 1);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1FBC, SCSetWpadMotorMode_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
extern "C" void SCGetWpadSensorBarPosition_HLE(CpuContext* ctx) {
|
||||
BluetoothSysconf::SetReturn(ctx, RuntimeConfigFile::WiiSensorBarAbove() ? 1u : 0u);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B1FC4, SCGetWpadSensorBarPosition_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
extern "C" void SCGetWpadSpeakerVolume_HLE(CpuContext* ctx) {
|
||||
BluetoothSysconf::SetReturn(ctx, static_cast<uint32_t>(std::clamp(RuntimeConfigFile::WiiSpeakerVolume(), 0, 0x7F)));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B2018, SCGetWpadSpeakerVolume_HLE, (CpuContext* ctx), (ctx));
|
||||
|
||||
// The Home Menu's speaker volume, saved by WPADSaveConfig.
|
||||
extern "C" void SCSetWpadSpeakerVolume_HLE(CpuContext* ctx) {
|
||||
const int32_t volume = std::min<int32_t>(static_cast<int32_t>(ctx->gpr[3] & 0xFF), 0x7F);
|
||||
if (volume != RuntimeConfigFile::WiiSpeakerVolume()) {
|
||||
RuntimeConfigFile::SetWiiSpeakerVolume(volume);
|
||||
}
|
||||
BluetoothSysconf::SetReturn(ctx, 1);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801B206C, SCSetWpadSpeakerVolume_HLE, (CpuContext* ctx), (ctx));
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "hle/bluetooth_hle.h"
|
||||
|
||||
#include "bluetooth/bt_controller.h"
|
||||
#include "hle/runtime_parse_helpers.h"
|
||||
#include "memory.h"
|
||||
#include "runtime_log.h"
|
||||
#include "wiimote_host.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
void NandQueueIosCallback(uint32_t callbackPtr, int32_t result, uint32_t callbackArg);
|
||||
|
||||
namespace BluetoothUsb {
|
||||
|
||||
constexpr const char* kDevicePath = "/dev/usb/oh1/57e/305";
|
||||
constexpr int32_t kDeviceFd = 5;
|
||||
constexpr int32_t kIpcSuccess = 0;
|
||||
constexpr int32_t kIpcEinval = -4;
|
||||
|
||||
constexpr uint32_t kCtrlMsg = 0;
|
||||
constexpr uint32_t kBulkMsg = 1;
|
||||
constexpr uint32_t kIntrMsg = 2;
|
||||
|
||||
constexpr uint8_t kEndpointEvents = 0x81;
|
||||
constexpr uint8_t kEndpointAclIn = 0x82;
|
||||
constexpr uint8_t kEndpointAclOut = 0x02;
|
||||
|
||||
// The guest copies each delivered packet into a GKI buffer that its 2 ms
|
||||
// Bluetooth tick frees; a pool it cannot refill drops the packet, so ACL data is
|
||||
// paced. Plenty for four remotes reporting at 200 Hz.
|
||||
constexpr double kAclPacketsPerMs = 3.0;
|
||||
constexpr double kAclBurst = 6.0;
|
||||
|
||||
struct HeldRead {
|
||||
bool active = false;
|
||||
uint32_t buffer = 0;
|
||||
uint32_t length = 0;
|
||||
uint32_t callback = 0;
|
||||
uint32_t callbackArg = 0;
|
||||
};
|
||||
|
||||
struct Device {
|
||||
bool open = false;
|
||||
std::unique_ptr<Bluetooth::Controller> controller;
|
||||
HeldRead eventRead;
|
||||
HeldRead aclRead;
|
||||
double aclTokens = kAclBurst;
|
||||
uint64_t lastRefillUs = 0;
|
||||
};
|
||||
|
||||
Device& Dev() {
|
||||
static Device device;
|
||||
return device;
|
||||
}
|
||||
|
||||
uint64_t NowUs() {
|
||||
using namespace std::chrono;
|
||||
return static_cast<uint64_t>(duration_cast<microseconds>(steady_clock::now().time_since_epoch()).count());
|
||||
}
|
||||
|
||||
Bluetooth::Controller& ControllerFor(Device& device) {
|
||||
if (!device.controller) {
|
||||
device.controller = std::make_unique<Bluetooth::Controller>(Wiimote::Host());
|
||||
}
|
||||
return *device.controller;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ReadGuest(uint32_t address, uint32_t length) {
|
||||
std::vector<uint8_t> data(length);
|
||||
if (length != 0) {
|
||||
std::memcpy(data.data(), Memory::GetPointer(address, length), length);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void Complete(HeldRead& read, const std::vector<uint8_t>& data) {
|
||||
const uint32_t size = static_cast<uint32_t>(std::min<size_t>(data.size(), read.length));
|
||||
if (size < data.size()) {
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: packet of " << data.size() << " bytes truncated to " << size << std::endl;
|
||||
}
|
||||
if (size != 0) {
|
||||
std::memcpy(Memory::GetPointer(read.buffer, size), data.data(), size);
|
||||
}
|
||||
read.active = false;
|
||||
NandQueueIosCallback(read.callback, static_cast<int32_t>(size), read.callbackArg);
|
||||
}
|
||||
|
||||
// Hands queued events and ACL data to whichever reads the guest has armed.
|
||||
bool Deliver(Device& device, uint64_t nowUs) {
|
||||
Bluetooth::Controller& controller = ControllerFor(device);
|
||||
bool delivered = false;
|
||||
std::vector<uint8_t> packet;
|
||||
if (device.eventRead.active && controller.PopEvent(packet)) {
|
||||
Complete(device.eventRead, packet);
|
||||
delivered = true;
|
||||
}
|
||||
if (nowUs > device.lastRefillUs) {
|
||||
device.aclTokens = std::min(kAclBurst, device.aclTokens + (nowUs - device.lastRefillUs) / 1000.0 * kAclPacketsPerMs);
|
||||
device.lastRefillUs = nowUs;
|
||||
}
|
||||
if (device.aclRead.active && device.aclTokens >= 1.0 && controller.PopAcl(packet)) {
|
||||
device.aclTokens -= 1.0;
|
||||
Complete(device.aclRead, packet);
|
||||
delivered = true;
|
||||
}
|
||||
return delivered;
|
||||
}
|
||||
|
||||
bool Hold(HeldRead& read, uint32_t buffer, uint32_t length, uint32_t callback, uint32_t callbackArg) {
|
||||
if (length != 0) {
|
||||
Memory::GetPointer(buffer, length);
|
||||
}
|
||||
if (read.active) {
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: a second read on the same endpoint replaced the first" << std::endl;
|
||||
NandQueueIosCallback(read.callback, kIpcEinval, read.callbackArg);
|
||||
}
|
||||
read = {true, buffer, length, callback, callbackArg};
|
||||
return true;
|
||||
}
|
||||
|
||||
// Executes a transfer. Returns true with `result` when it completed now.
|
||||
bool Transfer(uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr, uint32_t callback,
|
||||
uint32_t callbackArg, bool async, int32_t& result) {
|
||||
Device& device = Dev();
|
||||
Bluetooth::Controller& controller = ControllerFor(device);
|
||||
const uint64_t nowUs = NowUs();
|
||||
try {
|
||||
if (cmd == kCtrlMsg && numIn == 6 && numOut == 1) {
|
||||
const auto requestType = RuntimeHle::ReadIoVector(vectorPtr, 0);
|
||||
const auto lengthVector = RuntimeHle::ReadIoVector(vectorPtr, 4);
|
||||
const auto data = RuntimeHle::ReadIoVector(vectorPtr, 6);
|
||||
const uint8_t bmRequestType = Memory::Read8(requestType.address);
|
||||
// wLength is little-endian in the request block.
|
||||
const uint16_t wLength = static_cast<uint16_t>(Memory::Read8(lengthVector.address) |
|
||||
(Memory::Read8(lengthVector.address + 1) << 8));
|
||||
if (bmRequestType != 0x20 || wLength > data.size) {
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: unexpected control transfer type 0x" << std::hex
|
||||
<< static_cast<int>(bmRequestType) << std::dec << std::endl;
|
||||
result = kIpcEinval;
|
||||
return true;
|
||||
}
|
||||
const std::vector<uint8_t> command = ReadGuest(data.address, wLength);
|
||||
controller.ExecuteCommand(command.data(), command.size(), nowUs);
|
||||
result = wLength;
|
||||
return true;
|
||||
}
|
||||
if ((cmd == kBulkMsg || cmd == kIntrMsg) && numIn == 2 && numOut == 1) {
|
||||
const auto endpointVector = RuntimeHle::ReadIoVector(vectorPtr, 0);
|
||||
const auto lengthVector = RuntimeHle::ReadIoVector(vectorPtr, 1);
|
||||
const auto data = RuntimeHle::ReadIoVector(vectorPtr, 2);
|
||||
const uint8_t endpoint = Memory::Read8(endpointVector.address);
|
||||
// Bulk / interrupt lengths are big-endian.
|
||||
const uint16_t length = Memory::Read16(lengthVector.address);
|
||||
if (length > data.size) {
|
||||
result = kIpcEinval;
|
||||
return true;
|
||||
}
|
||||
if (cmd == kBulkMsg && endpoint == kEndpointAclOut) {
|
||||
const std::vector<uint8_t> packet = ReadGuest(data.address, length);
|
||||
controller.ReceiveAcl(packet.data(), packet.size(), nowUs);
|
||||
result = length;
|
||||
return true;
|
||||
}
|
||||
if (async && cmd == kBulkMsg && endpoint == kEndpointAclIn) {
|
||||
Hold(device.aclRead, data.address, length, callback, callbackArg);
|
||||
Deliver(device, nowUs);
|
||||
return false;
|
||||
}
|
||||
if (async && cmd == kIntrMsg && endpoint == kEndpointEvents) {
|
||||
Hold(device.eventRead, data.address, length, callback, callbackArg);
|
||||
Deliver(device, nowUs);
|
||||
return false;
|
||||
}
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: unsupported transfer cmd " << cmd << " endpoint 0x" << std::hex
|
||||
<< static_cast<int>(endpoint) << std::dec << (async ? "" : " (sync)") << std::endl;
|
||||
result = kIpcEinval;
|
||||
return true;
|
||||
}
|
||||
} catch (const Memory::AccessViolation& e) {
|
||||
LogMemoryError(RT_TAG_HLE, "Bluetooth transfer", e);
|
||||
result = kIpcEinval;
|
||||
return true;
|
||||
}
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: unsupported ioctlv " << cmd << " (" << numIn << " in, " << numOut << " out)"
|
||||
<< std::endl;
|
||||
result = kIpcEinval;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace BluetoothUsb
|
||||
|
||||
int32_t Bluetooth_HLE_OpenDevice(const char* path) {
|
||||
if (path == nullptr || std::strcmp(path, BluetoothUsb::kDevicePath) != 0) {
|
||||
return 0;
|
||||
}
|
||||
BluetoothUsb::Device& device = BluetoothUsb::Dev();
|
||||
if (device.open) {
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: device opened twice" << std::endl;
|
||||
}
|
||||
device.open = true;
|
||||
device.eventRead = {};
|
||||
device.aclRead = {};
|
||||
device.aclTokens = BluetoothUsb::kAclBurst;
|
||||
device.lastRefillUs = BluetoothUsb::NowUs();
|
||||
BluetoothUsb::ControllerFor(device).Open(device.lastRefillUs);
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: " << BluetoothUsb::kDevicePath << " opened" << std::endl;
|
||||
return BluetoothUsb::kDeviceFd;
|
||||
}
|
||||
|
||||
bool Bluetooth_HLE_IsFd(uint32_t fd) {
|
||||
return static_cast<int32_t>(fd) == BluetoothUsb::kDeviceFd;
|
||||
}
|
||||
|
||||
int32_t Bluetooth_HLE_Close(uint32_t fd) {
|
||||
if (!Bluetooth_HLE_IsFd(fd)) {
|
||||
return BluetoothUsb::kIpcEinval;
|
||||
}
|
||||
BluetoothUsb::Device& device = BluetoothUsb::Dev();
|
||||
// IOS cancels the transfers still queued on a device it closes.
|
||||
for (BluetoothUsb::HeldRead* read : {&device.eventRead, &device.aclRead}) {
|
||||
if (read->active) {
|
||||
read->active = false;
|
||||
NandQueueIosCallback(read->callback, BluetoothUsb::kIpcEinval, read->callbackArg);
|
||||
}
|
||||
}
|
||||
BluetoothUsb::ControllerFor(device).Close();
|
||||
device.open = false;
|
||||
RT_LOG(RT_TAG_HLE) << "Bluetooth: " << BluetoothUsb::kDevicePath << " closed" << std::endl;
|
||||
return BluetoothUsb::kIpcSuccess;
|
||||
}
|
||||
|
||||
bool Bluetooth_HLE_IoctlvAsync(uint32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr,
|
||||
uint32_t callback, uint32_t callbackArg, int32_t& result) {
|
||||
(void)fd;
|
||||
if (!BluetoothUsb::Dev().open) {
|
||||
result = BluetoothUsb::kIpcEinval;
|
||||
return true;
|
||||
}
|
||||
return BluetoothUsb::Transfer(cmd, numIn, numOut, vectorPtr, callback, callbackArg, true, result);
|
||||
}
|
||||
|
||||
int32_t Bluetooth_HLE_Ioctlv(uint32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr) {
|
||||
(void)fd;
|
||||
if (!BluetoothUsb::Dev().open) {
|
||||
return BluetoothUsb::kIpcEinval;
|
||||
}
|
||||
int32_t result = BluetoothUsb::kIpcEinval;
|
||||
BluetoothUsb::Transfer(cmd, numIn, numOut, vectorPtr, 0, 0, false, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Bluetooth_HLE_ProcessCompletions(CpuContext* cpu) {
|
||||
(void)cpu;
|
||||
BluetoothUsb::Device& device = BluetoothUsb::Dev();
|
||||
if (!device.open) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t nowUs = BluetoothUsb::NowUs();
|
||||
try {
|
||||
BluetoothUsb::ControllerFor(device).Tick(nowUs);
|
||||
return BluetoothUsb::Deliver(device, nowUs);
|
||||
} catch (const Memory::AccessViolation& e) {
|
||||
LogMemoryError(RT_TAG_HLE, "Bluetooth delivery", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -71,11 +71,14 @@ void InvalidateEfbCopyDestinationsForRange(uint32_t addr, uint32_t size) {
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetDispCopySrc_8016f438(uint32_t l, uint32_t t, uint32_t w, uint32_t h) {
|
||||
GX_DEFER_CALL(GX__SetDispCopySrc_8016f438, l, t, w, h);
|
||||
GXSetDispCopySrc((u16)l, (u16)t, (u16)w, (u16)h);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f438, GX__SetDispCopySrc_8016f438, (uint32_t l, uint32_t t, uint32_t w, uint32_t h), (l, t, w, h));
|
||||
|
||||
extern "C" void GX__SetDispCopyDst_8016f4b8(uint32_t w, uint32_t h) { GXSetDispCopyDst((u16)w, (u16)h); }
|
||||
extern "C" void GX__SetDispCopyDst_8016f4b8(uint32_t w, uint32_t h) {
|
||||
GxWorker::Run([w, h] { GXSetDispCopyDst((u16)w, (u16)h); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f4b8, GX__SetDispCopyDst_8016f4b8, (uint32_t w, uint32_t h), (w, h));
|
||||
|
||||
// ============================================================================
|
||||
@@ -83,14 +86,14 @@ PPC_NATIVE_OVERRIDE_VOID(8016f4b8, GX__SetDispCopyDst_8016f4b8, (uint32_t w, uin
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTexCopySrc_8016f478(uint32_t l, uint32_t t, uint32_t w, uint32_t h) {
|
||||
GXSetTexCopySrc((u16)l, (u16)t, (u16)w, (u16)h);
|
||||
GxWorker::Run([l, t, w, h] { GXSetTexCopySrc((u16)l, (u16)t, (u16)w, (u16)h); });
|
||||
g_texCopyState.srcLeft=(u16)l; g_texCopyState.srcTop=(u16)t;
|
||||
g_texCopyState.srcWidth=(u16)w; g_texCopyState.srcHeight=(u16)h;
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f478, GX__SetTexCopySrc_8016f478, (uint32_t l, uint32_t t, uint32_t w, uint32_t h), (l, t, w, h));
|
||||
|
||||
extern "C" void GX__SetTexCopyDst_8016f4dc(uint32_t w, uint32_t h, uint32_t f, uint32_t m) {
|
||||
GXSetTexCopyDst((u16)w, (u16)h, (GXTexFmt)f, (GXBool)m);
|
||||
GxWorker::Run([w, h, f, m] { GXSetTexCopyDst((u16)w, (u16)h, (GXTexFmt)f, (GXBool)m); });
|
||||
g_texCopyState.dstWidth=(u16)w; g_texCopyState.dstHeight=(u16)h;
|
||||
g_texCopyState.dstFormat=f; g_texCopyState.dstMipmap=m;
|
||||
}
|
||||
@@ -100,11 +103,19 @@ extern "C" void GX__SetCopyFilter_8016fa40(uint32_t aa, uint32_t spa, uint32_t v
|
||||
uint8_t sp[12][2]={}, vfb[7]={};
|
||||
if(spa) std::memcpy(sp, GuestToHostPtr(spa, 24), 24);
|
||||
if(vfa) std::memcpy(vfb, GuestToHostPtr(vfa, 7), 7);
|
||||
GXSetCopyFilter((GXBool)aa, sp, (GXBool)vf, vfb);
|
||||
std::array<uint8_t, 24> spCopy{};
|
||||
std::array<uint8_t, 7> vfCopy{};
|
||||
std::memcpy(spCopy.data(), sp, 24);
|
||||
std::memcpy(vfCopy.data(), vfb, 7);
|
||||
GxWorker::Run([aa, vf, spCopy, vfCopy]() mutable {
|
||||
GXSetCopyFilter((GXBool)aa, reinterpret_cast<u8(*)[2]>(spCopy.data()), (GXBool)vf, vfCopy.data());
|
||||
});
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016fa40, GX__SetCopyFilter_8016fa40, (uint32_t aa, uint32_t spa, uint32_t vf, uint32_t vfa), (aa, spa, vf, vfa));
|
||||
|
||||
extern "C" void GX__SetDispCopyGamma_8016fc24(uint32_t g) { GXSetDispCopyGamma((GXGamma)g); }
|
||||
extern "C" void GX__SetDispCopyGamma_8016fc24(uint32_t g) {
|
||||
GxWorker::Run([g] { GXSetDispCopyGamma((GXGamma)g); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016fc24, GX__SetDispCopyGamma_8016fc24, (uint32_t g), (g));
|
||||
|
||||
// ============================================================================
|
||||
@@ -113,18 +124,24 @@ PPC_NATIVE_OVERRIDE_VOID(8016fc24, GX__SetDispCopyGamma_8016fc24, (uint32_t g),
|
||||
|
||||
extern "C" void GX__CopyDisp_8016fc38(uint32_t da, uint32_t c) {
|
||||
EnsureAuroraFrameActive();
|
||||
// GX copies are FIFO-ordered on hardware. Drain submitted draws before
|
||||
// resolving the EFB so high-level copies see the same contents.
|
||||
GXDrawDone();
|
||||
GXCopyDisp(GuestToHostPtr(da), (GXBool)c);
|
||||
// No second GXDrawDone here: the frame-worker wait below is for the DONE
|
||||
// phase, which strictly subsumes the drain this call would perform.
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
// The worker drains like the inline path; EndAuroraFrame joins it before the next frame.
|
||||
GxWorker::Submit([da, c] {
|
||||
GXDrawDone();
|
||||
GXCopyDisp(GuestToHostPtr(da), (GXBool)c);
|
||||
});
|
||||
} else {
|
||||
// GX copies are FIFO-ordered on hardware. Drain submitted draws before
|
||||
// resolving the EFB so high-level copies see the same contents.
|
||||
GXDrawDone();
|
||||
GXCopyDisp(GuestToHostPtr(da), (GXBool)c);
|
||||
// Present immediately so post-copy draws don't leak into this frame. Join at the DONE phase
|
||||
// (not the cheaper SEALED phase GXDrawDone waits for) because ImGui's draw lists, owned by
|
||||
// Aurora's render worker, replay during encode; aurora_end_frame would join here anyway.
|
||||
aurora_wait_for_frame_worker();
|
||||
}
|
||||
++g_gxFrameCount;
|
||||
VI_HLE_SetXfbReady(da);
|
||||
// Present immediately so post-copy draws don't leak into this frame. Join at the DONE phase
|
||||
// (not the cheaper SEALED phase GXDrawDone waits for) because ImGui's draw lists, owned by
|
||||
// Aurora's render worker, replay during encode; aurora_end_frame would join here anyway.
|
||||
aurora_wait_for_frame_worker();
|
||||
settings_overlay::Draw();
|
||||
// Seal, pace to the VI retrace boundary (Aurora renders the sealed frame
|
||||
// during the wait), and pre-warm the next frame.
|
||||
@@ -135,7 +152,9 @@ PPC_NATIVE_OVERRIDE_VOID(8016fc38, GX__CopyDisp_8016fc38, (uint32_t da, uint32_t
|
||||
|
||||
|
||||
extern "C" void GX__CopyTex_8016fd74(uint32_t da, uint32_t c) {
|
||||
// Begin the frame before going inline so a deferred begin reaches the worker first.
|
||||
EnsureAuroraFrameActive();
|
||||
GX_SYNC_INLINE();
|
||||
// Match GX FIFO ordering: texture copies observe all prior draws.
|
||||
GXDrawDone();
|
||||
const uint16_t rawSrcLeft = g_texCopyState.srcLeft;
|
||||
|
||||
@@ -89,39 +89,42 @@ static inline uint32_t ReadModulatedLytColor(uint32_t colorAddr, int index, uint
|
||||
static_cast<uint32_t>(a);
|
||||
}
|
||||
|
||||
static inline void AppendLytTexCoords(uint8_t* packet, uint32_t& pos, uint32_t texCoordAddr,
|
||||
int texCoordCount, uint32_t cornerOffset) {
|
||||
if (texCoordCount <= 0) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < texCoordCount; ++i) {
|
||||
const uint32_t coordAddr = texCoordAddr + static_cast<uint32_t>(i * 32) + cornerOffset;
|
||||
BigEndian::AppendFloat32(packet, pos, Memory::ReadFloat32(coordAddr));
|
||||
BigEndian::AppendFloat32(packet, pos, Memory::ReadFloat32(coordAddr + 4));
|
||||
// An nw4r::lyt quad as read on the game thread: texcoords are 8 sets of 4 corners x (s, t).
|
||||
struct LytQuad {
|
||||
float x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
||||
int texCoordCount = 0;
|
||||
std::array<float, 8 * 4 * 2> texCoords{};
|
||||
bool hasColors = false;
|
||||
std::array<uint32_t, 4> colors{};
|
||||
};
|
||||
|
||||
static inline void AppendLytTexCoords(uint8_t* packet, uint32_t& pos, const LytQuad& quad,
|
||||
uint32_t cornerOffset) {
|
||||
for (int i = 0; i < quad.texCoordCount; ++i) {
|
||||
const size_t at = static_cast<size_t>(i) * 8u + cornerOffset / 4u;
|
||||
BigEndian::AppendFloat32(packet, pos, quad.texCoords[at]);
|
||||
BigEndian::AppendFloat32(packet, pos, quad.texCoords[at + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void AppendLytQuadVertex(uint8_t* packet, uint32_t& pos, float x, float y,
|
||||
uint32_t texCoordAddr, int texCoordCount,
|
||||
uint32_t cornerOffset, const uint32_t* colors, int colorIndex) {
|
||||
static inline void AppendLytQuadVertex(uint8_t* packet, uint32_t& pos, float x, float y, const LytQuad& quad,
|
||||
uint32_t cornerOffset, int colorIndex) {
|
||||
BigEndian::AppendFloat32(packet, pos, x);
|
||||
BigEndian::AppendFloat32(packet, pos, y);
|
||||
if (colors) {
|
||||
BigEndian::Append32(packet, pos, colors[colorIndex]);
|
||||
if (quad.hasColors) {
|
||||
BigEndian::Append32(packet, pos, quad.colors[colorIndex]);
|
||||
}
|
||||
AppendLytTexCoords(packet, pos, texCoordAddr, texCoordCount, cornerOffset);
|
||||
AppendLytTexCoords(packet, pos, quad, cornerOffset);
|
||||
}
|
||||
|
||||
// The four corners of an nw4r::lyt quad, in the order GX_QUADS expects, with
|
||||
// each corner's texcoord byte offset and colour index. The winding order is
|
||||
// load-bearing and both emit paths below share this one copy of it.
|
||||
static inline void AppendLytQuadVertices(uint8_t* packet, uint32_t& pos, float x0, float y0,
|
||||
float x1, float y1, uint32_t texCoordAddr,
|
||||
int texCoordCount, const uint32_t* colors) {
|
||||
AppendLytQuadVertex(packet, pos, x0, y0, texCoordAddr, texCoordCount, 0, colors, 0);
|
||||
AppendLytQuadVertex(packet, pos, x1, y0, texCoordAddr, texCoordCount, 8, colors, 1);
|
||||
AppendLytQuadVertex(packet, pos, x1, y1, texCoordAddr, texCoordCount, 24, colors, 3);
|
||||
AppendLytQuadVertex(packet, pos, x0, y1, texCoordAddr, texCoordCount, 16, colors, 2);
|
||||
static inline void AppendLytQuadVertices(uint8_t* packet, uint32_t& pos, const LytQuad& quad) {
|
||||
AppendLytQuadVertex(packet, pos, quad.x0, quad.y0, quad, 0, 0);
|
||||
AppendLytQuadVertex(packet, pos, quad.x1, quad.y0, quad, 8, 1);
|
||||
AppendLytQuadVertex(packet, pos, quad.x1, quad.y1, quad, 24, 3);
|
||||
AppendLytQuadVertex(packet, pos, quad.x0, quad.y1, quad, 16, 2);
|
||||
}
|
||||
|
||||
static bool CanSubmitLytDrawDirect(int texCoordCount, const uint32_t* colors) {
|
||||
@@ -175,9 +178,8 @@ static bool CanSubmitLytDrawDirect(int texCoordCount, const uint32_t* colors) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SubmitLytDrawDirect(float x0, float y0, float x1, float y1, int texCoordCount,
|
||||
uint32_t texCoordAddr, const uint32_t* colors) {
|
||||
if (!CanSubmitLytDrawDirect(texCoordCount, colors)) {
|
||||
static bool SubmitLytDrawDirect(const LytQuad& quad) {
|
||||
if (!CanSubmitLytDrawDirect(quad.texCoordCount, quad.hasColors ? quad.colors.data() : nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,7 +195,7 @@ static bool SubmitLytDrawDirect(float x0, float y0, float x1, float y1, int texC
|
||||
|
||||
std::array<uint8_t, 4u * (8u + 4u + 8u * 8u)> vertices{};
|
||||
uint32_t pos = 0;
|
||||
AppendLytQuadVertices(vertices.data(), pos, x0, y0, x1, y1, texCoordAddr, texCoordCount, colors);
|
||||
AppendLytQuadVertices(vertices.data(), pos, quad);
|
||||
|
||||
if (!aurora::gx::fifo::submit_raw_draw(GX_QUADS, GX_VTXFMT0, vertices.data(), 4, pos)) {
|
||||
return false;
|
||||
@@ -204,23 +206,8 @@ static bool SubmitLytDrawDirect(float x0, float y0, float x1, float y1, int texC
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void EmitLytDrawQuad(uint32_t posAddr, uint32_t sizeAddr, int texCoordCount,
|
||||
uint32_t texCoordAddr, const uint32_t* colors) {
|
||||
const float x0 = Memory::ReadFloat32(posAddr);
|
||||
const float y0 = Memory::ReadFloat32(posAddr + 4);
|
||||
// The casts pin the arithmetic to PPC single precision regardless of the
|
||||
// host's FLT_EVAL_METHOD.
|
||||
const float x1 = static_cast<float>(x0 + Memory::ReadFloat32(sizeAddr));
|
||||
const float y1 = static_cast<float>(y0 - Memory::ReadFloat32(sizeAddr + 4));
|
||||
|
||||
if (SubmitLytDrawDirect(x0, y0, x1, y1, texCoordCount, texCoordAddr, colors)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// GX has exactly 8 texture coordinates, so nw4r::lyt cannot ask for more.
|
||||
// The fixed packet buffer below is sized for that maximum; bail rather than
|
||||
// overrun it if the guest ever hands us something else.
|
||||
if (texCoordCount > 8) {
|
||||
static void EmitLytQuad(const LytQuad& quad) {
|
||||
if (SubmitLytDrawDirect(quad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,10 +215,39 @@ static inline void EmitLytDrawQuad(uint32_t posAddr, uint32_t sizeAddr, int texC
|
||||
uint32_t pos = 0;
|
||||
packet[pos++] = GX_DRAW_QUADS_CMD | GX_VTXFMT0;
|
||||
BigEndian::Append16(packet.data(), pos, 4);
|
||||
AppendLytQuadVertices(packet.data(), pos, x0, y0, x1, y1, texCoordAddr, texCoordCount, colors);
|
||||
AppendLytQuadVertices(packet.data(), pos, quad);
|
||||
SubmitLytDrawPacket(packet.data(), pos);
|
||||
}
|
||||
|
||||
static inline void EmitLytDrawQuad(uint32_t posAddr, uint32_t sizeAddr, int texCoordCount,
|
||||
uint32_t texCoordAddr, const uint32_t* colors) {
|
||||
// GX has exactly 8 texture coordinates, so nw4r::lyt cannot ask for more; the packet buffer
|
||||
// is sized for that maximum.
|
||||
if (texCoordCount > 8) {
|
||||
return;
|
||||
}
|
||||
LytQuad quad;
|
||||
quad.x0 = Memory::ReadFloat32(posAddr);
|
||||
quad.y0 = Memory::ReadFloat32(posAddr + 4);
|
||||
// The casts pin the arithmetic to PPC single precision regardless of the
|
||||
// host's FLT_EVAL_METHOD.
|
||||
quad.x1 = static_cast<float>(quad.x0 + Memory::ReadFloat32(sizeAddr));
|
||||
quad.y1 = static_cast<float>(quad.y0 - Memory::ReadFloat32(sizeAddr + 4));
|
||||
quad.texCoordCount = std::max(texCoordCount, 0);
|
||||
for (int i = 0; i < quad.texCoordCount; ++i) {
|
||||
for (uint32_t f = 0; f < 8; ++f) {
|
||||
quad.texCoords[static_cast<size_t>(i) * 8u + f] =
|
||||
Memory::ReadFloat32(texCoordAddr + static_cast<uint32_t>(i * 32) + f * 4u);
|
||||
}
|
||||
}
|
||||
if (colors != nullptr) {
|
||||
quad.hasColors = true;
|
||||
std::copy(colors, colors + 4, quad.colors.begin());
|
||||
}
|
||||
EnsureAuroraFrameActive();
|
||||
GxWorker::Run([quad] { EmitLytQuad(quad); });
|
||||
}
|
||||
|
||||
static uint32_t SubmitDLVertex(const uint8_t* ptr, GXVtxFmt vtxfmt, const GXAttrType* sourceVtxDesc) {
|
||||
uint32_t offset = 0;
|
||||
for (int attr = 0; attr < 26; ++attr) {
|
||||
@@ -1284,6 +1300,12 @@ extern "C" void GxNotifyDisplayListMemoryWrite(uint32_t addr, uint32_t size) {
|
||||
|
||||
extern "C" void GX__CallDisplayList_80172f64(uint32_t listAddr, uint32_t nbytes) {
|
||||
if (nbytes == 0 || listAddr == 0) return;
|
||||
// List bytes and the arrays it indexes are GPU-side reads, so they are read when the worker runs.
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameActive();
|
||||
GxWorker::Submit([=] { GX__CallDisplayList_80172f64(listAddr, nbytes); });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uint8_t* list = static_cast<const uint8_t*>(GuestToHostPtr(listAddr, nbytes));
|
||||
if (!list) return;
|
||||
|
||||
@@ -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;
|
||||
@@ -81,14 +81,20 @@ extern "C" void EGG__LightTexture__SetupTevFinish_HLE_8022e2bc(CpuContext* ctx)
|
||||
if (remainder > 0) {
|
||||
const GXColor black{0, 0, 0, 255};
|
||||
while (remainder < stageCount) {
|
||||
GXSetTevColor(static_cast<GXTevRegID>(remainder + 1), black);
|
||||
GXSetTevKColor(static_cast<GXTevKColorID>(remainder), black);
|
||||
GxWorker::Run([remainder, black] {
|
||||
GXSetTevColor(static_cast<GXTevRegID>(remainder + 1), black);
|
||||
GXSetTevKColor(static_cast<GXTevKColorID>(remainder), black);
|
||||
});
|
||||
++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);
|
||||
|
||||
@@ -115,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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -775,11 +775,25 @@ static bool WriteDisplayListBurst(const uint8_t* data, uint32_t sizeBytes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void FifoWriteBurstImpl(const uint8_t* data, uint32_t sizeBytes);
|
||||
|
||||
extern "C" void GX_HLE_FIFO_WriteBurst(const uint8_t* data, uint32_t sizeBytes) {
|
||||
if (data == nullptr || sizeBytes == 0) {
|
||||
return;
|
||||
}
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameForFifo();
|
||||
GxWorker::FifoBurst(data, sizeBytes);
|
||||
return;
|
||||
}
|
||||
FifoWriteBurstImpl(data, sizeBytes);
|
||||
}
|
||||
|
||||
void GxWorkerReplayFifoWrite(uint32_t value, uint32_t sizeBytes) { HleFifoWrite(value, sizeBytes); }
|
||||
|
||||
void GxWorkerReplayFifoBurst(const uint8_t* data, uint32_t sizeBytes) { FifoWriteBurstImpl(data, sizeBytes); }
|
||||
|
||||
static void FifoWriteBurstImpl(const uint8_t* data, uint32_t sizeBytes) {
|
||||
if (IsDisplayListActive() && WriteDisplayListBurst(data, sizeBytes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,8 +87,54 @@ void BeginNextAuroraFrameWithRetry(std::chrono::milliseconds timeout) {
|
||||
++s_beginExpiredLogCount;
|
||||
}
|
||||
|
||||
void EnsureAuroraFrameForFifo() {
|
||||
// The game thread can't see where draws start in recorded FIFO bytes, so any write may begin the
|
||||
// frame. A failed begin is only retried once the next frame is presented.
|
||||
static int s_attemptedFrame = -1;
|
||||
if (g_auroraFrameActive.load(std::memory_order_acquire) || s_attemptedFrame == g_gxFrameCount) {
|
||||
return;
|
||||
}
|
||||
s_attemptedFrame = g_gxFrameCount;
|
||||
UpdateAuroraAndProcessEvents();
|
||||
if (BeginAuroraFrame()) {
|
||||
g_auroraFrameActive.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureAuroraFrameActive() {
|
||||
// The game thread begins frames before it records work for the GX worker.
|
||||
if (GxWorker::OnWorker()) {
|
||||
return;
|
||||
}
|
||||
if (!g_auroraFrameActive.load(std::memory_order_acquire)) {
|
||||
BeginNextAuroraFrameWithRetry();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Whether the GX half of the current frame has begun, on either path. Inline frame calls only run
|
||||
// after a sync, so the ring orders every access.
|
||||
bool g_frameGxBegun = false;
|
||||
}
|
||||
|
||||
void SetAuroraFrameGxBegun(bool begun) { g_frameGxBegun = begun; }
|
||||
|
||||
void GxWorkerBeginFrame() {
|
||||
// gfx::begin_frame can briefly fail to map a staging buffer; retry like the inline path does.
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while (!(g_frameGxBegun = aurora_begin_frame_gx()) && std::chrono::steady_clock::now() < deadline) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
if (!g_frameGxBegun) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker could not begin an aurora frame\n");
|
||||
}
|
||||
}
|
||||
|
||||
void GxWorkerEndFrame(AuroraImguiFrame* imgui) {
|
||||
if (g_frameGxBegun) {
|
||||
aurora_end_frame_gx(imgui);
|
||||
} else {
|
||||
aurora_release_imgui_frame(imgui);
|
||||
}
|
||||
g_frameGxBegun = false;
|
||||
}
|
||||
|
||||
@@ -5,22 +5,25 @@
|
||||
// Indirect Texture Stages
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetNumIndStages_80171b38(uint32_t n) { GXSetNumIndStages((u8)n); }
|
||||
extern "C" void GX__SetNumIndStages_80171b38(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumIndStages_80171b38, n); GXSetNumIndStages((u8)n); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171b38, GX__SetNumIndStages_80171b38, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetIndTexOrder_80171a6c(uint32_t s, uint32_t c, uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetIndTexOrder_80171a6c, s, c, m);
|
||||
GXSetIndTexOrder((GXIndTexStageID)s, (GXTexCoordID)(c==0xFFu?0:c), (GXTexMapID)(m==0xFFu?0:m));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171a6c, GX__SetIndTexOrder_80171a6c, (uint32_t s, uint32_t c, uint32_t m), (s, c, m));
|
||||
|
||||
extern "C" void GX__SetIndTexCoordScale_80171968(uint32_t s, uint32_t ss, uint32_t ts) {
|
||||
GX_DEFER_CALL(GX__SetIndTexCoordScale_80171968, s, ss, ts);
|
||||
GXSetIndTexCoordScale((GXIndTexStageID)s, (GXIndTexScale)ss, (GXIndTexScale)ts);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171968, GX__SetIndTexCoordScale_80171968, (uint32_t s, uint32_t ss, uint32_t ts), (s, ss, ts));
|
||||
|
||||
extern "C" void GX__SetIndTexMtx_80171814(uint32_t id, uint32_t ma, uint32_t se) {
|
||||
float m[6]; for(int i=0; i<6; ++i) m[i]=Memory::ReadFloat32(ma+i*4);
|
||||
GXSetIndTexMtx((GXIndTexMtxID)id, m, (s8)se);
|
||||
GxWorker::Run([id, se, mtx = std::to_array(m)]() mutable { GXSetIndTexMtx((GXIndTexMtxID)id, mtx.data(), (s8)se); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171814, GX__SetIndTexMtx_80171814, (uint32_t id, uint32_t ma, uint32_t se), (id, ma, se));
|
||||
|
||||
@@ -28,16 +31,19 @@ PPC_NATIVE_OVERRIDE_VOID(80171814, GX__SetIndTexMtx_80171814, (uint32_t id, uint
|
||||
// TEV Indirect Texture Control
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevDirect_80171b58(uint32_t s) { GXSetTevDirect((GXTevStageID)s); }
|
||||
extern "C" void GX__SetTevDirect_80171b58(uint32_t s) {
|
||||
GX_DEFER_CALL(GX__SetTevDirect_80171b58, s); GXSetTevDirect((GXTevStageID)s); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171b58, GX__SetTevDirect_80171b58, (uint32_t s), (s));
|
||||
|
||||
extern "C" void GX__SetTevIndWarp_80171ba0(uint32_t ts, uint32_t is, uint32_t so, uint32_t rm, uint32_t ms) {
|
||||
GX_DEFER_CALL(GX__SetTevIndWarp_80171ba0, ts, is, so, rm, ms);
|
||||
GXSetTevIndWarp((GXTevStageID)std::min(ts, 15u), (GXIndTexStageID)std::min(is, 3u),
|
||||
(GXBool)so, (GXBool)rm, (GXIndTexMtxID)ms);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171ba0, GX__SetTevIndWarp_80171ba0, (uint32_t ts, uint32_t is, uint32_t so, uint32_t rm, uint32_t ms), (ts, is, so, rm, ms));
|
||||
|
||||
extern "C" void GX__SetTevIndirect_801717ac(uint32_t ts, uint32_t is, uint32_t f, uint32_t bs, uint32_t ms, uint32_t ws, uint32_t wt, uint32_t ap, uint32_t il, uint32_t as) {
|
||||
GX_DEFER_CALL(GX__SetTevIndirect_801717ac, ts, is, f, bs, ms, ws, wt, ap, il, as);
|
||||
GXSetTevIndirect((GXTevStageID)std::min(ts,15u), (GXIndTexStageID)std::min(is,3u), (GXIndTexFormat)f,
|
||||
(GXIndTexBiasSel)bs, (GXIndTexMtxID)ms, (GXIndTexWrap)ws, (GXIndTexWrap)wt,
|
||||
(GXBool)ap, (GXBool)il, (GXIndTexAlphaSel)as);
|
||||
|
||||
@@ -24,8 +24,9 @@ extern "C" GXFifoObj* GXInit(void* base, u32 size);
|
||||
*/
|
||||
extern "C" uint32_t GX__Init_8016b850(uint32_t fifoBase, uint32_t fifoSize)
|
||||
{
|
||||
constexpr uint32_t kFifoObjAddr = 0x80343740u;
|
||||
constexpr uint32_t kGXDataAddr = 0x803437C0u;
|
||||
GX_SYNC_INLINE();
|
||||
const uint32_t kFifoObjAddr = MKW_GADDR(80343740);
|
||||
const uint32_t kGXDataAddr = MKW_GADDR(803437C0);
|
||||
constexpr uint32_t kGXDataSize = 0x600u;
|
||||
|
||||
GXInit(GuestToHostPtr(fifoBase, fifoSize), fifoSize);
|
||||
@@ -77,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);
|
||||
|
||||
@@ -91,30 +91,30 @@ PPC_NATIVE_OVERRIDE(8016b850, GX__Init_8016b850, uint32_t, (uint32_t fifoBase, u
|
||||
|
||||
extern "C" void GX__InitFifoBase_8016c7c8(uint32_t fa, uint32_t ba, uint32_t s) { GXInitFifoBase((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj)), GuestToHostPtr(ba, s), s); }
|
||||
|
||||
extern "C" void GX__SetCPUFifo_8016c94c(uint32_t fa) { GXSetCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
extern "C" void GX__SetCPUFifo_8016c94c(uint32_t fa) { GX_SYNC_INLINE(); GXSetCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016c94c, GX__SetCPUFifo_8016c94c, (uint32_t fa), (fa));
|
||||
|
||||
extern "C" void GX__SetGPFifo_8016cb2c(uint32_t fa) { GXSetGPFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
extern "C" void GX__SetGPFifo_8016cb2c(uint32_t fa) { GX_SYNC_INLINE(); GXSetGPFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016cb2c, GX__SetGPFifo_8016cb2c, (uint32_t fa), (fa));
|
||||
|
||||
extern "C" void __GX__SaveFifo_8016cdbc(uint32_t fa) { GXSaveCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
extern "C" void __GX__SaveFifo_8016cdbc(uint32_t fa) { GX_SYNC_INLINE(); GXSaveCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016cdbc, __GX__SaveFifo_8016cdbc, (uint32_t fa), (fa));
|
||||
|
||||
extern "C" void GX__GetCPUFifo_8016cf10(uint32_t fa) { auto* d=(GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj)); auto* s=GXGetCPUFifo(); if(d&&s) std::memcpy(d,s,sizeof(GXFifoObj)); }
|
||||
extern "C" void GX__GetCPUFifo_8016cf10(uint32_t fa) { GX_SYNC_INLINE(); auto* d=(GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj)); auto* s=GXGetCPUFifo(); if(d&&s) std::memcpy(d,s,sizeof(GXFifoObj)); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016cf10, GX__GetCPUFifo_8016cf10, (uint32_t fa), (fa));
|
||||
|
||||
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);
|
||||
@@ -137,13 +137,14 @@ extern "C" void __GX__FifoInit_8016d180()
|
||||
|
||||
extern "C" void __GX__PEInit_8016ee14()
|
||||
{
|
||||
GX_SYNC_INLINE();
|
||||
constexpr uint32_t kPeTokenInterruptId = 0x12u;
|
||||
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);
|
||||
@@ -165,35 +166,51 @@ PPC_NATIVE_OVERRIDE_VOID(8016ee14, __GX__PEInit_8016ee14, (), ());
|
||||
// Display List Recording
|
||||
// ============================================================================
|
||||
|
||||
namespace {
|
||||
// Display-list recording runs inline: the guest list buffer and its size are needed synchronously.
|
||||
bool g_dlInline = false;
|
||||
}
|
||||
|
||||
extern "C" void GX__BeginDisplayList_80172e00(uint32_t la, uint32_t s) {
|
||||
if (!g_dlInline) {
|
||||
GxWorker::BeginInline();
|
||||
g_dlInline = true;
|
||||
}
|
||||
try {
|
||||
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));
|
||||
|
||||
static void EndDisplayListInline() {
|
||||
if (g_dlInline) {
|
||||
g_dlInline = false;
|
||||
GxWorker::EndInline();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -201,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);
|
||||
@@ -214,12 +231,14 @@ extern "C" uint32_t GX__EndDisplayList_80172eb4() {
|
||||
// "inactive" whenever the GXData pointer was null, so recording must
|
||||
// stop here even on the gd == 0 path.
|
||||
EndDisplayListRecording();
|
||||
EndDisplayListInline();
|
||||
|
||||
return wrapped == 0 ? Memory::Read32(kDlCountAddr) : 0;
|
||||
} catch (...) {
|
||||
// Leaving the shadow active would silently swallow every subsequent
|
||||
// FIFO write into a dead list.
|
||||
EndDisplayListRecording();
|
||||
EndDisplayListInline();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -229,5 +248,8 @@ PPC_NATIVE_OVERRIDE(80172EB4, GX__EndDisplayList_80172eb4, uint32_t, (), ());
|
||||
// Flush
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__Flush_8016e654() { GXFlush(); }
|
||||
extern "C" void GX__Flush_8016e654() {
|
||||
GX_DEFER_CALL0(GX__Flush_8016e654);
|
||||
GXFlush();
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016e654, GX__Flush_8016e654, (), ());
|
||||
|
||||
@@ -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"
|
||||
@@ -34,6 +35,7 @@
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
@@ -54,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);
|
||||
@@ -300,6 +302,9 @@ void EndDisplayListRecording();
|
||||
void WriteDisplayListData(uint32_t val, uint32_t sizeBytes);
|
||||
void BeginNextAuroraFrameWithRetry(std::chrono::milliseconds timeout = std::chrono::milliseconds(100));
|
||||
void EnsureAuroraFrameActive();
|
||||
void EnsureAuroraFrameForFifo();
|
||||
// Array base/stride as last set through GXSetArray on the game thread.
|
||||
bool ApiVtxArray(uint32_t attr, uint32_t& base, uint32_t& stride);
|
||||
|
||||
GXColor DecodeGxColor(uint32_t colorWord);
|
||||
void WriteGuestFloat(uint32_t addr, float value, const char* label = nullptr);
|
||||
@@ -342,6 +347,17 @@ bool ValidateTlutData(uint32_t objAddr, const TlutObjMeta& meta);
|
||||
TexObjMeta& GetTexObjMeta(uint32_t addr);
|
||||
TexObjMeta ExtractTexObjMetaFromGuest(uint32_t addr);
|
||||
bool TryGetOrExtractTexObjMeta(uint32_t addr, TexObjMeta& outMeta);
|
||||
|
||||
// Guest GXTexObj bytes captured on the game thread; while a scope is active on a thread, texobj
|
||||
// decoding for that address reads the captured bytes instead of live guest memory.
|
||||
using TexObjBytes = std::array<uint8_t, 32>;
|
||||
TexObjBytes CaptureTexObjBytes(uint32_t addr);
|
||||
struct TexObjSnapshotScope {
|
||||
TexObjSnapshotScope(uint32_t addr, const TexObjBytes& bytes) noexcept;
|
||||
~TexObjSnapshotScope();
|
||||
TexObjSnapshotScope(const TexObjSnapshotScope&) = delete;
|
||||
TexObjSnapshotScope& operator=(const TexObjSnapshotScope&) = delete;
|
||||
};
|
||||
TlutObjMeta& GetTlutObjMeta(uint32_t addr);
|
||||
GXTexObj* GetHostTexObj(uint32_t addr);
|
||||
GXTexObj* TryGetHostTexObj(uint32_t addr);
|
||||
|
||||
@@ -66,7 +66,7 @@ extern "C" void GX__LoadLightObjImm_80170320(uint32_t la, uint32_t lid) {
|
||||
if (!ValidateGuestLightObj(la, "GXLoadLightObjImm")) return;
|
||||
GXLightObj host{};
|
||||
InitializeHostLightFromGuest(host, la);
|
||||
GXLoadLightObjImm(&host, static_cast<GXLightID>(lid));
|
||||
GxWorker::Run([host, lid]() mutable { GXLoadLightObjImm(&host, static_cast<GXLightID>(lid)); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170320, GX__LoadLightObjImm_80170320, (uint32_t la, uint32_t lid), (la, lid));
|
||||
|
||||
@@ -77,21 +77,23 @@ PPC_NATIVE_OVERRIDE_VOID(80170320, GX__LoadLightObjImm_80170320, (uint32_t la, u
|
||||
extern "C" void GX__SetChanAmbColor_8017039c(uint32_t c, uint32_t cp) {
|
||||
EnsureAuroraFrameActive();
|
||||
GXColor color = DecodeGxColor(Memory::Read32(cp));
|
||||
GXSetChanAmbColor((GXChannelID)c, color);
|
||||
GxWorker::Run([c, color] { GXSetChanAmbColor((GXChannelID)c, color); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017039c, GX__SetChanAmbColor_8017039c, (uint32_t c, uint32_t cp), (c, cp));
|
||||
|
||||
extern "C" void GX__SetChanMatColor_80170474(uint32_t c, uint32_t cp) {
|
||||
EnsureAuroraFrameActive();
|
||||
GXColor color = DecodeGxColor(Memory::Read32(cp));
|
||||
GXSetChanMatColor((GXChannelID)c, color);
|
||||
GxWorker::Run([c, color] { GXSetChanMatColor((GXChannelID)c, color); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170474, GX__SetChanMatColor_80170474, (uint32_t c, uint32_t cp), (c, cp));
|
||||
|
||||
extern "C" void GX__SetNumChans_8017054c(uint32_t n) { GXSetNumChans((u8)n); }
|
||||
extern "C" void GX__SetNumChans_8017054c(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumChans_8017054c, n); GXSetNumChans((u8)n); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017054c, GX__SetNumChans_8017054c, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetChanCtrl_80170570(uint32_t ch, uint32_t en, uint32_t as, uint32_t ms, uint32_t lm, uint32_t df, uint32_t af) {
|
||||
GX_DEFER_CALL(GX__SetChanCtrl_80170570, ch, en, as, ms, lm, df, af);
|
||||
GXSetChanCtrl((GXChannelID)ch, en!=0, (GXColorSrc)as, (GXColorSrc)ms, lm, (GXDiffuseFn)df, (GXAttnFn)af);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170570, GX__SetChanCtrl_80170570, (uint32_t ch, uint32_t en, uint32_t as, uint32_t ms, uint32_t lm, uint32_t df, uint32_t af), (ch, en, as, ms, lm, df, af));
|
||||
|
||||
@@ -110,17 +110,65 @@ bool g_tlutObjBoundsDirty = true;
|
||||
// word3 data address (<<5), word4 user data, word5 texFmt, word6 tlut, byte 0x1F flags. Exact
|
||||
// bit positions are decoded inline below.
|
||||
|
||||
namespace {
|
||||
thread_local uint32_t t_texObjSnapshotAddr = 0;
|
||||
thread_local const uint8_t* t_texObjSnapshotBytes = nullptr;
|
||||
|
||||
const uint8_t* TexObjSnapshotFor(uint32_t addr) noexcept {
|
||||
return (t_texObjSnapshotBytes != nullptr && addr == t_texObjSnapshotAddr) ? t_texObjSnapshotBytes : nullptr;
|
||||
}
|
||||
|
||||
uint32_t ReadTexObj32(uint32_t addr, uint32_t offset) {
|
||||
if (const uint8_t* bytes = TexObjSnapshotFor(addr)) return BigEndian::Read32(bytes + offset);
|
||||
return Memory::Read32(addr + offset);
|
||||
}
|
||||
|
||||
uint8_t ReadTexObj8(uint32_t addr, uint32_t offset) {
|
||||
if (const uint8_t* bytes = TexObjSnapshotFor(addr)) return bytes[offset];
|
||||
return Memory::Read8(addr + offset);
|
||||
}
|
||||
|
||||
uint64_t ReadTexObj64(uint32_t addr, uint32_t offset) {
|
||||
if (const uint8_t* bytes = TexObjSnapshotFor(addr)) {
|
||||
return (static_cast<uint64_t>(BigEndian::Read32(bytes + offset)) << 32) | BigEndian::Read32(bytes + offset + 4);
|
||||
}
|
||||
return Memory::Read64(addr + offset);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TexObjBytes CaptureTexObjBytes(uint32_t addr) {
|
||||
TexObjBytes bytes{};
|
||||
for (uint32_t offset = 0; offset < bytes.size(); offset += 4) {
|
||||
const uint32_t word = Memory::Read32(addr + offset);
|
||||
bytes[offset + 0] = static_cast<uint8_t>(word >> 24);
|
||||
bytes[offset + 1] = static_cast<uint8_t>(word >> 16);
|
||||
bytes[offset + 2] = static_cast<uint8_t>(word >> 8);
|
||||
bytes[offset + 3] = static_cast<uint8_t>(word);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
TexObjSnapshotScope::TexObjSnapshotScope(uint32_t addr, const TexObjBytes& bytes) noexcept {
|
||||
t_texObjSnapshotAddr = addr;
|
||||
t_texObjSnapshotBytes = bytes.data();
|
||||
}
|
||||
|
||||
TexObjSnapshotScope::~TexObjSnapshotScope() {
|
||||
t_texObjSnapshotAddr = 0;
|
||||
t_texObjSnapshotBytes = nullptr;
|
||||
}
|
||||
|
||||
TexObjMeta ExtractTexObjMetaFromGuest(uint32_t addr) {
|
||||
TexObjMeta meta{};
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(addr + 0x00);
|
||||
uint32_t word1 = Memory::Read32(addr + 0x04);
|
||||
uint32_t word2 = Memory::Read32(addr + 0x08);
|
||||
uint32_t word3 = Memory::Read32(addr + 0x0C);
|
||||
uint32_t word4 = Memory::Read32(addr + 0x10);
|
||||
uint32_t word5 = Memory::Read32(addr + 0x14);
|
||||
uint32_t word6 = Memory::Read32(addr + 0x18);
|
||||
uint8_t flags = Memory::Read8(addr + 0x1F);
|
||||
uint32_t word0 = ReadTexObj32(addr, 0x00);
|
||||
uint32_t word1 = ReadTexObj32(addr, 0x04);
|
||||
uint32_t word2 = ReadTexObj32(addr, 0x08);
|
||||
uint32_t word3 = ReadTexObj32(addr, 0x0C);
|
||||
uint32_t word4 = ReadTexObj32(addr, 0x10);
|
||||
uint32_t word5 = ReadTexObj32(addr, 0x14);
|
||||
uint32_t word6 = ReadTexObj32(addr, 0x18);
|
||||
uint8_t flags = ReadTexObj8(addr, 0x1F);
|
||||
|
||||
meta.wrapS = word0 & 0x3;
|
||||
meta.wrapT = (word0 >> 2) & 0x3;
|
||||
@@ -449,10 +497,10 @@ TexObjMeta MergeGuestTexObjMeta(const TexObjMeta& cached, const TexObjMeta& gues
|
||||
// caller must treat the shadow as absent) when the address is unreadable.
|
||||
bool ReadTexObjShadow(uint32_t addr, uint64_t (&out)[4]) noexcept {
|
||||
try {
|
||||
out[0] = Memory::Read64(addr + 0x00);
|
||||
out[1] = Memory::Read64(addr + 0x08);
|
||||
out[2] = Memory::Read64(addr + 0x10);
|
||||
out[3] = Memory::Read64(addr + 0x18);
|
||||
out[0] = ReadTexObj64(addr, 0x00);
|
||||
out[1] = ReadTexObj64(addr, 0x08);
|
||||
out[2] = ReadTexObj64(addr, 0x10);
|
||||
out[3] = ReadTexObj64(addr, 0x18);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
@@ -642,8 +690,11 @@ extern "C" void GxNotifyGuestRamDmaWrite(uint32_t addr, uint32_t size) {
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
MarkTexObjsDirtyForRange(addr, size);
|
||||
MarkTlutObjsDirtyForRange(addr, size);
|
||||
// Ordered with recorded GX work: retiring EFB copy textures emits aurora commands.
|
||||
GxWorker::Run([addr, size] {
|
||||
MarkTexObjsDirtyForRange(addr, size);
|
||||
MarkTlutObjsDirtyForRange(addr, size);
|
||||
});
|
||||
GxNotifyDisplayListMemoryWrite(addr, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,20 @@
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetBlendMode_8017277c(uint32_t t, uint32_t s, uint32_t d, uint32_t op) {
|
||||
GX_DEFER_CALL(GX__SetBlendMode_8017277c, t, s, d, op);
|
||||
GXSetBlendMode(static_cast<GXBlendMode>(t), static_cast<GXBlendFactor>(s),
|
||||
static_cast<GXBlendFactor>(d), static_cast<GXLogicOp>(op));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017277c, GX__SetBlendMode_8017277c, (uint32_t t, uint32_t s, uint32_t d, uint32_t op), (t, s, d, op));
|
||||
|
||||
extern "C" void GX__SetColorUpdate_801727cc(uint32_t en) {
|
||||
GX_DEFER_CALL(GX__SetColorUpdate_801727cc, en);
|
||||
GXSetColorUpdate(static_cast<GXBool>(en));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801727cc, GX__SetColorUpdate_801727cc, (uint32_t en), (en));
|
||||
|
||||
extern "C" void GX__SetAlphaUpdate_801727f8(uint32_t en) {
|
||||
GX_DEFER_CALL(GX__SetAlphaUpdate_801727f8, en);
|
||||
GXSetAlphaUpdate(static_cast<GXBool>(en));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801727f8, GX__SetAlphaUpdate_801727f8, (uint32_t en), (en));
|
||||
@@ -26,11 +29,13 @@ PPC_NATIVE_OVERRIDE_VOID(801727f8, GX__SetAlphaUpdate_801727f8, (uint32_t en), (
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetZMode_80172824(uint32_t ce, uint32_t f, uint32_t ue) {
|
||||
GX_DEFER_CALL(GX__SetZMode_80172824, ce, f, ue);
|
||||
GXSetZMode(static_cast<GXBool>(ce), static_cast<GXCompare>(f), static_cast<GXBool>(ue));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172824, GX__SetZMode_80172824, (uint32_t ce, uint32_t f, uint32_t ue), (ce, f, ue));
|
||||
|
||||
extern "C" void GX__SetZCompLoc_80172858(uint32_t bt) {
|
||||
GX_DEFER_CALL(GX__SetZCompLoc_80172858, bt);
|
||||
GXSetZCompLoc(static_cast<GXBool>(bt));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172858, GX__SetZCompLoc_80172858, (uint32_t bt), (bt));
|
||||
@@ -40,16 +45,19 @@ PPC_NATIVE_OVERRIDE_VOID(80172858, GX__SetZCompLoc_80172858, (uint32_t bt), (bt)
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetPixelFmt_80172888(uint32_t pf, uint32_t zf) {
|
||||
GX_DEFER_CALL(GX__SetPixelFmt_80172888, pf, zf);
|
||||
GXSetPixelFmt(static_cast<GXPixelFmt>(pf), static_cast<GXZFmt16>(zf));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172888, GX__SetPixelFmt_80172888, (uint32_t pf, uint32_t zf), (pf, zf));
|
||||
|
||||
extern "C" void GX__SetDither_80172930(uint32_t d) {
|
||||
GX_DEFER_CALL(GX__SetDither_80172930, d);
|
||||
GXSetDither(static_cast<GXBool>(d));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172930, GX__SetDither_80172930, (uint32_t d), (d));
|
||||
|
||||
extern "C" void GX__SetDstAlpha_8017295c(uint32_t en, uint32_t a) {
|
||||
GX_DEFER_CALL(GX__SetDstAlpha_8017295c, en, a);
|
||||
GXSetDstAlpha(static_cast<GXBool>(en), static_cast<u8>(a));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017295c, GX__SetDstAlpha_8017295c, (uint32_t en, uint32_t a), (en, a));
|
||||
@@ -59,17 +67,20 @@ PPC_NATIVE_OVERRIDE_VOID(8017295c, GX__SetDstAlpha_8017295c, (uint32_t en, uint3
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetFog_801722cc(uint32_t t, float sz, float ez, float nz, float fz, uint32_t cp) {
|
||||
GXSetFog((GXFogType)t, sz, ez, nz, fz, DecodeGxColor(Memory::Read32(cp)));
|
||||
const GXColor color = DecodeGxColor(Memory::Read32(cp));
|
||||
GxWorker::Run([=] { GXSetFog((GXFogType)t, sz, ez, nz, fz, color); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801722cc, GX__SetFog_801722cc, (uint32_t t, float sz, float ez, float nz, float fz, uint32_t cp), (t, sz, ez, nz, fz, cp));
|
||||
|
||||
extern "C" void GX__SetAlphaCompare_80172088(uint32_t c0, uint32_t r0, uint32_t op, uint32_t c1, uint32_t r1) {
|
||||
GX_DEFER_CALL(GX__SetAlphaCompare_80172088, c0, r0, op, c1, r1);
|
||||
g_alphaCompareValid = true;
|
||||
GXSetAlphaCompare((GXCompare)c0, (u8)r0, (GXAlphaOp)op, (GXCompare)c1, (u8)r1);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172088, GX__SetAlphaCompare_80172088, (uint32_t c0, uint32_t r0, uint32_t op, uint32_t c1, uint32_t r1), (c0, r0, op, c1, r1));
|
||||
|
||||
extern "C" void GX__SetZTexture_801720c0(uint32_t op, uint32_t f, uint32_t b) { GXSetZTexture((GXZTexOp)op, (GXTexFmt)f, b); }
|
||||
extern "C" void GX__SetZTexture_801720c0(uint32_t op, uint32_t f, uint32_t b) {
|
||||
GX_DEFER_CALL(GX__SetZTexture_801720c0, op, f, b); GXSetZTexture((GXZTexOp)op, (GXTexFmt)f, b); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(801720c0, GX__SetZTexture_801720c0, (uint32_t op, uint32_t f, uint32_t b), (op, f, b));
|
||||
|
||||
// ============================================================================
|
||||
@@ -77,12 +88,15 @@ PPC_NATIVE_OVERRIDE_VOID(801720c0, GX__SetZTexture_801720c0, (uint32_t op, uint3
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetCullMode_8016f3b8(uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetCullMode_8016f3b8, m);
|
||||
GXSetCullMode(static_cast<GXCullMode>(m));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f3b8, GX__SetCullMode_8016f3b8, (uint32_t m), (m));
|
||||
|
||||
extern "C" void GX__SetCoPlanar_8016f3e0(uint32_t en) { GXSetCoPlanar((GXBool)en); }
|
||||
extern "C" void GX__SetCoPlanar_8016f3e0(uint32_t en) {
|
||||
GX_DEFER_CALL(GX__SetCoPlanar_8016f3e0, en); GXSetCoPlanar((GXBool)en); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f3e0, GX__SetCoPlanar_8016f3e0, (uint32_t en), (en));
|
||||
|
||||
extern "C" void GX__SetClipMode_8017351c(uint32_t m) { GXSetClipMode((GXClipMode)m); }
|
||||
extern "C" void GX__SetClipMode_8017351c(uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetClipMode_8017351c, m); GXSetClipMode((GXClipMode)m); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017351c, GX__SetClipMode_8017351c, (uint32_t m), (m));
|
||||
|
||||
@@ -7,14 +7,23 @@ extern "C" void __GXSetSUTexRegs();
|
||||
// FIFO Write Helpers
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX_HLE_FIFO_WriteFloat(float val) {
|
||||
u32 raw; std::memcpy(&raw, &val, 4);
|
||||
try { HleFifoWrite(raw, 4); } catch (...) { RT_LOGF(RT_TAG_GX, "FIFO write float failed\n"); }
|
||||
static void FifoWrite(u32 val, uint32_t sizeBytes) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameForFifo();
|
||||
GxWorker::FifoWrite(val, sizeBytes);
|
||||
return;
|
||||
}
|
||||
HleFifoWrite(val, sizeBytes);
|
||||
}
|
||||
|
||||
extern "C" void GX_HLE_FIFO_Write32(uint32_t val) { HleFifoWrite(val, 4); }
|
||||
extern "C" void GX_HLE_FIFO_Write16(uint16_t val) { HleFifoWrite(static_cast<u32>(val), 2); }
|
||||
extern "C" void GX_HLE_FIFO_Write8(uint8_t val) { HleFifoWrite(static_cast<u32>(val), 1); }
|
||||
extern "C" void GX_HLE_FIFO_WriteFloat(float val) {
|
||||
u32 raw; std::memcpy(&raw, &val, 4);
|
||||
try { FifoWrite(raw, 4); } catch (...) { RT_LOGF(RT_TAG_GX, "FIFO write float failed\n"); }
|
||||
}
|
||||
|
||||
extern "C" void GX_HLE_FIFO_Write32(uint32_t val) { FifoWrite(val, 4); }
|
||||
extern "C" void GX_HLE_FIFO_Write16(uint16_t val) { FifoWrite(static_cast<u32>(val), 2); }
|
||||
extern "C" void GX_HLE_FIFO_Write8(uint8_t val) { FifoWrite(static_cast<u32>(val), 1); }
|
||||
|
||||
extern "C" void GX__SetDrawSync_8016ed08(uint32_t token) {
|
||||
(void)token;
|
||||
@@ -37,6 +46,7 @@ extern "C" void GX__FinishInterruptHandler_8016ed94() {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016ed94, GX__FinishInterruptHandler_8016ed94, (), ());
|
||||
|
||||
extern "C" void GX__DrawDone_8016eab0() {
|
||||
GX_SYNC_INLINE();
|
||||
try { Memory::Write8(kGxDrawDoneFlagAddr, 0); } catch (...) {}
|
||||
GXDrawDone(); GX__FinishInterruptHandler_8016ed94();
|
||||
}
|
||||
@@ -44,7 +54,7 @@ PPC_NATIVE_OVERRIDE_VOID(8016eab0, GX__DrawDone_8016eab0, (), ());
|
||||
|
||||
extern "C" void GX__PixModeSync_8016eb70() {
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {}
|
||||
GXPixModeSync();
|
||||
GxWorker::Run([] { GXPixModeSync(); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016eb70, GX__PixModeSync_8016eb70, (), ());
|
||||
|
||||
@@ -60,7 +70,7 @@ PPC_NATIVE_OVERRIDE_VOID(8016b720, __GX__InitRevisionBits_8016b720, (), ());
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void __GX__SetSUTexRegs_801712f0() {
|
||||
__GXSetSUTexRegs();
|
||||
GxWorker::Run([] { __GXSetSUTexRegs(); });
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801712f0, __GX__SetSUTexRegs_801712f0, (), ());
|
||||
@@ -82,7 +92,7 @@ PPC_NATIVE_OVERRIDE_VOID(80171c28, __GX__FlushTextureState_80171c28, (), ());
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetDispCopyFrame2Field_8016f5f8(uint32_t f) {
|
||||
GXSetDispCopyFrame2Field(f);
|
||||
GxWorker::Run([f] { GXSetDispCopyFrame2Field(f); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) {
|
||||
@@ -94,7 +104,7 @@ extern "C" void GX__SetDispCopyFrame2Field_8016f5f8(uint32_t f) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f5f8, GX__SetDispCopyFrame2Field_8016f5f8, (uint32_t f), (f));
|
||||
|
||||
extern "C" void GX__SetCopyClamp_8016f618(uint32_t c) {
|
||||
GXSetCopyClamp(static_cast<GXFBClamp>(c));
|
||||
GxWorker::Run([c] { GXSetCopyClamp(static_cast<GXFBClamp>(c)); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) {
|
||||
@@ -107,7 +117,7 @@ extern "C" void GX__SetCopyClamp_8016f618(uint32_t c) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f618, GX__SetCopyClamp_8016f618, (uint32_t c), (c));
|
||||
|
||||
extern "C" void GX__ClearBoundingBox_8016fecc() {
|
||||
GXClearBoundingBox();
|
||||
GxWorker::Run([] { GXClearBoundingBox(); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) Memory::Write16(gd + 2, 0);
|
||||
|
||||
@@ -25,6 +25,7 @@ inline bool TevSwapOk(uint32_t id) { return GxTevIdOk(id, GX_MAX_TEVSWAP, "TEV s
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetNumTevStages_801722a8(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumTevStages_801722a8, n);
|
||||
// GXSetNumTevStages takes a count, not an index, so the inclusive bound is
|
||||
// GX_MAX_TEVSTAGE itself.
|
||||
if (n > GX_MAX_TEVSTAGE) {
|
||||
@@ -36,12 +37,14 @@ extern "C" void GX__SetNumTevStages_801722a8(uint32_t n) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(801722a8, GX__SetNumTevStages_801722a8, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetTevOp_80171c4c(uint32_t s, uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetTevOp_80171c4c, s, m);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevOp((GXTevStageID)s, (GXTevMode)m);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171c4c, GX__SetTevOp_80171c4c, (uint32_t s, uint32_t m), (s, m));
|
||||
|
||||
extern "C" void GX__SetTevOrder_8017214c(uint32_t s, uint32_t c, uint32_t m, uint32_t col) {
|
||||
GX_DEFER_CALL(GX__SetTevOrder_8017214c, s, c, m, col);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevOrder((GXTevStageID)s, (GXTexCoordID)c, (GXTexMapID)m, (GXChannelID)col);
|
||||
}
|
||||
@@ -52,12 +55,14 @@ PPC_NATIVE_OVERRIDE_VOID(8017214c, GX__SetTevOrder_8017214c, (uint32_t s, uint32
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevColorIn_80171ce0(uint32_t s, uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
GX_DEFER_CALL(GX__SetTevColorIn_80171ce0, s, a, b, c, d);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevColorIn((GXTevStageID)s, (GXTevColorArg)a, (GXTevColorArg)b, (GXTevColorArg)c, (GXTevColorArg)d);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171ce0, GX__SetTevColorIn_80171ce0, (uint32_t s, uint32_t a, uint32_t b, uint32_t c, uint32_t d), (s, a, b, c, d));
|
||||
|
||||
extern "C" void GX__SetTevAlphaIn_80171d20(uint32_t s, uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
GX_DEFER_CALL(GX__SetTevAlphaIn_80171d20, s, a, b, c, d);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevAlphaIn((GXTevStageID)s, (GXTevAlphaArg)a, (GXTevAlphaArg)b, (GXTevAlphaArg)c, (GXTevAlphaArg)d);
|
||||
}
|
||||
@@ -68,12 +73,14 @@ PPC_NATIVE_OVERRIDE_VOID(80171d20, GX__SetTevAlphaIn_80171d20, (uint32_t s, uint
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevColorOp_80171d60(uint32_t s, uint32_t op, uint32_t b, uint32_t sc, uint32_t cl, uint32_t or_) {
|
||||
GX_DEFER_CALL(GX__SetTevColorOp_80171d60, s, op, b, sc, cl, or_);
|
||||
if (!TevStageOk(s) || !TevRegOk(or_)) return;
|
||||
GXSetTevColorOp((GXTevStageID)s, (GXTevOp)op, (GXTevBias)b, (GXTevScale)sc, (GXBool)cl, (GXTevRegID)or_);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171d60, GX__SetTevColorOp_80171d60, (uint32_t s, uint32_t op, uint32_t b, uint32_t sc, uint32_t cl, uint32_t or_), (s, op, b, sc, cl, or_));
|
||||
|
||||
extern "C" void GX__SetTevAlphaOp_80171db8(uint32_t s, uint32_t op, uint32_t b, uint32_t sc, uint32_t cl, uint32_t or_) {
|
||||
GX_DEFER_CALL(GX__SetTevAlphaOp_80171db8, s, op, b, sc, cl, or_);
|
||||
if (!TevStageOk(s) || !TevRegOk(or_)) return;
|
||||
GXSetTevAlphaOp((GXTevStageID)s, (GXTevOp)op, (GXTevBias)b, (GXTevScale)sc, (GXBool)cl, (GXTevRegID)or_);
|
||||
}
|
||||
@@ -86,7 +93,7 @@ PPC_NATIVE_OVERRIDE_VOID(80171db8, GX__SetTevAlphaOp_80171db8, (uint32_t s, uint
|
||||
extern "C" void GX__SetTevColor_80171e10(uint32_t id, uint32_t cp) {
|
||||
if (!TevRegOk(id)) return;
|
||||
const uint8_t* p=Memory::GetPointer(cp, 4); GXColor c; c.r=p[0]; c.g=p[1]; c.b=p[2]; c.a=p[3];
|
||||
GXSetTevColor((GXTevRegID)id, c);
|
||||
GxWorker::Run([id, c] { GXSetTevColor((GXTevRegID)id, c); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171e10, GX__SetTevColor_80171e10, (uint32_t id, uint32_t cp), (id, cp));
|
||||
|
||||
@@ -94,21 +101,23 @@ extern "C" void GX__SetTevColorS10_80171e70(uint32_t id, uint32_t cp) {
|
||||
if (!TevRegOk(id)) return;
|
||||
const uint8_t* p=Memory::GetPointer(cp, 8); GXColorS10 c;
|
||||
c.r=(p[0]<<8)|p[1]; c.g=(p[2]<<8)|p[3]; c.b=(p[4]<<8)|p[5]; c.a=(p[6]<<8)|p[7];
|
||||
GXSetTevColorS10((GXTevRegID)id, c);
|
||||
GxWorker::Run([id, c] { GXSetTevColorS10((GXTevRegID)id, c); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171e70, GX__SetTevColorS10_80171e70, (uint32_t id, uint32_t cp), (id, cp));
|
||||
|
||||
extern "C" void GX__SetTevKColor_80171ed4(uint32_t id, uint32_t cp) {
|
||||
if (!TevKColorOk(id)) return;
|
||||
const uint8_t* p=Memory::GetPointer(cp, 4); GXColor c; c.r=p[0]; c.g=p[1]; c.b=p[2]; c.a=p[3];
|
||||
GXSetTevKColor((GXTevKColorID)id, c);
|
||||
GxWorker::Run([id, c] { GXSetTevKColor((GXTevKColorID)id, c); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171ed4, GX__SetTevKColor_80171ed4, (uint32_t id, uint32_t cp), (id, cp));
|
||||
|
||||
extern "C" void GX__SetTevKColorSel_80171f30(uint32_t s, uint32_t sel) { if (!TevStageOk(s)) return; GXSetTevKColorSel((GXTevStageID)s, (GXTevKColorSel)sel); }
|
||||
extern "C" void GX__SetTevKColorSel_80171f30(uint32_t s, uint32_t sel) {
|
||||
GX_DEFER_CALL(GX__SetTevKColorSel_80171f30, s, sel); if (!TevStageOk(s)) return; GXSetTevKColorSel((GXTevStageID)s, (GXTevKColorSel)sel); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171f30, GX__SetTevKColorSel_80171f30, (uint32_t s, uint32_t sel), (s, sel));
|
||||
|
||||
extern "C" void GX__SetTevKAlphaSel_80171f80(uint32_t s, uint32_t sel) { if (!TevStageOk(s)) return; GXSetTevKAlphaSel((GXTevStageID)s, (GXTevKAlphaSel)sel); }
|
||||
extern "C" void GX__SetTevKAlphaSel_80171f80(uint32_t s, uint32_t sel) {
|
||||
GX_DEFER_CALL(GX__SetTevKAlphaSel_80171f80, s, sel); if (!TevStageOk(s)) return; GXSetTevKAlphaSel((GXTevStageID)s, (GXTevKAlphaSel)sel); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171f80, GX__SetTevKAlphaSel_80171f80, (uint32_t s, uint32_t sel), (s, sel));
|
||||
|
||||
// ============================================================================
|
||||
@@ -116,12 +125,14 @@ PPC_NATIVE_OVERRIDE_VOID(80171f80, GX__SetTevKAlphaSel_80171f80, (uint32_t s, ui
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevSwapModeTable_8017200c(uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a) {
|
||||
GX_DEFER_CALL(GX__SetTevSwapModeTable_8017200c, id, r, g, b, a);
|
||||
if (!TevSwapOk(id)) return;
|
||||
GXSetTevSwapModeTable((GXTevSwapSel)id, (GXTevColorChan)r, (GXTevColorChan)g, (GXTevColorChan)b, (GXTevColorChan)a);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017200c, GX__SetTevSwapModeTable_8017200c, (uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a), (id, r, g, b, a));
|
||||
|
||||
extern "C" void GX__SetTevSwapMode_80171fd0(uint32_t s, uint32_t rs, uint32_t ts) {
|
||||
GX_DEFER_CALL(GX__SetTevSwapMode_80171fd0, s, rs, ts);
|
||||
if (!TevStageOk(s) || !TevSwapOk(rs) || !TevSwapOk(ts)) return;
|
||||
GXSetTevSwapMode((GXTevStageID)s, (GXTevSwapSel)rs, (GXTevSwapSel)ts);
|
||||
}
|
||||
|
||||
+179
-102
@@ -242,6 +242,31 @@ static uint32_t ComputeMaxMipLevel(uint16_t width, uint16_t height) noexcept {
|
||||
return levels;
|
||||
}
|
||||
|
||||
// GXInitTexObj* writes the guest struct synchronously; the host half runs on the GX worker with the
|
||||
// struct bytes from before that write, which is what an inline call's shadow capture sees.
|
||||
template <typename Hle, typename Guest>
|
||||
static void InitTexObjSplit(uint32_t oa, Hle&& hle, Guest&& guest) {
|
||||
if (!GxWorker::ShouldDefer()) {
|
||||
hle();
|
||||
guest();
|
||||
return;
|
||||
}
|
||||
std::optional<TexObjBytes> before;
|
||||
try {
|
||||
before = CaptureTexObjBytes(oa);
|
||||
} catch (...) {
|
||||
}
|
||||
guest();
|
||||
GxWorker::Submit([oa, before, hle = std::forward<Hle>(hle)] {
|
||||
if (before) {
|
||||
TexObjSnapshotScope snapshot(oa, *before);
|
||||
hle();
|
||||
} else {
|
||||
hle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void GX__InitTexObj_801707f8(uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m) {
|
||||
if (w == 0 || h == 0) {
|
||||
auto* cpu = TryGetCpuContext();
|
||||
@@ -250,12 +275,15 @@ extern "C" void GX__InitTexObj_801707f8(uint32_t oa, uint32_t da, uint32_t w, ui
|
||||
oa, da, w, h, f, ws, wt, m, cpu ? cpu->pc : 0u, cpu ? cpu->lr : 0u);
|
||||
}
|
||||
const uint32_t canonicalDataAddr = CanonicalizeGxMainRamAddress(da);
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObj(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXTexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m); MarkHostTexObjConstructed(oa);
|
||||
// Also write to guest memory so reads work
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, false, 0);
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObj(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXTexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m); MarkHostTexObjConstructed(oa);
|
||||
}, [=] {
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, false, 0);
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(801707f8, GX__InitTexObj_801707f8, (uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m), (oa, da, w, h, f, ws, wt, m));
|
||||
|
||||
extern "C" void GX__InitTexObj_switch_80170938(uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m) { GX__InitTexObj_801707f8(oa, da, w, h, f, ws, wt, m); }
|
||||
@@ -291,132 +319,151 @@ extern "C" void GX__InitTexObjCI_80170a04(uint32_t oa, uint32_t da, uint32_t w,
|
||||
oa, da, w, h, f, ws, wt, m, tl, cpu ? cpu->pc : 0u, cpu ? cpu->lr : 0u);
|
||||
}
|
||||
const uint32_t canonicalDataAddr = CanonicalizeGxMainRamAddress(da);
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.tlut=tl; meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObjCI(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXCITexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m, tl); MarkHostTexObjConstructed(oa);
|
||||
// Also write to guest memory so reads work
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, true, tl);
|
||||
// GXInitTexObjCI clears bit1 in the flags byte; keep guest memory consistent.
|
||||
try {
|
||||
uint8_t flags = Memory::Read8(oa + 0x1F);
|
||||
Memory::Write8(oa + 0x1F, static_cast<uint8_t>(flags & 0xFD));
|
||||
} catch (...) {
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.tlut=tl; meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObjCI(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXCITexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m, tl); MarkHostTexObjConstructed(oa);
|
||||
}, [=] {
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, true, tl);
|
||||
// GXInitTexObjCI clears bit1 in the flags byte; keep guest memory consistent.
|
||||
try {
|
||||
uint8_t flags = Memory::Read8(oa + 0x1F);
|
||||
Memory::Write8(oa + 0x1F, static_cast<uint8_t>(flags & 0xFD));
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170a04, GX__InitTexObjCI_80170a04, (uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m, uint32_t tl), (oa, da, w, h, f, ws, wt, m, tl));
|
||||
|
||||
extern "C" void GX__InitTexObjLOD_80170a4c(uint32_t oa, uint32_t mif, uint32_t maf, float mil, float mal, float lb, uint32_t bc, uint32_t el, uint32_t ma) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = GetHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
float fmal = std::isfinite(mal) && mal >= 0.f ? mal : 0.f, fmil = std::isfinite(mil) && mil >= 0.f ? std::min(mil, fmal) : 0.f;
|
||||
meta.minFilter=mif; meta.magFilter=maf; meta.minLod=fmil; meta.maxLod=fmal; meta.lodBias=lb; meta.biasClamp=(bc!=0); meta.edgeLod=(el!=0); meta.maxAniso=ma;
|
||||
GXInitTexObjLOD(obj, (GXTexFilter)mif, (GXTexFilter)maf, fmil, fmal, lb, (GXBool)bc, (GXBool)el, (GXAnisotropy)ma);
|
||||
// Also write LOD info to guest memory
|
||||
WriteGuestTexObjLOD(oa, mif, maf, fmil, fmal, lb, bc != 0, el != 0, ma);
|
||||
const float fmal = std::isfinite(mal) && mal >= 0.f ? mal : 0.f, fmil = std::isfinite(mil) && mil >= 0.f ? std::min(mil, fmal) : 0.f;
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = GetHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.minFilter=mif; meta.magFilter=maf; meta.minLod=fmil; meta.maxLod=fmal; meta.lodBias=lb; meta.biasClamp=(bc!=0); meta.edgeLod=(el!=0); meta.maxAniso=ma;
|
||||
GXInitTexObjLOD(obj, (GXTexFilter)mif, (GXTexFilter)maf, fmil, fmal, lb, (GXBool)bc, (GXBool)el, (GXAnisotropy)ma);
|
||||
}, [=] {
|
||||
WriteGuestTexObjLOD(oa, mif, maf, fmil, fmal, lb, bc != 0, el != 0, ma);
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170a4c, GX__InitTexObjLOD_80170a4c, (uint32_t oa, uint32_t mif, uint32_t maf, float mil, float mal, float lb, uint32_t bc, uint32_t el, uint32_t ma), (oa, mif, maf, mil, mal, lb, bc, el, ma));
|
||||
|
||||
extern "C" void GX__InitTexObjWrapMode_80170b50(uint32_t oa, uint32_t ws, uint32_t wt) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.wrapS = ws;
|
||||
meta.wrapT = wt;
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
word0 = (word0 & ~0xFu) | (ws & 0x3u) | ((wt & 0x3u) << 2);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {
|
||||
}
|
||||
GXInitTexObjWrapMode(obj, (GXTexWrapMode)ws, (GXTexWrapMode)wt);
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.wrapS = ws;
|
||||
meta.wrapT = wt;
|
||||
GXInitTexObjWrapMode(obj, (GXTexWrapMode)ws, (GXTexWrapMode)wt);
|
||||
}, [=] {
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
word0 = (word0 & ~0xFu) | (ws & 0x3u) | ((wt & 0x3u) << 2);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b50, GX__InitTexObjWrapMode_80170b50, (uint32_t oa, uint32_t ws, uint32_t wt), (oa, ws, wt));
|
||||
|
||||
extern "C" void GX__InitTexObjTlut_80170b64(uint32_t oa, uint32_t tl) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.tlut = tl;
|
||||
GXInitTexObjTlut(obj, tl);
|
||||
// Keep guest GXTexObj coherent for later GXLoadTexObj from memory.
|
||||
try {
|
||||
Memory::Write32(oa + 0x18, tl);
|
||||
} catch (...) {
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.tlut = tl;
|
||||
GXInitTexObjTlut(obj, tl);
|
||||
}, [=] {
|
||||
// Keep guest GXTexObj coherent for later GXLoadTexObj from memory.
|
||||
try {
|
||||
Memory::Write32(oa + 0x18, tl);
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b64, GX__InitTexObjTlut_80170b64, (uint32_t oa, uint32_t tl), (oa, tl));
|
||||
|
||||
// GXInitTexObjFilter - sets mag/min filter modes
|
||||
extern "C" void GX__InitTexObjFilter_80170b6c(uint32_t oa, uint32_t minFilter, uint32_t magFilter) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.minFilter = minFilter;
|
||||
meta.magFilter = magFilter;
|
||||
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
// bit 4: magFilter - set if magFilter == GX_LINEAR (1), clear otherwise
|
||||
// Original code used cntlzw to check: cntlzw(magFilter-1) >> 1 & 0x10
|
||||
// This sets bit 4 when magFilter-1 is 0 (i.e., magFilter == 1)
|
||||
uint32_t magBit = (magFilter == 1) ? 0x10 : 0;
|
||||
word0 = (word0 & 0xFFFFFFEF) | magBit;
|
||||
// bits [7:5]: minFilter from LUT
|
||||
uint32_t minBits = (kGxToHwMinFilter[minFilter & 7] & 7) << 5;
|
||||
word0 = (word0 & 0xFFFFFF0F) | magBit | minBits;
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)minFilter, (GXTexFilter)magFilter, meta.lodBias,
|
||||
(GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.minFilter = minFilter;
|
||||
meta.magFilter = magFilter;
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)minFilter, (GXTexFilter)magFilter, meta.lodBias,
|
||||
(GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
}, [=] {
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
// bit 4: magFilter - set if magFilter == GX_LINEAR (1), clear otherwise
|
||||
// Original code used cntlzw to check: cntlzw(magFilter-1) >> 1 & 0x10
|
||||
// This sets bit 4 when magFilter-1 is 0 (i.e., magFilter == 1)
|
||||
uint32_t magBit = (magFilter == 1) ? 0x10 : 0;
|
||||
word0 = (word0 & 0xFFFFFFEF) | magBit;
|
||||
// bits [7:5]: minFilter from LUT
|
||||
uint32_t minBits = (kGxToHwMinFilter[minFilter & 7] & 7) << 5;
|
||||
word0 = (word0 & 0xFFFFFF0F) | magBit | minBits;
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b6c, GX__InitTexObjFilter_80170b6c, (uint32_t oa, uint32_t minFilter, uint32_t magFilter), (oa, minFilter, magFilter));
|
||||
|
||||
// GXInitTexObjLODBias - sets LOD bias value
|
||||
extern "C" void GX__InitTexObjLODBias_80170b94(uint32_t oa, float bias) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
|
||||
// Clamp bias to [-4.0, 3.99...] range as per SDK
|
||||
constexpr float kMinBias = -4.0f;
|
||||
constexpr float kMaxBias = 3.99f; // Actually ~3.990000009536743
|
||||
float clampedBias = bias;
|
||||
if (clampedBias < kMinBias) clampedBias = kMinBias;
|
||||
if (clampedBias >= 4.0f) clampedBias = kMaxBias;
|
||||
|
||||
meta.lodBias = clampedBias;
|
||||
|
||||
// Update guest GXTexObj word0 with bias (scale by 32, store in bits [16:9])
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
int8_t biasScaled = static_cast<int8_t>(clampedBias * 32.0f);
|
||||
word0 = (word0 & 0xFFFE01FF) | ((static_cast<uint32_t>(static_cast<uint8_t>(biasScaled)) & 0xFF) << 9);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)meta.minFilter, (GXTexFilter)meta.magFilter,
|
||||
clampedBias, (GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.lodBias = clampedBias;
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)meta.minFilter, (GXTexFilter)meta.magFilter,
|
||||
clampedBias, (GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
}, [=] {
|
||||
// Update guest GXTexObj word0 with bias (scale by 32, store in bits [16:9])
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
int8_t biasScaled = static_cast<int8_t>(clampedBias * 32.0f);
|
||||
word0 = (word0 & 0xFFFE01FF) | ((static_cast<uint32_t>(static_cast<uint8_t>(biasScaled)) & 0xFF) << 9);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b94, GX__InitTexObjLODBias_80170b94, (uint32_t oa, float bias), (oa, bias));
|
||||
|
||||
extern "C" void GX__InitTexObjUserData_80170be8(uint32_t oa, uint32_t userData) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.userData = userData;
|
||||
|
||||
// The RVL SDK stores this opaque guest pointer verbatim at GXTexObj + 0x10.
|
||||
WriteGuest32(oa + 0x10, userData, "GXInitTexObjUserData");
|
||||
|
||||
// Aurora keeps an expanded host-side object, so mirror the pointer when
|
||||
// that representation has already been constructed.
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
GXInitTexObjUserData(obj, GuestToHostPtr(userData));
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.userData = userData;
|
||||
// Aurora keeps an expanded host-side object, so mirror the pointer when
|
||||
// that representation has already been constructed.
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
GXInitTexObjUserData(obj, GuestToHostPtr(userData));
|
||||
}
|
||||
}, [=] {
|
||||
// The RVL SDK stores this opaque guest pointer verbatim at GXTexObj + 0x10.
|
||||
WriteGuest32(oa + 0x10, userData, "GXInitTexObjUserData");
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170be8, GX__InitTexObjUserData_80170be8,
|
||||
(uint32_t oa, uint32_t userData), (oa, userData));
|
||||
|
||||
@@ -446,7 +493,7 @@ static void BindUnloadableTexturePlaceholder(uint32_t tid) {
|
||||
GXLoadTexObj(&s_placeholder, (GXTexMapID)tid);
|
||||
}
|
||||
|
||||
extern "C" void GX__LoadTexObj_80170f2c(uint32_t oa, uint32_t tid) {
|
||||
static void LoadTexObjImpl(uint32_t oa, uint32_t tid) {
|
||||
static uint32_t s_invalidMetaLogCount = 0;
|
||||
static uint32_t s_invalidTidLogCount = 0;
|
||||
static uint32_t s_invalidDimLogCount = 0;
|
||||
@@ -631,6 +678,27 @@ extern "C" void GX__LoadTexObj_80170f2c(uint32_t oa, uint32_t tid) {
|
||||
BindUnloadableTexturePlaceholder(tid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void GX__LoadTexObj_80170f2c(uint32_t oa, uint32_t tid) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
// The CPU-side SDK reads the GXTexObj at call time; the texture data itself is read later.
|
||||
std::optional<TexObjBytes> bytes;
|
||||
try {
|
||||
bytes = CaptureTexObjBytes(oa);
|
||||
} catch (...) {
|
||||
}
|
||||
GxWorker::Submit([oa, tid, bytes] {
|
||||
if (bytes) {
|
||||
TexObjSnapshotScope snapshot(oa, *bytes);
|
||||
LoadTexObjImpl(oa, tid);
|
||||
} else {
|
||||
LoadTexObjImpl(oa, tid);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LoadTexObjImpl(oa, tid);
|
||||
}
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) { Memory::Write32(gd + 0x5FCu, Memory::Read32(gd + 0x5FCu) | 1u); Memory::Write16(gd + 2, 0); } } catch (...) {}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170f2c, GX__LoadTexObj_80170f2c, (uint32_t oa, uint32_t tid), (oa, tid));
|
||||
@@ -643,6 +711,7 @@ PPC_NATIVE_OVERRIDE_VOID(80170dc8, GX__LoadTexObjPreLoaded_80170dc8, (uint32_t o
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__GetTexObjAll_80170bf8(uint32_t oa, uint32_t dp, uint32_t wp, uint32_t hp, uint32_t fp, uint32_t wsp, uint32_t wtp, uint32_t mp) {
|
||||
GX_SYNC_INLINE();
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta meta;
|
||||
if (!TryGetOrExtractTexObjMeta(oa, meta)) {
|
||||
@@ -657,6 +726,7 @@ extern "C" void GX__GetTexObjAll_80170bf8(uint32_t oa, uint32_t dp, uint32_t wp,
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170bf8, GX__GetTexObjAll_80170bf8, (uint32_t oa, uint32_t dp, uint32_t wp, uint32_t hp, uint32_t fp, uint32_t wsp, uint32_t wtp, uint32_t mp), (oa, dp, wp, hp, fp, wsp, wtp, mp));
|
||||
|
||||
extern "C" void GX__GetTexObjLODAll_80170cbc(uint32_t oa, uint32_t mifp, uint32_t mafp, uint32_t milp, uint32_t malp, uint32_t lbp, uint32_t bcp, uint32_t elp, uint32_t map) {
|
||||
GX_SYNC_INLINE();
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta meta;
|
||||
if (!TryGetOrExtractTexObjMeta(oa, meta)) {
|
||||
@@ -676,6 +746,7 @@ PPC_NATIVE_OVERRIDE_VOID(80170cbc, GX__GetTexObjLODAll_80170cbc, (uint32_t oa, u
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__InitTlutObj_80170f80(uint32_t oa, uint32_t da, uint32_t f, uint32_t e) {
|
||||
GX_DEFER_CALL(GX__InitTlutObj_80170f80, oa, da, f, e);
|
||||
std::lock_guard<std::mutex> guard(g_tlutObjMutex); GXTlutObj* obj = CreateHostTlutObj(oa); TlutObjMeta& meta = GetTlutObjMeta(oa);
|
||||
meta.dataAddr=CanonicalizeGxMainRamAddress(da); meta.format=f; meta.entries=(u16)e; meta.dirty=false;
|
||||
// Leave the object unconstructed on a bad descriptor. A later GXLoadTlut
|
||||
@@ -686,7 +757,12 @@ extern "C" void GX__InitTlutObj_80170f80(uint32_t oa, uint32_t da, uint32_t f, u
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170f80, GX__InitTlutObj_80170f80, (uint32_t oa, uint32_t da, uint32_t f, uint32_t e), (oa, da, f, e));
|
||||
|
||||
extern "C" void GX__LoadTlut_80170fa8(uint32_t oa, uint32_t tl) { std::lock_guard<std::mutex> guard(g_tlutObjMutex); if (tl >= kMaxTluts) { RT_LOGF(RT_TAG_GX, "GXLoadTlut: invalid TLUT index %u (oa=0x%08X)\n", tl, oa); return; } auto metaIt = g_TlutObjMeta.find(oa); if (metaIt != g_TlutObjMeta.end() && metaIt->second.dirty) { if (ValidateTlutData(oa, metaIt->second)) { GXTlutObj* rebuild = CreateHostTlutObj(oa); GXInitTlutObj(rebuild, GuestToHostPtr(metaIt->second.dataAddr), (GXTlutFmt)metaIt->second.format, metaIt->second.entries); MarkHostTlutObjConstructed(oa); } metaIt->second.dirty = false; } GXTlutObj* obj = GetHostTlutObj(oa); GXLoadTlut(obj, (GXTlut)tl); try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {} }
|
||||
static void LoadTlutImpl(uint32_t oa, uint32_t tl) { std::lock_guard<std::mutex> guard(g_tlutObjMutex); if (tl >= kMaxTluts) { RT_LOGF(RT_TAG_GX, "GXLoadTlut: invalid TLUT index %u (oa=0x%08X)\n", tl, oa); return; } auto metaIt = g_TlutObjMeta.find(oa); if (metaIt != g_TlutObjMeta.end() && metaIt->second.dirty) { if (ValidateTlutData(oa, metaIt->second)) { GXTlutObj* rebuild = CreateHostTlutObj(oa); GXInitTlutObj(rebuild, GuestToHostPtr(metaIt->second.dataAddr), (GXTlutFmt)metaIt->second.format, metaIt->second.entries); MarkHostTlutObjConstructed(oa); } metaIt->second.dirty = false; } GXTlutObj* obj = GetHostTlutObj(oa); GXLoadTlut(obj, (GXTlut)tl); }
|
||||
|
||||
extern "C" void GX__LoadTlut_80170fa8(uint32_t oa, uint32_t tl) {
|
||||
GxWorker::Run([oa, tl] { LoadTlutImpl(oa, tl); });
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170fa8, GX__LoadTlut_80170fa8, (uint32_t oa, uint32_t tl), (oa, tl));
|
||||
|
||||
// ============================================================================
|
||||
@@ -694,6 +770,7 @@ PPC_NATIVE_OVERRIDE_VOID(80170fa8, GX__LoadTlut_80170fa8, (uint32_t oa, uint32_t
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__InvalidateTexAll_80171110() {
|
||||
GX_DEFER_CALL0(GX__InvalidateTexAll_80171110);
|
||||
// Real GX invalidates its internal texture cache here. Aurora forwards the
|
||||
// invalidate to the renderer, which keeps unchanged source uploads hot but
|
||||
// revalidates reused guest buffers before serving cached texture handles.
|
||||
@@ -701,8 +778,8 @@ extern "C" void GX__InvalidateTexAll_80171110() {
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171110, GX__InvalidateTexAll_80171110, (), ());
|
||||
|
||||
extern "C" void GX__SetTexCoordScaleManually_80171180(uint32_t c, uint32_t en, uint32_t ss, uint32_t ts) { GXSetTexCoordScaleManually((GXTexCoordID)c, (GXBool)en, (u16)ss, (u16)ts); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ Memory::Write32(gd+0x5E4u, (Memory::Read32(gd+0x5E4u)&~(1u<<c))|((en&1u)<<c)); if(en){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFF0000u)|((ss-1)&0xFFFFu)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFF0000u)|((ts-1)&0xFFFFu)); Memory::Write16(gd+2, 0); } } }catch(...){} }
|
||||
extern "C" void GX__SetTexCoordScaleManually_80171180(uint32_t c, uint32_t en, uint32_t ss, uint32_t ts) { GxWorker::Run([c, en, ss, ts] { GXSetTexCoordScaleManually((GXTexCoordID)c, (GXBool)en, (u16)ss, (u16)ts); }); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ Memory::Write32(gd+0x5E4u, (Memory::Read32(gd+0x5E4u)&~(1u<<c))|((en&1u)<<c)); if(en){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFF0000u)|((ss-1)&0xFFFFu)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFF0000u)|((ts-1)&0xFFFFu)); Memory::Write16(gd+2, 0); } } }catch(...){} }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171180, GX__SetTexCoordScaleManually_80171180, (uint32_t c, uint32_t en, uint32_t ss, uint32_t ts), (c, en, ss, ts));
|
||||
|
||||
extern "C" void GX__SetTexCoordBias_801711fc(uint32_t c, uint32_t se, uint32_t te) { GXSetTexCoordBias((GXTexCoordID)c, (GXBool)se, (GXBool)te); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFEFFFFu)|((se&1u)<<16)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFEFFFFu)|((te&1u)<<16)); if(Memory::Read32(gd+0x5E4u)&(1u<<c)) Memory::Write16(gd+2, 0); } }catch(...){} }
|
||||
extern "C" void GX__SetTexCoordBias_801711fc(uint32_t c, uint32_t se, uint32_t te) { GxWorker::Run([c, se, te] { GXSetTexCoordBias((GXTexCoordID)c, (GXBool)se, (GXBool)te); }); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFEFFFFu)|((se&1u)<<16)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFEFFFFu)|((te&1u)<<16)); if(Memory::Read32(gd+0x5E4u)&(1u<<c)) Memory::Write16(gd+2, 0); } }catch(...){} }
|
||||
PPC_NATIVE_OVERRIDE_VOID(801711fc, GX__SetTexCoordBias_801711fc, (uint32_t c, uint32_t se, uint32_t te), (c, se, te));
|
||||
|
||||
@@ -40,13 +40,13 @@ namespace {
|
||||
// transform double-transformed menu viewports and broke 640x480 overscan, so it was removed.
|
||||
extern "C" void GX__SetViewportJitter_80173378(float l, float t, float w, float h, float nz, float fz, uint32_t f) {
|
||||
g_viewportState[0]=l; g_viewportState[1]=t; g_viewportState[2]=w; g_viewportState[3]=h; g_viewportState[4]=nz; g_viewportState[5]=fz;
|
||||
GXSetViewportJitter(l, t, w, h, nz, fz, f);
|
||||
GxWorker::Run([=] { GXSetViewportJitter(l, t, w, h, nz, fz, f); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173378, GX__SetViewportJitter_80173378, (float l, float t, float w, float h, float nz, float fz, uint32_t f), (l, t, w, h, nz, fz, f));
|
||||
|
||||
extern "C" void GX__SetViewport_801733b4(float l, float t, float w, float h, float nz, float fz) {
|
||||
g_viewportState[0]=l; g_viewportState[1]=t; g_viewportState[2]=w; g_viewportState[3]=h; g_viewportState[4]=nz; g_viewportState[5]=fz;
|
||||
GXSetViewport(l, t, w, h, nz, fz);
|
||||
GxWorker::Run([=] { GXSetViewport(l, t, w, h, nz, fz); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801733b4, GX__SetViewport_801733b4, (float l, float t, float w, float h, float nz, float fz), (l, t, w, h, nz, fz));
|
||||
|
||||
@@ -66,7 +66,7 @@ extern "C" void GX__GetViewportv_801733e0(uint32_t oa) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(801733e0, GX__GetViewportv_801733e0, (uint32_t oa), (oa));
|
||||
|
||||
extern "C" void GX__SetZScaleOffset_80173400(float s, float o) {
|
||||
GXSetZScaleOffset(s, o);
|
||||
GxWorker::Run([s, o] { GXSetZScaleOffset(s, o); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) {
|
||||
@@ -80,7 +80,7 @@ extern "C" void GX__SetZScaleOffset_80173400(float s, float o) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173400, GX__SetZScaleOffset_80173400, (float s, float o), (s, o));
|
||||
|
||||
extern "C" void GX__SetScissorBoxOffset_801734e0(int32_t xo, int32_t yo) {
|
||||
GXSetScissorBoxOffset(xo, yo);
|
||||
GxWorker::Run([xo, yo] { GXSetScissorBoxOffset(xo, yo); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) Memory::Write16(gd + 2, 0);
|
||||
@@ -104,7 +104,7 @@ extern "C" void GX__SetScissor_80173430(uint32_t l, uint32_t t, uint32_t w, uint
|
||||
// No viewport replay here: aurora recomputes viewport and scissor together
|
||||
// on every scissor change (set_logical_scissor -> apply_logical_render_state),
|
||||
// so re-issuing the current viewport would be duplicate work.
|
||||
GXSetScissor(l, t, w, h);
|
||||
GxWorker::Run([l, t, w, h] { GXSetScissor(l, t, w, h); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173430, GX__SetScissor_80173430, (uint32_t l, uint32_t t, uint32_t w, uint32_t h), (l, t, w, h));
|
||||
|
||||
@@ -115,7 +115,7 @@ PPC_NATIVE_OVERRIDE_VOID(80173430, GX__SetScissor_80173430, (uint32_t l, uint32_
|
||||
extern "C" void GX__SetProjection_8017301c(uint32_t ma, uint32_t pt) {
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma, 64); float m[16];
|
||||
SwapBeF32ArrayToHost(raw, m, 16);
|
||||
GXSetProjection(m, (GXProjectionType)pt);
|
||||
GxWorker::Run([mtx = std::to_array(m), pt]() mutable { GXSetProjection(mtx.data(), (GXProjectionType)pt); });
|
||||
UpdateProjectionVectorFromMatrix(m, (GXProjectionType)pt);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017301c, GX__SetProjection_8017301c, (uint32_t ma, uint32_t pt), (ma, pt));
|
||||
@@ -126,7 +126,7 @@ extern "C" void GX__SetProjectionv_80173080(uint32_t pa) {
|
||||
GXProjectionType pt=(v[0]!=0.f)?GX_ORTHOGRAPHIC:GX_PERSPECTIVE;
|
||||
float m[16]={0.f}; m[0]=v[1]; m[5]=v[3]; m[10]=v[5]; m[11]=v[6];
|
||||
if(pt==GX_PERSPECTIVE){ m[2]=v[2]; m[6]=v[4]; m[14]=-1.f; } else { m[3]=v[2]; m[7]=v[4]; m[15]=1.f; }
|
||||
GXSetProjection(m, pt);
|
||||
GxWorker::Run([mtx = std::to_array(m), pt]() mutable { GXSetProjection(mtx.data(), pt); });
|
||||
UpdateProjectionVectorFromProjV(v);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173080, GX__SetProjectionv_80173080, (uint32_t pa), (pa));
|
||||
@@ -146,28 +146,51 @@ PPC_NATIVE_OVERRIDE_VOID(801730cc, GX__GetProjectionv_801730cc, (uint32_t pa), (
|
||||
extern "C" void GX__LoadPosMtxImm_8017310c(uint32_t ma, uint32_t id) {
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma); float m[12];
|
||||
SwapBeF32ArrayToHost(raw, m, 12);
|
||||
GXLoadPosMtxImm((float(*)[4])m, id);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadPosMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017310c, GX__LoadPosMtxImm_8017310c, (uint32_t ma, uint32_t id), (ma, id));
|
||||
|
||||
// The matrix is read at load time, as Dolphin does, since the worker can run well behind the game.
|
||||
// Returns false only when the game thread doesn't know the array and the worker must resolve it.
|
||||
static bool IndexedMtxArray(uint32_t attr, uint32_t& base, uint32_t& stride) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
return ApiVtxArray(attr, base, stride);
|
||||
}
|
||||
base = g_hleGxState.vtxArray[attr].base;
|
||||
stride = g_hleGxState.vtxArray[attr].stride;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" void GX__LoadPosMtxIndx_8017315c(uint32_t mi, uint32_t id) {
|
||||
const auto& arr=g_hleGxState.vtxArray[GX_POS_MTX_ARRAY];
|
||||
if(arr.base==0||arr.stride==0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(arr.base+mi*arr.stride, 48);
|
||||
if(raw){ float m[12]; SwapBeF32ArrayToHost(raw,m,12); GXLoadPosMtxImm((float(*)[4])m,id); }
|
||||
uint32_t base = 0, stride = 0;
|
||||
if (!IndexedMtxArray(GX_POS_MTX_ARRAY, base, stride)) {
|
||||
GX_DEFER_CALL(GX__LoadPosMtxIndx_8017315c, mi, id);
|
||||
return;
|
||||
}
|
||||
if (base == 0 || stride == 0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(base+mi*stride, 48);
|
||||
if(raw){
|
||||
float m[12]; SwapBeF32ArrayToHost(raw,m,12);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadPosMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017315c, GX__LoadPosMtxIndx_8017315c, (uint32_t mi, uint32_t id), (mi, id));
|
||||
|
||||
extern "C" void GX__LoadNrmMtxImm_80173188(uint32_t ma, uint32_t id) {
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma, 48); float m[12];
|
||||
SwapBeF32ArrayToHost(raw, m, 12); GXLoadNrmMtxImm((float(*)[4])m, id);
|
||||
SwapBeF32ArrayToHost(raw, m, 12);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadNrmMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173188, GX__LoadNrmMtxImm_80173188, (uint32_t ma, uint32_t id), (ma, id));
|
||||
|
||||
extern "C" void GX__LoadNrmMtxIndx3x3_801731e0(uint32_t mi, uint32_t id) {
|
||||
const auto& arr=g_hleGxState.vtxArray[GX_NRM_MTX_ARRAY];
|
||||
if(arr.base==0||arr.stride==0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(arr.base+mi*arr.stride, 36);
|
||||
uint32_t base = 0, stride = 0;
|
||||
if (!IndexedMtxArray(GX_NRM_MTX_ARRAY, base, stride)) {
|
||||
GX_DEFER_CALL(GX__LoadNrmMtxIndx3x3_801731e0, mi, id);
|
||||
return;
|
||||
}
|
||||
if (base == 0 || stride == 0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(base+mi*stride, 36);
|
||||
if(raw){
|
||||
float src[9]{};
|
||||
float m[12]{};
|
||||
@@ -177,7 +200,7 @@ extern "C" void GX__LoadNrmMtxIndx3x3_801731e0(uint32_t mi, uint32_t id) {
|
||||
m[row * 4 + col] = src[row * 3 + col];
|
||||
}
|
||||
}
|
||||
GXLoadNrmMtxImm((float(*)[4])m, id);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadNrmMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801731e0, GX__LoadNrmMtxIndx3x3_801731e0, (uint32_t mi, uint32_t id), (mi, id));
|
||||
@@ -185,9 +208,13 @@ PPC_NATIVE_OVERRIDE_VOID(801731e0, GX__LoadNrmMtxIndx3x3_801731e0, (uint32_t mi,
|
||||
extern "C" void GX__LoadTexMtxImm_80173234(uint32_t ma, uint32_t id, uint32_t t) {
|
||||
size_t c=(t==(uint32_t)GX_MTX3x4)?12:8;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma,c*4); float l[12]={};
|
||||
SwapBeF32ArrayToHost(raw,l,c); GXLoadTexMtxImm(l, id, (GXTexMtxType)t);
|
||||
SwapBeF32ArrayToHost(raw,l,c);
|
||||
GxWorker::Run([mtx = std::to_array(l), id, t]() mutable { GXLoadTexMtxImm(mtx.data(), id, (GXTexMtxType)t); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173234, GX__LoadTexMtxImm_80173234, (uint32_t ma, uint32_t id, uint32_t t), (ma, id, t));
|
||||
|
||||
extern "C" void GX__SetCurrentMtx_80173214(uint32_t id) { GXSetCurrentMtx(id); }
|
||||
extern "C" void GX__SetCurrentMtx_80173214(uint32_t id) {
|
||||
GX_DEFER_CALL(GX__SetCurrentMtx_80173214, id);
|
||||
GXSetCurrentMtx(id);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173214, GX__SetCurrentMtx_80173214, (uint32_t id), (id));
|
||||
|
||||
@@ -13,6 +13,9 @@ uint32_t CanonicalVtxAttr(uint32_t attr) {
|
||||
return attr == GX_VA_NBT ? GX_VA_NRM : attr;
|
||||
}
|
||||
|
||||
// Array bases as last set through GXSetArray on the game thread.
|
||||
std::array<std::array<uint32_t, 2>, 26> g_apiVtxArrays{};
|
||||
|
||||
// There is deliberately no indexed-aurora path here. The immediate begin path
|
||||
// streams vertex data incrementally, and aurora's indexed-array upload must
|
||||
// precede the draw carrying the max-index bounds, which cannot be known without
|
||||
@@ -27,11 +30,19 @@ void ApplyAuroraVtxStateForBegin(GXVtxFmt fmt) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ApiVtxArray(uint32_t attr, uint32_t& base, uint32_t& stride) {
|
||||
if (attr >= 26) return false;
|
||||
base = g_apiVtxArrays[attr][0];
|
||||
stride = g_apiVtxArrays[attr][1];
|
||||
return base != 0 && stride != 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Vertex Descriptor
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__ClearVtxDesc_8016dc34() {
|
||||
GX_DEFER_CALL0(GX__ClearVtxDesc_8016dc34);
|
||||
bool changed = false;
|
||||
for(int i=0; i<26; ++i){
|
||||
changed |= g_hleGxState.vtxDesc[i] != GX_NONE;
|
||||
@@ -44,6 +55,7 @@ extern "C" void GX__ClearVtxDesc_8016dc34() {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016dc34, GX__ClearVtxDesc_8016dc34, (), ());
|
||||
|
||||
extern "C" void GX__SetVtxDesc_8016d3a4(uint32_t a, uint32_t t) {
|
||||
GX_DEFER_CALL(GX__SetVtxDesc_8016d3a4, a, t);
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if(attr>=26||attr==GX_VA_NULL) return;
|
||||
const GXAttrType oldType = g_hleGxState.vtxDesc[attr];
|
||||
@@ -59,16 +71,22 @@ PPC_NATIVE_OVERRIDE_VOID(8016d3a4, GX__SetVtxDesc_8016d3a4, (uint32_t a, uint32_
|
||||
|
||||
extern "C" void GX__SetVtxDescv_8016d608(uint32_t la) {
|
||||
if(!la) return; uint32_t p=la;
|
||||
std::vector<std::array<uint32_t, 2>> list;
|
||||
while(true){
|
||||
uint32_t a=Memory::Read32(p);
|
||||
if(a==0xFFu) break;
|
||||
GX__SetVtxDesc_8016d3a4(a, Memory::Read32(p+4));
|
||||
list.push_back({a, Memory::Read32(p+4)});
|
||||
p+=8;
|
||||
}
|
||||
GxWorker::Run([list = std::move(list)] {
|
||||
for (const auto& [a, t] : list) GX__SetVtxDesc_8016d3a4(a, t);
|
||||
});
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016d608, GX__SetVtxDescv_8016d608, (uint32_t la), (la));
|
||||
|
||||
// Display lists can change the vertex layout, so answer from the state the worker has applied.
|
||||
extern "C" void GX__GetVtxDesc_8016d9f0(uint32_t a, uint32_t tp) {
|
||||
GX_SYNC_INLINE();
|
||||
GXAttrType type = GX_NONE;
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if (attr < GX_VA_MAX_ATTR && attr != GX_VA_NULL) {
|
||||
@@ -84,6 +102,7 @@ extern "C" void GX__GetVtxDescv_8016dba4(uint32_t la) {
|
||||
if (!la) {
|
||||
return;
|
||||
}
|
||||
GX_SYNC_INLINE();
|
||||
|
||||
uint32_t p = la;
|
||||
for (uint32_t a = GX_VA_PNMTXIDX; a <= GX_VA_TEX7; ++a) {
|
||||
@@ -103,6 +122,7 @@ PPC_NATIVE_OVERRIDE_VOID(8016dba4, GX__GetVtxDescv_8016dba4, (uint32_t la), (la)
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetVtxAttrFmt_8016dc68(uint32_t vf, uint32_t a, uint32_t c, uint32_t t, uint32_t fr) {
|
||||
GX_DEFER_CALL(GX__SetVtxAttrFmt_8016dc68, vf, a, c, t, fr);
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if(vf<8&&attr<26){
|
||||
const VtxAttrFmt oldFmt = g_hleGxState.vtxAttrFmt[vf][attr];
|
||||
@@ -126,16 +146,21 @@ PPC_NATIVE_OVERRIDE_VOID(8016dc68, GX__SetVtxAttrFmt_8016dc68, (uint32_t vf, uin
|
||||
|
||||
extern "C" void GX__SetVtxAttrFmtv_8016de08(uint32_t vf, uint32_t la) {
|
||||
if(!la) return; uint32_t p=la;
|
||||
std::vector<std::array<uint32_t, 4>> list;
|
||||
while(true){
|
||||
uint32_t a=Memory::Read32(p);
|
||||
if(a==0xFFu) break;
|
||||
GX__SetVtxAttrFmt_8016dc68(vf, a, Memory::Read32(p+4), Memory::Read32(p+8), Memory::Read32(p+12)&0xFFu);
|
||||
list.push_back({a, Memory::Read32(p+4), Memory::Read32(p+8), Memory::Read32(p+12)&0xFFu});
|
||||
p+=16;
|
||||
}
|
||||
GxWorker::Run([vf, list = std::move(list)] {
|
||||
for (const auto& [a, c, t, fr] : list) GX__SetVtxAttrFmt_8016dc68(vf, a, c, t, fr);
|
||||
});
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016de08, GX__SetVtxAttrFmtv_8016de08, (uint32_t vf, uint32_t la), (vf, la));
|
||||
|
||||
extern "C" void GX__GetVtxAttrFmt_8016e04c(uint32_t vf, uint32_t a, uint32_t cp, uint32_t tp, uint32_t fp) {
|
||||
GX_SYNC_INLINE();
|
||||
VtxAttrFmt fmt{};
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if (vf < 8 && attr < GX_VA_MAX_ATTR && attr != GX_VA_NULL) {
|
||||
@@ -157,6 +182,7 @@ extern "C" void GX__GetVtxAttrFmtv_8016e2b8(uint32_t vf, uint32_t la) {
|
||||
if (!la) {
|
||||
return;
|
||||
}
|
||||
GX_SYNC_INLINE();
|
||||
|
||||
uint32_t p = la;
|
||||
for (uint32_t a = GX_VA_POS; a <= GX_VA_TEX7; ++a) {
|
||||
@@ -179,6 +205,11 @@ PPC_NATIVE_OVERRIDE_VOID(8016e2b8, GX__GetVtxAttrFmtv_8016e2b8, (uint32_t vf, ui
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetArray_8016e32c(uint32_t a, uint32_t ba, uint32_t str) {
|
||||
if (!GxWorker::OnWorker()) {
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if (attr < 26) g_apiVtxArrays[attr] = {ba, str};
|
||||
}
|
||||
GX_DEFER_CALL(GX__SetArray_8016e32c, a, ba, str);
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if(attr<26){ g_hleGxState.vtxArray[attr].base=ba; g_hleGxState.vtxArray[attr].stride=str; }
|
||||
}
|
||||
@@ -194,11 +225,13 @@ PPC_NATIVE_OVERRIDE_VOID(8016e1c4, GX__SetArray_8016e1c4, (uint32_t a, uint32_t
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetNumTexGens_8016e5a4(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumTexGens_8016e5a4, n);
|
||||
GXSetNumTexGens((u8)n);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016e5a4, GX__SetNumTexGens_8016e5a4, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetTexCoordGen2_8016e37c(uint32_t dc, uint32_t f, uint32_t sp, uint32_t m, uint32_t n, uint32_t pm) {
|
||||
GX_DEFER_CALL(GX__SetTexCoordGen2_8016e37c, dc, f, sp, m, n, pm);
|
||||
if (sp >= static_cast<uint32_t>(GX_MAX_TEXGENSRC)) {
|
||||
uint32_t pc = 0;
|
||||
uint32_t lr = 0;
|
||||
@@ -215,6 +248,7 @@ extern "C" void GX__SetTexCoordGen2_8016e37c(uint32_t dc, uint32_t f, uint32_t s
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016e37c, GX__SetTexCoordGen2_8016e37c, (uint32_t dc, uint32_t f, uint32_t sp, uint32_t m, uint32_t n, uint32_t pm), (dc, f, sp, m, n, pm));
|
||||
|
||||
extern "C" void GX__EnableTexOffsets_8016f37c(uint32_t coord, uint32_t lineEnable, uint32_t pointEnable) {
|
||||
GX_DEFER_CALL(GX__EnableTexOffsets_8016f37c, coord, lineEnable, pointEnable);
|
||||
GXEnableTexOffsets(static_cast<GXTexCoordID>(coord),
|
||||
lineEnable ? GX_TRUE : GX_FALSE,
|
||||
pointEnable ? GX_TRUE : GX_FALSE);
|
||||
@@ -222,11 +256,13 @@ extern "C" void GX__EnableTexOffsets_8016f37c(uint32_t coord, uint32_t lineEnabl
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f37c, GX__EnableTexOffsets_8016f37c, (uint32_t coord, uint32_t lineEnable, uint32_t pointEnable), (coord, lineEnable, pointEnable));
|
||||
|
||||
extern "C" void GX__SetLineWidth_8016f314(uint32_t width, uint32_t texOffsets) {
|
||||
GX_DEFER_CALL(GX__SetLineWidth_8016f314, width, texOffsets);
|
||||
GXSetLineWidth(static_cast<u8>(width), static_cast<GXTexOffset>(texOffsets));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f314, GX__SetLineWidth_8016f314, (uint32_t width, uint32_t texOffsets), (width, texOffsets));
|
||||
|
||||
extern "C" void GX__SetPointSize_8016f348(uint32_t pointSize, uint32_t texOffsets) {
|
||||
GX_DEFER_CALL(GX__SetPointSize_8016f348, pointSize, texOffsets);
|
||||
GXSetPointSize(static_cast<u8>(pointSize), static_cast<GXTexOffset>(texOffsets));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f348, GX__SetPointSize_8016f348, (uint32_t pointSize, uint32_t texOffsets), (pointSize, texOffsets));
|
||||
@@ -274,6 +310,8 @@ void ServiceDeferredTimingDuringGxWork() {
|
||||
}
|
||||
}
|
||||
|
||||
static void GxBeginImpl(uint32_t t, uint32_t vf, uint32_t nv);
|
||||
|
||||
extern "C" void GX__Begin_8016f0f0(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
if(IsDisplayListActive()){
|
||||
WriteDisplayListData((u8)(t|vf), 1);
|
||||
@@ -281,6 +319,11 @@ extern "C" void GX__Begin_8016f0f0(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
return;
|
||||
}
|
||||
ServiceDeferredTimingDuringGxWork();
|
||||
GX_DEFER_CALL(GxBeginImpl, t, vf, nv);
|
||||
GxBeginImpl(t, vf, nv);
|
||||
}
|
||||
|
||||
static void GxBeginImpl(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
ApplyAuroraVtxStateForBegin(static_cast<GXVtxFmt>(vf));
|
||||
g_hleGxState.currentVtxFmt=(GXVtxFmt)vf; g_hleGxState.currentPrim=(GXPrimitive)t;
|
||||
g_hleGxState.vertsRemaining=nv; g_hleGxState.inBegin=true; g_hleGxState.auroraBeginCalled=false;
|
||||
@@ -289,13 +332,19 @@ extern "C" void GX__Begin_8016f0f0(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f0f0, GX__Begin_8016f0f0, (uint32_t t, uint32_t vf, uint32_t nv), (t, vf, nv));
|
||||
|
||||
extern "C" void GX__End_80044b30() { g_hleGxState.inBegin=false; GXEnd(); }
|
||||
extern "C" void GX__End_80044b30() {
|
||||
GX_DEFER_CALL0(GX__End_80044b30); g_hleGxState.inBegin=false; GXEnd(); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80044b30, GX__End_80044b30, (), ());
|
||||
|
||||
extern "C" void GX__End_80048c30() { GX__End_80044b30(); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80048c30, GX__End_80048c30, (), ());
|
||||
|
||||
extern "C" void GX__DrawSphere_80172a30(uint32_t numMajor, uint32_t numMinor) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameActive();
|
||||
GxWorker::Submit([=] { GX__DrawSphere_80172a30(numMajor, numMinor); });
|
||||
return;
|
||||
}
|
||||
constexpr uint32_t kAttrCount = 26;
|
||||
constexpr float kSphereRadius = 1.0f;
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
#include "gx_worker.h"
|
||||
|
||||
#include "runtime_config.h"
|
||||
#include "runtime_log.h"
|
||||
|
||||
#include <dolphin/gx/GXAurora.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
void GxWorkerReplayFifoWrite(uint32_t value, uint32_t sizeBytes);
|
||||
void GxWorkerReplayFifoBurst(const uint8_t* data, uint32_t sizeBytes);
|
||||
|
||||
namespace GxWorker {
|
||||
namespace detail {
|
||||
std::atomic<bool> g_active{false};
|
||||
constinit thread_local bool t_onWorker = false;
|
||||
constinit thread_local int t_inlineDepth = 0;
|
||||
bool g_fifoPending = false;
|
||||
} // namespace detail
|
||||
|
||||
namespace {
|
||||
struct Header {
|
||||
void (*run)(void* payload);
|
||||
uint32_t size;
|
||||
uint32_t reserved;
|
||||
};
|
||||
constexpr size_t kHeaderSize = 16;
|
||||
static_assert(sizeof(Header) == kHeaderSize);
|
||||
|
||||
constexpr size_t kCapacity = size_t{8} << 20;
|
||||
// How long the worker keeps polling before it sleeps; recording arrives in bursts and each wake-up
|
||||
// costs the game thread a lock and notify.
|
||||
constexpr auto kIdleSpin = std::chrono::microseconds{50};
|
||||
constexpr size_t kFifoFlushBytes = 8 * 1024;
|
||||
constexpr uint8_t kFifoBurstTag = 0xB0;
|
||||
|
||||
uint8_t* g_ring = nullptr;
|
||||
uint64_t g_write = 0;
|
||||
std::atomic<uint64_t> g_head{0};
|
||||
std::atomic<uint64_t> g_tail{0};
|
||||
std::atomic<bool> g_consumerWaiting{false};
|
||||
std::atomic<bool> g_producerWaiting{false};
|
||||
std::mutex g_headMutex;
|
||||
std::condition_variable g_headCv;
|
||||
std::mutex g_tailMutex;
|
||||
std::condition_variable g_tailCv;
|
||||
void (*g_waitCallback)() = nullptr;
|
||||
std::atomic<bool> g_stop{false};
|
||||
std::atomic<bool> g_abandon{false};
|
||||
std::atomic<bool> g_exited{false};
|
||||
std::thread* g_thread = nullptr;
|
||||
std::vector<uint8_t> g_fifoBytes;
|
||||
|
||||
constexpr size_t AlignUp(size_t value) { return (value + detail::kAlign - 1) & ~(detail::kAlign - 1); }
|
||||
|
||||
inline void CpuRelax() {
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
_mm_pause();
|
||||
#endif
|
||||
}
|
||||
|
||||
// libc++ on Windows truncates sub-millisecond condvar timeouts to zero (a spin), so the bounded
|
||||
// wait goes straight to the OS there.
|
||||
void WaitTailBriefly(std::unique_lock<std::mutex>& lock) {
|
||||
#if defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
SleepConditionVariableSRW(reinterpret_cast<PCONDITION_VARIABLE>(g_tailCv.native_handle()),
|
||||
reinterpret_cast<PSRWLOCK>(lock.mutex()->native_handle()), 1, 0);
|
||||
#else
|
||||
g_tailCv.wait_for(lock, std::chrono::milliseconds{1});
|
||||
#endif
|
||||
}
|
||||
|
||||
void WaitForTail(uint64_t minTail, bool service) {
|
||||
for (int spin = 0; spin < 256 && g_tail.load(std::memory_order_acquire) < minTail; ++spin) {
|
||||
CpuRelax();
|
||||
}
|
||||
while (g_tail.load(std::memory_order_acquire) < minTail) {
|
||||
{
|
||||
std::unique_lock lock{g_tailMutex};
|
||||
g_producerWaiting.store(true, std::memory_order_seq_cst);
|
||||
if (g_tail.load(std::memory_order_seq_cst) < minTail) {
|
||||
WaitTailBriefly(lock);
|
||||
}
|
||||
g_producerWaiting.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
if (service && g_waitCallback != nullptr && g_tail.load(std::memory_order_acquire) < minTail) {
|
||||
g_waitCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForSpace(size_t bytes) {
|
||||
if (g_write + bytes > kCapacity) {
|
||||
WaitForTail(g_write + bytes - kCapacity, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayFifo(void* payload) {
|
||||
uint32_t size = 0;
|
||||
std::memcpy(&size, payload, sizeof(size));
|
||||
const uint8_t* data = static_cast<const uint8_t*>(payload) + sizeof(size);
|
||||
const uint8_t* end = data + size;
|
||||
while (data < end) {
|
||||
const uint8_t tag = *data++;
|
||||
if (tag == kFifoBurstTag) {
|
||||
uint32_t length = 0;
|
||||
std::memcpy(&length, data, sizeof(length));
|
||||
data += sizeof(length);
|
||||
try {
|
||||
GxWorkerReplayFifoBurst(data, length);
|
||||
} catch (...) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker FIFO burst failed\n");
|
||||
}
|
||||
data += length;
|
||||
} else {
|
||||
uint32_t value = 0;
|
||||
std::memcpy(&value, data, sizeof(value));
|
||||
data += sizeof(value);
|
||||
try {
|
||||
GxWorkerReplayFifoWrite(value, tag);
|
||||
} catch (...) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker FIFO write failed\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerMain() {
|
||||
detail::t_onWorker = true;
|
||||
struct SignalExit {
|
||||
~SignalExit() {
|
||||
std::lock_guard lock{g_tailMutex};
|
||||
g_exited.store(true, std::memory_order_release);
|
||||
g_tailCv.notify_all();
|
||||
}
|
||||
} signalExit;
|
||||
AuroraSetGxProducerThread();
|
||||
#if defined(_WIN32)
|
||||
SetThreadDescription(GetCurrentThread(), L"GX worker");
|
||||
#endif
|
||||
uint64_t tail = g_tail.load(std::memory_order_relaxed);
|
||||
for (;;) {
|
||||
uint64_t head = g_head.load(std::memory_order_acquire);
|
||||
if (head == tail) {
|
||||
const auto spinUntil = std::chrono::steady_clock::now() + kIdleSpin;
|
||||
while (head == tail) {
|
||||
for (int spin = 0; spin < 64 && head == tail; ++spin) {
|
||||
CpuRelax();
|
||||
head = g_head.load(std::memory_order_acquire);
|
||||
}
|
||||
if (head == tail && std::chrono::steady_clock::now() >= spinUntil) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (head == tail) {
|
||||
if (g_stop.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::unique_lock lock{g_headMutex};
|
||||
g_consumerWaiting.store(true, std::memory_order_seq_cst);
|
||||
while (g_head.load(std::memory_order_seq_cst) == tail && !g_stop.load(std::memory_order_acquire)) {
|
||||
g_headCv.wait(lock);
|
||||
}
|
||||
g_consumerWaiting.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
while (tail != head) {
|
||||
if (g_abandon.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
uint8_t* record = g_ring + tail % kCapacity;
|
||||
const Header* header = reinterpret_cast<const Header*>(record);
|
||||
if (header->run != nullptr) {
|
||||
try {
|
||||
header->run(record + kHeaderSize);
|
||||
} catch (const std::exception& error) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker command failed: %s\n", error.what());
|
||||
} catch (...) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker command failed\n");
|
||||
}
|
||||
}
|
||||
tail += header->size;
|
||||
g_tail.store(tail, std::memory_order_seq_cst);
|
||||
if (g_producerWaiting.load(std::memory_order_seq_cst)) {
|
||||
std::lock_guard lock{g_tailMutex};
|
||||
g_tailCv.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace detail {
|
||||
void* Reserve(size_t payloadSize, void (*run)(void*)) {
|
||||
const size_t total = kHeaderSize + AlignUp(payloadSize);
|
||||
if (total > kCapacity) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker command of %zu bytes exceeds the ring\n", total);
|
||||
std::abort();
|
||||
}
|
||||
size_t offset = static_cast<size_t>(g_write % kCapacity);
|
||||
const size_t remaining = kCapacity - offset;
|
||||
if (total > remaining) {
|
||||
WaitForSpace(remaining);
|
||||
auto* wrap = reinterpret_cast<Header*>(g_ring + offset);
|
||||
wrap->run = nullptr;
|
||||
wrap->size = static_cast<uint32_t>(remaining);
|
||||
g_write += remaining;
|
||||
// Publish the skip so the worker can drain past it while we wait for the front.
|
||||
Commit();
|
||||
offset = 0;
|
||||
}
|
||||
WaitForSpace(total);
|
||||
auto* header = reinterpret_cast<Header*>(g_ring + offset);
|
||||
header->run = run;
|
||||
header->size = static_cast<uint32_t>(total);
|
||||
g_write += total;
|
||||
return g_ring + offset + kHeaderSize;
|
||||
}
|
||||
|
||||
void Commit() {
|
||||
g_head.store(g_write, std::memory_order_seq_cst);
|
||||
if (g_consumerWaiting.load(std::memory_order_seq_cst)) {
|
||||
std::lock_guard lock{g_headMutex};
|
||||
g_headCv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void FlushFifo() {
|
||||
g_fifoPending = false;
|
||||
if (g_fifoBytes.empty()) {
|
||||
return;
|
||||
}
|
||||
const uint32_t size = static_cast<uint32_t>(g_fifoBytes.size());
|
||||
void* payload = Reserve(sizeof(size) + size, &ReplayFifo);
|
||||
std::memcpy(payload, &size, sizeof(size));
|
||||
std::memcpy(static_cast<uint8_t*>(payload) + sizeof(size), g_fifoBytes.data(), size);
|
||||
g_fifoBytes.clear();
|
||||
Commit();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
void FifoWrite(uint32_t value, uint32_t sizeBytes) {
|
||||
const size_t at = g_fifoBytes.size();
|
||||
g_fifoBytes.resize(at + 1 + sizeof(value));
|
||||
g_fifoBytes[at] = static_cast<uint8_t>(sizeBytes);
|
||||
std::memcpy(g_fifoBytes.data() + at + 1, &value, sizeof(value));
|
||||
detail::g_fifoPending = true;
|
||||
if (g_fifoBytes.size() >= kFifoFlushBytes) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
}
|
||||
|
||||
void FifoBurst(const uint8_t* data, uint32_t sizeBytes) {
|
||||
const size_t at = g_fifoBytes.size();
|
||||
g_fifoBytes.resize(at + 1 + sizeof(sizeBytes) + sizeBytes);
|
||||
g_fifoBytes[at] = kFifoBurstTag;
|
||||
std::memcpy(g_fifoBytes.data() + at + 1, &sizeBytes, sizeof(sizeBytes));
|
||||
std::memcpy(g_fifoBytes.data() + at + 1 + sizeof(sizeBytes), data, sizeBytes);
|
||||
detail::g_fifoPending = true;
|
||||
if (g_fifoBytes.size() >= kFifoFlushBytes) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
if (!Active() || detail::t_onWorker) {
|
||||
return;
|
||||
}
|
||||
// Guest callbacks serviced while waiting can record more work.
|
||||
do {
|
||||
if (detail::g_fifoPending) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
WaitForTail(g_write, true);
|
||||
} while (detail::g_fifoPending || g_tail.load(std::memory_order_acquire) != g_write);
|
||||
}
|
||||
|
||||
void SetWaitCallback(void (*callback)()) { g_waitCallback = callback; }
|
||||
|
||||
void BeginInline() {
|
||||
if (detail::t_inlineDepth == 0 && Active() && !detail::t_onWorker) {
|
||||
Sync();
|
||||
}
|
||||
++detail::t_inlineDepth;
|
||||
}
|
||||
|
||||
void EndInline() { --detail::t_inlineDepth; }
|
||||
|
||||
void Start() {
|
||||
if (Active()) {
|
||||
return;
|
||||
}
|
||||
if (!RuntimeConfigFile::GxWorkerEnabled(true)) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker disabled\n");
|
||||
return;
|
||||
}
|
||||
if (g_ring == nullptr) {
|
||||
g_ring = static_cast<uint8_t*>(::operator new(kCapacity, std::align_val_t{64}));
|
||||
}
|
||||
g_fifoBytes.reserve(kFifoFlushBytes + 4096);
|
||||
g_stop.store(false, std::memory_order_relaxed);
|
||||
g_abandon.store(false, std::memory_order_relaxed);
|
||||
g_exited.store(false, std::memory_order_relaxed);
|
||||
g_thread = new std::thread(WorkerMain);
|
||||
detail::g_active.store(true, std::memory_order_release);
|
||||
RT_LOGF(RT_TAG_GX, "GX worker started\n");
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (!Active()) {
|
||||
return;
|
||||
}
|
||||
// Guest state may already be torn down; don't service it while draining.
|
||||
g_waitCallback = nullptr;
|
||||
Sync();
|
||||
detail::g_active.store(false, std::memory_order_release);
|
||||
g_stop.store(true, std::memory_order_release);
|
||||
Submit([] {});
|
||||
if (g_thread != nullptr) {
|
||||
g_thread->join();
|
||||
delete g_thread;
|
||||
g_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Abandon() {
|
||||
if (!Active() || detail::t_onWorker) {
|
||||
return;
|
||||
}
|
||||
detail::g_active.store(false, std::memory_order_release);
|
||||
g_abandon.store(true, std::memory_order_release);
|
||||
g_stop.store(true, std::memory_order_release);
|
||||
{
|
||||
std::lock_guard lock{g_headMutex};
|
||||
g_headCv.notify_one();
|
||||
}
|
||||
bool exited = false;
|
||||
{
|
||||
std::unique_lock lock{g_tailMutex};
|
||||
exited = g_tailCv.wait_for(lock, std::chrono::seconds{2},
|
||||
[] { return g_exited.load(std::memory_order_acquire); });
|
||||
}
|
||||
if (g_thread != nullptr) {
|
||||
if (exited) {
|
||||
g_thread->join();
|
||||
delete g_thread;
|
||||
} else {
|
||||
g_thread->detach();
|
||||
}
|
||||
g_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GxWorker
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "hle/hollywood_gpio.h"
|
||||
|
||||
#include "runtime_log.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace HollywoodGpio {
|
||||
|
||||
constexpr uint32_t kSensorBarPower = 0x100;
|
||||
|
||||
std::atomic<uint32_t> g_gpioBOut{0};
|
||||
|
||||
} // namespace HollywoodGpio
|
||||
|
||||
bool HollywoodGpio_Read32(uint32_t address, uint32_t& value) {
|
||||
if (address != kHollywoodGpioBOut) {
|
||||
return false;
|
||||
}
|
||||
value = HollywoodGpio::g_gpioBOut.load(std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HollywoodGpio_Write32(uint32_t address, uint32_t value) {
|
||||
if (address != kHollywoodGpioBOut) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t previous = HollywoodGpio::g_gpioBOut.exchange(value, std::memory_order_relaxed);
|
||||
if ((previous ^ value) & HollywoodGpio::kSensorBarPower) {
|
||||
RT_LOG(RT_TAG_HLE) << "Sensor bar power " << ((value & HollywoodGpio::kSensorBarPower) ? "on" : "off") << std::endl;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
#include "hle_stubs.h"
|
||||
#include "input_bindings.h"
|
||||
#include "memory.h"
|
||||
#include "wii_remote_input.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
// KPAD HLE fed by a real Bluetooth Wii Remote. The game calls KPADRead once per
|
||||
// frame with room for 16 KPADStatus entries and only looks at entry 0; with a
|
||||
// Classic Controller it also calls KPADGetUnifiedWpadStatus for the raw
|
||||
// WPADCLStatus (buttons, sticks and triggers of the extension).
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kKpadStatusSize = 0x84;
|
||||
|
||||
// KPADStatus field offsets (RVL SDK).
|
||||
constexpr uint32_t kHold = 0x00, kTrig = 0x04, kRelease = 0x08, kAcc = 0x0C, kAccValue = 0x18,
|
||||
kAccSpeed = 0x1C, kPos = 0x20, kAccVertical = 0x54, kDevType = 0x5C, kWpadErr = 0x5D,
|
||||
kDpdValidFg = 0x5E, kDataFormat = 0x5F, kFsStick = 0x60, kFsAcc = 0x68, kFsAccValue = 0x74,
|
||||
kFsAccSpeed = 0x78;
|
||||
// KPADStatus.ex_status.cl (KPADEXStatus, Classic Controller view).
|
||||
constexpr uint32_t kClHold = 0x60, kClTrig = 0x64, kClRelease = 0x68, kClLStick = 0x6C, kClRStick = 0x74,
|
||||
kClLTrigger = 0x7C, kClRTrigger = 0x80;
|
||||
|
||||
// KPADUnifiedWpadStatus: WPADStatus / WPADFSStatus / WPADCLStatus union, then fmt.
|
||||
constexpr uint32_t kUnifiedSize = 0x38;
|
||||
constexpr uint32_t kUButton = 0x00, kUAccX = 0x02, kUAccY = 0x04, kUAccZ = 0x06, kUObj = 0x08, kUDev = 0x28,
|
||||
kUErr = 0x29, kUFsStickX = 0x2A, kUFsStickY = 0x2B, kUFsAccX = 0x2C, kUFsAccY = 0x2E,
|
||||
kUFsAccZ = 0x30, kUClButton = 0x2A, kUClLStickX = 0x2C, kUClLStickY = 0x2E, kUClRStickX = 0x30,
|
||||
kUClRStickY = 0x32, kUClTriggerL = 0x34, kUClTriggerR = 0x35, kUFmt = 0x36;
|
||||
|
||||
// WPAD device types (WPAD_DEV_*) and the data formats KPAD runs each of them
|
||||
// in (WPAD_FMT_*_ACC_DPD): the values KPADStatus.dev_type / data_format and
|
||||
// KPADUnifiedWpadStatus.dev / fmt carry on the console.
|
||||
constexpr uint8_t kDevCore = 0;
|
||||
constexpr uint8_t kDevFreestyle = 1;
|
||||
constexpr uint8_t kDevClassic = 2;
|
||||
constexpr uint8_t kFmtCoreAccDpd = 2;
|
||||
constexpr uint8_t kFmtFreestyleAccDpd = 5;
|
||||
constexpr uint8_t kFmtClassicAccDpd = 8;
|
||||
constexpr int8_t kWpadErrNone = 0;
|
||||
constexpr int8_t kWpadErrNoController = -1;
|
||||
|
||||
// Raw accelerometer as WPADStatus carries it: 10 bits, 0x200 at 0 g, 100 per g.
|
||||
constexpr float kRawAccZero = 512.0f;
|
||||
constexpr float kRawAccPerG = 100.0f;
|
||||
|
||||
struct ChannelState {
|
||||
uint32_t prevHold = 0;
|
||||
uint32_t prevClHold = 0;
|
||||
float prevAcc[3] = {0.0f, -1.0f, 0.0f};
|
||||
float prevFsAcc[3] = {0.0f, -1.0f, 0.0f};
|
||||
};
|
||||
|
||||
std::array<ChannelState, 4> g_channels{};
|
||||
|
||||
// Euclidean length of a 3-vector.
|
||||
float Length(const float* v) {
|
||||
return std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
}
|
||||
|
||||
// Euclidean distance between two 3-vectors.
|
||||
float Distance(const float* a, const float* b) {
|
||||
const float d[3] = {a[0] - b[0], a[1] - b[1], a[2] - b[2]};
|
||||
return Length(d);
|
||||
}
|
||||
|
||||
// Writes three big-endian floats to guest memory.
|
||||
void WriteVec3(uint32_t addr, const float* v) {
|
||||
Memory::WriteFloat32(addr, v[0]);
|
||||
Memory::WriteFloat32(addr + 4, v[1]);
|
||||
Memory::WriteFloat32(addr + 8, v[2]);
|
||||
}
|
||||
|
||||
// Zeroes `count` consecutive floats in guest memory.
|
||||
void WriteZeroFloats(uint32_t addr, uint32_t count) {
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
Memory::WriteFloat32(addr + i * 4, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Fills one KPADStatus at `addr` and returns the number of valid entries (1),
|
||||
// or writes an "unplugged" status and returns 0.
|
||||
int32_t WriteStatus(uint32_t chan, uint32_t addr, const WiiRemoteInput::KpadSample* sample) {
|
||||
ChannelState& state = g_channels[chan];
|
||||
if (sample == nullptr) {
|
||||
state = {};
|
||||
Memory::Write32(addr + kHold, 0);
|
||||
Memory::Write32(addr + kTrig, 0);
|
||||
Memory::Write32(addr + kRelease, 0);
|
||||
Memory::Write8(addr + kDevType, kDevCore);
|
||||
Memory::Write8(addr + kWpadErr, static_cast<uint8_t>(kWpadErrNoController));
|
||||
Memory::Write8(addr + kDpdValidFg, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint32_t hold = sample->hold;
|
||||
Memory::Write32(addr + kHold, hold);
|
||||
Memory::Write32(addr + kTrig, hold & ~state.prevHold);
|
||||
Memory::Write32(addr + kRelease, state.prevHold & ~hold);
|
||||
state.prevHold = hold;
|
||||
|
||||
WriteVec3(addr + kAcc, sample->acc);
|
||||
Memory::WriteFloat32(addr + kAccValue, Length(sample->acc));
|
||||
Memory::WriteFloat32(addr + kAccSpeed, Distance(sample->acc, state.prevAcc));
|
||||
for (int i = 0; i < 3; ++i) state.prevAcc[i] = sample->acc[i];
|
||||
|
||||
// No IR pointer: pos .. acc_vertical zeroed and dpd_valid_fg clear, which
|
||||
// the game treats as "pointing away from the screen".
|
||||
WriteZeroFloats(addr + kPos, (kAccVertical + 8 - kPos) / 4);
|
||||
Memory::Write8(addr + kDpdValidFg, 0);
|
||||
|
||||
const uint8_t devType = sample->hasClassic ? kDevClassic : sample->hasNunchuk ? kDevFreestyle : kDevCore;
|
||||
const uint8_t dataFormat =
|
||||
sample->hasClassic ? kFmtClassicAccDpd : sample->hasNunchuk ? kFmtFreestyleAccDpd : kFmtCoreAccDpd;
|
||||
Memory::Write8(addr + kDevType, devType);
|
||||
Memory::Write8(addr + kWpadErr, static_cast<uint8_t>(kWpadErrNone));
|
||||
Memory::Write8(addr + kDataFormat, dataFormat);
|
||||
|
||||
if (sample->hasClassic) {
|
||||
const uint32_t clHold = sample->clHold;
|
||||
Memory::Write32(addr + kClHold, clHold);
|
||||
Memory::Write32(addr + kClTrig, clHold & ~state.prevClHold);
|
||||
Memory::Write32(addr + kClRelease, state.prevClHold & ~clHold);
|
||||
state.prevClHold = clHold;
|
||||
Memory::WriteFloat32(addr + kClLStick, sample->clLStick[0]);
|
||||
Memory::WriteFloat32(addr + kClLStick + 4, sample->clLStick[1]);
|
||||
Memory::WriteFloat32(addr + kClRStick, sample->clRStick[0]);
|
||||
Memory::WriteFloat32(addr + kClRStick + 4, sample->clRStick[1]);
|
||||
Memory::WriteFloat32(addr + kClLTrigger, sample->clTriggerL / 255.0f);
|
||||
Memory::WriteFloat32(addr + kClRTrigger, sample->clTriggerR / 255.0f);
|
||||
} else if (sample->hasNunchuk) {
|
||||
state.prevClHold = 0;
|
||||
Memory::WriteFloat32(addr + kFsStick, sample->stick[0]);
|
||||
Memory::WriteFloat32(addr + kFsStick + 4, sample->stick[1]);
|
||||
WriteVec3(addr + kFsAcc, sample->nunchukAcc);
|
||||
Memory::WriteFloat32(addr + kFsAccValue, Length(sample->nunchukAcc));
|
||||
Memory::WriteFloat32(addr + kFsAccSpeed, Distance(sample->nunchukAcc, state.prevFsAcc));
|
||||
for (int i = 0; i < 3; ++i) state.prevFsAcc[i] = sample->nunchukAcc[i];
|
||||
} else {
|
||||
state.prevClHold = 0;
|
||||
WriteZeroFloats(addr + kFsStick, (kKpadStatusSize - kFsStick) / 4);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// One accelerometer axis of KPAD's g vector back to the 10-bit raw WPAD value.
|
||||
uint16_t RawAcc(float g) {
|
||||
const float raw = kRawAccZero + g * kRawAccPerG;
|
||||
return static_cast<uint16_t>(std::clamp(raw, 0.0f, 1023.0f));
|
||||
}
|
||||
|
||||
// Fills one KPADUnifiedWpadStatus at `addr` from the sample: the WPADStatus core
|
||||
// (remote buttons, raw accelerometer, no IR objects), then the Nunchuk or Classic
|
||||
// Controller tail, then the data format.
|
||||
void WriteUnifiedStatus(uint32_t addr, const WiiRemoteInput::KpadSample* sample) {
|
||||
for (uint32_t offset = 0; offset < kUnifiedSize; offset += 4) {
|
||||
Memory::Write32(addr + offset, 0);
|
||||
}
|
||||
if (sample == nullptr) {
|
||||
Memory::Write8(addr + kUDev, kDevCore);
|
||||
Memory::Write8(addr + kUErr, static_cast<uint8_t>(kWpadErrNoController));
|
||||
Memory::Write8(addr + kUFmt, kFmtCoreAccDpd);
|
||||
return;
|
||||
}
|
||||
Memory::Write16(addr + kUButton, static_cast<uint16_t>(sample->hold & 0xFFFF));
|
||||
// KPAD's acc is (-wiiX, -wiiZ, wiiY); WPADStatus keeps the remote's own axes.
|
||||
Memory::Write16(addr + kUAccX, RawAcc(-sample->acc[0]));
|
||||
Memory::Write16(addr + kUAccY, RawAcc(sample->acc[2]));
|
||||
Memory::Write16(addr + kUAccZ, RawAcc(-sample->acc[1]));
|
||||
// No IR: every DPDObject invalid (x/y at the sensor's out-of-range value).
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
Memory::Write16(addr + kUObj + i * 8, 0x3FF);
|
||||
Memory::Write16(addr + kUObj + i * 8 + 2, 0x3FF);
|
||||
}
|
||||
Memory::Write8(addr + kUErr, static_cast<uint8_t>(kWpadErrNone));
|
||||
if (sample->hasClassic) {
|
||||
Memory::Write8(addr + kUDev, kDevClassic);
|
||||
Memory::Write16(addr + kUClButton, static_cast<uint16_t>(sample->clHold & 0xFFFF));
|
||||
Memory::Write16(addr + kUClLStickX, static_cast<uint16_t>(sample->clLStickRaw[0]));
|
||||
Memory::Write16(addr + kUClLStickY, static_cast<uint16_t>(sample->clLStickRaw[1]));
|
||||
Memory::Write16(addr + kUClRStickX, static_cast<uint16_t>(sample->clRStickRaw[0]));
|
||||
Memory::Write16(addr + kUClRStickY, static_cast<uint16_t>(sample->clRStickRaw[1]));
|
||||
Memory::Write8(addr + kUClTriggerL, sample->clTriggerL);
|
||||
Memory::Write8(addr + kUClTriggerR, sample->clTriggerR);
|
||||
Memory::Write8(addr + kUFmt, kFmtClassicAccDpd);
|
||||
} else if (sample->hasNunchuk) {
|
||||
Memory::Write8(addr + kUDev, kDevFreestyle);
|
||||
// WPADFSStatus: 8-bit stick (centre 128) and 10-bit Nunchuk accelerometer.
|
||||
Memory::Write8(addr + kUFsStickX,
|
||||
static_cast<uint8_t>(std::clamp(128.0f + sample->stick[0] * 100.0f, 0.0f, 255.0f)));
|
||||
Memory::Write8(addr + kUFsStickY,
|
||||
static_cast<uint8_t>(std::clamp(128.0f + sample->stick[1] * 100.0f, 0.0f, 255.0f)));
|
||||
Memory::Write16(addr + kUFsAccX, RawAcc(-sample->nunchukAcc[0]));
|
||||
Memory::Write16(addr + kUFsAccY, RawAcc(sample->nunchukAcc[2]));
|
||||
Memory::Write16(addr + kUFsAccZ, RawAcc(-sample->nunchukAcc[1]));
|
||||
Memory::Write8(addr + kUFmt, kFmtFreestyleAccDpd);
|
||||
} else {
|
||||
Memory::Write8(addr + kUDev, kDevCore);
|
||||
Memory::Write8(addr + kUFmt, kFmtCoreAccDpd);
|
||||
}
|
||||
}
|
||||
|
||||
// Neutral sticks and no buttons while an overlay owns input; the remote stays connected.
|
||||
bool ReadSample(uint32_t chan, WiiRemoteInput::KpadSample& sample) {
|
||||
if (!WiiRemoteInput::ReadKpadSample(chan, sample)) {
|
||||
return false;
|
||||
}
|
||||
if (InputBindings::InputBlocked()) {
|
||||
sample.hold = 0;
|
||||
sample.clHold = 0;
|
||||
sample.stick[0] = sample.stick[1] = 0.0f;
|
||||
sample.clLStick[0] = sample.clLStick[1] = 0.0f;
|
||||
sample.clRStick[0] = sample.clRStick[1] = 0.0f;
|
||||
sample.clLStickRaw[0] = sample.clLStickRaw[1] = 0;
|
||||
sample.clRStickRaw[0] = sample.clRStickRaw[1] = 0;
|
||||
sample.clTriggerL = sample.clTriggerR = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// KPADRead: fills KPADStatus[0] for `chan` from the Bluetooth remote, returns the entry count.
|
||||
extern "C" int32_t KPAD__Read_HLE(uint32_t chan, uint32_t statusPtr, uint32_t count)
|
||||
{
|
||||
if (chan >= g_channels.size() || statusPtr == 0 || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
WiiRemoteInput::KpadSample sample;
|
||||
const bool have = ReadSample(chan, sample);
|
||||
try {
|
||||
return WriteStatus(chan, statusPtr, have ? &sample : nullptr);
|
||||
} catch (const Memory::AccessViolation&) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE(80197380, KPAD__Read_HLE, int32_t, (uint32_t chan, uint32_t statusPtr, uint32_t count),
|
||||
(chan, statusPtr, count));
|
||||
|
||||
// KPADGetUnifiedWpadStatus: the raw WPAD status behind KPADStatus. The game
|
||||
// reads the Classic Controller's buttons, sticks and triggers from here. The
|
||||
// SDK fills `count` entries with the channel's recent samples (the game asks for
|
||||
// as many as it asked KPADRead for and looks at entry 0); with one sample per
|
||||
// frame here, every entry gets the current one.
|
||||
extern "C" int32_t KPAD__GetUnifiedWpadStatus_HLE(uint32_t chan, uint32_t statusPtr, uint32_t count)
|
||||
{
|
||||
constexpr uint32_t kMaxEntries = 16; // KPAD_MAX_READ_BUFS
|
||||
if (chan >= g_channels.size() || statusPtr == 0 || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
WiiRemoteInput::KpadSample sample;
|
||||
const bool have = ReadSample(chan, sample);
|
||||
try {
|
||||
const uint32_t entries = std::min(count, kMaxEntries);
|
||||
for (uint32_t i = 0; i < entries; ++i) {
|
||||
WriteUnifiedStatus(statusPtr + i * kUnifiedSize, have ? &sample : nullptr);
|
||||
}
|
||||
} catch (const Memory::AccessViolation&) {
|
||||
return 0;
|
||||
}
|
||||
return have ? 1 : 0;
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE(8019812C, KPAD__GetUnifiedWpadStatus_HLE, int32_t,
|
||||
(uint32_t chan, uint32_t statusPtr, uint32_t count), (chan, statusPtr, count));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user