Decompiled source of Bonegle v1.1.0

Mods/Bonegle.dll

Decompiled a day ago
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using BoneLib;
using BoneLib.BoneMenu;
using Bonegle;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Input;
using Il2CppSLZ.Marrow.Utilities;
using Il2CppSLZ.VRMK;
using Il2CppSystem;
using Il2CppTMPro;
using LabFusion.Marrow.Proxies;
using LabFusion.Menu;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.Preferences.Client;
using LabFusion.SDK.Metadata;
using LabFusion.UI.Popups;
using LabFusion.Voice;
using LabFusion.Voice.Unity;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using Steamworks;
using Steamworks.Data;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(BonegleMod), "Bonegle", "1.1.0", "MarrowByte", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonAdditionalDependencies(new string[] { "BoneLib", "LabFusion" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("MarrowByte")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("Bonegle")]
[assembly: AssemblyTitle("Bonegle")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Bonegle
{
	public sealed class BonegleMod : MelonMod
	{
		private Preferences _prefs;

		private CallSession _call;

		private CallPanel _panel;

		private Contacts _contacts;

		private SocialMenu _social;

		private MicrophoneMenu _micMenu;

		private VideoMenu _videoMenu;

		private readonly CallNetwork _network = new CallNetwork();

		private readonly CallMicrophone _microphone = new CallMicrophone(new MicrophoneCapture(), new FusionMicrophone());

		private readonly VoicePlayer _voice = new VoicePlayer();

		private readonly VideoFeed _video = new VideoFeed();

		private bool _menuBuilt;

		private bool _suspended = true;

		private Guid _mediaCall;

		private float _nextMenu;

		private float _nextWarning;

		private float _sceneCooldown;

		private float _lastVideo;

		private FunctionElement? _networkName;

		public override void OnInitializeMelon()
		{
			_prefs = new Preferences();
			_call = new CallSession(_network);
			_contacts = new Contacts(_network, new ContactStore(Path.Combine(MelonEnvironment.UserDataDirectory, "CrossTalk")));
			_network.Initialize(_call, _contacts, ((MelonBase)this).HarmonyInstance);
			_social = new SocialMenu(_network, _call, _contacts, ShowPanel);
			_panel = new CallPanel(_call, _prefs, _video, _network, _contacts, _microphone, MediaChanged);
			_micMenu = new MicrophoneMenu(_microphone, _prefs, _call, _network.Voice);
			_videoMenu = new VideoMenu(_video, _prefs, _network);
			((MelonBase)this).LoggerInstance.Msg("Bonegle 1.1.0. Open the wrist panel or press F6 to call.");
		}

		public override void OnUpdate()
		{
			if (_call == null)
			{
				return;
			}
			try
			{
				EnsureMenu();
				_network.Tick(Time.unscaledTime);
				string text = "Call network: " + _network.NetworkName;
				if (_networkName != null && ((Element)_networkName).ElementName != text)
				{
					((Element)_networkName).ElementName = text;
				}
				_network.Voice.UseCall(_call.Call);
				_social.Tick(Time.unscaledTime);
				_micMenu.Tick(Time.unscaledTime);
				_videoMenu.Tick(Time.unscaledTime);
				if (_mediaCall != _call.Call)
				{
					StopMedia();
					_mediaCall = _call.Call;
				}
				Transform head = Player.Head;
				if (!((Object)(object)head != (Object)null) || !((Object)(object)Player.RigManager != (Object)null) || !(Time.unscaledTime >= _sceneCooldown))
				{
					Suspend();
					return;
				}
				if (_suspended)
				{
					_suspended = false;
					_call.SendMediaState();
				}
				if (Input.GetKeyDown((KeyCode)287))
				{
					_panel.Toggle(head);
				}
				_panel.Tick(head);
				if (_call.State != CallState.Active)
				{
					StopMedia();
					return;
				}
				if (_call.Mic)
				{
					_microphone.Tick(Time.unscaledTime, new MicSettings(_prefs.Device, _prefs.DeviceName, FusionMicrophone.DeviceName, _prefs.MicGain), delegate(byte[] bytes)
					{
						_network.SendMedia(2, _call.Call, bytes);
					});
				}
				else
				{
					_microphone.Dispose();
				}
				if (_call.Camera)
				{
					_video.Read((byte[] bytes) => _network.SendMedia(1, _call.Call, bytes), Time.unscaledTime);
				}
				else
				{
					_video.Pause();
				}
				byte[] data;
				while (_network.TryAudio(out data))
				{
					if (_call.Accepts(data))
					{
						try
						{
							_network.Voice.Playback(_call.Call, _voice.Play(data, _prefs.Volume));
						}
						catch (Exception ex)
						{
							_network.Voice.Playback(_call.Call, queued: false, ex.Message);
							_voice.Dispose();
						}
					}
				}
				if (!_call.RemoteMic)
				{
					_voice.Stop();
				}
				byte[] array = _network.TakeVideo();
				if (array != null && _call.Accepts(array) && _video.Receive(array, Time.unscaledTime))
				{
					_lastVideo = Time.unscaledTime;
				}
				if (!_call.RemoteCamera || Time.unscaledTime - _lastVideo > 3f)
				{
					_video.ClearRemote();
				}
			}
			catch (Exception ex2)
			{
				Suspend();
				if (Time.unscaledTime >= _nextWarning)
				{
					_nextWarning = Time.unscaledTime + 5f;
					((MelonBase)this).LoggerInstance.Warning("Bonegle paused: " + ex2.GetType().Name + ": " + ex2.Message);
					_call.Status = "Call paused. Check the MelonLoader log.";
				}
			}
		}

		public override void OnLateUpdate()
		{
			if (_call == null || _suspended || _call.State != CallState.Active || !_call.Camera || (Object)(object)Player.Head == (Object)null)
			{
				return;
			}
			try
			{
				_video.Capture(Player.Head, Time.unscaledTime, _prefs.Selfie, _prefs.SelfieDistance, _prefs.Video);
			}
			catch (Exception ex)
			{
				_call.Camera = false;
				MediaChanged();
				_call.Status = "Camera unavailable: " + ex.GetType().Name;
			}
		}

		private void Connect()
		{
			StopMedia();
			_network.Connect();
		}

		private void ShowPanel()
		{
			if ((Object)(object)Player.Head != (Object)null && !_panel.Visible)
			{
				_panel.Toggle(Player.Head);
			}
		}

		private void Disconnect()
		{
			_network.Disconnect();
			StopMedia();
		}

		private void MediaChanged()
		{
			if (!_call.Mic)
			{
				_microphone.Dispose();
			}
			if (!_call.Camera)
			{
				_video.Pause();
			}
			_network.ClearMedia();
			_call.SendMediaState();
		}

		private void StopMedia()
		{
			_microphone.Dispose();
			_video.Pause();
			_voice.Stop();
			_video.ClearRemote();
			_network.ClearMedia();
		}

		private void Suspend()
		{
			if (!_suspended && _call.State == CallState.Active)
			{
				_network.Send(_call.PeerId, new Signal
				{
					Type = "media",
					Call = _call.Call.ToString(),
					Mic = false,
					Camera = false
				});
			}
			_suspended = true;
			StopMedia();
			_panel.Suspend();
		}

		private void EnsureMenu()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: 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_01c2: Unknown result type (might be due to invalid IL or missing references)
			if (_menuBuilt || Time.unscaledTime < _nextMenu)
			{
				return;
			}
			_nextMenu = Time.unscaledTime + 2f;
			if (Page.Root == null)
			{
				return;
			}
			Color val = default(Color);
			((Color)(ref val))..ctor(0.27f, 0.94f, 0.76f);
			Page val2 = Page.Root.CreatePage("Bonegle", val, 0, true);
			_networkName = val2.CreateFunction("Call network: Offline", Color.white, (Action)null);
			val2.CreateFunction("Open / Close Wrist Panel", val, (Action)delegate
			{
				if ((Object)(object)Player.Head != (Object)null)
				{
					_panel.Toggle(Player.Head);
				}
			});
			_social.Build(val2);
			val2.CreateFunction("Refresh Friends", val, (Action)_network.RefreshFriends);
			val2.CreateFunction("Enable Calls", val, (Action)Connect);
			val2.CreateFunction("Disable Calls", Color.white, (Action)Disconnect);
			val2.CreateBool("Use Right Wrist", val, _prefs.RightWrist, (Action<bool>)delegate(bool value)
			{
				_prefs.RightWrist = value;
			});
			val2.CreateFloat("Call Volume", val, _prefs.Volume, 0.1f, 0f, 1f, (Action<float>)delegate(float value)
			{
				_prefs.Volume = value;
			});
			_micMenu.Build(val2);
			_videoMenu.Build(val2);
			val2.CreateFloat("Selfie Distance", val, _prefs.SelfieDistance, 0.1f, 0.4f, 2.5f, (Action<float>)delegate(float value)
			{
				_prefs.SelfieDistance = value;
			});
			val2.CreateFunction("Mute / Unmute Call", val, (Action)delegate
			{
				_call.Mic = !_call.Mic;
				MediaChanged();
			});
			val2.CreateFunction("Camera On / Off", val, (Action)delegate
			{
				_call.Camera = !_call.Camera;
				MediaChanged();
			});
			val2.CreateFunction("End Call", Color.red, (Action)_call.HangUp);
			_menuBuilt = true;
		}

		public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
		{
			if (_call != null)
			{
				_sceneCooldown = Time.unscaledTime + 0.5f;
				Suspend();
			}
		}

		public override void OnDeinitializeMelon()
		{
			_social?.Dispose();
			_network.Dispose();
			_microphone.Dispose();
			_voice.Dispose();
			_video.Dispose();
			_panel?.Dispose();
		}
	}
	internal readonly record struct MicSettings(int Device, string Name, string AutomaticName, float Gain);
	internal interface IMicrophoneCapture : IDisposable
	{
		bool Running { get; }

		IReadOnlyList<MicDevice> GetDevices();

		void Start(int device);

		void Poll(Action<byte[]> send);
	}
	internal interface ISharedMicrophone
	{
		bool Read(string device, float now, Action<byte[]> send);

		void Reset();
	}
	internal sealed class CallMicrophone(IMicrophoneCapture windows, ISharedMicrophone fusion) : IDisposable
	{
		private float _retry;

		private float _started;

		private float _lastPacket;

		private float _lastSignal;

		private float _nextSourceCheck;

		private int _selection = int.MinValue;

		private string _savedName = "";

		private string _fusionName = "";

		private bool _shared;

		internal bool Running { get; private set; }

		internal float Level { get; private set; }

		internal string Source { get; private set; } = "Microphone idle";

		internal string Error { get; private set; } = "";

		internal int Bars => Math.Clamp((int)Math.Ceiling(Level * 20f), 0, 5);

		internal bool Silent(float now)
		{
			if (Running)
			{
				return now - Math.Max(_started, _lastSignal) > 8f;
			}
			return false;
		}

		internal void Tick(float now, MicSettings settings, Action<byte[]> send)
		{
			Level *= 0.88f;
			if (now >= _nextSourceCheck)
			{
				_nextSourceCheck = now + 1f;
				if (_selection != settings.Device || _savedName != settings.Name || (settings.Device < 0 && _fusionName != settings.AutomaticName))
				{
					Dispose();
					_selection = settings.Device;
					_savedName = settings.Name;
					_fusionName = settings.AutomaticName;
				}
			}
			if (now < _retry)
			{
				return;
			}
			try
			{
				if (_shared && ReadShared(now, Emit))
				{
					Started(now);
					return;
				}
				if (!windows.Running)
				{
					MicDevice micDevice = MicDevice.Select(windows.GetDevices(), _selection, _savedName, _fusionName);
					try
					{
						windows.Start(micDevice.Id);
					}
					catch when (_selection < 0)
					{
						windows.Dispose();
						if (ReadShared(now, Emit))
						{
							Started(now);
							return;
						}
						throw;
					}
					fusion.Reset();
					_shared = false;
					Source = "Windows: " + micDevice.Name;
					_lastPacket = now;
				}
				windows.Poll(Emit);
				if (now - _lastPacket > 3f)
				{
					throw new InvalidOperationException("Microphone stopped responding. Retrying...");
				}
				Started(now);
			}
			catch (Exception ex)
			{
				Dispose();
				Error = ex.Message;
				_retry = now + 3f;
			}
			void Emit(byte[] bytes)
			{
				float num = PcmAudio.ApplyGain(bytes, settings.Gain);
				Level = Math.Max(Level, num);
				_lastPacket = now;
				if (num >= 0.0015f)
				{
					_lastSignal = now;
				}
				send(bytes);
			}
		}

		private bool ReadShared(float now, Action<byte[]> send)
		{
			try
			{
				if (fusion.Read(_fusionName, now, send))
				{
					_shared = true;
					Source = "Fusion: " + (string.IsNullOrEmpty(_fusionName) ? "default microphone" : _fusionName);
					return true;
				}
			}
			catch
			{
			}
			fusion.Reset();
			_shared = false;
			return false;
		}

		private void Started(float now)
		{
			if (!Running)
			{
				_started = now;
			}
			Running = true;
			Error = "";
		}

		internal void Restart()
		{
			Dispose();
			_nextSourceCheck = 0f;
		}

		public void Dispose()
		{
			Error = "";
			try
			{
				windows.Dispose();
			}
			catch (Exception ex)
			{
				Error = ex.Message;
			}
			fusion.Reset();
			_shared = false;
			Running = false;
			Level = 0f;
			_started = (_lastPacket = (_lastSignal = (_retry = 0f)));
			Source = "Microphone idle";
		}
	}
	internal interface ICallTransport : IContactNetwork, ICallNetwork, IDisposable
	{
		IReadOnlyList<FriendRow> Friends { get; }

		IReadOnlyList<LobbyRow> Players { get; }

		string VideoWarning { get; }

		void Initialize(CallSession call, Contacts contacts, Harmony harmony);

		void Tick(double now);

		void Connect();

		void Disconnect();

		void RefreshFriends();

		void RefreshPlayers();

		bool SendMedia(byte kind, Guid call, byte[] payload);

		bool TryAudio(out byte[] data);

		byte[]? TakeVideo();

		void ClearMedia();
	}
	internal sealed class CallNetwork : IContactNetwork, ICallNetwork, IDisposable
	{
		private ICallTransport? _transport;

		private CallSession _call;

		private Contacts _contacts;

		private Harmony _harmony;

		private object? _layer;

		private bool _enabled = true;

		internal VoiceStats Voice { get; } = new VoiceStats();

		public PeerId Self => _transport?.Self ?? default(PeerId);

		public bool Online
		{
			get
			{
				if (_enabled)
				{
					return _transport?.Online ?? false;
				}
				return false;
			}
		}

		public bool Busy => _call.State != CallState.Ready;

		internal bool HasSteamFriends => _transport is SteamNetwork;

		internal string NetworkName
		{
			get
			{
				if (!(_transport is EpicNetwork))
				{
					if (!HasSteamFriends)
					{
						return "Offline";
					}
					return "Steam";
				}
				return "Epic Online Services";
			}
		}

		internal IReadOnlyList<FriendRow> Friends => _transport?.Friends ?? Array.Empty<FriendRow>();

		internal IReadOnlyList<LobbyRow> Players => _transport?.Players ?? Array.Empty<LobbyRow>();

		internal string VideoWarning => _transport?.VideoWarning ?? "";

		internal void Initialize(CallSession call, Contacts contacts, Harmony harmony)
		{
			_call = call;
			_contacts = contacts;
			_harmony = harmony;
		}

		internal void Tick(double now)
		{
			NetworkLayer val = (NetworkLayerManager.LoggedIn ? NetworkLayerManager.Layer : null);
			if (val != _layer)
			{
				_transport?.Disconnect();
				_transport?.Dispose();
				_transport = null;
				_layer = val;
				if (((val != null) ? val.Platform : null) == "Epic")
				{
					_transport = new EpicNetwork(Voice);
				}
				else if (val is SteamNetworkLayer)
				{
					_transport = new SteamNetwork(Voice);
				}
				if (_transport != null)
				{
					_transport.Initialize(_call, _contacts, _harmony);
					if (!_enabled)
					{
						_transport.Disconnect();
					}
				}
			}
			if (_transport == null)
			{
				_call.Offline(_enabled ? "Connect to Fusion's Steam or Epic network." : "Calls are off. Enable calls in BoneMenu.");
			}
			else
			{
				_transport.Tick(now);
			}
		}

		internal void Connect()
		{
			_enabled = true;
			_transport?.Connect();
		}

		internal void Disconnect()
		{
			_enabled = false;
			_transport?.Disconnect();
		}

		public bool CanCall(PeerId peer)
		{
			if (Online)
			{
				return _transport.CanCall(peer);
			}
			return false;
		}

		public bool IsBlocked(PeerId peer)
		{
			if (Online)
			{
				return _transport.IsBlocked(peer);
			}
			return true;
		}

		public bool SameLobby(PeerId peer)
		{
			if (Online)
			{
				return _transport.SameLobby(peer);
			}
			return false;
		}

		public string Name(PeerId peer)
		{
			return _transport?.Name(peer) ?? _contacts.Get(peer)?.Name ?? "Player";
		}

		public bool Send(PeerId peer, Signal message)
		{
			if (Online)
			{
				return _transport.Send(peer, message);
			}
			return false;
		}

		public void Close(PeerId peer)
		{
			_transport?.Close(peer);
		}

		internal void RefreshFriends()
		{
			_transport?.RefreshFriends();
		}

		internal void RefreshPlayers()
		{
			_transport?.RefreshPlayers();
		}

		internal bool SendMedia(byte kind, Guid call, byte[] payload)
		{
			if (Online)
			{
				return _transport.SendMedia(kind, call, payload);
			}
			return false;
		}

		internal bool TryAudio(out byte[] data)
		{
			data = null;
			return _transport?.TryAudio(out data) ?? false;
		}

		internal byte[]? TakeVideo()
		{
			return _transport?.TakeVideo();
		}

		internal void ClearMedia()
		{
			_transport?.ClearMedia();
		}

		public void Dispose()
		{
			_transport?.Dispose();
			_transport = null;
		}
	}
	internal sealed class CallPanel : IDisposable
	{
		private sealed class Control
		{
			internal Image Image;

			internal Text Label;

			internal Rect Bounds;

			internal Color Color;

			internal Action Action;

			internal bool Enabled = true;
		}

		private sealed record PersonRow(string Name, string Status, bool Enabled, bool Social, Action Action, Action? Decline = null);

		private static readonly Color Ink = new Color(0.035f, 0.047f, 0.08f, 0.98f);

		private static readonly Color Card = new Color(0.075f, 0.09f, 0.145f, 1f);

		private static readonly Color Muted = new Color(0.55f, 0.61f, 0.72f, 1f);

		private static readonly Color Mint = new Color(0.27f, 0.94f, 0.76f, 1f);

		private static readonly Color Purple = new Color(0.49f, 0.4f, 0.98f, 1f);

		private static readonly Color Red = new Color(0.94f, 0.3f, 0.4f, 1f);

		private readonly CallSession _call;

		private readonly Preferences _prefs;

		private readonly CallNetwork _network;

		private readonly Contacts _contacts;

		private readonly Action _mediaChanged;

		private readonly VideoFeed _video;

		private readonly CallMicrophone _microphone;

		private readonly List<Control> _controls = new List<Control>();

		private readonly PanelDrag _drag = new PanelDrag();

		private static readonly Rect TitleBounds = new Rect(24f, 18f, 784f, 70f);

		private Image _titleBar;

		private Vector3 _dragAnchor;

		private bool _dragVr;

		private GameObject? _root;

		private GameObject? _wrist;

		private Font? _font;

		private Sprite? _round;

		private Texture2D? _roundTexture;

		private Text _status;

		private Text _friendCount;

		private Text _peopleTitle;

		private Text _page;

		private Text _overlay;

		private Text _peer;

		private Text _watchText;

		private Text _badge;

		private RawImage _remote;

		private RawImage _preview;

		private Control _search;

		private Control _end;

		private Control _mute;

		private Control _camera;

		private Control _view;

		private Control _next;

		private Control _answer;

		private Control _decline;

		private Control _previousPage;

		private Control _nextPage;

		private readonly Control[] _friendButtons = new Control[4];

		private readonly Control[] _declineRequest = new Control[4];

		private readonly Control[] _tabs = new Control[3];

		private readonly List<PersonRow> _people = new List<PersonRow>();

		private Control _notifications;

		private readonly bool[] _pressed = new bool[2];

		private readonly LineRenderer?[] _rays = (LineRenderer?[])(object)new LineRenderer[2];

		private Material? _rayMaterial;

		private int _friendPage;

		private int _peopleView;

		private float _rowsAt;

		private int _contactRevision = -1;

		private bool _visible;

		private float _openedAt;

		internal bool Visible => _visible;

		internal CallPanel(CallSession call, Preferences prefs, VideoFeed video, CallNetwork network, Contacts contacts, CallMicrophone microphone, Action mediaChanged)
		{
			_call = call;
			_prefs = prefs;
			_video = video;
			_network = network;
			_contacts = contacts;
			_microphone = microphone;
			_mediaChanged = mediaChanged;
		}

		internal void Toggle(Transform head)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			Ensure();
			_drag.Cancel();
			_visible = !_visible;
			if (_visible)
			{
				if (_contacts.PendingCount > 0 && _call.State == CallState.Ready)
				{
					SelectView(3);
				}
				_openedAt = Time.unscaledTime;
				Vector3 forward = head.forward;
				forward.y = 0f;
				if (((Vector3)(ref forward)).sqrMagnitude < 0.01f)
				{
					forward = Vector3.forward;
				}
				((Vector3)(ref forward)).Normalize();
				_root.transform.SetPositionAndRotation(head.position + forward * 1.05f - Vector3.up * 0.09f, Quaternion.LookRotation(forward));
			}
			_root.SetActive(_visible);
			_pressed[0] = (_pressed[1] = true);
		}

		internal void Tick(Transform head)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: 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_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			Ensure();
			object obj;
			if (!_prefs.RightWrist)
			{
				Hand leftHand = Player.LeftHand;
				obj = ((leftHand != null) ? ((Component)leftHand).transform : null);
			}
			else
			{
				Hand rightHand = Player.RightHand;
				obj = ((rightHand != null) ? ((Component)rightHand).transform : null);
			}
			Transform val = (Transform)obj;
			_wrist.SetActive((Object)(object)val != (Object)null);
			if ((Object)(object)val != (Object)null)
			{
				Vector3 val2 = head.position - val.position;
				if (((Vector3)(ref val2)).sqrMagnitude > 0.001f)
				{
					_wrist.transform.SetPositionAndRotation(val.position + ((Vector3)(ref val2)).normalized * 0.075f, Quaternion.LookRotation(-val2, head.up));
				}
			}
			_watchText.text = ((_call.State == CallState.Incoming) ? "INCOMING\nOPEN CALL" : ((_call.State == CallState.Active) ? "LIVE CALL\nBONEGLE" : ((_contacts.PendingCount > 0) ? "NEW REQUEST\nBONEGLE" : "BONEGLE\nOPEN")));
			foreach (Control control in _controls)
			{
				((Graphic)control.Image).color = (control.Enabled ? control.Color : Card);
			}
			((Graphic)_titleBar).color = (_drag.Active ? Card : Ink);
			if (_visible)
			{
				Refresh();
			}
			Pointer(0, head);
			Pointer(1, head);
		}

		private void Pointer(int hand, Transform head)
		{
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: 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_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0377: Unknown result type (might be due to invalid IL or missing references)
			//IL_037c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_0392: Unknown result type (might be due to invalid IL or missing references)
			//IL_0397: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_0348: Unknown result type (might be due to invalid IL or missing references)
			//IL_031c: Unknown result type (might be due to invalid IL or missing references)
			//IL_031e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0358: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_040a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0416: Unknown result type (might be due to invalid IL or missing references)
			//IL_0422: Unknown result type (might be due to invalid IL or missing references)
			//IL_0436: Unknown result type (might be due to invalid IL or missing references)
			//IL_0438: Unknown result type (might be due to invalid IL or missing references)
			//IL_043d: Unknown result type (might be due to invalid IL or missing references)
			//IL_044f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0455: Unknown result type (might be due to invalid IL or missing references)
			//IL_0459: Unknown result type (might be due to invalid IL or missing references)
			//IL_045f: Unknown result type (might be due to invalid IL or missing references)
			BaseController val = ((hand == 0) ? Player.LeftController : Player.RightController);
			object obj;
			if (hand != 0)
			{
				XRApi xr = MarrowGame.xr;
				obj = ((xr != null) ? xr.RightController : null);
			}
			else
			{
				XRApi xr2 = MarrowGame.xr;
				obj = ((xr2 != null) ? xr2.LeftController : null);
			}
			XRController val2 = (XRController)obj;
			bool flag = (Object)(object)val != (Object)null && val2 != null && ((XRDevice)val2).IsConnected;
			bool flag2 = ((!flag) ? (hand == 1 && Input.GetKey((KeyCode)288)) : (val2.TriggerButton || val2.Trigger >= 0.7f));
			Hand val3 = ((hand == 0) ? Player.LeftHand : Player.RightHand);
			bool flag3 = !flag || (Object)(object)val3 == (Object)null || !val3.HasAttachedObject();
			Transform val4 = (Transform)(flag ? ((object)((Component)val).transform) : ((object)head));
			bool flag4 = flag2 && !_pressed[hand] && flag3;
			_pressed[hand] = flag2;
			if (_drag.Hand == hand)
			{
				Vector3 panelPosition;
				Quaternion panelRotation;
				if (!_visible || !flag2 || !flag3 || flag != _dragVr)
				{
					_drag.Cancel();
					DrawRay(hand, default(Vector3), default(Vector3), visible: false);
				}
				else if (_drag.Move(hand, Point(val4.position), Rotation(val4.rotation), out panelPosition, out panelRotation))
				{
					_root.transform.SetPositionAndRotation(new Vector3(panelPosition.X, panelPosition.Y, panelPosition.Z), new Quaternion(panelRotation.X, panelRotation.Y, panelRotation.Z, panelRotation.W));
					if (flag)
					{
						DrawRay(hand, val4.position, _root.transform.TransformPoint(_dragAnchor), visible: true);
					}
				}
			}
			else
			{
				if (!flag && hand == 0)
				{
					return;
				}
				if (_drag.Active)
				{
					DrawRay(hand, default(Vector3), default(Vector3), visible: false);
					return;
				}
				Ray ray = default(Ray);
				((Ray)(ref ray))..ctor(val4.position, val4.forward);
				Control control = null;
				Vector3 hit = default(Vector3);
				bool flag5 = false;
				bool flag6 = false;
				int num = (_prefs.RightWrist ? 1 : 0);
				if (hand != num && _wrist.activeInHierarchy && Hit(_wrist.transform, ray, 180f, 84f, out var point, out hit))
				{
					flag5 = point.x >= 0f && point.x <= 180f && point.y >= 0f && point.y <= 84f;
				}
				if (_visible && Hit(_root.transform, ray, 1000f, 660f, out point, out var hit2))
				{
					foreach (Control control2 in _controls)
					{
						if (control2.Enabled && ((Component)control2.Image).gameObject.activeInHierarchy && ((Rect)(ref control2.Bounds)).Contains(point))
						{
							control = control2;
							hit = hit2;
							break;
						}
					}
					int num2;
					if (control == null)
					{
						Rect titleBounds = TitleBounds;
						num2 = (((Rect)(ref titleBounds)).Contains(point) ? 1 : 0);
					}
					else
					{
						num2 = 0;
					}
					flag6 = (byte)num2 != 0;
					if (flag6)
					{
						hit = hit2;
					}
				}
				bool flag7 = flag3 && (control != null || flag6 || flag5);
				if (flag)
				{
					DrawRay(hand, val4.position, hit, flag7);
				}
				if (control != null)
				{
					((Graphic)control.Image).color = Color.Lerp(control.Color, Color.white, 0.18f);
				}
				if (flag6 && flag3)
				{
					((Graphic)_titleBar).color = Card;
				}
				if (!flag4 || !flag7)
				{
					return;
				}
				if (flag6 && Time.unscaledTime - _openedAt > 0.25f)
				{
					Transform transform = _root.transform;
					if (_drag.Begin(hand, Point(val4.position), Rotation(val4.rotation), Point(transform.position), Rotation(transform.rotation)))
					{
						_dragAnchor = transform.InverseTransformPoint(hit);
						_dragVr = flag;
						DrawRay(1 - hand, default(Vector3), default(Vector3), visible: false);
					}
				}
				else if (control != null && Time.unscaledTime - _openedAt > 0.25f)
				{
					control.Action();
				}
				else if (flag5)
				{
					Toggle(head);
				}
			}
		}

		private static Vector3 Point(Vector3 value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(value.x, value.y, value.z);
		}

		private static Quaternion Rotation(Quaternion value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			return new Quaternion(value.x, value.y, value.z, value.w);
		}

		private static bool Hit(Transform panel, Ray ray, float width, float height, out Vector2 point, out Vector3 hit)
		{
			//IL_0002: 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)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			point = default(Vector2);
			hit = default(Vector3);
			Plane val = default(Plane);
			((Plane)(ref val))..ctor(panel.forward, panel.position);
			float num = default(float);
			if (!((Plane)(ref val)).Raycast(ray, ref num) || num < 0.015f || num > 2.5f)
			{
				return false;
			}
			hit = ((Ray)(ref ray)).GetPoint(num);
			Vector3 val2 = panel.InverseTransformPoint(hit);
			point = new Vector2(val2.x + width / 2f, height / 2f - val2.y);
			return true;
		}

		private void DrawRay(int hand, Vector3 from, Vector3 to, bool visible)
		{
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: 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_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			if ((Object)(object)_rays[hand] == (Object)null && visible)
			{
				Shader val = Shader.Find("Sprites/Default") ?? Shader.Find("Unlit/Color");
				if (!Object.op_Implicit((Object)(object)val))
				{
					return;
				}
				if (_rayMaterial == null)
				{
					_rayMaterial = new Material(val);
				}
				LineRenderer val2 = new GameObject("Bonegle Pointer")
				{
					layer = 5
				}.AddComponent<LineRenderer>();
				Object.DontDestroyOnLoad((Object)(object)((Component)val2).gameObject);
				((Renderer)val2).material = _rayMaterial;
				val2.positionCount = 2;
				val2.startWidth = 0.0014f;
				val2.endWidth = 0.002f;
				val2.startColor = new Color(Mint.r, Mint.g, Mint.b, 0.15f);
				val2.endColor = Mint;
				val2.useWorldSpace = true;
				_rays[hand] = val2;
			}
			LineRenderer val3 = _rays[hand];
			if (Object.op_Implicit((Object)(object)val3))
			{
				((Renderer)val3).enabled = visible;
				if (visible)
				{
					val3.SetPosition(0, from);
					val3.SetPosition(1, to);
				}
			}
		}

		private void Refresh()
		{
			//IL_0072: 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_0414: Unknown result type (might be due to invalid IL or missing references)
			//IL_0419: Unknown result type (might be due to invalid IL or missing references)
			//IL_040d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0406: Unknown result type (might be due to invalid IL or missing references)
			bool flag = _call.State == CallState.Active;
			bool flag2 = _call.State == CallState.Incoming;
			bool flag3 = _call.State == CallState.Ready;
			bool flag4 = _call.State == CallState.Offline;
			_badge.text = (flag ? "LIVE" : (flag4 ? "OFFLINE" : "STEAM ONLINE"));
			((Graphic)_badge).color = ((flag || flag3) ? Mint : Muted);
			RefreshFriends(flag3);
			Text status = _status;
			bool flag5 = flag3;
			if (flag5)
			{
				int peopleView = _peopleView;
				bool flag6 = ((peopleView == 1 || peopleView == 3) ? true : false);
				flag5 = flag6;
			}
			status.text = (flag5 ? _contacts.Status : _call.Status);
			if (flag && _call.Mic)
			{
				if (_microphone.Error.Length > 0)
				{
					_status.text = _microphone.Error;
				}
				else if (_microphone.Silent(Time.unscaledTime))
				{
					_status.text = "No mic signal. Check BoneMenu > Bonegle > Microphone.";
				}
			}
			_peer.text = (flag ? _call.Peer : "VIDEO CALL");
			_remote.texture = ((flag && _call.RemoteCamera) ? _video.Remote : null);
			((Behaviour)_remote).enabled = (Object)(object)_remote.texture != (Object)null;
			_preview.texture = ((flag && _call.Camera) ? _video.Local : null);
			((Behaviour)_preview).enabled = (Object)(object)_preview.texture != (Object)null;
			_overlay.text = (flag2 ? (_call.Peer + " is calling") : ((!flag) ? ((_call.State == CallState.Searching) ? "Searching for someone..." : ((_call.State == CallState.Dialing) ? "Ringing..." : "Call a friend\nor meet someone new.")) : (_call.RemoteCamera ? "Waiting for video..." : "Their camera is off")));
			((Component)_overlay).gameObject.SetActive(!((Behaviour)_remote).enabled);
			((Component)_answer.Image).gameObject.SetActive(flag2);
			_answer.Enabled = _call.CanAnswer;
			((Component)_decline.Image).gameObject.SetActive(flag2);
			_search.Label.text = (flag4 ? "CONNECT" : ((_call.State == CallState.Searching) ? "SEARCHING..." : "OMEGLE MODE  /  SEARCH"));
			_search.Enabled = flag4 || flag3;
			Control end = _end;
			flag5 = flag || flag2;
			if (!flag5)
			{
				CallState state = _call.State;
				bool flag6 = ((state == CallState.Dialing || state == CallState.Searching) ? true : false);
				flag5 = flag6;
			}
			end.Enabled = flag5;
			_end.Label.text = ((_call.State == CallState.Searching) ? "STOP SEARCH" : "END CALL");
			_mute.Label.text = ((!_call.Mic) ? "MIC  MUTED" : ((_microphone.Error.Length > 0) ? "MIC  ERROR" : (flag ? ("MIC  [" + new string('|', _microphone.Bars) + new string('.', 5 - _microphone.Bars) + "]") : "MIC  ON")));
			_mute.Color = ((!_call.Mic || _microphone.Error.Length > 0) ? Red : ((_microphone.Bars > 0) ? Purple : Card));
			_camera.Label.text = (_call.Camera ? "CAMERA  ON" : "CAMERA  OFF");
			_view.Label.text = (_prefs.Selfie ? "VIEW  SELFIE" : "VIEW  FORWARD");
			_next.Enabled = flag && _call.Random;
		}

		private void RefreshFriends(bool ready)
		{
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
			((Component)_tabs[2].Image).gameObject.SetActive(_network.HasSteamFriends);
			_tabs[2].Enabled = _network.HasSteamFriends;
			if (_peopleView == 2 && !_network.HasSteamFriends)
			{
				SelectView(0);
			}
			if (Time.unscaledTime >= _rowsAt || _contactRevision != _contacts.Revision)
			{
				BuildPeople();
			}
			int num = Math.Max(1, (_people.Count + 3) / 4);
			_friendPage = Math.Clamp(_friendPage, 0, num - 1);
			_page.text = _friendPage + 1 + " / " + num;
			_previousPage.Enabled = _friendPage > 0;
			_nextPage.Enabled = _friendPage + 1 < num;
			Text peopleTitle = _peopleTitle;
			peopleTitle.text = _peopleView switch
			{
				0 => "CONTACTS", 
				1 => "LOBBY PLAYERS", 
				2 => "STEAM FRIENDS", 
				_ => "NOTIFICATIONS", 
			};
			peopleTitle = _friendCount;
			string text = ((_people.Count != 0) ? (_people.Count + " players") : (_peopleView switch
			{
				0 => "Add someone from the Lobby tab", 
				1 => "No other players in this lobby", 
				2 => "No friends online", 
				_ => "No pending requests", 
			}));
			peopleTitle.text = text;
			for (int i = 0; i < _tabs.Length; i++)
			{
				_tabs[i].Color = ((_peopleView == i) ? Purple : Ink);
			}
			_notifications.Label.text = "NOTIFICATIONS" + ((_contacts.PendingCount > 0) ? (" (" + _contacts.PendingCount + ")") : "");
			_notifications.Color = ((_peopleView == 3) ? Purple : Ink);
			for (int j = 0; j < _friendButtons.Length; j++)
			{
				Control control = _friendButtons[j];
				int num2 = _friendPage * 4 + j;
				bool flag = num2 < _people.Count;
				((Component)control.Image).gameObject.SetActive(flag);
				((Component)_declineRequest[j].Image).gameObject.SetActive(flag && _people[num2].Decline != null);
				if (flag)
				{
					PersonRow personRow = _people[num2];
					string text2 = ((personRow.Name.Length > 22) ? (personRow.Name.Substring(0, 21) + "...") : personRow.Name);
					control.Label.text = text2 + "\n" + personRow.Status;
					control.Action = personRow.Action;
					control.Enabled = _network.Online && personRow.Enabled && (ready || personRow.Social);
					control.Color = Ink;
					float num3 = ((personRow.Decline == null) ? 266 : 216);
					((Component)control.Image).GetComponent<RectTransform>().sizeDelta = new Vector2(num3, 44f);
					((Component)control.Label).GetComponent<RectTransform>().sizeDelta = new Vector2(num3 - 8f, 44f);
					((Rect)(ref control.Bounds)).width = num3;
					_declineRequest[j].Action = personRow.Decline ?? ((Action)delegate
					{
					});
					_declineRequest[j].Enabled = _network.Online;
				}
			}
		}

		private void SelectView(int view)
		{
			_peopleView = view;
			_friendPage = 0;
			_rowsAt = 0f;
		}

		private void BuildPeople()
		{
			_rowsAt = Time.unscaledTime + 1f;
			_contactRevision = _contacts.Revision;
			_people.Clear();
			PeerId owner = _contacts.Owner;
			if (_peopleView == 0)
			{
				foreach (ContactRecord contact in from c in _contacts.Entries
					where c.State == ContactState.Accepted
					orderby c.Name
					select c)
				{
					_people.Add(new PersonRow(contact.Name, _contacts.Presence(contact.Id) + " / CALL", Enabled: true, Social: false, Guard(delegate
					{
						_call.Dial(contact.Id);
					})));
				}
				return;
			}
			if (_peopleView == 1)
			{
				foreach (LobbyRow player in _network.Players)
				{
					ContactRecord contact2 = _contacts.Get(player.Id);
					ContactRecord contactRecord = contact2;
					if ((object)contactRecord != null && contactRecord.State == ContactState.Accepted)
					{
						_people.Add(new PersonRow(player.Name, "CALL", Enabled: true, Social: false, Guard(delegate
						{
							_call.Dial(player.Id);
						})));
					}
					else
					{
						ContactRecord contactRecord2 = contact2;
						if ((object)contactRecord2 != null && contactRecord2.State == ContactState.Incoming)
						{
							_people.Add(new PersonRow(player.Name, "ACCEPT REQUEST", Enabled: true, Social: true, Guard(delegate
							{
								_contacts.Accept(player.Id, contact2.Request);
							}), Guard(delegate
							{
								_contacts.Decline(player.Id, contact2.Request);
							})));
						}
						else
						{
							List<PersonRow> people = _people;
							string name = player.Name;
							ContactRecord contactRecord3 = contact2;
							string status = (((object)contactRecord3 != null && contactRecord3.State == ContactState.Outgoing) ? _contacts.RequestStatus(player.Id) : "ADD CONTACT");
							ContactRecord contactRecord4 = contact2;
							people.Add(new PersonRow(name, status, (object)contactRecord4 == null || contactRecord4.State != ContactState.Outgoing, Social: true, Guard(delegate
							{
								_contacts.Request(player.Id);
							})));
						}
					}
				}
				return;
			}
			if (_peopleView == 2)
			{
				foreach (FriendRow friend in _network.Friends)
				{
					_people.Add(new PersonRow(friend.Name, friend.Status, friend.Callable, Social: false, Guard(delegate
					{
						_call.Dial(friend.Id);
					})));
				}
				return;
			}
			foreach (ContactRecord contact3 in _contacts.Entries.Where((ContactRecord c) => c.State == ContactState.Incoming))
			{
				_people.Add(new PersonRow(contact3.Name, "ACCEPT REQUEST", Enabled: true, Social: true, Guard(delegate
				{
					_contacts.Accept(contact3.Id, contact3.Request);
				}), Guard(delegate
				{
					_contacts.Decline(contact3.Id, contact3.Request);
				})));
			}
			Action Guard(Action action)
			{
				return delegate
				{
					if (_contacts.Owner == owner)
					{
						action();
					}
				};
			}
		}

		private void Ensure()
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: 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_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0331: Unknown result type (might be due to invalid IL or missing references)
			//IL_0361: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_048a: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_052e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0609: Unknown result type (might be due to invalid IL or missing references)
			//IL_064b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0678: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_070a: Unknown result type (might be due to invalid IL or missing references)
			//IL_074a: Unknown result type (might be due to invalid IL or missing references)
			//IL_078f: Unknown result type (might be due to invalid IL or missing references)
			//IL_07cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_080f: Unknown result type (might be due to invalid IL or missing references)
			//IL_084f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0896: Unknown result type (might be due to invalid IL or missing references)
			//IL_08f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0928: Unknown result type (might be due to invalid IL or missing references)
			//IL_095b: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a5: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)_root) && Object.op_Implicit((Object)(object)_wrist))
			{
				return;
			}
			if (Object.op_Implicit((Object)(object)_root) || Object.op_Implicit((Object)(object)_round))
			{
				Dispose();
			}
			_font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			MakeRound();
			_root = CanvasRoot("Bonegle Panel", 1000f, 660f, 0.001f);
			Transform transform = _root.transform;
			Plate(transform, "Panel", 0f, 0f, 1000f, 660f, Ink);
			Rect titleBounds = TitleBounds;
			float x = ((Rect)(ref titleBounds)).x;
			titleBounds = TitleBounds;
			float y = ((Rect)(ref titleBounds)).y;
			titleBounds = TitleBounds;
			float width = ((Rect)(ref titleBounds)).width;
			titleBounds = TitleBounds;
			_titleBar = Plate(transform, "Title Bar", x, y, width, ((Rect)(ref titleBounds)).height, Ink);
			Plate(transform, "Accent", 32f, 31f, 5f, 40f, Mint);
			Label(transform, "BONEGLE", 50f, 22f, 360f, 39f, 30, Color.white, (FontStyle)1, (TextAnchor)3);
			Label(transform, "CALL ACROSS SERVERS  /  DRAG TO MOVE", 51f, 62f, 500f, 20f, 13, Muted, (FontStyle)0, (TextAnchor)3);
			_badge = Label(transform, "OFFLINE", 645f, 36f, 150f, 30f, 17, Mint, (FontStyle)1, (TextAnchor)3);
			Button(transform, "HIDE", 832f, 26f, 136f, 48f, Card, delegate
			{
				_drag.Cancel();
				_visible = false;
				_root.SetActive(false);
			});
			Plate(transform, "Screen", 32f, 108f, 600f, 338f, new Color(0.019f, 0.027f, 0.047f));
			_remote = Raw(transform, "Remote", 32f, 108f, 600f, 338f);
			_peer = Label(transform, "VIDEO CALL", 52f, 123f, 450f, 25f, 15, Mint, (FontStyle)1, (TextAnchor)3);
			_overlay = Label(transform, "", 62f, 190f, 540f, 142f, 29, Color.white, (FontStyle)1, (TextAnchor)4);
			Plate(transform, "Preview Frame", 466f, 340f, 152f, 94f, Card);
			_preview = Raw(transform, "Preview", 471f, 345f, 142f, 80f);
			Label(transform, "YOU", 480f, 401f, 80f, 22f, 12, Color.white, (FontStyle)1, (TextAnchor)3);
			_answer = Button(transform, "ANSWER", 112f, 341f, 184f, 52f, Mint, _call.Accept, Ink);
			_decline = Button(transform, "DECLINE", 314f, 341f, 184f, 52f, Red, _call.HangUp);
			Plate(transform, "Friends", 656f, 108f, 312f, 414f, Card);
			_peopleTitle = Label(transform, "CONTACTS", 679f, 124f, 190f, 24f, 16, Mint, (FontStyle)1, (TextAnchor)3);
			Button(transform, "REFRESH", 869f, 124f, 76f, 28f, Ink, delegate
			{
				_network.RefreshFriends();
				_network.RefreshPlayers();
				_contacts.Refresh();
				_rowsAt = 0f;
			}).Label.fontSize = 11;
			string[] array = new string[3] { "CONTACTS", "LOBBY", "STEAM" };
			for (int num = 0; num < _tabs.Length; num++)
			{
				int view = num;
				_tabs[num] = Button(transform, array[num], 679 + num * 90, 157f, 86f, 28f, Ink, delegate
				{
					SelectView(view);
				});
				_tabs[num].Label.fontSize = 11;
			}
			_friendCount = Label(transform, "", 679f, 187f, 268f, 22f, 12, Muted, (FontStyle)0, (TextAnchor)3);
			for (int num2 = 0; num2 < _friendButtons.Length; num2++)
			{
				Control control = Button(transform, "", 679f, 215 + num2 * 51, 266f, 44f, Ink, delegate
				{
				});
				control.Label.fontSize = 14;
				_friendButtons[num2] = control;
				_declineRequest[num2] = Button(transform, "X", 903f, 215 + num2 * 51, 42f, 44f, Red, delegate
				{
				});
			}
			_previousPage = Button(transform, "BACK", 679f, 424f, 78f, 34f, Ink, delegate
			{
				_friendPage--;
			});
			_page = Label(transform, "", 762f, 424f, 89f, 34f, 14, Color.white, (FontStyle)0, (TextAnchor)4);
			_nextPage = Button(transform, "MORE", 867f, 424f, 78f, 34f, Ink, delegate
			{
				_friendPage++;
			});
			_notifications = Button(transform, "NOTIFICATIONS", 679f, 468f, 266f, 32f, Ink, delegate
			{
				SelectView(3);
			});
			_notifications.Label.fontSize = 13;
			_search = Button(transform, "OMEGLE MODE  /  SEARCH", 32f, 470f, 363f, 52f, Purple, delegate
			{
				if (_call.State == CallState.Offline)
				{
					_network.Connect();
				}
				else
				{
					_call.Search();
				}
			});
			_end = Button(transform, "END CALL", 409f, 470f, 223f, 52f, Red, _call.HangUp);
			_mute = Button(transform, "MIC  ON", 32f, 550f, 222f, 48f, Card, delegate
			{
				_call.Mic = !_call.Mic;
				_mediaChanged();
			});
			_camera = Button(transform, "CAMERA  ON", 270f, 550f, 222f, 48f, Card, delegate
			{
				_call.Camera = !_call.Camera;
				_mediaChanged();
			});
			_view = Button(transform, "VIEW  SELFIE", 508f, 550f, 222f, 48f, Card, delegate
			{
				_prefs.Selfie = !_prefs.Selfie;
			});
			_next = Button(transform, "NEXT PERSON", 746f, 550f, 222f, 48f, Card, _call.Next);
			_status = Label(transform, "", 32f, 619f, 936f, 25f, 16, Muted, (FontStyle)0, (TextAnchor)3);
			_root.SetActive(false);
			_wrist = CanvasRoot("Bonegle Wrist", 180f, 84f, 0.00065f);
			Plate(_wrist.transform, "Watch", 0f, 0f, 180f, 84f, Ink);
			Plate(_wrist.transform, "Indicator", 9f, 16f, 4f, 52f, Mint);
			_watchText = Label(_wrist.transform, "BONEGLE\nOPEN", 20f, 10f, 148f, 64f, 20, Mint, (FontStyle)1, (TextAnchor)4);
		}

		private GameObject CanvasRoot(string name, float width, float height, float scale)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = Node(name);
			obj.GetComponent<RectTransform>().sizeDelta = new Vector2(width, height);
			Canvas obj2 = obj.AddComponent<Canvas>();
			obj2.renderMode = (RenderMode)2;
			obj2.sortingOrder = 100;
			obj.transform.localScale = Vector3.one * scale;
			Object.DontDestroyOnLoad((Object)(object)obj);
			return obj;
		}

		private static GameObject Node(string name)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			return new GameObject(name, new Il2CppReferenceArray<Type>((Type[])(object)new Type[1] { Il2CppType.Of<RectTransform>() }))
			{
				layer = 5
			};
		}

		private static GameObject At(Transform parent, string name, float x, float y, float w, float h)
		{
			//IL_002d: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = Node(name);
			obj.transform.SetParent(parent, false);
			RectTransform component = obj.GetComponent<RectTransform>();
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0f, 1f);
			component.pivot = val;
			Vector2 anchorMin = (component.anchorMax = val);
			component.anchorMin = anchorMin;
			component.sizeDelta = new Vector2(w, h);
			component.anchoredPosition = new Vector2(x, 0f - y);
			return obj;
		}

		private Image Plate(Transform parent, string name, float x, float y, float w, float h, Color color)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			Image obj = At(parent, name, x, y, w, h).AddComponent<Image>();
			obj.sprite = _round;
			obj.type = (Type)1;
			((Graphic)obj).color = color;
			((Graphic)obj).raycastTarget = false;
			return obj;
		}

		private Text Label(Transform parent, string text, float x, float y, float w, float h, int size, Color color, FontStyle style = (FontStyle)0, TextAnchor align = (TextAnchor)3)
		{
			//IL_0033: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			Text obj = At(parent, "Label", x, y, w, h).AddComponent<Text>();
			obj.font = _font;
			obj.text = text;
			obj.fontSize = size;
			obj.fontStyle = style;
			((Graphic)obj).color = color;
			obj.alignment = align;
			obj.supportRichText = false;
			((Graphic)obj).raycastTarget = false;
			obj.horizontalOverflow = (HorizontalWrapMode)0;
			obj.verticalOverflow = (VerticalWrapMode)0;
			return obj;
		}

		private static RawImage Raw(Transform parent, string name, float x, float y, float w, float h)
		{
			RawImage obj = At(parent, name, x, y, w, h).AddComponent<RawImage>();
			((Graphic)obj).raycastTarget = false;
			((Behaviour)obj).enabled = false;
			return obj;
		}

		private Control Button(Transform parent, string name, float x, float y, float w, float h, Color color, Action action, Color? textColor = null)
		{
			//IL_000a: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			Image val = Plate(parent, name, x, y, w, h, color);
			Text label = Label(((Component)val).transform, name, 4f, 0f, w - 8f, h, 16, (Color)(((??)textColor) ?? Color.white), (FontStyle)1, (TextAnchor)4);
			Control control = new Control
			{
				Image = val,
				Label = label,
				Bounds = new Rect(x, y, w, h),
				Color = color,
				Action = action
			};
			_controls.Add(control);
			return control;
		}

		private void MakeRound()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			_roundTexture = new Texture2D(64, 64, (TextureFormat)4, false);
			Il2CppStructArray<Color32> val = new Il2CppStructArray<Color32>(4096L);
			for (int i = 0; i < 64; i++)
			{
				for (int j = 0; j < 64; j++)
				{
					float num = Mathf.Max(Mathf.Abs((float)j - 31.5f) - 17f, 0f);
					float num2 = Mathf.Max(Mathf.Abs((float)i - 31.5f) - 17f, 0f);
					byte b = (byte)(Mathf.Clamp01(14.5f - Mathf.Sqrt(num * num + num2 * num2)) * 255f);
					((Il2CppArrayBase<Color32>)(object)val)[i * 64 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b);
				}
			}
			_roundTexture.SetPixels32(val);
			_roundTexture.Apply(false, false);
			_round = Sprite.Create(_roundTexture, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(16f, 16f, 16f, 16f));
		}

		internal void Suspend()
		{
			_drag.Cancel();
			if (Object.op_Implicit((Object)(object)_wrist))
			{
				_wrist.SetActive(false);
			}
			if (Object.op_Implicit((Object)(object)_root))
			{
				_root.SetActive(false);
			}
			_visible = false;
			LineRenderer[] rays = _rays;
			foreach (LineRenderer val in rays)
			{
				if (Object.op_Implicit((Object)(object)val))
				{
					((Renderer)val).enabled = false;
				}
			}
			_pressed[0] = (_pressed[1] = true);
		}

		public void Dispose()
		{
			Suspend();
			if (Object.op_Implicit((Object)(object)_root))
			{
				Object.Destroy((Object)(object)_root);
			}
			if (Object.op_Implicit((Object)(object)_wrist))
			{
				Object.Destroy((Object)(object)_wrist);
			}
			LineRenderer[] rays = _rays;
			foreach (LineRenderer val in rays)
			{
				if (Object.op_Implicit((Object)(object)val))
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
			}
			if (Object.op_Implicit((Object)(object)_round))
			{
				Object.Destroy((Object)(object)_round);
			}
			if (Object.op_Implicit((Object)(object)_roundTexture))
			{
				Object.Destroy((Object)(object)_roundTexture);
			}
			if (Object.op_Implicit((Object)(object)_rayMaterial))
			{
				Object.Destroy((Object)(object)_rayMaterial);
			}
			_controls.Clear();
			_root = (_wrist = null);
		}
	}
	internal enum CallState
	{
		Offline,
		Ready,
		Dialing,
		Incoming,
		Searching,
		Active
	}
	internal interface ICallNetwork
	{
		PeerId Self { get; }

		bool CanCall(PeerId peer);

		bool IsBlocked(PeerId peer);

		string Name(PeerId peer);

		bool Send(PeerId peer, Signal message);

		void Close(PeerId peer);
	}
	internal sealed class CallSession(ICallNetwork network)
	{
		private readonly Queue<PeerId> _skipped = new Queue<PeerId>();

		private readonly Queue<Guid> _ended = new Queue<Guid>();

		private double _now;

		private double _deadline;

		private double _heartbeat;

		private double _lastHeard;

		private bool _outgoing;

		private bool _accepted;

		private PeerId _retryPeer;

		private double _retryUntil;

		internal CallState State { get; private set; }

		internal string Peer { get; private set; } = "";

		internal PeerId PeerId { get; private set; }

		internal string Status { get; set; } = "Waiting for Fusion's network connection...";

		internal Guid Call { get; private set; }

		internal int SearchId { get; private set; }

		internal bool Mic { get; set; } = true;

		internal bool Camera { get; set; } = true;

		internal bool RemoteMic { get; private set; }

		internal bool RemoteCamera { get; private set; }

		internal bool Random { get; private set; }

		internal bool CanAnswer
		{
			get
			{
				if (State == CallState.Incoming)
				{
					return !_accepted;
				}
				return false;
			}
		}

		internal bool Searching
		{
			get
			{
				if (State == CallState.Searching)
				{
					return PeerId == 0uL;
				}
				return false;
			}
		}

		internal void Online()
		{
			Reset();
			State = CallState.Ready;
			Status = "Choose a contact or search for someone.";
		}

		internal void Offline(string reason)
		{
			Reset();
			State = CallState.Offline;
			Status = reason;
		}

		internal void Dial(PeerId peer)
		{
			if (State != CallState.Ready)
			{
				return;
			}
			if (peer == 0uL || peer == network.Self || !network.CanCall(peer) || network.IsBlocked(peer))
			{
				Status = "Choose an accepted contact or Steam friend.";
				return;
			}
			Begin(peer, Guid.NewGuid(), random: false, outgoing: true);
			State = CallState.Dialing;
			Status = "Calling " + Peer + "...";
			_deadline = _now + 35.0;
			if (!network.Send(peer, Message("offer")))
			{
				Finish("Could not reach your friend.");
			}
		}

		internal void Search()
		{
			if (State == CallState.Ready)
			{
				_skipped.Clear();
				_retryPeer = 0uL;
				SearchAgain();
			}
		}

		private void SearchAgain()
		{
			State = CallState.Searching;
			SearchId++;
			Status = "Searching for another Bonegle player...";
		}

		internal bool CanMatch(PeerId peer)
		{
			if (Searching && peer != 0uL && peer != network.Self && !_skipped.Contains(peer) && (peer != _retryPeer || _now >= _retryUntil))
			{
				return !network.IsBlocked(peer);
			}
			return false;
		}

		internal void Match(PeerId peer)
		{
			if (CanMatch(peer) && !(peer > network.Self))
			{
				Begin(peer, Guid.NewGuid(), random: true, outgoing: true);
				_deadline = _now + 12.0;
				if (!network.Send(peer, Message("offer")))
				{
					Retry("Searching for another Bonegle player...");
				}
			}
		}

		internal void Accept()
		{
			if (CanAnswer)
			{
				_accepted = true;
				_deadline = _now + 12.0;
				Status = "Answering...";
				if (!network.Send(PeerId, Message("accept")))
				{
					Finish("The caller disconnected.");
				}
			}
		}

		internal void Receive(PeerId peer, Signal message)
		{
			if (peer == 0uL || peer == network.Self || State == CallState.Offline || !Guid.TryParse(message.Call, out var result) || result == Guid.Empty)
			{
				return;
			}
			if (message.Type == "offer")
			{
				if (PeerId == peer && Call == result)
				{
					return;
				}
				bool flag = message.Mode == "random";
				if (message.Version == 3 && !_ended.Contains(result) && !network.IsBlocked(peer))
				{
					bool num;
					if (!flag)
					{
						if (!(message.Mode == "friend") || State != CallState.Ready)
						{
							goto IL_00eb;
						}
						num = network.CanCall(peer);
					}
					else
					{
						num = CanMatch(peer);
					}
					if (num)
					{
						Begin(peer, result, flag, outgoing: false);
						_deadline = _now + (double)(flag ? 12 : 35);
						if (flag)
						{
							_accepted = true;
							if (!network.Send(peer, Message("accept")))
							{
								Retry("Searching for another Bonegle player...");
							}
							return;
						}
						State = CallState.Incoming;
						Status = Peer + " is calling.";
						if (!network.Send(peer, Message("ringing")))
						{
							Finish("The caller disconnected.");
						}
						return;
					}
				}
				goto IL_00eb;
			}
			if (peer != PeerId || result != Call)
			{
				return;
			}
			if (message.Version != 3)
			{
				Finish("Both players need the same Bonegle build.");
				return;
			}
			_lastHeard = _now;
			switch (message.Type)
			{
			case "accept":
			{
				bool flag2 = _outgoing;
				if (flag2)
				{
					CallState state = State;
					bool flag3 = ((state == CallState.Dialing || state == CallState.Searching) ? true : false);
					flag2 = flag3;
				}
				if (flag2)
				{
					if (network.Send(peer, Message("start")))
					{
						Activate();
					}
					else
					{
						Disconnected(peer);
					}
				}
				break;
			}
			case "start":
			{
				bool flag2 = !_outgoing && _accepted;
				if (flag2)
				{
					CallState state = State;
					bool flag3 = (uint)(state - 3) <= 1u;
					flag2 = flag3;
				}
				if (flag2)
				{
					Activate();
				}
				break;
			}
			case "media":
				if (State == CallState.Active)
				{
					RemoteMic = message.Mic;
					RemoteCamera = message.Camera;
				}
				break;
			case "ping":
				if (State == CallState.Active && !network.Send(peer, Message("pong")))
				{
					Disconnected(peer);
				}
				break;
			case "end":
				Disconnected(peer);
				break;
			}
			return;
			IL_00eb:
			if (PeerId != peer)
			{
				network.Send(peer, new Signal
				{
					Type = "end",
					Call = message.Call
				});
				network.Close(peer);
			}
		}

		internal void Disconnected(PeerId peer)
		{
			if (!(peer != PeerId) && !(peer == 0uL))
			{
				if (State == CallState.Searching)
				{
					Retry("Searching for another Bonegle player...");
				}
				else
				{
					Finish((State == CallState.Active) ? "Call ended. Choose a friend or press Search." : "Call ended or your friend is unavailable.");
				}
			}
		}

		internal void Tick(double now)
		{
			_now = now;
			if (PeerId == 0uL)
			{
				return;
			}
			if (State == CallState.Active)
			{
				if (now - _lastHeard > 30.0)
				{
					Finish("Connection lost.");
				}
				else if (now >= _heartbeat)
				{
					_heartbeat = now + 5.0;
					if (!network.Send(PeerId, Message("ping")))
					{
						Disconnected(PeerId);
					}
					else
					{
						SendMediaState();
					}
				}
			}
			else if (now >= _deadline)
			{
				network.Send(PeerId, Message("end"));
				if (State == CallState.Searching)
				{
					Retry("Searching for another Bonegle player...");
				}
				else
				{
					Finish("No answer. Both players need the same Bonegle build and Fusion network.");
				}
			}
		}

		internal void HangUp()
		{
			CallState state = State;
			if ((uint)state > 1u)
			{
				if (PeerId != 0uL)
				{
					network.Send(PeerId, Message("end"));
				}
				Finish("Call ended. Choose a friend or press Search.");
			}
		}

		internal void Next()
		{
			if (State == CallState.Active && Random)
			{
				Skip(PeerId);
				network.Send(PeerId, Message("end"));
				Finish("");
				SearchAgain();
			}
		}

		internal void SearchFailed(string reason)
		{
			if (State == CallState.Searching)
			{
				Finish(reason);
			}
		}

		internal void SendMediaState()
		{
			if (State == CallState.Active && !network.Send(PeerId, Message("media")with
			{
				Mic = Mic,
				Camera = Camera
			}))
			{
				Disconnected(PeerId);
			}
		}

		internal bool Accepts(byte[] data)
		{
			if (State == CallState.Active && Protocol.ValidMedia(data, out var call) && call == Call)
			{
				if (data[0] != 2)
				{
					return RemoteCamera;
				}
				return RemoteMic;
			}
			return false;
		}

		private Signal Message(string type)
		{
			return new Signal
			{
				Type = type,
				Call = Call.ToString(),
				Mode = (Random ? "random" : "friend")
			};
		}

		private void Begin(PeerId peer, Guid call, bool random, bool outgoing)
		{
			PeerId = peer;
			Peer = network.Name(peer);
			Call = call;
			Random = random;
			_outgoing = outgoing;
			_accepted = false;
			bool remoteMic = (RemoteCamera = false);
			RemoteMic = remoteMic;
		}

		private void Activate()
		{
			State = CallState.Active;
			_lastHeard = _now;
			_heartbeat = _now + 5.0;
			Status = "Connected to " + Peer;
			SendMediaState();
		}

		private void Retry(string reason)
		{
			_retryPeer = PeerId;
			_retryUntil = _now + 10.0;
			Finish(reason);
			State = CallState.Searching;
		}

		private void Skip(PeerId peer)
		{
			if (!(peer == 0uL))
			{
				_skipped.Enqueue(peer);
				while (_skipped.Count > 16)
				{
					_skipped.Dequeue();
				}
			}
		}

		private void Finish(string reason)
		{
			PeerId peerId = PeerId;
			Reset();
			State = CallState.Ready;
			Status = reason;
			if (peerId != 0uL)
			{
				network.Close(peerId);
			}
		}

		private void Reset()
		{
			if (Call != Guid.Empty)
			{
				_ended.Enqueue(Call);
				while (_ended.Count > 32)
				{
					_ended.Dequeue();
				}
			}
			PeerId = 0uL;
			Peer = "";
			Call = Guid.Empty;
			bool flag = (Random = (_accepted = (_outgoing = false)));
			bool remoteMic = (RemoteCamera = flag);
			RemoteMic = remoteMic;
		}
	}
	internal readonly record struct CameraPose(Vector3 Position, Quaternion Rotation)
	{
		internal static CameraPose Create(Vector3 head, Quaternion rotation, bool selfie, float scale, float distance)
		{
			scale = (float.IsFinite(scale) ? Math.Clamp(scale, 0.25f, 4f) : 1f);
			distance = (float.IsFinite(distance) ? Math.Clamp(distance, 0.4f, 2.5f) : 0.85f);
			Vector3 vector = (selfie ? new Vector3(0f, 0.03f, distance) : new Vector3(0f, 0f, 0.22f));
			Quaternion quaternion = (selfie ? Quaternion.CreateFromYawPitchRoll((float)Math.PI, MathF.Atan2(0.13f, distance), 0f) : Quaternion.Identity);
			return new CameraPose(head + Vector3.Transform(vector * scale, rotation), Quaternion.Normalize(rotation * quaternion));
		}
	}
	internal sealed class CaptureCursor(int capacity, int packetFrames, int rate, int position, float now)
	{
		private int _position = position;

		private float _lastPoll = now;

		private float _lastMovement = now;

		internal bool Stalled(float time)
		{
			return time - now >= 2f;
		}

		internal (int Offset, int Count) Read(int position, float now)
		{
			if (position < 0 || position >= capacity)
			{
				throw new ArgumentOutOfRangeException("position");
			}
			int num = (position - position + capacity) % capacity;
			if (now - now >= (float)capacity / (float)rate || num > packetFrames * 3)
			{
				num = Math.Min(num, packetFrames * 3);
				position = (position - num + capacity) % capacity;
			}
			now = now;
			int num2 = num / packetFrames;
			int position2 = position;
			position = (position + num2 * packetFrames) % capacity;
			if (num2 > 0)
			{
				now = now;
			}
			return (Offset: position2, Count: num2);
		}
	}
	internal interface IContactNetwork : ICallNetwork
	{
		bool Online { get; }

		bool Busy { get; }

		bool SameLobby(PeerId peer);
	}
	internal sealed record ContactNotice(PeerId Owner, string Kind, PeerId Peer, Guid Request, string Name, string Text);
	internal sealed class Contacts(IContactNetwork network, ContactStore store)
	{
		private readonly Dictionary<PeerId, ContactRecord> _entries = new Dictionary<PeerId, ContactRecord>();

		private readonly Dictionary<PeerId, (double Seen, bool Busy)> _presence = new Dictionary<PeerId, (double, bool)>();

		private readonly Dictionary<PeerId, double> _nextProbe = new Dictionary<PeerId, double>();

		private readonly HashSet<PeerId> _delivered = new HashSet<PeerId>();

		private readonly HashSet<PeerId> _failed = new HashSet<PeerId>();

		private readonly Dictionary<PeerId, double> _sentAt = new Dictionary<PeerId, double>();

		private readonly Queue<ContactNotice> _notices = new Queue<ContactNotice>();

		private bool _writable = true;

		private double _now;

		private double _pollAt;

		internal PeerId Owner { get; private set; }

		internal int Revision { get; private set; }

		internal string Status { get; private set; } = "Add people from Lobby Players.";

		internal IEnumerable<ContactRecord> Entries => _entries.Values;

		internal int PendingCount => _entries.Values.Count((ContactRecord c) => c.State == ContactState.Incoming);

		internal bool Knows(PeerId peer)
		{
			return _entries.ContainsKey(peer);
		}

		internal bool Accepted(PeerId peer)
		{
			ContactRecord? contactRecord = Get(peer);
			if ((object)contactRecord == null)
			{
				return false;
			}
			return contactRecord.State == ContactState.Accepted;
		}

		internal ContactRecord? Get(PeerId peer)
		{
			return _entries.GetValueOrDefault(peer);
		}

		internal bool TryNotice(out ContactNotice notice)
		{
			return _notices.TryDequeue(out notice);
		}

		internal static bool IsMessage(string type)
		{
			switch (type)
			{
			case "contact_decline":
			case "contact_pending":
			case "contact_request":
			case "contact_accept":
			case "contact_remove":
			case "contact_probe":
			case "contact_presence":
				return true;
			default:
				return false;
			}
		}

		internal void UseAccount(PeerId owner)
		{
			Offline();
			if (Owner == owner)
			{
				return;
			}
			Owner = owner;
			_entries.Clear();
			_notices.Clear();
			_writable = true;
			_delivered.Clear();
			_failed.Clear();
			_sentAt.Clear();
			try
			{
				ContactRecord[] array = store.Load(owner);
				foreach (ContactRecord contactRecord in array)
				{
					_entries.Add(contactRecord.Id, contactRecord);
				}
			}
			catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is JsonException) ? 1 : 0) != 0)
			{
				_writable = false;
				Status = "Could not load contacts. The existing file was kept.";
				Revision++;
				return;
			}
			Status = "Add people from Lobby Players.";
			foreach (ContactRecord item in _entries.Values.Where((ContactRecord c) => c.State == ContactState.Incoming))
			{
				Notice("request", item, item.Name + " wants to add you.");
			}
			Revision++;
		}

		internal void Offline()
		{
			_presence.Clear();
			_nextProbe.Clear();
			_pollAt = 0.0;
			Revision++;
		}

		internal string Presence(PeerId peer)
		{
			if (!network.Online)
			{
				return "Offline";
			}
			if (!_presence.TryGetValue(peer, out (double, bool) value) || _now - value.Item1 > 100.0)
			{
				return "Not reached";
			}
			if (!value.Item2)
			{
				return "Available";
			}
			return "Busy";
		}

		internal bool Request(PeerId peer)
		{
			if (!Ready() || peer == 0uL || peer == Owner || network.IsBlocked(peer))
			{
				return false;
			}
			ContactRecord contactRecord = Get(peer);
			if ((object)contactRecord != null && contactRecord.State == ContactState.Accepted)
			{
				Status = "Already in your contacts.";
				return false;
			}
			if ((object)contactRecord != null && contactRecord.State == ContactState.Incoming)
			{
				Status = "Open Notifications to accept their request.";
				return false;
			}
			if ((object)contactRecord != null && contactRecord.State == ContactState.Outgoing)
			{
				_failed.Remove(peer);
				_sentAt[peer] = _now;
				bool flag = Send(contactRecord, "contact_request");
				Status = (flag ? ("Retrying the request to " + contactRecord.Name + "...") : "Could not send the request. Both players need the same Bonegle build.");
				Revision++;
				return flag;
			}
			if (!network.SameLobby(peer))
			{
				Status = "Join their Fusion lobby to send a request.";
				return false;
			}
			if ((object)contactRecord != null && contactRecord.State == ContactState.Hidden && Age(contactRecord) < 60)
			{
				Status = "Wait a moment before sending another request.";
				return false;
			}
			if (_entries.Values.Count((ContactRecord c) => c.State != ContactState.Hidden) >= 128)
			{
				Status = "Your contact list is full. Remove a contact first.";
				return false;
			}
			ContactRecord contactRecord2 = new ContactRecord(peer, DisplayName.Clean(network.Name(peer)), ContactState.Outgoing, Guid.NewGuid(), Stamp());
			if (!Put(contactRecord2))
			{
				return false;
			}
			bool flag2 = Send(contactRecord2, "contact_request");
			_sentAt[peer] = _now;
			Status = (flag2 ? ("Sending a request to " + contactRecord2.Name + "...") : "Could not send the request. Both players need the same Bonegle build.");
			_nextProbe[peer] = _now + 45.0;
			return flag2;
		}

		internal bool IsPending(PeerId peer, Guid request)
		{
			ContactRecord contactRecord = Get(peer);
			if ((object)contactRecord != null && contactRecord.State == ContactState.Incoming)
			{
				return contactRecord.Request == request;
			}
			return false;
		}

		internal bool Accept(PeerId peer, Guid request)
		{
			if (!Ready() || !IsPending(peer, request) || network.IsBlocked(peer))
			{
				return false;
			}
			ContactRecord contactRecord = Get(peer)with
			{
				State = ContactState.Accepted,
				Updated = Stamp()
			};
			if (!Put(contactRecord))
			{
				return false;
			}
			Send(contactRecord, "contact_accept");
			_nextProbe[peer] = _now + 5.0;
			Status = contactRecord.Name + " is now a contact.";
			Notice("added", contactRecord, Status);
			return true;
		}

		internal bool Decline(PeerId peer, Guid request)
		{
			if (!Ready() || !IsPending(peer, request))
			{
				return false;
			}
			return Remove(peer);
		}

		internal bool Remove(PeerId peer)
		{
			if (Ready())
			{
				ContactRecord contactRecord = Get(peer);
				if ((object)contactRecord != null && contactRecord.State != ContactState.Hidden)
				{
					string type = ((contactRecord.State == ContactState.Accepted) ? "contact_remove" : "contact_decline");
					if (!Put(contactRecord with
					{
						State = ContactState.Hidden,
						Updated = Stamp()
					}))
					{
						return false;
					}
					Send(contactRecord, type);
					_presence.Remove(peer);
					_nextProbe.Remove(peer);
					_delivered.Remove(peer);
					Status = ((contactRecord.State == ContactState.Accepted) ? "Contact removed." : "Request closed.");
					return true;
				}
			}
			return false;
		}

		internal void Receive(PeerId peer, Signal message)
		{
			if (!network.Online || Owner != network.Self || peer == 0uL || peer == Owner || network.IsBlocked(peer) || message.Version != 3 || !Guid.TryParse(message.Request, out var result) || result == Guid.Empty)
			{
				return;
			}
			ContactRecord contactRecord = Get(peer);
			if (message.Type == "contact_request")
			{
				if ((object)contactRecord != null && contactRecord.Request == result)
				{
					ContactRecord contact = contactRecord;
					Send(contact, contactRecord.State switch
					{
						ContactState.Accepted => "contact_accept", 
						ContactState.Hidden => "contact_decline", 
						_ => "contact_pending", 
					});
				}
				else
				{
					if (!network.SameLobby(peer) || !_writable || ((object)contactRecord != null && contactRecord.State == ContactState.Hidden && Age(contactRecord) < 60) || _entries.Values.Count((ContactRecord c) => c.State == ContactState.Incoming) >= 16 || _entries.Values.Count((ContactRecord c) => c.State != ContactState.Hidden) >= 128 || ((object)contactRecord != null && contactRecord.State == ContactState.Incoming && Age(contactRecord) < 60))
					{
						return;
					}
					if ((object)contactRecord != null && contactRecord.State == ContactState.Outgoing && Owner < peer)
					{
						Send(contactRecord, "contact_request");
						return;
					}
					contactRecord = new ContactRecord(peer, DisplayName.Clean(network.Name(peer)), ContactState.Incoming, result, Stamp());
					if (Put(contactRecord))
					{
						Send(contactRecord, "contact_pending");
						Notice("request", contactRecord, contactRecord.Name + " wants to add you.");
					}
				}
			}
			else
			{
				if (contactRecord == null || contactRecord.Request != result)
				{
					return;
				}
				switch (message.Type)
				{
				case "contact_pending":
					if (contactRecord.State == ContactState.Outgoing)
					{
						_failed.Remove(peer);
						_sentAt.Remove(peer);
						if (_delivered.Add(peer))
						{
							Revision++;
						}
						Status = "Request sent to " + contactRecord.Name + ". Waiting for them to accept.";
					}
					break;
				case "contact_accept":
					if (contactRecord.State != ContactState.Outgoing)
					{
						break;
					}
					goto IL_02fe;
				case "contact_probe":
					if (contactRecord.State != ContactState.Outgoing)
					{
						if (contactRecord.State == ContactState.Accepted)
						{
							Seen(peer, message.Presence);
							Send(contactRecord, "contact_presence");
						}
						else if (contactRecord.State == ContactState.Hidden)
						{
							Send(contactRecord, "contact_remove");
						}
						break;
					}
					goto IL_02fe;
				case "contact_decline":
					if (contactRecord.State == ContactState.Hidden)
					{
						break;
					}
					goto IL_0393;
				case "contact_remove":
					if (contactRecord.State == ContactState.Hidden)
					{
						break;
					}
					goto IL_0393;
				case "contact_presence":
					{
						if (contactRecord.State == ContactState.Accepted)
						{
							Seen(peer, message.Presence);
						}
						break;
					}
					IL_0393:
					if (Put(contactRecord with
					{
						State = ContactState.Hidden,
						Updated = Stamp()
					}))
					{
						_presence.Remove(peer);
						Status = ((contactRecord.State == ContactState.Accepted) ? (contactRecord.Name + " removed the contact.") : ("Request closed by " + contactRecord.Name + "."));
						Notice("closed", contactRecord, Status);
					}
					break;
					IL_02fe:
					contactRecord = contactRecord with
					{
						State = ContactState.Accepted,
						Updated = Stamp()
					};
					if (Put(contactRecord))
					{
						Notice("added", contactRecord, contactRecord.Name + " accepted your request.");
						Status = contactRecord.Name + " is now a contact.";
						Send(contactRecord, "contact_presence");
						_nextProbe[peer] = _now + 5.0;
					}
					break;
				}
			}
		}

		internal void Tick(double now, bool canProbe)
		{
			_now = now;
			KeyValuePair<PeerId, double>[] array = _sentAt.Where((KeyValuePair<PeerId, double> x) => now - x.Value > 20.0).ToArray();
			for (int num = 0; num < array.Length; num++)
			{
				KeyValuePair<PeerId, double> keyValuePair = array[num];
				_sentAt.Remove(keyValuePair.Key);
				Unreachable(keyValuePair.Key);
			}
			if (!network.Online || Owner != network.Self || !canProbe || now < _pollAt)
			{
				return;
			}
			_pollAt = now + 1.0;
			ContactRecord contactRecord = (from c in _entries.Values
				where c.State == ContactState.Outgoing || (c.State == ContactState.Accepted && Owner > c.Id)
				where _nextProbe.GetValueOrDefault(c.Id) <= now && !network.IsBlocked(c.Id)
				orderby _nextProbe.GetValueOrDefault(c.Id)
				select c).FirstOrDefault();
			if (!(contactRecord == null))
			{
				_nextProbe[contactRecord.Id] = now + 45.0;
				if (contactRecord.State == ContactState.Accepted)
				{
					Send(contactRecord, "contact_probe");
				}
				else
				{
					Send(contactRecord, "contact_request");
				}
			}
		}

		internal void Refresh()
		{
			_nextProbe.Clear();
			_pollAt = 0.0;
			Revision++;
		}

		internal string RequestStatus(PeerId peer)
		{
			if (!_delivered.Contains(peer))
			{
				if (!_failed.Contains(peer))
				{
					return "Waiting for delivery";
				}
				return "Not reached / retry";
			}
			return "Waiting for acceptance";
		}

		internal void Unreachable(PeerId peer)
		{
			ContactRecord? contactRecord = Get(peer);
			if ((object)contactRecord != null && contactRecord.State == ContactState.Outgoing && !_delivered.Contains(peer) && _failed.Add(peer))
			{
				Status = Get(peer).Name + " was not reached. Both players need the same Bonegle build.";
				Revision++;
			}
		}

		private void Seen(PeerId peer, string presence)
		{
			if ((presence == "ready" || presence == "busy") ? true : false)
			{
				bool num = Presence(peer) != ((presence == "busy") ? "Busy" : "Available");
				_presence[peer] = (_now, presence == "busy");
				if (num)
				{
					Revision++;
				}
			}
		}

		private bool Ready()
		{
			if (!network.Online || Owner == 0uL || Owner != network.Self)
			{
				Status = "Enable calls and connect to Fusion first.";
				return false;
			}
			if (!_writable)
			{
				Status = "Contacts are unavailable. The existing file was kept.";
				return false;
			}
			return true;
		}

		private bool Send(ContactRecord contact, string type)
		{
			return network.Send(contact.Id, new Signal
			{
				Type = type,
				Request = contact.Request.ToString(),
				Presence = (network.Busy ? "busy" : "ready")
			});
		}

		private bool Put(ContactRecord contact)
		{
			if (!_writable)
			{
				return false;
			}
			ContactRecord[] array = (from c in _entries.Values.Where((ContactRecord c) => c.Id != contact.Id).Append(contact)
				orderby c.State == ContactState.Hidden, c.Updated descending
				select c).Take(192).ToArray();
			try
			{
				store.Save(Owner, array);
			}
			catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException) ? 1 : 0) != 0)
			{
				Status = "Could not save contacts. Try again after checking the UserData folder.";
				Revision++;
				return false;
			}
			_entries.Clear();
			ContactRecord[] array2 = array;
			foreach (ContactRecord contactRecord in array2)
			{
				_entries.Add(contactRecord.Id, contactRecord);
			}
			Revision++;
			return true;
		}

		private void Notice(string kind, ContactRecord contact, string text)
		{
			if (_notices.Count >= 32)
			{
				_notices.Dequeue();
			}
			_notices.Enqueue(new ContactNotice(Owner, kind, contact.Id, contact.Request, contact.Name, text));
		}

		private static long Stamp()
		{
			return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
		}

		private static long Age(ContactRecord contact)
		{
			return Stamp() - contact.Updated;
		}
	}
	internal enum ContactState
	{
		Incoming,
		Outgoing,
		Accepted,
		Hidden
	}
	internal sealed record ContactRecord(PeerId Id, string Name, ContactState State, Guid Request, long Updated);
	internal sealed class ContactStore(string directory)
	{
		private sealed record Profile(int Version, PeerId Owner, ContactRecord[] Contacts);

		internal ContactRecord[] Load(PeerId owner)
		{
			string text = PathFor(owner);
			if (!File.Exists(text))
			{
				return Array.Empty<ContactRecord>();
			}
			if (new FileInfo(text).Length > 262144)
			{
				throw new InvalidDataException("Contact file is too large.");
			}
			Profile profile = JsonSerializer.Deserialize<Profile>(File.ReadAllText(text));
			if (profile == null || profile.Version != 1 || profile.Owner != owner || profile.Contacts == null || profile.Contacts.Length > 192)
			{
				throw new InvalidDataException("Invalid contact file.");
			}
			HashSet<PeerId> hashSet = new HashSet<PeerId>();
			ContactRecord[] contacts = profile.Contacts;
			foreach (ContactRecord contactRecord in contacts)
			{
				if (contactRecord == null || contactRecord.Id == 0uL || contactRecord.Id == owner || contactRecord.Request == Guid.Empty || contactRecord.Id.IsEpic != owner.IsEpic || !Enum.IsDefined(typeof(ContactState), contactRecord.State) || contactRecord.Name == null || !hashSet.Add(contactRecord.Id))
				{
					throw new InvalidDataException("Invalid contact entry.");
				}
			}
			return profile.Contacts.Select((ContactRecord c) => c with
			{
				Name = DisplayName.Clean(c.Name)
			}).ToArray();
		}

		internal void Save(PeerId owner, IEnumerable<ContactRecord> contacts)
		{
			Profile profile = new Profile(1, owner, contacts.ToArray());
			if (owner == 0uL || profile.Contacts.Length > 192)
			{
				throw new InvalidDataException("Invalid contact profile.");
			}
			Directory.CreateDirectory(directory);
			string text = PathFor(owner);
			string text2 = text + ".tmp";
			File.WriteAllText(text2, JsonSerializer.Serialize(profile));
			File.Move(text2, text, overwrite: true);
		}

		private string PathFor(PeerId owner)
		{
			return Path.Combine(directory, "contacts-" + (owner.IsEpic ? ("epic-" + owner.Raw) : owner.ToString()) + ".json");
		}
	}
	internal static class DisplayName
	{
		internal static string Clean(string? name)
		{
			string text = new string((name ?? "").Where(delegate(char c)
			{
				bool flag = !char.IsControl(c);
				if (flag)
				{
					bool flag2 = ((c == '<' || c == '>') ? true : false);
					flag = !flag2;
				}
				return flag;
			}).Take(40).ToArray());
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return "Player";
		}
	}
	internal sealed class EpicBridge : IDisposable
	{
		private static Assembly? _assembly;

		private readonly object _instance;

		private readonly Func<bool> _alive;

		private readonly Func<string> _error;

		private readonly Action<double> _tick;

		private readonly Action<bool, int, double> _search;

		private readonly Func<string, byte, byte[], int, bool, bool> _send;

		private readonly Action<string> _close;

		private readonly Action _dispose;

		internal string Self { get; }

		internal string DisplayName { get; }

		internal bool Alive => _alive();

		internal string Error => _error();

		internal EpicBridge(object layer, Func<string, bool> allow, Action<string, byte, byte[], int> receive, Action<string> closed, Action<int, string, string> matched, Action<string> searchError)
		{
			if (_assembly == null)
			{
				using Stream stream = typeof(EpicBridge).Assembly.GetManifestResourceStream("Bonegle.Epic.dll") ?? throw new InvalidOperationException("Epic support is missing from this build.");
				using MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				memoryStream.Position = 0L;
				_assembly = (AssemblyLoadContext.GetLoadContext(layer.GetType().Assembly) ?? AssemblyLoadContext.Default).LoadFromStream(memoryStream);
			}
			Type type = _assembly.GetType("Bonegle.Epic.Bridge", throwOnError: true);
			_instance = Activator.CreateInstance(type, layer, allow, receive, closed, matched, searchError);
			Self = (string)type.GetProperty("Self").GetValue(_instance);
			DisplayName = (string)type.GetProperty("DisplayName").GetValue(_instance);
			_alive = Bind<Func<bool>>("get_Alive");
			_error = Bind<Func<string>>("get_Error");
			_tick = Bind<Action<double>>("Tick");
			_search = Bind<Action<bool, int, double>>("Search");
			_send = Bind<Func<string, byte, byte[], int, bool, bool>>("Send");
			_close = Bind<Action<string>>("Close");
			_dispose = Bind<Action>("Dispose");
			T Bind<T>(string name) where T : notnull, Delegate
			{
				return type.GetMethod(name).CreateDelegate<T>(_instance);
			}
		}

		internal void Tick(double now)
		{
			_tick(now);
		}

		internal void Search(bool wanted, int id, double now)
		{
			_search(wanted, id, now);
		}

		internal bool Send(string peer, byte channel, byte[] data, int count, bool reliable)
		{
			return _send(peer, channel, data, count, reliable);
		}

		internal void Close(string peer)
		{
			_close(peer);
		}

		public void Dispose()
		{
			_dispose();
		}
	}
	internal sealed class EpicNetwork(VoiceStats voice) : ICallTransport, IContactNetwork, ICallNetwork, IDisposable
	{
		private sealed class Peer
		{
			internal double LastSeen;

			internal double Window;

			internal int Controls;

			internal void Touch(double now)
			{
				LastSeen = now;
			}
		}

		private readonly List<LobbyRow> _players = new List<LobbyRow>();

		private readonly Dictionary<PeerId, string> _names = new Dictionary<PeerId, string>();

		private readonly Dictionary<PeerId, Peer> _peers = new Dictionary<PeerId, Peer>();

		private readonly Dictionary<PeerId, double> _closing = new Dictionary<PeerId, double>();

		private readonly Queue<byte[]> _audio = new Queue<byte[]>();

		private readonly EpicFrames _frames = new EpicFrames();

		private readonly VideoBudget _videoBudget = new VideoBudget();

		private readonly MediaBudget _receiveBudget = new MediaBudget();

		private readonly byte[] _packet = new byte[1170];

		private EpicBridge? _bridge;

		private object? _layer;

		private CallSession _call;

		private Contacts _contacts;

		private byte[]? _video;

		private Guid _mediaCall;

		private uint _audioSequence;

		private uint _videoSequence;

		private uint _lastAudio;

		private uint _lastVideo;

		private bool _enabled = true;

		private bool _hasAudio;

		private bool _hasVideo;

		private double _now;

		private double _retryAt;

		private double _nextPlayers;

		private string _name = "Player";

		public PeerId Self { get; private set; }

		public bool Online
		{
			get
			{
				if (_enabled)
				{
					EpicBridge? bridge = _bridge;
					if (bridge != null && bridge.Alive)
					{
						return NetworkLayerManager.Layer == _layer;
					}
				}
				return false;
			}
		}

		public bool Busy => _call.State != CallState.Ready;

		public IReadOnlyList<FriendRow> Friends => Array.Empty<FriendRow>();

		public IReadOnlyList<LobbyRow> Players => _players;

		public string VideoWarning { get; private set; } = "";

		public void Initialize(CallSession call, Contacts contacts, Harmony harmony)
		{
			_call = call;
			_contacts = contacts;
		}

		public void Connect()
		{
			_enabled = true;
			_retryAt = 0.0;
		}

		public void Tick(double now)
		{
			_now = now;
			if (!_enabled)
			{
				return;
			}
			if (NetworkLayerManager.LoggedIn)
			{
				NetworkLayer layer = NetworkLayerManager.Layer;
				if (!(((layer != null) ? layer.Platform : null) != "Epic"))
				{
					if (_bridge != null && !Online)
					{
						Stop();
					}
					if (_bridge == null)
					{
						if (now < _retryAt)
						{
							return;
						}
						try
						{
							_layer = NetworkLayerManager.Layer;
							_bridge = new EpicBridge(_layer, Allow, Received, Closed, Matched, delegate(string error)
							{
								_call.SearchFailed(error);
							});
							Self = PeerId.Epic(_bridge.Self);
							if (Self.IsEmpty)
							{
								throw new InvalidOperationException("Fusion's Epic account ID is unavailable.");
							}
							_name = DisplayName.Clean(_bridge.DisplayName);
							_contacts.UseAccount(Self);
							_call.Online();
							_nextPlayers = 0.0;
						}
						catch (Exception ex)
						{
							Stop();
							_retryAt = now + 10.0;
							_call.Offline("Epic calls unavailable: " + (ex.InnerException?.Message ?? ex.Message));
							return;
						}
					}
					if (_mediaCall != _call.Call)
					{
						ClearMedia();
						_mediaCall = _call.Call;
						_audioSequence = (_videoSequence = 0u);
						_hasAudio = (_hasVideo = false);
						_videoBudget.Reset();
					}
					if (now >= _nextPlayers)
					{
						RefreshPlayers();
					}
					_call.Tick(now);
					_bridge.Search(_call.Searching, _call.SearchId, now);
					_bridge.Tick(now);
					_bridge.Search(_call.Searching, _call.SearchId, now);
					_contacts.Tick(now, _peers.Count < 12);
					KeyValuePair<PeerId, double>[] array = _closing.Where((KeyValuePair<PeerId, double> pair) => now >= pair.Value).ToArray();
					for (int num = 0; num < array.Length; num++)
					{
						KeyValuePair<PeerId, double> keyValuePair = array[num];
						_closing.Remove(keyValuePair.Key);
						_bridge.Close(keyValuePair.Key.Raw);
						_peers.Remove(keyValuePair.Key);
					}
					KeyValuePair<PeerId, Peer>[] array2 = _peers.Where<KeyValuePair<PeerId, Peer>>((KeyValuePair<PeerId, Peer> pair) => pair.Key != _call.PeerId && now - pair.Value.LastSeen > 12.0).ToArray();
					for (int num = 0; num < array2.Length; num++)
					{
						KeyValuePair<PeerId, Peer> keyValuePair2 = array2[num];
						_bridge.Close(keyValuePair2.Key.Raw);
						_peers.Remove(keyValuePair2.Key);
					}
					return;
				}
			}
			Stop();
			_call.Offline("Connect to Fusion's Epic Online Services network.");
		}

		private bool Allow(string raw)
		{
			PeerId peerId = PeerId.Epic(raw);
			if (Online && !peerId.IsEmpty && peerId != Self && !IsBlocked(peerId))
			{
				if (!(peerId == _call.PeerId) && !_contacts.Knows(peerId) && !SameLobby(peerId))
				{
					return _call.CanMatch(peerId);