Decompiled source of Loadstone FIX v0.3.1

com.adibtw.loadstone.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.AI.Navigation;
using UnityEngine;
using UnityEngine.AI;

[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("com.adibtw.loadstone")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.1.0")]
[assembly: AssemblyInformationalVersion("0.3.1+4814bfb446a9ee5e2ead977325f90c0e8c82f3c1")]
[assembly: AssemblyProduct("Loadstone-FIX")]
[assembly: AssemblyTitle("Loadstone-FIX")]
[assembly: AssemblyVersion("0.3.1.0")]
[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 Loadstone.V81
{
	internal static class AsyncNavmesh
	{
		private sealed class SurfaceJob
		{
			internal NavMeshSurface Surface;

			internal NavMeshData Data;

			internal AsyncOperation Operation;
		}

		private sealed class Preparation
		{
			internal object Round;

			internal object Generator;

			internal readonly List<SurfaceJob> Jobs = new List<SurfaceJob>();

			internal bool Ready;

			internal bool Transferred;

			internal bool Cleaned;
		}

		internal const string HarmonyId = "com.adibtw.loadstone.asyncnavmesh";

		private static RuntimeBindings bindings;

		private static ManualLogSource log;

		private static Func<bool> enabled;

		private static Harmony harmony;

		private static MethodInfo bake;

		private static MethodInfo changeStatus;

		private static FieldInfo fullSurfaces;

		private static MethodInfo dataInstanceGetter;

		private static FieldInfo statusListeners;

		private static object round;

		private static bool capturing;

		private static bool installed;

		private static bool failedThisSession;

		private static bool conflictReported;

		private static Preparation preparation;

		private static bool barrierInFlight;

		internal static void Install(RuntimeBindings runtime, ManualLogSource logger, Func<bool> isEnabled)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Expected O, but got Unknown
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Expected O, but got Unknown
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Expected O, but got Unknown
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Expected O, but got Unknown
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Expected O, but got Unknown
			bindings = runtime;
			log = logger;
			enabled = isEnabled;
			harmony = new Harmony("com.adibtw.loadstone.asyncnavmesh");
			try
			{
				Type type = AccessTools.TypeByName("DunGen.DungeonGenerator") ?? throw new TypeLoadException("DunGen.DungeonGenerator");
				bake = AccessTools.DeclaredMethod(runtime.FinishGeneratingLevel.DeclaringType, "BakeDunGenNavMesh", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("RoundManager.BakeDunGenNavMesh");
				fullSurfaces = AccessTools.DeclaredField(bake.DeclaringType, "fullBakeSurfaces");
				if (fullSurfaces == null || fullSurfaces.FieldType != typeof(List<NavMeshSurface>))
				{
					throw new MissingFieldException("RoundManager.fullBakeSurfaces");
				}
				dataInstanceGetter = typeof(NavMeshSurface).GetMethod("get_navMeshDataInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (dataInstanceGetter == null || dataInstanceGetter.ReturnType != typeof(NavMeshDataInstance))
				{
					throw new MissingMethodException("NavMeshSurface.navMeshDataInstance");
				}
				changeStatus = AccessTools.DeclaredMethod(type, "ChangeStatus", (Type[])null, (Type[])null);
				statusListeners = AccessTools.DeclaredField(type, "OnGenerationStatusChanged");
				MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "InnerGenerate", new Type[1] { typeof(bool) }, (Type[])null);
				MethodInfo methodInfo2 = ((methodInfo == null) ? null : AccessTools.EnumeratorMoveNext((MethodBase)methodInfo));
				if (methodInfo2 == null || changeStatus == null || statusListeners == null)
				{
					throw new MissingMethodException("DunGen completion iterator");
				}
				harmony.Patch((MethodBase)runtime.FinishGeneratingLevel, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(AsyncNavmesh), "FinishBakeCall", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(AsyncNavmesh), "CompletionBarrier", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(NavMeshSurface), "BuildNavMesh", (Type[])null, (Type[])null), new HarmonyMethod(typeof(AsyncNavmesh), "CaptureBuild", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(type, "Clear", new Type[1] { typeof(bool) }, (Type[])null), new HarmonyMethod(typeof(AsyncNavmesh), "BeforeGeneratorClear", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(bake.DeclaringType, "OnDestroy", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(AsyncNavmesh), "BeforeRoundDestroyed", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				installed = true;
				log.LogInfo((object)"Loadstone async interior navmesh installed for 6+ players. Native completion waits for preparation; outside navmesh remains unchanged.");
			}
			catch (Exception arg)
			{
				harmony.UnpatchSelf();
				installed = false;
				log.LogWarning((object)$"Loadstone async navmesh unavailable; native loading retained: {arg}");
			}
		}

		internal static void BeginRound(object instance)
		{
			Cancel();
			round = instance;
		}

		private static bool HasForeignPatch(MethodBase method)
		{
			Patches patchInfo = Harmony.GetPatchInfo(method);
			if (patchInfo != null)
			{
				return patchInfo.Owners.Any((string owner) => owner != "com.adibtw.loadstone.asyncnavmesh" && owner != "com.adibtw.loadstone");
			}
			return false;
		}

		internal static bool ShouldPrepare(object generator)
		{
			try
			{
				if (installed && !failedThisSession && enabled() && round != null)
				{
					object obj = round;
					Object val = (Object)((obj is Object) ? obj : null);
					if (val != null && Object.op_Implicit(val) && bindings.GetGenerator(round) == generator && bindings.GetTotalPlayers(round) > 5 && bindings.SettingsFor(generator).Asynchronous)
					{
						Delegate[] array = (statusListeners.GetValue(generator) as Delegate)?.GetInvocationList();
						if ((array == null) ? true : ((byte)Array.FindIndex(array, (Delegate handler) => handler.Target == round && handler.Method.Name == "Generator_OnGenerationStatusChanged") != 0))
						{
							if (!conflictReported)
							{
								conflictReported = true;
								log.LogWarning((object)"Loadstone async navmesh skipped: native completion listener is missing or another status listener precedes it; native loading retained.");
							}
							return false;
						}
						if (HasForeignPatch(bake) || HasForeignPatch(AccessTools.DeclaredMethod(typeof(NavMeshSurface), "BuildNavMesh", (Type[])null, (Type[])null)) || HasForeignPatch(AccessTools.DeclaredMethod(typeof(NavMeshSurface), "UpdateNavMesh", (Type[])null, (Type[])null)))
						{
							if (!conflictReported)
							{
								conflictReported = true;
								log.LogWarning((object)"Loadstone async navmesh skipped: another Harmony owner modifies the bake/surface API. Native loading retained.");
							}
							return false;
						}
						return true;
					}
				}
				return false;
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("Loadstone navmesh eligibility check failed; native loading retained: " + ex.Message));
				return false;
			}
		}

		internal static IEnumerator Prepare(object generator)
		{
			barrierInFlight = true;
			Preparation context = new Preparation
			{
				Round = round,
				Generator = generator
			};
			preparation = context;
			return CoroutineGuard.Run(PrepareCore(context), delegate(Exception exception)
			{
				failedThisSession = true;
				Cleanup(context);
				log.LogWarning((object)$"Loadstone async preparation failed; normal completion will rebuild synchronously: {exception.GetBaseException()}");
			}, delegate
			{
				if (!context.Ready)
				{
					Cleanup(context);
				}
			});
		}

		private static bool Current(Preparation context)
		{
			if (preparation == context)
			{
				object obj = context.Round;
				Object val = (Object)((obj is Object) ? obj : null);
				if (val != null && Object.op_Implicit(val))
				{
					return bindings.GetGenerator(context.Round) == context.Generator;
				}
			}
			return false;
		}

		private static IEnumerator PrepareCore(Preparation context)
		{
			yield return null;
			if (!Current(context))
			{
				yield break;
			}
			Stopwatch timer = Stopwatch.StartNew();
			capturing = true;
			try
			{
				InvokeNativeBake(context.Round);
			}
			finally
			{
				capturing = false;
			}
			foreach (SurfaceJob job in context.Jobs)
			{
				yield return null;
				if (!Current(context))
				{
					yield break;
				}
				if (!Object.op_Implicit((Object)(object)job.Surface))
				{
					throw new InvalidOperationException("Navmesh surface was destroyed during preparation.");
				}
				NavMeshBuildSettings buildSettings = job.Surface.GetBuildSettings();
				job.Data = new NavMeshData(((NavMeshBuildSettings)(ref buildSettings)).agentTypeID)
				{
					position = ((Component)job.Surface).transform.position,
					rotation = ((Component)job.Surface).transform.rotation,
					name = ((Object)((Component)job.Surface).gameObject).name
				};
				job.Operation = job.Surface.UpdateNavMesh(job.Data) ?? throw new InvalidOperationException("UpdateNavMesh returned no operation.");
				Stopwatch timeout = Stopwatch.StartNew();
				while (!job.Operation.isDone)
				{
					if (!Current(context))
					{
						yield break;
					}
					if (timeout.Elapsed.TotalSeconds > 60.0)
					{
						throw new TimeoutException("Async navmesh exceeded 60 seconds.");
					}
					yield return null;
				}
				if (!Current(context) || !Object.op_Implicit((Object)(object)job.Surface))
				{
					yield break;
				}
				job.Surface.RemoveData();
				job.Surface.navMeshData = job.Data;
				if (((Behaviour)job.Surface).isActiveAndEnabled)
				{
					job.Surface.AddData();
					NavMeshDataInstance val = (NavMeshDataInstance)dataInstanceGetter.Invoke(job.Surface, null);
					if (!((NavMeshDataInstance)(ref val)).valid)
					{
						throw new InvalidOperationException("Async navmesh data could not be registered on its active surface.");
					}
				}
			}
			if (Current(context))
			{
				context.Ready = true;
				log.LogInfo((object)$"Loadstone async interior navmesh ready: {context.Jobs.Count} surfaces, {timer.Elapsed.TotalMilliseconds:F1} ms elapsed including frame yields; native completion resumes.");
			}
		}

		private static bool CaptureBuild(NavMeshSurface __instance)
		{
			if (!capturing || preparation == null)
			{
				return true;
			}
			if (!preparation.Jobs.Any((SurfaceJob job) => job.Surface == __instance))
			{
				preparation.Jobs.Add(new SurfaceJob
				{
					Surface = __instance
				});
			}
			return false;
		}

		internal static void BakeForFinish(object instance)
		{
			Preparation preparation = AsyncNavmesh.preparation;
			bool flag = preparation != null && preparation.Ready && preparation.Round == instance && Current(preparation);
			if (flag)
			{
				List<NavMeshSurface> surfaces = (List<NavMeshSurface>)fullSurfaces.GetValue(instance);
				flag = surfaces.Count == preparation.Jobs.Count && preparation.Jobs.All((SurfaceJob job) => Object.op_Implicit((Object)(object)job.Surface) && job.Surface.navMeshData == job.Data && surfaces.Contains(job.Surface));
			}
			if (flag)
			{
				preparation.Transferred = true;
				AsyncNavmesh.preparation = null;
				return;
			}
			if (preparation != null)
			{
				Cleanup(preparation);
			}
			InvokeNativeBake(instance);
		}

		private static void InvokeNativeBake(object instance)
		{
			try
			{
				bake.Invoke(instance, null);
			}
			catch (TargetInvocationException ex) when (ex.InnerException != null)
			{
				ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
				throw;
			}
		}

		internal static void Cancel()
		{
			if (preparation != null)
			{
				Cleanup(preparation);
			}
			round = null;
			capturing = false;
		}

		private static void BeforeGeneratorClear(object __instance)
		{
			if (preparation != null && preparation.Generator == __instance)
			{
				Cancel();
			}
		}

		private static void BeforeRoundDestroyed(object __instance)
		{
			if (round == __instance)
			{
				Cancel();
			}
		}

		private static void Cleanup(Preparation context)
		{
			if (context.Cleaned || context.Transferred)
			{
				return;
			}
			context.Cleaned = true;
			if (preparation == context)
			{
				preparation = null;
			}
			foreach (SurfaceJob job in context.Jobs)
			{
				try
				{
					if (Object.op_Implicit((Object)(object)job.Data))
					{
						if (job.Operation != null && !job.Operation.isDone)
						{
							NavMeshBuilder.Cancel(job.Data);
						}
						if (Object.op_Implicit((Object)(object)job.Surface) && job.Surface.navMeshData == job.Data)
						{
							job.Surface.RemoveData();
							job.Surface.navMeshData = null;
						}
						Object.Destroy((Object)(object)job.Data);
					}
				}
				catch (Exception ex)
				{
					log.LogWarning((object)("Loadstone navmesh cleanup: " + ex.Message));
				}
			}
		}

		internal static void Uninstall()
		{
			installed = false;
			Cancel();
			if (barrierInFlight)
			{
				ManualLogSource obj = log;
				if (obj != null)
				{
					obj.LogWarning((object)"Loadstone unloaded during a completion barrier; inert async hooks retained until restart so the existing iterator can resume safely.");
				}
			}
			else
			{
				Harmony obj2 = harmony;
				if (obj2 != null)
				{
					obj2.UnpatchSelf();
				}
			}
		}

		private static void BarrierResumed()
		{
			barrierInFlight = false;
		}

		private static MethodInfo Hook(string name)
		{
			return typeof(AsyncNavmesh).GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		}

		private static IEnumerable<CodeInstruction> FinishBakeCall(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			CodeInstruction[] array = list.Where((CodeInstruction i) => CodeInstructionExtensions.Calls(i, bake)).ToArray();
			if (array.Length != 1)
			{
				throw new InvalidOperationException("Expected exactly one native interior bake call.");
			}
			array[0].opcode = OpCodes.Call;
			array[0].operand = Hook("BakeForFinish");
			return list;
		}

		private static IList Labels(CodeInstruction instruction)
		{
			return (IList)typeof(CodeInstruction).GetField("labels").GetValue(instruction);
		}

		private static IEnumerable<CodeInstruction> CompletionBarrier(IEnumerable<CodeInstruction> instructions, ILGenerator generator, MethodBase __originalMethod)
		{
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Expected O, but got Unknown
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Expected O, but got Unknown
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Expected O, but got Unknown
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_030d: Expected O, but got Unknown
			//IL_0315: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Expected O, but got Unknown
			//IL_0323: Unknown result type (might be due to invalid IL or missing references)
			//IL_0329: Expected O, but got Unknown
			//IL_033a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Expected O, but got Unknown
			//IL_0349: Unknown result type (might be due to invalid IL or missing references)
			//IL_034f: Expected O, but got Unknown
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_035d: Expected O, but got Unknown
			//IL_036c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0372: Expected O, but got Unknown
			//IL_037c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0382: Expected O, but got Unknown
			//IL_038b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0391: Expected O, but got Unknown
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a0: Expected O, but got Unknown
			//IL_03af: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b5: Expected O, but got Unknown
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ca: Expected O, but got Unknown
			//IL_03d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03da: Expected O, but got Unknown
			//IL_03ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Expected O, but got Unknown
			List<CodeInstruction> code = instructions.ToList();
			MethodBody methodBody = __originalMethod.GetMethodBody();
			CodeInstruction val = code.Single((CodeInstruction i) => i.opcode == OpCodes.Switch);
			if (!(val.operand is Label[] array) || methodBody.LocalVariables.Count < 3 || methodBody.LocalVariables[0].LocalType != typeof(bool) || methodBody.LocalVariables[2].LocalType != changeStatus.DeclaringType)
			{
				throw new InvalidOperationException("Unsupported DunGen iterator local/switch layout.");
			}
			int[] array2 = (from i in Enumerable.Range(2, code.Count - 2)
				where CodeInstructionExtensions.Calls(code[i], changeStatus) && code[i - 1].opcode == OpCodes.Ldc_I4_8 && code[i - 2].opcode == OpCodes.Ldloc_2
				select i).ToArray();
			if (array2.Length != 1)
			{
				throw new InvalidOperationException("Expected one final ChangeStatus(Complete) call.");
			}
			int index = array2[0] - 2;
			CodeInstruction val2 = code[index];
			if (val2.blocks.Count != 0)
			{
				throw new InvalidOperationException("Completion anchor crosses an exception boundary.");
			}
			FieldInfo[] source = (from i in code
				where i.opcode == OpCodes.Stfld
				select i.operand).OfType<FieldInfo>().ToArray();
			FieldInfo fieldInfo = source.First((FieldInfo f) => f.Name == "<>1__state" && f.DeclaringType == __originalMethod.DeclaringType);
			FieldInfo fieldInfo2 = source.First((FieldInfo f) => f.Name == "<>2__current" && f.DeclaringType == __originalMethod.DeclaringType);
			int num = code.FindLastIndex((CodeInstruction i) => i.opcode == OpCodes.Ret);
			if (num < 1 || code[num - 1].opcode != OpCodes.Ldloc_0)
			{
				throw new InvalidOperationException("Unsupported DunGen iterator return layout.");
			}
			Label label = generator.DefineLabel();
			Labels(code[num - 1]).Add(label);
			Label label2 = generator.DefineLabel();
			Label label3 = generator.DefineLabel();
			CodeInstruction val3 = new CodeInstruction(OpCodes.Ldloc_2, (object)null);
			foreach (object item in Labels(val2))
			{
				Labels(val3).Add(item);
			}
			Labels(val2).Clear();
			Labels(val2).Add(label3);
			CodeInstruction val4 = new CodeInstruction(OpCodes.Ldarg_0, (object)null);
			Labels(val4).Add(label2);
			code.InsertRange(index, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[17]
			{
				val3,
				new CodeInstruction(OpCodes.Call, (object)Hook("ShouldPrepare")),
				new CodeInstruction(OpCodes.Brfalse, (object)label3),
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Ldloc_2, (object)null),
				new CodeInstruction(OpCodes.Call, (object)Hook("Prepare")),
				new CodeInstruction(OpCodes.Stfld, (object)fieldInfo2),
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Ldc_I4, (object)array.Length),
				new CodeInstruction(OpCodes.Stfld, (object)fieldInfo),
				new CodeInstruction(OpCodes.Ldc_I4_1, (object)null),
				new CodeInstruction(OpCodes.Stloc_0, (object)null),
				new CodeInstruction(OpCodes.Leave, (object)label),
				val4,
				new CodeInstruction(OpCodes.Ldc_I4_M1, (object)null),
				new CodeInstruction(OpCodes.Stfld, (object)fieldInfo),
				new CodeInstruction(OpCodes.Call, (object)Hook("BarrierResumed"))
			});
			val.operand = array.Concat(new Label[1] { label2 }).ToArray();
			Dictionary<string, OpCode> dictionary = (from f in typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public)
				where f.FieldType == typeof(OpCode)
				select (OpCode)f.GetValue(null)).ToDictionary((OpCode op) => op.Name);
			foreach (CodeInstruction item2 in code)
			{
				if (item2.opcode.OperandType == OperandType.ShortInlineBrTarget)
				{
					item2.opcode = dictionary[item2.opcode.Name.Substring(0, item2.opcode.Name.Length - 2)];
				}
			}
			return code;
		}
	}
	internal static class CoroutineGuard
	{
		internal static IEnumerator Run(IEnumerator inner, Action<Exception> failed, Action cleanup)
		{
			try
			{
				while (true)
				{
					bool flag = false;
					object obj = null;
					Exception ex = null;
					try
					{
						flag = inner.MoveNext();
						if (flag)
						{
							obj = inner.Current;
						}
					}
					catch (Exception ex2)
					{
						ex = ex2;
					}
					if (ex != null)
					{
						failed(ex);
						break;
					}
					if (!flag)
					{
						break;
					}
					yield return obj;
				}
			}
			finally
			{
				try
				{
					(inner as IDisposable)?.Dispose();
				}
				finally
				{
					cleanup();
				}
			}
		}
	}
	internal interface IGenerationSettings
	{
		object Identity { get; }

		bool Asynchronous { get; }

		float FrameMilliseconds { get; set; }
	}
	internal sealed class GenerationBudget
	{
		private sealed class Snapshot
		{
			internal IGenerationSettings Settings;

			internal float Original;

			internal float Applied;
		}

		private sealed class ReferenceComparer : IEqualityComparer<object>
		{
			internal static readonly ReferenceComparer Instance = new ReferenceComparer();

			public new bool Equals(object x, object y)
			{
				return x == y;
			}

			public int GetHashCode(object value)
			{
				return RuntimeHelpers.GetHashCode(value);
			}
		}

		private readonly Dictionary<object, Snapshot> active = new Dictionary<object, Snapshot>(ReferenceComparer.Instance);

		internal int ActiveCount => active.Count;

		internal bool Begin(IGenerationSettings settings, int totalPlayers, bool enabled, float targetMilliseconds)
		{
			if (settings == null || settings.Identity == null)
			{
				return false;
			}
			Restore(settings.Identity);
			if (!enabled || totalPlayers <= 5 || !settings.Asynchronous || float.IsNaN(targetMilliseconds) || float.IsInfinity(targetMilliseconds) || targetMilliseconds < 1f || targetMilliseconds > 16f)
			{
				return false;
			}
			float frameMilliseconds = settings.FrameMilliseconds;
			if (float.IsNaN(frameMilliseconds) || float.IsInfinity(frameMilliseconds) || frameMilliseconds <= 0f || frameMilliseconds >= targetMilliseconds)
			{
				return false;
			}
			Snapshot value = new Snapshot
			{
				Settings = settings,
				Original = frameMilliseconds,
				Applied = targetMilliseconds
			};
			active.Add(settings.Identity, value);
			settings.FrameMilliseconds = targetMilliseconds;
			return true;
		}

		internal void Restore(object identity)
		{
			if (identity != null && active.TryGetValue(identity, out var value))
			{
				active.Remove(identity);
				if (value.Settings.FrameMilliseconds == value.Applied)
				{
					value.Settings.FrameMilliseconds = value.Original;
				}
			}
		}

		internal void RestoreAll(Action<Exception> reportError)
		{
			foreach (object item in new List<object>(active.Keys))
			{
				try
				{
					Restore(item);
				}
				catch (Exception obj)
				{
					reportError(obj);
				}
			}
		}
	}
	internal sealed class LoadingDiagnostics
	{
		private long started;

		internal bool Active { get; private set; }

		internal int Stalls { get; private set; }

		internal double WorstFrameMilliseconds { get; private set; }

		internal void Begin()
		{
			Active = true;
			Stalls = 0;
			WorstFrameMilliseconds = 0.0;
			started = Stopwatch.GetTimestamp();
		}

		internal void ObserveFrame(double milliseconds)
		{
			if (Active && !double.IsNaN(milliseconds) && !double.IsInfinity(milliseconds) && !(milliseconds < 0.0))
			{
				WorstFrameMilliseconds = Math.Max(WorstFrameMilliseconds, milliseconds);
				if (milliseconds >= 200.0)
				{
					Stalls++;
				}
			}
		}

		internal string End()
		{
			if (!Active)
			{
				return null;
			}
			Active = false;
			double num = ElapsedMilliseconds(started);
			return $"Local loading window: {num:F1} ms; frames >=200 ms: {Stalls}; worst sampled frame: {WorstFrameMilliseconds:F1} ms. Includes waits, not just CPU work.";
		}

		internal static double ElapsedMilliseconds(long timestamp)
		{
			return (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency;
		}
	}
	[BepInPlugin("com.adibtw.loadstone", "Loadstone-FIX", "0.3.1")]
	public sealed class LoadstonePlugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static LogCallback <0>__OnUnityMessage;

			public static Action<Exception> <1>__ReportCleanupError;
		}

		public const string Guid = "com.adibtw.loadstone";

		private static readonly GenerationBudget Budget = new GenerationBudget();

		private static readonly LoadingDiagnostics Diagnostics = new LoadingDiagnostics();

		private static RuntimeBindings bindings;

		private static ManualLogSource log;

		private static ConfigEntry<bool> largeLobbyEnabled;

		private static ConfigEntry<float> frameBudget;

		private static ConfigEntry<bool> diagnosticsEnabled;

		private static ConfigEntry<bool> asyncInteriorNavmesh;

		private static bool pictureErrorReported;

		private Harmony harmony;

		private bool initialized;

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Expected O, but got Unknown
			//IL_014f: Expected O, but got Unknown
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Expected O, but got Unknown
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Expected O, but got Unknown
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Expected O, but got Unknown
			log = ((BaseUnityPlugin)this).Logger;
			largeLobbyEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("LargeLobby", "Enabled", false, "Use the native async generator's larger frame budget when more than five players are connected. Changes apply on the next generation.");
			frameBudget = ((BaseUnityPlugin)this).Config.Bind<float>("LargeLobby", "DungeonFrameMilliseconds", 4f, new ConfigDescription("Native dungeon work budget per frame for 6+ players. Higher values may reduce generation time but leave less time for voice/network/rendering. Default 4 ms; no measured speedup is claimed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 16f), Array.Empty<object>()));
			diagnosticsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Enabled", true, "Log inclusive loading-stage timings and one frame-stall summary per local load. Does not suppress existing errors or change navmesh generation.");
			asyncInteriorNavmesh = ((BaseUnityPlugin)this).Config.Bind<bool>("AsyncInteriorNavmesh", "Enabled", true, "For 6+ connected players, calculate the interior navmesh asynchronously before native generation completion. Falls back to native loading on unsupported IL or detected Harmony conflicts. Geometry collection and outside navmesh remain synchronous.");
			try
			{
				bindings = new RuntimeBindings(AccessTools.TypeByName("RoundManager") ?? throw new TypeLoadException("RoundManager"), AccessTools.TypeByName("StartOfRound") ?? throw new TypeLoadException("StartOfRound"), AccessTools.TypeByName("GameNetworkManager") ?? throw new TypeLoadException("GameNetworkManager"));
				harmony = new Harmony("com.adibtw.loadstone");
				harmony.Patch((MethodBase)bindings.GenerateNewFloor, new HarmonyMethod(typeof(LoadstonePlugin), "BeforeGenerate", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(LoadstonePlugin), "AfterGenerateException", (Type[])null), (HarmonyMethod)null);
				harmony.Patch((MethodBase)bindings.FinishGeneratingLevel, (HarmonyMethod)null, new HarmonyMethod(typeof(LoadstonePlugin), "AfterFinish", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)bindings.Disconnect, (HarmonyMethod)null, new HarmonyMethod(typeof(LoadstonePlugin), "AfterDisconnect", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				InstallDiagnostics();
				object obj = <>O.<0>__OnUnityMessage;
				if (obj == null)
				{
					LogCallback val = OnUnityMessage;
					<>O.<0>__OnUnityMessage = val;
					obj = (object)val;
				}
				Application.logMessageReceived += (LogCallback)obj;
				initialized = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Loadstone-FIX 0.3.1 loaded. Higher dungeon budget is opt-in; async interior navmesh installs after plugin startup. Legacy Loadstone patches are excluded.");
			}
			catch (Exception arg)
			{
				Harmony obj2 = harmony;
				if (obj2 != null)
				{
					obj2.UnpatchSelf();
				}
				((BaseUnityPlugin)this).Logger.LogError((object)$"Loadstone-FIX could not bind the game's loading API; optimization disabled. {arg}");
			}
		}

		private void Start()
		{
			if (initialized)
			{
				AsyncNavmesh.Install(bindings, log, () => asyncInteriorNavmesh.Value);
			}
		}

		[HarmonyPriority(0)]
		private static void BeforeGenerate(object __instance)
		{
			AsyncNavmesh.BeginRound(__instance);
			if (diagnosticsEnabled.Value)
			{
				Diagnostics.Begin();
			}
			try
			{
				object generator = bindings.GetGenerator(__instance);
				int totalPlayers = bindings.GetTotalPlayers(__instance);
				if (Budget.Begin(bindings.SettingsFor(generator), totalPlayers, largeLobbyEnabled.Value, frameBudget.Value))
				{
					log.LogInfo((object)$"Loadstone: {totalPlayers} players, native dungeon budget {frameBudget.Value} ms/frame.");
				}
			}
			catch (Exception arg)
			{
				Budget.RestoreAll(ReportCleanupError);
				log.LogWarning((object)$"Loadstone could not adjust this generation; game continues. {arg}");
			}
		}

		private static Exception AfterGenerateException(object __instance, Exception __exception)
		{
			if (__exception != null)
			{
				RestoreRound(__instance);
			}
			return __exception;
		}

		private static void AfterFinish(object __instance)
		{
			RestoreRound(__instance);
		}

		private static void AfterDisconnect()
		{
			AsyncNavmesh.Cancel();
			Budget.RestoreAll(ReportCleanupError);
			ReportLoadingSummary();
		}

		private void InstallDiagnostics()
		{
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Expected O, but got Unknown
			//IL_0188: Expected O, but got Unknown
			(string, string)[] array = new(string, string)[10]
			{
				("RoundManager", "GenerateNewFloor"),
				("RoundManager", "FinishGeneratingLevel"),
				("RoundManager", "BakeDunGenNavMesh"),
				("RoundManager", "SpawnOutsideHazards"),
				("RoundManager", "SetLevelObjectVariables"),
				("RoundManager", "SpawnScrapInLevel"),
				("RoundManager", "RefreshEnemiesList"),
				("Unity.AI.Navigation.NavMeshSurface", "BuildNavMesh"),
				("Unity.AI.Navigation.NavMeshSurface", "CollectSources"),
				("DunGen.Dungeon", "FromProxy")
			};
			for (int i = 0; i < array.Length; i++)
			{
				(string, string) tuple = array[i];
				Type type = AccessTools.TypeByName(tuple.Item1);
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.DeclaredMethod(type, tuple.Item2, (Type[])null, (Type[])null));
				if (methodInfo == null)
				{
					log.LogWarning((object)("Loadstone diagnostic target unavailable: " + tuple.Item1 + "." + tuple.Item2));
					continue;
				}
				try
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(LoadstonePlugin), "BeforeStage", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(LoadstonePlugin), "AfterStage", (Type[])null), (HarmonyMethod)null);
				}
				catch (Exception ex)
				{
					log.LogWarning((object)$"Loadstone diagnostic hook unavailable for {methodInfo}: {ex.Message}");
				}
			}
		}

		private static void BeforeStage(out long __state)
		{
			__state = (diagnosticsEnabled.Value ? Stopwatch.GetTimestamp() : 0);
		}

		private static Exception AfterStage(MethodBase __originalMethod, long __state, Exception __exception)
		{
			if (__state != 0L)
			{
				double num = LoadingDiagnostics.ElapsedMilliseconds(__state);
				if (Diagnostics.Active && (num >= 100.0 || __originalMethod.DeclaringType.Name == "RoundManager"))
				{
					log.LogInfo((object)$"Loadstone stage {__originalMethod.DeclaringType.FullName}.{__originalMethod.Name}: {num:F1} ms inclusive; exception={__exception != null}");
				}
				if (__originalMethod.Name == "RefreshEnemiesList" || (__originalMethod.Name == "GenerateNewFloor" && __exception != null))
				{
					ReportLoadingSummary();
				}
			}
			return __exception;
		}

		private void Update()
		{
			if (diagnosticsEnabled != null && diagnosticsEnabled.Value && Diagnostics.Active)
			{
				Diagnostics.ObserveFrame((double)Time.unscaledDeltaTime * 1000.0);
			}
		}

		private static void ReportLoadingSummary()
		{
			string text = Diagnostics.End();
			if (text != null)
			{
				log.LogInfo((object)("Loadstone " + text));
			}
		}

		private static void OnUnityMessage(string condition, string stackTrace, LogType type)
		{
			if (!pictureErrorReported && diagnosticsEnabled.Value && !(condition != "Cannot set picture material"))
			{
				pictureErrorReported = true;
				log.LogWarning((object)("Loadstone: first picture-material error context (original errors remain visible): " + (string.IsNullOrEmpty(stackTrace) ? ("Unity supplied no caller stack; logging callback stack follows: " + new StackTrace(1, fNeedFileInfo: true)) : stackTrace)));
			}
		}

		private static void RestoreRound(object round)
		{
			try
			{
				Budget.Restore(bindings.GetGenerator(round));
			}
			catch (Exception arg)
			{
				log.LogWarning((object)$"Loadstone budget cleanup failed: {arg}");
			}
		}

		private static void ReportCleanupError(Exception exception)
		{
			log.LogWarning((object)$"Loadstone budget cleanup failed: {exception}");
		}

		private void OnDestroy()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			object obj = <>O.<0>__OnUnityMessage;
			if (obj == null)
			{
				LogCallback val = OnUnityMessage;
				<>O.<0>__OnUnityMessage = val;
				obj = (object)val;
			}
			Application.logMessageReceived -= (LogCallback)obj;
			AsyncNavmesh.Uninstall();
			Budget.RestoreAll(ReportCleanupError);
			ReportLoadingSummary();
			Harmony obj2 = harmony;
			if (obj2 != null)
			{
				obj2.UnpatchSelf();
			}
		}
	}
	internal sealed class RuntimeBindings
	{
		private sealed class ReflectedSettings : IGenerationSettings
		{
			private readonly FieldInfo asynchronous;

			private readonly FieldInfo milliseconds;

			public object Identity { get; }

			public bool Asynchronous => (bool)asynchronous.GetValue(Identity);

			public float FrameMilliseconds
			{
				get
				{
					return (float)milliseconds.GetValue(Identity);
				}
				set
				{
					milliseconds.SetValue(Identity, value);
				}
			}

			internal ReflectedSettings(object generator, FieldInfo asyncField, FieldInfo budgetField)
			{
				Identity = generator;
				asynchronous = asyncField;
				milliseconds = budgetField;
			}
		}

		internal readonly MethodInfo GenerateNewFloor;

		internal readonly MethodInfo FinishGeneratingLevel;

		internal readonly MethodInfo Disconnect;

		private readonly FieldInfo dungeon;

		private readonly FieldInfo generator;

		private readonly FieldInfo playersManager;

		private readonly FieldInfo connectedPlayers;

		private readonly FieldInfo asynchronous;

		private readonly FieldInfo milliseconds;

		internal RuntimeBindings(Type roundType, Type startType, Type networkType)
		{
			GenerateNewFloor = RequireMethod(roundType, "GenerateNewFloor");
			FinishGeneratingLevel = RequireMethod(roundType, "FinishGeneratingLevel");
			Disconnect = RequireMethod(networkType, "SetInstanceValuesBackToDefault");
			dungeon = RequireField(roundType, "dungeonGenerator");
			generator = RequireField(dungeon.FieldType, "Generator");
			playersManager = RequireField(roundType, "playersManager", startType);
			connectedPlayers = RequireField(startType, "connectedPlayersAmount", typeof(int));
			asynchronous = RequireField(generator.FieldType, "GenerateAsynchronously", typeof(bool));
			milliseconds = RequireField(generator.FieldType, "MaxAsyncFrameMilliseconds", typeof(float));
			if (milliseconds.IsInitOnly || milliseconds.IsStatic)
			{
				throw new NotSupportedException("Dungeon frame budget is not a writable instance field.");
			}
		}

		internal object GetGenerator(object round)
		{
			object value = dungeon.GetValue(round);
			if (value != null)
			{
				return generator.GetValue(value);
			}
			return null;
		}

		internal int GetTotalPlayers(object round)
		{
			object value = playersManager.GetValue(round);
			if (value == null)
			{
				return 1;
			}
			int num = (int)connectedPlayers.GetValue(value);
			if (num >= 0)
			{
				if (num != int.MaxValue)
				{
					return num + 1;
				}
				return int.MaxValue;
			}
			return 1;
		}

		internal IGenerationSettings SettingsFor(object value)
		{
			if (value != null)
			{
				return new ReflectedSettings(value, asynchronous, milliseconds);
			}
			return null;
		}

		private static FieldInfo RequireField(Type type, string name, Type expected = null)
		{
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null || (expected != null && field.FieldType != expected))
			{
				throw new MissingFieldException(type.FullName, name);
			}
			return field;
		}

		private static MethodInfo RequireMethod(Type type, string name)
		{
			return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) ?? throw new MissingMethodException(type.FullName, name);
		}
	}
}