From b7f5e2624b0c4e2156b7ebe94be87749d6b05d0a Mon Sep 17 00:00:00 2001 From: Guffawaffle Date: Thu, 22 Oct 2026 06:14:25 -0500 Subject: [PATCH 1/3] perf: scope galactic anomaly owner discovery --- .../patches/parts/galactic_anomaly_timer.cc | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/mods/src/patches/parts/galactic_anomaly_timer.cc b/mods/src/patches/parts/galactic_anomaly_timer.cc index 7ed45ca32..447919198 100644 --- a/mods/src/patches/parts/galactic_anomaly_timer.cc +++ b/mods/src/patches/parts/galactic_anomaly_timer.cc @@ -1,10 +1,11 @@ #include "config.h" #include "patches/screen_update_hook.h" #include "prime/Color.h" +#include "prime/Hub.h" #include "prime/Vector2.h" -#include #include +#include #include #include @@ -166,10 +167,46 @@ void Clear() anomalyManager.reset(); } -Il2CppObject* Find(Il2CppClass* type) +bool InSystemNavigation() +{ + static auto* hub = Class("Assembly-CSharp", "Digit.Client.Core", "Hub"); + static auto* sectionsGetter = IL2CppClassHelper(hub).GetMethodInfo("get_SectionManager", 0); + Il2CppObject* sections = nullptr; + std::int32_t section = 0; + return TryInvoke(sectionsGetter, nullptr, nullptr, §ions) && Value(sections, "get_CurrentSection", section) + && static_cast(section) == SectionID::Navigation_System; +} + +Il2CppObject* NavigationHud() +{ + static auto* screen = Class("Assembly-CSharp", "Digit.Client.UI", "ScreenManager"); + static auto* top = IL2CppClassHelper(screen).GetMethodInfo("GetTopCanvas", 1); + bool visibleOnly = true; + void* args[] = {&visibleOnly}; + Il2CppObject* canvas = nullptr; + if (!TryInvoke(top, nullptr, args, &canvas) || !Alive(canvas)) + return nullptr; + static auto* hud = Class("Assembly-CSharp", "Digit.Prime.Navigation.UI", "HudNavigationViewController"); + return WithType(TypeMethod(canvas->klass, "GetComponentInChildren"), canvas, hud); +} + +Il2CppObject* ExistingAnomalyManager(Il2CppClass* managerClass) { - static auto* method = TypeMethod(UnityObject(), "FindObjectOfType"); - return WithType(method, nullptr, type); + // get_Instance may search the scene or create a replacement. Read only the + // native owner's existing singleton, and retry when it has not initialized. + static auto* parent = managerClass ? il2cpp_class_get_parent(managerClass) : nullptr; + if (!parent || std::strcmp(il2cpp_class_get_name(parent), "MonoSingleton`1")) + return nullptr; + static auto* instance = il2cpp_class_get_field_from_name(parent, "s_instance"); + if (!instance || !(il2cpp_field_get_flags(instance) & FIELD_ATTRIBUTE_STATIC) || !instance->type + || il2cpp_type_is_byref(instance->type)) + return nullptr; + const auto kind = il2cpp_type_get_type(instance->type); + if (kind != IL2CPP_TYPE_CLASS && kind != IL2CPP_TYPE_GENERICINST && kind != IL2CPP_TYPE_VAR) + return nullptr; + Il2CppObject* value = nullptr; + il2cpp_field_static_get_value(instance, &value); + return value && il2cpp_class_is_assignable_from(managerClass, value->klass) ? value : nullptr; } Il2CppObject* Component(Il2CppObject* object, Il2CppClass* type) @@ -199,7 +236,8 @@ Il2CppObject* NewObject(const char* name, Il2CppObject* parent, Root& root) auto* transform = WithType(TypeMethod(go, "AddComponent"), object, rt); bool worldPositionStays = false; void* parentArgs[] = {parent, &worldPositionStays}; - if (!transform || !TryInvoke(IL2CppClassHelper(transform->klass).GetMethodInfo("SetParent", 2), transform, parentArgs)) + if (!transform + || !TryInvoke(IL2CppClassHelper(transform->klass).GetMethodInfo("SetParent", 2), transform, parentArgs)) return nullptr; return transform; } @@ -273,12 +311,13 @@ bool Create(Il2CppObject* nav) if (!TryInvoke(IL2CppClassHelper(names->klass).GetMethodInfo("get_Item", 1), names, args, &name) || !Alive(name)) return false; static auto* drawerClass = Class("Assembly-CSharp", "Digit.Prime.HUD", "HudAllianceAndNewsViewController"); - auto* drawer = Find(drawerClass); - auto* chest = Get(Field(drawer, "_chestPromotion"), "get_transform"); - auto* parent = Get(chest, "get_parent"); - static auto* tmp = Class("Unity.TextMeshPro", "TMPro", "TextMeshProUGUI"); - auto* source = Component(name, tmp); - auto* font = Get(source, "get_font"); + auto* uiRoot = Get(Get(nav, "get_transform"), "get_root"); + auto* drawer = uiRoot ? WithType(TypeMethod(uiRoot->klass, "GetComponentInChildren"), uiRoot, drawerClass) : nullptr; + auto* chest = Get(Field(drawer, "_chestPromotion"), "get_transform"); + auto* parent = Get(chest, "get_parent"); + static auto* tmp = Class("Unity.TextMeshPro", "TMPro", "TextMeshProUGUI"); + auto* source = Component(name, tmp); + auto* font = Get(source, "get_font"); if (!parent || !font) return false; @@ -337,10 +376,14 @@ void Update() Clear(); return; } - if (!Alive(navigation.get())) { + if (!InSystemNavigation()) { + Clear(); + return; + } + bool active = false; + if (!Alive(navigation.get()) || !Value(navigation.get(), "get_isActiveAndEnabled", active) || !active) { Clear(); - static auto* cls = Class("Assembly-CSharp", "Digit.Prime.Navigation.UI", "HudNavigationViewController"); - navigation.reset(Find(cls)); + navigation.reset(NavigationHud()); } auto* nav = navigation.get(); bool show = false; @@ -353,12 +396,13 @@ void Update() // Cache the Unity object, not its timer data. Unity's liveness check detects // destruction even while the managed wrapper is retained by our GC handle. if (!Alive(anomalyManager.get())) - anomalyManager.reset(Find(managerClass)); + anomalyManager.reset(ExistingAnomalyManager(managerClass)); auto* manager = anomalyManager.get(); Il2CppObject* anomaly = nullptr; void* args[] = {&system}; show = manager && Value(address, "get_System", system) && system > 0 - && TryInvoke(IL2CppClassHelper(managerClass).GetMethodInfo("GetSystemGalacticAnomalies", 1), manager, args, &anomaly) + && TryInvoke(IL2CppClassHelper(managerClass).GetMethodInfo("GetSystemGalacticAnomalies", 1), manager, args, + &anomaly) && Value(anomaly, "get_IsActive", show) && show && Value(Get(anomaly, "get_EndTimeTimerDataContext"), "get_RemainingTime", ticks) && ticks > 0; } else { From 2084daeb50a3786a4655fdf93975a2984336b822 Mon Sep 17 00:00:00 2001 From: Guffawaffle Date: Thu, 22 Oct 2026 06:15:10 -0500 Subject: [PATCH 2/3] fix: include Hub diagnostic prerequisite --- mods/src/patches/parts/galactic_anomaly_timer.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/mods/src/patches/parts/galactic_anomaly_timer.cc b/mods/src/patches/parts/galactic_anomaly_timer.cc index 447919198..640427b5a 100644 --- a/mods/src/patches/parts/galactic_anomaly_timer.cc +++ b/mods/src/patches/parts/galactic_anomaly_timer.cc @@ -1,4 +1,5 @@ #include "config.h" +#include "errormsg.h" #include "patches/screen_update_hook.h" #include "prime/Color.h" #include "prime/Hub.h" From f77528ecbe099ca02a7cb6247d77dfbd532ce7fd Mon Sep 17 00:00:00 2001 From: Guffawaffle Date: Thu, 22 Oct 2026 06:21:24 -0500 Subject: [PATCH 3/3] fix: reject destroyed anomaly singleton owners --- mods/src/patches/parts/galactic_anomaly_timer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/src/patches/parts/galactic_anomaly_timer.cc b/mods/src/patches/parts/galactic_anomaly_timer.cc index 640427b5a..211074680 100644 --- a/mods/src/patches/parts/galactic_anomaly_timer.cc +++ b/mods/src/patches/parts/galactic_anomaly_timer.cc @@ -207,7 +207,7 @@ Il2CppObject* ExistingAnomalyManager(Il2CppClass* managerClass) return nullptr; Il2CppObject* value = nullptr; il2cpp_field_static_get_value(instance, &value); - return value && il2cpp_class_is_assignable_from(managerClass, value->klass) ? value : nullptr; + return value && il2cpp_class_is_assignable_from(managerClass, value->klass) && Alive(value) ? value : nullptr; } Il2CppObject* Component(Il2CppObject* object, Il2CppClass* type)