using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using API; using Agents; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using GameData; using Globals; using HarmonyLib; using Il2CppInterop.Runtime.Attributes; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using Player; using ReplayRecorder.API; using ReplayRecorder.API.Attributes; using ReplayRecorder.BepInEx; using ReplayRecorder.Core; using ReplayRecorder.Exceptions; using ReplayRecorder.Net; using ReplayRecorder.SNetUtils; using ReplayRecorder.Snapshot; using ReplayRecorder.Snapshot.Exceptions; using ReplayRecorder.Snapshot.Types; using ReplayRecorder.Steam; using SNetwork; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.Analytics; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ReplayRecorder")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5b9d5e33eabc67c260bd509eec8a67e29e1b75c6")] [assembly: AssemblyProduct("ReplayRecorder")] [assembly: AssemblyTitle("ReplayRecorder")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace API { [HarmonyPatch(typeof(GameDataInit))] internal class GameDataInit_Patches { [HarmonyPatch("Initialize")] [HarmonyWrapSafe] [HarmonyPostfix] public static void Initialize_Postfix() { Analytics.enabled = false; } } internal static class APILogger { private static readonly ManualLogSource logger; static APILogger() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown logger = new ManualLogSource("Rand-API"); Logger.Sources.Add((ILogSource)(object)logger); } private static string Format(string module, object msg) { return $"[{module}]: {msg}"; } public static void Info(string module, object data) { logger.LogMessage((object)Format(module, data)); } public static void Verbose(string module, object data) { } public static void Log(object data) { logger.LogDebug((object)Format("ReplayRecorder", data)); } public static void Debug(object data) { if (ConfigManager.Debug) { Log(data); } } public static void Warn(object data) { logger.LogWarning((object)Format("ReplayRecorder", data)); } public static void Error(object data) { logger.LogError((object)Format("ReplayRecorder", data)); } } } namespace ReplayRecorder { internal class BufferPool { private Stack pool = new Stack(); private readonly object lockObj = new object(); private int size; private int inUse; public int Size => size; public int InUse => inUse; public void Shrink(int count) { while (pool.Count > count) { pool.Pop(); size--; } } public ByteBuffer Checkout() { lock (lockObj) { inUse++; if (pool.Count == 0) { size++; return new ByteBuffer(); } ByteBuffer byteBuffer = pool.Pop(); byteBuffer.Clear(); return byteBuffer; } } public void Release(ByteBuffer buffer) { lock (lockObj) { if (inUse != 0) { inUse--; } else { size++; } pool.Push(buffer); } } } public class MainThread : MonoBehaviour { private static MainThread? _instance; private ConcurrentQueue queue = new ConcurrentQueue(); private static MainThread Instance { get { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_instance == (Object)null) { _instance = new GameObject().AddComponent(); } return _instance; } } public static void Run(Action action) { Instance.queue.Enqueue(action); } private void Update() { Action result; while (queue.TryDequeue(out result)) { result?.Invoke(); } } } public static class RNet { private class EventInfo { public string name; public Action>? onReceive; public byte[] header; public EventInfo(string name, byte[] header, Action>? onReceive = null) { this.name = name; this.header = header; this.onReceive = onReceive; } } private static Dictionary eventMap; [MethodImpl(MethodImplOptions.NoInlining)] internal static void Init() { } static RNet() { eventMap = new Dictionary(); ReplayRecorder.SNetUtils.SNetUtils.OnReceive = (Action, ulong>)Delegate.Combine(ReplayRecorder.SNetUtils.SNetUtils.OnReceive, new Action, ulong>(Receive)); } [HideFromIl2Cpp] private static void Receive(ArraySegment packet, ulong from) { int index = 0; if (BitHelper.ReadByte(packet, ref index) == 1) { string text = BitHelper.ReadString(packet, ref index); if (!eventMap.ContainsKey(text)) { APILogger.Warn("Received unknown RNet event, '" + text + "'."); return; } int count = BitHelper.ReadInt(packet, ref index); eventMap[text].onReceive?.Invoke(from, new ArraySegment(packet.Array, packet.Offset + index, count)); } } [HideFromIl2Cpp] public static void Register(string eventName, Action> callback) { if (!eventMap.ContainsKey(eventName)) { ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes((byte)1, byteBuffer); BitHelper.WriteBytes(eventName, byteBuffer); byte[] array = new byte[byteBuffer.Count]; Array.Copy(byteBuffer.Array.Array, byteBuffer.Array.Offset, array, 0, byteBuffer.Count); eventMap.Add(eventName, new EventInfo(eventName, array, callback)); } else { EventInfo eventInfo = eventMap[eventName]; eventInfo.onReceive = (Action>)Delegate.Combine(eventInfo.onReceive, callback); } } [HideFromIl2Cpp] public static void Trigger(string eventName, ByteBuffer bytes) { Trigger(eventName, bytes.Array); } [HideFromIl2Cpp] public static void Trigger(string eventName, ArraySegment bytes) { if (!eventMap.ContainsKey(eventName)) { throw new Exception("Event '" + eventName + "' does not exist."); } EventInfo eventInfo = eventMap[eventName]; byte[] array = new byte[eventInfo.header.Length + 4 + bytes.Count]; Array.Copy(eventInfo.header, array, eventInfo.header.Length); int index = eventInfo.header.Length; BitHelper.WriteBytes(bytes, (ArraySegment)array, ref index); ReplayRecorder.SNetUtils.SNetUtils.SendBytes(array, ReplayPlayerManager.playersWithReplayMod); } } internal class TCPServer : IDisposable { public delegate void OnAccept(EndPoint endpoint); public delegate void OnReceive(ArraySegment buffer, EndPoint endpoint); public delegate void OnDisconnect(EndPoint endpoint); public delegate void OnClose(); private class Connection : IDisposable { public enum State { waiting, reading } public Socket socket; public readonly EndPoint remoteEP; public byte[] recvBuffer; public byte[] sendBuffer; public byte[] messageBuffer; public SemaphoreSlim semaphoreSend = new SemaphoreSlim(1); public State state; public int messageSize; public int bytesWritten; public Connection(Socket socket, int bufferSize) { this.socket = socket; remoteEP = socket.RemoteEndPoint; messageBuffer = new byte[bufferSize]; recvBuffer = new byte[bufferSize]; sendBuffer = new byte[bufferSize]; } public void Dispose() { semaphoreSend.Dispose(); socket.Dispose(); } } private readonly int bufferSize; private Socket? socket; public OnAccept? onAccept; public OnReceive? onReceive; public OnDisconnect? onDisconnect; public OnClose? onClose; private ConcurrentDictionary acceptedConnections = new ConcurrentDictionary(); public ICollection Connections => acceptedConnections.Keys; public TCPServer(int bufferSize = 8192) { if (bufferSize < 4) { throw new ArgumentException("Buffer size cannot be smaller than a message header [sizeof(int)]."); } this.bufferSize = bufferSize; } private void Open() { if (socket != null) { Dispose(); } socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, optionValue: true); } public EndPoint Bind(EndPoint remoteEP, int backlog = 5) { Open(); socket.Bind(remoteEP); socket.Listen(backlog); Listen(); return socket.LocalEndPoint; } private async Task Listen() { if (socket == null) { return; } Socket incoming = await socket.AcceptAsync().ConfigureAwait(continueOnCapturedContext: false); EndPoint remoteEndPoint = incoming.RemoteEndPoint; if (remoteEndPoint != null) { Connection connection = new Connection(incoming, bufferSize); acceptedConnections.AddOrUpdate(remoteEndPoint, connection, delegate(EndPoint key, Connection old) { incoming.Dispose(); return old; }); onAccept?.Invoke(remoteEndPoint); ListenTo(connection); } else { incoming.Dispose(); } Listen(); } private async Task ListenTo(Connection connection) { try { int num = await connection.socket.ReceiveAsync(connection.recvBuffer, SocketFlags.None).ConfigureAwait(continueOnCapturedContext: false); if (num > 0) { int num2 = num; int index = 0; do { switch (connection.state) { case Connection.State.waiting: connection.messageSize = BitHelper.ReadInt(connection.recvBuffer, ref index); connection.bytesWritten = 0; if (connection.messageSize > 0) { connection.state = Connection.State.reading; } break; case Connection.State.reading: { int num3 = num2; if (connection.bytesWritten + num2 > connection.messageSize) { num3 = connection.messageSize - connection.bytesWritten; } Array.Copy(connection.recvBuffer, index, connection.messageBuffer, connection.bytesWritten, num3); connection.bytesWritten += num3; index += num3; if (connection.bytesWritten == connection.messageSize) { connection.state = Connection.State.waiting; onReceive?.Invoke(new ArraySegment(connection.messageBuffer, 0, connection.messageSize), connection.remoteEP); } break; } } num2 = num - index; } while (num2 > 0); ListenTo(connection); } else { Dispose(connection); onDisconnect?.Invoke(connection.remoteEP); } } catch (ObjectDisposedException) { Dispose(connection); onDisconnect?.Invoke(connection.remoteEP); } } private void Dispose(Connection connection) { acceptedConnections.Remove(connection.socket.RemoteEndPoint, out var _); connection.Dispose(); } public async Task Send(ArraySegment data) { List list = new List(); foreach (EndPoint key in acceptedConnections.Keys) { list.Add(SendTo(data, key)); } await Task.WhenAll(list).ConfigureAwait(continueOnCapturedContext: false); } public async Task RawSendTo(ArraySegment data, EndPoint remoteEP) { if (!acceptedConnections.TryGetValue(remoteEP, out Connection connection)) { return; } await connection.semaphoreSend.WaitAsync().ConfigureAwait(continueOnCapturedContext: false); try { int num = await connection.socket.SendAsync(data, SocketFlags.None).ConfigureAwait(continueOnCapturedContext: false); while (num < data.Count) { int num2 = num; num = num2 + await connection.socket.SendAsync(new ArraySegment(data.Array, data.Offset + num, data.Count - num), SocketFlags.None).ConfigureAwait(continueOnCapturedContext: false); } } catch (SocketException) { } finally { connection.semaphoreSend.Release(); } } public async Task SendTo(ArraySegment data, EndPoint remoteEP) { if (!acceptedConnections.TryGetValue(remoteEP, out Connection connection)) { return; } await connection.semaphoreSend.WaitAsync().ConfigureAwait(continueOnCapturedContext: false); try { if (data.Count <= int.MaxValue) { int size = 4 + data.Count; int num; for (num = connection.sendBuffer.Length; num < size; num *= 2) { } if (num > connection.sendBuffer.Length) { connection.sendBuffer = new byte[num]; } int index = 0; BitHelper.WriteBytes(data.Count, (ArraySegment)connection.sendBuffer, ref index); Array.Copy(data.Array, data.Offset, connection.sendBuffer, index, data.Count); int num2 = await connection.socket.SendAsync(new ArraySegment(connection.sendBuffer, 0, size), SocketFlags.None).ConfigureAwait(continueOnCapturedContext: false); while (num2 < size) { int num3 = num2; num2 = num3 + await connection.socket.SendAsync(new ArraySegment(connection.sendBuffer, num2, size - num2), SocketFlags.None).ConfigureAwait(continueOnCapturedContext: false); } } } catch (SocketException) { } finally { connection.semaphoreSend.Release(); } } public void Disconnect() { Dispose(); } public void DisconnectClients() { foreach (Connection value in acceptedConnections.Values) { value.Dispose(); } acceptedConnections.Clear(); } public void Dispose() { if (socket != null) { DisconnectClients(); socket.Dispose(); socket = null; onClose?.Invoke(); } } } [HarmonyPatch] internal class GameEventManager { private static bool initialized; [HarmonyPatch(typeof(SteamManager), "PostSetup")] [HarmonyPrefix] private static void SteamSetup() { if (!initialized) { initialized = true; Replay.OnPluginLoad?.Invoke(); } } [HarmonyPatch(typeof(ElevatorRide), "StartElevatorRide")] [HarmonyPostfix] private static void StartElevatorRide() { APILogger.Debug("Entered elevator!"); SnapshotManager.OnElevatorStart(); Replay.OnExpeditionStart?.Invoke(); } [HarmonyPatch(typeof(RundownManager), "EndGameSession")] [HarmonyPrefix] private static void EndGameSession() { APILogger.Debug("Level ended!"); SnapshotManager.OnExpeditionEnd(); } [HarmonyPatch(typeof(SNet_SessionHub), "LeaveHub")] [HarmonyPrefix] private static void LeaveHub() { APILogger.Debug("Level ended!"); SnapshotManager.OnExpeditionEnd(); } [HarmonyPatch(typeof(GS_ReadyToStopElevatorRide), "Enter")] [HarmonyPostfix] private static void StopElevatorRide() { APILogger.Debug("Stop elevator!"); Replay.OnElevatorStop?.Invoke(); } [HarmonyPatch(typeof(PUI_LocalPlayerStatus), "UpdateBPM")] [HarmonyWrapSafe] [HarmonyPostfix] public static void Initialize_Postfix(PUI_LocalPlayerStatus __instance) { if (ConfigManager.PerformanceDebug) { SnapshotInstance instance = SnapshotManager.GetInstance(); TextMeshPro pulseText = __instance.m_pulseText; ((TMP_Text)pulseText).text = ((TMP_Text)pulseText).text + $" | ({instance.pool.InUse}/{instance.pool.Size}) {Mathf.RoundToInt(instance.tickTime)}({Mathf.RoundToInt(instance.waitForWrite)})ms"; } } } public static class Raudy { public static long Now => ((DateTimeOffset)DateTime.Now).ToUnixTimeMilliseconds(); } public class BitHelperBufferTooLarge : Exception { public BitHelperBufferTooLarge(string message) : base(message) { } } public class ByteBuffer { internal ArraySegment _array = new byte[1024]; internal int count; internal ArraySegment Array => new ArraySegment(_array.Array, _array.Offset, count); internal byte this[int index] { get { return _array[index]; } set { _array[index] = value; } } public int Count => count; public ByteBuffer() { _array = new byte[1024]; } public ByteBuffer(ArraySegment array) { _array = array; } public void Clear() { count = 0; } internal void Copy(ByteBuffer other) { _array = new byte[other.count]; System.Array.Copy(other._array.Array, other._array.Offset, _array.Array, _array.Offset, other.count); } internal void Reserve(int size, bool increment = false) { if (_array.Count - count < size) { byte[] array = new byte[Mathf.Max(_array.Count * 2, count + size)]; System.Array.Copy(_array.Array, _array.Offset, array, 0, _array.Count); _array = array; } if (increment) { count += size; } } internal async Task AsyncFlush(FileStream fs) { if (ConfigManager.DebugTicks) { APILogger.Debug($"Async Flushed snapshot: {count} bytes."); } await fs.WriteAsync(Array).ConfigureAwait(continueOnCapturedContext: false); } internal void Shrink() { _array = new byte[1024]; GC.Collect(); } } public interface BufferWriteable { void Write(ByteBuffer buffer); } public static class BitHelper { public const int SizeOfHalf = 2; public const int SizeOfVector3 = 12; public const int SizeOfQuaternion = 13; public const int SizeOfHalfVector3 = 6; public const int SizeOfHalfQuaternion = 7; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint RotateLeft(uint value, int offset) { return (value << offset) | (value >> 32 - offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint RotateRight(uint value, int offset) { return (value >> offset) | (value << 32 - offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static long ReverseEndianness(long value) { return (long)ReverseEndianness((ulong)value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ReverseEndianness(int value) { return (int)ReverseEndianness((uint)value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static short ReverseEndianness(short value) { return (short)ReverseEndianness((ushort)value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort ReverseEndianness(ushort value) { return (ushort)((value >> 8) + (value << 8)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint ReverseEndianness(uint value) { return RotateRight(value & 0xFF00FFu, 8) + RotateLeft(value & 0xFF00FF00u, 8); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong ReverseEndianness(ulong value) { return ((ulong)ReverseEndianness((uint)value) << 32) + ReverseEndianness((uint)(value >> 32)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static void _WriteBytes(byte* source, int size, ArraySegment destination, ref int index) { int num = 0; while (num < size) { destination[index++] = source[num++]; } } public static void WriteBytes(byte value, ArraySegment destination, ref int index) { destination[index++] = value; } public static void WriteBytes(bool value, ArraySegment destination, ref int index) { WriteBytes(value ? ((byte)1) : ((byte)0), destination, ref index); } public unsafe static void WriteBytes(ulong value, ArraySegment destination, ref int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, destination, ref index); } public unsafe static void WriteBytes(uint value, ArraySegment destination, ref int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, destination, ref index); } public unsafe static void WriteBytes(ushort value, ArraySegment destination, ref int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, destination, ref index); } public unsafe static void WriteBytes(long value, ArraySegment destination, ref int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, destination, ref index); } public unsafe static void WriteBytes(int value, ArraySegment destination, ref int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, destination, ref index); } public unsafe static void WriteBytes(short value, ArraySegment destination, ref int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, destination, ref index); } public unsafe static void WriteBytes(float value, ArraySegment destination, ref int index) { int value2 = *(int*)(&value); if (!BitConverter.IsLittleEndian) { value2 = ReverseEndianness(value2); } _WriteBytes((byte*)(&value2), 4, destination, ref index); } public static void WriteBytes(string value, ArraySegment destination, ref int index) { byte[] bytes = Encoding.UTF8.GetBytes(value); if (bytes.Length > 65535) { throw new BitHelperBufferTooLarge($"String value is too large, byte length must be smaller than {65535}."); } WriteBytes((ushort)bytes.Length, destination, ref index); Array.Copy(bytes, 0, destination.Array, destination.Offset + index, bytes.Length); index += bytes.Length; } public static int SizeOfString(string value) { return 2 + Encoding.UTF8.GetBytes(value).Length; } public static void WriteBytes(ArraySegment buffer, ArraySegment destination, ref int index) { WriteBytes(buffer.Count, destination, ref index); Array.Copy(buffer.Array, buffer.Offset, destination.Array, destination.Offset + index, buffer.Count); index += buffer.Count; } public static void WriteHalf(float value, ArraySegment destination, ref int index) { WriteBytes(FloatToHalf(value), destination, ref index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static void _WriteBytes(byte* source, int size, ArraySegment destination, int index) { int num = 0; while (num < size) { destination[index++] = source[num++]; } } public static void WriteBytes(byte value, ArraySegment destination, int index) { destination[index++] = value; } public static void WriteBytes(bool value, ArraySegment destination, int index) { WriteBytes(value ? ((byte)1) : ((byte)0), destination, index); } public unsafe static void WriteBytes(ulong value, ArraySegment destination, int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, destination, index); } public unsafe static void WriteBytes(uint value, ArraySegment destination, int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, destination, index); } public unsafe static void WriteBytes(ushort value, ArraySegment destination, int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, destination, index); } public unsafe static void WriteBytes(long value, ArraySegment destination, int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, destination, index); } public unsafe static void WriteBytes(int value, ArraySegment destination, int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, destination, index); } public unsafe static void WriteBytes(short value, ArraySegment destination, int index) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, destination, index); } public unsafe static void WriteBytes(float value, ArraySegment destination, int index) { int value2 = *(int*)(&value); if (!BitConverter.IsLittleEndian) { value2 = ReverseEndianness(value2); } _WriteBytes((byte*)(&value2), 4, destination, index); } public static void WriteBytes(string value, ArraySegment destination, int index) { byte[] bytes = Encoding.UTF8.GetBytes(value); if (bytes.Length > 65535) { throw new BitHelperBufferTooLarge($"String value is too large, byte length must be smaller than {65535}."); } WriteBytes((ushort)bytes.Length, destination, index); Array.Copy(bytes, 0, destination.Array, destination.Offset + index, bytes.Length); index += bytes.Length; } public static void WriteBytes(ArraySegment buffer, ArraySegment destination, int index) { WriteBytes(buffer.Count, destination, index); Array.Copy(buffer.Array, buffer.Offset, destination.Array, destination.Offset + index, buffer.Count); index += buffer.Count; } public static void WriteHalf(float value, ArraySegment destination, int index) { WriteBytes(FloatToHalf(value), destination, index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static void _WriteBytes(byte* bytes, int size, ByteBuffer buffer) { buffer.Reserve(size); for (int i = 0; i < size; i++) { buffer[buffer.count++] = bytes[i]; } } public static void WriteBytes(byte value, ByteBuffer buffer) { buffer.Reserve(1); buffer[buffer.count++] = value; } public static void WriteBytes(bool value, ByteBuffer buffer) { WriteBytes(value ? ((byte)1) : ((byte)0), buffer); } public unsafe static void WriteBytes(ulong value, ByteBuffer buffer) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, buffer); } public unsafe static void WriteBytes(uint value, ByteBuffer buffer) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, buffer); } public unsafe static void WriteBytes(ushort value, ByteBuffer buffer) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, buffer); } public unsafe static void WriteBytes(long value, ByteBuffer buffer) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, buffer); } public unsafe static void WriteBytes(int value, ByteBuffer buffer) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, buffer); } public unsafe static void WriteBytes(short value, ByteBuffer buffer) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, buffer); } public unsafe static void WriteBytes(float value, ByteBuffer buffer) { int value2 = *(int*)(&value); if (!BitConverter.IsLittleEndian) { value2 = ReverseEndianness(value2); } _WriteBytes((byte*)(&value2), 4, buffer); } public static void WriteBytes(string value, ByteBuffer buffer) { byte[] bytes = Encoding.UTF8.GetBytes(value); if (value.Length > 65535) { throw new BitHelperBufferTooLarge($"String value is too large, length must be smaller than {65535}."); } WriteBytes((ushort)bytes.Length, buffer); buffer.Reserve(bytes.Length); Array.Copy(bytes, 0, buffer._array.Array, buffer._array.Offset + buffer.count, bytes.Length); buffer.count += bytes.Length; } public static void WriteBytes(ArraySegment bytes, ByteBuffer buffer, bool includeCount = true) { if (includeCount) { WriteBytes(bytes.Count, buffer); } buffer.Reserve(bytes.Count); Array.Copy(bytes.Array, bytes.Offset, buffer._array.Array, buffer._array.Offset + buffer.count, bytes.Count); buffer.count += bytes.Count; } public static void WriteBytes(BufferWriteable writeable, ByteBuffer buffer) { writeable.Write(buffer); } public static void WriteHalf(float value, ByteBuffer buffer) { WriteBytes(FloatToHalf(value), buffer); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static void _WriteBytes(byte* bytes, int size, FileStream fs) { for (int i = 0; i < size; i++) { fs.WriteByte(bytes[i]); } } public static void WriteBytes(byte value, FileStream fs) { fs.WriteByte(value); } public static void WriteBytes(bool value, FileStream fs) { WriteBytes(value ? ((byte)1) : ((byte)0), fs); } public unsafe static void WriteBytes(ulong value, FileStream fs) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, fs); } public unsafe static void WriteBytes(uint value, FileStream fs) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, fs); } public unsafe static void WriteBytes(ushort value, FileStream fs) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, fs); } public unsafe static void WriteBytes(long value, FileStream fs) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 8, fs); } public unsafe static void WriteBytes(int value, FileStream fs) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 4, fs); } public unsafe static void WriteBytes(short value, FileStream fs) { if (!BitConverter.IsLittleEndian) { value = ReverseEndianness(value); } _WriteBytes((byte*)(&value), 2, fs); } public unsafe static void WriteBytes(float value, FileStream fs) { int value2 = *(int*)(&value); if (!BitConverter.IsLittleEndian) { value2 = ReverseEndianness(value2); } _WriteBytes((byte*)(&value2), 4, fs); } public static void WriteBytes(string value, FileStream fs) { byte[] bytes = Encoding.UTF8.GetBytes(value); if (value.Length > 65535) { throw new BitHelperBufferTooLarge($"String value is too large, length must be smaller than {65535}."); } WriteBytes((ushort)bytes.Length, fs); fs.Write(bytes); } public static void WriteBytes(byte[] buffer, FileStream fs) { WriteBytes(buffer.Length, fs); fs.Write(buffer); } public static void WriteHalf(float value, FileStream fs) { WriteBytes(FloatToHalf(value), fs); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static uint AsUInt(float x) { return *(uint*)(&x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static float AsFloat(uint x) { return *(float*)(&x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float HalfToFloat(ushort x) { int num = (x & 0x7C00) >> 10; int num2 = (x & 0x3FF) << 13; int num3 = (int)(AsUInt(num2) >> 23); return AsFloat((uint)(((x & 0x8000) << 16) | (Convert.ToInt32(num != 0) * ((num + 112 << 23) | num2)) | ((Convert.ToInt32(num == 0) & Convert.ToInt32(num2 != 0)) * ((num3 - 37 << 23) | ((num2 << 150 - num3) & 0x7FE000))))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort FloatToHalf(float x) { uint num = AsUInt(x) + 4096; uint num2 = (num & 0x7F800000) >> 23; uint num3 = num & 0x7FFFFFu; return (ushort)(((num & 0x80000000u) >> 16) | (Convert.ToInt32(num2 > 112) * (((num2 - 112 << 10) & 0x7C00) | (num3 >> 13))) | ((Convert.ToInt32(num2 < 113) & Convert.ToInt32(num2 > 101)) * ((8384512 + num3 >> (int)(125 - num2)) + 1 >> 1)) | (Convert.ToUInt32(num2 > 143) * 32767)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Quantize(float x) { return HalfToFloat(FloatToHalf(x)); } public static byte ReadByte(ArraySegment source, ref int index) { return source[index++]; } public static bool ReadBool(ArraySegment source, ref int index) { return ReadByte(source, ref index) != 0; } public unsafe static ulong ReadULong(ArraySegment source, ref int index) { fixed (byte* ptr = source.Array) { byte* num = ptr + source.Offset + index; index += 8; ulong num2 = *(ulong*)num; if (!BitConverter.IsLittleEndian) { num2 = ReverseEndianness(num2); } return num2; } } public unsafe static long ReadLong(ArraySegment source, ref int index) { fixed (byte* ptr = source.Array) { byte* num = ptr + source.Offset + index; index += 8; long num2 = *(long*)num; if (!BitConverter.IsLittleEndian) { num2 = ReverseEndianness(num2); } return num2; } } public unsafe static uint ReadUInt(ArraySegment source, ref int index) { fixed (byte* ptr = source.Array) { byte* num = ptr + source.Offset + index; index += 4; uint num2 = *(uint*)num; if (!BitConverter.IsLittleEndian) { num2 = ReverseEndianness(num2); } return num2; } } public unsafe static int ReadInt(ArraySegment source, ref int index) { fixed (byte* ptr = source.Array) { byte* num = ptr + source.Offset + index; index += 4; int num2 = *(int*)num; if (!BitConverter.IsLittleEndian) { num2 = ReverseEndianness(num2); } return num2; } } public unsafe static ushort ReadUShort(ArraySegment source, ref int index) { fixed (byte* ptr = source.Array) { byte* num = ptr + source.Offset + index; index += 2; ushort num2 = *(ushort*)num; if (!BitConverter.IsLittleEndian) { num2 = ReverseEndianness(num2); } return num2; } } public unsafe static short ReadShort(ArraySegment source, ref int index) { fixed (byte* ptr = source.Array) { byte* num = ptr + source.Offset + index; index += 2; short num2 = *(short*)num; if (!BitConverter.IsLittleEndian) { num2 = ReverseEndianness(num2); } return num2; } } public static float ReadHalf(ArraySegment source, ref int index) { return HalfToFloat(ReadUShort(source, ref index)); } public unsafe static float ReadFloat(ArraySegment source, ref int index) { fixed (byte* ptr = source.Array) { byte* ptr2 = ptr + source.Offset + index; index += 4; if (!BitConverter.IsLittleEndian) { int num = ReverseEndianness(*(int*)ptr2); return *(float*)(&num); } return *(float*)ptr2; } } public static string ReadString(ArraySegment source, ref int index) { int num = ReadUShort(source, ref index); string @string = Encoding.UTF8.GetString(source.Array, source.Offset + index, num); index += num; return @string; } public static string ReadString(ArraySegment source, int length, ref int index) { string @string = Encoding.UTF8.GetString(source.Array, source.Offset + index, length); index += length; return @string; } public static void WriteBytes(Vector3 value, byte[] destination, ref int index) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) WriteBytes(value.x, (ArraySegment)destination, ref index); WriteBytes(value.y, (ArraySegment)destination, ref index); WriteBytes(value.z, (ArraySegment)destination, ref index); } public static void WriteBytes(Quaternion value, byte[] destination, ref int index) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0168: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0154: 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) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) value = ((Quaternion)(ref value)).normalized; float num = value.x; byte b = 0; if (value.y > num) { num = value.y; b = 1; } if (value.z > num) { num = value.z; b = 2; } if (value.w > num) { num = value.w; b = 3; } WriteBytes(b, (ArraySegment)destination, ref index); switch (b) { case 0: if (value.x >= 0f) { WriteBytes(value.y, (ArraySegment)destination, ref index); WriteBytes(value.z, (ArraySegment)destination, ref index); WriteBytes(value.w, (ArraySegment)destination, ref index); } else { WriteBytes(0f - value.y, (ArraySegment)destination, ref index); WriteBytes(0f - value.z, (ArraySegment)destination, ref index); WriteBytes(0f - value.w, (ArraySegment)destination, ref index); } break; case 1: if (value.y >= 0f) { WriteBytes(value.x, (ArraySegment)destination, ref index); WriteBytes(value.z, (ArraySegment)destination, ref index); WriteBytes(value.w, (ArraySegment)destination, ref index); } else { WriteBytes(0f - value.x, (ArraySegment)destination, ref index); WriteBytes(0f - value.z, (ArraySegment)destination, ref index); WriteBytes(0f - value.w, (ArraySegment)destination, ref index); } break; case 2: if (value.z >= 0f) { WriteBytes(value.x, (ArraySegment)destination, ref index); WriteBytes(value.y, (ArraySegment)destination, ref index); WriteBytes(value.w, (ArraySegment)destination, ref index); } else { WriteBytes(0f - value.x, (ArraySegment)destination, ref index); WriteBytes(0f - value.y, (ArraySegment)destination, ref index); WriteBytes(0f - value.w, (ArraySegment)destination, ref index); } break; case 3: if (value.w >= 0f) { WriteBytes(value.x, (ArraySegment)destination, ref index); WriteBytes(value.y, (ArraySegment)destination, ref index); WriteBytes(value.z, (ArraySegment)destination, ref index); } else { WriteBytes(0f - value.x, (ArraySegment)destination, ref index); WriteBytes(0f - value.y, (ArraySegment)destination, ref index); WriteBytes(0f - value.z, (ArraySegment)destination, ref index); } break; } } public static void WriteBytes(Vector3 value, ByteBuffer buffer) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) WriteBytes(value.x, buffer); WriteBytes(value.y, buffer); WriteBytes(value.z, buffer); } public static void WriteBytes(Quaternion value, ByteBuffer buffer) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) value = ((Quaternion)(ref value)).normalized; float num = value.x; byte b = 0; if (value.y > num) { num = value.y; b = 1; } if (value.z > num) { num = value.z; b = 2; } if (value.w > num) { num = value.w; b = 3; } WriteBytes(b, buffer); switch (b) { case 0: if (value.x >= 0f) { WriteBytes(value.y, buffer); WriteBytes(value.z, buffer); WriteBytes(value.w, buffer); } else { WriteBytes(0f - value.y, buffer); WriteBytes(0f - value.z, buffer); WriteBytes(0f - value.w, buffer); } break; case 1: if (value.y >= 0f) { WriteBytes(value.x, buffer); WriteBytes(value.z, buffer); WriteBytes(value.w, buffer); } else { WriteBytes(0f - value.x, buffer); WriteBytes(0f - value.z, buffer); WriteBytes(0f - value.w, buffer); } break; case 2: if (value.z >= 0f) { WriteBytes(value.x, buffer); WriteBytes(value.y, buffer); WriteBytes(value.w, buffer); } else { WriteBytes(0f - value.x, buffer); WriteBytes(0f - value.y, buffer); WriteBytes(0f - value.w, buffer); } break; case 3: if (value.w >= 0f) { WriteBytes(value.x, buffer); WriteBytes(value.y, buffer); WriteBytes(value.z, buffer); } else { WriteBytes(0f - value.x, buffer); WriteBytes(0f - value.y, buffer); WriteBytes(0f - value.z, buffer); } break; } } public static void WriteBytes(Vector3 value, FileStream fs) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) WriteBytes(value.x, fs); WriteBytes(value.y, fs); WriteBytes(value.z, fs); } public static void WriteBytes(Quaternion value, FileStream fs) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) value = ((Quaternion)(ref value)).normalized; float num = value.x; byte b = 0; if (value.y > num) { num = value.y; b = 1; } if (value.z > num) { num = value.z; b = 2; } if (value.w > num) { num = value.w; b = 3; } WriteBytes(b, fs); switch (b) { case 0: if (value.x >= 0f) { WriteBytes(value.y, fs); WriteBytes(value.z, fs); WriteBytes(value.w, fs); } else { WriteBytes(0f - value.y, fs); WriteBytes(0f - value.z, fs); WriteBytes(0f - value.w, fs); } break; case 1: if (value.y >= 0f) { WriteBytes(value.x, fs); WriteBytes(value.z, fs); WriteBytes(value.w, fs); } else { WriteBytes(0f - value.x, fs); WriteBytes(0f - value.z, fs); WriteBytes(0f - value.w, fs); } break; case 2: if (value.z >= 0f) { WriteBytes(value.x, fs); WriteBytes(value.y, fs); WriteBytes(value.w, fs); } else { WriteBytes(0f - value.x, fs); WriteBytes(0f - value.y, fs); WriteBytes(0f - value.w, fs); } break; case 3: if (value.w >= 0f) { WriteBytes(value.x, fs); WriteBytes(value.y, fs); WriteBytes(value.z, fs); } else { WriteBytes(0f - value.x, fs); WriteBytes(0f - value.y, fs); WriteBytes(0f - value.z, fs); } break; } } public static Vector3 ReadVector3(ArraySegment source, ref int index) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector3(ReadFloat(source, ref index), ReadFloat(source, ref index), ReadFloat(source, ref index)); } public static Quaternion ReadQuaternion(ArraySegment source, ref int index) { //IL_0124: Unknown result type (might be due to invalid IL or missing references) byte b = ReadByte(source, ref index); float num = 0f; float num2 = 0f; float num3 = 0f; float num4 = 0f; switch (b) { case 0: num2 = ReadFloat(source, ref index); num3 = ReadFloat(source, ref index); num4 = ReadFloat(source, ref index); num = Mathf.Sqrt(Mathf.Clamp01(1f - num2 * num2 - num3 * num3 - num4 * num4)); break; case 1: num = ReadFloat(source, ref index); num3 = ReadFloat(source, ref index); num4 = ReadFloat(source, ref index); num2 = Mathf.Sqrt(Mathf.Clamp01(1f - num * num - num3 * num3 - num4 * num4)); break; case 2: num = ReadFloat(source, ref index); num2 = ReadFloat(source, ref index); num4 = ReadFloat(source, ref index); num3 = Mathf.Sqrt(Mathf.Clamp01(1f - num * num - num2 * num2 - num4 * num4)); break; case 3: num = ReadFloat(source, ref index); num2 = ReadFloat(source, ref index); num3 = ReadFloat(source, ref index); num4 = Mathf.Sqrt(Mathf.Clamp01(1f - num * num - num2 * num2 - num3 * num3)); break; } return new Quaternion(num, num2, num3, num4); } public static void WriteHalf(Vector3 value, ArraySegment destination, ref int index) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) WriteHalf(value.x, destination, ref index); WriteHalf(value.y, destination, ref index); WriteHalf(value.z, destination, ref index); } public static void WriteHalf(Quaternion value, ArraySegment destination, ref int index) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: 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_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) value = ((Quaternion)(ref value)).normalized; float num = value.x; byte b = 0; if (value.y > num) { num = value.y; b = 1; } if (value.z > num) { num = value.z; b = 2; } if (value.w > num) { num = value.w; b = 3; } WriteBytes(b, destination, ref index); switch (b) { case 0: if (value.x >= 0f) { WriteHalf(value.y, destination, ref index); WriteHalf(value.z, destination, ref index); WriteHalf(value.w, destination, ref index); } else { WriteHalf(0f - value.y, destination, ref index); WriteHalf(0f - value.z, destination, ref index); WriteHalf(0f - value.w, destination, ref index); } break; case 1: if (value.y >= 0f) { WriteHalf(value.x, destination, ref index); WriteHalf(value.z, destination, ref index); WriteHalf(value.w, destination, ref index); } else { WriteHalf(0f - value.x, destination, ref index); WriteHalf(0f - value.z, destination, ref index); WriteHalf(0f - value.w, destination, ref index); } break; case 2: if (value.z >= 0f) { WriteHalf(value.x, destination, ref index); WriteHalf(value.y, destination, ref index); WriteHalf(value.w, destination, ref index); } else { WriteHalf(0f - value.x, destination, ref index); WriteHalf(0f - value.y, destination, ref index); WriteHalf(0f - value.w, destination, ref index); } break; case 3: if (value.w >= 0f) { WriteHalf(value.x, destination, ref index); WriteHalf(value.y, destination, ref index); WriteHalf(value.z, destination, ref index); } else { WriteHalf(0f - value.x, destination, ref index); WriteHalf(0f - value.y, destination, ref index); WriteHalf(0f - value.z, destination, ref index); } break; } } public static void WriteHalf(Vector3 value, ByteBuffer buffer) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) WriteHalf(value.x, buffer); WriteHalf(value.y, buffer); WriteHalf(value.z, buffer); } public static void WriteHalf(Quaternion value, ByteBuffer buffer) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) value = ((Quaternion)(ref value)).normalized; float num = value.x; byte b = 0; if (value.y > num) { num = value.y; b = 1; } if (value.z > num) { num = value.z; b = 2; } if (value.w > num) { num = value.w; b = 3; } WriteBytes(b, buffer); switch (b) { case 0: if (value.x >= 0f) { WriteHalf(value.y, buffer); WriteHalf(value.z, buffer); WriteHalf(value.w, buffer); } else { WriteHalf(0f - value.y, buffer); WriteHalf(0f - value.z, buffer); WriteHalf(0f - value.w, buffer); } break; case 1: if (value.y >= 0f) { WriteHalf(value.x, buffer); WriteHalf(value.z, buffer); WriteHalf(value.w, buffer); } else { WriteHalf(0f - value.x, buffer); WriteHalf(0f - value.z, buffer); WriteHalf(0f - value.w, buffer); } break; case 2: if (value.z >= 0f) { WriteHalf(value.x, buffer); WriteHalf(value.y, buffer); WriteHalf(value.w, buffer); } else { WriteHalf(0f - value.x, buffer); WriteHalf(0f - value.y, buffer); WriteHalf(0f - value.w, buffer); } break; case 3: if (value.w >= 0f) { WriteHalf(value.x, buffer); WriteHalf(value.y, buffer); WriteHalf(value.z, buffer); } else { WriteHalf(0f - value.x, buffer); WriteHalf(0f - value.y, buffer); WriteHalf(0f - value.z, buffer); } break; } } public static void WriteHalf(Vector3 value, FileStream fs) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) WriteHalf(value.x, fs); WriteHalf(value.y, fs); WriteHalf(value.z, fs); } public static void WriteHalf(Quaternion value, FileStream fs) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) value = ((Quaternion)(ref value)).normalized; float num = value.x; byte b = 0; if (value.y > num) { num = value.y; b = 1; } if (value.z > num) { num = value.z; b = 2; } if (value.w > num) { num = value.w; b = 3; } WriteBytes(b, fs); switch (b) { case 0: if (value.x >= 0f) { WriteHalf(value.y, fs); WriteHalf(value.z, fs); WriteHalf(value.w, fs); } else { WriteHalf(0f - value.y, fs); WriteHalf(0f - value.z, fs); WriteHalf(0f - value.w, fs); } break; case 1: if (value.y >= 0f) { WriteHalf(value.x, fs); WriteHalf(value.z, fs); WriteHalf(value.w, fs); } else { WriteHalf(0f - value.x, fs); WriteHalf(0f - value.z, fs); WriteHalf(0f - value.w, fs); } break; case 2: if (value.z >= 0f) { WriteHalf(value.x, fs); WriteHalf(value.y, fs); WriteHalf(value.w, fs); } else { WriteHalf(0f - value.x, fs); WriteHalf(0f - value.y, fs); WriteHalf(0f - value.w, fs); } break; case 3: if (value.w >= 0f) { WriteHalf(value.x, fs); WriteHalf(value.y, fs); WriteHalf(value.z, fs); } else { WriteHalf(0f - value.x, fs); WriteHalf(0f - value.y, fs); WriteHalf(0f - value.z, fs); } break; } } public static Vector3 ReadHalfVector3(ArraySegment source, ref int index) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector3(ReadHalf(source, ref index), ReadHalf(source, ref index), ReadHalf(source, ref index)); } public static Quaternion ReadHalfQuaternion(ArraySegment source, ref int index) { //IL_0124: Unknown result type (might be due to invalid IL or missing references) byte b = ReadByte(source, ref index); float num = 0f; float num2 = 0f; float num3 = 0f; float num4 = 0f; switch (b) { case 0: num2 = ReadHalf(source, ref index); num3 = ReadHalf(source, ref index); num4 = ReadHalf(source, ref index); num = Mathf.Sqrt(Mathf.Clamp01(1f - num2 * num2 - num3 * num3 - num4 * num4)); break; case 1: num = ReadHalf(source, ref index); num3 = ReadHalf(source, ref index); num4 = ReadHalf(source, ref index); num2 = Mathf.Sqrt(Mathf.Clamp01(1f - num * num - num3 * num3 - num4 * num4)); break; case 2: num = ReadHalf(source, ref index); num2 = ReadHalf(source, ref index); num4 = ReadHalf(source, ref index); num3 = Mathf.Sqrt(Mathf.Clamp01(1f - num * num - num2 * num2 - num4 * num4)); break; case 3: num = ReadHalf(source, ref index); num2 = ReadHalf(source, ref index); num3 = ReadHalf(source, ref index); num4 = Mathf.Sqrt(Mathf.Clamp01(1f - num * num - num2 * num2 - num3 * num3)); break; } return new Quaternion(num, num2, num3, num4); } } public static class Replay { public static Action? OnExpeditionEnd; public static Action? OnExpeditionStart; public static Action? OnElevatorStop; public static Action? OnGameplayStart; public static Action? OnHeaderCompletion; public static Action? OnTick; public static Action? OnPluginLoad; public static Dictionary?> EventHooks = new Dictionary>(); public static Dictionary?> SpawnHooks = new Dictionary>(); public static Dictionary?> DespawnHooks = new Dictionary>(); public static Dictionary?> DynamicHooks = new Dictionary>(); public static Dictionary?> DirtyDynamicHooks = new Dictionary>(); public static float tickRate => SnapshotManager.GetInstance().tickRate; public static bool Ready => SnapshotManager.Ready; public static bool Active => SnapshotManager.Active; private static Delegate CreateCompatibleDelegate(MethodInfo methodInfo, Type delegateType) { ParameterInfo[] parameters2 = methodInfo.GetParameters(); ParameterInfo[] parameters3 = delegateType.GetMethod("Invoke").GetParameters(); ParameterExpression[] parameters = parameters3.Select((ParameterInfo p) => Expression.Parameter(p.ParameterType, p.Name)).ToArray(); UnaryExpression[] array = parameters2.Select((ParameterInfo mp, int i) => Expression.Convert(parameters[i], mp.ParameterType)).ToArray(); Expression[] arguments = array; MethodCallExpression body = Expression.Call(null, methodInfo, arguments); return Expression.Lambda(delegateType, body, parameters).Compile(); } private static void RegisterMethods(Type t) { foreach (MethodInfo item in from m in t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null || m.GetCustomAttribute() != null select m) { if (item.IsStatic) { try { string value = "ReplayOnExpeditionEnd"; ReplayHook customAttribute = item.GetCustomAttribute(); ReplaySpawnHook customAttribute2 = item.GetCustomAttribute(); ReplayDespawnHook customAttribute3 = item.GetCustomAttribute(); if (customAttribute != null) { value = "ReplayHook"; if (customAttribute.type.IsAssignableTo(typeof(ReplayEvent))) { Action action = (Action)CreateCompatibleDelegate(item, typeof(Action)); if (!EventHooks.ContainsKey(customAttribute.type)) { EventHooks.Add(customAttribute.type, action); } else { Dictionary> eventHooks = EventHooks; Type type = customAttribute.type; eventHooks[type] = (Action)Delegate.Combine(eventHooks[type], action); } } else if (customAttribute.type.IsAssignableTo(typeof(ReplayDynamic))) { Action action2 = (Action)CreateCompatibleDelegate(item, typeof(Action)); if (customAttribute.triggerOnlyWhenDirty) { if (!DirtyDynamicHooks.ContainsKey(customAttribute.type)) { DirtyDynamicHooks.Add(customAttribute.type, action2); } else { Dictionary> dirtyDynamicHooks = DirtyDynamicHooks; Type type = customAttribute.type; dirtyDynamicHooks[type] = (Action)Delegate.Combine(dirtyDynamicHooks[type], action2); } } else if (!DynamicHooks.ContainsKey(customAttribute.type)) { DynamicHooks.Add(customAttribute.type, action2); } else { Dictionary> dirtyDynamicHooks = DynamicHooks; Type type = customAttribute.type; dirtyDynamicHooks[type] = (Action)Delegate.Combine(dirtyDynamicHooks[type], action2); } } else { APILogger.Error($"Failed to register method '{item}': Type '{customAttribute.type}' is not a valid ReplayDynamic / ReplayEvent."); } } else if (customAttribute2 != null) { value = "ReplaySpawnHook"; if (customAttribute2.type.IsAssignableTo(typeof(ReplayDynamic))) { Action action3 = (Action)CreateCompatibleDelegate(item, typeof(Action)); if (!SpawnHooks.ContainsKey(customAttribute2.type)) { SpawnHooks.Add(customAttribute2.type, action3); } else { Dictionary> dirtyDynamicHooks = SpawnHooks; Type type = customAttribute2.type; dirtyDynamicHooks[type] = (Action)Delegate.Combine(dirtyDynamicHooks[type], action3); } } else { APILogger.Error($"Failed to register method '{item}': Type '{customAttribute2.type}' is not a valid ReplayDynamic."); } } else if (customAttribute3 != null) { value = "ReplayDespawnHook"; if (customAttribute3.type.IsAssignableTo(typeof(ReplayDynamic))) { Action action4 = (Action)CreateCompatibleDelegate(item, typeof(Action)); if (!DespawnHooks.ContainsKey(customAttribute3.type)) { DespawnHooks.Add(customAttribute3.type, action4); } else { Dictionary> dirtyDynamicHooks = DespawnHooks; Type type = customAttribute3.type; dirtyDynamicHooks[type] = (Action)Delegate.Combine(dirtyDynamicHooks[type], action4); } } else { APILogger.Error($"Failed to register method '{item}': Type '{customAttribute3.type}' is not a valid ReplayDynamic."); } } else if (item.GetCustomAttribute() != null) { value = "ReplayInit"; OnExpeditionStart = (Action)Delegate.Combine(OnExpeditionStart, (Action)item.CreateDelegate(typeof(Action))); } else if (item.GetCustomAttribute() != null) { value = "ReplayOnHeaderCompletion"; OnHeaderCompletion = (Action)Delegate.Combine(OnHeaderCompletion, (Action)item.CreateDelegate(typeof(Action))); } else if (item.GetCustomAttribute() != null) { value = "ReplayOnGameplayStart"; OnGameplayStart = (Action)Delegate.Combine(OnGameplayStart, (Action)item.CreateDelegate(typeof(Action))); } else if (item.GetCustomAttribute() != null) { value = "ReplayTick"; OnTick = (Action)Delegate.Combine(OnTick, (Action)item.CreateDelegate(typeof(Action))); } else if (item.GetCustomAttribute() != null) { value = "ReplayOnElevatorStop"; OnElevatorStop = (Action)Delegate.Combine(OnElevatorStop, (Action)item.CreateDelegate(typeof(Action))); } else if (item.GetCustomAttribute() != null) { value = "ReplayOnPlayerSpawn"; ReplayPlayerManager.OnPlayerSpawn = (Action)Delegate.Combine(ReplayPlayerManager.OnPlayerSpawn, (Action)CreateCompatibleDelegate(item, typeof(Action))); } else if (item.GetCustomAttribute() != null) { value = "ReplayOnPlayerDespawn"; ReplayPlayerManager.OnPlayerDespawn = (Action)Delegate.Combine(ReplayPlayerManager.OnPlayerDespawn, (Action)CreateCompatibleDelegate(item, typeof(Action))); } else if (item.GetCustomAttribute() != null) { value = "ReplayPluginLoad"; OnPluginLoad = (Action)Delegate.Combine(OnPluginLoad, (Action)item.CreateDelegate(typeof(Action))); } else { OnExpeditionEnd = (Action)Delegate.Combine(OnExpeditionEnd, (Action)item.CreateDelegate(typeof(Action))); } APILogger.Debug($"Registered {value}: '{t.FullName}.{item.Name}'"); } catch (Exception value2) { APILogger.Error($"Failed to register method '{item}': {value2}"); } } else { APILogger.Error($"Replay attributes can only be applied to static methods. '{item}' is not static."); } } } public static void RegisterAll() { Assembly obj = new StackTrace()?.GetFrame(1)?.GetMethod()?.ReflectedType?.Assembly; if (obj == null) { throw new Exception("Unable to find assembly."); } CollectionExtensions.Do((IEnumerable)AccessTools.GetTypesFromAssembly(obj), (Action)delegate(Type t) { if (t.GetCustomAttribute() != null) { RegisterType(t); } RegisterMethods(t); }); } public static void RegisterAll(Type type) { Type[] nestedTypes = type.GetNestedTypes(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type2 in nestedTypes) { if (type.GetCustomAttribute() != null) { RegisterType(type2); } RegisterMethods(type2); RegisterAll(type2); } } public static void RegisterType(Type type) { ReplayData customAttribute = type.GetCustomAttribute(); if (customAttribute == null) { throw new ReplayTypeNotCompatible($"Type '{type}' is not a valid ReplayData type."); } SnapshotManager.types.RegisterType(customAttribute, type); } [HideFromIl2Cpp] public static void Configure(int tickRate = 1, int max = int.MaxValue) where T : ReplayDynamic { SnapshotManager.GetInstance().Configure(tickRate, max); } [HideFromIl2Cpp] public static bool Trigger(ReplayEvent e) { return SnapshotManager.GetInstance().Trigger(e); } [HideFromIl2Cpp] public static void Trigger(ReplayHeader header) { SnapshotManager.GetInstance().Trigger(header); } [HideFromIl2Cpp] public static bool Has(ReplayDynamic dynamic) { return SnapshotManager.GetInstance().Has(dynamic); } [HideFromIl2Cpp] public static bool Has(int id) where T : ReplayDynamic { return SnapshotManager.GetInstance().Has(typeof(T), id); } [HideFromIl2Cpp] public static T Get(int id) where T : ReplayDynamic { return (T)SnapshotManager.GetInstance().Get(typeof(T), id); } [HideFromIl2Cpp] public static bool TryGet(int id, [NotNullWhen(true)] out T dynamic) where T : ReplayDynamic { if (Has(id)) { dynamic = Get(id); return true; } dynamic = null; return false; } [HideFromIl2Cpp] public static void Clear() where T : ReplayDynamic { SnapshotManager.GetInstance().Clear(typeof(T)); } [HideFromIl2Cpp] public static void Spawn(ReplayDynamic dynamic, bool errorOnDuplicate = true) { SnapshotManager.GetInstance().Spawn(dynamic, errorOnDuplicate); } [HideFromIl2Cpp] public static void Despawn(ReplayDynamic dynamic, bool errorOnNotFound = true) { SnapshotManager.GetInstance().Despawn(dynamic, errorOnNotFound); } [HideFromIl2Cpp] public static bool TryDespawn(int id) where T : ReplayDynamic { if (Has(id)) { Despawn(Get(id)); return true; } return false; } } public class ConcurrentHashSet : ConcurrentDictionary where T : notnull { private const byte DummyByte = 0; public bool Contains(T item) { return ContainsKey(item); } public bool Add(T item) { return TryAdd(item, 0); } public bool Remove(T item) { byte value; return TryRemove(item, out value); } } public static class Utils { public const BindingFlags AnyBindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; public static string RemoveHTMLTags(string content) { return Regex.Replace(content, "<[^>]*>", ""); } public static string RemoveInvalidCharacters(string content, char replace = '_', bool isFullPath = true) { if (string.IsNullOrEmpty(content)) { return content; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); int num = content.IndexOfAny(invalidFileNameChars); if (num >= 0) { StringBuilder stringBuilder = new StringBuilder(content); while (num >= 0) { if (!isFullPath || (stringBuilder[num] != ':' && stringBuilder[num] != '\\' && stringBuilder[num] != '/')) { stringBuilder[num] = replace; } num = content.IndexOfAny(invalidFileNameChars, num + 1); } return stringBuilder.ToString(); } return content; } } } namespace ReplayRecorder.Steam { internal class rSteamClient : IDisposable { public delegate void OnAccept(rSteamClient connection); public delegate void OnReceive(ArraySegment buffer, rSteamClient connection); public delegate void OnClose(rSteamClient connection); public delegate void OnFail(rSteamClient connection); public OnAccept? onAccept; public OnReceive? onReceive; public OnClose? onClose; public OnFail? onFail; internal static HashSet localClients = new HashSet(); private bool running = true; private Task? receiveTask; private HSteamNetConnection connection; private SteamNetworkingIdentity identity; private Callback cb_OnConnectionStatusChanged; private bool connected; private readonly string debugName; private ConcurrentQueue resendQueue = new ConcurrentQueue(); private List resendQueueBuffer = new List(); public rSteamClient(ulong steamid, int virtualPort, SteamNetworkingConfigValue_t[]? options = null, string debugName = "Client") { //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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) this.debugName = debugName; identity = default(SteamNetworkingIdentity); ((SteamNetworkingIdentity)(ref identity)).SetSteamID(new CSteamID(steamid)); connection = SteamNetworkingSockets.ConnectP2P(ref identity, virtualPort, (options != null) ? options.Length : 0, options); localClients.Add(connection); cb_OnConnectionStatusChanged = Callback.Create((DispatchDelegate)OnConnectionStatusChanged); } public bool Send(ArraySegment data, bool dequeue = false) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) if (!dequeue && !resendQueue.IsEmpty) { resendQueue.Enqueue(new rSteamServer.ResendRequest { bytes = data }); return true; } if (data.Count > 81920) { APILogger.Error("Failed to send packet, packet was larger than maximum message send size."); throw new Exception("Failed to send packet, packet was larger than maximum message send size."); } IntPtr[] array = new IntPtr[1]; ArraySegment[] array2 = new ArraySegment[1]; long[] array3 = new long[1]; int num = 0; int num2 = 0; while (num < data.Count) { int num3 = Mathf.Min(data.Count - num, 81920); IntPtr intPtr = SteamGameServerNetworkingUtils.AllocateMessage(num3); SteamNetworkingMessage_t val = SteamNetworkingMessage_t.FromIntPtr(intPtr); val.m_conn = connection; val.m_nFlags = 9; Marshal.Copy(data.Array, data.Offset + num, val.m_pData, num3); array2[num2] = new ArraySegment(data.Array, data.Offset + num, num3); num += num3; array[num2] = intPtr; num2++; Marshal.StructureToPtr(val, intPtr, fDeleteOld: true); APILogger.Debug($"[{debugName}] Sent packet({dequeue}): {num}/{data.Count}"); } SteamNetworkingSockets.SendMessages(1, array, array3); bool result = true; for (int i = 0; i < array3.Length; i++) { if (array3[i] < 0) { APILogger.Debug($"[{debugName}] Failed to send packet, Error Code: {array3[i]}"); if (-array3[i] == 25) { resendQueue.Enqueue(new rSteamServer.ResendRequest { bytes = array2[i] }); APILogger.Debug("[" + debugName + "] Requeued packet."); result = false; } else { APILogger.Error($"[{debugName}] Failed to send packet, Error Code: {array3[i]}"); } } } return result; } private async Task ReceiveMessages() { IntPtr[] messageBuffer = new IntPtr[50]; while (true) { int num; int numMessages = (num = SteamNetworkingSockets.ReceiveMessagesOnConnection(connection, messageBuffer, messageBuffer.Length)); if (num > 0) { for (int i = 0; i < numMessages; i++) { SteamNetworkingMessage_t val = SteamNetworkingMessage_t.FromIntPtr(messageBuffer[i]); byte[] array = new byte[val.m_cbSize]; Marshal.Copy(val.m_pData, array, 0, array.Length); onReceive?.Invoke(array, this); SteamNetworkingMessage_t.Release(messageBuffer[i]); } continue; } if (!resendQueue.IsEmpty) { SteamNetConnectionRealTimeStatus_t val2 = default(SteamNetConnectionRealTimeStatus_t); SteamNetConnectionRealTimeLaneStatus_t val3 = default(SteamNetConnectionRealTimeLaneStatus_t); if ((int)SteamNetworkingSockets.GetConnectionRealTimeStatus(connection, ref val2, 0, ref val3) == 1 && val2.m_cbPendingReliable + val2.m_cbSentUnackedReliable == 0) { rSteamServer.ResendRequest result; while (resendQueue.TryDequeue(out result)) { resendQueueBuffer.Add(result); } int j; for (j = 0; j < resendQueueBuffer.Count && Send(resendQueueBuffer[j].bytes, dequeue: true); j++) { } for (; j < resendQueueBuffer.Count; j++) { resendQueue.Enqueue(resendQueueBuffer[j]); } resendQueueBuffer.Clear(); } } await Task.Delay(16); if (numMessages < 0 || !running) { break; } } } private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callbackData) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected I4, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) HSteamNetConnection hConn = callbackData.m_hConn; if (connection != hConn) { return; } SteamNetConnectionInfo_t info = callbackData.m_info; ESteamNetworkingConnectionState eState = info.m_eState; switch (eState - 1) { case 0: APILogger.Warn($"[{debugName}] {hConn.m_HSteamNetConnection} Connecting to: {((SteamNetConnectionInfo_t)(ref info)).m_szConnectionDescription}"); connected = false; break; case 2: APILogger.Warn($"[{debugName}] {hConn.m_HSteamNetConnection} Connection established: {((SteamNetConnectionInfo_t)(ref info)).m_szConnectionDescription}"); onAccept?.Invoke(this); connected = true; receiveTask = ReceiveMessages(); break; case 3: case 4: APILogger.Warn($"[{debugName}] {hConn.m_HSteamNetConnection} Connection closed: {((SteamNetConnectionInfo_t)(ref info)).m_szEndDebug}, established: {connected}"); if (!connected) { onFail?.Invoke(this); } Dispose(); break; case 1: break; } } public void Dispose() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) running = false; receiveTask?.Wait(); receiveTask?.Dispose(); receiveTask = null; onClose?.Invoke(this); localClients.Remove(connection); SteamNetworkingSockets.CloseConnection(connection, 0, "Disconnect", false); cb_OnConnectionStatusChanged.Dispose(); } } internal class rSteamServer : IDisposable { public delegate void OnAccept(HSteamNetConnection connection); public delegate void OnReceive(ArraySegment buffer, HSteamNetConnection connection); public delegate void OnDisconnect(HSteamNetConnection connection); public delegate void OnClose(); public struct ResendRequest { public ArraySegment bytes; } public class Connection { public ConcurrentQueue resendQueue = new ConcurrentQueue(); public List resendQueueBuffer = new List(); public readonly HSteamNetConnection connection; private rSteamServer server; public bool running = true; public string name = "Unknown"; public Connection(rSteamServer server, HSteamNetConnection connection) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) this.connection = connection; this.server = server; ReceiveMessages(); } public void Dispose() { running = false; } public async Task ReceiveMessages() { IntPtr[] messageBuffer = new IntPtr[50]; while (true) { int num; int numMessages = (num = SteamNetworkingSockets.ReceiveMessagesOnConnection(connection, messageBuffer, messageBuffer.Length)); if (num > 0) { for (int i = 0; i < numMessages; i++) { SteamNetworkingMessage_t val = SteamNetworkingMessage_t.FromIntPtr(messageBuffer[i]); byte[] array = new byte[val.m_cbSize]; Marshal.Copy(val.m_pData, array, 0, array.Length); server.onReceive?.Invoke(array, connection); SteamNetworkingMessage_t.Release(messageBuffer[i]); } continue; } if (!resendQueue.IsEmpty) { SteamNetConnectionRealTimeStatus_t val2 = default(SteamNetConnectionRealTimeStatus_t); SteamNetConnectionRealTimeLaneStatus_t val3 = default(SteamNetConnectionRealTimeLaneStatus_t); if ((int)SteamNetworkingSockets.GetConnectionRealTimeStatus(connection, ref val2, 0, ref val3) == 1 && val2.m_cbPendingReliable + val2.m_cbSentUnackedReliable == 0) { ResendRequest result; while (resendQueue.TryDequeue(out result)) { resendQueueBuffer.Add(result); } int j; for (j = 0; j < resendQueueBuffer.Count && server.SendTo(connection, resendQueueBuffer[j].bytes, dequeue: true); j++) { } for (; j < resendQueueBuffer.Count; j++) { resendQueue.Enqueue(resendQueueBuffer[j]); } resendQueueBuffer.Clear(); } } await Task.Delay(16); if (numMessages < 0 || !running) { break; } } } } public const int maxPacketSize = 81920; public OnAccept? onAccept; public OnReceive? onReceive; public OnDisconnect? onDisconnect; public OnClose? onClose; private HSteamListenSocket server; private Task? receiveTask; private Callback cb_OnConnectionStatusChanged; private readonly string debugName; public ConcurrentDictionary currentConnections = new ConcurrentDictionary(); public rSteamServer(int virtualPort, SteamNetworkingConfigValue_t[]? options = null, string debugName = "Server") { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) this.debugName = debugName; server = SteamNetworkingSockets.CreateListenSocketP2P(virtualPort, (options != null) ? options.Length : 0, options); cb_OnConnectionStatusChanged = Callback.Create((DispatchDelegate)OnConnectionStatusChanged); } public void Send(ArraySegment data) { //IL_0014: 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_001b: Unknown result type (might be due to invalid IL or missing references) foreach (HSteamNetConnection key in currentConnections.Keys) { SendTo(key, data); } } public bool SendTo(HSteamNetConnection connection, ArraySegment data, bool dequeue = false) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) Connection connection2 = currentConnections[connection]; if (!dequeue && !connection2.resendQueue.IsEmpty) { connection2.resendQueue.Enqueue(new ResendRequest { bytes = data }); return true; } if (data.Count > 81920) { APILogger.Error("Failed to send packet, packet was larger than maximum message send size."); throw new Exception("Failed to send packet, packet was larger than maximum message send size."); } IntPtr[] array = new IntPtr[1]; ArraySegment[] array2 = new ArraySegment[1]; long[] array3 = new long[1]; int num = 0; int num2 = 0; while (num < data.Count) { int num3 = Mathf.Min(data.Count - num, 81920); IntPtr intPtr = SteamGameServerNetworkingUtils.AllocateMessage(num3); SteamNetworkingMessage_t val = SteamNetworkingMessage_t.FromIntPtr(intPtr); val.m_conn = connection; val.m_nFlags = 9; Marshal.Copy(data.Array, data.Offset + num, val.m_pData, num3); array2[num2] = new ArraySegment(data.Array, data.Offset + num, num3); num += num3; array[num2] = intPtr; num2++; Marshal.StructureToPtr(val, intPtr, fDeleteOld: true); } SteamNetworkingSockets.SendMessages(1, array, array3); bool result = true; for (int i = 0; i < array3.Length; i++) { if (array3[i] < 0) { APILogger.Debug($"Failed to send packet, Error Code: {array3[i]}"); if (-array3[i] == 25) { connection2.resendQueue.Enqueue(new ResendRequest { bytes = array2[i] }); APILogger.Debug("Requeued packet."); result = false; } } } return result; } private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callbackData) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected I4, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Invalid comparison between Unknown and I4 //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_029f: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Invalid comparison between Unknown and I4 //IL_02b3: Unknown result type (might be due to invalid IL or missing references) HSteamNetConnection hConn = callbackData.m_hConn; SteamNetConnectionInfo_t info = callbackData.m_info; if (info.m_hListenSocket != server || rSteamClient.localClients.Contains(hConn)) { return; } ulong steamID = ((SteamNetworkingIdentity)(ref info.m_identityRemote)).GetSteamID64(); ESteamNetworkingConnectionState eState = info.m_eState; switch (eState - 1) { case 0: APILogger.Debug("[" + debugName + "] Incoming connection from: " + ((SteamNetConnectionInfo_t)(ref info)).m_szConnectionDescription); if (steamID == SteamUser.GetSteamID().m_SteamID || ConfigManager.allowAnySpectator || (ConfigManager.WhiteListFriends && (int)SteamFriends.GetFriendRelationship(new CSteamID(steamID)) == 3) || ConfigManager.steamIDWhitelist.Contains(steamID)) { EResult val = SteamNetworkingSockets.AcceptConnection(hConn); if ((int)val != 1) { APILogger.Debug($"[{debugName}] Failed to accept connection: {val}"); SteamNetworkingSockets.CloseConnection(hConn, 0, "Failed to accept", false); } else { APILogger.Warn($"[{debugName}] Allowed {steamID} to spectate your lobby."); } } else { APILogger.Warn($"[{debugName}] Rejected {steamID} from spectating your lobby."); } break; case 2: { APILogger.Warn("[" + debugName + "] Connection established: " + ((SteamNetConnectionInfo_t)(ref info)).m_szConnectionDescription); Connection conn = new Connection(this, hConn); currentConnections.AddOrUpdate(hConn, conn, (HSteamNetConnection key, Connection old) => conn); onAccept?.Invoke(hConn); break; } case 3: case 4: { APILogger.Warn($"[{debugName}] Connection closed: {((SteamNetConnectionInfo_t)(ref info)).m_szConnectionDescription} {((SteamNetConnectionInfo_t)(ref info)).m_szEndDebug}"); onDisconnect?.Invoke(hConn); currentConnections.Remove(hConn, out var value); value?.Dispose(); SteamNetworkingSockets.CloseConnection(hConn, 0, "Closed", false); break; } case 1: break; } } public void Dispose() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) receiveTask?.Wait(); receiveTask?.Dispose(); receiveTask = null; cb_OnConnectionStatusChanged.Dispose(); SteamNetworkingSockets.CloseListenSocket(server); onClose?.Invoke(); APILogger.Debug("[{debugName}] Listen server closed."); } } [HarmonyPatch] internal static class rSteamworks { public delegate void OnInit(); public delegate void OnDispose(); public static OnInit? onInit; public static OnDispose? onDispose; private static bool initialized; [HarmonyPatch(typeof(SteamManager), "PostSetup")] [HarmonyPrefix] private static void Prefix_Setup() { if (!ConfigManager.DisableSteamAPI && !initialized) { initialized = true; if (!SteamAPI.Init()) { APILogger.Error("Failed to start SteamAPI."); return; } if (!GameServer.Init(2130706433u, (ushort)0, (ushort)0, (EServerMode)1, "0.0.0.1")) { APILogger.Error("Failed to start GameServer"); return; } SteamNetworkingUtils.InitRelayNetworkAccess(); APILogger.Debug("Initialized Steamworks!"); onInit?.Invoke(); } } [HarmonyPatch(typeof(SteamManager), "Update")] [HarmonyPrefix] private static void Prefix_Update() { if (!ConfigManager.DisableSteamAPI) { SteamAPI.RunCallbacks(); SteamNetworkingSockets.RunCallbacks(); } } [HarmonyPatch(typeof(SNet_Core_STEAM), "OnQuit")] [HarmonyPostfix] private static void Postfix_OnQuit() { if (!ConfigManager.DisableSteamAPI && initialized) { onDispose?.Invoke(); SteamAPI.Shutdown(); GameServer.Shutdown(); APILogger.Debug("Shutdown Steamworks!"); } } } } namespace ReplayRecorder.SNetUtils { public class SNetUtils { internal static ushort currentRepKey; internal static int currentPacketIndex; internal static SNet_Player? currentSender; internal static uint magickey = 15202362u; internal static ushort repKey = 65530; internal const int sizeOfHeader = 10; public static List _playerBuff = new List(); public static Action, ulong>? OnReceive; public static bool TryGetSender(SNet_Packet packet, [MaybeNullWhen(false)] out SNet_Player player) { if ((Object)(object)currentSender != (Object)null && packet.Replicator.Key == currentRepKey && packet.Index == currentPacketIndex) { player = currentSender; return true; } player = null; return false; } public static void SendBytes(ArraySegment packet, List toPlayers) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (toPlayers.Count != 0) { int index = 0; byte[] array = new byte[10 + packet.Count]; BitHelper.WriteBytes(repKey, (ArraySegment)array, ref index); BitHelper.WriteBytes(magickey, (ArraySegment)array, ref index); BitHelper.WriteBytes(packet, (ArraySegment)array, ref index); SNet_ChannelType val = (SNet_ChannelType)0; SNet_SendGroup val2 = default(SNet_SendGroup); SNet_SendQuality val3 = default(SNet_SendQuality); int num = default(int); SNet.GetSendSettings(ref val, ref val2, ref val3, ref num); SNet.Core.SendBytes(Il2CppStructArray.op_Implicit(array), val3, num, toPlayers); } } } [HarmonyPatch] internal class Patches { [HarmonyPatch(typeof(SNet_Replication), "RecieveBytes")] [HarmonyWrapSafe] [HarmonyPriority(700)] [HarmonyPrefix] private static void Prefix_RecieveBytes(Il2CppStructArray bytes, uint size, ulong messagerID) { IReplicator val = default(IReplicator); int currentPacketIndex = default(int); SNet_Player currentSender = default(SNet_Player); if (SNet.Replication.TryGetReplicator(bytes, ref val, ref currentPacketIndex) && SNet.Core.TryGetPlayer(messagerID, ref currentSender, false)) { SNetUtils.currentSender = currentSender; SNetUtils.currentRepKey = val.Key; SNetUtils.currentPacketIndex = currentPacketIndex; } } [HarmonyPatch(typeof(SNet_Replication), "RecieveBytes")] [HarmonyWrapSafe] [HarmonyPriority(100)] [HarmonyPostfix] private static void Postfix_RecieveBytes(Il2CppStructArray bytes, uint size, ulong messagerID) { SNetUtils.currentSender = null; SNetUtils.currentRepKey = 0; SNetUtils.currentPacketIndex = 0; } [HarmonyPatch(typeof(SNet_Replication), "RecieveBytes")] [HarmonyWrapSafe] [HarmonyPrefix] private static bool RecieveBytes_Prefix(Il2CppStructArray bytes, uint size, ulong messagerID) { if (size < 10) { return true; } byte[] array = Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)bytes); ushort num = BitConverter.ToUInt16(array, 0); if (SNetUtils.repKey == num) { if (BitConverter.ToUInt32(array, 2) != SNetUtils.magickey) { return true; } int num2 = BitConverter.ToInt32(array, 6); byte[] array2 = new byte[num2]; Array.Copy(array, 10, array2, 0, num2); SNetUtils.OnReceive?.Invoke(array2, messagerID); return false; } return true; } } } namespace ReplayRecorder.Snapshot { internal class SnapshotInstance : MonoBehaviour { private class EventWrapper { private ushort id; private ReplayEvent eventObj; internal ByteBuffer? eventBuffer; internal long now; public string? Debug => eventObj.Debug; public EventWrapper(long now, ReplayEvent e, ByteBuffer buffer) { this.now = now; eventObj = e; eventBuffer = buffer; e.Write(eventBuffer); id = SnapshotManager.types[e.GetType()]; } ~EventWrapper() { Dispose(); } public void Dispose() { if (eventBuffer != null) { if ((Object)(object)SnapshotManager.instance != (Object)null) { SnapshotManager.instance.pool.Release(eventBuffer); } eventBuffer = null; } } public void Write(ByteBuffer buffer) { if (eventBuffer == null) { throw new Exception("Memory Buffer was disposed too early..."); } if (ConfigManager.Debug && ConfigManager.DebugDynamics) { APILogger.Debug($"[Event: {eventObj.GetType().FullName}({SnapshotManager.types[eventObj.GetType()]})]{((eventObj.Debug != null) ? (": " + eventObj.Debug) : "")}"); } BitHelper.WriteBytes(id, buffer); BitHelper.WriteBytes(eventBuffer.Array, buffer, includeCount: false); } } private class DynamicCollection : IEnumerable, IEnumerable { [CompilerGenerated] private sealed class d__19 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private ReplayDynamic <>2__current; public DynamicCollection <>4__this; private Dictionary.ValueCollection.Enumerator <>7__wrap1; ReplayDynamic IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__19(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = default(Dictionary.ValueCollection.Enumerator); <>1__state = -2; } private bool MoveNext() { try { int num = <>1__state; DynamicCollection dynamicCollection = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>7__wrap1 = dynamicCollection.mapOfDynamics.Values.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { ReplayDynamic current = <>7__wrap1.Current; <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap1 = default(Dictionary.ValueCollection.Enumerator); return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; ((IDisposable)<>7__wrap1).Dispose(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public int maxPerTick = int.MaxValue; private int currentDynamic; public int tickRate = 1; private int tick; private List _dynamics = new List(); private List dynamics = new List(); private Dictionary mapOfDynamics = new Dictionary(); private SnapshotInstance instance; private bool handleRemoval; public Type Type { get; private set; } public ushort Id { get; private set; } private bool performTick { get { tick = (tick + 1) % Mathf.Clamp(tickRate, 1, int.MaxValue); return tick == 0; } } public DynamicCollection(Type type, SnapshotInstance instance) { this.instance = instance; if (!SnapshotManager.types.Contains(type)) { throw new ReplayTypeDoesNotExist("Could not create DynamicCollection of type '" + type.FullName + "'."); } Type = type; Id = SnapshotManager.types[type]; } [IteratorStateMachine(typeof(d__19))] public IEnumerator GetEnumerator() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__19(0) { <>4__this = this }; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } [HideFromIl2Cpp] public bool Has(ReplayDynamic dynamic) { Type type = dynamic.GetType(); if (!Type.IsAssignableFrom(type)) { throw new ReplayIncompatibleType($"Cannot add '{type.FullName}' to DynamicCollection of type '{Type.FullName}'."); } return mapOfDynamics.ContainsKey(dynamic.id); } [HideFromIl2Cpp] public bool Has(int id) { return mapOfDynamics.ContainsKey(id); } [HideFromIl2Cpp] public ReplayDynamic Get(int id) { if (!mapOfDynamics.ContainsKey(id)) { throw new ReplayDynamicDoesNotExist($"Cannot get dynamic of id '{id}'. Type: '{Type.FullName}'."); } return mapOfDynamics[id]; } [HideFromIl2Cpp] public void Add(ReplayDynamic dynamic, bool errorOnDuplicate = true) { Type type = dynamic.GetType(); if (!Type.IsAssignableFrom(type)) { throw new ReplayIncompatibleType($"Cannot add '{type.FullName}' to DynamicCollection of type '{Type.FullName}'."); } if (mapOfDynamics.ContainsKey(dynamic.id)) { if (errorOnDuplicate) { throw new ReplayDynamicAlreadyExists($"Dynamic [{dynamic.id}] already exists in DynamicCollection of type '{Type.FullName}'."); } } else { AddNoChecks(dynamic); } } [HideFromIl2Cpp] internal void AddNoChecks(ReplayDynamic dynamic) { dynamics.Add(dynamic); mapOfDynamics.Add(dynamic.id, dynamic); } [HideFromIl2Cpp] public void Remove(int id, bool errorOnNotFound = true) { if (!mapOfDynamics.ContainsKey(id)) { if (errorOnNotFound) { throw new ReplayDynamicDoesNotExist($"Dynamic [{id}] does not exist in DynamicCollection of type '{Type.FullName}'."); } } else { RemoveNoChecks(id); } } [HideFromIl2Cpp] public void Remove(ReplayDynamic dynamic) { Type type = dynamic.GetType(); if (!Type.IsAssignableFrom(type)) { throw new ReplayIncompatibleType($"Cannot remove dynamic of type '{type.FullName}' from DynamicCollection of type '{Type.FullName}'."); } Remove(dynamic.id); } [HideFromIl2Cpp] internal void RemoveNoChecks(int id) { handleRemoval = true; mapOfDynamics[id].remove = true; mapOfDynamics.Remove(id); } public int Write(ByteBuffer buffer, long now) { if (!performTick) { return 0; } if (dynamics.Count == 0) { return 0; } int count = buffer.count; int num = 0; BitHelper.WriteBytes(Id, buffer); int index = buffer.count; buffer.Reserve(4, increment: true); bool flag = handleRemoval; handleRemoval = false; if (flag) { _dynamics.Clear(); } int num2 = 0; int num3 = (currentDynamic %= dynamics.Count); for (int i = 0; i < dynamics.Count; i++) { ReplayDynamic dynamic = dynamics[(num3 + i) % dynamics.Count]; Type type = dynamic.GetType(); if (Replay.DynamicHooks.ContainsKey(type)) { try { Replay.DynamicHooks[type]?.Invoke(now, dynamic); } catch (Exception value) { APILogger.Error($"[DynamicCollection] Failed to trigger hooks for [{type}]: {value}."); } } bool active = dynamic.Active; if (num2++ < maxPerTick) { currentDynamic++; if ((!dynamic.remove || tickRate == 1) && active && dynamic.IsDirty) { if (ConfigManager.Debug && ConfigManager.DebugDynamics) { APILogger.Debug($"[Dynamic: {dynamic.GetType().FullName}({SnapshotManager.types[dynamic.GetType()]})]{((dynamic.Debug != null) ? (": " + dynamic.Debug) : "")}"); } int count2 = buffer.count; try { if (Replay.DirtyDynamicHooks.ContainsKey(type)) { try { Replay.DirtyDynamicHooks[type]?.Invoke(now, dynamic); } catch (Exception value2) { APILogger.Error($"[DynamicCollection] Failed to trigger dirty hooks for [{type}]: {value2}."); } } dynamic._Write(buffer); dynamic.Write(buffer); num++; } catch (Exception ex) { buffer.count = count2; if (!dynamic.remove) { instance.Despawn(dynamic); APILogger.Warn($"[DynamicCollection] Despawning due to Error {Type} {dynamic.id}"); APILogger.Error($"Unexpected error occured whilst trying to write Dynamic[{Type}]({dynamic.id}) at [{instance.Now}ms]:\n{ex}\n{ex.StackTrace}"); } } } } if (!active && !dynamic.remove) { if (dynamics.Any((ReplayDynamic d) => (object)d != dynamic && d == dynamic && d.Active && !d.remove)) { dynamic.remove = true; handleRemoval = true; APILogger.Warn($"[DynamicCollection] Silent Removal {Type} {dynamic.id}"); } else { instance.Despawn(dynamic); APILogger.Warn($"[DynamicCollection] Forced Despawn {Type} {dynamic.id}"); } } if (flag && !dynamic.remove) { _dynamics.Add(dynamic); } else if (i < currentDynamic) { currentDynamic--; } } if (flag) { List list = dynamics; dynamics = _dynamics; _dynamics = list; } if (num == 0) { buffer.count = count; return 0; } BitHelper.WriteBytes(num, buffer._array, ref index); if (ConfigManager.Debug && ConfigManager.DebugTicks) { APILogger.Debug($"[DynamicCollection: {Type.FullName}({SnapshotManager.types[Type]})]: {num} dynamics serialized."); } return num; } } private class DeltaState { internal List events = new List(); internal Dictionary dynamics = new Dictionary(); internal void Clear() { events.Clear(); dynamics.Clear(); } internal bool Write(long now, ByteBuffer bs) { BitHelper.WriteBytes((uint)now, bs); BitHelper.WriteBytes(events.Count, bs); for (int i = 0; i < events.Count; i++) { EventWrapper eventWrapper = events[i]; long num = now - eventWrapper.now; if (num < 0) { num = 0L; } if (num > 65535) { APILogger.Warn($"Delta time of {num}ms is invalid. Max is {65535}ms."); num = 65535L; } BitHelper.WriteBytes((ushort)num, bs); eventWrapper.Write(bs); eventWrapper.Dispose(); } bool flag = events.Count != 0; if (ConfigManager.DebugTicks) { APILogger.Debug($"[Events] {events.Count} events written."); } events.Clear(); int num2 = 0; int index = bs.count; bs.Reserve(2, increment: true); foreach (DynamicCollection value in dynamics.Values) { int count = bs.count; try { if (value.Write(bs, now) > 0) { num2++; } } catch (Exception ex) { APILogger.Error($"Unexpected error occured whilst trying to write DynamicCollection[{value.Type}] at [{now}ms]:\n{ex}\n{ex.StackTrace}"); bs.count = count; } } BitHelper.WriteBytes((ushort)num2, bs._array, ref index); if (ConfigManager.Debug && ConfigManager.DebugDynamics) { APILogger.Debug($"Flushed {num2} dynamic collections."); } if (!flag) { return num2 != 0; } return true; } } private FileStream? fs; public int byteOffset; private DeltaState state = new DeltaState(); private ByteBuffer buffer = new ByteBuffer(); private ByteBuffer _buffer = new ByteBuffer(); internal BufferPool pool = new BufferPool(); private Task? writeTask; private long start; private bool completedHeader; private HashSet unwrittenHeaders = new HashSet(); private static byte _replayInstanceId; internal byte replayInstanceId; internal string fullpath = "replay.gtfo"; internal string filename = "replay.gtfo"; private Stopwatch stopwatch = new Stopwatch(); private int bufferShrinkTick; private int peakInUse; internal float tickTime = 1f; internal float waitForWrite = 1f; private HashSet alertedPlayers = new HashSet(); internal HashSet spectators = new HashSet(); public float tickRate = 0.05f; private float timer; public bool Ready { get { if (Active) { return completedHeader; } return false; } } public bool Active => fs != null; private long Now => Raudy.Now - start; public void Flush() { if (fs != null) { fs.Flush(); } } internal void Init() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (fs != null) { throw new ReplaySnapshotAlreadyInitialized(); } start = Raudy.Now; pActiveExpedition activeExpeditionData = RundownManager.GetActiveExpeditionData(); RundownDataBlock block = GameDataBlockBase.GetBlock(Global.RundownIdToLoad); ExpeditionInTierData expeditionData = block.GetExpeditionData(activeExpeditionData.tier, activeExpeditionData.expeditionIndex); string arg = Utils.RemoveHTMLTags(expeditionData.GetShortName(activeExpeditionData.expeditionIndex)); string arg2 = Utils.RemoveHTMLTags(expeditionData.Descriptive.PublicName); DateTime now = DateTime.Now; filename = string.Format(ConfigManager.ReplayFileName, arg, now, arg2); string text = Utils.RemoveInvalidCharacters(ConfigManager.ReplayFolder); filename = Utils.RemoveInvalidCharacters(filename, '_', isFullPath: false); Directory.CreateDirectory(text); if (!Directory.Exists(text)) { text = "./"; } string text2; if (ConfigManager.SeparateByRundown) { text2 = Path.Combine(text, Utils.RemoveInvalidCharacters(((GameDataBlockBase)(object)block).name)); fullpath = Path.Combine(text2, filename); } else { text2 = Utils.RemoveInvalidCharacters(text); fullpath = Path.Combine(text2, filename); } APILogger.Warn("REPLAY LOCATION: " + fullpath); try { Directory.CreateDirectory(text2); fs = new FileStream(fullpath, FileMode.Create, FileAccess.Write, FileShare.Read); } catch (Exception ex) { APILogger.Error("Failed to create filestream, falling back to 'replay.gtfo': " + ex.Message); fullpath = "replay.gtfo"; fs = new FileStream(fullpath, FileMode.Create, FileAccess.Write, FileShare.Read); } spectators.Clear(); alertedPlayers.Clear(); alertedPlayers.Add(PlayerManager.GetLocalPlayerAgent().Owner.Lookup); pool = new BufferPool(); byteOffset = 0; buffer.Clear(); buffer.Reserve(4, increment: true); SnapshotManager.types.Write(buffer); foreach (Type header in SnapshotManager.types.headers) { unwrittenHeaders.Add(header); } state.Clear(); foreach (Type dynamic in SnapshotManager.types.dynamics) { state.dynamics.Add(dynamic, new DynamicCollection(dynamic, this)); } replayInstanceId = _replayInstanceId++; } [HideFromIl2Cpp] internal void Trigger(ReplayHeader header) { if (fs == null) { throw new ReplaySnapshotNotInitialized(); } Type type = header.GetType(); if (completedHeader || unwrittenHeaders.Count == 0) { completedHeader = true; throw new ReplayAllHeadersAlreadyWritten("Cannot write header '" + type.FullName + "' as all headers have been written already."); } unwrittenHeaders.Remove(type); ushort value = SnapshotManager.types[type]; APILogger.Debug($"[Header: {type.FullName}({value})]{((header.Debug != null) ? (": " + header.Debug) : "")}"); BitHelper.WriteBytes(value, buffer); header.Write(buffer); if (unwrittenHeaders.Count == 0) { completedHeader = true; OnHeaderComplete(); } } [HideFromIl2Cpp] private async Task SendBufferOverNetwork(ByteBuffer buffer) { if (HostClient.Main.readyConnections.Count > 0) { int count = buffer.count; try { int num = 0; while (num < buffer.count) { int num2 = Mathf.Min(buffer.count - num, 81903); ByteBuffer byteBuffer = new ByteBuffer(new byte[11 + num2 + 4]); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(11 + num2, byteBuffer); BitHelper.WriteBytes((ushort)2, byteBuffer); BitHelper.WriteBytes(replayInstanceId, byteBuffer); BitHelper.WriteBytes(byteOffset + num, byteBuffer); BitHelper.WriteBytes(num2, byteBuffer); BitHelper.WriteBytes(new ArraySegment(buffer._array.Array, buffer._array.Offset + num, num2), byteBuffer, includeCount: false); if (HostClient.Main.socket != null) { foreach (HSteamNetConnection key in HostClient.Main.readyConnections.Keys) { HostClient.Main.socket.SendTo(key, byteBuffer.Array); } } num += num2; APILogger.Debug($"Sending Snapshot {num}/{buffer.count} ..."); } APILogger.Debug($"Finished send: {num} / {count} - {buffer.count}"); } catch (Exception value) { APILogger.Error($"Unable to send snapshot bytes: {value}"); } } byteOffset += buffer.count; } private void OnHeaderComplete() { if (fs == null) { throw new ReplaySnapshotNotInitialized(); } EndOfHeader endOfHeader = new EndOfHeader(); APILogger.Debug($"[Header: {typeof(EndOfHeader).FullName}({SnapshotManager.types[typeof(EndOfHeader)]})]{((endOfHeader.Debug != null) ? (": " + endOfHeader.Debug) : "")}"); endOfHeader.Write(buffer); APILogger.Debug($"Acknowledged Clients: {HostClient.Main.readyConnections.Count}"); int index = 0; BitHelper.WriteBytes(buffer.count - 4, buffer._array, ref index); APILogger.Debug($"Header size: {buffer.count - 4}"); Task.WaitAll(buffer.AsyncFlush(fs), SendBufferOverNetwork(buffer)); buffer.Clear(); buffer.Shrink(); Replay.OnHeaderCompletion?.Invoke(); } [HideFromIl2Cpp] internal void Configure(int tickRate, int max) where T : ReplayDynamic { Type typeFromHandle = typeof(T); if (!state.dynamics.ContainsKey(typeFromHandle)) { throw new ReplayTypeDoesNotExist("Type '" + typeFromHandle.FullName + "' does not exist."); } state.dynamics[typeFromHandle].maxPerTick = max; state.dynamics[typeFromHandle].tickRate = tickRate; } [HideFromIl2Cpp] internal bool Trigger(ReplayEvent e) { Type type = e.GetType(); long now = Now; if (Replay.EventHooks.ContainsKey(type)) { try { Replay.EventHooks[type]?.Invoke(now, e); } catch (Exception value) { APILogger.Error($"Failed to trigger event hooks for [{type}]: {value}."); } } try { EventWrapper item = new EventWrapper(now, e, pool.Checkout()); state.events.Add(item); return true; } catch (Exception ex) { APILogger.Error($"Unexpected error occured whilst trying to write Event[{type}] at [{Raudy.Now}ms]:\n{ex}\n{ex.StackTrace}"); } return false; } [HideFromIl2Cpp] internal bool Has(ReplayDynamic dynamic) { Type type = dynamic.GetType(); if (!state.dynamics.ContainsKey(type)) { throw new ReplayTypeDoesNotExist("Type '" + type.FullName + "' does not exist."); } return state.dynamics[type].Has(dynamic); } [HideFromIl2Cpp] internal bool Has(Type type, int id) { if (!state.dynamics.ContainsKey(type)) { throw new ReplayTypeDoesNotExist("Type '" + type.FullName + "' does not exist."); } return state.dynamics[type].Has(id); } [HideFromIl2Cpp] internal ReplayDynamic Get(Type type, int id) { if (!state.dynamics.ContainsKey(type)) { throw new ReplayTypeDoesNotExist("Type '" + type.FullName + "' does not exist."); } return state.dynamics[type].Get(id); } [HideFromIl2Cpp] internal void Clear(Type type) { if (!state.dynamics.ContainsKey(type)) { throw new ReplayTypeDoesNotExist("Type '" + type.FullName + "' does not exist."); } foreach (ReplayDynamic item in state.dynamics[type]) { Despawn(item); } } [HideFromIl2Cpp] internal void Spawn(ReplayDynamic dynamic, bool errorOnDuplicate = true) { Type type = dynamic.GetType(); if (!state.dynamics.ContainsKey(type)) { throw new ReplayTypeDoesNotExist("Type '" + type.FullName + "' does not exist."); } if (state.dynamics[type].Has(dynamic)) { if (errorOnDuplicate) { throw new ReplayDynamicAlreadyExists($"Dynamic [{dynamic.id}] already exists in DynamicCollection of type '{type.FullName}'."); } return; } if (!Trigger(new ReplaySpawn(dynamic))) { APILogger.Error($"Unable to spawn '{type}' as spawn event failed."); return; } state.dynamics[type].AddNoChecks(dynamic); if (!Replay.SpawnHooks.ContainsKey(type)) { return; } try { Replay.SpawnHooks[type]?.Invoke(Now, dynamic); } catch (Exception value) { APILogger.Error($"Failed to trigger spawn hooks for [{type}]: {value}."); } } [HideFromIl2Cpp] internal void Despawn(ReplayDynamic dynamic, bool errorOnNotFound = true) { Type type = dynamic.GetType(); if (!state.dynamics.ContainsKey(type)) { throw new ReplayTypeDoesNotExist("Type '" + type.FullName + "' does not exist."); } if (!state.dynamics[type].Has(dynamic)) { if (errorOnNotFound) { throw new ReplayDynamicDoesNotExist($"Dynamic [{dynamic.id}] does not exist in DynamicCollection of type '{type.FullName}'."); } return; } if (!Trigger(new ReplayDespawn(dynamic))) { APILogger.Error($"Unable to despawn '{type}' as despawn event failed."); return; } if (Replay.DespawnHooks.ContainsKey(type)) { try { Replay.DespawnHooks[type]?.Invoke(Now, dynamic); } catch (Exception value) { APILogger.Error($"Failed to trigger despawn hooks for [{type}]: {value}."); } } state.dynamics[type].RemoveNoChecks(dynamic.id); } private void Tick() { if (pool.InUse > peakInUse) { peakInUse = pool.InUse; } if (++bufferShrinkTick > 100) { bufferShrinkTick = 0; pool.Shrink(Mathf.Max(50, peakInUse)); peakInUse = 0; } stopwatch.Restart(); if (fs == null) { throw new ReplaySnapshotNotInitialized(); } Replay.OnTick?.Invoke(); long now = Now; if (now > uint.MaxValue) { Dispose(); throw new ReplayInvalidTimestamp($"ReplayRecorder does not support replays longer than {-1}ms."); } buffer.Clear(); buffer.Reserve(4, increment: true); bool flag; try { flag = state.Write(now, buffer); } catch (Exception ex) { flag = false; APILogger.Error($"Unexpected error occured whilst trying to write tick at [{now}ms]:\n{ex}\n{ex.StackTrace}"); } if (flag) { int index = 0; BitHelper.WriteBytes(buffer.Count - 4, buffer._array, ref index); float num = stopwatch.ElapsedMilliseconds; if (writeTask != null) { writeTask.Wait(); } waitForWrite = (float)stopwatch.ElapsedMilliseconds - num; ByteBuffer writeBuffer = buffer; writeTask = Task.Run(delegate { Task.WaitAll(writeBuffer.AsyncFlush(fs), SendBufferOverNetwork(writeBuffer)); writeBuffer.Clear(); }); ByteBuffer byteBuffer = _buffer; _buffer = buffer; buffer = byteBuffer; } stopwatch.Stop(); tickTime = 0.9f * tickTime + 0.100000024f * (float)stopwatch.ElapsedMilliseconds; } internal void Dispose() { APILogger.Debug("Ending Replay..."); if (fs != null) { fs.Flush(); fs.Dispose(); Task.Run(delegate { try { using (FileStream stream = new FileStream(fullpath + ".compressed", FileMode.Create)) { using ZipArchive destination = new ZipArchive(stream, ZipArchiveMode.Create); destination.CreateEntryFromFile(fullpath, filename, CompressionLevel.SmallestSize); } File.Delete(fullpath); } catch (Exception value) { APILogger.Error($"Failed to compress replay:\n{value}"); } }); fs = null; } Object.Destroy((Object)(object)((Component)this).gameObject); } private void Update() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_001b: Invalid comparison between Unknown and I4 //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) if (fs == null || !completedHeader) { return; } DRAMA_State currentStateEnum = DramaManager.CurrentStateEnum; float num = ((currentStateEnum - 5 > 3) ? 0.1f : 0.05f); if (tickRate != num) { timer = 0f; tickRate = num; } timer += Time.deltaTime; if (!(timer > tickRate)) { return; } timer = 0f; if (HostClient.Main.socket != null && HostClient.Main.readyConnections.Count > 0) { foreach (HSteamNetConnection key in HostClient.Main.readyConnections.Keys) { if (!spectators.Add(key)) { continue; } string text = "[" + HostClient.Main.socket.currentConnections[key].name + "] is spectating."; APILogger.Warn(text); if (!ConfigManager.DisableLeaveJoinMessages) { while (text.Length > 50) { PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), text.Substring(0, 50).Trim(), (PlayerAgent)null); text = text.Substring(50).Trim(); } PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), text, (PlayerAgent)null); } } if (PlayerManager.PlayerAgentsInLevel.Count > 1) { bool flag = true; Enumerator enumerator2 = PlayerManager.PlayerAgentsInLevel.GetEnumerator(); while (enumerator2.MoveNext()) { PlayerAgent current2 = enumerator2.Current; if (!alertedPlayers.Contains(current2.Owner.Lookup) && current2.Owner.IsInGame) { flag = false; break; } } if (!flag) { enumerator2 = PlayerManager.PlayerAgentsInLevel.GetEnumerator(); while (enumerator2.MoveNext()) { PlayerAgent current3 = enumerator2.Current; if (!alertedPlayers.Contains(current3.Owner.Lookup) && current3.Owner.IsInGame) { alertedPlayers.Add(current3.Owner.Lookup); } } string text2 = "GTFOReplay Live View is in use. This allows the spectating user to see all item and enemy locations which may be considered cheating."; while (text2.Length > 50) { PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), text2.Substring(0, 50).Trim(), (PlayerAgent)null); text2 = text2.Substring(50).Trim(); } PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), text2, (PlayerAgent)null); } } } Tick(); } } internal static class SnapshotManager { internal static SnapshotTypeManager types = new SnapshotTypeManager(); internal static SnapshotInstance? instance; internal static bool Ready { get { if ((Object)(object)instance != (Object)null) { return instance.Ready; } return false; } } internal static bool Active { get { if ((Object)(object)instance != (Object)null) { return instance.Active; } return false; } } internal static void OnElevatorStart() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)instance == (Object)null) { instance = new GameObject().AddComponent(); instance.Init(); return; } throw new ReplaySnapshotAlreadyInitialized(); } internal static SnapshotInstance GetInstance() { if ((Object)(object)instance == (Object)null) { throw new ReplaySnapshotNotInitialized(); } return instance; } internal static void OnExpeditionEnd() { if ((Object)(object)instance != (Object)null) { instance.Dispose(); instance = null; Replay.OnExpeditionEnd?.Invoke(); } } } } namespace ReplayRecorder.Snapshot.Types { internal class SnapshotTypeManager { public readonly HashSet dynamics = new HashSet(); public readonly HashSet events = new HashSet(); public readonly HashSet headers = new HashSet(); private Dictionary typenameMap = new Dictionary(); private Dictionary typeMap = new Dictionary(); private Dictionary versionMap = new Dictionary(); private ushort staticType; public ushort this[Type type] { get { if (typeMap.ContainsKey(type)) { return typeMap[type]; } throw new ReplayTypeDoesNotExist("Type '" + type.FullName + "' does not exist."); } } public ushort this[string typename] { get { if (typenameMap.ContainsKey(typename)) { return typeMap[typenameMap[typename]]; } throw new ReplayTypeDoesNotExist("Type '" + typename + "' does not exist."); } } public bool Contains(Type type) { return typeMap.ContainsKey(type); } public void RegisterType(ReplayData data, Type type) { if (data.Typename == string.Empty) { throw new ReplayEmptyTypename("Typename cannot be a blank string."); } if (typenameMap.ContainsKey(data.Typename)) { throw new ReplayDuplicateTypeName("Typename '" + data.Typename + "' already exists."); } if (typeMap.ContainsKey(type)) { throw new ReplayDuplicateType("Type '" + type.FullName + "' already exists."); } if (staticType == ushort.MaxValue) { throw new ReplayTypeOverflow("Could not assign type '" + data.Typename + "' as there are no more indicies that can be assigned."); } string value; if (typeof(ReplayDynamic).IsAssignableFrom(type)) { value = "Dynamic"; dynamics.Add(type); } else if (typeof(ReplayEvent).IsAssignableFrom(type)) { value = "Event"; events.Add(type); } else { if (!typeof(ReplayHeader).IsAssignableFrom(type)) { throw new ReplayIncompatibleType("Type '" + type.FullName + "' is not a Dynamic, Event or Header."); } value = "Header"; headers.Add(type); } ushort value2 = staticType++; typenameMap.Add(data.Typename, type); typeMap.Add(type, value2); versionMap.Add(type, data.Version); APILogger.Debug($"Registered {value}: '{data.Typename}' => {type.FullName}[{value2}]"); } public void Write(ByteBuffer buffer) { StringBuilder stringBuilder = new StringBuilder(); BitHelper.WriteBytes("0.0.1", buffer); BitHelper.WriteBytes((ushort)typenameMap.Count, buffer); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(12, 1, stringBuilder2); handler.AppendLiteral("\n\tTypeMap["); handler.AppendFormatted(typenameMap.Count); handler.AppendLiteral("]:"); stringBuilder3.AppendLine(ref handler); foreach (KeyValuePair item in typenameMap) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(10, 4, stringBuilder2); handler.AppendLiteral("\t"); handler.AppendFormatted(typeMap[item.Value]); handler.AppendLiteral(" => "); handler.AppendFormatted(item.Key); handler.AppendLiteral("("); handler.AppendFormatted(versionMap[item.Value]); handler.AppendLiteral(") ["); handler.AppendFormatted(item.Value.FullName); handler.AppendLiteral("]"); stringBuilder4.AppendLine(ref handler); BitHelper.WriteBytes(typeMap[item.Value], buffer); BitHelper.WriteBytes(item.Key, buffer); BitHelper.WriteBytes(versionMap[item.Value], buffer); } APILogger.Debug(stringBuilder.ToString()); } } } namespace ReplayRecorder.Snapshot.Exceptions { public class ReplaySnapshotAlreadyInitialized : Exception { public ReplaySnapshotAlreadyInitialized() : base("Snapshot has already been initialized.") { } } public class ReplaySnapshotNotInitialized : Exception { public ReplaySnapshotNotInitialized() : base("Snapshot has not been initialized yet.") { } } public class ReplayAllHeadersAlreadyWritten : Exception { public ReplayAllHeadersAlreadyWritten(string message) : base(message) { } } public class ReplayHeaderAlreadyWritten : Exception { public ReplayHeaderAlreadyWritten(string message) : base(message) { } } public class ReplayDynamicDoesNotExist : Exception { public ReplayDynamicDoesNotExist(string message) : base(message) { } } public class ReplayDynamicAlreadyExists : Exception { public ReplayDynamicAlreadyExists(string message) : base(message) { } } public class ReplayInvalidDeltaTime : Exception { public ReplayInvalidDeltaTime(string message) : base(message) { } } public class ReplayInvalidTimestamp : Exception { public ReplayInvalidTimestamp(string message) : base(message) { } } public class ReplayEmptyTypename : Exception { public ReplayEmptyTypename(string message) : base(message) { } } public class ReplayIncompatibleType : Exception { public ReplayIncompatibleType(string message) : base(message) { } } public class ReplayTypeDoesNotExist : Exception { public ReplayTypeDoesNotExist(string message) : base(message) { } } public class ReplayDuplicateTypeName : Exception { public ReplayDuplicateTypeName(string message) : base(message) { } } public class ReplayDuplicateType : Exception { public ReplayDuplicateType(string message) : base(message) { } } public class ReplayTypeOverflow : Exception { public ReplayTypeOverflow(string message) : base(message) { } } } namespace ReplayRecorder.Net { internal static class ClientViewer { public enum MessageType { StartGame, EndGame, LiveBytes, Acknowledgement, Connected, FailedToConnect, InGameMessage, AckInGameMessage } public static TCPServer socket; public static Dictionary endPointToSteam; [MethodImpl(MethodImplOptions.NoInlining)] internal static void Init() { } static ClientViewer() { socket = new TCPServer(); endPointToSteam = new Dictionary(); TCPServer tCPServer = socket; tCPServer.onAccept = (TCPServer.OnAccept)Delegate.Combine(tCPServer.onAccept, new TCPServer.OnAccept(onAccept)); TCPServer tCPServer2 = socket; tCPServer2.onReceive = (TCPServer.OnReceive)Delegate.Combine(tCPServer2.onReceive, new TCPServer.OnReceive(onReceive)); TCPServer tCPServer3 = socket; tCPServer3.onClose = (TCPServer.OnClose)Delegate.Combine(tCPServer3.onClose, new TCPServer.OnClose(onClose)); TCPServer tCPServer4 = socket; tCPServer4.onDisconnect = (TCPServer.OnDisconnect)Delegate.Combine(tCPServer4.onDisconnect, new TCPServer.OnDisconnect(onDisconnect)); socket.Bind(new IPEndPoint(IPAddress.Any, 56759)); } private static void onAccept(EndPoint endPoint) { APILogger.Warn($"[ClientViewer] {endPoint} connected."); } private static void onReceive(ArraySegment buffer, EndPoint endPoint) { APILogger.Debug($"[ClientViewer] Received bytes '{buffer.Count}' from {endPoint}."); int index = 0; MessageType messageType = (MessageType)BitHelper.ReadUShort(buffer, ref index); APILogger.Debug($"[ClientViewer] Received message of type '{messageType}'."); switch (messageType) { case MessageType.Acknowledgement: { ulong num = BitHelper.ReadULong(buffer, ref index); APILogger.Debug($"[ClientViewer] Acknowledged: {endPoint}, connecting to {num}."); if (endPointToSteam.ContainsKey(endPoint)) { endPointToSteam[endPoint].Dispose(); endPointToSteam.Remove(endPoint); } endPointToSteam.Add(endPoint, new HostClient.Connection(num, endPoint)); break; } case MessageType.InGameMessage: if (endPointToSteam.ContainsKey(endPoint)) { BitHelper.WriteBytes((ushort)2, buffer, 0); endPointToSteam[endPoint].slave.Send(buffer); } break; default: APILogger.Error($"[ClientViewer] No behaviour defined for message of type '{messageType}'"); break; } } private static void onDisconnect(EndPoint endPoint) { APILogger.Warn($"[ClientViewer] {endPoint} disconnected."); if (endPointToSteam.ContainsKey(endPoint)) { endPointToSteam[endPoint].Dispose(); endPointToSteam.Remove(endPoint); } } private static void onClose() { foreach (HostClient.Connection value in endPointToSteam.Values) { value.Dispose(); } endPointToSteam.Clear(); } } [HarmonyPatch] internal static class HostClient { public class Connection : IDisposable { public rSteamClient main; public rSteamClient slave; public readonly EndPoint associatedEndPoint; public Connection(ulong host, EndPoint associatedEndPoint) { this.associatedEndPoint = associatedEndPoint; main = new rSteamClient(host, 10420, (SteamNetworkingConfigValue_t[]?)(object)new SteamNetworkingConfigValue_t[0], "MainClient"); slave = new rSteamClient(host, 10069, null, "SlaveClient"); rSteamClient rSteamClient = main; rSteamClient.onAccept = (rSteamClient.OnAccept)Delegate.Combine(rSteamClient.onAccept, new rSteamClient.OnAccept(main_onAccept)); rSteamClient rSteamClient2 = main; rSteamClient2.onFail = (rSteamClient.OnFail)Delegate.Combine(rSteamClient2.onFail, new rSteamClient.OnFail(main_onFail)); rSteamClient rSteamClient3 = main; rSteamClient3.onReceive = (rSteamClient.OnReceive)Delegate.Combine(rSteamClient3.onReceive, new rSteamClient.OnReceive(onReceive)); rSteamClient rSteamClient4 = slave; rSteamClient4.onAccept = (rSteamClient.OnAccept)Delegate.Combine(rSteamClient4.onAccept, new rSteamClient.OnAccept(slave_onAccept)); rSteamClient rSteamClient5 = slave; rSteamClient5.onReceive = (rSteamClient.OnReceive)Delegate.Combine(rSteamClient5.onReceive, new rSteamClient.OnReceive(onReceive)); } private void onReceive(ArraySegment buffer, rSteamClient client) { int index = 0; MessageType messageType = (MessageType)BitHelper.ReadUShort(buffer, ref index); APILogger.Debug($"[HostClient.Connection] Received message of type '{messageType}'."); if (messageType == MessageType.ForwardMessage) { APILogger.Debug($"[HostClient.Connection] Forwarded {buffer.Count - index} bytes."); ClientViewer.socket.RawSendTo(new ArraySegment(buffer.Array, buffer.Offset + index, buffer.Count - index), associatedEndPoint); } else { APILogger.Error($"[HostClient.Connection] No behaviour defined for message of type '{messageType}'"); } } private void main_onFail(rSteamClient client) { ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes(2, byteBuffer); BitHelper.WriteBytes((ushort)5, byteBuffer); ClientViewer.socket.RawSendTo(byteBuffer.Array, associatedEndPoint); } private void slave_onAccept(rSteamClient client) { ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes((ushort)1, byteBuffer); BitHelper.WriteBytes(SNet.LocalPlayer.NickName, byteBuffer); client.Send(byteBuffer.Array); } private void main_onAccept(rSteamClient client) { slave_onAccept(client); ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes(2, byteBuffer); BitHelper.WriteBytes((ushort)4, byteBuffer); ClientViewer.socket.RawSendTo(byteBuffer.Array, associatedEndPoint); } public void Dispose() { main.Dispose(); slave.Dispose(); } } internal static class Main { public static rSteamServer? socket; public static ConcurrentHashSet readyConnections = new ConcurrentHashSet(); private static ushort anonymous = 0; internal static void Init() { if (socket == null) { socket = new rSteamServer(10420, (SteamNetworkingConfigValue_t[]?)(object)new SteamNetworkingConfigValue_t[0], "MainServer"); rSteamServer? rSteamServer = socket; rSteamServer.onAccept = (rSteamServer.OnAccept)Delegate.Combine(rSteamServer.onAccept, new rSteamServer.OnAccept(onAccept)); rSteamServer? rSteamServer2 = socket; rSteamServer2.onDisconnect = (rSteamServer.OnDisconnect)Delegate.Combine(rSteamServer2.onDisconnect, new rSteamServer.OnDisconnect(onDisconnect)); rSteamServer? rSteamServer3 = socket; rSteamServer3.onClose = (rSteamServer.OnClose)Delegate.Combine(rSteamServer3.onClose, new rSteamServer.OnClose(onClose)); rSteamServer? rSteamServer4 = socket; rSteamServer4.onReceive = (rSteamServer.OnReceive)Delegate.Combine(rSteamServer4.onReceive, new rSteamServer.OnReceive(onReceive)); APILogger.Warn("Started Main Server"); } } private static void onAccept(HSteamNetConnection connection) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (socket != null) { ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(2, byteBuffer); BitHelper.WriteBytes((ushort)4, byteBuffer); socket.SendTo(connection, byteBuffer.Array); } } private static void onReceive(ArraySegment buffer, HSteamNetConnection connection) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) if (socket == null) { return; } int index = 0; MessageType messageType = (MessageType)BitHelper.ReadUShort(buffer, ref index); APILogger.Debug($"[MainServer] Received message of type '{messageType}'."); if (messageType == MessageType.Connected) { string text = Utils.RemoveHTMLTags(BitHelper.ReadString(buffer, ref index)).Trim(); if (text.Length == 0) { text = $"Spectator_{anonymous++}"; } if (text.Length > 25) { text = text.Substring(0, 25); } APILogger.Warn("[MainServer] " + text + " is spectating."); socket.currentConnections[connection].name = text; if ((Object)(object)SnapshotManager.instance != (Object)null) { SnapshotInstance instance = SnapshotManager.instance; if (instance.Active) { APILogger.Debug("[MainServer] Already in match, sending start signal"); ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(3, byteBuffer); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(instance.replayInstanceId, byteBuffer); socket.SendTo(connection, byteBuffer.Array); APILogger.Debug("[MainServer] Already in match, sending initial bytes:"); Task.Run(async delegate { if (socket == null) { return; } int byteOffset = instance.byteOffset; byte[] buffer2; do { instance.Flush(); using FileStream fileStream = new FileStream(instance.fullpath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using MemoryStream memoryStream = new MemoryStream(); fileStream.CopyTo(memoryStream); buffer2 = memoryStream.ToArray(); } while (buffer2.Length < byteOffset); try { int bytesSent = 0; while (bytesSent < buffer2.Length) { int num = Mathf.Min(buffer2.Length - bytesSent, 81903); ByteBuffer byteBuffer2 = new ByteBuffer(new byte[11 + num + 4]); BitHelper.WriteBytes((ushort)0, byteBuffer2); BitHelper.WriteBytes(11 + num, byteBuffer2); BitHelper.WriteBytes((ushort)2, byteBuffer2); BitHelper.WriteBytes(instance.replayInstanceId, byteBuffer2); BitHelper.WriteBytes(bytesSent, byteBuffer2); BitHelper.WriteBytes(num, byteBuffer2); BitHelper.WriteBytes(new ArraySegment(buffer2, bytesSent, num), byteBuffer2, includeCount: false); socket.SendTo(connection, byteBuffer2.Array); bytesSent += num; APILogger.Debug($"[MainServer] Sending init {bytesSent}/{buffer2.Length} ..."); if (bytesSent < buffer2.Length) { await Task.Delay(16); } } } catch (Exception value) { APILogger.Error($"[MainServer] Unable to send initial bytes: {value}"); } }); } } readyConnections.Add(connection); } else { APILogger.Error($"[MainServer] No behaviour defined for message of type '{messageType}'"); } } private static void onDisconnect(HSteamNetConnection connection) { //IL_0005: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) readyConnections.Remove(connection); if (socket == null || !socket.currentConnections.ContainsKey(connection)) { return; } APILogger.Warn("[MainServer] " + socket.currentConnections[connection].name + " is no longer spectating"); if (!((Object)(object)SnapshotManager.instance != (Object)null)) { return; } SnapshotManager.instance.spectators.Remove(connection); string text = "[" + socket.currentConnections[connection].name + "] is no longer spectating."; if (!ConfigManager.DisableLeaveJoinMessages) { while (text.Length > 50) { PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), text.Substring(0, 50).Trim(), (PlayerAgent)null); text = text.Substring(50).Trim(); } PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), text, (PlayerAgent)null); } } private static void onClose() { readyConnections.Clear(); } public static void Dispose() { socket?.Dispose(); socket = null; } [ReplayInit] private static void TriggerGameStart() { if (socket != null) { SnapshotInstance instance = SnapshotManager.instance; if ((Object)(object)instance == (Object)null) { APILogger.Error("Snapshot instance was not running, but tried to trigger network game start."); return; } ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(3, byteBuffer); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(instance.replayInstanceId, byteBuffer); socket.Send(byteBuffer.Array); } } [ReplayOnExpeditionEnd] private static void TriggerGameEnd() { if (socket != null) { ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(2, byteBuffer); BitHelper.WriteBytes((ushort)1, byteBuffer); socket.Send(byteBuffer.Array); } } } [HarmonyPatch] internal static class Slave { public static rSteamServer? socket; private static ushort anonymous; private static rSteamServer.Connection? spectatorMessageSender; internal static void Init() { if (socket == null) { socket = new rSteamServer(10069, null, "SlaveServer"); rSteamServer? rSteamServer = socket; rSteamServer.onReceive = (rSteamServer.OnReceive)Delegate.Combine(rSteamServer.onReceive, new rSteamServer.OnReceive(onReceive)); } } private static void onReceive(ArraySegment buffer, HSteamNetConnection connection) { //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if (socket == null) { return; } int index = 0; MessageType messageType = (MessageType)BitHelper.ReadUShort(buffer, ref index); APILogger.Debug($"[SlaveServer] Received message of type '{messageType}'."); switch (messageType) { case MessageType.Connected: { string text = Utils.RemoveHTMLTags(BitHelper.ReadString(buffer, ref index)).Trim(); if (text.Length == 0) { text = $"Spectator_{anonymous++}"; } if (text.Length > 25) { text = text.Substring(0, 25); } APILogger.Warn("[SlaveServer] Setup socket for " + text + "."); socket.currentConnections[connection].name = text; break; } case MessageType.InGameMessage: { if (!socket.currentConnections.TryGetValue(connection, out rSteamServer.Connection conn) || conn == null) { break; } ushort messageId = BitHelper.ReadUShort(buffer, ref index); int num = BitHelper.ReadUShort(buffer, ref index); if (num > 150) { break; } string message = Utils.RemoveHTMLTags(BitHelper.ReadString(buffer, num, ref index).Trim()); APILogger.Warn("[SlaveServer] Spectator Message > > " + conn.name + ": " + message); if (message.Length == 0 || ConfigManager.MuteChat) { break; } MainThread.Run(delegate { //IL_0149: Unknown result type (might be due to invalid IL or missing references) if (SNet.LocalPlayer.IsInLobby) { spectatorMessageSender = conn; int num2 = 49 - "<#3ef>> ".Length; message = "[" + conn.name + "] " + message; string[] array = message.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); StringBuilder stringBuilder = new StringBuilder(); int num3 = 0; while (num3 < array.Length) { string text2 = array[num3++]; while (text2.Length > num2) { PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), "<#3ef>> " + text2.Substring(0, num2), (PlayerAgent)null); text2 = text2.Substring(num2); } stringBuilder.Clear(); stringBuilder.Append(text2); while (num3 < array.Length && stringBuilder.Length + array[num3].Length < num2) { stringBuilder.Append(" "); stringBuilder.Append(array[num3++]); } PlayerChatManager.WantToSentTextMessage(PlayerManager.GetLocalPlayerAgent(), "<#3ef>> " + stringBuilder.ToString(), (PlayerAgent)null); } spectatorMessageSender = null; ByteBuffer byteBuffer = new ByteBuffer(); BitHelper.WriteBytes((ushort)0, byteBuffer); BitHelper.WriteBytes(4, byteBuffer); BitHelper.WriteBytes((ushort)7, byteBuffer); BitHelper.WriteBytes(messageId, byteBuffer); socket.SendTo(conn.connection, byteBuffer.Array); } }); break; } default: APILogger.Error($"[SlaveServer] No behaviour defined for message of type '{messageType}'"); break; } } public static void Dispose() { socket?.Dispose(); socket = null; } [HarmonyPatch(typeof(PlayerChatManager), "WantToSentTextMessage")] [HarmonyPrefix] private static void OnWantToSendMessage(PlayerAgent fromPlayer, string message, PlayerAgent toPlayer) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (fromPlayer.Owner.Lookup != SNet.LocalPlayer.Lookup || socket == null) { return; } ByteBuffer packet = new ByteBuffer(); BitHelper.WriteBytes((ushort)0, packet); packet.Reserve(4, increment: true); BitHelper.WriteBytes((ushort)6, packet); BitHelper.WriteBytes(fromPlayer.Owner.Lookup, packet); BitHelper.WriteBytes(message, packet); BitHelper.WriteBytes(packet.count - 2 - 4, packet.Array, 2); if (spectatorMessageSender == null) { Task.Run(delegate { socket.Send(packet.Array); }); return; } HSteamNetConnection _sender = spectatorMessageSender.connection; Task.Run(delegate { //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_001e: 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_0031: Unknown result type (might be due to invalid IL or missing references) foreach (HSteamNetConnection key in socket.currentConnections.Keys) { if (key != _sender) { socket.SendTo(key, packet.Array); } } }); } [ReplayPluginLoad] private static void OnLoad() { PlayerChatManager.OnIncomingChatMessage += IncomingMessageDeleagte.op_Implicit((Action)delegate(string msg, SNet_Player srcPlayer, SNet_Player dstPlayer) { if (srcPlayer.Lookup != SNet.LocalPlayer.Lookup && socket != null) { ByteBuffer packet = new ByteBuffer(); BitHelper.WriteBytes((ushort)0, packet); packet.Reserve(4, increment: true); BitHelper.WriteBytes((ushort)6, packet); BitHelper.WriteBytes(srcPlayer.Lookup, packet); BitHelper.WriteBytes(msg, packet); BitHelper.WriteBytes(packet.count - 2 - 4, packet.Array, 2); Task.Run(delegate { socket.Send(packet.Array); }); } }); } } public enum MessageType { ForwardMessage, Connected, InGameMessage } private const int mainVPort = 10420; private const int slaveVPort = 10069; [MethodImpl(MethodImplOptions.NoInlining)] internal static void Init() { } static HostClient() { rSteamworks.onInit = (rSteamworks.OnInit)Delegate.Combine(rSteamworks.onInit, new rSteamworks.OnInit(OnSteamworksInit)); rSteamworks.onDispose = (rSteamworks.OnDispose)Delegate.Combine(rSteamworks.onDispose, new rSteamworks.OnDispose(OnSteamworksDispose)); } private static void OnSteamworksInit() { Main.Init(); Slave.Init(); } private static void OnSteamworksDispose() { Main.Dispose(); Slave.Dispose(); } } } namespace ReplayRecorder.BepInEx { public static class Module { public const string GUID = "randomuserhi.ReplayRecorder"; public const string Name = "ReplayRecorder"; public const string Version = "0.0.1"; } public static class ConfigManager { public static ConfigFile configFile; private static FileSystemWatcher configWatcher; public static bool allowAnySpectator; public static List steamIDWhitelist; private static ConfigEntry debug; private static ConfigEntry performanceDebug; private static ConfigEntry debugDynamics; private static ConfigEntry debugTicks; private static ConfigEntry replayFolder; private static ConfigEntry replayFilename; private static ConfigEntry spectatorWhiteList; private static ConfigEntry whiteListFriends; private static ConfigEntry disableLeaveJoinMessages; private static ConfigEntry muteChat; private static ConfigEntry separateByRundown; private static ConfigEntry disableSteamAPI; public static bool Debug { get { return debug.Value; } set { debug.Value = value; } } public static bool PerformanceDebug { get { return performanceDebug.Value; } set { performanceDebug.Value = value; } } public static bool DebugDynamics { get { return debugDynamics.Value; } set { debugDynamics.Value = value; } } public static bool DebugTicks { get { return debugTicks.Value; } set { debugTicks.Value = value; } } public static string ReplayFolder { get { return replayFolder.Value; } set { replayFolder.Value = value; } } public static string ReplayFileName { get { return replayFilename.Value; } set { replayFilename.Value = value; } } public static string SpectatorWhiteList { get { return spectatorWhiteList.Value; } set { spectatorWhiteList.Value = value; } } public static bool WhiteListFriends { get { return whiteListFriends.Value; } set { whiteListFriends.Value = value; } } public static bool DisableLeaveJoinMessages { get { return disableLeaveJoinMessages.Value; } set { disableLeaveJoinMessages.Value = value; } } public static bool MuteChat { get { return muteChat.Value; } set { muteChat.Value = value; } } public static bool SeparateByRundown { get { return separateByRundown.Value; } set { separateByRundown.Value = value; } } public static bool DisableSteamAPI { get { return disableSteamAPI.Value; } set { disableSteamAPI.Value = value; } } static ConfigManager() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown allowAnySpectator = false; steamIDWhitelist = new List(); string text = "ReplayRecorder.cfg"; configFile = new ConfigFile(Path.Combine(Paths.ConfigPath, text), true); debug = configFile.Bind("Debug", "enable", false, "Enables debug messages when true."); debugTicks = configFile.Bind("Debug", "ticks", false, "Enables debug messages for each tick when true."); debugDynamics = configFile.Bind("Debug", "dynamics", false, "Enables debug messages for dynamics when true."); performanceDebug = configFile.Bind("Debug", "performanceDebug", false, "Shows the amount of memory buffers used and time taken to write a snapshot to disk. Useful to know the performance impact of this mod."); replayFolder = configFile.Bind("Settings", "replayFolder", "./Replays", "Location of which replays will be stored. If invalid, defaults to the games location."); replayFilename = configFile.Bind("Settings", "replayFilename", "{0} {1:yyyy-MM-dd HH-mm}", "Filename format of stored replays. Follows C# string format syntax with (0: Rundown + Level, 1: Date, 2: Level Name). If filename is invalid (contains invalid characters etc...) default name of 'replay' is used."); separateByRundown = configFile.Bind("Settings", "separateByRundown", false, "Will create a new folder for each rundown. E.g R1A1 replay will be stored in path/to/replay/folder/R1/R1A1"); spectatorWhiteList = configFile.Bind("Settings", "spectatorWhiteList", "", "Comma separated list of Steam64 IDs of players allowed to connect and spectate your games. A value of 'ALL' allows anyone to connect. You do not need to whitelist yourself."); whiteListFriends = configFile.Bind("Settings", "whiteListFriends", true, "When set to true, considers any steam friends as part of the spectatorWhiteList."); disableLeaveJoinMessages = configFile.Bind("Settings", "disableLeaveJoinMessages", false, "When set to true, disables the spectators join / leave messages."); muteChat = configFile.Bind("Settings", "muteChat", false, "When set to true, disables the spectator chat."); disableSteamAPI = configFile.Bind("Settings", "disableSteamAPI", false, "When set to true, disables steamAPI setup and thus disables the spectate feature. This is here to allow use with cracked versions."); configWatcher = new FileSystemWatcher(Paths.ConfigPath, text) { NotifyFilter = NotifyFilters.LastWrite, EnableRaisingEvents = true }; configWatcher.Changed += async delegate { configWatcher.EnableRaisingEvents = false; await Task.Delay(500); APILogger.Warn("Reloading config..."); configFile.Reload(); LoadSpectatorWhiteList(); await Task.Delay(500); configWatcher.EnableRaisingEvents = true; }; LoadSpectatorWhiteList(); } private static void LoadSpectatorWhiteList() { steamIDWhitelist.Clear(); if (SpectatorWhiteList.Trim().ToLower() != "all") { string[] array = SpectatorWhiteList.Split(","); foreach (string text in array) { if (ulong.TryParse(text.Trim(), out var result)) { steamIDWhitelist.Add(result); } else { APILogger.Error("Unable to parse Steam64 ID: " + text); } } } else { allowAnySpectator = true; } } } [BepInPlugin("randomuserhi.ReplayRecorder", "ReplayRecorder", "0.0.1")] public class Plugin : BasePlugin { private static Harmony? harmony; public override void Load() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown APILogger.Log("Plugin is loaded!"); harmony = new Harmony("randomuserhi.ReplayRecorder"); harmony.PatchAll(); APILogger.Log("Debug is " + (ConfigManager.Debug ? "Enabled" : "Disabled")); ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); Replay.RegisterAll(); RundownManager.OnExpeditionGameplayStarted += Action.op_Implicit((Action)OnGameplayStart); RNet.Init(); ClientViewer.Init(); HostClient.Init(); } private static void OnGameplayStart() { Replay.OnGameplayStart?.Invoke(); } } } namespace ReplayRecorder.Exceptions { public class ReplayTypeNotCompatible : Exception { public ReplayTypeNotCompatible(string message) : base(message) { } } } namespace ReplayRecorder.Core { public abstract class Id : ReplayEvent { public readonly int id; public override string? Debug => $"{id}"; public Id(int id) { this.id = id; } public override void Write(ByteBuffer buffer) { BitHelper.WriteBytes(id, buffer); } } public struct AgentTransform : IReplayTransform { private Agent agent; public bool active => (Object)(object)agent != (Object)null; public byte dimensionIndex => (byte)agent.m_dimensionIndex; public Vector3 position => ((Component)agent).transform.position; public Quaternion rotation { get { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)agent).transform.rotation * Vector3.forward; val.y = 0f; return Quaternion.LookRotation(val); } } public AgentTransform(Agent agent) { this.agent = agent; } } public abstract class DynamicPosition : ReplayDynamic { public IReplayTransform transform; private Vector3 oldPosition; private Vector3 calculatedPosition; private byte oldDimensionIndex; private const float threshold = 50f; private Vector3 spawnPosition; private byte spawnDimensionIndex; public override string? Debug => $"{id} - [{transform.dimensionIndex}] ({transform.position.x}, {transform.position.y}, {transform.position.z})"; public override bool Active => transform.active; public override bool IsDirty { get { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (transform.dimensionIndex == oldDimensionIndex) { return transform.position != oldPosition; } return true; } } public DynamicPosition(int id, IReplayTransform transform) : base(id) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) this.transform = transform; spawnPosition = transform.position; spawnDimensionIndex = transform.dimensionIndex; } public override void Write(ByteBuffer buffer) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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_00ce: Unknown result type (might be due to invalid IL or missing references) base.Write(buffer); BitHelper.WriteBytes(transform.dimensionIndex, buffer); Vector3 val = transform.position - oldPosition; ((Vector3)(ref val))..ctor(BitHelper.Quantize(val.x), BitHelper.Quantize(val.y), BitHelper.Quantize(val.z)); calculatedPosition += val; if (!(((Vector3)(ref val)).sqrMagnitude > 2500f)) { Vector3 val2 = transform.position - calculatedPosition; if (!(((Vector3)(ref val2)).sqrMagnitude > 1f)) { BitHelper.WriteBytes((byte)0, buffer); BitHelper.WriteHalf(val, buffer); goto IL_00d5; } } BitHelper.WriteBytes((byte)1, buffer); BitHelper.WriteBytes(transform.position, buffer); calculatedPosition = transform.position; goto IL_00d5; IL_00d5: oldDimensionIndex = transform.dimensionIndex; oldPosition = transform.position; } public override void Spawn(ByteBuffer buffer) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) BitHelper.WriteBytes(spawnDimensionIndex, buffer); BitHelper.WriteBytes(spawnPosition, buffer); oldDimensionIndex = spawnDimensionIndex; oldPosition = spawnPosition; calculatedPosition = oldPosition; } } public abstract class DynamicRotation : ReplayDynamic { public IReplayTransform transform; private Quaternion oldRotation; private Quaternion spawnRotation; public override string? Debug => $"{id} - ({transform.rotation.x}, {transform.rotation.y}, {transform.rotation.z}, {transform.rotation.w})"; public override bool Active => transform.active; public override bool IsDirty => transform.rotation != oldRotation; public DynamicRotation(int id, IReplayTransform transform) : base(id) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) this.transform = transform; spawnRotation = transform.rotation; } public override void Write(ByteBuffer buffer) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0092: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) base.Write(buffer); if (float.IsNaN(transform.rotation.x) || float.IsNaN(transform.rotation.y) || float.IsNaN(transform.rotation.z) || float.IsNaN(transform.rotation.w)) { BitHelper.WriteHalf(Quaternion.identity, buffer); APILogger.Warn("Dynamic rotation had NaN component."); } else { BitHelper.WriteHalf(transform.rotation, buffer); } oldRotation = transform.rotation; } public override void Spawn(ByteBuffer buffer) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (float.IsNaN(spawnRotation.x) || float.IsNaN(spawnRotation.y) || float.IsNaN(spawnRotation.z) || float.IsNaN(spawnRotation.w)) { BitHelper.WriteHalf(Quaternion.identity, buffer); APILogger.Warn("Dynamic rotation had NaN component."); } else { BitHelper.WriteHalf(spawnRotation, buffer); } oldRotation = spawnRotation; } } public abstract class DynamicTransform : ReplayDynamic { public IReplayTransform transform; private Vector3 oldPosition; private Vector3 calculatedPosition; private Quaternion oldRotation; private byte oldDimensionIndex; private const float threshold = 1000f; private Vector3 spawnPosition; private Quaternion spawnRotation; private byte spawnDimensionIndex; public override string? Debug => $"{id} - [{transform.dimensionIndex}] ({transform.position.x}, {transform.position.y}, {transform.position.z}) ({transform.rotation.x}, {transform.rotation.y}, {transform.rotation.z}, {transform.rotation.w})"; public override bool Active => transform.active; public override bool IsDirty { get { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0037: Unknown result type (might be due to invalid IL or missing references) if (transform.dimensionIndex == oldDimensionIndex && !(transform.position != oldPosition)) { return transform.rotation != oldRotation; } return true; } } public DynamicTransform(int id, IReplayTransform transform) : base(id) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) this.transform = transform; spawnPosition = transform.position; spawnRotation = transform.rotation; spawnDimensionIndex = transform.dimensionIndex; } public override void Write(ByteBuffer buffer) { //IL_0017: 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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //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) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) BitHelper.WriteBytes(transform.dimensionIndex, buffer); Vector3 val = transform.position - oldPosition; ((Vector3)(ref val))..ctor(BitHelper.Quantize(val.x), BitHelper.Quantize(val.y), BitHelper.Quantize(val.z)); calculatedPosition += val; if (!(((Vector3)(ref val)).sqrMagnitude > 1000000f)) { Vector3 val2 = transform.position - calculatedPosition; if (!(((Vector3)(ref val2)).sqrMagnitude > 1f)) { BitHelper.WriteBytes((byte)0, buffer); BitHelper.WriteHalf(val, buffer); goto IL_00ce; } } BitHelper.WriteBytes((byte)1, buffer); BitHelper.WriteBytes(transform.position, buffer); calculatedPosition = transform.position; goto IL_00ce; IL_00ce: if (float.IsNaN(transform.rotation.x) || float.IsNaN(transform.rotation.y) || float.IsNaN(transform.rotation.z) || float.IsNaN(transform.rotation.w)) { BitHelper.WriteHalf(Quaternion.identity, buffer); APILogger.Warn("Dynamic rotation had NaN component."); } else { BitHelper.WriteHalf(transform.rotation, buffer); } oldDimensionIndex = transform.dimensionIndex; oldPosition = transform.position; oldRotation = transform.rotation; } public override void Spawn(ByteBuffer buffer) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) BitHelper.WriteBytes(spawnDimensionIndex, buffer); BitHelper.WriteBytes(spawnPosition, buffer); if (float.IsNaN(spawnRotation.x) || float.IsNaN(spawnRotation.y) || float.IsNaN(spawnRotation.z) || float.IsNaN(spawnRotation.w)) { BitHelper.WriteHalf(Quaternion.identity, buffer); APILogger.Warn("Dynamic rotation had NaN component."); } else { BitHelper.WriteHalf(spawnRotation, buffer); } oldDimensionIndex = spawnDimensionIndex; oldPosition = spawnPosition; calculatedPosition = oldPosition; oldRotation = spawnRotation; } } [ReplayData("ReplayRecorder.EndOfHeader", "0.0.1")] internal class EndOfHeader : ReplayEvent { public override void Write(ByteBuffer buffer) { BitHelper.WriteBytes(SnapshotManager.types[typeof(EndOfHeader)], buffer); } } [ReplayData("ReplayRecorder.Header", "0.0.1")] internal class HeaderData : ReplayHeader { [ReplayInit] private static void Init() { Replay.Trigger(new HeaderData()); } public override void Write(ByteBuffer buffer) { BitHelper.WriteBytes("0.0.1", buffer); BitHelper.WriteBytes(SNet.IsMaster, buffer); } } [HarmonyPatch] public static class ReplayPlayerManager { [HarmonyPatch] private static class SpawningPatches { [HarmonyPatch(typeof(PlayerSync), "OnSpawn")] [HarmonyPostfix] [HarmonyPriority(0)] private static void OnSpawn(PlayerSync __instance) { Spawn(__instance.m_agent); } [HarmonyPatch(typeof(PlayerSync), "OnDespawn")] [HarmonyPostfix] [HarmonyPriority(0)] private static void OnDespawn(PlayerSync __instance) { Despawn(__instance.m_agent); } } [ReplayData("ReplayRecorder.Player", "0.0.1")] internal class rPlayer : ReplayDynamic { public PlayerAgent agent; public ulong snet; public string name; private bool hasReplayMod; public bool _hasReplayMod; private bool isMaster; private bool _isMaster => SNet.Master.Lookup == snet; public override bool Active { get { if ((Object)(object)agent != (Object)null) { return (Object)(object)agent.Owner != (Object)null; } return false; } } public override bool IsDirty { get { if (hasReplayMod == _hasReplayMod) { return isMaster != _isMaster; } return true; } } public rPlayer(PlayerAgent player, bool hasReplayMod = false) : base(((Agent)player).GlobalID) { snet = player.Owner.Lookup; name = player.Owner.NickName; agent = player; this.hasReplayMod = hasReplayMod; } public override void Write(ByteBuffer buffer) { hasReplayMod = _hasReplayMod; isMaster = _isMaster; BitHelper.WriteBytes(hasReplayMod, buffer); BitHelper.WriteBytes(isMaster, buffer); } public override void Spawn(ByteBuffer buffer) { BitHelper.WriteBytes(agent.Owner.Lookup, buffer); BitHelper.WriteBytes(agent.Owner.NickName, buffer); } } private static ByteBuffer pingPacket; private static HashSet hasReplayModButWaiting; private static float broadcastTimer; public static List playersWithReplayMod; private static List players; private static List _players; public static Action? OnPlayerSpawn; public static Action? OnPlayerDespawn; static ReplayPlayerManager() { pingPacket = new ByteBuffer(); hasReplayModButWaiting = new HashSet(); broadcastTimer = 0f; playersWithReplayMod = new List(); players = new List(); _players = new List(); BitHelper.WriteBytes((byte)0, pingPacket); ReplayRecorder.SNetUtils.SNetUtils.OnReceive = (Action, ulong>)Delegate.Combine(ReplayRecorder.SNetUtils.SNetUtils.OnReceive, new Action, ulong>(Receive)); } [ReplayInit] private static void Init() { hasReplayModButWaiting.Clear(); } private static void Receive(ArraySegment packet, ulong from) { int index = 0; if (BitHelper.ReadByte(packet, ref index) != 0) { return; } rPlayer player = players.FirstOrDefault((rPlayer p) => p != null && p.snet == from, null); rPlayer rPlayer = player; object obj; if ((object)rPlayer == null) { obj = null; } else { PlayerAgent agent = rPlayer.agent; obj = ((agent != null) ? agent.Owner : null); } if ((Object)obj == (Object)null) { hasReplayModButWaiting.Add(from); return; } playersWithReplayMod.RemoveAll(Predicate.op_Implicit((Func)((SNet_Player p) => p.Lookup == player.snet))); playersWithReplayMod.Add(player.agent.Owner); player._hasReplayMod = true; } [ReplayOnHeaderCompletion] private static void OnHeaderCompletion() { players.Clear(); Enumerator enumerator = PlayerManager.PlayerAgentsInLevel.GetEnumerator(); while (enumerator.MoveNext()) { Spawn(enumerator.Current); } } [ReplayTick] private static void Tick() { PlayerAgent[] array = Il2CppArrayBase.op_Implicit(PlayerManager.PlayerAgentsInLevel.ToArray()); _players.Clear(); foreach (rPlayer player in players) { if ((Object)(object)player.agent == (Object)null || (Object)(object)player.agent.Owner == (Object)null || !array.Any((PlayerAgent p) => ((Agent)p).GlobalID == player.id)) { Despawn(player); } else { _players.Add(player); } } List list = players; players = _players; _players = list; bool flag = Clock.Time > broadcastTimer; if (flag) { broadcastTimer = Clock.Time + 5f; } PlayerAgent[] array2 = array; foreach (PlayerAgent player2 in array2) { if (!players.Any((rPlayer p) => p.id == ((Agent)player2).GlobalID)) { Spawn(player2); } if (flag && player2.Owner.Lookup != SNet.LocalPlayer.Lookup) { ReplayRecorder.SNetUtils.SNetUtils._playerBuff.Clear(); ReplayRecorder.SNetUtils.SNetUtils._playerBuff.Add(player2.Owner); ReplayRecorder.SNetUtils.SNetUtils.SendBytes(pingPacket.Array, ReplayRecorder.SNetUtils.SNetUtils._playerBuff); } } } private static void Spawn(PlayerAgent agent) { if (Replay.Ready) { RemoveAll(agent); APILogger.Debug("(SpawnPlayer) " + agent.Owner.NickName + " has joined."); rPlayer rPlayer = new rPlayer(agent); if (agent.Owner.Lookup == SNet.LocalPlayer.Lookup) { rPlayer._hasReplayMod = true; } Replay.Spawn(rPlayer, hasReplayModButWaiting.Remove(agent.Owner.Lookup)); players.Add(rPlayer); OnPlayerSpawn?.Invoke(rPlayer.agent); } } private static void Despawn(PlayerAgent agent) { if (Replay.Ready && Replay.Has(((Agent)agent).GlobalID)) { RemoveAll(agent); } } private static void RemoveAll(PlayerAgent agent) { PlayerAgent agent2 = agent; players.RemoveAll(delegate(rPlayer player) { int num; if (!((Object)(object)player.agent == (Object)null) && !((Object)(object)player.agent.Owner == (Object)null)) { num = ((player.snet == agent2.Owner.Lookup) ? 1 : 0); if (num == 0) { goto IL_0045; } } else { num = 1; } Despawn(player); goto IL_0045; IL_0045: return (byte)num != 0; }); } private static void Despawn(rPlayer player) { rPlayer player2 = player; APILogger.Debug("Despawned player " + player2.name + "."); Replay.TryDespawn(player2.id); playersWithReplayMod.RemoveAll(Predicate.op_Implicit((Func)((SNet_Player p) => (Object)(object)p == (Object)null || p.Lookup == player2.snet))); OnPlayerDespawn?.Invoke(player2.id); } } } namespace ReplayRecorder.API { [ReplayData("ReplayRecorder.Spawn", "0.0.1")] internal class ReplaySpawn : ReplayEvent { private ReplayDynamic dynamic; private ushort type; public override string? Debug => $"{dynamic.GetType().FullName}({type}) {dynamic.id}"; public ReplaySpawn(ReplayDynamic dynamic) { this.dynamic = dynamic; type = SnapshotManager.types[dynamic.GetType()]; } public override void Write(ByteBuffer buffer) { BitHelper.WriteBytes(type, buffer); BitHelper.WriteBytes(dynamic.id, buffer); dynamic.Spawn(buffer); } } [ReplayData("ReplayRecorder.Despawn", "0.0.1")] internal class ReplayDespawn : ReplayEvent { private ReplayDynamic dynamic; private ushort type; public override string? Debug => $"{dynamic.GetType().FullName}({type}) {dynamic.id}"; public ReplayDespawn(ReplayDynamic dynamic) { this.dynamic = dynamic; type = SnapshotManager.types[dynamic.GetType()]; } public override void Write(ByteBuffer buffer) { BitHelper.WriteBytes(type, buffer); BitHelper.WriteBytes(dynamic.id, buffer); dynamic.Despawn(buffer); } } public abstract class ReplayDynamic : IEquatable { internal bool remove; public readonly int id; public virtual string? Debug => null; public abstract bool IsDirty { get; } public abstract bool Active { get; } public ReplayDynamic(int id) { this.id = id; } internal virtual void _Write(ByteBuffer buffer) { BitHelper.WriteBytes(id, buffer); } public virtual void Write(ByteBuffer buffer) { } public virtual void Spawn(ByteBuffer buffer) { } public virtual void Despawn(ByteBuffer buffer) { } public static bool operator ==(ReplayDynamic? lhs, ReplayDynamic? rhs) { if ((object)lhs == null) { if ((object)rhs == null) { return true; } return false; } return lhs.Equals(rhs); } public static bool operator !=(ReplayDynamic? lhs, ReplayDynamic? rhs) { return !(lhs == rhs); } public override bool Equals(object? obj) { return Equals(obj as ReplayDynamic); } public bool Equals(ReplayDynamic? other) { if ((object)other == null) { return false; } if ((object)this == other) { return true; } if (GetType() != other.GetType()) { return false; } return id == other.id; } public override int GetHashCode() { return GetType().GetHashCode() ^ id.GetHashCode(); } } public abstract class ReplayEvent { public virtual string? Debug => null; public virtual void Write(ByteBuffer buffer) { } } public abstract class ReplayHeader { public virtual string? Debug => null; public abstract void Write(ByteBuffer buffer); } public interface IReplayTransform { bool active { get; } byte dimensionIndex { get; } Vector3 position { get; } Quaternion rotation { get; } } } namespace ReplayRecorder.API.Attributes { [AttributeUsage(AttributeTargets.Class)] public class ReplayData : Attribute { public string Typename { get; private set; } public string Version { get; private set; } private string Clean(string typename) { return typename.Replace(" ", "").Trim(); } public ReplayData(string typename, string version) { Typename = Clean(typename); Version = Clean(version); } } [AttributeUsage(AttributeTargets.Method)] public class ReplayHook : Attribute { public Type type { get; set; } public bool triggerOnlyWhenDirty { get; set; } public ReplayHook(Type type, bool triggerOnlyWhenDirty = true) { this.type = type; this.triggerOnlyWhenDirty = triggerOnlyWhenDirty; } } [AttributeUsage(AttributeTargets.Method)] public class ReplaySpawnHook : Attribute { public Type type { get; set; } public ReplaySpawnHook(Type type) { this.type = type; } } [AttributeUsage(AttributeTargets.Method)] public class ReplayDespawnHook : Attribute { public Type type { get; set; } public ReplayDespawnHook(Type type) { this.type = type; } } [AttributeUsage(AttributeTargets.Method)] public class ReplayInit : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayOnElevatorStop : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayOnExpeditionEnd : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayOnGameplayStart : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayOnHeaderCompletion : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayOnPlayerDespawn : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayOnPlayerSpawn : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayPluginLoad : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class ReplayTick : Attribute { } }