using System; using System.Reflection; using Harmony; using HutongGames.PlayMaker; using UnityEngine; namespace MSCO { /// /// Schutz vor Abstürzen durch nachgespielte Aktionen: Wird beim Beitreten eine Montage-Aktion in einer /// etwas anderen Welt nachgespielt, kann die "SetParent"-Aktion eines Teils ein Objekt unter sein eigenes /// Kind hängen. Das ergibt einen Kreis in der Hierarchie und Unity stürzt nativ ab (Stack Overflow in /// Transform::GetClassIDStatic). Solche Aufrufe werden hier abgefangen und übersprungen. /// public static class SafetyPatches { static bool applied; static FieldInfo gameObjectField, parentField; static int blocked; public static void Apply() { if (applied) return; applied = true; try { Type action = null; foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) { action = asm.GetType("HutongGames.PlayMaker.Actions.SetParent"); if (action != null) break; } MethodInfo onEnter = action != null ? action.GetMethod("OnEnter", BindingFlags.Instance | BindingFlags.Public) : null; if (onEnter == null) { MscoLog.Warning("[MSCO] SetParent action not found - hierarchy guard disabled"); return; } gameObjectField = action.GetField("gameObject"); parentField = action.GetField("parent"); HarmonyInstance.Create("mbm.msco.safety").Patch(onEnter, new HarmonyMethod(typeof(SafetyPatches).GetMethod("SetParentPrefix", BindingFlags.Static | BindingFlags.NonPublic)), null); } catch (Exception e) { MscoLog.Warning("[MSCO] Could not install hierarchy guard: " + e.Message); } PatchMenuQuit(); } /// /// ESC im Hauptmenü beendet beim Spiel sofort das Programm (PlayMaker-Aktion "ApplicationQuit" im Menü). Im /// MSCO-Hauptmenü gibt es dafür den QUIT-Knopf - dort also nicht über die Spiel-Logik beenden. Im Spiel selbst /// (Pausemenü) bleibt alles wie gehabt. /// static void PatchMenuQuit() { try { Type action = null; foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) { action = asm.GetType("HutongGames.PlayMaker.Actions.ApplicationQuit"); if (action != null) break; } MethodInfo onEnter = action != null ? action.GetMethod("OnEnter", BindingFlags.Instance | BindingFlags.Public) : null; if (onEnter == null) return; HarmonyInstance.Create("mbm.msco.menuquit").Patch(onEnter, new HarmonyMethod(typeof(SafetyPatches).GetMethod("QuitPrefix", BindingFlags.Static | BindingFlags.NonPublic)), null); } catch (Exception e) { MscoLog.Warning("[MSCO] Could not block ESC-quit in the main menu: " + e.Message); } } static bool QuitPrefix(FsmStateAction __instance) { if (!MenuHelper.InMenu) return true; MscoLog.Info("[MSCO] The game tried to quit from the main menu (ESC) - use QUIT in the MSCO menu to exit"); try { __instance.Finish(); } catch { } return false; } static bool SetParentPrefix(FsmStateAction __instance) { try { var owner = gameObjectField != null ? gameObjectField.GetValue(__instance) as FsmOwnerDefault : null; var parent = parentField != null ? parentField.GetValue(__instance) as FsmGameObject : null; if (owner == null || parent == null || parent.Value == null) return true; GameObject go = __instance.Fsm.GetOwnerDefaultTarget(owner); if (go == null) return true; Transform child = go.transform, newParent = parent.Value.transform; if (newParent != child && !newParent.IsChildOf(child)) return true; blocked++; if (blocked <= 20) MscoLog.Warning("[MSCO] Blocked SetParent that would crash the game: " + go.name + " -> " + parent.Value.name + " (" + __instance.Fsm.Name + ")"); __instance.Finish(); return false; } catch { return true; } } } }