diff --git a/example_community_patch_settings_da.toml b/example_community_patch_settings_da.toml index 2069b388a..254961603 100644 --- a/example_community_patch_settings_da.toml +++ b/example_community_patch_settings_da.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[=================================================================================]> [ui] +# Hide artifact shards "Exchange All"; individual exchanges remain available. Requires restart. +hide_artifact_exchange_all = false # Spring animationen ved åbning af kasser over, og åbn blot kasserne always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_de.toml b/example_community_patch_settings_de.toml index dc626072b..fc21eadd0 100644 --- a/example_community_patch_settings_de.toml +++ b/example_community_patch_settings_de.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[==================================================================================================================================]> [ui] +# Blendet "Alle umtauschen" bei Artefaktfragmenten aus; einzelne Tauschvorgänge bleiben verfügbar. Neustart erforderlich. +hide_artifact_exchange_all = false # Überspringt die Animation und öffnet die Boxen direkt always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_en-GB-x-cockney.toml b/example_community_patch_settings_en-GB-x-cockney.toml index e1aed8205..3ca06a10b 100644 --- a/example_community_patch_settings_en-GB-x-cockney.toml +++ b/example_community_patch_settings_en-GB-x-cockney.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[===================================================================]> [ui] +# Hide artifact shards "Exchange All"; individual exchanges remain available. Requires restart. +hide_artifact_exchange_all = false # Skip the reveal box animation, and just open the boxes always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_en-x-minionese.toml b/example_community_patch_settings_en-x-minionese.toml index 562af2608..7d6950e26 100644 --- a/example_community_patch_settings_en-x-minionese.toml +++ b/example_community_patch_settings_en-x-minionese.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[==================]> [ui] +# Hide artifact shards "Exchange All"; individual exchanges remain available. Requires restart. +hide_artifact_exchange_all = false # Skip the reveal box animation, and just open the boxes always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_en.toml b/example_community_patch_settings_en.toml index 8dcc031a6..48d9e7a49 100644 --- a/example_community_patch_settings_en.toml +++ b/example_community_patch_settings_en.toml @@ -263,6 +263,8 @@ loadingtiphooks = true doubleclickassignshiphooks = true forbiddentechconfirmhooks = true audioeventhooks = true +# Install the artifact popup hook independently of the hide preference. +artifactexchangehooks = true freeresizehooks = true game_version = true giftsbulkclaimhooks = true @@ -648,6 +650,8 @@ zoom_reset = "=" # <[===================================================================]> [ui] +# Hide artifact shards "Exchange All"; individual exchanges remain available. Requires restart. +hide_artifact_exchange_all = false # Skip the reveal box animation, and just open the boxes always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_es.toml b/example_community_patch_settings_es.toml index 795c8f1c8..243c180d2 100644 --- a/example_community_patch_settings_es.toml +++ b/example_community_patch_settings_es.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[============================================================]> [ui] +# Hide artifact shards "Exchange All"; individual exchanges remain available. Requires restart. +hide_artifact_exchange_all = false # Omite la animación de revelado de cajas y simplemente ábrelas always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_fr.toml b/example_community_patch_settings_fr.toml index 8bf856ea3..c1c5bbcaa 100644 --- a/example_community_patch_settings_fr.toml +++ b/example_community_patch_settings_fr.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[===================================================================]> [ui] +# Masque "Tout échanger" pour les fragments d’artefacts ; les échanges individuels restent disponibles. Redémarrage requis. +hide_artifact_exchange_all = false # Ignorer l'animation d'ouverture des coffres et les ouvrir directement always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_nl.toml b/example_community_patch_settings_nl.toml index 719b54a1d..bfa8626e2 100644 --- a/example_community_patch_settings_nl.toml +++ b/example_community_patch_settings_nl.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[===================================================================]> [ui] +# Verberg "Alles inwisselen" voor artefactfragmenten; afzonderlijk inwisselen blijft beschikbaar. Herstart vereist. +hide_artifact_exchange_all = false # Slaat de onthullig animatie van een kist altijd over en opent gewoon de kist always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_ru.toml b/example_community_patch_settings_ru.toml index 6e1d0ca2a..cf2859bcf 100644 --- a/example_community_patch_settings_ru.toml +++ b/example_community_patch_settings_ru.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[===================================================================]> [ui] +# Hide artifact shards "Exchange All"; individual exchanges remain available. Requires restart. +hide_artifact_exchange_all = false # Пропускать анимацию открытия контейнеров и сразу открывать их always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/example_community_patch_settings_tlh.toml b/example_community_patch_settings_tlh.toml index ca88ac0b1..1a667cf78 100644 --- a/example_community_patch_settings_tlh.toml +++ b/example_community_patch_settings_tlh.toml @@ -637,6 +637,8 @@ zoom_reset = "=" # <[========================================================================================]> [ui] +# Hide artifact shards "Exchange All"; individual exchanges remain available. Requires restart. +hide_artifact_exchange_all = false # Skip reveal box animation, 'ej just poSmoH boxes always_skip_reveal_sequence = true auto_confirm_discovery = true diff --git a/mods/src/config.cc b/mods/src/config.cc index b69377fbb..522c13e60 100644 --- a/mods/src/config.cc +++ b/mods/src/config.cc @@ -923,6 +923,8 @@ void Config::Load() get_config_or_default(config, parsed, "patches", "doubleclickassignshiphooks", DCP::doubleclickassignshiphooks, write_config); this->installForbiddenTechConfirmationHooks = get_config_or_default(config, parsed, "patches", "forbiddentechconfirmhooks", DCP::forbiddentechconfirmhooks, write_config); + this->installArtifactExchangeHooks = + get_config_or_default(config, parsed, "patches", "artifactexchangehooks", DCP::artifactexchangehooks, write_config); this->installAudioEventHooks = get_config_or_default(config, parsed, "patches", "audioeventhooks", DCP::audioeventhooks, write_config); this->installInstantCargoCounterHooks = @@ -1114,6 +1116,9 @@ void Config::Load() get_mission_hud_visibility(config, parsed, "hud_daily_goals", DCU::hud_daily_goals, write_config)); this->mission_hud_buttons.emplace( "missions", get_mission_hud_visibility(config, parsed, "hud_missions", DCU::hud_missions, write_config)); + this->hide_artifact_exchange_all = get_config_or_default( + config, parsed, "ui", "hide_artifact_exchange_all", DCU::hide_artifact_exchange_all, write_config); + spdlog::debug(""); this->sync_debug = get_config_or_default(config, parsed, "sync", "debug", DCS::debug, write_config); diff --git a/mods/src/config.h b/mods/src/config.h index 2ab320ce8..aab7736aa 100644 --- a/mods/src/config.h +++ b/mods/src/config.h @@ -266,6 +266,7 @@ class Config final bool installTestPatches; bool installMiscPatches; bool installMissionHudTweaksHooks; + bool hide_artifact_exchange_all; bool installChatPatches; bool installSyncPatches; bool installGameVersionHook; @@ -278,6 +279,7 @@ class Config final bool installForbiddenTechConfirmationHooks; bool installInstantWarpConfirmationHooks; bool installAudioEventHooks; + bool installArtifactExchangeHooks; std::string config_settings_url; std::string config_assets_url_override; diff --git a/mods/src/defaultconfig.h b/mods/src/defaultconfig.h index 10764b99f..b60614ef5 100644 --- a/mods/src/defaultconfig.h +++ b/mods/src/defaultconfig.h @@ -94,6 +94,7 @@ namespace Patches constexpr bool doubleclickassignshiphooks = true; constexpr bool forbiddentechconfirmhooks = true; constexpr bool audioeventhooks = true; + constexpr bool artifactexchangehooks = true; constexpr bool instantcargocounterhooks = true; constexpr bool cargoformathooks = true; // on by default: cargo number precision override constexpr bool officersorthooks = true; // restore Below Deck Ability sort option @@ -241,6 +242,7 @@ namespace Sync namespace UI { + constexpr bool hide_artifact_exchange_all = false; constexpr bool always_skip_reveal_sequence = true; constexpr bool arrow_keys_to_select_ship = true; constexpr bool auto_confirm_discovery = true; diff --git a/mods/src/il2cpp/method_contract.h b/mods/src/il2cpp/method_contract.h new file mode 100644 index 000000000..d7d1ccdc5 --- /dev/null +++ b/mods/src/il2cpp/method_contract.h @@ -0,0 +1,44 @@ +#pragma once + +#include "il2cpp-functions.h" +#include +#include +#include + +namespace method_contract +{ +inline bool Type(const Il2CppType* type, const char* name) +{ + if (!type || type->byref) return false; + auto* actual = il2cpp_type_get_name(type); + const bool matches = actual && std::strcmp(actual, name) == 0; + il2cpp_free(actual); + return matches; +} + +// Resolve the entire managed signature, including static/instance dispatch. +// A renamed, ambiguous or generic method is not a compatible callback. +inline const MethodInfo* Resolve(Il2CppClass* cls, const char* name, bool is_static, + const char* result, std::initializer_list parameters) +{ + if (!cls) return nullptr; + const MethodInfo* found = nullptr; + void* iterator = nullptr; + while (auto* method = il2cpp_class_get_methods(cls, &iterator)) { + if (std::strcmp(method->name, name) != 0 || !method->methodPointer || method->is_generic || method->is_inflated + || bool(method->flags & METHOD_ATTRIBUTE_STATIC) != is_static + || method->parameters_count != parameters.size() || !Type(method->return_type, result)) continue; + bool matches = true; + unsigned i = 0; + for (auto* parameter : parameters) + matches = Type(method->parameters[i++], parameter) && matches; + if (!matches) continue; + if (found) return nullptr; + found = method; + } + return found; +} + +inline void* Pointer(const MethodInfo* method) +{ return method ? reinterpret_cast(method->methodPointer) : nullptr; } +} // namespace method_contract diff --git a/mods/src/patches/parts/artifact_exchange.cc b/mods/src/patches/parts/artifact_exchange.cc new file mode 100644 index 000000000..463d2e5b0 --- /dev/null +++ b/mods/src/patches/parts/artifact_exchange.cc @@ -0,0 +1,59 @@ +#include "config.h" + +#include +#include +#include +#include +#include + +namespace +{ +FieldInfo* convert_all_field = nullptr; +const MethodInfo* get_game_object = nullptr; +const MethodInfo* set_active = nullptr; + +void InventoryUsePopup_Bind_Hook(auto original, void* controller) +{ + original(controller); + if (!Config::Get().hide_artifact_exchange_all || !controller) + return; + Il2CppObject* button = nullptr; + il2cpp_field_get_value(static_cast(controller), convert_all_field, &button); + Il2CppObject* object = nullptr; + if (!button || !Il2CppRuntime::TryInvoke(get_game_object, button, nullptr, &object) || !object) + return; + bool active = false; + void* args[]{&active}; + Il2CppRuntime::TryInvoke(set_active, object, args); +} +} // namespace + +void InstallArtifactExchangeHooks() +{ + // This dedicated field belongs to artifact bulk conversion, not the inventory + // entry button or individual exchanges. Preserve native binding first. + auto controller = + il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Inventories", "InventoryUsePopupViewController"); + auto component = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Component"); + auto game_object = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); + auto* cls = controller.get_cls(); + auto* field = cls ? il2cpp_class_get_field_from_name(cls, "_convertAllButton") : nullptr; + auto* field_class = field && field->type ? il2cpp_class_from_type(field->type) : nullptr; + const auto* bind = method_contract::Resolve(cls, "OnDidBindCanvasContext", false, "System.Void", {}); + get_game_object = method_contract::Resolve(component.get_cls(), "get_gameObject", false, "UnityEngine.GameObject", {}); + set_active = method_contract::Resolve(game_object.get_cls(), "SetActive", false, "System.Void", {"System.Boolean"}); + if (!cls || !component.get_cls() || !game_object.get_cls() || !field || !field->type || field->type->byref + || (il2cpp_field_get_flags(field) & FIELD_ATTRIBUTE_STATIC) || !field_class + || !il2cpp_class_is_assignable_from(component.get_cls(), field_class) + || !bind || !get_game_object || !set_active + || bind->has_full_generic_sharing_signature || get_game_object->has_full_generic_sharing_signature + || set_active->has_full_generic_sharing_signature) { + spdlog::warn("[ArtifactExchange] required popup API unavailable; leaving game unchanged"); + return; + } + convert_all_field = field; + if (SPUD_STATIC_DETOUR(bind->methodPointer, InventoryUsePopup_Bind_Hook)) + spdlog::info("[ArtifactExchange] installed popup bind hook"); + else + spdlog::warn("[ArtifactExchange] popup bind hook was not installed"); +} diff --git a/mods/src/patches/patches.cc b/mods/src/patches/patches.cc index d4e50a9d9..ef00d4e16 100644 --- a/mods/src/patches/patches.cc +++ b/mods/src/patches/patches.cc @@ -33,6 +33,7 @@ void InstallDailyFactionBulkClaimHooks(); void InstallTestPatches(); void InstallMiscPatches(); void InstallMissionHudTweaksHooks(); +void InstallArtifactExchangeHooks(); void InstallChatPatches(); void InstallTempCrashFixes(); void InstallSyncPatches(); @@ -137,6 +138,7 @@ __int64 il2cpp_init_hook(auto original, const char* domain_name) {"TestPatches", {InstallTestPatches, &cfg.installTestPatches}}, {"MiscPatches", {InstallMiscPatches, &cfg.installMiscPatches}}, {"MissionHudTweaksHooks", {InstallMissionHudTweaksHooks, &cfg.installMissionHudTweaksHooks}}, + {"ArtifactExchangeHooks", {InstallArtifactExchangeHooks, &cfg.installArtifactExchangeHooks}}, {"ChatPatches", {InstallChatPatches, &cfg.installChatPatches}}, {"SyncPatches", {InstallSyncPatches, &cfg.installSyncPatches}}, {"ObjectTracker", {InstallObjectTrackers, &cfg.installObjectTracker}},