Recover 16 modified and 6 previously untracked integration files. All 22 source SHA-256s match the pre-publication StarletOS snapshot. Add handoff instructions, byte-preserving attributes and private backup exclusion. This commit publishes existing behavior; it does not claim a new full build or hardware qualification. The two preceding local commits and staged files were screened before publication.
323 lines
18 KiB
PowerShell
323 lines
18 KiB
PowerShell
param(
|
|
[string]$ELF = '../starlet-ios-rust/target/armebv5te-none-eabi/release/revolution-ios-bootstrap',
|
|
[string]$BuildDirectory = '.starlet_msvc2',
|
|
[string]$UserDirectory = '.starlet_user3',
|
|
[ValidateSet('Idle', 'Panic', 'Fatal', 'Fault', 'Phase2', 'Phase3Raw', 'Phase3Ios', 'Phase4Fs', 'Phase5Dir', 'Phase5Es', 'Phase5Client', 'Phase6Write', 'Phase6Verify', 'Phase8Client', 'Phase9Resolve', 'Phase9Launch', 'Phase9Menu', 'Phase9Rescue', 'Phase10AOh1')][string]$ExpectedOutcome = 'Idle',
|
|
[string]$PpcDol = '',
|
|
[int]$TimeoutSeconds = 30,
|
|
# Phase9Launch: the launched program's minimum check count. 5 for the
|
|
# P09-H02 payload and a requested P09-H02M launch; 3 for a P09-H02M launch
|
|
# made by the kernel's watchdog (no launcher mailbox to check).
|
|
[ValidateRange(1, 5)][int]$Phase9MinimumChecks = 5,
|
|
# Phase10AOh1: the program's minimum check count. 40 for the P10A-H07
|
|
# reduced smoke; 16 for the P10A-H07B launched payload.
|
|
[ValidateRange(1, 1000)][int]$Phase10AMinimumChecks = 40,
|
|
[switch]$JIT,
|
|
[switch]$FullDolphin
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$elfCandidate = if ([System.IO.Path]::IsPathRooted($ELF)) { $ELF } else { Join-Path $repoRoot $ELF }
|
|
$elfPath = (Resolve-Path -LiteralPath $elfCandidate).Path
|
|
$dolPath = if ($PpcDol) { (Resolve-Path -LiteralPath $PpcDol).Path } else { '' }
|
|
$probePath = Join-Path $repoRoot "$BuildDirectory/Binaries/Tests/StarletBootstrapProbe.exe"
|
|
if (-not $FullDolphin) {
|
|
if ($PpcDol) { throw 'PpcDol requires FullDolphin' }
|
|
if (-not (Test-Path -LiteralPath $probePath -PathType Leaf)) {
|
|
throw "Build StarletBootstrapProbe first: $probePath"
|
|
}
|
|
$probeArguments = @($elfPath)
|
|
if ($JIT) { $probeArguments += '--jit' }
|
|
if ($ExpectedOutcome -eq 'Panic') { $probeArguments += '--expect-panic' }
|
|
if ($ExpectedOutcome -eq 'Fatal') { $probeArguments += '--expect-fatal-data' }
|
|
if ($ExpectedOutcome -eq 'Fault') { $probeArguments += '--expect-undefined-fault' }
|
|
if ($ExpectedOutcome -eq 'Phase2') { $probeArguments += '--expect-phase2' }
|
|
& $probePath @probeArguments
|
|
exit $LASTEXITCODE
|
|
}
|
|
|
|
$dolphinPath = Join-Path $repoRoot "$BuildDirectory/Binaries/DolphinNoGUI.exe"
|
|
$userPath = (Resolve-Path -LiteralPath (Join-Path $repoRoot $UserDirectory)).Path
|
|
$logPath = Join-Path $userPath 'Logs/dolphin.log'
|
|
|
|
if (-not (Test-Path -LiteralPath $dolphinPath -PathType Leaf)) {
|
|
throw "Build DolphinNoGUI first: $dolphinPath"
|
|
}
|
|
if ($TimeoutSeconds -lt 1) { throw 'TimeoutSeconds must be positive' }
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $logPath) | Out-Null
|
|
|
|
$bootArguments = if ($dolPath) { @('-e', "`"$dolPath`"") } else { @('-n', '0000000100000002') }
|
|
$arguments = @('-u', "`"$userPath`"") + $bootArguments + @(
|
|
'-C', 'Dolphin.Core.WiiIOSLLE=True',
|
|
'-C', 'Dolphin.Core.WiiSDCard=False',
|
|
'-C', "Dolphin.Core.WiiStarletJIT=$($JIT.IsPresent)",
|
|
'-C', "`"Dolphin.General.WiiStarletBootstrapELF=$elfPath`"",
|
|
'-C', 'Logger.Options.WriteToFile=True',
|
|
'-C', 'Logger.Options.Verbosity=4',
|
|
'-C', 'Logger.Logs.IOS=True',
|
|
'-v', 'Null', '-p', 'headless'
|
|
)
|
|
|
|
$position = if (Test-Path -LiteralPath $logPath) { (Get-Item -LiteralPath $logPath).Length } else { 0 }
|
|
$pending = ''
|
|
$trace = [System.Collections.Generic.List[string]]::new()
|
|
$rescueStarted = $false
|
|
$outcome = $null
|
|
$faultLine = $null
|
|
$probeCounters = $null
|
|
$probeChecks = $null
|
|
$dolphin = Start-Process -FilePath $dolphinPath -ArgumentList $arguments `
|
|
-WorkingDirectory $repoRoot -PassThru -WindowStyle Hidden
|
|
|
|
try {
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
while ((Get-Date) -lt $deadline) {
|
|
if (Test-Path -LiteralPath $logPath) {
|
|
$stream = [System.IO.File]::Open($logPath, [System.IO.FileMode]::Open,
|
|
[System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
|
try {
|
|
if ($position -gt $stream.Length) { $position = 0; $pending = '' }
|
|
[void]$stream.Seek($position, [System.IO.SeekOrigin]::Begin)
|
|
$reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8,
|
|
$false, 4096, $true)
|
|
try { $chunk = $reader.ReadToEnd() } finally { $reader.Dispose() }
|
|
$position = $stream.Position
|
|
} finally {
|
|
$stream.Dispose()
|
|
}
|
|
if ($chunk.Length -gt 0) {
|
|
$lines = ($pending + $chunk) -split "`n"
|
|
$pending = $lines[-1]
|
|
for ($index = 0; $index -lt $lines.Length - 1; ++$index) {
|
|
$line = $lines[$index]
|
|
if ($line -match 'Starlet bootstrap: (.+)$') {
|
|
$message = $Matches[1].TrimEnd("`r")
|
|
$trace.Add($message)
|
|
Write-Host $message
|
|
if ($message -eq 'REV:BOOT:IDLE' -and $ExpectedOutcome -notin @('Phase3Raw', 'Phase3Ios', 'Phase4Fs', 'Phase5Dir', 'Phase5Es', 'Phase5Client', 'Phase6Write', 'Phase6Verify', 'Phase8Client', 'Phase9Launch', 'Phase9Menu', 'Phase9Rescue', 'Phase10AOh1')) {
|
|
$outcome = if ($ExpectedOutcome -eq 'Phase2') { 'Phase2' } else { 'Idle' }
|
|
}
|
|
elseif ($message -like 'REV:PANIC*') { $outcome = 'Panic' }
|
|
# Revolution IOS Phase 09 P09-H01: the read-only System Menu resolution ends.
|
|
elseif ($ExpectedOutcome -eq 'Phase9Resolve' -and $message -like 'REV:P9:H01 DONE*') { $outcome = 'Phase9Resolve' }
|
|
elseif ($ExpectedOutcome -eq 'Phase9Resolve' -and $message -like 'REV:P9:H01 *FAIL*') { $outcome = 'Phase9ResolveFailed' }
|
|
# Revolution IOS Phase 09 P09-H04: the System Menu's observation ends with the kernel's
|
|
# summary (no request for a while, or none at all); a load failure ends it too.
|
|
elseif ($ExpectedOutcome -eq 'Phase9Menu' -and ($message -like 'REV:P9:H04 QUIET *' -or $message -like 'REV:P9:H04 NOIPC *')) { $outcome = 'Phase9Menu' }
|
|
elseif ($ExpectedOutcome -eq 'Phase9Menu' -and $message -like 'REV:P9:H0[34] FAIL*') { $outcome = 'Phase9MenuFailed' }
|
|
# P09-H04 rescue: the kernel stopped the Menu and launches the rescue screen, whose
|
|
# status (0x52500005 once drawn) ends the run below.
|
|
elseif ($ExpectedOutcome -eq 'Phase9Rescue' -and $message -like 'REV:P9:H04 RESCUE GO *') { $rescueStarted = $true }
|
|
elseif ($ExpectedOutcome -eq 'Phase9Rescue' -and ($message -like 'REV:P9:H0[34] FAIL*' -or $message -like 'REV:P9:H04 RESCUE PLAN FAIL*')) { $outcome = 'Phase9RescueFailed' }
|
|
elseif ($message -like 'REV:FATAL*') { $outcome = 'Fatal' }
|
|
} elseif ($line -match 'Starlet bootstrap exception|Starlet bootstrap trace length is invalid') {
|
|
$outcome = 'Fault'
|
|
$faultLine = $line.TrimEnd("`r")
|
|
Write-Host $faultLine
|
|
} elseif ($line -match 'Broadway ios-probe status=(0x[0-9a-f]+)') {
|
|
$probeStatus = $Matches[1]
|
|
Write-Host "PPC: $probeStatus"
|
|
if ($probeStatus -eq '0x52500002') { $outcome = 'Phase3Raw' }
|
|
elseif ($probeStatus -eq '0x52500005' -and $ExpectedOutcome -eq 'Phase10AOh1') { $outcome = 'Phase10AOh1' }
|
|
elseif ($probeStatus -eq '0x52500003') { $outcome = 'Phase3Ios' }
|
|
# Phase 09 P09-H02: the payload's success is final only with the kernel's
|
|
# release and first-request lines (checked after each chunk below).
|
|
elseif ($probeStatus -eq '0x52500005' -and $ExpectedOutcome -eq 'Phase9Launch') { $payloadPassed = $true }
|
|
elseif ($ExpectedOutcome -eq 'Phase9Rescue') { if ($probeStatus -eq '0x52500005' -and $rescueStarted) { $outcome = 'Phase9Rescue' } }
|
|
elseif ($probeStatus -eq '0x52500005') { $outcome = $(if ($ExpectedOutcome -in @('Phase5Dir', 'Phase5Es', 'Phase5Client', 'Phase6Write', 'Phase6Verify', 'Phase8Client')) { $ExpectedOutcome } else { 'Phase4Fs' }) }
|
|
# Phase 09 P09-H02M: a later program's status (the launched monitor
|
|
# starting) voids an earlier program's pass and its checks.
|
|
elseif ($ExpectedOutcome -eq 'Phase9Launch' -and $probeStatus -notmatch '^0x5250e') { $payloadPassed = $false; $probeChecks = $null }
|
|
elseif ($probeStatus -match '^0x5250e') { $outcome = 'ProbeFailed' }
|
|
} elseif ($line -match 'Broadway ios-probe counters sent=(\d+) received=(\d+) invalid=(\d+) duplicate=(\d+) timeout=(\d+)') {
|
|
$probeCounters = @([int]$Matches[1], [int]$Matches[2], [int]$Matches[3],
|
|
[int]$Matches[4], [int]$Matches[5])
|
|
Write-Host "PPC counters: $($probeCounters -join '/')"
|
|
} elseif ($line -match 'Broadway ios-probe checks=(\d+) failures=(\d+)') {
|
|
$probeChecks = @([int]$Matches[1], [int]$Matches[2])
|
|
Write-Host "PPC checks: $($probeChecks -join '/')"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (-not $outcome -and $ExpectedOutcome -eq 'Phase9Launch' -and $payloadPassed -and $probeChecks -and
|
|
@($trace | Where-Object { $_ -like 'REV:P9:H02 GO *' }).Count -eq 1 -and
|
|
@($trace | Where-Object { $_ -like 'REV:P9:H02 FIRST CMD *' }).Count -eq 1) {
|
|
$outcome = 'Phase9Launch'
|
|
}
|
|
if ($outcome) { break }
|
|
if ($dolphin.HasExited) { break }
|
|
Start-Sleep -Milliseconds 200
|
|
}
|
|
if ($outcome -ne $ExpectedOutcome) {
|
|
$exitDetail = if ($dolphin.HasExited) { "Dolphin exited with code $($dolphin.ExitCode). " } else { '' }
|
|
throw "$exitDetail Bootstrap outcome '$outcome' (expected '$ExpectedOutcome'); see $logPath"
|
|
}
|
|
if ($ExpectedOutcome -eq 'Fault') {
|
|
if ($faultLine -notmatch 'Starlet bootstrap exception 1: LR=0x[0-9a-f]+ SPSR=0x[0-9a-f]+ PC=0x[0-9a-f]+') {
|
|
throw "Expected an undefined-instruction exception with registers; see $logPath"
|
|
}
|
|
}
|
|
if ($ExpectedOutcome -eq 'Fatal') {
|
|
if (-not $trace.Contains('REV:BOOT:ENTRY') -or
|
|
-not $trace.Contains('REV:BOOT:BSS_OK') -or
|
|
-not $trace.Contains('REV:FATAL:DATA') -or
|
|
$trace.Contains('REV:BOOT:DATA_OK')) {
|
|
throw "Fatal data check did not produce the expected markers; see $logPath"
|
|
}
|
|
}
|
|
if ($ExpectedOutcome -eq 'Phase2') {
|
|
$requiredPhase2 = @('REV:P2:START', 'ARCH PASS', 'CRITICAL PASS', 'TIMER PASS',
|
|
'MONOTONIC PASS', 'MEM PASS', 'IRQ PASS', 'BARRIER PASS', 'LOGGING PASS',
|
|
'REV:P2:PASS', 'REV:P2:KERNEL_READY', 'REV:BOOT:IDLE')
|
|
$missingPhase2 = @($requiredPhase2 | Where-Object { -not $trace.Contains($_) })
|
|
if ($missingPhase2.Count -gt 0 -or @($trace | Where-Object { $_ -like '* FAIL' }).Count -gt 0) {
|
|
throw "Phase 02 self-test markers are incomplete; see $logPath"
|
|
}
|
|
}
|
|
if ($ExpectedOutcome -eq 'Phase3Raw' -and
|
|
(-not $trace.Contains('REV:P3A:START') -or
|
|
-not $trace.Contains('REV:P3A:ARM_128') -or
|
|
-not $trace.Contains('REV:P3A:ARM_PASS') -or
|
|
-not $probeCounters -or
|
|
$probeCounters[0] -ne 128 -or $probeCounters[1] -ne 128 -or
|
|
$probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or
|
|
$probeCounters[4] -ne 0)) {
|
|
throw "Phase 03A Starlet markers are incomplete; see $logPath"
|
|
}
|
|
if ($ExpectedOutcome -eq 'Phase3Ios' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
-not $trace.Contains('REV:P3B:OPEN_OK') -or
|
|
-not $trace.Contains('REV:P3B:PING_OK') -or
|
|
-not $trace.Contains('REV:P3B:CLOSE_OK') -or
|
|
-not $probeCounters -or $probeCounters[0] -lt 200 -or
|
|
$probeCounters[0] -ne $probeCounters[1] -or
|
|
$probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or
|
|
$probeCounters[4] -ne 0 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt 260 -or
|
|
$probeChecks[1] -ne 0)) {
|
|
throw "Phase 03B IOS probe markers or counters are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 04D: the shared /dev/fs cases (ppc/common/fs_cases.h).
|
|
if ($ExpectedOutcome -eq 'Phase4Fs' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or
|
|
-not $probeCounters -or $probeCounters[0] -lt 100 -or
|
|
$probeCounters[0] -ne $probeCounters[1] -or
|
|
$probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or
|
|
$probeCounters[4] -ne 0 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt 30 -or
|
|
$probeChecks[1] -ne 0)) {
|
|
throw "Phase 04D /dev/fs markers or counters are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 05B: the READDIR/GETUSAGE cases (ppc/common/fs_dir_cases.h).
|
|
if ($ExpectedOutcome -eq 'Phase5Dir' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or
|
|
-not $probeCounters -or $probeCounters[0] -lt 600 -or
|
|
$probeCounters[0] -ne $probeCounters[1] -or
|
|
$probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or
|
|
$probeCounters[4] -ne 0 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt 13 -or
|
|
$probeChecks[1] -ne 0)) {
|
|
throw "Phase 05B READDIR/GETUSAGE markers or counters are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 05C/05D: the /dev/es cases (ppc/common/es_cases.h).
|
|
if ($ExpectedOutcome -eq 'Phase5Es' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or
|
|
-not $probeCounters -or $probeCounters[0] -lt 600 -or
|
|
$probeCounters[0] -ne $probeCounters[1] -or
|
|
$probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or
|
|
$probeCounters[4] -ne 0 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt 18 -or
|
|
$probeChecks[1] -ne 0)) {
|
|
throw "Phase 05C /dev/es markers or counters are incomplete; see $logPath"
|
|
}
|
|
# P10A-H07 reduced smoke: real service on the emulator USB double; no
|
|
# local loopback and no physical acceptance. No synthetic IPC counters.
|
|
if ($ExpectedOutcome -eq 'Phase10AOh1' -and
|
|
(-not $trace.Contains('REV:P10A:OH1 READY REAL USB') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt $Phase10AMinimumChecks -or $probeChecks[1] -ne 0)) {
|
|
throw 'Phase 10A OH1 reduced smoke markers or checks are incomplete'
|
|
}
|
|
# Revolution IOS Phase 05F: the unchanged libogc stock client (ppc/stock-client).
|
|
if ($ExpectedOutcome -eq 'Phase5Client' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or
|
|
-not $probeCounters -or $probeCounters[0] -lt 16 -or
|
|
$probeCounters[0] -ne $probeCounters[1] -or
|
|
-not $probeChecks -or $probeChecks[0] -lt 16 -or
|
|
$probeChecks[1] -ne 0)) {
|
|
throw "Phase 05F stock client markers or counters are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 06H: the writable scenario (ppc/fs-write-probe, mode Write) on the
|
|
# emulator-only writable kernel over the disposable NAND (REV_BOOTSTRAP_NAND_DISPOSABLE).
|
|
if ($ExpectedOutcome -eq 'Phase6Write' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P6:FS MOUNT GEN * WRITABLE' }).Count -ne 1 -or
|
|
@($trace | Where-Object { $_ -like 'REV:P6:FS GEN * REMOUNT *' }).Count -ne 0 -or
|
|
-not $probeCounters -or $probeCounters[0] -lt 100 -or
|
|
$probeCounters[0] -ne $probeCounters[1] -or
|
|
$probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or
|
|
$probeCounters[4] -ne 0 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt 60 -or
|
|
$probeChecks[1] -ne 0)) {
|
|
throw "Phase 06H writable scenario markers or counters are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 06H: the resulting image after a fresh boot of the read-only kernel
|
|
# (ppc/fs-write-probe, mode Verify).
|
|
if ($ExpectedOutcome -eq 'Phase6Verify' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or
|
|
-not $probeCounters -or $probeCounters[0] -lt 30 -or
|
|
$probeCounters[0] -ne $probeCounters[1] -or
|
|
$probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or
|
|
$probeCounters[4] -ne 0 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt 25 -or
|
|
$probeChecks[1] -ne 0)) {
|
|
throw "Phase 06H verification markers or counters are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 08 H07R (section 69): the libogc read-after-reboot client
|
|
# (ppc/p8-client/reboot.c), the same boot.dol as on the console, on the emulator-only
|
|
# writable kernel over the disposable NAND. It writes only its status word (0x52500005 when
|
|
# every check of its part passed); the part is proven by the NAND copy, outside this script.
|
|
if ($ExpectedOutcome -eq 'Phase8Client' -and
|
|
(-not $trace.Contains('REV:P3B:START') -or
|
|
@($trace | Where-Object { $_ -like 'REV:P6:FS MOUNT GEN * WRITABLE' }).Count -ne 1)) {
|
|
throw "Phase 08 client markers are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 09 P09-H02: Broadway stopped and restarted into the payload while the
|
|
# kernel stays resident; the payload's own checks passed.
|
|
if ($ExpectedOutcome -eq 'Phase9Launch' -and
|
|
(@($trace | Where-Object { $_ -like 'REV:P9:H02 GO *' }).Count -ne 1 -or
|
|
@($trace | Where-Object { $_ -like 'REV:P9:H02 FIRST CMD *' }).Count -ne 1 -or
|
|
-not $probeChecks -or $probeChecks[0] -lt $Phase9MinimumChecks -or $probeChecks[1] -ne 0)) {
|
|
throw "Phase 09 launch markers or checks are incomplete; see $logPath"
|
|
}
|
|
# Revolution IOS Phase 09 P09-H04: the Menu was resolved, profiled, loaded and released
|
|
# (M0-M4) before the observation ended.
|
|
if ($ExpectedOutcome -in @('Phase9Menu', 'Phase9Rescue')) {
|
|
$missingMenu = @('REV:P9:H04 M0 ', 'REV:P9:H04 M1 ', 'REV:P9:H04 M2 STOPPED LOWMEM VERIFY OK', 'REV:P9:H04 M3 ', 'REV:P9:H04 M4 ') |
|
|
Where-Object { $prefix = $_; @($trace | Where-Object { $_.StartsWith($prefix) }).Count -ne 1 }
|
|
if (@($missingMenu).Count -gt 0) {
|
|
throw "Phase 09 System Menu milestones are incomplete ($($missingMenu -join ', ')); see $logPath"
|
|
}
|
|
}
|
|
$required = @('REV:BOOT:ENTRY', 'REV:BOOT:BSS_OK', 'REV:BOOT:DATA_OK',
|
|
'REV:BOOT:STACK_OK', 'REV:BOOT:RUST', 'Revolution IOS',
|
|
'Starlet Rust bootstrap reached')
|
|
$missing = @($required | Where-Object { -not $trace.Contains($_) })
|
|
if ($ExpectedOutcome -in @('Idle', 'Phase2') -and $missing.Count -gt 0) {
|
|
throw "Bootstrap reached idle without all expected markers; see $logPath"
|
|
}
|
|
Write-Host "Starlet bootstrap $outcome passed ($($trace.Count) trace lines)."
|
|
} finally {
|
|
if (-not $dolphin.HasExited) {
|
|
Stop-Process -Id $dolphin.Id
|
|
$dolphin.WaitForExit()
|
|
}
|
|
}
|