Decompiled source of Fast Load v1.0.1

FastLoad.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Xml.Serialization;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("FastLoad")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("FastLoad")]
[assembly: AssemblyTitle("FastLoad")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace FastLoad;

[HarmonyPatch]
internal static class Cleanup
{
	private static bool s_transitionCleanupDone;

	private static AsyncOperation s_pending;

	private static string s_pendingWhere;

	private static readonly Stopwatch s_pendingWatch = new Stopwatch();

	private static int s_pendingFrames;

	private static readonly MethodInfo s_unload = AccessTools.Method(typeof(Resources), "UnloadUnusedAssets", (Type[])null, (Type[])null);

	private static readonly MethodInfo s_collect = AccessTools.Method(typeof(GC), "Collect", Type.EmptyTypes, (Type[])null);

	internal static bool SkipSecond => Plugin.On(Plugin.SkipSecondCleanup, "gc");

	public static AsyncOperation UnloadTransition()
	{
		return Unload("transition scene");
	}

	public static AsyncOperation UnloadFinish()
	{
		if (SkipSecond && s_transitionCleanupDone)
		{
			Plugin.Log.LogInfo((object)"Cleanup: second UnloadUnusedAssets skipped (already done in the transition scene)");
			return null;
		}
		return Unload("new area");
	}

	public static void CollectTransition()
	{
		Collect("transition scene");
		s_transitionCleanupDone = true;
	}

	public static void CollectFinish()
	{
		if (SkipSecond && s_transitionCleanupDone)
		{
			s_transitionCleanupDone = false;
			return;
		}
		s_transitionCleanupDone = false;
		Collect("new area");
	}

	private static AsyncOperation Unload(string where)
	{
		Stopwatch stopwatch = Stopwatch.StartNew();
		AsyncOperation result = (s_pending = Resources.UnloadUnusedAssets());
		s_pendingWhere = where;
		s_pendingFrames = 0;
		s_pendingWatch.Restart();
		Plugin.Log.LogInfo((object)$"Cleanup ({where}): UnloadUnusedAssets started, {stopwatch.Elapsed.TotalMilliseconds:F0} ms in the call");
		return result;
	}

	private static void Collect(string where)
	{
		Stopwatch stopwatch = Stopwatch.StartNew();
		GC.Collect();
		Plugin.Log.LogInfo((object)$"Cleanup ({where}): GC.Collect {stopwatch.Elapsed.TotalMilliseconds:F0} ms");
	}

	internal static void Tick()
	{
		if (s_pending != null)
		{
			s_pendingFrames++;
			if (s_pending.isDone)
			{
				Plugin.Log.LogInfo((object)$"Cleanup ({s_pendingWhere}): UnloadUnusedAssets done after {s_pendingWatch.ElapsedMilliseconds} ms / {s_pendingFrames} frames");
				s_pending = null;
			}
		}
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(NetworkLevelLoader), "BaseLoadLevel")]
	private static void NewLoad()
	{
		s_transitionCleanupDone = false;
	}

	private static IEnumerable<CodeInstruction> Replace(IEnumerable<CodeInstruction> instructions, string unload, string collect, string where)
	{
		List<CodeInstruction> list = instructions.ToList();
		int num = 0;
		int num2 = 0;
		foreach (CodeInstruction item in list)
		{
			if (CodeInstructionExtensions.Calls(item, s_unload))
			{
				item.operand = AccessTools.Method(typeof(Cleanup), unload, (Type[])null, (Type[])null);
				num++;
			}
			else if (CodeInstructionExtensions.Calls(item, s_collect))
			{
				item.operand = AccessTools.Method(typeof(Cleanup), collect, (Type[])null, (Type[])null);
				num2++;
			}
		}
		if (num != 1 || num2 != 1)
		{
			Plugin.Log.LogWarning((object)$"Cleanup: {where} has {num} UnloadUnusedAssets / {num2} GC.Collect calls, expected 1/1");
		}
		return list;
	}

	[HarmonyTranspiler]
	[HarmonyPatch(typeof(NetworkLevelLoader), "LevelDoneLoading")]
	private static IEnumerable<CodeInstruction> TransitionCleanup(IEnumerable<CodeInstruction> instructions)
	{
		return Replace(instructions, "UnloadTransition", "CollectTransition", "LevelDoneLoading");
	}

	internal static void PatchFinish(Harmony harmony)
	{
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Expected O, but got Unknown
		Type type = AccessTools.Method(typeof(NetworkLevelLoader), "FinishLoadLevel", (Type[])null, (Type[])null)?.GetCustomAttribute<IteratorStateMachineAttribute>()?.StateMachineType;
		MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null) : null);
		if (methodInfo == null)
		{
			Plugin.Log.LogWarning((object)"Cleanup: FinishLoadLevel not found");
		}
		else
		{
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Cleanup), "FinishTranspiler", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null);
		}
	}

	private static IEnumerable<CodeInstruction> FinishTranspiler(IEnumerable<CodeInstruction> instructions)
	{
		return Replace(instructions, "UnloadFinish", "CollectFinish", "FinishLoadLevel");
	}
}
[BepInPlugin("tchernobill.fastload", "FastLoad", "1.0.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string GUID = "tchernobill.fastload";

	public const string NAME = "FastLoad";

	public const string VERSION = "1.0.1";

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> RemoveWaits;

	internal static ConfigEntry<bool> HighLoadingPriority;

	internal static ConfigEntry<bool> SkipLowMemoryScene;

	internal static ConfigEntry<bool> SlTextures;

	internal static ConfigEntry<bool> SlXml;

	internal static ConfigEntry<bool> SlBundles;

	internal static ConfigEntry<bool> SlVerify;

	internal static ConfigEntry<bool> SkipSecondCleanup;

	internal static ConfigEntry<float> WaitScale;

	private static HashSet<string> s_cli;

	private float m_nextTick;

	internal static bool On(ConfigEntry<bool> entry, string lever)
	{
		if (s_cli == null)
		{
			return entry.Value;
		}
		return s_cli.Contains(lever);
	}

	internal static bool Cli(string lever)
	{
		if (s_cli != null)
		{
			return s_cli.Contains(lever);
		}
		return false;
	}

	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_01df: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fa: Expected O, but got Unknown
		//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_020a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0210: Expected O, but got Unknown
		//IL_0215: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		RemoveWaits = ((BaseUnityPlugin)this).Config.Bind<bool>("Loading", "Remove fixed waits", true, "Shortens the fixed WaitForSeconds/Invoke delays of the level loader (about 8 s per load).");
		WaitScale = ((BaseUnityPlugin)this).Config.Bind<float>("Loading", "Fixed wait scale", 0f, new ConfigDescription("Multiplier applied to those delays. 0 = a single frame, 1 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		HighLoadingPriority = ((BaseUnityPlugin)this).Config.Bind<bool>("Loading", "High loading priority", true, "While the loading screen is up, let Unity spend up to 50 ms per frame integrating loaded assets (vanilla: about 4 ms).");
		SkipLowMemoryScene = ((BaseUnityPlugin)this).Config.Bind<bool>("Loading", "Skip low-memory transition scene", false, "Vanilla loads an empty scene between two areas so both are never in memory together. Skipping it is faster but raises the memory peak during loads: keep it off on PCs with 8 GB of RAM or less.");
		SkipSecondCleanup = ((BaseUnityPlugin)this).Config.Bind<bool>("Loading", "Skip second memory cleanup", true, "The loader frees unused assets and runs the garbage collector in the low-memory transition scene, then again once the new area is loaded. Skip the second pass when the first one ran in the same load (about 0.7 s per load; measured: no difference in memory 5 s after the load).");
		SlTextures = ((BaseUnityPlugin)this).Config.Bind<bool>("SideLoader start-up", "Decode textures in background", true, "Decode the PNG textures of SideLoader packs on worker threads while the game starts.");
		SlXml = ((BaseUnityPlugin)this).Config.Bind<bool>("SideLoader start-up", "Build XML serializers in background", true, "Build the XmlSerializers SideLoader needed at the previous start on worker threads. The first start records them.");
		SlBundles = ((BaseUnityPlugin)this).Config.Bind<bool>("SideLoader start-up", "Open asset bundles asynchronously", true, "Start opening SideLoader packs' asset bundles as soon as every plugin is loaded.");
		SlVerify = ((BaseUnityPlugin)this).Config.Bind<bool>("SideLoader start-up", "Verify decoded textures", false, "Debug: also decode each texture the vanilla way and log whether the bytes are identical.");
		string[] commandLineArgs = Environment.GetCommandLineArgs();
		for (int i = 0; i < commandLineArgs.Length - 1; i++)
		{
			if (commandLineArgs[i] == "--fastload")
			{
				s_cli = new HashSet<string>(from x in commandLineArgs[i + 1].Split(new char[1] { ',' })
					select x.Trim().ToLowerInvariant());
			}
		}
		if (s_cli != null)
		{
			Log.LogMessage((object)("Levers from the command line: " + string.Join(",", s_cli)));
		}
		Harmony val = new Harmony("tchernobill.fastload");
		val.PatchAll(typeof(LoaderPatches));
		LoaderPatches.ApplyWaitTranspilers(val);
		val.PatchAll(typeof(Cleanup));
		Cleanup.PatchFinish(val);
		SideLoaderBoost.Init(val);
	}

	private void Start()
	{
		SideLoaderBoost.StartPrefetch();
	}

	private void Update()
	{
		Cleanup.Tick();
		if (!(Time.unscaledTime < m_nextTick))
		{
			m_nextTick = Time.unscaledTime + 1f;
			SideLoaderBoost.Tick();
		}
	}
}
[HarmonyPatch]
internal static class LoaderPatches
{
	private static ThreadPriority s_savedPriority;

	private static bool s_priorityRaised;

	private static bool s_firstLoadSeen;

	private static readonly float[] s_finishWaits = new float[6] { 0.5f, 0.5f, 0.5f, 0.5f, 1.5f, 0.5f };

	private const int FirstAfterSpawn = 2;

	private const int LastAfterSpawn = 4;

	private const int DefeatSetup = 3;

	internal static bool s_finishLayoutOk;

	private static readonly ConstructorInfo s_waitCtor = AccessTools.Constructor(typeof(WaitForSeconds), new Type[1] { typeof(float) }, false);

	public static object Wait(float seconds)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Expected O, but got Unknown
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Expected O, but got Unknown
		if (!Plugin.On(Plugin.RemoveWaits, "waits"))
		{
			return (object)new WaitForSeconds(seconds);
		}
		float num = seconds * Plugin.WaitScale.Value;
		if (!(num > 0f))
		{
			return null;
		}
		return (object)new WaitForSeconds(num);
	}

	public static object WaitFinish(float seconds, int ordinal)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Expected O, but got Unknown
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Expected O, but got Unknown
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Expected O, but got Unknown
		if (!Plugin.On(Plugin.RemoveWaits, "waits"))
		{
			return (object)new WaitForSeconds(seconds);
		}
		bool flag = ordinal >= 2 && ordinal <= 4;
		if (flag && !s_finishLayoutOk)
		{
			return (object)new WaitForSeconds(seconds);
		}
		if (flag)
		{
			float num = 0f;
			for (int i = 2; i <= 4; i++)
			{
				num += s_finishWaits[i];
			}
			switch (ordinal)
			{
			case 2:
				if (!Plugin.Cli("noground"))
				{
					return new WaitForGround(num);
				}
				return null;
			case 3:
				if (!Plugin.Cli("nodefeatwait"))
				{
					return new WaitForDefeatSetup(num);
				}
				return null;
			default:
				return null;
			}
		}
		float num2 = seconds * Plugin.WaitScale.Value;
		if (!(num2 > 0f))
		{
			return null;
		}
		return (object)new WaitForSeconds(num2);
	}

	public static float InvokeDelay(float seconds)
	{
		if (!Plugin.On(Plugin.RemoveWaits, "waits"))
		{
			return seconds;
		}
		return seconds * Plugin.WaitScale.Value;
	}

	private static IEnumerable<CodeInstruction> ReplaceWaits(IEnumerable<CodeInstruction> instructions, MethodBase original)
	{
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Expected O, but got Unknown
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Expected O, but got Unknown
		bool flag = original.DeclaringType != null && original.DeclaringType.Name.Contains("FinishLoadLevel");
		List<CodeInstruction> list = instructions.ToList();
		List<float> list2 = new List<float>();
		int num = 0;
		for (int i = 0; i < list.Count; i++)
		{
			CodeInstruction val = list[i];
			if (!(val.opcode != OpCodes.Newobj) && object.Equals(val.operand, s_waitCtor))
			{
				list2.Add((i > 0 && list[i - 1].opcode == OpCodes.Ldc_R4) ? ((float)list[i - 1].operand) : float.NaN);
				CodeInstruction val2 = (flag ? new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(LoaderPatches), "WaitFinish", (Type[])null, (Type[])null)) : new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(LoaderPatches), "Wait", (Type[])null, (Type[])null)));
				CodeInstructionExtensions.MoveBlocksFrom(CodeInstructionExtensions.MoveLabelsFrom(val2, val), val);
				list[i] = val2;
				if (flag)
				{
					list.Insert(i, new CodeInstruction(OpCodes.Ldc_I4, (object)num));
					i++;
				}
				num++;
			}
		}
		if (flag)
		{
			s_finishLayoutOk = list2.SequenceEqual(s_finishWaits);
			if (!s_finishLayoutOk)
			{
				Plugin.Log.LogWarning((object)("FinishLoadLevel waits are [" + string.Join(", ", list2) + "], expected [" + string.Join(", ", s_finishWaits) + "]: the waits after the player's placement stay vanilla"));
			}
		}
		Plugin.Log.LogInfo((object)$"{original.DeclaringType?.Name}: {num} fixed waits made configurable");
		return list;
	}

	internal static void ApplyWaitTranspilers(Harmony harmony)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(LoaderPatches), "ReplaceWaits", (Type[])null, (Type[])null));
		(string, Type[])[] array = new(string, Type[])[2]
		{
			("LoadLevelAsync", new Type[1] { typeof(string) }),
			("FinishLoadLevel", Type.EmptyTypes)
		};
		for (int i = 0; i < array.Length; i++)
		{
			(string, Type[]) tuple = array[i];
			string item = tuple.Item1;
			Type[] item2 = tuple.Item2;
			Type type = AccessTools.Method(typeof(NetworkLevelLoader), item, item2, (Type[])null)?.GetCustomAttribute<IteratorStateMachineAttribute>()?.StateMachineType;
			MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null) : null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)("NetworkLevelLoader." + item + " not found: game changed?"));
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
	}

	[HarmonyTranspiler]
	[HarmonyPatch(typeof(NetworkLevelLoader), "LevelDoneLoading")]
	private static IEnumerable<CodeInstruction> MidLoadLevelDelay(IEnumerable<CodeInstruction> instructions)
	{
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Expected O, but got Unknown
		List<CodeInstruction> list = instructions.ToList();
		MethodInfo methodInfo = AccessTools.Method(typeof(MonoBehaviour), "Invoke", new Type[2]
		{
			typeof(string),
			typeof(float)
		}, (Type[])null);
		int num = 0;
		for (int i = 1; i < list.Count; i++)
		{
			if (CodeInstructionExtensions.Calls(list[i], methodInfo) && list[i - 1].opcode == OpCodes.Ldc_R4)
			{
				list.Insert(i, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(LoaderPatches), "InvokeDelay", (Type[])null, (Type[])null)));
				num++;
				i++;
			}
		}
		Plugin.Log.LogInfo((object)$"LevelDoneLoading: {num} Invoke delay made configurable");
		return list;
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(NetworkLevelLoader), "BaseLoadLevel")]
	private static void RaisePriority()
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		if (!s_firstLoadSeen)
		{
			s_firstLoadSeen = true;
			SideLoaderBoost.SetupFinished();
		}
		if (Plugin.On(Plugin.HighLoadingPriority, "priority") && !s_priorityRaised)
		{
			s_savedPriority = Application.backgroundLoadingPriority;
			Application.backgroundLoadingPriority = (ThreadPriority)4;
			s_priorityRaised = true;
		}
	}

	[HarmonyPostfix]
	[HarmonyPatch(typeof(NetworkLevelLoader), "UnPauseGameplay")]
	private static void RestorePriority(string _identifier)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		if (!(_identifier != "Loading") && s_priorityRaised)
		{
			Application.backgroundLoadingPriority = s_savedPriority;
			s_priorityRaised = false;
		}
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(NetworkLevelLoader), "AbortLoadingAndReturnToMainMenu")]
	private static void RestorePriorityOnAbort()
	{
		RestorePriority("Loading");
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(NetworkLevelLoader), "StartLoadLevel")]
	private static void SkipTransitionScene(NetworkLevelLoader __instance)
	{
		if (Plugin.On(Plugin.SkipLowMemoryScene, "lowmem"))
		{
			__instance.LowMemoryLoading = false;
		}
	}
}
internal static class PngDecoder
{
	internal sealed class Image
	{
		public int Width;

		public int Height;

		public byte[] Rgba;
	}

	private static readonly byte[] s_signature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };

	private static int BE32(byte[] b, int o)
	{
		return (b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3];
	}

	internal static Image Decode(byte[] png)
	{
		if (png.Length < 33)
		{
			return null;
		}
		for (int i = 0; i < 8; i++)
		{
			if (png[i] != s_signature[i])
			{
				return null;
			}
		}
		int num = 0;
		int num2 = 0;
		MemoryStream memoryStream = new MemoryStream();
		int num3 = 8;
		while (num3 + 8 <= png.Length)
		{
			int num4 = BE32(png, num3);
			if (num4 < 0 || num3 + 12 + num4 > png.Length)
			{
				return null;
			}
			int num5 = BE32(png, num3 + 4);
			int num6 = num3 + 8;
			switch (num5)
			{
			case 1229472850:
			{
				num = BE32(png, num6);
				num2 = BE32(png, num6 + 4);
				byte num7 = png[num6 + 8];
				byte b = png[num6 + 9];
				byte b2 = png[num6 + 12];
				if (num7 != 8 || b != 6 || b2 != 0 || num <= 0 || num2 <= 0)
				{
					return null;
				}
				break;
			}
			case 1229209940:
				memoryStream.Write(png, num6, num4);
				break;
			case 1229278788:
				num3 = png.Length;
				continue;
			}
			num3 += 12 + num4;
		}
		if (num == 0 || memoryStream.Length < 6)
		{
			return null;
		}
		int num8 = num * 4;
		byte[] array = new byte[(num8 + 1) * num2];
		memoryStream.Position = 2L;
		using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress))
		{
			int num9;
			for (int j = 0; j < array.Length; j += num9)
			{
				num9 = deflateStream.Read(array, j, array.Length - j);
				if (num9 <= 0)
				{
					return null;
				}
			}
		}
		byte[] array2 = new byte[num8 * num2];
		byte[] array3 = new byte[num8];
		byte[] array4 = new byte[num8];
		for (int k = 0; k < num2; k++)
		{
			int num10 = k * (num8 + 1);
			byte b3 = array[num10];
			Buffer.BlockCopy(array, num10 + 1, array4, 0, num8);
			switch (b3)
			{
			case 1:
			{
				for (int num12 = 4; num12 < num8; num12++)
				{
					array4[num12] += array4[num12 - 4];
				}
				break;
			}
			case 2:
			{
				for (int num13 = 0; num13 < num8; num13++)
				{
					array4[num13] += array3[num13];
				}
				break;
			}
			case 3:
			{
				for (int n = 0; n < 4; n++)
				{
					array4[n] += (byte)(array3[n] >> 1);
				}
				for (int num11 = 4; num11 < num8; num11++)
				{
					array4[num11] += (byte)(array4[num11 - 4] + array3[num11] >> 1);
				}
				break;
			}
			case 4:
			{
				for (int l = 0; l < 4; l++)
				{
					array4[l] += array3[l];
				}
				for (int m = 4; m < num8; m++)
				{
					array4[m] += Paeth(array4[m - 4], array3[m], array3[m - 4]);
				}
				break;
			}
			default:
				return null;
			case 0:
				break;
			}
			Buffer.BlockCopy(array4, 0, array2, (num2 - 1 - k) * num8, num8);
			byte[] array5 = array3;
			array3 = array4;
			array4 = array5;
		}
		return new Image
		{
			Width = num,
			Height = num2,
			Rgba = array2
		};
	}

	private static byte Paeth(byte a, byte b, byte c)
	{
		int num = a + b - c;
		int num2 = Math.Abs(num - a);
		int num3 = Math.Abs(num - b);
		int num4 = Math.Abs(num - c);
		if (num2 > num3 || num2 > num4)
		{
			if (num3 > num4)
			{
				return c;
			}
			return b;
		}
		return a;
	}
}
internal static class SideLoaderBoost
{
	private sealed class TexJob
	{
		public string Path;

		public string CacheFile;

		public PngDecoder.Image Image;

		public byte[] Dxt5;

		public int Width;

		public int Height;

		public bool Done;

		public long Bytes
		{
			get
			{
				byte[] dxt = Dxt5;
				int num;
				if (dxt == null)
				{
					PngDecoder.Image image = Image;
					num = ((image != null) ? image.Rgba.Length : 0);
				}
				else
				{
					num = dxt.Length;
				}
				return num;
			}
		}
	}

	private sealed class XmlJob
	{
		public Type Type;

		public XmlSerializer Serializer;

		public bool Done;
	}

	private static Type s_customTextures;

	private static Type s_serializer;

	private static Type s_slPack;

	private static readonly string s_typeCache = Path.Combine(Paths.CachePath, "FastLoad.xmltypes.txt");

	private static readonly string s_texCacheDir = Path.Combine(Paths.CachePath, "FastLoad.textures");

	private static readonly HashSet<string> s_cacheFilesUsed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static int s_texFromCache;

	private const int CacheMagic = 827608134;

	private static readonly Dictionary<string, TexJob> s_textures = new Dictionary<string, TexJob>();

	private static readonly object s_texLock = new object();

	private static long s_texRetained;

	private const long MaxRetained = 268435456L;

	private static int s_texHits;

	private static int s_texMisses;

	private static long s_texWaitTicks;

	private static readonly Dictionary<Type, XmlJob> s_xml = new Dictionary<Type, XmlJob>();

	private static readonly object s_xmlLock = new object();

	private static Type[] s_xmlExtraTypes;

	private static readonly HashSet<string> s_requestedTypes = new HashSet<string>();

	private static bool s_typesDirty;

	private static int s_xmlHits;

	private static int s_xmlMisses;

	private static long s_xmlWaitTicks;

	private static readonly Dictionary<string, AssetBundleCreateRequest> s_bundles = new Dictionary<string, AssetBundleCreateRequest>();

	private static int s_bundleHits;

	private static long s_bundleWaitTicks;

	internal static bool Present => s_customTextures != null;

	internal static void Init(Harmony harmony)
	{
		s_customTextures = AccessTools.TypeByName("SideLoader.CustomTextures");
		s_serializer = AccessTools.TypeByName("SideLoader.Serializer");
		s_slPack = AccessTools.TypeByName("SideLoader.SLPack");
		if (s_customTextures == null || s_serializer == null || s_slPack == null)
		{
			Plugin.Log.LogInfo((object)"SideLoader not found: SideLoader boosts disabled");
			s_customTextures = null;
			return;
		}
		Patch(harmony, AccessTools.Method(s_customTextures, "LoadTexture", new Type[3]
		{
			typeof(string),
			typeof(bool),
			typeof(bool)
		}, (Type[])null), "LoadTexturePrefix");
		Patch(harmony, AccessTools.Method(s_serializer, "GetXmlSerializer", new Type[1] { typeof(Type) }, (Type[])null), "GetXmlSerializerPrefix", "GetXmlSerializerPostfix");
		Patch(harmony, AccessTools.Method(s_slPack, "LoadAssetBundle", new Type[2]
		{
			typeof(string),
			typeof(string)
		}, (Type[])null), "LoadAssetBundlePrefix");
	}

	private static void Patch(Harmony harmony, MethodBase target, string prefix, string postfix = null)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Expected O, but got Unknown
		if (target == null)
		{
			Plugin.Log.LogWarning((object)("SideLoader changed: no target for " + prefix));
		}
		else
		{
			harmony.Patch(target, new HarmonyMethod(AccessTools.Method(typeof(SideLoaderBoost), prefix, (Type[])null, (Type[])null)), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(typeof(SideLoaderBoost), postfix, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
	}

	internal static void StartPrefetch()
	{
		if (Present)
		{
			List<string> packDirs = SidePackFolders();
			if (Plugin.On(Plugin.SlTextures, "sltex"))
			{
				StartTextureDecoding(packDirs);
			}
			if (Plugin.On(Plugin.SlXml, "slxml"))
			{
				StartXmlPrewarm();
			}
			if (Plugin.On(Plugin.SlBundles, "slbundle"))
			{
				StartBundlePrefetch(packDirs);
			}
		}
	}

	private static List<string> SidePackFolders()
	{
		try
		{
			return Directory.GetDirectories(Paths.PluginPath, "SideLoader", SearchOption.AllDirectories).ToList();
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Cannot list SideLoader packs: " + ex.Message));
			return new List<string>();
		}
	}

	private static string Key(string path)
	{
		return Path.GetFullPath(path).ToLowerInvariant();
	}

	private static string CacheFileFor(string png)
	{
		FileInfo fileInfo = new FileInfo(png);
		string s = $"{Key(png)}|{fileInfo.Length}|{fileInfo.LastWriteTimeUtc.Ticks}";
		using MD5 mD = MD5.Create();
		byte[] array = mD.ComputeHash(Encoding.UTF8.GetBytes(s));
		return Path.Combine(s_texCacheDir, BitConverter.ToString(array).Replace("-", "") + ".dxt5");
	}

	private static bool TryReadCache(TexJob job)
	{
		if (!File.Exists(job.CacheFile))
		{
			return false;
		}
		using BinaryReader binaryReader = new BinaryReader(File.OpenRead(job.CacheFile));
		if (binaryReader.ReadInt32() != 827608134)
		{
			return false;
		}
		int num = binaryReader.ReadInt32();
		int num2 = binaryReader.ReadInt32();
		int num3 = binaryReader.ReadInt32();
		if (num <= 0 || num2 <= 0 || num3 != num / 4 * (num2 / 4) * 16)
		{
			return false;
		}
		byte[] array = binaryReader.ReadBytes(num3);
		if (array.Length != num3)
		{
			return false;
		}
		job.Width = num;
		job.Height = num2;
		job.Dxt5 = array;
		return true;
	}

	private static void WriteCache(string file, int w, int h, byte[] dxt5)
	{
		Thread thread = new Thread((ThreadStart)delegate
		{
			try
			{
				Directory.CreateDirectory(s_texCacheDir);
				string text = file + ".tmp";
				using (BinaryWriter binaryWriter = new BinaryWriter(File.Create(text)))
				{
					binaryWriter.Write(827608134);
					binaryWriter.Write(w);
					binaryWriter.Write(h);
					binaryWriter.Write(dxt5.Length);
					binaryWriter.Write(dxt5);
				}
				if (File.Exists(file))
				{
					File.Delete(file);
				}
				File.Move(text, file);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Texture cache write failed: " + ex.Message));
			}
		});
		thread.IsBackground = true;
		thread.Name = "FastLoad cache";
		thread.Start();
	}

	private static void StartTextureDecoding(List<string> packDirs)
	{
		List<string> list = new List<string>();
		foreach (string packDir in packDirs)
		{
			string[] array = new string[2]
			{
				Path.Combine(packDir, "Texture2D"),
				Path.Combine(packDir, "Texture2D", "Local")
			};
			foreach (string path in array)
			{
				if (Directory.Exists(path))
				{
					list.AddRange(Directory.GetFiles(path, "*.png"));
				}
			}
		}
		if (list.Count == 0)
		{
			return;
		}
		Queue<TexJob> queue = new Queue<TexJob>();
		foreach (string item in list)
		{
			TexJob texJob = new TexJob
			{
				Path = item,
				CacheFile = CacheFileFor(item)
			};
			s_cacheFilesUsed.Add(texJob.CacheFile);
			s_textures[Key(item)] = texJob;
			queue.Enqueue(texJob);
		}
		int num = Math.Max(1, Math.Min(list.Count, Environment.ProcessorCount - 2));
		for (int j = 0; j < num; j++)
		{
			Thread thread = new Thread((ThreadStart)delegate
			{
				DecodeWorker(queue);
			});
			thread.IsBackground = true;
			thread.Name = "FastLoad PNG";
			thread.Priority = ThreadPriority.BelowNormal;
			thread.Start();
		}
		Plugin.Log.LogInfo((object)$"Decoding {list.Count} SideLoader textures on {num} threads");
	}

	private static void DecodeWorker(Queue<TexJob> queue)
	{
		while (true)
		{
			TexJob texJob;
			lock (queue)
			{
				if (queue.Count == 0)
				{
					break;
				}
				texJob = queue.Dequeue();
			}
			PngDecoder.Image image = null;
			try
			{
				lock (s_texLock)
				{
					while (s_texRetained > 268435456)
					{
						Monitor.Wait(s_texLock);
					}
				}
				bool flag = false;
				try
				{
					flag = TryReadCache(texJob);
				}
				catch
				{
					texJob.Dxt5 = null;
				}
				if (!flag)
				{
					image = PngDecoder.Decode(File.ReadAllBytes(texJob.Path));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Background decode failed for " + texJob.Path + ": " + ex.Message + " (SideLoader will load it)"));
			}
			lock (s_texLock)
			{
				texJob.Image = image;
				texJob.Done = true;
				s_texRetained += texJob.Bytes;
				Monitor.PulseAll(s_texLock);
			}
		}
	}

	private static bool LoadTexturePrefix(string __0, bool __1, bool __2, ref Texture2D __result)
	{
		try
		{
			return LoadTexturePrefixImpl(__0, __1, __2, ref __result);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("LoadTexturePrefix failed, SideLoader's own code runs: " + ex));
			return true;
		}
	}

	private static bool LoadTexturePrefixImpl(string __0, bool __1, bool __2, ref Texture2D __result)
	{
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Expected O, but got Unknown
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Expected O, but got Unknown
		if (__0 == null || __1)
		{
			return true;
		}
		string key;
		try
		{
			key = Key(__0);
		}
		catch
		{
			return true;
		}
		TexJob value;
		lock (s_texLock)
		{
			if (!s_textures.TryGetValue(key, out value))
			{
				s_texMisses++;
				return true;
			}
			s_textures.Remove(key);
			long timestamp = Stopwatch.GetTimestamp();
			while (!value.Done)
			{
				Monitor.Wait(s_texLock);
			}
			s_texWaitTicks += Stopwatch.GetTimestamp() - timestamp;
			s_texRetained -= value.Bytes;
			Monitor.PulseAll(s_texLock);
		}
		Texture2D val;
		if (value.Dxt5 != null)
		{
			val = new Texture2D(value.Width, value.Height, (TextureFormat)12, false, __2);
			val.LoadRawTextureData(value.Dxt5);
			val.Apply(false, false);
			s_texFromCache++;
		}
		else
		{
			PngDecoder.Image image = value.Image;
			if (image == null)
			{
				s_texMisses++;
				return true;
			}
			val = new Texture2D(image.Width, image.Height, (TextureFormat)4, false, __2);
			val.LoadRawTextureData(image.Rgba);
			if ((image.Width & 3) == 0 && (image.Height & 3) == 0)
			{
				val.Compress(true);
				WriteCache(value.CacheFile, image.Width, image.Height, val.GetRawTextureData());
			}
			val.Apply(false, false);
		}
		if (Plugin.On(Plugin.SlVerify, "slverify"))
		{
			Verify(__0, __2, val);
		}
		s_texHits++;
		__result = val;
		return false;
	}

	private static void Verify(string path, bool linear, Texture2D ours)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Expected O, but got Unknown
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		byte[] array = File.ReadAllBytes(path);
		PngDecoder.Image img = PngDecoder.Decode(array);
		Texture2D val = new Texture2D(4, 4, (TextureFormat)4, false, linear);
		ImageConversion.LoadImage(val, array);
		int diff = Diff(img?.Rgba, val.GetRawTextureData());
		Texture2D val2 = new Texture2D(4, 4, (TextureFormat)12, false, linear);
		ImageConversion.LoadImage(val2, array);
		byte[] want = val2.GetRawTextureData();
		Plugin.Log.LogMessage((object)("verify " + Path.GetFileName(path) + ": decoded pixels vs LoadImage RGBA32: " + Fmt(diff) + " differing bytes; vs LoadImage DXT5: Compress(false) " + Fmt(Compressed(hq: false)) + ", Compress(true) " + Fmt(Compressed(hq: true)) + ", " + $"used texture {Fmt(Diff(ours.GetRawTextureData(), want))} (reference format {val2.format}, mips {((Texture)val2).mipmapCount})"));
		Object.Destroy((Object)(object)val);
		Object.Destroy((Object)(object)val2);
		int Compressed(bool hq)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			Texture2D val3 = new Texture2D(img.Width, img.Height, (TextureFormat)4, false, linear);
			val3.LoadRawTextureData(img.Rgba);
			val3.Compress(hq);
			int result = Diff(val3.GetRawTextureData(), want);
			Object.Destroy((Object)val3);
			return result;
		}
	}

	private static string Fmt(int diff)
	{
		if (diff >= 0)
		{
			return diff.ToString();
		}
		return "size mismatch";
	}

	private static int Diff(byte[] a, byte[] b)
	{
		if (a == null || b == null || a.Length != b.Length)
		{
			return -1;
		}
		int num = 0;
		for (int i = 0; i < a.Length; i++)
		{
			if (a[i] != b[i])
			{
				num++;
			}
		}
		return num;
	}

	private static Type[] CurrentSlTypes()
	{
		return (Type[])AccessTools.Property(s_serializer, "SLTypes").GetValue(null, null);
	}

	private static IDictionary XmlCache()
	{
		return (IDictionary)AccessTools.Field(s_serializer, "xmlCache").GetValue(null);
	}

	private static void StartXmlPrewarm()
	{
		List<Type> list = new List<Type>();
		if (File.Exists(s_typeCache))
		{
			string[] array = File.ReadAllLines(s_typeCache);
			foreach (string text in array)
			{
				Type type = (string.IsNullOrWhiteSpace(text) ? null : Type.GetType(text.Trim(), throwOnError: false));
				if (type != null)
				{
					list.Add(type);
				}
			}
		}
		if (list.Count == 0)
		{
			Plugin.Log.LogInfo((object)"No recorded SideLoader serializer types yet: they are recorded during this start-up and used from the next one");
			return;
		}
		s_xmlExtraTypes = (Type[])CurrentSlTypes().Clone();
		Queue<XmlJob> queue = new Queue<XmlJob>();
		foreach (Type item in list)
		{
			XmlJob xmlJob = new XmlJob
			{
				Type = item
			};
			s_xml[item] = xmlJob;
			queue.Enqueue(xmlJob);
		}
		int num = Math.Max(1, Math.Min(list.Count, Environment.ProcessorCount - 2));
		for (int j = 0; j < num; j++)
		{
			Thread thread = new Thread((ThreadStart)delegate
			{
				XmlWorker(queue);
			});
			thread.IsBackground = true;
			thread.Name = "FastLoad XML";
			thread.Priority = ThreadPriority.BelowNormal;
			thread.Start();
		}
		Plugin.Log.LogInfo((object)$"Building {list.Count} SideLoader XmlSerializers on {num} threads");
	}

	private static void XmlWorker(Queue<XmlJob> queue)
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Expected O, but got Unknown
		while (true)
		{
			XmlJob xmlJob;
			lock (queue)
			{
				if (queue.Count == 0)
				{
					break;
				}
				xmlJob = queue.Dequeue();
			}
			XmlSerializer serializer = null;
			try
			{
				serializer = new XmlSerializer(xmlJob.Type, s_xmlExtraTypes);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Background XmlSerializer for " + xmlJob.Type.Name + " failed: " + ex.Message));
			}
			lock (s_xmlLock)
			{
				xmlJob.Serializer = serializer;
				xmlJob.Done = true;
				Monitor.PulseAll(s_xmlLock);
			}
		}
	}

	private static bool GetXmlSerializerPrefix(Type __0, ref XmlSerializer __result)
	{
		try
		{
			return GetXmlSerializerPrefixImpl(__0, ref __result);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("GetXmlSerializerPrefix failed, SideLoader's own code runs: " + ex));
			return true;
		}
	}

	private static bool GetXmlSerializerPrefixImpl(Type __0, ref XmlSerializer __result)
	{
		if (__0 == null)
		{
			return true;
		}
		IDictionary dictionary = XmlCache();
		if (dictionary.Contains(__0))
		{
			return true;
		}
		XmlJob value;
		lock (s_xmlLock)
		{
			if (!s_xml.TryGetValue(__0, out value))
			{
				return true;
			}
			s_xml.Remove(__0);
			long timestamp = Stopwatch.GetTimestamp();
			while (!value.Done)
			{
				Monitor.Wait(s_xmlLock);
			}
			s_xmlWaitTicks += Stopwatch.GetTimestamp() - timestamp;
		}
		Type[] array = CurrentSlTypes();
		if (value.Serializer == null || array.Length != s_xmlExtraTypes.Length || !array.SequenceEqual(s_xmlExtraTypes))
		{
			s_xmlMisses++;
			return true;
		}
		dictionary[__0] = value.Serializer;
		__result = value.Serializer;
		s_xmlHits++;
		return false;
	}

	private static void GetXmlSerializerPostfix(Type __0)
	{
		if ((object)__0 != null && __0.AssemblyQualifiedName != null && s_requestedTypes.Add(__0.AssemblyQualifiedName))
		{
			s_typesDirty = true;
		}
	}

	private static void StartBundlePrefetch(List<string> packDirs)
	{
		foreach (string packDir in packDirs)
		{
			string path = Path.Combine(packDir, "AssetBundles");
			if (!Directory.Exists(path))
			{
				continue;
			}
			string[] files = Directory.GetFiles(path);
			foreach (string text in files)
			{
				if (!text.EndsWith(".meta") && !text.EndsWith(".manifest"))
				{
					s_bundles[Key(text)] = AssetBundle.LoadFromFileAsync(text);
				}
			}
		}
		if (s_bundles.Count > 0)
		{
			Plugin.Log.LogInfo((object)$"Opening {s_bundles.Count} SideLoader asset bundles asynchronously");
		}
	}

	private static bool LoadAssetBundlePrefix(string __0, string __1, ref AssetBundle __result)
	{
		try
		{
			return LoadAssetBundlePrefixImpl(__0, __1, ref __result);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("LoadAssetBundlePrefix failed, SideLoader's own code runs: " + ex));
			return true;
		}
	}

	private static bool LoadAssetBundlePrefixImpl(string __0, string __1, ref AssetBundle __result)
	{
		string key;
		try
		{
			key = Key(Path.Combine(__0, __1));
		}
		catch
		{
			return true;
		}
		if (!s_bundles.TryGetValue(key, out var value))
		{
			return true;
		}
		s_bundles.Remove(key);
		long timestamp = Stopwatch.GetTimestamp();
		__result = value.assetBundle;
		s_bundleWaitTicks += Stopwatch.GetTimestamp() - timestamp;
		if ((Object)(object)__result == (Object)null)
		{
			return true;
		}
		s_bundleHits++;
		return false;
	}

	internal static void Tick()
	{
		if (!Present || !s_typesDirty)
		{
			return;
		}
		s_typesDirty = false;
		try
		{
			string[] first = (File.Exists(s_typeCache) ? File.ReadAllLines(s_typeCache) : new string[0]);
			File.WriteAllLines(s_typeCache, (from x in first.Concat(s_requestedTypes)
				where !string.IsNullOrWhiteSpace(x)
				select x).Distinct().ToArray());
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Cannot save serializer types: " + ex.Message));
		}
	}

	private static void PruneTextureCache()
	{
		if (s_cacheFilesUsed.Count == 0 || !Directory.Exists(s_texCacheDir))
		{
			return;
		}
		try
		{
			string[] files = Directory.GetFiles(s_texCacheDir, "*.dxt5");
			foreach (string text in files)
			{
				if (!s_cacheFilesUsed.Contains(text))
				{
					File.Delete(text);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Texture cache prune failed: " + ex.Message));
		}
	}

	private static double Ms(long ticks)
	{
		return (double)ticks * 1000.0 / (double)Stopwatch.Frequency;
	}

	internal static void SetupFinished()
	{
		if (!Present)
		{
			return;
		}
		Plugin.Log.LogMessage((object)($"SideLoader boost: textures {s_texHits} prepared ({s_texFromCache} from cache, main thread waited {Ms(s_texWaitTicks):F0} ms), {s_texMisses} by SideLoader; " + $"serializers {s_xmlHits} pre-built (waited {Ms(s_xmlWaitTicks):F0} ms), {s_xmlMisses} rebuilt; " + $"bundles {s_bundleHits} async (waited {Ms(s_bundleWaitTicks):F0} ms)"));
		foreach (AssetBundleCreateRequest value in s_bundles.Values)
		{
			if ((Object)(object)value.assetBundle != (Object)null)
			{
				value.assetBundle.Unload(true);
			}
		}
		s_bundles.Clear();
		lock (s_texLock)
		{
			s_textures.Clear();
			s_texRetained = 0L;
			Monitor.PulseAll(s_texLock);
		}
		PruneTextureCache();
		lock (s_xmlLock)
		{
			s_xml.Clear();
		}
	}
}
internal sealed class WaitForDefeatSetup : CustomYieldInstruction
{
	private const string Pending = "CheckForLoadSceneDefeat";

	private static readonly FieldRef<DefeatScenariosManager, bool> s_loadedSceneForDefeat = AccessTools.FieldRefAccess<DefeatScenariosManager, bool>("m_loadedSceneForDefeat");

	private static readonly FieldRef<DefeatScenariosManager, string> s_activeScenario = AccessTools.FieldRefAccess<DefeatScenariosManager, string>("m_activeDefeatScenarioID");

	private readonly float m_deadline;

	private readonly Stopwatch m_watch = Stopwatch.StartNew();

	private bool m_done;

	private bool m_waited;

	public override bool keepWaiting
	{
		get
		{
			if (m_done)
			{
				return false;
			}
			DefeatScenariosManager instance = DefeatScenariosManager.Instance;
			bool flag = (Object)(object)instance != (Object)null && ((MonoBehaviour)instance).IsInvoking("CheckForLoadSceneDefeat") && (s_loadedSceneForDefeat.Invoke(instance) || !string.IsNullOrEmpty(s_activeScenario.Invoke(instance)));
			if (flag && Time.realtimeSinceStartup < m_deadline)
			{
				m_waited = true;
				return true;
			}
			m_done = true;
			if (m_waited)
			{
				Plugin.Log.LogInfo((object)string.Format("Defeat scenario set-up: waited {0} ms ({1})", m_watch.ElapsedMilliseconds, flag ? "vanilla delay reached" : "done"));
			}
			return false;
		}
	}

	public WaitForDefeatSetup(float maxSeconds)
	{
		m_deadline = Time.realtimeSinceStartup + maxSeconds;
	}
}
internal sealed class WaitForGround : CustomYieldInstruction
{
	private const int StableFrames = 3;

	private readonly float m_deadline;

	private readonly Stopwatch m_watch = Stopwatch.StartNew();

	private readonly List<(Character c, Vector3 pos, Quaternion rot)> m_players = new List<(Character, Vector3, Quaternion)>();

	private int m_stable;

	private int m_frames;

	private bool m_done;

	public override bool keepWaiting
	{
		get
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			if (m_done)
			{
				return false;
			}
			m_frames++;
			bool flag = true;
			foreach (var (val, val2, val3) in m_players)
			{
				if (!((Object)(object)val == (Object)null))
				{
					if (!GroundUnder(val, val2))
					{
						flag = false;
					}
					Vector3 val4 = ((Component)val).transform.position - val2;
					if (((Vector3)(ref val4)).sqrMagnitude > 0.04f && !GroundUnder(val, ((Component)val).transform.position))
					{
						val.Internal_SendTeleport(val2, val3);
					}
				}
			}
			m_stable = (flag ? (m_stable + 1) : 0);
			if (m_stable >= 3)
			{
				return Finish("ground detected");
			}
			if (Time.realtimeSinceStartup >= m_deadline)
			{
				foreach (var (val5, val6, val7) in m_players)
				{
					if ((Object)(object)val5 != (Object)null && !GroundUnder(val5, ((Component)val5).transform.position))
					{
						val5.Internal_SendTeleport(val6, val7);
					}
				}
				return Finish("vanilla delay reached");
			}
			return true;
		}
	}

	public WaitForGround(float maxSeconds)
	{
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		m_deadline = Time.realtimeSinceStartup + maxSeconds;
		SplitScreenManager instance = SplitScreenManager.Instance;
		List<SplitPlayer> list = ((instance != null) ? instance.LocalPlayers : null);
		if (list == null)
		{
			return;
		}
		for (int i = 0; i < list.Count; i++)
		{
			Character assignedCharacter = list[i].AssignedCharacter;
			if ((Object)(object)assignedCharacter != (Object)null)
			{
				m_players.Add((assignedCharacter, ((Component)assignedCharacter).transform.position, ((Component)assignedCharacter).transform.rotation));
			}
		}
	}

	private bool Finish(string why)
	{
		m_done = true;
		Plugin.Log.LogInfo((object)$"Players placed: resumed after {m_watch.ElapsedMilliseconds} ms / {m_frames} frames ({why})");
		return false;
	}

	private static bool GroundUnder(Character c, Vector3 feet)
	{
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		int layer = ((Component)c).gameObject.layer;
		int num = 0;
		for (int i = 0; i < 32; i++)
		{
			if (!Physics.GetIgnoreLayerCollision(layer, i))
			{
				num |= 1 << i;
			}
		}
		RaycastHit[] array = Physics.RaycastAll(feet + Vector3.up * 0.5f, Vector3.down, 2.5f, num, (QueryTriggerInteraction)1);
		for (int j = 0; j < array.Length; j++)
		{
			RaycastHit val = array[j];
			if ((Object)(object)((RaycastHit)(ref val)).collider != (Object)null && !((Component)((RaycastHit)(ref val)).collider).transform.IsChildOf(((Component)c).transform))
			{
				return true;
			}
		}
		return false;
	}
}