Decompiled source of spacefixland v2.1.5

BepInEx/plugins/spacefixland/spacefixland.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using DunGen;
using DunGen.Graph;
using HarmonyLib;
using LethalLevelLoader;
using UnityEngine;

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

internal static class LogFloodProtection
{
	private static readonly RepeatedLogGate gate = new RepeatedLogGate(256);

	private static ConfigEntry<bool> enabled;

	private static ConfigEntry<int> interval;

	private static ManualLogSource log;

	[ThreadStatic]
	private static bool reporting;

	internal static void Install(ConfigFile config, ManualLogSource logger)
	{
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Expected O, but got Unknown
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Expected O, but got Unknown
		//IL_0110: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Expected O, but got Unknown
		log = logger;
		enabled = config.Bind<bool>("Performance", "ThrottleRepeatedUnityDiagnostics", true, "Keep the first identical Unity warning/error/exception per object, then at most one per interval. Does not fix the underlying errors. Disable for complete diagnostic logs.");
		interval = config.Bind<int>("Performance", "DiagnosticIntervalSeconds", 10, "Seconds between identical Unity diagnostics (clamped to 1-60). Different exception stacks and object contexts remain separate.");
		Harmony val = new Harmony("clair.dawnlandingcompatibility.logging");
		try
		{
			Type type = typeof(Debug).Assembly.GetType("UnityEngine.DebugLogHandler", throwOnError: true);
			val.Patch((MethodBase)AccessTools.Method(type, "LogException", new Type[2]
			{
				typeof(Exception),
				typeof(Object)
			}, (Type[])null), new HarmonyMethod(typeof(LogFloodProtection), "ExceptionPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			val.Patch((MethodBase)AccessTools.Method(type, "LogFormat", new Type[4]
			{
				typeof(LogType),
				typeof(Object),
				typeof(string),
				typeof(object[])
			}, (Type[])null), new HarmonyMethod(typeof(LogFloodProtection), "FormatPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			log.LogInfo((object)"Optional log-flood protection installed; first diagnostics and periodic repeats are preserved.");
		}
		catch (Exception ex)
		{
			val.UnpatchSelf();
			log.LogWarning((object)("Log-flood protection unavailable; Dawn compatibility remains active: " + ex.Message));
		}
	}

	internal static void Reset()
	{
		gate.Clear();
	}

	private static bool Allow(string key)
	{
		int suppressed;
		bool flag = gate.Allow(key, (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency, Math.Max(1, Math.Min(60, interval.Value)), out suppressed);
		if (flag && suppressed > 0)
		{
			reporting = true;
			try
			{
				log.LogWarning((object)("Log-flood protection suppressed " + suppressed + " identical Unity diagnostics; allowing the next occurrence below. Underlying error still needs repair."));
			}
			finally
			{
				reporting = false;
			}
		}
		return flag;
	}

	private static bool ExceptionPrefix(Exception __0, Object __1)
	{
		if (reporting || !enabled.Value || __0 == null)
		{
			return true;
		}
		int num = ((!object.ReferenceEquals(__1, null)) ? RuntimeHelpers.GetHashCode(__1) : 0);
		return Allow("exception|" + num + "|" + __0.GetType().FullName + "|" + __0.Message + "|" + __0.StackTrace);
	}

	private static bool FormatPrefix(LogType __0, Object __1, string __2, object[] __3)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Invalid comparison between Unknown and I4
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		if (reporting || !enabled.Value || ((int)__0 != 2 && (int)__0 != 0) || __2 == null)
		{
			return true;
		}
		string text = __2;
		if (__3 != null && __3.Length != 0)
		{
			foreach (object obj in __3)
			{
				if (obj != null && !(obj is string) && !obj.GetType().IsPrimitive && !obj.GetType().IsEnum)
				{
					return true;
				}
			}
			try
			{
				text = string.Format(CultureInfo.InvariantCulture, __2, __3);
			}
			catch (FormatException)
			{
				return true;
			}
		}
		int num = ((!object.ReferenceEquals(__1, null)) ? RuntimeHelpers.GetHashCode(__1) : 0);
		return Allow(string.Concat(__0, "|", num, "|", text));
	}
}
[BepInDependency("imabatby.lethallevelloader", "1.7.13")]
[BepInDependency("com.github.teamxiaolan.dawnlib", "0.9.27")]
[BepInPlugin("clair.dawnlandingcompatibility", "spacefixland", "2.1.5")]
public sealed class Plugin : BaseUnityPlugin
{
	private static ManualLogSource log;

	private static MethodInfo getInfo;

	private static MethodInfo setInfo;

	private static MethodInfo handoff;

	private static MethodInfo getArchetypeInfo;

	private static FieldInfo dungeonRegistry;

	private static PropertyInfo registryValues;

	private static PropertyInfo infoFlow;

	private static readonly HashSet<DungeonFlow> fallbackFlows = new HashSet<DungeonFlow>();

	private Harmony harmony;

	private void Awake()
	{
		//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ba: Expected O, but got Unknown
		//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e0: Expected O, but got Unknown
		//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0207: Expected O, but got Unknown
		//IL_0234: Unknown result type (might be due to invalid IL or missing references)
		//IL_0242: Expected O, but got Unknown
		log = ((BaseUnityPlugin)this).Logger;
		LogFloodProtection.Install(((BaseUnityPlugin)this).Config, log);
		PluginInfo val = Chainloader.PluginInfos["com.github.teamxiaolan.dawnlib"];
		if (val.Metadata.Version != new Version(0, 9, 27))
		{
			log.LogError((object)string.Concat("Unsupported DawnLib version ", val.Metadata.Version, "; no patches installed. Built for 0.9.27."));
			return;
		}
		try
		{
			Assembly assembly = ((object)val.Instance).GetType().Assembly;
			Type type = assembly.GetType("Dawn.DungeonFlowExtensions", throwOnError: true);
			getInfo = AccessTools.Method(type, "GetDawnInfo", (Type[])null, (Type[])null);
			setInfo = AccessTools.Method(type, "SetDawnInfo", (Type[])null, (Type[])null);
			dungeonRegistry = AccessTools.Field(assembly.GetType("Dawn.LethalContent", throwOnError: true), "Dungeons");
			handoff = AccessTools.Method(assembly.GetType("Dawn.Internal.LethalLevelLoaderCompat", throwOnError: true), "LetLLLHandleGeneration", (Type[])null, (Type[])null);
			getArchetypeInfo = AccessTools.Method(assembly.GetType("Dawn.DungeonArchetypeExtensions", throwOnError: true), "GetDawnInfo", (Type[])null, (Type[])null);
			Type type2 = assembly.GetType("Dawn.DungeonRegistrationHandler", throwOnError: true);
			MethodInfo methodInfo = AccessTools.Method(type2, "LoadDungeonBundle", Type.EmptyTypes, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(type2, "TryInjectTileSets", (Type[])null, (Type[])null);
			if (getInfo == null || setInfo == null || dungeonRegistry == null || handoff == null || getArchetypeInfo == null || methodInfo == null || methodInfo.ReturnType != typeof(IEnumerator) || methodInfo2 == null)
			{
				throw new MissingMethodException("Expected DawnLib 0.9.27 dungeon API is unavailable.");
			}
			harmony = new Harmony("clair.dawnlandingcompatibility");
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "WrapLoad", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Plugin), "CheckTileMetadata", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(RoundManager), "LoadNewLevel", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "ResetVisit", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			log.LogInfo((object)"DawnLib 0.9.27 dungeon compatibility installed. Valid Dawn loaders are preserved.");
		}
		catch (Exception ex)
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			log.LogError((object)("Could not install compatibility adapter: " + ex));
		}
	}

	private static void ResetVisit()
	{
		fallbackFlows.Clear();
		LogFloodProtection.Reset();
	}

	private static void WrapLoad(ref IEnumerator __result)
	{
		__result = LoadOrRepair(__result);
	}

	internal static bool OwnedByLLL(DungeonFlow flow)
	{
		foreach (ExtendedDungeonFlow extendedDungeonFlow in PatchedContent.ExtendedDungeonFlows)
		{
			if ((Object)(object)extendedDungeonFlow != (Object)null && (Object)(object)extendedDungeonFlow.DungeonFlow == (Object)(object)flow)
			{
				return true;
			}
		}
		return false;
	}

	private static bool RestoreMetadata(DungeonFlow flow)
	{
		object value = dungeonRegistry.GetValue(null);
		if (registryValues == null)
		{
			registryValues = AccessTools.Property(value.GetType(), "Values");
		}
		if (!(registryValues.GetValue(value, null) is IEnumerable enumerable))
		{
			return false;
		}
		object obj = null;
		foreach (object item in enumerable)
		{
			if (item != null)
			{
				if (infoFlow == null)
				{
					infoFlow = AccessTools.Property(item.GetType(), "DungeonFlow");
				}
				object? value2 = infoFlow.GetValue(item, null);
				DungeonFlow val = (DungeonFlow)((value2 is DungeonFlow) ? value2 : null);
				if (!((Object)(object)val == (Object)null) && (Object)(object)val == (Object)(object)flow)
				{
					obj = item;
					break;
				}
			}
		}
		if (obj == null)
		{
			return false;
		}
		setInfo.Invoke(null, new object[2] { flow, obj });
		return getInfo.Invoke(null, new object[1] { flow }) != null;
	}

	private static IEnumerator LoadOrRepair(IEnumerator original)
	{
		RoundManager round = RoundManager.Instance;
		DungeonFlow flow = (((Object)(object)round == (Object)null || (Object)(object)round.dungeonGenerator == (Object)null || round.dungeonGenerator.Generator == null) ? null : round.dungeonGenerator.Generator.DungeonFlow);
		bool fallback = false;
		if ((Object)(object)flow != (Object)null)
		{
			try
			{
				if (getInfo.Invoke(null, new object[1] { flow }) == null)
				{
					if (RestoreMetadata(flow))
					{
						log.LogInfo((object)("Restored Dawn metadata for dungeon " + ((Object)flow).name + "."));
					}
					else if (OwnedByLLL(flow))
					{
						fallback = true;
					}
					else
					{
						log.LogError((object)("Missing Dawn metadata for unrecognized dungeon " + ((Object)flow).name + "; preserving Dawn's loader. Check host/client mod parity."));
					}
				}
			}
			catch (Exception ex)
			{
				log.LogError((object)("Dungeon metadata inspection failed; preserving Dawn's loader: " + ex));
			}
		}
		if (fallback)
		{
			fallbackFlows.Add(flow);
			log.LogWarning((object)("Missing Dawn metadata on LLL-owned dungeon '" + ((Object)flow).name + "'; handing generation back to LethalLevelLoader. Install this adapter on host and every client."));
			if (original is IDisposable disposable)
			{
				disposable.Dispose();
			}
			yield return (object)new WaitForSeconds(0.1f);
			if (!((Object)(object)RoundManager.Instance != (Object)(object)round) && !((Object)(object)round.dungeonGenerator == (Object)null) && !((Object)(object)round.dungeonGenerator.Generator.DungeonFlow != (Object)(object)flow))
			{
				handoff.Invoke(null, null);
			}
		}
		else
		{
			if (original == null)
			{
				yield break;
			}
			try
			{
				while (original.MoveNext())
				{
					yield return original.Current;
				}
			}
			finally
			{
				if (original is IDisposable disposable2)
				{
					disposable2.Dispose();
				}
			}
		}
	}

	private static bool CheckTileMetadata(DungeonFlow __0)
	{
		if ((Object)(object)__0 == (Object)null || !fallbackFlows.Contains(__0))
		{
			return true;
		}
		DungeonArchetype[] usedArchetypes = __0.GetUsedArchetypes();
		foreach (DungeonArchetype val in usedArchetypes)
		{
			if ((Object)(object)val != (Object)null && getArchetypeInfo.Invoke(null, new object[1] { val }) == null)
			{
				log.LogWarning((object)("Skipping Dawn tile-set additions for fallback dungeon '" + ((Object)__0).name + "' because archetype metadata is missing. Authored LLL tiles remain in use."));
				return false;
			}
		}
		return true;
	}
}
internal sealed class RepeatedLogGate
{
	private sealed class Entry
	{
		internal double Last;

		internal int Suppressed;
	}

	private readonly Dictionary<string, Entry> entries = new Dictionary<string, Entry>();

	private readonly int capacity;

	internal RepeatedLogGate(int capacity)
	{
		this.capacity = Math.Max(1, capacity);
	}

	internal bool Allow(string key, double now, double interval, out int suppressed)
	{
		suppressed = 0;
		if (string.IsNullOrEmpty(key) || key.Length > 16384)
		{
			return true;
		}
		lock (entries)
		{
			if (!entries.TryGetValue(key, out var value))
			{
				if (entries.Count < capacity)
				{
					entries.Add(key, new Entry
					{
						Last = now
					});
				}
				return true;
			}
			if (now >= value.Last && now - value.Last < interval)
			{
				if (value.Suppressed < int.MaxValue)
				{
					value.Suppressed++;
				}
				return false;
			}
			suppressed = value.Suppressed;
			value.Suppressed = 0;
			value.Last = now;
			return true;
		}
	}

	internal void Clear()
	{
		lock (entries)
		{
			entries.Clear();
		}
	}
}