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