Decompiled source of SharePermissions v2.19.0

SharePermissions.dll

Decompiled 6 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using MenuLib;
using MenuLib.MonoBehaviors;
using MenuLib.Structs;
using Microsoft.CodeAnalysis;
using POpusCodec.Enums;
using Photon.Pun;
using Photon.Realtime;
using Photon.Voice;
using Photon.Voice.Unity;
using SharePermissions.Core;
using SharePermissions.Net;
using SharePermissions.Net.Commands;
using SharePermissions.Patches;
using SharePermissions.Players;
using SharePermissions.UI;
using SharePermissions.Voice;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("RED")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.19.0.0")]
[assembly: AssemblyInformationalVersion("2.19.0+0920f41608d48aad1d2b21e9e65e164aa3568d24")]
[assembly: AssemblyProduct("SharePermissions")]
[assembly: AssemblyTitle("SharePermissions")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.19.0.0")]
[module: UnverifiableCode]
[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 SharePermissions.Voice
{
	internal sealed class MicRing
	{
		private readonly float[] buffer;

		private volatile int writeIndex;

		private volatile int readIndex;

		private volatile float level;

		internal float Level => level;

		internal int Available
		{
			get
			{
				int num = writeIndex;
				int num2 = readIndex;
				if (num < num2)
				{
					return buffer.Length - num2 + num;
				}
				return num - num2;
			}
		}

		internal MicRing(int capacity)
		{
			if (capacity < 2)
			{
				capacity = 2;
			}
			buffer = new float[capacity];
		}

		internal void Write(float[] data, int count)
		{
			if (data != null && count > 0)
			{
				if (count > data.Length)
				{
					count = data.Length;
				}
				float num = 0f;
				for (int i = 0; i < count; i++)
				{
					num += Math.Abs(data[i]);
				}
				level = num / (float)count;
				int num2 = writeIndex;
				int num3 = buffer.Length - 1 - Available;
				if (count > num3)
				{
					count = num3;
				}
				for (int j = 0; j < count; j++)
				{
					buffer[num2] = data[j];
					num2 = ((num2 + 1 != buffer.Length) ? (num2 + 1) : 0);
				}
				writeIndex = num2;
			}
		}

		internal bool Read(float[] destination)
		{
			if (destination == null || destination.Length == 0)
			{
				return false;
			}
			if (Available < destination.Length)
			{
				return false;
			}
			int num = readIndex;
			for (int i = 0; i < destination.Length; i++)
			{
				destination[i] = buffer[num];
				num = ((num + 1 != buffer.Length) ? (num + 1) : 0);
			}
			readIndex = num;
			return true;
		}

		internal void Reset()
		{
			readIndex = 0;
			writeIndex = 0;
			level = 0f;
		}
	}
	[HarmonyPatch(typeof(MicWrapper), "Read")]
	internal static class MicTee
	{
		private const int RingCapacity = 48000;

		internal static readonly MicRing Ring = new MicRing(48000);

		private static volatile int samplingRate;

		private static volatile int channels;

		internal static int SamplingRate => samplingRate;

		internal static int Channels => channels;

		internal static bool IsLive
		{
			get
			{
				if (SamplingRate > 0)
				{
					return Channels > 0;
				}
				return false;
			}
		}

		internal static float Level => Ring.Level;

		[HarmonyPostfix]
		[HarmonyWrapSafe]
		private static void Read_Postfix(MicWrapper __instance, float[] buffer, bool __result)
		{
			if (__result && buffer != null && PrivateVoiceGate.WantCapture)
			{
				channels = __instance.Channels;
				samplingRate = __instance.SamplingRate;
				Ring.Write(buffer, buffer.Length);
			}
		}

		internal static void Flush()
		{
			Ring.Reset();
			Plugin.Logger.LogDebug((object)"Private voice: mic tee flushed");
		}
	}
	internal static class PrivateVoiceGate
	{
		internal static volatile bool WantCapture;
	}
	internal sealed class PeakLimiter
	{
		internal const float Ceiling = 0.891f;

		internal const float ReleaseSeconds = 0.25f;

		private float reduction = 1f;

		private float releaseCoefficient = 1f - (float)Math.Exp(-8.333333333333333E-05);

		internal void Configure(int sampleRate)
		{
			if (sampleRate > 0)
			{
				releaseCoefficient = 1f - (float)Math.Exp(-1.0 / (double)(0.25f * (float)sampleRate));
			}
		}

		internal void Reset()
		{
			reduction = 1f;
		}

		internal void Process(float[] data, int channels, float gain)
		{
			if (data == null)
			{
				return;
			}
			if (channels < 1)
			{
				channels = 1;
			}
			float num = reduction;
			float num2 = releaseCoefficient;
			for (int i = 0; i + channels <= data.Length; i += channels)
			{
				float num3 = 0f;
				for (int j = 0; j < channels; j++)
				{
					float num4 = data[i + j] * gain;
					if (num4 < 0f)
					{
						num4 = 0f - num4;
					}
					if (num4 > num3)
					{
						num3 = num4;
					}
				}
				float num5 = ((num3 > 0.891f) ? (0.891f / num3) : 1f);
				if (num5 < num)
				{
					num = num5;
				}
				else
				{
					num += (1f - num) * num2;
					if (num > num5)
					{
						num = num5;
					}
				}
				float num6 = gain * num;
				for (int k = 0; k < channels; k++)
				{
					data[i + k] *= num6;
				}
			}
			reduction = num;
		}
	}
	internal static class PrivateSpeakers
	{
		private sealed class Entry
		{
			internal GameObject Go;

			internal Speaker Speaker;

			internal AudioSource Source;

			internal PrivateSpeakerGain Gain;

			internal int Actor;

			internal volatile float Amplitude;

			internal volatile int LastFrameTick;
		}

		private const int AmplitudeStaleMs = 300;

		private static readonly Dictionary<int, Entry> ByPlayerId = new Dictionary<int, Entry>();

		private static readonly HashSet<Speaker> AmplitudeAttached = new HashSet<Speaker>();

		private static float volume = 1f;

		internal static Speaker? Create(int playerId, object userData)
		{
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Expected O, but got Unknown
			if (!(userData is int num) || num <= 0)
			{
				Plugin.Logger.LogWarning((object)$"Private voice: stream {playerId} carried no usable actor claim - refusing");
				return null;
			}
			if (!PrivateVoiceChannel.IsMemberActor(num))
			{
				Plugin.Logger.LogWarning((object)$"Private voice: stream from non-member actor {num} - refusing to play");
				return null;
			}
			foreach (KeyValuePair<int, Entry> item in ByPlayerId)
			{
				if (item.Key != playerId && item.Value.Actor == num)
				{
					Plugin.Logger.LogWarning((object)$"Private voice: actor {num} already claimed by another stream - refusing");
					return null;
				}
			}
			if (ByPlayerId.TryGetValue(playerId, out Entry value))
			{
				return value.Speaker;
			}
			GameObject val = new GameObject($"SP_PrivSpeaker_a{num}");
			Object.DontDestroyOnLoad((Object)(object)val);
			AudioSource val2 = val.AddComponent<AudioSource>();
			val2.spatialBlend = 0f;
			val2.mute = false;
			val2.dopplerLevel = 0f;
			val2.priority = 0;
			val2.outputAudioMixerGroup = null;
			val2.bypassEffects = false;
			val2.bypassListenerEffects = true;
			val2.bypassReverbZones = true;
			PrivateSpeakerGain gain = val.AddComponent<PrivateSpeakerGain>();
			Speaker val3 = val.AddComponent<Speaker>();
			Entry entry = new Entry
			{
				Go = val,
				Speaker = val3,
				Source = val2,
				Gain = gain,
				Actor = num
			};
			ByPlayerId[playerId] = entry;
			Apply(entry, volume);
			val3.OnRemoteVoiceRemoveAction = delegate(Speaker s)
			{
				ByPlayerId.Remove(playerId);
				if ((Object)(object)s != (Object)null && (Object)(object)((Component)s).gameObject != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)s).gameObject);
				}
			};
			Plugin.Logger.LogInfo((object)$"Private voice: speaker for actor {num}");
			return val3;
		}

		internal static void AttachAmplitude(Speaker speaker)
		{
			if ((Object)(object)speaker == (Object)null)
			{
				return;
			}
			RemoteVoiceLink remoteVoice = speaker.RemoteVoice;
			if (remoteVoice == null)
			{
				return;
			}
			Entry entry = null;
			foreach (KeyValuePair<int, Entry> item in ByPlayerId)
			{
				if (item.Value.Speaker == speaker)
				{
					entry = item.Value;
					break;
				}
			}
			if (entry == null || !AmplitudeAttached.Add(speaker))
			{
				return;
			}
			remoteVoice.FloatFrameDecoded += delegate(FrameOut<float> frame)
			{
				float[] buf = frame.Buf;
				if (buf != null && buf.Length != 0)
				{
					float num = 0f;
					for (int i = 0; i < buf.Length; i++)
					{
						num += ((buf[i] < 0f) ? (0f - buf[i]) : buf[i]);
					}
					entry.Amplitude = num / (float)buf.Length;
					entry.LastFrameTick = Environment.TickCount;
				}
			};
		}

		internal static void DestroyAll()
		{
			foreach (KeyValuePair<int, Entry> item in ByPlayerId)
			{
				Entry value = item.Value;
				if ((Object)(object)value.Speaker != (Object)null)
				{
					value.Speaker.OnRemoteVoiceRemoveAction = null;
				}
				if ((Object)(object)value.Go != (Object)null)
				{
					Object.Destroy((Object)(object)value.Go);
				}
			}
			ByPlayerId.Clear();
			AmplitudeAttached.Clear();
		}

		internal static float AmplitudeFor(int actorNumber)
		{
			foreach (KeyValuePair<int, Entry> item in ByPlayerId)
			{
				if (item.Value.Actor == actorNumber)
				{
					Entry value = item.Value;
					if (Environment.TickCount - value.LastFrameTick > 300)
					{
						return 0f;
					}
					return value.Amplitude;
				}
			}
			return 0f;
		}

		internal static int[] SpeakingActors()
		{
			List<int> list = new List<int>(ByPlayerId.Count);
			foreach (KeyValuePair<int, Entry> item in ByPlayerId)
			{
				list.Add(item.Value.Actor);
			}
			return list.ToArray();
		}

		internal static void SetVolume(float value)
		{
			if (value < 0f)
			{
				value = 0f;
			}
			float num = 3f;
			if (value > num)
			{
				value = num;
			}
			if (value == volume)
			{
				return;
			}
			volume = value;
			foreach (KeyValuePair<int, Entry> item in ByPlayerId)
			{
				Apply(item.Value, value);
			}
		}

		private static void Apply(Entry entry, float value)
		{
			if ((Object)(object)entry.Source != (Object)null)
			{
				entry.Source.volume = ((value > 1f) ? 1f : value);
			}
			if ((Object)(object)entry.Gain != (Object)null)
			{
				entry.Gain.Gain = ((value > 1f) ? value : 1f);
			}
		}
	}
	internal sealed class PrivateSpeakerGain : MonoBehaviour
	{
		private volatile float gain = 1f;

		private readonly PeakLimiter limiter = new PeakLimiter();

		internal float Gain
		{
			get
			{
				return gain;
			}
			set
			{
				gain = ((value < 1f) ? 1f : value);
			}
		}

		private void Awake()
		{
			limiter.Configure(AudioSettings.outputSampleRate);
		}

		private void OnAudioFilterRead(float[] data, int channels)
		{
			float num = gain;
			if (num == 1f)
			{
				limiter.Reset();
			}
			else
			{
				limiter.Process(data, channels, num);
			}
		}
	}
	internal sealed class PrivateVoiceClient : VoiceFollowClient
	{
		private const float ConnectWarnCooldownSeconds = 5f;

		private const float ConnectRetryCooldownSeconds = 3f;

		private const string MutedPropKey = "spm";

		private static bool quitting;

		private string token = string.Empty;

		private bool? publishedMuted;

		private Recorder? recorder;

		private int userDataActor;

		private float nextConnectWarnAt;

		private float nextConnectAttemptAt;

		private const int OpusBitrate = 64000;

		internal static PrivateVoiceClient? Instance { get; private set; }

		internal static bool EverCreated { get; private set; }

		internal Recorder? PrivateRecorder => recorder;

		protected override bool LeaderInRoom
		{
			get
			{
				if (PhotonNetwork.InRoom)
				{
					return PrivateVoiceChannel.IsValidRoomName(token);
				}
				return false;
			}
		}

		protected override bool LeaderOfflineMode => PhotonNetwork.OfflineMode;

		internal static void Init()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			if (!((Object)(object)Instance != (Object)null))
			{
				GameObject val = new GameObject("SP_PrivateVoice")
				{
					hideFlags = (HideFlags)61
				};
				Object.DontDestroyOnLoad((Object)(object)val);
				Instance = val.AddComponent<PrivateVoiceClient>();
				((VoiceConnection)Instance).SpeakerLinked += PrivateSpeakers.AttachAmplitude;
				EverCreated = true;
				Application.quitting += delegate
				{
					quitting = true;
				};
				Plugin.Logger.LogInfo((object)"Private voice client created");
			}
		}

		internal void Provision(string roomName)
		{
			if (!PrivateVoiceChannel.IsValidRoomName(roomName))
			{
				Plugin.Logger.LogWarning((object)"Private voice: refusing a malformed room name");
				return;
			}
			PrivateVoiceGate.WantCapture = true;
			bool flag = PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber > 0;
			if ((Object)(object)recorder == (Object)null)
			{
				if (flag && MicTee.IsLive)
				{
					CreateRecorder();
				}
			}
			else if (flag)
			{
				SyncUserData(recorder);
			}
			bool flag2 = ((VoiceConnection)this).Client != null && ((LoadBalancingClient)((VoiceConnection)this).Client).IsConnected;
			if (roomName == token && flag2)
			{
				return;
			}
			if (token.Length > 0 && roomName != token)
			{
				token = string.Empty;
				SafeDisconnect();
				return;
			}
			if (token.Length == 0)
			{
				if (flag2)
				{
					return;
				}
				token = roomName;
			}
			if (!(((VoiceConnection)this).Client == null || flag2) && PhotonNetwork.InRoom && !(Time.unscaledTime < nextConnectAttemptAt))
			{
				nextConnectAttemptAt = Time.unscaledTime + 3f;
				if (!((VoiceFollowClient)this).ConnectAndJoinRoom() && Time.unscaledTime >= nextConnectWarnAt)
				{
					nextConnectWarnAt = Time.unscaledTime + 5f;
					Plugin.Logger.LogWarning((object)("Private voice: connect/join refused for room " + PrivateVoiceChannel.Fingerprint(roomName)));
				}
			}
		}

		protected override void Start()
		{
			((VoiceFollowClient)this).Start();
			if (PhotonNetwork.NetworkingClient != null)
			{
				PhotonNetwork.NetworkingClient.StateChanged += OnLeaderStateChanged;
			}
		}

		private void OnLeaderStateChanged(ClientState fromState, ClientState toState)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			((VoiceFollowClient)this).LeaderStateChanged(toState);
		}

		protected override void OnDestroy()
		{
			if (Instance == this && !quitting)
			{
				Plugin.Logger.LogWarning((object)"Private voice: SP_PrivateVoice is being destroyed - the private channel cannot run again this session");
			}
			if (PhotonNetwork.NetworkingClient != null)
			{
				PhotonNetwork.NetworkingClient.StateChanged -= OnLeaderStateChanged;
			}
			((VoiceFollowClient)this).OnDestroy();
		}

		private void CreateRecorder()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("SP_PrivateRecorder");
			val.transform.SetParent(((Component)this).transform, false);
			Recorder val2 = val.AddComponent<Recorder>();
			PrivateVoiceDsp.Mirror(val);
			val2.SourceType = (InputSourceType)2;
			val2.MicrophoneType = (MicType)1;
			val2.InputFactory = () => (IAudioDesc)(object)new TeeAudioReader();
			userDataActor = PhotonNetwork.LocalPlayer.ActorNumber;
			val2.UserData = userDataActor;
			val2.TransmitEnabled = false;
			val2.VoiceDetection = false;
			val2.Encrypt = true;
			val2.DebugEchoMode = false;
			val2.InterestGroup = 0;
			val2.TargetPlayers = null;
			val2.SamplingRate = NearestSupportedRate(MicTee.SamplingRate);
			val2.Bitrate = 64000;
			((VoiceConnection)this).PrimaryRecorder = val2;
			if (!((VoiceConnection)this).AddRecorder(val2))
			{
				Plugin.Logger.LogWarning((object)"Private voice: AddRecorder refused - the recorder will never transmit");
				Object.Destroy((Object)(object)val);
			}
			else
			{
				recorder = val2;
				Plugin.Logger.LogInfo((object)$"Private voice recorder ready ({MicTee.SamplingRate} Hz, {MicTee.Channels} ch, Opus {64} kbps)");
			}
		}

		private void SyncUserData(Recorder rec)
		{
			int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber;
			if (actorNumber != userDataActor)
			{
				userDataActor = actorNumber;
				rec.UserData = actorNumber;
				Plugin.Logger.LogInfo((object)"Private voice: local actor number changed, rebinding recorder identity");
			}
		}

		private static SamplingRate NearestSupportedRate(int hz)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (hz <= 20000)
			{
				if (hz > 10000)
				{
					if (hz <= 14000)
					{
						return (SamplingRate)12000;
					}
					return (SamplingRate)16000;
				}
				return (SamplingRate)8000;
			}
			if (hz <= 36000)
			{
				return (SamplingRate)24000;
			}
			return (SamplingRate)48000;
		}

		internal void PublishMuted(bool muted)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			if (((VoiceConnection)this).Client == null || (int)((LoadBalancingClient)((VoiceConnection)this).Client).State != 9 || ((LoadBalancingClient)((VoiceConnection)this).Client).LocalPlayer == null)
			{
				publishedMuted = null;
			}
			else if (publishedMuted != muted)
			{
				publishedMuted = muted;
				((LoadBalancingClient)((VoiceConnection)this).Client).LocalPlayer.SetCustomProperties(new Hashtable { [(object)"spm"] = muted }, (Hashtable)null, (WebFlags)null);
			}
		}

		internal bool RemoteMuted(int actorNumber)
		{
			LoadBalancingTransport client = ((VoiceConnection)this).Client;
			object obj;
			if (client == null)
			{
				obj = null;
			}
			else
			{
				Room currentRoom = ((LoadBalancingClient)client).CurrentRoom;
				obj = ((currentRoom != null) ? currentRoom.Players : null);
			}
			Dictionary<int, Player> dictionary = (Dictionary<int, Player>)obj;
			if (dictionary == null)
			{
				return false;
			}
			PlayerRegistry instance = PlayerRegistry.Instance;
			if (instance == null)
			{
				return false;
			}
			string text = instance.TryGetSteamId(actorNumber);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			bool flag = default(bool);
			foreach (KeyValuePair<int, Player> item in dictionary)
			{
				Player value = item.Value;
				if (value == null || value.IsLocal || value.IsInactive || value.UserId != text)
				{
					continue;
				}
				int num;
				if (value.CustomProperties != null && ((Dictionary<object, object>)(object)value.CustomProperties).TryGetValue((object)"spm", out object value2))
				{
					if (value2 is bool)
					{
						flag = (bool)value2;
						num = 1;
					}
					else
					{
						num = 0;
					}
				}
				else
				{
					num = 0;
				}
				return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
			}
			return false;
		}

		internal bool ChannelLive(string expectedRoom)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			if (((VoiceConnection)this).Client == null || (int)((LoadBalancingClient)((VoiceConnection)this).Client).State != 9)
			{
				return false;
			}
			Room currentRoom = ((LoadBalancingClient)((VoiceConnection)this).Client).CurrentRoom;
			if (currentRoom != null && currentRoom.Name == expectedRoom)
			{
				return PrivateVoiceChannel.IsValidRoomName(currentRoom.Name);
			}
			return false;
		}

		private void SafeDisconnect()
		{
			if (((VoiceConnection)this).Client != null && ((LoadBalancingClient)((VoiceConnection)this).Client).IsConnected)
			{
				((VoiceFollowClient)this).Disconnect();
			}
		}

		internal void TearDown()
		{
			if ((Object)(object)recorder != (Object)null)
			{
				recorder.TransmitEnabled = false;
			}
			PrivateSpeakers.DestroyAll();
			SafeDisconnect();
			PrivateVoiceGate.WantCapture = false;
			MicTee.Flush();
			token = string.Empty;
			nextConnectWarnAt = 0f;
			nextConnectAttemptAt = 0f;
		}

		protected override string GetVoiceRoomName()
		{
			return token;
		}

		protected override bool ConnectVoice()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0049: 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_00b6: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			//IL_0076: Expected O, but got Unknown
			AppSettings val = null;
			((LoadBalancingClient)((VoiceConnection)this).Client).ServerPortOverrides = PhotonNetwork.ServerPortOverrides;
			val = PhotonNetwork.PhotonServerSettings.AppSettings.CopyTo(new AppSettings());
			if (!string.IsNullOrEmpty(PhotonNetwork.CloudRegion))
			{
				val.FixedRegion = PhotonNetwork.CloudRegion;
			}
			((LoadBalancingClient)((VoiceConnection)this).Client).SerializationProtocol = PhotonNetwork.NetworkingClient.SerializationProtocol;
			if (PhotonNetwork.AuthValues != null)
			{
				LoadBalancingTransport client = ((VoiceConnection)this).Client;
				if (((LoadBalancingClient)client).AuthValues == null)
				{
					AuthenticationValues val2 = new AuthenticationValues();
					AuthenticationValues val3 = val2;
					((LoadBalancingClient)client).AuthValues = val2;
				}
				((LoadBalancingClient)((VoiceConnection)this).Client).AuthValues = PhotonNetwork.AuthValues.CopyTo(((LoadBalancingClient)((VoiceConnection)this).Client).AuthValues);
			}
			((LoadBalancingClient)((VoiceConnection)this).Client).AuthMode = PhotonNetwork.NetworkingClient.AuthMode;
			((LoadBalancingClient)((VoiceConnection)this).Client).EncryptionMode = PhotonNetwork.NetworkingClient.EncryptionMode;
			return ((VoiceConnection)this).ConnectUsingSettings(val);
		}

		protected override bool JoinVoiceRoom(string voiceRoomName)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//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_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_006b: Expected O, but got Unknown
			if (!PrivateVoiceChannel.IsValidRoomName(voiceRoomName))
			{
				Plugin.Logger.LogWarning((object)"Private voice: join refused, malformed room name");
				return false;
			}
			RoomOptions roomOptions = new RoomOptions
			{
				IsVisible = false,
				IsOpen = true,
				MaxPlayers = 0,
				PlayerTtl = 2000,
				PublishUserId = true
			};
			EnterRoomParams val = new EnterRoomParams
			{
				RoomName = voiceRoomName,
				RoomOptions = roomOptions,
				Lobby = new TypedLobby("spv", (LobbyType)0)
			};
			Plugin.Logger.LogInfo((object)("Private voice: joining room " + PrivateVoiceChannel.Fingerprint(voiceRoomName)));
			return ((LoadBalancingClient)((VoiceConnection)this).Client).OpJoinOrCreateRoom(val);
		}

		protected override void OnOperationResponseReceived(OperationResponse operationResponse)
		{
			if (operationResponse.ReturnCode == 0)
			{
				((VoiceFollowClient)this).OnOperationResponseReceived(operationResponse);
				return;
			}
			Plugin.Logger.LogWarning((object)$"Private voice: op {operationResponse.OperationCode} failed ({operationResponse.ReturnCode})");
			if (operationResponse.OperationCode != 226)
			{
				((VoiceFollowClient)this).OnOperationResponseReceived(operationResponse);
				return;
			}
			string roomName = token;
			try
			{
				token = PrivateVoiceChannel.Fingerprint(roomName);
				((VoiceFollowClient)this).OnOperationResponseReceived(operationResponse);
			}
			finally
			{
				token = roomName;
			}
		}

		protected override Speaker? InstantiateSpeakerForRemoteVoice(int playerId, byte voiceId, object userData)
		{
			try
			{
				return PrivateSpeakers.Create(playerId, userData);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Private voice: speaker construction failed: {arg}");
				return null;
			}
		}
	}
	internal static class PrivateVoiceDriver
	{
		internal enum Health
		{
			Off,
			Provisioning,
			Live,
			Listening,
			Failed
		}

		private const float ReleaseGraceSeconds = 0.3f;

		private const float IntrusionConfirmSeconds = 2f;

		private const float MissingClientWarnCooldownSeconds = 5f;

		private static bool lastVoiceJoined;

		private static float releaseAt;

		private static float nextMissingClientWarnAt;

		private static string provisionedRoom = string.Empty;

		private static float unexpectedOccupantFirstSeenAt = -1f;

		private static PlayerVoiceChat? gameRecorderOwner;

		private static Recorder? gameRecorder;

		internal static Health State { get; private set; } = Health.Off;

		internal static void Tick()
		{
			int num = ((PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0);
			PrivateVoiceChannel.SetLocalMembership(num > 0 && PrivateVoiceChannel.IsMemberActor(num));
			bool localActive = PrivateVoiceChannel.LocalActive;
			if (!localActive && provisionedRoom.Length > 0)
			{
				TearDown("mode off");
			}
			RecorderTransmitPatch.Suppress = localActive || Time.realtimeSinceStartup < releaseAt;
			if (!localActive)
			{
				if (Time.realtimeSinceStartup < releaseAt)
				{
					ForceGameRecorderOff();
				}
				State = Health.Off;
				return;
			}
			ForceGameRecorderOff();
			PrivateSpeakers.SetVolume(GameVolume() * PrivateVoiceLocalPrefs.Factor);
			PrivateVoiceClient instance = PrivateVoiceClient.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.PublishMuted(LocalDisplayMuted());
				string roomName = PrivateVoiceChannel.RoomName;
				if (provisionedRoom != roomName)
				{
					if (provisionedRoom.Length > 0)
					{
						TearDown("room rotated");
					}
					else
					{
						MicTee.Flush();
					}
					instance.Provision(roomName);
					provisionedRoom = roomName;
					State = Health.Provisioning;
					return;
				}
				if ((Object)(object)instance.PrivateRecorder == (Object)null)
				{
					instance.Provision(roomName);
					bool flag = instance.ChannelLive(roomName);
					if (!lastVoiceJoined && flag)
					{
						MicTee.Flush();
					}
					lastVoiceJoined = flag;
					State = ((!flag) ? Health.Provisioning : Health.Listening);
					return;
				}
				bool flag2 = instance.ChannelLive(roomName);
				if (!lastVoiceJoined && flag2)
				{
					MicTee.Flush();
				}
				if (lastVoiceJoined && !flag2)
				{
					Plugin.Logger.LogInfo((object)"Private voice: channel dropped, re-provisioning");
				}
				lastVoiceJoined = flag2;
				if (!flag2)
				{
					instance.Provision(roomName);
					SetPrivateTransmit(on: false);
					State = Health.Provisioning;
					return;
				}
				AuditOccupants();
				if (State == Health.Failed)
				{
					return;
				}
				Recorder privateRecorder = instance.PrivateRecorder;
				if ((Object)(object)privateRecorder != (Object)null && privateRecorder.RecordingEnabled)
				{
					if (privateRecorder.InterestGroup != 0)
					{
						privateRecorder.InterestGroup = 0;
					}
					if (privateRecorder.TargetPlayers != null)
					{
						privateRecorder.TargetPlayers = null;
					}
				}
				SetPrivateTransmit(GameMicAllows());
				State = Health.Live;
			}
			else
			{
				if (Time.realtimeSinceStartup >= nextMissingClientWarnAt)
				{
					nextMissingClientWarnAt = Time.realtimeSinceStartup + 5f;
					Plugin.Logger.LogWarning((object)(PrivateVoiceClient.EverCreated ? "Private voice: the voice client was created at startup but no longer exists - something destroyed the SP_PrivateVoice object, so the channel cannot run" : "Private voice: the voice client was never created - PrivateVoiceClient.Init did not run or threw"));
				}
				State = Health.Failed;
			}
		}

		private static void AuditOccupants()
		{
			PrivateVoiceClient? instance = PrivateVoiceClient.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				LoadBalancingTransport client = ((VoiceConnection)instance).Client;
				if (client == null)
				{
					obj = null;
				}
				else
				{
					Room currentRoom = ((LoadBalancingClient)client).CurrentRoom;
					obj = ((currentRoom != null) ? currentRoom.Players : null);
				}
			}
			Dictionary<int, Player> dictionary = (Dictionary<int, Player>)obj;
			if (dictionary == null)
			{
				return;
			}
			PlayerRegistry instance2 = PlayerRegistry.Instance;
			if (instance2 == null)
			{
				return;
			}
			int[] array = PrivateVoiceChannel.MemberActors();
			for (int i = 0; i < array.Length; i++)
			{
				if (instance2.TryGetSteamId(array[i]) == null)
				{
					return;
				}
			}
			bool flag = false;
			foreach (KeyValuePair<int, Player> item in dictionary)
			{
				Player value = item.Value;
				if (value == null || value.IsLocal || value.IsInactive)
				{
					continue;
				}
				string userId = value.UserId;
				if (string.IsNullOrEmpty(userId))
				{
					continue;
				}
				bool flag2 = false;
				for (int j = 0; j < array.Length; j++)
				{
					if (instance2.TryGetSteamId(array[j]) == userId)
					{
						flag2 = true;
						break;
					}
				}
				if (!flag2)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				unexpectedOccupantFirstSeenAt = -1f;
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (unexpectedOccupantFirstSeenAt < 0f)
			{
				unexpectedOccupantFirstSeenAt = realtimeSinceStartup;
			}
			else if (!(realtimeSinceStartup - unexpectedOccupantFirstSeenAt < 2f))
			{
				Plugin.Logger.LogWarning((object)"Private voice: unexpected occupant persisted - muting the private channel");
				SetPrivateTransmit(on: false);
				State = Health.Failed;
			}
		}

		private static bool GameMicAllows()
		{
			PlayerVoiceChat instance = PlayerVoiceChat.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			if (!instance.microphoneEnabled)
			{
				return false;
			}
			if ((Object)(object)DataDirector.instance == (Object)null)
			{
				return false;
			}
			if (DataDirector.instance.toggleMute)
			{
				return false;
			}
			if ((Object)(object)AudioManager.instance == (Object)null)
			{
				return false;
			}
			if (AudioManager.instance.pushToTalk && !SemiFunc.InputHold((InputKey)25))
			{
				return false;
			}
			return true;
		}

		internal static bool LocalDisplayMuted()
		{
			PlayerVoiceChat instance = PlayerVoiceChat.instance;
			if ((Object)(object)instance == (Object)null || !instance.microphoneEnabled)
			{
				return true;
			}
			if (!((Object)(object)DataDirector.instance == (Object)null))
			{
				return DataDirector.instance.toggleMute;
			}
			return true;
		}

		private static float GameVolume()
		{
			if ((Object)(object)DataDirector.instance == (Object)null)
			{
				return 1f;
			}
			float num = (float)DataDirector.instance.SettingValueFetch((Setting)13) * 0.01f;
			float num2 = (float)DataDirector.instance.SettingValueFetch((Setting)4) * 0.01f;
			float num3 = num * num2;
			if (num3 < 0f)
			{
				num3 = 0f;
			}
			if (num3 > 1f)
			{
				num3 = 1f;
			}
			return num3;
		}

		private static void SetPrivateTransmit(bool on)
		{
			Recorder val = PrivateVoiceClient.Instance?.PrivateRecorder;
			if ((Object)(object)val != (Object)null && val.TransmitEnabled != on)
			{
				val.TransmitEnabled = on;
			}
		}

		private static void ForceGameRecorderOff()
		{
			PlayerVoiceChat instance = PlayerVoiceChat.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if ((Object)(object)gameRecorder == (Object)null || instance != gameRecorderOwner)
				{
					gameRecorderOwner = instance;
					gameRecorder = ((Component)instance).GetComponent<Recorder>();
				}
				if ((Object)(object)gameRecorder != (Object)null)
				{
					RecorderTransmitPatch.ForceOff(gameRecorder);
				}
			}
		}

		internal static void TearDown(string reason)
		{
			if (provisionedRoom.Length != 0 || State != Health.Off)
			{
				Plugin.Logger.LogInfo((object)("Private voice: tearing down (" + reason + ")"));
				SetPrivateTransmit(on: false);
				PrivateVoiceClient.Instance?.TearDown();
				provisionedRoom = string.Empty;
				lastVoiceJoined = false;
				unexpectedOccupantFirstSeenAt = -1f;
				State = Health.Off;
				releaseAt = Time.realtimeSinceStartup + 0.3f;
			}
		}
	}
	internal static class PrivateVoiceLocalPrefs
	{
		internal const int MaxVolumePercent = 300;

		internal static int VolumePercent => ModConfig.PrivateChannelVolume.Value;

		internal static int MicLiftDb
		{
			get
			{
				int value = ModConfig.PrivateChannelMicGain.Value;
				if (value >= 0)
				{
					if (value <= 40)
					{
						return value;
					}
					return 40;
				}
				return 0;
			}
		}

		internal static float Factor => (float)VolumePercent * 0.01f;

		internal static void SetVolumePercent(int value)
		{
			int num = ((value >= 0) ? ((value > 300) ? 300 : value) : 0);
			if (ModConfig.PrivateChannelVolume.Value != num)
			{
				ModConfig.PrivateChannelVolume.Value = num;
			}
		}

		internal static void SetMicLiftDb(int value)
		{
			int num = ((value >= 0) ? ((value > 40) ? 40 : value) : 0);
			if (ModConfig.PrivateChannelMicGain.Value != num)
			{
				ModConfig.PrivateChannelMicGain.Value = num;
			}
		}
	}
	internal static class PrivateVoiceDsp
	{
		internal const int DefaultMicGainDb = 20;

		internal const int MaxMicGainDb = 40;

		private static WebRtcAudioDsp? live;

		private static bool subscribed;

		internal static string LastResult { get; private set; } = "not attempted";

		private static int MicGainDb
		{
			get
			{
				int value = ModConfig.PrivateChannelMicGain.Value;
				if (value >= 0)
				{
					if (value <= 40)
					{
						return value;
					}
					return 40;
				}
				return 0;
			}
		}

		internal static void Mirror(GameObject recorderObject)
		{
			PlayerVoiceChat instance = PlayerVoiceChat.instance;
			WebRtcAudioDsp val = (((Object)(object)instance != (Object)null) ? ((Component)instance).GetComponent<WebRtcAudioDsp>() : null);
			if ((Object)(object)val == (Object)null)
			{
				LastResult = "not mirrored (the game's recorder carries no WebRtcAudioDsp)";
				Plugin.Logger.LogWarning((object)"Private voice: the game's recorder carries no WebRtcAudioDsp - the private stream keeps the raw microphone level and will stay quieter than the public channel");
				return;
			}
			if (MicTee.Channels != 1)
			{
				LastResult = $"skipped (capture is {MicTee.Channels} ch, WebRtcAudioDsp is mono-only)";
				Plugin.Logger.LogWarning((object)$"Private voice: capture is {MicTee.Channels} ch and WebRtcAudioDsp is mono-only - the DSP mirror is skipped and the private stream keeps the raw microphone level");
				return;
			}
			WebRtcAudioDsp val2 = recorderObject.AddComponent<WebRtcAudioDsp>();
			val2.AEC = val.AEC;
			val2.AecHighPass = val.AecHighPass;
			val2.ReverseStreamDelayMs = val.ReverseStreamDelayMs;
			val2.HighPass = val.HighPass;
			val2.NoiseSuppression = val.NoiseSuppression;
			val2.AGC = val.AGC;
			val2.AgcTargetLevel = val.AgcTargetLevel;
			val2.Bypass = val.Bypass;
			val2.AgcCompressionGain = MicGainDb;
			live = val2;
			if (!subscribed)
			{
				subscribed = true;
				ModConfig.PrivateChannelMicGain.SettingChanged += delegate
				{
					ApplyMicGainToLive();
				};
			}
			val2.VAD = false;
			LastResult = Describe(val2);
			Plugin.Logger.LogInfo((object)$"Private voice: DSP mirrored from the game's recorder (AGC {OnOff(val2.AGC)} target {val2.AgcTargetLevel} dBFS / max lift {val2.AgcCompressionGain} dB (game: {val.AgcCompressionGain}), noise suppression {OnOff(val2.NoiseSuppression)}, high pass {OnOff(val2.HighPass)}, AEC {OnOff(val2.AEC)}, VAD off by design, bypass {OnOff(val2.Bypass)})");
		}

		private static void ApplyMicGainToLive()
		{
			WebRtcAudioDsp val = live;
			if (!((Object)(object)val == (Object)null))
			{
				val.AgcCompressionGain = MicGainDb;
				LastResult = Describe(val);
				Plugin.Logger.LogInfo((object)$"Private voice: mic lift now up to {val.AgcCompressionGain} dB");
			}
		}

		private static string Describe(WebRtcAudioDsp dsp)
		{
			return $"mirrored (AGC {OnOff(dsp.AGC)}, mic lift up to {dsp.AgcCompressionGain} dB, noise suppression {OnOff(dsp.NoiseSuppression)}, AEC {OnOff(dsp.AEC)})";
		}

		private static string OnOff(bool value)
		{
			if (!value)
			{
				return "off";
			}
			return "on";
		}
	}
	internal static class PrivateVoicePresenceReporter
	{
		private static int lastReportedEpoch = -1;

		private static bool lastReportedConnected;

		internal static bool IsConnected(PrivateVoiceDriver.Health state)
		{
			if (state != PrivateVoiceDriver.Health.Live)
			{
				return state == PrivateVoiceDriver.Health.Listening;
			}
			return true;
		}

		internal static void Report(PrivateVoiceDriver.Health state)
		{
			if (!PrivateVoiceChannel.Active || !PrivateVoiceChannel.LocalActive)
			{
				lastReportedEpoch = -1;
				lastReportedConnected = false;
				return;
			}
			int epoch = PrivateVoiceChannel.Epoch;
			bool flag = IsConnected(state);
			if (epoch != lastReportedEpoch || flag != lastReportedConnected)
			{
				lastReportedEpoch = epoch;
				lastReportedConnected = flag;
				if (SemiFunc.IsMasterClient())
				{
					PrivateVoiceSync.NoteLocalPresence(flag);
					return;
				}
				CommandSender.Send("PrivateVoicePresence", new object[2]
				{
					epoch.ToString(),
					flag ? "1" : "0"
				}, (ReceiverGroup)2);
			}
		}

		internal static void Reset()
		{
			lastReportedEpoch = -1;
			lastReportedConnected = false;
		}
	}
	internal sealed class TeeAudioReader : IAudioReader<float>, IDataReader<float>, IDisposable, IAudioDesc
	{
		public int SamplingRate => MicTee.SamplingRate;

		public int Channels => MicTee.Channels;

		public string? Error
		{
			get
			{
				if (!MicTee.IsLive)
				{
					return "mic tee not live";
				}
				return null;
			}
		}

		public bool Read(float[] buffer)
		{
			return MicTee.Ring.Read(buffer);
		}

		public void Dispose()
		{
		}
	}
}
namespace SharePermissions.UI
{
	internal sealed class BandRow : MonoBehaviour
	{
		private sealed class Slot
		{
			internal RectTransform Rect;

			internal TextMeshProUGUI Label;

			internal REPOButton? Button;

			internal int Order;

			internal string? Reserve;
		}

		private readonly List<Slot> slots = new List<Slot>();

		private int settleFrames;

		internal static BandRow For(REPOPopupPage page)
		{
			BandRow component = ((Component)page).GetComponent<BandRow>();
			if (!((Object)(object)component != (Object)null))
			{
				return ((Component)page).gameObject.AddComponent<BandRow>();
			}
			return component;
		}

		internal void Add(REPOButton button, int order, string? reserve = null)
		{
			Add(((REPOElement)button).rectTransform, button.labelTMP, button, order, reserve);
		}

		internal void Add(REPOLabel label, int order, string? reserve = null)
		{
			Add(((REPOElement)label).rectTransform, label.labelTMP, null, order, reserve);
		}

		private void Add(RectTransform rect, TextMeshProUGUI label, REPOButton? button, int order, string? reserve)
		{
			slots.Add(new Slot
			{
				Rect = rect,
				Label = label,
				Button = button,
				Order = order,
				Reserve = reserve
			});
			slots.Sort((Slot a, Slot b) => a.Order.CompareTo(b.Order));
		}

		internal void Relayout()
		{
			settleFrames = 2;
			Apply();
		}

		private void LateUpdate()
		{
			if (settleFrames > 0)
			{
				settleFrames--;
				Apply();
			}
		}

		private void Apply()
		{
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			slots.RemoveAll((Slot s) => (Object)(object)s.Rect == (Object)null || (Object)(object)s.Label == (Object)null);
			List<Slot> list = new List<Slot>();
			foreach (Slot slot2 in slots)
			{
				if (((Component)slot2.Rect).gameObject.activeInHierarchy)
				{
					list.Add(slot2);
				}
			}
			if (list.Count == 0)
			{
				return;
			}
			Vector2[] array = (Vector2[])(object)new Vector2[list.Count];
			float totalWidth = MeasureAll(list, 16f, array);
			float num = 6f;
			float num2 = ModStyle.BandRight - ModStyle.BandLeft;
			if (num2 <= 0f)
			{
				return;
			}
			float num3 = TabBandLayout.FitScale(totalWidth, num2 - num * (float)(list.Count - 1));
			if (num3 < 1f)
			{
				MeasureAll(list, 16f * num3, array);
			}
			float[] array2 = new float[list.Count];
			for (int num4 = 0; num4 < list.Count; num4++)
			{
				array2[num4] = array[num4].x;
			}
			float num5 = 0f;
			for (int num6 = 0; num6 < list.Count; num6++)
			{
				num5 = Mathf.Max(num5, array[num6].y);
			}
			float[] array3 = TabBandLayout.SpreadBandLeftEdges(array2, ModStyle.BandLeft, ModStyle.BandRight, num);
			for (int num7 = 0; num7 < list.Count; num7++)
			{
				Slot slot = list[num7];
				if ((Object)(object)slot.Button != (Object)null)
				{
					MenuUiHelpers.PinButtonSize(slot.Button, new Vector2(array2[num7], num5));
				}
				else
				{
					slot.Rect.sizeDelta = new Vector2(array2[num7], num5);
					((TMP_Text)slot.Label).rectTransform.sizeDelta = new Vector2(array2[num7], num5);
				}
				((Transform)slot.Rect).localPosition = new Vector3(array3[num7], 20f, 0f);
			}
			AlignCaptionsToOneLine(list);
		}

		private static void AlignCaptionsToOneLine(List<Slot> live)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: 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)
			if (live.Count < 2)
			{
				return;
			}
			float[] array = new float[live.Count];
			for (int i = 0; i < live.Count; i++)
			{
				array[i] = CaptionCentre(live[i]);
			}
			float[] array2 = (float[])array.Clone();
			Array.Sort(array2);
			float num = array2[array2.Length / 2];
			for (int j = 0; j < live.Count; j++)
			{
				float num2 = num - array[j];
				if (!(Mathf.Abs(num2) < 0.01f))
				{
					RectTransform rect = live[j].Rect;
					Vector3 localPosition = ((Transform)rect).localPosition;
					localPosition.y = ((Transform)rect).localPosition.y + num2;
					((Transform)rect).localPosition = localPosition;
				}
			}
		}

		private static float CaptionCentre(Slot s)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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)
			TextMeshProUGUI label = s.Label;
			RectTransform rectTransform = ((TMP_Text)label).rectTransform;
			((TMP_Text)label).ForceMeshUpdate(false, false);
			TMP_TextInfo textInfo = ((TMP_Text)label).textInfo;
			float num;
			if (textInfo != null && textInfo.lineCount > 0)
			{
				num = textInfo.lineInfo[0].baseline;
			}
			else
			{
				Bounds textBounds = ((TMP_Text)label).textBounds;
				if (((Bounds)(ref textBounds)).extents.y > 0f)
				{
					textBounds = ((TMP_Text)label).textBounds;
					num = ((Bounds)(ref textBounds)).center.y;
				}
				else
				{
					Rect rect = rectTransform.rect;
					num = ((Rect)(ref rect)).center.y;
				}
			}
			return ((Transform)s.Rect).localPosition.y + ((Transform)rectTransform).localPosition.y + num;
		}

		private static float MeasureAll(List<Slot> live, float fontSize, Vector2[] sizes)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			float num = 0f;
			for (int i = 0; i < live.Count; i++)
			{
				Slot slot = live[i];
				((TMP_Text)slot.Label).enableAutoSizing = false;
				((TMP_Text)slot.Label).alignment = (TextAlignmentOptions)4097;
				((TMP_Text)slot.Label).fontSize = fontSize;
				((TMP_Text)slot.Label).ForceMeshUpdate(false, false);
				sizes[i] = ((TMP_Text)slot.Label).GetPreferredValues(slot.Reserve ?? ((TMP_Text)slot.Label).text);
				num += sizes[i].x;
			}
			return num;
		}
	}
	internal static class CaptionWidth
	{
		internal const int WideTenths = 28;

		private const int NarrowTenths = 10;

		internal static bool IsWide(char c)
		{
			if ((c < 'ᄀ' || c > 'ᅟ') && (c < '⺀' || c > '〾') && (c < 'ぁ' || c > '㏿') && (c < '㐀' || c > '䶿') && (c < '一' || c > '鿿') && (c < 'ꥠ' || c > '\ua97f') && (c < '가' || c > '힣') && (c < '豈' || c > '\ufaff') && (c < '︰' || c > '\ufe4f') && (c < '!' || c > '⦆'))
			{
				if (c >= '¢')
				{
					return c <= '₩';
				}
				return false;
			}
			return true;
		}

		internal static int Tenths(string text)
		{
			int num = 0;
			foreach (char c in text)
			{
				num += (IsWide(c) ? 28 : 10);
			}
			return num;
		}

		internal static string Fit(string text, int budget)
		{
			if (Tenths(text) <= budget * 10)
			{
				return text;
			}
			int num = (budget - 1) * 10;
			int num2 = 0;
			int i;
			for (i = 0; i < text.Length; i++)
			{
				int num3 = num2 + (IsWide(text[i]) ? 28 : 10);
				if (num3 > num)
				{
					break;
				}
				num2 = num3;
			}
			if (i > 0 && char.IsHighSurrogate(text[i - 1]))
			{
				i--;
			}
			return text.Substring(0, i) + "…";
		}
	}
	internal static class ColorPickerPage
	{
		private const float ContentWidth = 250f;

		private const float SwatchSize = 34f;

		private const float SwatchPitch = 38f;

		private const int SwatchesPerRow = 6;

		private static readonly IReadOnlyList<Color> FallbackPalette = (IReadOnlyList<Color>)(object)new Color[16]
		{
			ModStyle.RoleHost,
			ModStyle.RoleModerator,
			ModStyle.RoleModUser,
			ModStyle.RoleNone,
			ModStyle.SevBan,
			ModStyle.SevRevoke,
			ModStyle.SevGrant,
			ModStyle.SevFlood,
			Color.white,
			Color.red,
			Color.green,
			Color.blue,
			Color.yellow,
			Color.cyan,
			Color.magenta,
			Color.gray
		};

		internal static void Open(string title, string description, Color current, Action<Color> onPicked, string? clearCaption = null, Action? onClear = null)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			REPOPopupPage page = MenuUiHelpers.CreatePage(title, delegate
			{
			});
			MenuUiHelpers.AddLabel(page, description);
			MenuUiHelpers.AddLabel(page, "<size=75%><color=#6f7d88>" + Loc.T("Current") + "</color>  <color=#" + NameColors.ToHex(current) + ">#" + NameColors.ToHex(current) + "</color></size>");
			foreach (IReadOnlyList<Color> item in Rows(Palette()))
			{
				AddSwatchRow(page, item, Choose);
			}
			AddHexInput(page, current, Choose);
			if (onClear != null && clearCaption != null)
			{
				MenuUiHelpers.AddScrollViewButton(page, clearCaption, delegate
				{
					MenuUiHelpers.ClosePage(page);
					onClear();
				});
			}
			MenuUiHelpers.OpenPage(page);
			void Choose(Color color)
			{
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				MenuUiHelpers.ClosePage(page);
				onPicked(color);
			}
		}

		private static void AddSwatchRow(REPOPopupPage page, IReadOnlyList<Color> colors, Action<Color> onChoose)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Expected O, but got Unknown
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Expected O, but got Unknown
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0049: Unknown result type (might be due to invalid IL or missing references)
				//IL_005e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0081: Unknown result type (might be due to invalid IL or missing references)
				//IL_0086: Unknown result type (might be due to invalid IL or missing references)
				//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
				GameObject val = new GameObject("SharePermissions_SwatchRow", new Type[1] { typeof(RectTransform) });
				RectTransform val2 = (RectTransform)val.transform;
				((Transform)val2).SetParent(scrollView, false);
				val2.anchorMin = Vector2.zero;
				val2.anchorMax = Vector2.zero;
				val2.pivot = Vector2.zero;
				val2.sizeDelta = new Vector2(250f, 34f);
				for (int i = 0; i < colors.Count; i++)
				{
					Color color = colors[i];
					REPOButton button = MenuAPI.CreateREPOButton("", (Action)delegate
					{
						//IL_000c: Unknown result type (might be due to invalid IL or missing references)
						onChoose(color);
					}, (Transform)(object)val2, new Vector2((float)i * 38f, 0f));
					MenuUiHelpers.PinButtonSize(button, new Vector2(34f, 34f));
					StyleSwatch(button, color);
				}
				return val2;
			}, 0f, 2f);
		}

		private static void StyleSwatch(REPOButton button, Color color)
		{
			//IL_0011: 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_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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			Image component = ((Component)button).GetComponent<Image>();
			if ((Object)(object)component != (Object)null)
			{
				((Graphic)component).color = color;
				((Graphic)component).raycastTarget = true;
			}
			if (!((Object)(object)button.menuButton == (Object)null))
			{
				button.menuButton.resizeButton = false;
				button.menuButton.customColors = true;
				button.menuButton.colorNormal = NameColors.Dimmed(color, 0.75f);
				button.menuButton.colorHover = color;
				button.menuButton.colorClick = Color.Lerp(color, Color.white, 0.95f);
			}
		}

		private static void AddHexInput(REPOPopupPage page, Color current, Action<Color> onChoose)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0030: 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_0048: Unknown result type (might be due to invalid IL or missing references)
				string text = Loc.T("Hex");
				Action<string> obj = delegate(string value)
				{
					//IL_0010: Unknown result type (might be due to invalid IL or missing references)
					if (NameColors.TryParseHex(value, out var color))
					{
						onChoose(color);
					}
					else
					{
						Plugin.Logger.LogInfo((object)("Name color ignored: '" + value + "' is not a #RRGGBB value"));
					}
				};
				string text2 = "#" + NameColors.ToHex(current);
				REPOInputField val = MenuAPI.CreateREPOInputField(text, obj, scrollView, default(Vector2), true, text2, "");
				((TMP_Text)val.labelTMP).fontSize = 14f;
				return ((REPOElement)val).rectTransform;
			}, 4f, 2f);
			MenuUiHelpers.AddLabel(page, "<size=75%><color=#6f7d88>" + Loc.T("Or type a color as #RRGGBB and press enter.") + "</color></size>");
		}

		private static IEnumerable<IReadOnlyList<Color>> Rows(IReadOnlyList<Color> colors)
		{
			for (int start = 0; start < colors.Count; start += 6)
			{
				yield return colors.Skip(start).Take(6).ToList();
			}
		}

		private static IReadOnlyList<Color> Palette()
		{
			try
			{
				IReadOnlyList<Color> readOnlyList = GamePaletteRaw();
				if (readOnlyList.Count > 0)
				{
					return readOnlyList;
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("[SharePermissions] Game color palette unreadable, using the built-in one: " + ex.Message));
			}
			return FallbackPalette;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static IReadOnlyList<Color> GamePaletteRaw()
		{
			MetaManager instance = MetaManager.instance;
			if ((Object)(object)instance == (Object)null || instance.colors == null)
			{
				return Array.Empty<Color>();
			}
			List<SemiColor> order = instance.colorsUIOrder;
			return (from c in instance.colors
				where (Object)(object)c != (Object)null
				orderby (order != null) ? order.IndexOf(c) : 0
				select c.color).ToList();
		}
	}
	internal sealed class ExtrasPanelController
	{
		private readonly REPOPopupPage page;

		private REPOScrollViewElement? channelHeaderElem;

		private REPOLabel? offHint;

		private REPOScrollViewElement? offHintElem;

		private string offHintCache = string.Empty;

		private REPOButton? privateToggle;

		private REPOScrollViewElement? privateToggleElem;

		private const int MaxOneOnOneRows = 8;

		private const float OneOnOneRescanSeconds = 0.25f;

		private REPOScrollViewElement? oneOnOneHintElem;

		private REPOScrollViewElement? oneOnOneAskHintElem;

		private readonly REPOButton?[] oneOnOneRows = (REPOButton?[])(object)new REPOButton[8];

		private readonly REPOScrollViewElement?[] oneOnOneRowElems = (REPOScrollViewElement?[])(object)new REPOScrollViewElement[8];

		private readonly int[] oneOnOneRowActors = new int[8];

		private readonly string[] oneOnOneRowNames = new string[8];

		private readonly bool[] oneOnOneRowIsTarget = new bool[8];

		private readonly bool[] oneOnOneRowOffline = new bool[8];

		private readonly int[] oneOnOneCandidates = new int[8];

		private int oneOnOneCount;

		private float nextOneOnOneRescanAt;

		private float nextOneOnOneOverflowWarnAt;

		private REPOLabel? pendingLabel;

		private REPOScrollViewElement? pendingLabelElem;

		private bool pendingLabelForMe;

		private int pendingLabelActor;

		private REPOScrollViewElement? acceptButtonElem;

		private REPOScrollViewElement? declineButtonElem;

		private REPOScrollViewElement? cancelButtonElem;

		private REPOScrollViewElement? hangUpButtonElem;

		private REPOLabel? privateStatus;

		private REPOScrollViewElement? privateStatusElem;

		private bool privateTalking;

		private float privateTalkHoldUntil;

		private PrivateVoiceDriver.Health lastStatusState = (PrivateVoiceDriver.Health)(-1);

		private bool lastStatusTransmitting;

		private bool lastStatusShowTalkDot;

		private int lastToggleScope = -1;

		private REPOSlider? privateVolumeSlider;

		private REPOScrollViewElement? privateVolumeSliderElem;

		private REPOSlider? micLiftSlider;

		private REPOScrollViewElement? micLiftSliderElem;

		private REPOScrollViewElement? micLiftHintElem;

		private bool tabVisible;

		internal ExtrasPanelController(REPOPopupPage page)
		{
			this.page = page;
		}

		internal void Build()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Expected O, but got Unknown
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Expected O, but got Unknown
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Expected O, but got Unknown
			//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Expected O, but got Unknown
			channelHeaderElem = AddLabel("<size=115%>" + Loc.T("Private channel") + "</size>");
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: Unknown result type (might be due to invalid IL or missing references)
				privateToggle = MenuAPI.CreateREPOButton("", (Action)ModerationActions.TogglePrivateVoice, sv, default(Vector2));
				((TMP_Text)privateToggle.labelTMP).richText = true;
				((TMP_Text)privateToggle.labelTMP).fontSize = 14f;
				MenuUiHelpers.PinButtonSize(privateToggle, new Vector2(250f, 24f));
				return ((REPOElement)privateToggle).rectTransform;
			}, 0f, 2f);
			privateToggleElem = Elem((Component?)(object)privateToggle);
			oneOnOneHintElem = AddLabel("<size=75%><color=#6f7d88>" + Loc.T("Or with a single moderator - the others hear nothing:") + "</color></size>");
			oneOnOneAskHintElem = AddLabel("<size=75%><color=#6f7d88>" + Loc.T("Ask for a 1 on 1 - the others hear nothing:") + "</color></size>");
			for (int num = 0; num < oneOnOneRows.Length; num++)
			{
				int slot = num;
				page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
				{
					//IL_0038: Unknown result type (might be due to invalid IL or missing references)
					//IL_003e: Unknown result type (might be due to invalid IL or missing references)
					//IL_009f: Unknown result type (might be due to invalid IL or missing references)
					oneOnOneRows[slot] = MenuAPI.CreateREPOButton("", (Action)delegate
					{
						OnOneOnOneClicked(slot);
					}, sv, default(Vector2));
					((TMP_Text)oneOnOneRows[slot].labelTMP).richText = true;
					((TMP_Text)oneOnOneRows[slot].labelTMP).fontSize = 14f;
					MenuUiHelpers.PinButtonSize(oneOnOneRows[slot], new Vector2(250f, 24f));
					return ((REPOElement)oneOnOneRows[slot]).rectTransform;
				}, 0f, 2f);
				oneOnOneRowElems[slot] = Elem((Component?)(object)oneOnOneRows[slot]);
			}
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				pendingLabel = MenuAPI.CreateREPOLabel("", sv, default(Vector2));
				((TMP_Text)pendingLabel.labelTMP).richText = true;
				((TMP_Text)pendingLabel.labelTMP).fontSize = 14f;
				return ((REPOElement)pendingLabel).rectTransform;
			}, 0f, 6f);
			pendingLabelElem = Elem((Component?)(object)pendingLabel);
			AddActionButton(Loc.T("Accept"), delegate
			{
				ModerationActions.AnswerOneOnOne(accept: true);
			}, out acceptButtonElem);
			AddActionButton(Loc.T("Decline"), delegate
			{
				ModerationActions.AnswerOneOnOne(accept: false);
			}, out declineButtonElem);
			AddActionButton(Loc.T("Cancel"), ModerationActions.CancelOneOnOne, out cancelButtonElem);
			AddActionButton(Loc.T("Hang up"), ModerationActions.HangUp, out hangUpButtonElem);
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				offHint = MenuAPI.CreateREPOLabel("", sv, default(Vector2));
				((TMP_Text)offHint.labelTMP).richText = true;
				((TMP_Text)offHint.labelTMP).fontSize = 14f;
				return ((REPOElement)offHint).rectTransform;
			}, 0f, 6f);
			offHintElem = Elem((Component?)(object)offHint);
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				privateStatus = MenuAPI.CreateREPOLabel("", sv, default(Vector2));
				((TMP_Text)privateStatus.labelTMP).richText = true;
				((TMP_Text)privateStatus.labelTMP).fontSize = 14f;
				return ((REPOElement)privateStatus).rectTransform;
			}, 0f, 2f);
			privateStatusElem = Elem((Component?)(object)privateStatus);
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				string text = Loc.T("Private volume");
				Action<int> action = OnPrivateVolumeChanged;
				int volumePercent = PrivateVoiceLocalPrefs.VolumePercent;
				privateVolumeSlider = MenuAPI.CreateREPOSlider(text, "", action, sv, default(Vector2), 0, 300, volumePercent, "", "%", (BarBehavior)0);
				return ((REPOElement)privateVolumeSlider).rectTransform;
			}, 0f, 2f);
			privateVolumeSliderElem = Elem((Component?)(object)privateVolumeSlider);
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				string text = Loc.T("Mic lift");
				Action<int> action = OnMicLiftChanged;
				int micLiftDb = PrivateVoiceLocalPrefs.MicLiftDb;
				micLiftSlider = MenuAPI.CreateREPOSlider(text, "", action, sv, default(Vector2), 0, 40, micLiftDb, "", " dB", (BarBehavior)0);
				return ((REPOElement)micLiftSlider).rectTransform;
			}, 0f, 2f);
			micLiftSliderElem = Elem((Component?)(object)micLiftSlider);
			micLiftHintElem = AddLabel("<size=75%><color=#6f7d88>" + Loc.T("How far a quiet microphone is lifted before it goes out on the channel. The game's own chat allows 9 dB.") + "</color></size>");
		}

		internal void SetTabVisible(bool visible)
		{
			tabVisible = visible;
			if (!visible)
			{
				SetVisibility(channelHeaderElem, visible: false);
				SetVisibility(privateToggleElem, visible: false);
				HideOneOnOneRows();
				SetVisibility(offHintElem, visible: false);
				SetVisibility(privateStatusElem, visible: false);
				SetVisibility(privateVolumeSliderElem, visible: false);
				SetVisibility(micLiftSliderElem, visible: false);
				SetVisibility(micLiftHintElem, visible: false);
				SetVisibility(pendingLabelElem, visible: false);
				SetVisibility(acceptButtonElem, visible: false);
				SetVisibility(declineButtonElem, visible: false);
				SetVisibility(cancelButtonElem, visible: false);
				SetVisibility(hangUpButtonElem, visible: false);
			}
			else
			{
				Tick();
			}
		}

		internal void Tick()
		{
			if (!((Object)(object)page == (Object)null) && tabVisible)
			{
				bool flag = SemiFunc.IsMasterClient();
				bool localActive = PrivateVoiceChannel.LocalActive;
				bool mayStartOneOnOne = ModerationActions.MayStartOneOnOne;
				int num;
				if (!flag)
				{
					num = (Voice1on1Local.HasInvite ? 1 : 0);
				}
				else if (Voice1on1Invites.Pending)
				{
					int target = Voice1on1Invites.Target;
					Player localPlayer = PhotonNetwork.LocalPlayer;
					num = ((target == ((localPlayer != null) ? localPlayer.ActorNumber : (-1))) ? 1 : 0);
				}
				else
				{
					num = 0;
				}
				bool flag2 = (byte)num != 0;
				int inviteFrom = (flag ? Voice1on1Invites.Requester : Voice1on1Local.InviteFrom);
				bool flag3 = !flag && Voice1on1Local.Waiting;
				bool flag4 = !flag && !localActive && !flag2 && !flag3 && (!mayStartOneOnOne || PrivateVoiceChannel.Active);
				SetVisibility(channelHeaderElem, visible: true);
				SetVisibility(privateToggleElem, flag);
				SetVisibility(offHintElem, flag4);
				SetVisibility(privateStatusElem, localActive);
				SetVisibility(privateVolumeSliderElem, localActive);
				SetVisibility(micLiftSliderElem, localActive);
				SetVisibility(micLiftHintElem, localActive);
				SetVisibility(pendingLabelElem, flag2 || flag3);
				SetVisibility(acceptButtonElem, flag2);
				SetVisibility(declineButtonElem, flag2);
				SetVisibility(cancelButtonElem, flag3);
				SetVisibility(hangUpButtonElem, !flag && ModerationActions.LocalMayHangUp);
				if (flag2 || flag3)
				{
					UpdatePendingLabel(flag2, inviteFrom);
				}
				if (flag)
				{
					UpdateChannelToggle();
					UpdateOneOnOneRows();
				}
				else if (mayStartOneOnOne && !PrivateVoiceChannel.Active && !flag2 && !flag3)
				{
					UpdateOneOnOneRows();
				}
				else
				{
					HideOneOnOneRows();
				}
				if (flag4)
				{
					UpdateOffHint();
				}
				if (localActive)
				{
					UpdateStatus();
				}
				else
				{
					lastStatusState = (PrivateVoiceDriver.Health)(-1);
				}
			}
		}

		private void UpdatePendingLabel(bool inviteForMe, int inviteFrom)
		{
			if (!((Object)(object)pendingLabel == (Object)null))
			{
				int num = (inviteForMe ? inviteFrom : Voice1on1Local.WaitingOn);
				if (inviteForMe != pendingLabelForMe || num != pendingLabelActor)
				{
					pendingLabelForMe = inviteForMe;
					pendingLabelActor = num;
					string text = (inviteForMe ? Loc.T("{0} wants a 1 on 1", "<color=#4dd07a>" + ResolveModName(num) + "</color>") : Loc.T("Waiting for {0}", ResolveModName(num)));
					((TMP_Text)pendingLabel.labelTMP).text = text;
					MenuUiHelpers.SizeWrappedLabel(pendingLabel, text, 250f);
				}
			}
		}

		private void UpdateChannelToggle()
		{
			if (!((Object)(object)privateToggle == (Object)null))
			{
				int num = (PrivateVoiceChannel.Active ? ((PrivateVoiceSync.TargetActor > 0) ? 1 : ((PrivateVoiceSync.PairActors.A > 0) ? 2 : 3)) : 0);
				if (num != lastToggleScope)
				{
					lastToggleScope = num;
					string text = num switch
					{
						0 => Loc.T("Start private channel:") + " <color=#6f7d88>" + Loc.T("all moderators") + "</color>", 
						1 => Loc.T("Private channel:") + " <color=#4dd07a>" + Loc.T("ON - 1 on 1") + "</color>", 
						2 => Loc.T("Private channel:") + " <color=#4dd07a>" + Loc.T("ON - 1 on 1 between moderators") + "</color>", 
						_ => Loc.T("Private channel:") + " <color=#4dd07a>" + Loc.T("ON - all moderators") + "</color>", 
					};
					((TMP_Text)privateToggle.labelTMP).text = text;
				}
			}
		}

		private void UpdateOneOnOneRows()
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime >= nextOneOnOneRescanAt)
			{
				nextOneOnOneRescanAt = unscaledTime + 0.25f;
				RescanOneOnOneCandidates(unscaledTime);
			}
			bool flag = SemiFunc.IsMasterClient();
			SetVisibility(oneOnOneHintElem, flag && oneOnOneCount > 0);
			SetVisibility(oneOnOneAskHintElem, !flag && oneOnOneCount > 0);
			int num = (flag ? PrivateVoiceSync.TargetActor : 0);
			for (int i = 0; i < oneOnOneRows.Length; i++)
			{
				bool flag2 = i < oneOnOneCount;
				SetVisibility(oneOnOneRowElems[i], flag2);
				if (!flag2)
				{
					continue;
				}
				REPOButton val = oneOnOneRows[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				int num2 = oneOnOneCandidates[i];
				bool flag3 = num2 == num;
				bool flag4 = PrivateVoiceChannel.IsMemberOffline(num2);
				bool flag5 = false;
				if (num2 != oneOnOneRowActors[i])
				{
					oneOnOneRowActors[i] = num2;
					oneOnOneRowNames[i] = ResolveModName(num2);
					flag5 = true;
				}
				if (flag3 != oneOnOneRowIsTarget[i])
				{
					oneOnOneRowIsTarget[i] = flag3;
					flag5 = true;
				}
				if (flag4 != oneOnOneRowOffline[i])
				{
					oneOnOneRowOffline[i] = flag4;
					flag5 = true;
				}
				if (flag5)
				{
					string text = ((!flag) ? Loc.T("1 on 1: {0}", oneOnOneRowNames[i]) : (flag3 ? Loc.T("Stop 1 on 1: {0}", "<color=#4dd07a>" + oneOnOneRowNames[i] + "</color>") : Loc.T("1 on 1: {0}", oneOnOneRowNames[i])));
					if (flag4)
					{
						text = text + " <size=75%><color=#8a8a8a>" + Loc.T("(not on the channel)") + "</color></size>";
					}
					((TMP_Text)val.labelTMP).text = text;
				}
			}
		}

		private void RescanOneOnOneCandidates(float now)
		{
			int num = ((PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : (-1));
			int num2 = ((PhotonNetwork.MasterClient != null) ? PhotonNetwork.MasterClient.ActorNumber : (-1));
			bool flag = SemiFunc.IsMasterClient();
			int num3 = 0;
			int num4 = 0;
			if (!flag && num2 > 0 && num2 != num)
			{
				oneOnOneCandidates[num3++] = num2;
			}
			int num5 = num3;
			if (flag)
			{
				int[] array = AuthorizedActors.Snapshot();
				foreach (int num6 in array)
				{
					if (num6 != num)
					{
						if (num3 < oneOnOneCandidates.Length)
						{
							oneOnOneCandidates[num3++] = num6;
						}
						else
						{
							num4++;
						}
					}
				}
			}
			else
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom != null)
				{
					foreach (Player value in currentRoom.Players.Values)
					{
						if (value == null)
						{
							continue;
						}
						int actorNumber = value.ActorNumber;
						if (actorNumber != num && actorNumber != num2 && RoleResolver.ResolveByPlayer(value) == ModRole.Moderator)
						{
							if (num3 < oneOnOneCandidates.Length)
							{
								oneOnOneCandidates[num3++] = actorNumber;
							}
							else
							{
								num4++;
							}
						}
					}
				}
			}
			Array.Sort(oneOnOneCandidates, num5, num3 - num5);
			oneOnOneCount = num3;
			if (num4 > 0 && now >= nextOneOnOneOverflowWarnAt)
			{
				nextOneOnOneOverflowWarnAt = now + 30f;
				Plugin.Logger.LogWarning((object)$"[SharePermissions] Voice tab: {num4} moderator(s) beyond the {oneOnOneCandidates.Length} 1-on-1 rows are not shown");
			}
		}

		private void HideOneOnOneRows()
		{
			SetVisibility(oneOnOneHintElem, visible: false);
			SetVisibility(oneOnOneAskHintElem, visible: false);
			for (int i = 0; i < oneOnOneRowElems.Length; i++)
			{
				SetVisibility(oneOnOneRowElems[i], visible: false);
			}
		}

		private void OnOneOnOneClicked(int slot)
		{
			int num = oneOnOneRowActors[slot];
			if (num > 0)
			{
				if (SemiFunc.IsMasterClient())
				{
					ModerationActions.ToggleOneOnOnePrivateVoice(num);
				}
				else
				{
					ModerationActions.RequestOneOnOne(num);
				}
			}
		}

		private static string ResolveModName(int actor)
		{
			return ModerationActions.ActorNickname(actor);
		}

		private void UpdateOffHint()
		{
			if (!((Object)(object)offHint == (Object)null) && offHintCache.Length == 0)
			{
				string text = (offHintCache = "<size=75%><color=#6f7d88>" + Loc.T("Off - the host opens the private channel.") + "</color></size>");
				((TMP_Text)offHint.labelTMP).text = text;
				MenuUiHelpers.SizeWrappedLabel(offHint, text, 250f);
			}
		}

		private void UpdateStatus()
		{
			if (!((Object)(object)privateStatus == (Object)null))
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				PrivateVoiceClient instance = PrivateVoiceClient.Instance;
				bool flag = (Object)(object)instance != (Object)null && (Object)(object)instance.PrivateRecorder != (Object)null && instance.PrivateRecorder.TransmitEnabled;
				if (MicTee.Level > 0.005f)
				{
					privateTalking = true;
					privateTalkHoldUntil = realtimeSinceStartup + 0.18f;
				}
				else if (realtimeSinceStartup >= privateTalkHoldUntil)
				{
					privateTalking = false;
				}
				bool flag2 = privateTalking && flag && PrivateVoiceDriver.State == PrivateVoiceDriver.Health.Live;
				PrivateVoiceDriver.Health state = PrivateVoiceDriver.State;
				if (state != lastStatusState || flag != lastStatusTransmitting || flag2 != lastStatusShowTalkDot)
				{
					lastStatusState = state;
					lastStatusTransmitting = flag;
					lastStatusShowTalkDot = flag2;
					string text = state switch
					{
						PrivateVoiceDriver.Health.Live => flag ? ("<color=#4dd07a>" + Loc.T("live") + "</color>") : ("<color=#6f7d88>" + Loc.T("muted") + "</color>"), 
						PrivateVoiceDriver.Health.Listening => "<color=#4dd07a>" + Loc.T("listening") + "</color>", 
						PrivateVoiceDriver.Health.Provisioning => "<color=#e0b84d>" + Loc.T("connecting") + "</color>", 
						PrivateVoiceDriver.Health.Failed => "<color=#ef5350>" + Loc.T("failed") + "</color>", 
						_ => "<color=#6f7d88>" + Loc.T("off") + "</color>", 
					};
					string text2 = Loc.T("Private voice: {0}", text) + (flag2 ? "  <color=#d9e8a8>●</color>" : string.Empty);
					((TMP_Text)privateStatus.labelTMP).text = text2;
					MenuUiHelpers.SizeWrappedLabel(privateStatus, text2, 250f);
				}
			}
		}

		private static void OnPrivateVolumeChanged(int percent)
		{
			PrivateVoiceLocalPrefs.SetVolumePercent(percent);
		}

		private static void OnMicLiftChanged(int db)
		{
			PrivateVoiceLocalPrefs.SetMicLiftDb(db);
		}

		private REPOScrollViewElement? AddLabel(string richText)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			REPOLabel made = null;
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				REPOLabel val = MenuAPI.CreateREPOLabel(richText, sv, default(Vector2));
				((TMP_Text)val.labelTMP).richText = true;
				((TMP_Text)val.labelTMP).fontSize = 14f;
				MenuUiHelpers.SizeWrappedLabel(val, richText, 250f);
				made = val;
				return ((REPOElement)val).rectTransform;
			}, 0f, 6f);
			return Elem((Component?)(object)made);
		}

		private REPOButton? AddActionButton(string caption, Action onClick, out REPOScrollViewElement? elem)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			REPOButton made = null;
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_0057: Unknown result type (might be due to invalid IL or missing references)
				made = MenuAPI.CreateREPOButton(caption, onClick, sv, default(Vector2));
				((TMP_Text)made.labelTMP).richText = true;
				((TMP_Text)made.labelTMP).fontSize = 14f;
				MenuUiHelpers.PinButtonSize(made, new Vector2(250f, 24f));
				return ((REPOElement)made).rectTransform;
			}, 0f, 2f);
			elem = Elem((Component?)(object)made);
			return made;
		}

		private static REPOScrollViewElement? Elem(Component? made)
		{
			if (!((Object)(object)made == (Object)null))
			{
				return made.GetComponent<REPOScrollViewElement>();
			}
			return null;
		}

		private static void SetVisibility(REPOScrollViewElement? elem, bool visible)
		{
			if ((Object)(object)elem != (Object)null && elem.visibility != visible)
			{
				elem.visibility = visible;
			}
		}
	}
	internal sealed class GhostRowDriver : MonoBehaviour
	{
		private static readonly Color TalkNameColor = new Color(0.6f, 0.6f, 0.4f);

		internal static readonly Color IdleNameColor = new Color(0.2f, 0.2f, 0.2f);

		private const float ColorLerpSpeed = 10f;

		private const float TalkThreshold = 0.005f;

		private const float HoldTime = 0.18f;

		private const float WobbleDegreesPerLoudness = 200f;

		private const float FocusPushAlong = 12.5f;

		private const float FocusPushPerp = 6f;

		private const float EyeRestX = 50f;

		private const float EyeRestY = 25f;

		private const float EyeNudge = 10f;

		private const float EyeLerpSpeed = 10f;

		private const float CursorFocusOffsetX = 18f;

		private const float CursorFocusOffsetY = 15f;

		private int actorNumber;

		private MenuPlayerListed? row;

		private TextMeshProUGUI? playerName;

		private MenuPlayerHead? head;

		private RectTransform? rowRect;

		private RectTransform? headRect;

		private RectTransform? headTransform;

		private RectTransform? eyesTransform;

		private bool facingRight = true;

		private int listSpotPrev = -1;

		private float talkUntil;

		internal void Initialize(int actor)
		{
			actorNumber = actor;
		}

		private void Awake()
		{
			row = ((Component)this).GetComponent<MenuPlayerListed>();
			if ((Object)(object)row != (Object)null)
			{
				playerName = row.playerName;
				head = row.playerHead;
				rowRect = ((Component)row).GetComponent<RectTransform>();
				if ((Object)(object)head != (Object)null)
				{
					headRect = ((Component)head).GetComponent<RectTransform>();
				}
			}
		}

		private void Update()
		{
			float loud = ReadLoudness();
			bool talking = UpdateTalkHold(loud);
			PublishTalkState(talking);
			if ((Object)(object)row != (Object)null)
			{
				ApplyFacing(row.listSpot);
			}
			ApplyWobble(loud);
			UpdateNameColor(talking);
			SyncEyeContact();
		}

		private void SyncEyeContact()
		{
			try
			{
				SyncFocusPoint();
				SyncCursorFocus();
				SyncEyes();
			}
			catch
			{
			}
		}

		private float ReadLoudness()
		{
			return VoiceChatPatch.AmplitudeFor(actorNumber);
		}

		private bool UpdateTalkHold(float loud)
		{
			if (loud > 0.005f)
			{
				talkUntil = Time.time + 0.18f;
			}
			return Time.time < talkUntil;
		}

		private void PublishTalkState(bool talking)
		{
			if (!((Object)(object)head == (Object)null))
			{
				if (ShouldStampTalkStart(talking, head.isTalking))
				{
					head.startedTalkingAtTime = Time.time;
				}
				head.isTalking = talking;
			}
		}

		private void ApplyWobble(float loud)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)headTransform == (Object)null))
			{
				((Transform)headTransform).localEulerAngles = new Vector3(0f, 0f, facingRight ? (loud * 200f) : ((0f - loud) * 200f));
			}
		}

		private void UpdateNameColor(bool talking)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)playerName == (Object)null))
			{
				((Graphic)playerName).color = Color.Lerp(((Graphic)playerName).color, talking ? TalkNameColor : IdleNameColor, Time.deltaTime * 10f);
			}
		}

		private static bool ShouldStampTalkStart(bool talkingNow, bool wasTalking)
		{
			if (talkingNow)
			{
				return !wasTalking;
			}
			return false;
		}

		private static bool FacingIsRight(int listSpot)
		{
			return listSpot % 2 == 0;
		}

		private void ApplyFacing(int listSpot)
		{
			if (listSpot == listSpotPrev || (Object)(object)head == (Object)null)
			{
				return;
			}
			listSpotPrev = listSpot;
			facingRight = FacingIsRight(listSpot);
			Transform val = (facingRight ? head.headRight : head.headLeft);
			Transform val2 = (facingRight ? head.headLeft : head.headRight);
			if (!((Object)(object)val == (Object)null))
			{
				if ((Object)(object)val2 != (Object)null)
				{
					((Component)val2).gameObject.SetActive(false);
				}
				((Component)val).gameObject.SetActive(true);
				headTransform = ((Component)val).GetComponent<RectTransform>();
				Transform val3 = val.Find("Eyes");
				eyesTransform = (((Object)(object)val3 != (Object)null) ? ((Component)val3).GetComponent<RectTransform>() : null);
			}
		}

		private void SyncFocusPoint()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)head == (Object)null) && !((Object)(object)head.focusPoint == (Object)null) && !((Object)(object)headTransform == (Object)null) && !((Object)(object)rowRect == (Object)null) && !((Object)(object)headRect == (Object)null))
			{
				Vector3 val = ((Transform)rowRect).localPosition + ((Transform)headRect).localPosition + ((Transform)headTransform).localPosition * ((Transform)headRect).localScale.x;
				float z = ((Transform)headTransform).localEulerAngles.z;
				float num = (facingRight ? 12.5f : (-12.5f));
				val += new Vector3(MenuPlayerHead.LengthDirX(num, z), MenuPlayerHead.LengthDirY(num, z), 0f);
				val += new Vector3(MenuPlayerHead.LengthDirX(6f, z + 90f), MenuPlayerHead.LengthDirY(6f, z + 90f), 0f);
				((Transform)head.focusPoint).localPosition = val;
			}
		}

		private void SyncEyes()
		{
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)head == (Object)null || (Object)(object)eyesTransform == (Object)null || (Object)(object)head.focusPoint == (Object)null || (Object)(object)head.myFocusPoint == (Object)null)
			{
				return;
			}
			MenuPlayerHead val = null;
			float num = 0f;
			List<MenuPlayerHead> list = (((Object)(object)MenuManager.instance != (Object)null) ? MenuManager.instance.playerHeads : null);
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					MenuPlayerHead val2 = list[i];
					if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)head) && val2.isTalking && val2.startedTalkingAtTime > num)
					{
						num = val2.startedTalkingAtTime;
						val = val2;
					}
				}
			}
			Vector3 val3 = (((Object)(object)val != (Object)null && (Object)(object)val.focusPoint != (Object)null) ? ((Transform)val.focusPoint).localPosition : ((Transform)head.myFocusPoint).localPosition);
			Vector3 val4 = val3 - ((Transform)head.focusPoint).localPosition;
			val4.z = 0f;
			Vector3 val5 = new Vector3(facingRight ? 50f : (-50f), 25f, 0f) + ((Vector3)(ref val4)).normalized * 10f;
			((Transform)eyesTransform).localPosition = Vector3.Lerp(((Transform)eyesTransform).localPosition, val5, Time.deltaTime * 10f);
		}

		private void SyncCursorFocus()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Invalid comparison between Unknown and I4
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: 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_00d7: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)head == (Object)null) && !((Object)(object)head.myFocusPoint == (Object)null) && !((Object)(object)MenuManager.instance == (Object)null) && (int)MenuManager.instance.currentMenuPageIndex == 8 && !((Object)(object)MenuCursor.instance == (Object)null) && !((Object)(object)headRect == (Object)null) && !((Object)(object)((Transform)headRect).parent == (Object)null) && !((Object)(object)((Transform)headRect).parent.parent == (Object)null))
			{
				Vector3 val = ((Component)MenuCursor.instance).transform.localPosition - ((Transform)headRect).parent.parent.localPosition;
				((Transform)head.myFocusPoint).localPosition = new Vector3(val.x + 18f, val.y + 15f, 0f);
			}
		}
	}
	internal static class HangUpKey
	{
		internal static void Tick()
		{
			//IL_0005: 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_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			KeyCode value = ModConfig.Voice1on1HangUpKey.Value;
			if ((int)value != 0 && Input.GetKeyDown(value) && !SettingsPanelController.AnyKeyRowListening && !InvitePrompt.IsTyping() && ModerationActions.LocalMayHangUp)
			{
				ModerationActions.HangUp();
			}
		}
	}
	internal static class HeadArt
	{
		internal enum Part
		{
			Dome,
			Eyes,
			Jaw,
			Topper
		}

		internal const string TextureNamePrefix = "SP Head ";

		private const string ResourcePrefix = "SharePermissions.Heads.";

		private const string ResourceSuffix = ".png";

		private const string BodyTopperSuffix = "_topper_body";

		private const string TopperSuffix = "_topper";

		private const string FacingSuffix = "_facing";

		private static Dictionary<string, string>? index;

		private static readonly HashSet<string> BodyToppers = new HashSet<string>(StringComparer.Ordinal);

		private static readonly HashSet<string> FacingToppers = new HashSet<string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, Texture2D> Loaded = new Dictionary<string, Texture2D>(StringComparer.Ordinal);

		private static readonly Dictionary<string, float> TopperInk = new Dictionary<string, float>(StringComparer.Ordinal);

		private static readonly HashSet<string> Failed = new HashSet<string>(StringComparer.Ordinal);

		private static readonly Texture?[] Stock = (Texture?[])(object)new Texture[4];

		internal static void Init()
		{
			int count = Index().Count;
			if (count == 0)
			{
				Plugin.Logger.LogWarning((object)"Lobby heads: no art is embedded - every head stays stock");
			}
			else
			{
				Plugin.Logger.LogInfo((object)$"Lobby heads: {count} art parts embedded");
			}
		}

		internal static Texture2D? Get(string id, Part part)
		{
			if (!(id == "default"))
			{
				return Load(id, part);
			}
			return null;
		}

		internal static Texture2D? PreviewPart(string id, Part part)
		{
			object obj = Get(id, part);
			if (obj == null)
			{
				if (part != Part.Topper)
				{
					return Load("default", part);
				}
				obj = null;
			}
			return (Texture2D?)obj;
		}

		internal static bool TopperIsBody(string id)
		{
			Index();
			return BodyToppers.Contains(id);
		}

		internal static bool TopperFacesHead(string id)
		{
			Index();
			return FacingToppers.Contains(id);
		}

		internal static float TopperInkTop(string id)
		{
			if (!TopperInk.TryGetValue(id, out var value))
			{
				return 1f;
			}
			return value;
		}

		internal static bool IsModTexture(Texture? texture)
		{
			if ((Object)(object)texture != (Object)null)
			{
				return ((Object)texture).name.StartsWith("SP Head ", StringComparison.Ordinal);
			}
			return false;
		}

		internal static void RememberStock(Part part, Texture? texture)
		{
			if ((Object)(object)Stock[(int)part] == (Object)null && (Object)(object)texture != (Object)null && !IsModTexture(texture))
			{
				Stock[(int)part] = texture;
			}
		}

		internal static Texture? StockTexture(Part part)
		{
			return Stock[(int)part];
		}

		private static Texture2D? Load(string id, Part part)
		{
			string text = id + "_" + PartName(part);
			if (Loaded.TryGetValue(text, out Texture2D value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			if (Failed.Contains(text) || !Index().TryGetValue(text, out string value2))
			{
				return null;
			}
			bool flag = part == Part.Topper;
			Texture2D val = Decode(value2, flag ? id : null);
			if ((Object)(object)val == (Object)null)
			{
				Failed.Add(text);
				Plugin.Logger.LogError((object)("Lobby heads: could not decode " + value2 + " - that part stays stock"));
				return null;
			}
			Loaded[text] = val;
			return val;
		}

		private static Texture2D? Decode(string resource, string? measureInkFor)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			try
			{
				using Stream stream = typeof(HeadArt).Assembly.GetManifestResourceStream(resource);
				if (stream == null)
				{
					return null;
				}
				using MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true);
				if (!ImageConversion.LoadImage(val, memoryStream.ToArray(), measureInkFor == null))
				{
					Object.Destroy((Object)(object)val);
					return null;
				}
				if (measureInkFor != null)
				{
					TopperInk[measureInkFor] = InkTop(val);
					val.Apply(true, true);
				}
				((Object)val).name = "SP Head " + resource.Substring("SharePermissions.Heads.".Length);
				((Texture)val).wrapMode = (TextureWrapMode)1;
				((Texture)val).filterMode = (FilterMode)2;
				((Object)val).hideFlags = (HideFlags)61;
				return val;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Lobby heads: decoding " + resource + " threw: " + ex.Message));
				return null;
			}
		}

		private static float InkTop(Texture2D texture)
		{
			Color32[] pixels = texture.GetPixels32();
			int width = ((Texture)texture).width;
			int height = ((Texture)texture).height;
			for (int num = height - 1; num >= 0; num--)
			{
				int num2 = num * width;
				for (int i = 0; i < width; i++)
				{
					if (pixels[num2 + i].a >= 32)
					{
						return ((float)num + 1f) / (float)height;
					}
				}
			}
			return 0f;
		}

		private static Dictionary<string, string> Index()
		{
			if (index != null)
			{
				return index;
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			string[] manifestResourceNames = typeof(HeadArt).Assembly.GetManifestResourceNames();
			foreach (string text in manifestResourceNames)
			{
				if (text.StartsWith("SharePermissions.Heads.", StringComparison.Ordinal) && text.EndsWith(".png", StringComparison.Ordinal))
				{
					string text2 = text.Substring("SharePermissions.Heads.".Length, text.Length - "SharePermissions.Heads.".Length - ".png".Length);
					bool flag = text2.EndsWith("_facing", StringComparison.Ordinal);
					if (flag)
					{
						text2 = text2.Substring(0, text2.Length - "_facing".Length);
					}
					string text3 = null;
					if (text2.EndsWith("_topper_body", StringComparison.Ordinal))
					{
						text3 = text2.Substring(0, text2.Length - "_topper_body".Length);
						BodyToppers.Add(text3);
						text2 = text3 + "_" + PartName(Part.Topper);
					}
					else if (text2.EndsWith("_topper", StringComparison.Ordinal))
					{
						text3 = text2.Substring(0, text2.Length - "_topper".Length);
					}
					if (flag && text3 != null)
					{
						FacingToppers.Add(text3);
					}
					dictionary[text2] = text;
				}
			}
			index = dictionary;
			return index;
		}

		private static string PartName(Part part)
		{
			return part switch
			{
				Part.Dome => "dome", 
				Part.Eyes => "eyes", 
				Part.Jaw => "jaw", 
				_ => "topper", 
			};
		}
	}
	internal sealed class HeadMarkerComponent : MonoBehaviour
	{
		private const float AppearSeconds = 0.35f;

		private static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");

		private float spinDegreesPerSecond;

		private float pulsePeriodSeconds;

		private float bobAmplitude;

		private float bobPeriodSeconds;

		private Material? pulseMaterial;

		private Color baseColor;

		private bool emissionAvailable;

		private Vector3 basePosition;

		private float appearElapsed;

		private const float GlintSeconds = 0.25f;

		private Material? glintMaterial;

		private float glintPeriodSeconds;

		private float nextGlintAt;

		private float glintStartedAt = float.NegativeInfinity;

		private bool glintAvailable;

		private bool glintWasActive;

		private Color glintBaseEmission;

		private Material?[]? ownedMaterials;

		private bool materialsDestroyed;

		internal void Initialize(Material? material, Color color, float spinDps, float pulsePeriod, float bobAmp, float bobPeriod, Material? glintMat = null, float glintPeriod = 0f)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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)
			//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)
			pulseMaterial = material;
			baseColor = color;
			spinDegreesPerSecond = spinDps;
			pulsePeriodSeconds = pulsePeriod;
			bobAmplitude = bobAmp;
			bobPeriodSeconds = bobPeriod;
			basePosition = ((Component)this).transform.localPosition;
			if ((Object)(object)material != (Object)null && material.HasProperty("_EmissionColor"))
			{
				material.EnableKeyword("_EMISSION");
				emissionAvailable = true;
			}
			glintMaterial = glintMat;
			glintPeriodSeconds = glintPeriod;
			if ((Object)(object)glintMat != (Object)null && glintPeriod > 0f && glintMat.HasProperty("_EmissionColor"))
			{
				glintMat.EnableKeyword("_EMISSION");
				glintBaseEmission = glintMat.GetColor("_EmissionColor");
				glintAvailable = true;
				nextGlintAt = Time.time + glintPeriod * Random.Range(0.7f, 1.3f);
			}
		}

		internal void TakeOwnership(params Material?[] mats)
		{
			ownedMaterials = mats;
		}

		internal void DestroyOwnedMaterials()
		{
			if (materialsDestroyed || ownedMaterials == null)
			{
				return;
			}
			materialsDestroyed = true;
			Material[] array = ownedMaterials;
			foreach (Material val in array)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private void OnDestroy()
		{
			DestroyOwnedMaterials();
		}

		private void OnEnable()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			appearElapsed = 0f;
			((Component)this).transform.localScale = Vector3.zero;
			if (glintAvailable)
			{
				nextGlintAt = Time.time + glintPeriodSeconds * Random.Range(0.7f, 1.3f);
			}
		}

		private void Update()
		{
			//IL_003d: 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_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)