Decompiled source of APEXBanking Compat Patch v0.3.0

ApexBankingCompatPatch.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ApexBankingCompatPatch;

[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("agron.repo.apexbankingcompatpatch", "APEXBanking Compat Patch", "0.3.0")]
public class ApexBankingCompatPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "agron.repo.apexbankingcompatpatch";

	public const string PluginName = "APEXBanking Compat Patch";

	public const string PluginVersion = "0.3.0";

	public const string ApexLevelName = "Level - Bank";

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> ForceApexLevel;

	internal static ConfigEntry<bool> LoadingDiagnostics;

	internal static ConfigEntry<bool> PrefabBridge;

	internal static ConfigEntry<bool> SkipBrokenLevels;

	internal static ConfigEntry<bool> GuardBrokenText;

	internal static ConfigEntry<float> MonsterFixInterval;

	private readonly Harmony _harmony = new Harmony("agron.repo.apexbankingcompatpatch");

	private void Awake()
	{
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		ForceApexLevel = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Force APEX Level", false, "Always pick APEX Banking as the next level (for testing).");
		LoadingDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Loading Diagnostics", false, "While APEX Banking is loading, log which loading step the game is waiting on.");
		PrefabBridge = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "REPOLib Prefab Bridge", true, "Hand REPOLib-registered prefabs (modded levels, valuables, ...) to the game's prefab caches. Needed since the Unity 6 update, where modded levels fail with \"couldn't load prefab\".");
		SkipBrokenLevels = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "Skip Broken Levels", true, "If the picked level has no start rooms (old-format modded levels), pick another level instead of failing back to the menu.");
		GuardBrokenText = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "Guard Broken Text", true, "If a TextMeshPro text throws while rebuilding (e.g. a modded text with a font/material that no longer loads), disable just that text. Otherwise it blocks every other text/UI update and the screen can stay black.");
		MonsterFixInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Fixes", "Monster Fix Scan Interval", 10f, "Dafi's Monster Health ReAwakening scans every object in the scene every 0.5s, which stutters on big levels. Scan this often instead (seconds), plus right after an enemy spawns. 0 = leave it alone.");
		GameObject gameObject = ((Component)this).gameObject;
		((Object)gameObject).hideFlags = (HideFlags)(((Object)gameObject).hideFlags | 0x3D);
		_harmony.PatchAll();
		((MonoBehaviour)this).StartCoroutine(DiagnosticsLoop());
		Log.LogInfo((object)"APEXBanking Compat Patch 0.3.0 loaded.");
	}

	internal static bool IsApexLevel(Level level)
	{
		if ((Object)(object)level != (Object)null)
		{
			return ((Object)level).name == "Level - Bank";
		}
		return false;
	}

	private IEnumerator DiagnosticsLoop()
	{
		WaitForSeconds wait = new WaitForSeconds(3f);
		float loadingSince = -1f;
		while (true)
		{
			yield return wait;
			if (!LoadingDiagnostics.Value)
			{
				continue;
			}
			LevelGenerator gen = LevelGenerator.Instance;
			if ((Object)(object)gen == (Object)null || !IsApexLevel(gen.Level))
			{
				loadingSince = -1f;
				continue;
			}
			LoadingUI loading = LoadingUI.instance;
			object gameState = (((Object)(object)GameDirector.instance != (Object)null) ? Get<object>(GameDirector.instance, "currentState") : null);
			if (gameState == null || (!(gameState.ToString() == "Load") && !(gameState.ToString() == "Start")))
			{
				if (loadingSince >= 0f)
				{
					Log.LogInfo((object)("[Diag] APEX Banking finished loading after " + (Time.realtimeSinceStartup - loadingSince).ToString("0") + "s."));
					loadingSince = -1f;
				}
			}
			else
			{
				if (loadingSince < 0f)
				{
					loadingSince = Time.realtimeSinceStartup;
				}
				Log.LogWarning((object)("[Diag] " + DescribeState(gen, loading, Time.realtimeSinceStartup - loadingSince)));
			}
		}
	}

	private static string DescribeState(LevelGenerator gen, LoadingUI loading, float elapsed)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("t=").Append(elapsed.ToString("0")).Append("s");
		stringBuilder.Append(" State=").Append(Get<object>(gen, "State"));
		stringBuilder.Append(" Generated=").Append(Get<bool>(gen, "Generated"));
		stringBuilder.Append(" AllPlayersReady=").Append(Get<bool>(gen, "AllPlayersReady"));
		stringBuilder.Append(" waitingForSubCoroutine=").Append(Get<bool>(gen, "waitingForSubCoroutine"));
		stringBuilder.Append(" Modules=").Append(Get<int>(gen, "ModulesSpawned")).Append("/")
			.Append(Get<int>(gen, "ModuleAmount"));
		stringBuilder.Append(" ModulesReadyPlayers=").Append(Get<int>(gen, "ModulesReadyPlayers"));
		IList list = Get<IList>(gen, "LevelPathPoints");
		stringBuilder.Append(" LevelPathPoints=").Append((list == null) ? "null" : list.Count.ToString());
		if ((Object)(object)ValuableDirector.instance != (Object)null)
		{
			stringBuilder.Append(" ValuableSetupComplete=").Append(Get<bool>(ValuableDirector.instance, "setupComplete"));
		}
		stringBuilder.Append(" playerSpawned=").Append(Get<int>(gen, "playerSpawned"));
		if ((Object)(object)GameDirector.instance != (Object)null)
		{
			IList list2 = Get<IList>(GameDirector.instance, "PlayerList");
			stringBuilder.Append(" PlayerList=").Append((list2 == null) ? "null" : list2.Count.ToString());
			stringBuilder.Append(" GameState=").Append(Get<object>(GameDirector.instance, "currentState"));
		}
		stringBuilder.Append(" Enemies=").Append(Get<int>(gen, "EnemiesSpawned")).Append("/")
			.Append(Get<int>(gen, "EnemiesSpawnTarget"));
		stringBuilder.Append(" EnemyReady=").Append(Get<bool>(gen, "EnemyReady"));
		if ((Object)(object)loading != (Object)null)
		{
			stringBuilder.Append(" LevelAnimStarted=").Append(Get<bool>(loading, "levelAnimationStarted"));
			stringBuilder.Append(" LevelAnimCompleted=").Append(Get<bool>(loading, "levelAnimationCompleted"));
		}
		return stringBuilder.ToString();
	}

	private static T Get<T>(object target, string field)
	{
		try
		{
			object value = Traverse.Create(target).Field(field).GetValue();
			return (value is T) ? ((T)value) : default(T);
		}
		catch
		{
			return default(T);
		}
	}
}
internal static class PrefabBridge
{
	private static FieldInfo _repolibPrefabs;

	private static bool _loggedMissing;

	private static int _lastAdded = -1;

	internal static void Fill()
	{
		if (!ApexBankingCompatPlugin.PrefabBridge.Value)
		{
			return;
		}
		RunManager instance = RunManager.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		try
		{
			if (_repolibPrefabs == null)
			{
				Type type = AccessTools.TypeByName("REPOLib.Modules.NetworkPrefabs");
				_repolibPrefabs = ((type != null) ? AccessTools.Field(type, "_prefabs") : null);
			}
			IDictionary dictionary = ((_repolibPrefabs != null) ? (_repolibPrefabs.GetValue(null) as IDictionary) : null);
			Dictionary<string, GameObject> dictionary2 = Traverse.Create((object)instance).Field("multiplayerPool").Field("ResourceCache")
				.GetValue() as Dictionary<string, GameObject>;
			Dictionary<string, Object> dictionary3 = Traverse.Create((object)instance).Field("singleplayerPool").GetValue() as Dictionary<string, Object>;
			if (dictionary == null || (dictionary2 == null && dictionary3 == null))
			{
				if (!_loggedMissing)
				{
					ApexBankingCompatPlugin.Log.LogWarning((object)"[Bridge] REPOLib prefabs or game caches not found; bridge inactive.");
				}
				_loggedMissing = true;
				return;
			}
			int num = 0;
			foreach (DictionaryEntry item in dictionary)
			{
				string id = item.Key as string;
				object? value = item.Value;
				if (Put(dictionary2, dictionary3, id, (GameObject)((value is GameObject) ? value : null)))
				{
					num++;
				}
			}
			if (num > 0 && num != _lastAdded)
			{
				ApexBankingCompatPlugin.Log.LogInfo((object)("[Bridge] Added " + num + " REPOLib prefabs to the game's prefab cache."));
				_lastAdded = num;
			}
		}
		catch (Exception ex)
		{
			ApexBankingCompatPlugin.Log.LogError((object)("[Bridge] " + ex));
		}
	}

	internal static void AddOne(string id, GameObject prefab)
	{
		if (!ApexBankingCompatPlugin.PrefabBridge.Value)
		{
			return;
		}
		RunManager instance = RunManager.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		try
		{
			Dictionary<string, GameObject> cache = Traverse.Create((object)instance).Field("multiplayerPool").Field("ResourceCache")
				.GetValue() as Dictionary<string, GameObject>;
			Dictionary<string, Object> single = Traverse.Create((object)instance).Field("singleplayerPool").GetValue() as Dictionary<string, Object>;
			Put(cache, single, id, prefab);
		}
		catch (Exception ex)
		{
			ApexBankingCompatPlugin.Log.LogError((object)("[Bridge] " + ex));
		}
	}

	private static bool Put(Dictionary<string, GameObject> cache, Dictionary<string, Object> single, string id, GameObject prefab)
	{
		if (string.IsNullOrEmpty(id) || (Object)(object)prefab == (Object)null)
		{
			return false;
		}
		bool result = false;
		if (cache != null && (!cache.TryGetValue(id, out var value) || (Object)(object)value == (Object)null))
		{
			cache[id] = prefab;
			result = true;
		}
		if (single != null && (!single.TryGetValue(id, out var value2) || value2 == (Object)null))
		{
			single[id] = (Object)(object)prefab;
		}
		return result;
	}
}
[HarmonyPatch]
internal static class PrefabBridgeRegisterPatch
{
	private static bool Prepare()
	{
		return TargetMethod() != null;
	}

	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("REPOLib.Modules.NetworkPrefabs");
		if (!(type != null))
		{
			return null;
		}
		return AccessTools.Method(type, "RegisterNetworkPrefabInternal", new Type[2]
		{
			typeof(string),
			typeof(GameObject)
		}, (Type[])null);
	}

	private static void Postfix(string prefabId, GameObject prefab)
	{
		PrefabBridge.AddOne(prefabId, prefab);
	}
}
[HarmonyPatch(typeof(LevelGenerator), "Start")]
internal static class PrefabBridgeStartPatch
{
	private static void Postfix()
	{
		PrefabBridge.Fill();
	}
}
[HarmonyPatch(typeof(LevelGenerator), "PrefabsLoad")]
internal static class PrefabBridgeLoadPatch
{
	private static void Prefix()
	{
		PrefabBridge.Fill();
	}
}
[HarmonyPatch(typeof(LevelGenerator), "PrefabsLoadRemote")]
internal static class PrefabBridgeLoadRemotePatch
{
	private static void Prefix()
	{
		PrefabBridge.Fill();
	}
}
[HarmonyPatch(typeof(RunManager), "SetRunLevel")]
internal static class ForceApexLevelPatch
{
	private static void Postfix(RunManager __instance)
	{
		SkipBrokenLevel(__instance);
		if (!ApexBankingCompatPlugin.ForceApexLevel.Value || __instance.levels == null)
		{
			return;
		}
		foreach (Level level in __instance.levels)
		{
			if (ApexBankingCompatPlugin.IsApexLevel(level))
			{
				__instance.levelCurrent = level;
				ApexBankingCompatPlugin.Log.LogInfo((object)("[Debug] Forced next level to " + ((Object)level).name));
				return;
			}
		}
		ApexBankingCompatPlugin.Log.LogWarning((object)"[Debug] Could not find Level - Bank in RunManager.levels.");
	}

	private static void SkipBrokenLevel(RunManager run)
	{
		if (!ApexBankingCompatPlugin.SkipBrokenLevels.Value || run.levels == null || !IsBroken(run.levelCurrent))
		{
			return;
		}
		object value = Traverse.Create((object)run).Field("previousRunLevel").GetValue();
		Level val = (Level)((value is Level) ? value : null);
		List<Level> list = new List<Level>();
		foreach (Level level in run.levels)
		{
			if (!IsBroken(level) && (Object)(object)level != (Object)(object)val)
			{
				list.Add(level);
			}
		}
		if (list.Count == 0)
		{
			foreach (Level level2 in run.levels)
			{
				if (!IsBroken(level2))
				{
					list.Add(level2);
				}
			}
		}
		if (list.Count != 0)
		{
			Level val2 = list[Random.Range(0, list.Count)];
			ApexBankingCompatPlugin.Log.LogWarning((object)("[Fix] " + ((Object)run.levelCurrent).name + " has no start rooms; picked " + ((Object)val2).name + " instead."));
			run.levelCurrent = val2;
		}
	}

	private static bool IsBroken(Level level)
	{
		if ((Object)(object)level != (Object)null)
		{
			if (level.StartRooms != null)
			{
				return level.StartRooms.Count == 0;
			}
			return true;
		}
		return false;
	}
}
[HarmonyPatch]
internal static class BrokenTextGuardPatch
{
	private static readonly HashSet<int> _reported = new HashSet<int>();

	private static bool Prepare()
	{
		return TargetMethods().GetEnumerator().MoveNext();
	}

	private static IEnumerable<MethodBase> TargetMethods()
	{
		try
		{
			string[] array = new string[2] { "TMPro.TextMeshPro", "TMPro.TextMeshProUGUI" };
			foreach (string name in array)
			{
				Type type = AccessTools.TypeByName(name);
				MethodInfo method = ((type != null) ? AccessTools.DeclaredMethod(type, "Rebuild", (Type[])null, (Type[])null) : null);
				if (method != null)
				{
					yield return method;
				}
			}
		}
		finally
		{
		}
	}

	private static Exception Finalizer(Exception __exception, object __instance)
	{
		if (__exception == null || !ApexBankingCompatPlugin.GuardBrokenText.Value)
		{
			return __exception;
		}
		Behaviour val = (Behaviour)((__instance is Behaviour) ? __instance : null);
		if ((Object)(object)val == (Object)null)
		{
			return null;
		}
		try
		{
			val.enabled = false;
			if (_reported.Add(((Object)val).GetInstanceID()))
			{
				ApexBankingCompatPlugin.Log.LogWarning((object)("[Fix] Disabled broken text \"" + PathOf(((Component)val).transform) + "\": " + __exception.GetType().Name));
			}
		}
		catch
		{
		}
		return null;
	}

	private static string PathOf(Transform t)
	{
		string text = ((Object)t).name;
		Transform parent = t.parent;
		while ((Object)(object)parent != (Object)null)
		{
			text = ((Object)parent).name + "/" + text;
			parent = parent.parent;
		}
		return text;
	}
}
[HarmonyPatch]
internal static class MonsterFixThrottlePatch
{
	internal static object Helper;

	private static bool Prepare()
	{
		return TargetMethod() != null;
	}

	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("REPOMonsterFix.FixerHelper");
		if (!(type != null))
		{
			return null;
		}
		return AccessTools.Method(type, "Update", (Type[])null, (Type[])null);
	}

	private static void Prefix(object __instance)
	{
		float value = ApexBankingCompatPlugin.MonsterFixInterval.Value;
		if (!(value <= 0f) && Helper != __instance)
		{
			Helper = __instance;
			Traverse.Create(__instance).Field("checkInterval").SetValue((object)value);
			ApexBankingCompatPlugin.Log.LogInfo((object)("[Fix] Monster Health ReAwakening scan interval set to " + value + "s."));
		}
	}
}
[HarmonyPatch(typeof(EnemyHealth), "Awake")]
internal static class MonsterFixSpawnScanPatch
{
	private static void Postfix()
	{
		object helper = MonsterFixThrottlePatch.Helper;
		if (helper == null || ApexBankingCompatPlugin.MonsterFixInterval.Value <= 0f)
		{
			return;
		}
		try
		{
			Traverse.Create(helper).Field("immediateCheckRequested").SetValue((object)true);
		}
		catch
		{
		}
	}
}