#pragma once #include "runtime_config.h" #include "runtime_log.h" #include "system_bridge.h" #include #include #include #include #include #include #include #include namespace RuntimeNandPath { inline std::optional ExistingDirectory(const std::filesystem::path& path) { std::error_code ec; if (!path.empty() && std::filesystem::is_directory(path, ec) && !ec) { return path; } return std::nullopt; } [[noreturn]] inline void FailNandRoot(const char* message, const std::filesystem::path& path = {}) { if (path.empty()) { RT_LOGF(RT_TAG_NAND, "ERROR: %s\n", message); } else { RT_LOGF(RT_TAG_NAND, "ERROR: %s: %s\n", message, RuntimeConfigFile::PathToUtf8(path).c_str()); } RT_LOGF(RT_TAG_NAND, "Set [paths] nand_root in Config.toml.\n"); std::string details = message ? message : "The configured NAND could not be initialized."; if (!path.empty()) { details += "\n\nPath: "; details += RuntimeConfigFile::PathToUtf8(path); } details += "\n\nSet [paths] nand_root in Config.toml and try again."; // Same fatal idiom as the DVD and OS paths: crash artifacts first so the run // folder always has them, then a non-zero exit code, the popup, and the // "already reported" latch so the atexit handler does not stack a second // generic report on top of this one. RuntimeCrash::WriteCrashArtifacts("nand_root", details); SetRuntimeExitCode(EXIT_FAILURE); ShowRuntimeFatalPopup("NAND initialization failed", details); MarkFatalErrorReported(); std::exit(EXIT_FAILURE); } inline std::filesystem::path ResolveConfiguredPath(const std::string& value) { return RuntimeConfigFile::ResolveRelativeToConfig(value); } inline std::filesystem::path ManagedNandRootPath() { return RuntimeConfigFile::ApplicationDataDirectory() / "NAND"; } inline std::optional BootstrapPayloadPath() { if (auto executableDirectory = RuntimeConfigFile::ExecutableDirectory()) { const auto adjacent = *executableDirectory / "wii_bootstrap"; if (ExistingDirectory(adjacent / "shared2" / "wc24")) { return adjacent; } } // This makes developer-tree launches work without changing their release layout. for (auto base = std::filesystem::current_path(); !base.empty();) { const auto candidate = base / "runtime" / "assets" / "wii"; if (ExistingDirectory(candidate / "shared2" / "wc24")) { return candidate; } const auto parent = base.parent_path(); if (parent == base) { break; } base = parent; } return std::nullopt; } inline bool CopyBootstrapFile(const std::filesystem::path& sourceRoot, const std::filesystem::path& destinationRoot, const std::filesystem::path& relativePath, std::error_code& ec) { const auto source = sourceRoot / relativePath; const auto destination = destinationRoot / relativePath; if (std::filesystem::exists(destination, ec)) { return !ec; } std::filesystem::create_directories(destination.parent_path(), ec); if (ec) { return false; } std::filesystem::copy_file(source, destination, std::filesystem::copy_options::none, ec); return !ec; } // Create these WC24 files only for a new profile; never overwrite user data. constexpr std::string_view kBootstrapFiles[] = { "shared2/wc24/misc.bin", "shared2/wc24/nwc24dl.bin", "shared2/wc24/nwc24fl.bin", "shared2/wc24/nwc24fls.bin", "shared2/wc24/nwc24msg.cbk", "shared2/wc24/nwc24msg.cfg", "shared2/wc24/mbox/Readme.txt", "shared2/wc24/mbox/wc24recv.ctl", "shared2/wc24/mbox/wc24recv.mbx", "shared2/wc24/mbox/wc24send.ctl", "shared2/wc24/mbox/wc24send.mbx", }; // Add first-run WC24 files only when the NAND has none yet. inline bool SeedMissingBootstrapFiles(const std::filesystem::path& root) { const auto payload = BootstrapPayloadPath(); if (!payload) { return false; } std::error_code ec; for (const std::string_view file : kBootstrapFiles) { const std::filesystem::path relativePath{std::string(file)}; ec.clear(); if (!CopyBootstrapFile(*payload, root, relativePath, ec)) { RT_LOG(RT_TAG_NAND) << "could not create " << RuntimeConfigFile::PathToUtf8(root / relativePath) << std::endl; return false; } } return true; } inline std::filesystem::path CreateManagedNandRoot() { const std::filesystem::path root = ManagedNandRootPath(); std::error_code ec; std::filesystem::create_directories(root, ec); if (ec || !std::filesystem::is_directory(root, ec)) { FailNandRoot("Unable to create managed NAND root", root); } if (!SeedMissingBootstrapFiles(root)) { FailNandRoot("Unable to initialize managed NAND", root); } const auto marker = root / ".mkw_recompiled_managed_nand"; ec.clear(); if (!std::filesystem::exists(marker, ec)) { std::ofstream markerFile(marker, std::ios::trunc); if (!markerFile) { FailNandRoot("Managed NAND root is not writable", root); } markerFile << "version=1\n"; markerFile.close(); if (!markerFile) { FailNandRoot("Unable to finish managed NAND initialization", root); } } RT_LOG(RT_TAG_NAND) << "using managed NAND root: " << RuntimeConfigFile::PathToUtf8(root) << std::endl; return root; } inline std::filesystem::path DiscoverNandRootPath() { const std::string configPath = RuntimeConfigFile::NandRoot(); if (!configPath.empty()) { const auto path = ResolveConfiguredPath(configPath); if (auto existing = ExistingDirectory(path)) { // Seed only a new configured NAND so existing frontend data stays unchanged. if (!SeedMissingBootstrapFiles(*existing)) { RT_LOG(RT_TAG_NAND) << "first-run WC24 seeding failed for the configured NAND root" << std::endl; } return *existing; } FailNandRoot("Configured NAND root is not an existing directory", path); } return CreateManagedNandRoot(); } } // namespace RuntimeNandPath