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 Replay v1.0.0
LCReplay.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using LCReplay.Core; using LCReplay.Core.Archive; using LCReplay.Core.Lifecycle; using LCReplay.Plugin.Capture; using LCReplay.Plugin.Library; using LCReplay.Plugin.Playback; using LCReplay.Plugin.UI; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using TMPro; using Unity.Collections; using UnityEngine; using UnityEngine.Animations; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Utilities; using UnityEngine.Playables; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; using UnityEngine.VFX; [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("LCReplay")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Local gameplay recorder and isolated free-camera replay for Lethal Company.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+31677f6cc6ace662863f1d3840f84ade19c50bbc")] [assembly: AssemblyProduct("LCReplay")] [assembly: AssemblyTitle("LCReplay")] [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.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 LCReplay.Plugin { internal static class AnimationAssetRegistry { private sealed class Candidate { internal RuntimeAnimatorController Controller; internal Avatar? Avatar; internal string Kind = ""; } private static readonly List<Candidate> Candidates = new List<Candidate>(); private static object? scannedNetworkConfig; internal static void Remember(Animator animator, string kind = "") { if (Object.op_Implicit((Object)(object)animator) && Object.op_Implicit((Object)(object)animator.runtimeAnimatorController)) { if (kind.Length == 0) { kind = Classify(((Component)animator).gameObject); } Add(animator.runtimeAnimatorController, animator.avatar, kind); } } private static string Classify(GameObject obj) { Type type = GameAccess.Type("GameNetcodeStuff.PlayerControllerB"); if (type != null && (Object.op_Implicit((Object)(object)obj.GetComponentInParent(type)) || Object.op_Implicit((Object)(object)obj.GetComponentInChildren(type, true)))) { return "player"; } Type type2 = GameAccess.Type("EnemyAI"); if (!(type2 != null) || (!Object.op_Implicit((Object)(object)obj.GetComponentInParent(type2)) && !Object.op_Implicit((Object)(object)obj.GetComponentInChildren(type2, true)))) { return ""; } return "enemy"; } private static void Add(RuntimeAnimatorController controller, Avatar? avatar, string kind) { if (!Candidates.Any((Candidate candidate) => (Object)(object)candidate.Controller == (Object)(object)controller && (Object)(object)candidate.Avatar == (Object)(object)avatar && candidate.Kind == kind)) { Candidates.Add(new Candidate { Controller = controller, Avatar = avatar, Kind = kind }); } } private static void RememberInstalledPrefabs() { object obj = GameAccess.Read(GameAccess.Singleton("Unity.Netcode.NetworkManager"), "NetworkConfig"); if (obj == null || obj == scannedNetworkConfig) { return; } object? obj2 = GameAccess.Read(obj, "PlayerPrefab"); GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null); if (!Object.op_Implicit((Object)(object)val)) { object? obj3 = GameAccess.Read(GameAccess.Singleton("StartOfRound"), "playerPrefab"); val = (GameObject)((obj3 is GameObject) ? obj3 : null); } if (Object.op_Implicit((Object)(object)val)) { Animator[] componentsInChildren = val.GetComponentsInChildren<Animator>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Remember(componentsInChildren[i], "player"); } } if (!(GameAccess.Read(GameAccess.Read(obj, "Prefabs"), "Prefabs") is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { object? obj4 = GameAccess.Read(item, "Prefab"); GameObject val2 = (GameObject)((obj4 is GameObject) ? obj4 : null); if (Object.op_Implicit((Object)(object)val2)) { string kind = Classify(val2); Animator[] componentsInChildren = val2.GetComponentsInChildren<Animator>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Remember(componentsInChildren[i], kind); } } } scannedNetworkConfig = obj; } internal static (RuntimeAnimatorController? Controller, Avatar? Avatar) Resolve(string controllerName, string avatarName, string actorKind, IEnumerable<string> recordedClips) { if (controllerName.Length == 0) { return (Controller: null, Avatar: null); } RememberInstalledPrefabs(); Animator[] array = Resources.FindObjectsOfTypeAll<Animator>(); foreach (Animator val in array) { if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.runtimeAnimatorController) && ((Object)val.runtimeAnimatorController).name == controllerName) { Remember(val); } } RuntimeAnimatorController[] array2 = Resources.FindObjectsOfTypeAll<RuntimeAnimatorController>(); foreach (RuntimeAnimatorController controller in array2) { if (Object.op_Implicit((Object)(object)controller) && ((Object)controller).name == controllerName && !Candidates.Any((Candidate candidate) => (Object)(object)candidate.Controller == (Object)(object)controller)) { Add(controller, null, ""); } } Candidate[] array3 = Candidates.Where((Candidate candidate) => Object.op_Implicit((Object)(object)candidate.Controller) && ((Object)candidate.Controller).name == controllerName).ToArray(); if (actorKind == "player" || actorKind == "enemy") { Candidate[] array4 = array3.Where((Candidate candidate) => candidate.Kind == actorKind).ToArray(); array3 = ((array4.Length != 0) ? array4 : ((actorKind == "player") ? Array.Empty<Candidate>() : array3.Where((Candidate candidate) => candidate.Kind.Length == 0).ToArray())); } if (array3.Length == 0) { return (Controller: null, Avatar: null); } if (avatarName.Length != 0) { Candidate[] array5 = array3.Where((Candidate candidate) => Object.op_Implicit((Object)(object)candidate.Avatar) && ((Object)candidate.Avatar).name == avatarName).ToArray(); if (array5.Length == 0) { Avatar[] avatars = (from avatar in Resources.FindObjectsOfTypeAll<Avatar>() where Object.op_Implicit((Object)(object)avatar) && ((Object)avatar).name == avatarName select avatar).Take(2).ToArray(); if (avatars.Length != 1) { return (Controller: null, Avatar: null); } array3 = array3.Where((Candidate candidate) => (Object)(object)candidate.Avatar == (Object)null || (Object)(object)candidate.Avatar == (Object)(object)avatars[0]).ToArray(); if (array3.Length == 0) { return (Controller: null, Avatar: null); } return Select(array3, recordedClips, avatars[0]); } array3 = array5; } return Select(array3, recordedClips, null); } private static (RuntimeAnimatorController? Controller, Avatar? Avatar) Select(Candidate[] candidates, IEnumerable<string> recordedClips, Avatar? fallbackAvatar) { string[] clips = recordedClips.Where((string name) => !string.IsNullOrEmpty(name)).Distinct<string>(StringComparer.Ordinal).Take(32) .ToArray(); if (candidates.Select((Candidate item) => item.Controller).Distinct().Count() > 1 && clips.Length != 0) { candidates = candidates.Where(delegate(Candidate candidate2) { try { HashSet<string> hashSet = new HashSet<string>(from clip in candidate2.Controller.animationClips where Object.op_Implicit((Object)(object)clip) select ((Object)clip).name, StringComparer.Ordinal); return clips.All(hashSet.Contains); } catch { return false; } }).ToArray(); } RuntimeAnimatorController[] controllers = candidates.Select((Candidate item) => item.Controller).Distinct().ToArray(); if (controllers.Length != 1) { return (Controller: null, Avatar: null); } Candidate candidate = candidates.First((Candidate item) => (Object)(object)item.Controller == (Object)(object)controllers[0]); return (Controller: candidate.Controller, Avatar: Object.op_Implicit((Object)(object)candidate.Avatar) ? candidate.Avatar : fallbackAvatar); } } internal static class ItemAssetRegistry { private sealed class Candidate { internal string ItemId = ""; internal AudioClip Clip; } private sealed class HandCandidate { internal string Name = ""; internal bool TwoHandedAnimation; internal Vector3 PositionOffset; internal Vector3 RotationOffset; } private static readonly List<Candidate> Candidates = new List<Candidate>(); private static readonly List<HandCandidate> HandCandidates = new List<HandCandidate>(); private static readonly Dictionary<string, bool> HandCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, float> NextHandScan = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, (Vector3 Position, Vector3 Rotation)> HeldPoseCache = new Dictionary<string, (Vector3, Vector3)>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, float> NextHeldPoseScan = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static object? scannedConfig; private static void Remember(object? properties) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) if (properties == null) { return; } string name = GameAccess.Read(properties, "itemName") as string; if (!string.IsNullOrWhiteSpace(name)) { object obj = GameAccess.Read(properties, "twoHandedAnimation"); if (obj is bool) { bool twoHanded = (bool)obj; obj = GameAccess.Read(properties, "positionOffset"); if (obj is Vector3) { Vector3 positionOffset = (Vector3)obj; obj = GameAccess.Read(properties, "rotationOffset"); if (obj is Vector3) { Vector3 rotationOffset = (Vector3)obj; if (GameAccess.Finite(positionOffset) && GameAccess.Finite(rotationOffset) && !HandCandidates.Any((HandCandidate candidate) => candidate.Name == name && candidate.TwoHandedAnimation == twoHanded && candidate.PositionOffset == positionOffset && candidate.RotationOffset == rotationOffset)) { HandCandidates.Add(new HandCandidate { Name = name, TwoHandedAnimation = twoHanded, PositionOffset = positionOffset, RotationOffset = rotationOffset }); } } } } } AudioClip clip = default(AudioClip); ref AudioClip reference = ref clip; object? obj2 = GameAccess.Read(properties, "dropSFX"); reference = (AudioClip)((obj2 is AudioClip) ? obj2 : null); if (Object.op_Implicit((Object)(object)clip)) { string id = GameAccess.Scalar(GameAccess.Read(properties, "itemId")) ?? ""; if (!Candidates.Any((Candidate candidate) => candidate.ItemId == id && (Object)(object)candidate.Clip == (Object)(object)clip)) { Candidates.Add(new Candidate { ItemId = id, Clip = clip }); } } } private static void Scan() { object obj = GameAccess.Read(GameAccess.Singleton("Unity.Netcode.NetworkManager"), "NetworkConfig"); if (obj != null && obj != scannedConfig) { Type type = GameAccess.Type("GrabbableObject"); IEnumerable enumerable = GameAccess.Read(GameAccess.Read(obj, "Prefabs"), "Prefabs") as IEnumerable; if (type != null && enumerable != null) { foreach (object item in enumerable) { object? obj2 = GameAccess.Read(item, "Prefab"); GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null); if (Object.op_Implicit((Object)(object)val)) { Component componentInChildren = val.GetComponentInChildren(type, true); if (Object.op_Implicit((Object)(object)componentInChildren)) { Remember(GameAccess.Read(componentInChildren, "itemProperties")); } } } } scannedConfig = obj; } Type type2 = GameAccess.Type("Item"); if (type2 != null) { Object[] array = Resources.FindObjectsOfTypeAll(type2); for (int i = 0; i < array.Length; i++) { Remember(array[i]); } } } internal static AudioClip? Resolve(string itemId, string clipName) { if (itemId.Length == 0 && clipName.Length == 0) { return null; } AudioClip val = Match(itemId, clipName); if ((Object)(object)val != (Object)null) { return val; } Scan(); return Match(itemId, clipName); } internal static bool? ResolveTwoHandedAnimation(string itemName) { if (string.IsNullOrWhiteSpace(itemName)) { return null; } if (HandCache.TryGetValue(itemName, out var value)) { return value; } if (NextHandScan.TryGetValue(itemName, out var value2) && Time.realtimeSinceStartup < value2) { return null; } Scan(); bool[] array = (from candidate in HandCandidates where string.Equals(candidate.Name, itemName, StringComparison.OrdinalIgnoreCase) select candidate.TwoHandedAnimation).Distinct().Take(2).ToArray(); if (array.Length == 1) { Dictionary<string, bool> handCache = HandCache; string key = itemName; bool num = array[0]; bool value3 = num; handCache[key] = num; return value3; } NextHandScan[itemName] = Time.realtimeSinceStartup + 1f; return null; } internal static bool TryResolveHeldPose(string itemName, out Vector3 position, out Quaternion rotation) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; rotation = Quaternion.identity; if (string.IsNullOrWhiteSpace(itemName)) { return false; } if (!HeldPoseCache.TryGetValue(itemName, out (Vector3, Vector3) value)) { if (NextHeldPoseScan.TryGetValue(itemName, out var value2) && Time.realtimeSinceStartup < value2) { return false; } Scan(); (Vector3, Vector3)[] array = (from candidate in HandCandidates where string.Equals(candidate.Name, itemName, StringComparison.OrdinalIgnoreCase) select (PositionOffset: candidate.PositionOffset, RotationOffset: candidate.RotationOffset)).Distinct().Take(2).ToArray(); if (array.Length != 1) { NextHeldPoseScan[itemName] = Time.realtimeSinceStartup + 1f; return false; } value = array[0]; HeldPoseCache[itemName] = value; } position = value.Item1; rotation = Quaternion.Euler(value.Item2); return true; } private static AudioClip? Match(string itemId, string clipName) { AudioClip[] array = (from candidate in Candidates where Object.op_Implicit((Object)(object)candidate.Clip) && (itemId.Length == 0 || candidate.ItemId == itemId) && (clipName.Length == 0 || ((Object)candidate.Clip).name == clipName) select candidate.Clip).Distinct().Take(2).ToArray(); if (array.Length != 1) { return null; } return array[0]; } } internal sealed class LiveReplayInput : IDisposable { private readonly Dictionary<InputAction, bool> actions = new Dictionary<InputAction, bool>(); private readonly HashSet<InputActionAsset> assets = new HashSet<InputActionAsset>(); private object? hudOwner; private bool disposed; internal void Block(Component player) { if (disposed) { return; } Add(InputSystem.actions); object value = GameAccess.Read(player, "playerActions"); object? obj = GameAccess.Read(value, "asset"); Add((InputActionAsset?)((obj is InputActionAsset) ? obj : null)); object obj2 = GameAccess.Singleton("HUDManager"); if (hudOwner != obj2) { hudOwner = obj2; ResetSpectatorVoteHold(obj2); } object value2 = GameAccess.Read(obj2, "playerActions"); object? obj3 = GameAccess.Read(value2, "asset"); Add((InputActionAsset?)((obj3 is InputActionAsset) ? obj3 : null)); foreach (InputAction key in actions.Keys) { if (key.enabled) { key.Disable(); } } } private void Add(InputActionAsset? asset) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)asset) || !assets.Add(asset)) { return; } Enumerator<InputActionMap> enumerator = asset.actionMaps.GetEnumerator(); try { while (enumerator.MoveNext()) { Enumerator<InputAction> enumerator2 = enumerator.Current.actions.GetEnumerator(); try { while (enumerator2.MoveNext()) { InputAction current = enumerator2.Current; actions[current] = current.enabled; } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } public void Dispose() { if (disposed) { return; } disposed = true; ResetSpectatorVoteHold(hudOwner); object obj = GameAccess.Singleton("HUDManager"); if (obj != hudOwner) { ResetSpectatorVoteHold(obj); } foreach (KeyValuePair<InputAction, bool> action in actions) { try { if (action.Value) { action.Key.Enable(); } } catch (Exception) { } } actions.Clear(); assets.Clear(); hudOwner = null; } private static void ResetSpectatorVoteHold(object? hud) { if (hud == null) { return; } try { hud.GetType().GetField("holdButtonToEndGameEarlyHoldTime", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(hud, 0f); } catch (Exception) { } } } internal static class NativeSoundAssets { private static readonly Dictionary<string, Dictionary<string, AudioClip>> Catalogs = new Dictionary<string, Dictionary<string, AudioClip>>(StringComparer.Ordinal); private static readonly Dictionary<string, WeakReference<AudioClip>?> UniqueClips = new Dictionary<string, WeakReference<AudioClip>>(StringComparer.Ordinal); private static readonly Dictionary<string, AudioSource?> Sources = new Dictionary<string, AudioSource>(StringComparer.Ordinal); private static float nextScan; private static Component? playerPrefab; private static Component? Prefab(string key) { if (key != "player") { return PrefabAssetRegistry.ResolvePrefab(key); } if (Object.op_Implicit((Object)(object)playerPrefab)) { return playerPrefab; } Type type = GameAccess.Type("GameNetcodeStuff.PlayerControllerB"); if (type == null) { return null; } object? obj = GameAccess.Read(GameAccess.Singleton("StartOfRound"), "playerPrefab"); GameObject val = (GameObject)((obj is GameObject) ? obj : null); playerPrefab = (Component?)(Object.op_Implicit((Object)(object)val) ? ((object)val.GetComponentInChildren(type, true)) : ((object)Resources.FindObjectsOfTypeAll(type).OfType<Component>().FirstOrDefault((Func<Component, bool>)delegate(Component component) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)component)) { Scene scene = component.gameObject.scene; return !((Scene)(ref scene)).IsValid(); } return false; }))); return playerPrefab; } internal static string Identity(Component owner, AudioClip clip) { return Catalog(owner).FirstOrDefault<KeyValuePair<string, AudioClip>>((KeyValuePair<string, AudioClip> pair) => (Object)(object)pair.Value == (Object)(object)clip).Key ?? ""; } internal static Dictionary<string, AudioClip> Catalog(Component owner) { Dictionary<string, AudioClip> dictionary = new Dictionary<string, AudioClip>(StringComparer.Ordinal); MonoBehaviour[] componentsInChildren = owner.GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val in componentsInChildren) { if (Object.op_Implicit((Object)(object)val) && !(((object)val).GetType().Namespace ?? "").StartsWith("UnityEngine", StringComparison.Ordinal)) { string path = EntityTracker.RelativePath(owner.transform, ((Component)val).transform) + "|" + ((object)val).GetType().FullName; Fields(val, path, dictionary, 0); } } AudioSource[] componentsInChildren2 = owner.GetComponentsInChildren<AudioSource>(true); foreach (AudioSource val2 in componentsInChildren2) { if (Object.op_Implicit((Object)(object)val2.clip)) { dictionary[EntityTracker.RelativePath(owner.transform, ((Component)val2).transform) + "|AudioSource|clip"] = val2.clip; } } return dictionary; } internal static Dictionary<string, AudioClip>? InstalledCatalog(string key) { if (Catalogs.TryGetValue(key, out Dictionary<string, AudioClip> value)) { return value; } Component val = Prefab(key); if (!Object.op_Implicit((Object)(object)val)) { return null; } value = Catalog(val); if (Catalogs.Count < 1024) { Catalogs[key] = value; } return value; } private static void Fields(object obj, string path, Dictionary<string, AudioClip> clips, int depth) { Type type = obj.GetType(); while (type != null && type != typeof(MonoBehaviour) && type != typeof(ScriptableObject)) { FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.IsStatic) { continue; } string text = path + "|" + fieldInfo.Name; if (fieldInfo.FieldType == typeof(AudioClip)) { object? value = fieldInfo.GetValue(obj); AudioClip val = (AudioClip)((value is AudioClip) ? value : null); if (val != null && Object.op_Implicit((Object)(object)val)) { clips[text] = val; } continue; } if (fieldInfo.FieldType == typeof(AudioClip[]) || fieldInfo.FieldType == typeof(List<AudioClip>)) { if (!(fieldInfo.GetValue(obj) is IEnumerable enumerable)) { continue; } int num = 0; foreach (object item in enumerable) { AudioClip val2 = (AudioClip)((item is AudioClip) ? item : null); if (val2 != null && Object.op_Implicit((Object)(object)val2)) { clips[text + "|" + num] = val2; } if (++num >= 1024) { break; } } continue; } if (depth == 0 && typeof(ScriptableObject).IsAssignableFrom(fieldInfo.FieldType)) { object? value2 = fieldInfo.GetValue(obj); ScriptableObject val3 = (ScriptableObject)((value2 is ScriptableObject) ? value2 : null); if (val3 != null && Object.op_Implicit((Object)(object)val3)) { Fields(val3, text, clips, depth + 1); continue; } } if (depth >= 2 || !fieldInfo.FieldType.IsArray || !(fieldInfo.FieldType.GetElementType()?.Name == "EnemyBehaviourState") || !(fieldInfo.GetValue(obj) is IEnumerable enumerable2)) { continue; } int num2 = 0; foreach (object item2 in enumerable2) { if (item2 != null) { Fields(item2, text + "|" + num2, clips, depth + 1); } if (++num2 >= 128) { break; } } } type = type.BaseType; } } internal static AudioClip? Resolve(string prefabKey, string identity, string name, out Component? prefab) { prefab = ((prefabKey.Length == 0) ? null : Prefab(prefabKey)); if (!Object.op_Implicit((Object)(object)prefab) && prefabKey.StartsWith("enemy:", StringComparison.Ordinal)) { return null; } if (Object.op_Implicit((Object)(object)prefab)) { Dictionary<string, AudioClip> dictionary = InstalledCatalog(prefabKey); if (identity.Length != 0 && dictionary.TryGetValue(identity, out var value) && Object.op_Implicit((Object)(object)value) && ((Object)value).name == name) { return value; } AudioClip[] array = dictionary.Values.Where((AudioClip clip) => Object.op_Implicit((Object)(object)clip) && ((Object)clip).name == name).Distinct().Take(2) .ToArray(); if (array.Length == 1) { return array[0]; } if (array.Length > 1 || prefabKey.StartsWith("enemy:", StringComparison.Ordinal)) { return null; } } WeakReference<AudioClip> value2; bool num = UniqueClips.TryGetValue(name, out value2); AudioClip target = null; value2?.TryGetTarget(out target); if ((!num || (value2 != null && !Object.op_Implicit((Object)(object)target))) && Time.realtimeSinceStartup >= nextScan) { nextScan = Time.realtimeSinceStartup + 2f; UniqueClips.Clear(); AudioClip[] array2 = Resources.FindObjectsOfTypeAll<AudioClip>(); foreach (AudioClip val in array2) { if (!Object.op_Implicit((Object)(object)val) || ((Object)val).name.StartsWith("LCReplay", StringComparison.Ordinal) || ((Object)val).name.StartsWith("LC Replay", StringComparison.Ordinal) || ((Object)val).name.StartsWith("Dissonance", StringComparison.OrdinalIgnoreCase)) { continue; } if (UniqueClips.TryGetValue(((Object)val).name, out WeakReference<AudioClip> value3)) { if (value3 == null || !value3.TryGetTarget(out var target2) || (Object)(object)target2 != (Object)(object)val) { UniqueClips[((Object)val).name] = null; } } else { UniqueClips[((Object)val).name] = new WeakReference<AudioClip>(val); } } target = null; if (UniqueClips.TryGetValue(name, out value2)) { value2?.TryGetTarget(out target); } } if (!Object.op_Implicit((Object)(object)target) || !(((Object)target).name == name)) { return null; } return target; } internal static AudioSource? Source(Component? prefab, string path, int index) { if (!Object.op_Implicit((Object)(object)prefab)) { return null; } string key = ((Object)prefab).GetInstanceID() + "\n" + index + "\n" + path; if (Sources.TryGetValue(key, out AudioSource value) && Object.op_Implicit((Object)(object)value)) { return value; } Transform val = ((IEnumerable<Transform>)prefab.GetComponentsInChildren<Transform>(true)).FirstOrDefault((Func<Transform, bool>)((Transform child) => EntityTracker.RelativePath(prefab.transform, child) == path)); AudioSource val2 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponents<AudioSource>().ElementAtOrDefault(Math.Max(0, index)) : null); if (!Object.op_Implicit((Object)(object)val2)) { string leaf = path.Substring(path.LastIndexOf('/') + 1); AudioSource[] array = (from audio in (from field in ((object)prefab).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where field.FieldType == typeof(AudioSource) select field).Select(delegate(FieldInfo field) { object? value2 = field.GetValue(prefab); return (AudioSource)((value2 is AudioSource) ? value2 : null); }) where Object.op_Implicit((Object)(object)audio) && EntityTracker.RelativePath(prefab.transform, ((Component)audio).transform).Split('/').Last() == leaf select audio).Distinct().Take(2).ToArray(); if (array.Length == 1) { val2 = array[0]; } } if (Object.op_Implicit((Object)(object)val2) && Sources.Count < 4096) { Sources[key] = val2; } return val2; } } internal static class PrefabAssetRegistry { private static object? scannedConfig; private static float nextScan; private static readonly Dictionary<string, Component> Prefabs = new Dictionary<string, Component>(StringComparer.Ordinal); private static readonly Dictionary<string, Renderer> Renderers = new Dictionary<string, Renderer>(StringComparer.Ordinal); private static readonly Dictionary<string, Animator> Animators = new Dictionary<string, Animator>(StringComparer.Ordinal); private static readonly Dictionary<string, GeometrySnapshot> Meshes = new Dictionary<string, GeometrySnapshot>(StringComparer.Ordinal); private static readonly Queue<string> meshOrder = new Queue<string>(); private static long meshBytes; internal static RuntimeAnimatorController? LocalPlayerController { get; private set; } internal static RuntimeAnimatorController? RemotePlayerController { get; private set; } internal static bool CachePlayerAssets(Component prefab, RuntimeAnimatorController? local, RuntimeAnimatorController? remote) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Type type = GameAccess.Type("GameNetcodeStuff.PlayerControllerB"); if (Object.op_Implicit((Object)(object)prefab)) { Scene scene = prefab.gameObject.scene; if (!((Scene)(ref scene)).IsValid() && !(type == null) && type.IsInstanceOfType(prefab)) { Add(prefab, "player"); if (Object.op_Implicit((Object)(object)local)) { LocalPlayerController = local; } if (Object.op_Implicit((Object)(object)remote)) { RemotePlayerController = remote; } return true; } } return false; } internal static string Key(Component component, string kind) { if (kind == "player") { return "player"; } if (kind == "hazard") { return "hazard:" + ((object)component).GetType().Name; } string text = GameAccess.Scalar(GameAccess.Read(GameAccess.Read(component, (kind == "enemy") ? "enemyType" : "itemProperties"), (kind == "enemy") ? "enemyName" : "itemId")); if (!string.IsNullOrEmpty(text)) { return kind + ":" + text; } return ""; } internal static void Warm(bool force = false) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Unknown result type (might be due to invalid IL or missing references) object? obj = GameAccess.Singleton("StartOfRound"); Component val = (Component)((obj is Component) ? obj : null); if (val != null && Object.op_Implicit((Object)(object)val)) { ReplayParticleAssets.CacheShipMagnet(val); } object obj2 = GameAccess.Read(GameAccess.Singleton("Unity.Netcode.NetworkManager"), "NetworkConfig"); Scene scene; if (!Prefabs.TryGetValue("player", out Component value) || !Object.op_Implicit((Object)(object)value)) { Type type = GameAccess.Type("GameNetcodeStuff.PlayerControllerB"); object value2 = GameAccess.Singleton("StartOfRound"); object? obj3 = GameAccess.Read(value2, "playerPrefab"); GameObject val2 = (GameObject)((obj3 is GameObject) ? obj3 : null); if (!Object.op_Implicit((Object)(object)val2)) { object? obj4 = GameAccess.Read(obj2, "PlayerPrefab"); val2 = (GameObject)((obj4 is GameObject) ? obj4 : null); } if (!Object.op_Implicit((Object)(object)val2)) { Type type2 = GameAccess.Type("StartOfRound"); if ((object)type2 != null) { Object[] array = Resources.FindObjectsOfTypeAll(type2); foreach (Object val3 in array) { object? obj5 = GameAccess.Read(val3, "playerPrefab"); GameObject val4 = (GameObject)((obj5 is GameObject) ? obj5 : null); if (val4 != null && Object.op_Implicit((Object)(object)val4)) { scene = val4.scene; if (!((Scene)(ref scene)).IsValid()) { val2 = val4; value2 = val3; break; } } } } } if (type != null && Object.op_Implicit((Object)(object)val2)) { scene = val2.scene; if (!((Scene)(ref scene)).IsValid()) { Component componentInChildren = val2.GetComponentInChildren(type, true); if (componentInChildren != null) { object local = (object)/*isinst with value type is only supported in some contexts*/; object? obj6 = GameAccess.Read(value2, "otherClientsAnimatorController"); CachePlayerAssets(componentInChildren, (RuntimeAnimatorController?)local, (RuntimeAnimatorController?)((obj6 is RuntimeAnimatorController) ? obj6 : null)); } } } if (type != null && (!Prefabs.TryGetValue("player", out value) || !Object.op_Implicit((Object)(object)value))) { foreach (Component item in Resources.FindObjectsOfTypeAll(type).OfType<Component>()) { if (Object.op_Implicit((Object)(object)item)) { scene = item.gameObject.scene; if (!((Scene)(ref scene)).IsValid()) { Add(item, "player"); break; } } } } } if ((!force && obj2 != null && obj2 == scannedConfig) || (!force && obj2 == null && Time.realtimeSinceStartup < nextScan)) { return; } nextScan = Time.realtimeSinceStartup + 2f; string[] array2 = new string[2] { "Turret", "Landmine" }; foreach (string text in array2) { Type type3 = GameAccess.Type(text); if (type3 == null) { continue; } Component val5 = Resources.FindObjectsOfTypeAll(type3).OfType<Component>().FirstOrDefault((Func<Component, bool>)delegate(Component component) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)component)) { Scene scene2 = component.gameObject.scene; return !((Scene)(ref scene2)).IsValid(); } return false; }); if (Object.op_Implicit((Object)(object)val5)) { Prefabs["hazard:" + text] = val5; } } array2 = new string[2] { "enemy", "item" }; foreach (string text2 in array2) { Type type4 = GameAccess.Type((text2 == "enemy") ? "EnemyAI" : "GrabbableObject"); if (type4 == null) { continue; } foreach (Component item2 in Resources.FindObjectsOfTypeAll(type4).OfType<Component>()) { if (Object.op_Implicit((Object)(object)item2)) { scene = item2.gameObject.scene; if (!((Scene)(ref scene)).IsValid()) { Add(item2, text2); } } } } if (!(GameAccess.Read(GameAccess.Read(obj2, "Prefabs"), "Prefabs") is IEnumerable enumerable)) { return; } foreach (object item3 in enumerable) { object? obj7 = GameAccess.Read(item3, "Prefab"); GameObject val6 = (GameObject)((obj7 is GameObject) ? obj7 : null); if (!Object.op_Implicit((Object)(object)val6)) { continue; } array2 = new string[2] { "enemy", "item" }; foreach (string text3 in array2) { Type type5 = GameAccess.Type((text3 == "enemy") ? "EnemyAI" : "GrabbableObject"); if (!(type5 == null)) { Component componentInChildren2 = val6.GetComponentInChildren(type5, true); if (Object.op_Implicit((Object)(object)componentInChildren2)) { Add(componentInChildren2, text3); } } } } scannedConfig = obj2; } private static void Add(Component component, string kind) { string text = Key(component, kind); if (text.Length == 0 || (Prefabs.TryGetValue(text, out Component value) && Object.op_Implicit((Object)(object)value))) { return; } Prefabs[text] = component; Renderer[] componentsInChildren = component.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if (val is MeshRenderer || val is SkinnedMeshRenderer) { Renderers[text + "\n" + EntityTracker.RelativePath(component.transform, ((Component)val).transform)] = val; } } Animator[] componentsInChildren2 = component.GetComponentsInChildren<Animator>(true); foreach (Animator val2 in componentsInChildren2) { Animators[text + "\n" + EntityTracker.RelativePath(component.transform, ((Component)val2).transform)] = val2; AnimationAssetRegistry.Remember(val2, kind); } } internal static bool Reference(EntityTracker.Entry owner, Renderer renderer, GeometrySnapshot geometry) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) if (owner.Kind != "enemy" && owner.Kind != "item" && owner.Kind != "player") { return false; } Warm(); string text = Key(owner.Component, owner.Kind); if (owner.Kind == "enemy" && (!Prefabs.TryGetValue(text, out Component value) || !Object.op_Implicit((Object)(object)value))) { object? obj = GameAccess.Read(GameAccess.Read(owner.Component, "enemyType"), "enemyPrefab"); GameObject val = (GameObject)((obj is GameObject) ? obj : null); Type type = GameAccess.Type("EnemyAI"); if (Object.op_Implicit((Object)(object)val)) { Scene scene = val.scene; if (!((Scene)(ref scene)).IsValid() && type != null) { Component componentInChildren = val.GetComponentInChildren(type, true); if (componentInChildren != null) { Add(componentInChildren, "enemy"); } } } } string text2 = EntityTracker.RelativePath(owner.Component.transform, ((Component)renderer).transform); if (text2.Length == 0 || !Renderers.TryGetValue(text + "\n" + text2, out Renderer value2) || !Object.op_Implicit((Object)(object)value2) || (Object)(object)Mesh(renderer) != (Object)(object)Mesh(value2)) { return false; } geometry.PrefabKey = text; geometry.PrefabRendererPath = text2; geometry.MeshName = ((Object)Mesh(value2)).name; geometry.IsBoundsProxy = false; return true; } internal static bool NativeMaterials(Renderer renderer, GeometrySnapshot geometry) { if (!Renderers.TryGetValue(geometry.PrefabKey + "\n" + geometry.PrefabRendererPath, out Renderer value) || !Object.op_Implicit((Object)(object)value)) { return false; } Material[] sharedMaterials = renderer.sharedMaterials; Material[] source = value.sharedMaterials; if (sharedMaterials.Length == source.Length) { return !sharedMaterials.Where((Material material, int index) => (Object)(object)material != (Object)(object)source[index]).Any(); } return false; } internal static void Restore(WorldSnapshot world, ReplaySession session) { //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Expected O, but got Unknown //IL_0514: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_0573: Unknown result type (might be due to invalid IL or missing references) //IL_057a: Expected O, but got Unknown //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05bd: Unknown result type (might be due to invalid IL or missing references) //IL_05c7: Unknown result type (might be due to invalid IL or missing references) //IL_05d1: Unknown result type (might be due to invalid IL or missing references) //IL_0556: Unknown result type (might be due to invalid IL or missing references) //IL_055b: Unknown result type (might be due to invalid IL or missing references) Warm(); Dictionary<string, EntitySnapshot> dictionary = (from actor in session.Frames.SelectMany((ReplayFrame frame) => frame.Entities) where actor.Kind == "enemy" group actor by actor.Id).ToDictionary((IGrouping<string, EntitySnapshot> group) => group.Key, (IGrouping<string, EntitySnapshot> group) => group.First()); foreach (GeometrySnapshot geometry in world.Geometry) { bool flag = geometry.PrefabKey.Length != 0 && geometry.MaterialIds.Count != 0; string text = geometry.PrefabKey; string text2 = geometry.PrefabRendererPath; if (text.Length == 0 && dictionary.TryGetValue(geometry.EntityId, out var value)) { text = "enemy:" + value.Name; if (!Prefabs.TryGetValue(text, out Component value2) || !Object.op_Implicit((Object)(object)value2)) { continue; } Renderer[] array = (from renderer in value2.GetComponentsInChildren<Renderer>(true) where Object.op_Implicit((Object)(object)renderer) && ((Object)renderer).name == geometry.Name && Object.op_Implicit((Object)(object)Mesh(renderer)) && (((Object)Mesh(renderer)).name == geometry.MeshName || geometry.IsBoundsProxy) select renderer).ToArray(); if (array.Length != 1) { continue; } text2 = EntityTracker.RelativePath(value2.transform, ((Component)array[0]).transform); } string text3 = text + "\n" + text2; if (!Renderers.TryGetValue(text3, out Renderer value3) || !Object.op_Implicit((Object)(object)value3) || !Prefabs.TryGetValue(text, out Component value4) || !Object.op_Implicit((Object)(object)value4)) { continue; } if (!Meshes.TryGetValue(text3, out GeometrySnapshot value5)) { value5 = new GeometrySnapshot(); Mesh val = Mesh(value3); if (!Object.op_Implicit((Object)(object)val) || !MeshSnapshotReader.Read(val, value5, 1000000, 6000000)) { continue; } SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((value3 is SkinnedMeshRenderer) ? value3 : null); if (val2 != null && !MeshSnapshotReader.Skin(val2, value4.transform, value5)) { continue; } long num = Bytes(value5); if (num <= 67108864) { while (meshBytes + num > 67108864 && meshOrder.Count != 0) { string key = meshOrder.Dequeue(); if (Meshes.TryGetValue(key, out GeometrySnapshot value6)) { meshBytes -= Bytes(value6); Meshes.Remove(key); } } Meshes[text3] = value5; meshOrder.Enqueue(text3); meshBytes += num; } } geometry.Vertices = value5.Vertices; geometry.Normals = value5.Normals; geometry.Uvs = value5.Uvs; geometry.Uvs1 = value5.Uvs1; geometry.Uvs2 = value5.Uvs2; geometry.Uvs3 = value5.Uvs3; geometry.Tangents = value5.Tangents; geometry.Triangles = value5.Triangles; geometry.SubmeshTriangles = value5.SubmeshTriangles; geometry.BonePaths = value5.BonePaths; geometry.BindPoses = value5.BindPoses; geometry.BoneIndices = value5.BoneIndices; geometry.BoneWeights = value5.BoneWeights; geometry.RigBones = value5.RigBones; geometry.RootBonePath = value5.RootBonePath; geometry.AnimatorPath = value5.AnimatorPath; geometry.AnimatorController = value5.AnimatorController; geometry.AnimatorAvatar = value5.AnimatorAvatar; geometry.IsBoundsProxy = false; geometry.PrefabKey = text; geometry.PrefabRendererPath = text2; if (flag) { continue; } geometry.MaterialIds = new List<string>(); Material[] sharedMaterials = value3.sharedMaterials; foreach (Material val3 in sharedMaterials) { if (!Object.op_Implicit((Object)(object)val3) || !Object.op_Implicit((Object)(object)val3.shader)) { geometry.MaterialIds.Add(""); continue; } string id = "native-prefab:" + ((Object)val3).GetInstanceID(); if (!world.Materials.Any((MaterialSnapshot snapshot) => snapshot.Id == id)) { Color val4 = Color.white; string[] array2 = new string[3] { "_BaseColor", "_UnlitColor", "_Color" }; foreach (string text4 in array2) { if (val3.HasProperty(text4)) { val4 = val3.GetColor(text4); break; } } List<MaterialSnapshot> materials = world.Materials; MaterialSnapshot val5 = new MaterialSnapshot(); val5.Id = id; val5.Name = ((Object)val3).name; val5.ShaderName = ((Object)val3.shader).name; val5.Color = new float[4] { val4.r, val4.g, val4.b, val4.a }; materials.Add(val5); } geometry.MaterialIds.Add(id); } } } private static Mesh? Mesh(Renderer renderer) { SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((renderer is SkinnedMeshRenderer) ? renderer : null); if (val == null) { MeshFilter component = ((Component)renderer).GetComponent<MeshFilter>(); if (component == null) { return null; } return component.sharedMesh; } return val.sharedMesh; } internal static Mesh? ResolveMesh(GeometrySnapshot geometry) { if (geometry.PrefabKey.Length == 0 || geometry.PrefabRendererPath.Length == 0 || !Renderers.TryGetValue(geometry.PrefabKey + "\n" + geometry.PrefabRendererPath, out Renderer value) || !Object.op_Implicit((Object)(object)value)) { return null; } Mesh val = Mesh(value); if (!Object.op_Implicit((Object)(object)val) || val.vertexCount != geometry.Vertices.Length / 3) { return null; } return val; } internal static Animator? ResolveAnimator(string key, string path) { if (!Animators.TryGetValue(key + "\n" + path, out Animator value) || !Object.op_Implicit((Object)(object)value)) { return null; } return value; } internal static Component? ResolvePrefab(string key) { Warm(); if (!Prefabs.TryGetValue(key, out Component value) || !Object.op_Implicit((Object)(object)value)) { return null; } return value; } private static long Bytes(GeometrySnapshot geometry) { return 4 * (geometry.Vertices.Length + geometry.Normals.Length + geometry.Uvs.Length + geometry.Uvs1.Length + geometry.Uvs2.Length + geometry.Uvs3.Length + geometry.Tangents.Length + geometry.Triangles.Length + geometry.BoneWeights.Length + geometry.BoneIndices.Length + geometry.BindPoses.Length + ((IEnumerable<int[]>)geometry.SubmeshTriangles).Sum((Func<int[], long>)((int[] indices) => indices.Length))); } } internal static class RenderVisibilityPolicy { private static readonly string[] VegetationNames = new string[13] { "tree", "grass", "leaf", "leaves", "bush", "flower", "forest", "foliage", "vine", "plant", "fern", "shrub", "moss" }; internal static bool IsGroundSurface(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("terrain", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("ground", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("landscape", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("splatmap", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal static bool IsRockSurface(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("rock", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("stone", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("boulder", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("cliff", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal static bool IsArchitectureSurface(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("wall", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("floor", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("ceiling", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal static bool IsVegetation(string name) { if (string.IsNullOrEmpty(name)) { return false; } string[] vegetationNames = VegetationNames; foreach (string value in vegetationNames) { if (name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } internal static bool IsDebugMaterial(string name) { if (!name.StartsWith("testTrigger", StringComparison.OrdinalIgnoreCase)) { return name.StartsWith("wireframe_", StringComparison.OrdinalIgnoreCase); } return true; } internal static bool IsDebugObject(string name) { if (!name.StartsWith("ImpVis_", StringComparison.Ordinal) && !name.StartsWith("ImpGizmo_", StringComparison.Ordinal)) { switch (name) { default: if (!string.Equals(name, "ScanSphere", StringComparison.OrdinalIgnoreCase) && !(name == "MapDot")) { return name.StartsWith("MapDot (", StringComparison.Ordinal); } break; case "VehicleBounds": case "InsideTruckNavBounds": case "OntopOfTruckBounds": case "WindshieldInteractBlocker": break; } } return true; } internal static bool IsRadarPath(string path) { if (!path.StartsWith("MapDot[", StringComparison.Ordinal) && !path.StartsWith("MapDot (", StringComparison.Ordinal) && path.IndexOf("/MapDot[", StringComparison.Ordinal) < 0) { return path.IndexOf("/MapDot (", StringComparison.Ordinal) >= 0; } return true; } internal static bool IsFirstPersonOverlay(string name) { if (!string.Equals(name, "ScavengerHelmet", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "ScreenHelmetGoopPreload", StringComparison.OrdinalIgnoreCase)) { return string.Equals(name, "VisorCracks", StringComparison.OrdinalIgnoreCase); } return true; } internal static bool IsDebugGeometry(GeometrySnapshot geometry, ISet<string> debugMaterials) { if (IsDebugObject(geometry.Name) || IsFirstPersonOverlay(geometry.Name)) { return true; } if (geometry.MaterialIds.Count == 0) { return false; } foreach (string materialId in geometry.MaterialIds) { if (materialId.Length == 0 || !debugMaterials.Contains(materialId)) { return false; } } return true; } } public static class ReplayApi { private static readonly Dictionary<string, Func<IDictionary<string, string>>> Providers = new Dictionary<string, Func<IDictionary<string, string>>>(); internal static Action<ReplayEvent>? EventSink; public static bool IsRecording => EventSink != null; public static void RegisterStateProvider(string id, Func<IDictionary<string, string>> provider) { if (string.IsNullOrWhiteSpace(id) || id.Length > 128) { throw new ArgumentException("Provider id must contain 1 to 128 characters.", "id"); } if (provider == null) { throw new ArgumentNullException("provider"); } lock (Providers) { if (Providers.Count >= 64 && !Providers.ContainsKey(id)) { throw new InvalidOperationException("Replay supports at most 64 state providers."); } Providers[id] = provider; } } public static void UnregisterStateProvider(string id) { lock (Providers) { Providers.Remove(id); } } public static void LogEvent(string category, string name, IDictionary<string, string>? data = null) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown Action<ReplayEvent> eventSink = EventSink; if (eventSink == null) { return; } Dictionary<string, string> dictionary = new Dictionary<string, string>(); if (data != null) { foreach (KeyValuePair<string, string> datum in data) { if (dictionary.Count >= 128) { dictionary["$truncated"] = "128 event fields"; break; } dictionary[Limit(datum.Key, 128)] = Limit(datum.Value, 1024); } } eventSink(new ReplayEvent { Category = Limit(category ?? "custom", 128), Name = Limit(name ?? "event", 256), Data = dictionary }); } private static string Limit(string? value, int length) { if (value != null) { if (value.Length <= length) { return value; } return value.Substring(0, length); } return ""; } internal static void CaptureProviders(Dictionary<string, string> state, Action<string> log) { KeyValuePair<string, Func<IDictionary<string, string>>>[] array; lock (Providers) { array = new KeyValuePair<string, Func<IDictionary<string, string>>>[Providers.Count]; ((ICollection<KeyValuePair<string, Func<IDictionary<string, string>>>>)Providers).CopyTo(array, 0); } KeyValuePair<string, Func<IDictionary<string, string>>>[] array2 = array; for (int i = 0; i < array2.Length; i++) { KeyValuePair<string, Func<IDictionary<string, string>>> keyValuePair = array2[i]; if (state.Count >= 1000) { state["$providersTruncated"] = "frame field limit"; break; } try { int num = 0; foreach (KeyValuePair<string, string> item in keyValuePair.Value()) { if (++num > 128 || state.Count >= 1000) { state["mod:" + keyValuePair.Key + ":$truncated"] = "provider/frame field limit"; break; } string value = item.Value ?? ""; state["mod:" + keyValuePair.Key + ":" + Limit(item.Key, 128)] = Limit(value, 1024); } } catch (Exception ex) { log("State provider disabled after error: " + keyValuePair.Key + " / " + ex.Message); lock (Providers) { if (Providers.TryGetValue(keyValuePair.Key, out Func<IDictionary<string, string>> value2) && value2 == keyValuePair.Value) { Providers.Remove(keyValuePair.Key); } } } } } } internal sealed class ReplayBookmarkInput : IDisposable { internal const string InputUtilsGuid = "com.rune580.LethalCompanyInputUtils"; private readonly ConfigEntry<KeyCode> fallback; private Func<bool>? pressed; private Action? disable; internal ReplayBookmarkInput(ConfigEntry<KeyCode> fallback, Action<string> log) { this.fallback = fallback; if (!Chainloader.PluginInfos.ContainsKey("com.rune580.LethalCompanyInputUtils")) { return; } try { AttachInputUtils(); log("Replay bookmark key registered with InputUtils's game settings."); } catch (Exception ex) { log("Replay bookmark uses the configured fallback key: " + ex.Message); } } [MethodImpl(MethodImplOptions.NoInlining)] private void AttachInputUtils() { BaseUnityPlugin instance = Chainloader.PluginInfos["com.rune580.LethalCompanyInputUtils"].Instance; BepInPlugin metadata = Chainloader.PluginInfos["pasta.replay"].Metadata; object actions = ReplayKeybinds.Create(((object)instance).GetType().Assembly, metadata); PropertyInfo property = actions.GetType().GetProperty("AddBookmark"); InputAction action = null; pressed = delegate { InputAction obj = action ?? (action = (InputAction)/*isinst with value type is only supported in some contexts*/); return obj != null && obj.WasPerformedThisFrame(); }; disable = delegate { actions.GetType().GetMethod("Disable").Invoke(actions, null); }; } internal bool WasPressed() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (pressed == null) { return ReplayInput.WasPressed(((object)fallback.Value/*cast due to .constrained prefix*/).ToString()); } return pressed(); } public void Dispose() { disable?.Invoke(); pressed = null; disable = null; } } internal static class ReplayIsolation { private static readonly HashSet<int> scenes; private static object? networkSceneManager; private static Func<Scene, bool>? networkFilter; internal static bool PlaybackActive { get; set; } static ReplayIsolation() { scenes = new HashSet<int>(); SceneManager.sceneUnloaded += delegate(Scene scene) { scenes.Remove(((Scene)(ref scene)).handle); }; } internal static void Register(Scene scene) { if (((Scene)(ref scene)).IsValid()) { scenes.Add(((Scene)(ref scene)).handle); } } internal static void Unregister(Scene scene) { if (((Scene)(ref scene)).IsValid() && !((Scene)(ref scene)).isLoaded) { scenes.Remove(((Scene)(ref scene)).handle); } } internal static void Unloaded(Scene scene) { scenes.Remove(((Scene)(ref scene)).handle); } internal static bool IsReplayScene(Scene scene) { if (((Scene)(ref scene)).IsValid()) { return scenes.Contains(((Scene)(ref scene)).handle); } return false; } internal static bool EnsureNetworkExclusion() { object obj = GameAccess.Read(GameAccess.Singleton("Unity.Netcode.NetworkManager"), "SceneManager"); if (obj == null) { return false; } FieldInfo field = obj.GetType().GetField("ExcludeSceneFromSychronization", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field?.FieldType != typeof(Func<Scene, bool>)) { return false; } try { Func<Scene, bool> current = field.GetValue(obj) as Func<Scene, bool>; if (obj == networkSceneManager && (object)current == networkFilter) { return true; } Func<Scene, bool> value = (Scene scene) => !IsReplayScene(scene) && (current?.Invoke(scene) ?? true); field.SetValue(obj, value); networkSceneManager = obj; networkFilter = value; return true; } catch (Exception) { return false; } } } internal static class ReplayKeybinds { internal static object Create(Assembly sdk, BepInPlugin metadata) { Type type = sdk.GetType("LethalCompanyInputUtils.Api.LcInputActions", throwOnError: true); Type type2 = sdk.GetType("LethalCompanyInputUtils.Api.InputActionAttribute", throwOnError: true); TypeBuilder typeBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("LCReplay.OptionalInputUtils"), AssemblyBuilderAccess.Run).DefineDynamicModule("Keybinds").DefineType("LCReplay.BookmarkActions", TypeAttributes.Public | TypeAttributes.Sealed, type); ILGenerator iLGenerator = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[1] { typeof(BepInPlugin) }).GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Ldarg_1); iLGenerator.Emit(OpCodes.Call, type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(BepInPlugin) }, null)); iLGenerator.Emit(OpCodes.Ret); MethodInfo getMethod = type.GetProperty("MapName", BindingFlags.Instance | BindingFlags.NonPublic).GetGetMethod(nonPublic: true); MethodBuilder methodBuilder = typeBuilder.DefineMethod("get_MapName", MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.SpecialName, typeof(string), Type.EmptyTypes); iLGenerator = methodBuilder.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldstr, "Replay"); iLGenerator.Emit(OpCodes.Ret); typeBuilder.DefineMethodOverride(methodBuilder, getMethod); FieldBuilder field = typeBuilder.DefineField("bookmark", typeof(InputAction), FieldAttributes.Private); PropertyBuilder propertyBuilder = typeBuilder.DefineProperty("AddBookmark", PropertyAttributes.None, typeof(InputAction), null); MethodBuilder methodBuilder2 = typeBuilder.DefineMethod("get_AddBookmark", MethodAttributes.Public | MethodAttributes.SpecialName, typeof(InputAction), Type.EmptyTypes); iLGenerator = methodBuilder2.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Ldfld, field); iLGenerator.Emit(OpCodes.Ret); MethodBuilder methodBuilder3 = typeBuilder.DefineMethod("set_AddBookmark", MethodAttributes.Public | MethodAttributes.SpecialName, typeof(void), new Type[1] { typeof(InputAction) }); iLGenerator = methodBuilder3.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Ldarg_1); iLGenerator.Emit(OpCodes.Stfld, field); iLGenerator.Emit(OpCodes.Ret); propertyBuilder.SetGetMethod(methodBuilder2); propertyBuilder.SetSetMethod(methodBuilder3); propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(type2.GetConstructor(new Type[1] { typeof(string) }), new object[1] { "<Keyboard>/backquote" }, new PropertyInfo[2] { type2.GetProperty("Name"), type2.GetProperty("ActionId") }, new object[2] { "Add replay bookmark", "AddBookmark" })); MethodBuilder methodBuilder4 = typeBuilder.DefineMethod("OnAssetLoaded", MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), Type.EmptyTypes); iLGenerator = methodBuilder4.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Call, type.GetMethod("Enable")); iLGenerator.Emit(OpCodes.Ret); typeBuilder.DefineMethodOverride(methodBuilder4, type.GetMethod("OnAssetLoaded")); return Activator.CreateInstance(typeBuilder.CreateType(), metadata); } } internal static class ReplayLightProperties { private static readonly Dictionary<string, (float Min, float Max)> Floats = new Dictionary<string, (float, float)>(StringComparer.Ordinal) { ["shapeRadius"] = (0f, 10000f), ["shapeWidth"] = (0f, 10000f), ["shapeHeight"] = (0f, 10000f), ["aspectRatio"] = (0.01f, 1000f), ["innerSpotPercent"] = (0f, 100f), ["volumetricDimmer"] = (0f, 16f), ["volumetricShadowDimmer"] = (0f, 16f), ["fadeDistance"] = (0f, 100000f), ["volumetricFadeDistance"] = (0f, 100000f), ["maxSmoothness"] = (0f, 1f), ["angularDiameter"] = (0f, 90f), ["normalBias"] = (0f, 64f), ["slopeBias"] = (0f, 64f), ["shadowNearPlane"] = (0f, 1000f) }; private static readonly HashSet<string> Bools = new HashSet<string>(StringComparer.Ordinal) { "applyRangeAttenuation", "affectDiffuse", "affectSpecular", "enableSpotReflector", "interactsWithSky" }; private static readonly HashSet<string> Enums = new HashSet<string>(StringComparer.Ordinal) { "type", "spotLightShape", "areaLightShape" }; internal static List<EnvironmentParameterSnapshot> Capture(Component? data) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown List<EnvironmentParameterSnapshot> list = new List<EnvironmentParameterSnapshot>(); if (!Object.op_Implicit((Object)(object)data)) { return list; } foreach (KeyValuePair<string, (float, float)> @float in Floats) { if (GameAccess.Read(data, @float.Key) is float num && GameAccess.Finite(num)) { EnvironmentParameterSnapshot val = new EnvironmentParameterSnapshot(); val.Name = @float.Key; val.Kind = "float"; val.Values = new float[1] { Mathf.Clamp(num, @float.Value.Item1, @float.Value.Item2) }; list.Add(val); } } foreach (string @bool in Bools) { if (GameAccess.Read(data, @bool) is bool flag) { EnvironmentParameterSnapshot val = new EnvironmentParameterSnapshot(); val.Name = @bool; val.Kind = "bool"; val.Values = new float[1] { flag ? 1f : 0f }; list.Add(val); } } foreach (string @enum in Enums) { object obj = GameAccess.Read(data, @enum); if (obj != null && obj.GetType().IsEnum) { list.Add(new EnvironmentParameterSnapshot { Name = @enum, Kind = "enum", Text = obj.ToString() }); } } return list; } internal static void Apply(Component? data, IReadOnlyList<EnvironmentParameterSnapshot> parameters, Light light, float intensity) { //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Invalid comparison between Unknown and I4 //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Invalid comparison between Unknown and I4 //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)data)) { light.intensity = intensity; return; } foreach (EnvironmentParameterSnapshot parameter in parameters) { if (parameter.Name == "type" && parameter.Kind == "enum") { SetEnum(data, "type", parameter.Text); } } foreach (EnvironmentParameterSnapshot parameter2 in parameters) { if (parameter2.Name != "type" && parameter2.Kind == "enum" && Enums.Contains(parameter2.Name)) { SetEnum(data, parameter2.Name, parameter2.Text); } } foreach (EnvironmentParameterSnapshot parameter3 in parameters) { if (parameter3.Values.Length != 1) { continue; } float num = parameter3.Values[0]; if (GameAccess.Finite(num)) { if (parameter3.Kind == "float" && Floats.TryGetValue(parameter3.Name, out (float, float) value)) { Set(data, parameter3.Name, Mathf.Clamp(num, value.Item1, value.Item2)); } else if (parameter3.Kind == "bool" && Bools.Contains(parameter3.Name)) { Set(data, parameter3.Name, num != 0f); } } } bool flag = (int)light.type == 0 && GameAccess.Read(data, "spotLightShape")?.ToString() == "Box"; bool flag2 = GameAccess.Read(data, "type")?.ToString() == "Area"; string value2 = (((int)light.type == 1 || flag) ? "Lux" : ((flag2 || (int)light.type == 3 || (int)light.type == 4) ? "Nits" : "Candela")); SetEnum(data, "lightUnit", value2); Set(data, "intensity", intensity); light.intensity = intensity; } private static void Set(Component data, string name, object value) { try { PropertyInfo property = ((object)data).GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); if ((object)property != null && property.CanWrite) { property.SetValue(data, value, null); } else { ((object)data).GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public)?.SetValue(data, value); } } catch { } } private static void SetEnum(Component data, string name, string value) { PropertyInfo property = ((object)data).GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); if ((object)property != null && property.CanWrite && property.PropertyType.IsEnum && Enum.IsDefined(property.PropertyType, value)) { Set(data, name, Enum.Parse(property.PropertyType, value)); } } } [BepInPlugin("pasta.replay", "LC Replay", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class ReplayPlugin : BaseUnityPlugin { private sealed class SaveResult { internal readonly string Path; internal readonly double Duration; internal readonly Exception? Error; internal readonly bool MetadataError; internal SaveResult(string path, double duration, Exception? error, bool metadataError) { Path = path; Duration = duration; Error = error; MetadataError = metadataError; } } public const string Guid = "pasta.replay"; public const string Version = "1.0.0"; private GUIStyle? overlayLabel; private GUIStyle? errorNoticeLabel; private ConfigEntry<bool> bones; private ConfigEntry<bool> world; private ConfigEntry<bool> chat; private ConfigEntry<bool> noShadow; private ConfigEntry<bool> showDebugOverlay; private ConfigEntry<bool> cinematicMove; private ConfigEntry<bool> showFog; private ConfigEntry<int> rate; private ConfigEntry<int> maxFields; private ConfigEntry<int> maxObjects; private ConfigEntry<int> maxVertices; private ConfigEntry<float> replayResolution; private ConfigEntry<float> replayGamma; private ConfigEntry<float> cameraSpeed; private ConfigEntry<string> folder; private ConfigEntry<string> extraTypes; private ReplayBookmarkInput? bookmarkInput; private EventHooks? hooks; private Recorder? recorder; private ReplayViewer? viewer; private ReplayViewer? cachedViewer; private string? viewerPath; private string? cachedViewerPath; private int viewerSceneHandle; private bool viewerConnected; private int cachedSceneHandle; private bool cachedConnected; private List<(string Path, long Length, DateTime ModifiedUtc)>? cachedFiles; private ReplayLibraryWindow? library; private ReplayMenuButton? menuButton; private ReplayPauseMenuButton? pauseMenuButton; private Harmony? inputHarmony; private static ReplayPlugin? activeInputGuard; private ReplayRuntime? runtime; private ReplayArchive? archive; private ArchiveIndex index = new ArchiveIndex(); private ArchiveRun? run; private ArchiveSession? session; private ArchiveDay? day; private ArchiveSegment? segment; private IReadOnlyList<WorldSnapshot>? reusableWorld; private readonly List<Task<SaveResult>> pendingSaves = new List<Task<SaveResult>>(); private readonly List<Task> pendingGroupSaves = new List<Task>(); private DayLifecycle dayLifecycle = new DayLifecycle(); private Component? sessionRound; private Component? deletedLobbyRound; private Task<ArchiveIndex>? scanning; private Task<LoadedRecording>? loading; private IEnumerator<float>? loadingPlayerAssets; private CancellationTokenSource? loadingCancellation; private bool rescanRequested; private bool recordingBlocked; private bool wasConnected; private bool quitting; private bool checkpointRequested; private bool dayHadRecoveryGap; private int recordingRetryFailures; private float nextRecordingRetryAt; private bool loadCancelled; private bool closeLibraryAfterSceneCleanup; private bool shutdownCompleted; private bool inputReported; private bool inputReadyReported; private Component? playbackPlayer; private bool savedMoveInput; private bool savedLookInput; private LiveReplayInput? liveReplayInput; private int part; private float nextMemberScan; private string status = "Gameplay is recorded and saved automatically."; private string recordingFailure = ""; private string errorNotice = ""; private float errorNoticeUntil; private float errorNoticeStarted; private bool errorNoticePending; private string? loadingPath; private float loadingSpeed = 1f; private int loadingPercent; private string loadingStage = ""; private string ReplayDirectory => Path.GetFullPath(Path.IsPathRooted(folder.Value) ? folder.Value : Path.Combine(Paths.BepInExRootPath, folder.Value)); private void Awake() { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Expected O, but got Unknown //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Expected O, but got Unknown //IL_0609: Unknown result type (might be due to invalid IL or missing references) //IL_060e: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Expected O, but got Unknown //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Expected O, but got Unknown //IL_03fb: Unknown result type (might be due to invalid IL or missing references) //IL_0405: Expected O, but got Unknown //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_0471: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, "io.lcreplay.recorder.cfg"); if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) && File.Exists(text)) { try { File.Copy(text, ((BaseUnityPlugin)this).Config.ConfigFilePath, overwrite: false); ((BaseUnityPlugin)this).Config.Reload(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not migrate legacy replay settings: " + ex.Message)); } } activeInputGuard = this; rate = ((BaseUnityPlugin)this).Config.Bind<int>("Recording", "SampleRate", 10, new ConfigDescription("Automatic recording snapshots per second.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 60), Array.Empty<object>())); bones = ((BaseUnityPlugin)this).Config.Bind<bool>("Recording", "CaptureBones", false, "Also record player and item bone poses on each sample. Enemy visual poses are always recorded after animation and procedural movement; other actors use installed Animator controllers when off."); world = ((BaseUnityPlugin)this).Config.Bind<bool>("Recording", "CaptureWorld", true, "Save bounded geometry, textures and actor appearance for offline replay."); chat = ((BaseUnityPlugin)this).Config.Bind<bool>("Recording", "CaptureChat", false, "Capture displayed text chat. Restart required."); maxFields = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "FieldsPerEntity", 192, new ConfigDescription("Primitive fields per component.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 512), Array.Empty<object>())); maxObjects = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "WorldObjects", 4000, new ConfigDescription("Render object budget per world snapshot.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 20000), Array.Empty<object>())); maxVertices = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "WorldVertices", 500000, new ConfigDescription("Unique mesh vertex budget per world snapshot; repeated room meshes share data.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1000, 1000000), Array.Empty<object>())); ConfigEntry<int> val = ((BaseUnityPlugin)this).Config.Bind<int>("Compatibility", "CaptureSettingsVersion", 0, "Internal capture settings migration version."); if (val.Value < 4) { if (maxVertices.Value == 120000) { maxVertices.Value = 500000; } val.Value = 4; } if (val.Value < 5) { bones.Value = false; val.Value = 5; } if (val.Value < 6) { val.Value = 6; } folder = ((BaseUnityPlugin)this).Config.Bind<string>("Storage", "ReplayDirectory", "replays", "Absolute folder, or relative to BepInEx. Restart after changing."); extraTypes = ((BaseUnityPlugin)this).Config.Bind<string>("Compatibility", "ExtraTrackedTypes", "", "Comma-separated full Unity Component names supplied by other mods."); replayResolution = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "ResolutionMultiplier", 1f, new ConfigDescription("Replay resolution relative to the HDLethalCompany vanilla baseline: 1 = 860x520. Independent of window size; UI stays sharp. Replaces RenderResolutionScale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 4.5f), Array.Empty<object>())); ConfigEntry<int> val2 = ((BaseUnityPlugin)this).Config.Bind<int>("Compatibility", "PlaybackSettingsVersion", 0, "Internal playback defaults migration version."); replayGamma = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "Gamma", 1f, new ConfigDescription("Replay image gamma; 1.0 is neutral.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>())); noShadow = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "NoShadow", false, "Keep outside illumination inside the facility and disable replay light shadows."); showFog = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "ShowFog", true, "Render recorded HDRP fog, local fog and fog-named scenery in replay."); if (val2.Value < 3) { replayResolution.Value = 1f; replayGamma.Value = 1f; noShadow.Value = false; showFog.Value = true; val2.Value = 3; ((BaseUnityPlugin)this).Config.Save(); } cinematicMove = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "CinematicMove", false, "Use smooth acceleration and deceleration for the replay free camera. Press C in replay to toggle."); cameraSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "CameraSpeed", 8f, new ConfigDescription("Free camera speed in replay.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); showDebugOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "ShowOverlay", false, "Show the recording status in the top-left corner during gameplay. Errors appear briefly in the top-right corner regardless of this setting."); ConfigEntry<KeyCode> fallback = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "BookmarkKey", (KeyCode)96, "Recording bookmark key when InputUtils is absent. With InputUtils, change Add replay bookmark in the game's Controls menu."); bookmarkInput = new ReplayBookmarkInput(fallback, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); try { archive = new ReplayArchive(ReplayDirectory); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Replay archive directory: " + archive.RootDirectory)); library = new ReplayLibraryWindow(); menuButton = new ReplayMenuButton(OpenLibrary, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); pauseMenuButton = new ReplayPauseMenuButton(OpenLibrary, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }); InstallInputGuards(); library.RefreshRequested += RefreshArchive; library.PlayRequested += delegate(string path) { LoadReplay(path); }; library.OpenFolderRequested += OpenFolder; library.DeleteRequested += DeleteRecording; library.CloseRequested += CloseLibrary; hooks = new EventHooks(delegate(ReplayEvent evt) { recorder?.Event(evt); if (evt.Name.Contains("OnShipLanded")) { recorder?.RefreshEnvironmentAfterLanding(); } else if (evt.Name.Contains("StartGame")) { recorder?.MarkWorldDirty(); } else if (evt.Name == "SandSpiderAI.SpawnWebTrapClientRpc") { recorder?.NoticeSpiderWeb(); } }, chat.Value, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("LC Replay {0}: automatic quota/deadline archive; {1} event hooks active.", "1.0.0", hooks.Installed.Count)); } catch (Exception error) { ReportRecordingFailure("Initialization failed", error); } SceneManager.sceneLoaded += SceneLoaded; Application.quitting += ApplicationQuitting; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Replay bootstrap ready; waiting for a loaded scene before creating the runtime."); Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).isLoaded) { EnsureRuntime(); } } private void SceneLoaded(Scene scene, LoadSceneMode mode) { EnsureRuntime(); } private void EnsureRuntime() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (quitting || shutdownCompleted || Object.op_Implicit((Object)(object)runtime)) { return; } try { runtime = ReplayRuntime.Create(RunFrame, DrawUi, RunLateFrame, delegate { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Replay runtime active: independent persistent frame and UI driver."); }, delegate(string stage, Exception ex) { ShowErrorNotice("Replay runtime " + stage + " failed: " + ex.Message); ((BaseUnityPlugin)this).Logger.LogError((object)("Replay runtime " + stage + " failed: " + ex)); }); ManualLogSource logger = ((BaseUnityPlugin)this).Logger; Scene activeScene = SceneManager.GetActiveScene(); logger.LogInfo((object)("Replay runtime created after scene load: " + ((Scene)(ref activeScene)).name + ".")); } catch (Exception error) { ReportRecordingFailure("Replay runtime initialization failed", error); } } private void RunFrame() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if (quitting) { return; } NativePlayerAssetLoader.Tick(); if (viewer != null || cachedViewer != null || loading != null || SceneAssetReplay.HasPendingLoads || ReplayViewer.HasPendingSceneCleanup || NativePlayerAssetLoader.RequiresIsolation) { ReplayIsolation.EnsureNetworkExclusion(); } Scene activeScene; if (cachedViewer != null) { if (cachedConnected == GameAccess.Connected) { int num = cachedSceneHandle; activeScene = SceneManager.GetActiveScene(); if (num == ((Scene)(ref activeScene)).handle) { goto IL_0071; } } DiscardCachedViewer(); } goto IL_0071; IL_01d1: object replayPauseMenuButton; int allowed; ((ReplayPauseMenuButton)replayPauseMenuButton).Tick((byte)allowed != 0); goto IL_01d6; IL_01d6: if (viewer != null) { BlockLocalPlayerInput(); if (viewer.CloseRequested || ReplayInput.WasPressed("Escape") || ReplayInput.WasPressed("F11")) { CloseViewer(reopenLibrary: true); return; } try { viewer.Tick(Time.unscaledDeltaTime); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); CloseViewer(reopenLibrary: true, allowCache: false); status = "Playback error: " + ex.Message; ShowErrorNotice(status); } if (viewer?.Error != null) { Exception error = viewer.Error; ((BaseUnityPlugin)this).Logger.LogError((object)error); CloseViewer(reopenLibrary: true, allowCache: false); status = "A recording part could not be loaded: " + error.Message; ShowErrorNotice(status); } return; } bool connected; CompleteLoad(connected); if (viewer != null) { return; } if (ReplayInput.WasPressed("F9")) { ReplayLibraryWindow? replayLibraryWindow = library; if (replayLibraryWindow != null && replayLibraryWindow.IsOpen) { if (!library.DismissDialog()) { CloseLibrary(); } } else { OpenLibrary(); } } ReplayLibraryWindow? replayLibraryWindow2 = library; if (replayLibraryWindow2 != null && replayLibraryWindow2.IsOpen) { library.Tick(); BlockLocalPlayerInput(); ReplayLibraryWindow? replayLibraryWindow3 = library; string text = status; bool networkStateKnown = GameAccess.NetworkStateKnown; bool isLoading = loading != null || scanning != null; string text2 = recordingFailure; float loadProgress = ((loading != null) ? ((float)Volatile.Read(in loadingPercent) / 100f) : 0f); string loadStage = ((loading != null) ? Volatile.Read(in loadingStage) : ((scanning != null) ? "Scanning archive" : "")); ArchiveDay? obj = day; replayLibraryWindow3.DrawGui(text, networkStateKnown, isLoading, text2, loadProgress, loadStage, ((obj != null) ? ((ArchiveFolder)obj).Id : null) ?? ""); if (ReplayInput.WasPressed("Escape") && !library.DismissDialog()) { CloseLibrary(); } } if (ReplayInput.WasPressed("F8") && GameAccess.CanRecord && archive != null && hooks != null) { try { if (recordingBlocked && day != null) { archive.EndDay(day, "interrupted"); day = null; segment = null; part = 0; reusableWorld = null; } recordingBlocked = false; nextRecordingRetryAt = 0f; recordingRetryFailures = 0; if (recorder != null) { checkpointRequested = true; } } catch (Exception error2) { RecoverRecording("Save checkpoint failed", error2); } } ReplayBookmarkInput? replayBookmarkInput = bookmarkInput; if (replayBookmarkInput != null && replayBookmarkInput.WasPressed()) { AddRecordingBookmark(); } if (ReplayInput.WasPressed("F11")) { OpenLibrary(); } return; IL_0197: replayPauseMenuButton = pauseMenuButton; if (replayPauseMenuButton == null) { goto IL_01d6; } if (connected && viewer == null) { ReplayLibraryWindow? replayLibraryWindow4 = library; if (replayLibraryWindow4 == null || !replayLibraryWindow4.IsOpen) { allowed = ((loading == null) ? 1 : 0); goto IL_01d1; } } allowed = 0; goto IL_01d1; IL_0071: if (closeLibraryAfterSceneCleanup && !SceneAssetReplay.HasPendingLoads && !ReplayViewer.HasPendingSceneCleanup) { CloseLibrary(); } bool available = ReplayInput.Available; if (!inputReported || (available && !inputReadyReported)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)(available ? "Replay keyboard input ready (F9 opens the archive)." : "Replay keyboard input is not ready; waiting for an Input System keyboard.")); inputReported = true; inputReadyReported |= available; } connected = GameAccess.Connected; if (viewer != null) { if (viewerConnected == connected) { int num2 = viewerSceneHandle; activeScene = SceneManager.GetActiveScene(); if (num2 == ((Scene)(ref activeScene)).handle) { goto IL_0115; } } CloseViewer(reopenLibrary: false, allowCache: false); status = "Replay closed after the live game changed scenes."; return; } goto IL_0115; IL_0115: if (!connected && wasConnected) { EndSession(); recordingBlocked = false; recordingRetryFailures = 0; nextRecordingRetryAt = 0f; } wasConnected = connected; CompleteSaves(); ResumeRecordingWhenReady(); CompleteScan(); ReplayMenuButton? replayMenuButton = menuButton; if (replayMenuButton == null) { goto IL_0197; } int allowed2; if (!connected && viewer == null) { ReplayLibraryWindow? replayLibraryWindow5 = library; if (replayLibraryWindow5 == null || !replayLibraryWindow5.IsOpen) { allowed2 = ((loading == null) ? 1 : 0); goto IL_0192; } } allowed2 = 0; goto IL_0192; IL_0192: replayMenuButton.Tick((byte)allowed2 != 0); goto IL_0197; } private void RunLateFrame() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown if (quitting) { return; } viewer?.RefreshCursor(); if (hooks != null && archive != null && GameAccess.CanRecord) { try { if (checkpointRequested) { recorder?.Event(new ReplayEvent { Category = "capture", Name = "manual-checkpoint" }); } RecordAutomatically(viewer != null); } catch (Exception error) { RecoverRecording("Automatic recording stopped", error); } } checkpointRequested = false; } private unsafe void RecordAutomatically(bool playbackActive = false) { //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Invalid comparison between Unknown and I4 //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Expected O, but got Unknown //IL_0428: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Expected O, but got Unknown //IL_049d: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Unknown result type (might be due to invalid IL or missing references) //IL_04ac: Unknown result type (might be due to invalid IL or missing references) //IL_04b7: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Unknown result type (might be due to invalid IL or missing references) //IL_04f9: Expected O, but got Unknown //IL_0507: Unknown result type (might be due to invalid IL or missing references) //IL_050a: Invalid comparison between Unknown and I4 if (recordingBlocked) { return; } object obj = GameAccess.Singleton("StartOfRound"); if (session != null) { Component val = (Component)((obj is Component) ? obj : null); if (val != null && (Object)(object)val != (Object)(object)sessionRound) { EndSession(); } } if (recordingBlocked) { return; } if (!(GameAccess.Read(obj, "inShipPhase") is bool flag)) { throw new InvalidOperationException("This game version does not expose round phase (inShipPhase)."); } if ((Object)(object)deletedLobbyRound != (Object)null) { if (obj == deletedLobbyRound && flag) { return; } deletedLobbyRound = null; } ArchiveQuotaSnapshot val2 = CurrentQuota(); if (session == null && (!val2.Target.HasValue || val2.Target.Value <= 0 || !val2.DeadlineDaysRemaining.HasValue || (val2.DeadlineDaysRemaining.Value == 0 && (!(GameAccess.Read(GameAccess.Singleton("TimeOfDay"), "timeUntilDeadline") is float num) || !(num > 0f))))) { return; } if (session == null) { object value = GameAccess.Singleton("GameNetworkManager"); string text = (GameAccess.Read(value, "steamLobbyName") as string)?.Trim(); if (string.IsNullOrWhiteSpace(text) && GameAccess.IsHost) { text = (GameAccess.Read(GameAccess.Read(value, "lobbyHostSettings"), "lobbyName") as string)?.Trim(); } if (string.IsNullOrWhiteSpace(text)) { text = "Unknown lobby"; } if (run != null && !string.Equals(((ArchiveFolder)run).Label, text, StringComparison.Ordinal)) { archive.EndRun(run); run = null; } run = run ?? archive.BeginRun(DateTimeOffset.UtcNow, text); session = archive.BeginQuota(run, DateTimeOffset.UtcNow, val2, GameAccess.IsHost ? "host-observed" : "client-observed"); sessionRound = (Component?)((obj is Component) ? obj : null); dayLifecycle = new DayLifecycle(); } DayTransition val3 = dayLifecycle.Observe(flag); if (day != null && (int)val3 == 2) { recorder?.Event(new ReplayEvent { Category = "day", Name = ((object)(*(DayTransition*)(&val3))/*cast due to .constrained prefix*/).ToString(), Data = new Dictionary<string, string> { ["day"] = dayLifecycle.DayNumber.ToString() } }); recorder?.Tick(forceFrame: true, playbackActive); StopSegment(); ArchiveDay val4 = day; day = null; part = 0; archive.EndDay(val4, (recordingBlocked || dayHadRecoveryGap) ? "interrupted" : "saved"); dayHadRecoveryGap = false; } else { if (day == null && dayLifecycle.HasReturned) { return; } if (day == null && session.QuotaRemaining != val2.Remaining) { archive.EndSession(session); session = archive.BeginQuota(run, DateTimeOffset.UtcNow, val2, GameAccess.IsHost ? "host-observed" : "client-observed"); } if (day == null) { BeginDay(obj, val2); } if (day == null) { return; } if (Time.realtimeSinceStartup >= nextMemberScan) { nextMemberScan = Time.realtimeSinceStartup + 2f; if (archive.UpdateMembers(day, CurrentMembers(obj))) { RefreshArchiveIfOpen(); } } string text2 = CurrentMoon(obj); if ((!dayLifecycle.HasReturned && text2 != day.Moon) || (int)val3 != 0) { archive.UpdateDay(day, dayLifecycle.HasReturned ? day.Moon : text2, dayLifecycle.HasReturned ? "returned" : (dayLifecycle.HasDeparted ? "exploring" : "preparing")); } if (recorder == null) { StartSegment(); } if (recorder != null) { if ((int)val3 != 0) { recorder.Event(new ReplayEvent { Category = "day", Name = ((object)(*(DayTransition*)(&val3))/*cast due to .constrained prefix*/).ToString(), Data = new Dictionary<string, string> { ["day"] = dayLifecycle.DayNumber.ToString() } }); } recorder.Tick(checkpointRequested || (int)val3 == 2, playbackActive); if (recorder.Error != null) { Exception error = recorder.Error; RecoverRecording("Automatic saving stopped", error); } else if (recorder.Duration >= 30.0) { recordingRetryFailures = 0; } } } } private void BeginDay(object? round, ArchiveQuotaSnapshot quota) { int? num = null; if (GameAccess.IsHost && GameAccess.Read(GameAccess.Read(round, "gameStats"), "daysSpent") is int num2 && num2 >= 0 && num2 < 1000000) { num = num2 + 1; } day = archive.BeginQuotaDay(session, dayLifecycle.DayNumber, DateTimeOffset.UtcNow, CurrentMoon(round), quota, num); nextMemberScan = 0f; dayHadRecoveryGap = false; reusableWorld = null; archive.UpdateDay(day, day.Moon, dayLifecycle.HasReturned ? "returned" : (dayLifecycle.HasDeparted ? "exploring" : "preparing")); part = 0; RefreshArchiveIfOpen(); } private static string CurrentMoon(object? round) { return GameAccess.Scalar(GameAccess.Read(GameAccess.Read(round, "currentLevel"), "PlanetName")) ?? "Unknown moon"; } private static ArchiveQuotaSnapshot CurrentQuota() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown object value = GameAccess.Singleton("TimeOfDay"); int? num = NonnegativeInt(GameAccess.Read(value, "daysUntilDeadline")); object obj = GameAccess.Read(value, "timeUntilDeadline"); object obj2 = GameAccess.Read(value, "totalTime"); if (obj is float num2 && obj2 is float num3) { num = QuotaDay.Remaining(num2, num3, num); } return new ArchiveQuotaSnapshot { Target = NonnegativeInt(GameAccess.Read(value, "profitQuota")), Fulfilled = NonnegativeInt(GameAccess.Read(value, "quotaFulfilled")), DeadlineDaysRemaining = num, DeadlineDaysTotal = NonnegativeInt(GameAccess.Read(GameAccess.Read(value, "quotaVariables"), "deadlineDaysAmount")), QuotaCycle = NonnegativeInt(GameAccess.Read(value, "timesFulfilledQuota")) }; } private static int? NonnegativeInt(object? value) { if (value == null) { return null; } try { return Math.Max(0, Convert.ToInt32(value, CultureInfo.InvariantCulture)); } catch { return null; } } private static IEnumerable<string> CurrentMembers(object? round) { if (!(GameAccess.Read(round, "allPlayerScripts") is IEnumerable enumerable)) { yield break; } foreach (object item in enumerable) { if (item != null && GameAccess.Bool(item, "isPlayerControlled") && GameAccess.Read(item, "playerUsername") is string text && !string.IsNullOrWhiteSpace(text)) { yield return text; } } } private void StartSegment() { if (recorder != null || recordingBlocked || day == null || hooks == null) { return; } segment = archive.AllocateSegment(day, ++part); if (day.Members.Count != 0) { segment.Metadata["members"] = string.Join(", ", day.Members); } part = segment.Part; try { recorder = new Recorder(((ArchiveFolder)day).DirectoryPath, rate.Value, bones.Value, world.Value, maxFields.Value, maxObjects.Value, maxVertices.Value, extraTypes.Value, hooks, chat.Value, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }, ((ArchiveFolder)day).Id, part, segment.FilePath, segment.Metadata, reusableWorld); ReplayApi.EventSink = recorder.Event; recordingFailure = ""; library?.SetRecordingPath(segment.FilePath); status = "Quota " + (day.QuotaRemaining?.ToString() ?? "unknown") + " / Deadline " + (day.DeadlineDaysRemaining?.ToString() ?? "unknown") + " days - recording and saving automatically"; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Recording " + segment.FilePath)); RefreshArchiveIfOpen(); } catch (Exception ex) { try { archive.CompleteSegment(segment, 0.0, false, ex.Message, (int?)
LCReplay.Core.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.Versioning; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Newtonsoft.Json; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("LCReplay.Core.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LCReplay.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+31677f6cc6ace662863f1d3840f84ade19c50bbc")] [assembly: AssemblyProduct("LCReplay.Core")] [assembly: AssemblyTitle("LCReplay.Core")] [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.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 LCReplay.Core { public sealed class ReplayHeader { public int SchemaVersion { get; set; } = 1; public string SessionId { get; set; } = ""; public string GameVersion { get; set; } = ""; public string UnityVersion { get; set; } = ""; public string RecorderVersion { get; set; } = ""; public string StartedUtc { get; set; } = ""; public string Perspective { get; set; } = ""; public int SampleRate { get; set; } = 10; public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>(); public List<string> Capabilities { get; set; } = new List<string>(); public List<string> Warnings { get; set; } = new List<string>(); } public sealed class ReplayRecord { public string Kind { get; set; } = ""; public double Time { get; set; } [JsonIgnore] public double? SourceTime { get; set; } public ReplayHeader? Header { get; set; } public ReplayFrame? Frame { get; set; } public ReplayEvent? Event { get; set; } public WorldSnapshot? World { get; set; } } public sealed class ReplayFrame { public double Time { get; set; } public List<EntitySnapshot> Entities { get; set; } = new List<EntitySnapshot>(); public List<AnchorPose> Anchors { get; set; } = new List<AnchorPose>(); public List<RenderPose> SceneRenderers { get; set; } = new List<RenderPose>(); public List<ParticlePose> Particles { get; set; } = new List<ParticlePose>(); public List<ParticleStyleSnapshot> ParticleStyles { get; set; } = new List<ParticleStyleSnapshot>(); public List<LinePose> Lines { get; set; } = new List<LinePose>(); public Dictionary<string, string> State { get; set; } = new Dictionary<string, string>(); } public sealed class ParticlePose { public string EmitterId { get; set; } = ""; public Vec3 Position { get; set; } public Vec3 Velocity { get; set; } public Vec3 Size3D { get; set; } public Vec3 Rotation3D { get; set; } public float Lifetime { get; set; } = 1f; public float RemainingLifetime { get; set; } = 1f; public uint RandomSeed { get; set; } public float Size { get; set; } public float Rotation { get; set; } public float[] Color { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public bool IsInterior { get; set; } } public sealed class LinePose { public string Id { get; set; } = ""; public string MaterialName { get; set; } = ""; public string ShaderName { get; set; } = ""; public int TextureMode { get; set; } public int Alignment { get; set; } public float[] Positions { get; set; } = Array.Empty<float>(); public float[] StartColor { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public float[] EndColor { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public float StartWidth { get; set; } public float EndWidth { get; set; } public bool IsInterior { get; set; } } public sealed class AnchorPose { public string Id { get; set; } = ""; public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public Vec3 Scale { get; set; } = Vec3.One; } public sealed class EntitySnapshot { public string Id { get; set; } = ""; public string Kind { get; set; } = ""; public string Name { get; set; } = ""; public Vec3 Position { get; set; } public Vec3 Scale { get; set; } = Vec3.One; public Quat Rotation { get; set; } = Quat.Identity; public bool Active { get; set; } = true; public Quat? ViewRotation { get; set; } public Vec3? ViewPosition { get; set; } public bool PoseFromItemEvents { get; set; } public Dictionary<string, string> State { get; set; } = new Dictionary<string, string>(); public List<BonePose> Bones { get; set; } = new List<BonePose>(); public List<RenderPose> Renderers { get; set; } = new List<RenderPose>(); public bool ShouldSerializeViewRotation() { return ViewRotation.HasValue; } public bool ShouldSerializeViewPosition() { return ViewPosition.HasValue; } public bool ShouldSerializePoseFromItemEvents() { return PoseFromItemEvents; } public bool ShouldSerializePosition() { return !PoseFromItemEvents; } public bool ShouldSerializeRotation() { return !PoseFromItemEvents; } public bool ShouldSerializeScale() { return !PoseFromItemEvents; } } public struct Vec3 { public float X { get; set; } public float Y { get; set; } public float Z { get; set; } public static Vec3 One => new Vec3(1f, 1f, 1f); public Vec3(float x, float y, float z) { X = x; Y = y; Z = z; } } public struct Quat { public float X { get; set; } public float Y { get; set; } public float Z { get; set; } public float W { get; set; } public static Quat Identity => new Quat(0f, 0f, 0f, 1f); public Quat(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } } public sealed class BonePose { public string Path { get; set; } = ""; public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public Vec3 Scale { get; set; } = Vec3.One; } public sealed class RenderPose { public string Id { get; set; } = ""; public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public Vec3 Scale { get; set; } = Vec3.One; public bool Active { get; set; } = true; } public sealed class ReplayEvent { public double Time { get; set; } public string Category { get; set; } = ""; public string Name { get; set; } = ""; public string EntityId { get; set; } = ""; public Dictionary<string, string> Data { get; set; } = new Dictionary<string, string>(); public AnimationTrackSnapshot? AnimationTrack { get; set; } public ItemMotionSnapshot? ItemMotion { get; set; } public EnvironmentComponentSnapshot? PostProcess { get; set; } } public sealed class ItemMotionSnapshot { public string Mode { get; set; } = ""; public string AnchorId { get; set; } = ""; public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public Vec3 Scale { get; set; } = Vec3.One; public Vec3 Target { get; set; } public Quat TargetRotation { get; set; } = Quat.Identity; public float FallTime { get; set; } public float FallRate { get; set; } public float[] Curve { get; set; } = new float[0]; public bool ShouldSerializeAnchorId() { return AnchorId.Length != 0; } public bool ShouldSerializeTarget() { return Mode == "fall"; } public bool ShouldSerializeTargetRotation() { return Mode == "fall"; } public bool ShouldSerializeFallTime() { return Mode == "fall"; } public bool ShouldSerializeFallRate() { return Mode == "fall"; } public bool ShouldSerializeCurve() { return Mode == "fall"; } } public sealed class AnimationTrackSnapshot { public string Clip { get; set; } = ""; public string AnimatorPath { get; set; } = ""; public int StateHash { get; set; } public int Layer { get; set; } public bool Looping { get; set; } public List<string> BonePaths { get; set; } = new List<string>(); public float[] Phases { get; set; } = new float[0]; public float[] Positions { get; set; } = new float[0]; public float[] Rotations { get; set; } = new float[0]; } public sealed class WorldSnapshot { public string Scene { get; set; } = ""; public string CaptureSetId { get; set; } = ""; public string Layer { get; set; } = ""; public double CaptureCompletedAt { get; set; } public string AssetScene { get; set; } = ""; public string AssetGameVersion { get; set; } = ""; public int AssetBuildIndex { get; set; } = -1; public int MapSeed { get; set; } public int LevelId { get; set; } = -1; public int DungeonSeed { get; set; } public int DungeonFlow { get; set; } = -1; public List<string> AssetRendererPaths { get; set; } = new List<string>(); public List<string> AssetTerrainPaths { get; set; } = new List<string>(); public List<GeometrySnapshot> Geometry { get; set; } = new List<GeometrySnapshot>(); public List<TextureSnapshot> Textures { get; set; } = new List<TextureSnapshot>(); public List<MaterialSnapshot> Materials { get; set; } = new List<MaterialSnapshot>(); public List<LightSnapshot> Lights { get; set; } = new List<LightSnapshot>(); public List<ParticleEmitterSnapshot> ParticleEmitters { get; set; } = new List<ParticleEmitterSnapshot>(); public List<LocalFogSnapshot> LocalFogs { get; set; } = new List<LocalFogSnapshot>(); public List<RoomSnapshot> Rooms { get; set; } = new List<RoomSnapshot>(); public EnvironmentSnapshot? Environment { get; set; } public bool ShouldSerializeCaptureCompletedAt() { return CaptureCompletedAt > 0.0; } } public sealed class RoomSnapshot { public string Id { get; set; } = ""; public Vec3 Center { get; set; } public Vec3 Size { get; set; } public List<RoomVolumeSnapshot> AdditionalVolumes { get; set; } = new List<RoomVolumeSnapshot>(); } public sealed class RoomVolumeSnapshot { public Vec3 Center { get; set; } public Vec3 Size { get; set; } } public sealed class EnvironmentSnapshot { public float[] AmbientSkyColor { get; set; } = new float[4] { 0.15f, 0.2f, 0.28f, 1f }; public List<EnvironmentComponentSnapshot> Components { get; set; } = new List<EnvironmentComponentSnapshot>(); public List<TextureSnapshot> SkyFaces { get; set; } = new List<TextureSnapshot>(); public string SkyFaceEncoding { get; set; } = ""; public List<PostProcessPassSnapshot> CustomPasses { get; set; } = new List<PostProcessPassSnapshot>(); public bool CustomPassCaptureComplete { get; set; } public bool ShouldSerializeSkyFaceEncoding() { return SkyFaceEncoding.Length != 0; } public bool ShouldSerializeCustomPasses() { return CustomPasses.Count != 0; } public bool ShouldSerializeCustomPassCaptureComplete() { return CustomPassCaptureComplete; } } public sealed class PostProcessPassSnapshot { public string Name { get; set; } = ""; public string ShaderName { get; set; } = ""; public string MaterialName { get; set; } = ""; public string InjectionPoint { get; set; } = ""; public string MaterialPassName { get; set; } = ""; public bool? FetchColorBuffer { get; set; } public List<EnvironmentParameterSnapshot> Properties { get; set; } = new List<EnvironmentParameterSnapshot>(); public bool ShouldSerializeMaterialName() { return MaterialName.Length != 0; } public bool ShouldSerializeFetchColorBuffer() { return FetchColorBuffer.HasValue; } } public sealed class EnvironmentComponentSnapshot { public string Type { get; set; } = ""; public List<EnvironmentParameterSnapshot> Parameters { get; set; } = new List<EnvironmentParameterSnapshot>(); } public sealed class EnvironmentParameterSnapshot { public string Name { get; set; } = ""; public string Kind { get; set; } = ""; public float[] Values { get; set; } = new float[0]; public string Text { get; set; } = ""; public float[] CurveKeys { get; set; } = new float[0]; public bool ShouldSerializeText() { return Text.Length != 0; } public bool ShouldSerializeCurveKeys() { return CurveKeys.Length != 0; } } public sealed class LightSnapshot { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public string AnchorId { get; set; } = ""; public string EntityId { get; set; } = ""; public bool IsInterior { get; set; } public string Type { get; set; } = "Point"; public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public float[] Color { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public bool UseColorTemperature { get; set; } public float ColorTemperature { get; set; } = 6500f; public float Intensity { get; set; } = 1f; public float LightDimmer { get; set; } = 1f; public float Range { get; set; } = 10f; public float SpotAngle { get; set; } = 30f; public bool Shadows { get; set; } public float ShadowStrength { get; set; } = 1f; public float ShadowDimmer { get; set; } = 1f; public string BakeType { get; set; } = ""; public List<EnvironmentParameterSnapshot> LightPipelineParameters { get; set; } = new List<EnvironmentParameterSnapshot>(); public bool ShouldSerializeName() { return Name.Length != 0; } public bool ShouldSerializeLightPipelineParameters() { return LightPipelineParameters.Count != 0; } public bool ShouldSerializeShadowStrength() { return ShadowStrength != 1f; } public bool ShouldSerializeLightDimmer() { return LightDimmer != 1f; } public bool ShouldSerializeShadowDimmer() { return ShadowDimmer != 1f; } public bool ShouldSerializeBakeType() { return BakeType.Length != 0; } } public sealed class ParticleEmitterSnapshot { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public string EntityId { get; set; } = ""; public string MaterialId { get; set; } = ""; public ParticleStyleSnapshot? Style { get; set; } public bool IsInterior { get; set; } public string RoomId { get; set; } = ""; public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public float[] Color { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public float Rate { get; set; } = 10f; public float Lifetime { get; set; } = 1f; public float Speed { get; set; } public float Size { get; set; } = 0.1f; public float Radius { get; set; } = 0.1f; } public sealed class ParticleStyleSnapshot { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public string ParentName { get; set; } = ""; public string MaterialName { get; set; } = ""; public string ShaderName { get; set; } = ""; public string MeshName { get; set; } = ""; public bool Simulate { get; set; } public bool IsInterior { get; set; } public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public float Time { get; set; } public uint RandomSeed { get; set; } public int RenderMode { get; set; } public int Alignment { get; set; } public int[] VertexStreams { get; set; } = Array.Empty<int>(); public float LengthScale { get; set; } = 2f; public float VelocityScale { get; set; } public float CameraVelocityScale { get; set; } public Vec3 Scale { get; set; } = Vec3.One; public Vec3 Pivot { get; set; } } public sealed class LocalFogSnapshot { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public bool IsInterior { get; set; } public string RoomId { get; set; } = ""; public Vec3 Position { get; set; } public Quat Rotation { get; set; } = Quat.Identity; public Vec3 Size { get; set; } = new Vec3(1f, 1f, 1f); public Vec3 PositiveFade { get; set; } = new Vec3(0.1f, 0.1f, 0.1f); public Vec3 NegativeFade { get; set; } = new Vec3(0.1f, 0.1f, 0.1f); public float[] Albedo { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public float MeanFreePath { get; set; } = 10f; public float Anisotropy { get; set; } public float DistanceFadeStart { get; set; } = 10000f; public float DistanceFadeEnd { get; set; } = 10000f; public int Priority { get; set; } public int BlendingMode { get; set; } public int FalloffMode { get; set; } public bool InvertFade { get; set; } public int MaskMode { get; set; } public Vec3 TextureTiling { get; set; } = Vec3.One; public Vec3 TextureScrollingSpeed { get; set; } public int MaskWidth { get; set; } public int MaskHeight { get; set; } public int MaskDepth { get; set; } public byte[] MaskRgba { get; set; } = new byte[0]; } public sealed class TextureSnapshot { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public int Width { get; set; } public int Height { get; set; } public bool Linear { get; set; } public int FilterMode { get; set; } = -1; public byte[] Png { get; set; } = new byte[0]; public bool ShouldSerializeName() { return Name.Length != 0; } public bool ShouldSerializeFilterMode() { return FilterMode >= 0; } } public sealed class MaterialSnapshot { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public string ShaderName { get; set; } = ""; public float[] Color { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public string TextureId { get; set; } = ""; public float[] TextureScaleOffset { get; set; } = new float[4] { 1f, 1f, 0f, 0f }; public bool AlphaClip { get; set; } public bool Transparent { get; set; } public float Cutoff { get; set; } = 0.5f; public int RenderQueue { get; set; } = -1; public List<string> Keywords { get; set; } = new List<string>(); public bool? KeywordsComplete { get; set; } public List<MaterialPropertySnapshot> Properties { get; set; } = new List<MaterialPropertySnapshot>(); public bool ShouldSerializeKeywordsComplete() { return KeywordsComplete.HasValue; } } public sealed class MaterialPropertySnapshot { public string Name { get; set; } = ""; public string Kind { get; set; } = ""; public float[] Values { get; set; } = new float[0]; public string TextureId { get; set; } = ""; public float[] TextureScaleOffset { get; set; } = new float[4] { 1f, 1f, 0f, 0f }; } public sealed class GeometrySnapshot { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public Vec3 Position { get; set; } public Vec3 Scale { get; set; } = Vec3.One; public Quat Rotation { get; set; } = Quat.Identity; public Vec3 BoundsCenter { get; set; } public Vec3 BoundsSize { get; set; } public float[] Vertices { get; set; } = new float[0]; public int[] Triangles { get; set; } = new int[0]; public float[] Color { get; set; } = new float[4] { 0.6f, 0.6f, 0.6f, 1f }; public bool IsBoundsProxy { get; set; } public int ShadowCastingMode { get; set; } = -1; public bool ReceiveShadows { get; set; } = true; public string EntityId { get; set; } = ""; public string AnchorId { get; set; } = ""; public string MeshName { get; set; } = ""; public string MeshSourceId { get; set; } = ""; public string PrefabKey { get; set; } = ""; public string PrefabRendererPath { get; set; } = ""; public bool IsInterior { get; set; } public string RoomId { get; set; } = ""; public bool IsMovingSceneRenderer { get; set; } public bool Active { get; set; } = true; public string LodGroupId { get; set; } = ""; public int LodLevel { get; set; } public Vec3 LodCenter { get; set; } public float LodSwitchDistance { get; set; } public float[] Uvs { get; set; } = new float[0]; public float[] Uvs1 { get; set; } = new float[0]; public float[] Uvs2 { get; set; } = new float[0]; public float[] Uvs3 { get; set; } = new float[0]; public float[] Tangents { get; set; } = new float[0]; public float[] Normals { get; set; } = new float[0]; public float[] Instances { get; set; } = new float[0]; public List<int[]> SubmeshTriangles { get; set; } = new List<int[]>(); public List<string> MaterialIds { get; set; } = new List<string>(); public List<string> BonePaths { get; set; } = new List<string>(); public string AttachedBonePath { get; set; } = ""; public string RootBonePath { get; set; } = ""; public float[] BindPoses { get; set; } = new float[0]; public int[] BoneIndices { get; set; } = new int[0]; public float[] BoneWeights { get; set; } = new float[0]; public List<BonePose> RigBones { get; set; } = new List<BonePose>(); public string AnimatorPath { get; set; } = ""; public string AnimatorController { get; set; } = ""; public string AnimatorAvatar { get; set; } = ""; public bool ShouldSerializeShadowCastingMode() { return ShadowCastingMode != -1; } public bool ShouldSerializeReceiveShadows() { return !ReceiveShadows; } public bool ShouldSerializePrefabKey() { return PrefabKey.Length != 0; } public bool ShouldSerializePrefabRendererPath() { return PrefabRendererPath.Length != 0; } public bool ShouldSerializeUvs1() { return Uvs1.Length != 0; } public bool ShouldSerializeUvs2() { return Uvs2.Length != 0; } public bool ShouldSerializeUvs3() { return Uvs3.Length != 0; } public bool ShouldSerializeTangents() { return Tangents.Length != 0; } } public sealed class ReplaySession { public ReplayHeader Header { get; set; } = new ReplayHeader(); public List<ReplayFrame> Frames { get; set; } = new List<ReplayFrame>(); public List<ReplayEvent> Events { get; set; } = new List<ReplayEvent>(); public List<ReplayRecord> Worlds { get; set; } = new List<ReplayRecord>(); [JsonIgnore] public Dictionary<string, List<EntitySnapshot>> DoorPoseReferences { get; set; } = new Dictionary<string, List<EntitySnapshot>>(StringComparer.Ordinal); public List<string> Warnings { get; set; } = new List<string>(); public double Duration { get; set; } public bool IsComplete { get; set; } } public sealed class ReplayReadLimits { public long MaxFileBytes { get; set; } = 1073741824L; public int MaxCompressedRecordBytes { get; set; } = 50331648; public int MaxUncompressedRecordBytes { get; set; } = 100663296; public long MaxTotalUncompressedBytes { get; set; } = 536870912L; public int MaxRecords { get; set; } = 500000; public int MaxFrames { get; set; } = 432000; public int MaxEvents { get; set; } = 100000; public int MaxWorlds { get; set; } = 256; public int MaxEntitiesPerFrame { get; set; } = 4096; public int MaxTotalEntitySnapshots { get; set; } = 1000000; public int MaxBonesPerEntity { get; set; } = 512; public int MaxRenderersPerEntity { get; set; } = 512; public int MaxGeometryPerWorld { get; set; } = 50000; public int MaxParticleEmittersPerWorld { get; set; } = 256; public int MaxLocalFogsPerWorld { get; set; } = 128; public int MaxVerticesPerGeometry { get; set; } = 1000000; public int MaxTriangleIndicesPerGeometry { get; set; } = 6000000; public int MaxInstancesPerGeometry { get; set; } = 8192; public int MaxTexturesPerWorld { get; set; } = 256; public int MaxTextureDimension { get; set; } = 2048; public int MaxTextureBytesPerWorld { get; set; } = 33554432; public int MaxMaterialsPerWorld { get; set; } = 8192; public int MaxMaterialSlotsPerGeometry { get; set; } = 16; public int MaxDictionaryEntries { get; set; } = 1024; public int MaxStringLength { get; set; } = 32768; public double MaxDurationSeconds { get; set; } = 604800.0; } public static class ReplayAlphaCoverage { public static float SelectScale(ReadOnlySpan<int> histogram, int pixelCount, int threshold, float targetCoverage) { if (histogram.Length != 256) { throw new ArgumentException("Expected one count per alpha byte.", "histogram"); } if (pixelCount <= 0) { throw new ArgumentOutOfRangeException("pixelCount"); } if (threshold < 0 || threshold > 255) { throw new ArgumentOutOfRangeException("threshold"); } Span<int> span = stackalloc int[256]; int num = 0; for (int num2 = 255; num2 >= 0; num2--) { num += histogram[num2]; span[num2] = num; } float result = 1f; float num3 = float.MaxValue; for (float num4 = 1f; num4 <= 2.5f; num4 += 0.125f) { float num5 = Math.Abs((float)span[(int)Math.Ceiling((double)threshold / (double)num4)] / (float)pixelCount - targetCoverage); if (num5 < num3) { num3 = num5; result = num4; } } return result; } } public static class ReplayAnimationClock { public static bool TrySample(ReplayEvent state, double time, out float normalized, ReplayEvent? next = null) { normalized = 0f; if (!Number(state, "normalizedTime", out var value) || !Finite(time)) { return false; } double num = Math.Max(0.0, time - state.Time); double num2; if (Number(state, "normalizedRate", out var value2)) { num2 = value + num * value2; } else { double value3; double num3 = (Number(state, "speed", out value3) ? value3 : 1.0); num2 = ((next == null || !(next.Time > state.Time) || !(time <= next.Time) || !Same(state, next, "hash") || !Same(state, next, "clip") || !Same(state, next, "controller") || !Number(next, "normalizedTime", out var value4) || ((!(num3 > 0.0) || !(value4 >= value)) && (!(num3 < 0.0) || !(value4 <= value)) && (num3 != 0.0 || value4 != value))) ? (value + ((Number(state, "duration", out var value5) && value5 > 0.001) ? (num * (double)Math.Sign(num3) / value5) : 0.0)) : (value + (value4 - value) * num / (next.Time - state.Time))); } if (!Finite(num2) || num2 < -3.4028234663852886E+38 || num2 > 3.4028234663852886E+38) { return false; } normalized = (float)num2; return true; } private static bool Same(ReplayEvent a, ReplayEvent b, string key) { if (a.Data.TryGetValue(key, out string value) == b.Data.TryGetValue(key, out string value2)) { return value == value2; } return false; } private static bool Number(ReplayEvent evt, string key, out double value) { value = 0.0; if (evt.Data.TryGetValue(key, out string value2) && double.TryParse(value2, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) { return Finite(value); } return false; } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } public static class ReplayAudioCodec { public const int SampleRate = 22050; public const int MaxSamplesPerBlock = 8192; private static readonly int[] Steps = new int[89] { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; private static readonly int[] IndexChange = new int[16] { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 }; public static byte[] Encode(short[] samples, int count) { if (samples == null || count < 1 || count > samples.Length || count > 8192) { throw new ArgumentOutOfRangeException("count"); } byte[] array = new byte[2 + count / 2]; int num = samples[0]; int num2 = 0; array[0] = (byte)num; array[1] = (byte)(num >> 8); for (int i = 1; i < count; i++) { int num3 = Steps[num2]; int num4 = samples[i] - num; int num5 = ((num4 < 0) ? 8 : 0); if (num4 < 0) { num4 = -num4; } int num6 = num3 >> 3; if (num4 >= num3) { num5 |= 4; num4 -= num3; num6 += num3; } if (num4 >= num3 >> 1) { num5 |= 2; num4 -= num3 >> 1; num6 += num3 >> 1; } if (num4 >= num3 >> 2) { num5 |= 1; num6 += num3 >> 2; } num = Math.Max(-32768, Math.Min(32767, num + (((num5 & 8) != 0) ? (-num6) : num6))); num2 = Math.Max(0, Math.Min(88, num2 + IndexChange[num5])); int num7 = 2 + (i - 1) / 2; if ((i & 1) != 0) { array[num7] = (byte)num5; } else { array[num7] |= (byte)(num5 << 4); } } return array; } public static float[] Decode(byte[] data, int count) { if (data == null || count < 1 || count > 8192 || data.Length != 2 + count / 2) { throw new InvalidDataException("Invalid replay audio block size."); } float[] array = new float[count]; int num = (short)(data[0] | (data[1] << 8)); int num2 = 0; array[0] = (float)num / 32768f; for (int i = 1; i < count; i++) { byte b = data[2 + (i - 1) / 2]; int num3 = (((i & 1) != 0) ? (b & 0xF) : (b >> 4)); int num4 = Steps[num2]; int num5 = num4 >> 3; if ((num3 & 4) != 0) { num5 += num4; } if ((num3 & 2) != 0) { num5 += num4 >> 1; } if ((num3 & 1) != 0) { num5 += num4 >> 2; } num = Math.Max(-32768, Math.Min(32767, num + (((num3 & 8) != 0) ? (-num5) : num5))); num2 = Math.Max(0, Math.Min(88, num2 + IndexChange[num3])); array[i] = (float)num / 32768f; } return array; } } internal static class ReplayBlendShapes { internal sealed class Cache { private Dictionary<string, float[]?>? values; internal float[]? Parse(string text) { if (values != null && values.TryGetValue(text, out float[] value)) { return value; } float[] array = ParseWeights(text); if (values == null) { values = new Dictionary<string, float[]>(StringComparer.Ordinal); } if (values.Count >= 256) { values.Clear(); } values[text] = array; return array; } } internal const string Prefix = "$blendshape:"; internal static Dictionary<string, string>? Interpolate(EntitySnapshot left, EntitySnapshot right, double amount, Cache? cache) { if (!left.Active || !right.Active || left.Kind != right.Kind || amount <= 0.0 || amount >= 1.0) { return null; } Dictionary<string, string> dictionary = null; int num = 0; int num2 = 0; bool flag = false; foreach (KeyValuePair<string, string> item in left.State) { if (!item.Key.StartsWith("$blendshape:", StringComparison.Ordinal)) { continue; } if (++num > 32) { break; } if (!right.State.TryGetValue(item.Key, out string value) || item.Value == value) { continue; } if (!flag) { if (!SameForms(left, right)) { return null; } flag = true; } float[] array = ((cache == null) ? ParseWeights(item.Value) : cache.Parse(item.Value)); float[] array2 = ((cache == null) ? ParseWeights(value) : cache.Parse(value)); if (array == null || array2 == null || array.Length != array2.Length) { continue; } num2 += array.Length; if (num2 > 128) { break; } StringBuilder stringBuilder = new StringBuilder(array.Length * 6); for (int i = 0; i < array.Length; i++) { if (i != 0) { stringBuilder.Append(','); } stringBuilder.Append(((float)((double)array[i] + ((double)array2[i] - (double)array[i]) * amount)).ToString("R", CultureInfo.InvariantCulture)); } if (dictionary == null) { dictionary = new Dictionary<string, string>(left.State, StringComparer.Ordinal); } dictionary[item.Key] = stringBuilder.ToString(); } return dictionary; } private static bool SameForms(EntitySnapshot left, EntitySnapshot right) { if (left.Renderers.Count != right.Renderers.Count) { return false; } for (int i = 0; i < left.Renderers.Count; i++) { RenderPose renderPose = left.Renderers[i]; if (renderPose.Id == right.Renderers[i].Id) { if (renderPose.Active != right.Renderers[i].Active) { return false; } continue; } bool flag = false; foreach (RenderPose renderer in right.Renderers) { if (renderPose.Id == renderer.Id) { if (renderPose.Active != renderer.Active) { return false; } flag = true; break; } } if (!flag) { return false; } } return true; } private static float[]? ParseWeights(string text) { if (text.Length == 0 || text.Length > 4096) { return null; } string[] array = text.Split(new char[1] { ',' }, 129); if (array.Length > 128) { return null; } float[] array2 = new float[array.Length]; for (int i = 0; i < array.Length; i++) { if (!float.TryParse(array[i], NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i]) || float.IsNaN(array2[i]) || float.IsInfinity(array2[i])) { return null; } } return array2; } } public sealed class ReplayCaptureBuffer { private readonly ReplayWriter writer; private readonly int capacity; private readonly long maxBytes; private readonly Queue<(ReplayRecord Record, long Bytes)> pending = new Queue<(ReplayRecord, long)>(); private long pendingBytes; private bool pendingLargeWorld; private bool completing; private bool throttled; private bool drainingBulkTail; public Exception? Error { get; private set; } public int PendingCount => pending.Count; public long PendingBytes => pendingBytes; public bool ShouldPauseCapture { get { long bulkWorldBytes = writer.BulkWorldBytes; if (bulkWorldBytes != 0L) { drainingBulkTail = true; } else if (pending.Count == 0 && writer.QueuedCount <= 12 && writer.QueuedBytes <= 1048576) { drainingBulkTail = false; } if (drainingBulkTail) { long num = pendingBytes + Math.Max(0L, writer.QueuedBytes - bulkWorldBytes); int num2 = pending.Count + writer.QueuedCount; if (num2 >= Math.Max(1, capacity * 3 / 4) || num >= maxBytes * 3 / 4) { throttled = true; } else if (num2 <= capacity / 4 && num <= maxBytes / 4) { throttled = false; } return throttled; } if (pending.Count != 0 || writer.QueuedCount >= 48 || writer.QueuedBytes >= 4194304) { throttled = true; } else if (writer.QueuedCount <= 12 && writer.QueuedBytes <= 1048576) { throttled = false; } return throttled; } } public ReplayCaptureBuffer(ReplayWriter writer, int capacity = 4096, long maxBytes = 8388608L) { if (capacity < 1 || maxBytes < 1024) { throw new ArgumentOutOfRangeException("capacity"); } this.writer = writer ?? throw new ArgumentNullException("writer"); this.capacity = capacity; this.maxBytes = maxBytes; } public bool TryWrite(ReplayRecord record) { if (completing || Error != null || writer.Error != null) { return false; } long num = ReplayRecordMemory.Estimate(record); if (pending.Count == 0 && writer.TryWrite(record, num)) { return true; } bool flag = record.Kind == "world" && num > maxBytes; if (pending.Count >= capacity || pendingLargeWorld || (!flag && pendingBytes != 0L && num > maxBytes - pendingBytes)) { Error = new IOException("Capture backlog exceeded its bounded memory budget; previously accepted recording data is preserved."); return false; } pending.Enqueue((record, num)); pendingBytes += num; if (flag) { pendingLargeWorld = true; } return true; } public void Drain(int maxRecords = 16, double maxMilliseconds = 0.75) { if (completing) { return; } long timestamp = Stopwatch.GetTimestamp(); while (maxRecords-- > 0 && pending.Count != 0) { (ReplayRecord, long) tuple = pending.Peek(); if (writer.TryWrite(tuple.Item1, tuple.Item2)) { (ReplayRecord, long) tuple2 = pending.Dequeue(); pendingBytes -= tuple2.Item2; if (tuple2.Item2 > maxBytes && tuple2.Item1.Kind == "world") { pendingLargeWorld = false; } if ((double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency >= maxMilliseconds) { break; } continue; } break; } } public Task CompleteAsync() { return CompleteAsync(null); } public async Task CompleteAsync(IEnumerable<ReplayRecord>? trailingRecords) { completing = true; try { while (pending.Count != 0) { (ReplayRecord, long) tuple = pending.Peek(); if (!(await writer.WriteAsync(tuple.Item1, tuple.Item2).ConfigureAwait(continueOnCapturedContext: false))) { break; } (ReplayRecord, long) tuple2 = pending.Dequeue(); pendingBytes -= tuple2.Item2; if (tuple2.Item2 > maxBytes && tuple2.Item1.Kind == "world") { pendingLargeWorld = false; } } if (writer.Error != null || trailingRecords == null) { return; } foreach (ReplayRecord trailingRecord in trailingRecords) { if (!(await writer.WriteAsync(trailingRecord).ConfigureAwait(continueOnCapturedContext: false))) { break; } } } finally { pending.Clear(); pendingBytes = 0L; pendingLargeWorld = false; await writer.CompleteAsync().ConfigureAwait(continueOnCapturedContext: false); } } } public sealed class ReplayEnemyLife { public string EntityId { get; set; } = ""; public string Name { get; set; } = ""; public double SpawnTime { get; set; } public Vec3 SpawnPosition { get; set; } public double? DeathTime { get; set; } public Vec3 DeathPosition { get; set; } } public static class ReplayReader { private sealed class PendingRecord { internal ReplayFileIndex.Entry Entry; internal byte[]? Payload; internal ReplayRecord? Record; } private const long MaxSingleFileBytes = 17179869184L; private const long MaxSingleFileExpandedBytes = 25769803776L; public static IReadOnlyList<ReplayEnemyLife> ReadEnemyHistory(ReplayFileIndex index, CancellationToken cancellation = default(CancellationToken)) { FileStream input = new FileStream(index.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536, FileOptions.SequentialScan); BinaryReader reader; try { reader = new BinaryReader(input, Encoding.UTF8, leaveOpen: true); try { if (input.Length != index.FileLength || File.GetLastWriteTimeUtc(index.FilePath).Ticks != index.LastWriteUtcTicks) { throw new InvalidDataException("Replay changed after its index was created."); } return EnemyHistory(Records()); } finally { if (reader != null) { ((IDisposable)reader).Dispose(); } } } finally { if (input != null) { ((IDisposable)input).Dispose(); } } IEnumerable<ReplayRecord> Records() { ReplayReadLimits limits = new ReplayReadLimits(); foreach (ReplayFileIndex.Entry entry in index.Entries) { cancellation.ThrowIfCancellationRequested(); if (!(entry.Kind != "frame") || (!(entry.Kind != "event") && entry.EventCategory.Length == 0)) { input.Position = entry.Offset; if (reader.ReadInt32() != entry.Compressed || reader.ReadInt32() != entry.Expanded) { throw new InvalidDataException("Replay changed after its index was created."); } byte[] array = reader.ReadBytes(entry.Compressed); if (array.Length != entry.Compressed) { throw new InvalidDataException("Truncated replay record."); } ReplayRecord replayRecord = ReplayFormat.Decode(array, entry.Expanded, cancellation); ReplayValidation.Record(replayRecord, limits); yield return replayRecord; } } } } public static IReadOnlyList<ReplayEnemyLife> EnemyHistory(IEnumerable<ReplayRecord> records) { Dictionary<string, ReplayEnemyLife> dictionary = new Dictionary<string, ReplayEnemyLife>(); Dictionary<string, Vec3> dictionary2 = new Dictionary<string, Vec3>(); foreach (ReplayRecord record in records) { if (record.Frame != null) { foreach (EntitySnapshot entity in record.Frame.Entities) { if (entity.Kind != "enemy") { continue; } if (!dictionary.TryGetValue(entity.Id, out var value)) { if (!entity.Active) { continue; } value = (dictionary[entity.Id] = new ReplayEnemyLife { EntityId = entity.Id, Name = entity.Name, SpawnTime = record.Frame.Time, SpawnPosition = entity.Position }); } dictionary2[entity.Id] = entity.Position; if (!value.DeathTime.HasValue && entity.State.TryGetValue("isEnemyDead", out string value2) && string.Equals(value2, "True", StringComparison.OrdinalIgnoreCase)) { value.DeathTime = record.Frame.Time; value.DeathPosition = entity.Position; } } } ReplayEvent replayEvent = record.Event; if (replayEvent != null && replayEvent.Category == "state" && replayEvent.Name == "isEnemyDead" && replayEvent.Data.TryGetValue("to", out string value3) && string.Equals(value3, "True", StringComparison.OrdinalIgnoreCase) && dictionary.TryGetValue(replayEvent.EntityId, out var value4) && (!value4.DeathTime.HasValue || replayEvent.Time < value4.DeathTime)) { value4.DeathTime = replayEvent.Time; value4.DeathPosition = dictionary2[replayEvent.EntityId]; } } return dictionary.Values.OrderBy((ReplayEnemyLife life) => life.SpawnTime).ToArray(); } public static IReadOnlyList<ReplayPlayerDeath> ReadPlayerDeaths(ReplayFileIndex index, CancellationToken cancellation = default(CancellationToken)) { if (index == null) { throw new ArgumentNullException("index"); } List<ReplayPlayerDeath> list = new List<ReplayPlayerDeath>(); ReplayReadLimits limits = new ReplayReadLimits(); FileStream input = new FileStream(index.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536, FileOptions.RandomAccess); BinaryReader reader; try { reader = new BinaryReader(input, Encoding.UTF8, leaveOpen: true); try { if (input.Length != index.FileLength || File.GetLastWriteTimeUtc(index.FilePath).Ticks != index.LastWriteUtcTicks) { throw new InvalidDataException("Replay changed after its index was created."); } List<ReplayFileIndex.Entry> entries = index.Entries; int num = -1; for (int i = 0; i < entries.Count; i++) { ReplayFileIndex.Entry entry = entries[i]; cancellation.ThrowIfCancellationRequested(); if (entry.Kind == "frame") { num = i; } else { if (entry.Kind != "event" || entry.EventCategory != "death") { continue; } ReplayEvent evt = Decode(entry).Event; if (evt?.Category != "state" || evt.Name != "isPlayerDead" || !evt.Data.TryGetValue("to", out string value) || !string.Equals(value, "True", StringComparison.OrdinalIgnoreCase) || num < 0) { continue; } EntitySnapshot entitySnapshot = null; int num2 = 0; int num3 = num; while (num3 >= 0 && num2 < 64) { if (!(entries[num3].Kind != "frame")) { num2++; EntitySnapshot entitySnapshot2 = Decode(entries[num3]).Frame?.Entities.Find((EntitySnapshot entity) => entity.Id == evt.EntityId && entity.Kind == "player"); if (entitySnapshot2 != null) { if (entitySnapshot == null) { entitySnapshot = entitySnapshot2; } if (!entitySnapshot2.State.TryGetValue("isPlayerDead", out string value2) || !string.Equals(value2, "True", StringComparison.OrdinalIgnoreCase)) { entitySnapshot = entitySnapshot2; break; } } } num3--; } if (entitySnapshot != null) { list.Add(new ReplayPlayerDeath { Time = evt.Time, EntityId = evt.EntityId, Name = ((entitySnapshot.Name.Length != 0) ? entitySnapshot.Name : (evt.Data.TryGetValue("entity", out string value3) ? value3 : "")), Position = entitySnapshot.Position }); } } } return list; } finally { if (reader != null) { ((IDisposable)reader).Dispose(); } } } finally { if (input != null) { ((IDisposable)input).Dispose(); } } ReplayRecord Decode(ReplayFileIndex.Entry entry2) { cancellation.ThrowIfCancellationRequested(); input.Position = entry2.Offset; if (reader.ReadInt32() != entry2.Compressed || reader.ReadInt32() != entry2.Expanded) { throw new InvalidDataException("Replay changed after its index was created."); } byte[] array = reader.ReadBytes(entry2.Compressed); if (array.Length != entry2.Compressed) { throw new InvalidDataException("Indexed replay record was truncated."); } ReplayRecord replayRecord = ReplayFormat.Decode(array, entry2.Expanded, cancellation); ReplayValidation.Record(replayRecord, limits); return replayRecord; } } public static ReplayFileIndex IndexSingleFile(string path, CancellationToken cancellationToken = default(CancellationToken), long windowExpandedBytes = 50331648L, Action<double>? progress = null, long initialWindowExpandedBytes = 0L, bool deferPayloadValidation = false) { if (windowExpandedBytes < 1024 || windowExpandedBytes > 377487360) { throw new ArgumentOutOfRangeException("windowExpandedBytes"); } if (initialWindowExpandedBytes < 0 || initialWindowExpandedBytes > windowExpandedBytes || (initialWindowExpandedBytes != 0L && initialWindowExpandedBytes < 1024)) { throw new ArgumentOutOfRangeException("initialWindowExpandedBytes"); } long num = ((initialWindowExpandedBytes == 0L) ? windowExpandedBytes : initialWindowExpandedBytes); ReplayFileIndex replayFileIndex = TryReadSidecar(path, cancellationToken, windowExpandedBytes, num, progress); if (replayFileIndex != null) { return replayFileIndex; } ReplayReadLimits replayReadLimits = new ReplayReadLimits(); ReplayFileIndex index = new ReplayFileIndex { FilePath = Path.GetFullPath(path), LastWriteUtcTicks = File.GetLastWriteTimeUtc(path).Ticks }; List<double> list = new List<double> { 0.0 }; long num2 = 0L; long num3 = 0L; int num4 = 0; double num5 = -1.0; using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536, FileOptions.SequentialScan)) { using BinaryReader binaryReader = new BinaryReader(fileStream, Encoding.UTF8, leaveOpen: true); index.FileLength = fileStream.Length; if (fileStream.Length > 17179869184L || !((ReadOnlySpan<byte>)binaryReader.ReadBytes(8)).SequenceEqual((ReadOnlySpan<byte>)ReplayFormat.Magic)) { throw new InvalidDataException("Invalid or oversized single-file replay."); } while (fileStream.Position < index.FileLength) { cancellationToken.ThrowIfCancellationRequested(); if (index.Entries.Count >= 2000000) { throw new InvalidDataException("Single-file replay record limit exceeded."); } long position = fileStream.Position; if (index.FileLength - position < 8) { break; } int num6 = binaryReader.ReadInt32(); int num7 = binaryReader.ReadInt32(); if (num6 <= 0 || num6 > replayReadLimits.MaxCompressedRecordBytes || num7 <= 0 || num7 > replayReadLimits.MaxUncompressedRecordBytes) { throw new InvalidDataException("Invalid single-file record size at byte " + position + "."); } num3 += num7; if (num3 > 25769803776L) { throw new InvalidDataException("Single-file replay expanded-size limit exceeded."); } if (index.FileLength - fileStream.Position < num6) { break; } ReplayRecord replayRecord; if (deferPayloadValidation) { replayRecord = ReplayFormat.ReadIndexRecord(fileStream, num6, num7, cancellationToken); fileStream.Position = position + 8 + num6; } else { byte[] array = binaryReader.ReadBytes(num6); if (array.Length != num6) { break; } replayRecord = ReplayFormat.Decode(array, num7, cancellationToken); } ReplayValidation.Record(replayRecord, replayReadLimits); if (index.Entries.Count == 0) { if (replayRecord.Kind != "header") { throw new InvalidDataException("Single-file replay has no header."); } index.Header = replayRecord.Header; } else if (replayRecord.Kind == "header") { throw new InvalidDataException("Duplicate replay header."); } ReplayFileIndex.Entry item = new ReplayFileIndex.Entry { Offset = position, Compressed = num6, Expanded = num7, Kind = replayRecord.Kind, Time = replayRecord.Time, CaptureSetId = (replayRecord.World?.CaptureSetId ?? ""), EventCategory = ReplayEventIndex.Category(replayRecord.Event), EventKey = ReplayEventIndex.Key(replayRecord.Event) }; if (replayRecord.Kind == "frame") { if (replayRecord.Time < num5) { throw new InvalidDataException("Frame timestamps are out of order."); } num5 = replayRecord.Time; if (++num4 > 1000000) { throw new InvalidDataException("Single-file frame limit exceeded."); } if (num2 >= ((list.Count == 1) ? num : windowExpandedBytes) && replayRecord.Time > list[list.Count - 1]) { list.Add(replayRecord.Time); num2 = 0L; } } if (replayRecord.Kind == "frame" || replayRecord.Kind == "event") { num2 += num7; } if (replayRecord.Kind == "end") { if (fileStream.Position != index.FileLength) { throw new InvalidDataException("Unexpected data after replay end marker."); } index.IsComplete = true; } index.Duration = Math.Max(index.Duration, replayRecord.Time); index.Entries.Add(item); if ((index.Entries.Count & 0xFF) == 0) { progress?.Invoke(Math.Min(1.0, (double)fileStream.Position / (double)index.FileLength)); } } } if (index.Entries.Count == 0) { throw new InvalidDataException("Single-file replay has no complete header."); } list.RemoveAll((double time) => time >= index.Duration && time > 0.0); List<ReplayFileWindow> list2 = new List<ReplayFileWindow>(); for (int num8 = 0; num8 < list.Count; num8++) { list2.Add(new ReplayFileWindow { Index = index, Number = num8, Start = list[num8], End = ((num8 + 1 < list.Count) ? list[num8 + 1] : index.Duration) }); } index.Windows = list2.AsReadOnly(); TryPersistSidecar(index); progress?.Invoke(1.0); return index; } private static void TryPersistSidecar(ReplayFileIndex index) { if (!index.IsComplete) { return; } string text = Path.ChangeExtension(index.FilePath, ".lci"); string text2 = text + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { using (FileStream output = new FileStream(text2, FileMode.CreateNew, FileAccess.Write, FileShare.None, 65536)) { using BinaryWriter binaryWriter = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true); binaryWriter.Write(ReplayFileIndex.SidecarMagic); foreach (ReplayFileIndex.Entry entry in index.Entries) { byte[] bytes = Encoding.UTF8.GetBytes(entry.CaptureSetId); byte[] bytes2 = Encoding.UTF8.GetBytes(entry.EventKey); if (bytes.Length > 96 || bytes2.Length > 65535) { return; } binaryWriter.Write(entry.Offset); binaryWriter.Write(entry.Compressed); binaryWriter.Write(entry.Expanded); binaryWriter.Write(entry.Time); binaryWriter.Write(ReplayFileIndex.KindCode(entry.Kind)); binaryWriter.Write((byte)bytes.Length); binaryWriter.Write(bytes); binaryWriter.Write(ReplayEventIndex.Code(entry.EventCategory)); binaryWriter.Write((ushort)bytes2.Length); binaryWriter.Write(bytes2); } } if (File.Exists(text)) { File.Replace(text2, text, null, ignoreMetadataErrors: true); } else { File.Move(text2, text); } } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is SecurityException) { } finally { try { if (File.Exists(text2)) { File.Delete(text2); } } catch (IOException) { } catch (UnauthorizedAccessException) { } } } private static ReplayFileIndex? TryReadSidecar(string path, CancellationToken cancellationToken, long windowExpandedBytes, long firstBudget, Action<double>? progress) { string path2 = Path.ChangeExtension(path, ".lci"); if (!File.Exists(path2)) { return null; } try { ReplayFileIndex index = new ReplayFileIndex { FilePath = Path.GetFullPath(path), Header = ReadHeader(path), LastWriteUtcTicks = File.GetLastWriteTimeUtc(path).Ticks }; index.FileLength = new FileInfo(path).Length; if (index.FileLength > 17179869184L) { throw new InvalidDataException("Single-file replay is too large."); } using (FileStream fileStream = new FileStream(path2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536)) { using BinaryReader binaryReader = new BinaryReader(fileStream, Encoding.UTF8, leaveOpen: true); if (fileStream.Length > 268435456 || !((ReadOnlySpan<byte>)binaryReader.ReadBytes(8)).SequenceEqual((ReadOnlySpan<byte>)ReplayFileIndex.SidecarMagic)) { return null; } long num = 8L; long num2 = 0L; double num3 = -1.0; int num4 = 0; while (fileStream.Position < fileStream.Length) { cancellationToken.ThrowIfCancellationRequested(); if (index.Entries.Count >= 2000000 || fileStream.Length - fileStream.Position < 28) { return null; } long num5 = binaryReader.ReadInt64(); int num6 = binaryReader.ReadInt32(); int num7 = binaryReader.ReadInt32(); double num8 = binaryReader.ReadDouble(); string text = ReplayFileIndex.KindName(binaryReader.ReadByte()); byte b = binaryReader.ReadByte(); if (b > 96 || fileStream.Length - fileStream.Position < b + 3) { return null; } string captureSetId = Encoding.UTF8.GetString(binaryReader.ReadBytes(b)); byte b2 = binaryReader.ReadByte(); ushort num9 = binaryReader.ReadUInt16(); if (b2 > 8 || (b2 != 0 && text != "event") || fileStream.Length - fileStream.Position < num9 || (((b2 >= 1 && b2 <= 5) || b2 == 8) && num9 == 0) || ((b2 == 0 || b2 == 6 || b2 == 7) && num9 != 0)) { return null; } string eventKey = Encoding.UTF8.GetString(binaryReader.ReadBytes(num9)); if (num5 != num || num6 <= 0 || num6 > 50331648 || num7 <= 0 || num7 > 100663296 || double.IsNaN(num8) || double.IsInfinity(num8) || num8 < 0.0 || num8 > 604800.0 || num5 > index.FileLength - 8 - num6 || (index.Entries.Count == 0 && text != "header") || (index.Entries.Count != 0 && text == "header")) { return null; } if (text == "frame") { if (num8 < num3 || ++num4 > 1000000) { return null; } num3 = num8; } num = num5 + 8 + num6; num2 += num7; if (num2 > 25769803776L) { return null; } index.Duration = Math.Max(index.Duration, num8); index.Entries.Add(new ReplayFileIndex.Entry { Offset = num5, Compressed = num6, Expanded = num7, Time = num8, Kind = text, CaptureSetId = captureSetId, EventKey = eventKey, EventCategory = ReplayEventIndex.Name(b2) }); if ((index.Entries.Count & 0xFF) == 0) { progress?.Invoke(Math.Min(1.0, (double)fileStream.Position / (double)fileStream.Length)); } } index.IsComplete = index.Entries.Count != 0 && index.Entries[index.Entries.Count - 1].Kind == "end" && num == index.FileLength; if (!index.IsComplete) { return null; } } List<double> list = new List<double> { 0.0 }; long num10 = 0L; foreach (ReplayFileIndex.Entry entry in index.Entries) { if (entry.Kind == "frame" && num10 >= ((list.Count == 1) ? firstBudget : windowExpandedBytes) && entry.Time > list[list.Count - 1]) { list.Add(entry.Time); num10 = 0L; } if (entry.Kind == "frame" || entry.Kind == "event") { num10 += entry.Expanded; } } list.RemoveAll((double time) => time >= index.Duration && time > 0.0); List<ReplayFileWindow> list2 = new List<ReplayFileWindow>(); for (int num11 = 0; num11 < list.Count; num11++) { list2.Add(new ReplayFileWindow { Index = index, Number = num11, Start = list[num11], End = ((num11 + 1 < list.Count) ? list[num11 + 1] : index.Duration) }); } index.Windows = list2.AsReadOnly(); progress?.Invoke(1.0); return index; } catch (Exception ex) when (ex is IOException || ex is InvalidDataException || ex is UnauthorizedAccessException || ex is ArgumentException || ex is EndOfStreamException) { cancellationToken.ThrowIfCancellationRequested(); return null; } } public static ReplaySession ReadWindow(ReplayFileWindow window, CancellationToken cancellationToken = default(CancellationToken), Action<double>? progress = null, bool preloadInitialMap = false) { if (window == null || window.Index == null || window.Number < 0 || window.Number >= window.Index.Windows.Count || window.Index.Windows[window.Number] != window) { throw new ArgumentException("The replay window is not part of its file index.", "window"); } ReplayFileIndex index = window.Index; List<ReplayFileIndex.Entry> entries = index.Entries; HashSet<int> hashSet = new HashSet<int>(); int num = -1; int num2 = -1; for (int i = 0; i < entries.Count; i++) { ReplayFileIndex.Entry entry = entries[i]; if (entry.Time < window.Start) { if (entry.Kind == "frame") { num = i; } if (entry.Kind == "world") { num2 = i; } } else if ((entry.Time < window.End || (window.Number == index.Windows.Count - 1 && entry.Time <= window.End)) && (entry.Kind == "frame" || entry.Kind == "event" || entry.Kind == "world")) { hashSet.Add(i); } } if (num >= 0) { hashSet.Add(num); } double num3 = 0.0; if (num2 >= 0) { string captureSetId = entries[num2].CaptureSetId; num3 = entries[num2].Time; for (int j = 0; j <= num2; j++) { if (entries[j].Kind == "world" && ((captureSetId.Length == 0) ? (j == num2) : (entries[j].CaptureSetId == captureSetId))) { hashSet.Add(j); num3 = Math.Min(num3, entries[j].Time); } } } ReplayReadLimits replayReadLimits = new ReplayReadLimits(); ReplaySession replaySession = new ReplaySession { Header = index.Header, Duration = window.Duration, IsComplete = (index.IsComplete && window.Number == index.Windows.Count - 1) }; replaySession.Warnings.AddRange(index.Header.Warnings); if (!index.IsComplete) { replaySession.Warnings.Add("Recording has no end marker; recovered data may omit its final moments."); } long num4 = 0L; long num5 = 0L; HashSet<string> hashSet2 = null; if (index.TrimInactiveActorState) { hashSet2 = new HashSet<string>(StringComparer.Ordinal); int[] array = (from position in hashSet where entries[position].Kind == "frame" orderby position select position).ToArray(); int num6 = 0; foreach (ReplayRecord item in ReadWindowRecords(index, array, replayReadLimits, cancellationToken)) { item.Time = Math.Max(0.0, item.Time - window.Start); item.Frame.Time = item.Time; num4 += item.Frame.Entities.Count; if (replaySession.Frames.Count >= replayReadLimits.MaxFrames || num4 > replayReadLimits.MaxTotalEntitySnapshots) { throw new InvalidDataException("Replay playback window exceeds entity budget."); } replaySession.Frames.Add(item.Frame); foreach (EntitySnapshot entity in item.Frame.Entities) { hashSet2.Add(entity.Id); } if ((++num6 & 0xF) == 0) { progress?.Invoke(0.5 * (double)num6 / (double)Math.Max(1, array.Length)); } } num5 = ((IEnumerable<int>)array).Sum((Func<int, long>)((int position) => entries[position].Expanded)); hashSet.ExceptWith(array); } Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); for (int num7 = 0; num7 < entries.Count; num7++) { if ((num7 & 0xFF) == 0) { cancellationToken.ThrowIfCancellationRequested(); } if (!(entries[num7].Time < window.Start) || (!(entries[num7].EventCategory != "visual") && !(entries[num7].Time >= num3))) { continue; } foreach (string item2 in ReplayEventIndex.CarryKeys(entries[num7].EventCategory, entries[num7].EventKey, hashSet2)) { if (!dictionary.TryGetValue(item2, out var value) || entries[num7].Time >= entries[value].Time) { if (entries[num7].EventCategory == "animation" && entries[num7].Time >= window.Start - 0.5 && value >= 0 && dictionary.ContainsKey(item2)) { hashSet.Add(value); } dictionary[item2] = num7; } } } foreach (int value2 in dictionary.Values) { hashSet.Add(value2); } long num8 = num5 + ((IEnumerable<int>)hashSet).Sum((Func<int, long>)((int position) => entries[position].Expanded)); if (num8 > replayReadLimits.MaxTotalUncompressedBytes) { throw new InvalidDataException("Replay playback window exceeds memory budget."); } int num9 = 0; foreach (ReplayRecord item3 in ReadWindowRecords(index, hashSet.OrderBy((int result) => result), replayReadLimits, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); double time = item3.Time; item3.Time = ((item3.Kind == "event") ? (item3.Time - window.Start) : Math.Max(0.0, item3.Time - window.Start)); if (item3.Kind == "world") { item3.SourceTime = time; } if (item3.Kind == "frame") { item3.Frame.Time = item3.Time; num4 += item3.Frame.Entities.Count; if (replaySession.Frames.Count >= replayReadLimits.MaxFrames || num4 > replayReadLimits.MaxTotalEntitySnapshots) { throw new InvalidDataException("Replay playback window exceeds entity budget."); } replaySession.Frames.Add(item3.Frame); } else if (item3.Kind == "event") { item3.Event.Time = item3.Time; if (replaySession.Events.Count >= replayReadLimits.MaxEvents) { throw new InvalidDataException("Replay playback window exceeds event budget."); } replaySession.Events.Add(item3.Event); } else if (item3.Kind == "world") { if (replaySession.Worlds.Count >= replayReadLimits.MaxWorlds) { throw new InvalidDataException("Replay playback window exceeds world budget."); } replaySession.Worlds.Add(item3); } if ((++num9 & 0xF) == 0) { progress?.Invoke((hashSet2 == null) ? ((double)num9 / (double)Math.Max(1, hashSet.Count)) : (0.5 + 0.5 * (double)num9 / (double)Math.Max(1, hashSet.Count))); } } if (preloadInitialMap && window.Number == 0) { int num10 = -1; for (int num11 = 0; num11 < entries.Count; num11++) { if (hashSet.Contains(num11) && entries[num11].Kind == "world") { num10 = num11; break; } } if (num10 >= 0) { string initialSet = entries[num10].CaptureSetId; bool flag = replaySession.Worlds.Any((ReplayRecord record) => record.World?.CaptureSetId == initialSet && record.World.Layer == "interior"); bool flag2 = replaySession.Worlds.Any((ReplayRecord record) => record.World?.CaptureSetId != initialSet && record.World?.Layer == "interior"); if (initialSet.Length != 0 && !flag2) { int num12 = 0; long num13 = 0L; List<ReplayRecord> list = new List<ReplayRecord>(); string text = ""; for (int num14 = num10 + 1; num14 < entries.Count; num14++) { cancellationToken.ThrowIfCancellationRequested(); ReplayFileIndex.Entry entry2 = entries[num14]; if (entry2.Kind != "world") { continue; } if (entry2.CaptureSetId.Length == 0) { break; } if (entry2.CaptureSetId != initialSet) { if (text.Length == 0) { text = entry2.CaptureSetId; list.Clear(); } else if (entry2.CaptureSetId != text) { break; } } else if (text.Length != 0) { break; } if (hashSet.Contains(num14) || (entry2.CaptureSetId == initialSet && flag)) { continue; } if (num12 >= 8 || num13 + entry2.Expanded > 402653184 || num8 + num13 + entry2.Expanded > replayReadLimits.MaxTotalUncompressedBytes || replaySession.Worlds.Count + list.Count >= replayReadLimits.MaxWorlds) { break; } ReplayRecord replayRecord = ReadWindowRecords(index, new int[1] { num14 }, replayReadLimits, cancellationToken).Single(); replayRecord.SourceTime = replayRecord.Time; replayRecord.Time = Math.Max(0.0, replayRecord.Time - window.Start); list.Add(replayRecord); num12++; num13 += entry2.Expanded; if (replayRecord.World.Layer == "interior") { replaySession.Worlds.AddRange(list); list.Clear(); if (entry2.CaptureSetId != initialSet) { break; } flag = true; } } } } } foreach (ReplayRecord item4 in (from @group in replaySession.Worlds.Where((ReplayRecord record) => record.World?.Layer == "interior" && record.World.Geometry.Any((GeometrySnapshot geometry) => geometry.IsMovingSceneRenderer && geometry.Name == "DoorMesh")).GroupBy<ReplayRecord, string>((ReplayRecord record) => record.World.CaptureSetId, StringComparer.Ordinal) select @group.First()).Take(2)) { double num15 = ((item4.World.CaptureCompletedAt > 0.0) ? item4.World.CaptureCompletedAt : (item4.SourceTime ?? (item4.Time + window.Start))); ReplayFileIndex.Entry entry3 = null; double num16 = 5.0; foreach (ReplayFileIndex.Entry item5 in entries) { if (!(item5.Kind != "frame")) { double num17 = Math.Abs(item5.Time - num15); if (!(num17 >= num16)) { num16 = num17; entry3 = item5; } } } if (entry3 != null) { int num18 = entries.IndexOf(entry3); ReplayFrame frame = ReadWindowRecords(index, new int[1] { num18 }, replayReadLimits, cancellationToken).Single().Frame; if (frame != null) { StoreDoorPoseReference(replaySession, item4.World.CaptureSetId, frame); } } } replaySession.Worlds = replaySession.Worlds.OrderBy((ReplayRecord record) => record.Time).ToList(); replaySession.Events = replaySession.Events.OrderBy((ReplayEvent record) => record.Time).ToList(); progress?.Invoke(1.0); return replaySession; } private static void StoreDoorPoseReference(ReplaySession session, string captureSetId, ReplayFrame frame) { session.DoorPoseReferences[captureSetId] = (from entity in frame.Entities.Where((EntitySnapshot entity) => entity.Kind == "door").Take(512) select new EntitySnapshot { Id = entity.Id, Kind = entity.Kind, Position = entity.Position, Rotation = entity.Rotation }).ToList(); } public static ReplayHeader ReadHeader(string path, int maxHeaderBytes = 262144) { if (maxHeaderBytes < 256 || maxHeaderBytes > 16777216) { throw new ArgumentOutOfRangeException("maxHeaderBytes"); } using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using BinaryReader binaryReader = new BinaryReader(fileStream, Encoding.UTF8, leaveOpen: true); if (!((ReadOnlySpan<byte>)binaryReader.ReadBytes(ReplayFormat.Magic.Length)).SequenceEqual((ReadOnlySpan<byte>)ReplayFormat.Magic)) { throw new InvalidDataException("Unrecognized or incomplete replay header."); } if (fileStream.Length - fileStream.Position < 8) { throw new InvalidDataException("Replay header length is incomplete."); } int num = binaryReader.ReadInt32(); int num2 = binaryReader.ReadInt32(); if (num <= 0 || num2 <= 0 || num > maxHeaderBytes || num2 > maxHeaderBytes) { throw new InvalidDataException("Replay header exceeds the bounded index read limit."); } if (fileStream.Length - fileStream.Position < num) { throw new InvalidDataException("Replay header is incomplete."); } byte[] array = binaryReader.ReadBytes(num); if (array.Length != num) { throw new InvalidDataException("Replay header was truncated while reading."); } ReplayRecord replayRecord = ReplayFormat.Decode(array, num2); ReplayValidation.Record(replayRecord, new ReplayReadLimits()); if (replayRecord.Kind != "header") { throw new InvalidDataException("The first replay record must be a header."); } return replayRecord.Header; } public static ReplaySession Read(string path, ReplayReadLimits? limits = null, CancellationToken cancellationToken = default(CancellationToken), Action<double>? progress = null) { limits = limits ?? new ReplayReadLimits(); ReplayValidation.Limits(limits); ReplaySession replaySession = new ReplaySession(); cancellationToken.ThrowIfCancellationRequested(); using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 65536, FileOptions.SequentialScan)) { using BinaryReader binaryReader = new BinaryReader(fileStream, Encoding.UTF8, leaveOpen: true); long length = fileStream.Length; if (length > limits.MaxFileBytes) { throw new InvalidDataException("Replay file exceeds configured size limit."); } byte[] array = binaryReader.ReadBytes(ReplayFormat.Magic.Length); if (array.Length != ReplayFormat.Magic.Length) { throw new InvalidDataException("Replay header is incomplete."); } for (int i = 0; i < array.Length; i++) { if (array[i] != ReplayFormat.Magic[i]) { throw new InvalidDataException("Unrecognized replay format."); } } int num = 0; long num2 = 0L; long num3 = 0L; bool flag = false; double num4 = -1.0; while (fileStream.Position < length) { cancellationToken.ThrowIfCancellationRequested(); long position = fileStream.Position; if (length - fileStream.Position < 8) { replaySession.Warnings.Add("Recovered complete prefix: the final record length is truncated."); break; } int num5 = binaryReader.ReadInt32(); int num6 = binaryReader.ReadInt32(); if (num5 <= 0 || num5 > limits.MaxCompressedRecordBytes || num6 <= 0 || num6 > limits.MaxUncompressedRecordBytes) { throw new InvalidDataException("Invalid or excessive record size at byte " + position + "."); } num2 += num6; if (num2 > limits.MaxTotalUncompressedBytes || ++num > limits.MaxRecords) { throw new InvalidDataException("Replay exceeds configured aggregate read limits."); } if (length - fileStream.Position < num5) { replaySession.Warnings.Add("Recovered complete prefix: the final compressed record is truncated."); break; } byte[] array2 = binaryReader.ReadBytes(num5); if (array2.Length != num5) { replaySession.Warnings.Add("Recovered complete prefix: the file was truncated while reading."); break; } if ((num & 0x3F) == 0) { progress?.Invoke(Math.Min(1.0, (double)fileStream.Position / (double)length)); } ReplayRecord replayRecord; try { replayRecord = ReplayFormat.Decode(array2, num6, cancellationToken); ReplayValidation.Record(replayRecord, limits); } catch (InvalidDataException ex) { throw new InvalidDataException("Invalid replay record at byte " + position + ": " + ex.Message, ex); } if (!flag) { if (replayRecord.Kind != "header") { throw new InvalidDataException("The first record must be a header."); } replaySession.Header = replayRecord.Header; replaySession.Warnings.AddRange(replaySession.Header.Warnings); flag = true; continue; } replaySession.Duration = Math.Max(replaySession.Duration, replayRecord.Time); switch (replayRecord.Kind) { case "header": throw new InvalidDataException("Duplicate replay header."); case "frame": if (replayRecord.Time < num4) { throw new InvalidDataException("Frame timestamps are out of order."); } num4 = replayRecord.Time; num3 += replayRecord.Frame.Entities.Count; if (replaySession.Frames.Count >= limits.MaxFrames || num3 > limits.MaxTotalEntitySnapshots) { throw new InvalidDataException("Replay exceeds configured frame/entity limits."); } replaySession.Frames.Add(replayRecord.Frame); break; case "event": if (replaySession.Events.Count >= limits.MaxEvents) { throw new InvalidDataException("Replay exceeds event limit."); } replaySession.Events.Add(replayRecord.Event); break; case "world": if (replaySession.Worlds.Count >= limits.MaxWorlds) { throw new InvalidDataException("Replay exceeds world snapshot limit."); } replaySession.Worlds.Add(replayRecord); break; case "end": if (fileStream.Position != length) { throw new InvalidDataException("Unexpected data after the replay end marker."); } replaySession.IsComplete = true; break; } } if (!flag) { throw new InvalidDataException("No complete replay header was found."); } } if (!replaySession.IsComplete) { replaySession.Warnings.Add("Recording has no end marker; recovered data may omit its final moments."); } replaySession.Events = replaySession.Events.OrderBy((ReplayEvent item) => item.Time).ToList(); replaySession.Worlds = replaySession.Worlds.OrderBy((ReplayRecord item) => item.Time).ToList(); foreach (ReplayRecord item in (from @group in replaySession.Worlds.Where((ReplayRecord record) => record.World?.Layer == "interior" && record.World.Geometry.Any((GeometrySnapshot geometry) => geometry.IsMovingSceneRenderer && geometry.Name == "DoorMesh")).GroupBy<ReplayRecord, string>((ReplayRecord record) => record.World.CaptureSetId, StringComparer.Ordinal) select @group.First()).Take(2)) { ReplayFrame replayFrame = null; double num7 = 5.0; foreach (ReplayFrame frame in replaySession.Frames) { double num8 = ((item.World.CaptureCompletedAt > 0.0) ? item.World.CaptureCompletedAt : item.Time); double num9 = Math.Abs(frame.Time - num8); if (!(num9 >= num7)) { replayFrame = frame; num7 = num9; } } if (replayFrame != null) { StoreDoorPoseReference(replaySession, item.World.CaptureSetId, replayFrame); } } progress?.Invoke(1.0); return replaySession; } public static double ReadDuration(string path, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); if (ReadHeader(path).Metadata.TryGetValue("singleFile", out string value) && value == "true") { return IndexSingleFile(path, cancellationToken, 50331648L, null, 0L, deferPayloadValidation: true).Duration; } ReplayReadLimits replayReadLimits = new ReplayReadLimits(); using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096)) { using BinaryReader binaryReader = new BinaryReader(fileStream, Encoding.UTF8, leaveOpen: true); if (fileStream.Length > replayReadLimits.MaxFileBytes || !((ReadOnlySpan<byte>)binaryReader.ReadBytes(8)).SequenceEqual((ReadOnlySpan<byte>)ReplayFormat.Magic)) { throw new InvalidDataException("Invalid replay while indexing duration."); } long num = -1L; long num2 = 0L; int num3 = 0; int num4 = 0; int num5 = 0; while (fileStream.Length - fileStream.Position >= 8) { cancellationToken.ThrowIfCancellationRequested(); num3 = binaryReader.ReadInt32(); num4 = binaryReader.ReadInt32(); if (num3 <= 0 || num3 > replayReadLimits.MaxCompressedRecordBytes || num4 <= 0 || num4 > replayReadLimits.MaxUncompressedRecordBytes) { throw new InvalidDataException("Invalid replay record size while indexing duration."); } num2 += num4; if (++num5 > replayReadLimits.MaxRecords || num2 > replayReadLimits.MaxTotalUncompressedBytes) { throw new InvalidDataException("Replay index exceeds read limits."); } if (fileStream.Length - fileStream.Position < num3) { num = -1L; break; } num = fileStream.Position; fileStream.Position += num3; } if (num >= 0 && fileStream.Position == fileStream.Length && num4 < 1024 && num3 < 1024) { fileStream.Position = num; ReplayRecord replayRecord = ReplayFormat.Decode(binaryReader.ReadBytes(num3), num4); if (replayRecord.Kind == "end") { ReplayValidation.Record(replayRecord, replayReadLimits); return replayRecord.Time; } } } return Read(path, replayReadLimits, cancellationToken).Duration; } private static IEnumerable<ReplayRecord> ReadWindowRecords(ReplayFileIndex index, IEnumerable<int> positions, ReplayReadLimits limits, CancellationToken cancellation) { List<PendingRecord> batch = new List<PendingRecord>(128); long expandedTotal = 0L; long num = 0L; using FileStream input = new FileStream(index.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536, FileOptions.SequentialScan); using BinaryReader reader = new BinaryReader(input, Encoding.UTF8, leaveOpen: true); bool reuseWorlds = index.ReuseWorldPayloads && index.IsComplete; if (reuseWorlds && (input.Length != index.FileLength || File.GetLastWriteTimeUtc(index.FilePath).Ticks != index.LastWriteUtcTicks)) { throw new InvalidDataException("Replay changed after its index was created."); } foreach (int position in positions) { cancellation.ThrowIfCancellationRequested(); ReplayFileIndex.Entry entry = index.Entries[position]; expandedTotal += entry.Expanded; if (expandedTotal > limits.MaxTotalUncompressedBytes) { throw new InvalidDataException("Replay playback window exceeds memory budget."); } if (batch.Count != 0 && (batch.Count >= 128 || num + entry.Expanded > 100663296)) { DecodeBatch(batch, limits, reuseWorlds, cancellation); foreach (PendingRecord item in batch) { yield return item.Record; } batch.Clear(); num = 0L; } input.Position = entry.Offset; if (reader.ReadInt32() != entry.Compressed || reader.ReadInt32() != entry.Expanded) { throw new InvalidDataException("Replay changed after its index was created."); } PendingRecord pendingRecord = new PendingRecord { Entry = entry }; if (reuseWorlds && entry.Kind == "world" && entry.World != null && entry.World.TryGetTarget(out WorldSnapshot target)) { pendingRecord.Record = new ReplayRecord { Kind = "world", Time = entry.Time, World = target }; } else { pendingRecord.Payload = reader.ReadBytes(entry.Compressed); if (pendingRecord.Payload.Length != entry.Compressed) { throw new InvalidDataException("Indexed replay record was truncated."); } num += entry.Expanded; } batch.Add(pendingRecord); } DecodeBatch(batch, limits, reuseWorlds, cancellation); foreach (PendingRecord item2 in batch) { yield return item2.Record; } } private static void DecodeBatch(List<PendingRecord> batch, ReplayReadLimits limits, bool reuseWorlds, CancellationToken cancellation) { int num = Math.Min(4, Math.Max(1, Environment.ProcessorCount / 2)); if (batch.Count < 2 || num == 1) { for (int i = 0; i < batch.Count; i++) { Decode(i); } return; } try { Parallel.For(0, batch.Count, new ParallelOptions { MaxDegreeOfParallelism = num, CancellationToken = cancellation }, Decode); } catch (AggregateException ex) { cancellation.ThrowIfCancellationRequested(); ExceptionDispatchInfo.Capture(ex.Flatten().InnerExceptions[0]).Throw(); throw; } void Decode(int number) { cancellation.ThrowIfCancellationRequested(); PendingRecord pendingRecord = batch[number]; if (pendingRecord.Record != null) { ReplayValidation.Record(pendingRecord.Record, limits); } else { ReplayRecord replayRecord = ReplayFormat.Decode(pendingRecord.Payload, pendingRecord.Entry.Expanded, cancellation); pendingRecord.Payload = null; ReplayValidation.Record(replayRecord, limits); if (replayRecord.Kind != pendingRecord.Entry.Kind || Math.Abs(replayRecord.Time - pendingRecord.Entry.Time) > 1E-09 || (replayRecord.World?.CaptureSetId ?? "") != pendingRecord.Entry.CaptureSetId || ReplayEventIndex.Category(replayRecord.Event) != pendingRecord.Entry.EventCategory || ReplayEventIndex.Key(replayRecord.Event) != pendingRecord.Entry.EventKey) { throw new InvalidDataException("Replay record disagrees with its index."); } pendingRecord.Record = replayRecord; if (reuseWorlds && replayRecord.World != null) { pendingRecord.Entry.World = new WeakReference<WorldSnapshot>(replayRecord.World); } } } } } internal static class ReplayEventIndex { internal static string Category(ReplayEvent? evt) { if (evt == null) { return ""; } if (evt.Category == "marker" && evt.Name == "bookmark") { return "bookmark"; } if (evt.Category == "visual" && (evt.Name == "renderer" || evt.Name == "spray")) { return "visual"; } if (evt.Category == "animation" && (evt.Name == "state" || evt.Name == "parameters" || evt.Name == "track")) { return "animation"; } if (evt.Category == "item" && evt.Name == "pose") { return "item"; } if (evt.Category == "postfx" && evt.Name == "component") { return "postfx"; } if (evt.Category == "lighting" && evt.Name == "sun") { return "lighting"; } if (evt.Category == "sound" && (evt.Name == "stop" || evt.Data.GetValueOrDefault("loop") == "true")) { return "sound"; } if (evt.Category == "state" && evt.Name == "isPlayerDead" && string.Equals(evt.Data.GetValueOrDefault("to"), "True", StringComparison.OrdinalIgnoreCase)) { return "death"; } return ""; } internal static byte Code(string category) { return category switch { "lighting" => 8, "bookmark" => 7, "death" => 6, "sound" => 5, "postfx" => 4, "item" => 3, "animation" => 2, "visual" => 1, _ => 0, }; } internal static string Name(byte code) { return code switch { 8 => "lighting", 7 => "bookmark", 6 => "death", 5 => "sound", 4 => "postfx", 3 => "item", 2 => "animation", 1 => "visual", _ => "", }; } internal static string Key(ReplayEvent? evt) { string text = Category(evt); if (evt != null && text.Length != 0) { switch (text) { case "death": case "bookmark": break; case "postfx": return evt.PostProcess?.Type ?? ""; default: { List<string> list = new List<string> { text, evt.Name, evt.EntityId }; switch (text) { case "animation": list.Add(evt.AnimationTrack?.AnimatorPath ?? evt.Data.GetValueOrDefault("animatorPath", "")); if (evt.Name == "parameters") { list.AddRange(evt.Data.Keys.Where((string key) => key.Length > 1 && (key[0] == 'b' || key[0] == 'f' || key[0] == 'i') && int.TryParse(key.Substring(1), out var _)).OrderBy<string, string>((string key) => key, StringComparer.Ordinal)); break; } list.Add(evt.AnimationTrack?.Layer.ToString(CultureInfo.InvariantCulture) ?? evt.Data.GetValueOrDefault("layer", "0")); if (evt.Name == "track") { list.Add(evt.AnimationTrack?.StateHash.ToString(CultureInfo.InvariantCulture) ?? ""); list.Add(evt.AnimationTrack?.Clip ?? ""); } break; case "visual": list.Add(evt.Data.GetValueOrDefault("set", "")); list.Add(evt.Data.GetValueOrDefault("asset", "false")); break; case "sound": list = new List<string> { text, "loop", evt.Data.GetValueOrDefault("source", ""), evt.EntityId }; break; } return JsonConvert.SerializeObject((object)list); } } } return ""; } internal static IEnumerable<string> CarryKeys(string category, string key, ISet<string>? actors = null) { if (category == "postfx") { yield return "postfx:" + key; } else { if (key.Length == 0 || category == "death" || category == "bookmark") { yield break; } string[] array = JsonConvert.DeserializeObject<string[]>(key) ?? Array.Empty<string>(); if (actors != null) { string text = (((category == "animation" || category == "item") && array.Length >= 3) ? array[2] : ((category == "sound" && array.Length >= 4) ? array[3] : "")); if (text.Length != 0 && !actors.Contains(text)) { yield break; } } if (array.Length >= 4 && array[0] == "animation" && array[1] == "parameters") { string binding = JsonConvert.SerializeObject((object)array.Take(4)); foreach (string item in array.Skip(4)) { yield return binding + ":" + item; } } else { yield return key; } } } } public sealed class ReplayFileIndex { internal sealed class Entry { internal long Offset; internal int Compressed; internal int Expanded; internal string Kind = ""; internal string CaptureSetId = ""; internal string EventCategory = ""; internal string EventKey = ""; internal double Time; internal WeakReference<WorldSnapshot>? World; } internal static readonly byte[] SidecarMagic = Encoding.ASCII.GetBytes("LCIX0007"); internal readonly List<Entry> Entries = new List<Entry>(); internal long FileLength; internal long LastWriteUtcTicks; public string FilePath { get; internal set; } = ""; public ReplayHeader Header { get; internal set; } = new ReplayHeader(); public IReadOnlyList<ReplayFileWindow> Windows { get; internal set; } = Array.Empty<ReplayFileWindow>(); public double Duration { get; internal set; } public bool IsComplete { get; internal set; } public bool ReuseWorldPayloads { get; set; } public bool TrimInactiveActorState { get; set; } public int FrameCount => Entries.Count((Entry entry) => entry.Kind == "frame"); public int EventCount => Entries.Count((Entry entry) => entry.Kind == "event"); public int WorldCount => Entries.Count((Entry entry) => entry.Kind == "world"); public IReadOnlyList<double> Bookmarks => (from entry in Entries where entry.EventCategory == "bookmark" select entry.Time into time orderby time select time).ToArray(); internal static byte KindCode(string kind) { return kind switch { "header" => 0, "frame" => 1, "event" => 2, "world" => 3, "end" => 4, _ => throw new InvalidDataException("Unknown indexed replay record kind."), }; } internal static string KindName(byte code) { return code switch { 0 => "header", 1 => "frame", 2 => "event", 3 => "world", 4 => "end", _ => throw new InvalidDataException("Unknown replay sidecar record kind."), }; } public long WorldRevisionAt(double time) { long result = -1L; foreach (Entry entry in Entries) { if (entry.Kind == "world" && entry.Time <= time) { result = entry.Offset; } } return result; } } public sealed class ReplayFileWindow { public ReplayFileIndex Index { get; internal set; } public double Start { get; internal set; } public double End { get; internal set; } public int Number { get; internal set; } public double Duration => End - Start; } public sealed class ReplayPlayerDeath { public double Time { get; set; } public string EntityId { get; set; } = ""; public string Name { get; set; } = ""; public Vec3 Position { get; set; } } internal static class ReplayFormat { private sealed class LimitedWriteStream : Stream { private readonly Stream target; private readonly long limit; private long length; public override bool CanRead => false; public override bool CanSeek => false; public override bool CanWrite => true; public override long Length => length; public override long Position { get { return length; } set { throw new NotSupportedException(); } } internal LimitedWriteStream(Stream target, long limit) { this.target = target; this.limit = limit; } public override void Write(byte[] buffer, int offset, int count) { if (count > limit - length) { throw new InvalidDataException("Replay record exceeds size limit."); } target.Write(buffer, offset, count); length += count; } public override void Flush() { target.Flush(); } public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } } private sealed class RecordSliceStream : Stream { private readonly Stream input; private readonly int length; private int position; public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override long Length => length; public override long Position { get { return position; } set { throw new NotSupportedException(); } } internal RecordSliceStream(Stream input, int length) { this.input = input; this.length = length; } public override int Read(byte[] buffer, int offset, int count) { int num = input.Read(buffer, offset, Math.Min(count, length - position)); position += num; return num; } public override void Flush() { } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } } private sealed class ExpandedRecordStream : Stream { private readonly Stream input; private readonly int length; private readonly CancellationToken cancellation; private int position; public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override long Length => length; public override long Position { get { return position; } set { throw new NotSupportedException(); } } internal ExpandedRecordStream(Stream input, int length, CancellationToken cancellation) { this.input = input; this.length = length; this.cancellation = cancellation; } public override int Read(byte[] buffer, int offset, int count) { cancellation.ThrowIfCancellationRequested(); if (count == 0) { return 0; } if (position == length) { VerifyComplete(); return 0; } int num = input.Read(buffer, offset, Math.Min(count, length - position)); if (num == 0) { throw new InvalidDataException("Compressed record is shorter than its declared expanded length."); } position += num; return num; } internal void VerifyComplete() { cancellation.ThrowIfCancellationRequested(); if (position != length) { throw new InvalidDataException("Compressed record is shorter than its declared expanded length."); } if (input.ReadByte() != -1) { throw new InvalidDataException("Compressed record exceeds its declared expanded length."); } } public override void Flush() { } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } } internal static readonly byte[] Magic = Encoding.ASCII.GetBytes("LCREPL01"); private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { TypeNameHandling = (TypeNameHandling)0, MetadataPropertyHandling = (MetadataPropertyHandling)2, DateParseHandling = (DateParseHandling)0, FloatParseHandling = (FloatParseHandling)0, MaxDepth = 64, CheckAdditionalContent = true, NullValueHandling = (NullValueHandling)1, Formatting = (Formatting)0 }; internal static int Write(Stream destination, ReplayRecord record, ReplayReadLimits limits) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown ReplayValidation.Record(record, limits); checked { using MemoryStream memoryStream = new MemoryStream(); int num; using (LimitedWriteStream stream = new LimitedWriteStream(memoryStream, limits.MaxCompressedRecordBytes)) { using GZipStream target = new GZipStream(stream, CompressionLevel.Fastest, leaveOpen: true); using LimitedWriteStream limitedWriteStream = new LimitedWriteStream(target, limits.MaxUncompressedRecordBytes); using (StreamWriter streamWriter = new StreamWriter(limitedWriteStream, Utf8, 16384, leaveOpen: true)) { JsonTextWriter val = new JsonTextWriter((TextWriter)streamWriter) { CloseOutput = false }; try { JsonSerializer.Create(Settings).Serialize((JsonWriter)(object)val, (object)record); } finally { ((IDisposable)val)?.Dispose(); } } num = (int)limitedWriteStream.Length; } using BinaryWriter binaryWriter = new BinaryWriter(destination, Utf8, leaveOpen: true); binaryWriter.Write((int)memoryStream.Length); binaryWriter.Write(num); binaryWriter.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); return num; } } internal static ReplayRecord Decode(byte[] compressed, int expandedLength, CancellationToken cancellation = default(CancellationToken)) { //IL_0093: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown try { using MemoryStream stream = new MemoryStream(compressed, writable: false); using GZipStream input = new GZipStream(stream, CompressionMode.Decompress); using ExpandedRecordStream expandedRecordStream = new ExpandedRecordStream(input, expandedLength, cancellation); using StreamReader streamReader = new StreamReader(expandedRecordStream, Utf8, detectEncodingFromByteOrderMarks: false, 16384); JsonTextReader val = new JsonTextReader((TextReader)streamReader); try { ReplayRecord result = JsonSerializer.Create(Settings).Deserialize<ReplayRecord>((JsonReader)(object)val) ?? throw new InvalidDataException("Null JSON record."); expandedRecordStream.VerifyComplete(); return result; } finally { ((IDisposable)val)?.Dispose(); } } catch (JsonException ex) { JsonException innerException = ex; throw new InvalidDataException("Invalid replay JSON.", (Exception?)(object)innerException); } catch (DecoderFallbackException innerException2) { throw new InvalidDataException("Invalid replay UTF-8.", innerException2); } } internal static ReplayRecord ReadIndexRecord(Stream input, int compressedLength, int expandedLength, CancellationToken cancellation) { //IL_0336: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Invalid comparison between Unknown and I4 //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Invalid comparison between Unknown and I4 //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Invalid comparison between Unknown and I4 //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Invalid comparison between Unknown and I4 //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Invalid comparison between Unknown and I4 //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Invalid comparison between Unknown and I4 //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Invalid comparison between Unknown and I4 try { using RecordSliceStream stream = new RecordSliceStream(input, compressedLength); using GZipStream input2 = new GZipStream(stream, CompressionMode.Decompress); using ExpandedRecordStream expandedRecordStream = new ExpandedRecordStream(input2, expandedLength, cancellation); using StreamReader streamReader = new StreamReader(expandedRecordStream, Utf8, detectEncodingFromByteOrderMarks: false, 1024); JsonTextReader val = new JsonTextReader((TextReader)streamReader) { MaxDepth = 64, DateParseHandling = (DateParseHandling)0, FloatParseHandling = (FloatParseHandling)0 }; try { JsonSerializer val2 = JsonSerializer.Create(Settings); val2.CheckAdditionalContent = false; ReplayRecord replayRecord = new ReplayRecord(); bool flag = false; if (!((JsonReader)val).Read() || (int)((JsonReader)val).TokenType != 1) { throw new InvalidDataException("Invalid replay record envelope."); } while (((JsonReader)val).Read() && (int)((JsonReader)val).TokenType != 13) { cancellation.ThrowIfCancellationRequested(); if ((int)((JsonReader)val).TokenType != 4 || ((JsonReader)val).Depth != 1) { throw new InvalidDataException("Invalid replay record envelope."); } string text = (string)((JsonReader)val).Value; if (!((JsonReader)val).Read()) { throw new InvalidDataException("Incomplete replay record envelope."); } switch (text) { case "Kind": replayRecord.Kind = val2.Deserialize<string>((JsonReader)(object)val); break; case "Time"