// Boot/init hooks, OSFatal, and assorted hardware-init stubs. #include #include #include #include #include #include #include #include #include #include "abi_bridge.h" #include "memory.h" #include "hle_stubs.h" #include "ppc_runtime.h" #include "recomp_mod_loader.h" #include "runtime_log.h" #include "system_bridge.h" extern "C" void func_801A961C(CpuContext* ctx); extern "C" void func_8055531C(CpuContext* ctx); extern "C" void OSInitAlarm_RecompModLateInit_801a961c(CpuContext* ctx) { func_801A961C(ctx); } REGISTER_NATIVE_FUNCTION_AS(0x801A961C, OSInitAlarm_RecompModLateInit_801a961c, "OSInitAlarm_RecompModLateInit_801a961c"); extern "C" void StaticRProlog_RecompModInit_8055531c(CpuContext* ctx) { RecompMod::RunMemoryInitializers(); func_8055531C(ctx); RecompMod::RunPostRelInitializers(); } REGISTER_NATIVE_FUNCTION_AS(0x8055531C, StaticRProlog_RecompModInit_8055531c, "StaticRProlog_RecompModInit_8055531c"); namespace { std::string ReadGuestCStringLimited(uint32_t address, size_t limit = 4096) { if (address == 0) { return ""; } std::string text; text.reserve(128); for (size_t i = 0; i < limit; ++i) { const uint8_t ch = Memory::Read8(address + static_cast(i)); if (ch == 0) { return text; } text.push_back(static_cast(ch)); } text += ""; return text; } } extern "C" void OSFatal_HLE_801a4ec4(CpuContext* ctx) { const uint32_t fg = ctx ? ctx->gpr[3] : 0; const uint32_t bg = ctx ? ctx->gpr[4] : 0; const uint32_t messagePtr = ctx ? ctx->gpr[5] : 0; RT_LOG(RT_TAG_OS) << "OS::Fatal called fg=0x" << std::hex << fg << " bg=0x" << bg << " message=0x" << messagePtr << std::dec << " '" << ReadGuestCStringLimited(messagePtr) << "'" << std::endl; if (ctx) { SystemBridge::DumpCpuState(ctx); } const std::string guestMessage = ReadGuestCStringLimited(messagePtr); const std::string details = guestMessage.empty() ? std::string("OS::Fatal was called without a message.") : guestMessage; // MarkFatalErrorReported below suppresses the atexit reporter, so this path // has to write its own artifacts or the run folder gets nothing. RuntimeCrash::WriteCrashArtifacts("osfatal", details); SetRuntimeExitCode(EXIT_FAILURE); ShowRuntimeFatalPopup("the guest operating system reported a fatal error", details); MarkFatalErrorReported(); std::exit(EXIT_FAILURE); } REGISTER_NATIVE_FUNCTION_AS(0x801A4EC4, OSFatal_HLE_801a4ec4, "OSFatal_HLE_801a4ec4"); extern "C" void GKI_delay_HLE_801301b4() { // The guest's GKI_delay is empty: BTA_Init spins on it while the Bluetooth // module answers through IPC interrupts and the 2 ms BTU alarm. Service both // here and let the wall clock advance. OS_HLE_ProcessAlarms(32); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } PPC_NATIVE_OVERRIDE_VOID(801301B4, GKI_delay_HLE_801301b4, (), ()); // Serial Interface (SI) - GameCube controller ports; stubbed since we don't emulate the MMIO. // SIInit (0x801b2de0): skips MMIO setup at 0xCD006434 and controller detection. extern "C" void SIInit_801b2de0() { RT_LOG(RT_TAG_OS) << "SIInit_801b2de0 called: skipping MMIO register setup and controller detection" << std::endl; } // SISetSamplingRate (0x801b3acc): ignored, we don't emulate SI polling timing. extern "C" void HLE_SISetSamplingRate_801b3acc(uint32_t msec) { RT_LOG(RT_TAG_OS) << "HLE_SISetSamplingRate_801b3acc called: msec=" << msec << ": Stubbed success." << std::endl; } // Video Interface (VI) - TV output. // VIGetTvFormat (0x801bacd8): CRITICAL, must return 1 (VI_PAL) not 0, or PAL builds // misbehave/panic. extern "C" uint32_t HLE_VIGetTvFormat_801bacd8() { // VI_NTSC = 0, VI_PAL = 1, VI_MPAL = 2 RT_LOG(RT_TAG_OS) << "HLE_VIGetTvFormat_801bacd8 called: returning VI_PAL (1)" << std::endl; return 1; } REGISTER_NATIVE_FUNCTION(0x801B2DE0, SIInit_801b2de0); PPC_NATIVE_OVERRIDE_VOID(801B3ACC, HLE_SISetSamplingRate_801b3acc, (uint32_t msec), (msec)); // OS____InitMemoryProtection (0x801A7DFC): real version touches MMU/MMIO we don't emulate; // no-op and return success so boot doesn't stall. extern "C" uint32_t OS____InitMemoryProtection_801a7dfc(uint32_t r3, uint32_t r4, uint32_t r5, uint32_t r6, uint32_t r7, uint32_t r8) { RT_LOG(RT_TAG_OS) << "OS____InitMemoryProtection_801a7dfc called (stubbed): r3=0x" << std::hex << r3 << std::dec << std::endl; return 0; } PPC_NATIVE_OVERRIDE(801A7DFC, OS____InitMemoryProtection_801a7dfc, uint32_t, (uint32_t r3, uint32_t r4, uint32_t r5, uint32_t r6, uint32_t r7, uint32_t r8), (r3, r4, r5, r6, r7, r8)); // OSGetConsoleType (0x8019f33c): standard Wii = 0x12, NDEV (expanded MEM2) = 0x10000012; // MKWii uses the NDEV result to enable its extra-memory heap path. extern "C" uint32_t OS__GetConsoleType_8019f33c(uint32_t /*r4*/, uint32_t /*r5*/, uint32_t /*r6*/, uint32_t /*r7*/, uint32_t /*r8*/, uint32_t /*r31*/) { constexpr uint32_t kRetailMem2Size = 64u * 1024u * 1024u; const uint32_t physicalMem2Size = Memory::Read32(0x80003118u); const uint32_t consoleType = physicalMem2Size == kRetailMem2Size ? 0x00000012u : 0x10000012u; static std::atomic logged{false}; if (!logged.exchange(true)) { RT_LOG(RT_TAG_OS) << "OSGetConsoleType: MEM2=" << (physicalMem2Size / (1024u * 1024u)) << " MB, returning 0x" << std::hex << consoleType << std::dec << std::endl; } return consoleType; } // Register the function PPC_NATIVE_OVERRIDE(8019F33C, OS__GetConsoleType_8019f33c, uint32_t, (uint32_t r4, uint32_t r5, uint32_t r6, uint32_t r7, uint32_t r8, uint32_t r31), (r4, r5, r6, r7, r8, r31)); // OSGetResetCode (0x801a8a50): real version reads MMIO 0xCC003024; we always report // Cold Boot (0). extern "C" uint32_t OSGetResetCode_801a8a50() { // Log occasionally just to track boot flow static bool logged = false; if (!logged) { RT_LOG(RT_TAG_OS) << "OSGetResetCode_801a8a50 called: returning 0 (Cold Boot)" << std::endl; logged = true; } return 0; } // Register the function PPC_NATIVE_OVERRIDE(801A8A50, OSGetResetCode_801a8a50, uint32_t, (), ()); // __OSInitSTM (0x801AB848): real version opens /dev/stm/* handles. We stub it by writing // fake handles and the success flag into the SDA (r13) block so OSResetSystem's checks pass. extern "C" uint32_t __OSInitSTM_HLE_801ab848(CpuContext* ctx) { CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext(); if (!cpu) return 0; RT_LOG(RT_TAG_OS) << "__OSInitSTM_HLE_801ab848 called: initializing STM state" << std::endl; // R13 (SDA2) holds the base for small data variables const uint32_t r13 = cpu->gpr[13]; if (r13 == 0) { RT_LOG(RT_TAG_OS) << "__OSInitSTM: Warning - R13 is 0, cannot write state." << std::endl; return 0; } // Offsets from disassembly: r13-0x62cc=STM_Initialized, r13-0x62c8=/dev/stm/immediate, // r13-0x62c4=/dev/stm/eventhook. try { // Mark STM as initialized ::Memory::Write32(r13 - 0x62ccu, 1); // Fake non-zero handles so callers' zero-checks pass. ::Memory::Write32(r13 - 0x62c8u, 0x00535401); // "ST\x01" ::Memory::Write32(r13 - 0x62c4u, 0x00535402); // "ST\x02" // Default Power/Reset callback pointers are left unset; safe since we never fire // the STM hardware interrupt that would invoke them. } catch (const ::Memory::AccessViolation& e) { RT_LOG(RT_TAG_OS) << "__OSInitSTM: Failed to write STM state to SDA @ 0x" << std::hex << e.address() << std::dec << " (" << e.reason() << ")" << std::endl; return 0; // Return failure } // Return 1 (success) return 1; } // Register the function PPC_NATIVE_OVERRIDE(801AB848, __OSInitSTM_HLE_801ab848, uint32_t, (CpuContext* ctx), (ctx)); extern "C" void OS____PSInit_801a04a0() { RT_LOG(RT_TAG_OS) << "OS____PSInit_801a04a0 called (stubbed)" << std::endl; } PPC_NATIVE_OVERRIDE_VOID(801A04A0, OS____PSInit_801a04a0, (), ()); extern "C" void __init_hardware_80006348() { RT_LOG(RT_TAG_OS) << "__init_hardware_80006348 called (stubbed)" << std::endl; } PPC_NATIVE_OVERRIDE_VOID(80006348,__init_hardware_80006348, (), ()); // PPC SPR (Special Purpose Register) access stubs; these registers don't exist on x86 so // each one just logs and no-ops. // Each PPC_NATIVE_OVERRIDE_VOID line stays written out per stub (not looped) because the // translator text-scans them to decide which addresses to skip translating. #define PPC_SPR_STUB_BODY(name, message) \ extern "C" void name() { RT_LOG(RT_TAG_OS) << message << std::endl; } PPC_SPR_STUB_BODY(PPCMfhid0_8012e574, "PPCMfhid0 called (stubbed) - Move From HID0") PPC_NATIVE_OVERRIDE_VOID(8012e574, PPCMfhid0_8012e574, (), ()); PPC_SPR_STUB_BODY(PPCMthid0_8012e57c, "PPCMthid0 called (stubbed) - Move To HID0") PPC_NATIVE_OVERRIDE_VOID(8012e57c, PPCMthid0_8012e57c, (), ()); extern "C" void PPCMtdec_8012e594() { static std::atomic logCount{0}; if (logCount.fetch_add(1) < 4) { RT_LOG(RT_TAG_OS) << "PPCMtdec called (stubbed) - Move To Decrementer" << std::endl; } // We don't simulate decrementer exceptions, so pump due alarms here instead. Capped at // 32, not 1: a single alarm let unrelated periodic alarms backlog and delay // AsyncDisplay's pacing alarm by multiple retraces. OS_HLE_ProcessAlarms(32); } PPC_NATIVE_OVERRIDE_VOID(8012E594, PPCMtdec_8012e594, (), ()); extern "C" void PPCSync_8012e59c() { // PowerPC `sync`. Translated guest code runs on one host thread at a time // and the runtime's own cross-thread state uses C++ atomics, so there is no // guest-visible reordering for this barrier to prevent. } PPC_NATIVE_OVERRIDE_VOID(8012e59c, PPCSync_8012e59c, (), ()); PPC_SPR_STUB_BODY(PPCMtmmcr0_8012e5b8, "PPCMtmmcr0 called (stubbed) - Move To MMCR0") PPC_NATIVE_OVERRIDE_VOID(8012e5b8, PPCMtmmcr0_8012e5b8, (), ()); PPC_SPR_STUB_BODY(PPCMtmmcr1_8012e5c0, "PPCMtmmcr1 called (stubbed) - Move To MMCR1") PPC_NATIVE_OVERRIDE_VOID(8012e5c0, PPCMtmmcr1_8012e5c0, (), ()); PPC_SPR_STUB_BODY(PPCMtpmc1_8012e5c8, "PPCMtpmc1 called (stubbed) - Move To PMC1") PPC_NATIVE_OVERRIDE_VOID(8012e5c8, PPCMtpmc1_8012e5c8, (), ()); PPC_SPR_STUB_BODY(PPCMtpmc2_8012e5d0, "PPCMtpmc2 called (stubbed) - Move To PMC2") PPC_NATIVE_OVERRIDE_VOID(8012e5d0, PPCMtpmc2_8012e5d0, (), ()); PPC_SPR_STUB_BODY(PPCMtpmc3_8012e5d8, "PPCMtpmc3 called (stubbed) - Move To PMC3") PPC_NATIVE_OVERRIDE_VOID(8012e5d8, PPCMtpmc3_8012e5d8, (), ()); PPC_SPR_STUB_BODY(PPCMtpmc4_8012e5e0, "PPCMtpmc4 called (stubbed) - Move To PMC4") PPC_NATIVE_OVERRIDE_VOID(8012e5e0, PPCMtpmc4_8012e5e0, (), ()); extern "C" uint32_t PPCMfhid2_8012e630_impl() { if (CpuContext* cpu = TryGetCpuContext()) { if (cpu->hid2 == 0) { cpu->hid2 = 0x10000000u; } return cpu->hid2; } return 0x10000000u; } // Register the function using a return-value stub extern "C" void PPCMfhid2_HLE_8012e630(CpuContext* ctx) { ctx->gpr[3] = PPCMfhid2_8012e630_impl(); } REGISTER_TRANSLATED_FUNCTION(0x8012e630, PPCMfhid2_HLE_8012e630); extern "C" void PPCMthid2_8012e638(CpuContext* ctx) { CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext(); cpu->hid2 = cpu->gpr[3]; static std::atomic logCount{0}; if (logCount.fetch_add(1) < 4) { RT_LOG(RT_TAG_OS) << "PPCMthid2 set HID2=0x" << std::hex << cpu->hid2 << std::dec << std::endl; } } PPC_NATIVE_OVERRIDE_VOID(8012e638, PPCMthid2_8012e638, (CpuContext* ctx), (ctx)); PPC_SPR_STUB_BODY(PPCMfwpar_8012e640, "PPCMfwpar called (stubbed) - Move From Write Pipe Address Register") PPC_NATIVE_OVERRIDE_VOID(8012e640, PPCMfwpar_8012e640, (), ()); PPC_SPR_STUB_BODY(PPCMtwpar_8012e64c, "PPCMtwpar called (stubbed) - Move To Write Pipe Address Register") PPC_NATIVE_OVERRIDE_VOID(8012e64c, PPCMtwpar_8012e64c, (), ()); PPC_SPR_STUB_BODY(PPCDisableSpeculation_8012e654, "PPCDisableSpeculation called (stubbed)") PPC_NATIVE_OVERRIDE_VOID(8012e654, PPCDisableSpeculation_8012e654, (), ()); PPC_SPR_STUB_BODY(PPCMthid4_8012e684, "PPCMthid4 called (stubbed) - Move To HID4") PPC_NATIVE_OVERRIDE_VOID(8012e684, PPCMthid4_8012e684, (), ());