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 RunicVelocity v1.0.0
RunicVelocity.dll
Decompiled 9 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Collections.Generic; using RunicVelocity.Contracts; using RunicVelocity.Core; using RunicVelocity.Integration; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Velocity")] [assembly: AssemblyDescription("Change-aware bounded startup profiling and plugin manifest caching")] [assembly: AssemblyCompany("Chazman")] [assembly: AssemblyProduct("Runic Velocity")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 RunicVelocity { internal static class VelocityConfig { internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<bool> WarmManifest { get; private set; } internal static ConfigEntry<int> MaximumFiles { get; private set; } internal static ConfigEntry<bool> DetailedTracing { get; private set; } internal static void Bind(ConfigFile config) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Enable bounded startup measurement and the integrity-checked local manifest cache. When false, no timeline, worker, module, or service is started."); WarmManifest = config.Bind<bool>("Manifest", "WarmCache", true, "Reuse a cached SHA-256 and plugin metadata only when the exact relative path, size, and UTC modification time are unchanged. A server challenge may still require fresh hashing."); MaximumFiles = config.Bind<int>("Manifest", "MaximumFiles", 2048, new ConfigDescription("Maximum DLLs considered in one bounded scan.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 4096), Array.Empty<object>())); DetailedTracing = config.Bind<bool>("Diagnostics", "DetailedTracing", false, "Log one bounded manifest summary after the background scan. File paths are not logged."); } } [BepInPlugin("chazman.RunicVelocity", "Runic Velocity", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicVelocity"; public const string Name = "Runic Velocity"; public const string Version = "1.0.0"; private VelocityRuntime _runtime; internal static ManualLogSource Log { get; private set; } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; VelocityConfig.Bind(((BaseUnityPlugin)this).Config); ConfigEntry<bool> enabled = VelocityConfig.Enabled; if (enabled != null && !enabled.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Velocity is disabled; no timeline or manifest worker was started."); return; } try { _runtime = new VelocityRuntime(); _runtime.Start(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Velocity v1.0.0 ready. Manifest hashing runs on a worker and unchanged local files may reuse integrity metadata. Third-party initialization remains untouched."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Velocity failed closed; BepInEx startup remains unchanged. " + ex)); Shutdown(); } } private void Update() { try { _runtime?.Tick(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Velocity diagnostics stopped after an unexpected error. " + ex.Message)); _runtime?.Stop(); _runtime = null; } } private void OnDestroy() { Shutdown(); } private void Shutdown() { try { _runtime?.Stop(); } catch { } _runtime = null; } } } namespace RunicVelocity.Integration { internal sealed class VelocityRuntime { private static readonly long ProcessOrigin = Stopwatch.GetTimestamp(); private readonly StartupTimeline _timeline = new StartupTimeline(ProcessOrigin); private readonly object _gate = new object(); private Task<PluginManifestSnapshot> _scan; private CancellationTokenSource _cancellation; private int _generation; private bool _logged; private bool _enabled; private bool _firstUpdatePending; private bool _mainMenuPending; private bool _networkPending; private bool _worldPending; private bool _playerPending; private bool _scanPending; internal void Start() { _enabled = VelocityConfig.Enabled?.Value ?? true; if (_enabled) { _firstUpdatePending = true; _mainMenuPending = true; _networkPending = true; _worldPending = true; _playerPending = true; _timeline.Record("plugin-awake", 0L); BeginScan(); } } internal void Tick() { if (!_enabled) { return; } if (_firstUpdatePending) { _timeline.Record("first-update", 0L); _firstUpdatePending = false; } if (_mainMenuPending && (Object)(object)FejdStartup.instance != (Object)null) { _timeline.Record("main-menu-instance", 0L); _mainMenuPending = false; } if (_networkPending && (Object)(object)ZNet.instance != (Object)null) { _timeline.Record("network-session-instance", 0L); _networkPending = false; } if (_worldPending && (Object)(object)Game.instance != (Object)null) { _timeline.Record("world-game-instance", 0L); _worldPending = false; } if (_playerPending && (Object)(object)Player.m_localPlayer != (Object)null) { _timeline.Record("local-player-ready", 0L); _playerPending = false; } if (!_firstUpdatePending && !_mainMenuPending && !_networkPending && !_worldPending && !_playerPending && !_scanPending) { return; } Task<PluginManifestSnapshot> scan; lock (_gate) { scan = _scan; } if (scan == null || !scan.IsCompleted) { return; } PluginManifestSnapshot pluginManifestSnapshot; try { pluginManifestSnapshot = scan.GetAwaiter().GetResult(); } catch { pluginManifestSnapshot = new PluginManifestSnapshot(Array.Empty<PluginManifestEntry>(), 0, 0, truncated: true, "scan-failed"); } lock (_gate) { if (_scan != scan) { return; } _scan = null; _scanPending = false; } _timeline.Record("manifest-scan-complete", 0L); if (_logged) { return; } ConfigEntry<bool> detailedTracing = VelocityConfig.DetailedTracing; if (detailedTracing != null && detailedTracing.Value) { _logged = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)($"Velocity manifest: {pluginManifestSnapshot.Entries.Count} DLLs, " + $"{pluginManifestSnapshot.ReusedFiles} reused, {pluginManifestSnapshot.HashedFiles} hashed, status={pluginManifestSnapshot.Status}.")); } } } internal void Stop() { _enabled = false; Interlocked.Increment(ref _generation); try { _cancellation?.Cancel(); } catch { } try { _cancellation?.Dispose(); } catch { } _cancellation = null; lock (_gate) { _scan = null; } _scanPending = false; } private void BeginScan() { int num = Interlocked.Increment(ref _generation); try { _cancellation?.Cancel(); } catch { } try { _cancellation?.Dispose(); } catch { } _cancellation = new CancellationTokenSource(); CancellationToken cancellationToken = _cancellation.Token; string root = Paths.PluginPath; string cache = Path.Combine(Paths.ConfigPath, "RunicVelocity.manifest-cache.bin"); bool warm = VelocityConfig.WarmManifest?.Value ?? true; int maximum = VelocityConfig.MaximumFiles?.Value ?? 2048; _timeline.Record("manifest-scan-start", 0L); _scanPending = true; PluginManifestScanner scanner = new PluginManifestScanner(); Task<PluginManifestSnapshot> scan = Task.Run(() => scanner.Scan(root, cache, warm, maximum, cancellationToken), cancellationToken); lock (_gate) { if (num == _generation) { _scan = scan; } } } } } namespace RunicVelocity.Core { internal static class ManifestCacheCodec { private const int Magic = 1381387331; private const int Schema = 2; private const int MaximumDependencies = 64; private const int MaximumIdentityCharacters = 256; private const int MaximumClassificationCharacters = 64; private static readonly Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); internal static bool TryRead(string path, out Dictionary<string, PluginManifestEntry> entries) { entries = new Dictionary<string, PluginManifestEntry>(ManifestCachePolicy.PathComparer); try { if (!TryReadStableBounded(path, out var bytes)) { return false; } byte[] array; using (MemoryStream memoryStream = new MemoryStream(bytes, writable: false)) { using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: false); if (binaryReader.ReadInt32() != 1381387331 || binaryReader.ReadInt32() != 2) { return false; } int num = binaryReader.ReadInt32(); if (num <= 0 || num > 8388564) { return false; } array = binaryReader.ReadBytes(num); byte[] array2 = binaryReader.ReadBytes(32); if (array.Length != num || array2.Length != 32 || memoryStream.Position != memoryStream.Length) { return false; } using SHA256 sHA = SHA256.Create(); if (!ConstantTimeEquals(array2, sHA.ComputeHash(array))) { return false; } } using MemoryStream memoryStream2 = new MemoryStream(array, writable: false); using BinaryReader binaryReader2 = new BinaryReader(memoryStream2, Encoding.UTF8, leaveOpen: false); int num2 = binaryReader2.ReadInt32(); if (num2 < 0 || num2 > 4096) { return false; } for (int i = 0; i < num2; i++) { string text = ReadBounded(binaryReader2, 1024); long num3 = binaryReader2.ReadInt64(); long num4 = binaryReader2.ReadInt64(); string text2 = ReadBounded(binaryReader2, 64); string pluginId = ReadBounded(binaryReader2, 256); string pluginVersion = ReadBounded(binaryReader2, 256); string classification = ReadBounded(binaryReader2, 64); long lastManifestScanUtcTicks = binaryReader2.ReadInt64(); int num5 = binaryReader2.ReadInt32(); if (num5 < 0 || num5 > 64) { return false; } string[] array3 = new string[num5]; for (int j = 0; j < num5; j++) { array3[j] = ReadBounded(binaryReader2, 256); } if (!ManifestCachePolicy.IsSafeRelativePath(text) || num3 < 0 || num4 < 0 || !ManifestCachePolicy.IsSha256(text2) || entries.ContainsKey(text)) { return false; } entries.Add(text, new PluginManifestEntry(text, num3, num4, text2, pluginId, pluginVersion, array3, classification, lastManifestScanUtcTicks)); } if (memoryStream2.Position != memoryStream2.Length) { return false; } return true; } catch { entries.Clear(); return false; } } private static bool TryReadStableBounded(string path, out byte[] bytes) { bytes = null; try { FileInfo fileInfo = new FileInfo(path); if (!fileInfo.Exists || fileInfo.Length <= 0 || fileInfo.Length > 8388608 || fileInfo.Length > int.MaxValue) { return false; } long length = fileInfo.Length; DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; bytes = new byte[(int)length]; using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, Math.Max(1, Math.Min(65536, bytes.Length)), FileOptions.SequentialScan)) { if (fileStream.Length != length) { bytes = null; return false; } int num; for (int i = 0; i < bytes.Length; i += num) { num = fileStream.Read(bytes, i, bytes.Length - i); if (num <= 0) { bytes = null; return false; } } if (fileStream.ReadByte() != -1 || fileStream.Length != length) { bytes = null; return false; } } fileInfo.Refresh(); if (!fileInfo.Exists || fileInfo.Length != length || fileInfo.LastWriteTimeUtc != lastWriteTimeUtc) { bytes = null; return false; } return true; } catch { bytes = null; return false; } } internal static bool TryWrite(string path, IReadOnlyList<PluginManifestEntry> entries, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrWhiteSpace(path) || entries == null || entries.Count > 4096) { return false; } string text = path + ".pending"; try { cancellationToken.ThrowIfCancellationRequested(); HashSet<string> hashSet = new HashSet<string>(ManifestCachePolicy.PathComparer); byte[] array; using (MemoryStream memoryStream = new MemoryStream()) { using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write(entries.Count); for (int i = 0; i < entries.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); PluginManifestEntry pluginManifestEntry = entries[i]; if (!Validate(pluginManifestEntry) || !hashSet.Add(pluginManifestEntry.RelativePath)) { return false; } WriteBounded(binaryWriter, pluginManifestEntry.RelativePath, 1024); binaryWriter.Write(pluginManifestEntry.Length); binaryWriter.Write(pluginManifestEntry.LastWriteUtcTicks); WriteBounded(binaryWriter, pluginManifestEntry.Sha256, 64); WriteBounded(binaryWriter, pluginManifestEntry.PluginId, 256); WriteBounded(binaryWriter, pluginManifestEntry.PluginVersion, 256); WriteBounded(binaryWriter, pluginManifestEntry.Classification, 64); binaryWriter.Write(pluginManifestEntry.LastManifestScanUtcTicks); binaryWriter.Write(pluginManifestEntry.Dependencies.Count); for (int j = 0; j < pluginManifestEntry.Dependencies.Count; j++) { WriteBounded(binaryWriter, pluginManifestEntry.Dependencies[j], 256); } } binaryWriter.Flush(); if (memoryStream.Length > 8388564) { return false; } array = memoryStream.ToArray(); } byte[] array2; using (SHA256 sHA = SHA256.Create()) { using MemoryStream memoryStream2 = new MemoryStream(); using BinaryWriter binaryWriter2 = new BinaryWriter(memoryStream2, Encoding.UTF8, leaveOpen: true); binaryWriter2.Write(1381387331); binaryWriter2.Write(2); binaryWriter2.Write(array.Length); binaryWriter2.Write(array); binaryWriter2.Write(sHA.ComputeHash(array)); binaryWriter2.Flush(); if (memoryStream2.Length > 8388608) { return false; } array2 = memoryStream2.ToArray(); } string directoryName = Path.GetDirectoryName(path); if (string.IsNullOrEmpty(directoryName)) { return false; } Directory.CreateDirectory(directoryName); using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None)) { int num; for (int k = 0; k < array2.Length; k += num) { cancellationToken.ThrowIfCancellationRequested(); num = Math.Min(65536, array2.Length - k); fileStream.Write(array2, k, num); } fileStream.Flush(flushToDisk: true); } cancellationToken.ThrowIfCancellationRequested(); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } return true; } catch (OperationCanceledException) { try { if (File.Exists(text)) { File.Delete(text); } } catch { } throw; } catch { try { if (File.Exists(text)) { File.Delete(text); } } catch { } return false; } } private static bool Validate(PluginManifestEntry entry) { if (entry == null || !ManifestCachePolicy.IsSafeRelativePath(entry.RelativePath) || entry.Length < 0 || entry.LastWriteUtcTicks < 0 || !ManifestCachePolicy.IsSha256(entry.Sha256) || entry.PluginId.Length > 256 || entry.PluginVersion.Length > 256 || entry.Classification.Length > 64 || entry.Dependencies.Count > 64) { return false; } for (int i = 0; i < entry.Dependencies.Count; i++) { if ((entry.Dependencies[i] ?? string.Empty).Length > 256) { return false; } } return true; } private static string ReadBounded(BinaryReader reader, int maximum) { if (reader == null || maximum < 0) { throw new InvalidDataException("Manifest string bound is invalid."); } int maximum2 = checked(maximum * 4); int num = Read7BitEncodedInt(reader, maximum2); Stream baseStream = reader.BaseStream; if (baseStream.CanSeek && (num < 0 || num > baseStream.Length - baseStream.Position)) { throw new EndOfStreamException("Manifest string exceeds the remaining payload."); } byte[] array = reader.ReadBytes(num); if (array.Length != num) { throw new EndOfStreamException("Manifest string ended early."); } string text = StrictUtf8.GetString(array); if (text.Length > maximum) { throw new InvalidDataException("Manifest field exceeded its bound."); } return text; } private static void WriteBounded(BinaryWriter writer, string value, int maximum) { value = value ?? string.Empty; if (value.Length > maximum) { throw new InvalidDataException("Manifest field exceeded its bound."); } byte[] bytes = StrictUtf8.GetBytes(value); if (bytes.Length > checked(maximum * 4)) { throw new InvalidDataException("Manifest field exceeded its UTF-8 bound."); } Write7BitEncodedInt(writer, bytes.Length); writer.Write(bytes); } private static int Read7BitEncodedInt(BinaryReader reader, int maximum) { uint num = 0u; for (int i = 0; i < 5; i++) { byte b = reader.ReadByte(); if (i == 4 && (b & 0xF0) != 0) { throw new InvalidDataException("Manifest string length is not canonical."); } num |= (uint)((b & 0x7F) << i * 7); if ((b & 0x80) == 0) { if (i > 0 && b == 0) { throw new InvalidDataException("Manifest string length is not canonical."); } if (num > maximum) { throw new InvalidDataException("Manifest string byte length exceeded its bound."); } return (int)num; } } throw new InvalidDataException("Manifest string length is not canonical."); } private static void Write7BitEncodedInt(BinaryWriter writer, int value) { uint num; for (num = checked((uint)value); num >= 128; num >>= 7) { writer.Write((byte)((num & 0x7F) | 0x80)); } writer.Write((byte)num); } private static bool ConstantTimeEquals(byte[] left, byte[] right) { if (left == null || right == null || left.Length != right.Length) { return false; } int num = 0; for (int i = 0; i < left.Length; i++) { num |= left[i] ^ right[i]; } return num == 0; } } internal static class ManifestCachePolicy { internal const int MaximumFiles = 4096; internal const int MaximumDirectories = 4096; internal const long MaximumFileBytes = 536870912L; internal const long MaximumScanBytes = 4294967296L; internal const long MaximumMetadataFileBytes = 67108864L; internal const int MaximumCacheBytes = 8388608; internal const int MaximumPathCharacters = 1024; internal static bool UsesCaseInsensitivePaths => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); internal static StringComparer PathComparer => PathComparerFor(UsesCaseInsensitivePaths); internal static StringComparison PathComparison { get { if (!UsesCaseInsensitivePaths) { return StringComparison.Ordinal; } return StringComparison.OrdinalIgnoreCase; } } internal static StringComparer PathComparerFor(bool caseInsensitive) { if (!caseInsensitive) { return StringComparer.Ordinal; } return StringComparer.OrdinalIgnoreCase; } internal static bool CanReuse(PluginManifestEntry cached, long currentLength, long currentLastWriteUtcTicks) { if (cached != null && cached.Length == currentLength && cached.LastWriteUtcTicks == currentLastWriteUtcTicks) { return IsSha256(cached.Sha256); } return false; } internal static bool CanAdmitScanBytes(long alreadyAdmitted, long nextFileBytes) { if (alreadyAdmitted >= 0 && nextFileBytes >= 0 && nextFileBytes <= 536870912) { return alreadyAdmitted <= 4294967296L - nextFileBytes; } return false; } internal static bool IsSafeRelativePath(string value) { if (!string.IsNullOrWhiteSpace(value) && value.Length <= 1024 && !Path.IsPathRooted(value) && value.IndexOf('\0') < 0) { return !HasParentSegment(value); } return false; } internal static bool IsSha256(string value) { if (value == null || value.Length != 64) { return false; } foreach (char c in value) { if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) { return false; } } return true; } internal static string Classification(string pluginId, string relativePath) { if (!string.IsNullOrEmpty(pluginId) && pluginId.StartsWith("chazman.Runic", StringComparison.Ordinal)) { return "runic-plugin"; } if (!string.IsNullOrEmpty(pluginId)) { return "third-party-plugin"; } return "library"; } private static bool HasParentSegment(string value) { string[] array = value.Replace('\\', '/').Split('/'); for (int i = 0; i < array.Length; i++) { if (array[i] == "..") { return true; } } return false; } } internal sealed class PluginManifestScanner { private const string PluginAttribute = "BepInEx.BepInPlugin"; private const string DependencyAttribute = "BepInEx.BepInDependency"; internal PluginManifestSnapshot Scan(string root, string cachePath, bool allowWarmCache, int configuredMaximumFiles, CancellationToken cancellationToken = default(CancellationToken)) { int maximum = Math.Max(1, Math.Min(4096, configuredMaximumFiles)); if (!allowWarmCache || !ManifestCacheCodec.TryRead(cachePath, out var entries)) { entries = new Dictionary<string, PluginManifestEntry>(ManifestCachePolicy.PathComparer); } bool truncated; List<string> list = EnumerateDlls(root, maximum, cancellationToken, out truncated); List<PluginManifestEntry> list2 = new List<PluginManifestEntry>(list.Count); int num = 0; int num2 = 0; bool metadataPartial = false; long num3 = 0L; long ticks = DateTime.UtcNow.Ticks; byte[] buffer = new byte[131072]; foreach (string item in list) { cancellationToken.ThrowIfCancellationRequested(); FileInfo fileInfo; string text; try { fileInfo = new FileInfo(item); text = RelativePath(root, item); } catch { truncated = true; continue; } if (!ManifestCachePolicy.IsSafeRelativePath(text) || !fileInfo.Exists || !ManifestCachePolicy.CanAdmitScanBytes(num3, fileInfo.Length)) { truncated = true; continue; } num3 += fileInfo.Length; if (entries.TryGetValue(text, out var value) && ManifestCachePolicy.CanReuse(value, fileInfo.Length, fileInfo.LastWriteTimeUtc.Ticks)) { list2.Add(new PluginManifestEntry(text, value.Length, value.LastWriteUtcTicks, value.Sha256, value.PluginId, value.PluginVersion, value.Dependencies, value.Classification, ticks)); num2++; continue; } long length = fileInfo.Length; long ticks2 = fileInfo.LastWriteTimeUtc.Ticks; bool inspectMetadata = length <= 67108864; if (!TryReadEvidence(item, length, buffer, inspectMetadata, cancellationToken, out var sha, out var pluginId, out var version, out var dependencies, out var metadataComplete)) { truncated = true; continue; } if (!metadataComplete) { metadataPartial = true; } fileInfo.Refresh(); if (!fileInfo.Exists || fileInfo.Length != length || fileInfo.LastWriteTimeUtc.Ticks != ticks2) { truncated = true; continue; } list2.Add(new PluginManifestEntry(text, length, ticks2, sha, pluginId, version, dependencies, ManifestCachePolicy.Classification(pluginId, text), ticks)); num++; } list2.Sort(delegate(PluginManifestEntry left, PluginManifestEntry right) { int num4 = ManifestCachePolicy.PathComparer.Compare(left.RelativePath, right.RelativePath); return (num4 == 0) ? StringComparer.Ordinal.Compare(left.RelativePath, right.RelativePath) : num4; }); cancellationToken.ThrowIfCancellationRequested(); bool cacheWritten = ManifestCacheCodec.TryWrite(cachePath, list2, cancellationToken); string status = Status(truncated, metadataPartial, cacheWritten, num, num2); return new PluginManifestSnapshot(list2, num, num2, truncated, status); } private static string Status(bool truncated, bool metadataPartial, bool cacheWritten, int hashed, int reused) { if (truncated) { return "bounded-partial"; } if (!cacheWritten) { return "manifest-cache-write-failed"; } if (metadataPartial) { return "hash-complete-metadata-partial"; } if (reused > 0) { if (hashed <= 0) { return "warm-cache-reused"; } return "mixed-fresh-and-warm"; } return "freshly-hashed"; } private static List<string> EnumerateDlls(string root, int maximum, CancellationToken cancellationToken, out bool truncated) { truncated = false; List<string> list = new List<string>(maximum); if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root)) { truncated = true; return list; } Queue<string> queue = new Queue<string>(); queue.Enqueue(Path.GetFullPath(root)); int num = 0; int num2 = 1; while (queue.Count > 0) { cancellationToken.ThrowIfCancellationRequested(); if (++num > 4096) { truncated = true; break; } string path = queue.Dequeue(); try { foreach (string item in Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly)) { cancellationToken.ThrowIfCancellationRequested(); if (list.Count >= maximum) { truncated = true; break; } list.Add(item); } if (list.Count >= maximum) { break; } foreach (string item2 in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)) { cancellationToken.ThrowIfCancellationRequested(); if (num2 >= 4096) { truncated = true; break; } if ((File.GetAttributes(item2) & FileAttributes.ReparsePoint) == 0) { queue.Enqueue(item2); num2++; } } continue; } catch { truncated = true; continue; } } return list; } private static string RelativePath(string root, string fullPath) { string text = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; string fullPath2 = Path.GetFullPath(fullPath); if (!fullPath2.StartsWith(text, ManifestCachePolicy.PathComparison)) { return string.Empty; } return fullPath2.Substring(text.Length).Replace(Path.DirectorySeparatorChar, '/'); } private static bool TryReadEvidence(string path, long expectedLength, byte[] buffer, bool inspectMetadata, CancellationToken cancellationToken, out string sha, out string pluginId, out string version, out string[] dependencies, out bool metadataComplete) { sha = string.Empty; pluginId = string.Empty; version = string.Empty; dependencies = Array.Empty<string>(); metadataComplete = false; try { if (expectedLength < 0 || expectedLength > 536870912 || buffer == null || buffer.Length == 0) { return false; } using SHA256 sHA = SHA256.Create(); using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, buffer.Length, FileOptions.SequentialScan); if (fileStream.Length != expectedLength) { return false; } long num = expectedLength; while (num > 0) { cancellationToken.ThrowIfCancellationRequested(); int count = (int)Math.Min(buffer.Length, num); int num2 = fileStream.Read(buffer, 0, count); if (num2 <= 0) { return false; } sHA.TransformBlock(buffer, 0, num2, buffer, 0); num -= num2; } if (fileStream.ReadByte() != -1 || fileStream.Length != expectedLength) { return false; } sHA.TransformFinalBlock(Array.Empty<byte>(), 0, 0); sha = BitConverter.ToString(sHA.Hash).Replace("-", string.Empty); if (!ManifestCachePolicy.IsSha256(sha)) { return false; } if (inspectMetadata) { cancellationToken.ThrowIfCancellationRequested(); fileStream.Position = 0L; metadataComplete = ReadMetadata(fileStream, cancellationToken, out pluginId, out version, out dependencies); } return true; } catch (OperationCanceledException) { throw; } catch { return false; } } private static bool ReadMetadata(Stream stream, CancellationToken cancellationToken, out string pluginId, out string version, out string[] dependencies) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) pluginId = string.Empty; version = string.Empty; dependencies = Array.Empty<string>(); try { AssemblyDefinition val = AssemblyDefinition.ReadAssembly(stream, new ReaderParameters { ReadingMode = (ReadingMode)2, ReadSymbols = false, InMemory = false }); try { SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.Ordinal); Queue<TypeDefinition> queue = new Queue<TypeDefinition>(); Enumerator<TypeDefinition> enumerator = val.MainModule.Types.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current = enumerator.Current; cancellationToken.ThrowIfCancellationRequested(); if (queue.Count >= 65536) { throw new InvalidDataException("Metadata pending-type bound exceeded."); } queue.Enqueue(current); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } int num = 0; int num2 = 0; while (queue.Count > 0) { cancellationToken.ThrowIfCancellationRequested(); if (++num > 65536) { throw new InvalidDataException("Metadata type bound exceeded."); } TypeDefinition val2 = queue.Dequeue(); enumerator = val2.NestedTypes.GetEnumerator(); try { while (enumerator.MoveNext()) { TypeDefinition current2 = enumerator.Current; cancellationToken.ThrowIfCancellationRequested(); if (queue.Count >= 65536) { throw new InvalidDataException("Metadata pending-type bound exceeded."); } queue.Enqueue(current2); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } Enumerator<CustomAttribute> enumerator2 = val2.CustomAttributes.GetEnumerator(); try { while (enumerator2.MoveNext()) { CustomAttribute current3 = enumerator2.Current; cancellationToken.ThrowIfCancellationRequested(); if (++num2 > 262144) { throw new InvalidDataException("Metadata attribute bound exceeded."); } CustomAttributeArgument val3; if (((MemberReference)current3.AttributeType).FullName == "BepInEx.BepInPlugin" && current3.ConstructorArguments.Count >= 3 && string.IsNullOrEmpty(pluginId)) { val3 = current3.ConstructorArguments[0]; pluginId = BoundedExact(((CustomAttributeArgument)(ref val3)).Value as string); val3 = current3.ConstructorArguments[2]; version = BoundedExact(((CustomAttributeArgument)(ref val3)).Value as string); } else { if (!(((MemberReference)current3.AttributeType).FullName == "BepInEx.BepInDependency") || current3.ConstructorArguments.Count < 1) { continue; } val3 = current3.ConstructorArguments[0]; string text = BoundedExact(((CustomAttributeArgument)(ref val3)).Value as string); if (!string.IsNullOrEmpty(text) && !sortedSet.Contains(text)) { if (sortedSet.Count >= 64) { throw new InvalidDataException("Metadata dependency bound exceeded."); } sortedSet.Add(text); } } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } dependencies = sortedSet.ToArray(); return true; } finally { ((IDisposable)val)?.Dispose(); } } catch (OperationCanceledException) { throw; } catch { pluginId = string.Empty; version = string.Empty; dependencies = Array.Empty<string>(); return false; } } private static string BoundedExact(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } if (value.Length > 256) { throw new InvalidDataException("Metadata identity bound exceeded."); } return value; } } internal sealed class StartupTimeline { internal const int MaximumStages = 64; private readonly object _gate = new object(); private readonly long _origin; private readonly List<StartupStageSample> _stages = new List<StartupStageSample>(); private readonly HashSet<string> _recorded = new HashSet<string>(StringComparer.Ordinal); internal StartupTimelineSnapshot Current { get { lock (_gate) { return new StartupTimelineSnapshot(_stages); } } } internal StartupTimeline(long origin) { _origin = ((origin > 0) ? origin : Stopwatch.GetTimestamp()); } internal bool Record(string stageId, long timestamp = 0L) { if (string.IsNullOrWhiteSpace(stageId) || stageId.Length > 96) { return false; } lock (_gate) { if (_stages.Count >= 64 || !_recorded.Add(stageId)) { return false; } long num = ((timestamp > 0) ? timestamp : Stopwatch.GetTimestamp()); long num2 = Math.Max(0L, num - _origin); _stages.Add(new StartupStageSample(stageId, (double)num2 * 1000.0 / (double)Stopwatch.Frequency)); return true; } } } } namespace RunicVelocity.Contracts { public sealed class StartupStageSample { public string StageId { get; } public double MillisecondsFromAssemblyLoad { get; } public StartupStageSample(string stageId, double millisecondsFromAssemblyLoad) { if (string.IsNullOrWhiteSpace(stageId) || stageId.Length > 96 || millisecondsFromAssemblyLoad < 0.0 || double.IsNaN(millisecondsFromAssemblyLoad) || double.IsInfinity(millisecondsFromAssemblyLoad)) { throw new ArgumentOutOfRangeException("stageId"); } StageId = stageId; MillisecondsFromAssemblyLoad = millisecondsFromAssemblyLoad; } } public sealed class StartupTimelineSnapshot { public IReadOnlyList<StartupStageSample> Stages { get; } public StartupTimelineSnapshot(IEnumerable<StartupStageSample> stages) { Stages = new List<StartupStageSample>(stages ?? Array.Empty<StartupStageSample>()).AsReadOnly(); } } public sealed class PluginManifestEntry { public string RelativePath { get; } public long Length { get; } public long LastWriteUtcTicks { get; } public string Sha256 { get; } public string PluginId { get; } public string PluginVersion { get; } public IReadOnlyList<string> Dependencies { get; } public string Classification { get; } public long LastManifestScanUtcTicks { get; } public PluginManifestEntry(string relativePath, long length, long lastWriteUtcTicks, string sha256, string pluginId, string pluginVersion, IEnumerable<string> dependencies, string classification, long lastManifestScanUtcTicks = 0L) { RelativePath = relativePath ?? string.Empty; Length = length; LastWriteUtcTicks = lastWriteUtcTicks; Sha256 = sha256 ?? string.Empty; PluginId = pluginId ?? string.Empty; PluginVersion = pluginVersion ?? string.Empty; Dependencies = new List<string>(dependencies ?? Array.Empty<string>()).AsReadOnly(); Classification = classification ?? string.Empty; LastManifestScanUtcTicks = Math.Max(0L, lastManifestScanUtcTicks); } } public sealed class PluginManifestSnapshot { public IReadOnlyList<PluginManifestEntry> Entries { get; } public int HashedFiles { get; } public int ReusedFiles { get; } public bool Truncated { get; } public string Status { get; } public static PluginManifestSnapshot Empty { get; } = new PluginManifestSnapshot(Array.Empty<PluginManifestEntry>(), 0, 0, truncated: false, "not-started"); public PluginManifestSnapshot(IEnumerable<PluginManifestEntry> entries, int hashedFiles, int reusedFiles, bool truncated, string status) { Entries = new List<PluginManifestEntry>(entries ?? Array.Empty<PluginManifestEntry>()).AsReadOnly(); HashedFiles = Math.Max(0, hashedFiles); ReusedFiles = Math.Max(0, reusedFiles); Truncated = truncated; Status = status ?? string.Empty; } } }