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;
}
}
}