Files
Yaya48 c230971f07 IOS LLE: checkpoint NAND conformance and diagnostic tooling
Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
2026-09-23 13:30:17 +02:00

158 lines
7.2 KiB
PowerShell

param(
[Parameter(Mandatory)][string]$CaptureDirectory,
[Parameter(Mandatory)][string]$OutputDirectory,
[string]$PythonExecutable,
[ValidateRange(0,2147483647)][int]$NativePID=0,
[string]$NativePDB,
[string]$XperfRangeStart,
[string]$XperfRangeEnd
)
# Read-only input: qualify a synthetic capture, or inspect one native Dolphin PID.
# Output is a new directory; no emulator, NAND or WPR session is touched.
$ErrorActionPreference = 'Stop'
if (($NativePID -eq 0) -ne (-not $NativePDB)) {
throw 'For native analysis pass both -NativePID and -NativePDB.'
}
if ([bool]$XperfRangeStart -ne [bool]$XperfRangeEnd) {
throw 'Pass both -XperfRangeStart and -XperfRangeEnd, or neither.'
}
if ($XperfRangeStart -and -not $NativePID) {
throw 'Time-window export is only supported for exploratory native analysis.'
}
if ($PythonExecutable) {
$p1Python = (Resolve-Path -LiteralPath $PythonExecutable).Path
} else {
$p1Candidates = @(Get-Command python.exe -All -ErrorAction SilentlyContinue |
Where-Object { $_.Source -notlike '*\WindowsApps\*' } |
Select-Object -ExpandProperty Source)
$p1Candidates += 'C:\msys64\mingw64\bin\python.exe'
foreach ($p1Candidate in $p1Candidates) {
if ($p1Candidate -and (Test-Path -LiteralPath $p1Candidate)) {
$p1Python = $p1Candidate
break
}
}
}
if (-not $p1Python) {
throw 'Python was not found before analysis. Pass -PythonExecutable with the path to a working python.exe.'
}
& $p1Python --version | Out-Null
if ($LASTEXITCODE) { throw "Python could not start: $p1Python" }
function ConvertTo-P1WindowsArgument([string]$Value) {
$p1Quoted = '"'
$p1Slashes = 0
foreach ($p1Char in $Value.ToCharArray()) {
if ($p1Char -eq '\') { ++$p1Slashes; continue }
if ($p1Char -eq '"') {
$p1Quoted += ('\' * (2 * $p1Slashes + 1)) + '"'
$p1Slashes = 0
continue
}
$p1Quoted += ('\' * $p1Slashes) + $p1Char
$p1Slashes = 0
}
return $p1Quoted + ('\' * (2 * $p1Slashes)) + '"'
}
$p1Repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
$p1Input = [IO.Path]::GetFullPath($(if ([IO.Path]::IsPathRooted($CaptureDirectory)) {
$CaptureDirectory
} else {
Join-Path (Get-Location).ProviderPath $CaptureDirectory
}))
$p1Output = [IO.Path]::GetFullPath($(if ([IO.Path]::IsPathRooted($OutputDirectory)) {
$OutputDirectory
} else {
Join-Path $p1Repo $OutputDirectory
}))
if (Test-Path -LiteralPath $p1Output) { throw 'Use a NEW analysis directory; no results are overwritten.' }
$p1ETL = Join-Path $p1Input 'capture.etl'
$p1ManifestPath = Join-Path $p1Input 'manifest.json'
$p1StdoutPath = Join-Path $p1Input 'probe.stdout.log'
$p1Decoder = Join-Path $p1Repo '.starlet-profile/P1-tools/DecodeP1.exe'
$p1RequiredInputs = @($p1ETL, $p1ManifestPath, $p1Decoder)
if ($NativePID) {
$NativePDB = (Resolve-Path -LiteralPath $NativePDB).Path
$p1RequiredInputs += $NativePDB
} else {
$p1RequiredInputs += $p1StdoutPath
}
foreach ($p1Required in $p1RequiredInputs) {
if (-not (Test-Path -LiteralPath $p1Required)) { throw "Missing analysis input: $p1Required" }
}
$p1Drive = [IO.DriveInfo]::new([IO.Path]::GetPathRoot($p1Output))
$p1RequiredFree = if ($NativePID) { 20GB } else { 7GB }
if ($p1Drive.AvailableFreeSpace -lt $p1RequiredFree) {
throw "Need at least $([math]::Round($p1RequiredFree / 1GB)) GiB free for offline analysis."
}
New-Item -ItemType Directory -Path $p1Output | Out-Null
Copy-Item -LiteralPath $p1ManifestPath -Destination $p1Output
if (-not $NativePID) { Copy-Item -LiteralPath $p1StdoutPath -Destination $p1Output }
$p1Manifest = Get-Content -LiteralPath $p1ManifestPath -Raw | ConvertFrom-Json
$p1TargetPID = if ($NativePID) { $NativePID } else { $p1Manifest.ProbePID }
if (-not $p1TargetPID) { throw 'Target PID missing from capture manifest.' }
$p1Native = Join-Path $p1Output 'native-events.jsonl'
$p1DecodeText = & $p1Decoder $p1ETL $p1Native
$p1DecodeExit = $LASTEXITCODE
$p1DecodeText | Out-File -LiteralPath (Join-Path $p1Output 'decoder-summary.json') -Encoding utf8
if ($p1DecodeExit) { throw 'TDH native decode or ETW loss check failed; see decoder-summary.json.' }
$p1Csv = Join-Path $p1Output 'xperf-symbols.csv'
$p1PriorSymbols = [Environment]::GetEnvironmentVariable('_NT_SYMBOL_PATH', 'Process')
$p1PriorCache = [Environment]::GetEnvironmentVariable('_NT_SYMCACHE_PATH', 'Process')
try {
$env:_NT_SYMBOL_PATH = if ($NativePID) { Split-Path $NativePDB -Parent } else {
Split-Path $p1Manifest.ProbePDB -Parent
}
$env:_NT_SYMCACHE_PATH = Join-Path $p1Output 'symcache'
$p1Info = [Diagnostics.ProcessStartInfo]::new((Get-Command xperf.exe).Source)
$p1Info.UseShellExecute = $false
$p1Info.CreateNoWindow = $true
$p1Info.RedirectStandardOutput = $true
$p1Info.RedirectStandardError = $true
$p1Info.WorkingDirectory = $p1Repo
$p1XperfArgs = @('-i', $p1ETL, '-o', $p1Csv, '-symbols', '-target',
'machine', '-quiet', '-a', 'dumper')
if ($XperfRangeStart) {
$p1XperfArgs += @('-range', $XperfRangeStart, $XperfRangeEnd)
[pscustomobject]@{ Start=$XperfRangeStart; End=$XperfRangeEnd;
Note='Xperf CPU samples are limited to this window; native scopes and counters span the full ETL.' } |
ConvertTo-Json | Out-File -LiteralPath (Join-Path $p1Output 'analysis-range.json') -Encoding utf8
}
$p1Info.Arguments = ($p1XperfArgs | ForEach-Object {
ConvertTo-P1WindowsArgument $_
}) -join ' '
$p1Process = [Diagnostics.Process]::Start($p1Info)
$p1Stdout = $p1Process.StandardOutput.ReadToEndAsync()
$p1Stderr = $p1Process.StandardError.ReadToEndAsync()
$p1TimeoutMs = if ($NativePID) { 900000 } else { 180000 }
if (-not $p1Process.WaitForExit($p1TimeoutMs)) {
$p1Process.Kill() # Only the xperf process created by this analysis script.
$p1Process.WaitForExit()
throw "Owned Xperf decoder exceeded $($p1TimeoutMs / 1000) seconds; no input ETL was modified."
}
$p1Stdout.GetAwaiter().GetResult() | Out-File `
-LiteralPath (Join-Path $p1Output 'xperf-stdout.log') -Encoding utf8
$p1Stderr.GetAwaiter().GetResult() | Out-File `
-LiteralPath (Join-Path $p1Output 'xperf-warnings.log') -Encoding utf8
if ($p1Process.ExitCode) { throw 'Xperf kernel decode failed; see xperf-warnings.log.' }
} finally {
[Environment]::SetEnvironmentVariable('_NT_SYMBOL_PATH', $p1PriorSymbols, 'Process')
[Environment]::SetEnvironmentVariable('_NT_SYMCACHE_PATH', $p1PriorCache, 'Process')
}
$p1Combined = Join-Path $p1Output 'combined.jsonl'
$p1XperfSummary = Join-Path $p1Output 'xperf-summary.json'
& $p1Python (Join-Path $PSScriptRoot 'xperf_to_p1.py') $p1Csv $p1Native `
--pid $p1TargetPID --output $p1Combined --summary $p1XperfSummary
if ($LASTEXITCODE) { throw 'Xperf/native clock alignment failed.' }
if ($NativePID) {
& $p1Python (Join-Path $PSScriptRoot 'analyze_p1.py') $p1Combined `
--pid $p1TargetPID --output (Join-Path $p1Output 'native-report.json')
if ($LASTEXITCODE) { throw 'Native offline analysis failed.' }
Write-Output "Exploratory native report: $p1Output\native-report.json (NOT qualified as a complete Wii-update bottleneck report)."
} else {
& $p1Python (Join-Path $PSScriptRoot 'qualify_p1.py') $p1Output `
--events $p1Combined --xperf-summary $p1XperfSummary
if ($LASTEXITCODE) { throw 'Local WPR qualification failed; see qualification.json.' }
Write-Output "Local WPR qualification finished: $p1Output\qualification.json"
}