From 15da28ad7be2465e25dd46f1476a1b22fda7f1b4 Mon Sep 17 00:00:00 2001 From: Yaya48 Date: Tue, 13 Oct 2026 22:40:28 +0200 Subject: [PATCH] StarletBootstrapProbe: --sbr1-record and --sbr1-absent StarOS's SBR1 boot record (MEM1 0x00200000) is created by its Broadway loader before the image runs; the probe never had one, so the paths that publish into it ran only on the console. --sbr1-record writes the record as the loader and the ARM relay leave it (header, relay at its last step, EXI channel 1 given to the Starlet, magic last); after the run both options print the record's magic, state, reason, EXI owner, fault code, ring length and dropped count, and how many of its 1024 words are nonzero (0 is expected with --sbr1-absent). Without either option the probe behaves as before. With the StarOS M1 images (interpreter and JIT): idle publishes state 3 and hands EXI back (owner 1, ring 258 bytes, no Gecko emulated); fatal-data state 4 reason 3; undefined-entry state 6, fault 1, ring empty, as on the console (gates HW2-HW4). Co-Authored-By: Claude Opus 5.5 --- .../StarletBootstrapProbe.cpp | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp index a6b306d70b..8f76e925b7 100644 --- a/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp +++ b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp @@ -32,6 +32,22 @@ enum class ExpectedOutcome // Exit status of a run stopped by a Dolphin alert (1: failed expectation, 2: usage). constexpr int ALERT_EXIT_STATUS = 3; +// StarOS's SBR1 boot record in MEM1 (StarOS crates/boot-contract, report.rs). +enum class SBR1Mode +{ + None, + // --sbr1-record: create the record as the loader does, report it after the run. + Record, + // --sbr1-absent: no record; report the area after the run. + Absent, +}; +constexpr u32 SBR1_BASE = 0x00200000; +constexpr u32 SBR1_MAGIC = 0x53425231; +constexpr u32 SBR1_VERSION = 1; +constexpr u32 SBR1_SIZE = 0x1000; +constexpr u32 SBR1_EXI_STARLET = 2; +constexpr u32 SBR1_RELAY_JUMPING = 4; + // Dolphin's default handler shows an alert in a modal MessageBox on Windows, which blocks the // probe until someone closes it. Report the alert on stderr and fail the run at once instead. bool FailOnAlert(const char* caption, const char* text, bool, Common::MsgType) @@ -167,14 +183,16 @@ bool Run(const std::string& path, bool jit, bool mmu_faults, ExpectedOutcome exp int main(int argc, char** argv) { Common::RegisterMsgAlertHandler(FailOnAlert); - if (argc < 2 || argc > 5) + if (argc < 2 || argc > 6) { std::cerr << "Usage: StarletBootstrapProbe [--jit] [--mmu-faults] " + "[--sbr1-record|--sbr1-absent] " "[--expect-panic|--expect-fatal-data|--expect-undefined-fault|--expect-phase2]\n"; return 2; } bool jit = false; bool mmu_faults = false; + SBR1Mode sbr1 = SBR1Mode::None; ExpectedOutcome expected = ExpectedOutcome::Idle; bool outcome_selected = false; for (int index = 2; index < argc; ++index) @@ -184,6 +202,15 @@ int main(int argc, char** argv) jit = true; else if (option == "--mmu-faults") mmu_faults = true; + else if (option == "--sbr1-record" || option == "--sbr1-absent") + { + if (sbr1 != SBR1Mode::None) + { + std::cerr << "Select only one SBR1 mode\n"; + return 2; + } + sbr1 = option == "--sbr1-record" ? SBR1Mode::Record : SBR1Mode::Absent; + } else if (option == "--expect-panic" || option == "--expect-fatal-data" || option == "--expect-undefined-fault" || option == "--expect-phase2") { @@ -211,7 +238,34 @@ int main(int argc, char** argv) auto& system = Core::System::GetInstance(); system.SetIsWii(true); system.GetMemory().Init(); + auto& memory = system.GetMemory(); + if (sbr1 == SBR1Mode::Record) + { + // As the Broadway loader and the ARM relay leave StarOS's SBR1 boot record before the image + // runs (StarOS docs/contracts/wii-dev-boot.md): header, the relay at its last step, EXI + // channel 1 given to the Starlet, the magic written last. + memory.Write_U32(SBR1_VERSION, SBR1_BASE + 0x04); + memory.Write_U32(SBR1_SIZE, SBR1_BASE + 0x08); + memory.Write_U32(SBR1_EXI_STARLET, SBR1_BASE + 0x0c); + memory.Write_U32(SBR1_RELAY_JUMPING, SBR1_BASE + 0x10); + memory.Write_U32(SBR1_MAGIC, SBR1_BASE); + } const bool passed = Run(argv[1], jit, mmu_faults, expected); + if (sbr1 != SBR1Mode::None) + { + // The record's summary after the run; with --sbr1-absent every word must still be zero. + std::cerr << std::hex << "SBR1 magic 0x" << memory.Read_U32(SBR1_BASE) << std::dec + << " state " << memory.Read_U32(SBR1_BASE + 0x18) << " reason " + << memory.Read_U32(SBR1_BASE + 0x1c) << " owner " + << memory.Read_U32(SBR1_BASE + 0x0c) << " fault " + << memory.Read_U32(SBR1_BASE + 0x20) << " ring " + << memory.Read_U32(SBR1_BASE + 0x2c) << " dropped " + << memory.Read_U32(SBR1_BASE + 0x30); + u32 written = 0; + for (u32 offset = 0; offset < SBR1_SIZE; offset += 4) + written += memory.Read_U32(SBR1_BASE + offset) != 0; + std::cerr << " nonzero_words " << written << '\n'; + } system.GetMemory().Shutdown(); return passed ? 0 : 1; }