Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Loadstone FIX v0.3.1
com.adibtw.loadstone.dll
Decompiled 2 hours agousing 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); } } }