Decompiled source of Buttplug Song v1.1.9

ButtplugSong.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
using BepInEx;
using ButtplugManaged;
using ButtplugSong;
using ButtplugSong.GUI;
using ButtplugSong.GUI.CustomUI;
using ButtplugSong.GUI.Network;
using ButtplugSong.GUI.VibeSettings;
using ButtplugSong.GUI.VibeSettings.LimitSettings;
using ButtplugSong.GUI.VibeSettings.Presets;
using ButtplugSong.GUI.VibeSettings.VibeSources;
using ButtplugSong.Helper;
using ButtplugSong.Network;
using GlobalEnums;
using GlobalSettings;
using GoodVibes;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Scripting;
using UnityEngine.UIElements;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ButtplugSong")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.9.0")]
[assembly: AssemblyInformationalVersion("1.1.9+28274baeffe51568729da97fcd6e03e66d858f89")]
[assembly: AssemblyProduct("ButtplugSong")]
[assembly: AssemblyTitle("Buttplug_Song")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/danatron1/ButtplugSong")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.9.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
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;
		}
	}
}
public static class FloatHelper
{
	public static float Clamp(this float value, float min, float max)
	{
		if (value > max)
		{
			return max;
		}
		if (value < min)
		{
			return min;
		}
		return value;
	}
}
namespace BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Microsoft.CodeAnalysis.Embedded]
	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")]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace Microsoft.CodeAnalysis
{
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace GoodVibes
{
	internal enum WaveType
	{
		ConstantLinear,
		ConstantExponental,
		SineSmall,
		SineBig,
		Square,
		PulseWidth,
		Triangle,
		InverseTriangle,
		Bounce,
		ZigZag
	}
	public enum AfterZeroMode
	{
		Subtract,
		Multiply
	}
	internal class VibeLogic
	{
		private class ActivityLogEntry
		{
			internal string Identifier;

			internal string Details;

			internal ActivityLogEntry? TriggeredLog;

			public ActivityLogEntry(string identifier, string details)
			{
				Identifier = identifier;
				Details = details;
				base..ctor();
			}
		}

		public static bool Armed;

		private Action<string>? _logger;

		private (string identifier, string details)? triggeredActivityLog;

		internal bool timerCountdownPaused = true;

		internal bool vibeWhileTimerPaused = true;

		internal const float _actualHardTimerMaximum = 5999f;

		private float _maxTimer = 600f;

		private float _timeRemaining;

		internal AfterZeroMode afterZeroMode;

		internal float afterZeroPowerChange = 1f;

		internal float afterZeroTimer;

		internal float afterZeroPunctuate;

		private float punctuatingTimer;

		private float _maxPower = 1f;

		private float _minPower;

		private float _targetPower;

		public WaveType waveType;

		public float wavePeriod = 1f;

		public float ComboMultiplier = 1f;

		private float _timeSinceLastActivityLog;

		public float MaxTimer
		{
			get
			{
				return _maxTimer;
			}
			set
			{
				_maxTimer = value.Clamp(0f, 5999f);
				if (Time > _maxTimer)
				{
					Time = _maxTimer;
				}
			}
		}

		public bool TimerZero => _timeRemaining <= 0f;

		public bool IsVibing => ActualPower > 0f;

		public float Time
		{
			get
			{
				return _timeRemaining;
			}
			set
			{
				bool flag = !TimerZero;
				_timeRemaining = value.Clamp(0f, MaxTimer);
				if (TimerZero && flag)
				{
					TimerBecameZero();
				}
			}
		}

		public bool Punctuating => punctuatingTimer > 0f;

		public float MaxPower
		{
			get
			{
				return _maxPower;
			}
			set
			{
				float maxPower = _maxPower;
				_maxPower = value.Clamp(0f, 1f);
				if (MinPower > MaxPower)
				{
					MinPower = MaxPower;
				}
				if (_maxPower != maxPower)
				{
					UpdatePowerClamped(TargetPower);
				}
			}
		}

		public float MinPower
		{
			get
			{
				return _minPower;
			}
			set
			{
				float minPower = _minPower;
				_minPower = value.Clamp(0f, MaxPower);
				if (_minPower != minPower)
				{
					UpdatePowerClamped(TimerZero ? _minPower : TargetPower);
				}
			}
		}

		public float TargetPower
		{
			get
			{
				return _targetPower;
			}
			private set
			{
				UpdatePowerClamped(value);
			}
		}

		public float ActualPower
		{
			get
			{
				if (timerCountdownPaused && !vibeWhileTimerPaused)
				{
					return GetWavePower(MinPower);
				}
				if (Punctuating)
				{
					return MaxPower;
				}
				if (TimerZero)
				{
					return GetWavePower(MinPower);
				}
				return GetWavePower(TargetPower);
			}
		}

		public event Action? PowerChanged;

		public event Action<bool>? PunctuateChanged;

		public event Action? VibeSourceActivated;

		public event Action? TimerHitZero;

		public event Action<string, string, float>? AddActivityLog;

		private void TimerBecameZero()
		{
			if (TargetPower == MinPower && TimerZero)
			{
				return;
			}
			string text = "Timer Hit Zero";
			string text2;
			if (TargetPower == MinPower)
			{
				text += " : Power Min";
				text2 = "Power hit " + ((MinPower == 0f) ? "zero" : "minimum") + ".\nTimer reset to 0 seconds.";
				Time = 0f;
			}
			else
			{
				float num = (afterZeroMode switch
				{
					AfterZeroMode.Subtract => TargetPower - afterZeroPowerChange, 
					AfterZeroMode.Multiply => TargetPower * afterZeroPowerChange, 
					_ => throw new NotImplementedException(), 
				}).Clamp(MinPower, MaxPower);
				text2 = $"Power: {TargetPower * 100f:0.#}% {AfterZeroModeString()} {afterZeroPowerChange * 100f:0.#}% = {num * 100f:0.#}%";
				if (MinPower != 0f)
				{
					text2 += $" (minimum {MinPower * 100f:0.#}%)";
				}
				if (num - MinPower < 0.02f)
				{
					if (num - MinPower > Mathf.Epsilon)
					{
						text2 = text2 + "\n(New power almost " + ((MinPower == 0f) ? "zero" : "minimum") + "; cutting off)";
					}
					num = MinPower;
				}
				else if (afterZeroTimer <= 0f)
				{
					text2 = text2 + "\nNo timer increase, so setting power to " + ((MinPower == 0f) ? "0" : "minimum") + ".";
					num = MinPower;
				}
				else
				{
					text2 += string.Format("\nTimer set to {0} second{1}.", afterZeroTimer, (afterZeroTimer != 1f) ? "s" : "");
					Time += afterZeroTimer;
				}
				TargetPower = num;
			}
			if (afterZeroPunctuate > 0f)
			{
				AddPunctuateHit(afterZeroPunctuate);
				text2 += $"\nPunctuate: +{afterZeroPunctuate}s of max power";
			}
			triggeredActivityLog = (text, text2);
			this.TimerHitZero?.Invoke();
			string AfterZeroModeString()
			{
				return afterZeroMode switch
				{
					AfterZeroMode.Subtract => "-", 
					AfterZeroMode.Multiply => "*", 
					_ => throw new NotImplementedException(), 
				};
			}
		}

		public void DecreaseTimer(float realTimeAmount, float timerAmount)
		{
			TickActivityLogs(realTimeAmount);
			if (Punctuating)
			{
				punctuatingTimer -= realTimeAmount;
				if (punctuatingTimer <= 0f)
				{
					Time += punctuatingTimer;
					punctuatingTimer = 0f;
					this.PunctuateChanged?.Invoke(obj: false);
				}
			}
			else
			{
				Time -= timerAmount;
			}
			timerCountdownPaused = timerAmount <= float.Epsilon;
		}

		private void UpdatePowerClamped(float value)
		{
			float targetPower = _targetPower;
			_targetPower = value.Clamp(MinPower, MaxPower);
			if (_targetPower != targetPower)
			{
				PowerUpdated(targetPower, _targetPower);
			}
		}

		private void PowerUpdated(float before, float after)
		{
			if (after == MinPower)
			{
				TimerBecameZero();
			}
			this.PowerChanged?.Invoke();
		}

		private float GetWavePower(float power)
		{
			if (waveType == WaveType.ConstantLinear || power == 0f)
			{
				return power;
			}
			float num = Time / wavePeriod;
			return (waveType switch
			{
				WaveType.ConstantExponental => power * power, 
				WaveType.SineSmall => power * ((Mathf.Sin(num * MathF.PI * 2f) + 2f) / 3f), 
				WaveType.SineBig => Mathf.Sin(num * MathF.PI * 2f) * 0.8f * power + 0.8f * power, 
				WaveType.Square => (num % 1f > 0.5f) ? 0f : power, 
				WaveType.PulseWidth => (num % 1f > power) ? 0f : 1f, 
				WaveType.Triangle => num % 1f * power, 
				WaveType.InverseTriangle => power * (1f - num % 1f), 
				WaveType.Bounce => num % 1f * (1f - num % 1f) * power * 4f, 
				WaveType.ZigZag => Mathf.Max(num % 1f, 1f - num % 1f) * power + power / 4f, 
				_ => power, 
			}).Clamp(MinPower, MaxPower);
		}

		public VibeLogic(Action<string>? logger = null)
		{
			_logger = logger;
		}

		public void VibeSourceActivation(string identifier, float power, string powerMode, float time, string timeMode, float punctuateTime, float? basePower = null, float? baseTime = null)
		{
			string powerMode2 = powerMode;
			string timeMode2 = timeMode;
			string identifier2 = identifier;
			float powerBefore;
			float timeBefore;
			float newPower;
			float newTime;
			if (Armed)
			{
				powerBefore = TargetPower;
				timeBefore = Time;
				if (timeMode2 == "+" && !TimerZero)
				{
					time *= ComboMultiplier;
				}
				newPower = ChangeByMode(TargetPower, power, powerMode2).Clamp(MinPower, MaxPower);
				newTime = ChangeByMode(Time, time, timeMode2).Clamp(0f, MaxTimer);
				if (newTime != 0f)
				{
					TargetPower = newPower;
				}
				if (TargetPower != MinPower)
				{
					Time = newTime;
				}
				if (punctuateTime != 0f)
				{
					AddPunctuateHit(punctuateTime);
				}
				SendDetailedActivityLog();
				this.VibeSourceActivated?.Invoke();
			}
			static float ChangeByMode(float original, float modifier, string mode)
			{
				return mode switch
				{
					"+" => original + modifier, 
					"-" => original - modifier, 
					"=" => modifier, 
					"≥" => Mathf.Max(modifier, original), 
					"≤" => Mathf.Min(modifier, original), 
					"x" => original * modifier, 
					_ => original, 
				};
			}
			void SendDetailedActivityLog()
			{
				string text = "";
				if (!PowerFieldMeaningless(power, powerMode2))
				{
					text += $"Power: {powerMode2}{power * 100f:0.#}%";
					if (basePower.HasValue && basePower.Value != power)
					{
						text += $" ({basePower.Value * 100f:0.#}% x {power / basePower.Value:0.###})";
					}
					text = ((newPower != powerBefore) ? (text + $", {powerBefore * 100f:0.#}% -> {newPower * 100f:0.#}%") : (text + $", remain at {powerBefore * 100f:0.#}%"));
					if (MinPower != 0f && newPower == MinPower)
					{
						text += " (min)";
					}
					else if (MaxPower != 1f && newPower == MaxPower)
					{
						text += " (max)";
					}
				}
				if (!TimeFieldMeaningless(time, timeMode2))
				{
					text = text + "\nTime: " + timeMode2 + DisplayTime(time);
					if (baseTime.HasValue && baseTime.Value != time)
					{
						text += $" ({DisplayTime(baseTime.Value, includeS: false)} x {time / baseTime.Value:0.###})";
					}
					text = ((newTime != timeBefore) ? (text + ", " + DisplayTime(timeBefore, includeS: false) + " -> " + DisplayTime(newTime, includeS: false)) : (text + ", remain at " + DisplayTime(timeBefore)));
					if (MaxTimer == newTime)
					{
						text += " (max)";
					}
				}
				if (punctuateTime != 0f)
				{
					text += $"\nPunctuate: +{punctuateTime:0.##}s (now at {punctuatingTimer:0.##}s)";
				}
				text = text.Trim('\n');
				if (!string.IsNullOrWhiteSpace(text))
				{
					ProcessActivityLog(identifier2, text);
				}
			}
		}

		internal static string DisplayTime(float time, bool includeS = true)
		{
			int num = (int)(time / 60f);
			int num2 = num / 60;
			if (num2 >= 1)
			{
				return $"{num2:f0}:{num:00}:{time % 60f:00}";
			}
			if (num >= 1)
			{
				return $"{num:f0}:{time % 60f:00}";
			}
			if (includeS)
			{
				return $"{time:0.#}s";
			}
			return $"{time:0.#}";
		}

		private void AddPunctuateHit(float punctuateTime)
		{
			bool punctuating = Punctuating;
			punctuatingTimer += punctuateTime;
			if (Punctuating != punctuating)
			{
				this.PunctuateChanged?.Invoke(Punctuating);
			}
		}

		internal static bool PowerFieldMeaningless(float power, string powerMode)
		{
			switch (powerMode)
			{
			case "+":
			case "-":
			case "≥":
				return power <= 0f;
			case "≤":
				return power >= 1f;
			case "x":
				return power == 1f;
			default:
				return false;
			}
		}

		internal static bool TimeFieldMeaningless(float time, string timeMode)
		{
			switch (timeMode)
			{
			case "+":
			case "-":
			case "≥":
				return time <= 0f;
			case "x":
				return time == 1f;
			default:
				return false;
			}
		}

		private void Log(string s)
		{
			_logger?.Invoke(s);
		}

		private void TickActivityLogs(float deltaTime)
		{
			_timeSinceLastActivityLog += deltaTime;
			ProcessTriggeredActivityLog();
		}

		private void ProcessActivityLog(string identifier, string details)
		{
			this.AddActivityLog?.Invoke(identifier, details, _timeSinceLastActivityLog);
			_timeSinceLastActivityLog = 0f;
			ProcessTriggeredActivityLog();
		}

		private void ProcessTriggeredActivityLog()
		{
			(string, string)? tuple = triggeredActivityLog;
			if (tuple.HasValue)
			{
				string item = triggeredActivityLog.Value.identifier;
				string item2 = triggeredActivityLog.Value.details;
				triggeredActivityLog = null;
				ProcessActivityLog(item, item2);
			}
		}

		internal void MultiplyTimer(float multiplier, string? subID = null)
		{
			if (multiplier != 1f && Time != 0f)
			{
				float time = Time;
				Time *= multiplier;
				string text = "Multiply Timer";
				if (!string.IsNullOrWhiteSpace(subID))
				{
					text = text + " : " + subID;
				}
				ProcessActivityLog(text, $"Timer {multiplier}x multiplier: {DisplayTime(time, includeS: false)} -> {DisplayTime(Time, includeS: false)}");
			}
		}
	}
	public class VibeManager
	{
		private static VibeManager? _instance;

		internal GUIManager UI;

		internal VibeLogic Logic;

		internal PlugManager plug;

		public float PlugUpdateFrequency = 0.125f;

		private float timeSinceLastPlugUpdate;

		public static VibeManager Instance
		{
			get
			{
				if (_instance == null)
				{
					throw new NotImplementedException("Instantiate properly before referencing! >:(");
				}
				return _instance;
			}
			private set
			{
				_instance = value;
			}
		}

		public bool HasDevice => GetDevices().Any();

		public event Action? PlugReconnectEstablished;

		public event Action<float, float>? NeedsUpdate;

		public event Action<string>? LogMessage;

		public VibeManager(string modPath, Action<string>? logger = null)
		{
			Instance = this;
			if (logger != null)
			{
				LogMessage += logger;
			}
			Logic = new VibeLogic(Log);
			UI = GUIManager.CreateAndInitialize();
			Logic.PowerChanged += TargetPowerChanged;
			Logic.PunctuateChanged += PunctuateChanged;
			NeedsUpdate += Logic.DecreaseTimer;
			ModHooks.OnFinishedLoadingModsHook += FinishedLoading;
			ReconnectPlug();
		}

		private void FinishedLoading()
		{
			VibeLogic.Armed = true;
			UI.FinishedLoading();
		}

		public void Update(float realTime, float timerTime)
		{
			this.NeedsUpdate?.Invoke(realTime, timerTime);
			timeSinceLastPlugUpdate += realTime;
			if (timeSinceLastPlugUpdate > NetworkSettings.UpdateFrequency)
			{
				ForcePlugUpdate(routineUpdate: true);
			}
		}

		public void TargetPowerChanged()
		{
			ForcePlugUpdate(routineUpdate: false);
		}

		public void PunctuateChanged(bool _)
		{
			ForcePlugUpdate(routineUpdate: false);
		}

		public void ForcePlugUpdate(bool routineUpdate)
		{
			timeSinceLastPlugUpdate = 0f;
			plug.SetPowerLevel(Logic.ActualPower, routineUpdate);
		}

		internal IEnumerable<ButtplugClientDevice> GetDevices()
		{
			return plug.GetDevices();
		}

		internal void ReconnectPlug()
		{
			DisconnectPlug();
			plug = new PlugManager(Log, NetworkSettings.ServerAddress, NetworkSettings.Port, NetworkSettings.RetryAttempts);
			this.PlugReconnectEstablished?.Invoke();
		}

		internal void DisconnectPlug()
		{
			plug?.ShutDown();
		}

		internal void Log(string message)
		{
			LogAsync(message, 5);
		}

		internal async void LogAsync(string message, int attempts)
		{
			message = $"[@{DateTime.UtcNow.Minute:D2}:{DateTime.UtcNow.Second:D2}] {message}";
			for (int logAttempts = 0; logAttempts < attempts; logAttempts++)
			{
				try
				{
					this.LogMessage?.Invoke(message);
					break;
				}
				catch (IOException)
				{
					await Task.Delay(50);
				}
			}
		}
	}
}
namespace ButtplugSong
{
	[BepInPlugin("danatron1-ButtplugSongMod-Silksong", "ButtplugSong", "1.1.9")]
	public class ButtplugSongPlugin : BaseUnityPlugin
	{
		public static string ModPath = "Not yet loaded";

		private const string ModId = "danatron1-ButtplugSongMod-Silksong";

		private const string ModName = "ButtplugSong";

		private const string ModVersion = "1.1.9";

		private readonly Harmony harmony = new Harmony("danatron1-ButtplugSongMod-Silksong");

		private VibeManager vibe;

		public const string Id = "danatron1-ButtplugSongMod-Silksong";

		public static string Name => "ButtplugSong";

		public static string Version => "1.1.9";

		private void Awake()
		{
			ModPath = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			vibe = new VibeManager(ModPath, (Action<string>?)((BaseUnityPlugin)this).Logger.LogInfo);
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin " + Name + " (danatron1-ButtplugSongMod-Silksong) has loaded!"));
		}

		private void OnDestroy()
		{
			vibe?.DisconnectPlug();
			harmony.UnpatchSelf();
		}
	}
	[HarmonyPatch]
	internal static class ModHooks
	{
		[HarmonyPatch]
		private static class SavedItemGetPatch
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				List<MethodBase> list = new List<MethodBase>();
				MethodInfo method = typeof(SavedItem).GetMethod("Get", new Type[2]
				{
					typeof(int),
					typeof(bool)
				});
				if (method != null && !method.IsAbstract)
				{
					list.Add(method);
				}
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					Type[] array;
					try
					{
						array = assembly.GetTypes();
					}
					catch (ReflectionTypeLoadException ex)
					{
						array = ex.Types.Where((Type t) => t != null).ToArray();
					}
					catch
					{
						continue;
					}
					Type[] array2 = array;
					foreach (Type type in array2)
					{
						if (!typeof(SavedItem).IsAssignableFrom(type) || type == typeof(SavedItem))
						{
							continue;
						}
						MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
						foreach (MethodInfo methodInfo in methods)
						{
							if (methodInfo.Name == "Get" && !methodInfo.IsAbstract)
							{
								list.Add(methodInfo);
							}
						}
					}
				}
				return list;
			}

			[HarmonyPrefix]
			private static void Prefix(SavedItem __instance)
			{
				ModHooks.OnItemPickupHook?.Invoke(__instance);
			}
		}

		public static event Func<PlayerData, int, int>? OnTakeDamageHook;

		public static event Action<HeroController>? OnBindInterruptedHook;

		public static event Func<PlayerData, int, int>? OnAddHealthHook;

		public static event Func<HealthManager, HitInstance, int>? OnDealDamageHook;

		public static event Action<HeroController, bool, bool>? OnBeforeDeathHook;

		public static event Action<GameManager>? OnAfterDeathHook;

		public static event Action<HeroController>? OnGetCocoonHook;

		public static event Action<string, bool>? OnSetBoolHook;

		public static event Action<string, int>? OnSetIntHook;

		public static event Action<string?>? OnRumbleHook;

		public static event Func<PlayerData, int, int>? OnAddRosariesHook;

		public static event Func<PlayerData, int, int>? OnTakeRosariesHook;

		public static event Func<PlayerData, int, int>? OnAddShardsHook;

		public static event Func<PlayerData, int, int>? OnTakeShardsHook;

		public static event Action<HeroController>? OnHardLandingHook;

		public static event Action<DeliveryQuestItem>? OnCourierBreakItemHook;

		public static event Action? OnFinishedLoadingModsHook;

		public static event Action<int>? OnMaxHealthUpHook;

		public static event Action<int>? OnMaxSilkUpHook;

		public static event Action<ToolItem>? OnToolUnlockHook;

		public static event Action<SavedItem>? OnItemPickupHook;

		[HarmonyPatch(typeof(PlayerData), "TakeHealth")]
		[HarmonyPrefix]
		private static void PlayerData_TakeHealth(PlayerData __instance, ref int amount, ref bool hasBlueHealth, ref bool allowFracturedMaskBreak)
		{
			if (ModHooks.OnTakeDamageHook != null)
			{
				amount = ModHooks.OnTakeDamageHook(__instance, amount);
			}
		}

		[HarmonyPatch(typeof(HeroController), "BindInterrupted")]
		[HarmonyPostfix]
		private static void OnBindInterrupted(HeroController __instance)
		{
			ModHooks.OnBindInterruptedHook?.Invoke(__instance);
		}

		[HarmonyPatch(typeof(PlayerData), "AddHealth")]
		[HarmonyPrefix]
		private static void PlayerData_AddHealth(PlayerData __instance, ref int amount)
		{
			if (ModHooks.OnAddHealthHook != null)
			{
				amount = ModHooks.OnAddHealthHook(__instance, amount);
			}
		}

		[HarmonyPatch(typeof(HealthManager), "TakeDamage")]
		[HarmonyPrefix]
		private static void OnDealDamage(HealthManager __instance, ref HitInstance hitInstance)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (ModHooks.OnDealDamageHook != null)
			{
				hitInstance.DamageDealt = ModHooks.OnDealDamageHook(__instance, hitInstance);
			}
		}

		[HarmonyPatch(typeof(HeroController), "Die")]
		[HarmonyPrefix]
		private static void OnBeforePlayerDead(HeroController __instance, bool nonLethal, bool frostDeath)
		{
			ModHooks.OnBeforeDeathHook?.Invoke(__instance, nonLethal, frostDeath);
		}

		[HarmonyPatch(typeof(GameManager), "PlayerDead")]
		[HarmonyPrefix]
		private static void OnAfterPlayerDead(GameManager __instance, float waitTime)
		{
			ModHooks.OnAfterDeathHook?.Invoke(__instance);
		}

		[HarmonyPatch(typeof(HeroController), "CocoonBroken", new Type[]
		{
			typeof(bool),
			typeof(bool)
		})]
		[HarmonyPrefix]
		private static void OnGetCocoon(HeroController __instance, bool doAirPause, bool forceCanBind)
		{
			ModHooks.OnGetCocoonHook?.Invoke(__instance);
		}

		[HarmonyPatch(typeof(PlayerData), "SetBool")]
		[HarmonyPostfix]
		private static void OnSetBool(string boolName, bool value)
		{
			ModHooks.OnSetBoolHook?.Invoke(boolName, value);
		}

		[HarmonyPatch(typeof(PlayerData), "SetInt")]
		[HarmonyPostfix]
		private static void OnSetInt(string intName, int value)
		{
			ModHooks.OnSetIntHook?.Invoke(intName, value);
		}

		[HarmonyPatch(typeof(VibrationManager), "PlayVibrationClipOneShot", new Type[]
		{
			typeof(VibrationData),
			typeof(VibrationTarget?),
			typeof(bool),
			typeof(string),
			typeof(bool)
		})]
		[HarmonyPrefix]
		private static void OnPlayVibrationClipOneShot(ref VibrationData vibrationData, ref VibrationTarget? vibrationTarget, ref bool isLooping, ref string tag, ref bool isRealtime)
		{
			ModHooks.OnRumbleHook?.Invoke(tag);
		}

		[HarmonyPatch(typeof(PlayerData), "AddGeo")]
		[HarmonyPrefix]
		private static void OnAddRosaries(PlayerData __instance, ref int amount)
		{
			if (ModHooks.OnAddRosariesHook != null)
			{
				amount = ModHooks.OnAddRosariesHook(__instance, amount);
			}
		}

		[HarmonyPatch(typeof(PlayerData), "TakeGeo")]
		[HarmonyPrefix]
		private static void OnTakeRosaries(PlayerData __instance, ref int amount)
		{
			if (ModHooks.OnTakeRosariesHook != null)
			{
				amount = ModHooks.OnTakeRosariesHook(__instance, amount);
			}
		}

		[HarmonyPatch(typeof(PlayerData), "AddShards")]
		[HarmonyPrefix]
		private static void OnAddShards(PlayerData __instance, ref int amount)
		{
			if (ModHooks.OnAddShardsHook != null)
			{
				amount = ModHooks.OnAddShardsHook(__instance, amount);
			}
		}

		[HarmonyPatch(typeof(PlayerData), "TakeShards")]
		[HarmonyPrefix]
		private static void OnTakeShards(PlayerData __instance, ref int amount)
		{
			if (ModHooks.OnTakeShardsHook != null)
			{
				amount = ModHooks.OnTakeShardsHook(__instance, amount);
			}
		}

		[HarmonyPatch(typeof(HeroController), "DoHardLandingEffectNoHit")]
		[HarmonyPrefix]
		private static void OnHardLanding(HeroController __instance)
		{
			ModHooks.OnHardLandingHook?.Invoke(__instance);
		}

		[HarmonyPatch(typeof(DeliveryQuestItem), "BreakEffect")]
		[HarmonyPrefix]
		private static void CourierBreakItem(DeliveryQuestItem __instance, Vector2 heroPos)
		{
			ModHooks.OnCourierBreakItemHook?.Invoke(__instance);
		}

		[HarmonyPatch(typeof(OnScreenDebugInfo), "Awake")]
		[HarmonyPrefix]
		private static void OnFinishedLoadingMods()
		{
			ModHooks.OnFinishedLoadingModsHook?.Invoke();
		}

		[HarmonyPatch(typeof(PlayerData), "AddToMaxHealth")]
		[HarmonyPostfix]
		private static void OnMaxHealthUp(int amount)
		{
			ModHooks.OnMaxHealthUpHook?.Invoke(amount);
		}

		[HarmonyPatch(typeof(HeroController), "AddToMaxSilk")]
		[HarmonyPostfix]
		private static void OnMaxSilkUp(int amount)
		{
			ModHooks.OnMaxSilkUpHook?.Invoke(amount);
		}

		[HarmonyPatch(typeof(ToolItem), "Unlock")]
		[HarmonyPostfix]
		private static void OnToolUnlock(ToolItem __instance)
		{
			ModHooks.OnToolUnlockHook?.Invoke(__instance);
		}
	}
}
namespace ButtplugSong.Helper
{
	public static class ExtHelper
	{
		public delegate void DropdownChanged<T>(string newValue, bool isEnum, T type) where T : Enum;

		public static Random rng = new Random();

		public static void RunTask(this Task t)
		{
			Task t2 = t;
			Task.Run(() => t2);
		}

		public static async void FireAndForget(this Task t, Action<string> logging)
		{
			try
			{
				await t;
			}
			catch (Exception ex)
			{
				logging?.Invoke($"FAF Exception: {ex.GetType()}; {ex.Message}");
				if (ex.InnerException != null)
				{
					logging?.Invoke($"    Inner: {ex.InnerException.GetType()}; {ex.InnerException.Message}");
				}
			}
		}

		public static T ChooseRandom<T>() where T : Enum
		{
			Array values = Enum.GetValues(typeof(T));
			return (T)values.GetValue(rng.Next(values.Length));
		}

		public static T ChooseRandom<T>(this IEnumerable<T> collection)
		{
			return collection.ElementAt(rng.Next(collection.Count()));
		}

		public static void SetClassListIf<T>(this T field, string classList, Func<T, bool> predicate) where T : VisualElement
		{
			if (predicate(field))
			{
				if (!((VisualElement)field).ClassListContains(classList))
				{
					((VisualElement)field).AddToClassList(classList);
				}
			}
			else if (((VisualElement)field).ClassListContains(classList))
			{
				((VisualElement)field).RemoveFromClassList(classList);
			}
		}

		public static bool OutsideRange<T>(this T value, T lowerBound, T upperBound, out T inRangeValue) where T : IComparable<T>
		{
			if (value.CompareTo(lowerBound) < 0)
			{
				inRangeValue = lowerBound;
				return true;
			}
			if (value.CompareTo(upperBound) > 0)
			{
				inRangeValue = upperBound;
				return true;
			}
			inRangeValue = value;
			return false;
		}

		public static BaseField<T> SetupValueClamping<T>(this BaseField<T> notifier, T lowerBound, T upperBound, bool delay = true) where T : IComparable<T>
		{
			T lowerBound2 = lowerBound;
			T upperBound2 = upperBound;
			BaseField<T> notifier2 = notifier;
			if (delay && notifier2 is TextInputBaseField<T> val)
			{
				val.isDelayed = true;
			}
			INotifyValueChangedExtensions.RegisterValueChangedCallback<T>((INotifyValueChanged<T>)(object)notifier2, (EventCallback<ChangeEvent<T>>)delegate(ChangeEvent<T> evt)
			{
				if (evt.newValue.OutsideRange(lowerBound2, upperBound2, out var inRangeValue))
				{
					notifier2.value = inRangeValue;
				}
			});
			return notifier2;
		}

		public static BaseField<T> SetupGreyout<T>(this BaseField<T> notifier, Func<T, bool> predicate) where T : IComparable<T>
		{
			BaseField<T> notifier2 = notifier;
			Func<T, bool> predicate2 = predicate;
			INotifyValueChangedExtensions.RegisterValueChangedCallback<T>((INotifyValueChanged<T>)(object)notifier2, (EventCallback<ChangeEvent<T>>)delegate
			{
				notifier2.SetClassListIf<BaseField<T>>("grey-value", (Func<BaseField<T>, bool>)((BaseField<T> n) => predicate2(n.value)));
			});
			return notifier2;
		}

		public static BaseField<string> SetupSaving(this BaseField<string> element, string? defaultValue = null, string? settingName = null)
		{
			string settingName2 = settingName;
			if (settingName2 == null)
			{
				settingName2 = ((VisualElement)element).name;
			}
			Preset.BindElementToSetting((VisualElement)(object)element, settingName2);
			INotifyValueChangedExtensions.RegisterValueChangedCallback<string>((INotifyValueChanged<string>)(object)element, (EventCallback<ChangeEvent<string>>)delegate(ChangeEvent<string> evt)
			{
				Preset.Custom.SaveSettingChange(settingName2, evt.newValue.Replace(" ", ""));
			});
			if (defaultValue != null)
			{
				PresetDefault.SaveDefaultSetting(settingName2, defaultValue.Replace(" ", ""));
			}
			return element;
		}

		public static BaseField<T> SetupSaving<T>(this BaseField<T> element, T? defaultValue = null, string? settingName = null) where T : struct
		{
			string settingName2 = settingName;
			if (settingName2 == null)
			{
				settingName2 = ((VisualElement)element).name;
			}
			Preset.BindElementToSetting((VisualElement)(object)element, settingName2);
			INotifyValueChangedExtensions.RegisterValueChangedCallback<T>((INotifyValueChanged<T>)(object)element, (EventCallback<ChangeEvent<T>>)delegate(ChangeEvent<T> evt)
			{
				Preset.Custom.SaveSettingChange(settingName2, evt.newValue);
			});
			if (defaultValue.HasValue)
			{
				PresetDefault.SaveDefaultSetting(settingName2, defaultValue);
			}
			return element;
		}

		public static void Load<T>(this BaseField<T> element, Preset preset) where T : struct, IComparable<T>
		{
			if (Preset.TryGetBoundSetting((VisualElement)(object)element, out string settingName))
			{
				T val = preset.Get<T>(settingName) ?? element.value;
				ChangeEvent<T> pooled = ChangeEvent<T>.GetPooled(element.value, val);
				((EventBase)pooled).target = (IEventHandler)(object)element;
				element.SetValueWithoutNotify(val);
				((CallbackEventHandler)element).SendEvent((EventBase)(object)pooled);
				return;
			}
			throw new ArgumentException("Saving not setup! Couldn't load setting from preset " + preset.Identifier + " for element " + ((VisualElement)element).name + " (" + ((VisualElement)element).typeName + ")");
		}

		public static void Load(this BaseField<string> element, Preset preset, bool friendlyName = true)
		{
			if (Preset.TryGetBoundSetting((VisualElement)(object)element, out string settingName))
			{
				string text = preset.GetString(settingName) ?? element.value;
				if (friendlyName)
				{
					text = text.FriendlyName();
				}
				ChangeEvent<string> pooled = ChangeEvent<string>.GetPooled(element.value, text);
				((EventBase)pooled).target = (IEventHandler)(object)element;
				element.SetValueWithoutNotify(text);
				((CallbackEventHandler)element).SendEvent((EventBase)(object)pooled);
				return;
			}
			throw new ArgumentException("Saving not setup! Couldn't load setting from preset " + preset.Identifier + " for element " + ((VisualElement)element).name + " (" + ((VisualElement)element).typeName + ")");
		}

		public static BaseField<T> DependsOn<T>(this BaseField<T> element, Toggle other, Func<Toggle, bool> predicate)
		{
			BaseField<T> element2 = element;
			Func<Toggle, bool> predicate2 = predicate;
			Toggle other2 = other;
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)other2, (EventCallback<ChangeEvent<bool>>)delegate(ChangeEvent<bool> evt)
			{
				((VisualElement)element2).SetEnabled(evt.newValue && predicate2(other2));
			});
			return element2;
		}

		public static BaseField<TSelf> DependsOn<TSelf, TOther>(this BaseField<TSelf> element, BaseField<TOther> other, Func<BaseField<TOther>, bool> predicate)
		{
			BaseField<TSelf> element2 = element;
			Func<BaseField<TOther>, bool> predicate2 = predicate;
			BaseField<TOther> other2 = other;
			INotifyValueChangedExtensions.RegisterValueChangedCallback<TOther>((INotifyValueChanged<TOther>)(object)other2, (EventCallback<ChangeEvent<TOther>>)delegate
			{
				((VisualElement)element2).SetEnabled(predicate2(other2));
			});
			return element2;
		}

		public static BaseField<T> DependsOn<T>(this BaseField<T> element, params Toggle[] others)
		{
			BaseField<T> element2 = element;
			Toggle[] others2 = others;
			if (others2.Length == 0)
			{
				return element2;
			}
			if (others2.Length == 1)
			{
				INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)others2[0], (EventCallback<ChangeEvent<bool>>)delegate(ChangeEvent<bool> evt)
				{
					((VisualElement)element2).SetEnabled(evt.newValue);
				});
			}
			else
			{
				Toggle[] array = others2;
				for (int i = 0; i < array.Length; i++)
				{
					INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)array[i], (EventCallback<ChangeEvent<bool>>)delegate(ChangeEvent<bool> evt)
					{
						((VisualElement)element2).SetEnabled(evt.newValue && others2.All((Toggle x) => ((BaseField<bool>)(object)x).value));
					});
				}
			}
			return element2;
		}

		public static string FriendlyName(this string s)
		{
			string text = $"{char.ToUpper(s[0])}";
			bool flag = true;
			for (int i = 1; i < s.Length; i++)
			{
				if (char.IsUpper(s[i]))
				{
					if (!char.IsWhiteSpace(s[i - 1]) && (!flag || i + 1 >= s.Length || char.IsLower(s[i + 1])))
					{
						text += " ";
					}
					flag = true;
				}
				else
				{
					flag = false;
				}
				text += s[i];
			}
			return text;
		}

		public static string[] DisplayFriendlyEnumNames<T>() where T : Enum
		{
			return Enum.GetNames(typeof(T)).Select(FriendlyName).ToArray();
		}

		public static DropdownField PopulateDropdown<T>(this DropdownField dropdown, params string[] additionalSettings) where T : Enum
		{
			string[] array = DisplayFriendlyEnumNames<T>();
			int num = 0;
			string[] array2 = new string[array.Length + additionalSettings.Length];
			ReadOnlySpan<string> readOnlySpan = new ReadOnlySpan<string>(array);
			readOnlySpan.CopyTo(new Span<string>(array2).Slice(num, readOnlySpan.Length));
			num += readOnlySpan.Length;
			ReadOnlySpan<string> readOnlySpan2 = new ReadOnlySpan<string>(additionalSettings);
			readOnlySpan2.CopyTo(new Span<string>(array2).Slice(num, readOnlySpan2.Length));
			num += readOnlySpan2.Length;
			return dropdown.PopulateDropdown(array2);
		}

		public static DropdownField PopulateDropdown(this DropdownField dropdown, params string[] options)
		{
			((BasePopupField<string, string>)(object)dropdown).choices = options.ToList();
			return dropdown;
		}

		public static DropdownField RegisterDropdownChangedCallback<T>(this DropdownField dropdown, DropdownChanged<T> callWhenChanged) where T : struct, Enum
		{
			DropdownChanged<T> callWhenChanged2 = callWhenChanged;
			INotifyValueChangedExtensions.RegisterValueChangedCallback<string>((INotifyValueChanged<string>)(object)dropdown, (EventCallback<ChangeEvent<string>>)delegate(ChangeEvent<string> evt)
			{
				if (Enum.TryParse<T>(evt.newValue.Replace(" ", ""), ignoreCase: true, out var result))
				{
					callWhenChanged2(evt.newValue, isEnum: true, result);
				}
				else
				{
					callWhenChanged2(evt.newValue, isEnum: false, default(T));
				}
			});
			return dropdown;
		}

		public static T Next<T>(this T current) where T : Enum
		{
			T[] array = (T[])Enum.GetValues(typeof(T));
			int num = Array.IndexOf(array, current) + 1;
			if (array.Length != num)
			{
				return array[num];
			}
			return array[0];
		}
	}
}
namespace ButtplugSong.GUI
{
	internal class GUIManager : MonoBehaviour
	{
		private static GUIManager? _instance;

		public GameObject GUI_GameObject;

		public UIDocument UIDoc;

		public VisualElement Root;

		internal VisualTreeAsset LogRow;

		internal VisualTreeAsset Device;

		public VibeManager Vibe;

		public VisualTreeAsset TreeAsset;

		public TemplateContainer TreeContainer;

		public StyleSheet Style;

		public PanelSettings Settings;

		private const float _displayUpdateFrequency = 0.0625f;

		private float _timeSinceLastUpdate = 0.1f;

		public WaveDisplay VibeDisplay;

		public VisualElement WaveDisplayHolder;

		public Label DebugInfo;

		public VisualElement SettingsPanel;

		public TabView MainTabView;

		public Label TimerReadout;

		public Label PowerReadout;

		public Label PowerReadoutActual;

		public Label DeathRoll;

		public List<Foldout> SettingsFoldouts;

		public List<VibeSource> Sources;

		public LimitsSettings Limits;

		public WaveSettings Wave;

		public TimerSettings Timer;

		public PresetSettings Presets;

		public StopTheVibes StopVibes;

		public NetworkSettings Network;

		public UISettings UISettings;

		public GroupBox VibeLog;

		private const int MAX_VIBELOG_ENTRIES = 100;

		private Queue<VisualElement> vibeLogEntries = new Queue<VisualElement>();

		private DateTime? _lastLogEntry;

		private float deathRollTimeRemaining;

		private bool _settingsCurrentlyLocked;

		public static GUIManager Instance
		{
			get
			{
				if ((Object)(object)_instance == (Object)null)
				{
					throw new NotImplementedException("Instantiate properly before referencing! >:(");
				}
				return _instance;
			}
			private set
			{
				_instance = value;
			}
		}

		public bool UIHidden { get; private set; }

		public static GUIManager CreateAndInitialize()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			GameObject val = new GameObject("ButtplugSong GUI Manager");
			Instance = val.AddComponent<GUIManager>();
			Object.DontDestroyOnLoad((Object)val);
			Instance.Vibe = VibeManager.Instance;
			Instance.Vibe.UI = Instance;
			Instance.LoadAssetBundle();
			Instance.CreateGUI();
			Instance.CreateUIHandles();
			Instance.AddHooks();
			return Instance;
		}

		public void LoadAssetBundle()
		{
			AssetBundle val = AssetBundle.LoadFromFile(Path.Combine(ButtplugSongPlugin.ModPath, "buttplugsong.gui"));
			TreeAsset = val.LoadAsset<VisualTreeAsset>("ButtplugSong-UXML");
			Style = val.LoadAsset<StyleSheet>("ButtplugSong-USS");
			Settings = val.LoadAsset<PanelSettings>("ButtplugSong-PanelSettings");
			LogRow = val.LoadAsset<VisualTreeAsset>("ButtplugSong-LogRow");
			Device = val.LoadAsset<VisualTreeAsset>("ButtplugSong-Device");
			val.Unload(false);
		}

		public void CreateGUI()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			GUI_GameObject = new GameObject("ButtplugSong GUI");
			UIDoc = GUI_GameObject.AddComponent<UIDocument>();
			UIDoc.visualTreeAsset = TreeAsset;
			UIDoc.panelSettings = Settings;
			Root = UIDoc.rootVisualElement;
			CreateWaveDisplay();
			GUI_GameObject.transform.SetParent(((Component)this).transform);
			Root.AddToClassList("hide");
			void CreateWaveDisplay()
			{
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					WaveDisplayHolder = UQueryExtensions.Q(Root, "WaveDisplayHolder", (string)null);
					VibeDisplay = new WaveDisplay
					{
						LineColor = new Color(1f, 0.72156864f, 82f / 85f, 0.8f),
						GlowColor = new Color(0.59607846f, 0.07450981f, 0.56078434f, 0.5f),
						LineWidth = 1f,
						GlowWidth = 1.2f,
						MinimumValue = 0f,
						MaximumValue = 1f,
						DefaultValue = 0f,
						LineBufferTop = 0.2f,
						LineBufferBottom = 0.8f,
						RecordSteps = 128
					};
					((VisualElement)VibeDisplay).SetSize(new Vector2(320f, 35f));
					((VisualElement)VibeDisplay).AddToClassList("wave-display");
					WaveDisplayHolder.Add((VisualElement)(object)VibeDisplay);
				}
				catch (Exception ex)
				{
					Vibe.Log($"Failed to create WaveDisplay: {ex.Message} - {ex.InnerException} - {ex.StackTrace}");
				}
			}
		}

		private void CreateUIHandles()
		{
			DebugInfo = UQueryExtensions.Q<Label>(Root, "DebugInfo", (string)null);
			SettingsPanel = UQueryExtensions.Q<VisualElement>(Root, "SettingsPanel", (string)null);
			MainTabView = UQueryExtensions.Q<TabView>(Root, "MainTabView", (string)null);
			TimerReadout = UQueryExtensions.Q<Label>(Root, "TimerReadout", (string)null);
			PowerReadout = UQueryExtensions.Q<Label>(Root, "PowerReadout", (string)null);
			PowerReadoutActual = UQueryExtensions.Q<Label>(Root, "PowerReadoutActual", (string)null);
			DeathRoll = UQueryExtensions.Q<Label>(Root, "DeathRoll", (string)null);
			SettingsFoldouts = new List<Foldout>(6)
			{
				UQueryExtensions.Q<Foldout>(Root, "VibeSources", (string)null),
				UQueryExtensions.Q<Foldout>(Root, "LimitsSettings", (string)null),
				UQueryExtensions.Q<Foldout>(Root, "WaveTypeSettings", (string)null),
				UQueryExtensions.Q<Foldout>(Root, "TimerSettings", (string)null),
				UQueryExtensions.Q<Foldout>(Root, "PresetsSettings", (string)null),
				UQueryExtensions.Q<Foldout>(Root, "StopVibesSettings", (string)null)
			};
			Sources = new List<VibeSource>(7)
			{
				new BuzzOnDamage(),
				new BuzzOnHeal(),
				new BuzzOnStrike(),
				new BuzzOnDeath(),
				new BuzzOnPickups(),
				new BuzzOnRumble(),
				new BuzzOnRandom()
			};
			Presets = new PresetSettings();
			Limits = new LimitsSettings();
			Wave = new WaveSettings();
			Timer = new TimerSettings();
			StopVibes = new StopTheVibes();
			Network = new NetworkSettings();
			UISettings = new UISettings();
			VibeLog = UQueryExtensions.Q<GroupBox>(Root, "VibeLog", (string)null);
			LogActivity("Vibe Log Initialized!\nDebug logs are found in \"LogOutput.log\" in your BepInEx folder. This log is just for vibe activity.");
			((VisualElement)DebugInfo).AddToClassList("hide");
		}

		public void FinishedLoading()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			SetToPreset(Preset.Custom);
			Root.RemoveFromClassList("hide");
			if ((Object)(object)ManagerSingleton<InputHandler>.Instance != (Object)null)
			{
				ManagerSingleton<InputHandler>.Instance.OnCursorVisibilityChange += new CursorVisibilityChange(UpdateSettingsPanelVisibility);
			}
		}

		public void SetToPreset(Preset preset)
		{
			Vibe.Log($"Setting to preset: {preset.Identifier} ({preset.Settings.Count})");
			foreach (VibeSource source in Sources)
			{
				source.SetToPreset(preset);
			}
			Limits.SetToPreset(preset);
			Wave.SetToPreset(preset);
			Timer.SetToPreset(preset);
			Presets.SetToPreset(preset);
			StopVibes.SetToPreset(preset);
			Network.SetToPreset(preset);
			UISettings.SetToPreset(preset);
		}

		public void AddHooks()
		{
			Vibe.Logic.PunctuateChanged += UpdatePunctuateGraphic;
			Vibe.Logic.AddActivityLog += LogActivity;
			Vibe.Logic.PowerChanged += UpdateLockSettings;
			Vibe.Logic.VibeSourceActivated += UpdateLockSettings;
		}

		public void UpdatePunctuateGraphic(bool punctuating)
		{
			VibeDisplay.SetClassListIf("punctuating", (WaveDisplay x) => Vibe.Logic.Punctuating);
		}

		public void LogActivity(object generalMessage)
		{
			LogActivity("Debug Message", generalMessage.ToString());
		}

		public void LogActivity(string sourceLine, string vibeLine)
		{
			LogActivity(sourceLine, vibeLine, -1f);
		}

		public void LogActivity(string sourceLine, string vibeLine, float timerSeconds)
		{
			if (string.IsNullOrWhiteSpace(sourceLine) || string.IsNullOrWhiteSpace(vibeLine))
			{
				return;
			}
			VisualElement val = (VisualElement)(object)LogRow.Instantiate();
			Label val2 = UQueryExtensions.Q<Label>(val, "TimeData", (string)null);
			Label val3 = UQueryExtensions.Q<Label>(val, "SourceData", (string)null);
			Label val4 = UQueryExtensions.Q<Label>(val, "VibeData", (string)null);
			DateTime now = DateTime.Now;
			float? num = (_lastLogEntry.HasValue ? new float?((float)(now - _lastLogEntry.Value).TotalSeconds) : null);
			string text = $"[{now.Hour:D2}:{now.Minute:D2}:{now.Second:D2}  :{now.Millisecond:D3}]     ";
			if (num.HasValue)
			{
				text = text + "Passed: " + VibeLogic.DisplayTime(num.Value) + " real";
				if (timerSeconds >= 0f)
				{
					text = text + ", " + VibeLogic.DisplayTime(timerSeconds) + " timer";
				}
			}
			else
			{
				text += "First log";
			}
			((TextElement)val3).text = sourceLine;
			((TextElement)val4).text = vibeLine;
			((TextElement)val2).text = text;
			vibeLogEntries.Enqueue(val);
			((VisualElement)VibeLog).Add(val);
			_lastLogEntry = now;
			while (vibeLogEntries.Count > 100)
			{
				((VisualElement)VibeLog).Remove(vibeLogEntries.Dequeue());
			}
		}

		public void Update()
		{
			float realTime = Time.unscaledDeltaTime;
			float timerCountdown = Timer.GetTimerCountdown(Time.unscaledDeltaTime);
			Vibe.Update(realTime, timerCountdown);
			UpdateWaveDisplay();
			UpdatePowerDisplay();
			UpdateTimeDisplay();
			UpdateDeathRollDisplay();
			UpdateToggleUI();
			void UpdateDeathRollDisplay()
			{
				if (!(deathRollTimeRemaining <= 0f))
				{
					deathRollTimeRemaining -= realTime;
					if (deathRollTimeRemaining <= 0f)
					{
						((VisualElement)DeathRoll).AddToClassList("hide");
						((VisualElement)DeathRoll).opacity = 0f;
						deathRollTimeRemaining = 0f;
					}
					else if (deathRollTimeRemaining <= 5f)
					{
						((VisualElement)DeathRoll).opacity = (deathRollTimeRemaining / 5f).Clamp(0f, 1f);
					}
				}
			}
			void UpdatePowerDisplay()
			{
				((TextElement)PowerReadout).text = $"{Vibe.Logic.TargetPower * 100f:f0}%";
				if (Vibe.Logic.TargetPower != Vibe.Logic.ActualPower)
				{
					((TextElement)PowerReadoutActual).text = $"{Vibe.Logic.ActualPower * 100f:f0}%";
					((VisualElement)PowerReadoutActual).opacity = 0.75f;
				}
				else if (((VisualElement)PowerReadoutActual).opacity > 0f)
				{
					((TextElement)PowerReadoutActual).text = $"{Vibe.Logic.ActualPower * 100f:f0}%";
					Label powerReadoutActual = PowerReadoutActual;
					((VisualElement)powerReadoutActual).opacity = ((VisualElement)powerReadoutActual).opacity - realTime / 3f;
				}
			}
			void UpdateTimeDisplay()
			{
				if (Vibe.Logic.Time >= 60f)
				{
					((TextElement)TimerReadout).text = $"{(int)(Vibe.Logic.Time / 60f)}:{Vibe.Logic.Time % 60f:00}";
				}
				else
				{
					((TextElement)TimerReadout).text = $"{Vibe.Logic.Time:f2}";
				}
			}
			void UpdateToggleUI()
			{
				if (Input.GetKeyDown((KeyCode)287))
				{
					UIHidden = !UIHidden;
					Root.SetClassListIf<VisualElement>("hide", (Func<VisualElement, bool>)((VisualElement x) => UIHidden));
				}
			}
			void UpdateWaveDisplay()
			{
				_timeSinceLastUpdate += realTime;
				while (_timeSinceLastUpdate >= 0.0625f)
				{
					_timeSinceLastUpdate -= 0.0625f;
					VibeDisplay?.PushRecordStep(Vibe.Logic.ActualPower);
				}
			}
		}

		private void UpdateSettingsPanelVisibility(bool isVisible)
		{
			SettingsPanel.SetClassListIf<VisualElement>("hide", (Func<VisualElement, bool>)((VisualElement x) => UISettings.AutoCollapseSettings && !isVisible));
		}

		internal void DisplayDeathDice(int rollAmount)
		{
			((TextElement)DeathRoll).text = $"Death roll result: {rollAmount}";
			((VisualElement)DeathRoll).opacity = 1f;
			((VisualElement)DeathRoll).RemoveFromClassList("hide");
			deathRollTimeRemaining = 20 + Math.Abs(11 - rollAmount);
		}

		internal void UpdateLockSettings()
		{
			LockSettings(!Vibe.Logic.TimerZero && StopVibes != null && StopVibes.LockSettings);
		}

		private void LockSettings(bool disabled = true)
		{
			if (_settingsCurrentlyLocked == disabled)
			{
				return;
			}
			_settingsCurrentlyLocked = disabled;
			foreach (Foldout settingsFoldout in SettingsFoldouts)
			{
				((VisualElement)settingsFoldout).enabledSelf = !disabled;
			}
		}
	}
	public abstract class GUISection
	{
		public string Identifier { get; private set; }

		protected static VibeManager Vibe => VibeManager.Instance;

		protected GUISection(string identifier)
		{
			Identifier = identifier;
			base..ctor();
		}

		protected static void Log(object message)
		{
			Log(message.ToString());
		}

		protected static void Log(string message)
		{
			Vibe.Log(message);
		}

		protected static T Get<T>(string elementName) where T : VisualElement
		{
			return Get<T>(elementName, Vibe.UI.Root);
		}

		protected static T Get<T>(string elementName, VisualElement root) where T : VisualElement
		{
			T val = UQueryExtensions.Q<T>(root, elementName, (string)null);
			if (val == null)
			{
				Log("Searched for " + elementName + " but couldn't find it");
				throw new NullReferenceException("Could not find " + elementName + " under root " + root.name);
			}
			return val;
		}
	}
	internal class UISettings : GUISection, IPresetLoadable
	{
		private static readonly string[] UIScaleOptions = new string[8] { "0.6x", "0.8x", "1x", "1.2x", "1.4x", "1.6x", "1.8x", "2x" };

		private static readonly Dictionary<string, string> UIHeightOptions = new Dictionary<string, string>
		{
			{ "Short", "tab-view-short" },
			{ "Medium", "tab-view-medium" },
			{ "Long", "tab-view-long" }
		};

		protected readonly DropdownField _uiScale;

		protected readonly DropdownField _uiHeight;

		protected readonly Toggle _displayWaveform;

		protected readonly Toggle _autoCollapseSettings;

		public bool AutoCollapseSettings
		{
			get
			{
				if (_autoCollapseSettings != null)
				{
					return ((BaseField<bool>)(object)_autoCollapseSettings).value;
				}
				return false;
			}
			set
			{
				((BaseField<bool>)(object)_autoCollapseSettings).value = value;
			}
		}

		public UISettings()
			: base("UI")
		{
			_uiScale = GUISection.Get<DropdownField>("UIScale");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<string>((INotifyValueChanged<string>)(object)((BaseField<string>)(object)_uiScale.PopulateDropdown(UIScaleOptions)).SetupSaving(), (EventCallback<ChangeEvent<string>>)UIScaleChanged);
			_uiHeight = GUISection.Get<DropdownField>("UIHeight");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<string>((INotifyValueChanged<string>)(object)((BaseField<string>)(object)_uiHeight.PopulateDropdown(UIHeightOptions.Select<KeyValuePair<string, string>, string>((KeyValuePair<string, string> x) => x.Key).ToArray())).SetupSaving(), (EventCallback<ChangeEvent<string>>)UIHeightChanged);
			_displayWaveform = GUISection.Get<Toggle>("DisplayWaveform");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_displayWaveform).SetupSaving<bool>(), (EventCallback<ChangeEvent<bool>>)DisplayWaveformChanged);
			_autoCollapseSettings = GUISection.Get<Toggle>("AutoCollapseSettings");
			((BaseField<bool>)(object)_autoCollapseSettings).SetupSaving<bool>();
			((PopupField<string>)(object)_uiScale).SetIndexWithoutNotify(2);
			((PopupField<string>)(object)_uiHeight).SetIndexWithoutNotify(1);
		}

		public void SetToPreset(Preset preset)
		{
			((BaseField<string>)(object)_uiScale).Load(preset);
			((BaseField<string>)(object)_uiHeight).Load(preset);
			((BaseField<bool>)(object)_displayWaveform).Load<bool>(preset);
			((BaseField<bool>)(object)_autoCollapseSettings).Load<bool>(preset);
		}

		private void DisplayWaveformChanged(ChangeEvent<bool> evt)
		{
			ChangeEvent<bool> evt2 = evt;
			GUISection.Vibe.UI.VibeDisplay.SetClassListIf("hide", (WaveDisplay x) => !evt2.newValue);
		}

		private void UIHeightChanged(ChangeEvent<string> evt)
		{
			string value;
			string text = (UIHeightOptions.TryGetValue(evt.newValue, out value) ? value : "tab-view-medium");
			foreach (KeyValuePair<string, string> uIHeightOption in UIHeightOptions)
			{
				((VisualElement)GUISection.Vibe.UI.MainTabView).RemoveFromClassList(uIHeightOption.Value);
			}
			((VisualElement)GUISection.Vibe.UI.MainTabView).AddToClassList(text);
		}

		private void UIScaleChanged(ChangeEvent<string> evt)
		{
			PanelSettings panelSettings = GUISection.Vibe.UI.UIDoc.panelSettings;
			string newValue = evt.newValue;
			if (newValue == null)
			{
				goto IL_013c;
			}
			int length = newValue.Length;
			float scale;
			if (length != 2)
			{
				if (length != 4)
				{
					goto IL_013c;
				}
				switch (newValue[2])
				{
				case '6':
					break;
				case '8':
					goto IL_00a4;
				case '2':
					goto IL_00c0;
				case '4':
					goto IL_00cf;
				default:
					goto IL_013c;
				}
				if (!(newValue == "0.6x"))
				{
					if (!(newValue == "1.6x"))
					{
						goto IL_013c;
					}
					scale = 1.6f;
				}
				else
				{
					scale = 0.6f;
				}
			}
			else
			{
				char c = newValue[0];
				if (c != '1')
				{
					if (c != '2' || !(newValue == "2x"))
					{
						goto IL_013c;
					}
					scale = 2f;
				}
				else
				{
					if (!(newValue == "1x"))
					{
						goto IL_013c;
					}
					scale = 1f;
				}
			}
			goto IL_0142;
			IL_0142:
			panelSettings.scale = scale;
			return;
			IL_013c:
			scale = 1f;
			goto IL_0142;
			IL_00cf:
			if (!(newValue == "1.4x"))
			{
				goto IL_013c;
			}
			scale = 1.4f;
			goto IL_0142;
			IL_00a4:
			if (!(newValue == "0.8x"))
			{
				if (!(newValue == "1.8x"))
				{
					goto IL_013c;
				}
				scale = 1.8f;
			}
			else
			{
				scale = 0.8f;
			}
			goto IL_0142;
			IL_00c0:
			if (!(newValue == "1.2x"))
			{
				goto IL_013c;
			}
			scale = 1.2f;
			goto IL_0142;
		}
	}
}
namespace ButtplugSong.GUI.VibeSettings
{
	internal class StopTheVibes : GUISection, IPresetLoadable
	{
		protected readonly Button _stopTheVibes;

		protected readonly Label _stopTheVibesCostLabel;

		protected readonly Toggle _enabled;

		protected readonly Toggle _buttonCostsResources;

		protected readonly Toggle _buttonCostsRosaries;

		protected readonly Toggle _buttonCostsShards;

		protected readonly FloatField _rosaryCostPercent;

		protected readonly FloatField _shardCostPercent;

		protected readonly IntegerField _rosaryCostFlat;

		protected readonly IntegerField _shardCostFlat;

		protected readonly Toggle _buttonDisablesMinimums;

		protected readonly Toggle _lockSettings;

		public int RosaryCost;

		public int ShardCost;

		public bool LockSettings
		{
			get
			{
				return ((BaseField<bool>)(object)_lockSettings).value;
			}
			set
			{
				((BaseField<bool>)(object)_lockSettings).value = value;
			}
		}

		public StopTheVibes()
			: base("StopVibes")
		{
			_stopTheVibes = GUISection.Get<Button>("StopVibes");
			_stopTheVibes.clicked += StopTheVibesButtonClicked;
			_stopTheVibesCostLabel = GUISection.Get<Label>("StopVibesCostLabel");
			_enabled = GUISection.Get<Toggle>("StopVibesEnabled");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_enabled).SetupSaving<bool>(true), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>);
			_buttonCostsResources = GUISection.Get<Toggle>("StopCostsResources");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_buttonCostsResources).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled }), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>);
			_buttonCostsRosaries = GUISection.Get<Toggle>("StopRosaryCost");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_buttonCostsRosaries).SetupSaving<bool>(false).DependsOn<bool>((Toggle[])(object)new Toggle[2] { _enabled, _buttonCostsResources }), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>);
			_rosaryCostPercent = GUISection.Get<FloatField>("StopRosaryCostPercent");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_rosaryCostPercent).SetupSaving<float>(50f).DependsOn<float>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsRosaries }).SetupValueClamping(0f, 100f)
				.SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)RecalculateCosts<float>);
			_rosaryCostFlat = GUISection.Get<IntegerField>("StopRosaryCostFlat");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<int>((INotifyValueChanged<int>)(object)((BaseField<int>)(object)_rosaryCostFlat).SetupSaving<int>(0).DependsOn<int>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsRosaries }).SetupValueClamping(0, 9999)
				.SetupGreyout((int x) => x == 0), (EventCallback<ChangeEvent<int>>)RecalculateCosts<int>);
			_buttonCostsShards = GUISection.Get<Toggle>("StopShardCost");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_buttonCostsShards).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[2] { _enabled, _buttonCostsResources }), (EventCallback<ChangeEvent<bool>>)RecalculateCosts<bool>);
			_shardCostPercent = GUISection.Get<FloatField>("StopShardCostPercent");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_shardCostPercent).SetupSaving<float>(0f).DependsOn<float>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsShards }).SetupValueClamping(0f, 100f)
				.SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)RecalculateCosts<float>);
			_shardCostFlat = GUISection.Get<IntegerField>("StopShardCostFlat");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<int>((INotifyValueChanged<int>)(object)((BaseField<int>)(object)_shardCostFlat).SetupSaving<int>(400).DependsOn<int>((Toggle[])(object)new Toggle[3] { _enabled, _buttonCostsResources, _buttonCostsShards }).SetupValueClamping(0, 800)
				.SetupGreyout((int x) => x == 0), (EventCallback<ChangeEvent<int>>)RecalculateCosts<int>);
			_buttonDisablesMinimums = GUISection.Get<Toggle>("ButtonDisablesMinimums");
			((BaseField<bool>)(object)_buttonDisablesMinimums).SetupSaving<bool>(false).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled });
			_lockSettings = GUISection.Get<Toggle>("LockSettings");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_lockSettings).SetupSaving<bool>(false), (EventCallback<ChangeEvent<bool>>)LockSettingsChanged);
			ModHooks.OnAddRosariesHook += CurrencyChanged;
			ModHooks.OnTakeRosariesHook += CurrencyChanged;
			ModHooks.OnAddShardsHook += CurrencyChanged;
			ModHooks.OnTakeShardsHook += CurrencyChanged;
		}

		public void SetToPreset(Preset preset)
		{
			((BaseField<bool>)(object)_enabled).Load<bool>(preset);
			((BaseField<bool>)(object)_buttonCostsResources).Load<bool>(preset);
			((BaseField<bool>)(object)_buttonCostsRosaries).Load<bool>(preset);
			((BaseField<float>)(object)_rosaryCostPercent).Load<float>(preset);
			((BaseField<int>)(object)_rosaryCostFlat).Load<int>(preset);
			((BaseField<bool>)(object)_buttonCostsShards).Load<bool>(preset);
			((BaseField<float>)(object)_shardCostPercent).Load<float>(preset);
			((BaseField<int>)(object)_shardCostFlat).Load<int>(preset);
			((BaseField<bool>)(object)_buttonDisablesMinimums).Load<bool>(preset);
			((BaseField<bool>)(object)_lockSettings).Load<bool>(preset);
		}

		private void LockSettingsChanged(ChangeEvent<bool> evt)
		{
			GUISection.Vibe.UI.UpdateLockSettings();
		}

		private int CurrencyChanged(PlayerData data, int amount)
		{
			RecalculateCosts();
			return amount;
		}

		private void RecalculateCosts<T>(ChangeEvent<T> evt)
		{
			RecalculateCosts();
		}

		private void RecalculateCosts()
		{
			if (!((BaseField<bool>)(object)_enabled).value || !((BaseField<bool>)(object)_buttonCostsResources).value)
			{
				((VisualElement)_stopTheVibes).enabledSelf = ((BaseField<bool>)(object)_enabled).value;
				((TextElement)_stopTheVibesCostLabel).text = (((BaseField<bool>)(object)_enabled).value ? "Free!" : "Button disabled");
				return;
			}
			RosaryCost = CalculateCost((CurrencyType)0);
			ShardCost = CalculateCost((CurrencyType)1);
			if (RosaryCost == 0 && ShardCost == 0)
			{
				((TextElement)_stopTheVibesCostLabel).text = "Free!";
			}
			else if (RosaryCost == 0)
			{
				((TextElement)_stopTheVibesCostLabel).text = string.Format("Costs {0} shard{1}", ShardCost, (ShardCost == 1) ? "" : "s");
			}
			else if (ShardCost == 0)
			{
				((TextElement)_stopTheVibesCostLabel).text = string.Format("Costs {0} rosar{1}", RosaryCost, (RosaryCost == 1) ? "y" : "ies");
			}
			else
			{
				((TextElement)_stopTheVibesCostLabel).text = string.Format("Costs {0} rosar{1}, {2} shard{3}", RosaryCost, (RosaryCost == 1) ? "y" : "ies", ShardCost, (ShardCost == 1) ? "" : "s");
			}
			((VisualElement)_stopTheVibes).enabledSelf = CurrencyManager.GetCurrencyAmount((CurrencyType)0) >= RosaryCost && CurrencyManager.GetCurrencyAmount((CurrencyType)1) >= ShardCost;
			int CalculateCost(CurrencyType type)
			{
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_0057: Unknown result type (might be due to invalid IL or missing references)
				//IL_0059: Invalid comparison between Unknown and I4
				if (!((BaseField<bool>)(object)_enabled).value || !((BaseField<bool>)(object)_buttonCostsResources).value)
				{
					return 0;
				}
				int currencyAmount = CurrencyManager.GetCurrencyAmount(type);
				if ((int)type == 0 && ((BaseField<bool>)(object)_buttonCostsRosaries).value)
				{
					currencyAmount = (int)((float)currencyAmount * ((BaseField<float>)(object)_rosaryCostPercent).value / 100f);
					return currencyAmount + ((BaseField<int>)(object)_rosaryCostFlat).value;
				}
				if ((int)type == 1 && ((BaseField<bool>)(object)_buttonCostsShards).value)
				{
					currencyAmount = (int)((float)currencyAmount * ((BaseField<float>)(object)_shardCostPercent).value / 100f);
					return currencyAmount + ((BaseField<int>)(object)_shardCostFlat).value;
				}
				return 0;
			}
		}

		private void StopTheVibesButtonClicked()
		{
			if (((BaseField<bool>)(object)_buttonCostsResources).value)
			{
				if (((BaseField<bool>)(object)_buttonCostsRosaries).value)
				{
					if (RosaryCost > CurrencyManager.GetCurrencyAmount((CurrencyType)0))
					{
						return;
					}
					CurrencyManager.TakeCurrency(RosaryCost, (CurrencyType)0, true);
				}
				if (((BaseField<bool>)(object)_buttonCostsShards).value)
				{
					if (ShardCost > CurrencyManager.GetCurrencyAmount((CurrencyType)1))
					{
						return;
					}
					CurrencyManager.TakeCurrency(ShardCost, (CurrencyType)1, true);
				}
			}
			if (((BaseField<bool>)(object)_buttonDisablesMinimums).value)
			{
				((BaseField<bool>)(object)GUISection.Vibe.UI.Limits._minimumsEnabled).value = false;
			}
			GUISection.Vibe.Logic.VibeSourceActivation("Stop the Vibes!", 1f, "-", 0f, "+", 0f);
		}
	}
	internal enum CountdownMode
	{
		Always,
		Default,
		InGame,
		Unpaused,
		SuperHot,
		Never
	}
	internal class TimerSettings : GUISection, IPresetLoadable
	{
		private readonly DropdownField _timerCountdownMode;

		private readonly Label _countdownModeDescription;

		private readonly Toggle _timeStacking;

		private readonly FloatField _timeStackingMultiplier;

		private readonly Toggle _vibeWhileTimerPaused;

		private readonly FloatField _afterZeroPunctuate;

		private readonly Label _afterZeroPunctuateLabel;

		private readonly FloatField _afterZeroPower;

		private readonly Label _percentagesReminder;

		private readonly Label _zeroPowerWarning;

		private readonly FloatField _afterZeroTimer;

		private readonly CyclingButton<AfterZeroMode> _afterZeroPowerMode;

		public int TimerCountdownModeIndex
		{
			get
			{
				return ((PopupField<string>)(object)_timerCountdownMode).index;
			}
			set
			{
				((PopupField<string>)(object)_timerCountdownMode).index = value;
			}
		}

		public CountdownMode TimerCountdownMode => (CountdownMode)TimerCountdownModeIndex;

		public TimerSettings()
			: base("Timer")
		{
			_timerCountdownMode = GUISection.Get<DropdownField>("TimerCountdownMode");
			((BaseField<string>)(object)_timerCountdownMode.PopulateDropdown<CountdownMode>(Array.Empty<string>()).RegisterDropdownChangedCallback<CountdownMode>(CountdownModeChanged)).SetupSaving(CountdownMode.Default.ToString());
			_countdownModeDescription = GUISection.Get<Label>("CountdownModeDescription");
			_timeStacking = GUISection.Get<Toggle>("TimeStacking");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_timeStacking).SetupSaving<bool>(true), (EventCallback<ChangeEvent<bool>>)TimeStackingChanged<bool>);
			_timeStackingMultiplier = GUISection.Get<FloatField>("TimeStackingMultiplier");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_timeStackingMultiplier).SetupSaving<float>(1.2f).SetupGreyout((float x) => x == 1f).SetupValueClamping(0f, 999f)
				.DependsOn<float>((Toggle[])(object)new Toggle[1] { _timeStacking }), (EventCallback<ChangeEvent<float>>)TimeStackingChanged<float>);
			_vibeWhileTimerPaused = GUISection.Get<Toggle>("VibeWhileTimerPaused");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_vibeWhileTimerPaused).SetupSaving<bool>(true), (EventCallback<ChangeEvent<bool>>)VibeWhilePausedChanged);
			_afterZeroPunctuate = GUISection.Get<FloatField>("AfterZeroPunctuate");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_afterZeroPunctuate).SetupSaving<float>(0f).SetupValueClamping(0f, 999f).SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)UpdateAfterZeroPunctuate);
			_afterZeroPunctuateLabel = GUISection.Get<Label>("AfterZeroPunctuateLabel");
			_afterZeroTimer = GUISection.Get<FloatField>("AfterZeroTimer");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_afterZeroTimer).SetupSaving<float>(0f).SetupValueClamping(0f, 999f).SetupGreyout((float x) => x == 0f), (EventCallback<ChangeEvent<float>>)AfterZeroTimerChanged);
			_percentagesReminder = GUISection.Get<Label>("PercentagesReminder");
			_zeroPowerWarning = GUISection.Get<Label>("ZeroPowerWarning");
			_afterZeroPower = GUISection.Get<FloatField>("AfterZeroPower");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<float>((INotifyValueChanged<float>)(object)((BaseField<float>)(object)_afterZeroPower).SetupSaving<float>(100f).SetupValueClamping(0f, 100f).SetupGreyout<float>(AfterZeroPowerMeaningless), (EventCallback<ChangeEvent<float>>)AfterZeroPowerChanged);
			_afterZeroPowerMode = new CyclingButton<AfterZeroMode>("AfterZeroPowerMode", AfterZeroMode.Subtract, AfterZeroModeTextSelector);
			_afterZeroPowerMode.SetupSaving(AfterZeroMode.Subtract);
			CyclingButton<AfterZeroMode> afterZeroPowerMode = _afterZeroPowerMode;
			afterZeroPowerMode.clicked = (Action)Delegate.Combine(afterZeroPowerMode.clicked, new Action(AfterZeroPowerModeChanged));
		}

		public void SetToPreset(Preset preset)
		{
			((BaseField<string>)(object)_timerCountdownMode).Load(preset);
			((BaseField<bool>)(object)_timeStacking).Load<bool>(preset);
			((BaseField<float>)(object)_timeStackingMultiplier).Load<float>(preset);
			((BaseField<bool>)(object)_vibeWhileTimerPaused).Load<bool>(preset);
			((BaseField<float>)(object)_afterZeroPunctuate).Load<float>(preset);
			((BaseField<float>)(object)_afterZeroTimer).Load<float>(preset);
			((BaseField<float>)(object)_afterZeroPower).Load<float>(preset);
			_afterZeroPowerMode.Load(preset);
		}

		private void AfterZeroTimerChanged(ChangeEvent<float> evt)
		{
			GUISection.Vibe.Logic.afterZeroTimer = evt.newValue;
		}

		private void AfterZeroPowerModeChanged()
		{
			GUISection.Vibe.Logic.afterZeroMode = _afterZeroPowerMode.currentMode;
			UpdateAfterZeroPowerReminderLabels();
		}

		private void AfterZeroPowerChanged(ChangeEvent<float> evt)
		{
			GUISection.Vibe.Logic.afterZeroPowerChange = evt.newValue / 100f;
			UpdateAfterZeroPowerReminderLabels();
		}

		private void UpdateAfterZeroPowerReminderLabels()
		{
			_percentagesReminder.SetClassListIf<Label>("hide", (Func<Label, bool>)((Label x) => _afterZeroPowerMode.currentMode != AfterZeroMode.Multiply || ((BaseField<float>)(object)_afterZeroPower).value > 1f));
			_zeroPowerWarning.SetClassListIf<Label>("hide", (Func<Label, bool>)((Label x) => !AfterZeroPowerMeaningless(((BaseField<float>)(object)_afterZeroPower).value)));
		}

		private bool AfterZeroPowerMeaningless(float value)
		{
			return _afterZeroPowerMode.currentMode switch
			{
				AfterZeroMode.Subtract => value <= 0f, 
				AfterZeroMode.Multiply => value >= 100f, 
				_ => false, 
			};
		}

		private void VibeWhilePausedChanged(ChangeEvent<bool> evt)
		{
			GUISection.Vibe.Logic.vibeWhileTimerPaused = evt.newValue;
		}

		private void UpdateAfterZeroPunctuate(ChangeEvent<float> evt)
		{
			if (!(evt.newValue < 0f))
			{
				((TextElement)_afterZeroPunctuateLabel).text = $"Punctuate with {evt.newValue}s of max power, then";
				GUISection.Vibe.Logic.afterZeroPunctuate = evt.newValue;
			}
		}

		private void CountdownModeChanged(string newValue, bool isEnum, CountdownMode type)
		{
			if (isEnum)
			{
				((TextElement)_countdownModeDescription).text = CountdownModeExplanation(type);
			}
		}

		private void TimeStackingChanged<T>(ChangeEvent<T> evt)
		{
			GUISection.Vibe.Logic.ComboMultiplier = (((BaseField<bool>)(object)_timeStacking).value ? ((BaseField<float>)(object)_timeStackingMultiplier).value : 1f);
		}

		private static string AfterZeroModeTextSelector(AfterZeroMode mode)
		{
			return mode switch
			{
				AfterZeroMode.Subtract => "-", 
				AfterZeroMode.Multiply => "x", 
				_ => "?", 
			};
		}

		internal float GetTimerCountdown(float unscaledDeltaTime)
		{
			if (!ShouldTimerCountDown(TimerCountdownMode))
			{
				return 0f;
			}
			if (TimerCountdownMode == CountdownMode.SuperHot && (Object)(object)HeroController.instance != (Object)null)
			{
				float magnitude = ((Vector2)(ref HeroController.instance.current_velocity)).magnitude;
				float num = ((magnitude <= float.Epsilon) ? 0.03f : ((!(magnitude > 10f)) ? (((Vector2)(ref HeroController.instance.current_velocity)).magnitude / 10f) : (0.9f + ((Vector2)(ref HeroController.instance.current_velocity)).magnitude / 100f)));
				return unscaledDeltaTime * num;
			}
			return unscaledDeltaTime;
		}

		private static bool ShouldTimerCountDown(CountdownMode mode)
		{
			switch (mode)
			{
			case CountdownMode.Always:
				return true;
			case CountdownMode.Default:
				return Time.timeScale > float.Epsilon;
			case CountdownMode.InGame:
				return (Object)(object)HeroController.instance != (Object)null && HeroController.instance.isGameplayScene;
			case CountdownMode.Unpaused:
			case CountdownMode.SuperHot:
				return (Object)(object)HeroController.instance != (Object)null && HeroController.instance.isGameplayScene && !HeroController.instance.IsPaused();
			case CountdownMode.Never:
				return false;
			default:
				return true;
			}
		}

		private static string CountdownModeExplanation(CountdownMode mode)
		{
			return mode switch
			{
				CountdownMode.Always => "\"Always\": Always counts down (unless the game freezes, i.e. when loading).", 
				CountdownMode.Default => "\"Default\": Counts down when time is simulated (includes main menu, excludes pausing).", 
				CountdownMode.InGame => "\"In Game\": excludes non-gameplay scenes like the main menu, but includes pausing.", 
				CountdownMode.Unpaused => "\"Unpaused\": stops the timer unless the game is playing (i.e. unpaused).", 
				CountdownMode.SuperHot => "\"Super Hot\": the timer only moves when you move.", 
				CountdownMode.Never => "\"Never\": for if you want to control power/time reduction via subtractive vibe sources.", 
				_ => $"DESCRIPTION FOR COUNTDOWN MODE {mode} NOT IMPLEMENTED", 
			};
		}
	}
	internal class WaveSettings : GUISection, IPresetLoadable
	{
		private static float[] WavePeriods = new float[14]
		{
			0.25f, 0.5f, 0.75f, 1f, 1.5f, 2f, 3f, 4f, 6f, 8f,
			12f, 16f, 24f, 32f
		};

		private const string RandomWaveString = "Random Wave";

		private readonly DropdownField _waveTypeSelector;

		private readonly SliderInt _wavePeriod;

		private readonly Label _wavePeriodLabel;

		private readonly Button _testWave;

		public string WaveTypeSelector => ((BaseField<string>)(object)_waveTypeSelector).value;

		public WaveSettings()
			: base("WaveType")
		{
			_waveTypeSelector = GUISection.Get<DropdownField>("WaveTypeSelector");
			_wavePeriod = GUISection.Get<SliderInt>("WavePeriod");
			_wavePeriodLabel = GUISection.Get<Label>("WavePeriodLabel");
			_testWave = GUISection.Get<Button>("TestWave");
			((BaseField<string>)(object)_waveTypeSelector.PopulateDropdown<WaveType>(new string[1] { "Random Wave" }).RegisterDropdownChangedCallback<WaveType>(WaveTypeChanged)).SetupSaving(WaveType.ConstantLinear.ToString(), "WaveType");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<int>((INotifyValueChanged<int>)(object)((BaseField<int>)(object)_wavePeriod).SetupSaving<int>(3, "WavePeriodOptionNumber").DependsOn<int, string>((BaseField<string>)(object)_waveTypeSelector, (BaseField<string> x) => !x.value.StartsWith("Constant")), (EventCallback<ChangeEvent<int>>)WavePeriodChanged);
			_testWave.clicked += TestWaveClicked;
			GUISection.Vibe.Logic.VibeSourceActivated += ReRandomizeWave;
		}

		public void SetToPreset(Preset preset)
		{
			((BaseField<string>)(object)_waveTypeSelector).Load(preset);
			((BaseField<int>)(object)_wavePeriod).Load<int>(preset);
			WavePeriodChanged();
		}

		private void WavePeriodChanged(ChangeEvent<int> evt)
		{
			WavePeriodChanged();
		}

		private void WavePeriodChanged()
		{
			GUISection.Vibe.Logic.wavePeriod = WavePeriods[((BaseField<int>)(object)_wavePeriod).value];
			((TextElement)_wavePeriodLabel).text = WavePeriods[((BaseField<int>)(object)_wavePeriod).value].ToString();
		}

		private void TestWaveClicked()
		{
			GUISection.Vibe.Logic.VibeSourceActivation("Test Button", 0.25f, "+", 2f, "+", 0f);
		}

		private void WaveTypeChanged(string newValue, bool isEnum, WaveType type)
		{
			if (isEnum)
			{
				GUISection.Vibe.Logic.waveType = type;
			}
		}

		internal void ReRandomizeWave()
		{
			if (WaveTypeSelector == "Random Wave")
			{
				GUISection.Vibe.Logic.waveType = ExtHelper.ChooseRandom<WaveType>();
			}
		}
	}
}
namespace ButtplugSong.GUI.VibeSettings.VibeSources
{
	internal class BuzzOnDamage : VibeSourceWithPunctuate
	{
		private readonly Toggle _scaleWithDamage;

		private readonly Toggle _vulnerableVibing;

		private readonly FloatField _vulnerableVibingThreshold;

		public bool ScaleWithDamage
		{
			get
			{
				return ((BaseField<bool>)(object)_scaleWithDamage).value;
			}
			set
			{
				((BaseField<bool>)(object)_scaleWithDamage).value = value;
			}
		}

		public bool VulnerableVibingEnabled
		{
			get
			{
				return ((BaseField<bool>)(object)_vulnerableVibing).value;
			}
			set
			{
				((BaseField<bool>)(object)_vulnerableVibing).value = value;
			}
		}

		public float VulnerableVibingThreshold
		{
			get
			{
				return ((BaseField<float>)(object)_vulnerableVibingThreshold).value / 100f;
			}
			set
			{
				((BaseField<float>)(object)_vulnerableVibingThreshold).value = value * 100f;
			}
		}

		public bool VulnerableVibingActive
		{
			get
			{
				if (VulnerableVibingEnabled)
				{
					return GUISection.Vibe.Logic.TargetPower >= VulnerableVibingThreshold;
				}
				return false;
			}
		}

		protected override string _punctuateReminderDescription => "taking damage";

		public BuzzOnDamage()
			: base("Damage", onByDefault: true, 20f, 5f, defaultPunctuate: true)
		{
			ModHooks.OnTakeDamageHook += PlayerHit;
			_scaleWithDamage = GUISection.Get<Toggle>("ScaleWithDamage");
			((BaseField<bool>)(object)_scaleWithDamage).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled });
			_vulnerableVibing = GUISection.Get<Toggle>("VulnerableVibing");
			((BaseField<bool>)(object)_vulnerableVibing).SetupSaving<bool>(false);
			_vulnerableVibingThreshold = GUISection.Get<FloatField>("VulnerableVibingThreshold");
			((BaseField<float>)(object)_vulnerableVibingThreshold).SetupSaving<float>(50f).SetupValueClamping(0f, 100f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _vulnerableVibing });
		}

		public override void SetToPreset(Preset preset)
		{
			base.SetToPreset(preset);
			((BaseField<bool>)(object)_scaleWithDamage).Load<bool>(preset);
			((BaseField<bool>)(object)_vulnerableVibing).Load<bool>(preset);
			((BaseField<float>)(object)_vulnerableVibingThreshold).Load<float>(preset);
		}

		public int PlayerHit(PlayerData data, int damage)
		{
			if (GUISection.Vibe.Logic.IsVibing && VulnerableVibingActive)
			{
				damage *= 2;
			}
			if (!base.Enabled)
			{
				return damage;
			}
			string subID = ((damage > 1) ? damage.ToString() : string.Empty);
			if (ScaleWithDamage)
			{
				Activate(base.Power * (float)damage, base.Time * (float)damage, subID);
			}
			else
			{
				Activate(subID);
			}
			return damage;
		}
	}
	internal class BuzzOnDeath : VibeSourceWithPunctuate
	{
		private readonly Toggle _includeNonLethal;

		private readonly Toggle _raceTheTimer;

		public bool RaceTheTimerActive;

		private readonly Toggle _corpseRunFail;

		private readonly FloatField _corpseRunFailMultiplier;

		private readonly Toggle _steelSoul;

		private readonly FloatField _steelSoulMultiplier;

		private readonly Toggle _raiseMinimum;

		private readonly FloatField _raiseMinimumAmount;

		private readonly Toggle _deathDice;

		private readonly Label _mostRecentRoll;

		[CompilerGenerated]
		private MinimumDefault? <_minimumDefault>k__BackingField;

		public bool IncludeNonLethal
		{
			get
			{
				return ((BaseField<bool>)(object)_includeNonLethal).value;
			}
			set
			{
				((BaseField<bool>)(object)_includeNonLethal).value = value;
			}
		}

		public bool RaceTheTimer
		{
			get
			{
				return ((BaseField<bool>)(object)_raceTheTimer).value;
			}
			set
			{
				((BaseField<bool>)(object)_raceTheTimer).value = value;
			}
		}

		public bool CorpseRunFail
		{
			get
			{
				return ((BaseField<bool>)(object)_corpseRunFail).value;
			}
			set
			{
				((BaseField<bool>)(object)_corpseRunFail).value = value;
			}
		}

		public float CorpseRunFailMultiplier
		{
			get
			{
				return ((BaseField<float>)(object)_corpseRunFailMultiplier).value;
			}
			set
			{
				((BaseField<float>)(object)_corpseRunFailMultiplier).value = value;
			}
		}

		public bool SteelSoul
		{
			get
			{
				return ((BaseField<bool>)(object)_steelSoul).value;
			}
			set
			{
				((BaseField<bool>)(object)_steelSoul).value = value;
			}
		}

		public float SteelSoulMultiplier
		{
			get
			{
				return ((BaseField<float>)(object)_steelSoulMultiplier).value;
			}
			set
			{
				((BaseField<float>)(object)_steelSoulMultiplier).value = value;
			}
		}

		public bool RaiseMinimum
		{
			get
			{
				return ((BaseField<bool>)(object)_raiseMinimum).value;
			}
			set
			{
				((BaseField<bool>)(object)_raiseMinimum).value = value;
			}
		}

		public float RaiseMinimumAmount
		{
			get
			{
				return ((BaseField<float>)(object)_raiseMinimumAmount).value / 100f;
			}
			set
			{
				((BaseField<float>)(object)_raiseMinimumAmount).value = value * 100f;
			}
		}

		public bool DeathDice
		{
			get
			{
				return ((BaseField<bool>)(object)_deathDice).value;
			}
			set
			{
				((BaseField<bool>)(object)_deathDice).value = value;
			}
		}

		private MinimumDefault? _minimumDefault
		{
			get
			{
				if (<_minimumDefault>k__BackingField == null)
				{
					<_minimumDefault>k__BackingField = (MinimumDefault)GUISection.Vibe.UI.Limits.Minimums.FirstOrDefault((MinimumBase x) => x is MinimumDefault);
				}
				return <_minimumDefault>k__BackingField;
			}
		}

		protected override string _punctuateReminderDescription => "death";

		public BuzzOnDeath()
			: base("Death", onByDefault: true, 100f, 10f, defaultPunctuate: true, 60)
		{
			ModHooks.OnBeforeDeathHook += OnDeathBefore;
			ModHooks.OnAfterDeathHook += OnDeathAfter;
			ModHooks.OnGetCocoonHook += OnGetCocoon;
			GUISection.Vibe.Logic.TimerHitZero += OnTimerZero;
			_includeNonLethal = GUISection.Get<Toggle>("IncludeNonLethal");
			((BaseField<bool>)(object)_includeNonLethal).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled });
			_raceTheTimer = GUISection.Get<Toggle>("RaceTheTimer");
			((BaseField<bool>)(object)_raceTheTimer).SetupSaving<bool>(false).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled });
			_corpseRunFail = GUISection.Get<Toggle>("CorpseRunFail");
			((BaseField<bool>)(object)_corpseRunFail).SetupSaving<bool>(false);
			_corpseRunFailMultiplier = GUISection.Get<FloatField>("CorpseRunFailMultiplier");
			((BaseField<float>)(object)_corpseRunFailMultiplier).SetupSaving<float>(2f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _corpseRunFail }).SetupValueClamping(0f, 999f)
				.SetupGreyout((float x) => x == 1f);
			_steelSoul = GUISection.Get<Toggle>("SteelSoul");
			((BaseField<bool>)(object)_steelSoul).SetupSaving<bool>(false);
			_steelSoulMultiplier = GUISection.Get<FloatField>("SteelSoulMultiplier");
			((BaseField<float>)(object)_steelSoulMultiplier).SetupSaving<float>(10f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _steelSoul }).SetupValueClamping(0f, 999f)
				.SetupGreyout((float x) => x == 1f);
			_raiseMinimum = GUISection.Get<Toggle>("RaiseMinimum");
			INotifyValueChangedExtensions.RegisterValueChangedCallback<bool>((INotifyValueChanged<bool>)(object)((BaseField<bool>)(object)_raiseMinimum).SetupSaving<bool>(false), (EventCallback<ChangeEvent<bool>>)RaiseMinimumChanged<bool>);
			_raiseMinimumAmount = GUISection.Get<FloatField>("RaiseMinimumAmount");
			((BaseField<float>)(object)_raiseMinimumAmount).SetupSaving<float>(5f).DependsOn<float>((Toggle[])(object)new Toggle[1] { _raiseMinimum }).SetupValueClamping(0f, 100f)
				.SetupGreyout((float x) => x == 0f);
			_deathDice = GUISection.Get<Toggle>("DeathDice");
			((BaseField<bool>)(object)_deathDice).SetupSaving<bool>(false);
			_mostRecentRoll = GUISection.Get<Label>("MostRecentRoll");
		}

		public override void SetToPreset(Preset preset)
		{
			base.SetToPreset(preset);
			((BaseField<bool>)(object)_includeNonLethal).Load<bool>(preset);
			((BaseField<bool>)(object)_raceTheTimer).Load<bool>(preset);
			((BaseField<bool>)(object)_corpseRunFail).Load<bool>(preset);
			((BaseField<float>)(object)_corpseRunFailMultiplier).Load<float>(preset);
			((BaseField<bool>)(object)_steelSoul).Load<bool>(preset);
			((BaseField<float>)(object)_steelSoulMultiplier).Load<float>(preset);
			((BaseField<bool>)(object)_raiseMinimum).Load<bool>(preset);
			((BaseField<float>)(object)_raiseMinimumAmount).Load<float>(preset);
			((BaseField<bool>)(object)_deathDice).Load<bool>(preset);
		}

		private static bool HasCocoonOut(PlayerData data)
		{
			return !string.IsNullOrEmpty(data.HeroCorpseScene);
		}

		private static bool IsSteelSoulMode(PlayerData data)
		{
			//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_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			PermadeathModes permadeathMode = data.permadeathMode;
			if (permadeathMode - 1 <= 1)
			{
				return true;
			}
			return false;
		}

		private void RaiseMinimumChanged<T>(ChangeEvent<T> evt)
		{
			_minimumDefault?.UpdateReminderText();
		}

		private void OnDeathBefore(HeroController manager, bool nonLethal, bool frostDeath)
		{
			if (manager.gm.IsMemoryScene())
			{
				nonLethal = true;
			}
			if (base.Enabled && (IncludeNonLethal || !nonLethal))
			{
				Activate();
			}
			PlayerData playerData = manager.playerData;
			if (SteelSoul && IsSteelSoulMode(playerData))
			{
				GUISection.Vibe.Logic.MultiplyTimer(SteelSoulMultiplier, "Steel Soul Death");
			}
			if (CorpseRunFail && HasCocoonOut(playerData) && !IsSteelSoulMode(playerData))
			{
				GUISection.Vibe.Logic.MultiplyTimer(CorpseRunFailMultiplier, "Corpse Run Death");
			}
			if (RaiseMinimum)
			{
				_minimumDefault?.RaiseMinimumDeath(RaiseMinimumAmount);
			}
			if (DeathDice)
			{
				DeathRoll();
			}
		}

		private void DeathRoll()
		{
			int num = ExtHelper.rng.Next(20) + 1;
			((VisualElement)_mostRecentRoll).RemoveFromClassList("hide");
			((TextElement)_mostRecentRoll).text = $"Most recent roll: {num}";
			GUISection.Vibe.UI.DisplayDeathDice(num);
		}

		private void OnDeathAfter(GameManager gm)
		{
			if (((BaseField<bool>)(object)_enabled).value && RaceTheTimer && !IsSteelSoulMode(gm.playerData) && HasCocoonOut(gm.playerData) && !GUISection.Vibe.Logic.TimerZero)
			{
				StartRaceTheTimer();
			}
		}

		private void StartRaceTheTimer()
		{
			RaceTheTimerActive = true;
			UpdateRaceTheTimerGraphic();
		}

		private void OnGetCocoon(HeroController controller)
		{
			if (RaceTheTimerActive)
			{
				EndRaceTheTimer(timerHitZero: false);
			}
		}

		private void OnTimerZero()
		{
			if (RaceTheTimerActive)
			{
				EndRaceTheTimer(timerHitZero: true);
			}
		}

		private void EndRaceTheTimer(bool timerHitZero)
		{
			if (RaceTheTimerActive && timerHitZero && HasCocoonOut(PlayerData.instance))
			{
				int num = DeleteCorpse();
				GUISection.Vibe.UI.LogActivity("Timer Hit Zero : Race the Timer", string.Format("Timer hit 0. Deleting cocoon.\nPlayer lost {0} rosaries.{1}", num, (num > 0) ? " Sorry!" : ""));
			}
			RaceTheTimerActive = false;
			UpdateRaceTheTimerGraphic();
		}

		private static int DeleteCorpse()
		{
			int silk = PlayerData.instance.silk;
			int heroCorpseMoneyPool = PlayerData.instance.HeroCorpseMoneyPool;
			PlayerData.instance.HeroCorpseMoneyPool = 0;
			PlayerData.instance.HeroCorpseScene = string.Empty;
			PlayerData.instance.HeroCorpseMarkerGuid = null;
			EventRegister.SendEvent("BREAK HERO CORPSE", (GameObject)null);
			if (PlayerData.instance.silkMax > 9)
			{
				PlayerData.instance.IsSilkSpoolBroken = false;
				EventRegister.SendEvent(EventRegisterEvents.SpoolUnbroken, (GameObject)null);
			}
			if (PlayerData.instance.silk > silk)
			{
				HeroController.instance.TakeSilk(PlayerData.instance.silk - silk);
			}
			return heroCorpseMoneyPool;
		}

		private void UpdateRaceTheTimerGraphic()
		{
			GUISection.Vibe.UI.TimerReadout.SetClassListIf<Label>("race-the-timer", (Func<Label, bool>)((Label x) => RaceTheTimerActive));
		}
	}
	internal class BuzzOnHeal : VibeSourceWithPunctuate
	{
		private readonly Toggle _noHealingWhileVibing;

		private readonly Toggle _interruption;

		private readonly FloatField _interruptionTime;

		private DateTime _lastHealInterruptionTime = DateTime.MinValue;

		private static TimeSpan _healInterruptionCooldown = TimeSpan.FromSeconds(1.0);

		public bool NoHealingWhileVibing
		{
			get
			{
				return ((BaseField<bool>)(object)_noHealingWhileVibing).value;
			}
			set
			{
				((BaseField<bool>)(object)_noHealingWhileVibing).value = value;
			}
		}

		public bool NoHealing
		{
			get
			{
				if (NoHealingWhileVibing)
				{
					return !GUISection.Vibe.Logic.TimerZero;
				}
				return false;
			}
		}

		public bool Interruption
		{
			get
			{
				return ((BaseField<bool>)(object)_interruption).value;
			}
			set
			{
				((BaseField<bool>)(object)_interruption).value = value;
			}
		}

		public float InterruptionTime
		{
			get
			{
				return ((BaseField<float>)(object)_interruptionTime).value;
			}
			set
			{
				((BaseField<float>)(object)_interruptionTime).value = value;
			}
		}

		protected override string _punctuateReminderDescription => "healing";

		public BuzzOnHeal()
			: base("Heal", onByDefault: true, 10f, 5f)
		{
			ModHooks.OnAddHealthHook += Healed;
			ModHooks.OnBindInterruptedHook += HealInterrupted;
			_noHealingWhileVibing = GUISection.Get<Toggle>("NoHealingWhileVibing");
			((BaseField<bool>)(object)_noHealingWhileVibing).SetupSaving<bool>(false);
			_interruption = GUISection.Get<Toggle>("Interruption");
			((BaseField<bool>)(object)_interruption).SetupSaving<bool>(true);
			_interruptionTime = GUISection.Get<FloatField>("InterruptionTime");
			((BaseField<float>)(object)_interruptionTime).SetupSaving<float>(2f).SetupValueClamping(0f, 999f).SetupGreyout((float x) => x == 0f)
				.DependsOn<float>((Toggle[])(object)new Toggle[1] { _interruption });
		}

		public override void SetToPreset(Preset preset)
		{
			base.SetToPreset(preset);
			((BaseField<bool>)(object)_noHealingWhileVibing).Load<bool>(preset);
			((BaseField<bool>)(object)_interruption).Load<bool>(preset);
			((BaseField<float>)(object)_interruptionTime).Load<float>(preset);
		}

		private int Healed(PlayerData data, int amount)
		{
			if (NoHealing)
			{
				amount = 0;
			}
			if (!base.Enabled)
			{
				return amount;
			}
			Activate();
			return amount;
		}

		private void HealInterrupted(HeroController controller)
		{
			if (Interruption && !(DateTime.Now - _lastHealInterruptionTime < _healInterruptionCooldown))
			{
				if (controller.WillDoBellBindHit())
				{
					ActivatePunctuation(InterruptionTime / 2f);
				}
				else
				{
					ActivatePunctuation(InterruptionTime);
				}
				_lastHealInterruptionTime = DateTime.Now;
			}
		}
	}
	internal class BuzzOnPickups : VibeSourceWithPunctuate
	{
		private readonly Toggle _scaleWithWeighting;

		private readonly Dictionary<string, WeightedEvent> KnownItems = new Dictionary<string, WeightedEvent>();

		public bool ScaleWithWeighting
		{
			get
			{
				return ((BaseField<bool>)(object)_scaleWithWeighting).value;
			}
			set
			{
				((BaseField<bool>)(object)_scaleWithWeighting).value = value;
			}
		}

		protected override string _punctuateReminderDescription => "collecting an item";

		public BuzzOnPickups()
			: base("Pickups", onByDefault: false, 50f, 5f, defaultPunctuate: true)
		{
			ModHooks.OnItemPickupHook += ItemPickup;
			ModHooks.OnSetBoolHook += OnSetBool;
			ModHooks.OnSetIntHook += OnSetInt;
			ModHooks.OnMaxHealthUpHook += OnMaxHealthUp;
			ModHooks.OnMaxSilkUpHook += OnMaxSilkUp;
			ModHooks.OnToolUnlockHook += ToolUnlock;
			_scaleWithWeighting = GUISection.Get<Toggle>("PickupsScaleWithWeighting");
			((BaseField<bool>)(object)_scaleWithWeighting).SetupSaving<bool>(true).DependsOn<bool>((Toggle[])(object)new Toggle[1] { _enabled });
			VisualElement parent = ((VisualElement)GUISection.Get<Label>("PickupsItemListLabel")).parent;
			KnownItems["Rosary_Set_Frayed"] = CreateUI("FrayedRosaryString", 0.3f, defaultOn: true, gapBelow: false, "Collectable Items");
			KnownItems["Rosary_Set_Small"] = CreateUI("RosaryString", 0.6f, defaultOn: true);
			KnownItems["Rosary_Set_Medium"] = CreateUI("RosaryNecklace", 1.2f, defaultOn: true);
			KnownItems["Rosary_Set_Large"] = CreateUI("HeavyRosaryNecklace", 2.2f, defaultOn: true);
			KnownItems["Rosary_Set_Huge_White"] = CreateUI("PaleRosaryNecklace", 3.4f, defaultOn: true, gapBelow: true);
			KnownItems["Shard Pouch"] = CreateUI("ShardBundle", 0.5f, defaultOn: true);
			KnownItems["Great Shard"] = CreateUI("BeastShard", 1.4f, defaultOn: true);
			KnownItems["Pristine Core"] = CreateUI("PristineCore", 2.2f, defaultOn: true);
			KnownItems["Fixer Idol"] = CreateUI("HornetStatuette", 3f, defaultOn: true);
			KnownItems["Growstone"] = CreateUI("Growstone", 3f, defaultOn: true, gapBelow: true);
			KnownItems["Silk Grub"] = CreateUI("SilkEater", 0.8f, defaultOn: true);
			KnownItems["Tool Metal"] = CreateUI("Craftmetal", 0.5f, defaultOn: true);
			MapCategory(CreateUI("Relics", 0.6f, defaultOn: true), new string[14]
			{
				"Bone Record Wisp Top", "Bone Record Greymoor_flooded_corridor", "Bone Record Bone_East_14", "Bone Record Understore_Map_Room", "Seal Chit City Merchant", "Seal Chit Silk Siphon", "Seal Chit Ward Corpse", "Seal Chit Aspid_01", "Weaver Record Conductor", "Weaver Record Sprint_Challenge",
				"Weaver Record Weave_08", "Weaver Totem Slab_Bottom", "Weaver Totem Bonetown_upper_room", "Weaver Totem Witch"
			});
			KnownItems["Ancient Egg Abyss Middle"] = CreateUI("ArcaneEgg", 2.4f, defaultOn: true);
			MapCategory(CreateUI("PsalmCylinders", 0.6f, defaultOn: true), new string[5] { "Psalm Cylinder Hang", "Psalm Cylinder Librarian", "Psalm Cylinder Grindle", "Psalm Cylinder Library Roof", "Psalm Cylinder Ward" });
			KnownItems["Librarian Melody Cylinder"] = CreateUI("SacredCylinder", 2f, defaultOn: true, gapBelow: true);
			MapCategory(CreateUI("ShamanSouls", 0.8f, defaultOn: true), new string[3] { "Snare Soul Bell Hermit", "Snare Soul Churchkeeper", "Snare Soul Swamp Bug" });
			MapCategory(CreateUI("OldHearts", 1.5f, defaultOn: true), new string[4] { "Clover Heart", "Coral Heart", "Flower Heart", "Hunter Heart" });
			KnownItems["Pale_Oil"] = CreateUI("PaleOil", 2f, defaultOn: true);
			KnownItems["Wood Witch Item"] = CreateUI("TwistedBud", 6f, defaultOn: true);
			KnownItems["Plasmium Gland"] = CreateUI("PlasmiumGland", 1f, defaultOn: true);
			KnownItems["White Flower"] = CreateUI("Everbloom", 2.5f, defaultOn: true, gapBelow: true);
			MapCategory(CreateUI("OtherQuestItems", 0.2f, defaultOn: true, gapBelow: true), new string[31]
			{
				"Broodmother Remains", "Cog Heart Pieces", "Skull King Fragment", "Coral Ingredient", "Rock Roller Item", "Ant Trapper Item", "Beastfly Remains", "Mossberry", "Mossberry Stew", "Pickled Roach Egg",
				"Shell Flower", "Broken SilkShot", "Extractor Machine Pins", "Vintage Nectar", "Courier Supplies Gourmand", "Courier Supplies", "Courier Supplies Mask Maker", "Courier Supplies Slave", "Song Pilgrim Cloak", "Fine Pin",
				"Pilgrim Rag", "Plasmium Blood", "Plasmium", "Crow Feather", "Roach Corpse Item", "Enemy Morsel Seared", "Enemy Morsel Shredded", "Silver Bellclapper", "Enemy Morsel Speared", "Common Spine",
				"Flintgem"
			});
			MapCategory(CreateUI("Mementos", 1f, defaultOn: true, gapBelow: true), new string[7] { "Crowman Memento", "Grey Memento", "Memento Seth", "Memento Garmond", "Hunter Memento", "Sprintmaster Memento", "Memento Surface" });
			MapCategory(CreateUI("RedTools", 0.8f, defaultOn: true, gapBelow: false, "Equipment / Upgrades"), new string[23]
			{
				"Cogwork Flier", "Cogwork Saw", "Conch Drill", "Curve Claws", "Curve Claws Upgraded", "Screw Attack", "Flea Brew", "Flintstone", "Harpoon", "Extractor",
				"Pimpilo", "Lifeblood Syringe", "Rosary Cannon", "WebShot Forge", "WebShot Weaver", "WebShot Architect", "Silk Snare", "Sting Shard", "Straight Pin", "Tack",
				"Tri Pin", "Shakra Ring", "Lightning Rod"
			});
			MapCategory(CreateUI("BlueTools", 0.7f, defaultOn: true), new string[23]
			{
				"Dazzle Bind", "Dazzle Bind Upgraded", "Mosscreep Tool 1", "Mosscreep Tool 2", "Flea Charm", "Fractured Mask", "Quickbind", "Longneedle", "Lava Charm", "Revenge Crystal",
				"Multibind", "Pinstress Tool", "Poison Pouch", "Quick Sling", "Reserve Bind", "Brolly Spike", "Thief Claw", "Spool Extender", "Zap Imbuement", "Bell Bind",
				"White Ring", "Wisp Lantern", "Maggot Charm"
			});
			MapCategory(CreateUI("YellowTools", 0.6f, defaultOn: true), new string[13]
			{
				"Wallcling", "Barbed Wire", "Compass", "Dead Mans Purse", "Rosary Magnet", "Magnetite Dice", "Scuttlebrace", "Bone Necklace", "Shell Satchel", "Sprintmaster",
				"Musician Charm", "Thief Charm", "Weighted Anklet"
			});
			KnownItems["ToolPouchUpgrades"] = CreateUI("ToolPouch", 0.5f, defaultOn: true);
			KnownItems["Tool Pouch&Kit Inv"] = KnownItems["ToolPouchUpgrades"];
			KnownItems["ToolKitUpgrades"] = CreateUI("CraftingKit", 0.5f, defaultOn: true, gapBelow: true);
			KnownItems["silkRegenMax"] = CreateUI("SilkHeart", 1.2f, defaultOn: true);
			KnownItems["Crest Socket Unlocker"] = CreateUI("MemoryLocket", 0.8f, defaultOn: true);
			KnownItems["heartPieces"] = CreateUI("MaskShard", 0.6f, defaultOn: true);
			KnownItems["fullHeart"] = CreateUI("FullMask", 1f, defaultOn: true);
			KnownItems["silkSpoolParts"] = CreateUI("SpoolFragment", 0.4f, defaultOn: true);
			KnownItems["fullSpool"] = CreateUI("FullSpool", 0.6f, defaultOn: true, gapBelow: true);
			MapCategory(CreateUI("Maps", 0.2f, defaultOn: true, gapBelow: false, "Navigation / World"), new string[28]
			{
				"HasBellhartMap", "HasSwampMap", "HasJudgeStepsMap", "HasHallsMap", "HasCogMap", "HasCradleMap", "HasDocksMap", "HasWildsMap", "HasSongGateMap", "HasGreymoorMap",
				"HasHangMap", "HasHuntersNestMap", "HasArboriumMap", "HasMossGrottoMap", "HasPeakMap", "HasAqueductMap", "HasCoralMap", "HasShellwoodMap", "HasDustpensMap", "HasAbyssMap",
				"HasBoneforestMap", "HasSlabMap", "HasCitadelUnderstoreMap", "HasCloverMap", "HasWeavehomeMap", "HasLibraryMap", "HasWardMap", "HasCrawlMap"
			});
			MapCategory(CreateUI("MapMarkers", 0.1f, defaultOn: true), new string[15]
			{
				"hasMarker_a", "hasMarker_b", "hasMarker_c", "hasMarker_d", "hasMarker_e", "hasPinBench", "hasPinStag", "hasPinShop", "hasPinTube", "hasPinFleaMarrowlands",
				"hasPinFleaMidlands", "hasPinFleaBlastedlands", "hasPinFleaCitadel", "hasPinFleaPeaklands", "hasPinFleaMucklands"
			});
			KnownItems["QuillState"] = CreateUI("Quill", 0.3f, defaultOn: true, gapBelow: true);
			MapCategory(CreateUI("BellwayUnlocks", 0.4f, defaultOn: true), new string[10] { "UnlockedDocksStation", "UnlockedBoneforestEastStation", "UnlockedGreymoorStation", "UnlockedBelltownStation", "UnlockedCoralTowerStation", "UnlockedCityStation", "UnlockedPeakStation", "UnlockedShellwoodStation", "UnlockedShadowStation", "UnlockedAqueductStation" });
			MapCategory(CreateUI("VentricaUnlocks", 0.5f, defaultOn: true, gapBelow: true), new string[6] { "UnlockedSongTube", "UnlockedUnderTube", "UnlockedCityBellwayTube", "UnlockedHangTube", "UnlockedEnclaveTube", "UnlockedArboriumTube" });
			MapCategory(CreateUI("Keys", 0.6f, defaultOn: true, gapBelow: true), new string[8] { "Architect Key", "Belltown House Key", "Craw Summons", "Dock Key", "Slab Key", "Simple Key", "Ward Boss Key", "Ward Key" });
			MapCategory(CreateUI("BellhomeUpgrades", 0.4f, defaultOn: true), new string[7] { "Crawbell", "Farsight", "Materium", "BelltownFurnishingDesk", "BelltownFurnishingFairyLights", "BelltownFurnishingGramaphone", "BelltownFurnishingSpa" });
			MapCategory(CreateUI("MateriumEntries", 0.3f, defaultOn: true, gapBelow: true), new string[4] { "Materium-Flintstone", "Materium-Magnetite", "Materium-Voltridian", "Journal_Entry-Void_Tendrils" });
			MapCategory(CreateUI("SilkSkills", 1f, defaultOn: true, gapBelow: false, "Abilities"), new string[6] { "Parry", "Silk Boss Needle", "Silk Bomb", "Silk Charge", "Silk Spear", "Thread Sphere" });
			KnownItems["hasDash"] = CreateUI("SwiftStep", 0.6f, defaultOn: true);
			KnownItems["hasWalljump"] = CreateUI("ClingGrip", 1.2f, defaultOn: true);
			KnownItems["hasHarpoonDash"] = CreateUI("Clawline", 1f, defaultOn: true);
			KnownItems["hasSuperJump"] = CreateUI("SilkSoar", 2f, defaultOn: true);
			KnownItems["hasChargeSlash"] = CreateUI("NeedleStrike", 0.6f, defaultOn: true, gapBelow: true);
			KnownItems["hasBrolly"] = CreateUI("DriftersCloak", 1f, defaultOn: true);
			KnownItems["hasDoubleJump"] = CreateUI("FaydownCloak", 2f, defaultOn: true, gapBelow: true);
			KnownItems["hasNeedolin"] = Create

websocket-sharp.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Timers;
using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
using WebSocketSharp.Server;

[assembly: AssemblyProduct("websocket-sharp.dll")]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyCompany("")]
[assembly: AssemblyCopyright("sta.blockhead")]
[assembly: AssemblyTrademark("")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyTitle("websocket-sharp")]
[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client and server")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyVersion("1.0.2.59611")]
namespace WebSocketSharp
{
	public static class Ext
	{
		private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";

		private static readonly byte[] _last;

		private static readonly int _retry;

		private static byte[] compress(this byte[] data)
		{
			if (data.LongLength == 0)
			{
				return data;
			}
			using MemoryStream stream = new MemoryStream(data);
			return stream.compressToArray();
		}

		private static MemoryStream compress(this Stream stream)
		{
			MemoryStream memoryStream = new MemoryStream();
			if (stream.Length == 0)
			{
				return memoryStream;
			}
			stream.Position = 0L;
			using DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress, leaveOpen: true);
			CopyTo(stream, deflateStream, 1024);
			deflateStream.Close();
			memoryStream.Write(_last, 0, 1);
			memoryStream.Position = 0L;
			return memoryStream;
		}

		private static byte[] compressToArray(this Stream stream)
		{
			using MemoryStream memoryStream = stream.compress();
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		private static byte[] decompress(this byte[] data)
		{
			if (data.LongLength == 0)
			{
				return data;
			}
			using MemoryStream stream = new MemoryStream(data);
			return stream.decompressToArray();
		}

		private static MemoryStream decompress(this Stream stream)
		{
			MemoryStream memoryStream = new MemoryStream();
			if (stream.Length == 0)
			{
				return memoryStream;
			}
			stream.Position = 0L;
			using DeflateStream source = new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: true);
			CopyTo(source, memoryStream, 1024);
			memoryStream.Position = 0L;
			return memoryStream;
		}

		private static byte[] decompressToArray(this Stream stream)
		{
			using MemoryStream memoryStream = stream.decompress();
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		private static void times(this ulong n, Action action)
		{
			for (ulong num = 0uL; num < n; num++)
			{
				action();
			}
		}

		internal static byte[] Append(this ushort code, string reason)
		{
			byte[] array = code.InternalToByteArray(ByteOrder.Big);
			if (reason != null && reason.Length > 0)
			{
				List<byte> list = new List<byte>(array);
				list.AddRange(Encoding.UTF8.GetBytes(reason));
				array = list.ToArray();
			}
			return array;
		}

		internal static string CheckIfAvailable(this ServerState state, bool ready, bool start, bool shutting)
		{
			return ((!ready && (state == ServerState.Ready || state == ServerState.Stop)) || (!start && state == ServerState.Start) || (!shutting && state == ServerState.ShuttingDown)) ? ("This operation isn't available in: " + state.ToString().ToLower()) : null;
		}

		internal static string CheckIfAvailable(this WebSocketState state, bool connecting, bool open, bool closing, bool closed)
		{
			return ((!connecting && state == WebSocketState.Connecting) || (!open && state == WebSocketState.Open) || (!closing && state == WebSocketState.Closing) || (!closed && state == WebSocketState.Closed)) ? ("This operation isn't available in: " + state.ToString().ToLower()) : null;
		}

		internal static string CheckIfValidProtocols(this string[] protocols)
		{
			return protocols.Contains((string protocol) => protocol == null || protocol.Length == 0 || !protocol.IsToken()) ? "Contains an invalid value." : (protocols.ContainsTwice() ? "Contains a value twice." : null);
		}

		internal static string CheckIfValidServicePath(this string path)
		{
			return (path == null || path.Length == 0) ? "'path' is null or empty." : ((path[0] != '/') ? "'path' isn't an absolute path." : ((path.IndexOfAny(new char[2] { '?', '#' }) > -1) ? "'path' includes either or both query and fragment components." : null));
		}

		internal static string CheckIfValidSessionID(this string id)
		{
			return (id == null || id.Length == 0) ? "'id' is null or empty." : null;
		}

		internal static string CheckIfValidWaitTime(this TimeSpan time)
		{
			return (time <= TimeSpan.Zero) ? "A wait time is zero or less." : null;
		}

		internal static bool CheckWaitTime(this TimeSpan time, out string message)
		{
			message = null;
			if (time <= TimeSpan.Zero)
			{
				message = "A wait time is zero or less.";
				return false;
			}
			return true;
		}

		internal static void Close(this WebSocketSharp.Net.HttpListenerResponse response, WebSocketSharp.Net.HttpStatusCode code)
		{
			response.StatusCode = (int)code;
			response.OutputStream.Close();
		}

		internal static void CloseWithAuthChallenge(this WebSocketSharp.Net.HttpListenerResponse response, string challenge)
		{
			response.Headers.InternalSet("WWW-Authenticate", challenge, response: true);
			response.Close(WebSocketSharp.Net.HttpStatusCode.Unauthorized);
		}

		internal static byte[] Compress(this byte[] data, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? data.compress() : data;
		}

		internal static Stream Compress(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.compress() : stream;
		}

		internal static byte[] CompressToArray(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.compressToArray() : stream.ToByteArray();
		}

		internal static bool Contains<T>(this IEnumerable<T> source, Func<T, bool> condition)
		{
			foreach (T item in source)
			{
				if (condition(item))
				{
					return true;
				}
			}
			return false;
		}

		internal static bool ContainsTwice(this string[] values)
		{
			int len = values.Length;
			Func<int, bool> contains = null;
			contains = delegate(int idx)
			{
				if (idx < len - 1)
				{
					for (int i = idx + 1; i < len; i++)
					{
						if (values[i] == values[idx])
						{
							return true;
						}
					}
					return contains(++idx);
				}
				return false;
			};
			return contains(0);
		}

		internal static T[] Copy<T>(this T[] source, long length)
		{
			T[] array = new T[length];
			Array.Copy(source, 0L, array, 0L, length);
			return array;
		}

		internal static void CopyTo(this Stream source, Stream destination, int bufferLength)
		{
			byte[] buffer = new byte[bufferLength];
			int num = 0;
			while ((num = source.Read(buffer, 0, bufferLength)) > 0)
			{
				destination.Write(buffer, 0, num);
			}
		}

		internal static void CopyToAsync(this Stream source, Stream destination, int bufferLength, Action completed, Action<Exception> error)
		{
			byte[] buff = new byte[bufferLength];
			AsyncCallback callback = null;
			callback = delegate(IAsyncResult ar)
			{
				try
				{
					int num = source.EndRead(ar);
					if (num <= 0)
					{
						if (completed != null)
						{
							completed();
						}
					}
					else
					{
						destination.Write(buff, 0, num);
						source.BeginRead(buff, 0, bufferLength, callback, null);
					}
				}
				catch (Exception obj2)
				{
					if (error != null)
					{
						error(obj2);
					}
				}
			};
			try
			{
				source.BeginRead(buff, 0, bufferLength, callback, null);
			}
			catch (Exception obj)
			{
				if (error != null)
				{
					error(obj);
				}
			}
		}

		internal static byte[] Decompress(this byte[] data, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? data.decompress() : data;
		}

		internal static Stream Decompress(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.decompress() : stream;
		}

		internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.decompressToArray() : stream.ToByteArray();
		}

		internal static bool EqualsWith(this int value, char c, Action<int> action)
		{
			action(value);
			return value == c;
		}

		internal static string GetAbsolutePath(this Uri uri)
		{
			if (uri.IsAbsoluteUri)
			{
				return uri.AbsolutePath;
			}
			string originalString = uri.OriginalString;
			if (originalString[0] != '/')
			{
				return null;
			}
			int num = originalString.IndexOfAny(new char[2] { '?', '#' });
			return (num > 0) ? originalString.Substring(0, num) : originalString;
		}

		internal static string GetMessage(this CloseStatusCode code)
		{
			return code switch
			{
				CloseStatusCode.TlsHandshakeFailure => "An error has occurred during a TLS handshake.", 
				CloseStatusCode.ServerError => "WebSocket server got an internal error.", 
				CloseStatusCode.MandatoryExtension => "WebSocket client didn't receive expected extension(s).", 
				CloseStatusCode.TooBig => "A too big message has been received.", 
				CloseStatusCode.PolicyViolation => "A policy violation has occurred.", 
				CloseStatusCode.InvalidData => "Invalid data has been received.", 
				CloseStatusCode.Abnormal => "An exception has occurred.", 
				CloseStatusCode.UnsupportedData => "Unsupported data has been received.", 
				CloseStatusCode.ProtocolError => "A WebSocket protocol error has occurred.", 
				_ => string.Empty, 
			};
		}

		internal static string GetName(this string nameAndValue, char separator)
		{
			int num = nameAndValue.IndexOf(separator);
			return (num > 0) ? nameAndValue.Substring(0, num).Trim() : null;
		}

		internal static string GetValue(this string nameAndValue, char separator)
		{
			int num = nameAndValue.IndexOf(separator);
			return (num > -1 && num < nameAndValue.Length - 1) ? nameAndValue.Substring(num + 1).Trim() : null;
		}

		internal static string GetValue(this string nameAndValue, char separator, bool unquote)
		{
			int num = nameAndValue.IndexOf(separator);
			if (num < 0 || num == nameAndValue.Length - 1)
			{
				return null;
			}
			string text = nameAndValue.Substring(num + 1).Trim();
			return unquote ? text.Unquote() : text;
		}

		internal static TcpListenerWebSocketContext GetWebSocketContext(this TcpClient tcpClient, string protocol, bool secure, ServerSslConfiguration sslConfig, Logger logger)
		{
			return new TcpListenerWebSocketContext(tcpClient, protocol, secure, sslConfig, logger);
		}

		internal static byte[] InternalToByteArray(this ushort value, ByteOrder order)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			if (!order.IsHostOrder())
			{
				Array.Reverse((Array)bytes);
			}
			return bytes;
		}

		internal static byte[] InternalToByteArray(this ulong value, ByteOrder order)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			if (!order.IsHostOrder())
			{
				Array.Reverse((Array)bytes);
			}
			return bytes;
		}

		internal static bool IsCompressionExtension(this string value, CompressionMethod method)
		{
			return value.StartsWith(method.ToExtensionString());
		}

		internal static bool IsControl(this byte opcode)
		{
			return opcode > 7 && opcode < 16;
		}

		internal static bool IsControl(this Opcode opcode)
		{
			return (int)opcode >= 8;
		}

		internal static bool IsData(this byte opcode)
		{
			return opcode == 1 || opcode == 2;
		}

		internal static bool IsData(this Opcode opcode)
		{
			return opcode == Opcode.Text || opcode == Opcode.Binary;
		}

		internal static bool IsPortNumber(this int value)
		{
			return value > 0 && value < 65536;
		}

		internal static bool IsReserved(this ushort code)
		{
			return code == 1004 || code == 1005 || code == 1006 || code == 1015;
		}

		internal static bool IsReserved(this CloseStatusCode code)
		{
			return code == CloseStatusCode.Undefined || code == CloseStatusCode.NoStatus || code == CloseStatusCode.Abnormal || code == CloseStatusCode.TlsHandshakeFailure;
		}

		internal static bool IsSupported(this byte opcode)
		{
			return Enum.IsDefined(typeof(Opcode), opcode);
		}

		internal static bool IsText(this string value)
		{
			int length = value.Length;
			for (int i = 0; i < length; i++)
			{
				char c = value[i];
				if (c < ' ' && !Contains("\r\n\t", c))
				{
					return false;
				}
				switch (c)
				{
				case '\u007f':
					return false;
				case '\n':
					if (++i < length)
					{
						c = value[i];
						if (!Contains(" \t", c))
						{
							return false;
						}
					}
					break;
				}
			}
			return true;
		}

		internal static bool IsToken(this string value)
		{
			foreach (char c in value)
			{
				if (c < ' ' || c >= '\u007f' || Contains("()<>@,;:\\\"/[]?={} \t", c))
				{
					return false;
				}
			}
			return true;
		}

		internal static string Quote(this string value)
		{
			return string.Format("\"{0}\"", value.Replace("\"", "\\\""));
		}

		internal static byte[] ReadBytes(this Stream stream, int length)
		{
			byte[] array = new byte[length];
			int num = 0;
			try
			{
				int num2 = 0;
				while (length > 0)
				{
					num2 = stream.Read(array, num, length);
					if (num2 == 0)
					{
						break;
					}
					num += num2;
					length -= num2;
				}
			}
			catch
			{
			}
			return array.SubArray(0, num);
		}

		internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength)
		{
			using MemoryStream memoryStream = new MemoryStream();
			try
			{
				byte[] buffer = new byte[bufferLength];
				int num = 0;
				while (length > 0)
				{
					if (length < bufferLength)
					{
						bufferLength = (int)length;
					}
					num = stream.Read(buffer, 0, bufferLength);
					if (num == 0)
					{
						break;
					}
					memoryStream.Write(buffer, 0, num);
					length -= num;
				}
			}
			catch
			{
			}
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		internal static void ReadBytesAsync(this Stream stream, int length, Action<byte[]> completed, Action<Exception> error)
		{
			byte[] buff = new byte[length];
			int offset = 0;
			int retry = 0;
			AsyncCallback callback = null;
			callback = delegate(IAsyncResult ar)
			{
				try
				{
					int num = stream.EndRead(ar);
					if (num == 0 && retry < _retry)
					{
						retry++;
						stream.BeginRead(buff, offset, length, callback, null);
					}
					else if (num == 0 || num == length)
					{
						if (completed != null)
						{
							completed(buff.SubArray(0, offset + num));
						}
					}
					else
					{
						retry = 0;
						offset += num;
						length -= num;
						stream.BeginRead(buff, offset, length, callback, null);
					}
				}
				catch (Exception obj2)
				{
					if (error != null)
					{
						error(obj2);
					}
				}
			};
			try
			{
				stream.BeginRead(buff, offset, length, callback, null);
			}
			catch (Exception obj)
			{
				if (error != null)
				{
					error(obj);
				}
			}
		}

		internal static void ReadBytesAsync(this Stream stream, long length, int bufferLength, Action<byte[]> completed, Action<Exception> error)
		{
			MemoryStream dest = new MemoryStream();
			byte[] buff = new byte[bufferLength];
			int retry = 0;
			Action<long> read = null;
			read = delegate(long len)
			{
				if (len < bufferLength)
				{
					bufferLength = (int)len;
				}
				stream.BeginRead(buff, 0, bufferLength, delegate(IAsyncResult ar)
				{
					try
					{
						int num = stream.EndRead(ar);
						if (num > 0)
						{
							dest.Write(buff, 0, num);
						}
						if (num == 0 && retry < _retry)
						{
							retry++;
							read(len);
						}
						else if (num == 0 || num == len)
						{
							if (completed != null)
							{
								dest.Close();
								completed(dest.ToArray());
							}
							dest.Dispose();
						}
						else
						{
							retry = 0;
							read(len - num);
						}
					}
					catch (Exception obj2)
					{
						dest.Dispose();
						if (error != null)
						{
							error(obj2);
						}
					}
				}, null);
			};
			try
			{
				read(length);
			}
			catch (Exception obj)
			{
				dest.Dispose();
				if (error != null)
				{
					error(obj);
				}
			}
		}

		internal static string RemovePrefix(this string value, params string[] prefixes)
		{
			int num = 0;
			foreach (string text in prefixes)
			{
				if (value.StartsWith(text))
				{
					num = text.Length;
					break;
				}
			}
			return (num > 0) ? value.Substring(num) : value;
		}

		internal static T[] Reverse<T>(this T[] array)
		{
			int num = array.Length;
			T[] array2 = new T[num];
			int num2 = num - 1;
			for (int i = 0; i <= num2; i++)
			{
				array2[i] = array[num2 - i];
			}
			return array2;
		}

		internal static IEnumerable<string> SplitHeaderValue(this string value, params char[] separators)
		{
			int len = value.Length;
			string seps = new string(separators);
			StringBuilder buff = new StringBuilder(32);
			bool escaped = false;
			bool quoted = false;
			for (int i = 0; i < len; i++)
			{
				char c = value[i];
				switch (c)
				{
				case '"':
					if (escaped)
					{
						escaped = !escaped;
					}
					else
					{
						quoted = !quoted;
					}
					break;
				case '\\':
					if (i < len - 1 && value[i + 1] == '"')
					{
						escaped = true;
					}
					break;
				default:
					if (Contains(seps, c) && !quoted)
					{
						yield return buff.ToString();
						buff.Length = 0;
						continue;
					}
					break;
				}
				buff.Append(c);
			}
			if (buff.Length > 0)
			{
				yield return buff.ToString();
			}
		}

		internal static byte[] ToByteArray(this Stream stream)
		{
			using MemoryStream memoryStream = new MemoryStream();
			stream.Position = 0L;
			CopyTo(stream, memoryStream, 1024);
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		internal static CompressionMethod ToCompressionMethod(this string value)
		{
			foreach (CompressionMethod value2 in Enum.GetValues(typeof(CompressionMethod)))
			{
				if (value2.ToExtensionString() == value)
				{
					return value2;
				}
			}
			return CompressionMethod.None;
		}

		internal static string ToExtensionString(this CompressionMethod method, params string[] parameters)
		{
			if (method == CompressionMethod.None)
			{
				return string.Empty;
			}
			string text = $"permessage-{method.ToString().ToLower()}";
			if (parameters == null || parameters.Length == 0)
			{
				return text;
			}
			return string.Format("{0}; {1}", text, parameters.ToString("; "));
		}

		internal static IPAddress ToIPAddress(this string hostnameOrAddress)
		{
			if (IPAddress.TryParse(hostnameOrAddress, out IPAddress address))
			{
				return address;
			}
			try
			{
				return Dns.GetHostAddresses(hostnameOrAddress)[0];
			}
			catch
			{
				return null;
			}
		}

		internal static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
		{
			return new List<TSource>(source);
		}

		internal static ushort ToUInt16(this byte[] source, ByteOrder sourceOrder)
		{
			return BitConverter.ToUInt16(source.ToHostOrder(sourceOrder), 0);
		}

		internal static ulong ToUInt64(this byte[] source, ByteOrder sourceOrder)
		{
			return BitConverter.ToUInt64(source.ToHostOrder(sourceOrder), 0);
		}

		internal static string TrimEndSlash(this string value)
		{
			value = value.TrimEnd(new char[1] { '/' });
			return (value.Length > 0) ? value : "/";
		}

		internal static bool TryCreateWebSocketUri(this string uriString, out Uri result, out string message)
		{
			result = null;
			Uri uri = uriString.ToUri();
			if (uri == null)
			{
				message = "An invalid URI string: " + uriString;
				return false;
			}
			if (!uri.IsAbsoluteUri)
			{
				message = "Not an absolute URI: " + uriString;
				return false;
			}
			string scheme = uri.Scheme;
			if (!(scheme == "ws") && !(scheme == "wss"))
			{
				message = "The scheme part isn't 'ws' or 'wss': " + uriString;
				return false;
			}
			if (uri.Fragment.Length > 0)
			{
				message = "Includes the fragment component: " + uriString;
				return false;
			}
			int port = uri.Port;
			if (port == 0)
			{
				message = "The port part is zero: " + uriString;
				return false;
			}
			result = ((port != -1) ? uri : new Uri(string.Format("{0}://{1}:{2}{3}", scheme, uri.Host, (scheme == "ws") ? 80 : 443, uri.PathAndQuery)));
			message = string.Empty;
			return true;
		}

		internal static string Unquote(this string value)
		{
			int num = value.IndexOf('"');
			if (num < 0)
			{
				return value;
			}
			int num2 = value.LastIndexOf('"');
			int num3 = num2 - num - 1;
			return (num3 < 0) ? value : ((num3 == 0) ? string.Empty : value.Substring(num + 1, num3).Replace("\\\"", "\""));
		}

		internal static string UTF8Decode(this byte[] bytes)
		{
			try
			{
				return Encoding.UTF8.GetString(bytes);
			}
			catch
			{
				return null;
			}
		}

		internal static byte[] UTF8Encode(this string s)
		{
			return Encoding.UTF8.GetBytes(s);
		}

		internal static void WriteBytes(this Stream stream, byte[] bytes, int bufferLength)
		{
			using MemoryStream source = new MemoryStream(bytes);
			CopyTo(source, stream, bufferLength);
		}

		internal static void WriteBytesAsync(this Stream stream, byte[] bytes, int bufferLength, Action completed, Action<Exception> error)
		{
			MemoryStream input = new MemoryStream(bytes);
			input.CopyToAsync(stream, bufferLength, delegate
			{
				if (completed != null)
				{
					completed();
				}
				input.Dispose();
			}, delegate(Exception ex)
			{
				input.Dispose();
				if (error != null)
				{
					error(ex);
				}
			});
		}

		public static bool Contains(this string value, params char[] chars)
		{
			return chars == null || chars.Length == 0 || (value != null && value.Length != 0 && value.IndexOfAny(chars) > -1);
		}

		public static bool Contains(this NameValueCollection collection, string name)
		{
			return collection != null && collection.Count > 0 && collection[name] != null;
		}

		public static bool Contains(this NameValueCollection collection, string name, string value)
		{
			if (collection == null || collection.Count == 0)
			{
				return false;
			}
			string text = collection[name];
			if (text == null)
			{
				return false;
			}
			string[] array = text.Split(new char[1] { ',' });
			foreach (string text2 in array)
			{
				if (text2.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public static void Emit(this EventHandler eventHandler, object sender, EventArgs e)
		{
			eventHandler?.Invoke(sender, e);
		}

		public static void Emit<TEventArgs>(this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e) where TEventArgs : EventArgs
		{
			eventHandler?.Invoke(sender, e);
		}

		public static WebSocketSharp.Net.CookieCollection GetCookies(this NameValueCollection headers, bool response)
		{
			string name = (response ? "Set-Cookie" : "Cookie");
			return (headers != null && headers.Contains(name)) ? WebSocketSharp.Net.CookieCollection.Parse(headers[name], response) : new WebSocketSharp.Net.CookieCollection();
		}

		public static string GetDescription(this WebSocketSharp.Net.HttpStatusCode code)
		{
			return ((int)code).GetStatusDescription();
		}

		public static string GetStatusDescription(this int code)
		{
			return code switch
			{
				100 => "Continue", 
				101 => "Switching Protocols", 
				102 => "Processing", 
				200 => "OK", 
				201 => "Created", 
				202 => "Accepted", 
				203 => "Non-Authoritative Information", 
				204 => "No Content", 
				205 => "Reset Content", 
				206 => "Partial Content", 
				207 => "Multi-Status", 
				300 => "Multiple Choices", 
				301 => "Moved Permanently", 
				302 => "Found", 
				303 => "See Other", 
				304 => "Not Modified", 
				305 => "Use Proxy", 
				307 => "Temporary Redirect", 
				400 => "Bad Request", 
				401 => "Unauthorized", 
				402 => "Payment Required", 
				403 => "Forbidden", 
				404 => "Not Found", 
				405 => "Method Not Allowed", 
				406 => "Not Acceptable", 
				407 => "Proxy Authentication Required", 
				408 => "Request Timeout", 
				409 => "Conflict", 
				410 => "Gone", 
				411 => "Length Required", 
				412 => "Precondition Failed", 
				413 => "Request Entity Too Large", 
				414 => "Request-Uri Too Long", 
				415 => "Unsupported Media Type", 
				416 => "Requested Range Not Satisfiable", 
				417 => "Expectation Failed", 
				422 => "Unprocessable Entity", 
				423 => "Locked", 
				424 => "Failed Dependency", 
				500 => "Internal Server Error", 
				501 => "Not Implemented", 
				502 => "Bad Gateway", 
				503 => "Service Unavailable", 
				504 => "Gateway Timeout", 
				505 => "Http Version Not Supported", 
				507 => "Insufficient Storage", 
				_ => string.Empty, 
			};
		}

		public static bool IsCloseStatusCode(this ushort value)
		{
			return value > 999 && value < 5000;
		}

		public static bool IsEnclosedIn(this string value, char c)
		{
			return value != null && value.Length > 1 && value[0] == c && value[value.Length - 1] == c;
		}

		public static bool IsHostOrder(this ByteOrder order)
		{
			return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
		}

		public static bool IsLocal(this IPAddress address)
		{
			if (address == null)
			{
				return false;
			}
			if (address.Equals(IPAddress.Any))
			{
				return true;
			}
			if (address.Equals(IPAddress.Loopback))
			{
				return true;
			}
			if (Socket.OSSupportsIPv6)
			{
				if (address.Equals(IPAddress.IPv6Any))
				{
					return true;
				}
				if (address.Equals(IPAddress.IPv6Loopback))
				{
					return true;
				}
			}
			string hostName = Dns.GetHostName();
			IPAddress[] hostAddresses = Dns.GetHostAddresses(hostName);
			IPAddress[] array = hostAddresses;
			foreach (IPAddress obj in array)
			{
				if (address.Equals(obj))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsNullOrEmpty(this string value)
		{
			return value == null || value.Length == 0;
		}

		public static bool IsPredefinedScheme(this string value)
		{
			if (value == null || value.Length < 2)
			{
				return false;
			}
			char c = value[0];
			if (c == 'h')
			{
				return value == "http" || value == "https";
			}
			if (c == 'w')
			{
				return value == "ws" || value == "wss";
			}
			if (c == 'f')
			{
				return value == "file" || value == "ftp";
			}
			if (c == 'n')
			{
				c = value[1];
				return (c != 'e') ? (value == "nntp") : (value == "news" || value == "net.pipe" || value == "net.tcp");
			}
			return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto");
		}

		public static bool IsUpgradeTo(this WebSocketSharp.Net.HttpListenerRequest request, string protocol)
		{
			if (request == null)
			{
				throw new ArgumentNullException("request");
			}
			if (protocol == null)
			{
				throw new ArgumentNullException("protocol");
			}
			if (protocol.Length == 0)
			{
				throw new ArgumentException("An empty string.", "protocol");
			}
			return request.Headers.Contains("Upgrade", protocol) && request.Headers.Contains("Connection", "Upgrade");
		}

		public static bool MaybeUri(this string value)
		{
			if (value == null || value.Length == 0)
			{
				return false;
			}
			int num = value.IndexOf(':');
			if (num == -1)
			{
				return false;
			}
			if (num >= 10)
			{
				return false;
			}
			return value.Substring(0, num).IsPredefinedScheme();
		}

		public static T[] SubArray<T>(this T[] array, int startIndex, int length)
		{
			int num;
			if (array == null || (num = array.Length) == 0)
			{
				return new T[0];
			}
			if (startIndex < 0 || length <= 0 || startIndex + length > num)
			{
				return new T[0];
			}
			if (startIndex == 0 && length == num)
			{
				return array;
			}
			T[] array2 = new T[length];
			Array.Copy(array, startIndex, array2, 0, length);
			return array2;
		}

		public static T[] SubArray<T>(this T[] array, long startIndex, long length)
		{
			long longLength;
			if (array == null || (longLength = array.LongLength) == 0)
			{
				return new T[0];
			}
			if (startIndex < 0 || length <= 0 || startIndex + length > longLength)
			{
				return new T[0];
			}
			if (startIndex == 0 && length == longLength)
			{
				return array;
			}
			T[] array2 = new T[length];
			Array.Copy(array, startIndex, array2, 0L, length);
			return array2;
		}

		public static void Times(this int n, Action action)
		{
			if (n > 0 && action != null)
			{
				((ulong)n).times(action);
			}
		}

		public static void Times(this long n, Action action)
		{
			if (n > 0 && action != null)
			{
				((ulong)n).times(action);
			}
		}

		public static void Times(this uint n, Action action)
		{
			if (n != 0 && action != null)
			{
				times(n, action);
			}
		}

		public static void Times(this ulong n, Action action)
		{
			if (n != 0 && action != null)
			{
				n.times(action);
			}
		}

		public static void Times(this int n, Action<int> action)
		{
			if (n > 0 && action != null)
			{
				for (int i = 0; i < n; i++)
				{
					action(i);
				}
			}
		}

		public static void Times(this long n, Action<long> action)
		{
			if (n > 0 && action != null)
			{
				for (long num = 0L; num < n; num++)
				{
					action(num);
				}
			}
		}

		public static void Times(this uint n, Action<uint> action)
		{
			if (n != 0 && action != null)
			{
				for (uint num = 0u; num < n; num++)
				{
					action(num);
				}
			}
		}

		public static void Times(this ulong n, Action<ulong> action)
		{
			if (n != 0 && action != null)
			{
				for (ulong num = 0uL; num < n; num++)
				{
					action(num);
				}
			}
		}

		public static T To<T>(this byte[] source, ByteOrder sourceOrder) where T : struct
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			if (source.Length == 0)
			{
				return default(T);
			}
			Type typeFromHandle = typeof(T);
			byte[] value = source.ToHostOrder(sourceOrder);
			return ((object)typeFromHandle == typeof(bool)) ? ((T)(object)BitConverter.ToBoolean(value, 0)) : (((object)typeFromHandle == typeof(char)) ? ((T)(object)BitConverter.ToChar(value, 0)) : (((object)typeFromHandle == typeof(double)) ? ((T)(object)BitConverter.ToDouble(value, 0)) : (((object)typeFromHandle == typeof(short)) ? ((T)(object)BitConverter.ToInt16(value, 0)) : (((object)typeFromHandle == typeof(int)) ? ((T)(object)BitConverter.ToInt32(value, 0)) : (((object)typeFromHandle == typeof(long)) ? ((T)(object)BitConverter.ToInt64(value, 0)) : (((object)typeFromHandle == typeof(float)) ? ((T)(object)BitConverter.ToSingle(value, 0)) : (((object)typeFromHandle == typeof(ushort)) ? ((T)(object)BitConverter.ToUInt16(value, 0)) : (((object)typeFromHandle == typeof(uint)) ? ((T)(object)BitConverter.ToUInt32(value, 0)) : (((object)typeFromHandle == typeof(ulong)) ? ((T)(object)BitConverter.ToUInt64(value, 0)) : default(T))))))))));
		}

		public static byte[] ToByteArray<T>(this T value, ByteOrder order) where T : struct
		{
			Type typeFromHandle = typeof(T);
			byte[] array = (((object)typeFromHandle == typeof(bool)) ? BitConverter.GetBytes((bool)(object)value) : (((object)typeFromHandle == typeof(byte)) ? new byte[1] { (byte)(object)value } : (((object)typeFromHandle == typeof(char)) ? BitConverter.GetBytes((char)(object)value) : (((object)typeFromHandle == typeof(double)) ? BitConverter.GetBytes((double)(object)value) : (((object)typeFromHandle == typeof(short)) ? BitConverter.GetBytes((short)(object)value) : (((object)typeFromHandle == typeof(int)) ? BitConverter.GetBytes((int)(object)value) : (((object)typeFromHandle == typeof(long)) ? BitConverter.GetBytes((long)(object)value) : (((object)typeFromHandle == typeof(float)) ? BitConverter.GetBytes((float)(object)value) : (((object)typeFromHandle == typeof(ushort)) ? BitConverter.GetBytes((ushort)(object)value) : (((object)typeFromHandle == typeof(uint)) ? BitConverter.GetBytes((uint)(object)value) : (((object)typeFromHandle == typeof(ulong)) ? BitConverter.GetBytes((ulong)(object)value) : WebSocket.EmptyBytes)))))))))));
			if (array.Length > 1 && !order.IsHostOrder())
			{
				Array.Reverse((Array)array);
			}
			return array;
		}

		public static byte[] ToHostOrder(this byte[] source, ByteOrder sourceOrder)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			return (source.Length > 1 && !sourceOrder.IsHostOrder()) ? source.Reverse() : source;
		}

		public static string ToString<T>(this T[] array, string separator)
		{
			if (array == null)
			{
				throw new ArgumentNullException("array");
			}
			int num = array.Length;
			if (num == 0)
			{
				return string.Empty;
			}
			if (separator == null)
			{
				separator = string.Empty;
			}
			StringBuilder buff = new StringBuilder(64);
			(num - 1).Times(delegate(int i)
			{
				buff.AppendFormat("{0}{1}", array[i].ToString(), separator);
			});
			buff.Append(array[num - 1].ToString());
			return buff.ToString();
		}

		public static Uri ToUri(this string uriString)
		{
			Uri.TryCreate(uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out Uri result);
			return result;
		}

		public static string UrlDecode(this string value)
		{
			return (value != null && value.Length > 0) ? HttpUtility.UrlDecode(value) : value;
		}

		public static string UrlEncode(this string value)
		{
			return (value != null && value.Length > 0) ? HttpUtility.UrlEncode(value) : value;
		}

		public static void WriteContent(this WebSocketSharp.Net.HttpListenerResponse response, byte[] content)
		{
			if (response == null)
			{
				throw new ArgumentNullException("response");
			}
			if (content == null)
			{
				throw new ArgumentNullException("content");
			}
			long longLength = content.LongLength;
			if (longLength == 0)
			{
				response.Close();
				return;
			}
			response.ContentLength64 = longLength;
			Stream outputStream = response.OutputStream;
			if (longLength <= int.MaxValue)
			{
				outputStream.Write(content, 0, (int)longLength);
			}
			else
			{
				outputStream.WriteBytes(content, 1024);
			}
			outputStream.Close();
		}

		static Ext()
		{
			byte[] last = new byte[1];
			_last = last;
			_retry = 5;
		}
	}
	public class MessageEventArgs : EventArgs
	{
		private string _data;

		private bool _dataSet;

		private Opcode _opcode;

		private byte[] _rawData;

		public string Data
		{
			get
			{
				if (!_dataSet)
				{
					_data = ((_opcode != Opcode.Binary) ? _rawData.UTF8Decode() : BitConverter.ToString(_rawData));
					_dataSet = true;
				}
				return _data;
			}
		}

		public bool IsBinary => _opcode == Opcode.Binary;

		public bool IsPing => _opcode == Opcode.Ping;

		public bool IsText => _opcode == Opcode.Text;

		public byte[] RawData => _rawData;

		[Obsolete("This property will be removed. Use any of the Is properties instead.")]
		public Opcode Type => _opcode;

		internal MessageEventArgs(WebSocketFrame frame)
		{
			_opcode = frame.Opcode;
			_rawData = frame.PayloadData.ApplicationData;
		}

		internal MessageEventArgs(Opcode opcode, byte[] rawData)
		{
			if ((ulong)rawData.LongLength > PayloadData.MaxLength)
			{
				throw new WebSocketException(CloseStatusCode.TooBig);
			}
			_opcode = opcode;
			_rawData = rawData;
		}
	}
	public class CloseEventArgs : EventArgs
	{
		private bool _clean;

		private ushort _code;

		private PayloadData _payloadData;

		private string _reason;

		internal PayloadData PayloadData => _payloadData ?? (_payloadData = new PayloadData(_code.Append(_reason)));

		public ushort Code => _code;

		public string Reason => _reason ?? string.Empty;

		public bool WasClean
		{
			get
			{
				return _clean;
			}
			internal set
			{
				_clean = value;
			}
		}

		internal CloseEventArgs()
		{
			_code = 1005;
			_payloadData = PayloadData.Empty;
		}

		internal CloseEventArgs(ushort code)
		{
			_code = code;
		}

		internal CloseEventArgs(CloseStatusCode code)
			: this((ushort)code)
		{
		}

		internal CloseEventArgs(PayloadData payloadData)
		{
			_payloadData = payloadData;
			byte[] applicationData = payloadData.ApplicationData;
			int num = applicationData.Length;
			_code = (ushort)((num > 1) ? applicationData.SubArray(0, 2).ToUInt16(ByteOrder.Big) : 1005);
			_reason = ((num > 2) ? applicationData.SubArray(2, num - 2).UTF8Decode() : string.Empty);
		}

		internal CloseEventArgs(ushort code, string reason)
		{
			_code = code;
			_reason = reason;
		}

		internal CloseEventArgs(CloseStatusCode code, string reason)
			: this((ushort)code, reason)
		{
		}
	}
	public enum ByteOrder
	{
		Little,
		Big
	}
	public class ErrorEventArgs : EventArgs
	{
		private Exception _exception;

		private string _message;

		public Exception Exception => _exception;

		public string Message => _message;

		internal ErrorEventArgs(string message)
			: this(message, null)
		{
		}

		internal ErrorEventArgs(string message, Exception exception)
		{
			_message = message;
			_exception = exception;
		}
	}
	public class WebSocket : IDisposable
	{
		private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

		private const string _version = "13";

		private AuthenticationChallenge _authChallenge;

		private string _base64Key;

		private bool _client;

		private Action _closeContext;

		private CompressionMethod _compression;

		private WebSocketContext _context;

		private WebSocketSharp.Net.CookieCollection _cookies;

		private WebSocketSharp.Net.NetworkCredential _credentials;

		private bool _emitOnPing;

		private bool _enableRedirection;

		private AutoResetEvent _exitReceiving;

		private string _extensions;

		private bool _extensionsRequested;

		private object _forConn;

		private object _forMessageEventQueue;

		private object _forSend;

		private MemoryStream _fragmentsBuffer;

		private bool _fragmentsCompressed;

		private Opcode _fragmentsOpcode;

		private Func<WebSocketContext, string> _handshakeRequestChecker;

		private bool _ignoreExtensions;

		private bool _inContinuation;

		private volatile bool _inMessage;

		private volatile Logger _logger;

		private Action<MessageEventArgs> _message;

		private Queue<MessageEventArgs> _messageEventQueue;

		private uint _nonceCount;

		private string _origin;

		private bool _preAuth;

		private string _protocol;

		private string[] _protocols;

		private bool _protocolsRequested;

		private WebSocketSharp.Net.NetworkCredential _proxyCredentials;

		private Uri _proxyUri;

		private volatile WebSocketState _readyState;

		private AutoResetEvent _receivePong;

		private bool _secure;

		private ClientSslConfiguration _sslConfig;

		private Stream _stream;

		private TcpClient _tcpClient;

		private Uri _uri;

		private TimeSpan _waitTime;

		internal static readonly byte[] EmptyBytes;

		internal static readonly int FragmentLength;

		internal static readonly RandomNumberGenerator RandomNumber;

		internal WebSocketSharp.Net.CookieCollection CookieCollection => _cookies;

		internal Func<WebSocketContext, string> CustomHandshakeRequestChecker
		{
			get
			{
				return _handshakeRequestChecker;
			}
			set
			{
				_handshakeRequestChecker = value;
			}
		}

		internal bool HasMessage
		{
			get
			{
				lock (_forMessageEventQueue)
				{
					return _messageEventQueue.Count > 0;
				}
			}
		}

		internal bool IgnoreExtensions
		{
			get
			{
				return _ignoreExtensions;
			}
			set
			{
				_ignoreExtensions = value;
			}
		}

		internal bool IsConnected => _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing;

		public CompressionMethod Compression
		{
			get
			{
				return _compression;
			}
			set
			{
				lock (_forConn)
				{
					if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
					{
						_logger.Error(text);
						error("An error has occurred in setting the compression.", null);
					}
					else
					{
						_compression = value;
					}
				}
			}
		}

		public IEnumerable<WebSocketSharp.Net.Cookie> Cookies
		{
			get
			{
				object syncRoot;
				object obj = (syncRoot = _cookies.SyncRoot);
				Monitor.Enter(syncRoot);
				try
				{
					foreach (WebSocketSharp.Net.Cookie cookie in _cookies)
					{
						yield return cookie;
					}
				}
				finally
				{
					Monitor.Exit(obj);
				}
			}
		}

		public WebSocketSharp.Net.NetworkCredential Credentials => _credentials;

		public bool EmitOnPing
		{
			get
			{
				return _emitOnPing;
			}
			set
			{
				_emitOnPing = value;
			}
		}

		public bool EnableRedirection
		{
			get
			{
				return _enableRedirection;
			}
			set
			{
				lock (_forConn)
				{
					if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
					{
						_logger.Error(text);
						error("An error has occurred in setting the enable redirection.", null);
					}
					else
					{
						_enableRedirection = value;
					}
				}
			}
		}

		public string Extensions => _extensions ?? string.Empty;

		public bool IsAlive => Ping();

		public bool IsSecure => _secure;

		public Logger Log
		{
			get
			{
				return _logger;
			}
			internal set
			{
				_logger = value;
			}
		}

		public string Origin
		{
			get
			{
				return _origin;
			}
			set
			{
				lock (_forConn)
				{
					Uri result;
					if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
					{
						_logger.Error(text);
						error("An error has occurred in setting the origin.", null);
					}
					else if (value.IsNullOrEmpty())
					{
						_origin = value;
					}
					else if (!Uri.TryCreate(value, UriKind.Absolute, out result) || result.Segments.Length > 1)
					{
						_logger.Error("The syntax of an origin must be '<scheme>://<host>[:<port>]'.");
						error("An error has occurred in setting the origin.", null);
					}
					else
					{
						_origin = value.TrimEnd(new char[1] { '/' });
					}
				}
			}
		}

		public string Protocol
		{
			get
			{
				return _protocol ?? string.Empty;
			}
			internal set
			{
				_protocol = value;
			}
		}

		public WebSocketState ReadyState => _readyState;

		public ClientSslConfiguration SslConfiguration
		{
			get
			{
				return _client ? (_sslConfig ?? (_sslConfig = new ClientSslConfiguration(_uri.DnsSafeHost))) : null;
			}
			set
			{
				lock (_forConn)
				{
					if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
					{
						_logger.Error(text);
						error("An error has occurred in setting the ssl configuration.", null);
					}
					else
					{
						_sslConfig = value;
					}
				}
			}
		}

		public Uri Url => _client ? _uri : _context.RequestUri;

		public TimeSpan WaitTime
		{
			get
			{
				return _waitTime;
			}
			set
			{
				lock (_forConn)
				{
					if (!checkIfAvailable(client: true, server: true, connecting: true, open: false, closing: false, closed: true, out var text) || !value.CheckWaitTime(out text))
					{
						_logger.Error(text);
						error("An error has occurred in setting the wait time.", null);
					}
					else
					{
						_waitTime = value;
					}
				}
			}
		}

		public event EventHandler<CloseEventArgs> OnClose;

		public event EventHandler<ErrorEventArgs> OnError;

		public event EventHandler<MessageEventArgs> OnMessage;

		public event EventHandler OnOpen;

		static WebSocket()
		{
			EmptyBytes = new byte[0];
			FragmentLength = 1016;
			RandomNumber = new RNGCryptoServiceProvider();
		}

		internal WebSocket(HttpListenerWebSocketContext context, string protocol)
		{
			_context = context;
			_protocol = protocol;
			_closeContext = context.Close;
			_logger = context.Log;
			_message = messages;
			_secure = context.IsSecureConnection;
			_stream = context.Stream;
			_waitTime = TimeSpan.FromSeconds(1.0);
			init();
		}

		internal WebSocket(TcpListenerWebSocketContext context, string protocol)
		{
			_context = context;
			_protocol = protocol;
			_closeContext = context.Close;
			_logger = context.Log;
			_message = messages;
			_secure = context.IsSecureConnection;
			_stream = context.Stream;
			_waitTime = TimeSpan.FromSeconds(1.0);
			init();
		}

		public WebSocket(string url, params string[] protocols)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}
			if (url.Length == 0)
			{
				throw new ArgumentException("An empty string.", "url");
			}
			if (!url.TryCreateWebSocketUri(out _uri, out var text))
			{
				throw new ArgumentException(text, "url");
			}
			if (protocols != null && protocols.Length > 0)
			{
				text = protocols.CheckIfValidProtocols();
				if (text != null)
				{
					throw new ArgumentException(text, "protocols");
				}
				_protocols = protocols;
			}
			_base64Key = CreateBase64Key();
			_client = true;
			_logger = new Logger();
			_message = messagec;
			_secure = _uri.Scheme == "wss";
			_waitTime = TimeSpan.FromSeconds(5.0);
			init();
		}

		private bool accept()
		{
			lock (_forConn)
			{
				if (!checkIfAvailable(connecting: true, open: false, closing: false, closed: false, out var text))
				{
					_logger.Error(text);
					error("An error has occurred in accepting.", null);
					return false;
				}
				try
				{
					if (!acceptHandshake())
					{
						return false;
					}
					_readyState = WebSocketState.Open;
				}
				catch (Exception ex)
				{
					_logger.Fatal(ex.ToString());
					fatal("An exception has occurred while accepting.", ex);
					return false;
				}
				return true;
			}
		}

		private bool acceptHandshake()
		{
			_logger.Debug($"A request from {_context.UserEndPoint}:\n{_context}");
			if (!checkHandshakeRequest(_context, out var text))
			{
				sendHttpResponse(createHandshakeFailureResponse(WebSocketSharp.Net.HttpStatusCode.BadRequest));
				_logger.Fatal(text);
				fatal("An error has occurred while accepting.", CloseStatusCode.ProtocolError);
				return false;
			}
			if (!customCheckHandshakeRequest(_context, out text))
			{
				sendHttpResponse(createHandshakeFailureResponse(WebSocketSharp.Net.HttpStatusCode.BadRequest));
				_logger.Fatal(text);
				fatal("An error has occurred while accepting.", CloseStatusCode.PolicyViolation);
				return false;
			}
			_base64Key = _context.Headers["Sec-WebSocket-Key"];
			if (_protocol != null)
			{
				processSecWebSocketProtocolHeader(_context.SecWebSocketProtocols);
			}
			if (!_ignoreExtensions)
			{
				processSecWebSocketExtensionsClientHeader(_context.Headers["Sec-WebSocket-Extensions"]);
			}
			return sendHttpResponse(createHandshakeResponse());
		}

		private bool checkHandshakeRequest(WebSocketContext context, out string message)
		{
			message = null;
			if (context.RequestUri == null)
			{
				message = "Specifies an invalid Request-URI.";
				return false;
			}
			if (!context.IsWebSocketRequest)
			{
				message = "Not a WebSocket handshake request.";
				return false;
			}
			NameValueCollection headers = context.Headers;
			if (!validateSecWebSocketKeyHeader(headers["Sec-WebSocket-Key"]))
			{
				message = "Includes no Sec-WebSocket-Key header, or it has an invalid value.";
				return false;
			}
			if (!validateSecWebSocketVersionClientHeader(headers["Sec-WebSocket-Version"]))
			{
				message = "Includes no Sec-WebSocket-Version header, or it has an invalid value.";
				return false;
			}
			if (!validateSecWebSocketProtocolClientHeader(headers["Sec-WebSocket-Protocol"]))
			{
				message = "Includes an invalid Sec-WebSocket-Protocol header.";
				return false;
			}
			if (!_ignoreExtensions && !validateSecWebSocketExtensionsClientHeader(headers["Sec-WebSocket-Extensions"]))
			{
				message = "Includes an invalid Sec-WebSocket-Extensions header.";
				return false;
			}
			return true;
		}

		private bool checkHandshakeResponse(HttpResponse response, out string message)
		{
			message = null;
			if (response.IsRedirect)
			{
				message = "Indicates the redirection.";
				return false;
			}
			if (response.IsUnauthorized)
			{
				message = "Requires the authentication.";
				return false;
			}
			if (!response.IsWebSocketResponse)
			{
				message = "Not a WebSocket handshake response.";
				return false;
			}
			NameValueCollection headers = response.Headers;
			if (!validateSecWebSocketAcceptHeader(headers["Sec-WebSocket-Accept"]))
			{
				message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value.";
				return false;
			}
			if (!validateSecWebSocketProtocolServerHeader(headers["Sec-WebSocket-Protocol"]))
			{
				message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value.";
				return false;
			}
			if (!validateSecWebSocketExtensionsServerHeader(headers["Sec-WebSocket-Extensions"]))
			{
				message = "Includes an invalid Sec-WebSocket-Extensions header.";
				return false;
			}
			if (!validateSecWebSocketVersionServerHeader(headers["Sec-WebSocket-Version"]))
			{
				message = "Includes an invalid Sec-WebSocket-Version header.";
				return false;
			}
			return true;
		}

		private bool checkIfAvailable(bool connecting, bool open, bool closing, bool closed, out string message)
		{
			message = null;
			if (!connecting && _readyState == WebSocketState.Connecting)
			{
				message = "This operation isn't available in: connecting";
				return false;
			}
			if (!open && _readyState == WebSocketState.Open)
			{
				message = "This operation isn't available in: open";
				return false;
			}
			if (!closing && _readyState == WebSocketState.Closing)
			{
				message = "This operation isn't available in: closing";
				return false;
			}
			if (!closed && _readyState == WebSocketState.Closed)
			{
				message = "This operation isn't available in: closed";
				return false;
			}
			return true;
		}

		private bool checkIfAvailable(bool client, bool server, bool connecting, bool open, bool closing, bool closed, out string message)
		{
			message = null;
			if (!client && _client)
			{
				message = "This operation isn't available in: client";
				return false;
			}
			if (!server && !_client)
			{
				message = "This operation isn't available in: server";
				return false;
			}
			return checkIfAvailable(connecting, open, closing, closed, out message);
		}

		private bool checkReceivedFrame(WebSocketFrame frame, out string message)
		{
			message = null;
			bool isMasked = frame.IsMasked;
			if (_client && isMasked)
			{
				message = "A frame from the server is masked.";
				return false;
			}
			if (!_client && !isMasked)
			{
				message = "A frame from a client isn't masked.";
				return false;
			}
			if (_inContinuation && frame.IsData)
			{
				message = "A data frame has been received while receiving continuation frames.";
				return false;
			}
			if (frame.IsCompressed && _compression == CompressionMethod.None)
			{
				message = "A compressed frame has been received without any agreement for it.";
				return false;
			}
			if (frame.Rsv2 == Rsv.On)
			{
				message = "The RSV2 of a frame is non-zero without any negotiation for it.";
				return false;
			}
			if (frame.Rsv3 == Rsv.On)
			{
				message = "The RSV3 of a frame is non-zero without any negotiation for it.";
				return false;
			}
			return true;
		}

		private void close(CloseEventArgs e, bool send, bool receive, bool received)
		{
			lock (_forConn)
			{
				if (_readyState == WebSocketState.Closing)
				{
					_logger.Info("The closing is already in progress.");
					return;
				}
				if (_readyState == WebSocketState.Closed)
				{
					_logger.Info("The connection has been closed.");
					return;
				}
				send = send && _readyState == WebSocketState.Open;
				receive = receive && send;
				_readyState = WebSocketState.Closing;
			}
			_logger.Trace("Begin closing the connection.");
			byte[] frameAsBytes = (send ? WebSocketFrame.CreateCloseFrame(e.PayloadData, _client).ToArray() : null);
			e.WasClean = closeHandshake(frameAsBytes, receive, received);
			releaseResources();
			_logger.Trace("End closing the connection.");
			_readyState = WebSocketState.Closed;
			try
			{
				this.OnClose.Emit(this, e);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
				error("An exception has occurred during the OnClose event.", ex);
			}
		}

		private void closeAsync(CloseEventArgs e, bool send, bool receive, bool received)
		{
			Action<CloseEventArgs, bool, bool, bool> closer = close;
			closer.BeginInvoke(e, send, receive, received, delegate(IAsyncResult ar)
			{
				closer.EndInvoke(ar);
			}, null);
		}

		private bool closeHandshake(byte[] frameAsBytes, bool receive, bool received)
		{
			bool flag = frameAsBytes != null && sendBytes(frameAsBytes);
			received = received || (receive && flag && _exitReceiving != null && _exitReceiving.WaitOne(_waitTime));
			bool flag2 = flag && received;
			_logger.Debug($"Was clean?: {flag2}\n  sent: {flag}\n  received: {received}");
			return flag2;
		}

		private bool connect()
		{
			lock (_forConn)
			{
				if (!checkIfAvailable(connecting: true, open: false, closing: false, closed: true, out var text))
				{
					_logger.Error(text);
					error("An error has occurred in connecting.", null);
					return false;
				}
				try
				{
					_readyState = WebSocketState.Connecting;
					if (!doHandshake())
					{
						return false;
					}
					_readyState = WebSocketState.Open;
				}
				catch (Exception ex)
				{
					_logger.Fatal(ex.ToString());
					fatal("An exception has occurred while connecting.", ex);
					return false;
				}
				return true;
			}
		}

		private string createExtensions()
		{
			StringBuilder stringBuilder = new StringBuilder(80);
			if (_compression != 0)
			{
				string arg = _compression.ToExtensionString("server_no_context_takeover", "client_no_context_takeover");
				stringBuilder.AppendFormat("{0}, ", arg);
			}
			int length = stringBuilder.Length;
			if (length > 2)
			{
				stringBuilder.Length = length - 2;
				return stringBuilder.ToString();
			}
			return null;
		}

		private HttpResponse createHandshakeFailureResponse(WebSocketSharp.Net.HttpStatusCode code)
		{
			HttpResponse httpResponse = HttpResponse.CreateCloseResponse(code);
			httpResponse.Headers["Sec-WebSocket-Version"] = "13";
			return httpResponse;
		}

		private HttpRequest createHandshakeRequest()
		{
			HttpRequest httpRequest = HttpRequest.CreateWebSocketRequest(_uri);
			NameValueCollection headers = httpRequest.Headers;
			if (!_origin.IsNullOrEmpty())
			{
				headers["Origin"] = _origin;
			}
			headers["Sec-WebSocket-Key"] = _base64Key;
			_protocolsRequested = _protocols != null;
			if (_protocolsRequested)
			{
				headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", ");
			}
			_extensionsRequested = _compression != CompressionMethod.None;
			if (_extensionsRequested)
			{
				headers["Sec-WebSocket-Extensions"] = createExtensions();
			}
			headers["Sec-WebSocket-Version"] = "13";
			AuthenticationResponse authenticationResponse = null;
			if (_authChallenge != null && _credentials != null)
			{
				authenticationResponse = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount);
				_nonceCount = authenticationResponse.NonceCount;
			}
			else if (_preAuth)
			{
				authenticationResponse = new AuthenticationResponse(_credentials);
			}
			if (authenticationResponse != null)
			{
				headers["Authorization"] = authenticationResponse.ToString();
			}
			if (_cookies.Count > 0)
			{
				httpRequest.SetCookies(_cookies);
			}
			return httpRequest;
		}

		private HttpResponse createHandshakeResponse()
		{
			HttpResponse httpResponse = HttpResponse.CreateWebSocketResponse();
			NameValueCollection headers = httpResponse.Headers;
			headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);
			if (_protocol != null)
			{
				headers["Sec-WebSocket-Protocol"] = _protocol;
			}
			if (_extensions != null)
			{
				headers["Sec-WebSocket-Extensions"] = _extensions;
			}
			if (_cookies.Count > 0)
			{
				httpResponse.SetCookies(_cookies);
			}
			return httpResponse;
		}

		private bool customCheckHandshakeRequest(WebSocketContext context, out string message)
		{
			message = null;
			return _handshakeRequestChecker == null || (message = _handshakeRequestChecker(context)) == null;
		}

		private MessageEventArgs dequeueFromMessageEventQueue()
		{
			lock (_forMessageEventQueue)
			{
				return (_messageEventQueue.Count > 0) ? _messageEventQueue.Dequeue() : null;
			}
		}

		private bool doHandshake()
		{
			setClientStream();
			HttpResponse httpResponse = sendHandshakeRequest();
			if (!checkHandshakeResponse(httpResponse, out var text))
			{
				_logger.Fatal(text);
				fatal("An error has occurred while connecting.", CloseStatusCode.ProtocolError);
				return false;
			}
			if (_protocolsRequested)
			{
				_protocol = httpResponse.Headers["Sec-WebSocket-Protocol"];
			}
			if (_extensionsRequested)
			{
				processSecWebSocketExtensionsServerHeader(httpResponse.Headers["Sec-WebSocket-Extensions"]);
			}
			processCookies(httpResponse.Cookies);
			return true;
		}

		private void enqueueToMessageEventQueue(MessageEventArgs e)
		{
			lock (_forMessageEventQueue)
			{
				_messageEventQueue.Enqueue(e);
			}
		}

		private void error(string message, Exception exception)
		{
			try
			{
				this.OnError.Emit(this, new ErrorEventArgs(message, exception));
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
			}
		}

		private void fatal(string message, Exception exception)
		{
			CloseStatusCode code = ((exception is WebSocketException) ? ((WebSocketException)exception).Code : CloseStatusCode.Abnormal);
			fatal(message, code);
		}

		private void fatal(string message, CloseStatusCode code)
		{
			close(new CloseEventArgs(code, message), !code.IsReserved(), receive: false, received: false);
		}

		private void init()
		{
			_compression = CompressionMethod.None;
			_cookies = new WebSocketSharp.Net.CookieCollection();
			_forConn = new object();
			_forSend = new object();
			_messageEventQueue = new Queue<MessageEventArgs>();
			_forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
			_readyState = WebSocketState.Connecting;
		}

		private void message()
		{
			MessageEventArgs obj = null;
			lock (_forMessageEventQueue)
			{
				if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
				{
					return;
				}
				_inMessage = true;
				obj = _messageEventQueue.Dequeue();
			}
			_message(obj);
		}

		private void messagec(MessageEventArgs e)
		{
			while (true)
			{
				try
				{
					this.OnMessage.Emit(this, e);
				}
				catch (Exception ex)
				{
					_logger.Error(ex.ToString());
					error("An exception has occurred during an OnMessage event.", ex);
				}
				lock (_forMessageEventQueue)
				{
					if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
					{
						_inMessage = false;
						break;
					}
					e = _messageEventQueue.Dequeue();
				}
				bool flag = true;
			}
		}

		private void messages(MessageEventArgs e)
		{
			try
			{
				this.OnMessage.Emit(this, e);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
				error("An exception has occurred during an OnMessage event.", ex);
			}
			lock (_forMessageEventQueue)
			{
				if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
				{
					_inMessage = false;
					return;
				}
				e = _messageEventQueue.Dequeue();
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				messages(e);
			});
		}

		private void open()
		{
			_inMessage = true;
			startReceiving();
			try
			{
				this.OnOpen.Emit(this, EventArgs.Empty);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
				error("An exception has occurred during the OnOpen event.", ex);
			}
			MessageEventArgs obj = null;
			lock (_forMessageEventQueue)
			{
				if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
				{
					_inMessage = false;
					return;
				}
				obj = _messageEventQueue.Dequeue();
			}
			_message.BeginInvoke(obj, delegate(IAsyncResult ar)
			{
				_message.EndInvoke(ar);
			}, null);
		}

		private bool processCloseFrame(WebSocketFrame frame)
		{
			PayloadData payloadData = frame.PayloadData;
			close(new CloseEventArgs(payloadData), !payloadData.IncludesReservedCloseStatusCode, receive: false, received: true);
			return false;
		}

		private void processCookies(WebSocketSharp.Net.CookieCollection cookies)
		{
			if (cookies.Count != 0)
			{
				_cookies.SetOrRemove(cookies);
			}
		}

		private bool processDataFrame(WebSocketFrame frame)
		{
			enqueueToMessageEventQueue(frame.IsCompressed ? new MessageEventArgs(frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression)) : new MessageEventArgs(frame));
			return true;
		}

		private bool processFragmentFrame(WebSocketFrame frame)
		{
			if (!_inContinuation)
			{
				if (frame.IsContinuation)
				{
					return true;
				}
				_fragmentsOpcode = frame.Opcode;
				_fragmentsCompressed = frame.IsCompressed;
				_fragmentsBuffer = new MemoryStream();
				_inContinuation = true;
			}
			_fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024);
			if (frame.IsFinal)
			{
				using (_fragmentsBuffer)
				{
					byte[] rawData = (_fragmentsCompressed ? _fragmentsBuffer.DecompressToArray(_compression) : _fragmentsBuffer.ToArray());
					enqueueToMessageEventQueue(new MessageEventArgs(_fragmentsOpcode, rawData));
				}
				_fragmentsBuffer = null;
				_inContinuation = false;
			}
			return true;
		}

		private bool processPingFrame(WebSocketFrame frame)
		{
			if (send(new WebSocketFrame(Opcode.Pong, frame.PayloadData, _client).ToArray()))
			{
				_logger.Trace("Returned a pong.");
			}
			if (_emitOnPing)
			{
				enqueueToMessageEventQueue(new MessageEventArgs(frame));
			}
			return true;
		}

		private bool processPongFrame(WebSocketFrame frame)
		{
			_receivePong.Set();
			_logger.Trace("Received a pong.");
			return true;
		}

		private bool processReceivedFrame(WebSocketFrame frame)
		{
			if (!checkReceivedFrame(frame, out var text))
			{
				throw new WebSocketException(CloseStatusCode.ProtocolError, text);
			}
			frame.Unmask();
			return frame.IsFragment ? processFragmentFrame(frame) : (frame.IsData ? processDataFrame(frame) : (frame.IsPing ? processPingFrame(frame) : (frame.IsPong ? processPongFrame(frame) : (frame.IsClose ? processCloseFrame(frame) : processUnsupportedFrame(frame)))));
		}

		private void processSecWebSocketExtensionsClientHeader(string value)
		{
			if (value == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder(80);
			bool flag = false;
			foreach (string item in value.SplitHeaderValue(','))
			{
				string value2 = item.Trim();
				if (!flag && value2.IsCompressionExtension(CompressionMethod.Deflate))
				{
					_compression = CompressionMethod.Deflate;
					stringBuilder.AppendFormat("{0}, ", _compression.ToExtensionString("client_no_context_takeover", "server_no_context_takeover"));
					flag = true;
				}
			}
			int length = stringBuilder.Length;
			if (length > 2)
			{
				stringBuilder.Length = length - 2;
				_extensions = stringBuilder.ToString();
			}
		}

		private void processSecWebSocketExtensionsServerHeader(string value)
		{
			if (value == null)
			{
				_compression = CompressionMethod.None;
			}
			else
			{
				_extensions = value;
			}
		}

		private void processSecWebSocketProtocolHeader(IEnumerable<string> values)
		{
			if (!values.Contains((string p) => p == _protocol))
			{
				_protocol = null;
			}
		}

		private bool processUnsupportedFrame(WebSocketFrame frame)
		{
			_logger.Fatal("An unsupported frame:" + frame.PrintToString(dumped: false));
			fatal("There is no way to handle it.", CloseStatusCode.PolicyViolation);
			return false;
		}

		private void releaseClientResources()
		{
			if (_stream != null)
			{
				_stream.Dispose();
				_stream = null;
			}
			if (_tcpClient != null)
			{
				_tcpClient.Close();
				_tcpClient = null;
			}
		}

		private void releaseCommonResources()
		{
			if (_fragmentsBuffer != null)
			{
				_fragmentsBuffer.Dispose();
				_fragmentsBuffer = null;
				_inContinuation = false;
			}
			if (_receivePong != null)
			{
				_receivePong.Close();
				_receivePong = null;
			}
			if (_exitReceiving != null)
			{
				_exitReceiving.Close();
				_exitReceiving = null;
			}
		}

		private void releaseResources()
		{
			if (_client)
			{
				releaseClientResources();
			}
			else
			{
				releaseServerResources();
			}
			releaseCommonResources();
		}

		private void releaseServerResources()
		{
			if (_closeContext != null)
			{
				_closeContext();
				_closeContext = null;
				_stream = null;
				_context = null;
			}
		}

		private bool send(byte[] frameAsBytes)
		{
			lock (_forConn)
			{
				if (_readyState != WebSocketState.Open)
				{
					_logger.Error("The sending has been interrupted.");
					return false;
				}
				return sendBytes(frameAsBytes);
			}
		}

		private bool send(Opcode opcode, Stream stream)
		{
			lock (_forSend)
			{
				Stream stream2 = stream;
				bool flag = false;
				bool flag2 = false;
				try
				{
					if (_compression != 0)
					{
						stream = stream.Compress(_compression);
						flag = true;
					}
					flag2 = send(opcode, stream, flag);
					if (!flag2)
					{
						error("The sending has been interrupted.", null);
					}
				}
				catch (Exception ex)
				{
					_logger.Error(ex.ToString());
					error("An exception has occurred while sending data.", ex);
				}
				finally
				{
					if (flag)
					{
						stream.Dispose();
					}
					stream2.Dispose();
				}
				return flag2;
			}
		}

		private bool send(Opcode opcode, Stream stream, bool compressed)
		{
			long length = stream.Length;
			if (length == 0)
			{
				return send(Fin.Final, opcode, EmptyBytes, compressed);
			}
			long num = length / FragmentLength;
			int num2 = (int)(length % FragmentLength);
			byte[] array = null;
			if (num == 0)
			{
				array = new byte[num2];
				return stream.Read(array, 0, num2) == num2 && send(Fin.Final, opcode, array, compressed);
			}
			array = new byte[FragmentLength];
			if (num == 1 && num2 == 0)
			{
				return stream.Read(array, 0, FragmentLength) == FragmentLength && send(Fin.Final, opcode, array, compressed);
			}
			if (stream.Read(array, 0, FragmentLength) != FragmentLength || !send(Fin.More, opcode, array, compressed))
			{
				return false;
			}
			long num3 = ((num2 == 0) ? (num - 2) : (num - 1));
			for (long num4 = 0L; num4 < num3; num4++)
			{
				if (stream.Read(array, 0, FragmentLength) != FragmentLength || !send(Fin.More, Opcode.Cont, array, compressed))
				{
					return false;
				}
			}
			if (num2 == 0)
			{
				num2 = FragmentLength;
			}
			else
			{
				array = new byte[num2];
			}
			return stream.Read(array, 0, num2) == num2 && send(Fin.Final, Opcode.Cont, array, compressed);
		}

		private bool send(Fin fin, Opcode opcode, byte[] data, bool compressed)
		{
			lock (_forConn)
			{
				if (_readyState != WebSocketState.Open)
				{
					_logger.Error("The sending has been interrupted.");
					return false;
				}
				return sendBytes(new WebSocketFrame(fin, opcode, data, compressed, _client).ToArray());
			}
		}

		private void sendAsync(Opcode opcode, Stream stream, Action<bool> completed)
		{
			Func<Opcode, Stream, bool> sender = send;
			sender.BeginInvoke(opcode, stream, delegate(IAsyncResult ar)
			{
				try
				{
					bool obj = sender.EndInvoke(ar);
					if (completed != null)
					{
						completed(obj);
					}
				}
				catch (Exception ex)
				{
					_logger.Error(ex.ToString());
					error("An exception has occurred during a send callback.", ex);
				}
			}, null);
		}

		private bool sendBytes(byte[] bytes)
		{
			try
			{
				_stream.Write(bytes, 0, bytes.Length);
				return true;
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
				return false;
			}
		}

		private HttpResponse sendHandshakeRequest()
		{
			HttpRequest httpRequest = createHandshakeRequest();
			HttpResponse httpResponse = sendHttpRequest(httpRequest, 90000);
			if (httpResponse.IsUnauthorized)
			{
				string text = httpResponse.Headers["WWW-Authenticate"];
				_logger.Warn($"Received an authentication requirement for '{text}'.");
				if (text.IsNullOrEmpty())
				{
					_logger.Error("No authentication challenge is specified.");
					return httpResponse;
				}
				_authChallenge = AuthenticationChallenge.Parse(text);
				if (_authChallenge == null)
				{
					_logger.Error("An invalid authentication challenge is specified.");
					return httpResponse;
				}
				if (_credentials != null && (!_preAuth || _authChallenge.Scheme == WebSocketSharp.Net.AuthenticationSchemes.Digest))
				{
					if (httpResponse.HasConnectionClose)
					{
						releaseClientResources();
						setClientStream();
					}
					AuthenticationResponse authenticationResponse = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount);
					_nonceCount = authenticationResponse.NonceCount;
					httpRequest.Headers["Authorization"] = authenticationResponse.ToString();
					httpResponse = sendHttpRequest(httpRequest, 15000);
				}
			}
			if (httpResponse.IsRedirect)
			{
				string text2 = httpResponse.Headers["Location"];
				_logger.Warn($"Received a redirection to '{text2}'.");
				if (_enableRedirection)
				{
					if (text2.IsNullOrEmpty())
					{
						_logger.Error("No url to redirect is located.");
						return httpResponse;
					}
					if (!text2.TryCreateWebSocketUri(out var result, out var text3))
					{
						_logger.Error("An invalid url to redirect is located: " + text3);
						return httpResponse;
					}
					releaseClientResources();
					_uri = result;
					_secure = result.Scheme == "wss";
					setClientStream();
					return sendHandshakeRequest();
				}
			}
			return httpResponse;
		}

		private HttpResponse sendHttpRequest(HttpRequest request, int millisecondsTimeout)
		{
			_logger.Debug("A request to the server:\n" + request.ToString());
			HttpResponse response = request.GetResponse(_stream, millisecondsTimeout);
			_logger.Debug("A response to this request:\n" + response.ToString());
			return response;
		}

		private bool sendHttpResponse(HttpResponse response)
		{
			_logger.Debug("A response to this request:\n" + response.ToString());
			return sendBytes(response.ToByteArray());
		}

		private void sendProxyConnectRequest()
		{
			HttpRequest httpRequest = HttpRequest.CreateConnectRequest(_uri);
			HttpResponse httpResponse = sendHttpRequest(httpRequest, 90000);
			if (httpResponse.IsProxyAuthenticationRequired)
			{
				string text = httpResponse.Headers["Proxy-Authenticate"];
				_logger.Warn($"Received a proxy authentication requirement for '{text}'.");
				if (text.IsNullOrEmpty())
				{
					throw new WebSocketException("No proxy authentication challenge is specified.");
				}
				AuthenticationChallenge authenticationChallenge = AuthenticationChallenge.Parse(text);
				if (authenticationChallenge == null)
				{
					throw new WebSocketException("An invalid proxy authentication challenge is specified.");
				}
				if (_proxyCredentials != null)
				{
					if (httpResponse.HasConnectionClose)
					{
						releaseClientResources();
						_tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
						_stream = _tcpClient.GetStream();
					}
					AuthenticationResponse authenticationResponse = new AuthenticationResponse(authenticationChallenge, _proxyCredentials, 0u);
					httpRequest.Headers["Proxy-Authorization"] = authenticationResponse.ToString();
					httpResponse = sendHttpRequest(httpRequest, 15000);
				}
				if (httpResponse.IsProxyAuthenticationRequired)
				{
					throw new WebSocketException("A proxy authentication is required.");
				}
			}
			if (httpResponse.StatusCode[0] != '2')
			{
				throw new WebSocketException("The proxy has failed a connection to the requested host and port.");
			}
		}

		private void setClientStream()
		{
			if (_proxyUri != null)
			{
				_tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
				_stream = _tcpClient.GetStream();
				sendProxyConnectRequest();
			}
			else
			{
				_tcpClient = new TcpClient(_uri.DnsSafeHost, _uri.Port);
				_stream = _tcpClient.GetStream();
			}
			if (_secure)
			{
				ClientSslConfiguration sslConfiguration = SslConfiguration;
				string targetHost = sslConfiguration.TargetHost;
				if (targetHost != _uri.DnsSafeHost)
				{
					throw new WebSocketException(CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified.");
				}
				try
				{
					SslStream sslStream = new SslStream(_stream, leaveInnerStreamOpen: false, sslConfiguration.ServerCertificateValidationCallback, sslConfiguration.ClientCertificateSelectionCallback);
					sslStream.AuthenticateAsClient(targetHost, sslConfiguration.ClientCertificates, sslConfiguration.EnabledSslProtocols, sslConfiguration.CheckCertificateRevocation);
					_stream = sslStream;
				}
				catch (Exception innerException)
				{
					throw new WebSocketException(CloseStatusCode.TlsHandshakeFailure, innerException);
				}
			}
		}

		private void startReceiving()
		{
			if (_messageEventQueue.Count > 0)
			{
				_messageEventQueue.Clear();
			}
			_exitReceiving = new AutoResetEvent(initialState: false);
			_receivePong = new AutoResetEvent(initialState: false);
			Action receive = null;
			receive = delegate
			{
				WebSocketFrame.ReadFrameAsync(_stream, unmask: false, delegate(WebSocketFrame frame)
				{
					if (!processReceivedFrame(frame) || _readyState == WebSocketState.Closed)
					{
						_exitReceiving?.Set();
					}
					else
					{
						receive();
						if (!_inMessage && HasMessage && _readyState == WebSocketState.Open)
						{
							message();
						}
					}
				}, delegate(Exception ex)
				{
					_logger.Fatal(ex.ToString());
					fatal("An exception has occurred while receiving.", ex);
				});
			};
			receive();
		}

		private bool validateSecWebSocketAcceptHeader(string value)
		{
			return value != null && value == CreateResponseKey(_base64Key);
		}

		private bool validateSecWebSocketExtensionsClientHeader(string value)
		{
			return value == null || value.Length > 0;
		}

		private bool validateSecWebSocketExtensionsServerHeader(string value)
		{
			if (value == null)
			{
				return true;
			}
			if (value.Length == 0)
			{
				return false;
			}
			if (!_extensionsRequested)
			{
				return false;
			}
			bool flag = _compression != CompressionMethod.None;
			foreach (string item in value.SplitHeaderValue(','))
			{
				string text = item.Trim();
				if (flag && text.IsCompressionExtension(_compression))
				{
					if (!text.Contains("server_no_context_takeover"))
					{
						_logger.Error("The server hasn't sent back 'server_no_context_takeover'.");
						return false;
					}
					if (!text.Contains("client_no_context_takeover"))
					{
						_logger.Warn("The server hasn't sent back 'client_no_context_takeover'.");
					}
					string method = _compression.ToExtensionString();
					if (text.SplitHeaderValue(';').Contains(delegate(string t)
					{
						t = t.Trim();
						return t != method && t != "server_no_context_takeover" && t != "client_no_context_takeover";
					}))
					{
						return false;
					}
					continue;
				}
				return false;
			}
			return true;
		}

		private bool validateSecWebSocketKeyHeader(string value)
		{
			return value != null && value.Length > 0;
		}

		private bool validateSecWebSocketProtocolClientHeader(string value)
		{
			return value == null || value.Length > 0;
		}

		private bool validateSecWebSocketProtocolServerHeader(string value)
		{
			if (value == null)
			{
				return !_protocolsRequested;
			}
			if (value.Length == 0)
			{
				return false;
			}
			return _protocolsRequested && _protocols.Contains((string p) => p == value);
		}

		private bool validateSecWebSocketVersionClientHeader(string value)
		{
			return value != null && value == "13";
		}

		private bool validateSecWebSocketVersionServerHeader(string value)
		{
			return value == null || value == "13";
		}

		internal static string CheckCloseParameters(ushort code, string reason, bool client)
		{
			return (!code.IsCloseStatusCode()) ? "An invalid close status code." : ((code != 1005) ? ((code == 1010 && !client) ? "MandatoryExtension cannot be used by a server." : ((code == 1011 && client) ? "ServerError cannot be used by a client." : ((!reason.IsNullOrEmpty() && reason.UTF8Encode().Length > 123) ? "A reason has greater than the allowable max size." : null))) : ((!reason.IsNullOrEmpty()) ? "NoStatus cannot have a reason." : null));
		}

		internal static string CheckCloseParameters(CloseStatusCode code, string reason, bool client)
		{
			return (code != CloseStatusCode.NoStatus) ? ((code == CloseStatusCode.MandatoryExtension && !client) ? "MandatoryExtension cannot be used by a server." : ((code == CloseStatusCode.ServerError && client) ? "ServerError cannot be used by a client." : ((!reason.IsNullOrEmpty() && reason.UTF8Encode().Length > 123) ? "A reason has greater than the allowable max size." : null))) : ((!reason.IsNullOrEmpty()) ? "NoStatus cannot have a reason." : null);
		}

		internal static string CheckPingParameter(string message, out byte[] bytes)
		{
			bytes = message.UTF8Encode();
			return (bytes.Length > 125) ? "A message has greater than the allowable max size." : null;
		}

		internal static string CheckSendParameter(byte[] data)
		{
			return (data == null) ? "'data' is null." : null;
		}

		internal static string CheckSendParameter(FileInfo file)
		{
			return (file == null) ? "'file' is null." : null;
		}

		internal static string CheckSendParameter(string data)
		{
			return (data == null) ? "'data' is null." : null;
		}

		internal static string CheckSendParameters(Stream stream, int length)
		{
			return (stream == null) ? "'stream' is null." : ((!stream.CanRead) ? "'stream' cannot be read." : ((length < 1) ? "'length' is less than 1." : null));
		}

		internal void Close(HttpResponse response)
		{
			_readyState = WebSocketState.Closing;
			sendHttpResponse(response);
			releaseServerResources();
			_readyState = WebSocketState.Closed;
		}

		internal void Close(WebSocketSharp.Net.HttpStatusCode code)
		{
			Close(createHandshakeFailureResponse(code));
		}

		internal void Close(CloseEventArgs e, byte[] frameAsBytes, bool receive)
		{
			lock (_forConn)
			{
				if (_readyState == WebSocketState.Closing)
				{
					_logger.Info("The closing is already in progress.");
					return;
				}
				if (_readyState == WebSocketState.Closed)
				{
					_logger.Info("The connection has been closed.");
					return;
				}
				_readyState = WebSocketState.Closing;
			}
			e.WasClean = closeHandshake(frameAsBytes, receive, received: false);
			releaseServerResources();
			releaseCommonResources();
			_readyState = WebSocketState.Closed;
			try
			{
				this.OnClose.Emit(this, e);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
			}
		}

		internal static string CreateBase64Key()
		{
			byte[] array = new byte[16];
			RandomNumber.GetBytes(array);
			return Convert.ToBase64String(array);
		}

		internal static string CreateResponseKey(string base64Key)
		{
			StringBuilder stringBuilder = new StringBuilder(base64Key, 64);
			stringBuilder.Append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
			SHA1 sHA = new SHA1CryptoServiceProvider();
			byte[] inArray = sHA.ComputeHash(stringBuilder.ToString().UTF8Encode());
			return Convert.ToBase64String(inArray);
		}

		internal void InternalAccept()
		{
			try
			{
				if (!acceptHandshake())
				{
					return;
				}
				_readyState = WebSocketState.Open;
			}
			catch (Exception ex)
			{
				_logger.Fatal(ex.ToString());
				fatal("An exception has occurred while accepting.", ex);
				return;
			}
			open();
		}

		internal bool Ping(byte[] frameAsBytes, TimeSpan timeout)
		{
			if (_readyState != WebSocketState.Open)
			{
				return false;
			}
			if (!send(frameAsBytes))
			{
				return false;
			}
			return _receivePong?.WaitOne(timeout) ?? false;
		}

		internal void Send(Opcode opcode, byte[] data, Dictionary<CompressionMethod, byte[]> cache)
		{
			lock (_forSend)
			{
				lock (_forConn)
				{
					if (_readyState != WebSocketState.Open)
					{
						_logger.Error("The sending has been interrupted.");
						return;
					}
					try
					{
						if (!cache.TryGetValue(_compression, out var value))
						{
							value = new WebSocketFrame(Fin.Final, opcode, data.Compress(_compression), _compression != CompressionMethod.None, mask: false).ToArray();
							cache.Add(_compression, value);
						}
						sendBytes(value);
					}
					catch (Exception ex)
					{
						_logger.Error(ex.ToString());
					}
				}
			}
		}

		internal void Send(Opcode opcode, Stream stream, Dictionary<CompressionMethod, Stream> cache)
		{
			lock (_forSend)
			{
				try
				{
					if (!cache.TryGetValue(_compression, out var value))
					{
						value = stream.Compress(_compression);
						cache.Add(_compression, value);
					}
					else
					{
						value.Position = 0L;
					}
					send(opcode, value, _compression != CompressionMethod.None);
				}
				catch (Exception ex)
				{
					_logger.Error(ex.ToString());
				}
			}
		}

		public void Accept()
		{
			if (!checkIfAvailable(client: false, server: true, connecting: true, open: false, closing: false, closed: false, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in accepting.", null);
			}
			else if (accept())
			{
				open();
			}
		}

		public void AcceptAsync()
		{
			if (!checkIfAvailable(client: false, server: true, connecting: true, open: false, closing: false, closed: false, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in accepting.", null);
				return;
			}
			Func<bool> acceptor = accept;
			acceptor.BeginInvoke(delegate(IAsyncResult ar)
			{
				if (acceptor.EndInvoke(ar))
				{
					open();
				}
			}, null);
		}

		public void Close()
		{
			if (!checkIfAvailable(connecting: true, open: true, closing: false, closed: false, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else
			{
				close(new CloseEventArgs(), send: true, receive: true, received: false);
			}
		}

		public void Close(ushort code)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, null, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == 1005)
			{
				close(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				close(new CloseEventArgs(code), receive, receive, received: false);
			}
		}

		public void Close(CloseStatusCode code)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, null, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == CloseStatusCode.NoStatus)
			{
				close(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				close(new CloseEventArgs(code), receive, receive, received: false);
			}
		}

		public void Close(ushort code, string reason)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, reason, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == 1005)
			{
				close(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				close(new CloseEventArgs(code, reason), receive, receive, received: false);
			}
		}

		public void Close(CloseStatusCode code, string reason)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, reason, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == CloseStatusCode.NoStatus)
			{
				close(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				close(new CloseEventArgs(code, reason), receive, receive, received: false);
			}
		}

		public void CloseAsync()
		{
			if (!checkIfAvailable(connecting: true, open: true, closing: false, closed: false, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else
			{
				closeAsync(new CloseEventArgs(), send: true, receive: true, received: false);
			}
		}

		public void CloseAsync(ushort code)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, null, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == 1005)
			{
				closeAsync(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				closeAsync(new CloseEventArgs(code), receive, receive, received: false);
			}
		}

		public void CloseAsync(CloseStatusCode code)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, null, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == CloseStatusCode.NoStatus)
			{
				closeAsync(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				closeAsync(new CloseEventArgs(code), receive, receive, received: false);
			}
		}

		public void CloseAsync(ushort code, string reason)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, reason, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == 1005)
			{
				closeAsync(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				closeAsync(new CloseEventArgs(code, reason), receive, receive, received: false);
			}
		}

		public void CloseAsync(CloseStatusCode code, string reason)
		{
			string text = _readyState.CheckIfAvailable(connecting: true, open: true, closing: false, closed: false) ?? CheckCloseParameters(code, reason, _client);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in closing the connection.", null);
			}
			else if (code == CloseStatusCode.NoStatus)
			{
				closeAsync(new CloseEventArgs(), send: true, receive: true, received: false);
			}
			else
			{
				bool receive = !code.IsReserved();
				closeAsync(new CloseEventArgs(code, reason), receive, receive, received: false);
			}
		}

		public void Connect()
		{
			if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in connecting.", null);
			}
			else if (connect())
			{
				open();
			}
		}

		public void ConnectAsync()
		{
			if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in connecting.", null);
				return;
			}
			Func<bool> connector = connect;
			connector.BeginInvoke(delegate(IAsyncResult ar)
			{
				if (connector.EndInvoke(ar))
				{
					open();
				}
			}, null);
		}

		public bool Ping()
		{
			byte[] frameAsBytes = (_client ? WebSocketFrame.CreatePingFrame(mask: true).ToArray() : WebSocketFrame.EmptyPingBytes);
			return Ping(frameAsBytes, _waitTime);
		}

		public bool Ping(string message)
		{
			if (message == null || message.Length == 0)
			{
				return Ping();
			}
			byte[] bytes;
			string text = CheckPingParameter(message, out bytes);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending a ping.", null);
				return false;
			}
			return Ping(WebSocketFrame.CreatePingFrame(bytes, _client).ToArray(), _waitTime);
		}

		public void Send(byte[] data)
		{
			string text = _readyState.CheckIfAvailable(connecting: false, open: true, closing: false, closed: false) ?? CheckSendParameter(data);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending data.", null);
			}
			else
			{
				send(Opcode.Binary, new MemoryStream(data));
			}
		}

		public void Send(FileInfo file)
		{
			string text = _readyState.CheckIfAvailable(connecting: false, open: true, closing: false, closed: false) ?? CheckSendParameter(file);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending data.", null);
			}
			else
			{
				send(Opcode.Binary, file.OpenRead());
			}
		}

		public void Send(string data)
		{
			string text = _readyState.CheckIfAvailable(connecting: false, open: true, closing: false, closed: false) ?? CheckSendParameter(data);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending data.", null);
			}
			else
			{
				send(Opcode.Text, new MemoryStream(data.UTF8Encode()));
			}
		}

		public void SendAsync(byte[] data, Action<bool> completed)
		{
			string text = _readyState.CheckIfAvailable(connecting: false, open: true, closing: false, closed: false) ?? CheckSendParameter(data);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending data.", null);
			}
			else
			{
				sendAsync(Opcode.Binary, new MemoryStream(data), completed);
			}
		}

		public void SendAsync(FileInfo file, Action<bool> completed)
		{
			string text = _readyState.CheckIfAvailable(connecting: false, open: true, closing: false, closed: false) ?? CheckSendParameter(file);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending data.", null);
			}
			else
			{
				sendAsync(Opcode.Binary, file.OpenRead(), completed);
			}
		}

		public void SendAsync(string data, Action<bool> completed)
		{
			string text = _readyState.CheckIfAvailable(connecting: false, open: true, closing: false, closed: false) ?? CheckSendParameter(data);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending data.", null);
			}
			else
			{
				sendAsync(Opcode.Text, new MemoryStream(data.UTF8Encode()), completed);
			}
		}

		public void SendAsync(Stream stream, int length, Action<bool> completed)
		{
			string text = _readyState.CheckIfAvailable(connecting: false, open: true, closing: false, closed: false) ?? CheckSendParameters(stream, length);
			if (text != null)
			{
				_logger.Error(text);
				error("An error has occurred in sending data.", null);
				return;
			}
			stream.ReadBytesAsync(length, delegate(byte[] data)
			{
				int num = data.Length;
				if (num == 0)
				{
					_logger.Error("The data cannot be read from 'stream'.");
					error("An error has occurred in sending data.", null);
				}
				else
				{
					if (num < length)
					{
						_logger.Warn($"The length of the data is less than 'length':\n  expected: {length}\n  actual: {num}");
					}
					bool obj = send(Opcode.Binary, new MemoryStream(data));
					if (completed != null)
					{
						completed(obj);
					}
				}
			}, delegate(Exception ex)
			{
				_logger.Error(ex.ToString());
				error("An exception has occurred while sending data.", ex);
			});
		}

		public void SetCookie(WebSocketSharp.Net.Cookie cookie)
		{
			if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in setting a cookie.", null);
				return;
			}
			lock (_forConn)
			{
				if (!checkIfAvailable(connecting: true, open: false, closing: false, closed: true, out text))
				{
					_logger.Error(text);
					error("An error has occurred in setting a cookie.", null);
					return;
				}
				if (cookie == null)
				{
					_logger.Error("'cookie' is null.");
					error("An error has occurred in setting a cookie.", null);
					return;
				}
				lock (_cookies.SyncRoot)
				{
					_cookies.SetOrRemove(cookie);
				}
			}
		}

		public void SetCredentials(string username, string password, bool preAuth)
		{
			if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in setting the credentials.", null);
				return;
			}
			lock (_forConn)
			{
				if (!checkIfAvailable(connecting: true, open: false, closing: false, closed: true, out text))
				{
					_logger.Error(text);
					error("An error has occurred in setting the credentials.", null);
				}
				else if (username.IsNullOrEmpty())
				{
					_logger.Warn("The credentials are set back to the default.");
					_credentials = null;
					_preAuth = false;
				}
				else if (Ext.Contains(username, ':') || !username.IsText())
				{
					_logger.Error("'username' contains an invalid character.");
					error("An error has occurred in setting the credentials.", null);
				}
				else if (!password.IsNullOrEmpty() && !password.IsText())
				{
					_logger.Error("'password' contains an invalid character.");
					error("An error has occurred in setting the credentials.", null);
				}
				else
				{
					_credentials = new WebSocketSharp.Net.NetworkCredential(username, password, _uri.PathAndQuery);
					_preAuth = preAuth;
				}
			}
		}

		public void SetProxy(string url, string username, string password)
		{
			if (!checkIfAvailable(client: true, server: false, connecting: true, open: false, closing: false, closed: true, out var text))
			{
				_logger.Error(text);
				error("An error has occurred in setting the proxy.", null);
				return;
			}
			lock (_forConn)
			{
				Uri result;
				if (!checkIfAvailable(connecting: true, open: false, closing: false, closed: true, out text))
				{
					_logger.Error(text);
					error("An error has occurred in setting the proxy.", null);
				}
				else if (url.IsNullOrEmpty())
				{
					_logger.Warn("The proxy url and credentials are set back to the default.");
					_proxyUri = null;
					_proxyCredentials = null;
				}
				else if (!Uri.TryCreate(url, UriKind.Absolute, out result) || result.Scheme != "http" || result.Segments.Length > 1)
				{
					_logger.Error("The syntax of a proxy url must be 'http://<host>[:<port>]'.");
					error("An error has occurred in setting the proxy.", null);
				}
				else if (username.IsNullOrEmpty())
				{
					_logger.Warn("The proxy credentials are set back to the default.");
					_proxyUri = result;
					_proxyCredentials = null;
				}
				else if (Ext.Contains(username, ':') || !username.IsText())
				{
					_logger.Error("'username' contains an invalid character.");
					error("An error has occurred in setting the proxy.", null);
				}
				else if (!password.IsNullOrEmpty() && !password.IsText())
				{
					_logger.Error("'password' contains an invalid character.");
					error("An error has occurred in setting the proxy.", null);
				}
				else
				{
					_proxyUri = result;
					_proxyCredentials = new WebSocketSharp.Net.NetworkCredential(username, password, $"{_uri.DnsSafeHost}:{_uri.Port}");
				}
			}
		}

		void IDisposable.Dispose()
		{
			close(new CloseEventArgs(CloseStatusCode.Away), send: true, receive: true, received: false);
		}
	}
}
namespace WebSocketSharp.Server
{
	public class WebSocketServer
	{
		private IPAddress _address;

		private WebSocketSharp.Net.AuthenticationSchemes _authSchemes;

		private static readonly string _defaultRealm;

		private bool _dnsStyle;

		private string _hostname;

		private TcpListener _listener;

		private Logger _logger;

		private int _port;

		private string _realm;

		private Thread _receiveThread;

		private bool _reuseAddress;

		private bool _secure;

		private WebSocketServiceManager _services;

		private ServerSslConfiguration _sslConfig;

		private volatile ServerState _state;

		private object _sync;

		private Func<IIdentity, WebSocketSharp.Net.NetworkCredential> _userCredFinder;

		public IPAddress Address => _address;

		public WebSocketSharp.Net.AuthenticationSchemes AuthenticationSchemes
		{
			get
			{
				return _authSchemes;
			}
			set
			{
				string text = _state.CheckIfAvailable(ready: true, start: false, shutting: false);
				if (text != null)
				{
					_logger.Error(text);
				}
				else
				{
					_authSchemes = value;
				}
			}
		}

		public bool IsListening => _state == ServerState.Start;

		public bool IsSecure => _secure;

		public bool KeepClean
		{
			get
			{
				return _services.KeepClean;
			}
			set
			{
				string text = _state.CheckIfAvailable(ready: true, start: false, shutting: false);
				if (text != null)
				{
					_logger.Error(text);
				}
				else
				{
					_services.KeepClean = value;
				}
			}
		}

		public Logger Log => _logger;

		public int Port => _port;

		public string Realm
		{
			get
			{
				return _realm;
			}
			set
			{
				string text = _state.CheckIfAvailable(ready: true, start: false, shutting: false);
				if (text != null)
				{
					_logger.Error(text);
				}
				else
				{
					_realm = value;
				}
			}
		}

		public bool ReuseAddress
		{
			get
			{
				return _reuseAddress;
			}
			set
			{
				string text = _state.CheckIfAvailable(ready: true, start: false, shutting: false);
				if (text != null)
				{
					_logger.Error(text);
				}
				else
				{
					_reuseAddress = value;
				}
			}
		}

		public ServerSslConfiguration SslConfiguration
		{
			get
			{
				return _sslConfig ?? (_sslConfig = new ServerSslConfiguration(null));
			}
			set
			{
				string text = _state.CheckIfAvailable(ready: true, start: false, shutting: false);
				if (text != null)
				{
					_logger.Error(text);
				}
				else
				{
					_sslConfig = value;
				}
			}
		}

		public Func<IIdentity, WebSocketSharp.Net.NetworkCredential> UserCredentialsFinder
		{
			get
			{
				return _userCredFinder;
			}
			set
			{
				string text = _state.CheckIfAvailable(ready: true, start: false, shutting: false);
				if (text != null)
				{
					_logger.Error(text);
				}
				else
				{
					_userCredFinder = value;
				}
			}
		}

		public TimeSpan WaitTime
		{
			get
			{
				return _services.WaitTime;
			}
			set
			{
				string text = _state.CheckIfAvailable(ready: true, start: false, shutting: false) ?? value.CheckIfValidWaitTime();
				if (text != null)
				{
					_logger.Error(text);
				}
				else
				{
					_services.WaitTime = value;
				}
			}
		}

		public WebSocketServiceManager WebSocketServices => _services;

		static WebSocketServer()
		{
			_defaultRealm = "SECRET AREA";
		}

		public WebSocketServer()
		{
			init(null, IPAddress.Any, 80, secure: false);
		}

		public WebSocketServer(int port)
			: this(port, port == 443)
		{
		}

		public WebSocketServer(string url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}
			if (url.Length == 0)
			{
				throw new ArgumentException("An empty string.", "url");
			}
			if (!tryCreateUri(url, out var result, out var message))
			{
				throw new ArgumentException(message, "url");
			}
			string dnsSafeHost = result.DnsSafeHost;
			IPAddress address = dnsSafeHost.ToIPAddress();
			if (!address.IsLocal())
			{
				throw new ArgumentException("The host part isn't a local host name: " + url, "url");
			}
			init(dnsSafeHost, address, result.Port, result.Scheme == "wss");
		}

		public WebSocketServer(int port, bool secure)
		{
			if (!port.IsPortNumber())
			{
				throw new ArgumentOutOfRangeException("port", "Not between 1 and 65535 inclusive: " + port);
			}
			init(null, IPAddress.Any, port, secure);
		}

		public WebSocketServer(IPAddress address, int port)
			: this(address, port, port == 443)
		{
		}

		public WebSocketServer(IPAddress address, int port, bool secure)
		{
			if (address == null)
			{
				throw new ArgumentNullException("address");
			}
			if (!address.IsLocal())
			{
				throw new ArgumentException("Not a local IP address: " + address, "address");
			}
			if (!port.IsPortNumber())
			{
				throw new ArgumentOutOfRangeException("port", "Not between 1 and 65535 inclusive: " + port);
			}
			init(null, address, port, secure);
		}

		private void abort()
		{
			lock (_sync)
			{
				if (!IsListening)
				{
					return;
				}
				_state = ServerState.ShuttingDown;
			}
			_listener.Stop();
			_services.Stop(new CloseEventArgs(CloseStatusCode.ServerError), send: true, receive: false);
			_state = ServerState.Stop;
		}

		private string checkIfCertificateExists()
		{
			return (_secure && (_sslConfig == null || _sslConfig.ServerCertificate == null)) ? "The secure connection requires a server certificate." : null;
		}

		private string getRealm()
		{
			string realm = _realm;
			return (realm != null && realm.Length > 0) ? realm : _defaultRealm;
		}

		private void init(string hostname, IPAddress address, int port, bool secure)
		{
			_hostname = hostname ?? address.ToString();
			_address = address;
			_port = port;
			_secure = se

ButtplugManaged.dll

Decompiled 2 days ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using WebSocketSharp;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("Nonpolynomial, LLC")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyCopyright("Copyright 2017-2021 © Nonpolynomial. All rights reserved.")]
[assembly: AssemblyDescription("Buttplug Sex Toy Control Library for .Net. FFI layer on top of Buttplug Rust for .Net Access. Contains full system (Core, Client, Server, Device Comm Managers, etc...) for Windows. (.Net Framework 4.7+/.Net Standard 2.0)")]
[assembly: AssemblyFileVersion("2.0.6.0")]
[assembly: AssemblyInformationalVersion("2.0.6")]
[assembly: AssemblyProduct("ButtplugManaged")]
[assembly: AssemblyTitle("ButtplugManaged")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/buttplugio/buttplug-rs-ffi")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.0.6.0")]
[module: UnverifiableCode]
namespace ButtplugManaged;

public class ButtplugClient
{
	internal ButtplugMessageManager _messageManager;

	private readonly ConcurrentDictionary<uint, ButtplugClientDevice> _devices;

	public ButtplugClientDevice[] Devices => _devices.Values.ToArray();

	public string Name { get; }

	public bool Connected { get; private set; }

	public bool IsScanning { get; private set; }

	public event EventHandler<DeviceAddedEventArgs> DeviceAdded;

	public event EventHandler<DeviceRemovedEventArgs> DeviceRemoved;

	public event EventHandler<ButtplugExceptionEventArgs> ErrorReceived;

	public event EventHandler ScanningFinished;

	public event EventHandler PingTimeout;

	public event EventHandler ServerDisconnect;

	public void OnDeviceAdded(object sender, DeviceAddedEventArgs args)
	{
		_devices.TryAdd(args.Device.Index, args.Device);
		this.DeviceAdded?.Invoke(sender, args);
	}

	public void OnDeviceRemoved(object sender, DeviceRemovedEventArgs args)
	{
		_devices.TryRemove(args.Device.Index, out var _);
		this.DeviceRemoved?.Invoke(sender, args);
	}

	public void OnErrorReceived(object sender, ButtplugExceptionEventArgs args)
	{
		this.ErrorReceived?.Invoke(sender, args);
	}

	public void OnScanningFinished(object sender, EventArgs args)
	{
		IsScanning = false;
		this.ScanningFinished?.Invoke(sender, args);
	}

	public void OnPingTimeout(object sender, EventArgs args)
	{
		this.PingTimeout?.Invoke(sender, args);
	}

	public void OnServerDisconnect(object sender, EventArgs args)
	{
		Connected = false;
		_devices.Clear();
		this.ServerDisconnect?.Invoke(sender, args);
	}

	public ButtplugClient(string aClientName)
	{
		Name = aClientName;
		_devices = new ConcurrentDictionary<uint, ButtplugClientDevice>();
	}

	public async Task ConnectAsync(ButtplugEmbeddedConnectorOptions aConnector)
	{
		throw new NotImplementedException("This feature doesnt exist in the managed Client");
	}

	public async Task ConnectAsync(ButtplugWebsocketConnectorOptions aConnector)
	{
		_messageManager = new ButtplugMessageManager(aConnector, this);
		await _messageManager.Connect();
		Connected = true;
	}

	public async Task DisconnectAsync()
	{
		_messageManager.Disconnect();
		_devices.Clear();
		Connected = false;
	}

	public async Task StartScanningAsync()
	{
		IsScanning = true;
		await _messageManager.SendClientMessage(new StartScanning());
	}

	public async Task StopScanningAsync()
	{
		IsScanning = false;
		await _messageManager.SendClientMessage(new StopScanning());
	}

	public async Task StopAllDevicesAsync()
	{
		await _messageManager.SendClientMessage(new StopAllDevices());
	}

	public async Task PingAsync()
	{
		await _messageManager.SendClientMessage(new Ping());
	}
}
public class ButtplugClientDevice
{
	private readonly ButtplugMessageManager _manager;

	public uint Index { get; }

	public string Name { get; }

	public Dictionary<string, DeviceMessagesDetails> AllowedMessages { get; }

	internal ButtplugClientDevice(ButtplugMessageManager manager, uint aIndex, string aName, Dictionary<string, DeviceMessagesDetails> aAllowedMessages)
	{
		_manager = manager;
		Index = aIndex;
		Name = aName;
		AllowedMessages = aAllowedMessages;
	}

	public bool Equals(ButtplugClientDevice aDevice)
	{
		return Index == aDevice.Index;
	}

	public Task SendVibrateCmd(double aSpeed)
	{
		uint num = 1u;
		if (AllowedMessages.ContainsKey("VibrateCmd"))
		{
			num = AllowedMessages["VibrateCmd"].FeatureCount;
		}
		Dictionary<uint, double> dictionary = new Dictionary<uint, double>();
		for (uint num2 = 0u; num2 < num; num2++)
		{
			dictionary.Add(num2, aSpeed);
		}
		return SendVibrateCmd(dictionary);
	}

	public Task SendVibrateCmd(IEnumerable<double> aCmds)
	{
		return SendVibrateCmd(aCmds.Select((double cmd, int index) => (cmd, index)).ToDictionary<(double, int), uint, double>(((double cmd, int index) x) => (uint)x.index, ((double cmd, int index) x) => x.cmd));
	}

	public Task SendVibrateCmd(Dictionary<uint, double> aCmds)
	{
		VibrateCmd vibrateCmd = new VibrateCmd();
		vibrateCmd.DeviceIndex = Index;
		vibrateCmd.Speeds = new List<VibrateSpeed>();
		foreach (KeyValuePair<uint, double> aCmd in aCmds)
		{
			vibrateCmd.Speeds.Add(new VibrateSpeed
			{
				Index = aCmd.Key,
				Speed = aCmd.Value
			});
		}
		return _manager.SendClientMessage(vibrateCmd);
	}

	public Task SendRotateCmd(double aSpeed, bool aClockwise)
	{
		uint num = 1u;
		if (AllowedMessages.ContainsKey("RotateCmd"))
		{
			num = AllowedMessages["RotateCmd"].FeatureCount;
		}
		Dictionary<uint, (double, bool)> dictionary = new Dictionary<uint, (double, bool)>();
		for (uint num2 = 0u; num2 < num; num2++)
		{
			dictionary.Add(num2, (aSpeed, aClockwise));
		}
		return SendRotateCmd(dictionary);
	}

	public Task SendRotateCmd(IEnumerable<(double, bool)> aCmds)
	{
		return SendRotateCmd(aCmds.Select(((double, bool) cmd, int index) => (cmd, index)).ToDictionary<((double, bool), int), uint, (double, bool)>((((double, bool) cmd, int index) x) => (uint)x.index, (((double, bool) cmd, int index) x) => x.cmd));
	}

	public Task SendRotateCmd(Dictionary<uint, (double, bool)> aCmds)
	{
		RotateCmd rotateCmd = new RotateCmd();
		rotateCmd.DeviceIndex = Index;
		rotateCmd.Rotations = new List<Rotations>();
		foreach (KeyValuePair<uint, (double, bool)> aCmd in aCmds)
		{
			rotateCmd.Rotations.Add(new Rotations
			{
				Index = aCmd.Key,
				Speed = aCmd.Value.Item1,
				Clockwise = aCmd.Value.Item2
			});
		}
		return _manager.SendClientMessage(rotateCmd);
	}

	public Task SendLinearCmd(uint aDuration, double aPosition)
	{
		uint num = 1u;
		if (AllowedMessages.ContainsKey("LinearCmd"))
		{
			num = AllowedMessages["LinearCmd"].FeatureCount;
		}
		Dictionary<uint, (uint, double)> dictionary = new Dictionary<uint, (uint, double)>();
		for (uint num2 = 0u; num2 < num; num2++)
		{
			dictionary.Add(num2, (aDuration, aPosition));
		}
		return SendLinearCmd(dictionary);
	}

	public Task SendLinearCmd(IEnumerable<(uint, double)> aCmds)
	{
		return SendLinearCmd(aCmds.Select(((uint, double) cmd, int index) => (cmd, index)).ToDictionary<((uint, double), int), uint, (uint, double)>((((uint, double) cmd, int index) x) => (uint)x.index, (((uint, double) cmd, int index) x) => x.cmd));
	}

	public Task SendLinearCmd(Dictionary<uint, (uint, double)> aCmds)
	{
		LinearCmd linearCmd = new LinearCmd();
		linearCmd.DeviceIndex = Index;
		linearCmd.Vectors = new List<LinearVector>();
		foreach (KeyValuePair<uint, (uint, double)> aCmd in aCmds)
		{
			linearCmd.Vectors.Add(new LinearVector
			{
				Index = aCmd.Key,
				Duration = aCmd.Value.Item1,
				Position = aCmd.Value.Item2
			});
		}
		return _manager.SendClientMessage(linearCmd);
	}

	public async Task<double> SendBatteryLevelCmd()
	{
		MessageBase result = await _manager.SendClientMessage(new BatteryLevelCmd
		{
			DeviceIndex = Index
		});
		if (result is BatteryLevelReading reading)
		{
			return reading.BatteryLevel;
		}
		throw new ButtplugDeviceException($"Expected message type of BatteryLevelReading not received, got {result} instead.");
	}

	public async Task<int> SendRSSIBatteryLevelCmd()
	{
		MessageBase result = await _manager.SendClientMessage(new RSSILevelCmd
		{
			DeviceIndex = Index
		});
		if (result is RSSILevelReading reading)
		{
			return reading.RSSILevel;
		}
		throw new ButtplugDeviceException($"Expected message type of RssiLevelReading not received, got {result} instead.");
	}

	public async Task<byte[]> SendRawReadCmd(string aEndpoint, uint aExpectedLength, uint aTimeout)
	{
		Task<MessageBase> task = _manager.SendClientMessage(new RawReadCmd
		{
			DeviceIndex = Index,
			Endpoint = aEndpoint,
			ExpectedLength = aExpectedLength
		});
		if (await Task.WhenAny(new Task[2]
		{
			task,
			Task.Delay((int)aTimeout)
		}) == task)
		{
			MessageBase result = await task;
			if (result is RawReading reading)
			{
				return reading.Data.Select((int x) => (byte)x).ToArray();
			}
			throw new ButtplugDeviceException($"Expected message type of RawReading not received, got {result} instead.");
		}
		throw new ButtplugDeviceException("No Message returned");
	}

	public Task SendRawWriteCmd(string aEndpoint, byte[] aData, bool aWriteWithResponse)
	{
		return _manager.SendClientMessage(new RawWriteCmd
		{
			DeviceIndex = Index,
			Endpoint = aEndpoint,
			Data = ((IEnumerable<byte>)aData).Select((Func<byte, int>)((byte x) => x)).ToList(),
			WriteWithResponse = aWriteWithResponse
		});
	}

	public Task SendRawSubscribeCmd(string aEndpoint)
	{
		return _manager.SendClientMessage(new RawSubscribeCmd
		{
			DeviceIndex = Index,
			Endpoint = aEndpoint
		});
	}

	public Task SendRawUnsubscribeCmd(string aEndpoint)
	{
		return _manager.SendClientMessage(new RawUnsubscribeCmd
		{
			DeviceIndex = Index,
			Endpoint = aEndpoint
		});
	}

	public Task SendStopDeviceCmd()
	{
		return _manager.SendClientMessage(new StopDeviceCmd
		{
			DeviceIndex = Index
		});
	}
}
public class ButtplugConnectorException : ButtplugException
{
	public ButtplugConnectorException()
	{
	}

	public ButtplugConnectorException(string aMessage)
		: base(aMessage)
	{
	}

	public ButtplugConnectorException(string aMessage, Exception aInner)
		: base(aMessage, aInner)
	{
	}
}
public class ButtplugDeviceException : ButtplugException
{
	public ButtplugDeviceException()
	{
	}

	public ButtplugDeviceException(string aMessage)
		: base(aMessage)
	{
	}

	public ButtplugDeviceException(string aMessage, Exception aInner)
		: base(aMessage, aInner)
	{
	}
}
public class ButtplugEmbeddedConnectorOptions
{
	public string ServerName { get; set; } = "Buttplug C# Embedded Server";


	public string DeviceConfigJSON { get; set; } = "";


	public string UserDeviceConfigJSON { get; set; } = "";


	public ushort DeviceCommunicationManagerTypes { get; set; } = 0;


	public bool AllowRawMessages { get; set; } = false;


	public uint MaxPingTime { get; set; } = 0u;

}
public class ButtplugException : Exception
{
	public ButtplugException()
	{
	}

	public ButtplugException(string aMessage)
		: base(aMessage)
	{
	}

	public ButtplugException(string aMessage, Exception aInner)
		: base(aMessage, aInner)
	{
	}

	public static ButtplugException FromError(Error aMsg)
	{
		string errorMessage = aMsg.ErrorMessage;
		return aMsg.ErrorCode switch
		{
			Error.ErrorCodeEnum.ERROR_INIT => new ButtplugConnectorException(errorMessage), 
			Error.ErrorCodeEnum.ERROR_PING => new ButtplugPingException(errorMessage), 
			Error.ErrorCodeEnum.ERROR_MSG => new ButtplugMessageException(errorMessage), 
			Error.ErrorCodeEnum.ERROR_UNKNOWN => new ButtplugUnknownException(errorMessage), 
			Error.ErrorCodeEnum.ERROR_DEVICE => new ButtplugDeviceException(errorMessage), 
			_ => new ButtplugUnknownException($"Unknown error type: {aMsg.ErrorCode} | Message: {aMsg.ErrorMessage}"), 
		};
	}
}
public class ButtplugExceptionEventArgs : EventArgs
{
	public ButtplugException Exception { get; }

	public ButtplugExceptionEventArgs(ButtplugException ex)
	{
		Exception = ex;
	}
}
public class ButtplugHandshakeException : ButtplugException
{
	public ButtplugHandshakeException()
	{
	}

	public ButtplugHandshakeException(string aMessage)
		: base(aMessage)
	{
	}

	public ButtplugHandshakeException(string aMessage, Exception aInner)
		: base(aMessage, aInner)
	{
	}
}
public class ButtplugMessageException : ButtplugException
{
	public ButtplugMessageException()
	{
	}

	public ButtplugMessageException(string aMessage)
		: base(aMessage)
	{
	}

	public ButtplugMessageException(string aMessage, Exception aInner)
		: base(aMessage, aInner)
	{
	}
}
public class ButtplugMessageManager
{
	private readonly ConcurrentDictionary<uint, TaskCompletionSource<MessageBase>> _waitingMsgs = new ConcurrentDictionary<uint, TaskCompletionSource<MessageBase>>();

	private int _counter;

	private readonly WebSocket _webSocket;

	private uint _pingTimeout;

	private readonly ButtplugClient _client;

	public uint NextMsgId => Convert.ToUInt32(Interlocked.Increment(ref _counter));

	public ButtplugMessageManager(ButtplugWebsocketConnectorOptions connectorOptions, ButtplugClient client)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Expected O, but got Unknown
		_webSocket = new WebSocket(connectorOptions.NetworkAddress.AbsoluteUri, Array.Empty<string>());
		_client = client;
		_webSocket.OnMessage += RecieveMessage;
		_webSocket.OnClose += delegate(object sender, CloseEventArgs args)
		{
			_client.OnServerDisconnect(sender, (EventArgs)(object)args);
		};
		_webSocket.OnError += delegate(object sender, ErrorEventArgs args)
		{
			_client.OnServerDisconnect(sender, (EventArgs)(object)args);
		};
	}

	public async Task Connect()
	{
		_webSocket.Connect();
		if (!(await SendClientMessage(new RequestServerInfo
		{
			ClientName = _client.Name,
			MessageVersion = 2u
		}) is ServerInfo serverInfo))
		{
			return;
		}
		Console.WriteLine(serverInfo.MaxPingTime);
		_pingTimeout = serverInfo.MaxPingTime;
		new Thread(Pings).Start();
		if (!(await SendClientMessage(new RequestDeviceList()) is DeviceList list))
		{
			return;
		}
		foreach (DeviceAdded device in list.Devices)
		{
			AddDevice(device);
		}
	}

	private void Pings()
	{
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Invalid comparison between Unknown and I4
		bool flag = _pingTimeout == 0;
		if (flag)
		{
			_pingTimeout = 20000u;
		}
		while ((int)_webSocket.ReadyState == 1)
		{
			if (flag)
			{
				_webSocket.Ping();
			}
			else
			{
				SendClientMessage(new Ping());
			}
			Thread.Sleep((int)_pingTimeout / 2);
		}
	}

	public void Disconnect()
	{
		_webSocket.Close();
	}

	private void RecieveMessage(object sender, MessageEventArgs e)
	{
		if (!e.IsText)
		{
			return;
		}
		List<Message> list = JsonConvert.DeserializeObject<List<Message>>(e.Data);
		foreach (Message item in list)
		{
			PropertyInfo[] properties = typeof(Message).GetProperties();
			foreach (PropertyInfo propertyInfo in properties)
			{
				if (propertyInfo.GetValue(item) is MessageBase aMsg)
				{
					CheckMessage(aMsg);
				}
			}
		}
	}

	public void Shutdown()
	{
		foreach (TaskCompletionSource<MessageBase> value in _waitingMsgs.Values)
		{
			value.TrySetException(new ButtplugConnectorException("Sorter has been destroyed with live tasks still in queue, most likely due to a disconnection."));
		}
		_webSocket.Close();
	}

	public Task<MessageBase> SendClientMessage(MessageBase aMsg)
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Expected O, but got Unknown
		uint key = (aMsg.Id = NextMsgId);
		TaskCompletionSource<MessageBase> taskCompletionSource = new TaskCompletionSource<MessageBase>();
		_waitingMsgs.TryAdd(key, taskCompletionSource);
		string text = JsonConvert.SerializeObject((object)new List<Message> { Message.From(aMsg) }, (Formatting)1, new JsonSerializerSettings
		{
			NullValueHandling = (NullValueHandling)1
		});
		_webSocket.Send(text);
		return taskCompletionSource.Task;
	}

	public void CheckMessage(MessageBase aMsg)
	{
		if (aMsg is Error error)
		{
			if (error.ErrorCode == Error.ErrorCodeEnum.ERROR_PING)
			{
				_client.OnPingTimeout(this, null);
			}
			_client.OnErrorReceived(this, new ButtplugExceptionEventArgs(ButtplugException.FromError(error)));
		}
		if (aMsg.Id == 0)
		{
			if (aMsg is DeviceAdded deviceAdded)
			{
				AddDevice(deviceAdded);
			}
			DeviceRemoved deviceRemoved = aMsg as DeviceRemoved;
			if (deviceRemoved != null)
			{
				ButtplugClientDevice buttplugClientDevice = _client.Devices.SingleOrDefault((ButtplugClientDevice x) => x.Index == deviceRemoved.DeviceIndex);
				if (buttplugClientDevice != null)
				{
					_client.OnDeviceRemoved(_client, new DeviceRemovedEventArgs(buttplugClientDevice));
				}
				else
				{
					_client.OnErrorReceived(this, new ButtplugExceptionEventArgs(new ButtplugDeviceException($"Cannot remove device index {deviceRemoved.DeviceIndex}, device not found.")));
				}
			}
			if (aMsg is ScanningFinished)
			{
				_client.OnScanningFinished(_client, new EventArgs());
			}
		}
		else
		{
			if (!_waitingMsgs.TryRemove(aMsg.Id, out var value))
			{
				throw new ButtplugMessageException("Message with non-matching ID received.");
			}
			if (aMsg is Error aMsg2)
			{
				value.SetException(ButtplugException.FromError(aMsg2));
			}
			else
			{
				value.SetResult(aMsg);
			}
		}
	}

	private void AddDevice(DeviceAdded deviceAdded)
	{
		if (_client.Devices.Any((ButtplugClientDevice x) => x.Index == deviceAdded.DeviceIndex))
		{
			_client.OnErrorReceived(this, new ButtplugExceptionEventArgs(new ButtplugDeviceException("A duplicate device index was received. This is most likely a bug, please file at https://github.com/buttplugio/buttplug-rs-ffi")));
		}
		else
		{
			_client.OnDeviceAdded(_client, new DeviceAddedEventArgs(new ButtplugClientDevice(this, deviceAdded.DeviceIndex, deviceAdded.DeviceName, deviceAdded.DeviceMessages)));
		}
	}
}
public class ButtplugPingException : ButtplugException
{
	public ButtplugPingException()
	{
	}

	public ButtplugPingException(string aMessage)
		: base(aMessage)
	{
	}

	public ButtplugPingException(string aMessage, Exception aInner)
		: base(aMessage, aInner)
	{
	}
}
public class ButtplugUnknownException : ButtplugException
{
	public ButtplugUnknownException()
	{
	}

	public ButtplugUnknownException(string aMessage)
		: base(aMessage)
	{
	}

	public ButtplugUnknownException(string aMessage, Exception aInner)
		: base(aMessage, aInner)
	{
	}
}
public class ButtplugWebsocketConnectorOptions
{
	public Uri NetworkAddress { get; set; }

	public ButtplugWebsocketConnectorOptions(Uri aAddress)
	{
		NetworkAddress = aAddress;
	}
}
public class DeviceAddedEventArgs
{
	public ButtplugClientDevice Device { get; }

	public DeviceAddedEventArgs(ButtplugClientDevice aDevice)
	{
		Device = aDevice;
	}
}
public class DeviceRemovedEventArgs
{
	public ButtplugClientDevice Device { get; }

	public DeviceRemovedEventArgs(ButtplugClientDevice aDevice)
	{
		Device = aDevice;
	}
}
public class Message
{
	public Ok Ok { get; set; }

	public Error Error { get; set; }

	public Ping Ping { get; set; }

	public RequestServerInfo RequestServerInfo { get; set; }

	public ServerInfo ServerInfo { get; set; }

	public StartScanning StartScanning { get; set; }

	public StopScanning StopScanning { get; set; }

	public ScanningFinished ScanningFinished { get; set; }

	public RequestDeviceList RequestDeviceList { get; set; }

	public DeviceList DeviceList { get; set; }

	public DeviceAdded DeviceAdded { get; set; }

	public DeviceRemoved DeviceRemoved { get; set; }

	public RawWriteCmd RawWriteCmd { get; set; }

	public RawReadCmd RawReadCmd { get; set; }

	public RawReading RawReading { get; set; }

	public RawSubscribeCmd RawSubscribeCmd { get; set; }

	public RawUnsubscribeCmd RawUnsubscribeCmd { get; set; }

	public StopDeviceCmd StopDeviceCmd { get; set; }

	public StopAllDevices StopAllDevices { get; set; }

	public VibrateCmd VibrateCmd { get; set; }

	public LinearCmd LinearCmd { get; set; }

	public RotateCmd RotateCmd { get; set; }

	public BatteryLevelCmd BatteryLevelCmd { get; set; }

	public BatteryLevelReading BatteryLevelReading { get; set; }

	public RSSILevelCmd RSSILevelCmd { get; set; }

	public RSSILevelReading RSSILevelReading { get; set; }

	public static Message From(MessageBase messageBase)
	{
		Message message = new Message();
		if (messageBase is Ping ping)
		{
			message.Ping = ping;
		}
		if (messageBase is RequestServerInfo requestServerInfo)
		{
			message.RequestServerInfo = requestServerInfo;
		}
		if (messageBase is StartScanning startScanning)
		{
			message.StartScanning = startScanning;
		}
		if (messageBase is StopScanning stopScanning)
		{
			message.StopScanning = stopScanning;
		}
		if (messageBase is RequestDeviceList requestDeviceList)
		{
			message.RequestDeviceList = requestDeviceList;
		}
		if (messageBase is RawWriteCmd rawWriteCmd)
		{
			message.RawWriteCmd = rawWriteCmd;
		}
		if (messageBase is RawReadCmd rawReadCmd)
		{
			message.RawReadCmd = rawReadCmd;
		}
		if (messageBase is RawSubscribeCmd rawSubscribeCmd)
		{
			message.RawSubscribeCmd = rawSubscribeCmd;
		}
		if (messageBase is RawUnsubscribeCmd rawUnsubscribeCmd)
		{
			message.RawUnsubscribeCmd = rawUnsubscribeCmd;
		}
		if (messageBase is StopDeviceCmd stopDeviceCmd)
		{
			message.StopDeviceCmd = stopDeviceCmd;
		}
		if (messageBase is StopAllDevices stopAllDevices)
		{
			message.StopAllDevices = stopAllDevices;
		}
		if (messageBase is VibrateCmd vibrateCmd)
		{
			message.VibrateCmd = vibrateCmd;
		}
		if (messageBase is LinearCmd linearCmd)
		{
			message.LinearCmd = linearCmd;
		}
		if (messageBase is RotateCmd rotateCmd)
		{
			message.RotateCmd = rotateCmd;
		}
		if (messageBase is BatteryLevelCmd batteryLevelCmd)
		{
			message.BatteryLevelCmd = batteryLevelCmd;
		}
		if (messageBase is RSSILevelCmd rSSILevelCmd)
		{
			message.RSSILevelCmd = rSSILevelCmd;
		}
		return message;
	}
}
public class MessageBase
{
	public uint Id { get; set; }
}
public class Ok : MessageBase
{
}
public class Error : MessageBase
{
	public enum ErrorCodeEnum
	{
		ERROR_UNKNOWN,
		ERROR_INIT,
		ERROR_PING,
		ERROR_MSG,
		ERROR_DEVICE
	}

	public string ErrorMessage { get; set; }

	public ErrorCodeEnum ErrorCode { get; set; }
}
public class Ping : MessageBase
{
}
public class ServerInfo : MessageBase
{
	public string ServerName { get; set; }

	public uint MessageVersion { get; set; }

	public uint MaxPingTime { get; set; }
}
public class RequestServerInfo : MessageBase
{
	public string ClientName { get; set; }

	public uint MessageVersion { get; set; }
}
public class StartScanning : MessageBase
{
}
public class StopScanning : MessageBase
{
}
public class ScanningFinished : MessageBase
{
}
public class RequestDeviceList : MessageBase
{
}
public class DeviceList : MessageBase
{
	public List<DeviceAdded> Devices { get; set; }
}
public class DeviceAdded : MessageBase
{
	public uint DeviceIndex { get; set; }

	public string DeviceName { get; set; }

	public Dictionary<string, DeviceMessagesDetails> DeviceMessages { get; set; }
}
public class DeviceMessages
{
	public const string VibrateCmd = "VibrateCmd";

	public const string BatteryLevelCmd = "BatteryLevelCmd";

	public const string StopDeviceCmd = "StopDeviceCmd";

	public const string LinearCmd = "LinearCmd";

	public const string RotateCmd = "RotateCmd";
}
public class DeviceMessagesDetails
{
	public uint FeatureCount { get; set; }

	public List<uint> StepCount { get; set; }

	public List<string> Endpoints { get; set; }

	public List<uint> MaxDuration { get; set; }
}
public class DeviceRemoved : MessageBase
{
	public uint DeviceIndex { get; set; }
}
public class RawWriteCmd : MessageBase
{
	public uint DeviceIndex { get; set; }

	public string Endpoint { get; set; }

	public List<int> Data { get; set; }

	public bool WriteWithResponse { get; set; }
}
public class RawReadCmd : MessageBase
{
	public uint DeviceIndex { get; set; }

	public string Endpoint { get; set; }

	public uint ExpectedLength { get; set; }

	public bool WaitForData { get; set; }
}
public class RawReading : MessageBase
{
	public uint DeviceIndex { get; set; }

	public string Endpoint { get; set; }

	public List<int> Data { get; set; }
}
public class RawSubscribeCmd : MessageBase
{
	public uint DeviceIndex { get; set; }

	public string Endpoint { get; set; }
}
public class RawUnsubscribeCmd : MessageBase
{
	public uint DeviceIndex { get; set; }

	public string Endpoint { get; set; }
}
public class StopDeviceCmd : MessageBase
{
	public uint DeviceIndex { get; set; }
}
public class StopAllDevices : MessageBase
{
}
public class VibrateCmd : MessageBase
{
	public uint DeviceIndex { get; set; }

	public List<VibrateSpeed> Speeds { get; set; }
}
public class VibrateSpeed
{
	public uint Index { get; set; }

	public double Speed { get; set; }
}
public class LinearCmd : MessageBase
{
	public uint DeviceIndex { get; set; }

	public List<LinearVector> Vectors { get; set; }
}
public class LinearVector
{
	public uint Index { get; set; }

	public uint Duration { get; set; }

	public double Position { get; set; }
}
public class RotateCmd : MessageBase
{
	public uint DeviceIndex { get; set; }

	public List<Rotations> Rotations { get; set; }
}
public class Rotations
{
	public uint Index { get; set; }

	public double Speed { get; set; }

	public bool Clockwise { get; set; }
}
public class BatteryLevelCmd : MessageBase
{
	public uint DeviceIndex { get; set; }
}
public class BatteryLevelReading : MessageBase
{
	public uint DeviceIndex { get; set; }

	public double BatteryLevel { get; set; }
}
public class RSSILevelCmd : MessageBase
{
	public uint DeviceIndex { get; set; }
}
public class RSSILevelReading : MessageBase
{
	public uint DeviceIndex { get; set; }

	public int RSSILevel { get; set; }
}