From 641855e49ee896f6ee5f349191362e930ff05c6c Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 21 Aug 2026 11:52:52 -0600 Subject: [PATCH] Fix randomizer seed serialization --- mods/randomizer/src/randomizer_context.cpp | 38 ++++---- mods/randomizer/src/randomizer_context.hpp | 2 +- mods/randomizer/src/ui/rando_config.cpp | 106 +++++++++++---------- 3 files changed, 75 insertions(+), 71 deletions(-) diff --git a/mods/randomizer/src/randomizer_context.cpp b/mods/randomizer/src/randomizer_context.cpp index 1874f8923e..250b3f16cd 100644 --- a/mods/randomizer/src/randomizer_context.cpp +++ b/mods/randomizer/src/randomizer_context.cpp @@ -56,16 +56,20 @@ RandomizerContext::FlowNodeType parse_flow_node_type(const YAML::Node& node) { throw std::runtime_error("Unknown flow node type: " + type); } -void write_flow_reference( - YAML::Node node, const RandomizerContext::FlowReference& reference) { +YAML::Node write_flow_reference(const RandomizerContext::FlowReference& reference) { + YAML::Node node{}; if (reference.nativeId.has_value()) { node = reference.nativeId.value(); } else { node = reference.name; } + return node; } RandomizerContext::FlowReference parse_flow_reference(const YAML::Node& node) { + if (!node.IsScalar()) { + throw std::runtime_error("Flow reference must be a node ID or name"); + } RandomizerContext::FlowReference reference{}; try { reference.nativeId = node.as(); @@ -78,15 +82,15 @@ RandomizerContext::FlowReference parse_flow_reference(const YAML::Node& node) { void write_message_style( YAML::Node node, const RandomizerContext::MessageStyleData& style) { node["eventLabelId"] = style.eventLabelId; - node["speaker"] = style.speaker; - node["boxKind"] = style.boxKind; - node["drawType"] = style.drawType; - node["boxPosition"] = style.boxPosition; - node["lineAlignment"] = style.lineAlignment; - node["speakerMood"] = style.speakerMood; - node["cameraAttr"] = style.cameraAttr; - node["talkAnim"] = style.talkAnim; - node["faceAnim"] = style.faceAnim; + node["speaker"] = static_cast(style.speaker); + node["boxKind"] = static_cast(style.boxKind); + node["drawType"] = static_cast(style.drawType); + node["boxPosition"] = static_cast(style.boxPosition); + node["lineAlignment"] = static_cast(style.lineAlignment); + node["speakerMood"] = static_cast(style.speakerMood); + node["cameraAttr"] = static_cast(style.cameraAttr); + node["talkAnim"] = static_cast(style.talkAnim); + node["faceAnim"] = static_cast(style.faceAnim); node["trailingData"] = style.trailingData; } @@ -191,7 +195,7 @@ std::optional RandomizerContext::WriteToFile() { for (const auto& flow : mFlowNodes) { YAML::Node node{}; node["type"] = flow_node_type_name(flow.type); - node["group"] = flow.group; + node["group"] = static_cast(flow.group); if (flow.patchIndex.has_value()) { node["patchIndex"] = flow.patchIndex.value(); } else { @@ -202,22 +206,20 @@ std::optional RandomizerContext::WriteToFile() { node["operation"] = flow.operation; } if (flow.type == FlowNodeType::MESSAGE) { - write_flow_reference(node["message"], flow.message); + node["message"] = write_flow_reference(flow.message); } if (flow.type != FlowNodeType::BRANCH) { - write_flow_reference(node["next"], flow.next); + node["next"] = write_flow_reference(flow.next); } for (const auto& result : flow.results) { - YAML::Node resultNode{}; - write_flow_reference(resultNode, result); - node["results"].push_back(resultNode); + node["results"].push_back(write_flow_reference(result)); } out["mFlowNodes"].push_back(node); } for (const auto& message : mCustomMessages) { YAML::Node node{}; - node["group"] = message.group; + node["group"] = static_cast(message.group); node["name"] = message.name; write_message_style(node["style"], message.style); for (const auto& [language, text] : message.text) { diff --git a/mods/randomizer/src/randomizer_context.hpp b/mods/randomizer/src/randomizer_context.hpp index 91cee30c2f..d15e92f593 100644 --- a/mods/randomizer/src/randomizer_context.hpp +++ b/mods/randomizer/src/randomizer_context.hpp @@ -19,7 +19,7 @@ */ class RandomizerContext { public: - static constexpr u32 FORMAT_VERSION = 2; + static constexpr u32 FORMAT_VERSION = 3; static constexpr size_t ACTR_CRC_SIZE = 32; static constexpr size_t TGSC_CRC_SIZE = 35; // 3 extra bytes for scale x, y, z static constexpr size_t OBJ_DELETE_SIZE = 1; diff --git a/mods/randomizer/src/ui/rando_config.cpp b/mods/randomizer/src/ui/rando_config.cpp index 3138d2de71..627352edcb 100644 --- a/mods/randomizer/src/ui/rando_config.cpp +++ b/mods/randomizer/src/ui/rando_config.cpp @@ -14,6 +14,7 @@ #include "rando_seed_generation.hpp" #include "config_store.hpp" +#include #include #include #include @@ -107,6 +108,31 @@ UiMenuTabHandle g_menu_tab{}; FileSelectGateWindowCtx g_file_select_window_ctx{}; namespace { +std::vector get_compatible_seed_hashes() { + const std::filesystem::path seedDir = paths::GetRandomizerSeedsPath(); + std::filesystem::create_directories(seedDir); + + std::vector seedHashes; + for (const auto& entry : std::filesystem::directory_iterator(seedDir)) { + if (!entry.is_directory()) { + continue; + } + + try { + const YAML::Node seedData = LoadYAML(entry.path() / "seed.dat"); + if (seedData["formatVersion"] && + seedData["formatVersion"].as() == RandomizerContext::FORMAT_VERSION) { + seedHashes.push_back(entry.path().filename().string()); + } + } catch (const std::exception&) { + // Incomplete or malformed seeds cannot be activated and should not be offered. + } + } + + std::ranges::sort(seedHashes); + return seedHashes; +} + // Control Helpers ModResult add_button(UiElementHandle pane, const char* label, const char* help_rml, UiPressedFn on_pressed, void* userdata = nullptr, UiElementHandle* out_handle = nullptr) @@ -353,18 +379,9 @@ ModResult buildSeedManagementTab(ModContext* ctx, UiWindowHandle, UiElementHandl }); { - std::filesystem::path seed_dir = paths::GetRandomizerSeedsPath(); - if (!std::filesystem::exists(seed_dir)) - std::filesystem::create_directory(seed_dir); - std::string help_rml = "Select a seed above to delete it."; - std::vector seedHashes; - for (const auto& entry : std::filesystem::directory_iterator(seed_dir)) { - if (entry.is_directory()) { - seedHashes.push_back(entry.path().filename().string()); - } - } + const std::vector seedHashes = get_compatible_seed_hashes(); std::vector availableSeeds; for (const auto& hash : seedHashes) { @@ -380,20 +397,16 @@ ModResult buildSeedManagementTab(ModContext* ctx, UiWindowHandle, UiElementHandl out_value->int_value = 0; }, [](ModContext*, void*, const UiControlValue* value) { - int idx = 0; - for (const auto& entry : std::filesystem::directory_iterator(paths::GetRandomizerSeedsPath())) { - if (entry.is_directory()) { - if (idx == value->int_value) { - std::string hash = entry.path().filename().string(); - if (randomizer_GetContext().mHash == hash) { - randomizer_GetContext() = RandomizerContext{}; - } - std::filesystem::remove_all(entry); - break; - } - idx++; - } + const std::vector seedHashes = get_compatible_seed_hashes(); + if (value->int_value < 0 || static_cast(value->int_value) >= seedHashes.size()) { + return; } + + const std::string& hash = seedHashes[value->int_value]; + if (randomizer_GetContext().mHash == hash) { + randomizer_GetContext() = RandomizerContext{}; + } + std::filesystem::remove_all(paths::GetRandomizerSeedsPath() / hash); }); } @@ -1087,24 +1100,20 @@ void OnMenuTabSelected(ModContext* ctx, void*) { ModResult buildPlayTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane, UiElementHandle rightPane, void*, ModError*) { - std::filesystem::path seed_dir = paths::GetRandomizerSeedsPath(); - if (!std::filesystem::exists(seed_dir)) - std::filesystem::create_directory(seed_dir); + const std::vector seedHashes = get_compatible_seed_hashes(); std::string help_rml = ""; - if (std::filesystem::is_empty(seed_dir)) { + if (seedHashes.empty()) { help_rml = "No seeds generated! You can generate a seed from the Seed Management Tab."; } else { help_rml = "Choose which seed you want to play."; } - std::vector seedHashes; - for (const auto& entry : std::filesystem::directory_iterator(seed_dir)) { - if (entry.is_directory()) { - seedHashes.push_back(entry.path().filename().string()); - } + if (!session::g_pending_seed_hash.empty() && + !std::ranges::contains(seedHashes, session::g_pending_seed_hash)) { + session::g_pending_seed_hash.clear(); } - + std::vector availableSeeds; for (const auto& hash : seedHashes) { availableSeeds.push_back(hash.c_str()); @@ -1116,30 +1125,23 @@ ModResult buildPlayTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane availableSeeds.data(), availableSeeds.size(), [](ModContext*, void*, UiControlValue* out_value) { - int idx = 0; - for (const auto& entry : std::filesystem::directory_iterator(paths::GetRandomizerSeedsPath())) { - if (entry.is_directory()) { - std::string hash = entry.path().filename().string(); - if (session::g_pending_seed_hash == hash) { - break; - } - idx++; - } + const std::vector seedHashes = get_compatible_seed_hashes(); + const auto selected = std::ranges::find(seedHashes, session::g_pending_seed_hash); + if (selected == seedHashes.end()) { + out_value->int_value = 0; + return; } - out_value->int_value = idx; + out_value->int_value = static_cast(std::distance(seedHashes.begin(), selected)); }, [](ModContext*, void*, const UiControlValue* value) { - int idx = 0; - for (const auto& entry : std::filesystem::directory_iterator(paths::GetRandomizerSeedsPath())) { - if (entry.is_directory()) { - if (idx == value->int_value) { - session::g_pending_seed_hash = entry.path().filename().string(); - break; - } - idx++; - } + const std::vector seedHashes = get_compatible_seed_hashes(); + if (value->int_value < 0 || static_cast(value->int_value) >= seedHashes.size()) { + session::g_pending_seed_hash.clear(); + return; } + + session::g_pending_seed_hash = seedHashes[value->int_value]; }); {