Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BepInEx Faster Load AssetBundles Patcher v1.2.0
BepInEx/patchers/BepInExFasterLoadAssetBundles/BepInExFasterLoadAssetBundles.dll
Decompiled 2 weeks agousing System; using System.Buffers; 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.Configuration; using BepInEx.Logging; using BepInExFasterLoadAssetBundles.Helpers; using BepInExFasterLoadAssetBundles.Managers; using BepInExFasterLoadAssetBundles.Models; using HarmonyLib; using LethalConfig; using LethalConfig.AutoConfig; using LethalConfig.ConfigItems; using LethalConfig.Mods; using LethalConfig.Utils; 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: IgnoresAccessChecksTo("LethalConfig")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BepInExFasterLoadAssetBundles")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0+91ac06de1aa74b02b6b38b9eb62de23fc53a54a6")] [assembly: AssemblyProduct("BepInExFasterLoadAssetBundles")] [assembly: AssemblyTitle("BepInExFasterLoadAssetBundles")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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; } internal static ConfigManager ConfigManager { get; private set; } [HarmonyPatch(typeof(Chainloader), "Initialize")] [HarmonyPostfix] public static void ChainloaderInitialized() { AsyncHelper.InitUnitySynchronizationContext(); ConfigManager = new ConfigManager(); 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")); PatchAssetBundle(); BepInExFasterLoadAssetBundlesPatcher.Harmony.CreateProcessor((MethodBase)typeof(Chainloader).GetMethod("Start", AccessTools.all)).AddPostfix(typeof(Patcher).GetMethod("ChainloaderStarted", AccessTools.all)).Patch(); } public static void ChainloaderStarted() { if (LethalConfigHelper.IsEnabled) { LethalConfigHelper.RegisterConfig(); } } private static void PatchAssetBundle() { //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 //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0190: 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); harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle2, "LoadFromMemory_Internal", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle.GetMethod("LoadAssetBundleFromMemoryFast", 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 LoadAssetBundleFromMemoryFast(byte[] binary, ref AssetBundle? __result) { byte[] array = ArrayPool<byte>.Shared.Rent(binary.Length); binary.CopyTo(array, 0); using MemoryStream stream = new MemoryStream(array, 0, binary.Length); string path; bool flag = HandleStreamBundle(stream, out path); ArrayPool<byte>.Shared.Return(array); if (flag) { __result = AssetBundle.LoadFromFile_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 count = 0; try { foreach (string item in Directory.EnumerateFiles(CachePath, "*.tmp").Concat(Directory.EnumerateFiles(m_PathForTemp, "*.assetbundle"))) { DeleteFileSafely(ref count, item); } } catch (Exception arg) { Patcher.Logger.LogError((object)$"Failed to delete temp files\n{arg}"); } if (count > 0) { Patcher.Logger.LogWarning((object)$"Deleted {count} temp files"); } static void DeleteFileSafely(ref int reference, string tempFile) { if (!FileHelper.TryDeleteFile(tempFile, out Exception exception)) { Patcher.Logger.LogError((object)$"Failed to delete temp file\n{exception}"); } else { reference++; } } } 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 metadata = Patcher.MetadataManager.FindMetadataByHash(hash); if (metadata == null) { return false; } if (metadata.ShouldNotDecompress) { ModifyAccessTimeAndSave(metadata); return true; } if (metadata.UncompressedAssetBundleName == null) { return false; } string text = Path.Combine(CachePath, metadata.UncompressedAssetBundleName); if (!File.Exists(text)) { Patcher.Logger.LogWarning((object)("Failed to find decompressed assetbundle at \"" + text + "\". Probably it was deleted?")); Patcher.MetadataManager.DeleteMetadata(metadata); return false; } Patcher.Logger.LogDebug((object)("Loading uncompressed bundle \"" + metadata.UncompressedAssetBundleName + "\"")); path = text; ModifyAccessTimeAndSave(metadata); return true; static void ModifyAccessTimeAndSave(Metadata metadata2) { metadata2.LastAccessTime = DateTime.Now; Patcher.MetadataManager.SaveMetadata(metadata2); } } 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 ConfigManager { internal ConfigFile Config; internal ConfigEntry<int> CacheRetentionDays; public ConfigManager() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown Config = new ConfigFile(Path.Combine(new string[2] { Application.persistentDataPath, "BepInExFasterLoadAssetBundlesPatcher.cfg" }), false); CacheRetentionDays = Config.Bind<int>("Cache", "Cache retention (days)", 7, new ConfigDescription("Deletes cached bundles after the specified number of days", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 99), Array.Empty<object>())); } } 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) { lock (m_Lock) { int num = m_Metadata.FindIndex((Metadata m) => m.OriginalAssetBundleHash.Equals(metadata.OriginalAssetBundleHash, StringComparison.InvariantCulture)); if (num == -1) { m_Metadata.Add(metadata); } else { m_Metadata[num] = metadata; } } SaveFile(); } public void DeleteMetadata(Metadata metadata) { bool flag = false; lock (m_Lock) { int num = m_Metadata.FindIndex((Metadata m) => m.OriginalAssetBundleHash.Equals(metadata.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 < (double)Patcher.ConfigManager.CacheRetentionDays.Value)) { 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 counter = 0; string[] files = Directory.GetFiles(Patcher.AssetBundleManager.CachePath, "*.assetbundle", SearchOption.TopDirectoryOnly); foreach (string path in files) { string bundleName = Path.GetFileName(path); Metadata metadata2 = m_Metadata.Find((Metadata m) => m.UncompressedAssetBundleName != null && m.UncompressedAssetBundleName.Equals(bundleName, StringComparison.InvariantCulture)); if (metadata2 == null) { DeleteFileSafely(ref counter, path); } } if (counter > 0) { Patcher.Logger.LogWarning((object)$"Deleted {counter} unknown bundles. Metadata file got corrupted?"); } static void DeleteFileSafely(ref int reference, string path2) { if (!FileHelper.TryDeleteFile(path2, out Exception exception)) { Patcher.Logger.LogWarning((object)$"Failed to delete cache\n{exception}"); } else { reference++; } } } } } 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) { Task.Run(async delegate { try { await func(); } 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 { private delegate void GetDiskFreeSpace(string path, out ulong availableFreeSpace, out ulong totalSize, out ulong totalFreeSpace); private static readonly bool s_IgnoreDriveSpace; private static readonly GetDiskFreeSpace s_GetDiskFreeSpace; static DriveHelper() { s_IgnoreDriveSpace = Environment.GetCommandLineArgs().Contains<string>("--ignore-space-check", StringComparer.OrdinalIgnoreCase); MethodInfo method = typeof(DriveInfo).GetMethod("GetDiskFreeSpace", BindingFlags.Static | BindingFlags.NonPublic); s_GetDiskFreeSpace = AccessTools.MethodDelegate<GetDiskFreeSpace>(method, (object)null, true); } public static bool HasDriveSpaceOnPath(string path, long expectedSpaceGB) { if (s_IgnoreDriveSpace) { return true; } path = Path.GetFullPath(path); s_GetDiskFreeSpace(path, out var availableFreeSpace, out var _, out var _); return availableFreeSpace > (ulong)(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; } } internal static class LethalConfigHelper { public static bool IsEnabled => Chainloader.PluginInfos.ContainsKey("ainavt.lc.lethalconfig"); [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static void RegisterConfig() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown ModInfo val = new ModInfo(); val.Name = "BepInExFasterLoadAssetBundlesPatcher"; val.Guid = "BepInExFasterLoadAssetBundlesPatcher"; val.Version = typeof(BepInExFasterLoadAssetBundlesPatcher).Assembly.GetName().Version.ToString(); val.Description = "Makes start up loading time faster"; val.Icon = Assets.DefaultModIcon; ModInfo val2 = val; Mod val3 = new Mod(val2); LethalConfigManager.Mods.Add(val2.Guid, val3); BaseConfigItem val4 = AutoConfigGenerator.GenerateConfigForEntry((ConfigEntryBase)(object)Patcher.ConfigManager.CacheRetentionDays); if (val4 != null) { val4.IsAutoGenerated = true; val4.Owner = val3; val3.ConfigItems.Add(val4); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }