Decompiled source of BrutalistickCoop v0.5.1

Mods/BrutalistickCoop.dll

Decompiled 8 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BrutalistickCoop;
using Il2Cpp;
using Il2CppHurricaneVR.Framework.Core.Grabbers;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(CoopMod), "BrutalistickCoop", "0.5.1", "Aidan", null)]
[assembly: MelonGame("Kubunautilus", "BrutalistickVR")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0")]
[assembly: AssemblyCompany("BrutalistickCoop")]
[assembly: AssemblyProduct("BrutalistickCoop")]
[assembly: AssemblyTitle("BrutalistickCoop")]
[assembly: AssemblyFileVersion("0.5.1.0")]
[assembly: AssemblyInformationalVersion("0.5.1")]
[assembly: AssemblyVersion("0.5.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BrutalistickCoop
{
	public sealed class CoopMod : MelonMod
	{
		public const string Version = "0.5.1";

		private const float SendInterval = 1f / 30f;

		private const float RigSearchInterval = 1f;

		private const float DisconnectTimeout = 5f;

		private MelonPreferences_Entry<bool> _enabled;

		private MelonPreferences_Entry<bool> _voiceEnabled;

		private readonly SteamP2P _transport = new SteamP2P();

		private readonly SpawnedObjectSync _objectSync = new SpawnedObjectSync();

		private readonly VoiceChat _voiceChat = new VoiceChat();

		private SteamLobby _lobby;

		private RemoteAvatar _remoteAvatar;

		private bool _showRoomMenu = true;

		private bool _showUpdaterWarning;

		private bool _updaterWarningChecked;

		private Transform _head;

		private Transform _leftHand;

		private Transform _rightHand;

		private ulong _activePeer;

		private uint _sequence;

		private uint _lastRemoteSequence;

		private float _nextSendTime;

		private float _nextRigSearchTime;

		private float _lastReceiveTime = float.NegativeInfinity;

		public override void OnInitializeMelon()
		{
			MelonPreferences_Category val = MelonPreferences.CreateCategory("BrutalistickCoop", "BRUTALISTICK Co-op");
			_enabled = val.CreateEntry<bool>("Enabled", true, "Enable two-player pose synchronization", (string)null, false, false, (ValueValidator)null, (string)null);
			_voiceEnabled = val.CreateEntry<bool>("VoiceChat", true, "Enable room voice chat using the default microphone", (string)null, false, false, (ValueValidator)null, (string)null);
			_lobby = new SteamLobby(delegate(string message)
			{
				((MelonBase)this).LoggerInstance.Msg(message);
			});
			((MelonBase)this).LoggerInstance.Msg("Loaded rooms, visible players/items, and room voice chat.");
			((MelonBase)this).LoggerInstance.Msg("The room menu opens automatically. Press F6 to toggle it.");
		}

		public override void OnSceneWasLoaded(int buildIndex, string sceneName)
		{
			ResetSceneState();
			((MelonBase)this).LoggerInstance.Msg("Scene loaded: " + sceneName + ". Searching for the VR rig.");
		}

		public override void OnUpdate()
		{
			MelonPreferences_Entry<bool> enabled = _enabled;
			if (enabled == null || !enabled.Value)
			{
				Disconnect();
				return;
			}
			Keyboard current = Keyboard.current;
			if (current != null && ((ButtonControl)current.f6Key).wasPressedThisFrame)
			{
				_showRoomMenu = !_showRoomMenu;
			}
			_lobby.Tick(Time.unscaledTime);
			RefreshConnection();
			if (Time.unscaledTime >= _nextRigSearchTime)
			{
				FindLocalRig();
				_nextRigSearchTime = Time.unscaledTime + 1f;
			}
			if (_transport.IsReady)
			{
				ReceivePackets();
				if (Time.unscaledTime >= _nextSendTime)
				{
					SendPose();
					_nextSendTime = Time.unscaledTime + 1f / 30f;
				}
				_objectSync.Tick(Time.unscaledTime, SendObjectPacket, SendReliableObjectPacket);
				_voiceChat.Tick(_voiceEnabled?.Value ?? false, _remoteAvatar?.HeadTransform, SendVoicePacket);
			}
			else
			{
				_voiceChat.Reset();
			}
			_remoteAvatar?.Tick();
			if (_remoteAvatar != null && Time.unscaledTime - _lastReceiveTime > 5f)
			{
				_remoteAvatar.Hide();
			}
		}

		public override void OnDeinitializeMelon()
		{
			Disconnect();
			_lobby?.Dispose();
			_objectSync.Dispose();
			_voiceChat.Dispose();
			_transport.Dispose();
		}

		public override void OnGUI()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: 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_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_033a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0376: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dd: 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_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0488: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c1: Unknown result type (might be due to invalid IL or missing references)
			if (_showUpdaterWarning)
			{
				DrawUpdaterWarning();
			}
			if (!_showRoomMenu || _lobby == null)
			{
				return;
			}
			float num = 30f;
			float num2 = 30f;
			Rect val = new Rect(num, num2, 430f, 480f);
			Color color = GUI.color;
			GUI.color = new Color(0.025f, 0.03f, 0.04f, 0.97f);
			GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
			GUI.Box(val, "BRUTALISTICK CO-OP ROOMS");
			num2 += 35f;
			GUI.Label(new Rect(num + 15f, num2, 400f, 30f), _lobby.Status);
			num2 += 38f;
			if (!_lobby.IsInLobby)
			{
				if (GUI.Button(new Rect(num + 15f, num2, 190f, 30f), "Host public room"))
				{
					_lobby.CreatePublicRoom();
				}
				if (GUI.Button(new Rect(num + 220f, num2, 190f, 30f), "Host private room"))
				{
					_lobby.CreatePrivateRoom();
				}
				num2 += 38f;
				if (GUI.Button(new Rect(num + 15f, num2, 395f, 30f), "Refresh public rooms"))
				{
					_lobby.RefreshRooms();
				}
				num2 += 38f;
				if (GUI.Button(new Rect(num + 15f, num2, 395f, 30f), "Join room code from clipboard"))
				{
					_lobby.JoinRoomCode(GUIUtility.systemCopyBuffer);
				}
				num2 += 42f;
				GUI.Label(new Rect(num + 15f, num2, 395f, 30f), "PUBLIC ROOMS");
				num2 += 28f;
				int num3 = Math.Min(_lobby.Rooms.Count, 5);
				for (int i = 0; i < num3; i++)
				{
					RoomInfo roomInfo = _lobby.Rooms[i];
					GUI.Label(new Rect(num + 15f, num2, 280f, 30f), $"{roomInfo.Name} ({roomInfo.Members}/2)");
					if (GUI.Button(new Rect(num + 305f, num2, 105f, 30f), "Join"))
					{
						_lobby.JoinRoom(roomInfo.LobbyId);
					}
					num2 += 34f;
				}
			}
			else
			{
				GUI.Label(new Rect(num + 15f, num2, 395f, 30f), (_lobby.IsPublicRoom ? "Public" : "Private") + " room code: " + _lobby.RoomCode);
				num2 += 38f;
				if (GUI.Button(new Rect(num + 15f, num2, 190f, 30f), "Copy room code"))
				{
					GUIUtility.systemCopyBuffer = _lobby.RoomCode;
				}
				if (GUI.Button(new Rect(num + 220f, num2, 190f, 30f), "Leave room"))
				{
					_lobby.LeaveRoom();
					Disconnect();
				}
				num2 += 42f;
				GUI.Label(new Rect(num + 15f, num2, 395f, 30f), (_lobby.PeerSteamId == 0L) ? "Waiting for another player to join..." : $"Player connected: {_lobby.PeerSteamId}");
				num2 += 34f;
				GUI.Label(new Rect(num + 15f, num2, 395f, 30f), $"Remote spawned objects: {_objectSync.RemoteObjectCount} (waiting: {_objectSync.PendingSpawnCount})");
				num2 += 34f;
				MelonPreferences_Entry<bool> voiceEnabled = _voiceEnabled;
				string text = ((voiceEnabled != null && voiceEnabled.Value) ? "Mute microphone" : "Enable microphone");
				if (GUI.Button(new Rect(num + 15f, num2, 190f, 30f), text))
				{
					_voiceEnabled.Value = !_voiceEnabled.Value;
				}
				GUI.Label(new Rect(num + 220f, num2, 190f, 30f), _voiceChat.Status);
			}
			GUI.Label(new Rect(num + 15f, 470f, 395f, 30f), "Press F6 to close this menu.");
		}

		private void RefreshConnection()
		{
			ulong num = _lobby?.PeerSteamId ?? 0;
			if (num == 0L)
			{
				if (_activePeer != 0L)
				{
					Disconnect();
				}
			}
			else
			{
				if (num == _activePeer && _transport.IsReady)
				{
					return;
				}
				Disconnect();
				try
				{
					if (!_transport.Open(num))
					{
						((MelonBase)this).LoggerInstance.Error("Steam P2P could not open. Check that Steam is running.");
						return;
					}
					_activePeer = num;
					CheckUpdaterWarning();
					((MelonBase)this).LoggerInstance.Msg($"Steam P2P enabled for peer {num}.");
				}
				catch (Exception value)
				{
					((MelonBase)this).LoggerInstance.Error($"Steam P2P initialization failed: {value}");
				}
			}
		}

		private void FindLocalRig()
		{
			try
			{
				if ((Object)(object)_head == (Object)null)
				{
					GameObject playerHeadStatic = PlayerStaticReferences.playerHeadStatic;
					object head;
					if (!((Object)(object)playerHeadStatic != (Object)null))
					{
						Camera main = Camera.main;
						head = ((main != null) ? ((Component)main).transform : null);
					}
					else
					{
						head = playerHeadStatic.transform;
					}
					_head = (Transform)head;
				}
				if ((Object)(object)_leftHand == (Object)null || (Object)(object)_rightHand == (Object)null)
				{
					FistPunchManager val = Object.FindObjectOfType<FistPunchManager>();
					if ((Object)(object)val != (Object)null)
					{
						HVRHandGrabber leftHandGrabber = val.leftHandGrabber;
						_leftHand = ((leftHandGrabber != null) ? ((Component)leftHandGrabber).transform : null);
						HVRHandGrabber rightHandGrabber = val.rightHandGrabber;
						_rightHand = ((rightHandGrabber != null) ? ((Component)rightHandGrabber).transform : null);
					}
				}
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("VR rig lookup failed: " + ex.Message);
			}
		}

		private void SendPose()
		{
			if ((Object)(object)_head == (Object)null || (Object)(object)_leftHand == (Object)null || (Object)(object)_rightHand == (Object)null)
			{
				return;
			}
			PlayerPose pose = new PlayerPose(++_sequence, Capture(_head), Capture(_leftHand), Capture(_rightHand));
			try
			{
				_transport.Send(PosePacket.Encode(pose));
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Pose send failed: " + ex.Message);
			}
		}

		private void ReceivePackets()
		{
			try
			{
				int num = 0;
				byte[] packet;
				while (num++ < 128 && _transport.TryReceive(out packet))
				{
					NetworkObjectMessage message;
					VoiceFrame frame;
					if (PosePacket.TryDecode(packet, out var pose))
					{
						if (_lastRemoteSequence == 0 || IsNewer(pose.Sequence, _lastRemoteSequence))
						{
							_lastRemoteSequence = pose.Sequence;
							_lastReceiveTime = Time.unscaledTime;
							if (_remoteAvatar == null)
							{
								_remoteAvatar = new RemoteAvatar();
							}
							_remoteAvatar.SetTarget(pose);
						}
					}
					else if (ObjectPacket.TryDecode(packet, out message))
					{
						_objectSync.HandleMessage(message);
					}
					else if (VoicePacket.TryDecode(packet, out frame))
					{
						_voiceChat.HandleFrame(frame, _remoteAvatar?.HeadTransform);
					}
				}
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Network receive failed: " + ex.Message);
			}
		}

		private void SendObjectPacket(byte[] packet)
		{
			try
			{
				_transport.Send(packet);
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Object transform send failed: " + ex.Message);
			}
		}

		private void SendReliableObjectPacket(byte[] packet)
		{
			try
			{
				_transport.SendReliablePacket(packet);
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Object event send failed: " + ex.Message);
			}
		}

		private void SendVoicePacket(byte[] packet)
		{
			try
			{
				_transport.Send(packet);
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Voice send failed: " + ex.Message);
			}
		}

		private void ResetSceneState()
		{
			_head = null;
			_leftHand = null;
			_rightHand = null;
			_nextRigSearchTime = 0f;
			_lastReceiveTime = float.NegativeInfinity;
			_lastRemoteSequence = 0u;
			_remoteAvatar?.Dispose();
			_remoteAvatar = null;
			_objectSync.Reset();
			_voiceChat.Reset();
		}

		private void Disconnect()
		{
			if (_activePeer != 0L)
			{
				((MelonBase)this).LoggerInstance.Msg("Steam P2P session closed.");
			}
			_transport.Close();
			_activePeer = 0uL;
			_lastRemoteSequence = 0u;
			_remoteAvatar?.Hide();
			_objectSync.Reset();
			_voiceChat.Reset();
			_showUpdaterWarning = false;
			_updaterWarningChecked = false;
		}

		private void CheckUpdaterWarning()
		{
			if (!_updaterWarningChecked)
			{
				_updaterWarningChecked = true;
				string path = Path.Combine(MelonEnvironment.PluginsDirectory, "BrutalistickCoopUpdater.dll");
				_showUpdaterWarning = !File.Exists(path);
				if (_showUpdaterWarning)
				{
					((MelonBase)this).LoggerInstance.Warning("BrutalistickCoopUpdater.dll is not installed. Automatic multiplayer updates are unavailable.");
				}
			}
		}

		private void DrawUpdaterWarning()
		{
			//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)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: 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)
			float num = ((float)Screen.width - 520f) * 0.5f;
			float num2 = ((float)Screen.height - 230f) * 0.5f;
			Rect val = new Rect(num, num2, 520f, 230f);
			Color color = GUI.color;
			GUI.color = new Color(0.12f, 0.015f, 0.02f, 0.98f);
			GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
			GUI.Box(val, "AUTO-UPDATER NOT INSTALLED");
			GUI.Label(new Rect(num + 25f, num2 + 48f, 470f, 80f), "You joined without BrutalistickCoopUpdater.dll. You may miss required multiplayer updates and become unable to join players using newer versions.");
			GUI.Label(new Rect(num + 25f, num2 + 125f, 470f, 35f), "Install the updater in the game's Plugins folder.");
			if (GUI.Button(new Rect(num + 150f, num2 + 175f, 220f, 34f), "I understand"))
			{
				_showUpdaterWarning = false;
			}
		}

		private static TrackedPose Capture(Transform transform)
		{
			//IL_0001: 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)
			return new TrackedPose(transform.position, transform.rotation);
		}

		private static bool IsNewer(uint incoming, uint previous)
		{
			return (int)(incoming - previous) > 0;
		}
	}
	internal enum ObjectMessageType : byte
	{
		Spawn = 2,
		Transform,
		Despawn
	}
	internal readonly record struct NetworkObjectMessage(ObjectMessageType Type, uint ObjectId, string PrefabName, Vector3 Position, Quaternion Rotation);
	internal static class ObjectPacket
	{
		private const uint Magic = 1296258626u;

		private const byte ProtocolVersion = 1;

		private const int HeaderLength = 10;

		private const int TransformLength = 38;

		private const int MaxNameBytes = 192;

		public static byte[] EncodeSpawn(uint objectId, string prefabName, Vector3 position, Quaternion rotation)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			byte[] array = Encoding.UTF8.GetBytes(prefabName ?? "");
			if (array.Length > 192)
			{
				Array.Resize(ref array, 192);
			}
			byte[] array2 = new byte[40 + array.Length];
			int offset = WriteHeader(array2, ObjectMessageType.Spawn, objectId);
			WriteUShort(array2, ref offset, (ushort)array.Length);
			Buffer.BlockCopy(array, 0, array2, offset, array.Length);
			offset += array.Length;
			WriteTransform(array2, ref offset, position, rotation);
			return array2;
		}

		public static byte[] EncodeTransform(uint objectId, Vector3 position, Quaternion rotation)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			byte[] array = new byte[38];
			int offset = WriteHeader(array, ObjectMessageType.Transform, objectId);
			WriteTransform(array, ref offset, position, rotation);
			return array;
		}

		public static byte[] EncodeDespawn(uint objectId)
		{
			byte[] array = new byte[10];
			WriteHeader(array, ObjectMessageType.Despawn, objectId);
			return array;
		}

		public static bool TryDecode(ReadOnlySpan<byte> data, out NetworkObjectMessage message)
		{
			//IL_006e: 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_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: 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_0104: 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_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: 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_0128: 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)
			message = default(NetworkObjectMessage);
			if (data.Length < 10)
			{
				return false;
			}
			int offset = 0;
			if (ReadUInt(data, ref offset) != 1296258626 || data[offset++] != 1)
			{
				return false;
			}
			ObjectMessageType objectMessageType = (ObjectMessageType)data[offset++];
			uint num = ReadUInt(data, ref offset);
			if (num == 0)
			{
				return false;
			}
			if (objectMessageType == ObjectMessageType.Despawn)
			{
				if (data.Length != 10)
				{
					return false;
				}
				message = new NetworkObjectMessage(objectMessageType, num, "", default(Vector3), default(Quaternion));
				return true;
			}
			string prefabName = "";
			switch (objectMessageType)
			{
			case ObjectMessageType.Spawn:
			{
				if (data.Length < 40)
				{
					return false;
				}
				int num2 = ReadUShort(data, ref offset);
				if (num2 <= 0 || num2 > 192 || offset + num2 + 28 != data.Length)
				{
					return false;
				}
				prefabName = Encoding.UTF8.GetString(data.Slice(offset, num2));
				offset += num2;
				break;
			}
			case ObjectMessageType.Transform:
				if (data.Length != 38)
				{
					return false;
				}
				break;
			default:
				return false;
			}
			Vector3 val = ReadVector(data, ref offset);
			Quaternion val2 = ReadQuaternion(data, ref offset);
			if (!IsFinite(val) || !IsFinite(val2))
			{
				return false;
			}
			message = new NetworkObjectMessage(objectMessageType, num, prefabName, val, val2);
			return true;
		}

		private static int WriteHeader(byte[] data, ObjectMessageType type, uint objectId)
		{
			int offset = 0;
			WriteUInt(data, ref offset, 1296258626u);
			data[offset++] = 1;
			data[offset++] = (byte)type;
			WriteUInt(data, ref offset, objectId);
			return offset;
		}

		private static void WriteTransform(byte[] data, ref int offset, Vector3 position, Quaternion rotation)
		{
			//IL_0002: 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_001c: 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_0036: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			WriteFloat(data, ref offset, position.x);
			WriteFloat(data, ref offset, position.y);
			WriteFloat(data, ref offset, position.z);
			WriteFloat(data, ref offset, rotation.x);
			WriteFloat(data, ref offset, rotation.y);
			WriteFloat(data, ref offset, rotation.z);
			WriteFloat(data, ref offset, rotation.w);
		}

		private static Vector3 ReadVector(ReadOnlySpan<byte> data, ref int offset)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(ReadFloat(data, ref offset), ReadFloat(data, ref offset), ReadFloat(data, ref offset));
		}

		private static Quaternion ReadQuaternion(ReadOnlySpan<byte> data, ref int offset)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			return new Quaternion(ReadFloat(data, ref offset), ReadFloat(data, ref offset), ReadFloat(data, ref offset), ReadFloat(data, ref offset));
		}

		private static void WriteUInt(byte[] data, ref int offset, uint value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static uint ReadUInt(ReadOnlySpan<byte> data, ref int offset)
		{
			uint result = BitConverter.ToUInt32(data.Slice(offset, 4));
			offset += 4;
			return result;
		}

		private static void WriteUShort(byte[] data, ref int offset, ushort value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static ushort ReadUShort(ReadOnlySpan<byte> data, ref int offset)
		{
			ushort result = BitConverter.ToUInt16(data.Slice(offset, 2));
			offset += 2;
			return result;
		}

		private static void WriteFloat(byte[] data, ref int offset, float value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static float ReadFloat(ReadOnlySpan<byte> data, ref int offset)
		{
			float result = BitConverter.ToSingle(data.Slice(offset, 4));
			offset += 4;
			return result;
		}

		private static bool IsFinite(Vector3 value)
		{
			//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)
			if (IsFinite(value.x) && IsFinite(value.y))
			{
				return IsFinite(value.z);
			}
			return false;
		}

		private static bool IsFinite(Quaternion value)
		{
			//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)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (IsFinite(value.x) && IsFinite(value.y) && IsFinite(value.z))
			{
				return IsFinite(value.w);
			}
			return false;
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal readonly record struct TrackedPose(Vector3 Position, Quaternion Rotation);
	internal readonly record struct PlayerPose(uint Sequence, TrackedPose Head, TrackedPose LeftHand, TrackedPose RightHand);
	internal static class PosePacket
	{
		private const uint Magic = 1296258626u;

		private const byte ProtocolVersion = 1;

		private const byte PoseMessage = 1;

		private const int PacketLength = 94;

		public static byte[] Encode(PlayerPose pose)
		{
			byte[] array = new byte[94];
			int offset = 0;
			WriteUInt(array, ref offset, 1296258626u);
			array[offset++] = 1;
			array[offset++] = 1;
			WriteUInt(array, ref offset, pose.Sequence);
			WritePose(array, ref offset, pose.Head);
			WritePose(array, ref offset, pose.LeftHand);
			WritePose(array, ref offset, pose.RightHand);
			return array;
		}

		public static bool TryDecode(ReadOnlySpan<byte> data, out PlayerPose pose)
		{
			pose = default(PlayerPose);
			if (data.Length != 94)
			{
				return false;
			}
			int offset = 0;
			if (ReadUInt(data, ref offset) != 1296258626 || data[offset++] != 1 || data[offset++] != 1)
			{
				return false;
			}
			uint sequence = ReadUInt(data, ref offset);
			TrackedPose trackedPose = ReadPose(data, ref offset);
			TrackedPose trackedPose2 = ReadPose(data, ref offset);
			TrackedPose trackedPose3 = ReadPose(data, ref offset);
			if (!IsFinite(trackedPose) || !IsFinite(trackedPose2) || !IsFinite(trackedPose3))
			{
				return false;
			}
			pose = new PlayerPose(sequence, trackedPose, trackedPose2, trackedPose3);
			return true;
		}

		private static void WritePose(byte[] data, ref int offset, TrackedPose pose)
		{
			//IL_0004: 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_002a: 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_0050: 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_0076: Unknown result type (might be due to invalid IL or missing references)
			WriteFloat(data, ref offset, pose.Position.x);
			WriteFloat(data, ref offset, pose.Position.y);
			WriteFloat(data, ref offset, pose.Position.z);
			WriteFloat(data, ref offset, pose.Rotation.x);
			WriteFloat(data, ref offset, pose.Rotation.y);
			WriteFloat(data, ref offset, pose.Rotation.z);
			WriteFloat(data, ref offset, pose.Rotation.w);
		}

		private static TrackedPose ReadPose(ReadOnlySpan<byte> data, ref int offset)
		{
			//IL_0015: 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)
			Vector3 position = new Vector3(ReadFloat(data, ref offset), ReadFloat(data, ref offset), ReadFloat(data, ref offset));
			Quaternion rotation = default(Quaternion);
			((Quaternion)(ref rotation))..ctor(ReadFloat(data, ref offset), ReadFloat(data, ref offset), ReadFloat(data, ref offset), ReadFloat(data, ref offset));
			return new TrackedPose(position, rotation);
		}

		private static void WriteUInt(byte[] data, ref int offset, uint value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static uint ReadUInt(ReadOnlySpan<byte> data, ref int offset)
		{
			uint result = BitConverter.ToUInt32(data.Slice(offset, 4));
			offset += 4;
			return result;
		}

		private static void WriteFloat(byte[] data, ref int offset, float value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static float ReadFloat(ReadOnlySpan<byte> data, ref int offset)
		{
			float result = BitConverter.ToSingle(data.Slice(offset, 4));
			offset += 4;
			return result;
		}

		private static bool IsFinite(TrackedPose pose)
		{
			//IL_0002: 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_0028: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (IsFinite(pose.Position.x) && IsFinite(pose.Position.y) && IsFinite(pose.Position.z) && IsFinite(pose.Rotation.x) && IsFinite(pose.Rotation.y) && IsFinite(pose.Rotation.z))
			{
				return IsFinite(pose.Rotation.w);
			}
			return false;
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal sealed class RemoteAvatar : IDisposable
	{
		private const float PositionSmoothing = 18f;

		private const float RotationSmoothing = 22f;

		private readonly GameObject _root;

		private readonly Material _material;

		private readonly Transform _head;

		private readonly Transform _torso;

		private readonly Transform _leftHand;

		private readonly Transform _rightHand;

		private PlayerPose _target;

		private bool _hasPose;

		private bool _disposed;

		public Transform HeadTransform => _head;

		public RemoteAvatar()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: 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)
			_root = new GameObject("BrutalistickCoop_RemotePlayer");
			_root.layer = ResolveVisibleLayer();
			_material = CreateVisibleMaterial();
			_head = CreatePart("Remote Head", (PrimitiveType)0, new Vector3(0.22f, 0.27f, 0.22f));
			_torso = CreatePart("Remote Torso", (PrimitiveType)1, new Vector3(0.32f, 0.32f, 0.22f));
			_leftHand = CreatePart("Remote Left Hand", (PrimitiveType)0, Vector3.one * 0.13f);
			_rightHand = CreatePart("Remote Right Hand", (PrimitiveType)0, Vector3.one * 0.13f);
			SetVisible(visible: false);
		}

		public void SetTarget(PlayerPose pose)
		{
			_target = pose;
			if (!_hasPose)
			{
				Snap(_head, pose.Head);
				SnapTorso(pose);
				Snap(_leftHand, pose.LeftHand);
				Snap(_rightHand, pose.RightHand);
				_hasPose = true;
				SetVisible(visible: true);
			}
		}

		public void Tick()
		{
			if (_hasPose)
			{
				float positionT = 1f - Mathf.Exp(-18f * Time.unscaledDeltaTime);
				float rotationT = 1f - Mathf.Exp(-22f * Time.unscaledDeltaTime);
				Smooth(_head, _target.Head, positionT, rotationT);
				SmoothTorso(_target, positionT, rotationT);
				Smooth(_leftHand, _target.LeftHand, positionT, rotationT);
				Smooth(_rightHand, _target.RightHand, positionT, rotationT);
			}
		}

		public void Hide()
		{
			_hasPose = false;
			SetVisible(visible: false);
		}

		private Transform CreatePart(string name, PrimitiveType type, Vector3 scale)
		{
			//IL_0000: 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_009d: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = GameObject.CreatePrimitive(type);
			((Object)obj).name = name;
			obj.layer = _root.layer;
			obj.transform.SetParent(_root.transform, false);
			obj.transform.localScale = scale;
			Collider component = obj.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			Renderer component2 = obj.GetComponent<Renderer>();
			if ((Object)(object)component2 != (Object)null)
			{
				if ((Object)(object)_material != (Object)null)
				{
					component2.material = _material;
				}
				else
				{
					component2.material.color = new Color(0.12f, 0.58f, 1f, 1f);
				}
			}
			return obj.transform;
		}

		private void SetVisible(bool visible)
		{
			_root.SetActive(visible);
		}

		private static void Snap(Transform target, TrackedPose pose)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			target.SetPositionAndRotation(pose.Position, pose.Rotation);
		}

		private void SnapTorso(PlayerPose pose)
		{
			//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_001f: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			_torso.SetPositionAndRotation(pose.Head.Position + Vector3.down * 0.42f, YawOnly(pose.Head.Rotation));
		}

		private static void Smooth(Transform target, TrackedPose pose, float positionT, float rotationT)
		{
			//IL_0002: 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_000f: 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_0022: 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)
			target.position = Vector3.Lerp(target.position, pose.Position, positionT);
			target.rotation = Quaternion.Slerp(target.rotation, pose.Rotation, rotationT);
		}

		private void SmoothTorso(PlayerPose pose, float positionT, float rotationT)
		{
			//IL_000a: 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_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = pose.Head.Position + Vector3.down * 0.42f;
			Quaternion val2 = YawOnly(pose.Head.Rotation);
			_torso.position = Vector3.Lerp(_torso.position, val, positionT);
			_torso.rotation = Quaternion.Slerp(_torso.rotation, val2, rotationT);
		}

		private static Quaternion YawOnly(Quaternion rotation)
		{
			//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_000d: 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)
			Vector3 eulerAngles = ((Quaternion)(ref rotation)).eulerAngles;
			return Quaternion.Euler(0f, eulerAngles.y, 0f);
		}

		private static Material CreateVisibleMaterial()
		{
			//IL_002b: 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)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			Shader val = Shader.Find("Universal Render Pipeline/Unlit");
			if ((Object)(object)val == (Object)null)
			{
				val = Shader.Find("Unlit/Color");
			}
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			return new Material(val)
			{
				color = new Color(0.12f, 0.58f, 1f, 1f)
			};
		}

		private static int ResolveVisibleLayer()
		{
			Camera main = Camera.main;
			int num = (((Object)(object)main != (Object)null) ? main.cullingMask : (-1));
			if ((num & 1) != 0)
			{
				return 0;
			}
			for (int i = 8; i < 32; i++)
			{
				if ((num & (1 << i)) != 0)
				{
					return i;
				}
			}
			for (int j = 0; j < 32; j++)
			{
				if (j != 5 && (num & (1 << j)) != 0)
				{
					return j;
				}
			}
			return 0;
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				Object.Destroy((Object)(object)_root);
				if ((Object)(object)_material != (Object)null)
				{
					Object.Destroy((Object)(object)_material);
				}
				_disposed = true;
			}
		}
	}
	internal sealed class SpawnedObjectSync : IDisposable
	{
		private sealed class LocalObject
		{
			public readonly uint Id;

			public readonly GameObject GameObject;

			public readonly string PrefabName;

			public LocalObject(uint id, GameObject gameObject, string prefabName)
			{
				Id = id;
				GameObject = gameObject;
				PrefabName = prefabName;
			}
		}

		private sealed class RemoteObject
		{
			public readonly GameObject GameObject;

			public Vector3 TargetPosition;

			public Quaternion TargetRotation;

			public RemoteObject(GameObject gameObject, Vector3 position, Quaternion rotation)
			{
				//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_0016: Unknown result type (might be due to invalid IL or missing references)
				GameObject = gameObject;
				TargetPosition = position;
				TargetRotation = rotation;
			}
		}

		private const float ScanInterval = 0.2f;

		private const float TransformInterval = 1f / 15f;

		private const int MaxObjects = 64;

		private const float PositionSmoothing = 24f;

		private const float RotationSmoothing = 28f;

		private readonly Dictionary<int, LocalObject> _localObjects = new Dictionary<int, LocalObject>();

		private readonly Dictionary<uint, RemoteObject> _remoteObjects = new Dictionary<uint, RemoteObject>();

		private readonly Dictionary<uint, NetworkObjectMessage> _pendingSpawns = new Dictionary<uint, NetworkObjectMessage>();

		private readonly List<SpawnGunManager> _spawnGuns = new List<SpawnGunManager>();

		private uint _nextObjectId = 1u;

		private float _nextScan;

		private float _nextTransformSend;

		private float _nextManagerSearch;

		private bool _disposed;

		public int RemoteObjectCount => _remoteObjects.Count;

		public int PendingSpawnCount => _pendingSpawns.Count;

		public void Tick(float now, Action<byte[]> sendUnreliable, Action<byte[]> sendReliable)
		{
			if (!_disposed)
			{
				if (now >= _nextManagerSearch)
				{
					RefreshSpawnGunManagers();
					_nextManagerSearch = now + 1f;
				}
				RetryPendingSpawns();
				if (now >= _nextScan)
				{
					ScanLocalObjects(sendReliable);
					_nextScan = now + 0.2f;
				}
				if (now >= _nextTransformSend)
				{
					SendLocalTransforms(sendUnreliable);
					_nextTransformSend = now + 1f / 15f;
				}
				SmoothRemoteObjects();
			}
		}

		public void HandleMessage(NetworkObjectMessage message)
		{
			switch (message.Type)
			{
			case ObjectMessageType.Spawn:
				HandleSpawn(message);
				break;
			case ObjectMessageType.Transform:
				HandleTransform(message);
				break;
			case ObjectMessageType.Despawn:
				RemoveRemote(message.ObjectId);
				_pendingSpawns.Remove(message.ObjectId);
				break;
			}
		}

		public void Reset()
		{
			foreach (RemoteObject value in _remoteObjects.Values)
			{
				if ((Object)(object)value.GameObject != (Object)null)
				{
					Object.Destroy((Object)(object)value.GameObject);
				}
			}
			_localObjects.Clear();
			_remoteObjects.Clear();
			_pendingSpawns.Clear();
			_spawnGuns.Clear();
			_nextObjectId = 1u;
			_nextScan = 0f;
			_nextTransformSend = 0f;
			_nextManagerSearch = 0f;
		}

		private void ScanLocalObjects(Action<byte[]> sendReliable)
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if (_spawnGuns.Count == 0)
			{
				return;
			}
			HashSet<int> hashSet = new HashSet<int>();
			int num = 0;
			foreach (SpawnGunManager spawnGun in _spawnGuns)
			{
				if ((Object)(object)spawnGun == (Object)null || spawnGun.spawnedItems == null)
				{
					continue;
				}
				int num2 = spawnGun.spawnedItems.Count - 1;
				while (num2 >= 0 && num < 64)
				{
					GameObject val = spawnGun.spawnedItems[num2];
					if (!((Object)(object)val == (Object)null))
					{
						int instanceID = ((Object)val).GetInstanceID();
						if (hashSet.Add(instanceID))
						{
							num++;
							if (!_localObjects.ContainsKey(instanceID))
							{
								uint num3 = _nextObjectId++;
								if (num3 == 0)
								{
									num3 = _nextObjectId++;
								}
								string prefabName = ResolvePrefabName(val);
								LocalObject value = new LocalObject(num3, val, prefabName);
								_localObjects.Add(instanceID, value);
								sendReliable(ObjectPacket.EncodeSpawn(num3, prefabName, val.transform.position, val.transform.rotation));
							}
						}
					}
					num2--;
				}
			}
			List<int> list = new List<int>();
			foreach (KeyValuePair<int, LocalObject> localObject in _localObjects)
			{
				if (!hashSet.Contains(localObject.Key) || (Object)(object)localObject.Value.GameObject == (Object)null)
				{
					sendReliable(ObjectPacket.EncodeDespawn(localObject.Value.Id));
					list.Add(localObject.Key);
				}
			}
			foreach (int item in list)
			{
				_localObjects.Remove(item);
			}
		}

		private void RefreshSpawnGunManagers()
		{
			_spawnGuns.Clear();
			SpawnGunManager[] array = Il2CppArrayBase<SpawnGunManager>.op_Implicit(Object.FindObjectsOfType<SpawnGunManager>());
			if (array == null)
			{
				return;
			}
			SpawnGunManager[] array2 = array;
			foreach (SpawnGunManager val in array2)
			{
				if ((Object)(object)val != (Object)null)
				{
					_spawnGuns.Add(val);
				}
			}
		}

		private void SendLocalTransforms(Action<byte[]> sendUnreliable)
		{
			//IL_003d: 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)
			foreach (LocalObject value in _localObjects.Values)
			{
				if (!((Object)(object)value.GameObject == (Object)null))
				{
					Transform transform = value.GameObject.transform;
					sendUnreliable(ObjectPacket.EncodeTransform(value.Id, transform.position, transform.rotation));
				}
			}
		}

		private void HandleSpawn(NetworkObjectMessage message)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_00c5: Unknown result type (might be due to invalid IL or missing references)
			RemoveRemote(message.ObjectId);
			GameObject val = FindPrefab(message.PrefabName);
			if ((Object)(object)val == (Object)null)
			{
				_pendingSpawns[message.ObjectId] = message;
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(val, message.Position, message.Rotation);
			if (!((Object)(object)val2 == (Object)null))
			{
				((Object)val2).name = $"BrutalistickCoop_Remote_{message.ObjectId}_{message.PrefabName}";
				PrepareRemoteReplica(val2);
				val2.SetActive(true);
				_remoteObjects[message.ObjectId] = new RemoteObject(val2, message.Position, message.Rotation);
				_pendingSpawns.Remove(message.ObjectId);
			}
		}

		private void HandleTransform(NetworkObjectMessage message)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			NetworkObjectMessage value2;
			if (_remoteObjects.TryGetValue(message.ObjectId, out var value))
			{
				value.TargetPosition = message.Position;
				value.TargetRotation = message.Rotation;
			}
			else if (_pendingSpawns.TryGetValue(message.ObjectId, out value2))
			{
				_pendingSpawns[message.ObjectId] = value2 with
				{
					Position = message.Position,
					Rotation = message.Rotation
				};
			}
		}

		private void RetryPendingSpawns()
		{
			if (_pendingSpawns.Count == 0)
			{
				return;
			}
			foreach (NetworkObjectMessage item in new List<NetworkObjectMessage>(_pendingSpawns.Values))
			{
				if ((Object)(object)FindPrefab(item.PrefabName) != (Object)null)
				{
					HandleSpawn(item);
				}
			}
		}

		private void SmoothRemoteObjects()
		{
			//IL_0064: 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)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			float num = 1f - Mathf.Exp(-24f * Time.unscaledDeltaTime);
			float num2 = 1f - Mathf.Exp(-28f * Time.unscaledDeltaTime);
			foreach (RemoteObject value in _remoteObjects.Values)
			{
				if (!((Object)(object)value.GameObject == (Object)null))
				{
					Transform transform = value.GameObject.transform;
					transform.position = Vector3.Lerp(transform.position, value.TargetPosition, num);
					transform.rotation = Quaternion.Slerp(transform.rotation, value.TargetRotation, num2);
				}
			}
		}

		private static GameObject FindPrefab(string requestedName)
		{
			List<SpawnableItemInfo> spawnableItems = SpawnGunManager.spawnableItems;
			if (spawnableItems == null)
			{
				return null;
			}
			string text = NormalizeName(requestedName);
			for (int i = 0; i < spawnableItems.Count; i++)
			{
				SpawnableItemInfo val = spawnableItems[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.itemPrefab == (Object)null) && (NormalizeName(((Object)val.itemPrefab).name) == text || NormalizeName(val.spawnableName) == text))
				{
					return val.itemPrefab;
				}
			}
			return null;
		}

		private static void PrepareRemoteReplica(GameObject clone)
		{
			int layer = ResolveVisibleLayer();
			Transform[] array = Il2CppArrayBase<Transform>.op_Implicit(clone.GetComponentsInChildren<Transform>(true));
			for (int i = 0; i < array.Length; i++)
			{
				((Component)array[i]).gameObject.layer = layer;
			}
			Renderer[] array2 = Il2CppArrayBase<Renderer>.op_Implicit(clone.GetComponentsInChildren<Renderer>(true));
			for (int i = 0; i < array2.Length; i++)
			{
				array2[i].enabled = true;
			}
			Rigidbody[] array3 = Il2CppArrayBase<Rigidbody>.op_Implicit(clone.GetComponentsInChildren<Rigidbody>(true));
			foreach (Rigidbody obj in array3)
			{
				obj.isKinematic = true;
				obj.useGravity = false;
			}
			Collider[] array4 = Il2CppArrayBase<Collider>.op_Implicit(clone.GetComponentsInChildren<Collider>(true));
			for (int i = 0; i < array4.Length; i++)
			{
				array4[i].enabled = false;
			}
		}

		private static string ResolvePrefabName(GameObject gameObject)
		{
			string text = NormalizeName((gameObject != null) ? ((Object)gameObject).name : null);
			List<SpawnableItemInfo> spawnableItems = SpawnGunManager.spawnableItems;
			if (spawnableItems == null)
			{
				return text;
			}
			for (int i = 0; i < spawnableItems.Count; i++)
			{
				SpawnableItemInfo val = spawnableItems[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.itemPrefab == (Object)null))
				{
					string text2 = NormalizeName(((Object)val.itemPrefab).name);
					if (text2 == text || NormalizeName(val.spawnableName) == text)
					{
						return text2;
					}
				}
			}
			return text;
		}

		private static int ResolveVisibleLayer()
		{
			Camera main = Camera.main;
			int num = (((Object)(object)main != (Object)null) ? main.cullingMask : (-1));
			if ((num & 1) != 0)
			{
				return 0;
			}
			for (int i = 8; i < 32; i++)
			{
				if ((num & (1 << i)) != 0)
				{
					return i;
				}
			}
			for (int j = 0; j < 32; j++)
			{
				if (j != 5 && (num & (1 << j)) != 0)
				{
					return j;
				}
			}
			return 0;
		}

		private static string NormalizeName(string name)
		{
			string text = (name ?? "").Trim();
			if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase))
			{
				text = text.Substring(0, text.Length - "(Clone)".Length).Trim();
			}
			return text;
		}

		private void RemoveRemote(uint objectId)
		{
			if (_remoteObjects.TryGetValue(objectId, out var value))
			{
				if ((Object)(object)value.GameObject != (Object)null)
				{
					Object.Destroy((Object)(object)value.GameObject);
				}
				_remoteObjects.Remove(objectId);
			}
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				Reset();
				_disposed = true;
			}
		}
	}
	internal readonly record struct RoomInfo(ulong LobbyId, string Name, int Members);
	internal sealed class SteamLobby : IDisposable
	{
		private enum PendingOperation
		{
			None,
			Create,
			Join,
			List,
			ResolveCode
		}

		private enum LobbyType
		{
			Private,
			FriendsOnly,
			Public,
			Invisible
		}

		private enum LobbyDistanceFilter
		{
			Close,
			Default,
			Far,
			Worldwide
		}

		[StructLayout(LayoutKind.Sequential, Pack = 8)]
		private struct LobbyCreatedResult
		{
			public int Result;

			public ulong LobbyId;
		}

		[StructLayout(LayoutKind.Sequential, Pack = 8)]
		private struct LobbyEnterResult
		{
			public ulong LobbyId;

			public uint ChatPermissions;

			public byte Locked;

			public uint Response;
		}

		[StructLayout(LayoutKind.Sequential, Pack = 8)]
		private struct LobbyMatchListResult
		{
			public uint LobbiesMatching;
		}

		private static class Native
		{
			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern IntPtr SteamAPI_SteamMatchmaking_v009();

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern IntPtr SteamAPI_SteamUtils_v009();

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern IntPtr SteamAPI_SteamUser_v020();

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern IntPtr SteamAPI_SteamFriends_v017();

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern ulong SteamAPI_ISteamUser_GetSteamID(IntPtr self);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern IntPtr SteamAPI_ISteamFriends_GetPersonaName(IntPtr self);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern ulong SteamAPI_ISteamMatchmaking_CreateLobby(IntPtr self, int lobbyType, int maxMembers);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern ulong SteamAPI_ISteamMatchmaking_JoinLobby(IntPtr self, ulong lobbyId);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern void SteamAPI_ISteamMatchmaking_LeaveLobby(IntPtr self, ulong lobbyId);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern ulong SteamAPI_ISteamMatchmaking_RequestLobbyList(IntPtr self);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern void SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(IntPtr self, [MarshalAs(UnmanagedType.LPUTF8Str)] string key, [MarshalAs(UnmanagedType.LPUTF8Str)] string value, int comparison);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern void SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter(IntPtr self, int maxResults);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern void SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter(IntPtr self, int distanceFilter);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern ulong SteamAPI_ISteamMatchmaking_GetLobbyByIndex(IntPtr self, int index);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern int SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(IntPtr self, ulong lobbyId);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern ulong SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(IntPtr self, ulong lobbyId, int index);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern ulong SteamAPI_ISteamMatchmaking_GetLobbyOwner(IntPtr self, ulong lobbyId);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamMatchmaking_SetLobbyJoinable(IntPtr self, ulong lobbyId, [MarshalAs(UnmanagedType.I1)] bool joinable);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamMatchmaking_SetLobbyData(IntPtr self, ulong lobbyId, [MarshalAs(UnmanagedType.LPUTF8Str)] string key, [MarshalAs(UnmanagedType.LPUTF8Str)] string value);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern IntPtr SteamAPI_ISteamMatchmaking_GetLobbyData(IntPtr self, ulong lobbyId, [MarshalAs(UnmanagedType.LPUTF8Str)] string key);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamUtils_IsAPICallCompleted(IntPtr self, ulong call, [MarshalAs(UnmanagedType.I1)] ref bool failed);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamUtils_GetAPICallResult(IntPtr self, ulong call, IntPtr callback, int callbackSize, int expectedCallback, [MarshalAs(UnmanagedType.I1)] ref bool failed);
		}

		private const string ProtocolKey = "brutalistick_coop_protocol";

		private const string ProtocolValue = "3";

		private const string RoomNameKey = "room_name";

		private const string RoomCodeKey = "room_code";

		private const string RoomVisibilityKey = "room_visibility";

		private const string PublicVisibility = "public";

		private const string PrivateVisibility = "private";

		private const int LobbyCreatedCallback = 513;

		private const int LobbyEnterCallback = 504;

		private const int LobbyMatchListCallback = 510;

		private readonly Action<string> _log;

		private readonly List<RoomInfo> _rooms = new List<RoomInfo>();

		private IntPtr _matchmaking;

		private IntPtr _utils;

		private IntPtr _user;

		private IntPtr _friends;

		private PendingOperation _pending;

		private ulong _pendingCall;

		private ulong _lobbyId;

		private string _roomCode = "";

		private string _pendingRoomCode = "";

		private ulong _localSteamId;

		private ulong _peerSteamId;

		private float _nextMemberRefresh;

		private bool _pendingPublicRoom;

		private bool _isPublicRoom;

		private bool _disposed;

		public IReadOnlyList<RoomInfo> Rooms => _rooms;

		public string Status { get; private set; }

		public ulong LobbyId => _lobbyId;

		public string RoomCode => _roomCode;

		public ulong LocalSteamId => _localSteamId;

		public ulong PeerSteamId => _peerSteamId;

		public bool IsPublicRoom => _isPublicRoom;

		public bool IsReady
		{
			get
			{
				if (_matchmaking != IntPtr.Zero)
				{
					return _utils != IntPtr.Zero;
				}
				return false;
			}
		}

		public bool IsInLobby => _lobbyId != 0;

		public bool IsHost
		{
			get
			{
				if (IsInLobby && _localSteamId != 0L)
				{
					return Native.SteamAPI_ISteamMatchmaking_GetLobbyOwner(_matchmaking, _lobbyId) == _localSteamId;
				}
				return false;
			}
		}

		public SteamLobby(Action<string> log)
		{
			_log = log;
			Status = "Waiting for Steam...";
		}

		public void Tick(float now)
		{
			if (EnsureInterfaces())
			{
				PollPendingOperation();
				if (_lobbyId != 0L && now >= _nextMemberRefresh)
				{
					RefreshPeer();
					_nextMemberRefresh = now + 0.5f;
				}
			}
		}

		public void CreatePublicRoom()
		{
			CreateRoom(isPublic: true);
		}

		public void CreatePrivateRoom()
		{
			CreateRoom(isPublic: false);
		}

		private void CreateRoom(bool isPublic)
		{
			if (EnsureInterfaces() && _pending == PendingOperation.None)
			{
				if (_lobbyId != 0L)
				{
					LeaveRoom();
				}
				_pendingCall = Native.SteamAPI_ISteamMatchmaking_CreateLobby(_matchmaking, 2, 2);
				if (_pendingCall == 0L)
				{
					Status = "Steam rejected the create-room request.";
					return;
				}
				_pendingPublicRoom = isPublic;
				_pending = PendingOperation.Create;
				Status = (isPublic ? "Creating public room..." : "Creating private code-only room...");
			}
		}

		public void RefreshRooms()
		{
			if (EnsureInterfaces() && _pending == PendingOperation.None)
			{
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(_matchmaking, "brutalistick_coop_protocol", "3", 0);
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(_matchmaking, "room_visibility", "public", 0);
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter(_matchmaking, 20);
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter(_matchmaking, 3);
				_pendingCall = Native.SteamAPI_ISteamMatchmaking_RequestLobbyList(_matchmaking);
				if (_pendingCall == 0L)
				{
					Status = "Steam rejected the room-list request.";
					return;
				}
				_pending = PendingOperation.List;
				Status = "Searching for public rooms worldwide...";
			}
		}

		public void JoinRoom(ulong lobbyId)
		{
			if (EnsureInterfaces() && _pending == PendingOperation.None && lobbyId != 0L)
			{
				if (_lobbyId != 0L)
				{
					LeaveRoom();
				}
				_pendingCall = Native.SteamAPI_ISteamMatchmaking_JoinLobby(_matchmaking, lobbyId);
				if (_pendingCall == 0L)
				{
					Status = "Steam rejected the join-room request.";
					return;
				}
				_pending = PendingOperation.Join;
				Status = $"Joining room {lobbyId}...";
			}
		}

		public void JoinRoomCode(string code)
		{
			code = code?.Trim() ?? "";
			if (code.Length != 6 || !code.All(char.IsDigit))
			{
				Status = "Room codes contain exactly 6 numbers.";
			}
			else if (EnsureInterfaces() && _pending == PendingOperation.None)
			{
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(_matchmaking, "brutalistick_coop_protocol", "3", 0);
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(_matchmaking, "room_code", code, 0);
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter(_matchmaking, 50);
				Native.SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter(_matchmaking, 3);
				_pendingCall = Native.SteamAPI_ISteamMatchmaking_RequestLobbyList(_matchmaking);
				if (_pendingCall == 0L)
				{
					Status = "Steam rejected the room-code search.";
					return;
				}
				_pendingRoomCode = code;
				_pending = PendingOperation.ResolveCode;
				Status = "Finding room " + code + "...";
			}
		}

		public void LeaveRoom()
		{
			if (_matchmaking != IntPtr.Zero && _lobbyId != 0L)
			{
				Native.SteamAPI_ISteamMatchmaking_LeaveLobby(_matchmaking, _lobbyId);
			}
			_lobbyId = 0uL;
			_roomCode = "";
			_peerSteamId = 0uL;
			_isPublicRoom = false;
			Status = "Not in a room.";
			_log("Left the Steam room.");
		}

		private bool EnsureInterfaces()
		{
			if (IsReady)
			{
				return true;
			}
			_matchmaking = Native.SteamAPI_SteamMatchmaking_v009();
			_utils = Native.SteamAPI_SteamUtils_v009();
			_user = Native.SteamAPI_SteamUser_v020();
			_friends = Native.SteamAPI_SteamFriends_v017();
			if (!IsReady || _user == IntPtr.Zero || _friends == IntPtr.Zero)
			{
				Status = "Steam is not ready yet.";
				return false;
			}
			_localSteamId = Native.SteamAPI_ISteamUser_GetSteamID(_user);
			Status = "Not in a room. Host one or browse public rooms.";
			return true;
		}

		private void PollPendingOperation()
		{
			if (_pending == PendingOperation.None || _pendingCall == 0L)
			{
				return;
			}
			bool failed = false;
			if (!Native.SteamAPI_ISteamUtils_IsAPICallCompleted(_utils, _pendingCall, ref failed))
			{
				return;
			}
			PendingOperation pending = _pending;
			ulong pendingCall = _pendingCall;
			_pending = PendingOperation.None;
			_pendingCall = 0uL;
			if (failed)
			{
				Status = "Steam room request failed.";
				return;
			}
			switch (pending)
			{
			case PendingOperation.Create:
				CompleteCreate(pendingCall);
				break;
			case PendingOperation.Join:
				CompleteJoin(pendingCall);
				break;
			case PendingOperation.List:
				CompleteList(pendingCall);
				break;
			case PendingOperation.ResolveCode:
				CompleteRoomCodeSearch(pendingCall);
				break;
			}
		}

		private void CompleteCreate(ulong call)
		{
			if (!TryGetCallResult<LobbyCreatedResult>(call, 513, out var result) || result.Result != 1 || result.LobbyId == 0L)
			{
				Status = "Could not create the room.";
				return;
			}
			_lobbyId = result.LobbyId;
			_roomCode = GenerateRoomCode(_lobbyId);
			_peerSteamId = 0uL;
			_isPublicRoom = _pendingPublicRoom;
			Native.SteamAPI_ISteamMatchmaking_SetLobbyJoinable(_matchmaking, _lobbyId, joinable: true);
			Native.SteamAPI_ISteamMatchmaking_SetLobbyData(_matchmaking, _lobbyId, "brutalistick_coop_protocol", "3");
			string personaName = GetPersonaName();
			Native.SteamAPI_ISteamMatchmaking_SetLobbyData(_matchmaking, _lobbyId, "room_name", personaName + "'s room");
			Native.SteamAPI_ISteamMatchmaking_SetLobbyData(_matchmaking, _lobbyId, "room_code", _roomCode);
			Native.SteamAPI_ISteamMatchmaking_SetLobbyData(_matchmaking, _lobbyId, "room_visibility", _isPublicRoom ? "public" : "private");
			Status = (_isPublicRoom ? ("Public room created. Code: " + _roomCode) : ("Private room created. Share code: " + _roomCode));
			_log($"Created {(_isPublicRoom ? "public" : "private code-only")} Steam room {_lobbyId}.");
		}

		private void CompleteJoin(ulong call)
		{
			if (!TryGetCallResult<LobbyEnterResult>(call, 504, out var result) || result.Response != 1 || result.LobbyId == 0L)
			{
				Status = "Could not join that room.";
				return;
			}
			if (GetLobbyData(result.LobbyId, "brutalistick_coop_protocol") != "3")
			{
				Native.SteamAPI_ISteamMatchmaking_LeaveLobby(_matchmaking, result.LobbyId);
				Status = "That room uses an incompatible mod version.";
				return;
			}
			_lobbyId = result.LobbyId;
			_roomCode = GetLobbyData(_lobbyId, "room_code");
			_isPublicRoom = GetLobbyData(_lobbyId, "room_visibility") == "public";
			if (string.IsNullOrWhiteSpace(_roomCode))
			{
				_roomCode = GenerateRoomCode(_lobbyId);
			}
			RefreshPeer();
			Status = "Joined room " + _roomCode + ".";
			_log($"Joined Steam room {_lobbyId}.");
		}

		private void CompleteRoomCodeSearch(ulong call)
		{
			string pendingRoomCode = _pendingRoomCode;
			_pendingRoomCode = "";
			if (!TryGetCallResult<LobbyMatchListResult>(call, 510, out var result) || result.LobbiesMatching == 0)
			{
				Status = "Room " + pendingRoomCode + " was not found.";
				return;
			}
			uint num = Math.Min(result.LobbiesMatching, 50u);
			for (int i = 0; i < num; i++)
			{
				ulong num2 = Native.SteamAPI_ISteamMatchmaking_GetLobbyByIndex(_matchmaking, i);
				if (num2 != 0L)
				{
					JoinRoom(num2);
					return;
				}
			}
			Status = "Room " + pendingRoomCode + " was not found.";
		}

		private void CompleteList(ulong call)
		{
			if (!TryGetCallResult<LobbyMatchListResult>(call, 510, out var result))
			{
				Status = "Could not retrieve the room list.";
				return;
			}
			_rooms.Clear();
			uint num = Math.Min(result.LobbiesMatching, 20u);
			for (int i = 0; i < num; i++)
			{
				ulong num2 = Native.SteamAPI_ISteamMatchmaking_GetLobbyByIndex(_matchmaking, i);
				if (num2 != 0L && !(GetLobbyData(num2, "room_visibility") != "public"))
				{
					string text = GetLobbyData(num2, "room_name");
					if (string.IsNullOrWhiteSpace(text))
					{
						text = $"Room {num2}";
					}
					int members = Native.SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(_matchmaking, num2);
					_rooms.Add(new RoomInfo(num2, text, members));
				}
			}
			Status = ((_rooms.Count == 0) ? "No public rooms found." : $"Found {_rooms.Count} public room(s).");
		}

		private void RefreshPeer()
		{
			if (_lobbyId == 0L)
			{
				_peerSteamId = 0uL;
				return;
			}
			int num = Native.SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(_matchmaking, _lobbyId);
			ulong num2 = 0uL;
			for (int i = 0; i < num; i++)
			{
				ulong num3 = Native.SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(_matchmaking, _lobbyId, i);
				if (num3 != 0L && num3 != _localSteamId)
				{
					num2 = num3;
					break;
				}
			}
			if (num2 != _peerSteamId)
			{
				_peerSteamId = num2;
				Status = ((num2 == 0L) ? ("Room " + _roomCode + ": waiting for another player...") : $"Connected to Steam peer {num2}.");
			}
		}

		private static string GenerateRoomCode(ulong lobbyId)
		{
			return (100000 + lobbyId % 900000).ToString();
		}

		private string GetLobbyData(ulong lobbyId, string key)
		{
			IntPtr intPtr = Native.SteamAPI_ISteamMatchmaking_GetLobbyData(_matchmaking, lobbyId, key);
			object obj;
			if (!(intPtr == IntPtr.Zero))
			{
				obj = Marshal.PtrToStringUTF8(intPtr);
				if (obj == null)
				{
					return "";
				}
			}
			else
			{
				obj = "";
			}
			return (string)obj;
		}

		private string GetPersonaName()
		{
			if (_friends == IntPtr.Zero)
			{
				return "Friend";
			}
			IntPtr intPtr = Native.SteamAPI_ISteamFriends_GetPersonaName(_friends);
			object obj;
			if (!(intPtr == IntPtr.Zero))
			{
				obj = Marshal.PtrToStringUTF8(intPtr);
				if (obj == null)
				{
					return "Friend";
				}
			}
			else
			{
				obj = "Friend";
			}
			return (string)obj;
		}

		private bool TryGetCallResult<T>(ulong call, int callbackId, out T result) where T : struct
		{
			int num = Marshal.SizeOf<T>();
			IntPtr intPtr = Marshal.AllocHGlobal(num);
			try
			{
				bool failed = false;
				if (!Native.SteamAPI_ISteamUtils_GetAPICallResult(_utils, call, intPtr, num, callbackId, ref failed) || failed)
				{
					result = default(T);
					return false;
				}
				result = Marshal.PtrToStructure<T>(intPtr);
				return true;
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				LeaveRoom();
				_disposed = true;
			}
		}
	}
	internal sealed class SteamP2P : IDisposable
	{
		private static class Native
		{
			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			internal static extern IntPtr SteamAPI_SteamNetworking_v006();

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamNetworking_SendP2PPacket(IntPtr self, ulong steamIdRemote, IntPtr data, uint dataSize, int sendType, int channel);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamNetworking_IsP2PPacketAvailable(IntPtr self, ref uint messageSize, int channel);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamNetworking_ReadP2PPacket(IntPtr self, IntPtr destination, uint destinationSize, ref uint messageSize, ref ulong steamIdRemote, int channel);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(IntPtr self, ulong steamIdRemote);

			[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
			[return: MarshalAs(UnmanagedType.I1)]
			internal static extern bool SteamAPI_ISteamNetworking_CloseP2PSessionWithUser(IntPtr self, ulong steamIdRemote);
		}

		private const int Channel = 7319;

		private const int SendUnreliableNoDelay = 1;

		private const int SendReliable = 2;

		private const int MaxPacketSize = 1024;

		private IntPtr _networking;

		private ulong _peer;

		private bool _disposed;

		public bool IsReady
		{
			get
			{
				if (_networking != IntPtr.Zero)
				{
					return _peer != 0;
				}
				return false;
			}
		}

		public bool Open(ulong peer)
		{
			Close();
			_networking = Native.SteamAPI_SteamNetworking_v006();
			_peer = peer;
			if (!IsReady)
			{
				return false;
			}
			Native.SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(_networking, _peer);
			return true;
		}

		public bool Send(ReadOnlySpan<byte> packet)
		{
			return Send(packet, 1);
		}

		public bool SendReliablePacket(ReadOnlySpan<byte> packet)
		{
			return Send(packet, 2);
		}

		private bool Send(ReadOnlySpan<byte> packet, int sendType)
		{
			if (!IsReady || packet.Length == 0 || packet.Length > 1024)
			{
				return false;
			}
			IntPtr intPtr = Marshal.AllocHGlobal(packet.Length);
			try
			{
				Marshal.Copy(packet.ToArray(), 0, intPtr, packet.Length);
				return Native.SteamAPI_ISteamNetworking_SendP2PPacket(_networking, _peer, intPtr, (uint)packet.Length, sendType, 7319);
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}

		public bool TryReceive(out byte[] packet)
		{
			packet = Array.Empty<byte>();
			if (!IsReady)
			{
				return false;
			}
			uint messageSize = 0u;
			if (!Native.SteamAPI_ISteamNetworking_IsP2PPacketAvailable(_networking, ref messageSize, 7319) || messageSize == 0 || messageSize > 1024)
			{
				return false;
			}
			IntPtr intPtr = Marshal.AllocHGlobal((int)messageSize);
			try
			{
				uint messageSize2 = 0u;
				ulong steamIdRemote = 0uL;
				if (!Native.SteamAPI_ISteamNetworking_ReadP2PPacket(_networking, intPtr, messageSize, ref messageSize2, ref steamIdRemote, 7319) || steamIdRemote != _peer || messageSize2 == 0 || messageSize2 > messageSize)
				{
					return false;
				}
				packet = new byte[messageSize2];
				Marshal.Copy(intPtr, packet, 0, (int)messageSize2);
				return true;
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}

		public void Close()
		{
			if (_networking != IntPtr.Zero && _peer != 0L)
			{
				Native.SteamAPI_ISteamNetworking_CloseP2PSessionWithUser(_networking, _peer);
			}
			_networking = IntPtr.Zero;
			_peer = 0uL;
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				Close();
				_disposed = true;
			}
		}
	}
	internal sealed class VoiceChat : IDisposable
	{
		private const int CaptureSeconds = 1;

		private const int PlaybackSamples = 32000;

		private const int PlaybackLeadSamples = 1600;

		private const int MaximumFramesPerTick = 4;

		private AudioClip _microphoneClip;

		private int _microphoneReadPosition;

		private uint _sendSequence;

		private GameObject _speakerObject;

		private AudioSource _speaker;

		private AudioClip _playbackClip;

		private int _playbackWritePosition;

		private uint _lastReceiveSequence;

		private bool _hasReceiveSequence;

		private bool _disposed;

		public string Status { get; private set; } = "Voice: waiting";

		public void Tick(bool enabled, Transform remoteHead, Action<byte[]> send)
		{
			if (!_disposed)
			{
				AttachSpeaker(remoteHead);
				if (!enabled)
				{
					StopMicrophone();
					Status = "Voice: muted";
				}
				else if (EnsureMicrophone())
				{
					CaptureAvailableFrames(send);
					Status = "Voice: live";
				}
			}
		}

		public void HandleFrame(VoiceFrame frame, Transform remoteHead)
		{
			if (_disposed || (Object)(object)remoteHead == (Object)null)
			{
				return;
			}
			EnsureSpeaker(remoteHead);
			if ((Object)(object)_speaker == (Object)null || (Object)(object)_playbackClip == (Object)null || (_hasReceiveSequence && !IsNewer(frame.Sequence, _lastReceiveSequence)))
			{
				return;
			}
			int num = 0;
			if (_hasReceiveSequence)
			{
				uint num2 = frame.Sequence - _lastReceiveSequence;
				if (num2 > 1 && num2 <= 6)
				{
					num = (int)(num2 - 1);
				}
			}
			_lastReceiveSequence = frame.Sequence;
			_hasReceiveSequence = true;
			if (!_speaker.isPlaying)
			{
				ClearPlaybackClip();
				_playbackWritePosition = 1600;
				_speaker.timeSamples = 0;
				_speaker.Play();
			}
			else
			{
				int timeSamples = _speaker.timeSamples;
				int num3 = (_playbackWritePosition - timeSamples + 32000) % 32000;
				if (num3 < 640 || num3 > 16000)
				{
					_playbackWritePosition = (timeSamples + 1600) % 32000;
				}
			}
			for (int i = 0; i < num; i++)
			{
				WritePlaybackFrame(null);
			}
			WritePlaybackFrame(frame.Samples);
		}

		public void Reset()
		{
			StopMicrophone();
			if ((Object)(object)_speakerObject != (Object)null)
			{
				Object.Destroy((Object)(object)_speakerObject);
			}
			if ((Object)(object)_playbackClip != (Object)null)
			{
				Object.Destroy((Object)(object)_playbackClip);
			}
			_speakerObject = null;
			_speaker = null;
			_playbackClip = null;
			_playbackWritePosition = 0;
			_lastReceiveSequence = 0u;
			_hasReceiveSequence = false;
			_sendSequence = 0u;
			Status = "Voice: waiting";
		}

		private bool EnsureMicrophone()
		{
			if ((Object)(object)_microphoneClip != (Object)null && Microphone.IsRecording((string)null))
			{
				return true;
			}
			try
			{
				StopMicrophone();
				_microphoneClip = Microphone.Start((string)null, true, 1, 16000);
				if ((Object)(object)_microphoneClip == (Object)null)
				{
					Status = "Voice: no microphone";
					return false;
				}
				_microphoneReadPosition = Microphone.GetPosition((string)null);
				Status = "Voice: starting...";
				return true;
			}
			catch
			{
				_microphoneClip = null;
				Status = "Voice: mic unavailable";
				return false;
			}
		}

		private void CaptureAvailableFrames(Action<byte[]> send)
		{
			int position = Microphone.GetPosition((string)null);
			if (position < 0 || (Object)(object)_microphoneClip == (Object)null)
			{
				return;
			}
			int num = ((position >= _microphoneReadPosition) ? (position - _microphoneReadPosition) : (_microphoneClip.samples - _microphoneReadPosition + position));
			int num2 = 0;
			while (num >= 320 && num2++ < 4)
			{
				Il2CppStructArray<float> val = new Il2CppStructArray<float>(320L);
				if (_microphoneClip.GetData(val, _microphoneReadPosition))
				{
					short[] array = new short[320];
					for (int i = 0; i < array.Length; i++)
					{
						float num3 = Mathf.Clamp(((Il2CppArrayBase<float>)(object)val)[i], -1f, 1f);
						array[i] = (short)Mathf.RoundToInt(num3 * 32767f);
					}
					send(VoicePacket.Encode(++_sendSequence, array));
					_microphoneReadPosition = (_microphoneReadPosition + 320) % _microphoneClip.samples;
					num -= 320;
					continue;
				}
				break;
			}
		}

		private void EnsureSpeaker(Transform remoteHead)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_speakerObject != (Object)null)
			{
				AttachSpeaker(remoteHead);
				return;
			}
			_speakerObject = new GameObject("BrutalistickCoop_RemoteVoice");
			_speakerObject.transform.SetParent(remoteHead, false);
			_speakerObject.transform.localPosition = Vector3.zero;
			_speaker = _speakerObject.AddComponent<AudioSource>();
			_speaker.playOnAwake = false;
			_speaker.loop = true;
			_speaker.spatialBlend = 1f;
			_speaker.volume = 1f;
			_speaker.minDistance = 1f;
			_speaker.maxDistance = 25f;
			_speaker.dopplerLevel = 0f;
			_speaker.rolloffMode = (AudioRolloffMode)1;
			_playbackClip = AudioClip.Create("BrutalistickCoop_RemoteVoiceClip", 32000, 1, 16000, false);
			_speaker.clip = _playbackClip;
			ClearPlaybackClip();
		}

		private void AttachSpeaker(Transform remoteHead)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_speakerObject == (Object)null) && !((Object)(object)remoteHead == (Object)null) && (Object)(object)_speakerObject.transform.parent != (Object)(object)remoteHead)
			{
				_speakerObject.transform.SetParent(remoteHead, false);
				_speakerObject.transform.localPosition = Vector3.zero;
			}
		}

		private void WritePlaybackFrame(short[] samples)
		{
			Il2CppStructArray<float> val = new Il2CppStructArray<float>(320L);
			if (samples != null)
			{
				for (int i = 0; i < samples.Length; i++)
				{
					((Il2CppArrayBase<float>)(object)val)[i] = (float)samples[i] / 32768f;
				}
			}
			_playbackClip.SetData(val, _playbackWritePosition);
			_playbackWritePosition = (_playbackWritePosition + 320) % 32000;
		}

		private void ClearPlaybackClip()
		{
			if (!((Object)(object)_playbackClip == (Object)null))
			{
				Il2CppStructArray<float> val = new Il2CppStructArray<float>(32000L);
				_playbackClip.SetData(val, 0);
			}
		}

		private void StopMicrophone()
		{
			if ((Object)(object)_microphoneClip == (Object)null)
			{
				return;
			}
			try
			{
				if (Microphone.IsRecording((string)null))
				{
					Microphone.End((string)null);
				}
			}
			catch
			{
			}
			Object.Destroy((Object)(object)_microphoneClip);
			_microphoneClip = null;
			_microphoneReadPosition = 0;
		}

		private static bool IsNewer(uint incoming, uint previous)
		{
			return (int)(incoming - previous) > 0;
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				Reset();
				_disposed = true;
			}
		}
	}
	internal readonly record struct VoiceFrame(uint Sequence, short[] Samples);
	internal static class VoicePacket
	{
		private const uint Magic = 1296258626u;

		private const byte ProtocolVersion = 1;

		private const byte VoiceMessage = 5;

		private const int HeaderLength = 12;

		public const int FrameSamples = 320;

		public const int SampleRate = 16000;

		public const int PacketLength = 652;

		public static byte[] Encode(uint sequence, short[] samples)
		{
			if (samples == null || samples.Length != 320)
			{
				throw new ArgumentException($"Voice frames contain exactly {320} samples.");
			}
			byte[] array = new byte[652];
			int offset = 0;
			WriteUInt(array, ref offset, 1296258626u);
			array[offset++] = 1;
			array[offset++] = 5;
			WriteUInt(array, ref offset, sequence);
			WriteUShort(array, ref offset, 320);
			for (int i = 0; i < samples.Length; i++)
			{
				WriteShort(array, ref offset, samples[i]);
			}
			return array;
		}

		public static bool TryDecode(ReadOnlySpan<byte> data, out VoiceFrame frame)
		{
			frame = default(VoiceFrame);
			if (data.Length != 652)
			{
				return false;
			}
			int offset = 0;
			if (ReadUInt(data, ref offset) != 1296258626 || data[offset++] != 1 || data[offset++] != 5)
			{
				return false;
			}
			uint sequence = ReadUInt(data, ref offset);
			if (ReadUShort(data, ref offset) != 320)
			{
				return false;
			}
			short[] array = new short[320];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = ReadShort(data, ref offset);
			}
			frame = new VoiceFrame(sequence, array);
			return true;
		}

		private static void WriteUInt(byte[] data, ref int offset, uint value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static uint ReadUInt(ReadOnlySpan<byte> data, ref int offset)
		{
			uint result = BitConverter.ToUInt32(data.Slice(offset, 4));
			offset += 4;
			return result;
		}

		private static void WriteUShort(byte[] data, ref int offset, ushort value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static ushort ReadUShort(ReadOnlySpan<byte> data, ref int offset)
		{
			ushort result = BitConverter.ToUInt16(data.Slice(offset, 2));
			offset += 2;
			return result;
		}

		private static void WriteShort(byte[] data, ref int offset, short value)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			Buffer.BlockCopy(bytes, 0, data, offset, bytes.Length);
			offset += bytes.Length;
		}

		private static short ReadShort(ReadOnlySpan<byte> data, ref int offset)
		{
			short result = BitConverter.ToInt16(data.Slice(offset, 2));
			offset += 2;
			return result;
		}
	}
}