Decompiled source of Big Lobbies v1.0.0

BepInEx/plugins/BigLobbies.dll

Decompiled 10 hours ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP.Hook;
using Dissonance;
using Dissonance.Audio.Capture;
using Dissonance.Audio.Codecs;
using Dissonance.Audio.Codecs.Opus;
using Dissonance.Audio.Playback;
using Dissonance.Config;
using Dissonance.Integrations.MirrorIgnorance;
using Dissonance.Networking;
using Dissonance.Networking.Client;
using Dissonance.Threading;
using Epic.OnlineServices;
using Epic.OnlineServices.Lobby;
using EpicTransport;
using EventVoice;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Collections.ObjectModel;
using Microsoft.CodeAnalysis;
using Mirror;
using PlayEveryWare.EpicOnlineServices;
using Steamworks;
using UnityEngine;
using UnityEngine.Audio;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("AdamMady")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+960e9384c8e2209a4200b1001dc1bab9375f8e4a")]
[assembly: AssemblyProduct("Big Lobbies")]
[assembly: AssemblyTitle("BigLobbies")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace EventVoice
{
	internal static class VoicePacket
	{
		internal static bool TryReadType(ArraySegment<byte> packet, out MessageTypes type)
		{
			type = (MessageTypes)0;
			if (!Valid(packet) || packet.Count < 3)
			{
				return false;
			}
			Il2CppArrayBase<byte> array = packet.Array;
			int offset = packet.Offset;
			if (array[offset] != 139 || array[offset + 1] != 199)
			{
				return false;
			}
			type = (MessageTypes)array[offset + 2];
			return true;
		}

		internal static bool TryRelayPayload(ArraySegment<byte> packet, out ArraySegment<byte> payload)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			payload = null;
			if (!TryReadType(packet, out var type) || ((int)type != 7 && (int)type != 8) || packet.Count < 10)
			{
				return false;
			}
			Il2CppArrayBase<byte> array = packet.Array;
			int offset = packet.Offset;
			int num = 8 + array[offset + 7] * 2;
			if (num > packet.Count - 2)
			{
				return false;
			}
			int num2 = (array[offset + num] << 8) | array[offset + num + 1];
			int num3 = num + 2;
			if (num2 < 3 || num2 > packet.Count - num3)
			{
				return false;
			}
			payload = new ArraySegment<byte>(array, offset + num3, num2);
			return true;
		}

		private static bool Valid(ArraySegment<byte> packet)
		{
			if (packet != null && packet.Array != null && packet.Offset >= 0 && packet.Count >= 0)
			{
				return packet.Offset <= packet.Array.Length - packet.Count;
			}
			return false;
		}
	}
}
namespace Bigger_Walk
{
	internal static class EventLoad
	{
		private static bool hosting;

		private static int previousRate;

		private static int appliedRate;

		internal static void Tick()
		{
			if (!NetworkServer.active)
			{
				if (hosting && NetworkServer.tickRate == appliedRate)
				{
					NetworkServer.tickRate = previousRate;
				}
				hosting = false;
				return;
			}
			if (!hosting)
			{
				previousRate = NetworkServer.tickRate;
				appliedRate = previousRate;
				hosting = true;
			}
			int updateCapValue = Plugin.UpdateCapValue;
			int num = ((updateCapValue == 0) ? previousRate : ((previousRate > 0) ? Math.Min(previousRate, updateCapValue) : updateCapValue));
			if (appliedRate != num)
			{
				NetworkServer.tickRate = (appliedRate = num);
				Plugin.Log.LogInfo((object)("[LOAD] World update rate=" + num + "Hz; game default=" + previousRate + "Hz."));
			}
		}
	}
	internal static class HostPlayback
	{
		private sealed class Slot
		{
			internal Bank Bank;

			internal SoundCue Cue;

			internal AudioBus Bus;

			internal AudioMixerGroup Group;

			internal int Index;

			internal bool Used;
		}

		private sealed class Bank
		{
			internal AudioMixer Mixer;

			internal readonly List<Slot> Slots = new List<Slot>();

			internal readonly Dictionary<string, float> Globals = new Dictionary<string, float>(StringComparer.Ordinal);

			internal void Dispose()
			{
				foreach (Slot slot in Slots)
				{
					if ((Object)(object)slot.Cue != (Object)null)
					{
						Object.Destroy((Object)(object)slot.Cue);
					}
					if ((Object)(object)slot.Group != (Object)null)
					{
						AudioBus.S_AllMixerGroups?.Remove(slot.Group);
					}
					if ((Object)(object)slot.Bus != (Object)null)
					{
						Object.Destroy((Object)(object)slot.Bus);
					}
				}
				if ((Object)(object)Mixer != (Object)null)
				{
					Object.Destroy((Object)(object)Mixer);
				}
			}
		}

		private static readonly string[] globals = new string[14]
		{
			"VoicePitch", "Room", "RoomHF", "RoomLF", "DecayTime", "DecayHFRatio", "Reflections", "ReflectDelay", "Reverb", "ReverbDelay",
			"HFReference", "LFReference", "Diffusion", "Density"
		};

		private static readonly List<Bank> banks = new List<Bank>();

		private static readonly Dictionary<IntPtr, Slot> borrowed = new Dictionary<IntPtr, Slot>();

		private static AudioMixer template;

		private static bool failed;

		private static bool closing;

		private static int lastFrame = -1;

		private static int oldRealVoices;

		private static int reservedRealVoices;

		private static int oldVirtualVoices;

		private static int reservedVirtualVoices;

		internal static int Count { get; private set; }

		internal static string Status
		{
			get
			{
				if (!failed)
				{
					return "native host playback: " + Count + ", extra cue banks: " + banks.Count;
				}
				return "host overflow unavailable (see log)";
			}
		}

		internal static void ReserveVoices()
		{
			//IL_002c: 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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_0081: 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_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			closing = false;
			if (borrowed.Count == 0 && banks.Count == 0)
			{
				failed = false;
			}
			if (reservedRealVoices != 0)
			{
				return;
			}
			AudioConfiguration configuration = AudioSettings.GetConfiguration();
			oldRealVoices = configuration.numRealVoices;
			oldVirtualVoices = configuration.numVirtualVoices;
			int num = Math.Clamp(MaxConnectionsPatch.ConfiguredMaxConnections, 1, 128) + 32;
			if (configuration.numRealVoices >= num)
			{
				return;
			}
			configuration.numRealVoices = num;
			configuration.numVirtualVoices = Math.Max(configuration.numVirtualVoices, num);
			if (AudioSettings.Reset(configuration))
			{
				configuration = AudioSettings.GetConfiguration();
				reservedRealVoices = configuration.numRealVoices;
				reservedVirtualVoices = configuration.numVirtualVoices;
				Plugin.Log.LogInfo((object)("[VOICE] Host Unity audio budget: " + reservedRealVoices + " real voices (includes 32 for world audio)."));
				if (reservedRealVoices < num)
				{
					Plugin.Log.LogWarning((object)"[VOICE] Unity clamped host audio budget; simultaneous playback capacity needs checking.");
				}
			}
			else
			{
				Plugin.Log.LogWarning((object)"[VOICE] Unity rejected larger host audio budget.");
			}
		}

		internal static bool TryTake(PlayerVoicePlaybackControl control)
		{
			if (!VoiceRoster.Enabled || (Object)(object)control == (Object)null || failed || closing || (Object)(object)control._cue != (Object)null)
			{
				return false;
			}
			Stack<SoundCue> cueStack = PlayerVoicePlaybackControl.cueStack;
			if (cueStack == null || cueStack.Count > 0)
			{
				return false;
			}
			if (borrowed.ContainsKey(((Il2CppObjectBase)control).Pointer))
			{
				return false;
			}
			try
			{
				Slot slot = null;
				foreach (Bank bank2 in banks)
				{
					foreach (Slot slot2 in bank2.Slots)
					{
						if (!slot2.Used)
						{
							slot = slot2;
							break;
						}
					}
					if (slot != null)
					{
						break;
					}
				}
				if (slot == null)
				{
					int num = (Math.Clamp(MaxConnectionsPatch.ConfiguredMaxConnections, 1, 128) + 12 - 1) / 12;
					if (banks.Count >= num)
					{
						return false;
					}
					Bank bank = CreateBank();
					banks.Add(bank);
					slot = bank.Slots[0];
					Plugin.Log.LogInfo((object)("[VOICE] Independent native host cue bank " + banks.Count + " created: " + bank.Slots.Count + " slots. Multiplayer audio validation required."));
				}
				slot.Used = true;
				borrowed.Add(((Il2CppObjectBase)control).Pointer, slot);
				control._cue = slot.Cue;
				control._index = slot.Index;
				control._mixer = slot.Bank.Mixer;
				control._cueUnusable = false;
				control._reportedNoCue = false;
				return true;
			}
			catch (Exception ex)
			{
				failed = true;
				Plugin.Log.LogError((object)("[VOICE] Cannot extend native host cue capacity; keeping vanilla playback instead of approximating audio: " + ex));
				return false;
			}
		}

		private static Bank CreateBank()
		{
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0381: Unknown result type (might be due to invalid IL or missing references)
			//IL_0388: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
			GlobalAudioEffects instance = GlobalAudioEffects.Instance;
			Il2CppReferenceArray<SoundCue> val = ((instance != null) ? instance.VoiceCues : null);
			if (val == null || ((Il2CppArrayBase<SoundCue>)(object)val).Length == 0)
			{
				throw new InvalidOperationException("Native voice cues missing.");
			}
			SoundCue obj = ((Il2CppArrayBase<SoundCue>)(object)val)[0];
			object obj2;
			if (obj == null)
			{
				obj2 = null;
			}
			else
			{
				AudioBus bus = obj.Bus;
				if (bus == null)
				{
					obj2 = null;
				}
				else
				{
					AudioMixerGroup mixerGroup = bus.MixerGroup;
					obj2 = ((mixerGroup != null) ? mixerGroup.audioMixer : null);
				}
			}
			AudioMixer val2 = (AudioMixer)obj2;
			if ((Object)(object)val2 == (Object)null)
			{
				throw new InvalidOperationException("Native voice mixer missing.");
			}
			if ((Object)(object)template != (Object)null && ((Il2CppObjectBase)template).Pointer != ((Il2CppObjectBase)val2).Pointer)
			{
				throw new InvalidOperationException("Voice mixer changed while host banks exist.");
			}
			Bank bank = new Bank();
			try
			{
				bank.Mixer = Object.Instantiate<AudioMixer>(val2);
				if ((Object)(object)bank.Mixer == (Object)null || ((Il2CppObjectBase)bank.Mixer).Pointer == ((Il2CppObjectBase)val2).Pointer)
				{
					throw new InvalidOperationException("Unity did not clone the voice mixer.");
				}
				((Object)bank.Mixer).name = "bigger walk+talk native voice bank " + (banks.Count + 1);
				AudioMixer mixer = bank.Mixer;
				((Object)mixer).hideFlags = (HideFlags)(((Object)mixer).hideFlags | 0x20);
				bank.Mixer.outputAudioMixerGroup = val2.outputAudioMixerGroup;
				HashSet<int> hashSet = new HashSet<int>();
				float num2 = default(float);
				for (int i = 0; i < ((Il2CppArrayBase<SoundCue>)(object)val).Length; i++)
				{
					SoundCue val3 = ((Il2CppArrayBase<SoundCue>)(object)val)[i];
					AudioBus val4 = ((val3 != null) ? val3.Bus : null);
					AudioMixerGroup val5 = ((val4 != null) ? val4.MixerGroup : null);
					if ((Object)(object)val5 == (Object)null || (Object)(object)val5.audioMixer == (Object)null || ((Il2CppObjectBase)val5.audioMixer).Pointer != ((Il2CppObjectBase)val2).Pointer || !int.TryParse(((Object)val5).name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
					{
						throw new InvalidOperationException("Unexpected native voice group layout: cue=" + ((val3 != null) ? ((Object)val3).name : null) + ", group=" + ((val5 != null) ? ((Object)val5).name : null));
					}
					int num = result - 1;
					if (num < 0 || num >= ((Il2CppArrayBase<string>)(object)PlayerVoicePlaybackControl.PARAM_DRY).Length || !hashSet.Add(num))
					{
						throw new InvalidOperationException("Invalid or duplicate native voice channel.");
					}
					AudioMixerGroup val6 = null;
					foreach (AudioMixerGroup item in (Il2CppArrayBase<AudioMixerGroup>)(object)bank.Mixer.FindMatchingGroups(((Object)val5).name))
					{
						if (((Object)item).name == ((Object)val5).name && ((Il2CppObjectBase)item.audioMixer).Pointer == ((Il2CppObjectBase)bank.Mixer).Pointer && ((Il2CppObjectBase)item).Pointer != ((Il2CppObjectBase)val5).Pointer)
						{
							val6 = item;
							break;
						}
					}
					if ((Object)(object)val6 == (Object)null)
					{
						throw new InvalidOperationException("Mixer clone shares/misses original channel " + ((Object)val5).name + ".");
					}
					string[] array = new string[4]
					{
						((Il2CppArrayBase<string>)(object)PlayerVoicePlaybackControl.PARAM_DRY)[num],
						((Il2CppArrayBase<string>)(object)PlayerVoicePlaybackControl.PARAM_HIGH)[num],
						((Il2CppArrayBase<string>)(object)PlayerVoicePlaybackControl.PARAM_REVERB_FALL_WET)[num],
						((Il2CppArrayBase<string>)(object)PlayerVoicePlaybackControl.PARAM_REVERB_BOOST_WET)[num]
					};
					foreach (string text in array)
					{
						if (!bank.Mixer.GetFloat(text, ref num2))
						{
							throw new InvalidOperationException("Mixer clone lacks parameter " + text + ".");
						}
					}
					Slot slot = new Slot
					{
						Bank = bank,
						Index = num,
						Group = val6
					};
					bank.Slots.Add(slot);
					slot.Bus = Object.Instantiate<AudioBus>(val4);
					AudioBus bus2 = slot.Bus;
					((Object)bus2).hideFlags = (HideFlags)(((Object)bus2).hideFlags | 0x20);
					slot.Bus.MixerGroup = val6;
					AudioBus.S_AllMixerGroups?.Add(val6);
					slot.Cue = Object.Instantiate<SoundCue>(val3);
					SoundCue cue = slot.Cue;
					((Object)cue).hideFlags = (HideFlags)(((Object)cue).hideFlags | 0x20);
					slot.Cue.Bus = slot.Bus;
				}
				template = val2;
				SyncBank(bank);
				return bank;
			}
			catch
			{
				bank.Dispose();
				throw;
			}
		}

		private static void SyncBank(Bank bank)
		{
			string[] array = globals;
			float num = default(float);
			foreach (string text in array)
			{
				if (template.GetFloat(text, ref num) && (!bank.Globals.TryGetValue(text, out var value) || value != num))
				{
					if (!bank.Mixer.SetFloat(text, num))
					{
						throw new InvalidOperationException("Cannot sync native voice effect " + text + ".");
					}
					bank.Globals[text] = num;
				}
			}
		}

		internal static void SyncEffects()
		{
			if (!VoiceRoster.Enabled || (Object)(object)template == (Object)null)
			{
				return;
			}
			string[] array = globals;
			float num = default(float);
			foreach (string text in array)
			{
				if (!template.GetFloat(text, ref num))
				{
					continue;
				}
				foreach (Bank bank in banks)
				{
					if (!bank.Globals.TryGetValue(text, out var value) || value != num)
					{
						if (!bank.Mixer.SetFloat(text, num))
						{
							throw new InvalidOperationException("Cannot sync native voice effect " + text + ".");
						}
						bank.Globals[text] = num;
					}
				}
			}
		}

		internal static void BeforeDisable(PlayerVoicePlaybackControl control)
		{
			if ((Object)(object)control != (Object)null && borrowed.ContainsKey(((Il2CppObjectBase)control).Pointer))
			{
				control._cue = null;
				control._mixer = null;
			}
		}

		internal static void AfterDisable(PlayerVoicePlaybackControl control)
		{
			if ((Object)(object)control != (Object)null && borrowed.Remove(((Il2CppObjectBase)control).Pointer, out var value))
			{
				value.Used = false;
			}
			if (closing && borrowed.Count == 0)
			{
				DisposeBanks();
			}
		}

		private static void DisposeBanks()
		{
			foreach (Bank bank in banks)
			{
				bank.Dispose();
			}
			banks.Clear();
			template = null;
			failed = false;
		}

		internal static void Discard(PlayerNetworking player)
		{
			List<PlayerVoicePlaybackControl> controls = PlayerVoicePlaybackControl.controls;
			if (controls == null)
			{
				return;
			}
			for (int i = 0; i < controls.Count; i++)
			{
				PlayerVoicePlaybackControl val = controls[i];
				IntPtr? obj;
				if (val == null)
				{
					obj = null;
				}
				else
				{
					PlayerCharacter playerCharacter = val.playerCharacter;
					if (playerCharacter == null)
					{
						obj = null;
					}
					else
					{
						PlayerNetworking playerNetworking = playerCharacter.playerNetworking;
						obj = ((playerNetworking != null) ? new IntPtr?(((Il2CppObjectBase)playerNetworking).Pointer) : ((IntPtr?)null));
					}
				}
				if (obj == ((Il2CppObjectBase)player).Pointer)
				{
					SamplePlaybackComponent dissonanceSampleProvider = val.dissonanceSampleProvider;
					if (dissonanceSampleProvider != null)
					{
						dissonanceSampleProvider.DiscardSession();
					}
				}
			}
		}

		internal static void Tick()
		{
			if (lastFrame == Time.frameCount)
			{
				return;
			}
			lastFrame = Time.frameCount;
			Count = 0;
			List<PlayerVoicePlaybackControl> controls = PlayerVoicePlaybackControl.controls;
			if (controls == null)
			{
				return;
			}
			for (int i = 0; i < controls.Count; i++)
			{
				PlayerVoicePlaybackControl val = controls[i];
				if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled)
				{
					if (val._setUp && (Object)(object)val._sourceController != (Object)null)
					{
						Count++;
					}
					SamplePlaybackComponent dissonanceSampleProvider = val.dissonanceSampleProvider;
					if ((Object)(object)dissonanceSampleProvider != (Object)null)
					{
						VoicePlayback voicePlayback = val.voicePlayback;
						VoiceRoster.ObserveVoice((voicePlayback != null) ? ((BaseVoicePlayback)voicePlayback).PlayerName : null, dissonanceSampleProvider.HasActiveSession ? dissonanceSampleProvider.ARV : 0f);
					}
				}
			}
		}

		internal static void Clear()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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)
			closing = true;
			Count = 0;
			lastFrame = -1;
			if (borrowed.Count == 0)
			{
				DisposeBanks();
			}
			if (reservedRealVoices != 0)
			{
				AudioConfiguration configuration = AudioSettings.GetConfiguration();
				if (configuration.numRealVoices == reservedRealVoices && configuration.numVirtualVoices == reservedVirtualVoices)
				{
					configuration.numRealVoices = oldRealVoices;
					configuration.numVirtualVoices = oldVirtualVoices;
					AudioSettings.Reset(configuration);
				}
				reservedRealVoices = (reservedVirtualVoices = 0);
			}
		}
	}
	internal static class EosCapacityPatch
	{
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void NativeCreate(IntPtr lobby, IntPtr options, IntPtr clientData, IntPtr completion, IntPtr method);

		private static INativeDetour createHook;

		private static NativeCreate originalCreate;

		private static readonly NativeCreate createCallback = Create;

		internal static void Install(Harmony harmony)
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Expected O, but got Unknown
			//IL_0106: Expected O, but got Unknown
			if (!OperatingSystem.IsWindows() || IntPtr.Size != 8)
			{
				throw new PlatformNotSupportedException("EOS capacity hook requires Windows x64.");
			}
			RuntimeHelpers.RunClassConstructor(typeof(CreateLobbyOptions).TypeHandle);
			RuntimeHelpers.RunClassConstructor(typeof(LobbyInterface).TypeHandle);
			if (IL2CPP.il2cpp_field_get_offset(IL2CPP.GetIl2CppField(Il2CppClassPointerStore<CreateLobbyOptions>.NativeClassPtr, "<MaxLobbyMembers>k__BackingField")) - 2 * IntPtr.Size != 8)
			{
				throw new InvalidOperationException("EOS lobby creation options layout changed.");
			}
			FieldInfo? field = typeof(LobbyInterface).GetField("NativeMethodInfoPtr_CreateLobby_Public_Void_byref_CreateLobbyOptions_Object_OnCreateLobbyCallback_0", BindingFlags.Static | BindingFlags.NonPublic);
			if (field == null)
			{
				throw new MissingFieldException("EOS CreateLobby native method info not found.");
			}
			createHook = INativeDetour.CreateAndApply<NativeCreate>(Marshal.ReadIntPtr((IntPtr)field.GetValue(null)), createCallback, ref originalCreate);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(LobbyModification), "SetMaxMembers", (Type[])null, (Type[])null), new HarmonyMethod(typeof(EosCapacityPatch), "MaxMembersPrefix", (Type[])null), new HarmonyMethod(typeof(EosCapacityPatch), "MaxMembersPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Plugin.Log.LogInfo((object)("[CAPACITY] EOS SDK creation/update hooks installed; configured player limit=" + MaxConnectionsPatch.ConfiguredMaxConnections));
		}

		private static void Create(IntPtr lobby, IntPtr options, IntPtr clientData, IntPtr completion, IntPtr method)
		{
			try
			{
				if (NetworkServer.active && options != IntPtr.Zero)
				{
					int num = Marshal.ReadInt32(options, 8);
					int configuredMaxConnections = MaxConnectionsPatch.ConfiguredMaxConnections;
					Marshal.WriteInt32(options, 8, configuredMaxConnections);
					Plugin.Log.LogInfo((object)("[CAPACITY] EOS CreateLobby MaxLobbyMembers " + num + " -> " + configuredMaxConnections));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[CAPACITY] Cannot update lobby creation options: " + ex));
			}
			originalCreate(lobby, options, clientData, completion, method);
		}

		private static void MaxMembersPrefix(ref LobbyModificationSetMaxMembersOptions options)
		{
			if (NetworkServer.active)
			{
				((LobbyModificationSetMaxMembersOptions)(ref options)).MaxMembers = MaxConnectionsPatch.ConfiguredMaxConnectionsUInt;
			}
		}

		private unsafe static void MaxMembersPostfix(Result __result)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			if (NetworkServer.active && (int)__result != 0)
			{
				Plugin.Log.LogError((object)("[CAPACITY] EOS SetMaxMembers rejected: " + ((object)(*(Result*)(&__result))/*cast due to .constrained prefix*/).ToString()));
			}
		}
	}
	public static class EosLobbyManagerPatch
	{
		public static void FeedUpdateAttributesPostfix(object update)
		{
			MaxConnectionsPatch.ApplyToLobbyModification(update);
		}
	}
	public static class EosLobbyPatch
	{
		public static void CreateLobbyPrefix(ref uint maxConnections)
		{
			maxConnections = MaxConnectionsPatch.ConfiguredMaxConnectionsUInt;
		}
	}
	public static class EpicTransportServerPatch
	{
		public static void CreateServerPrefix(ref int maxConnections)
		{
			maxConnections = MaxConnectionsPatch.ConfiguredMaxConnections;
		}

		public static void CreateServerPostfix(object __result)
		{
			MaxConnectionsPatch.ActiveTransportServer = __result;
			MaxConnectionsPatch.ApplyToEosServer(__result);
			Server val = (Server)((__result is Server) ? __result : null);
			if (val != null)
			{
				Plugin.Log.LogInfo((object)("[CAPACITY] Transport player limit=" + val.maxConnections));
			}
		}
	}
	public static class HouseNetworkManagerPatch
	{
		public static void OnStartServerPrefix(HouseNetworkManager __instance)
		{
			MaxConnectionsPatch.ApplyToNetworkManager((NetworkManager)(object)__instance);
		}

		public static void OnStartHostPrefix(HouseNetworkManager __instance)
		{
			MaxConnectionsPatch.ApplyToNetworkManager((NetworkManager)(object)__instance);
		}

		public static void CreateHostEosLobbyAsyncPrefix(HouseNetworkManager __instance)
		{
			MaxConnectionsPatch.ApplyToNetworkManager((NetworkManager)(object)__instance);
		}
	}
	public static class MaxConnectionsPatch
	{
		private struct NativeUpdateOptions
		{
			public int ApiVersion;

			public IntPtr ModificationHandle;
		}

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void UpdateCallback(IntPtr info);

		private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static Type lobbyModificationSetMaxMembersOptionsType;

		private static MethodInfo setMaxMembersMethod;

		internal static object ActiveTransportServer;

		private static bool updatingLobby;

		private static string appliedLobby;

		private static int appliedLimit;

		private static float nextLobbyAttempt;

		private static object configuredTransport;

		private static int localLimit;

		private static string updatingLobbyId;

		private static int updatingLimit;

		private static readonly UpdateCallback LobbyUpdated = OnLobbyUpdated;

		public static int ConfiguredMaxConnections => Math.Max(1, Plugin.MaxConnections.Value);

		public static uint ConfiguredMaxConnectionsUInt => (uint)ConfiguredMaxConnections;

		[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
		private static extern void EOS_Lobby_UpdateLobby(IntPtr handle, ref NativeUpdateOptions options, IntPtr clientData, UpdateCallback callback);

		private unsafe static void OnLobbyUpdated(IntPtr info)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Result val = (Result)Marshal.ReadInt32(info);
				if ((int)val == 0)
				{
					appliedLobby = updatingLobbyId;
					appliedLimit = updatingLimit;
					Plugin.Log.LogInfo((object)("[CAPACITY] Active lobby limit=" + appliedLimit));
				}
				else
				{
					nextLobbyAttempt = Time.unscaledTime + 5f;
					Plugin.Log.LogWarning((object)("[CAPACITY] Active lobby update failed: " + ((object)(*(Result*)(&val))/*cast due to .constrained prefix*/).ToString()));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[CAPACITY] Lobby update callback failed: " + ex.Message));
			}
			finally
			{
				updatingLobby = false;
			}
		}

		internal unsafe static void ApplyLive()
		{
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: 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_0105: Expected O, but got Unknown
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: 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)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: 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_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: 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 (!NetworkServer.active)
			{
				appliedLobby = null;
				appliedLimit = 0;
				return;
			}
			int configuredMaxConnections = ConfiguredMaxConnections;
			if (localLimit != configuredMaxConnections || configuredTransport != ActiveTransportServer || NetworkServer.maxConnections != configuredMaxConnections)
			{
				ApplyToNetworkManager(NetworkManager.singleton);
				NetworkServer.maxConnections = configuredMaxConnections;
				ApplyToEosServer(ActiveTransportServer);
				configuredTransport = ActiveTransportServer;
				localLimit = configuredMaxConnections;
			}
			EOSPrivateLobbyManager instance = EOSPrivateLobbyManager.Instance;
			string text = ((instance != null) ? instance.currentLobbyID : null);
			if ((Object)(object)instance == (Object)null || !instance.IsHostingLobby || string.IsNullOrEmpty(text) || updatingLobby || (appliedLobby == text && appliedLimit == configuredMaxConnections) || Time.unscaledTime < nextLobbyAttempt)
			{
				return;
			}
			EOSSingleton instance2 = EOSManager.Instance;
			LobbyInterface val = ((instance2 != null) ? instance2.GetEOSLobbyInterface() : null);
			ProductUserId val2 = ((instance2 != null) ? instance2.GetProductUserId() : null);
			if ((Handle)(object)val == (Handle)null || (Handle)(object)val2 == (Handle)null)
			{
				return;
			}
			UpdateLobbyModificationOptions val3 = new UpdateLobbyModificationOptions
			{
				LobbyId = Utf8String.op_Implicit(text),
				LocalUserId = val2
			};
			LobbyModification val5 = default(LobbyModification);
			Result val4 = val.UpdateLobbyModification(ref val3, ref val5);
			if ((int)val4 != 0 || (Handle)(object)val5 == (Handle)null)
			{
				nextLobbyAttempt = Time.unscaledTime + 5f;
				Plugin.Log.LogWarning((object)("[CAPACITY] Cannot update active lobby: " + ((object)(*(Result*)(&val4))/*cast due to .constrained prefix*/).ToString()));
				return;
			}
			try
			{
				LobbyModificationSetMaxMembersOptions val6 = default(LobbyModificationSetMaxMembersOptions);
				((LobbyModificationSetMaxMembersOptions)(ref val6)).MaxMembers = (uint)configuredMaxConnections;
				LobbyModificationSetMaxMembersOptions val7 = val6;
				val4 = val5.SetMaxMembers(ref val7);
				if ((int)val4 != 0)
				{
					nextLobbyAttempt = Time.unscaledTime + 5f;
					Plugin.Log.LogWarning((object)("[CAPACITY] Cannot set active lobby limit: " + ((object)(*(Result*)(&val4))/*cast due to .constrained prefix*/).ToString()));
					return;
				}
				NativeUpdateOptions options = new NativeUpdateOptions
				{
					ApiVersion = 1,
					ModificationHandle = ((Handle)val5).InnerHandle
				};
				updatingLobbyId = text;
				updatingLimit = configuredMaxConnections;
				updatingLobby = true;
				EOS_Lobby_UpdateLobby(((Handle)val).InnerHandle, ref options, IntPtr.Zero, LobbyUpdated);
			}
			catch
			{
				updatingLobby = false;
				nextLobbyAttempt = Time.unscaledTime + 5f;
				throw;
			}
			finally
			{
				val5.Release();
			}
		}

		public static void ApplyToNetworkManager(NetworkManager networkManager)
		{
			if ((Object)(object)networkManager != (Object)null)
			{
				networkManager.maxConnections = ConfiguredMaxConnections;
			}
		}

		public static void ApplyToEosServer(object server)
		{
			try
			{
				PropertyInfo propertyInfo = server?.GetType().GetProperty("maxConnections", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if ((object)propertyInfo != null && propertyInfo.CanWrite && propertyInfo.PropertyType == typeof(int))
				{
					propertyInfo.SetValue(server, ConfiguredMaxConnections);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to set EOS server maxConnections " + ex));
			}
		}

		public static void ApplyToLobbyModification(object lobbyModification)
		{
			if (lobbyModification == null)
			{
				return;
			}
			try
			{
				Type type = lobbyModificationSetMaxMembersOptionsType ?? (lobbyModificationSetMaxMembersOptionsType = Plugin.ResolveKnownType("Epic.OnlineServices.Lobby.LobbyModificationSetMaxMembersOptions"));
				if (type == null)
				{
					Plugin.Log.LogWarning((object)"Failed to set EOS lobby MaxMembers: options type not found");
					return;
				}
				object obj = Activator.CreateInstance(type);
				SetNumericMember(obj, "MaxMembers", ConfiguredMaxConnections);
				MethodInfo methodInfo = setMaxMembersMethod ?? (setMaxMembersMethod = lobbyModification.GetType().GetMethod("SetMaxMembers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
				if (methodInfo == null)
				{
					Plugin.Log.LogWarning((object)"Failed to set EOS lobby MaxMembers SetMaxMembers method not found");
					return;
				}
				object obj2 = methodInfo.Invoke(lobbyModification, new object[1] { obj });
				if (obj2 == null || obj2.ToString() != "Success")
				{
					Plugin.Log.LogError((object)("[CAPACITY] EOS SetMaxMembers(" + ConfiguredMaxConnections + ") failed: " + obj2));
				}
			}
			catch (TargetInvocationException ex)
			{
				Plugin.Log.LogWarning((object)("Failed to set EOS lobby MaxMembers " + ((ex.InnerException == null) ? ex.ToString() : ex.InnerException.ToString())));
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Failed to set EOS lobby MaxMembers " + ex2));
			}
		}

		private static void SetNumericMember(object target, string memberName, int value)
		{
			Type type = target.GetType();
			FieldInfo fieldInfo = type.GetField("_" + memberName + "_k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (fieldInfo != null)
			{
				fieldInfo.SetValue(target, ConvertNumber(value, fieldInfo.FieldType));
				return;
			}
			PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if ((object)property != null && property.CanWrite && property.GetIndexParameters().Length == 0)
			{
				property.SetValue(target, ConvertNumber(value, property.PropertyType));
			}
		}

		private static object ConvertNumber(int value, Type targetType)
		{
			if (targetType == typeof(uint))
			{
				return (uint)Math.Max(0, value);
			}
			if (targetType == typeof(ushort))
			{
				return (ushort)Math.Max(0, value);
			}
			if (targetType == typeof(byte))
			{
				return (byte)Math.Max(0, value);
			}
			if (targetType == typeof(long))
			{
				return (long)value;
			}
			if (targetType == typeof(ulong))
			{
				return (ulong)Math.Max(0, value);
			}
			return value;
		}
	}
	public static class NetworkManagerPatch
	{
		public static void StartServerPrefix(NetworkManager __instance)
		{
			MaxConnectionsPatch.ApplyToNetworkManager(__instance);
		}

		public static void StartHostPrefix(NetworkManager __instance)
		{
			MaxConnectionsPatch.ApplyToNetworkManager(__instance);
		}
	}
	public static class NetworkServerPatch
	{
		public static void ListenPrefix(ref int maxConns)
		{
			int maxConnections = (maxConns = MaxConnectionsPatch.ConfiguredMaxConnections);
			try
			{
				NetworkServer.maxConnections = maxConnections;
				Plugin.Log.LogInfo((object)("[CAPACITY] Mirror Listen player limit=" + maxConnections));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to set NetworkServer.maxConnections " + ex));
			}
		}
	}
	public static class SteamMatchmakingPatch
	{
		public static void CreateLobbyPrefix(ref int cMaxMembers)
		{
			cMaxMembers = MaxConnectionsPatch.ConfiguredMaxConnections;
		}

		public static void SetLobbyMemberLimitPrefix(ref int cMaxMembers)
		{
			cMaxMembers = MaxConnectionsPatch.ConfiguredMaxConnections;
		}
	}
	[BepInPlugin("AdamMady.BigLobbies", "Big Lobbies", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BasePlugin
	{
		internal static ManualLogSource Log;

		private Harmony harmony;

		internal static ConfigEntry<int> MaxConnections;

		internal static ConfigEntry<float> QuietThreshold;

		internal static ConfigEntry<int> VoiceQuality;

		internal static ConfigEntry<int> HostVoiceQuality;

		internal static ConfigEntry<string> UpdateCapHz;

		internal static ConfigEntry<float> HearingDistance;

		private static int settingsPending;

		internal static int VoicePercentage => VoiceQuality.Value;

		internal static int VoiceBitrate => 17000 * VoicePercentage / 100;

		internal static int HostVoiceBitrate => 17000 * HostVoiceQuality.Value / 100;

		internal static int UpdateCapValue
		{
			get
			{
				if (!int.TryParse(UpdateCapHz.Value.Replace(" Hz", ""), out var result))
				{
					return 0;
				}
				return result;
			}
		}

		public override void Load()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Expected O, but got Unknown
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Expected O, but got Unknown
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Expected O, but got Unknown
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Expected O, but got Unknown
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Expected O, but got Unknown
			Log = ((BasePlugin)this).Log;
			MigrateConfigFilename();
			MigrateVoiceConfig();
			MaxConnections = ((BasePlugin)this).Config.Bind<int>("General", "MaxPlayerCount", 25, new ConfigDescription("Maximum players. Applies to the current hosted lobby; lowering this does not kick existing players.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), SettingTags("General", 10, 10, 1.0)));
			harmony = new Harmony("AdamMady.BigLobbies");
			HarmonyPatches();
			try
			{
				EosCapacityPatch.Install(harmony);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("[CAPACITY] EOS SDK hook installation failed: " + ex));
			}
			QuietThreshold = ((BasePlugin)this).Config.Bind<float>("Voice", "QuietThreshold", 0.004f, new ConfigDescription("Decoded amplitude below which an ordinary speaker loses priority ONLY when a listener's voice slots are full.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.0001f, 0.05f), SettingTags("Voice", 20, 10, 0.0001)));
			HostVoiceQuality = ((BasePlugin)this).Config.Bind<int>("Voice", "Host Voice Quality", 100, new ConfigDescription("Your own transmitted voice bitrate, independent of guest Voice Quality. Percentage of 17 kbps, from 15% to 100%. Applies live. Does not change volume, FEC settings or frame duration.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(15, 100), SettingTags("Voice", 20, 20, 1.0)));
			VoiceQuality = ((BasePlugin)this).Config.Bind<int>("Wi-Fi Optimization", "Voice Quality", 100, new ConfigDescription("Guest relay bitrate as a percentage of 17 kbps, from 15% to 100%. 100% preserves original guest packets. Lower values recompress them on the host using extra CPU. Host listening remains original; Host Voice Quality separately controls your own voice. Applies live; frame duration and FEC settings are unchanged.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(15, 100), SettingTags("Wi-Fi Optimization", 30, 10, 1.0)));
			UpdateCapHz = ((BasePlugin)this).Config.Bind<string>("Wi-Fi Optimization", "Update Cap Hz", "Normal", new ConfigDescription("Normal uses the game's original rate. Lower caps reduce upload traffic but make movement less smooth. Never exceeds the game's original rate. Voice packet rate is unchanged. Applies live.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[10] { "5 Hz", "10 Hz", "15 Hz", "20 Hz", "25 Hz", "Normal", "25 Hz", "20 Hz", "15 Hz", "10 Hz" }), SettingTags("Wi-Fi Optimization", 30, 20, 5.0)));
			HearingDistance = ((BasePlugin)this).Config.Bind<float>("Wi-Fi Optimization", "Hearing Distance", 30f, new ConfigDescription("Ordinary guest voice relay radius in metres. Does not amplify voices or change native host hearing. Active megaphones, walkie-talkies and radio stations keep their special routes. Applies live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), SettingTags("Wi-Fi Optimization", 30, 30, 1.0)));
			((BasePlugin)this).Config.SettingChanged += OnSettingChanged;
			RegisterSettingsMetadata();
			((BasePlugin)this).Config.Save();
			VoicePatches.Install();
			((BasePlugin)this).AddComponent<VoicePump>();
			Log.LogInfo((object)"Big Lobbies loaded. Live settings and native host hearing.");
		}

		private void MigrateConfigFilename()
		{
			string text = Path.Combine(Paths.ConfigPath, "AdamMady.BiggerWalkAndTalk.cfg");
			if (!File.Exists(text))
			{
				return;
			}
			if (File.Exists(((BasePlugin)this).Config.ConfigFilePath))
			{
				foreach (string item in File.ReadLines(((BasePlugin)this).Config.ConfigFilePath))
				{
					string text2 = item.Trim();
					if (!text2.StartsWith("#") && !text2.StartsWith(";") && text2.IndexOf('=') > 0)
					{
						return;
					}
				}
			}
			File.Copy(text, ((BasePlugin)this).Config.ConfigFilePath, overwrite: true);
			((BasePlugin)this).Config.Reload();
			Log.LogInfo((object)"Migrated saved settings to AdamMady.BigLobbies.cfg.");
		}

		private void MigrateVoiceConfig()
		{
			if (!File.Exists(((BasePlugin)this).Config.ConfigFilePath))
			{
				return;
			}
			string[] array = File.ReadAllLines(((BasePlugin)this).Config.ConfigFilePath);
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			string text = "";
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text2 = array2[i].Trim();
				if (text2.StartsWith("["))
				{
					text = text2;
				}
				else if (!text2.StartsWith("#") && !text2.StartsWith(";"))
				{
					int num = text2.IndexOf('=');
					if (num > 0)
					{
						dictionary[text + ":" + text2.Substring(0, num).Trim()] = text2.Substring(num + 1).Trim();
					}
				}
			}
			dictionary.TryGetValue("[Wi-Fi Optimization]:Voice Quality", out var value);
			dictionary.TryGetValue("[Wi-Fi Optimization]:Quality Mode", out var value2);
			dictionary.TryGetValue("[Wi-Fi Optimization]:Manual Voice Quality", out var value3);
			dictionary.TryGetValue("[Voice]:Host Microphone Quality", out var value4);
			dictionary.TryGetValue("[Wi-Fi Optimization]:Update Cap Hz", out var value5);
			int result;
			bool flag = int.TryParse(value, out result);
			if (value2 == null && value3 == null && value4 == null && !(value5 == "30 Hz") && !dictionary.ContainsKey("[Wi-Fi Optimization]:Compress Host Listening") && (value == null || flag))
			{
				return;
			}
			if (!flag)
			{
				result = 100;
				if (value2 == "Manual" && int.TryParse(value3, out var result2))
				{
					result = result2;
				}
				else if (value != null && int.TryParse(value.Split('%')[0], out result2))
				{
					result = result2;
				}
			}
			List<string> list = new List<string>();
			text = "";
			array2 = array;
			foreach (string text3 in array2)
			{
				string text4 = text3.Trim();
				if (text4.StartsWith("["))
				{
					text = text4;
					list.Add(text3);
					continue;
				}
				int num2 = text4.IndexOf('=');
				if (num2 > 0 && !text4.StartsWith("#") && !text4.StartsWith(";"))
				{
					string text5 = text4.Substring(0, num2).Trim();
					if (text == "[Wi-Fi Optimization]")
					{
						switch (text5)
						{
						case "Voice Quality":
							list.Add("Voice Quality = " + Math.Clamp(result, 15, 100));
							continue;
						case "Update Cap Hz":
							if (value5 == "30 Hz")
							{
								list.Add("Update Cap Hz = Normal");
								continue;
							}
							break;
						case "Quality Mode":
						case "Manual Voice Quality":
						case "Compress Host Listening":
							continue;
						}
					}
					if (text == "[Voice]" && text5 == "Host Microphone Quality")
					{
						if (!dictionary.ContainsKey("[Voice]:Host Voice Quality"))
						{
							list.Add("Host Voice Quality = " + (int.TryParse(value4, out var result3) ? Math.Clamp(result3, 15, 100) : 100));
						}
						continue;
					}
				}
				list.Add(text3);
			}
			File.WriteAllLines(((BasePlugin)this).Config.ConfigFilePath, list, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			((BasePlugin)this).Config.Reload();
			Log.LogInfo((object)"[VOICE QUALITY] Migrated legacy quality settings to percentage sliders.");
		}

		private static void RegisterSettingsMetadata()
		{
			Type type = ResolveKnownType("ModSettingsMenu.Api.ModSettingsRegistry");
			Type type2 = ResolveKnownType("ModSettingsMenu.Api.ModSettingsModOptions");
			if (type == null || type2 == null)
			{
				return;
			}
			try
			{
				object obj = Activator.CreateInstance(type2);
				type2.GetProperty("Name").SetValue(obj, "Big Lobbies");
				type2.GetProperty("Author").SetValue(obj, "AdamMady");
				type2.GetProperty("Version").SetValue(obj, "1.0.0");
				type.GetMethod("Register", new Type[2]
				{
					typeof(string),
					type2
				}).Invoke(null, new object[2] { "AdamMady.BigLobbies", obj });
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Cannot register Mod Settings metadata: " + ex.GetBaseException().Message));
			}
		}

		private static void OnSettingChanged(object sender, SettingChangedEventArgs args)
		{
			Interlocked.Exchange(ref settingsPending, 1);
		}

		internal static void ApplyPendingSettings()
		{
			if (Interlocked.Exchange(ref settingsPending, 0) != 0)
			{
				VoiceRoster.Refresh(immediate: true);
			}
		}

		private static object[] SettingTags(string section, int sectionOrder, int entryOrder, double step)
		{
			Type type = ResolveKnownType("ModSettingsMenu.Api.ModSettingsEntryTag");
			if (type == null)
			{
				return Array.Empty<object>();
			}
			object obj = Activator.CreateInstance(type);
			type.GetProperty("SliderStep").SetValue(obj, step);
			type.GetProperty("Order").SetValue(obj, entryOrder);
			Type type2 = ResolveKnownType("ModSettingsMenu.Api.ModSettingsSectionTag");
			if (!(type2 == null))
			{
				object obj2 = Activator.CreateInstance(type2);
				type2.GetProperty("Section").SetValue(obj2, section);
				type2.GetProperty("Order").SetValue(obj2, sectionOrder);
				return new object[2] { obj, obj2 };
			}
			return new object[1] { obj };
		}

		private void HarmonyPatches()
		{
			TryPatch(AccessTools.Method(typeof(NetworkManager), "StartServer", Type.EmptyTypes, (Type[])null), AccessTools.Method(typeof(NetworkManagerPatch), "StartServerPrefix", (Type[])null, (Type[])null), null, "NetworkManager.StartServer");
			TryPatch(AccessTools.Method(typeof(NetworkManager), "StartHost", Type.EmptyTypes, (Type[])null), AccessTools.Method(typeof(NetworkManagerPatch), "StartHostPrefix", (Type[])null, (Type[])null), null, "NetworkManager.StartHost");
			TryPatch(AccessTools.Method(typeof(NetworkServer), "Listen", new Type[1] { typeof(int) }, (Type[])null), AccessTools.Method(typeof(NetworkServerPatch), "ListenPrefix", (Type[])null, (Type[])null), null, "NetworkServer.Listen");
			TryPatch(AccessTools.Method(typeof(HouseNetworkManager), "OnStartServer", Type.EmptyTypes, (Type[])null), AccessTools.Method(typeof(HouseNetworkManagerPatch), "OnStartServerPrefix", (Type[])null, (Type[])null), null, "HouseNetworkManager.OnStartServer");
			TryPatch(AccessTools.Method(typeof(HouseNetworkManager), "OnStartHost", Type.EmptyTypes, (Type[])null), AccessTools.Method(typeof(HouseNetworkManagerPatch), "OnStartHostPrefix", (Type[])null, (Type[])null), null, "HouseNetworkManager.OnStartHost");
			TryPatch(AccessTools.Method(typeof(HouseNetworkManager), "CreateHostEosLobbyAsync", Type.EmptyTypes, (Type[])null), AccessTools.Method(typeof(HouseNetworkManagerPatch), "CreateHostEosLobbyAsyncPrefix", (Type[])null, (Type[])null), null, "HouseNetworkManager.CreateHostEosLobbyAsync");
			TryPatch(AccessTools.Method(typeof(EOSLobby), "CreateLobby", (Type[])null, (Type[])null), AccessTools.Method(typeof(EosLobbyPatch), "CreateLobbyPrefix", (Type[])null, (Type[])null), null, "EOSLobby.CreateLobby");
			TryPatch(MethodByTypeName("EOSPrivateLobbyManager", "FeedUpdateAttributes") ?? MethodByTypeName("EOSLobbyManager", "FeedUpdateAttributes"), null, AccessTools.Method(typeof(EosLobbyManagerPatch), "FeedUpdateAttributesPostfix", (Type[])null, (Type[])null), "EOSLobbyManager.FeedUpdateAttributes");
			TryPatch(AccessTools.Method(typeof(SteamMatchmaking), "CreateLobby", (Type[])null, (Type[])null), AccessTools.Method(typeof(SteamMatchmakingPatch), "CreateLobbyPrefix", (Type[])null, (Type[])null), null, "SteamMatchmaking.CreateLobby");
			TryPatch(AccessTools.Method(typeof(SteamMatchmaking), "SetLobbyMemberLimit", (Type[])null, (Type[])null), AccessTools.Method(typeof(SteamMatchmakingPatch), "SetLobbyMemberLimitPrefix", (Type[])null, (Type[])null), null, "SteamMatchmaking.SetLobbyMemberLimit");
			TryPatch(AccessTools.Method(typeof(Server), "CreateServer", (Type[])null, (Type[])null), AccessTools.Method(typeof(EpicTransportServerPatch), "CreateServerPrefix", (Type[])null, (Type[])null), AccessTools.Method(typeof(EpicTransportServerPatch), "CreateServerPostfix", (Type[])null, (Type[])null), "EpicTransport.Server.CreateServer");
		}

		private static MethodInfo MethodByTypeName(string typeName, string methodName)
		{
			Type type = ResolveKnownType(typeName);
			if (!(type == null))
			{
				return AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			}
			return null;
		}

		internal static Type ResolveKnownType(string typeName)
		{
			Type type = typeof(Server).Assembly.GetType(typeName, throwOnError: false, ignoreCase: false) ?? typeof(HouseNetworkManager).Assembly.GetType(typeName, throwOnError: false, ignoreCase: false) ?? typeof(NetworkManager).Assembly.GetType(typeName, throwOnError: false, ignoreCase: false) ?? Type.GetType(typeName, throwOnError: false, ignoreCase: false);
			if (type != null)
			{
				return type;
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				type = assemblies[i].GetType(typeName, throwOnError: false, ignoreCase: false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		private void TryPatch(MethodInfo original, MethodInfo prefix, MethodInfo postfix, string patchName)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if (original == null)
			{
				Log.LogError((object)("Failed to find patch target: " + patchName));
				return;
			}
			try
			{
				harmony.Patch((MethodBase)original, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Failed to apply the patch for " + patchName + ": " + ex));
			}
		}
	}
	internal static class RelayQuality
	{
		internal sealed class Job
		{
			internal ushort Source;

			internal ushort Sequence;

			internal byte ChannelSession;

			internal string Name;

			internal IntPtr Connection;

			internal uint Session;

			internal byte[] Data;

			internal int AudioOffset;

			internal int AudioLength;

			internal int Rate;

			internal int Samples;

			internal ushort[] Recipients;

			internal float At;

			internal float Loss;

			internal bool Fec;

			internal bool Reencoded;

			internal int OutputAudioLength;

			internal long Submitted;
		}

		private sealed class Worker
		{
			internal readonly BlockingCollection<Job> Input = new BlockingCollection<Job>(128);

			internal readonly ConcurrentQueue<Job> Completed = new ConcurrentQueue<Job>();

			internal readonly ConcurrentQueue<string> Errors = new ConcurrentQueue<string>();

			private readonly Dictionary<(string, IntPtr), Stream> streams = new Dictionary<(string, IntPtr), Stream>();

			private readonly HashSet<(string, IntPtr)> unsupported = new HashSet<(string, IntPtr)>();

			private int bitrate;

			private volatile bool stopped;

			internal Worker(int bitrate)
			{
				this.bitrate = bitrate;
				Thread thread = new Thread(Run);
				thread.IsBackground = true;
				thread.Name = "Big Lobbies voice quality";
				thread.Start();
			}

			internal void SetBitrate(int value)
			{
				Volatile.Write(ref bitrate, value);
			}

			internal void Stop()
			{
				stopped = true;
				try
				{
					Input.CompleteAdding();
				}
				catch (ObjectDisposedException)
				{
				}
			}

			private void Run()
			{
				try
				{
					foreach (Job item2 in Input.GetConsumingEnumerable())
					{
						if (stopped)
						{
							break;
						}
						if ((double)(Stopwatch.GetTimestamp() - item2.Submitted) / (double)Stopwatch.Frequency > 0.2)
						{
							continue;
						}
						(string, IntPtr) tuple = (item2.Name, item2.Connection);
						if (unsupported.Contains(tuple))
						{
							Completed.Enqueue(item2);
							continue;
						}
						try
						{
							if (!streams.TryGetValue(tuple, out var value))
							{
								value = (streams[tuple] = new Stream(item2, bitrate));
							}
							if (value.Encode(item2, Volatile.Read(in bitrate)))
							{
								Completed.Enqueue(item2);
							}
						}
						catch (Exception ex)
						{
							if (streams.Remove(tuple, out var value2))
							{
								value2.Dispose();
							}
							unsupported.Add(tuple);
							if (Errors.IsEmpty)
							{
								Errors.Enqueue("Recompression failed for a voice stream; forwarding original frame: " + ex.Message);
							}
							Completed.Enqueue(item2);
						}
						if (streams.Count <= 0)
						{
							continue;
						}
						List<(string, IntPtr)> list = null;
						foreach (KeyValuePair<(string, IntPtr), Stream> stream2 in streams)
						{
							if (item2.Submitted - stream2.Value.LastUsed > Stopwatch.Frequency * 15)
							{
								(list ?? (list = new List<(string, IntPtr)>())).Add(stream2.Key);
							}
						}
						if (list == null)
						{
							continue;
						}
						foreach (var item3 in list)
						{
							streams[item3].Dispose();
							streams.Remove(item3);
						}
					}
				}
				finally
				{
					foreach (Stream value3 in streams.Values)
					{
						value3.Dispose();
					}
					streams.Clear();
					Job item;
					while (Input.TryTake(out item))
					{
					}
					Input.Dispose();
				}
			}
		}

		private sealed class Stream : IDisposable
		{
			private IntPtr decoder;

			private IntPtr encoder;

			private readonly int rate;

			private readonly int samples;

			private readonly float[] pcm;

			private readonly byte[] encoded = new byte[1276];

			private ushort sequence;

			private byte channelSession;

			private bool started;

			private int appliedBitrate;

			internal long LastUsed;

			internal Stream(Job job, int bitrate)
			{
				rate = job.Rate;
				samples = job.Samples;
				if (rate != 48000 || samples <= 0 || samples > 5760)
				{
					throw new InvalidOperationException("Unsupported Opus frame format.");
				}
				pcm = new float[5760];
				try
				{
					decoder = Native.CreateDecoder(rate, 1, out var error);
					if (decoder == IntPtr.Zero || error != 0)
					{
						throw new InvalidOperationException("Opus decoder: " + error);
					}
					encoder = Native.CreateEncoder(rate, 1, 2048, out error);
					if (encoder == IntPtr.Zero || error != 0)
					{
						throw new InvalidOperationException("Opus encoder: " + error);
					}
					Check(Native.EncoderCtl(encoder, 4002, bitrate));
					appliedBitrate = bitrate;
					Check(Native.EncoderCtl(encoder, 4012, job.Fec ? 1 : 0));
				}
				catch
				{
					Dispose();
					throw;
				}
			}

			internal unsafe bool Encode(Job job, int bitrate)
			{
				if (bitrate != appliedBitrate)
				{
					Check(Native.EncoderCtl(encoder, 4002, bitrate));
					appliedBitrate = bitrate;
				}
				if (job.Rate != rate || job.Samples != samples || job.AudioOffset < 2 || job.AudioLength < 1 || job.AudioOffset > job.Data.Length - job.AudioLength || job.AudioOffset + job.AudioLength != job.Data.Length)
				{
					throw new InvalidOperationException("Voice payload layout changed.");
				}
				int num = job.AudioOffset - 2;
				if (((job.Data[num] << 8) | job.Data[num + 1]) != job.AudioLength)
				{
					throw new InvalidOperationException("Voice payload length field mismatch.");
				}
				fixed (byte* data = job.Data)
				{
					fixed (float* ptr = pcm)
					{
						fixed (byte* ptr2 = encoded)
						{
							if (Native.PacketChannels((IntPtr)(data + job.AudioOffset)) != 1)
							{
								throw new InvalidOperationException("Stereo voice is not recompressed.");
							}
							if (Native.PacketSamples((IntPtr)(data + job.AudioOffset), job.AudioLength, rate) != samples)
							{
								throw new InvalidOperationException("Voice frame duration mismatch.");
							}
							int num2 = ((!started) ? 1 : ((ushort)(job.Sequence - sequence)));
							if (started && job.ChannelSession == channelSession && job.Submitted - LastUsed <= Stopwatch.Frequency && (num2 == 0 || num2 >= 32768))
							{
								return false;
							}
							if (!started || job.ChannelSession != channelSession || num2 > 4 || job.Submitted - LastUsed > Stopwatch.Frequency)
							{
								Check(Native.DecoderCtl(decoder, 4028, 0));
								if (!started || job.ChannelSession != channelSession)
								{
									Check(Native.EncoderCtl(encoder, 4028, 0));
								}
							}
							else
							{
								for (int i = 1; i < num2; i++)
								{
									Check(Native.Decode(decoder, IntPtr.Zero, 0, (IntPtr)ptr, samples, 0));
								}
							}
							int num3 = Native.Decode(decoder, (IntPtr)(data + job.AudioOffset), job.AudioLength, (IntPtr)ptr, pcm.Length, 0);
							Check(num3);
							if (num3 != samples)
							{
								throw new InvalidOperationException("Unexpected decoded frame length.");
							}
							Check(Native.EncoderCtl(encoder, 4012, job.Fec ? 1 : 0));
							Check(Native.EncoderCtl(encoder, 4014, Math.Clamp((int)(job.Loss * 100f), 0, 100)));
							num3 = Native.Encode(encoder, (IntPtr)ptr, samples, (IntPtr)ptr2, encoded.Length);
							Check(num3);
							byte[] array = new byte[job.AudioOffset + num3];
							Buffer.BlockCopy(job.Data, 0, array, 0, job.AudioOffset);
							array[num] = (byte)(num3 >> 8);
							array[num + 1] = (byte)num3;
							Buffer.BlockCopy(encoded, 0, array, job.AudioOffset, num3);
							job.Data = array;
							job.Reencoded = true;
							job.OutputAudioLength = num3;
							sequence = job.Sequence;
							channelSession = job.ChannelSession;
							started = true;
							LastUsed = job.Submitted;
							return true;
						}
					}
				}
			}

			private static void Check(int result)
			{
				if (result < 0)
				{
					throw new InvalidOperationException("Opus error " + result);
				}
			}

			public void Dispose()
			{
				if (decoder != IntPtr.Zero)
				{
					Native.DestroyDecoder(decoder);
					decoder = IntPtr.Zero;
				}
				if (encoder != IntPtr.Zero)
				{
					Native.DestroyEncoder(encoder);
					encoder = IntPtr.Zero;
				}
			}
		}

		private static class Native
		{
			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate IntPtr NewEncoder(int rate, int channels, int application, out int error);

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate IntPtr NewDecoder(int rate, int channels, out int error);

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate void Destroy(IntPtr handle);

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate int EncodeFrame(IntPtr handle, IntPtr pcm, int samples, IntPtr output, int capacity);

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate int DecodeFrame(IntPtr handle, IntPtr input, int length, IntPtr pcm, int capacity, int fec);

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate int Control(IntPtr handle, int request, int value);

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate int Channels(IntPtr packet);

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			internal delegate int Samples(IntPtr packet, int length, int rate);

			private static IntPtr library;

			internal static NewEncoder CreateEncoder;

			internal static NewDecoder CreateDecoder;

			internal static Destroy DestroyEncoder;

			internal static Destroy DestroyDecoder;

			internal static EncodeFrame Encode;

			internal static DecodeFrame Decode;

			internal static Control EncoderCtl;

			internal static Control DecoderCtl;

			internal static Channels PacketChannels;

			internal static Samples PacketSamples;

			internal static void Load(string path)
			{
				if (library != IntPtr.Zero)
				{
					return;
				}
				IntPtr handle = NativeLibrary.Load(path);
				try
				{
					CreateEncoder = Get<NewEncoder>("opus_encoder_create");
					CreateDecoder = Get<NewDecoder>("opus_decoder_create");
					DestroyEncoder = Get<Destroy>("opus_encoder_destroy");
					DestroyDecoder = Get<Destroy>("opus_decoder_destroy");
					Encode = Get<EncodeFrame>("opus_encode_float");
					Decode = Get<DecodeFrame>("opus_decode_float");
					EncoderCtl = Get<Control>("dissonance_opus_encoder_ctl_in");
					DecoderCtl = Get<Control>("dissonance_opus_decoder_ctl_in");
					PacketChannels = Get<Channels>("opus_packet_get_nb_channels");
					PacketSamples = Get<Samples>("opus_packet_get_nb_samples");
					library = handle;
				}
				catch
				{
					NativeLibrary.Free(handle);
					throw;
				}
				T Get<T>(string name) where T : Delegate
				{
					return Marshal.GetDelegateForFunctionPointer<T>(NativeLibrary.GetExport(handle, name));
				}
			}
		}

		private static Worker worker;

		private static int applied;

		private static int microphoneBitrate = 17000;

		private static volatile bool hosting;

		private static bool unavailable;

		private static float nextWarning;

		private static float nextStatistics;

		private static long inputBytes;

		private static long outputBytes;

		private static int reencodedFrames;

		private static int fallbackFrames;

		private static OpusEncoder microphone;

		private static IntPtr appliedMicrophone;

		private static int originalMicBitrate;

		private static int lastMicBitrate;

		private static int microphoneWarning;

		private static float nextMicrophoneScan;

		internal static bool Recompress
		{
			get
			{
				if (hosting && worker != null)
				{
					return !unavailable;
				}
				return false;
			}
		}

		internal static void Tick()
		{
			hosting = VoiceRoster.Enabled;
			Volatile.Write(ref microphoneBitrate, Plugin.HostVoiceBitrate);
			ApplyMicrophone();
			int num = (hosting ? Plugin.VoiceBitrate : 0);
			if (num != applied)
			{
				bool flag = worker != null;
				if (worker != null && num > 0 && num < 17000)
				{
					worker.SetBitrate(num);
				}
				else
				{
					worker?.Stop();
					worker = null;
					unavailable = false;
					if (num > 0 && num < 17000)
					{
						try
						{
							Native.Load(Path.Combine(Application.dataPath, "Plugins", "x86_64", "opus.dll"));
							worker = new Worker(num);
						}
						catch (Exception ex)
						{
							unavailable = true;
							Plugin.Log.LogWarning((object)("[VOICE QUALITY] Guest recompression unavailable; preserving original packets: " + ex.Message));
						}
					}
				}
				bool flag2 = worker != null;
				if (applied > 0 && num > 0 && flag != flag2)
				{
					VoiceRoster.ResetGuestQuality();
				}
				applied = num;
				inputBytes = (outputBytes = 0L);
				reencodedFrames = (fallbackFrames = 0);
				nextStatistics = Time.unscaledTime + 10f;
				if (num > 0)
				{
					Plugin.Log.LogInfo((object)("[VOICE QUALITY] Guest target " + Plugin.VoicePercentage + "% (" + num + "bps); relay " + (Recompress ? "background recompression" : "original packets") + "."));
				}
			}
			if (worker == null)
			{
				return;
			}
			string result;
			while (worker.Errors.TryDequeue(out result))
			{
				if (Time.unscaledTime >= nextWarning)
				{
					nextWarning = Time.unscaledTime + 10f;
					Plugin.Log.LogWarning((object)("[VOICE QUALITY] " + result));
				}
			}
			Job result2;
			while (worker.Completed.TryDequeue(out result2))
			{
				inputBytes += result2.AudioLength;
				outputBytes += (result2.Reencoded ? result2.OutputAudioLength : result2.AudioLength);
				if (result2.Reencoded)
				{
					reencodedFrames++;
				}
				else
				{
					fallbackFrames++;
				}
				VoiceRoster.FinishQuality(result2);
			}
			if (Time.unscaledTime >= nextStatistics && reencodedFrames + fallbackFrames > 0)
			{
				nextStatistics = Time.unscaledTime + 10f;
				Plugin.Log.LogDebug((object)("[VOICE QUALITY] Payload bytes " + inputBytes + " -> " + outputBytes + "; reencoded=" + reencodedFrames + ", original fallbacks=" + fallbackFrames + "."));
				inputBytes = (outputBytes = 0L);
				reencodedFrames = (fallbackFrames = 0);
			}
		}

		internal static bool Submit(Job job)
		{
			if (worker == null)
			{
				return false;
			}
			job.Submitted = Stopwatch.GetTimestamp();
			if (!worker.Input.TryAdd(job) && Time.unscaledTime >= nextWarning)
			{
				nextWarning = Time.unscaledTime + 10f;
				Plugin.Log.LogWarning((object)"[VOICE QUALITY] Encoder queue full; dropping voice instead of building latency. Set quality to 100% to disable guest recompression.");
			}
			return true;
		}

		internal static void Reset()
		{
			worker?.Stop();
			worker = null;
			applied = 0;
			hosting = false;
			unavailable = false;
		}

		private static void ApplyMicrophone()
		{
			try
			{
				int num = (hosting ? Volatile.Read(in microphoneBitrate) : originalMicBitrate);
				bool flag = num != lastMicBitrate;
				if (hosting && (flag || Time.unscaledTime >= nextMicrophoneScan))
				{
					nextMicrophoneScan = Time.unscaledTime + 0.5f;
					DissonanceComms captureComms = VoiceRoster.CaptureComms;
					object obj;
					if (captureComms == null)
					{
						obj = null;
					}
					else
					{
						CapturePipelineManager capture = captureComms._capture;
						if (capture == null)
						{
							obj = null;
						}
						else
						{
							EncoderPipeline encoder = capture._encoder;
							obj = ((encoder != null) ? encoder._encoder : null);
						}
					}
					ReadonlyLockedValue<IVoiceEncoder> val = (ReadonlyLockedValue<IVoiceEncoder>)obj;
					if (val != null)
					{
						Unlocker<IVoiceEncoder> val2 = val.Lock();
						try
						{
							IVoiceEncoder value = val2.Value;
							object obj2;
							if (value == null)
							{
								obj2 = null;
							}
							else
							{
								OpusEncoder obj3 = ((Il2CppObjectBase)value).TryCast<OpusEncoder>();
								obj2 = ((obj3 != null) ? obj3._encoder : null);
							}
							OpusEncoder val3 = (OpusEncoder)obj2;
							if (val3 != null && !val3._disposed)
							{
								OpusEncoder obj4 = microphone;
								if (((obj4 != null) ? new IntPtr?(((Il2CppObjectBase)obj4).Pointer) : ((IntPtr?)null)) != ((Il2CppObjectBase)val3).Pointer)
								{
									microphone = val3;
									originalMicBitrate = val3.Bitrate;
									appliedMicrophone = IntPtr.Zero;
								}
							}
						}
						finally
						{
							val2.Dispose();
						}
					}
				}
				OpusEncoder val4 = microphone;
				if (val4 == null || val4._disposed)
				{
					return;
				}
				num = (hosting ? Volatile.Read(in microphoneBitrate) : originalMicBitrate);
				if (!(appliedMicrophone == ((Il2CppObjectBase)val4).Pointer) || lastMicBitrate != num || val4.Bitrate != num)
				{
					if (val4.Bitrate != num)
					{
						val4.Bitrate = num;
					}
					int bitrate = val4.Bitrate;
					if (bitrate != num)
					{
						throw new InvalidOperationException("Encoder reported " + bitrate + "bps instead of " + num + ".");
					}
					appliedMicrophone = ((Il2CppObjectBase)val4).Pointer;
					lastMicBitrate = num;
					Plugin.Log.LogInfo((object)("[VOICE QUALITY] Host microphone encoder confirmed " + bitrate + "bps."));
				}
			}
			catch (Exception ex)
			{
				if (hosting && Interlocked.Exchange(ref microphoneWarning, 1) == 0)
				{
					Plugin.Log.LogWarning((object)("[VOICE QUALITY] Cannot apply microphone bitrate; native encoding continues: " + ex.Message));
				}
			}
		}
	}
	internal static class VoicePatches
	{
		internal static bool Ready;

		private static Harmony harmony;

		private static bool replayingRadio;

		internal static void Install()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			harmony = new Harmony("AdamMady.BigLobbies.Voice");
			try
			{
				VoiceSendHook.Install();
				Patch(typeof(MirrorIgnoranceServer), "OnMessageReceived", "Receive");
				Patch(typeof(MirrorIgnoranceServer), "Connect", null, "Connected");
				Patch(typeof(MirrorIgnoranceServer), "Update", null, "ServerUpdated");
				Patch(typeof(MirrorIgnoranceServer), "Disconnect", "Disconnect");
				Patch(typeof(MirrorIgnoranceCommsNetwork), "PreprocessPacketToServer", "HostSend");
				Patch(typeof(PlayerVoicePlaybackControl), "TryTakeCue", "TakeCue");
				Patch(typeof(PlayerVoicePlaybackControl), "OnDisable", "PlaybackDisabling", "PlaybackDisabled");
				Patch(typeof(GlobalAudioEffects), "Update", null, "AudioEffectsUpdated");
				Patch(typeof(RadioVoiceAssigner), "Peck", "RadioUse", "RadioUsed");
				ClassInjector.RegisterTypeInIl2Cpp<VoicePump>();
				Ready = true;
			}
			catch (Exception ex)
			{
				harmony.UnpatchSelf();
				VoiceSendHook.Dispose();
				Ready = false;
				Plugin.Log.LogError((object)("[VOICE] Initialization failed; voice patches rolled back. Lobby cap still active. " + ex));
				throw;
			}
		}

		private static void Patch(Type type, string name, string prefix = null, string postfix = null)
		{
			MethodInfo methodInfo = AccessTools.Method(type, name, (Type[])null, (Type[])null) ?? throw new MissingMethodException(type.FullName, name);
			harmony.Patch((MethodBase)methodInfo, Hook(prefix), Hook(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			static HarmonyMethod Hook(string method)
			{
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Expected O, but got Unknown
				if (method != null)
				{
					return new HarmonyMethod(typeof(VoicePatches), method, (Type[])null);
				}
				return null;
			}
		}

		[HarmonyPriority(0)]
		private static bool Receive(MirrorIgnoranceServer __instance, NetworkConnection source, DissonanceNetworkMessage msg, bool __runOriginal)
		{
			if (!__runOriginal)
			{
				return true;
			}
			try
			{
				if (!VoiceRoster.Incoming(__instance, source, msg.Data, local: false))
				{
					return true;
				}
				msg.Dispose();
				return false;
			}
			catch (Exception error)
			{
				VoiceRoster.Report("incoming packet", error);
				msg.Dispose();
				return false;
			}
		}

		private static bool HostSend(ArraySegment<byte> packet, ref bool __result)
		{
			if (!VoiceRoster.Enabled || VoiceRoster.Server == null)
			{
				return true;
			}
			try
			{
				if (!VoiceRoster.Incoming(VoiceRoster.Server, null, packet, local: true))
				{
					return true;
				}
				__result = true;
				return false;
			}
			catch (Exception error)
			{
				VoiceRoster.Report("host packet", error);
				__result = true;
				return false;
			}
		}

		private static void Connected(MirrorIgnoranceServer __instance)
		{
			VoiceRoster.Bind(__instance);
		}

		private static void ServerUpdated(MirrorIgnoranceServer __instance)
		{
			if (!VoiceRoster.Enabled)
			{
				return;
			}
			try
			{
				VoiceRoster.Bind(__instance);
				VoiceRoster.Tick();
			}
			catch (Exception error)
			{
				VoiceRoster.Report("server update", error);
			}
		}

		private static void Disconnect()
		{
			VoiceRoster.Reset();
		}

		private static bool TakeCue(PlayerVoicePlaybackControl __instance, ref bool __result)
		{
			if (!HostPlayback.TryTake(__instance))
			{
				return true;
			}
			__result = true;
			return false;
		}

		private static void AudioEffectsUpdated()
		{
			if (!VoiceRoster.Enabled)
			{
				return;
			}
			try
			{
				HostPlayback.SyncEffects();
			}
			catch (Exception error)
			{
				VoiceRoster.Report("native voice effects", error);
			}
		}

		private static void PlaybackDisabling(PlayerVoicePlaybackControl __instance)
		{
			HostPlayback.BeforeDisable(__instance);
		}

		private static void PlaybackDisabled(PlayerVoicePlaybackControl __instance)
		{
			HostPlayback.AfterDisable(__instance);
		}

		private static bool RadioUse(RadioVoiceAssigner __instance, PeckContext context)
		{
			if (replayingRadio)
			{
				return true;
			}
			try
			{
				return VoiceRoster.BeforeUse(__instance, context);
			}
			catch (Exception error)
			{
				VoiceRoster.Report("radio use", error);
				return true;
			}
		}

		internal static void ApplyRadioLocally(RadioVoiceAssigner radio, PeckContext context)
		{
			bool flag = replayingRadio;
			replayingRadio = true;
			try
			{
				if (context.state != 0)
				{
					radio.isWaitingForMessageEnd = false;
				}
				radio.Peck(context);
			}
			finally
			{
				replayingRadio = flag;
			}
		}

		private static void RadioUsed(RadioVoiceAssigner __instance, PeckContext context)
		{
			if (replayingRadio || context == null || !((Object)(object)__instance.trackedStateSystem != (Object)null) || !VoiceRoster.Enabled)
			{
				return;
			}
			try
			{
				VoiceRoster.AfterUse(__instance, context);
			}
			catch (Exception error)
			{
				VoiceRoster.Report("radio activation", error);
			}
		}
	}
	public sealed class VoicePump : MonoBehaviour
	{
		public VoicePump(IntPtr pointer)
			: base(pointer)
		{
		}

		public void Update()
		{
			try
			{
				RelayQuality.Tick();
				Plugin.ApplyPendingSettings();
				MaxConnectionsPatch.ApplyLive();
				EventLoad.Tick();
				VoiceRoster.Tick();
				VoiceRoster.Flush();
			}
			catch (Exception error)
			{
				VoiceRoster.Report("voice pump", error);
			}
		}
	}
	internal static class VoiceRoster
	{
		private sealed class Peer
		{
			internal ClientInfo<MirrorConn> Info;

			internal ushort Id;

			internal string Name;

			internal MirrorConn Transport;

			internal NetworkConnection Connection;

			internal PlayerCharacter Player;

			internal Vector3 Position;

			internal bool Located;

			internal bool Host;

			internal bool Muted;

			internal float LastLoud = -100f;

			internal float Loudness;

			internal readonly Dictionary<ushort, float> Roster = new Dictionary<ushort, float>();

			internal readonly Queue<(float Time, byte[] Data)> Onset = new Queue<(float, byte[])>();
		}

		private readonly struct Route
		{
			internal readonly ushort Source;

			internal readonly Vector3 Position;

			internal readonly float Radius;

			internal readonly bool Positional;

			internal Route(ushort source, Vector3 position, float radius, bool positional)
			{
				//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)
				Source = source;
				Position = position;
				Radius = radius;
				Positional = positional;
			}
		}

		private sealed class PendingUse
		{
			internal RadioVoiceAssigner Radio;

			internal PlayerCharacter Player;

			internal PeckContext Context;

			internal float Until;
		}

		private sealed class RadioRefresh
		{
			internal RadioVoiceAssigner Radio;

			internal PeckContext Context;

			internal string Room;

			internal readonly HashSet<ushort> Listeners = new HashSet<ushort>();

			internal float At;

			internal float Deadline;

			internal bool Reset;

			internal bool Released;
		}

		private sealed class Delayed
		{
			internal readonly ushort Listener;

			internal readonly ushort Source;

			internal readonly float At;

			internal readonly byte[] Data;

			internal readonly string ListenerName;

			internal readonly string SourceName;

			internal readonly IntPtr ListenerConnection;

			internal readonly IntPtr SourceConnection;

			internal Delayed(Peer listener, Peer source, float at, byte[] data)
			{
				Listener = listener.Id;
				Source = source.Id;
				At = at;
				Data = data;
				ListenerName = listener.Name;
				SourceName = source.Name;
				NetworkConnection connection = listener.Connection;
				ListenerConnection = ((connection != null) ? ((Il2CppObjectBase)connection).Pointer : IntPtr.Zero);
				NetworkConnection connection2 = source.Connection;
				SourceConnection = ((connection2 != null) ? ((Il2CppObjectBase)connection2).Pointer : IntPtr.Zero);
			}
		}

		internal const int Slots = 12;

		internal static MirrorIgnoranceServer Server;

		internal static bool Sending;

		private static readonly Dictionary<ushort, Peer> peers = new Dictionary<ushort, Peer>();

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

		private static readonly Dictionary<IntPtr, PendingUse> pendingUses = new Dictionary<IntPtr, PendingUse>();

		private static readonly Dictionary<IntPtr, RadioRefresh> radioRefreshes = new Dictionary<IntPtr, RadioRefresh>();

		private static readonly Dictionary<int, ushort> connections = new Dictionary<int, ushort>();

		private static readonly HashSet<int> handshakes = new HashSet<int>();

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

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

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

		private static readonly List<Route> special = new List<Route>();

		private static readonly HashSet<ushort> pinnedSources = new HashSet<ushort>();

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

		private static readonly List<ClientInfo<MirrorConn>> clientBuffer = new List<ClientInfo<MirrorConn>>();

		private static readonly List<ClientInfo<MirrorConn>> roomBuffer = new List<ClientInfo<MirrorConn>>();

		private static DissonanceComms comms;

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

		private static ushort? hostId;

		private static uint session;

		private static float nextRefresh;

		private static float nextError;

		private static float nextDiagnostic;

		private static bool refreshing;

		private static int publishedRoomCount;

		private static long receivedVoicePackets;

		private static long sentVoicePackets;

		private static readonly Queue<Delayed> delayed = new Queue<Delayed>();

		internal static float Range => Plugin.HearingDistance.Value;

		internal static bool Enabled
		{
			get
			{
				if (VoicePatches.Ready)
				{
					return NetworkServer.active;
				}
				return false;
			}
		}

		internal static DissonanceComms CaptureComms => comms;

		internal static int AllocatedSlots => peers.Values.Where((Peer p) => !p.Host).Sum((Peer p) => p.Roster.Count);

		internal static int PeerCount => peers.Count;

		internal static void Bind(MirrorIgnoranceServer server)
		{
			if (Enabled && server != null && (Server == null || !(((Il2CppObjectBase)Server).Pointer == ((Il2CppObjectBase)server).Pointer) || session != ((BaseServer<MirrorIgnoranceServer, MirrorIgnoranceClient, MirrorConn>)(object)server).SessionId))
			{
				ClearState(clearMute: false);
				Server = server;
				session = ((BaseServer<MirrorIgnoranceServer, MirrorIgnoranceClient, MirrorConn>)(object)server).SessionId;
				comms = Object.FindObjectOfType<DissonanceComms>();
				HostPlayback.ReserveVoices();
				Plugin.Log.LogInfo((object)"[VOICE] Session started: per-listener 12-slot rosters; host reserves one routing slot.");
			}
		}

		internal static void Reset()
		{
			ClearState(clearMute: true);
			Server = null;
			comms = null;
			session = 0u;
			HostPlayback.Clear();
		}

		private static void ClearState(bool clearMute)
		{
			RelayQuality.Reset();
			qualityFormatWarnings.Clear();
			if ((Object)(object)comms != (Object)null)
			{
				foreach (RoomMembership value in joinedRooms.Values)
				{
					try
					{
						comms.Rooms.Leave(value);
					}
					catch
					{
					}
				}
			}
			joinedRooms.Clear();
			roomNames.Clear();
			peers.Clear();
			voiceNames.Clear();
			connections.Clear();
			pendingUses.Clear();
			radioRefreshes.Clear();
			handshakes.Clear();
			special.Clear();
			pinnedSources.Clear();
			mutedNames.Clear();
			hostId = null;
			nextRefresh = (nextDiagnostic = 0f);
			delayed.Clear();
			publishedRoomCount = 0;
			receivedVoicePackets = (sentVoicePackets = 0L);
			if (clearMute)
			{
				muted.Clear();
			}
		}

		private static string MuteKey(PlayerNetworking player)
		{
			if ((Object)(object)player == (Object)null || ((NetworkBehaviour)player).isLocalPlayer)
			{
				return null;
			}
			return MuteKey((NetworkConnection)(object)((NetworkBehaviour)player).connectionToClient);
		}

		private static string MuteKey(NetworkConnection connection)
		{
			if (connection == null || connection.connectionId == 0)
			{
				return null;
			}
			NetworkConnectionToClient obj = ((Il2CppObjectBase)connection).TryCast<NetworkConnectionToClient>();
			string text = ((obj != null) ? obj.address : null);
			if (!string.IsNullOrEmpty(text))
			{
				return "eos:" + text;
			}
			return "session:" + connection.connectionId;
		}

		internal static bool IsMuted(PlayerNetworking player)
		{
			string text = MuteKey(player);
			if (text != null)
			{
				return muted.Contains(text);
			}
			return false;
		}

		internal static bool SetMuted(PlayerNetworking player, bool value)
		{
			if (!Enabled || (Object)(object)player == (Object)null)
			{
				return false;
			}
			string text = MuteKey(player);
			if (text == null)
			{
				return false;
			}
			if (value)
			{
				muted.Add(text);
			}
			else
			{
				muted.Remove(text);
			}
			if (value)
			{
				StopMutedBroadcasts();
				HostPlayback.Discard(player);
			}
			Refresh(immediate: true);
			Plugin.Log.LogInfo((object)("[VOICE] " + (value ? "Muted" : "Unmuted") + " connection " + ((NetworkConnection)((NetworkBehaviour)player).connectionToClient).connectionId + "."));
			return true;
		}

		private static bool IsMuted(Peer peer)
		{
			return peer.Muted;
		}

		internal static bool NameMuted(string name)
		{
			if (name != null)
			{
				return mutedNames.Contains(name);
			}
			return false;
		}

		internal static void ObserveVoice(string name, float amplitude)
		{
			if (name == null || !voiceNames.TryGetValue(name, out var value) || value.Muted || !float.IsFinite(amplitude))
			{
				return;
			}
			value.Loudness = Mathf.Lerp(value.Loudness, Mathf.Max(0f, amplitude), 1f - Mathf.Exp((0f - Time.unscaledDeltaTime) * 8f));
			if (!(amplitude < Plugin.QuietThreshold.Value))
			{
				float unscaledTime = Time.unscaledTime;
				if (unscaledTime - value.LastLoud >= 1f)
				{
					nextRefresh = 0f;
				}
				value.LastLoud = unscaledTime;
			}
		}

		internal static void Tick()
		{
			if (!Enabled)
			{
				if (Server != null)
				{
					Reset();
				}
			}
			else
			{
				if (Server == null)
				{
					return;
				}
				try
				{
					if (((BaseServer<MirrorIgnoranceServer, MirrorIgnoranceClient, MirrorConn>)(object)Server).SessionId != session)
					{
						Bind(Server);
					}
					HostPlayback.Tick();
					if (Time.unscaledTime >= nextRefresh)
					{
						Refresh(immediate: false);
					}
					RefreshRadioProviders();
				}
				catch (Exception error)
				{
					Report("update", error);
				}
			}
		}

		internal static void Report(string operation, Exception error)
		{
			if (!(Time.unscaledTime < nextError))
			{
				nextError = Time.unscaledTime + 5f;
				Plugin.Log.LogError((object)("[VOICE] " + operation + ": " + error));
			}
		}

		internal static void Refresh(bool immediate)
		{
			//IL_0435: Unknown result type (might be due to invalid IL or missing references)
			//IL_043a: Unknown result type (might be due to invalid IL or missing references)
			if (Server == null || refreshing || (!immediate && Time.unscaledTime < nextRefresh))
			{
				return;
			}
			refreshing = true;
			try
			{
				float unscaledTime = Time.unscaledTime;
				nextRefresh = unscaledTime + 0.1f;
				if ((Object)(object)comms == (Object)null)
				{
					comms = Object.FindObjectOfType<DissonanceComms>();
				}
				clientBuffer.Clear();
				((BaseClientCollection<MirrorConn>)(object)((BaseServer<MirrorIgnoranceServer, MirrorIgnoranceClient, MirrorConn>)(object)Server)._clients).GetClients(clientBuffer);
				HashSet<ushort> alive = new HashSet<ushort>();
				connections.Clear();
				voiceNames.Clear();
				mutedNames.Clear();
				hostId = null;
				for (int i = 0; i < clientBuffer.Count; i++)
				{
					ClientInfo<MirrorConn> val = clientBuffer[i];
					if (val == null || !val.IsConnected)
					{
						continue;
					}
					alive.Add(val.PlayerId);
					if (!peers.TryGetValue(val.PlayerId, out var value))
					{
						value = (peers[val.PlayerId] = new Peer());
					}
					string playerName = val.PlayerName;
					MirrorConn connection = val.Connection;
					NetworkConnection connection2 = connection.Connection;
					if (value.Info != null)
					{
						if (!(value.Name != playerName))
						{
							NetworkConnection connection3 = value.Connection;
							if (!(((connection3 != null) ? new IntPtr?(((Il2CppObjectBase)connection3).Pointer) : ((IntPtr?)null)) != ((connection2 != null) ? new IntPtr?(((Il2CppObjectBase)connection2).Pointer) : ((IntPtr?)null))))
							{
								goto IL_01df;
							}
						}
						value.Onset.Clear();
						value.LastLoud = -100f;
						value.Loudness = 0f;
					}
					goto IL_01df;
					IL_01df:
					value.Info = val;
					value.Located = false;
					value.Player = null;
					value.Id = val.PlayerId;
					value.Name = playerName;
					voiceNames[value.Name] = value;
					value.Transport = connection;
					value.Connection = connection2;
					value.Host = (Object)(object)comms != (Object)null && value.Name == comms.LocalPlayerName;
					if (value.Host)
					{
						hostId = val.PlayerId;
					}
					string text = MuteKey(connection2);
					value.Muted = text != null && muted.Contains(text);
					if (value.Muted)
					{
						mutedNames.Add(value.Name);
					}
					if (connection2 != null)
					{
						connections[connection2.connectionId] = val.PlayerId;
					}
					for (int j = 0; j < val.Rooms.Count; j++)
					{
						roomNames.Add(val.Rooms[j]);
					}
				}
				ushort[] array = peers.Keys.Where((ushort id) => !alive.Contains(id)).ToArray();
				foreach (ushort key in array)
				{
					peers.Remove(key);
				}
				List<PlayerCharacter> allPlayerCharacters = PlayerCharacter.allPlayerCharacters;
				if (allPlayerCharacters != null)
				{
					for (int num2 = 0; num2 < allPlayerCharacters.Count; num2++)
					{
						PlayerCharacter val2 = allPlayerCharacters[num2];
						PlayerNetworking val3 = ((val2 != null) ? val2.playerNetworking : null);
						if ((Object)(object)val3 == (Object)null)
						{
							continue;
						}
						Peer value2 = null;
						ushort value3;
						if (((NetworkBehaviour)val3).isLocalPlayer && hostId.HasValue)
						{
							peers.TryGetValue(hostId.Value, out value2);
						}
						else if (((NetworkBehaviour)val3).connectionToClient != null && connections.TryGetValue(((NetworkConnection)((NetworkBehaviour)val3).connectionToClient).connectionId, out value3))
						{
							peers.TryGetValue(value3, out value2);
						}
						if (value2 != null)
						{
							value2.Player = val2;
							value2.Position = ((Component)val2).transform.position;
							value2.Located = true;
							value2.Muted |= val3.isMuted;
							if (value2.Muted)
							{
								mutedNames.Add(value2.Name);
							}
							while (value2.Onset.Count > 0 && unscaledTime - value2.Onset.Peek().Time > 0.25f)
							{
								value2.Onset.Dequeue();
							}
						}
					}
				}
				StopMutedBroadcasts();
				BuildSpecialRoutes(unscaledTime);
				if ((Object)(object)comms != (Object)null)
				{
					foreach (string roomName in roomNames)
					{
						if (!comms.Rooms.Contains(roomName) && !joinedRooms.ContainsKey(roomName))
						{
							joinedRooms[roomName] = comms.Rooms.Join(roomName);
						}
					}
				}
				bool flag = publishedRoomCount != roomNames.Count;
				foreach (Peer value5 in peers.Values)
				{
					if (!value5.Host && value5.Connection != null && handshakes.Contains(value5.Connection.connectionId))
					{
						if (flag && hostId.HasValue && value5.Roster.ContainsKey(hostId.Value) && peers.TryGetValue(hostId.Value, out var value4))
						{
							SendState(value5, value4);
						}
						UpdateRoster(value5, unscaledTime);
					}
				}
				publishedRoomCount = roomNames.Count;
				if (unscaledTime >= nextDiagnostic)
				{
					nextDiagnostic = unscaledTime + 15f;
					Plugin.Log.LogInfo((object)("[VOICE] Routing: peers=" + peers.Count + ", host=" + (hostId?.ToString() ?? "missing") + ", located=" + peers.Values.Count((Peer p) => p.Located) + ", guest cues=" + AllocatedSlots + ", audio packets in/sent=" + receivedVoicePackets + "/" + sentVoicePackets + ", host playbacks=" + HostPlayback.Count + "."));
				}
			}
			finally
			{
				refreshing = false;
			}
		}

		private static void BuildSpecialRoutes(float now)
		{
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_029c: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a1: Invalid comparison between Unknown and I4
			//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: Invalid comparison between Unknown and I4
			//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
			special.Clear();
			pinnedSources.Clear();
			IntPtr[] array = (from p in pendingUses
				where p.Value.Until < now || (Object)(object)p.Value.Radio == (Object)null
				select p.Key).ToArray();
			foreach (IntPtr key in array)
			{
				pendingUses.Remove(key);
			}
			List<RadioVoiceAssigner> allRadios = RadioVoiceAssigner.allRadios;
			if (allRadios == null)
			{
				return;
			}
			Dictionary<string, List<(RadioVoiceAssigner, PlayerCharacter)>> dictionary = new Dictionary<string, List<(RadioVoiceAssigner, PlayerCharacter)>>(StringComparer.Ordinal);
			for (int num2 = 0; num2 < allRadios.Count; num2++)
			{
				RadioVoiceAssigner val = allRadios[num2];
				if ((Object)(object)val == (Object)null || !((Behaviour)val).isActiveAndEnabled || val.isInDeadZone || string.IsNullOrEmpty(val.roomName))
				{
					continue;
				}
				PlayerCharacter val2 = val.latestBroadcastPlayer;
				bool flag = val.isBroadcasting;
				if (pendingUses.TryGetValue(((Il2CppObjectBase)val).Pointer, out var value))
				{
					flag = true;
					val2 = value.Player;
				}
				if (flag && !((Object)(object)val2 == (Object)null) && !IsMuted(val2.playerNetworking))
				{
					if (!dictionary.TryGetValue(val.roomName, out var value2))
					{
						value2 = (dictionary[val.roomName] = new List<(RadioVoiceAssigner, PlayerCharacter)>());
					}
					value2.Add((val, val2));
					roomNames.Add(val.roomName);
				}
			}
			float num4 = default(float);
			foreach (KeyValuePair<string, List<(RadioVoiceAssigner, PlayerCharacter)>> item in dictionary)
			{
				if (item.Value.Count != 1)
				{
					continue;
				}
				(RadioVoiceAssigner Radio, PlayerCharacter Player) broadcaster = item.Value[0];
				Peer peer = peers.Values.FirstOrDefault((Peer p) => (Object)(object)p.Player == (Object)(object)broadcaster.Player);
				if (peer == null)
				{
					continue;
				}
				for (int num3 = 0; num3 < allRadios.Count; num3++)
				{
					RadioVoiceAssigner val3 = allRadios[num3];
					if (!((Object)(object)val3 == (Object)null) && ((Behaviour)val3).isActiveAndEnabled && !val3.isInDeadZone && !(val3.roomName != item.Key) && !((Object)(object)val3.voicePlayer == (Object)null) && (!((Object)(object)val3 == (Object)(object)broadcaster.Radio) || val3.fullDuplex))
					{
						VoicePlayer voicePlayer = val3.voicePlayer;
						VoicePlayerType cachedVoiceType = val3._cachedVoiceType;
						float radius = (((int)cachedVoiceType == 3) ? 800f : (((int)cachedVoiceType == 4) ? 30f : 20f));
						bool positional = true;
						if (voicePlayer.GetX((XAxisType)170, ref num4))
						{
							positional = num4 > 0.001f;
						}
						special.Add(new Route(peer.Id, ((Component)voicePlayer).transform.position, radius, positional));
						pinnedSources.Add(peer.Id);
					}
				}
			}
		}

		private static bool SpecialAudible(ushort source, Peer listener)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			foreach (Route item in special)
			{
				if (item.Source == source && (!item.Positional || DistanceSquared(item.Position, listener.Position) < item.Radius * item.Radius))
				{
					return true;
				}
			}
			return false;
		}

		private static bool SpecialPinned(ushort source)
		{
			return pinnedSources.Contains(source);
		}

		private static bool Audible(Peer source, Peer listener)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (listener.Located && source.Located)
			{
				if (!(DistanceSquared(source.Position, listener.Position) < Range * Range))
				{
					return SpecialAudible(source.Id, listener);
				}
				return true;
			}
			return false;
		}

		private static float DistanceSquared(Vector3 first, Vector3 second)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			float num = first.x - second.x;
			float num2 = first.y - second.y;
			float num3 = first.z - second.z;
			return num * num + num2 * num2 + num3 * num3;
		}

		private static void UpdateRoster(Peer listener, float now)
		{
			HashSet<ushort> desired = new HashSet<ushort>();
			if (hostId.HasValue && hostId.Value != listener.Id)
			{
				desired.Add(hostId.Value);
			}
			List<(Peer, bool, float)> list = (from p in peers.Values
				where p != listener && !p.Host && p.Located && listener.Located && !IsMuted(p)
				select (Peer: p, Special: SpecialAudible(p.Id, listener), Distance: DistanceSquared(p.Position, listener.Position)) into p
				where p.Special || p.Distance < Range * Range
				select p).ToList();
			int num = 12 - desired.Count;
			bool congested = list.Count > num;
			foreach (var item in (from p in list
				where !congested || p.Special || now - p.Peer.LastLoud < 1f
				orderby p.Special descending, (!congested) ? 0f : Mathf.Round(p.Peer.Loudness * 1000f) descending, p.Distance, listener.Roster.ContainsKey(p.Peer.Id) descending, p.Peer.Id
				select p).Take(num))
			{
				desired.Add(item.Item1.Id);
			}
			ushort[] array = listener.Roster.Keys.Where((ushort id) => !desired.Contains(id)).ToArray();
			foreach (ushort num3 in array)
			{
				SendRemove(listener, num3);
				listener.Roster.Remove(num3);
			}
			foreach (ushort item2 in desired)
			{
				if (listener.Roster.ContainsKey(item2) || !peers.TryGetValue(item2, out var value))
				{
					continue;
				}
				SendState(listener, value);
				listener.Roster[item2] = now + 0.15f;
				if (SpecialPinned(item2))
				{
					ScheduleRadioRefresh(value, listener, now);
				}
				foreach (var item3 in value.Onset)
				{
					if (now - item3.Time <= 0.2f)
					{
						QueueOnset(listener, value, item3.Data, now + 0.15f);
					}
				}
			}
		}

		private static PeckContext CopyContext(PeckContext context, int state)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: 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_002b: Expected O, but got Unknown
			return new PeckContext(state)
			{
				playerIdentity = context.playerIdentity,
				propIdentity = context.propIdentity,
				actionNumber = context.actionNumber
			};
		}

		private static bool SameUse(PeckContext first, PeckContext second)
		{
			if (first != null && second != null && first.state == second.state && first.actionNumber == second.actionNumber)
			{
				NetworkIdentity playerIdentity = first.playerIdentity;
				IntPtr? intPtr = ((playerIdentity != null) ? new IntPtr?(((Il2CppObjectBase)playerIdentity).Pointer) : ((IntPtr?)null));
				NetworkIdentity playerIdentity2 = second.playerIdentity;
				if (intPtr == ((playerIdentity2 != null) ? new IntPtr?(((Il2CppObjectBase)playerIdentity2).Pointer) : ((IntPtr?)null)))
				{
					NetworkIdentity propIdentity = first.propIdentity;
					IntPtr? intPtr2 = ((propIdentity != null) ? new IntPtr?(((Il2CppObjectBase)propIdentity).Pointer) : ((IntPtr?)null));
					NetworkIdentity propIdentity2 = second.propIdentity;
					intPtr = ((propIdentity2 != null) ? new IntPtr?(((Il2CppObjectBase)propIdentity2).Pointer) : ((IntPtr?)null));
					if (intPtr2.HasValue != intPtr.HasValue)
					{
						return false;
					}
					if (!intPtr2.HasValue)
					{
						return true;
					}
					return intPtr2.GetValueOrDefault() == intPtr.GetValueOrDefault();
				}
			}
			return false;
		}

		private static RadioRefresh QueueRadioRefresh(RadioVoiceAssigner radio, PeckContext context, float at)
		{
			if (!radioRefreshes.TryGetValue(((Il2CppObjectBase)radio).Pointer, out var value) || value.Room != radio.roomName || !SameUse(value.Context, context))
			{
				Dictionary<IntPtr, RadioRefresh> dictionary = radioRefreshes;
				IntPtr pointer = ((Il2CppObjectBase)radio).Pointer;
				RadioRefresh obj = new RadioRefresh
				{
					Radio = radio,
					Context = CopyContext(context, context.state),
					Room = radio.roomName,
					At = at
				};
				value = obj;
				dictionary[pointer] = obj;
			}
			return value;
		}

		private static void ScheduleRadioRefresh(Peer source, Peer listener, float now)
		{
			List<RadioVoiceAssigner> allRadios = RadioVoiceAssigner.allRadios;
			if (allRadios == null)
			{
				return;
			}
			for (int i = 0; i < allRadios.Count; i++)
			{
				RadioVoiceAssigner val = allRadios[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.trackedStateSystem == (Object)null))
				{
					pendingUses.TryGetValue(((Il2CppObjectBase)val).Pointer, out var value);
					PlayerCharacter obj = value?.Player ?? val.latestBroadcastPlayer;
					PeckContext val2 = value?.Context ?? val.trackedStateSystem.currentPeckContext;
					if (!((Object)(object)obj != (Object)(object)source.Player) && (val.isBroadcasting || value != null) && val2 != null && val2.state != 0)
					{
						QueueRadioRefresh(val, val2, now + 0.25f).Listeners.Add(listener.Id);
					}
				}
			}
		}

		private static void RefreshRadioProviders()
		{
			float now = Time.unscaledTime;
			IntPtr[] array = (from p in radioRefreshes
				where p.Value.At <= now
				select p.Key).ToArray();
			foreach (IntPtr key in array)
			{
				RadioRefresh radioRefresh = radioRefreshes[key];
				RadioVoiceAssigner radio = radioRefresh.Radio;
				TrackedPeckState val = ((radio != null) ? radio.trackedStateSystem : null);
				PlayerCharacter playerCharacter = radioRefresh.Context.GetPlayerCharacter();
				if ((Object)(object)radio == (Object)null || !((Behaviour)radio).isActiveAndEnabled || radio.roomName != radioRefresh.Room || (Object)(object)val == (Object)null || !SameUse(val.currentPeckContext, radioRefresh.Context) || (Object)(object)playerCharacter == (Object)null || IsMuted(playerCharacter.playerNetworking))
				{
					radioRefreshes.Remove(key);
					continue;
				}
				if (radioRefresh.Reset && !radioRefresh.Released)
				{
					radioRefresh.Released = true;
					radioRefresh.At = now + 0.25f;
					radioRefresh.Deadline = now + 2f;
					PeckContext val2 = CopyContext(radioRefresh.Context, 0);
					val2.actionNumber = -1;
					VoicePatches.ApplyRadioLocally(radio, val2);
					foreach (Peer value3 in