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 _enabled; private MelonPreferences_Entry _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("Enabled", true, "Enable two-player pose synchronization", (string)null, false, false, (ValueValidator)null, (string)null); _voiceEnabled = val.CreateEntry("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 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 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(); 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 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 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 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 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 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 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 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 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 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 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(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } Renderer component2 = obj.GetComponent(); 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 _localObjects = new Dictionary(); private readonly Dictionary _remoteObjects = new Dictionary(); private readonly Dictionary _pendingSpawns = new Dictionary(); private readonly List _spawnGuns = new List(); 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 sendUnreliable, Action 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 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 hashSet = new HashSet(); 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 list = new List(); foreach (KeyValuePair 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.op_Implicit(Object.FindObjectsOfType()); 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 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(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(_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 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.op_Implicit(clone.GetComponentsInChildren(true)); for (int i = 0; i < array.Length; i++) { ((Component)array[i]).gameObject.layer = layer; } Renderer[] array2 = Il2CppArrayBase.op_Implicit(clone.GetComponentsInChildren(true)); for (int i = 0; i < array2.Length; i++) { array2[i].enabled = true; } Rigidbody[] array3 = Il2CppArrayBase.op_Implicit(clone.GetComponentsInChildren(true)); foreach (Rigidbody obj in array3) { obj.isKinematic = true; obj.useGravity = false; } Collider[] array4 = Il2CppArrayBase.op_Implicit(clone.GetComponentsInChildren(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 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 _log; private readonly List _rooms = new List(); 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 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 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(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(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(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(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(ulong call, int callbackId, out T result) where T : struct { int num = Marshal.SizeOf(); 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(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 packet) { return Send(packet, 1); } public bool SendReliablePacket(ReadOnlySpan packet) { return Send(packet, 2); } private bool Send(ReadOnlySpan 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(); 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 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 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 val = new Il2CppStructArray(320L); if (_microphoneClip.GetData(val, _microphoneReadPosition)) { short[] array = new short[320]; for (int i = 0; i < array.Length; i++) { float num3 = Mathf.Clamp(((Il2CppArrayBase)(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(); _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 val = new Il2CppStructArray(320L); if (samples != null) { for (int i = 0; i < samples.Length; i++) { ((Il2CppArrayBase)(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 val = new Il2CppStructArray(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 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 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 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 data, ref int offset) { short result = BitConverter.ToInt16(data.Slice(offset, 2)); offset += 2; return result; } } }