Decompiled source of SSMPVoiceChat v0.1.3

plugins/SSMPVoiceChat.dll

Decompiled a week ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
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.Threading;
using BepInEx;
using BepInEx.Configuration;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using OpenTK;
using OpenTK.Audio.OpenAL;
using SSMP.Api.Client;
using SSMP.Api.Client.Networking;
using SSMP.Api.Command;
using SSMP.Api.Command.Client;
using SSMP.Api.Command.Server;
using SSMP.Api.Server;
using SSMP.Api.Server.Networking;
using SSMP.Game;
using SSMP.Game.Settings;
using SSMP.Logging;
using SSMP.Math;
using SSMP.Networking.Packet;
using SSMP.Networking.Packet.Data;
using SsmpVoiceChat.Client;
using SsmpVoiceChat.Client.Voice;
using SsmpVoiceChat.Common;
using SsmpVoiceChat.Common.Command;
using SsmpVoiceChat.Common.Net;
using SsmpVoiceChat.Common.Opus;
using SsmpVoiceChat.Common.RNNoise;
using SsmpVoiceChat.Common.WebRtcVad;
using SsmpVoiceChat.Server;
using TMProOld;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: Guid("0B95F3A6-6628-4FF5-8574-3CC58419572D")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SSMPVoiceChat")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: AssemblyInformationalVersion("0.1.3+7445a96e9285a8f0fde3d69c167c83d20ab275f9")]
[assembly: AssemblyProduct("SSMPVoiceChat")]
[assembly: AssemblyTitle("SSMPVoiceChat")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/BobbyTheCatfish/SSMP.VoiceChat")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.3.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace SsmpVoiceChat
{
	public static class Identifier
	{
		public const string AddonName = "ProximityChat";

		public const string AddonVersion = "0.1.3";

		public const uint ApiVersion = 1u;
	}
}
namespace SsmpVoiceChat.Server
{
	public class ServerNetManager
	{
		private readonly IServerAddonNetworkSender<ClientPacketId> _netSender;

		public event Action<ushort, byte[]> VoiceEvent;

		public ServerNetManager(ServerAddon addon, INetServer netServer)
		{
			_netSender = netServer.GetNetworkSender<ClientPacketId>(addon);
			netServer.GetNetworkReceiver<ServerPacketId>(addon, (Func<ServerPacketId, IPacketData>)InstantiatePacket).RegisterPacketHandler<ServerVoicePacket>(ServerPacketId.Voice, (GenericServerPacketHandler<ServerVoicePacket>)delegate(ushort id, ServerVoicePacket packet)
			{
				this.VoiceEvent?.Invoke(id, packet.VoiceData);
			});
		}

		public void SendVoiceData(ushort receiver, ushort sender, byte[] data, bool proximity)
		{
			if (data.Length > 65535)
			{
				ServerVoiceChat.Logger.Info("Voice data exceeds max size!");
				return;
			}
			_netSender.SendCollectionData<ClientVoicePacket>(ClientPacketId.Voice, new ClientVoicePacket
			{
				Id = sender,
				Proximity = proximity,
				VoiceData = data
			}, receiver);
		}

		private static IPacketData InstantiatePacket(ServerPacketId packetId)
		{
			if (packetId == ServerPacketId.Voice)
			{
				return (IPacketData)(object)new PacketDataCollection<ServerVoicePacket>();
			}
			return null;
		}
	}
	public class ServerSettings
	{
		private const string FileName = "voicechat_server_settings.json";

		[JsonProperty("proximity_based_volume")]
		[SettingAlias(new string[] { "proximity", "prox" })]
		public bool ProximityBasedVolume { get; set; } = true;


		[JsonProperty("team_voices_globally")]
		[SettingAlias(new string[] { "teamglobal", "teamglobally" })]
		public bool TeamVoicesGlobally { get; set; } = true;


		[JsonProperty("team_voices_only")]
		[SettingAlias(new string[] { "teamonly" })]
		public bool TeamVoicesOnly { get; set; }

		public void SaveToFile()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				return;
			}
			string path = Path.Combine(directoryName, "voicechat_server_settings.json");
			string contents = JsonConvert.SerializeObject((object)this, (Formatting)1);
			try
			{
				File.WriteAllText(path, contents);
			}
			catch (Exception arg)
			{
				ServerVoiceChat.Logger.Error($"Could not write server settings to file:\n{arg}");
			}
		}

		public static ServerSettings LoadFromFile()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				return new ServerSettings();
			}
			string path = Path.Combine(directoryName, "voicechat_server_settings.json");
			if (!File.Exists(path))
			{
				ServerSettings serverSettings = new ServerSettings();
				serverSettings.SaveToFile();
				return serverSettings;
			}
			try
			{
				return JsonConvert.DeserializeObject<ServerSettings>(File.ReadAllText(path)) ?? new ServerSettings();
			}
			catch (Exception arg)
			{
				ServerVoiceChat.Logger.Error($"Could not load server settings from file:\n{arg}");
				return new ServerSettings();
			}
		}
	}
	public class ServerVoiceChat
	{
		private readonly IServerApi _serverApi;

		private readonly ServerNetManager _netManager;

		private readonly ServerSettings _settings;

		private readonly HashSet<ushort> _broadcasters;

		public static ILogger Logger { get; private set; }

		public ServerVoiceChat(ServerAddon addon, IServerApi serverApi, ILogger logger)
		{
			Logger = logger;
			_serverApi = serverApi;
			_netManager = new ServerNetManager(addon, serverApi.NetServer);
			_settings = ServerSettings.LoadFromFile();
			_broadcasters = new HashSet<ushort>();
		}

		public void Initialize()
		{
			((ICommandManager<IServerCommand>)(object)_serverApi.CommandManager).RegisterCommand((IServerCommand)(object)new ServerVoiceChatCommand(_settings, _broadcasters));
			_netManager.VoiceEvent += OnVoice;
		}

		private void OnVoice(ushort id, byte[] data)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Invalid comparison between Unknown and I4
			IServerPlayer val = default(IServerPlayer);
			if (!_serverApi.ServerManager.TryGetPlayer(id, ref val))
			{
				Logger.Warn($"Could not find player '{id}' for received voice data");
				return;
			}
			Team team = val.Team;
			foreach (IServerPlayer player in _serverApi.ServerManager.Players)
			{
				if (val == player)
				{
					continue;
				}
				if (_broadcasters.Contains(val.Id))
				{
					_netManager.SendVoiceData(player.Id, val.Id, data, proximity: false);
					continue;
				}
				bool flag = team == player.Team && (int)team > 0;
				if (!_settings.TeamVoicesOnly || flag)
				{
					if (_settings.TeamVoicesGlobally && flag)
					{
						_netManager.SendVoiceData(player.Id, val.Id, data, proximity: false);
					}
					else if (!(val.CurrentScene != player.CurrentScene))
					{
						_netManager.SendVoiceData(player.Id, val.Id, data, _settings.ProximityBasedVolume);
					}
				}
			}
		}
	}
	public class ServerVoiceChatCommand : IServerCommand, ICommand
	{
		private readonly ServerSettings _settings;

		private readonly HashSet<ushort> _broadcasters;

		public string Trigger => "/voicechatserver";

		public string[] Aliases => new string[1] { "/vcs" };

		public bool AuthorizedOnly => true;

		public ServerVoiceChatCommand(ServerSettings settings, HashSet<ushort> broadcasters)
		{
			_settings = settings;
			_broadcasters = broadcasters;
		}

		public void Execute(ICommandSender commandSender, string[] args)
		{
			ICommandSender commandSender2 = commandSender;
			if (args.Length < 2)
			{
				SendUsage();
				return;
			}
			string text = args[1];
			if (text == "set")
			{
				HandleSet(commandSender2, args);
			}
			else if (text == "broadcast")
			{
				HandleBroadcast(commandSender2);
			}
			else
			{
				SendUsage();
			}
			void SendUsage()
			{
				commandSender2.SendMessage("Invalid usage: " + Trigger + " <set|broadcast>");
			}
		}

		private void HandleSet(ICommandSender commandSender, string[] args)
		{
			CommandUtil.HandleSetCommand(Trigger, args, _settings, (Action<string>)commandSender.SendMessage, (Action)delegate
			{
				_settings.SaveToFile();
			}, requireSettingAliasAttribute: false);
		}

		private void HandleBroadcast(ICommandSender commandSender)
		{
			IPlayerCommandSender val = (IPlayerCommandSender)(object)((commandSender is IPlayerCommandSender) ? commandSender : null);
			if (val == null)
			{
				commandSender.SendMessage("Cannot execute this command as a non-player");
				return;
			}
			ushort id = val.Id;
			if (_broadcasters.Contains(id))
			{
				_broadcasters.Remove(id);
				((ICommandSender)val).SendMessage("You are no longer broadcasting your voice");
			}
			else
			{
				_broadcasters.Add(id);
				((ICommandSender)val).SendMessage("You are now broadcasting your voice");
			}
		}
	}
	public class VoiceChatServerAddon : ServerAddon
	{
		protected override string Name => "ProximityChat";

		protected override string Version => "0.1.3";

		public override uint ApiVersion => 1u;

		public override bool NeedsNetwork => true;

		public override void Initialize(IServerApi serverApi)
		{
			new ServerVoiceChat((ServerAddon)(object)this, serverApi, ((ServerAddon)this).Logger).Initialize();
		}
	}
}
namespace SsmpVoiceChat.Common
{
	public static class DataUtils
	{
		private const float FloatShortScale = 32767f;

		private const float FloatClip = 32766f;

		private const float FloatShortScalingFactor = 3.051851E-05f;

		public static short[] FloatsToShortsNormalized(float[] audioData)
		{
			short[] array = new short[audioData.Length];
			for (int i = 0; i < audioData.Length; i++)
			{
				array[i] = (short)Math.Max(Math.Min(audioData[i] * 32767f, 32766f), -32767f);
			}
			return array;
		}

		public static byte[] ShortsToBytes(short[] shorts)
		{
			byte[] array = new byte[shorts.Length * 2];
			for (int i = 0; i < shorts.Length; i++)
			{
				short num = shorts[i];
				array[i * 2] = (byte)((uint)num & 0xFFu);
				array[i * 2 + 1] = (byte)((uint)(num >> 8) & 0xFFu);
			}
			return array;
		}

		public static short[] BytesToShorts(byte[] bytes)
		{
			if (bytes.Length % 2 != 0)
			{
				throw new ArgumentException("Byte array length must be even", "bytes");
			}
			short[] array = new short[bytes.Length / 2];
			for (int i = 0; i < bytes.Length; i += 2)
			{
				byte b = bytes[i];
				byte b2 = bytes[i + 1];
				array[i / 2] = (short)(((b2 & 0xFF) << 8) | (b & 0xFF));
			}
			return array;
		}
	}
	public static class LibraryLoader
	{
		private static readonly List<IntPtr> Libraries = new List<IntPtr>();

		[DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true)]
		private static extern IntPtr LoadLibrary(string lpFileName);

		[DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true)]
		private static extern bool FreeLibrary(IntPtr module);

		[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
		private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

		[DllImport("dl.so.2", CharSet = CharSet.Ansi)]
		private static extern IntPtr dlopen(string filename, int flags);

		[DllImport("dl.so.2", CharSet = CharSet.Ansi)]
		private static extern void dlclose(IntPtr module);

		[DllImport("dl.so.2", CharSet = CharSet.Ansi)]
		private static extern IntPtr dlsym(IntPtr handle, string symbol);

		public static void UnloadAll()
		{
			foreach (IntPtr library in Libraries)
			{
				Free(library);
			}
		}

		internal static IntPtr Load(string fileName)
		{
			IntPtr intPtr = (PlatformDetails.IsWindows ? LoadLibrary(fileName) : dlopen(fileName, 1));
			Libraries.Add(intPtr);
			return intPtr;
		}

		internal static bool Free(IntPtr module)
		{
			if (PlatformDetails.IsWindows)
			{
				return FreeLibrary(module);
			}
			dlclose(module);
			return true;
		}

		internal static IntPtr ResolveSymbol(IntPtr image, string symbol)
		{
			if (!PlatformDetails.IsWindows)
			{
				return dlsym(image, symbol);
			}
			return GetProcAddress(image, symbol);
		}
	}
	public static class PlatformDetails
	{
		public static bool IsMac { get; private set; }

		public static bool IsWindows { get; private set; }

		static PlatformDetails()
		{
			if (Directory.Exists("/Applications") && Directory.Exists("/System") && Directory.Exists("/Users") && Directory.Exists("/Volumes"))
			{
				IsMac = true;
			}
			if (Environment.OSVersion.Platform == PlatformID.Win32NT || Environment.OSVersion.Platform == PlatformID.Win32Windows)
			{
				IsWindows = true;
			}
		}
	}
}
namespace SsmpVoiceChat.Common.WebRtcVad
{
	public class NativeMethods
	{
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate IntPtr Vad_Create_delegate();

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int Vad_Init_delegate(IntPtr vadInst);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int Vad_SetMode_delegate(IntPtr vadInst, int mode);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int Vad_ValidRateAndFrameLength_delegate(int rate, UIntPtr frameLength);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int Vad_Process_delegate(IntPtr vadInst, int fs, IntPtr audioFrame, UIntPtr frameLength);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void Vad_Free_delegate(IntPtr vadInst);

		internal static Vad_Create_delegate Vad_Create;

		internal static Vad_Init_delegate Vad_Init;

		internal static Vad_SetMode_delegate Vad_SetMode;

		internal static Vad_ValidRateAndFrameLength_delegate Vad_ValidRateAndFrameLength;

		internal static Vad_Process_delegate Vad_Process;

		internal static Vad_Free_delegate Vad_Free;

		static NativeMethods()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				ClientVoiceChat.Logger.Error("Could not get path of executing assembly, cannot initialize NativeMethods for Web RTC VAD");
				return;
			}
			IntPtr intPtr = (PlatformDetails.IsMac ? LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Mac", "libwebrtcvad.dylib")) : ((!PlatformDetails.IsWindows) ? LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Linux", "libwebrtcvad.so")) : LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Windows", "webrtcvad.dll"))));
			if (!(intPtr != IntPtr.Zero))
			{
				return;
			}
			FieldInfo[] fields = typeof(NativeMethods).GetFields(BindingFlags.Static | BindingFlags.NonPublic);
			foreach (FieldInfo obj in fields)
			{
				string name = obj.Name;
				Type fieldType = obj.FieldType;
				IntPtr intPtr2 = LibraryLoader.ResolveSymbol(intPtr, name);
				if (intPtr2 == IntPtr.Zero)
				{
					throw new Exception("Could not resolve symbol \"" + name + "\"");
				}
				obj.SetValue(null, Marshal.GetDelegateForFunctionPointer(intPtr2, fieldType));
			}
		}
	}
	public enum OperatingMode
	{
		HighQuality,
		LowBitrate,
		Aggressive,
		VeryAggressive
	}
	public class WebRtcVad : IDisposable
	{
		private IntPtr _handle;

		private int _sampleRate;

		private int _frameLength;

		private OperatingMode _mode;

		private bool _disposed;

		public int SampleRate
		{
			get
			{
				return _sampleRate;
			}
			set
			{
				if (!ValidateRateAndFrameLength(value, _frameLength))
				{
					throw new InvalidOperationException("Invalid sample rate");
				}
				_sampleRate = value;
			}
		}

		public int FrameLength
		{
			get
			{
				return _frameLength;
			}
			set
			{
				if (!ValidateRateAndFrameLength(_sampleRate, value))
				{
					throw new InvalidOperationException("Invalid frame length");
				}
				_frameLength = value;
			}
		}

		public OperatingMode OperatingMode
		{
			get
			{
				return _mode;
			}
			set
			{
				if (NativeMethods.Vad_SetMode(_handle, (int)value) != 0)
				{
					throw new InvalidOperationException("Invalid operating mode specified");
				}
				_mode = value;
			}
		}

		public WebRtcVad()
		{
			_sampleRate = 48000;
			_frameLength = 20;
			_mode = OperatingMode.HighQuality;
			_handle = NativeMethods.Vad_Create();
			if (NativeMethods.Vad_Init(_handle) != 0)
			{
				throw new InvalidOperationException("Could not initialize WebRtcVad");
			}
		}

		public bool HasSpeech(short[] audioFrame)
		{
			return HasSpeech(audioFrame, _sampleRate, _frameLength);
		}

		private unsafe bool HasSpeech(short[] audioFrame, int sampleRate, int frameLength)
		{
			int num = CalculateSamples(sampleRate, frameLength);
			int num2;
			fixed (short* ptr = audioFrame)
			{
				num2 = NativeMethods.Vad_Process(_handle, sampleRate, (IntPtr)ptr, (UIntPtr)(ulong)num);
			}
			return num2 == 1;
		}

		private bool ValidateRateAndFrameLength(int sampleRate, int frameLength)
		{
			int num = CalculateSamples(sampleRate, frameLength);
			return NativeMethods.Vad_ValidRateAndFrameLength(sampleRate, (UIntPtr)(ulong)num) == 0;
		}

		private static int CalculateSamples(int sampleRate, int frameLength)
		{
			return sampleRate / 1000 * frameLength;
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				if (_handle != IntPtr.Zero)
				{
					NativeMethods.Vad_Free(_handle);
					_handle = IntPtr.Zero;
				}
				_disposed = true;
			}
		}
	}
}
namespace SsmpVoiceChat.Common.RNNoise
{
	public class NativeMethods
	{
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int rnnoise_get_frame_size_delegate();

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate IntPtr rnnoise_create_delegate(IntPtr model);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate IntPtr rnnoise_destroy_delegate(IntPtr denoiseState);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate IntPtr rnnoise_process_frame_delegate(IntPtr denoiseState, float[] processed, float[] input);

		internal static rnnoise_get_frame_size_delegate rnnoise_get_frame_size;

		internal static rnnoise_create_delegate rnnoise_create;

		internal static rnnoise_destroy_delegate rnnoise_destroy;

		internal static rnnoise_process_frame_delegate rnnoise_process_frame;

		static NativeMethods()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				ClientVoiceChat.Logger.Error("Could not get path of executing assembly, cannot initialize NativeMethods for RNNoise");
				return;
			}
			IntPtr intPtr = (PlatformDetails.IsMac ? LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Mac", "librnnoise.dylib")) : ((!PlatformDetails.IsWindows) ? LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Linux", "librnnoise.so")) : LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Windows", "rnnoise.dll"))));
			if (intPtr != IntPtr.Zero)
			{
				ClientVoiceChat.Logger.Info("RNNoise library loaded");
				FieldInfo[] fields = typeof(NativeMethods).GetFields(BindingFlags.Static | BindingFlags.NonPublic);
				foreach (FieldInfo obj in fields)
				{
					string name = obj.Name;
					Type fieldType = obj.FieldType;
					IntPtr intPtr2 = LibraryLoader.ResolveSymbol(intPtr, name);
					if (intPtr2 == IntPtr.Zero)
					{
						throw new Exception("Could not resolve symbol \"" + name + "\"");
					}
					obj.SetValue(null, Marshal.GetDelegateForFunctionPointer(intPtr2, fieldType));
				}
			}
			else
			{
				ClientVoiceChat.Logger.Error("RNNoise library could not be loaded");
			}
		}
	}
	public class RNNoise : IDisposable
	{
		private IntPtr _handle;

		private bool _disposed;

		public RNNoise()
		{
			_handle = NativeMethods.rnnoise_create(IntPtr.Zero);
		}

		public short[] ProcessFrame(short[] data)
		{
			int num = NativeMethods.rnnoise_get_frame_size();
			float[] array = new float[data.Length];
			for (int i = 0; i < data.Length; i++)
			{
				array[i] = data[i];
			}
			float[] array2 = new float[array.Length];
			for (int j = 0; j < array.Length; j += num)
			{
				float[] array3 = new float[num];
				for (int k = 0; k < num; k++)
				{
					array3[k] = array[j + k];
				}
				float[] array4 = new float[num];
				NativeMethods.rnnoise_process_frame(_handle, array4, array3);
				for (int l = 0; l < num; l++)
				{
					array2[j + l] = array4[l];
				}
			}
			float num2 = float.MinValue;
			float num3 = float.MaxValue;
			float[] array5 = array2;
			foreach (float num4 in array5)
			{
				if (num4 > num2)
				{
					num2 = num4;
				}
				if (num4 < num3)
				{
					num3 = num4;
				}
			}
			float num5 = Math.Min(1f, 32766f / Math.Max(Math.Abs(num2), Math.Abs(num3)));
			short[] array6 = new short[array2.Length];
			for (int n = 0; n < array2.Length; n++)
			{
				array6[n] = (short)(array2[n] * num5);
			}
			return array6;
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				if (_handle != IntPtr.Zero)
				{
					NativeMethods.rnnoise_destroy(_handle);
					_handle = IntPtr.Zero;
				}
				_disposed = true;
			}
		}
	}
}
namespace SsmpVoiceChat.Common.Opus
{
	public enum Application
	{
		Voip = 2048,
		Audio = 2049,
		RestrictedLowLatency = 2051
	}
	public static class Constants
	{
		public const int DefaultAudioSampleRate = 48000;

		public const byte DefaultAudioSampleBits = 16;

		public const byte DefaultAudioSampleChannels = 1;

		public const ushort DefaultAudioFrameSize = 960;
	}
	internal class NativeMethods
	{
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate IntPtr opus_encoder_create_delegate(int sampleRate, int channelCount, int application, out IntPtr error);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void opus_encoder_destroy_delegate(IntPtr encoder);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int opus_encode_delegate(IntPtr encoder, IntPtr pcm, int frameSize, IntPtr data, int maxDataBytes);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate IntPtr opus_decoder_create_delegate(int sampleRate, int channelCount, out IntPtr error);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void opus_decoder_destroy_delegate(IntPtr decoder);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int opus_decode_delegate(IntPtr decoder, IntPtr data, int len, IntPtr pcm, int frameSize, int decodeFec);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int opus_packet_get_nb_channels_delegate(IntPtr data);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int opus_packet_get_nb_samples_delegate(IntPtr data, int len, int sampleRate);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int opus_encoder_ctl_delegate(IntPtr encoder, Ctl request, int value);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate int opus_encoder_ctl_out_delegate(IntPtr encoder, Ctl request, out int value);

		public enum Ctl
		{
			SetBitrateRequest = 4002,
			GetBitrateRequest = 4003,
			SetInbandFecRequest = 4012,
			GetInbandFecRequest = 4013
		}

		public enum OpusErrors
		{
			Ok = 0,
			BadArgument = -1,
			BufferToSmall = -2,
			InternalError = -3,
			InvalidPacket = -4,
			NotImplemented = -5,
			InvalidState = -6,
			AllocFail = -7
		}

		internal static opus_encoder_create_delegate opus_encoder_create;

		internal static opus_encoder_destroy_delegate opus_encoder_destroy;

		internal static opus_encode_delegate opus_encode;

		internal static opus_decoder_create_delegate opus_decoder_create;

		internal static opus_decoder_destroy_delegate opus_decoder_destroy;

		internal static opus_decode_delegate opus_decode;

		internal static opus_packet_get_nb_channels_delegate opus_packet_get_nb_channels;

		internal static opus_packet_get_nb_samples_delegate opus_packet_get_nb_samples;

		internal static opus_encoder_ctl_delegate opus_encoder_ctl;

		internal static opus_encoder_ctl_out_delegate opus_encoder_ctl_out;

		static NativeMethods()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				ClientVoiceChat.Logger.Error("Could not get path of executing assembly, cannot initialize NativeMethods for Opus");
				return;
			}
			IntPtr intPtr;
			if (PlatformDetails.IsMac)
			{
				intPtr = LibraryLoader.Load("libopus.dylib");
				if (intPtr.Equals((object?)(nint)IntPtr.Zero))
				{
					intPtr = LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Mac", "libopus.dylib"));
				}
			}
			else if (PlatformDetails.IsWindows)
			{
				intPtr = LibraryLoader.Load("opus.dll");
				if (intPtr.Equals((object?)(nint)IntPtr.Zero))
				{
					intPtr = LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Windows", "opus.dll"));
				}
			}
			else
			{
				intPtr = LibraryLoader.Load("libopus.so.0");
				if (intPtr.Equals((object?)(nint)IntPtr.Zero))
				{
					intPtr = LibraryLoader.Load(Path.Combine(directoryName, "Natives", "Linux", "libopus.so.0"));
				}
			}
			if (!(intPtr != IntPtr.Zero))
			{
				return;
			}
			FieldInfo[] fields = typeof(NativeMethods).GetFields(BindingFlags.Static | BindingFlags.NonPublic);
			foreach (FieldInfo obj in fields)
			{
				string text = obj.Name;
				if (text == "opus_encoder_ctl_out")
				{
					text = "opus_encoder_ctl";
				}
				Type fieldType = obj.FieldType;
				IntPtr intPtr2 = LibraryLoader.ResolveSymbol(intPtr, text);
				if (intPtr2 == IntPtr.Zero)
				{
					throw new Exception("Could not resolve symbol \"" + text + "\"");
				}
				obj.SetValue(null, Marshal.GetDelegateForFunctionPointer(intPtr2, fieldType));
			}
		}
	}
	public class OpusCodec
	{
		private readonly OpusDecoder _decoder;

		private readonly OpusEncoder _encoder;

		private readonly int _sampleRate;

		private readonly ushort _frameSize;

		public OpusCodec(int sampleRate = 48000, byte channels = 1, ushort frameSize = 960)
		{
			_sampleRate = sampleRate;
			_frameSize = frameSize;
			_decoder = new OpusDecoder(sampleRate, channels)
			{
				EnableForwardErrorCorrection = true
			};
			_encoder = new OpusEncoder(sampleRate, channels)
			{
				EnableForwardErrorCorrection = true
			};
		}

		public byte[] Decode(byte[] encodedData)
		{
			if (encodedData == null)
			{
				_decoder.Decode(null, 0, 0, new byte[_sampleRate / _frameSize], 0);
				return null;
			}
			int samples = OpusDecoder.GetSamples(encodedData, 0, encodedData.Length, _sampleRate);
			if (samples < 1)
			{
				return null;
			}
			byte[] array = new byte[samples * 2];
			int num = _decoder.Decode(encodedData, 0, encodedData.Length, array, 0);
			if (array.Length != num)
			{
				Array.Resize(ref array, num);
			}
			return array;
		}

		public byte[] Encode(byte[] data)
		{
			int num = data.Length / 2;
			byte[] array = new byte[_encoder.FrameSizeInBytes(num)];
			int newSize = _encoder.Encode(data, 0, array, 0, num);
			Array.Resize(ref array, newSize);
			return array;
		}
	}
	public class OpusDecoder : IDisposable
	{
		private IntPtr _decoder;

		private readonly int _sampleSize;

		public bool EnableForwardErrorCorrection { get; set; }

		public OpusDecoder(int outputSampleRate, int outputChannelCount)
		{
			if (outputSampleRate != 8000 && outputSampleRate != 12000 && outputSampleRate != 16000 && outputSampleRate != 24000 && outputSampleRate != 48000)
			{
				throw new ArgumentOutOfRangeException("outputSampleRate");
			}
			if (outputChannelCount != 1 && outputChannelCount != 2)
			{
				throw new ArgumentOutOfRangeException("outputChannelCount");
			}
			_decoder = NativeMethods.opus_decoder_create(outputSampleRate, outputChannelCount, out var error);
			if ((int)error != 0)
			{
				throw new Exception($"Exception occured while creating decoder, {(NativeMethods.OpusErrors)(int)error}");
			}
			_sampleSize = 2 * outputChannelCount;
		}

		~OpusDecoder()
		{
			Dispose();
		}

		public void Dispose()
		{
			if (_decoder != IntPtr.Zero)
			{
				NativeMethods.opus_decoder_destroy(_decoder);
				_decoder = IntPtr.Zero;
			}
		}

		public unsafe int Decode(byte[] srcEncodedBuffer, int srcOffset, int srcLength, byte[] dstBuffer, int dstOffset)
		{
			int frameSize = (dstBuffer.Length - dstOffset) / _sampleSize;
			int num;
			fixed (byte* value = dstBuffer)
			{
				IntPtr pcm = IntPtr.Add(new IntPtr(value), dstOffset);
				if (srcEncodedBuffer != null)
				{
					fixed (byte* value2 = srcEncodedBuffer)
					{
						IntPtr data = IntPtr.Add(new IntPtr(value2), srcOffset);
						num = NativeMethods.opus_decode(_decoder, data, srcLength, pcm, frameSize, 0);
					}
				}
				else
				{
					num = NativeMethods.opus_decode(_decoder, IntPtr.Zero, 0, pcm, frameSize, Convert.ToInt32(EnableForwardErrorCorrection));
				}
			}
			if (num < 0)
			{
				NativeMethods.OpusErrors opusErrors = (NativeMethods.OpusErrors)num;
				throw new Exception("Decoding failed - " + opusErrors);
			}
			return num * _sampleSize;
		}

		public unsafe static int GetSamples(byte[] srcEncodedBuffer, int srcOffset, int srcLength, int sampleRate)
		{
			fixed (byte* value = srcEncodedBuffer)
			{
				IntPtr data = IntPtr.Add(new IntPtr(value), srcOffset);
				return NativeMethods.opus_packet_get_nb_samples(data, srcLength, sampleRate);
			}
		}

		public unsafe static int GetChannels(byte[] srcEncodedBuffer, int srcOffset)
		{
			fixed (byte* value = srcEncodedBuffer)
			{
				IntPtr data = IntPtr.Add(new IntPtr(value), srcOffset);
				return NativeMethods.opus_packet_get_nb_channels(data);
			}
		}
	}
	public class OpusEncoder : IDisposable
	{
		private IntPtr _encoder;

		private readonly int _sampleSize;

		private readonly float[] _permittedFrameSizesInMilliSec = new float[6] { 2.5f, 5f, 10f, 20f, 40f, 60f };

		public int[] PermittedFrameSizes { get; }

		public int Bitrate
		{
			get
			{
				if (_encoder == IntPtr.Zero)
				{
					throw new ObjectDisposedException("OpusEncoder");
				}
				int value;
				int num = NativeMethods.opus_encoder_ctl_out(_encoder, NativeMethods.Ctl.GetBitrateRequest, out value);
				if (num < 0)
				{
					NativeMethods.OpusErrors opusErrors = (NativeMethods.OpusErrors)num;
					throw new Exception("Encoder error - " + opusErrors);
				}
				return value;
			}
			set
			{
				if (_encoder == IntPtr.Zero)
				{
					throw new ObjectDisposedException("OpusEncoder");
				}
				int num = NativeMethods.opus_encoder_ctl(_encoder, NativeMethods.Ctl.SetBitrateRequest, value);
				if (num < 0)
				{
					NativeMethods.OpusErrors opusErrors = (NativeMethods.OpusErrors)num;
					throw new Exception("Encoder error - " + opusErrors);
				}
			}
		}

		public bool EnableForwardErrorCorrection
		{
			get
			{
				if (_encoder == IntPtr.Zero)
				{
					throw new ObjectDisposedException("OpusEncoder");
				}
				int value;
				int num = NativeMethods.opus_encoder_ctl_out(_encoder, NativeMethods.Ctl.GetInbandFecRequest, out value);
				if (num < 0)
				{
					NativeMethods.OpusErrors opusErrors = (NativeMethods.OpusErrors)num;
					throw new Exception("Encoder error - " + opusErrors);
				}
				return value > 0;
			}
			set
			{
				if (_encoder == IntPtr.Zero)
				{
					throw new ObjectDisposedException("OpusEncoder");
				}
				int num = NativeMethods.opus_encoder_ctl(_encoder, NativeMethods.Ctl.SetInbandFecRequest, Convert.ToInt32(value));
				if (num < 0)
				{
					NativeMethods.OpusErrors opusErrors = (NativeMethods.OpusErrors)num;
					throw new Exception("Encoder error - " + opusErrors);
				}
			}
		}

		public OpusEncoder(int srcSamplingRate, int srcChannelCount)
		{
			if (srcSamplingRate != 8000 && srcSamplingRate != 12000 && srcSamplingRate != 16000 && srcSamplingRate != 24000 && srcSamplingRate != 48000)
			{
				throw new ArgumentOutOfRangeException("srcSamplingRate");
			}
			if (srcChannelCount != 1 && srcChannelCount != 2)
			{
				throw new ArgumentOutOfRangeException("srcChannelCount");
			}
			IntPtr error;
			IntPtr encoder = NativeMethods.opus_encoder_create(srcSamplingRate, srcChannelCount, 2048, out error);
			if ((int)error != 0)
			{
				throw new Exception("Exception occured while creating encoder");
			}
			_encoder = encoder;
			_sampleSize = SampleSize(16, srcChannelCount);
			PermittedFrameSizes = new int[_permittedFrameSizesInMilliSec.Length];
			for (int i = 0; i < _permittedFrameSizesInMilliSec.Length; i++)
			{
				PermittedFrameSizes[i] = (int)((float)srcSamplingRate / 1000f * _permittedFrameSizesInMilliSec[i]);
			}
		}

		private static int SampleSize(int bitDepth, int channelCount)
		{
			return bitDepth / 8 * channelCount;
		}

		~OpusEncoder()
		{
			Dispose();
		}

		public unsafe int Encode(byte[] srcPcmSamples, int srcOffset, byte[] dstOutputBuffer, int dstOffset, int sampleCount)
		{
			if (srcPcmSamples == null)
			{
				throw new ArgumentNullException("srcPcmSamples");
			}
			if (dstOutputBuffer == null)
			{
				throw new ArgumentNullException("dstOutputBuffer");
			}
			if (!PermittedFrameSizes.Contains(sampleCount))
			{
				throw new Exception("Frame size is not permitted");
			}
			int num = _sampleSize * sampleCount;
			if (srcOffset + num > srcPcmSamples.Length)
			{
				throw new Exception("Not enough samples in source");
			}
			int maxDataBytes = dstOutputBuffer.Length - dstOffset;
			int num2;
			fixed (byte* value = dstOutputBuffer)
			{
				fixed (byte* value2 = srcPcmSamples)
				{
					IntPtr data = IntPtr.Add(new IntPtr(value), dstOffset);
					IntPtr pcm = IntPtr.Add(new IntPtr(value2), srcOffset);
					num2 = NativeMethods.opus_encode(_encoder, pcm, sampleCount, data, maxDataBytes);
				}
			}
			if (num2 < 0)
			{
				NativeMethods.OpusErrors opusErrors = (NativeMethods.OpusErrors)num2;
				throw new Exception("Encoding failed - " + opusErrors);
			}
			return num2;
		}

		public int FrameSizeInBytes(int frameSizeInSamples)
		{
			return frameSizeInSamples * _sampleSize;
		}

		public void Dispose()
		{
			if (_encoder != IntPtr.Zero)
			{
				NativeMethods.opus_encoder_destroy(_encoder);
				_encoder = IntPtr.Zero;
			}
		}
	}
}
namespace SsmpVoiceChat.Common.Net
{
	public class ServerVoicePacket : IPacketData
	{
		public const ushort MaxSize = ushort.MaxValue;

		public byte[] VoiceData { get; set; }

		public bool IsReliable => false;

		public bool DropReliableDataIfNewerExists => false;

		public virtual void WriteData(IPacket packet)
		{
			if (VoiceData.Length > 65535)
			{
				throw new InvalidOperationException($"Voice data exceeds maximum size of {ushort.MaxValue} bytes");
			}
			ushort num = (ushort)VoiceData.Length;
			packet.Write(num);
			for (int i = 0; i < num; i++)
			{
				packet.Write(VoiceData[i]);
			}
		}

		public virtual void ReadData(IPacket packet)
		{
			ushort num = packet.ReadUShort();
			VoiceData = new byte[num];
			for (int i = 0; i < num; i++)
			{
				VoiceData[i] = packet.ReadByte();
			}
		}
	}
	public class ClientVoicePacket : ServerVoicePacket
	{
		public ushort Id { get; set; }

		public bool Proximity { get; set; }

		public override void WriteData(IPacket packet)
		{
			packet.Write(Id);
			packet.Write(Proximity);
			base.WriteData(packet);
		}

		public override void ReadData(IPacket packet)
		{
			Id = packet.ReadUShort();
			Proximity = packet.ReadBool();
			base.ReadData(packet);
		}
	}
	public enum ServerPacketId
	{
		Voice
	}
	public enum ClientPacketId
	{
		Voice
	}
}
namespace SsmpVoiceChat.Common.Command
{
	public static class CommandUtil
	{
		public static void HandleSetCommand<TSettings>(string trigger, string[] args, TSettings settings, Action<string> feedbackAction, Action successAction = null, bool requireSettingAliasAttribute = false)
		{
			PropertyInfo[] properties = typeof(TSettings).GetProperties();
			if (args.Length < 3)
			{
				feedbackAction?.Invoke("Available settings: " + string.Join(", ", properties.Select((PropertyInfo p) => p.Name)));
				return;
			}
			string text = args[2];
			PropertyInfo propertyInfo = null;
			PropertyInfo[] array = properties;
			foreach (PropertyInfo propertyInfo2 in array)
			{
				SettingAliasAttribute customAttribute = ((MemberInfo)propertyInfo2).GetCustomAttribute<SettingAliasAttribute>();
				if (!(customAttribute == null && requireSettingAliasAttribute))
				{
					text = text.ToLower().Replace("_", "");
					if (propertyInfo2.Name.ToLower().Equals(text))
					{
						propertyInfo = propertyInfo2;
						break;
					}
					if (customAttribute != null && customAttribute.Aliases.Contains(text))
					{
						propertyInfo = propertyInfo2;
						break;
					}
				}
			}
			if (propertyInfo == null || !propertyInfo.CanRead)
			{
				feedbackAction?.Invoke("Could not find setting with name: " + text);
				return;
			}
			if (args.Length < 4)
			{
				object value = propertyInfo.GetValue(settings);
				feedbackAction?.Invoke($"Setting '{text}' currently has value: {value}");
				return;
			}
			if (!propertyInfo.CanWrite)
			{
				feedbackAction?.Invoke("Could not change value of setting with name: " + text + " (non-writable)");
				return;
			}
			string text2 = args[3];
			object obj;
			if (propertyInfo.PropertyType == typeof(int))
			{
				if (!int.TryParse(text2, out var result))
				{
					feedbackAction?.Invoke("Please provide an integer value for this setting");
					return;
				}
				obj = result;
			}
			else if (propertyInfo.PropertyType == typeof(bool))
			{
				if (!bool.TryParse(text2, out var result2))
				{
					feedbackAction?.Invoke("Please provide a boolean value for this setting");
					return;
				}
				obj = result2;
			}
			else
			{
				if (!(propertyInfo.PropertyType == typeof(float)))
				{
					feedbackAction?.Invoke("Could not change value of setting with name: " + text + " (unhandled type)");
					return;
				}
				if (!float.TryParse(text2, out var result3))
				{
					feedbackAction?.Invoke("Please provide a float value for this setting");
					return;
				}
				obj = result3;
			}
			propertyInfo.SetValue(settings, obj);
			feedbackAction?.Invoke($"Changed setting '{text}' to: {obj}");
			successAction?.Invoke();
		}
	}
}
namespace SsmpVoiceChat.Client
{
	public class ClientNetManager
	{
		private readonly IClientAddonNetworkSender<ServerPacketId> _netSender;

		public event Action<ushort, byte[], bool> VoiceEvent;

		public ClientNetManager(ClientAddon addon, INetClient netClient)
		{
			_netSender = netClient.GetNetworkSender<ServerPacketId>(addon);
			netClient.GetNetworkReceiver<ClientPacketId>(addon, (Func<ClientPacketId, IPacketData>)InstantiatePacket).RegisterPacketHandler<ClientVoicePacket>(ClientPacketId.Voice, (GenericClientPacketHandler<ClientVoicePacket>)delegate(ClientVoicePacket packet)
			{
				this.VoiceEvent?.Invoke(packet.Id, packet.VoiceData, packet.Proximity);
			});
		}

		public void SendVoiceData(byte[] data)
		{
			if (data.Length > 65535)
			{
				ClientVoiceChat.Logger.Error("Voice data exceeds max size!");
				return;
			}
			_netSender.SendCollectionData<ServerVoicePacket>(ServerPacketId.Voice, new ServerVoicePacket
			{
				VoiceData = data
			});
		}

		private static IPacketData InstantiatePacket(ClientPacketId packetId)
		{
			if (packetId == ClientPacketId.Voice)
			{
				return (IPacketData)(object)new PacketDataCollection<ClientVoicePacket>();
			}
			return null;
		}
	}
	public class ClientVoiceChat
	{
		private VoiceStatusIcon? VoiceStatusIcon;

		private readonly IClientApi _clientApi;

		private readonly ClientNetManager _netManager;

		private readonly MicrophoneManager _micManager;

		private readonly SoundManager _soundManager;

		private bool _muted;

		private bool _pushToggle;

		public static ILogger Logger { get; private set; }

		private bool Muted
		{
			get
			{
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				if (_muted)
				{
					return true;
				}
				KeyCode pushToTalkKey = VoiceChatMod.ModSettings.PushToTalkKey;
				ModSettings.InputMethod inputMode = VoiceChatMod.ModSettings.InputMode;
				if ((int)pushToTalkKey != 0)
				{
					switch (inputMode)
					{
					case ModSettings.InputMethod.PushToTalk:
						return !Input.GetKey(pushToTalkKey);
					default:
						return VoiceChatMod.ToggleMuted;
					case ModSettings.InputMethod.Normal:
						break;
					}
				}
				return false;
			}
		}

		public ClientVoiceChat(ClientAddon addon, IClientApi clientApi, ILogger logger)
		{
			Logger = logger;
			_clientApi = clientApi;
			_netManager = new ClientNetManager(addon, clientApi.NetClient);
			_micManager = new MicrophoneManager();
			_soundManager = new SoundManager();
		}

		public void Initialize()
		{
			ClientVoiceChatCommand clientVoiceChatCommand = new ClientVoiceChatCommand(_clientApi.UiManager.ChatBox);
			((ICommandManager<IClientCommand>)(object)_clientApi.CommandManager).RegisterCommand((IClientCommand)(object)clientVoiceChatCommand);
			VoiceChatMod.ModSettings.SetMicrophoneEvent += delegate
			{
				ReloadAudio();
			};
			VoiceChatMod.ModSettings.SetSpeakerEvent += delegate
			{
				ReloadAudio();
			};
			VoiceChatMod.ModSettings.SetMicFail += delegate
			{
				VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.Error);
			};
			clientVoiceChatCommand.ToggleMuteEvent += delegate
			{
				_muted = !_muted;
				_clientApi.UiManager.ChatBox.AddMessage("Microphone is now " + (_muted ? "" : "un") + "muted");
				if (!_muted && Muted)
				{
					_clientApi.UiManager.ChatBox.AddMessage("Push To Talk is still enabled.");
				}
				if (!Muted)
				{
					VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.NotTalking);
				}
				else if (_muted)
				{
					VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.Muted);
				}
				else
				{
					VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.PushMuted);
				}
			};
			ReloadAudio();
			_clientApi.ClientManager.ConnectEvent += OnConnect;
			_clientApi.ClientManager.DisconnectEvent += OnDisconnect;
			_clientApi.ClientManager.PlayerEnterSceneEvent += OnPlayerEnterScene;
			_clientApi.ClientManager.PlayerLeaveSceneEvent += OnPlayerLeaveScene;
			_netManager.VoiceEvent += OnVoiceReceived;
		}

		private void OnConnect()
		{
			Logger.Debug("Client is connected, starting mic capture");
			VoiceStatusIcon = new VoiceStatusIcon();
			_micManager.Start();
			_micManager.VoiceDataEvent += OnVoiceGenerated;
			_micManager.VoiceOffEvent += OnVoiceStopped;
			ReloadAudio();
		}

		private void OnDisconnect()
		{
			Logger.Debug("Client is disconnected, stopping mic capture");
			VoiceStatusIcon?.DestroyIcon();
			VoiceStatusIcon = null;
			_micManager.VoiceDataEvent -= OnVoiceGenerated;
			_micManager.Stop();
			_soundManager.Close();
		}

		private void OnVoiceGenerated(byte[] data)
		{
			if (_clientApi.NetClient.IsConnected)
			{
				if (!Muted)
				{
					VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.Talking);
					_netManager.SendVoiceData(data);
				}
				else if (_muted)
				{
					VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.Muted);
				}
				else
				{
					VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.PushMuted);
				}
			}
		}

		private void OnVoiceStopped()
		{
			if (!Muted)
			{
				VoiceStatusIcon?.SetTalking(SsmpVoiceChat.Client.VoiceStatusIcon.Status.NotTalking);
			}
		}

		private void OnPlayerEnterScene(IClientPlayer player)
		{
			Logger.Debug("Player entered scene, adding speaker");
			_soundManager.TryGetOrCreateSpeaker(player.Id, out Speaker _);
		}

		private void OnPlayerLeaveScene(IClientPlayer player)
		{
			Logger.Debug("Player left scene, closing and removing speaker");
			_soundManager.TryRemoveSpeaker(player.Id);
		}

		private void OnVoiceReceived(ushort id, byte[] data, bool proximity)
		{
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			EnableRemoteStatusIcon(id);
			if (!_soundManager.TryGetOrCreateSpeaker(id, out Speaker speaker))
			{
				Logger.Warn($"Could not get or create speaker for player '{id}', cannot play voice");
				return;
			}
			if (!proximity)
			{
				speaker.Play(data);
				return;
			}
			HeroController instance = HeroController.instance;
			IClientPlayer val = default(IClientPlayer);
			if ((Object)(object)instance == (Object)null || (Object)(object)((Component)instance).gameObject == (Object)null)
			{
				Logger.Warn("Local player could not be found, cannot play voice positionally");
				speaker.Play(data);
			}
			else if (!_clientApi.ClientManager.TryGetPlayer(id, ref val))
			{
				Logger.Warn($"No player found for '{id}', cannot play voice positionally");
				speaker.Play(data);
			}
			else
			{
				Vector3 position = ((Component)instance).gameObject.transform.position;
				Vector3 val2 = val.PlayerObject.transform.position - position;
				speaker.Play(data, new Vector3(val2.x, val2.y, val2.z));
			}
		}

		private void EnableRemoteStatusIcon(ushort id)
		{
			IClientPlayer val = default(IClientPlayer);
			if (!_clientApi.ClientManager.TryGetPlayer(id, ref val))
			{
				Logger.Warn("Local player could not be found, cannot enable status indicator");
			}
			else
			{
				if (val == null || !val.IsInLocalScene)
				{
					return;
				}
				GameObject playerContainer = val.PlayerContainer;
				if ((Object)(object)playerContainer == (Object)null)
				{
					Logger.Warn("Local player could not be found, cannot enable status indicator");
					return;
				}
				RemoteStatusIndicator iconOnPlayerContainer = RemoteStatusIndicator.GetIconOnPlayerContainer(playerContainer);
				if ((Object)(object)iconOnPlayerContainer != (Object)null)
				{
					iconOnPlayerContainer.UpdateState(talking: true);
				}
			}
		}

		private void ReloadAudio()
		{
			_micManager.Stop();
			_soundManager.Close();
			_soundManager.Open();
			Logger.Debug("Reloading Audio");
			if (_clientApi.NetClient.IsConnected)
			{
				_micManager.Start();
			}
		}
	}
	public class ClientVoiceChatCommand : IClientCommand, ICommand
	{
		private readonly IChatBox _chatBox;

		private readonly Dictionary<int, string> _microphoneNames;

		private readonly Dictionary<int, string> _speakerNames;

		public string Trigger => "/voicechatclient";

		public string[] Aliases => new string[1] { "/vcc" };

		public event Action<string> SetMicrophoneEvent;

		public event Action<string> SetSpeakerEvent;

		public event Action ToggleMuteEvent;

		public ClientVoiceChatCommand(IChatBox chatBox)
		{
			_chatBox = chatBox;
			_microphoneNames = new Dictionary<int, string>();
			_speakerNames = new Dictionary<int, string>();
		}

		public void Execute(string[] args)
		{
			if (args.Length < 2)
			{
				SendUsage();
				return;
			}
			string text = args[1];
			if (text == "mute")
			{
				HandleMute();
			}
			else if (text == "devices")
			{
				HandleDeviceList(args);
			}
			else
			{
				SendUsage();
			}
			void SendUsage()
			{
				_chatBox.AddMessage("Invalid usage: " + Trigger + " <mute|devices>");
			}
		}

		private void HandleMute()
		{
			this.ToggleMuteEvent?.Invoke();
		}

		private void SendMicList()
		{
			List<string> allMicrophones = Microphone.GetAllMicrophones();
			if (allMicrophones.Count == 0)
			{
				_chatBox.AddMessage("No microphones could be found");
				return;
			}
			_microphoneNames.Clear();
			_chatBox.AddMessage("Microphones (id, name):");
			int num = 1;
			foreach (string item in allMicrophones)
			{
				_chatBox.AddMessage($"{num}: {item}");
				_microphoneNames[num++] = item;
			}
		}

		private void SendSpeakerList()
		{
			List<string> allDeviceSpeakers = SoundManager.GetAllDeviceSpeakers();
			if (allDeviceSpeakers.Count == 0)
			{
				_chatBox.AddMessage("No speakers could be found");
				return;
			}
			_speakerNames.Clear();
			_chatBox.AddMessage("Speakers (id, name):");
			int num = 1;
			foreach (string item in allDeviceSpeakers)
			{
				_chatBox.AddMessage($"{num}: {item}");
				_speakerNames[num++] = item;
			}
		}

		private void HandleDeviceList(string[] args)
		{
			string text = "";
			if (args.Length >= 4)
			{
				text = args[3];
			}
			_chatBox.AddMessage("If you don't see the device you want in the config, you'll have to reload the game.");
			bool flag;
			switch (text)
			{
			default:
				if (text.Length == 0)
				{
					goto case "mics";
				}
				goto case null;
			case "mics":
			case "mic":
				flag = true;
				break;
			case null:
				flag = false;
				break;
			}
			if (flag)
			{
				SendMicList();
				return;
			}
			if ((text == "speakers" || text == "speaker") ? true : false)
			{
				SendSpeakerList();
				return;
			}
			SendMicList();
			_chatBox.AddMessage("");
			SendSpeakerList();
		}

		private void HandleDeviceSet(string[] args)
		{
			if (args.Length < 5)
			{
				SendUsage();
				return;
			}
			string text = args[3];
			string text2 = args[4];
			if ((text == "mic" || text == "speaker") ? true : false)
			{
				int result;
				bool flag = int.TryParse(text2, out result);
				if (text == "mic")
				{
					if (flag && _microphoneNames.TryGetValue(result, out string value))
					{
						this.SetMicrophoneEvent?.Invoke(value);
						_chatBox.AddMessage("Set microphone to \"" + value + "\"");
					}
					else if (_microphoneNames.Values.Contains(text2))
					{
						this.SetMicrophoneEvent?.Invoke(text2);
						_chatBox.AddMessage("Set microphone to \"" + text2 + "\"");
					}
					else
					{
						_chatBox.AddMessage("Could not find microphone with ID or name: \"" + text2 + "\"");
					}
				}
				else if (text == "speaker")
				{
					if (flag && _speakerNames.TryGetValue(result, out string value2))
					{
						this.SetSpeakerEvent?.Invoke(value2);
						_chatBox.AddMessage("Set speaker to \"" + value2 + "\"");
					}
					else if (_speakerNames.Values.Contains(text2))
					{
						this.SetSpeakerEvent?.Invoke(text2);
						_chatBox.AddMessage("Set speaker to \"" + text2 + "\"");
					}
					else
					{
						_chatBox.AddMessage("Could not find speaker with ID or name: \"" + text2 + "\"");
					}
				}
			}
			else
			{
				SendUsage();
			}
			void SendUsage()
			{
				_chatBox.AddMessage("Invalid usage: " + Trigger + " device set <mic|speaker> <value>");
			}
		}

		private void HandleSet(string[] args)
		{
			CommandUtil.HandleSetCommand(Trigger, args, VoiceChatMod.ModSettings, (Action<string>)_chatBox.AddMessage, (Action)null, requireSettingAliasAttribute: true);
		}
	}
	internal class ModSettings
	{
		internal enum InputMethod
		{
			Normal,
			PushToTalk,
			PushToToggle
		}

		private ConfigEntry<string> _microphoneDevice;

		private ConfigEntry<string> _speakerDevice;

		private ConfigEntry<int> _microphoneAmplification;

		private ConfigEntry<int> _voiceChatVolume;

		private ConfigEntry<bool> _smoothChannelTransition;

		private ConfigEntry<KeyCode> _pushToTalkKey;

		private ConfigEntry<InputMethod> _inputMode;

		private ConfigEntry<bool> _talkingIndicator;

		private ConfigEntry<float> _maxDistance;

		private ConfigEntry<float> _rolloffFactor;

		public const string SystemDeviceName = "System Default";

		public string MicrophoneDeviceName
		{
			get
			{
				string text = _microphoneDevice?.Value ?? "";
				if (text == "System Default")
				{
					return Microphone.GetDefaultMicrophone();
				}
				return text;
			}
		}

		public string SpeakerDeviceName
		{
			get
			{
				string text = _speakerDevice?.Value ?? "";
				if (text == "System Default")
				{
					return SoundManager.GetDefaultDeviceSpeaker();
				}
				return text;
			}
		}

		public float MicrophoneAmplification => Mathf.Clamp((float)_microphoneAmplification.Value / 5f, 0f, 3f);

		public float VoiceChatVolume => Mathf.Clamp((float)_voiceChatVolume.Value / 10f, 0f, 1f);

		public bool SmoothChannelTransition => _smoothChannelTransition.Value;

		public KeyCode PushToTalkKey => _pushToTalkKey.Value;

		public InputMethod InputMode => _inputMode.Value;

		public bool TalkingIndicator => _talkingIndicator?.Value ?? true;

		public float MaxDistance => _maxDistance?.Value ?? 60f;

		public float RolloffFactor => _rolloffFactor?.Value ?? 1.5f;

		public event Action<string> SetMicrophoneEvent = delegate
		{
		};

		public event Action<string> SetSpeakerEvent = delegate
		{
		};

		public event Action SetMicFail;

		public event Action OnTalkingIndicatorToggled;

		public ModSettings(ConfigFile config)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Expected O, but got Unknown
			List<string> allMicrophones = Microphone.GetAllMicrophones();
			allMicrophones.Insert(0, "System Default");
			ConfigDescription val = new ConfigDescription("The microphone device currently used.", (AcceptableValueBase)(object)new AcceptableValueList<string>(allMicrophones.ToArray()), Array.Empty<object>());
			_microphoneDevice = config.Bind<string>("Devices", "Microphone", "System Default", val);
			_microphoneDevice.SettingChanged += OnMicrophoneChanged;
			OnMicrophoneChanged(null, null);
			List<string> allDeviceSpeakers = SoundManager.GetAllDeviceSpeakers();
			allDeviceSpeakers.Insert(0, "System Default");
			ConfigDescription val2 = new ConfigDescription("The speaker device currently used.", (AcceptableValueBase)(object)new AcceptableValueList<string>(allDeviceSpeakers.ToArray()), Array.Empty<object>());
			_speakerDevice = config.Bind<string>("Devices", "Speaker", "System Default", val2);
			_speakerDevice.SettingChanged += OnSpeakerChanged;
			OnSpeakerChanged(null, null);
			ConfigDescription val3 = new ConfigDescription("Modifies the volume of the microphone input.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 15), Array.Empty<object>());
			_microphoneAmplification = config.Bind<int>("Volume", "Microphone Amplification", 5, val3);
			ConfigDescription val4 = new ConfigDescription("The volume of the voice chat of other players.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>());
			_voiceChatVolume = config.Bind<int>("Volume", "Chat Volume", 6, val4);
			_smoothChannelTransition = config.Bind<bool>("Volume", "Smooth Channel Transition", true, "Whether the transition between audio from a player moving from the left to the right of the local player is smooth or not");
			_pushToTalkKey = config.Bind<KeyCode>("Microphone Toggle", "Input Key", (KeyCode)0, "The key to press to enable/toggle your microphone.");
			_inputMode = config.Bind<InputMethod>("Microphone Toggle", "Input Mode", InputMethod.Normal, "The method for microphone input (Push to talk / toggle / normal)");
			_inputMode.SettingChanged += delegate
			{
				VoiceChatMod.ToggleMuted = false;
			};
			_talkingIndicator = config.Bind<bool>("Visuals", "Mic Status Indicator", true, "Whether the microphone icon should be displayed or not");
			_talkingIndicator.SettingChanged += delegate
			{
				this.OnTalkingIndicatorToggled?.Invoke();
			};
		}

		private void OnMicrophoneChanged(object sender, EventArgs e)
		{
			string microphoneDeviceName = MicrophoneDeviceName;
			bool num = Microphone.GetAllMicrophones().Contains(microphoneDeviceName);
			ILogger logger = ClientVoiceChat.Logger;
			if (logger != null)
			{
				logger.Info(string.Join(", ", Microphone.GetAllMicrophones()));
			}
			if (!num)
			{
				IChatBox chatBox = VoiceChatMod.ChatBox;
				if (chatBox != null)
				{
					chatBox.AddMessage("[VC]: Couldn't find a microphone with the name " + microphoneDeviceName);
				}
				ILogger logger2 = ClientVoiceChat.Logger;
				if (logger2 != null)
				{
					logger2.Error("[VC]: Couldn't find a microphone with the name " + microphoneDeviceName);
				}
				this.SetMicFail?.Invoke();
			}
			else
			{
				this.SetMicrophoneEvent?.Invoke(microphoneDeviceName);
			}
		}

		private void OnSpeakerChanged(object sender, EventArgs e)
		{
			string speakerDeviceName = SpeakerDeviceName;
			if (!SoundManager.GetAllDeviceSpeakers().Contains(speakerDeviceName))
			{
				IChatBox chatBox = VoiceChatMod.ChatBox;
				if (chatBox != null)
				{
					chatBox.AddMessage("[VC]: Couldn't find a speaker with the name " + speakerDeviceName);
				}
				ILogger logger = ClientVoiceChat.Logger;
				if (logger != null)
				{
					logger.Error("[VC]: Couldn't find a speaker with the name " + speakerDeviceName);
				}
			}
			else
			{
				this.SetSpeakerEvent?.Invoke(speakerDeviceName);
			}
		}
	}
	public class ModSettingsBkp
	{
		private const string FileName = "voicechat_client_settings.json";

		[JsonProperty("microphone_device_name")]
		public string MicrophoneDeviceName { get; set; }

		[JsonProperty("speaker_device_name")]
		public string SpeakerDeviceName { get; set; }

		[JsonProperty("microphone_amplification")]
		[SettingAlias(new string[] { "micvol", "micvolume", "micamp" })]
		public float MicrophoneAmplification { get; set; } = 1f;


		[JsonProperty("voice_chat_volume")]
		[SettingAlias(new string[] { "speakervol", "speakervolume" })]
		public float VoiceChatVolume { get; set; } = 1f;


		[JsonProperty("smooth_channel_transition")]
		[SettingAlias(new string[] { "smoothaudio" })]
		public bool SmoothChannelTransition { get; set; } = true;


		public void SaveToFile()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				return;
			}
			string path = Path.Combine(directoryName, "voicechat_client_settings.json");
			string contents = JsonConvert.SerializeObject((object)this, (Formatting)1);
			try
			{
				File.WriteAllText(path, contents);
			}
			catch (Exception arg)
			{
				ServerVoiceChat.Logger.Error($"Could not write server settings to file:\n{arg}");
			}
		}

		public static ModSettingsBkp LoadFromFile()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				return new ModSettingsBkp();
			}
			string path = Path.Combine(directoryName, "voicechat_client_settings.json");
			if (!File.Exists(path))
			{
				ModSettingsBkp modSettingsBkp = new ModSettingsBkp();
				modSettingsBkp.SaveToFile();
				return modSettingsBkp;
			}
			try
			{
				return JsonConvert.DeserializeObject<ModSettingsBkp>(File.ReadAllText(path)) ?? new ModSettingsBkp();
			}
			catch (Exception arg)
			{
				ServerVoiceChat.Logger.Error($"Could not load server settings from file:\n{arg}");
				return new ModSettingsBkp();
			}
		}
	}
	[RequireComponent(typeof(TextMeshPro))]
	internal class RemoteStatusIndicator : MonoBehaviour
	{
		public bool Talking;

		private TextMeshPro textComponent;

		private float timeout;

		private void Awake()
		{
			textComponent = ((Component)this).GetComponent<TextMeshPro>();
		}

		private void OnEnable()
		{
			UpdateState(talking: false);
		}

		private void OnDisable()
		{
			UpdateState(talking: false);
		}

		private void FixedUpdate()
		{
			if (Talking)
			{
				if (timeout > 0f)
				{
					timeout -= Time.deltaTime;
				}
				else
				{
					UpdateState(talking: false);
				}
			}
		}

		public void UpdateState(bool talking)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			Talking = talking;
			if (Talking)
			{
				((TMP_Text)textComponent).outlineColor = Color32.op_Implicit(new Color(0.02f, 0.35f, 0f));
				timeout = 0.25f;
			}
			else
			{
				((TMP_Text)textComponent).outlineColor = Color32.op_Implicit(Color.black);
				timeout = 0f;
			}
		}

		public static RemoteStatusIndicator? GetIconOnPlayerContainer(GameObject playerContainer)
		{
			GameObject val = VoiceStatusIcon.FindChild(playerContainer.transform, "Username");
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			return Extensions.AddComponentIfNotPresent<RemoteStatusIndicator>(val);
		}
	}
	public class VoiceChatClientAddon : ClientAddon
	{
		protected override string Name => "ProximityChat";

		protected override string Version => "0.1.3";

		public override uint ApiVersion => 1u;

		public override bool NeedsNetwork => true;

		public override void Initialize(IClientApi clientApi)
		{
			new ClientVoiceChat((ClientAddon)(object)this, clientApi, ((ClientAddon)this).Logger).Initialize();
			VoiceChatMod.ChatBox = clientApi.UiManager.ChatBox;
		}
	}
	[BepInPlugin("io.github.bobbythecatfish.SSMP.VoiceChat", "SSMPVoiceChat", "0.1.3")]
	public class VoiceChatMod : BaseUnityPlugin
	{
		internal static ModSettings ModSettings;

		internal static IChatBox ChatBox;

		internal static bool ToggleMuted;

		private const string url = "https://www.openal.org/downloads";

		public const string Id = "io.github.bobbythecatfish.SSMP.VoiceChat";

		public static string Name => "SSMPVoiceChat";

		public static string Version => "0.1.3";

		public void Awake()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Alc.GetError(IntPtr.Zero);
			}
			catch (DllNotFoundException)
			{
				try
				{
					Process.Start(new ProcessStartInfo("https://www.openal.org/downloads")
					{
						UseShellExecute = true
					});
				}
				catch
				{
				}
				((BaseUnityPlugin)this).Logger.LogError((object)"OpenAL not installed. Please install at https://www.openal.org/downloads");
				SceneManager.sceneLoaded += OpenALErrorWarning;
			}
			ClientAddon.RegisterAddon((ClientAddon)(object)new VoiceChatClientAddon());
			ServerAddon.RegisterAddon((ServerAddon)(object)new VoiceChatServerAddon());
			ModSettings = new ModSettings(((BaseUnityPlugin)this).Config);
		}

		private void Update()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (ModSettings.InputMode == SsmpVoiceChat.Client.ModSettings.InputMethod.PushToToggle && Input.GetKeyDown(ModSettings.PushToTalkKey))
			{
				ToggleMuted = !ToggleMuted;
			}
		}

		private void OpenALErrorWarning(Scene scene, LoadSceneMode mode)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			if (!(((Scene)(ref scene)).name != "Menu_Title"))
			{
				SceneManager.sceneLoaded -= OpenALErrorWarning;
				Transform transform = ((Component)UIManager.instance.UICanvas).transform;
				Transform val = Object.Instantiate<Transform>(transform.Find("MainMenuScreen").GetChild(0).GetChild(0)
					.GetChild(0), transform);
				ContentSizeFitter val2 = default(ContentSizeFitter);
				if (((Component)val).TryGetComponent<ContentSizeFitter>(ref val2))
				{
					Object.DestroyImmediate((Object)(object)val2);
				}
				FixVerticalAlign val3 = default(FixVerticalAlign);
				if (((Component)val).TryGetComponent<FixVerticalAlign>(ref val3))
				{
					Object.DestroyImmediate((Object)(object)val3);
				}
				((Component)val).GetComponent<RectTransform>().sizeDelta = new Vector2(1055f, 300f);
				Extensions.SetLocalPosition2D(val, 0f, 190f);
				GameObject val4 = new GameObject("Warning Background");
				val4.transform.parent = transform;
				val4.transform.position = val.position;
				Extensions.SetScale2D(val4.transform, Vector2.one);
				Extensions.AddComponentIfNotPresent<RectTransform>(val4).sizeDelta = new Vector2(1055f, 300f);
				((Graphic)val4.AddComponent<Image>()).color = new Color(0.34f, 0.34f, 0.34f, 0.8f);
				Extensions.SetParentReset(val, val4.transform);
				Extensions.SetPositionZ(val, -15f);
				Text component = ((Component)val).GetComponent<Text>();
				component.text = "You need to install OpenAL for SSMP Voice Chat to work. Download at OpenAL.org";
				component.lineSpacing = 1f;
			}
		}
	}
	internal class VoiceStatusIcon
	{
		public enum Status
		{
			Talking,
			Muted,
			PushMuted,
			NotTalking,
			Error
		}

		private const int IMAGE_SIZE = 66;

		private Sprite Unmuted;

		private Sprite Muted;

		private SpriteRenderer MicStatus;

		private Sprite MicNotFound;

		private GameObject? MicrophoneIcons;

		private SpriteRenderer TalkingIndicator;

		private Status CurrentStatus = Status.NotTalking;

		public static Color TalkingColor = new Color(0.3f, 0.5f, 0.3f, 1f);

		public static Color NotTalkingColor = new Color(0.2f, 0.2f, 0.2f, 1f);

		public static Color MutedColor = new Color(0.9f, 0.17f, 0.15f, 1f);

		public static Color ErrorColor = new Color(0.5f, 0.15f, 0.9f, 1f);

		public VoiceStatusIcon()
		{
			CreateSprites();
			CreateIconObject();
			VoiceChatMod.ModSettings.OnTalkingIndicatorToggled += OnIndicatorToggled;
			OnIndicatorToggled();
		}

		public static GameObject? FindChild(Transform currentObject, string path)
		{
			string[] array = path.Split('/');
			foreach (string text in array)
			{
				int childCount = currentObject.childCount;
				for (int j = 0; j < childCount; j++)
				{
					Transform child = currentObject.GetChild(j);
					if (((Object)child).name == text)
					{
						currentObject = child;
						break;
					}
				}
				if (((Object)currentObject).name != text)
				{
					return null;
				}
			}
			return ((Component)currentObject).gameObject;
		}

		private void CreateIconObject()
		{
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0266: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0290: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_029b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Expected O, but got Unknown
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = FindChild(((Component)GameCameras.instance.hudCamera).transform, "In-game/Anchor TL/Hud Canvas Offset/Hud Canvas/Extras").transform;
			GameObject target = FindChild(transform.parent, "Thread/Spool/Thread Spool/Parent/Extender Tool/Extender Sprite");
			GameObject val = FindChild(transform, "Reserve Bind/Reserve Bind Sprite");
			GameObject target2 = FindChild(transform, "Lava Bell HUD/lava_bell_icon");
			GameObject target3 = FindChild(transform, "Maggot Charm/Maggot Charm Sprite");
			string text = "Parent/Canvas/Background Image/Radial Image";
			GameObject target4 = FindChild(transform.parent, "Tool Icons/Tool Icon U/" + text);
			GameObject val2 = FindChild(transform.parent, "Tool Icons/Tool Icon N/" + text);
			GameObject target5 = FindChild(transform.parent, "Tool Icons/Tool Icon D/" + text);
			MicrophoneIcons = new GameObject("Microphone Icons");
			MicrophoneIcons.transform.SetParent(transform, false);
			MicrophoneIcons.transform.localPosition = new Vector3(6.82f, -1.35f, 0f);
			MicrophoneIcons.layer = 5;
			PositionRelativeTo obj = MicrophoneIcons.AddComponent<PositionRelativeTo>();
			PositionRelativeTo component = ((Component)val.transform.parent).GetComponent<PositionRelativeTo>();
			obj.inSpace = component.inSpace;
			obj.target = component.target;
			obj.positionX = true;
			obj.offset = new Vector3(1.35f, 0f, 0f);
			obj.extensions = (ExtensionPair[])(object)new ExtensionPair[7]
			{
				new ExtensionPair
				{
					AddOffset = new Vector3(0.54f, 0f, 0f),
					Target = target
				},
				new ExtensionPair
				{
					AddOffset = new Vector3(1.33f, 0f, 0f),
					Target = val.gameObject
				},
				new ExtensionPair
				{
					AddOffset = new Vector3(1.2f, 0f, 0f),
					Target = target2
				},
				new ExtensionPair
				{
					AddOffset = new Vector3(1.2f, 0f, 0f),
					Target = target3
				},
				new ExtensionPair
				{
					AddOffset = new Vector3(1.34f, 0f, 0f),
					Target = target4
				},
				new ExtensionPair
				{
					AddOffset = new Vector3(1.2f, 0f, 0f),
					Target = val2
				},
				new ExtensionPair
				{
					AddOffset = new Vector3(1.2f, 0f, 0f),
					Target = target5
				}
			};
			SpriteRenderer val3 = MicrophoneIcons.AddComponent<SpriteRenderer>();
			val3.sprite = Unmuted;
			((Renderer)val3).sortingLayerName = "Over";
			((Renderer)val3).sortingOrder = 1;
			MicStatus = val3;
			GameObject val4 = new GameObject("Speaking Indicator");
			Extensions.SetParentReset(val4.transform, MicrophoneIcons.transform);
			val4.transform.localPosition = new Vector3(0f, 0f, 0.23f);
			val4.layer = 5;
			TalkingIndicator = val4.AddComponent<SpriteRenderer>();
			TalkingIndicator.sprite = val2.GetComponent<Image>().sprite;
			((Renderer)TalkingIndicator).sortingLayerName = "Over";
			((Renderer)val3).sortingOrder = 0;
			SetTalking(Status.NotTalking);
		}

		public void DestroyIcon()
		{
			if ((Object)(object)MicrophoneIcons != (Object)null)
			{
				Object.Destroy((Object)(object)MicrophoneIcons);
				MicrophoneIcons = null;
				VoiceChatMod.ModSettings.OnTalkingIndicatorToggled -= OnIndicatorToggled;
			}
		}

		private void OnIndicatorToggled()
		{
			if (!((Object)(object)MicrophoneIcons == (Object)null))
			{
				bool talkingIndicator = VoiceChatMod.ModSettings.TalkingIndicator;
				MicrophoneIcons.SetActive(talkingIndicator);
			}
		}

		public void SetTalking(Status talking)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			if (talking != CurrentStatus)
			{
				CurrentStatus = talking;
				switch (talking)
				{
				case Status.Talking:
					MicStatus.sprite = Unmuted;
					TalkingIndicator.color = TalkingColor;
					break;
				case Status.NotTalking:
					MicStatus.sprite = Unmuted;
					TalkingIndicator.color = NotTalkingColor;
					break;
				case Status.Muted:
					MicStatus.sprite = Muted;
					TalkingIndicator.color = MutedColor;
					break;
				case Status.PushMuted:
					MicStatus.sprite = Unmuted;
					TalkingIndicator.color = MutedColor;
					break;
				default:
					MicStatus.sprite = MicNotFound;
					TalkingIndicator.color = ErrorColor;
					break;
				}
			}
		}

		private void CreateSprites()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			byte[] array = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "microphone_icons.png"));
			Texture2D val = new Texture2D(198, 66);
			ImageConversion.LoadImage(val, array);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 0.5f);
			Unmuted = Sprite.Create(val, new Rect(0f, 0f, 66f, 66f), val2);
			Muted = Sprite.Create(val, new Rect(66f, 0f, 66f, 66f), val2);
			MicNotFound = Sprite.Create(val, new Rect(132f, 0f, 66f, 66f), val2);
		}
	}
}
namespace SsmpVoiceChat.Client.Voice
{
	public class Microphone
	{
		private readonly string _deviceName;

		private IntPtr _device;

		public bool IsOpen => _device != IntPtr.Zero;

		public bool IsStarted { get; private set; }

		public Microphone(string deviceName)
		{
			_device = IntPtr.Zero;
			_deviceName = deviceName;
		}

		public void Open()
		{
			if (IsOpen)
			{
				throw new Exception("Microphone already open");
			}
			_device = OpenMic(_deviceName);
		}

		public void Start()
		{
			if (IsOpen && !IsStarted)
			{
				Alc.CaptureStart(_device);
				SoundManager.CheckAlcError(_device, 0);
				IsStarted = true;
			}
		}

		public void Stop()
		{
			if (!IsOpen || !IsStarted)
			{
				return;
			}
			Alc.CaptureStop(_device);
			SoundManager.CheckAlcError(_device, 0);
			IsStarted = false;
			short[] array = new short[Available()];
			GCHandle gCHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
			try
			{
				Alc.CaptureSamples(_device, gCHandle.AddrOfPinnedObject(), array.Length);
				SoundManager.CheckAlcError(_device, 1);
			}
			catch (Exception arg)
			{
				ClientVoiceChat.Logger.Error($"Exception while capturing samples:\n{arg}");
			}
			finally
			{
				gCHandle.Free();
			}
		}

		public void Close()
		{
			if (IsOpen)
			{
				Stop();
				Alc.CaptureCloseDevice(_device);
				SoundManager.CheckAlcError(_device, 0);
				_device = IntPtr.Zero;
			}
		}

		public int Available()
		{
			int result = default(int);
			Alc.GetInteger(_device, (AlcGetInteger)786, 1, ref result);
			SoundManager.CheckAlcError(_device, 0);
			return result;
		}

		public short[] Read()
		{
			int num = Available();
			if (num < 960)
			{
				throw new InvalidOperationException($"Failed to read from microphone: Capacity {960}, available {num}");
			}
			short[] array = new short[960];
			GCHandle gCHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
			try
			{
				Alc.CaptureSamples(_device, gCHandle.AddrOfPinnedObject(), array.Length);
				SoundManager.CheckAlcError(_device, 0);
			}
			catch (Exception arg)
			{
				ClientVoiceChat.Logger.Error($"Exception while capturing samples:\n{arg}");
			}
			finally
			{
				gCHandle.Free();
			}
			return array;
		}

		private IntPtr OpenMic(string name)
		{
			try
			{
				return TryOpenMic(name);
			}
			catch (Exception)
			{
				if (name != null)
				{
					ClientVoiceChat.Logger.Error("Failed to open microphone '" + name + "', falling back to default microphone");
				}
				try
				{
					return TryOpenMic(GetDefaultMicrophone());
				}
				catch (Exception)
				{
					return TryOpenMic(null);
				}
			}
		}

		private IntPtr TryOpenMic(string name)
		{
			IntPtr intPtr = Alc.CaptureOpenDevice(name, 48000, (ALFormat)4353, 960);
			if (intPtr == IntPtr.Zero)
			{
				SoundManager.CheckAlcError(IntPtr.Zero, 0);
				throw new Exception("Failed to open microphone");
			}
			return intPtr;
		}

		public static string GetDefaultMicrophone()
		{
			string @string = Alc.GetString(IntPtr.Zero, (AlcGetString)785);
			SoundManager.CheckAlcError(IntPtr.Zero, 0);
			return @string;
		}

		public static List<string> GetAllMicrophones()
		{
			List<string> list = Alc.GetString(IntPtr.Zero, (AlcGetStringList)784).ToList();
			list.Sort();
			SoundManager.CheckAlcError(IntPtr.Zero, 0);
			if (list != null)
			{
				return list.ToList();
			}
			return new List<string>();
		}
	}
	public class MicrophoneManager
	{
		private readonly OpusCodec _encoder;

		private readonly RNNoise _denoiser;

		private readonly WebRtcVad _webRtcVad;

		private Thread _thread;

		private bool _isRunning;

		private Microphone _microphone;

		private bool _activating;

		private byte[] _lastBuff;

		public event Action<byte[]> VoiceDataEvent;

		public event Action VoiceOffEvent;

		public MicrophoneManager()
		{
			_encoder = new OpusCodec(48000, 1, 960);
			_denoiser = new RNNoise();
			_webRtcVad = new WebRtcVad
			{
				SampleRate = 48000,
				FrameLength = 20,
				OperatingMode = OperatingMode.Aggressive
			};
		}

		public void Start()
		{
			if (_isRunning)
			{
				Stop();
			}
			_thread = new Thread((ThreadStart)delegate
			{
				_isRunning = true;
				if (GetMic())
				{
					while (_isRunning)
					{
						try
						{
							if (PollMic(out short[] buff))
							{
								byte[] array = DataUtils.ShortsToBytes(buff);
								bool flag = _webRtcVad.HasSpeech(buff);
								if (!_activating)
								{
									if (flag)
									{
										if (_lastBuff != null)
										{
											this.VoiceDataEvent?.Invoke(_encoder.Encode(_lastBuff));
										}
										this.VoiceDataEvent?.Invoke(_encoder.Encode(array));
										_activating = true;
									}
								}
								else if (!flag)
								{
									_activating = false;
									this.VoiceOffEvent?.Invoke();
								}
								else
								{
									this.VoiceDataEvent?.Invoke(_encoder.Encode(array));
								}
								_lastBuff = array;
							}
						}
						catch (Exception arg)
						{
							ClientVoiceChat.Logger.Error($"Error in mic thread:\n{arg}");
						}
					}
				}
			});
			_thread.Start();
		}

		public void Stop()
		{
			if (_isRunning)
			{
				_isRunning = false;
				_thread.Join(100);
				_thread = null;
				_microphone.Close();
				_microphone = null;
			}
		}

		private bool GetMic()
		{
			if (!_isRunning)
			{
				return false;
			}
			if (_microphone == null)
			{
				_microphone = new Microphone(VoiceChatMod.ModSettings.MicrophoneDeviceName);
			}
			if (!_microphone.IsOpen)
			{
				_microphone.Open();
			}
			return true;
		}

		private bool PollMic(out short[] buff)
		{
			if (_microphone == null)
			{
				throw new InvalidOperationException("Cannot poll unknown microphone");
			}
			if (!_microphone.IsStarted)
			{
				_microphone.Start();
			}
			if (_microphone.Available() < 960)
			{
				Thread.Sleep(5);
				buff = null;
				return false;
			}
			buff = _microphone.Read();
			if (buff == null)
			{
				Thread.Sleep(5);
				buff = null;
				return false;
			}
			buff = VolumeManager.AmplifyAudioData(buff, VoiceChatMod.ModSettings.MicrophoneAmplification);
			buff = _denoiser.ProcessFrame(buff);
			return true;
		}
	}
	public class SoundManager
	{
		public const int SampleRate = 48000;

		public const int FrameLength = 20;

		public const int BufferSize = 960;

		private IntPtr _device;

		private ContextHandle _context;

		private readonly ConcurrentDictionary<ushort, Speaker> _speakers;

		private bool IsClosed => _device == IntPtr.Zero;

		public SoundManager()
		{
			_speakers = new ConcurrentDictionary<ushort, Speaker>();
		}

		public void Open()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			string name = ((!string.IsNullOrEmpty(VoiceChatMod.ModSettings.SpeakerDeviceName)) ? VoiceChatMod.ModSettings.SpeakerDeviceName : GetDefaultDeviceSpeaker());
			_device = OpenDeviceSpeaker(name);
			_context = Alc.CreateContext(_device, Array.Empty<int>());
			Alc.MakeContextCurrent(_context);
		}

		public void Close()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			foreach (Speaker value in _speakers.Values)
			{
				value.Close();
			}
			_speakers.Clear();
			if (_context != ContextHandle.Zero)
			{
				Alc.DestroyContext(_context);
				CheckAlcError(_device, 0);
			}
			if (_device != IntPtr.Zero)
			{
				Alc.CloseDevice(_device);
			}
			_context = ContextHandle.Zero;
			_device = IntPtr.Zero;
		}

		public bool TryGetOrCreateSpeaker(ushort id, [MaybeNullWhen(false)] out Speaker speaker)
		{
			if (IsClosed)
			{
				speaker = null;
				return false;
			}
			if (!_speakers.TryGetValue(id, out speaker))
			{
				speaker = new Speaker();
				speaker.Open();
				_speakers.TryAdd(id, speaker);
			}
			return true;
		}

		public bool TryRemoveSpeaker(ushort id)
		{
			if (IsClosed)
			{
				return false;
			}
			if (_speakers.TryRemove(id, out Speaker value))
			{
				value.Close();
			}
			return true;
		}

		private IntPtr OpenDeviceSpeaker(string name)
		{
			try
			{
				return TryOpenDeviceSpeaker(name);
			}
			catch (Exception)
			{
				if (name != null)
				{
					ClientVoiceChat.Logger.Error("Failed to open audio channel '" + name + "', falling back to default");
				}
				try
				{
					return TryOpenDeviceSpeaker(GetDefaultDeviceSpeaker());
				}
				catch (Exception)
				{
					return TryOpenDeviceSpeaker(null);
				}
			}
		}

		private IntPtr TryOpenDeviceSpeaker(string name)
		{
			IntPtr intPtr = Alc.OpenDevice(name);
			if (intPtr == IntPtr.Zero)
			{
				throw new Exception("Failed to open audio device: Audio device not found");
			}
			CheckAlcError(intPtr, 0);
			return intPtr;
		}

		public static string GetDefaultDeviceSpeaker()
		{
			string @string = Alc.GetString(IntPtr.Zero, (AlcGetString)4114);
			CheckAlcError(IntPtr.Zero, 0);
			return @string;
		}

		public static List<string> GetAllDeviceSpeakers()
		{
			IList<string> @string = Alc.GetString(IntPtr.Zero, (AlcGetStringList)4115);
			CheckAlcError(IntPtr.Zero, 0);
			if (@string != null)
			{
				return @string.ToList();
			}
			return new List<string>();
		}

		public static bool CheckAlError(int index)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			ALError error = AL.GetError();
			if ((int)error == 0)
			{
				return false;
			}
			StackFrame stackFrame = new StackFrame(1);
			ClientVoiceChat.Logger.Error($"VoiceChat sound manager AL error: {stackFrame.GetMethod().DeclaringType}.{stackFrame.GetMethod().Name}[{index}] {error}");
			return true;
		}

		public static bool CheckAlcError(IntPtr device, int index)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			AlcError error = Alc.GetError(device);
			if ((int)error == 0)
			{
				return false;
			}
			StackFrame stackFrame = new StackFrame(1);
			ClientVoiceChat.Logger.Error($"VoiceChat sound manager ALC error: {stackFrame.GetMethod().DeclaringType}.{stackFrame.GetMethod().Name}[{index}] {error}");
			return true;
		}
	}
	public class Speaker
	{
		private const float DefaultMaxDistance = 60f;

		private const int NumBuffers = 32;

		private readonly OpusCodec _decoder;

		private int _source;

		private int[] _buffers;

		private int _bufferIndex;

		public Speaker()
		{
			_decoder = new OpusCodec(48000, 1, 960);
		}

		public void Open()
		{
			if (!HasValidSource())
			{
				_source = AL.GenSource();
				SoundManager.CheckAlError(0);
				AL.Source(_source, (ALSourceb)4103, false);
				SoundManager.CheckAlError(1);
				AL.DistanceModel((ALDistanceModel)53251);
				SoundManager.CheckAlError(2);
				AL.Source(_source, (ALSourcef)4131, VoiceChatMod.ModSettings.MaxDistance);
				SoundManager.CheckAlError(3);
				AL.Source(_source, (ALSourcef)4128, 0f);
				SoundManager.CheckAlError(4);
				AL.Source(_source, (ALSource3f)4101, 0f, 0f, 0f);
				SoundManager.CheckAlError(5);
				_buffers = AL.GenBuffers(32);
				SoundManager.CheckAlError(6);
			}
		}

		public void Play(byte[] encodedData, Vector3? position = null)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Invalid comparison between Unknown and I4
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			short[] data = DataUtils.BytesToShorts(_decoder.Decode(encodedData));
			RemoveProcessedBuffers();
			Write(data, position);
			int queuedBuffers = GetQueuedBuffers();
			if ((int)GetState() == 4113 || (int)GetState() == 4116 || queuedBuffers <= 1)
			{
				AL.SourcePlay(_source);
				SoundManager.CheckAlError(0);
			}
		}

		private void Write(short[] data, Vector3? position)
		{
			SetPosition(position);
			float voiceChatVolume = VoiceChatMod.ModSettings.VoiceChatVolume;
			AL.Source(_source, (ALSourcef)4110, 1f);
			SoundManager.CheckAlError(0);
			AL.Source(_source, (ALSourcef)4106, voiceChatVolume);
			SoundManager.CheckAlError(1);
			AL.Listener((ALListenerf)4106, 1f);
			SoundManager.CheckAlError(2);
			if (GetQueuedBuffers() >= _buffers.Length)
			{
				int num = default(int);
				AL.GetSource(_source, (ALGetSourcei)4133, ref num);
				SoundManager.CheckAlError(3);
				AL.Source(_source, (ALSourcei)4133, num + 960);
				SoundManager.CheckAlError(4);
				RemoveProcessedBuffers();
			}
			AL.BufferData<short>(_buffers[_bufferIndex], (ALFormat)4353, data, data.Length * 2, 48000);
			SoundManager.CheckAlError(5);
			AL.SourceQueueBuffer(_source, _buffers[_bufferIndex]);
			SoundManager.CheckAlError(6);
			_bufferIndex = (_bufferIndex + 1) % _buffers.Length;
		}

		private void LinearAttenuation()
		{
			float maxDistance = VoiceChatMod.ModSettings.MaxDistance;
			AL.DistanceModel((ALDistanceModel)53251);
			SoundManager.CheckAlError(0);
			AL.Source(_source, (ALSourcef)4131, maxDistance);
			SoundManager.CheckAlError(1);
			AL.Source(_source, (ALSourcef)4129, VoiceChatMod.ModSettings.RolloffFactor);
			SoundManager.CheckAlError(2);
		}

		private void SetPosition(Vector3? soundPos)
		{
			AL.Listener((ALListener3f)4100, 0f, 0f, 0f);
			SoundManager.CheckAlError(0);
			float[] array = new float[6] { 0f, 0f, -1f, 0f, 1f, 0f };
			AL.Listener((ALListenerfv)4111, ref array);
			SoundManager.CheckAlError(1);
			if (soundPos != null)
			{
				float x = soundPos.X;
				float y = soundPos.Y;
				float num = soundPos.Z;
				if (VoiceChatMod.ModSettings.SmoothChannelTransition)
				{
					num = -5f;
				}
				LinearAttenuation();
				AL.Source(_source, (ALSourceb)514, true);
				SoundManager.CheckAlError(2);
				AL.Source(_source, (ALSource3f)4100, x, y, num);
				SoundManager.CheckAlError(3);
			}
			else
			{
				LinearAttenuation();
				AL.Source(_source, (ALSourceb)514, true);
				SoundManager.CheckAlError(4);
				AL.Source(_source, (ALSource3f)4100, 0f, 0f, 0f);
				SoundManager.CheckAlError(5);
			}
		}

		public void Close()
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			if (HasValidSource())
			{
				if ((int)GetState() == 4114)
				{
					AL.SourceStop(_source);
					SoundManager.CheckAlError(0);
				}
				int num = default(int);
				AL.GetSource(_source, (ALGetSourcei)4118, ref num);
				SoundManager.CheckAlError(1);
				if (num > 0)
				{
					AL.SourceUnqueueBuffers(_source, num);
					SoundManager.CheckAlError(2);
				}
				AL.DeleteSource(_source);
				SoundManager.CheckAlError(3);
				AL.DeleteBuffers(_buffers);
				SoundManager.CheckAlError(4);
			}
			_source = 0;
		}

		private void RemoveProcessedBuffers()
		{
			int num = default(int);
			AL.GetSource(_source, (ALGetSourcei)4118, ref num);
			SoundManager.CheckAlError(0);
			if (num > 0)
			{
				AL.SourceUnqueueBuffers(_source, num);
				SoundManager.CheckAlError(1);
			}
		}

		private ALSourceState GetState()
		{
			int num = default(int);
			AL.GetSource(_source, (ALGetSourcei)4112, ref num);
			SoundManager.CheckAlError(0);
			return (ALSourceState)num;
		}

		private int GetQueuedBuffers()
		{
			int result = default(int);
			AL.GetSource(_source, (ALGetSourcei)4117, ref result);
			SoundManager.CheckAlError(0);
			return result;
		}

		private bool HasValidSource()
		{
			return AL.IsSource(_source);
		}
	}
	public static class VolumeManager
	{
		private const short MaxAmplification = 32766;

		private static readonly float[] MaxMultipliers;

		private static int _index;

		static VolumeManager()
		{
			MaxMultipliers = new float[50];
		}

		public static short[] AmplifyAudioData(short[] audio, float multiplier)
		{
			MaxMultipliers[_index] = GetMaximumAmplification(audio, multiplier);
			_index = (_index + 1) % MaxMultipliers.Length;
			float num = -1f;
			float[] maxMultipliers = MaxMultipliers;
			foreach (float num2 in maxMultipliers)
			{
				if (!(num2 < 0f))
				{
					if (num < 0f)
					{
						num = num2;
					}
					else if (num2 < num)
					{
						num = num2;
					}
				}
			}
			if (num < 0f)
			{
				num = 1f;
			}
			float num3 = Math.Min(num, multiplier);
			short[] array = new short[audio.Length];
			for (int j = 0; j < audio.Length; j++)
			{
				array[j] = (short)((float)audio[j] * num3);
			}
			return array;
		}

		private static float GetMaximumAmplification(short[] audio, float multiplier)
		{
			short num = 0;
			foreach (short num2 in audio)
			{
				short num3 = ((num2 != short.MinValue) ? Math.Abs(num2) : short.MaxValue);
				if (num3 > num)
				{
					num = num3;
				}
			}
			if (num == 0)
			{
				return 1f;
			}
			return Math.Min(multiplier, 32766f / (float)num);
		}
	}
}