Track exact StarletOS bootstrap integration and checkpoint provenance

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.
This commit is contained in:
Yaya48 committed 2026-10-03 16:27:07 +02:00
1 parent a8dbbab62d
commit 54f5a74bbf
28 files changed
+9161 -7407

No files matched your search

+27 -22
View File
@@ -1,27 +1,32 @@
enable_testing()
add_custom_target(unittests)
add_custom_command(TARGET unittests POST_BUILD COMMAND ${CMAKE_CTEST_COMMAND} "--output-on-failure")
string(APPEND CMAKE_RUNTIME_OUTPUT_DIRECTORY "/Tests")
add_executable(tests EXCLUDE_FROM_ALL UnitTestsMain.cpp StubHost.cpp)
set_target_properties(tests PROPERTIES FOLDER Tests)
target_link_libraries(tests PRIVATE fmt::fmt gtest::gtest core uicommon)
add_test(NAME tests COMMAND tests)
add_custom_command(TARGET tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/Data/Sys" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys"
)
enable_testing()
add_custom_target(unittests)
add_custom_command(TARGET unittests POST_BUILD COMMAND ${CMAKE_CTEST_COMMAND} "--output-on-failure")
string(APPEND CMAKE_RUNTIME_OUTPUT_DIRECTORY "/Tests")
add_executable(tests EXCLUDE_FROM_ALL UnitTestsMain.cpp StubHost.cpp)
set_target_properties(tests PROPERTIES FOLDER Tests)
target_link_libraries(tests PRIVATE fmt::fmt gtest::gtest core uicommon)
add_test(NAME tests COMMAND tests)
add_custom_command(TARGET tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/Data/Sys" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys"
)
add_dependencies(unittests tests)
macro(add_dolphin_test target)
add_library(${target} OBJECT ${ARGN})
target_link_libraries(${target} PUBLIC fmt::fmt gtest::gtest PRIVATE core uicommon)
target_link_libraries(tests PRIVATE ${target})
endmacro()
add_subdirectory(Common)
add_subdirectory(Core)
# Runs a real Starlet ELF through the fork's CPU and SRAM bus without NAND or a frontend.
add_executable(StarletBootstrapProbe EXCLUDE_FROM_ALL
${CMAKE_SOURCE_DIR}/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp StubHost.cpp)
target_link_libraries(StarletBootstrapProbe PRIVATE fmt::fmt core uicommon)
macro(add_dolphin_test target)
add_library(${target} OBJECT ${ARGN})
target_link_libraries(${target} PUBLIC fmt::fmt gtest::gtest PRIVATE core uicommon)
target_link_libraries(tests PRIVATE ${target})
endmacro()
add_subdirectory(Common)
add_subdirectory(Core)
add_subdirectory(VideoCommon)
# Opt-in local trace qualification; never starts a console or accesses a NAND dump.
+46 -45
View File
@@ -1,20 +1,21 @@
add_dolphin_test(MMIOTest MMIOTest.cpp)
add_dolphin_test(PageFaultTest PageFaultTest.cpp)
add_dolphin_test(CoreTimingTest CoreTimingTest.cpp)
add_dolphin_test(PatchAllowlistTest PatchAllowlistTest.cpp)
add_dolphin_test(DSPAcceleratorTest DSP/DSPAcceleratorTest.cpp)
add_dolphin_test(DSPAssemblyTest
DSP/DSPAssemblyTest.cpp
DSP/DSPTestBinary.cpp
DSP/DSPTestText.cpp
DSP/HermesBinary.cpp
DSP/HermesText.cpp
)
add_dolphin_test(MMIOTest MMIOTest.cpp)
add_dolphin_test(PageFaultTest PageFaultTest.cpp)
add_dolphin_test(CoreTimingTest CoreTimingTest.cpp)
add_dolphin_test(PatchAllowlistTest PatchAllowlistTest.cpp)
add_dolphin_test(DSPAcceleratorTest DSP/DSPAcceleratorTest.cpp)
add_dolphin_test(DSPAssemblyTest
DSP/DSPAssemblyTest.cpp
DSP/DSPTestBinary.cpp
DSP/DSPTestText.cpp
DSP/HermesBinary.cpp
DSP/HermesText.cpp
)
add_dolphin_test(ESFormatsTest IOS/ES/FormatsTest.cpp)
add_dolphin_test(StarletARMCoreTest IOS/Starlet/ARMCoreTest.cpp)
add_dolphin_test(StarletBootstrapELFTest IOS/Starlet/BootstrapELFTest.cpp)
add_dolphin_test(StarletDMATest IOS/Starlet/DMATest.cpp)
add_dolphin_test(StarletSRAMReadTest IOS/Starlet/SRAMReadTest.cpp)
add_dolphin_test(StarletSRAMWriteTest IOS/Starlet/SRAMWriteTest.cpp)
@@ -24,36 +25,36 @@ add_dolphin_test(StarletEthernetTest IOS/Starlet/AX88772Test.cpp)
target_include_directories(StarletEthernetTest PRIVATE ${PROJECT_SOURCE_DIR}/Externals/libslirp/include)
add_dolphin_test(FileSystemTest IOS/FS/FileSystemTest.cpp)
add_dolphin_test(SkylandersTest IOS/USB/SkylandersTest.cpp)
if(_M_X86_64)
add_dolphin_test(PowerPCTest
PowerPC/DivUtilsTest.cpp
PowerPC/PageTableHostMappingTest.cpp
add_dolphin_test(SkylandersTest IOS/USB/SkylandersTest.cpp)
if(_M_X86_64)
add_dolphin_test(PowerPCTest
PowerPC/DivUtilsTest.cpp
PowerPC/PageTableHostMappingTest.cpp
PowerPC/Jit64Common/ConvertDoubleToSingle.cpp
PowerPC/Jit64Common/DCache.cpp
PowerPC/Jit64Common/Fres.cpp
PowerPC/Jit64Common/Frsqrte.cpp
)
elseif(_M_ARM_64)
add_dolphin_test(PowerPCTest
PowerPC/DivUtilsTest.cpp
PowerPC/PageTableHostMappingTest.cpp
PowerPC/JitArm64/ConvertSingleDouble.cpp
PowerPC/JitArm64/FPRF.cpp
PowerPC/JitArm64/Fres.cpp
PowerPC/JitArm64/Frsqrte.cpp
PowerPC/JitArm64/MovI2R.cpp
)
else()
add_dolphin_test(PowerPCTest
PowerPC/DivUtilsTest.cpp
PowerPC/PageTableHostMappingTest.cpp
)
endif()
target_sources(PowerPCTest PRIVATE
PowerPC/TestValues.h
StubJit.h
)
PowerPC/Jit64Common/Fres.cpp
PowerPC/Jit64Common/Frsqrte.cpp
)
elseif(_M_ARM_64)
add_dolphin_test(PowerPCTest
PowerPC/DivUtilsTest.cpp
PowerPC/PageTableHostMappingTest.cpp
PowerPC/JitArm64/ConvertSingleDouble.cpp
PowerPC/JitArm64/FPRF.cpp
PowerPC/JitArm64/Fres.cpp
PowerPC/JitArm64/Frsqrte.cpp
PowerPC/JitArm64/MovI2R.cpp
)
else()
add_dolphin_test(PowerPCTest
PowerPC/DivUtilsTest.cpp
PowerPC/PageTableHostMappingTest.cpp
)
endif()
target_sources(PowerPCTest PRIVATE
PowerPC/TestValues.h
StubJit.h
)
@@ -0,0 +1,236 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <string>
#include <unordered_map>
#include <gtest/gtest.h>
#include "Core/IOS/Starlet/ARMCore.h"
#include "Core/IOS/Starlet/BootstrapELF.h"
namespace IOS::LLE
{
namespace
{
constexpr u32 TEXT_BASE = 0xffff0000;
constexpr u32 DATA_BASE = 0xfffe0000;
class RecordingBus final : public ARMBus
{
public:
u8 Read8(u32 address) override
{
const auto it = bytes.find(address);
return it == bytes.end() ? 0 : it->second;
}
void Write8(u32 address, u8 value) override { bytes[address] = value; }
std::unordered_map<u32, u8> bytes;
};
void Put16(std::array<u8, 0x300>& image, size_t offset, u16 value)
{
image[offset] = static_cast<u8>(value >> 8);
image[offset + 1] = static_cast<u8>(value);
}
void Put32(std::array<u8, 0x300>& image, size_t offset, u32 value)
{
image[offset] = static_cast<u8>(value >> 24);
image[offset + 1] = static_cast<u8>(value >> 16);
image[offset + 2] = static_cast<u8>(value >> 8);
image[offset + 3] = static_cast<u8>(value);
}
std::array<u8, 0x300> MakeImage()
{
std::array<u8, 0x300> image{};
image[0] = 0x7f;
image[1] = 'E';
image[2] = 'L';
image[3] = 'F';
image[4] = 1; // ELF32
image[5] = 2; // big endian
image[6] = 1;
Put16(image, 16, 2); // ET_EXEC
Put16(image, 18, 40); // ARM
Put32(image, 20, 1); // ELF version
Put32(image, 24, TEXT_BASE);
Put32(image, 28, 52); // program headers
Put32(image, 32, 0x200); // section headers
Put16(image, 40, 52);
Put16(image, 42, 32);
Put16(image, 44, 2);
Put16(image, 46, 40);
Put16(image, 48, 3);
Put32(image, 52, 1);
Put32(image, 56, 0x100);
Put32(image, 60, TEXT_BASE);
Put32(image, 64, TEXT_BASE);
Put32(image, 68, 4);
Put32(image, 72, 8);
Put32(image, 76, 5);
image[0x100] = 0xea; // ARM branch instruction
Put32(image, 84, 1);
Put32(image, 88, 0x110);
Put32(image, 92, DATA_BASE);
Put32(image, 96, DATA_BASE);
Put32(image, 100, 4);
Put32(image, 104, 0x420);
Put32(image, 108, 6);
image[0x110] = 0x12;
image[0x111] = 0x34;
image[0x112] = 0x56;
image[0x113] = 0x78;
constexpr char names[] = "\0REV_TRACE\0__fault_code\0__fault_lr\0__fault_spsr";
for (size_t index = 0; index < sizeof(names); ++index)
image[0x180 + index] = static_cast<u8>(names[index]);
Put32(image, 0x228 + 4, 3); // SHT_STRTAB
Put32(image, 0x228 + 16, 0x180);
Put32(image, 0x228 + 20, sizeof(names));
Put32(image, 0x250 + 4, 2); // SHT_SYMTAB
Put32(image, 0x250 + 16, 0x120);
Put32(image, 0x250 + 20, 5 * 16);
Put32(image, 0x250 + 24, 1);
Put32(image, 0x250 + 36, 16);
const auto symbol = [&image](size_t index, u32 name, u32 address, u32 size) {
const size_t offset = 0x120 + index * 16;
Put32(image, offset, name);
Put32(image, offset + 4, address);
Put32(image, offset + 8, size);
};
symbol(1, 1, DATA_BASE + 8, 1032);
symbol(2, 11, DATA_BASE + 0x410, 4);
symbol(3, 24, DATA_BASE + 0x414, 4);
symbol(4, 35, DATA_BASE + 0x418, 4);
return image;
}
TEST(BootstrapELFTest, LoadsSegmentsAndZerosUninitializedData)
{
const auto image = MakeImage();
RecordingBus bus;
BootstrapELFInfo info;
std::string error;
ASSERT_TRUE(LoadBootstrapELF(image, bus, &info, &error)) << error;
EXPECT_EQ(info.entry, TEXT_BASE);
EXPECT_EQ(info.trace, DATA_BASE + 8);
EXPECT_EQ(info.fault_spsr, DATA_BASE + 0x418);
EXPECT_EQ(bus.Read8(TEXT_BASE), 0xea);
EXPECT_EQ(bus.Read8(TEXT_BASE + 4), 0);
EXPECT_EQ(bus.Read32(DATA_BASE), 0x12345678u);
EXPECT_EQ(bus.Read32(DATA_BASE + 0x41c), 0u);
EXPECT_EQ(bus.bytes.size(), size_t{8 + 0x420});
}
TEST(BootstrapELFTest, RejectsMalformedImageBeforeWritingMemory)
{
auto image = MakeImage();
RecordingBus bus;
BootstrapELFInfo info;
std::string error;
image[5] = 1;
EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error));
EXPECT_TRUE(bus.bytes.empty());
image = MakeImage();
Put32(image, 0x120 + 2 * 16 + 4, DATA_BASE + 0x420);
EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error));
EXPECT_TRUE(bus.bytes.empty());
image = MakeImage();
Put32(image, 88, 0x300);
EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error));
EXPECT_TRUE(bus.bytes.empty());
}
// Revolution IOS Phase 06H: the optional MEM2 text segment of the emulator-only
// disposable-image kernel, accepted only when the caller allows it.
constexpr u32 MEM2_TEXT = 0x12600000;
std::array<u8, 0x300> MakeMem2Image(u32 address, u32 flags)
{
auto image = MakeImage();
Put16(image, 44, 3);
Put32(image, 116, 1);
Put32(image, 120, 0x114);
Put32(image, 124, address);
Put32(image, 128, address);
Put32(image, 132, 4);
Put32(image, 136, 0x10);
Put32(image, 140, flags);
Put32(image, 0x114, 0xe12fff1e); // BX LR
return image;
}
TEST(BootstrapELFTest, Mem2TextSegmentOnlyWhenAllowed)
{
RecordingBus bus;
BootstrapELFInfo info;
std::string error;
auto image = MakeMem2Image(MEM2_TEXT, 5);
EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error));
EXPECT_TRUE(bus.bytes.empty());
ASSERT_TRUE(LoadBootstrapELF(image, bus, &info, &error, true)) << error;
EXPECT_EQ(bus.Read32(MEM2_TEXT), 0xe12fff1eu);
EXPECT_EQ(bus.Read32(MEM2_TEXT + 0xc), 0u);
EXPECT_EQ(bus.Read8(TEXT_BASE), 0xea);
EXPECT_EQ(bus.Read32(DATA_BASE), 0x12345678u);
EXPECT_EQ(bus.bytes.size(), size_t{8 + 0x420 + 0x10});
RecordingBus plain;
const auto two_segments = MakeImage();
EXPECT_TRUE(LoadBootstrapELF(two_segments, plain, &info, &error, true)) << error;
}
TEST(BootstrapELFTest, RejectsInvalidMem2TextSegmentBeforeWritingMemory)
{
RecordingBus bus;
BootstrapELFInfo info;
std::string error;
// Writable, outside MEM2, crossing its end, or a second SRAM text segment.
for (const auto& image : {MakeMem2Image(MEM2_TEXT, 6), MakeMem2Image(0x0fff0000, 5),
MakeMem2Image(0x13fffff8, 5), MakeMem2Image(TEXT_BASE, 5)})
{
EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error, true));
EXPECT_TRUE(bus.bytes.empty());
}
}
TEST(ARMCoreTest, Armv5DoublewordTransfersAndAlignmentAbort)
{
RecordingBus bus;
bus.Write32(TEXT_BASE, 0xe1c020d0); // LDRD r2, r3, [r0]
bus.Write32(TEXT_BASE + 4, 0xe1c120f0); // STRD r2, r3, [r1]
bus.Write32(DATA_BASE, 0x11223344);
bus.Write32(DATA_BASE + 4, 0x55667788);
ARMCore core(bus);
core.Reset(TEXT_BASE);
core.SetBigEndian(true);
core.SetRegister(0, DATA_BASE);
core.SetRegister(1, DATA_BASE + 8);
core.Step();
EXPECT_EQ(core.GetRegister(2), 0x11223344u);
EXPECT_EQ(core.GetRegister(3), 0x55667788u);
core.Step();
EXPECT_EQ(bus.Read32(DATA_BASE + 8), 0x11223344u);
EXPECT_EQ(bus.Read32(DATA_BASE + 12), 0x55667788u);
core.Reset(TEXT_BASE);
core.SetBigEndian(true);
core.SetRegister(0, DATA_BASE + 4);
core.Step();
EXPECT_EQ(core.GetRegister(15), TEXT_BASE + 0x10);
EXPECT_EQ(core.GetRegister(14), TEXT_BASE + 8);
EXPECT_EQ(core.GetCP15State().fault_address, DATA_BASE + 4);
}
} // namespace
} // namespace IOS::LLE
@@ -135,6 +135,21 @@ protected:
EXPECT_EQ(m_memory->GetDMAMemoryPointer(address, 4), nullptr);
}
void CheckBootstrapStoredKeys()
{
m_memory->PrepareBootstrap();
for (const u8 read_all : {0, 1})
{
SCOPED_TRACE(read_all);
m_memory->m_ohci1_hci_events.clear();
m_memory->ExecuteOHCI1HCICommand({0x0d, 0x0c, 7, 0, 0, 0, 0, 0, 0, read_all});
ASSERT_EQ(m_memory->m_ohci1_hci_events.size(), 1u);
// Command Complete: capacity 255, zero stored keys, no Return Link Keys event.
EXPECT_EQ(m_memory->m_ohci1_hci_events.front(),
(std::vector<u8>{0x0e, 8, 1, 0x0d, 0x0c, 0, 0xff, 0, 0, 0}));
}
}
void CheckOHCI()
{
for (const u32 base : {0x10000U, 0x10010000U, 0x0d404000U})
@@ -381,6 +396,11 @@ TEST_F(StarletDMATest, OHCIUsesTwoPhysicalPagesAndLittleEndianDescriptors)
CheckOHCI();
}
TEST_F(StarletDMATest, BootstrapReadStoredKeysHasNoWiimoteDevices)
{
CheckBootstrapStoredKeys();
}
TEST_F(StarletDMATest, NANDSeparatesDataSpareAndECCIncludingPartialTransfers)
{
CheckNAND();