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; }