Decompiled source of BepInEx Faster Load AssetBundles Patcher v0.6.5

BepInEx/patchers/BepInExFasterLoadAssetBundles/BepInExFasterLoadAssetBundles.dll

Decompiled a month ago
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using BepInExFasterLoadAssetBundles.Helpers;
using BepInExFasterLoadAssetBundles.Managers;
using BepInExFasterLoadAssetBundles.Models;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Newtonsoft.Json;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BepInExFasterLoadAssetBundles")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.6.5.0")]
[assembly: AssemblyInformationalVersion("0.6.5+ed6441ffd4afda0ee3f636c9a4567f3534c893a1")]
[assembly: AssemblyProduct("BepInExFasterLoadAssetBundles")]
[assembly: AssemblyTitle("BepInExFasterLoadAssetBundles")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.6.5.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BepInExFasterLoadAssetBundles
{
	public class BepInExFasterLoadAssetBundlesPatcher
	{
		internal static Harmony Harmony { get; } = new Harmony("BepInExFasterLoadAssetBundlesPatcher");


		public static IEnumerable<string> TargetDLLs { get; } = Array.Empty<string>();


		public static void Finish()
		{
			Harmony.PatchAll(typeof(BepInExFasterLoadAssetBundlesPatcher).Assembly);
		}

		public static void Patch(AssemblyDefinition _)
		{
		}
	}
	[HarmonyPatch]
	internal static class Patcher
	{
		internal static ManualLogSource Logger { get; private set; }

		internal static AssetBundleManager AssetBundleManager { get; private set; }

		internal static MetadataManager MetadataManager { get; private set; }

		[HarmonyPatch(typeof(Chainloader), "Initialize")]
		[HarmonyPostfix]
		public static void ChainloaderInitialized()
		{
			AsyncHelper.InitUnitySynchronizationContext();
			Logger = Logger.CreateLogSource("BepInExFasterLoadAssetBundlesPatcher");
			string fullName = new DirectoryInfo(Application.dataPath).Parent.FullName;
			string text = Path.Combine(fullName, "Cache", "AssetBundles");
			if (!Directory.Exists(text))
			{
				Directory.CreateDirectory(text);
			}
			AssetBundleManager = new AssetBundleManager(text);
			MetadataManager = new MetadataManager(Path.Combine(text, "metadata.json"));
			Patch();
		}

		private static void Patch()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Expected O, but got Unknown
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Expected O, but got Unknown
			Type typeFromHandle = typeof(Patcher);
			Harmony harmony = BepInExFasterLoadAssetBundlesPatcher.Harmony;
			BindingFlags all = AccessTools.all;
			HarmonyMethod val = new HarmonyMethod(typeFromHandle.GetMethod("LoadAssetBundleFromFileFast", all));
			Type typeFromHandle2 = typeof(AssetBundle);
			string[] array = new string[2] { "LoadFromFile", "LoadFromFileAsync" };
			string[] array2 = array;
			foreach (string text in array2)
			{
				harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle2, text, new Type[1] { typeof(string) }, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle2, text, new Type[2]
				{
					typeof(string),
					typeof(uint)
				}, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle2, text, new Type[3]
				{
					typeof(string),
					typeof(uint),
					typeof(ulong)
				}, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle2, "LoadFromStreamInternal", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle.GetMethod("LoadAssetBundleFromStreamFast", all)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle2, "LoadFromStreamAsyncInternal", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle.GetMethod("LoadAssetBundleFromStreamAsyncFast", all)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void LoadAssetBundleFromFileFast(ref string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				return;
			}
			try
			{
				using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 16777216, FileOptions.SequentialScan);
				if (HandleStreamBundle(stream, out string path2))
				{
					path = path2;
				}
			}
			catch (Exception arg)
			{
				Logger.LogError((object)$"Failed to decompress assetbundle\n{arg}");
			}
		}

		private static bool LoadAssetBundleFromStreamFast(Stream stream, ref AssetBundle? __result)
		{
			if (HandleStreamBundle(stream, out string path))
			{
				__result = AssetBundle.LoadFromFile_Internal(path, 0u, 0uL);
				return false;
			}
			return true;
		}

		private static bool LoadAssetBundleFromStreamAsyncFast(Stream stream, ref AssetBundleCreateRequest? __result)
		{
			if (HandleStreamBundle(stream, out string path))
			{
				__result = AssetBundle.LoadFromFileAsync_Internal(path, 0u, 0uL);
				return false;
			}
			return true;
		}

		private static bool HandleStreamBundle(Stream stream, [NotNullWhen(true)] out string? path)
		{
			long position = stream.Position;
			try
			{
				return AssetBundleManager.TryRecompressAssetBundle(stream, out path);
			}
			catch (Exception arg)
			{
				Logger.LogError((object)$"Failed to decompress assetbundle\n{arg}");
			}
			stream.Position = position;
			path = null;
			return false;
		}
	}
}
namespace BepInExFasterLoadAssetBundles.Models
{
	internal class Metadata
	{
		public string? UncompressedAssetBundleName { get; set; }

		public string OriginalAssetBundleHash { get; set; }

		public bool ShouldNotDecompress { get; set; }

		public DateTime LastAccessTime { get; set; }
	}
}
namespace BepInExFasterLoadAssetBundles.Managers
{
	internal class AssetBundleManager
	{
		private readonly struct WorkAsset
		{
			public string Path { get; }

			public string Hash { get; }

			public bool DeleteBundleAfterOperation { get; }

			public WorkAsset(string path, string hash, bool deleteBundleAfterOperation)
			{
				Path = path;
				Hash = hash;
				DeleteBundleAfterOperation = deleteBundleAfterOperation;
			}
		}

		private readonly ConcurrentQueue<WorkAsset> m_WorkAssets = new ConcurrentQueue<WorkAsset>();

		private readonly object m_Lock = new object();

		private readonly string m_PathForTemp;

		private bool m_IsProcessingQueue;

		public string CachePath { get; }

		public AssetBundleManager(string cachePath)
		{
			CachePath = cachePath;
			if (!Directory.Exists(CachePath))
			{
				Directory.CreateDirectory(CachePath);
			}
			m_PathForTemp = Path.Combine(CachePath, "temp");
			if (!Directory.Exists(m_PathForTemp))
			{
				Directory.CreateDirectory(m_PathForTemp);
			}
			DeleteTempFiles();
		}

		private void DeleteTempFiles()
		{
			int count2 = 0;
			try
			{
				foreach (string item in Directory.EnumerateFiles(CachePath, "*.tmp").Concat(Directory.EnumerateFiles(m_PathForTemp, "*.assetbundle")))
				{
					DeleteFileSafely(ref count2, item);
				}
			}
			catch (Exception arg)
			{
				Patcher.Logger.LogError((object)$"Failed to delete temp files\n{arg}");
			}
			if (count2 > 0)
			{
				Patcher.Logger.LogWarning((object)$"Deleted {count2} temp files");
			}
			static void DeleteFileSafely(ref int count, string tempFile)
			{
				if (!FileHelper.TryDeleteFile(tempFile, out Exception exception))
				{
					Patcher.Logger.LogError((object)$"Failed to delete temp file\n{exception}");
				}
				else
				{
					count++;
				}
			}
		}

		public unsafe bool TryRecompressAssetBundle(Stream stream, [NotNullWhen(true)] out string? path)
		{
			if (BundleHelper.CheckBundleIsAlreadyDecompressed(stream))
			{
				Patcher.Logger.LogInfo((object)"Original bundle is already uncompressed, using it instead");
				path = null;
				return false;
			}
			Span<char> span = stackalloc char[32];
			HashingHelper.WriteHash(span, stream);
			path = null;
			if (FindCachedBundleByHash(span, out string path2))
			{
				if (path2 != null)
				{
					path = path2;
					return true;
				}
				Patcher.Logger.LogDebug((object)"Found assetbundle metadata, but path was null. Probably bundle is already uncompressed!");
				return false;
			}
			if (stream is FileStream fileStream)
			{
				path = string.Copy(fileStream.Name);
				RecompressAssetBundleInternal(new WorkAsset(path, span.ToString(), deleteBundleAfterOperation: false));
				return false;
			}
			string path3 = Guid.NewGuid().ToString("N") + ".assetbundle";
			string path4 = Path.Combine(m_PathForTemp, path3);
			using (FileStream fileStream2 = new FileStream(path4, FileMode.CreateNew, FileAccess.Write, FileShare.None, 8192, FileOptions.SequentialScan))
			{
				stream.Seek(0L, SeekOrigin.Begin);
				if (stream is UnmanagedMemoryStream unmanagedMemoryStream && unmanagedMemoryStream.Length < int.MaxValue)
				{
					ReadOnlySpan<byte> buffer = new ReadOnlySpan<byte>(unmanagedMemoryStream.PositionPointer, (int)unmanagedMemoryStream.Length);
					fileStream2.Write(buffer);
				}
				else
				{
					stream.CopyTo(fileStream2);
				}
			}
			RecompressAssetBundleInternal(new WorkAsset(path4, span.ToString(), deleteBundleAfterOperation: true));
			return false;
		}

		public void DeleteCachedAssetBundle(string path)
		{
			FileHelper.TryDeleteFile(path, out Exception exception);
			if (exception != null)
			{
				Patcher.Logger.LogError((object)$"Failed to delete uncompressed assetbundle\n{exception}");
			}
		}

		private bool FindCachedBundleByHash(ReadOnlySpan<char> hash, out string? path)
		{
			path = null;
			Metadata metadata2 = Patcher.MetadataManager.FindMetadataByHash(hash);
			if (metadata2 == null)
			{
				return false;
			}
			if (metadata2.ShouldNotDecompress)
			{
				ModifyAccessTimeAndSave(metadata2);
				return true;
			}
			if (metadata2.UncompressedAssetBundleName == null)
			{
				return false;
			}
			string text = Path.Combine(CachePath, metadata2.UncompressedAssetBundleName);
			if (!File.Exists(text))
			{
				Patcher.Logger.LogWarning((object)("Failed to find decompressed assetbundle at \"" + text + "\". Probably it was deleted?"));
				Patcher.MetadataManager.DeleteMetadata(metadata2);
				return false;
			}
			Patcher.Logger.LogDebug((object)("Loading uncompressed bundle \"" + metadata2.UncompressedAssetBundleName + "\""));
			path = text;
			ModifyAccessTimeAndSave(metadata2);
			return true;
			static void ModifyAccessTimeAndSave(Metadata metadata)
			{
				metadata.LastAccessTime = DateTime.Now;
				Patcher.MetadataManager.SaveMetadata(metadata);
			}
		}

		private void RecompressAssetBundleInternal(WorkAsset workAsset)
		{
			if (!DriveHelper.HasDriveSpaceOnPath(CachePath, 10L))
			{
				Patcher.Logger.LogWarning((object)"Ignoring request of decompressing, because the free drive space is less than 10GB");
				return;
			}
			Patcher.Logger.LogDebug((object)("Queued recompress of \"" + Path.GetFileName(workAsset.Path) + "\" assetbundle"));
			m_WorkAssets.Enqueue(workAsset);
			StartRunner();
		}

		private void StartRunner()
		{
			if (m_IsProcessingQueue)
			{
				return;
			}
			lock (m_Lock)
			{
				if (m_IsProcessingQueue)
				{
					return;
				}
				m_IsProcessingQueue = true;
			}
			AsyncHelper.Schedule(ProcessQueue);
		}

		private async Task ProcessQueue()
		{
			try
			{
				WorkAsset result;
				while (m_WorkAssets.TryDequeue(out result))
				{
					await DecompressAssetBundleAsync(result);
				}
			}
			finally
			{
				lock (m_Lock)
				{
					if (m_IsProcessingQueue)
					{
						m_IsProcessingQueue = false;
					}
				}
			}
		}

		private async Task DecompressAssetBundleAsync(WorkAsset workAsset)
		{
			Metadata metadata = new Metadata
			{
				OriginalAssetBundleHash = workAsset.Hash,
				LastAccessTime = DateTime.Now
			};
			string originalFileName = Path.GetFileNameWithoutExtension(workAsset.Path);
			string outputName = originalFileName + "_" + metadata.GetHashCode() + ".assetbundle";
			string outputPath = Path.Combine(CachePath, outputName);
			BuildCompression buildCompression = BuildCompression.LZ4Runtime;
			Patcher.Logger.LogDebug((object)("Decompressing \"" + originalFileName + "\""));
			await FileHelper.RetryUntilFileIsClosedAsync(workAsset.Path);
			await AsyncHelper.SwitchToMainThread();
			AssetBundleRecompressOperation op = AssetBundle.RecompressAssetBundleAsync(workAsset.Path, outputPath, buildCompression, 0u, (ThreadPriority)2);
			await op.WaitCompletionAsync<AssetBundleRecompressOperation>();
			AssetBundleLoadResult result = op.result;
			string humanReadableResult = op.humanReadableResult;
			bool success = op.success;
			string newHash = GetHashOfFile(outputPath);
			await AsyncHelper.SwitchToThreadPool();
			if (workAsset.DeleteBundleAfterOperation)
			{
				FileHelper.TryDeleteFile(workAsset.Path, out Exception _);
			}
			Patcher.Logger.LogDebug((object)$"Result of decompression \"{originalFileName}\": {result} ({success}), {humanReadableResult}");
			if ((int)result != 0 || !success)
			{
				Patcher.Logger.LogWarning((object)$"Failed to decompress a assetbundle at \"{workAsset.Path}\"\nResult: {result}, {humanReadableResult}");
			}
			else if (newHash.Equals(workAsset.Hash, StringComparison.InvariantCultureIgnoreCase))
			{
				Patcher.Logger.LogDebug((object)("Assetbundle \"" + originalFileName + "\" is already uncompressed, adding to ignore list"));
				metadata.ShouldNotDecompress = true;
				Patcher.MetadataManager.SaveMetadata(metadata);
				DeleteCachedAssetBundle(outputPath);
			}
			else
			{
				Patcher.Logger.LogDebug((object)("Assetbundle \"" + originalFileName + "\" is now uncompressed!"));
				metadata.UncompressedAssetBundleName = outputName;
				Patcher.MetadataManager.SaveMetadata(metadata);
			}
			static string GetHashOfFile(string filePath)
			{
				Span<char> destination = stackalloc char[32];
				HashingHelper.HashFile(destination, filePath);
				return destination.ToString();
			}
		}
	}
	internal class MetadataManager
	{
		private readonly string m_MetadataFile;

		private readonly object m_Lock = new object();

		private List<Metadata> m_Metadata;

		public MetadataManager(string metadataFile)
		{
			m_MetadataFile = metadataFile;
			LoadFile();
		}

		public Metadata? FindMetadataByHash(ReadOnlySpan<char> hash)
		{
			lock (m_Lock)
			{
				foreach (Metadata metadatum in m_Metadata)
				{
					if (hash.SequenceEqual(metadatum.OriginalAssetBundleHash))
					{
						return metadatum;
					}
				}
			}
			return null;
		}

		public void SaveMetadata(Metadata metadata)
		{
			Metadata metadata2 = metadata;
			lock (m_Lock)
			{
				int num = m_Metadata.FindIndex((Metadata m) => m.OriginalAssetBundleHash.Equals(metadata2.OriginalAssetBundleHash, StringComparison.InvariantCulture));
				if (num == -1)
				{
					m_Metadata.Add(metadata2);
				}
				else
				{
					m_Metadata[num] = metadata2;
				}
			}
			SaveFile();
		}

		public void DeleteMetadata(Metadata metadata)
		{
			Metadata metadata2 = metadata;
			bool flag = false;
			lock (m_Lock)
			{
				int num = m_Metadata.FindIndex((Metadata m) => m.OriginalAssetBundleHash.Equals(metadata2.OriginalAssetBundleHash, StringComparison.InvariantCulture));
				if (num >= 0)
				{
					flag = true;
					m_Metadata.RemoveAt(num);
				}
			}
			if (flag)
			{
				SaveFile();
			}
		}

		private void LoadFile()
		{
			if (!File.Exists(m_MetadataFile))
			{
				m_Metadata = new List<Metadata>();
				return;
			}
			try
			{
				m_Metadata = JsonConvert.DeserializeObject<List<Metadata>>(File.ReadAllText(m_MetadataFile)) ?? new List<Metadata>();
			}
			catch (Exception arg)
			{
				Patcher.Logger.LogError((object)$"Failed to deserialize metadata.json file\n{arg}");
				m_Metadata = new List<Metadata>();
				return;
			}
			if (!UpgradeMetadata())
			{
				DeleteOldBundles();
			}
		}

		private bool UpgradeMetadata()
		{
			bool flag = false;
			foreach (Metadata metadatum in m_Metadata)
			{
				bool flag2 = metadatum.LastAccessTime == default(DateTime);
				if (flag2)
				{
					metadatum.LastAccessTime = DateTime.Now;
				}
				flag = flag || flag2;
			}
			if (flag)
			{
				SaveFile();
			}
			return flag;
		}

		private void SaveFile()
		{
			lock (m_Lock)
			{
				File.WriteAllText(m_MetadataFile, JsonConvert.SerializeObject((object)m_Metadata));
			}
		}

		private void DeleteOldBundles()
		{
			for (int num = m_Metadata.Count - 1; num >= 0; num--)
			{
				Metadata metadata = m_Metadata[num];
				if (!((DateTime.Now - metadata.LastAccessTime).TotalDays < 3.0))
				{
					m_Metadata.RemoveAt(num);
					if (metadata.UncompressedAssetBundleName != null)
					{
						Patcher.Logger.LogInfo((object)("Deleting unused asset bundle cache " + metadata.UncompressedAssetBundleName));
						Patcher.AssetBundleManager.DeleteCachedAssetBundle(Path.Combine(Patcher.AssetBundleManager.CachePath, metadata.UncompressedAssetBundleName));
					}
				}
			}
			int counter2 = 0;
			string[] files = Directory.GetFiles(Patcher.AssetBundleManager.CachePath, "*.assetbundle", SearchOption.TopDirectoryOnly);
			foreach (string path2 in files)
			{
				string bundleName = Path.GetFileName(path2);
				Metadata metadata2 = m_Metadata.Find((Metadata m) => m.UncompressedAssetBundleName != null && m.UncompressedAssetBundleName.Equals(bundleName, StringComparison.InvariantCulture));
				if (metadata2 == null)
				{
					DeleteFileSafely(ref counter2, path2);
				}
			}
			if (counter2 > 0)
			{
				Patcher.Logger.LogWarning((object)$"Deleted {counter2} unknown bundles. Metadata file got corrupted?");
			}
			static void DeleteFileSafely(ref int counter, string path)
			{
				if (!FileHelper.TryDeleteFile(path, out Exception exception))
				{
					Patcher.Logger.LogWarning((object)$"Failed to delete cache\n{exception}");
				}
				else
				{
					counter++;
				}
			}
		}
	}
}
namespace BepInExFasterLoadAssetBundles.Helpers
{
	internal static class AsyncHelper
	{
		[StructLayout(LayoutKind.Sequential, Size = 1)]
		public readonly struct SwitchToMainThreadAwaiter : ICriticalNotifyCompletion, INotifyCompletion
		{
			private static readonly SendOrPostCallback s_OnPostAction = OnPost;

			public bool IsCompleted => Thread.CurrentThread.ManagedThreadId == s_MainThreadId;

			public SwitchToMainThreadAwaiter GetAwaiter()
			{
				return this;
			}

			public void GetResult()
			{
			}

			public void OnCompleted(Action continuation)
			{
				UnsafeOnCompleted(continuation);
			}

			public void UnsafeOnCompleted(Action continuation)
			{
				((SynchronizationContext)(object)s_SynchronizationContext).Post(s_OnPostAction, (object?)continuation);
			}

			private static void OnPost(object state)
			{
				if (state is Action action)
				{
					action();
				}
			}
		}

		[StructLayout(LayoutKind.Sequential, Size = 1)]
		public readonly struct SwitchToThreadPoolAwaiter : ICriticalNotifyCompletion, INotifyCompletion
		{
			private static readonly WaitCallback s_OnPostAction = OnPost;

			public bool IsCompleted => false;

			public SwitchToThreadPoolAwaiter GetAwaiter()
			{
				return this;
			}

			public void GetResult()
			{
			}

			public void OnCompleted(Action continuation)
			{
				ThreadPool.QueueUserWorkItem(s_OnPostAction, continuation);
			}

			public void UnsafeOnCompleted(Action continuation)
			{
				ThreadPool.UnsafeQueueUserWorkItem(s_OnPostAction, continuation);
			}

			private static void OnPost(object state)
			{
				if (state is Action action)
				{
					action();
				}
			}
		}

		private static UnitySynchronizationContext s_SynchronizationContext = null;

		private static int s_MainThreadId = -1;

		public static void InitUnitySynchronizationContext()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			s_SynchronizationContext = (UnitySynchronizationContext)SynchronizationContext.Current;
			s_MainThreadId = Thread.CurrentThread.ManagedThreadId;
		}

		public static void Schedule(Func<Task> func)
		{
			Func<Task> func2 = func;
			Task.Run(async delegate
			{
				try
				{
					await func2();
				}
				catch (Exception ex)
				{
					Patcher.Logger.LogError((object)ex);
				}
			});
		}

		public static SwitchToMainThreadAwaiter SwitchToMainThread()
		{
			return default(SwitchToMainThreadAwaiter);
		}

		public static SwitchToThreadPoolAwaiter SwitchToThreadPool()
		{
			return default(SwitchToThreadPoolAwaiter);
		}
	}
	internal static class AsyncOperationHelper
	{
		public struct AsyncOperationAwaiter : ICriticalNotifyCompletion, INotifyCompletion
		{
			private AsyncOperation? m_AsyncOperation;

			private Action? m_ContinuationAction;

			public readonly bool IsCompleted => m_AsyncOperation.isDone;

			public AsyncOperationAwaiter(AsyncOperation asyncOperation)
			{
				m_AsyncOperation = asyncOperation;
				m_ContinuationAction = null;
			}

			public readonly AsyncOperationAwaiter GetAwaiter()
			{
				return this;
			}

			public void GetResult()
			{
				if (m_AsyncOperation != null)
				{
					m_AsyncOperation.completed -= OnCompleted;
				}
				m_AsyncOperation = null;
				m_ContinuationAction = null;
			}

			public void OnCompleted(Action continuation)
			{
				UnsafeOnCompleted(continuation);
			}

			public void UnsafeOnCompleted(Action continuation)
			{
				m_ContinuationAction = continuation;
				m_AsyncOperation.completed += OnCompleted;
			}

			private readonly void OnCompleted(AsyncOperation _)
			{
				m_ContinuationAction?.Invoke();
			}
		}

		public static AsyncOperationAwaiter WaitCompletionAsync<T>(this T op) where T : AsyncOperation
		{
			return new AsyncOperationAwaiter((AsyncOperation)(object)op);
		}
	}
	internal static class BundleHelper
	{
		public static bool CheckBundleIsAlreadyDecompressed(Stream stream)
		{
			stream.Seek(0L, SeekOrigin.Begin);
			SkipString(stream);
			stream.Position += 4L;
			SkipString(stream);
			SkipString(stream);
			stream.Position += 16L;
			Span<byte> span = stackalloc byte[4];
			stream.Read(span);
			int num = BinaryPrimitives.ReadInt32BigEndian(span);
			int num2 = num & 0x3F;
			if (num2 == 0 || num2 == 2)
			{
				return true;
			}
			return false;
		}

		private static void SkipString(Stream stream)
		{
			while (stream.ReadByte() != 0)
			{
			}
		}
	}
	internal static class DriveHelper
	{
		public static bool HasDriveSpaceOnPath(string path, long expectedSpaceGB)
		{
			string pathRoot = Path.GetPathRoot(Path.GetFullPath(path));
			DriveInfo driveInfo = new DriveInfo(pathRoot);
			return driveInfo.TotalFreeSpace > expectedSpaceGB * 1073741824;
		}
	}
	internal static class FileHelper
	{
		public const long c_GBToBytes = 1073741824L;

		public const long c_MBToBytes = 1048576L;

		public static bool TryDeleteFile(string path, [NotNullWhen(false)] out Exception? exception)
		{
			try
			{
				File.Delete(path);
				exception = null;
				return true;
			}
			catch (Exception ex)
			{
				exception = ex;
				return false;
			}
		}

		public static async Task RetryUntilFileIsClosedAsync(string path, int maxTries = 5)
		{
			int tries = maxTries;
			while (true)
			{
				int num = tries - 1;
				tries = num;
				if (num <= 0)
				{
					break;
				}
				try
				{
					using (new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
					{
					}
				}
				catch (IOException)
				{
					await Task.Delay(1000);
				}
			}
		}
	}
	internal class HashingHelper
	{
		private const int c_BufferSize = 16777216;

		public static int HashFile(Span<char> destination, string path)
		{
			using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 16777216, FileOptions.SequentialScan);
			return WriteHash(destination, stream);
		}

		public unsafe static int WriteHash(Span<char> destination, Stream stream)
		{
			//IL_0028: 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)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			stream.Seek(0L, SeekOrigin.Begin);
			void* ptr = UnsafeUtility.Malloc(4096L, 16, (Allocator)2);
			Span<byte> buffer = new Span<byte>(ptr, 4096);
			Hash128 val = default(Hash128);
			int num;
			while ((num = stream.Read(buffer)) > 0)
			{
				((Hash128)(ref val)).Append(ptr, (ulong)num);
			}
			UnsafeUtility.Free(ptr, (Allocator)2);
			Span<byte> span = stackalloc byte[16];
			BinaryPrimitives.WriteUInt64LittleEndian(span, val.u64_0);
			BinaryPrimitives.WriteUInt64LittleEndian(span.Slice(8), val.u64_1);
			return HashToString(destination, span);
		}

		private static int HashToString(Span<char> destination, ReadOnlySpan<byte> hash)
		{
			for (int i = 0; i < hash.Length; i++)
			{
				hash[i].TryFormat(destination.Slice(i * 2), out var _, "X2", CultureInfo.InvariantCulture);
			}
			return hash.Length * 2;
		}
	}
}