From 8229879aabc760c479d22b9e83bd6d8a13c07eba Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 13 Aug 2026 02:04:41 -0700 Subject: [PATCH] refactor seed load/save and add file info hook --- include/d/d_file_sel_info.h | 2 +- mods/randomizer/src/hooks.cpp | 91 ++++++++++++++++++-- mods/randomizer/src/hooks.hpp | 1 + mods/randomizer/src/mod.cpp | 3 +- mods/randomizer/src/session.cpp | 107 +++++++++++++++++++++--- mods/randomizer/src/session.hpp | 4 + mods/randomizer/src/ui/rando_config.cpp | 9 +- 7 files changed, 191 insertions(+), 26 deletions(-) diff --git a/include/d/d_file_sel_info.h b/include/d/d_file_sel_info.h index 973349c469..37dadbeb1b 100644 --- a/include/d/d_file_sel_info.h +++ b/include/d/d_file_sel_info.h @@ -37,7 +37,7 @@ public: CPaneMgrAlpha* getNoDatBase() { return mNoDatBase; } void draw() { _draw(); } -private: +// private: /* 0x04 */ JKRArchive* mArchive; /* 0x08 */ dDlst_FileInfo_c mFileInfo; /* 0x1C */ u8 field_0x1c[4]; diff --git a/mods/randomizer/src/hooks.cpp b/mods/randomizer/src/hooks.cpp index 7b2a906ca1..e8e0c3a2ac 100644 --- a/mods/randomizer/src/hooks.cpp +++ b/mods/randomizer/src/hooks.cpp @@ -13,13 +13,15 @@ #include "d/actor/d_a_alink.h" #include "d/d_file_select.h" +#include "d/d_file_sel_info.h" #include "d/d_meter2_info.h" #include "d/d_save.h" #include "d/d_shop_system.h" DEFINE_HOOK(&dFile_select_c::selectDataNameMove, dFile_select_c__selectDataNameMove); DEFINE_HOOK(&dFile_select_c::dataSelect, dFile_select_c__dataSelect); -DEFINE_HOOK(&dFile_select_c::nameInput2, dFile_select_c__nameInput2); + +DEFINE_HOOK(&dFile_info_c::setSaveData, dFile_info_c__setSaveData); DEFINE_HOOK(&dSv_event_c::isEventBit, dSv_event_c__isEventBit); DEFINE_HOOK(&dSv_event_c::onEventBit, dSv_event_c__onEventBit); @@ -142,12 +144,41 @@ HookAction hookPreSelectDataNameMove(ModContext*, void* args, void* retval, void return HOOK_SKIP_ORIGINAL; } -void hookPostNameInput2(ModContext*, void* args, void* retval, void* userdata) { - dFile_select_c* i_this = mods::arg(args, 0); +void hookPostSetSaveData(ModContext* ctx, void* args, void* retval, void* userdata) { + dFile_info_c* i_this = mods::arg(args, 0); + u8 i_dataNo = mods::arg(args, 3); - if (i_this->mIsSelectEnd) { - if (!randomizer_GetContext().mHash.empty()) { - session::setupRandomizerFile(); + if (*static_cast(retval) == 0) { + char hash[64]; + size_t size = sizeof(hash) - 1; + + ModResult rt = session::svc_mng.save->peek_blob(ctx, i_dataNo, "seed_hash", hash, &size); + if (rt != MOD_OK || size == 0) { + // leave file text vanilla if seed hash isn't found + mods::log::debug("no seed_hash found for file {}", i_dataNo); + return; + } + + hash[size] = 0; + const std::string curFileSeedHash = hash; + if (!curFileSeedHash.empty()) { + const auto setHBinding = [](J2DTextBox* tbox, J2DTextBoxHBinding bind) { + tbox->mFlags &= 0b0011; + tbox->mFlags |= ((bind & 3) << 2); + }; + + // Overwrite "Save time" text with "Randomizer" + auto saveTimeText = (J2DTextBox*)i_this->mFileInfo.Scr->search(MULTI_CHAR('f_s_t_02')); + SafeStringCopy(saveTimeText->getStringPtr(), "Randomizer"); + setHBinding(saveTimeText, J2DTextBoxHBinding::HBIND_LEFT); + + // Overwrite the "Total play time" text with the seed hash + auto playTimeText = (J2DTextBox*)i_this->mFileInfo.Scr->search(MULTI_CHAR('f_p_t_02')); + SafeStringCopy(playTimeText->getStringPtr(), curFileSeedHash.c_str()); + + // Give the text double the space on the menu incase the seed hash is long + setHBinding(playTimeText, J2DTextBoxHBinding::HBIND_LEFT); + playTimeText->resize(playTimeText->getWidth() * 2, playTimeText->getHeight()); } } } @@ -1191,6 +1222,8 @@ ModResult initialize() { ADD_HOOK_PRE(dFile_select_c__selectDataNameMove, hookPreSelectDataNameMove); ADD_HOOK_PRE(dFile_select_c__dataSelect, hookPreDataSelect); + ADD_HOOK_POST(dFile_info_c__setSaveData, hookPostSetSaveData); + ADD_HOOK_PRE(dSv_event_c__isEventBit, hookPreIsEventBit); ADD_HOOK_PRE(dSv_event_c__onEventBit, hookPreOnEventBit); @@ -1219,8 +1252,6 @@ ModResult initialize() { ADD_HOOK_POST(dEvt_control_c__talkEnd, hookPostTalkEnd); - ADD_HOOK_POST(dFile_select_c__nameInput2, hookPostNameInput2); - ADD_HOOK_PRE(dComIfG_play_c__getLayerNo_common_common, hookPreGetLayerNo); ADD_HOOK_PRE(dItem_getItemFunc, hookPreGetItemFunc); @@ -1230,4 +1261,48 @@ ModResult initialize() { return MOD_OK; } + +ModResult uninstall() { + auto svc_hook = session::svc_mng.hook; + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + return MOD_OK; +} } \ No newline at end of file diff --git a/mods/randomizer/src/hooks.hpp b/mods/randomizer/src/hooks.hpp index a27ea0aaba..b71f56bd35 100644 --- a/mods/randomizer/src/hooks.hpp +++ b/mods/randomizer/src/hooks.hpp @@ -4,4 +4,5 @@ namespace randomizer::hooks { ModResult initialize(); +ModResult uninstall(); } \ No newline at end of file diff --git a/mods/randomizer/src/mod.cpp b/mods/randomizer/src/mod.cpp index 8a2eb32ccc..219431f41a 100644 --- a/mods/randomizer/src/mod.cpp +++ b/mods/randomizer/src/mod.cpp @@ -56,8 +56,9 @@ MOD_EXPORT ModResult mod_update(ModError*) { } MOD_EXPORT ModResult mod_shutdown(ModError*) { + randomizer::session::deactivateSeed(); + randomizer::hooks::uninstall(); svc_log->info(mod_ctx, "randomizer unloaded"); - randomizer::item::restore_item_data_tables(); return MOD_OK; } } diff --git a/mods/randomizer/src/session.cpp b/mods/randomizer/src/session.cpp index c6691df8b1..5aedde25a9 100644 --- a/mods/randomizer/src/session.cpp +++ b/mods/randomizer/src/session.cpp @@ -16,15 +16,14 @@ namespace randomizer::session { ServiceManager svc_mng; +std::string g_pending_seed_hash{}; -ModResult initialize(const ServiceManager& services) { - svc_mng = services; - - return MOD_OK; -} - +SaveObserverHandle s_save_observer{}; ItemCheckHandle s_check_resolver{}; ItemGiveHandle s_check_observer{}; +std::vector s_stage_edits{}; + +constexpr const char* kSeedHashBlobName = "seed_hash"; struct DerivedKey { int stage_id; @@ -123,18 +122,46 @@ void observe_give(ModContext*, const ItemGiveInfo* info, void*) { } } -void activateSeed() { +bool activateSeed(const char* hash) { auto& ctx = randomizer_GetContext(); + ctx = RandomizerContext(); + if (auto err = ctx.LoadFromHash(hash); err.has_value() || ctx.mHash.empty()) { + mods::log::error("failed to load seed {}", hash); + return false; + } item::apply_item_data_tables(); svc_mng.item->set_check_resolver(mod_ctx, nullptr, resolve_check, nullptr, &s_check_resolver); svc_mng.item->observe_gives(mod_ctx, observe_give, nullptr, &s_check_observer); + + registerStageEdits(); + mods::log::info("activated seed {}", ctx.mHash); + return true; +} + +void deactivateSeed() { + if (s_check_resolver != 0) { + svc_mng.item->clear_check_resolver(mod_ctx, s_check_resolver); + s_check_resolver = 0; + } + + if (s_check_observer != 0) { + svc_mng.item->unobserve_gives(mod_ctx, s_check_observer); + s_check_observer = 0; + } + + for (auto handle : s_stage_edits) { + svc_mng.stage->remove_actor_edit(mod_ctx, handle); + } + s_stage_edits.clear(); + + item::restore_item_data_tables(); + randomizer_GetContext() = RandomizerContext{}; + g_randomizerState = RandomizerState{}; } void setupRandomizerFile() { - activateSeed(); - // Setup file based on randomizer data auto& randoData = randomizer_GetContext(); randoData.mCreatingSave = true; @@ -239,6 +266,10 @@ void registerStageEdits() { res = svc_mng.stage->patch_actor( mod_ctx, stage, room, layer, crc, bytes.data(), bytes.size(), &handle); } + + if (res == MOD_OK) { + s_stage_edits.push_back(handle); + } } } @@ -252,9 +283,65 @@ void registerStageEdits() { const s8 layer = static_cast(key & 0xFF); for (const auto& bytes : additions) { StageActorHandle handle{}; - svc_mng.stage->add_actor(mod_ctx, stage, room, layer, bytes.data(), bytes.size(), &handle); + ModResult rt; + rt = svc_mng.stage->add_actor(mod_ctx, stage, room, layer, bytes.data(), bytes.size(), &handle); + if (rt == MOD_OK) { + s_stage_edits.push_back(handle); + } } } } +void onNewSave(ModContext*, uint32_t, void*) { + const std::string hash = g_pending_seed_hash; + if (hash.empty()) + return; + + if (!activateSeed(hash.c_str())) + return; + + svc_mng.save->set_blob(svc_mng.mod_ctx, kSeedHashBlobName, hash.data(), hash.size()); + setupRandomizerFile(); +} + +void onSaveLoaded(ModContext*, uint32_t, void*) { + size_t size = 0; + if (svc_mng.save->get_blob(mod_ctx, kSeedHashBlobName, nullptr, &size) != MOD_OK || size == 0) { + mods::log::error("seed_hash not found!"); + deactivateSeed(); + return; + } + + std::string hash(size, '\0'); + if (svc_mng.save->get_blob(mod_ctx, kSeedHashBlobName, hash.data(), &size) != MOD_OK) { + mods::log::error("failed to get seed_hash!"); + deactivateSeed(); + return; + } + + if (randomizer_GetContext().mHash != hash) { + deactivateSeed(); + activateSeed(hash.c_str()); + } + + loadAncientDocumentNum(); +} + +ModResult initialize(const ServiceManager& services) { + svc_mng = services; + + ModResult rt = svc_mng.save->observe_saves( + svc_mng.mod_ctx, + onNewSave, + onSaveLoaded, + nullptr, + nullptr, + &s_save_observer); + if (rt != MOD_OK) { + return rt; + } + + return MOD_OK; +} + } \ No newline at end of file diff --git a/mods/randomizer/src/session.hpp b/mods/randomizer/src/session.hpp index 3c58f15b26..ca658df57e 100644 --- a/mods/randomizer/src/session.hpp +++ b/mods/randomizer/src/session.hpp @@ -10,6 +10,8 @@ #include "mods/svc/stage.h" #include "mods/svc/item.h" +#include + namespace randomizer::session { struct ServiceManager { ModContext* mod_ctx; @@ -25,9 +27,11 @@ struct ServiceManager { }; extern ServiceManager svc_mng; +extern std::string g_pending_seed_hash; ModResult initialize(const ServiceManager& services); +void deactivateSeed(); void setupRandomizerFile(); void registerStageEdits(); } \ No newline at end of file diff --git a/mods/randomizer/src/ui/rando_config.cpp b/mods/randomizer/src/ui/rando_config.cpp index 58253075ac..6bcc42bbe4 100644 --- a/mods/randomizer/src/ui/rando_config.cpp +++ b/mods/randomizer/src/ui/rando_config.cpp @@ -553,7 +553,7 @@ ModResult buildPlayTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane for (const auto& entry : std::filesystem::directory_iterator(paths::GetRandomizerSeedsPath())) { if (entry.is_directory()) { std::string hash = entry.path().filename().string(); - if (randomizer_GetContext().mHash == hash) { + if (session::g_pending_seed_hash == hash) { break; } idx++; @@ -567,10 +567,7 @@ ModResult buildPlayTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane 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(); - randomizer_GetContext() = RandomizerContext(); - randomizer_GetContext().LoadFromHash(hash); - session::registerStageEdits(); + session::g_pending_seed_hash = entry.path().filename().string(); break; } idx++; @@ -590,7 +587,7 @@ ModResult buildPlayTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane }; desc.user_data = &g_file_select_window_ctx.window_handle; desc.is_disabled = [](ModContext*, void*) { - return randomizer_GetContext().mHash.empty(); + return session::g_pending_seed_hash.empty(); }; session::svc_mng.ui->pane_add_control(session::svc_mng.mod_ctx, leftPane, &desc, nullptr); }