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;
}
}