// Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include #include "Core/Core.h" #include "Core/HW/WII_IPC.h" #include "Core/IOS/Starlet/StarletMemory.h" #include "Core/System.h" namespace IOS::LLE { namespace { // Deliberately branch-based reference, independent of the optimized aperture-bit swap. std::optional MapSRAMByte(u32 address, bool split, bool boot0) { if (!(address >= 0x0d400000 && address < 0x0d420000) && address < 0xfff00000) return std::nullopt; const u32 window = address & 0x1ffff; if (boot0 && address >= 0xfffe0000 && (split ? window < 0x10000 : window >= 0x10000)) return std::nullopt; if (!split) return window < 0x18000 ? std::optional(window) : std::nullopt; if (window < 0x8000) return 0x10000 + window; if (window >= 0x10000) return window - 0x10000; return std::nullopt; } class StarletSRAMWrite : public testing::Test { protected: void SetUp() override { Core::DeclareAsCPUThread(); Core::System::GetInstance().GetWiiIPC().Reset(); } static void SetMapping(StarletMemory& memory, bool split, bool boot0) { memory.Write32(0x0d800060, split ? 0x20 : 0); memory.Write32(0x0d80018c, boot0 ? 0 : 0x1000); } }; TEST_F(StarletSRAMWrite, EveryApertureByteMatchesIndependentMapping) { auto memory = std::make_unique(Core::System::GetInstance()); memory->Reset(); u8* const sram = memory->GetFastmemSRAMBase(); for (u32 i = 0; i < StarletMemory::SRAM_SIZE; ++i) sram[i] = static_cast((i * 13) ^ (i >> 8) ^ 0xa5); for (const bool split : {false, true}) { for (const bool boot0 : {false, true}) { SetMapping(*memory, split, boot0); for (const u32 base : {0x0d400000u, 0xfff00000u, 0xfffe0000u}) { for (u32 i = 0; i < 0x20000; ++i) { const u32 address = base + i; const auto offset = MapSRAMByte(address, split, boot0); // Reset leaves the synthetic boot ROM zero-filled. Holes also read zero. ASSERT_EQ(memory->Read8(address), offset ? sram[*offset] : 0) << "address=" << std::hex << address << " split=" << split << " boot0=" << boot0; } } } } } TEST_F(StarletSRAMWrite, WideStoresMatchByteOracleAcrossLiveMappingsAndAllMirrors) { auto memory = std::make_unique(Core::System::GetInstance()); memory->Reset(); std::array expected; for (u32 i = 0; i < expected.size(); ++i) expected[i] = static_cast((i * 13) ^ (i >> 8) ^ 0xa5); std::copy(expected.begin(), expected.end(), memory->GetFastmemSRAMBase()); // Toggle back, too: no cached host pointer may survive changes to SM/boot0. for (const bool split : {false, true, false}) { for (const bool boot0 : {false, true, false}) { SetMapping(*memory, split, boot0); for (const u32 base : {0x0d400000u, 0xfff00000u, 0xfff20000u, 0xfff40000u, 0xfff60000u, 0xfff80000u, 0xfffa0000u, 0xfffc0000u, 0xfffe0000u}) { for (u32 page = 0; page < 32; ++page) { for (const u32 in_page : {0u, 1u, 2u, 3u, 0xffcu, 0xffdu, 0xffeu, 0xfffu}) { const u32 address = base + page * 0x1000 + in_page; for (const u32 width : {2u, 4u}) { if (address > 0xffffffffu - (width - 1)) continue; // Do not wrap into uninitialized MEM1. const u32 value = (address * 0x12345u) ^ (width == 2 ? 0x89abcdefu : 0x76543210u); for (u32 byte = 0; byte < width; ++byte) { if (const auto offset = MapSRAMByte(address + byte, split, boot0)) expected[*offset] = static_cast(value >> ((width - 1 - byte) * 8)); } if (width == 2) memory->Write16(address, static_cast(value)); else memory->Write32(address, value); ASSERT_TRUE( std::equal(expected.begin(), expected.end(), memory->GetFastmemSRAMBase())) << "address=" << std::hex << address << " width=" << width << " split=" << split << " boot0=" << boot0; } } } } } } } } // namespace } // namespace IOS::LLE