using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace SteveFixesMod; [BepInPlugin("boogie.stevefixes", "Steve Fixes", "1.2.8")] public sealed class SteveFixes : BaseUnityPlugin { private sealed class MacronState { public bool ShovelAlreadySpawned; public bool HitPointsInitialized; public int DanceIndex = -1; } private sealed class LevelBaseline { public int LevelInstanceId; public string StableKey; public string PlanetName; public readonly List Entries = new List(); } private sealed class EnemySnapshot { public EnemyType EnemyType; public string EnemyName; public int Rarity; } public const string PluginGuid = "boogie.stevefixes"; public const string PluginName = "Steve Fixes"; public const string PluginVersion = "1.2.8"; private const float PollInterval = 0.2f; private const float FirstBaselineDelay = 0.75f; private const float RepairPass1Delay = 0.15f; private const float RepairPass2Delay = 1f; private const float RepairPass3Delay = 3f; private const float CurrentLevelSanityInterval = 1f; private ConfigEntry enemyPoolLifecycleFixEnabled; private ConfigEntry verboseLogging; private ConfigEntry lllBundleStatusLifecycleFixEnabled; private ConfigEntry lllVerboseLogging; private ConfigEntry macronCompatibilityFixEnabled; private ConfigEntry macronHitsToKill; private float nextPoll; private float nextCurrentSanity; private int lastSorInstanceId = int.MinValue; private int lastCurrentLevelInstanceId = int.MinValue; private int lobbyGeneration; private bool baselineCaptured; private float baselineCaptureAt = -1f; private int repairStage; private float nextRepairAt = -1f; private static ManualLogSource SharedLog; private static Type lllNetworkBundleManagerType; private static MethodInfo lllRefreshLoadStatusMethod; private static MemberInfo lllInstanceMember; private static bool lllPatchInstalled; private static bool lllVerbose; private static float lastLllRedirectAt = -100f; private Harmony harmony; private static Type macronAiType; private static MethodInfo macronSpawnShovelMethod; private static MethodInfo macronHitEnemyMethod; private static MethodInfo macronPlayDanceMusicMethod; private static MethodInfo macronPlayMusicMethod; private static MethodInfo macronNetworkObjectIdGetter; private static FieldInfo macronEnemyHpField; private static int macronConfiguredHitsToKill = 8; private static bool macronPatchInstalled; private static bool macronDancePatchInstalled; private static bool macronDancePlaylistReady; private static int macronDanceClipCount; private static float nextMacronPatchAttempt; private static readonly AudioClip[] MacronDanceClips = (AudioClip[])(object)new AudioClip[10]; private static readonly ConditionalWeakTable MacronStates = new ConditionalWeakTable(); private Harmony macronHarmony; private readonly Dictionary baselineByInstanceId = new Dictionary(); private readonly Dictionary baselineByStableKey = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet LethalLibBlock = new HashSet(StringComparer.OrdinalIgnoreCase) { "Locker", "ImmortalSnail", "Macron", "Light Eater", "Doctor's Brain", "MysteryButton", "SCP 106", "SCP 939", "scp1507", "scp1507Alpha", "Shy guy", "CountryRoadCreature", "Cabinet", "Scary Lamp", "Thing" }; private void Awake() { enemyPoolLifecycleFixEnabled = ((BaseUnityPlugin)this).Config.Bind("EnemyPoolLifecycleFix", "Enabled", true, "Restore only missing LethalLib enemy entries after recreating a lobby in the same game process."); verboseLogging = ((BaseUnityPlugin)this).Config.Bind("EnemyPoolLifecycleFix", "VerboseLogging", false, "Log individual repaired moon names in addition to summary lines."); lllBundleStatusLifecycleFixEnabled = ((BaseUnityPlugin)this).Config.Bind("LLLBundleStatusLifecycleFix", "Enabled", true, "Reroute stale LethalLevelLoader NetworkBundleManager refresh callbacks to the current spawned manager after lobby recreation."); lllVerboseLogging = ((BaseUnityPlugin)this).Config.Bind("LLLBundleStatusLifecycleFix", "VerboseLogging", false, "Log every LethalLevelLoader bundle-status refresh decision. Leave disabled for normal play."); macronCompatibilityFixEnabled = ((BaseUnityPlugin)this).Config.Bind("MacronCompatibilityFix", "Enabled", true, "Enable the pack-specific Macron Enemy v81 compatibility/runtime fixes."); macronHitsToKill = ((BaseUnityPlugin)this).Config.Bind("MacronCompatibilityFix", "HitsToKill", 8, "Base Macron HP in shovel-force units. 8 means eight force=1 hits; stronger damage upgrades naturally reduce the hit count."); macronConfiguredHitsToKill = Math.Max(1, Math.Min(100, macronHitsToKill.Value)); SharedLog = ((BaseUnityPlugin)this).Logger; lllVerbose = lllVerboseLogging.Value; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[SteveFixes] Loaded v1.2.8. EnemyPoolLifecycleFix=" + enemyPoolLifecycleFixEnabled.Value + " LLLBundleStatusLifecycleFix=" + lllBundleStatusLifecycleFixEnabled.Value + " MacronCompatibilityFix=" + macronCompatibilityFixEnabled.Value)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[SteveFixes] EnemyPoolLifecycleFix is the validated v1.0.2 implementation (15/15 restoration, idempotent)."); if (lllBundleStatusLifecycleFixEnabled.Value) { InstallLllBundleStatusPatch(); } nextMacronPatchAttempt = 0f; } private void Update() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (macronCompatibilityFixEnabled.Value && !macronPatchInstalled && realtimeSinceStartup >= nextMacronPatchAttempt) { nextMacronPatchAttempt = realtimeSinceStartup + 1f; TryInstallMacronRuntimePatches(); } if (!enemyPoolLifecycleFixEnabled.Value || realtimeSinceStartup < nextPoll) { return; } nextPoll = realtimeSinceStartup + 0.2f; StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return; } int num = SafeInstanceId((Object)(object)instance); if (num != lastSorInstanceId) { OnStartOfRoundChanged(instance, num, realtimeSinceStartup); } if (!baselineCaptured) { if (baselineCaptureAt > 0f && realtimeSinceStartup >= baselineCaptureAt) { CaptureBaseline(instance); } return; } if (lobbyGeneration >= 2 && repairStage > 0 && nextRepairAt > 0f && realtimeSinceStartup >= nextRepairAt) { RepairAllLevels(instance, "new-lobby-pass-" + repairStage); if (repairStage == 1) { repairStage = 2; nextRepairAt = realtimeSinceStartup + 0.85f; } else if (repairStage == 2) { repairStage = 3; nextRepairAt = realtimeSinceStartup + 2f; } else { repairStage = 0; nextRepairAt = -1f; } } if (lobbyGeneration < 2 || !(realtimeSinceStartup >= nextCurrentSanity)) { return; } nextCurrentSanity = realtimeSinceStartup + 1f; SelectableLevel currentLevel = instance.currentLevel; int num2 = SafeInstanceId((Object)(object)currentLevel); if (num2 != lastCurrentLevelInstanceId) { lastCurrentLevelInstanceId = num2; if ((Object)(object)currentLevel != (Object)null) { RepairLevel(currentLevel, "current-level-change", logWhenRepaired: true); } } else if ((Object)(object)currentLevel != (Object)null) { RepairLevel(currentLevel, "current-level-sanity", logWhenRepaired: false); } } private void OnStartOfRoundChanged(StartOfRound sor, int sorId, float now) { int num = lastSorInstanceId; lastSorInstanceId = sorId; lastCurrentLevelInstanceId = int.MinValue; lobbyGeneration++; ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes] StartOfRound changed " + num + " -> " + sorId + "; lobbyGeneration=" + lobbyGeneration)); if (!baselineCaptured) { baselineCaptureAt = now + 0.75f; repairStage = 0; nextRepairAt = -1f; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[SteveFixes] First lobby detected. Baseline capture scheduled in " + 0.75f.ToString("0.00") + "s.")); } else { repairStage = 1; nextRepairAt = now + 0.15f; nextCurrentSanity = now + 0.5f; ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes] Later lobby detected. Enemy pool repair armed."); } } private void CaptureBaseline(StartOfRound sor) { baselineCaptureAt = -1f; if ((Object)(object)sor == (Object)null || sor.levels == null || sor.levels.Length == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes] Baseline capture delayed: StartOfRound.levels unavailable."); baselineCaptureAt = Time.realtimeSinceStartup + 1f; return; } baselineByInstanceId.Clear(); baselineByStableKey.Clear(); int num = 0; int num2 = 0; int num3 = 0; for (int i = 0; i < sor.levels.Length; i++) { SelectableLevel val = sor.levels[i]; if ((Object)(object)val == (Object)null) { continue; } num++; LevelBaseline levelBaseline = BuildLevelBaseline(val); if (levelBaseline != null && levelBaseline.Entries.Count != 0) { num2++; num3 += levelBaseline.Entries.Count; int num4 = SafeInstanceId((Object)(object)val); if (num4 != 0) { baselineByInstanceId[num4] = levelBaseline; } string text = StableLevelKey(val); if (!string.IsNullOrEmpty(text) && !baselineByStableKey.ContainsKey(text)) { baselineByStableKey.Add(text, levelBaseline); } } } baselineCaptured = num3 > 0; if (!baselineCaptured) { ((BaseUnityPlugin)this).Logger.LogError((object)"[SteveFixes] Baseline capture found ZERO tracked LethalLib entries. Fix will stay disarmed and retry in 1s."); baselineCaptureAt = Time.realtimeSinceStartup + 1f; return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[SteveFixes] BASELINE_CAPTURED levelsSeen=" + num + " levelsWithLL=" + num2 + " entries=" + num3 + " trackedNames=" + LethalLibBlock.Count)); } private LevelBaseline BuildLevelBaseline(SelectableLevel level) { if ((Object)(object)level == (Object)null || level.Enemies == null) { return null; } LevelBaseline levelBaseline = new LevelBaseline(); levelBaseline.LevelInstanceId = SafeInstanceId((Object)(object)level); levelBaseline.StableKey = StableLevelKey(level); levelBaseline.PlanetName = Safe(level.PlanetName); for (int i = 0; i < level.Enemies.Count; i++) { SpawnableEnemyWithRarity val = level.Enemies[i]; if (val != null && !((Object)(object)val.enemyType == (Object)null)) { string text = Safe(val.enemyType.enemyName); if (LethalLibBlock.Contains(text)) { EnemySnapshot enemySnapshot = new EnemySnapshot(); enemySnapshot.EnemyType = val.enemyType; enemySnapshot.EnemyName = text; enemySnapshot.Rarity = val.rarity; levelBaseline.Entries.Add(enemySnapshot); } } } return levelBaseline; } private void RepairAllLevels(StartOfRound sor, string reason) { if ((Object)(object)sor == (Object)null || sor.levels == null) { return; } int num = 0; int num2 = 0; int num3 = 0; for (int i = 0; i < sor.levels.Length; i++) { SelectableLevel val = sor.levels[i]; if ((Object)(object)val == (Object)null) { continue; } LevelBaseline levelBaseline = FindBaseline(val); if (levelBaseline == null || levelBaseline.Entries.Count == 0) { continue; } num++; int num4 = RepairLevelWithBaseline(val, levelBaseline); if (num4 > 0) { num2++; num3 += num4; if (verboseLogging.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes] REPAIRED planet='" + Safe(val.PlanetName) + "' added=" + num4 + " reason=" + reason)); } } } if (num3 > 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes] REPAIR_SUMMARY reason=" + reason + " checked=" + num + " repairedLevels=" + num2 + " entriesAdded=" + num3)); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[SteveFixes] REPAIR_SUMMARY reason=" + reason + " checked=" + num + " repairedLevels=0 entriesAdded=0")); } } private int RepairLevel(SelectableLevel level, string reason, bool logWhenRepaired) { LevelBaseline levelBaseline = FindBaseline(level); if (levelBaseline == null || levelBaseline.Entries.Count == 0) { return 0; } int num = RepairLevelWithBaseline(level, levelBaseline); if (num > 0 && (logWhenRepaired || verboseLogging.Value)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes] REPAIRED_CURRENT planet='" + Safe(level.PlanetName) + "' added=" + num + " reason=" + reason)); } return num; } private int RepairLevelWithBaseline(SelectableLevel level, LevelBaseline baseline) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown if ((Object)(object)level == (Object)null || baseline == null || level.Enemies == null) { return 0; } int num = 0; for (int i = 0; i < baseline.Entries.Count; i++) { EnemySnapshot enemySnapshot = baseline.Entries[i]; if (enemySnapshot != null && !((Object)(object)enemySnapshot.EnemyType == (Object)null) && !ContainsEnemy(level.Enemies, enemySnapshot)) { SpawnableEnemyWithRarity item = new SpawnableEnemyWithRarity(enemySnapshot.EnemyType, enemySnapshot.Rarity); level.Enemies.Add(item); num++; } } return num; } private bool ContainsEnemy(List list, EnemySnapshot expected) { if (list == null || expected == null) { return false; } for (int i = 0; i < list.Count; i++) { SpawnableEnemyWithRarity val = list[i]; if (val != null && !((Object)(object)val.enemyType == (Object)null)) { if (object.ReferenceEquals(val.enemyType, expected.EnemyType)) { return true; } string a = Safe(val.enemyType.enemyName); if (!string.IsNullOrEmpty(expected.EnemyName) && string.Equals(a, expected.EnemyName, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } private LevelBaseline FindBaseline(SelectableLevel level) { if ((Object)(object)level == (Object)null) { return null; } int num = SafeInstanceId((Object)(object)level); if (num != 0 && baselineByInstanceId.TryGetValue(num, out var value)) { return value; } string text = StableLevelKey(level); if (!string.IsNullOrEmpty(text) && baselineByStableKey.TryGetValue(text, out value)) { return value; } return null; } private void InstallLllBundleStatusPatch() { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown try { lllNetworkBundleManagerType = AccessTools.TypeByName("LethalLevelLoader.NetworkBundleManager"); if (lllNetworkBundleManagerType == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes][LLL] NetworkBundleManager type not found. LLL bundle-status fix disabled for this process."); return; } lllRefreshLoadStatusMethod = AccessTools.Method(lllNetworkBundleManagerType, "RefreshLoadStatus", (Type[])null, (Type[])null); if (lllRefreshLoadStatusMethod == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes][LLL] RefreshLoadStatus method not found. LLL bundle-status fix disabled for this process."); return; } PropertyInfo property = lllNetworkBundleManagerType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { lllInstanceMember = property; } else { FieldInfo field = lllNetworkBundleManagerType.GetField("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { field = lllNetworkBundleManagerType.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } lllInstanceMember = field; } harmony = new Harmony("boogie.stevefixes.lll_bundle_status"); HarmonyMethod val = new HarmonyMethod(typeof(SteveFixes).GetMethod("LllRefreshLoadStatusPrefix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(SteveFixes).GetMethod("LllRefreshLoadStatusFinalizer", BindingFlags.Static | BindingFlags.NonPublic)); harmony.Patch((MethodBase)lllRefreshLoadStatusMethod, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null); lllPatchInstalled = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[SteveFixes][LLL] LLL_BUNDLE_STATUS_PATCH_READY method=" + lllNetworkBundleManagerType.FullName + "." + lllRefreshLoadStatusMethod.Name + " singletonMember=" + ((lllInstanceMember == null) ? "" : lllInstanceMember.Name))); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[SteveFixes][LLL] Failed to install bundle-status patch: " + ex)); } } private static bool LllRefreshLoadStatusPrefix(object __instance) { if (!lllPatchInstalled || __instance == null) { return true; } try { object obj = ResolveCurrentLllNetworkBundleManager(__instance); if (obj == null || object.ReferenceEquals(obj, __instance)) { if (lllVerbose && SharedLog != null) { SharedLog.LogInfo((object)("[SteveFixes][LLL] REFRESH_OK manager=" + DescribeLllManager(__instance))); } return true; } if (!IsUsableLllManager(obj)) { return true; } if (SharedLog != null) { SharedLog.LogWarning((object)("[SteveFixes][LLL] STALE_REFRESH_REDIRECT stale=" + DescribeLllManager(__instance) + " current=" + DescribeLllManager(obj))); } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - lastLllRedirectAt > 0.05f) { lastLllRedirectAt = realtimeSinceStartup; lllRefreshLoadStatusMethod.Invoke(obj, null); } else if (lllVerbose && SharedLog != null) { SharedLog.LogInfo((object)"[SteveFixes][LLL] STALE_REFRESH_REDIRECT coalesced duplicate callback."); } return false; } catch (Exception ex) { if (SharedLog != null) { SharedLog.LogError((object)("[SteveFixes][LLL] Prefix failed open; allowing original RefreshLoadStatus. " + Unwrap(ex))); } return true; } } private static Exception LllRefreshLoadStatusFinalizer(object __instance, Exception __exception) { if (__exception == null || !lllPatchInstalled || __instance == null) { return __exception; } Exception ex = Unwrap(__exception); if (!(ex is KeyNotFoundException)) { return __exception; } try { object obj = ResolveCurrentLllNetworkBundleManager(__instance); if (obj == null || object.ReferenceEquals(obj, __instance) || !IsUsableLllManager(obj)) { if (SharedLog != null) { SharedLog.LogError((object)("[SteveFixes][LLL] CURRENT_MANAGER_RPC_FAILURE preserved: " + ex.Message)); } return __exception; } if (SharedLog != null) { SharedLog.LogWarning((object)("[SteveFixes][LLL] STALE_RPC_RECOVERY retrying RefreshLoadStatus on current manager after: " + ex.Message)); } lllRefreshLoadStatusMethod.Invoke(obj, null); return null; } catch (Exception ex2) { if (SharedLog != null) { SharedLog.LogError((object)("[SteveFixes][LLL] STALE_RPC_RECOVERY failed: " + Unwrap(ex2))); } return __exception; } } private static object ResolveCurrentLllNetworkBundleManager(object invokingInstance) { object obj = ReadLllSingleton(); if (IsUsableLllManager(obj)) { return obj; } if (lllNetworkBundleManagerType != null) { Object[] array = Resources.FindObjectsOfTypeAll(lllNetworkBundleManagerType); object obj2 = null; foreach (object obj3 in array) { if (IsUnityAlive(obj3)) { if (obj2 == null) { obj2 = obj3; } if (ReadBool(obj3, "IsSpawned") == true) { return obj3; } } } if (obj2 != null && object.ReferenceEquals(obj2, invokingInstance)) { return invokingInstance; } return obj2; } return null; } private static object ReadLllSingleton() { if (lllInstanceMember == null) { return null; } try { PropertyInfo propertyInfo = lllInstanceMember as PropertyInfo; if (propertyInfo != null) { return propertyInfo.GetValue(null, null); } FieldInfo fieldInfo = lllInstanceMember as FieldInfo; if (fieldInfo != null) { return fieldInfo.GetValue(null); } } catch { } return null; } private static bool IsUsableLllManager(object candidate) { if (!IsUnityAlive(candidate)) { return false; } bool? flag = ReadBool(candidate, "IsSpawned"); if (flag.HasValue) { return flag.Value; } return true; } private static bool IsUnityAlive(object candidate) { if (candidate == null) { return false; } Object val = (Object)((candidate is Object) ? candidate : null); if (val != (Object)null) { return true; } if (candidate is Object) { return false; } return true; } private static bool? ReadBool(object instance, string propertyName) { if (instance == null) { return null; } try { PropertyInfo property = instance.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(bool)) { return (bool)property.GetValue(instance, null); } } catch { } return null; } private static string DescribeLllManager(object manager) { if (manager == null) { return ""; } int num = 0; Object val = (Object)((manager is Object) ? manager : null); if (val != (Object)null) { try { num = val.GetInstanceID(); } catch { } } bool? flag = ReadBool(manager, "IsSpawned"); return manager.GetType().Name + "#" + num + " IsSpawned=" + (flag.HasValue ? flag.Value.ToString() : "?"); } private static Exception Unwrap(Exception ex) { Exception ex2 = ex; while (ex2 is TargetInvocationException && ex2.InnerException != null) { ex2 = ex2.InnerException; } return ex2; } private void TryInstallMacronRuntimePatches() { //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Expected O, but got Unknown //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Expected O, but got Unknown //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Expected O, but got Unknown if (macronPatchInstalled || !macronCompatibilityFixEnabled.Value) { return; } try { macronAiType = AccessTools.TypeByName("MacronAI"); if (macronAiType == null) { return; } macronSpawnShovelMethod = AccessTools.Method(macronAiType, "SpawnShovel", Type.EmptyTypes, (Type[])null); if (macronSpawnShovelMethod == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes][Macron] SpawnShovel() not found. Macron runtime fix left inactive."); return; } MethodInfo[] methods = macronAiType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (string.Equals(methodInfo.Name, "HitEnemy", StringComparison.Ordinal)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 4 && parameters[0].ParameterType == typeof(int)) { macronHitEnemyMethod = methodInfo; } } } macronPlayDanceMusicMethod = AccessTools.Method(macronAiType, "PlayDanceMusic", Type.EmptyTypes, (Type[])null); macronPlayMusicMethod = AccessTools.Method(macronAiType, "PlayMusic", new Type[1] { typeof(AudioClip) }, (Type[])null); macronNetworkObjectIdGetter = AccessTools.PropertyGetter(macronAiType, "NetworkObjectId"); Type baseType = macronAiType; while (baseType != null && macronEnemyHpField == null) { macronEnemyHpField = baseType.GetField("enemyHP", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); baseType = baseType.BaseType; } if (macronHitEnemyMethod == null || macronEnemyHpField == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes][Macron] HitEnemy/enemyHP not found. Refusing partial runtime patch so combat balance remains predictable."); return; } macronHarmony = new Harmony("boogie.stevefixes.macron_runtime"); macronHarmony.Patch((MethodBase)macronSpawnShovelMethod, new HarmonyMethod(typeof(SteveFixes).GetMethod("MacronSpawnShovelPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); macronHarmony.Patch((MethodBase)macronHitEnemyMethod, new HarmonyMethod(typeof(SteveFixes).GetMethod("MacronHitEnemyPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (macronPlayDanceMusicMethod != null && macronPlayMusicMethod != null) { macronHarmony.Patch((MethodBase)macronPlayDanceMusicMethod, new HarmonyMethod(typeof(SteveFixes).GetMethod("MacronPlayDanceMusicPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); macronDancePatchInstalled = true; } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes][Macron] Dance API shape not found. dance1-10 override disabled; stock dance audio remains available."); } macronPatchInstalled = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[SteveFixes][Macron] MACRON_FIX_READY safeMode=True oneShovel=True hitsToKill=" + macronConfiguredHitsToKill + " dancePatch=" + macronDancePatchInstalled + " deathPatch=Binary9s")); if (!macronDancePatchInstalled) { return; } string text = null; try { if (macronAiType.Assembly != null && !string.IsNullOrEmpty(macronAiType.Assembly.Location)) { text = Path.GetDirectoryName(macronAiType.Assembly.Location); } } catch { } if (!string.IsNullOrEmpty(text)) { ((MonoBehaviour)this).StartCoroutine(LoadMacronDancePlaylist(text)); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes][Macron] Could not resolve MacronMod.dll folder. dance1-10 override disabled; stock dance audio remains available."); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[SteveFixes][Macron] Failed to install runtime patches: " + Unwrap(ex))); } } private IEnumerator LoadMacronDancePlaylist(string macronDir) { macronDancePlaylistReady = false; macronDanceClipCount = 0; for (int i = 0; i < MacronDanceClips.Length; i++) { MacronDanceClips[i] = null; } for (int j = 1; j <= 10; j++) { string path = Path.Combine(macronDir, "macron_dance" + j + ".mp3"); if (!File.Exists(path)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes][Macron] Missing macron_dance" + j + ".mp3")); continue; } string uri = "file:///" + path.Replace("\\", "/"); UnityWebRequest request; try { request = UnityWebRequestMultimedia.GetAudioClip(uri, (AudioType)13); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes][Macron] Could not create audio request for dance" + j + ": " + ex.Message)); continue; } yield return request.SendWebRequest(); if ((int)request.result != 1) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes][Macron] Failed loading dance" + j + ": " + request.error)); request.Dispose(); continue; } AudioClip clip = null; try { clip = DownloadHandlerAudioClip.GetContent(request); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[SteveFixes][Macron] Failed decoding dance" + j + ": " + ex2.Message)); } request.Dispose(); if ((Object)(object)clip != (Object)null) { ((Object)clip).name = "SteveFixes_MacronDance" + j; MacronDanceClips[j - 1] = clip; macronDanceClipCount++; } } macronDancePlaylistReady = macronDanceClipCount == 10; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[SteveFixes][Macron] DANCE_PLAYLIST_READY clips=" + macronDanceClipCount + "/10 active=" + macronDancePlaylistReady)); if (!macronDancePlaylistReady) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[SteveFixes][Macron] dance1-10 playlist incomplete. Falling back to Macron's original dance1-3 behavior."); } } private static bool MacronSpawnShovelPrefix(object __instance) { if (!macronPatchInstalled || __instance == null) { return true; } try { MacronState orCreateValue = MacronStates.GetOrCreateValue(__instance); if (orCreateValue.ShovelAlreadySpawned) { return false; } orCreateValue.ShovelAlreadySpawned = true; return true; } catch (Exception ex) { if (SharedLog != null) { SharedLog.LogWarning((object)("[SteveFixes][Macron] One-shovel guard failed open: " + Unwrap(ex).Message)); } return true; } } private static bool MacronPlayDanceMusicPrefix(object __instance) { if (!macronPatchInstalled || !macronDancePatchInstalled || !macronDancePlaylistReady || __instance == null) { return true; } try { ulong num = 0uL; bool flag = false; int num2; if (macronNetworkObjectIdGetter != null) { object obj = macronNetworkObjectIdGetter.Invoke(__instance, null); if (obj != null) { num = Convert.ToUInt64(obj); num2 = (int)(num % 10); flag = true; } else { num2 = 0; } } else { num2 = 0; } if (!flag) { MacronState orCreateValue = MacronStates.GetOrCreateValue(__instance); if (orCreateValue.DanceIndex < 0 || orCreateValue.DanceIndex >= 10) { orCreateValue.DanceIndex = Random.Range(0, 10); } num2 = orCreateValue.DanceIndex; } AudioClip val = MacronDanceClips[num2]; if ((Object)(object)val == (Object)null) { return true; } macronPlayMusicMethod.Invoke(__instance, new object[1] { val }); if (SharedLog != null) { string text = (flag ? num.ToString() : "fallback"); SharedLog.LogInfo((object)("[SteveFixes][Macron] DANCE_PICK " + (num2 + 1) + "/10 mode=DirectStable networkObjectId=" + text)); } return false; } catch (Exception ex) { if (SharedLog != null) { SharedLog.LogWarning((object)("[SteveFixes][Macron] Dance override failed open: " + Unwrap(ex).Message)); } return true; } } private static void MacronHitEnemyPrefix(object __instance) { if (!macronPatchInstalled || __instance == null || macronEnemyHpField == null) { return; } try { MacronState orCreateValue = MacronStates.GetOrCreateValue(__instance); if (!orCreateValue.HitPointsInitialized) { int num = Convert.ToInt32(macronEnemyHpField.GetValue(__instance)); macronEnemyHpField.SetValue(__instance, macronConfiguredHitsToKill); orCreateValue.HitPointsInitialized = true; if (SharedLog != null) { SharedLog.LogInfo((object)("[SteveFixes][Macron] HP_INIT " + num + " -> " + macronConfiguredHitsToKill)); } } } catch (Exception ex) { if (SharedLog != null) { SharedLog.LogWarning((object)("[SteveFixes][Macron] HP initialization failed open: " + Unwrap(ex).Message)); } } } private static string StableLevelKey(SelectableLevel level) { if ((Object)(object)level == (Object)null) { return string.Empty; } string text = Safe(((Object)level).name); string text2 = Safe(level.PlanetName); return text + "|" + text2; } private static int SafeInstanceId(Object obj) { if (obj == (Object)null) { return 0; } try { return obj.GetInstanceID(); } catch { return 0; } } private static string Safe(string text) { return text ?? string.Empty; } }