Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DestroyedMoons v1.0.8
BepInEx/plugins/DestroyedMoons/DestroyedMoons.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DestroyedMoons.Config; using DestroyedMoons.Core; using DestroyedMoons.Integrations; using DestroyedMoons.Meltdown; using DestroyedMoons.Networking; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Collections; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("DestroyedMoons")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.8.0")] [assembly: AssemblyInformationalVersion("1.0.8")] [assembly: AssemblyProduct("DestroyedMoons")] [assembly: AssemblyTitle("DestroyedMoons")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.8.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DestroyedMoons { [BepInPlugin("soldierling.DestroyedMoons", "DestroyedMoons", "1.0.8")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private Harmony _harmony; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Init(((BaseUnityPlugin)this).Config); if (!ModConfig.Enabled.Value) { Log.LogWarning((object)"DestroyedMoons is disabled in config. Skipping all patches."); return; } _harmony = new Harmony("soldierling.DestroyedMoons"); _harmony.PatchAll(typeof(Plugin).Assembly); SafeInit("MeltdownDetector", delegate { MeltdownDetector.Initialize(_harmony); }); SafeInit("RandomMoonFX", delegate { RandomMoonFxIntegration.Initialize(_harmony); }); SafeInit("MeltdownChance", delegate { MeltdownChanceIntegration.Initialize(); }); SafeInit("SaveDeletion", delegate { SaveDeletionPatch.Initialize(_harmony); }); Log.LogInfo((object)("DestroyedMoons v1.0.8 loaded. " + $"FacilityMeltdown={SoftDeps.HasFacilityMeltdown}, " + $"RandomMoonFX={SoftDeps.HasRandomMoonFX}, " + $"MeltdownChance={SoftDeps.HasMeltdownChance}, " + $"LethalLevelLoader={SoftDeps.HasLethalLevelLoader}.")); } private static void SafeInit(string label, Action action) { try { action(); } catch (Exception arg) { Log.LogError((object)$"{label} init failed (mod continues): {arg}"); } } } internal static class PluginInfo { public const string GUID = "soldierling.DestroyedMoons"; public const string NAME = "DestroyedMoons"; public const string VERSION = "1.0.8"; public const string FACILITY_MELTDOWN_GUID = "me.loaforc.facilitymeltdown"; public const string FACILITY_MELTDOWN_GUID_LEGACY = "TeamXiaolan.FacilityMeltdown"; public const string RANDOM_MOON_FX_GUID = "zigzag.randommoonfx"; public const string MELTDOWN_CHANCE_GUID = "den.meltdownchance"; public const string LETHAL_LEVEL_LOADER_GUID = "imabatby.lethallevelloader"; } } namespace DestroyedMoons.Routing { [HarmonyPatch(typeof(StartOfRound), "ChangeLevelServerRpc")] internal static class ChangeLevelServerRpcPatch { [HarmonyPrefix] private static bool Prefix(int levelID) { try { string text = MoonKey.FromSelectableLevel(MoonResolver.LevelById(levelID)); if (DestroyedMoonsService.IsBlockedForRouting(text)) { Plugin.Log.LogWarning((object)$"Cancelled level change to destroyed moon '{text}' (levelID {levelID})."); HUDNotify.Show(ModConfig.BlockedMessage.Value); return false; } } catch { } return true; } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] internal static class ParsePlayerSentencePatch { [HarmonyPostfix] private static void Postfix(Terminal __instance, ref TerminalNode __result) { try { Handle(__instance, ref __result); } catch { } } private static void Handle(Terminal __instance, ref TerminalNode __result) { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.screenText == (Object)null) { return; } string text = __instance.screenText.text; int textAdded = __instance.textAdded; if (textAdded <= 0 || textAdded > text.Length) { return; } string text2 = text.Substring(text.Length - textAdded).Trim().ToLowerInvariant(); if (text2.Length == 0) { return; } if (ModConfig.EnableTerminalCommands.Value && text2 == "moonstatus") { List<string> list = DestroyedMoonsService.DescribeBlocked(); string text3 = ((list.Count == 0) ? "No moons are currently destroyed." : ("Destroyed / blocked moons:\n - " + string.Join("\n - ", list))); __result = TerminalNodeFactory.Message(text3); } else if (ModConfig.EnableMoonBuyback.Value) { if (text2 == "buyback") { __result = TerminalNodeFactory.Message(BuybackBody("MOON BUYBACK\n\n")); } else if (text2.StartsWith("buyback ")) { __result = TerminalNodeFactory.Message(DoBuyback(__instance, text2.Substring(8).Trim())); } else if ((text2 == "help" || text2 == "other") && DestroyedMoonsService.DescribeAutoBlocked().Count > 0) { __result = AppendBuybackSection(__result); } } } private static TerminalNode AppendBuybackSection(TerminalNode original) { string text = "\n\n>MOON BUYBACK\n____________________________\n\n" + BuybackBody(string.Empty); if ((Object)(object)original == (Object)null) { return TerminalNodeFactory.Message(text); } TerminalNode obj = Object.Instantiate<TerminalNode>(original); obj.displayText = (original.displayText ?? string.Empty) + text; return obj; } private static string BuybackBody(string header) { int value = ModConfig.MoonBuybackPrice.Value; StringBuilder stringBuilder = new StringBuilder(); if (!string.IsNullOrEmpty(header)) { stringBuilder.Append(header); } List<string> list = DestroyedMoonsService.DescribeAutoBlocked(); if (list.Count == 0) { stringBuilder.Append("No moons are currently destroyed.\n"); return stringBuilder.ToString(); } stringBuilder.Append("These moons were lost to nuclear meltdowns.\n"); stringBuilder.Append("Restore one for ").Append(value).Append(" credits with:\n"); stringBuilder.Append(" >BUYBACK [moon name]\n\n"); foreach (string item in list) { stringBuilder.Append(" * ").Append(item).Append(" (") .Append(value) .Append("cr)\n"); } return stringBuilder.ToString(); } private static string DoBuyback(Terminal terminal, string arg) { if (string.IsNullOrEmpty(arg)) { return "Usage: BUYBACK [moon name]"; } string text = DestroyedMoonsService.ResolveAutoBlockedKey(arg); if (text == null) { return "'" + arg + "' is not a destroyed moon. Type BUYBACK to see the list."; } int value = ModConfig.MoonBuybackPrice.Value; if (terminal.groupCredits < value) { return $"Not enough credits. Restoring a moon costs {value}; you have {terminal.groupCredits}."; } string arg2 = text; foreach (SelectableLevel item in MoonResolver.AllLevels()) { if (MoonKey.FromSelectableLevel(item) == text) { arg2 = item.PlanetName; break; } } terminal.groupCredits -= value; try { terminal.SyncGroupCreditsServerRpc(terminal.groupCredits, terminal.numberOfItemsInDropship); } catch { } BlockedMoonsNetwork.RequestUnblock(text); return $"'{arg2}' has been restored for {value} credits.\nRemaining balance: {terminal.groupCredits}."; } } internal static class TerminalNodeFactory { public static TerminalNode Message(string text) { TerminalNode obj = ScriptableObject.CreateInstance<TerminalNode>(); ((Object)obj).name = "DestroyedMoonsMessage"; obj.displayText = "\n" + text + "\n\n"; obj.clearPreviousText = true; obj.maxCharactersToType = 999; obj.buyRerouteToMoon = -1; obj.buyItemIndex = -1; obj.itemCost = 0; obj.displayPlanetInfo = -1; obj.shipUnlockableID = -1; obj.playSyncedClip = -1; obj.acceptAnything = false; obj.overrideOptions = false; return obj; } } [HarmonyPatch(typeof(Terminal), "LoadNewNode")] internal static class LoadNewNodePatch { [HarmonyPrefix] private static void Prefix(ref TerminalNode node) { try { if ((Object)(object)node == (Object)null || node.buyRerouteToMoon < 0) { return; } string text = MoonKey.FromSelectableLevel(MoonResolver.LevelById(node.buyRerouteToMoon)); if (DestroyedMoonsService.IsBlockedForRouting(text)) { if (ModConfig.Verbose) { Plugin.Log.LogInfo((object)$"Blocked terminal reroute to '{text}' (levelID {node.buyRerouteToMoon})."); } node = TerminalNodeFactory.Message(ModConfig.BlockedMessage.Value); } } catch { } } } [HarmonyPatch(typeof(Terminal), "TextPostProcess")] [HarmonyAfter(new string[] { "imabatby.lethallevelloader" })] internal static class TextPostProcessStrikethroughPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ref string __result) { if (!ModConfig.StrikethroughBlockedMoons.Value || string.IsNullOrEmpty(__result) || __result.IndexOf("<s>", StringComparison.OrdinalIgnoreCase) >= 0) { return; } try { foreach (SelectableLevel item in MoonResolver.AllLevels()) { if (!DestroyedMoonsService.IsBlocked(MoonKey.FromSelectableLevel(item))) { continue; } string planetName = item.PlanetName; if (!string.IsNullOrEmpty(planetName) && __result.IndexOf(planetName, StringComparison.OrdinalIgnoreCase) >= 0) { __result = StrikeAll(__result, planetName); continue; } string text = MoonKey.NumberlessDisplay(planetName); if (!string.IsNullOrEmpty(text)) { __result = StrikeAll(__result, text); } } } catch { } } private static string StrikeAll(string text, string name) { int startIndex = 0; while (true) { int num = text.IndexOf(name, startIndex, StringComparison.OrdinalIgnoreCase); if (num < 0) { break; } if (num >= 3 && text.Substring(num - 3, 3).Equals("<s>", StringComparison.OrdinalIgnoreCase)) { startIndex = num + name.Length; continue; } string text2 = text.Substring(num, name.Length); string text3 = "<s>" + text2 + "</s>"; text = text.Substring(0, num) + text3 + text.Substring(num + name.Length); startIndex = num + text3.Length; } return text; } } } namespace DestroyedMoons.Networking { internal static class BlockedMoonsNetwork { [CompilerGenerated] private static class <>O { public static HandleNamedMessageDelegate <0>__OnSyncReceived; public static HandleNamedMessageDelegate <1>__OnRequestReceived; public static HandleNamedMessageDelegate <2>__OnUnblockReceived; } private const string MSG_SYNC = "DestroyedMoons_Sync"; private const string MSG_REQUEST = "DestroyedMoons_Req"; private const string MSG_UNBLOCK = "DestroyedMoons_Unblock"; private static bool _registered; public static void Register() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.CustomMessagingManager == null || _registered) { return; } try { CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager; object obj = <>O.<0>__OnSyncReceived; if (obj == null) { HandleNamedMessageDelegate val = OnSyncReceived; <>O.<0>__OnSyncReceived = val; obj = (object)val; } customMessagingManager.RegisterNamedMessageHandler("DestroyedMoons_Sync", (HandleNamedMessageDelegate)obj); CustomMessagingManager customMessagingManager2 = singleton.CustomMessagingManager; object obj2 = <>O.<1>__OnRequestReceived; if (obj2 == null) { HandleNamedMessageDelegate val2 = OnRequestReceived; <>O.<1>__OnRequestReceived = val2; obj2 = (object)val2; } customMessagingManager2.RegisterNamedMessageHandler("DestroyedMoons_Req", (HandleNamedMessageDelegate)obj2); CustomMessagingManager customMessagingManager3 = singleton.CustomMessagingManager; object obj3 = <>O.<2>__OnUnblockReceived; if (obj3 == null) { HandleNamedMessageDelegate val3 = OnUnblockReceived; <>O.<2>__OnUnblockReceived = val3; obj3 = (object)val3; } customMessagingManager3.RegisterNamedMessageHandler("DestroyedMoons_Unblock", (HandleNamedMessageDelegate)obj3); _registered = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to register net messages: " + ex.Message)); } } public unsafe static void BroadcastFullListToAll() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!NetworkUtils.IsHost) { return; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.CustomMessagingManager == null) { return; } string text = BlockedMoonsStore.SerializeForSync(); FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(text.Length * 2 + 256, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(text, false); try { singleton.CustomMessagingManager.SendNamedMessageToAll("DestroyedMoons_Sync", val, (NetworkDelivery)4); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Broadcast failed: " + ex.Message)); } } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } private unsafe static void SendListToClient(ulong clientId) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.CustomMessagingManager == null) { return; } string text = BlockedMoonsStore.SerializeForSync(); FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(text.Length * 2 + 256, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(text, false); try { singleton.CustomMessagingManager.SendNamedMessage("DestroyedMoons_Sync", clientId, val, (NetworkDelivery)4); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Targeted sync failed: " + ex.Message)); } } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } public unsafe static void RequestFullListFromHost() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (NetworkUtils.IsHost) { return; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.CustomMessagingManager == null) { return; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(8, (Allocator)2, -1); try { int num = 0; ((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives)); try { singleton.CustomMessagingManager.SendNamedMessage("DestroyedMoons_Req", 0uL, val, (NetworkDelivery)2); } catch (Exception ex) { Plugin.Log.LogWarning((object)("List request failed: " + ex.Message)); } } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } public unsafe static void RequestUnblock(string moonKey) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (NetworkUtils.IsHost) { DestroyedMoonsService.UnblockMoon(moonKey); return; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.CustomMessagingManager == null || string.IsNullOrEmpty(moonKey)) { return; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(moonKey.Length * 2 + 64, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(moonKey, false); try { singleton.CustomMessagingManager.SendNamedMessage("DestroyedMoons_Unblock", 0uL, val, (NetworkDelivery)2); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Unblock request failed: " + ex.Message)); } } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } private static void OnSyncReceived(ulong senderClientId, FastBufferReader reader) { if (NetworkUtils.IsHost) { return; } try { string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); string[] array = (string.IsNullOrEmpty(text) ? Array.Empty<string>() : text.Split('\n')); BlockedMoonsStore.ReplaceFromSync(array); Plugin.Log.LogInfo((object)$"Synced {array.Length} destroyed moon(s) from host."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to read sync: " + ex.Message)); } } private static void OnRequestReceived(ulong senderClientId, FastBufferReader reader) { if (NetworkUtils.IsHost) { SendListToClient(senderClientId); } } private static void OnUnblockReceived(ulong senderClientId, FastBufferReader reader) { if (!NetworkUtils.IsHost) { return; } try { string key = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref key, false); DestroyedMoonsService.UnblockMoon(key); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to read unblock request: " + ex.Message)); } } } } namespace DestroyedMoons.Meltdown { internal static class MeltdownDetector { private const string HANDLER_TYPE = "FacilityMeltdown.MeltdownSequence.Behaviours.MeltdownHandler"; private const string START_METHOD = "StartMeltdownClientRpc"; private const string EXPLOSION_TYPE = "FacilityMeltdown.MeltdownSequence.Behaviours.FacilityExplosionHandler"; private const string EXPLOSION_METHOD = "Awake"; private const string API_TYPE = "FacilityMeltdown.API.MeltdownAPI"; private static Harmony _harmony; private static bool _bound; private static string _lastKey; private static DateTime _lastTime; private static readonly string[] MethodNameCandidates = new string[12] { "Detonate", "Explode", "Explosion", "SpawnExplosion", "MeltdownActually", "MeltdownEnd", "OnMeltdownFinished", "FinishMeltdown", "CauseMeltdown", "TriggerMeltdown", "StartMeltdown", "Meltdown" }; public static bool IsBound => _bound; public static void Initialize(Harmony harmony) { _harmony = harmony; if (!SoftDeps.HasFacilityMeltdown) { Plugin.Log.LogWarning((object)"FacilityMeltdown not detected. Auto-block on meltdown is disabled. Manual/terminal blocking and routing patches still work."); return; } string value = ModConfig.MeltdownPatchTarget.Value; if (string.IsNullOrWhiteSpace(value) || !TryPatchByTarget(value)) { int num = 0; if (ModConfig.BlockOnMeltdownStart.Value && TryPatchMethod("FacilityMeltdown.MeltdownSequence.Behaviours.MeltdownHandler", "StartMeltdownClientRpc", "meltdown start")) { num++; } if (TryPatchMethod("FacilityMeltdown.MeltdownSequence.Behaviours.FacilityExplosionHandler", "Awake", "real detonation")) { num++; } if (num == 0 && TrySubscribeApi()) { num++; } if (num == 0 && TryPatchByScan()) { num++; } if (num == 0) { Plugin.Log.LogError((object)"Could not locate any FacilityMeltdown detonation hook. Set 'Advanced/MeltdownPatchTarget' in the config to 'Namespace.Type:Method'. Routing block, persistence and the 'destroymoon' command still work."); } } } private static bool TryPatchMethod(string typeName, string methodName, string label) { try { Type type = AccessTools.TypeByName(typeName); if (type == null) { return false; } MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { return false; } _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, PostfixMethod(), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _bound = true; Plugin.Log.LogInfo((object)("Meltdown hook bound to " + typeName + ":" + methodName + " (" + label + ").")); return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Hook " + typeName + ":" + methodName + " failed: " + ex.Message)); return false; } } private static bool TryPatchByTarget(string target) { try { string[] array = target.Split(':'); if (array.Length != 2) { Plugin.Log.LogError((object)("Invalid MeltdownPatchTarget '" + target + "'.")); return false; } Type type = AccessTools.TypeByName(array[0]); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, array[1], (Type[])null, (Type[])null) : null); if (methodInfo == null) { Plugin.Log.LogError((object)("MeltdownPatchTarget '" + target + "' not found.")); return false; } _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, PostfixMethod(), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _bound = true; Plugin.Log.LogInfo((object)("Meltdown hook bound to override target " + type.FullName + ":" + methodInfo.Name + ".")); return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to patch override '{target}': {arg}"); return false; } } private static bool TrySubscribeApi() { try { Type type = AccessTools.TypeByName("FacilityMeltdown.API.MeltdownAPI"); if (type == null) { return false; } Action action = OnDetonationEvent; MethodInfo methodInfo = AccessTools.Method(type, "RegisterMeltdownListener", (Type[])null, (Type[])null); if (methodInfo != null) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(typeof(Action))) { methodInfo.Invoke(null, new object[1] { action }); _bound = true; Plugin.Log.LogInfo((object)"Meltdown hook subscribed via FacilityMeltdown.API.MeltdownAPI.RegisterMeltdownListener."); return true; } } FieldInfo field = type.GetField("OnMeltdownStart", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && typeof(Delegate).IsAssignableFrom(field.FieldType) && field.IsStatic) { Delegate a = field.GetValue(null) as Delegate; field.SetValue(null, Delegate.Combine(a, action)); _bound = true; Plugin.Log.LogInfo((object)"Meltdown hook attached to FacilityMeltdown.API.MeltdownAPI.OnMeltdownStart field."); return true; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("MeltdownAPI hook failed: " + ex.Message)); } return false; } private static bool TryPatchByScan() { List<(int, MethodInfo)> list = new List<(int, MethodInfo)>(); foreach (Type item in MeltdownTypes()) { MethodInfo[] methods; try { methods = item.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } MethodInfo[] array = methods; foreach (MethodInfo m in array) { int num = Array.FindIndex(MethodNameCandidates, (string n) => string.Equals(n, m.Name, StringComparison.OrdinalIgnoreCase)); if (num < 0) { continue; } try { if (m.IsAbstract || m.IsGenericMethodDefinition || m.GetParameters().Length != 0) { continue; } goto IL_009d; } catch { } continue; IL_009d: list.Add((MethodNameCandidates.Length - num, m)); } } foreach (var item2 in list.OrderByDescending<(int, MethodInfo), int>(((int score, MethodInfo m) s) => s.score)) { try { _harmony.Patch((MethodBase)item2.Item2, (HarmonyMethod)null, PostfixMethod(), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _bound = true; Plugin.Log.LogInfo((object)("Meltdown hook bound to method " + item2.Item2.DeclaringType?.FullName + ":" + item2.Item2.Name + " (auto-scan).")); return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to patch " + item2.Item2.Name + ": " + ex.Message)); } } return false; } private static HarmonyMethod PostfixMethod() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown return new HarmonyMethod(typeof(MeltdownDetector).GetMethod("DetonationPostfix", BindingFlags.Static | BindingFlags.NonPublic)); } private static IEnumerable<Type> MeltdownTypes() { Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name.IndexOf("FacilityMeltdown", StringComparison.OrdinalIgnoreCase) >= 0); if (assembly == null) { yield break; } Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } catch { yield break; } Type[] array2 = array; for (int num = 0; num < array2.Length; num++) { yield return array2[num]; } } private static void DetonationPostfix() { HandleDetonation(); } private static void OnDetonationEvent() { HandleDetonation(); } private static void HandleDetonation() { if (!ModConfig.Enabled.Value) { return; } string currentMoonKey = MoonResolver.GetCurrentMoonKey(); bool isHost = NetworkUtils.IsHost; Plugin.Log.LogInfo((object)string.Format("Meltdown signal received. moon='{0}', IsHost={1}.", currentMoonKey ?? "?", isHost)); if (!isHost) { return; } if (string.IsNullOrEmpty(currentMoonKey)) { Plugin.Log.LogWarning((object)"Meltdown signal but current moon unresolved."); } else if (!(currentMoonKey == _lastKey) || !((DateTime.UtcNow - _lastTime).TotalSeconds < 10.0)) { _lastKey = currentMoonKey; _lastTime = DateTime.UtcNow; if (DestroyedMoonsService.Whitelist().Contains(currentMoonKey)) { Plugin.Log.LogInfo((object)("Moon '" + currentMoonKey + "' had a meltdown but is whitelisted — not blocking.")); return; } BlockedMoonsStore.EnsureLoaded(); Plugin.Log.LogInfo((object)("FacilityMeltdown on '" + currentMoonKey + "'. Blocking this moon permanently.")); DestroyedMoonsService.BlockMoon(currentMoonKey); } } } } namespace DestroyedMoons.Integrations { internal static class MeltdownChanceIntegration { public static bool Present { get; private set; } public static void Initialize() { Present = SoftDeps.HasMeltdownChance; if (Present) { Plugin.Log.LogInfo((object)"Meltdown_Chance detected. Blocking will only occur on real detonations."); } else if (ModConfig.Verbose) { Plugin.Log.LogInfo((object)"Meltdown_Chance not present."); } } } internal static class RandomMoonFxIntegration { private const string UTILS_TYPE = "RandomMoonFX.Utils"; private const string BLACKLIST_METHOD = "IsMoonBlacklisted"; private const string VALID_METHOD = "IsMoonValid"; private static bool _initialized; private static readonly Stopwatch _callClock = Stopwatch.StartNew(); private static long _lastCallMs = -1000L; private static int _burst; private const int BurstLimit = 2000; public static void Initialize(Harmony harmony) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected O, but got Unknown if (_initialized) { return; } _initialized = true; if (!SoftDeps.HasRandomMoonFX) { if (ModConfig.Verbose) { Plugin.Log.LogInfo((object)"RandomMoonFX not present — integration skipped."); } return; } if (!ModConfig.FilterRandomMoonFXPool.Value) { Plugin.Log.LogInfo((object)"RandomMoonFX pool filtering disabled by config. Destroyed moons are still blocked by the terminal/level-change guard."); return; } Type type = AccessTools.TypeByName("RandomMoonFX.Utils"); if (type == null) { Plugin.Log.LogWarning((object)"RandomMoonFX present but 'RandomMoonFX.Utils' not found. Destroyed moons still blocked by routing patches, but not filtered from the random pool."); return; } bool flag = false; MethodInfo methodInfo = AccessTools.Method(type, "IsMoonBlacklisted", (Type[])null, (Type[])null); if (methodInfo != null) { try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(RandomMoonFxIntegration).GetMethod("IsMoonBlacklistedPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); flag = true; Plugin.Log.LogInfo((object)"RandomMoonFX filtered via postfix on RandomMoonFX.Utils.IsMoonBlacklisted."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to patch IsMoonBlacklisted: " + ex.Message)); } } MethodInfo methodInfo2 = AccessTools.Method(type, "IsMoonValid", (Type[])null, (Type[])null); if (methodInfo2 != null) { try { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(RandomMoonFxIntegration).GetMethod("IsMoonValidPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); flag = true; if (ModConfig.Verbose) { Plugin.Log.LogInfo((object)"RandomMoonFX also filtered via RandomMoonFX.Utils.IsMoonValid."); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Failed to patch IsMoonValid: " + ex2.Message)); } } if (!flag) { Plugin.Log.LogWarning((object)"RandomMoonFX present but no filter method could be patched. Routing patches remain the backstop."); } Type type2 = AccessTools.TypeByName("RandomMoonFX.Plugin"); if (!(type2 != null)) { return; } string[] array = new string[2] { "RouteRandomPlanet", "StartRandomPlanet" }; foreach (string text in array) { MethodInfo methodInfo3 = AccessTools.Method(type2, text, (Type[])null, (Type[])null); if (!(methodInfo3 == null)) { try { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RandomMoonFxIntegration).GetMethod("RoutingFinalizer", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null); } catch (Exception ex3) { Plugin.Log.LogWarning((object)("Failed to add finalizer to " + text + ": " + ex3.Message)); } } } } private static Exception RoutingFinalizer(Exception __exception) { if (__exception != null) { Plugin.Log.LogWarning((object)("Suppressed a RandomMoonFX routing exception to avoid a crash: " + __exception.Message)); DestroyedMoonsService.BeginRoutingBypass(3000); } return null; } public static void SyncBlacklist(IEnumerable<string> blockedKeys) { } private static void IsMoonBlacklistedPostfix(object[] __args, ref bool __result) { try { if (!RerollStormGuard() && !__result && ArgIsBlockedLevel(__args)) { __result = true; } } catch { } } private static void IsMoonValidPostfix(object[] __args, ref bool __result) { try { if (!RerollStormGuard() && __result && ArgIsBlockedLevel(__args)) { __result = false; } } catch { } } private static bool RerollStormGuard() { long elapsedMilliseconds = _callClock.ElapsedMilliseconds; if (elapsedMilliseconds - _lastCallMs <= 3) { _burst++; } else { _burst = 0; } _lastCallMs = elapsedMilliseconds; if (_burst > 2000) { _burst = 0; DestroyedMoonsService.BeginRoutingBypass(3000); Plugin.Log.LogWarning((object)"RandomMoonFX reroll-storm detected — temporarily allowing routing to prevent a hang."); return true; } return false; } private static bool ArgIsBlockedLevel(object[] args) { if (args == null) { return false; } foreach (object obj in args) { SelectableLevel val = (SelectableLevel)((obj is SelectableLevel) ? obj : null); if (val != null) { return DestroyedMoonsService.IsBlockedForRouting(MoonKey.FromSelectableLevel(val)); } if (obj is string raw) { return DestroyedMoonsService.IsBlockedForRouting(MoonKey.Normalize(raw)); } } return false; } } } namespace DestroyedMoons.Core { internal static class BlockedMoonsStore { private const string ES3_KEY = "DestroyedMoons_BlockedList"; private static readonly HashSet<string> _blocked = new HashSet<string>(); private static string _loadedSaveFile; public static int Generation { get; private set; } private static string CurrentSaveFile { get { GameNetworkManager instance = GameNetworkManager.Instance; if (!((Object)(object)instance != (Object)null) || string.IsNullOrEmpty(instance.currentSaveFileName)) { return "LCSaveFile1"; } return instance.currentSaveFileName; } } private static string JsonMirrorPath => Path.Combine(Paths.ConfigPath, "DestroyedMoons", "blocked_" + CurrentSaveFile + ".json"); public static IReadOnlyCollection<string> AutoBlocked => _blocked; public static void EnsureLoaded() { if (_loadedSaveFile == null || !string.Equals(_loadedSaveFile, CurrentSaveFile, StringComparison.Ordinal)) { Load(); } } public static void Load() { _blocked.Clear(); _loadedSaveFile = CurrentSaveFile; if (ModConfig.ResetOnNextLoad.Value) { Plugin.Log.LogWarning((object)("ResetOnNextLoad was set — clearing destroyed-moon list for '" + _loadedSaveFile + "'.")); DeleteJsonMirror(); Save(); ModConfig.ResetOnNextLoad.Value = false; Generation++; return; } try { string text = null; if (Es3Interop.Available) { if (Es3Interop.KeyExists("DestroyedMoons_BlockedList", _loadedSaveFile)) { text = Es3Interop.LoadString("DestroyedMoons_BlockedList", _loadedSaveFile, null); } else { DeleteJsonMirror(); } } else { text = ReadJsonMirror(); } if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new char[2] { '\n', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = MoonKey.Normalize(array[i]); if (!string.IsNullOrEmpty(text2)) { _blocked.Add(text2); } } } } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to load destroyed-moon list: {arg}"); } Generation++; Plugin.Log.LogInfo((object)($"Loaded {_blocked.Count} destroyed moon(s) for save '{_loadedSaveFile}': " + "[" + string.Join(", ", _blocked) + "]")); } public static void Save() { string text = string.Join("\n", _blocked); try { Es3Interop.SaveString("DestroyedMoons_BlockedList", text, CurrentSaveFile); } catch (Exception ex) { Plugin.Log.LogWarning((object)("ES3 save failed: " + ex.Message)); } WriteJsonMirror(text); } public static bool Add(string key) { if (string.IsNullOrEmpty(key)) { return false; } if (_blocked.Add(key)) { Generation++; Save(); return true; } return false; } public static bool Remove(string key) { if (string.IsNullOrEmpty(key)) { return false; } if (_blocked.Remove(key)) { Generation++; Save(); return true; } return false; } public static void Clear() { _blocked.Clear(); Generation++; Save(); } public static void ReplaceFromSync(IEnumerable<string> keys) { _blocked.Clear(); foreach (string key in keys) { string text = MoonKey.Normalize(key); if (!string.IsNullOrEmpty(text)) { _blocked.Add(text); } } Generation++; } public static string SerializeForSync() { return string.Join("\n", _blocked); } public static void OnSaveDeleted(string saveFilePath) { if (!string.IsNullOrEmpty(saveFilePath)) { string fileName = Path.GetFileName(saveFilePath); DeleteJsonMirrorFor(fileName); if (string.Equals(fileName, _loadedSaveFile, StringComparison.Ordinal)) { _blocked.Clear(); _loadedSaveFile = null; Generation++; } Plugin.Log.LogInfo((object)("Save '" + fileName + "' deleted — cleared DestroyedMoons data for that slot.")); } } private static void DeleteJsonMirror() { DeleteJsonMirrorFor(CurrentSaveFile); } private static void DeleteJsonMirrorFor(string saveFileName) { try { string path = Path.Combine(Paths.ConfigPath, "DestroyedMoons", "blocked_" + saveFileName + ".json"); if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("JSON mirror delete failed: " + ex.Message)); } } private static void WriteJsonMirror(string joined) { try { string directoryName = Path.GetDirectoryName(JsonMirrorPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } IEnumerable<string> values = from s in joined.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries) select "\"" + s.Replace("\"", "\\\"") + "\""; File.WriteAllText(JsonMirrorPath, "{\n \"saveFile\": \"" + CurrentSaveFile + "\",\n \"blockedMoons\": [" + string.Join(", ", values) + "]\n}\n"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("JSON mirror write failed: " + ex.Message)); } } private static string ReadJsonMirror() { try { if (!File.Exists(JsonMirrorPath)) { return null; } string text = File.ReadAllText(JsonMirrorPath); int num = text.IndexOf('['); int num2 = text.IndexOf(']'); if (num < 0 || num2 < 0 || num2 <= num) { return null; } string text2 = text.Substring(num + 1, num2 - num - 1); List<string> list = new List<string>(); string[] array = text2.Split(','); for (int i = 0; i < array.Length; i++) { string text3 = array[i].Trim().Trim('"', ' '); if (!string.IsNullOrEmpty(text3)) { list.Add(text3); } } return string.Join("\n", list); } catch { return null; } } } internal static class DestroyedMoonsService { private static HashSet<string> _wlCache; private static string _wlRaw; private static HashSet<string> _blCache; private static string _blRaw; private static bool _arbCache; private static int _arbGen = -1; private static string _arbWl; private static string _arbBl; private static int _arbLevelCount = -1; private static readonly Stopwatch _clock = Stopwatch.StartNew(); private static long _bypassUntilMs = -1L; private static bool RoutingBypassActive => _clock.ElapsedMilliseconds < _bypassUntilMs; private static HashSet<string> ParseList(string csv) { HashSet<string> hashSet = new HashSet<string>(); if (string.IsNullOrWhiteSpace(csv)) { return hashSet; } string[] array = csv.Split(','); for (int i = 0; i < array.Length; i++) { string text = MoonKey.Normalize(array[i]); if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); } } return hashSet; } public static HashSet<string> Whitelist() { string text = ModConfig.Whitelist.Value ?? string.Empty; if (_wlCache == null || !string.Equals(text, _wlRaw, StringComparison.Ordinal)) { _wlRaw = text; _wlCache = ParseList(text); } return _wlCache; } public static HashSet<string> ManualBlacklist() { string text = ModConfig.ManualBlacklist.Value ?? string.Empty; if (_blCache == null || !string.Equals(text, _blRaw, StringComparison.Ordinal)) { _blRaw = text; _blCache = ParseList(text); } return _blCache; } public static HashSet<string> EffectiveBlocked() { HashSet<string> hashSet = new HashSet<string>(BlockedMoonsStore.AutoBlocked); hashSet.UnionWith(ManualBlacklist()); hashSet.ExceptWith(Whitelist()); return hashSet; } public static bool IsBlocked(string key) { if (string.IsNullOrEmpty(key)) { return false; } if (Whitelist().Contains(key)) { return false; } if (!BlockedMoonsStore.AutoBlocked.Contains(key)) { return ManualBlacklist().Contains(key); } return true; } public static bool IsBlocked(SelectableLevel level) { return IsBlocked(MoonKey.FromSelectableLevel(level)); } public static bool AllRoutableBlocked() { StartOfRound instance = StartOfRound.Instance; int num = (((Object)(object)instance != (Object)null && instance.levels != null) ? instance.levels.Length : 0); string text = ModConfig.Whitelist.Value ?? string.Empty; string text2 = ModConfig.ManualBlacklist.Value ?? string.Empty; if (_arbGen == BlockedMoonsStore.Generation && _arbLevelCount == num && string.Equals(text, _arbWl, StringComparison.Ordinal) && string.Equals(text2, _arbBl, StringComparison.Ordinal)) { return _arbCache; } int num2 = 0; int num3 = 0; foreach (SelectableLevel item in MoonResolver.AllLevels()) { if (!MoonResolver.IsCompanyMoon(item)) { num2++; if (!IsBlocked(MoonKey.FromSelectableLevel(item))) { num3++; } } } _arbCache = num2 > 0 && num3 == 0; _arbGen = BlockedMoonsStore.Generation; _arbLevelCount = num; _arbWl = text; _arbBl = text2; return _arbCache; } public static void BeginRoutingBypass(int milliseconds) { _bypassUntilMs = _clock.ElapsedMilliseconds + milliseconds; } public static bool IsBlockedForRouting(string key) { if (RoutingBypassActive) { return false; } if (IsBlocked(key)) { return !AllRoutableBlocked(); } return false; } public static bool IsBlockedForRouting(SelectableLevel level) { return IsBlockedForRouting(MoonKey.FromSelectableLevel(level)); } public static void BlockMoon(string key) { if (string.IsNullOrEmpty(key)) { return; } if (!NetworkUtils.IsHost) { if (ModConfig.Verbose) { Plugin.Log.LogInfo((object)("Ignoring BlockMoon('" + key + "') — not host.")); } return; } BlockedMoonsStore.EnsureLoaded(); bool num = BlockedMoonsStore.Add(key); if (num) { Plugin.Log.LogInfo((object)("Moon '" + key + "' permanently blocked for this save (nuclear meltdown).")); } RandomMoonFxIntegration.SyncBlacklist(EffectiveBlocked()); BlockedMoonsNetwork.BroadcastFullListToAll(); if (num) { MaybeResetIfExhausted(); } } public static void UnblockMoon(string key) { if (!string.IsNullOrEmpty(key) && NetworkUtils.IsHost) { BlockedMoonsStore.EnsureLoaded(); if (BlockedMoonsStore.Remove(key)) { Plugin.Log.LogInfo((object)("Moon '" + key + "' restored (buyback).")); } RandomMoonFxIntegration.SyncBlacklist(EffectiveBlocked()); BlockedMoonsNetwork.BroadcastFullListToAll(); } } private static void MaybeResetIfExhausted() { if (ModConfig.AutoResetWhenAllBlocked.Value && AllRoutableBlocked()) { Plugin.Log.LogInfo((object)"All routable moon(s) destroyed — auto-resetting the destroyed-moon list."); HUDNotify.Show("Every moon has been destroyed. The list resets — moons are available again."); ResetAll(); } } public static void ResetAll() { BlockedMoonsStore.Clear(); RandomMoonFxIntegration.SyncBlacklist(EffectiveBlocked()); BlockedMoonsNetwork.BroadcastFullListToAll(); Plugin.Log.LogInfo((object)"Destroyed-moon list cleared."); } private static Dictionary<string, string> NiceNameMap() { Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (SelectableLevel item in MoonResolver.AllLevels()) { string text = MoonKey.FromSelectableLevel(item); if (!string.IsNullOrEmpty(text) && !dictionary.ContainsKey(text)) { dictionary[text] = item.PlanetName; } } return dictionary; } public static List<string> DescribeBlocked() { Dictionary<string, string> map = NiceNameMap(); string value; return (from k in EffectiveBlocked() select (!map.TryGetValue(k, out value)) ? k : value into x orderby x select x).ToList(); } public static List<string> DescribeAutoBlocked() { Dictionary<string, string> map = NiceNameMap(); HashSet<string> wl = Whitelist(); string value; return (from k in BlockedMoonsStore.AutoBlocked where !wl.Contains(k) select (!map.TryGetValue(k, out value)) ? k : value into x orderby x select x).ToList(); } public static string ResolveAutoBlockedKey(string typedName) { string text = MoonKey.Normalize(typedName); if (string.IsNullOrEmpty(text)) { return null; } foreach (string item in BlockedMoonsStore.AutoBlocked) { if (item == text) { return item; } } foreach (string item2 in BlockedMoonsStore.AutoBlocked) { if (item2.Contains(text) || text.Contains(item2)) { return item2; } } return null; } } internal static class Es3Interop { private static Type _es3; private static MethodInfo _keyExists; private static MethodInfo _saveObj; private static MethodInfo _loadDefault; private static bool _resolved; public static bool Available => Resolve(); private static bool Resolve() { if (_resolved) { return _es3 != null; } _resolved = true; try { _es3 = AccessTypeByName("ES3"); if (_es3 == null) { return false; } _keyExists = _es3.GetMethod("KeyExists", new Type[2] { typeof(string), typeof(string) }); _saveObj = _es3.GetMethod("Save", new Type[3] { typeof(string), typeof(object), typeof(string) }); _loadDefault = _es3.GetMethod("Load", new Type[3] { typeof(string), typeof(string), typeof(object) }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("ES3 reflection failed: " + ex.Message)); } if (_es3 != null && _keyExists != null && _saveObj != null) { return _loadDefault != null; } return false; } private static Type AccessTypeByName(string name) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(name, throwOnError: false); if (type != null) { return type; } } return null; } public static bool KeyExists(string key, string saveFile) { if (!Resolve()) { return false; } try { return (bool)_keyExists.Invoke(null, new object[2] { key, saveFile }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("ES3.KeyExists failed: " + ex.Message)); return false; } } public static void SaveString(string key, string value, string saveFile) { if (!Resolve()) { return; } try { _saveObj.Invoke(null, new object[3] { key, value, saveFile }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("ES3.Save failed: " + ex.Message)); } } public static string LoadString(string key, string saveFile, string fallback) { if (!Resolve()) { return fallback; } try { return (string)_loadDefault.Invoke(null, new object[3] { key, saveFile, fallback }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("ES3.Load failed: " + ex.Message)); return fallback; } } } internal static class HUDNotify { public static void Show(string body, string header = "Meltdown Aftermath") { HUDManager instance = HUDManager.Instance; if ((Object)(object)instance != (Object)null) { try { instance.DisplayTip(header, body, true, false, "LC_Tip1"); } catch { } } } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class StartOfRoundStartPatch { [HarmonyPostfix] private static void Postfix() { bool isHost = NetworkUtils.IsHost; Plugin.Log.LogInfo((object)$"StartOfRound.Start — IsHost={isHost}. Wiring DestroyedMoons."); BlockedMoonsNetwork.Register(); if (isHost) { BlockedMoonsStore.Load(); RandomMoonFxIntegration.SyncBlacklist(DestroyedMoonsService.EffectiveBlocked()); BlockedMoonsNetwork.BroadcastFullListToAll(); } else { BlockedMoonsNetwork.RequestFullListFromHost(); } } } [HarmonyPatch(typeof(StartOfRound), "SetTimeAndPlanetToSavedSettings")] internal static class SetTimeAndPlanetToSavedSettingsPatch { [HarmonyPostfix] private static void Postfix() { if (NetworkUtils.IsHost) { BlockedMoonsStore.Load(); RandomMoonFxIntegration.SyncBlacklist(DestroyedMoonsService.EffectiveBlocked()); BlockedMoonsNetwork.BroadcastFullListToAll(); } } } internal static class MoonKey { public static string FromSelectableLevel(SelectableLevel level) { if ((Object)(object)level == (Object)null) { return null; } return Normalize(level.PlanetName); } public static string NumberlessDisplay(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return raw; } raw = raw.Trim(); int i; for (i = 0; i < raw.Length && char.IsDigit(raw[i]); i++) { } if (i > 0 && i < raw.Length && (raw[i] == ' ' || raw[i] == '-' || raw[i] == '.')) { return raw.Substring(i + 1).Trim(); } return raw; } public static string Normalize(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return null; } raw = raw.Trim(); int i; for (i = 0; i < raw.Length && char.IsDigit(raw[i]); i++) { } if (i > 0 && i < raw.Length && (raw[i] == ' ' || raw[i] == '-' || raw[i] == '.')) { raw = raw.Substring(i + 1).Trim(); } StringBuilder stringBuilder = new StringBuilder(raw.Length); string text = raw; foreach (char c in text) { if (!char.IsWhiteSpace(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return null; } } internal static class MoonResolver { public static string GetCurrentMoonKey() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null) { return null; } return MoonKey.FromSelectableLevel(instance.currentLevel); } public static SelectableLevel CurrentLevel() { if (!((Object)(object)StartOfRound.Instance != (Object)null)) { return null; } return StartOfRound.Instance.currentLevel; } public static SelectableLevel LevelById(int levelID) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.levels == null) { return null; } SelectableLevel[] levels = instance.levels; foreach (SelectableLevel val in levels) { if ((Object)(object)val != (Object)null && val.levelID == levelID) { return val; } } return null; } public static IEnumerable<SelectableLevel> AllLevels() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.levels == null) { yield break; } SelectableLevel[] levels = instance.levels; foreach (SelectableLevel val in levels) { if ((Object)(object)val != (Object)null) { yield return val; } } } public static string DisplayName(SelectableLevel level) { if (!((Object)(object)level != (Object)null) || string.IsNullOrEmpty(level.PlanetName)) { return "Unknown"; } return level.PlanetName; } public static bool IsCompanyMoon(SelectableLevel level) { if ((Object)(object)level == (Object)null) { return true; } string planetName = level.PlanetName; if (string.IsNullOrEmpty(planetName)) { return false; } planetName = planetName.ToLowerInvariant(); if (!planetName.Contains("gordion")) { return planetName.Contains("company"); } return true; } } internal static class NetworkUtils { public static bool IsHost { get { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null) { if (!singleton.IsHost) { return singleton.IsServer; } return true; } return false; } } public static bool IsClientConnected { get { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null) { return singleton.IsListening; } return false; } } } internal static class SaveDeletionPatch { public static void Initialize(Harmony harmony) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("ES3"); if (type == null) { Plugin.Log.LogWarning((object)"ES3 type not found — save-deletion cleanup disabled."); return; } MethodInfo methodInfo = AccessTools.Method(type, "DeleteFile", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"ES3.DeleteFile(string) not found — save-deletion cleanup disabled."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SaveDeletionPatch).GetMethod("OnDeleteFile", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"Save-deletion cleanup hooked (ES3.DeleteFile)."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to hook save deletion: " + ex.Message)); } } private static void OnDeleteFile(object[] __args) { try { if (__args != null && __args.Length != 0 && __args[0] is string saveFilePath) { BlockedMoonsStore.OnSaveDeleted(saveFilePath); } } catch { } } } internal static class SoftDeps { public static bool HasFacilityMeltdown => Has("me.loaforc.facilitymeltdown", "TeamXiaolan.FacilityMeltdown"); public static bool HasRandomMoonFX => Has("zigzag.randommoonfx"); public static bool HasMeltdownChance => Has("den.meltdownchance"); public static bool HasLethalLevelLoader => Has("imabatby.lethallevelloader"); private static bool Has(params string[] guids) { foreach (string key in Chainloader.PluginInfos.Keys) { foreach (string b in guids) { if (string.Equals(key, b, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } } } namespace DestroyedMoons.Config { internal static class ModConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<string> ManualBlacklist; public static ConfigEntry<string> Whitelist; public static ConfigEntry<bool> ResetOnNextLoad; public static ConfigEntry<string> BlockedMessage; public static ConfigEntry<bool> EnableTerminalCommands; public static ConfigEntry<bool> VerboseLogging; public static ConfigEntry<bool> StrikethroughBlockedMoons; public static ConfigEntry<bool> BlockOnMeltdownStart; public static ConfigEntry<bool> AutoResetWhenAllBlocked; public static ConfigEntry<bool> EnableMoonBuyback; public static ConfigEntry<int> MoonBuybackPrice; public static ConfigEntry<bool> FilterRandomMoonFXPool; public static ConfigEntry<string> MeltdownPatchTarget; public static bool Verbose { get { if (VerboseLogging != null) { return VerboseLogging.Value; } return false; } } public static void Init(ConfigFile cfg) { Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. If false the mod applies no patches at all."); BlockedMessage = cfg.Bind<string>("General", "BlockedMessage", "This moon was destroyed in a nuclear meltdown. Routing is impossible.", "Message shown when a player tries to travel to a destroyed moon."); ManualBlacklist = cfg.Bind<string>("Lists", "ManualBlacklist", "", "Moons that are ALWAYS blocked (comma-separated planet names, e.g. \"Titan, March\"). Numbers and case are ignored, so \"8 Titan\" and \"titan\" both match Titan."); Whitelist = cfg.Bind<string>("Lists", "Whitelist", "", "Moons that are NEVER blocked, even after a meltdown (comma-separated). Overrides everything else."); ResetOnNextLoad = cfg.Bind<bool>("Maintenance", "ResetBlockedListOnNextLoad", false, "If true, the stored destroyed-moon list for the loaded save is cleared on next load. This flag auto-resets itself back to false afterwards."); EnableTerminalCommands = cfg.Bind<bool>("Maintenance", "EnableTerminalCommands", true, "Adds terminal commands: 'moonstatus' (list blocked moons), 'destroymoon' (host: block current moon), 'restoremoons' (host: clear the list). Useful for testing the pipeline."); VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Extra logging for diagnosing detection and routing."); StrikethroughBlockedMoons = cfg.Bind<bool>("Visuals", "StrikethroughBlockedMoons", true, "Draw a line through destroyed moon names in the terminal (e.g. the >MOONS list) for clarity."); EnableMoonBuyback = cfg.Bind<bool>("Buyback", "EnableMoonBuyback", true, "Allow players to pay to restore a destroyed moon from the terminal. A 'MOON BUYBACK' section appears on the terminal HELP page once moons have been destroyed. Use the command: BUYBACK [moon name]."); MoonBuybackPrice = cfg.Bind<int>("Buyback", "MoonBuybackPrice", 500, "Credit cost to buy back (restore) a single destroyed moon."); FilterRandomMoonFXPool = cfg.Bind<bool>("Compatibility", "FilterRandomMoonFXPool", true, "Remove destroyed moons from RandomMoonFX's random lever pool. Has a built-in anti-hang guard, but if the lever ever freezes the game you can set this to false — routing to destroyed moons is still blocked by the terminal/level-change guard."); AutoResetWhenAllBlocked = cfg.Bind<bool>("General", "AutoResetWhenAllBlocked", true, "When every routable (non-Company) moon has been destroyed, automatically clear the destroyed-moon list so the moons become available again — prevents a dead-end where nowhere is reachable."); BlockOnMeltdownStart = cfg.Bind<bool>("General", "BlockOnMeltdownStart", true, "Block the moon the moment a meltdown STARTS (reliable, catches meltdowns even if you leave before the blast). A started FacilityMeltdown always ends in an explosion, so the moon is doomed either way. If false, only the final detonation triggers the block."); MeltdownPatchTarget = cfg.Bind<string>("Advanced", "MeltdownPatchTarget", "", "Optional override for the FacilityMeltdown explosion hook. Leave blank for auto-detection. Format 'Namespace.Type:Method'. Check the BepInEx log to see what auto-detection found."); } } }