#include "nand_settings.h" #include #include #include #include #include static void Require(bool condition, const char* message = "NAND settings check failed") { if (!condition) { throw std::runtime_error(message); } } static std::string ReadBytes(const std::filesystem::path& path) { std::ifstream input(path, std::ios::binary); return {std::istreambuf_iterator(input), std::istreambuf_iterator()}; } int main() { const auto root = std::filesystem::temp_directory_path() / ("wiicomp-nand-settings-" + std::to_string( std::chrono::steady_clock::now().time_since_epoch().count())); const auto path = root / "title/00000001/00000002/data/setting.txt"; try { using namespace RuntimeNandSettings; Require(GenerateSerial(1800000123) == "800000123", "Dolphin timestamp modulo"); Require(GenerateSerial(1000000001) == "000000001", "Dolphin leading zero padding"); Require(GenerateSerial(-1).empty(), "Invalid clock must not supply an identity"); // Golden bytes generated by Dolphin's unmodified SettingsHandler.cpp // (upstream 2026-09-06), PAL boot fields and synthetic serial 000000001. // Everything after this prefix is raw zero padding to 256 bytes. const std::string goldenHex = "bba6ac929a0bc96b7eed83d27f33a1e7e73d9b836d8b47c59ee23df6b275baab" "bec9d9dead03cc7a3bdafee50c30ab9fb86194e119fe4ba19eff62d5ec3aacb3" "b5c9d9e3977eac0943d7ff903120a49ef024eafe1cf77be79cf6229a823aabf0f0"; std::array golden{}; for (size_t i = 0; i < goldenHex.size() / 2; ++i) { golden[i] = static_cast(std::stoul(goldenHex.substr(i * 2, 2), nullptr, 16)); } Require(EncodeNew("000000001") == golden, "Exact Dolphin writer golden fixture"); std::string error; Require(!RuntimeNandSettings::Read(root)); Require(!std::filesystem::exists(root)); std::filesystem::create_directories(path.parent_path()); const auto scratchParent = root / "scratch-collisions"; std::filesystem::create_directories(scratchParent / ".setting-init-fixed-0"); const auto sentinel = scratchParent / ".setting-init-fixed-0" / "setting.txt"; { std::ofstream output(sentinel); output << "another launch owns this"; } const auto occupiedFile = scratchParent / ".setting-init-fixed-1"; { std::ofstream output(occupiedFile); output << "leave this file alone"; } std::error_code scratchError; const auto claimed = CreateScratchDirectory(scratchParent, "fixed", scratchError); Require(claimed && *claimed == scratchParent / ".setting-init-fixed-2" && !scratchError, "Retry collisions with both existing directories and files"); Require(ReadBytes(sentinel) == "another launch owns this" && ReadBytes(occupiedFile) == "leave this file alone", "Never modify another launch's scratch data"); Require(!CreateScratchDirectory(occupiedFile / "not-a-directory", "fixed", scratchError) && scratchError, "Real filesystem errors must fail rather than retry indefinitely"); // Force all claimants to use the same token; this deterministically // exercises the collision path even when host clock precision is high. std::array, 16> claims; std::vector claimants; for (size_t i = 0; i < claims.size(); ++i) { claimants.emplace_back([&, i] { std::error_code ec; claims[i] = CreateScratchDirectory(scratchParent, "shared", ec); }); } for (auto& claimant : claimants) claimant.join(); for (size_t i = 0; i < claims.size(); ++i) { Require(claims[i].has_value(), "Every concurrent claimant must acquire a scratch directory"); for (size_t j = 0; j < i; ++j) { Require(claims[i] != claims[j], "Concurrent claimants must own different scratch directories"); } } const std::string plain = "AREA=USA\r\n\nCODE=LU\r\nSERNO=987654321\r\nGAME=US\r\n"; std::array fixture{}; for (size_t i = 0; i < fixture.size(); ++i) { const unsigned shift = i % 32; const uint32_t key = shift == 0 ? 0x73B5DBFAu : (0x73B5DBFAu << shift) | (0x73B5DBFAu >> (32 - shift)); fixture[i] = static_cast(key) ^ (i < plain.size() ? plain[i] : 0); } { std::ofstream output(path, std::ios::binary); output.write(reinterpret_cast(fixture.data()), fixture.size()); } auto settings = RuntimeNandSettings::Read(root); Require(settings && RuntimeNandSettings::HasIdentity(*settings)); Require(settings->at("SERNO") == "987654321" && settings->at("CODE") == "LU"); Require(settings->at("AREA") == "USA" && settings->at("GAME") == "US"); Require(Ensure(root, error, 1800000123), "Existing imported NAND must work"); std::array after{}; { std::ifstream input(path, std::ios::binary); input.read(reinterpret_cast(after.data()), after.size()); } Require(after == fixture); for (const auto serial : {"", "000000000", "1234567890", "123ABC789"}) { (*settings)["SERNO"] = serial; Require(!RuntimeNandSettings::HasIdentity(*settings)); } (*settings)["SERNO"] = "012345678"; Require(RuntimeNandSettings::HasIdentity(*settings)); (*settings)["CODE"] = "TOOLONG"; Require(!RuntimeNandSettings::HasIdentity(*settings)); (*settings)["CODE"] = "LEH"; settings->erase("GAME"); Require(!RuntimeNandSettings::HasIdentity(*settings)); std::filesystem::resize_file(path, 128); Require(!RuntimeNandSettings::Read(root)); const auto damaged = ReadBytes(path); Require(!Ensure(root, error, 1800000123), "Do not replace a truncated identity"); Require(ReadBytes(path) == damaged, "Damaged file must remain untouched"); const auto fresh = root / "fresh"; Require(Ensure(fresh, error, 1800000123), "Missing setting.txt must initialize"); const auto generated = Read(fresh); Require(generated && HasIdentity(*generated), "Generated file must be readable"); Require(generated->at("SERNO") == "800000123", "Persist Dolphin-generated serial"); Require(generated->at("CODE") == "LEH" && generated->at("AREA") == "EUR" && generated->at("GAME") == "EU", "PAL first-boot fields"); Require(generated->at("MODEL") == "RVL-001(EUR)" && generated->at("VIDEO") == "PAL" && generated->at("DVD") == "0" && generated->at("MPCH") == "0x7FFE", "Complete Dolphin boot settings"); const auto firstBoot = ReadBytes(FilePath(fresh)); Require(firstBoot.size() == 256 && firstBoot.back() == 0, "Dolphin buffer size and raw zero padding"); Require(Ensure(fresh, error, 1900000999), "Second boot"); Require(ReadBytes(FilePath(fresh)) == firstBoot, "Second boot must not change any bytes"); const auto blocked = root / "blocked"; { std::ofstream output(blocked); output << "file obstructing NAND directory"; } Require(!Ensure(blocked, error, 1800000123), "Write failure must not return an ephemeral identity"); Require(!Ensure(root / "bad-clock", error, -1), "Clock failure must not initialize"); const auto concurrent = root / "concurrent"; std::array results{}; std::vector workers; for (size_t i = 0; i < results.size(); ++i) { workers.emplace_back([&, i] { std::string detail; results[i] = Ensure(concurrent, detail, 1800000001 + i); }); } for (auto& worker : workers) worker.join(); for (const bool result : results) Require(result, "Concurrent boot must read the persisted winner"); const auto winner = ReadBytes(FilePath(concurrent)); Require(Read(concurrent) && HasIdentity(*Read(concurrent)), "Concurrent boot must persist valid settings"); Require(Ensure(concurrent, error, 1900000999), "Boot after concurrent initialization"); Require(ReadBytes(FilePath(concurrent)) == winner, "Concurrent winner must remain stable"); // Independently decode as Nintendo does: stop at the first encoded NUL. // Exercise serials that force Dolphin's extra-LF escaping, not only // values that happen to work with a plain rotating-XOR encoder. bool sawExtraLf = false; for (int serial = 1; serial <= 10000; ++serial) { const auto number = GenerateSerial(1000000000 + serial); const auto encoded = EncodeNew(number); Require(encoded.has_value(), "Serial encoding must fit"); std::string decoded; for (size_t i = 0; i < encoded->size() && (*encoded)[i] != 0; ++i) { const unsigned shift = i % 32; const uint32_t key = shift == 0 ? 0x73B5DBFAu : (0x73B5DBFAu << shift) | (0x73B5DBFAu >> (32 - shift)); decoded += static_cast((*encoded)[i] ^ static_cast(key)); } Require(decoded.find("SERNO=" + number + "\r\n") != std::string::npos && decoded.find("GAME=EU\r\n") != std::string::npos, "Encoded NUL must not truncate settings"); sawExtraLf |= decoded.find("\r\n\n") != std::string::npos; } Require(sawExtraLf, "Exercise Dolphin LF escape path"); std::filesystem::remove_all(root); std::cout << "NAND settings checks passed\n"; return 0; } catch (const std::exception& error) { std::filesystem::remove_all(root); std::cerr << error.what() << '\n'; return 1; } }