Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RumbleRain v0.5.2
plugins/RumbleRain/RumbleRain.dll
Decompiled 5 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Buttplug.Client; using Buttplug.Core; using R2API.Utils; using RiskOfOptions; using RiskOfOptions.OptionConfigs; using RiskOfOptions.Options; using RoR2; using UnityEngine; using UnityEngine.Events; [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("quasikyo")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.2.0")] [assembly: AssemblyInformationalVersion("0.5.2+d6c4e0c83c8b138b944e8e96fbd83ba91c8c77cf")] [assembly: AssemblyProduct("RumbleRain")] [assembly: AssemblyTitle("RumbleRain")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/quasikyo/ror2-mods/tree/main/RumbleRain")] [assembly: AssemblyVersion("0.0.0.0")] namespace RumbleRain; internal static class ConfigManager { internal static ConfigFile VibrationConfigFile { get; set; } internal static ConfigEntry<string> ServerUri { get; set; } internal static ConfigEntry<float> PollingRateSeconds { get; set; } internal static ConfigEntry<bool> IsRewardingEnabled { get; set; } internal static ConfigEntry<bool> IsPunishingEnabled { get; set; } internal static ConfigEntry<bool> IsMinionRewardingEnabled { get; set; } internal static ConfigEntry<bool> IsMinionPunishingEnabled { get; set; } internal static ConfigEntry<float> DamageDealtBaseVibrationIntensity { get; set; } internal static ConfigEntry<float> DamageReceivedBaseVibrationIntensity { get; set; } internal static ConfigEntry<float> MaximumVibrationIntensity { get; set; } internal static ConfigEntry<float> BaseVibrationDurationSeconds { get; set; } internal static ConfigEntry<float> MaximumVibrationDurationSeconds { get; set; } internal static ConfigEntry<VibrationInfoProvider.VibrationBehavior> VibrationBehavior { get; set; } internal static ConfigEntry<bool> AllowExcessDamage { get; set; } internal static ConfigEntry<KeyboardShortcut> ToggleVibrationKeybind { get; set; } static ConfigManager() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Expected O, but got Unknown //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Expected O, but got Unknown //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Expected O, but got Unknown //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Expected O, but got Unknown //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Expected O, but got Unknown //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Expected O, but got Unknown //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Expected O, but got Unknown //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Expected O, but got Unknown //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Expected O, but got Unknown //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Expected O, but got Unknown //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Expected O, but got Unknown //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Expected O, but got Unknown //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Expected O, but got Unknown //IL_038c: Unknown result type (might be due to invalid IL or missing references) //IL_0396: Expected O, but got Unknown //IL_03c3: Unknown result type (might be due to invalid IL or missing references) //IL_03cd: Expected O, but got Unknown //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_03f2: Unknown result type (might be due to invalid IL or missing references) //IL_0402: Expected O, but got Unknown //IL_03fd: Unknown result type (might be due to invalid IL or missing references) //IL_0407: Expected O, but got Unknown //IL_0445: Unknown result type (might be due to invalid IL or missing references) //IL_044f: Expected O, but got Unknown //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_047d: Expected O, but got Unknown //IL_04a2: Unknown result type (might be due to invalid IL or missing references) //IL_04bb: Unknown result type (might be due to invalid IL or missing references) //IL_04c5: Expected O, but got Unknown VibrationConfigFile = new ConfigFile(Paths.ConfigPath + "\\RumbleRain.cfg", true); ModSettingsManager.SetModDescription("Vibrates BPio-capable devices in response to in-game damage events."); ServerUri = VibrationConfigFile.Bind<string>("Devices", "Server Uri", "ws://localhost:12345", "URI of the Intiface server."); ModSettingsManager.AddOption((BaseOption)new StringInputFieldOption(ServerUri, true)); PollingRateSeconds = VibrationConfigFile.Bind<float>("Devices", "Polling Rate Seconds", 0.2f, new ConfigDescription("How often to update vibrations.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), Array.Empty<object>())); ModSettingsManager.AddOption((BaseOption)new StepSliderOption(PollingRateSeconds, new StepSliderConfig { min = 0.1f, max = 3f, increment = 0.1f })); ModSettingsManager.AddOption((BaseOption)new GenericButtonOption("Connect Devices", "Devices", "Attempts to connect to the Intiface server.", "Connect", (UnityAction)delegate { RumbleRain.DeviceManager.ConnectDevices(); })); IsRewardingEnabled = VibrationConfigFile.Bind<bool>("Activated By", "Dealing Damage", false, "Receive vibrations when dealing damage."); ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(IsRewardingEnabled)); IsPunishingEnabled = VibrationConfigFile.Bind<bool>("Activated By", "Receiving Damage", true, "Receive vibrations when receiving damage."); ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(IsPunishingEnabled)); IsMinionRewardingEnabled = VibrationConfigFile.Bind<bool>("Activated By", "Minions Dealing Damage", false, "Receive vibrations when your minions (drones, turrets, etc.) deal damage."); ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(IsMinionRewardingEnabled)); IsMinionPunishingEnabled = VibrationConfigFile.Bind<bool>("Activated By", "Minions Receiving Damage", false, "Receive vibrations when your minions (drones, turrets, etc.) receive damage."); ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(IsMinionPunishingEnabled)); DamageDealtBaseVibrationIntensity = VibrationConfigFile.Bind<float>("Vibration Values", "Damage Dealt Base Vibration Intensity", 0.05f, new ConfigDescription("Base vibration intensity percentage for vibrations generated by dealing damage. Multiplied by (damageDealt / maxHealthOfDamagedEntity).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); ModSettingsManager.AddOption((BaseOption)new StepSliderOption(DamageDealtBaseVibrationIntensity, new StepSliderConfig { min = 0f, max = 1f, increment = 0.01f })); DamageReceivedBaseVibrationIntensity = VibrationConfigFile.Bind<float>("Vibration Values", "Damage Received Base Vibration Intensity", 1f, new ConfigDescription("Base vibration intensity percentage for vibrations generated by receiving damage. Multiplied by (damageDealt / maxHealthOfDamagedEntity).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); ModSettingsManager.AddOption((BaseOption)new StepSliderOption(DamageReceivedBaseVibrationIntensity, new StepSliderConfig { min = 0f, max = 1f, increment = 0.01f })); MaximumVibrationIntensity = VibrationConfigFile.Bind<float>("Vibration Values", "Maximum Vibration Intensity", 1f, new ConfigDescription("Max percentage of total vibration intensity expressed as a decimal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); ModSettingsManager.AddOption((BaseOption)new StepSliderOption(MaximumVibrationIntensity, new StepSliderConfig { min = 0f, max = 1f, increment = 0.01f })); MaximumVibrationDurationSeconds = VibrationConfigFile.Bind<float>("Vibration Values", "Maximum Vibration Duration Seconds", 20f, new ConfigDescription("The longest amount of seconds vibrations will last for assuming no new vibrations are generated.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, (float)TimeSpan.FromMinutes(5.0).TotalSeconds), Array.Empty<object>())); ModSettingsManager.AddOption((BaseOption)new StepSliderOption(MaximumVibrationDurationSeconds, new StepSliderConfig { min = 0f, max = (float)TimeSpan.FromMinutes(5.0).TotalSeconds, increment = 0.5f })); BaseVibrationDurationSeconds = VibrationConfigFile.Bind<float>("Vibration Values", "Base Vibration Duration Seconds", 5f, new ConfigDescription("The base vibration duration in seconds to add when damage is dealt or received. Multiplied by (damageDealt / maxHealthOfDamagedEntity).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); ModSettingsManager.AddOption((BaseOption)new StepSliderOption(BaseVibrationDurationSeconds, new StepSliderConfig { min = 0f, max = 100f, increment = 0.5f })); VibrationBehavior = VibrationConfigFile.Bind<VibrationInfoProvider.VibrationBehavior>("Vibration Behavior", "Vibration Behavior", VibrationInfoProvider.VibrationBehavior.AdditiveWithLinearDecay, "How vibrations should handle new information and evolve over time."); VibrationBehavior.SettingChanged += delegate { RumbleRain.DeviceManager.InfoProvider = VibrationInfoProvider.From(VibrationBehavior.Value); }; ModSettingsManager.AddOption((BaseOption)new ChoiceOption((ConfigEntryBase)(object)VibrationBehavior)); AllowExcessDamage = VibrationConfigFile.Bind<bool>("Vibration Behavior", "Allow For Excess Damage", false, "Allow for excess damage dealt over an entity's max combined health to affect vibrations."); ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(AllowExcessDamage)); ConfigFile vibrationConfigFile = VibrationConfigFile; KeyCode[] array = new KeyCode[3]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); ToggleVibrationKeybind = vibrationConfigFile.Bind<KeyboardShortcut>("Keybinds", "Toggle Devices", new KeyboardShortcut((KeyCode)306, (KeyCode[])(object)array), "This keybind will toggle devices to be active or not. See the Properties section at https://docs.unity3d.com/ScriptReference/KeyCode.html for expected values."); ModSettingsManager.AddOption((BaseOption)new KeyBindOption(ToggleVibrationKeybind)); } } internal class DeviceManager { private enum DeviceState { Active, Inactive, Paused } [CompilerGenerated] private sealed class <PollVibrations>d__19 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public DeviceManager <>4__this; private float <secondsToWaitFor>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <PollVibrations>d__19(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int num = <>1__state; DeviceManager deviceManager = <>4__this; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; if (deviceManager.ButtplugClient.Connected && deviceManager.State == DeviceState.Active) { if (deviceManager.InfoProvider.Info.IsImpotent() && deviceManager.State == DeviceState.Active) { deviceManager.StopConnectedDevices(); } else { deviceManager.InfoProvider.UpdateVibrationInfo(TimeSpan.FromSeconds(<secondsToWaitFor>5__2)); deviceManager.VibrateConnectedDevices(deviceManager.InfoProvider.Info.Intensity); } } } else { <>1__state = -1; Log.Info($"Beginning polling every {ConfigManager.PollingRateSeconds.Value} seconds"); } <secondsToWaitFor>5__2 = ConfigManager.PollingRateSeconds.Value; <>2__current = (object)new WaitForSeconds(<secondsToWaitFor>5__2); <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private DeviceState _state; private VibrationInfoProvider _infoProvider; private DeviceState State { get { return _state; } set { _state = value; } } private ButtplugClient ButtplugClient { get; set; } private List<ButtplugClientDevice> ConnectedDevices { get; set; } internal VibrationInfoProvider InfoProvider { get { return _infoProvider; } set { StopConnectedDevices(); _infoProvider = value; } } public DeviceManager(VibrationInfoProvider vibrationInfoProvider, string clientName) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown State = DeviceState.Inactive; ConnectedDevices = new List<ButtplugClientDevice>(); ButtplugClient = new ButtplugClient(clientName); Log.Info("BP client created for " + clientName); ButtplugClient.DeviceAdded += HandleDeviceAdded; ButtplugClient.DeviceRemoved += HandleDeviceRemoved; InfoProvider = vibrationInfoProvider; } public async void ConnectDevices() { if (ButtplugClient.Connected) { return; } try { Log.Info("Attempting to connect to Intiface server at \"" + ConfigManager.ServerUri.Value + "\""); await ButtplugClient.ConnectAsync((IButtplugClientConnector)new ButtplugWebsocketConnector(new Uri(ConfigManager.ServerUri.Value)), default(CancellationToken)); Log.Info("Connection successful. Beginning scan for devices"); await ButtplugClient.StartScanningAsync(default(CancellationToken)); } catch (ButtplugException) { Log.Error("Attempt to connect to devices failed. Ensure Intiface is running and attempt to reconnect from the 'Devices' section in the mod's in-game settings."); } } [IteratorStateMachine(typeof(<PollVibrations>d__19))] internal IEnumerator PollVibrations() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <PollVibrations>d__19(0) { <>4__this = this }; } internal void SendVibrationInfo(VibrationInfo vibrationInfo) { if (State != DeviceState.Paused) { InfoProvider.Input(vibrationInfo); VibrateConnectedDevices(InfoProvider.Info.Intensity); } } private void VibrateConnectedDevices(float intensity) { State = DeviceState.Active; ConnectedDevices.ForEach(async delegate(ButtplugClientDevice device) { await device.VibrateAsync((double)intensity); }); } private void StopConnectedDevices(DeviceState newState = DeviceState.Inactive) { if (newState == DeviceState.Active) { throw new ArgumentException(string.Format("{0}={1} is invalid. Expecting {2} or {3}.", "newState", newState, DeviceState.Inactive, DeviceState.Active)); } State = newState; ConnectedDevices.ForEach(async delegate(ButtplugClientDevice device) { await device.Stop(); }); } internal void ToggleConnectedDevices() { switch (State) { case DeviceState.Active: case DeviceState.Inactive: StopConnectedDevices(DeviceState.Paused); break; case DeviceState.Paused: VibrateConnectedDevices(InfoProvider.Info.Intensity); break; } } internal void CleanUp() { StopConnectedDevices(); InfoProvider.Reset(); } private void HandleDeviceAdded(object sender, DeviceAddedEventArgs args) { if (!IsVibratableDevice(args.Device)) { Log.Info(args.Device.Name + " was detected but ignored due to it not being vibratable."); return; } Log.Info(args.Device.Name + " connected to client " + ButtplugClient.Name); ConnectedDevices.Add(args.Device); } private void HandleDeviceRemoved(object sender, DeviceRemovedEventArgs args) { if (IsVibratableDevice(args.Device)) { Log.Info(args.Device.Name + " disconnected from client " + ButtplugClient.Name); ConnectedDevices.Remove(args.Device); } } private bool IsVibratableDevice(ButtplugClientDevice device) { return device.VibrateAttributes.Count > 0; } } internal static class Log { private static ManualLogSource Logger { get; set; } internal static void Init(ManualLogSource logger) { Logger = logger; } [Conditional("DEBUG")] internal static void Debug(object data) { Logger.LogDebug(data); } internal static void Info(object data) { Logger.LogInfo(data); } internal static void Message(object data) { Logger.LogMessage(data); } internal static void Warning(object data) { Logger.LogWarning(data); } internal static void Error(object data) { Logger.LogError(data); } internal static void Fatal(object data) { Logger.LogFatal(data); } } [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInPlugin("quasikyo.RumbleRain", "RumbleRain", "0.5.2")] public class RumbleRain : BaseUnityPlugin { internal static DeviceManager DeviceManager { get; private set; } public void Awake() { Log.Init(((BaseUnityPlugin)this).Logger); Log.Info("Performing setup for $RumbleRain"); DeviceManager = new DeviceManager(VibrationInfoProvider.From(ConfigManager.VibrationBehavior.Value), "RumbleRain"); DeviceManager.ConnectDevices(); Run.onRunStartGlobal += delegate { ((MonoBehaviour)this).StartCoroutine(DeviceManager.PollVibrations()); }; Run.onRunDestroyGlobal += delegate { ((MonoBehaviour)this).StopAllCoroutines(); DeviceManager.CleanUp(); }; GlobalEventManager.onClientDamageNotified += VibrateDevicesOnDamage; } public void Update() { //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) KeyboardShortcut value = ConfigManager.ToggleVibrationKeybind.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { DeviceManager.ToggleConnectedDevices(); } } private void VibrateDevicesOnDamage(DamageDealtMessage damageMessage) { CharacterMaster cachedMaster = LocalUserManager.GetFirstLocalUser().cachedMaster; CharacterBody body = cachedMaster.GetBody(); GameObject victim = damageMessage.victim; CharacterBody val = ((victim != null) ? victim.GetComponent<CharacterBody>() : null); GameObject attacker = damageMessage.attacker; CharacterBody val2 = ((attacker != null) ? attacker.GetComponent<CharacterBody>() : null); if (!((Object)(object)val == (Object)null)) { float fullCombinedHealth = val.healthComponent.fullCombinedHealth; float num = damageMessage.damage / fullCombinedHealth; if (!ConfigManager.AllowExcessDamage.Value) { num = Math.Min(num, 1f); } bool flag = (Object)(object)body == (Object)(object)val2; bool num2 = (Object)(object)body == (Object)(object)val; object obj; if (val2 == null) { obj = null; } else { CharacterMaster master = val2.master; obj = ((master != null) ? master.minionOwnership.ownerMaster : null); } bool flag2 = (Object)obj == (Object)(object)cachedMaster; CharacterMaster master2 = val.master; bool flag3 = (Object)(object)((master2 != null) ? master2.minionOwnership.ownerMaster : null) == (Object)(object)cachedMaster; VibrationInfo vibrationInfo = new VibrationInfo(TimeSpan.FromSeconds(ConfigManager.BaseVibrationDurationSeconds.Value * num)); if ((flag && ConfigManager.IsRewardingEnabled.Value) || (flag2 && ConfigManager.IsMinionRewardingEnabled.Value)) { vibrationInfo.Intensity = ConfigManager.DamageDealtBaseVibrationIntensity.Value * num; DeviceManager.SendVibrationInfo(vibrationInfo); } if ((num2 && ConfigManager.IsPunishingEnabled.Value) || (flag3 && ConfigManager.IsMinionPunishingEnabled.Value)) { vibrationInfo.Intensity = ConfigManager.DamageReceivedBaseVibrationIntensity.Value * num; DeviceManager.SendVibrationInfo(vibrationInfo); } } } } internal class VibrationInfo { private float _intensity; private TimeSpan _duration; private float _intensitySnapshot; private TimeSpan _durationSnapshot; protected internal float Intensity { get { return _intensity; } set { _intensity = Clamp(value, 0f, ConfigManager.MaximumVibrationIntensity.Value); } } protected internal TimeSpan Duration { get { return _duration; } set { _duration = Clamp(value, TimeSpan.Zero, TimeSpan.FromSeconds(ConfigManager.MaximumVibrationDurationSeconds.Value)); } } protected internal float IntensitySnapshot { get { return _intensitySnapshot; } set { _intensitySnapshot = Clamp(value, 0f, ConfigManager.MaximumVibrationIntensity.Value); } } protected internal TimeSpan DurationSnapshot { get { return _durationSnapshot; } set { _durationSnapshot = Clamp(value, TimeSpan.Zero, TimeSpan.FromSeconds(ConfigManager.MaximumVibrationDurationSeconds.Value)); } } internal VibrationInfo() : this(0f, TimeSpan.Zero) { } internal VibrationInfo(float intensity) : this(intensity, TimeSpan.Zero) { } internal VibrationInfo(TimeSpan duration) : this(0f, duration) { } internal VibrationInfo(float intensity, TimeSpan duration) { Intensity = intensity; Duration = duration; SnapshotValues(); } private T Clamp<T>(T value, T inclusiveMinimum, T inclusiveMaximum) where T : IComparable<T> { if (value.CompareTo(inclusiveMinimum) < 0) { return inclusiveMinimum; } if (value.CompareTo(inclusiveMaximum) > 0) { return inclusiveMaximum; } return value; } public static VibrationInfo operator +(VibrationInfo a, VibrationInfo b) { return new VibrationInfo(a.Intensity + b.Intensity, a.Duration + b.Duration); } protected internal void SnapshotValues() { IntensitySnapshot = Intensity; DurationSnapshot = Duration; } protected internal bool IsImpotent() { if (!((double)Intensity < 0.01)) { return Duration < TimeSpan.FromSeconds(1.0); } return true; } public override string ToString() { return $"{base.ToString()}(intensity={Intensity}, duration={Duration.TotalSeconds} seconds)"; } } internal abstract class VibrationInfoProvider { internal enum VibrationBehavior { AdditiveWithLinearDecay, AdditiveWithExponentialDecay } protected internal VibrationInfo Info { get; set; } protected internal VibrationInfoProvider() { Info = new VibrationInfo(); } protected internal static VibrationInfoProvider From(VibrationBehavior vibrationBehavior) { return vibrationBehavior switch { VibrationBehavior.AdditiveWithLinearDecay => new AdditiveInfoProviderWithLinearDecay(), VibrationBehavior.AdditiveWithExponentialDecay => new AdditiveInfoProvierWithExponentialDecay(), _ => throw new ArgumentException($"{vibrationBehavior} is not a valid behavior."), }; } protected internal abstract void Input(VibrationInfo newVibrationInfo); protected internal abstract void UpdateVibrationInfo(TimeSpan timeSinceLastUpdate); protected internal virtual void Reset() { Info = new VibrationInfo(); } } internal class AdditiveInfoProviderWithLinearDecay : VibrationInfoProvider { protected internal override void Input(VibrationInfo newVibrationInfo) { base.Info += newVibrationInfo; base.Info.SnapshotValues(); } protected internal override void UpdateVibrationInfo(TimeSpan timeSinceLastUpdate) { base.Info.Duration -= timeSinceLastUpdate; double num = base.Info.Duration.TotalSeconds / base.Info.DurationSnapshot.TotalSeconds; base.Info.Intensity = base.Info.IntensitySnapshot * (float)num; } } internal class AdditiveInfoProvierWithExponentialDecay : VibrationInfoProvider { protected internal override void Input(VibrationInfo newVibrationInfo) { base.Info += newVibrationInfo; base.Info.SnapshotValues(); } protected internal override void UpdateVibrationInfo(TimeSpan timeSinceLastUpdate) { base.Info.Duration -= timeSinceLastUpdate; double num = base.Info.Duration.TotalSeconds / base.Info.DurationSnapshot.TotalSeconds; base.Info.Intensity *= (float)num; } } public static class MyPluginInfo { public const string PLUGIN_GUID = "quasikyo.RumbleRain"; public const string PLUGIN_NAME = "RumbleRain"; public const string PLUGIN_VERSION = "0.5.2"; }
plugins/RumbleRain/Buttplug.dll
Decompiled 5 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.WebSockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using Buttplug.Core; using Buttplug.Core.Messages; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("Buttplug.Test")] [assembly: InternalsVisibleTo("Buttplug.Client.Test")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Nonpolynomial Labs, LLC")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright Nonpolynomial Labs, LLC")] [assembly: AssemblyDescription("Buttplug Sex Toy Control Library. Contains Core (messages, errors, etc), Client, and Websocket Connector components")] [assembly: AssemblyFileVersion("4.0.0.0")] [assembly: AssemblyInformationalVersion("4.0.0+41a6e2363781583c9c9f475b1544b7ebee02bd52")] [assembly: AssemblyProduct("Buttplug")] [assembly: AssemblyTitle("Buttplug")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/buttplugio/buttplug-csharp")] [assembly: AssemblyVersion("4.0.0.0")] namespace Buttplug.Core { public static class ButtplugConsts { public const uint SystemMsgId = 0u; public const uint DefaultMsgId = 1u; public const uint CurrentSpecVersion = 3u; } public class ButtplugDeviceException : ButtplugException { public ButtplugDeviceException(string message, uint id = 0u, Exception inner = null) : base(message, Error.ErrorClass.ERROR_DEVICE, id, inner) { } } public class ButtplugException : Exception { public Error ButtplugErrorMessage { get; } public static ButtplugException FromError(Error msg) { return msg.ErrorCode switch { Error.ErrorClass.ERROR_DEVICE => new ButtplugDeviceException(msg.ErrorMessage, msg.Id), Error.ErrorClass.ERROR_INIT => new ButtplugHandshakeException(msg.ErrorMessage, msg.Id), Error.ErrorClass.ERROR_MSG => new ButtplugMessageException(msg.ErrorMessage, msg.Id), Error.ErrorClass.ERROR_PING => new ButtplugPingException(msg.ErrorMessage, msg.Id), Error.ErrorClass.ERROR_UNKNOWN => new ButtplugException(msg.ErrorMessage, msg.Id), _ => new ButtplugException(msg.ErrorMessage, msg.Id), }; } public ButtplugException(string message, uint id = 0u, Exception inner = null) : this(message, Error.ErrorClass.ERROR_UNKNOWN, id, inner) { } public ButtplugException(string message, Error.ErrorClass err = Error.ErrorClass.ERROR_UNKNOWN, uint id = 0u, Exception inner = null) : base(message, inner) { ButtplugErrorMessage = new Error(message, err, id); } } public class ButtplugExceptionEventArgs : EventArgs { public ButtplugException Exception { get; } public ButtplugExceptionEventArgs(ButtplugException ex) { Exception = ex; } } public class ButtplugHandshakeException : ButtplugException { public ButtplugHandshakeException(string message, uint id = 0u, Exception inner = null) : base(message, Error.ErrorClass.ERROR_INIT, id, inner) { } } public class ButtplugJsonMessageParser { private readonly Dictionary<string, Type> _messageTypes; private readonly JsonSerializer _serializer; public ButtplugJsonMessageParser() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown _serializer = new JsonSerializer { MissingMemberHandling = (MissingMemberHandling)1 }; _messageTypes = new Dictionary<string, Type>(); foreach (Type allMessageType in ButtplugUtils.GetAllMessageTypes()) { _messageTypes.Add(allMessageType.Name, allMessageType); } if (!_messageTypes.Any()) { throw new ButtplugMessageException("No message types available."); } } public IEnumerable<ButtplugMessage> Deserialize(string jsonMsg) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0031: Expected O, but got Unknown //IL_005b: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) JsonTextReader val = new JsonTextReader((TextReader)new StringReader(jsonMsg)) { CloseInput = false, SupportMultipleContent = true }; List<ButtplugMessage> list = new List<ButtplugMessage>(); while (true) { try { if (!((JsonReader)val).Read()) { return list; } } catch (JsonReaderException val2) { JsonReaderException val3 = val2; throw new ButtplugMessageException("Not valid JSON: " + jsonMsg + " - " + ((Exception)(object)val3).Message); } JArray val4; try { val4 = JArray.Load((JsonReader)(object)val); } catch (JsonReaderException val5) { JsonReaderException val6 = val5; throw new ButtplugMessageException("Not valid JSON: " + jsonMsg + " - " + ((Exception)(object)val6).Message); } foreach (JObject item in ((JToken)val4).Children<JObject>()) { string name = item.Properties().First().Name; if (!_messageTypes.ContainsKey(name)) { throw new ButtplugMessageException(name + " is not a valid message class"); } list.Add(DeserializeAs(item, _messageTypes[name])); } } } private ButtplugMessage DeserializeAs(JObject obj, Type msgType) { //IL_00c0: Expected O, but got Unknown if (!msgType.IsSubclassOf(typeof(ButtplugMessage))) { throw new ButtplugMessageException("Type " + msgType.Name + " is not a subclass of ButtplugMessage"); } if (msgType.Namespace != "Buttplug.Core.Messages") { throw new ButtplugMessageException("Type " + msgType.Name + " (" + msgType.Namespace + ") is not in the namespace of Buttplug.Core.Messages"); } string name = ButtplugMessage.GetName(msgType); try { return (ButtplugMessage)((JToken)Extensions.Value<JObject>((IEnumerable<JToken>)obj[name])).ToObject(msgType, _serializer); } catch (InvalidCastException ex) { throw new ButtplugMessageException($"Could not create message for JSON {obj}: {ex.Message}"); } catch (JsonSerializationException val) { JsonSerializationException val2 = val; throw new ButtplugMessageException($"Could not create message for JSON {obj}: {((Exception)(object)val2).Message}"); } } public string Serialize(ButtplugMessage msg) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if (msg.GetType().Namespace != "Buttplug.Core.Messages") { throw new ButtplugMessageException("Type " + msg.GetType().Name + " (" + msg.GetType().Namespace + ") is not in the namespace of Buttplug.Core.Messages"); } JObject val = ButtplugMessageToJObject(msg); if (val == null) { throw new ButtplugMessageException("Message cannot be converted to JSON.", msg.Id); } JArray val2 = new JArray(); val2.Add((JToken)(object)val); return ((JToken)val2).ToString((Formatting)0, Array.Empty<JsonConverter>()); } public string Serialize(IEnumerable<ButtplugMessage> msgs) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown JArray val = new JArray(); foreach (ButtplugMessage msg in msgs) { JObject val2 = ButtplugMessageToJObject(msg); if (val2 != null) { val.Add((JToken)(object)val2); } } if (!((IEnumerable<JToken>)val).Any()) { throw new ButtplugMessageException("No messages serialized."); } return ((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>()); } private JObject ButtplugMessageToJObject(ButtplugMessage msg) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown return new JObject((object)new JProperty(msg.Name, (object)JObject.FromObject((object)msg))); } } public class ButtplugMessageException : ButtplugException { public ButtplugMessageException(string message, uint id = 0u, Exception inner = null) : base(message, Error.ErrorClass.ERROR_MSG, id, inner) { } } public class ButtplugPingException : ButtplugException { public ButtplugPingException(string message, uint id = 0u, Exception inner = null) : base(message, Error.ErrorClass.ERROR_PING, id, inner) { } } public static class ButtplugUtils { public static IEnumerable<Type> GetAllMessageTypes() { IEnumerable<Type> enumerable; try { enumerable = Assembly.GetAssembly(typeof(ButtplugMessage))?.GetTypes(); } catch (ReflectionTypeLoadException ex) { enumerable = ex.Types; } return (enumerable ?? throw new InvalidOperationException()).Where((Type type) => type != null && type.IsClass && type.IsSubclassOf(typeof(ButtplugMessage)) && type != typeof(ButtplugDeviceMessage)); } [DebuggerStepThrough] public static void ArgumentNotNull(object argument, string argumentName) { if (argument == null) { throw new ArgumentNullException(argumentName); } } public static Type GetMessageType(string messageName) { return Type.GetType("Buttplug.Core.Messages." + messageName); } } } namespace Buttplug.Core.Messages { public class ButtplugDeviceMessage : ButtplugMessage { [JsonProperty(/*Could not decode attribute arguments.*/)] public uint DeviceIndex { get; set; } public ButtplugDeviceMessage(uint id = 1u, uint deviceIndex = uint.MaxValue) : base(id) { DeviceIndex = deviceIndex; } } public abstract class ButtplugMessage { private static readonly Dictionary<Type, ButtplugMessageMetadata> _metadataCache = new Dictionary<Type, ButtplugMessageMetadata>(); [JsonProperty(/*Could not decode attribute arguments.*/)] public uint Id { get; set; } [JsonIgnore] public string Name => GetName(GetType()); protected ButtplugMessage(uint id) { Id = id; } private static T GetMessageAttribute<T>(Type msgType, Func<ButtplugMessageMetadata, T> func) { ButtplugUtils.ArgumentNotNull(msgType, "msgType"); ButtplugUtils.ArgumentNotNull(func, "func"); if (!msgType.IsSubclassOf(typeof(ButtplugMessage))) { throw new ArgumentException("Argument " + msgType.Name + " must be a subclass of ButtplugMessage"); } if (_metadataCache.ContainsKey(msgType)) { return func(_metadataCache[msgType]); } Attribute[] customAttributes = Attribute.GetCustomAttributes(msgType); for (int i = 0; i < customAttributes.Length; i++) { if (customAttributes[i] is ButtplugMessageMetadata buttplugMessageMetadata) { _metadataCache[msgType] = buttplugMessageMetadata; return func(buttplugMessageMetadata); } } throw new ArgumentException($"Type {msgType} does not have ButtplugMessageMetadata Attributes"); } public static string GetName(Type msgType) { return GetMessageAttribute(msgType, (ButtplugMessageMetadata md) => md.Name); } } public interface IButtplugMessageOutgoingOnly { } public interface IButtplugDeviceInfoMessage { string DeviceName { get; } uint DeviceIndex { get; } DeviceMessageAttributes DeviceMessages { get; } string DeviceDisplayName { get; } uint DeviceMessageTimingGap { get; } } [AttributeUsage(AttributeTargets.Class)] public class ButtplugMessageMetadata : Attribute { public string Name { get; } public ButtplugMessageMetadata(string name) { Name = name; } } [JsonConverter(typeof(StringEnumConverter))] public enum ActuatorType { [EnumMember(Value = "Unknown")] Unknown, [EnumMember(Value = "Vibrate")] Vibrate, [EnumMember(Value = "Rotate")] Rotate, [EnumMember(Value = "Oscillate")] Oscillate, [EnumMember(Value = "Constrict")] Constrict, [EnumMember(Value = "Inflate")] Inflate, [EnumMember(Value = "Position")] Position } [JsonConverter(typeof(StringEnumConverter))] public enum SensorType { [EnumMember(Value = "Unknown")] Unknown, [EnumMember(Value = "Battery")] Battery, [EnumMember(Value = "RSSI")] RSSI, [EnumMember(Value = "Button")] Button, [EnumMember(Value = "Pressure")] Pressure } public class GenericDeviceMessageAttributes { [JsonIgnore] internal uint _index; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly string FeatureDescriptor; [JsonProperty(/*Could not decode attribute arguments.*/)] [JsonConverter(typeof(StringEnumConverter))] public readonly ActuatorType ActuatorType; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly uint StepCount; [JsonIgnore] public uint Index => _index; } public class SensorDeviceMessageAttributes { [JsonIgnore] internal uint _index; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly string FeatureDescriptor; [JsonProperty(/*Could not decode attribute arguments.*/)] [JsonConverter(typeof(StringEnumConverter))] public readonly SensorType SensorType; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly uint[][] SensorRange; [JsonIgnore] public uint Index => _index; } public class RawDeviceMessageAttributes { public readonly string[] Endpoints; } public class NullDeviceMessageAttributes { } public class DeviceMessageAttributes { internal class EnumeratePair<T> { public readonly int index; public readonly T attr; public EnumeratePair(T attr, int index) { this.index = index; this.attr = attr; } } public GenericDeviceMessageAttributes[] ScalarCmd; public GenericDeviceMessageAttributes[] RotateCmd; public GenericDeviceMessageAttributes[] LinearCmd; public SensorDeviceMessageAttributes[] SensorReadCmd; public SensorDeviceMessageAttributes[] SensorSubscribeCmd; public readonly RawDeviceMessageAttributes[] RawReadCmd; public readonly RawDeviceMessageAttributes[] RawWriteCmd; public readonly RawDeviceMessageAttributes[] RawSubscribeCmd; public readonly NullDeviceMessageAttributes StopDeviceCmd; [OnDeserialized] internal void OnDeserializedMethod(StreamingContext context) { ScalarCmd?.Select((GenericDeviceMessageAttributes x, int i) => new EnumeratePair<GenericDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<GenericDeviceMessageAttributes> x) { x.attr._index = (uint)x.index; }); RotateCmd?.Select((GenericDeviceMessageAttributes x, int i) => new EnumeratePair<GenericDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<GenericDeviceMessageAttributes> x) { x.attr._index = (uint)x.index; }); LinearCmd?.Select((GenericDeviceMessageAttributes x, int i) => new EnumeratePair<GenericDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<GenericDeviceMessageAttributes> x) { x.attr._index = (uint)x.index; }); SensorReadCmd?.Select((SensorDeviceMessageAttributes x, int i) => new EnumeratePair<SensorDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<SensorDeviceMessageAttributes> x) { x.attr._index = (uint)x.index; }); SensorSubscribeCmd?.Select((SensorDeviceMessageAttributes x, int i) => new EnumeratePair<SensorDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<SensorDeviceMessageAttributes> x) { x.attr._index = (uint)x.index; }); } } public class MessageReceivedEventArgs : EventArgs { public ButtplugMessage Message { get; } public MessageReceivedEventArgs(ButtplugMessage message) { Message = message; } } [ButtplugMessageMetadata("Ok")] public class Ok : ButtplugMessage, IButtplugMessageOutgoingOnly { public Ok(uint id) : base(id) { } } [ButtplugMessageMetadata("Test")] public class Test : ButtplugMessage { private string _testStringImpl; [JsonProperty(/*Could not decode attribute arguments.*/)] public string TestString { get { return _testStringImpl; } set { if (value == "Error") { throw new ArgumentException("Got an Error Message"); } _testStringImpl = value; } } public Test(string str, uint id = 1u) : base(id) { TestString = str; } } [ButtplugMessageMetadata("Error")] public class Error : ButtplugMessage, IButtplugMessageOutgoingOnly { public enum ErrorClass { ERROR_UNKNOWN, ERROR_INIT, ERROR_PING, ERROR_MSG, ERROR_DEVICE } [JsonProperty(/*Could not decode attribute arguments.*/)] public ErrorClass ErrorCode; [JsonProperty(/*Could not decode attribute arguments.*/)] public string ErrorMessage; public Error(string errorMessage, ErrorClass errorCode, uint id) : base(id) { ErrorMessage = errorMessage; ErrorCode = errorCode; } } public class MessageAttributes : IEquatable<MessageAttributes> { [JsonProperty(/*Could not decode attribute arguments.*/)] public uint? FeatureCount; public MessageAttributes() { } public MessageAttributes(uint featureCount) { FeatureCount = featureCount; } public bool Equals(MessageAttributes attrs) { return FeatureCount == attrs.FeatureCount; } } public class DeviceMessageInfo : IButtplugDeviceInfoMessage { [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly string DeviceName; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly uint DeviceIndex; public readonly string DeviceDisplayName; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly uint DeviceMessageTimingGap; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly DeviceMessageAttributes DeviceMessages; string IButtplugDeviceInfoMessage.DeviceName => DeviceName; uint IButtplugDeviceInfoMessage.DeviceIndex => DeviceIndex; DeviceMessageAttributes IButtplugDeviceInfoMessage.DeviceMessages => DeviceMessages; string IButtplugDeviceInfoMessage.DeviceDisplayName => DeviceDisplayName; uint IButtplugDeviceInfoMessage.DeviceMessageTimingGap => DeviceMessageTimingGap; public DeviceMessageInfo(uint index, string name, DeviceMessageAttributes messages) { DeviceName = name; DeviceIndex = index; DeviceMessages = messages; } } [ButtplugMessageMetadata("DeviceList")] public class DeviceList : ButtplugMessage, IButtplugMessageOutgoingOnly { [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly DeviceMessageInfo[] Devices = new DeviceMessageInfo[0]; public DeviceList(DeviceMessageInfo[] deviceList, uint id) : base(id) { Devices = deviceList; } internal DeviceList() : base(0u) { } } [ButtplugMessageMetadata("DeviceAdded")] public class DeviceAdded : ButtplugDeviceMessage, IButtplugMessageOutgoingOnly, IButtplugDeviceInfoMessage { [JsonProperty(/*Could not decode attribute arguments.*/)] public string DeviceName; public readonly string DeviceDisplayName; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly uint DeviceMessageTimingGap; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly DeviceMessageAttributes DeviceMessages; string IButtplugDeviceInfoMessage.DeviceName => DeviceName; uint IButtplugDeviceInfoMessage.DeviceIndex => base.DeviceIndex; DeviceMessageAttributes IButtplugDeviceInfoMessage.DeviceMessages => DeviceMessages; string IButtplugDeviceInfoMessage.DeviceDisplayName => DeviceDisplayName; uint IButtplugDeviceInfoMessage.DeviceMessageTimingGap => DeviceMessageTimingGap; public DeviceAdded(uint index, string name, DeviceMessageAttributes messages) : base(0u, index) { DeviceName = name; DeviceMessages = messages; } internal DeviceAdded() : base(0u) { } } [ButtplugMessageMetadata("DeviceRemoved")] public class DeviceRemoved : ButtplugMessage, IButtplugMessageOutgoingOnly { [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly uint DeviceIndex; public DeviceRemoved(uint index) : base(0u) { DeviceIndex = index; } } [ButtplugMessageMetadata("RequestDeviceList")] public class RequestDeviceList : ButtplugMessage { public RequestDeviceList(uint id = 1u) : base(id) { } } [ButtplugMessageMetadata("StartScanning")] public class StartScanning : ButtplugMessage { public StartScanning(uint id = 1u) : base(id) { } } [ButtplugMessageMetadata("StopScanning")] public class StopScanning : ButtplugMessage { public StopScanning(uint id = 1u) : base(id) { } } [ButtplugMessageMetadata("ScanningFinished")] public class ScanningFinished : ButtplugMessage, IButtplugMessageOutgoingOnly { public ScanningFinished() : base(0u) { } } [ButtplugMessageMetadata("RequestServerInfo")] public class RequestServerInfo : ButtplugMessage { [JsonProperty(/*Could not decode attribute arguments.*/)] public string ClientName; [JsonProperty(/*Could not decode attribute arguments.*/)] public uint MessageVersion; public RequestServerInfo(string clientName, uint id = 1u, uint schemversion = 3u) : base(id) { ClientName = clientName; MessageVersion = schemversion; } } [ButtplugMessageMetadata("ServerInfo")] public class ServerInfo : ButtplugMessage, IButtplugMessageOutgoingOnly { [JsonProperty(/*Could not decode attribute arguments.*/)] public uint MessageVersion; [JsonProperty(/*Could not decode attribute arguments.*/)] public uint MaxPingTime; [JsonProperty(/*Could not decode attribute arguments.*/)] public string ServerName; public ServerInfo(string serverName, uint messageVersion, uint maxPingTime, uint id = 1u) : base(id) { ServerName = serverName; MessageVersion = messageVersion; MaxPingTime = maxPingTime; } } [ButtplugMessageMetadata("Ping")] public class Ping : ButtplugMessage { public Ping(uint id = 1u) : base(id) { } } public class GenericMessageSubcommand { [JsonProperty(/*Could not decode attribute arguments.*/)] public uint Index; protected GenericMessageSubcommand(uint index) { Index = index; } } [ButtplugMessageMetadata("ScalarCmd")] public class ScalarCmd : ButtplugDeviceMessage { public class ScalarCommand { public readonly uint index; public readonly double scalar; public ScalarCommand(uint index, double scalar) { this.index = index; this.scalar = scalar; } } public class ScalarSubcommand : GenericMessageSubcommand { private double _scalarImpl; public readonly ActuatorType ActuatorType; [JsonProperty(/*Could not decode attribute arguments.*/)] public double Scalar { get { return _scalarImpl; } set { if (value < 0.0) { throw new ArgumentException("ScalarCmd value cannot be less than 0!"); } if (value > 1.0) { throw new ArgumentException("ScalarCmd value cannot be greater than 1!"); } _scalarImpl = value; } } public ScalarSubcommand(uint index, double scalar, ActuatorType actuatorType) : base(index) { Scalar = scalar; ActuatorType = actuatorType; } } [JsonProperty(/*Could not decode attribute arguments.*/)] public List<ScalarSubcommand> Scalars; [JsonConstructor] public ScalarCmd(uint deviceIndex, List<ScalarSubcommand> scalars, uint id = 1u) : base(id, deviceIndex) { Scalars = scalars; } public ScalarCmd(List<ScalarSubcommand> scalars) : this(uint.MaxValue, scalars) { } } [ButtplugMessageMetadata("RotateCmd")] public class RotateCmd : ButtplugDeviceMessage { public class RotateCommand { public readonly double speed; public readonly bool clockwise; public RotateCommand(double speed, bool clockwise) { this.speed = speed; this.clockwise = clockwise; } } public class RotateSubcommand : GenericMessageSubcommand { private double _speedImpl; [JsonProperty(/*Could not decode attribute arguments.*/)] public bool Clockwise; [JsonProperty(/*Could not decode attribute arguments.*/)] public double Speed { get { return _speedImpl; } set { if (value < 0.0) { throw new ArgumentException("RotateCmd Speed cannot be less than 0!"); } if (value > 1.0) { throw new ArgumentException("RotateCmd Speed cannot be greater than 1!"); } _speedImpl = value; } } public RotateSubcommand(uint index, double speed, bool clockwise) : base(index) { Speed = speed; Clockwise = clockwise; } } [JsonProperty(/*Could not decode attribute arguments.*/)] public List<RotateSubcommand> Rotations; public static RotateCmd Create(double speed, bool clockwise, uint cmdCount) { return Create(uint.MaxValue, 1u, Enumerable.Repeat(new RotateCommand(speed, clockwise), (int)cmdCount)); } public static RotateCmd Create(IEnumerable<RotateCommand> cmds) { return Create(uint.MaxValue, 1u, cmds); } public static RotateCmd Create(uint deviceIndex, uint msgId, double speed, bool clockwise, uint cmdCount) { return Create(deviceIndex, msgId, Enumerable.Repeat(new RotateCommand(speed, clockwise), (int)cmdCount)); } public static RotateCmd Create(uint deviceIndex, uint msgId, IEnumerable<RotateCommand> cmds) { List<RotateSubcommand> list = new List<RotateSubcommand>(cmds.Count()); uint num = 0u; foreach (RotateCommand cmd in cmds) { list.Add(new RotateSubcommand(num, cmd.speed, cmd.clockwise)); num++; } return new RotateCmd(deviceIndex, list, msgId); } [JsonConstructor] public RotateCmd(uint deviceIndex, List<RotateSubcommand> rotations, uint id = 1u) : base(id, deviceIndex) { Rotations = rotations; } public RotateCmd(List<RotateSubcommand> rotations) : this(uint.MaxValue, rotations) { } } [ButtplugMessageMetadata("LinearCmd")] public class LinearCmd : ButtplugDeviceMessage { public class VectorCommand { public readonly double position; public readonly uint duration; public VectorCommand(double position, uint duration) { this.position = position; this.duration = duration; } } public class VectorSubcommand : GenericMessageSubcommand { private double _positionImpl; [JsonProperty(/*Could not decode attribute arguments.*/)] public uint Duration; [JsonProperty(/*Could not decode attribute arguments.*/)] public double Position { get { return _positionImpl; } set { if (value < 0.0) { throw new ArgumentException("LinearCmd Speed cannot be less than 0!"); } if (value > 1.0) { throw new ArgumentException("LinearCmd Speed cannot be greater than 1!"); } _positionImpl = value; } } public VectorSubcommand(uint index, uint duration, double position) : base(index) { Duration = duration; Position = position; } } [JsonProperty(/*Could not decode attribute arguments.*/)] public List<VectorSubcommand> Vectors; public static LinearCmd Create(uint duration, double position, uint cmdCount) { return Create(uint.MaxValue, 1u, Enumerable.Repeat(new VectorCommand(position, duration), (int)cmdCount)); } public static LinearCmd Create(uint deviceIndex, uint msgId, uint duration, double position, uint cmdCount) { return Create(deviceIndex, msgId, Enumerable.Repeat(new VectorCommand(position, duration), (int)cmdCount)); } public static LinearCmd Create(IEnumerable<VectorCommand> cmds) { return Create(uint.MaxValue, 1u, cmds); } public static LinearCmd Create(uint deviceIndex, uint msgId, IEnumerable<VectorCommand> cmds) { List<VectorSubcommand> list = new List<VectorSubcommand>(cmds.Count()); uint num = 0u; foreach (VectorCommand cmd in cmds) { list.Add(new VectorSubcommand(num, cmd.duration, cmd.position)); num++; } return new LinearCmd(deviceIndex, list, msgId); } [JsonConstructor] public LinearCmd(uint deviceIndex, List<VectorSubcommand> vectors, uint id = 1u) : base(id, deviceIndex) { Vectors = vectors; } public LinearCmd(List<VectorSubcommand> vectors) : this(uint.MaxValue, vectors) { } } [ButtplugMessageMetadata("StopDeviceCmd")] public class StopDeviceCmd : ButtplugDeviceMessage { public StopDeviceCmd(uint deviceIndex = uint.MaxValue, uint id = 1u) : base(id, deviceIndex) { } } [ButtplugMessageMetadata("StopAllDevices")] public class StopAllDevices : ButtplugMessage { public StopAllDevices(uint id = 1u) : base(id) { } } [ButtplugMessageMetadata("SensorReadCmd")] public class SensorReadCmd : ButtplugDeviceMessage { [JsonProperty(/*Could not decode attribute arguments.*/)] public uint SensorIndex; [JsonProperty(/*Could not decode attribute arguments.*/)] public SensorType SensorType; [JsonConstructor] public SensorReadCmd(uint deviceIndex, uint sensorIndex, SensorType sensorType, uint id = 1u) : base(id, deviceIndex) { SensorIndex = sensorIndex; SensorType = sensorType; } public SensorReadCmd(uint sensorIndex, SensorType sensorType) : this(uint.MaxValue, sensorIndex, sensorType) { } } [ButtplugMessageMetadata("SensorReading")] public class SensorReading : ButtplugDeviceMessage { [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly uint SensorIndex; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly SensorType SensorType; [JsonProperty(/*Could not decode attribute arguments.*/)] public readonly List<int> data; } } namespace Buttplug.Client { public class ButtplugClient : IDisposable, IAsyncDisposable { protected Timer _pingTimer; internal ButtplugClientMessageHandler _handler; private readonly ConcurrentDictionary<uint, ButtplugClientDevice> _devices = new ConcurrentDictionary<uint, ButtplugClientDevice>(); private IButtplugClientConnector _connector; public string Name { get; } public ButtplugClientDevice[] Devices => _devices.Values.ToArray(); public bool Connected => _connector?.Connected ?? false; 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 ButtplugClient(string clientName) { Name = clientName; } public async Task ConnectAsync(IButtplugClientConnector connector, CancellationToken token = default(CancellationToken)) { if (Connected) { throw new ButtplugHandshakeException("Client already connected to a server."); } ButtplugUtils.ArgumentNotNull(connector, "connector"); _connector = connector; _connector.Disconnected += delegate(object obj, EventArgs eventArgs) { this.ServerDisconnect?.Invoke(obj, eventArgs); }; _connector.InvalidMessageReceived += ConnectorErrorHandler; _connector.MessageReceived += MessageReceivedHandler; _devices.Clear(); _handler = new ButtplugClientMessageHandler(connector); await _connector.ConnectAsync(token).ConfigureAwait(continueOnCapturedContext: false); ButtplugMessage res = await _handler.SendMessageAsync(new RequestServerInfo(Name), token).ConfigureAwait(continueOnCapturedContext: false); if (!(res is ServerInfo si)) { if (res is Error e) { await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false); throw ButtplugException.FromError(e); } await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false); throw new ButtplugHandshakeException("Unrecognized message " + res.Name + " during handshake", res.Id); } if (si.MaxPingTime != 0) { _pingTimer?.Dispose(); _pingTimer = new Timer(OnPingTimer, null, 0, Convert.ToInt32(Math.Round((double)si.MaxPingTime / 2.0, 0))); } if (si.MessageVersion < 3) { await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false); throw new ButtplugHandshakeException($"Buttplug Server's schema version ({si.MessageVersion}) is less than the client's ({3u}). A newer server is required.", res.Id); } ButtplugMessage resp = await _handler.SendMessageAsync(new RequestDeviceList()).ConfigureAwait(continueOnCapturedContext: false); if (resp is DeviceList deviceList) { DeviceMessageInfo[] devices = deviceList.Devices; foreach (DeviceMessageInfo deviceMessageInfo in devices) { if (!_devices.ContainsKey(deviceMessageInfo.DeviceIndex)) { ButtplugClientDevice buttplugClientDevice = new ButtplugClientDevice(_handler, deviceMessageInfo); _devices[deviceMessageInfo.DeviceIndex] = buttplugClientDevice; this.DeviceAdded?.Invoke(this, new DeviceAddedEventArgs(buttplugClientDevice)); } } return; } await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false); if (resp is Error msg) { throw ButtplugException.FromError(msg); } throw new ButtplugHandshakeException("Received unknown response to DeviceList handshake query"); } public async Task DisconnectAsync() { if (Connected) { _connector.MessageReceived -= MessageReceivedHandler; await _connector.DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false); this.ServerDisconnect?.Invoke(this, EventArgs.Empty); } } public async Task StartScanningAsync(CancellationToken token = default(CancellationToken)) { await _handler.SendMessageExpectOk(new StartScanning(), token).ConfigureAwait(continueOnCapturedContext: false); } public async Task StopScanningAsync(CancellationToken token = default(CancellationToken)) { await _handler.SendMessageExpectOk(new StopScanning(), token).ConfigureAwait(continueOnCapturedContext: false); } public async Task StopAllDevicesAsync(CancellationToken token = default(CancellationToken)) { await _handler.SendMessageExpectOk(new StopAllDevices(), token).ConfigureAwait(continueOnCapturedContext: false); } private void ConnectorErrorHandler(object sender, ButtplugExceptionEventArgs exception) { this.ErrorReceived?.Invoke(this, exception); } private async void MessageReceivedHandler(object sender, MessageReceivedEventArgs args) { ButtplugMessage message = args.Message; if (!(message is DeviceAdded deviceAdded)) { ButtplugClientDevice value; if (!(message is DeviceRemoved deviceRemoved)) { if (!(message is ScanningFinished)) { if (message is Error error) { this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(ButtplugException.FromError(error))); if (error.ErrorCode == Error.ErrorClass.ERROR_PING) { this.PingTimeout?.Invoke(this, EventArgs.Empty); await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false); } } else { this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(new ButtplugMessageException($"Got unhandled message: {message}", message.Id))); } } else { this.ScanningFinished?.Invoke(this, EventArgs.Empty); } } else if (!_devices.ContainsKey(deviceRemoved.DeviceIndex)) { this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(new ButtplugDeviceException("Got device removed message for unknown device.", message.Id))); } else if (_devices.TryRemove(deviceRemoved.DeviceIndex, out value)) { this.DeviceRemoved?.Invoke(this, new DeviceRemovedEventArgs(value)); } } else { ButtplugClientDevice dev = new ButtplugClientDevice(_handler, deviceAdded); _devices.AddOrUpdate(deviceAdded.DeviceIndex, dev, (uint u, ButtplugClientDevice device) => dev); this.DeviceAdded?.Invoke(this, new DeviceAddedEventArgs(dev)); } } private async void OnPingTimer(object state) { try { await _handler.SendMessageExpectOk(new Ping()).ConfigureAwait(continueOnCapturedContext: false); } catch (Exception inner) { this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(new ButtplugPingException("Exception thrown during ping update", 0u, inner))); await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false); } } protected virtual void Dispose(bool disposing) { DisconnectAsync().GetAwaiter().GetResult(); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual async ValueTask DisposeAsync(bool disposing) { await DisconnectAsync(); } public async ValueTask DisposeAsync() { await DisposeAsync(disposing: true); GC.SuppressFinalize(this); } } public class ButtplugClientConnectorException : ButtplugException { public ButtplugClientConnectorException(string message, Exception inner = null) : base(message, Error.ErrorClass.ERROR_UNKNOWN, 0u, inner) { } } public class ButtplugClientDevice { private readonly ButtplugClientMessageHandler _handler; public uint Index { get; } public string Name { get; } public string DisplayName { get; } public uint MessageTimingGap { get; } public DeviceMessageAttributes MessageAttributes { get; } public List<GenericDeviceMessageAttributes> VibrateAttributes => GenericAcutatorAttributes(ActuatorType.Vibrate); public List<GenericDeviceMessageAttributes> OscillateAttributes => GenericAcutatorAttributes(ActuatorType.Oscillate); public List<GenericDeviceMessageAttributes> RotateAttributes { get { if (MessageAttributes.RotateCmd != null) { return MessageAttributes.RotateCmd.ToList(); } return Enumerable.Empty<GenericDeviceMessageAttributes>().ToList(); } } public List<GenericDeviceMessageAttributes> LinearAttributes { get { if (MessageAttributes.LinearCmd != null) { return MessageAttributes.LinearCmd.ToList(); } return Enumerable.Empty<GenericDeviceMessageAttributes>().ToList(); } } public bool HasBattery => SensorReadAttributes(SensorType.Battery).Any(); internal ButtplugClientDevice(ButtplugClientMessageHandler handler, IButtplugDeviceInfoMessage devInfo) : this(handler, devInfo.DeviceIndex, devInfo.DeviceName, devInfo.DeviceMessages, devInfo.DeviceDisplayName, devInfo.DeviceMessageTimingGap) { ButtplugUtils.ArgumentNotNull(devInfo, "devInfo"); } internal ButtplugClientDevice(ButtplugClientMessageHandler handler, uint index, string name, DeviceMessageAttributes messages, string displayName, uint messageTimingGap) { ButtplugUtils.ArgumentNotNull(handler, "handler"); _handler = handler; Index = index; Name = name; MessageAttributes = messages; DisplayName = displayName; MessageTimingGap = messageTimingGap; } public List<GenericDeviceMessageAttributes> GenericAcutatorAttributes(ActuatorType actuator) { if (MessageAttributes.ScalarCmd != null) { return MessageAttributes.ScalarCmd.Where((GenericDeviceMessageAttributes x) => x.ActuatorType == actuator).ToList(); } return Enumerable.Empty<GenericDeviceMessageAttributes>().ToList(); } public async Task ScalarAsync(ScalarCmd.ScalarSubcommand command) { List<ScalarCmd.ScalarSubcommand> scalars = new List<ScalarCmd.ScalarSubcommand>(); GenericAcutatorAttributes(command.ActuatorType).ForEach(delegate(GenericDeviceMessageAttributes x) { scalars.Add(new ScalarCmd.ScalarSubcommand(x.Index, command.Scalar, command.ActuatorType)); }); if (!scalars.Any()) { throw new ButtplugDeviceException("Scalar command for device " + Name + " did not generate any commands. Are you sure the device supports the ActuatorType sent?"); } await _handler.SendMessageExpectOk(new ScalarCmd(Index, scalars)).ConfigureAwait(continueOnCapturedContext: false); } public async Task ScalarAsync(List<ScalarCmd.ScalarSubcommand> command) { if (!command.Any()) { throw new ArgumentException("Command List for ScalarAsync must have at least 1 command."); } await _handler.SendMessageExpectOk(new ScalarCmd(Index, command)).ConfigureAwait(continueOnCapturedContext: false); } public async Task VibrateAsync(double speed) { await ScalarAsync(new ScalarCmd.ScalarSubcommand(uint.MaxValue, speed, ActuatorType.Vibrate)); } public async Task VibrateAsync(IEnumerable<double> cmds) { List<GenericDeviceMessageAttributes> vibrateAttributes = VibrateAttributes; if (cmds.Count() > vibrateAttributes.Count()) { throw new ButtplugDeviceException($"Device {Name} only has {vibrateAttributes.Count()} vibrators, but {cmds.Count()} commands given."); } await ScalarAsync(vibrateAttributes.Select((GenericDeviceMessageAttributes x, int i) => new ScalarCmd.ScalarSubcommand(x.Index, cmds.ElementAt(i), ActuatorType.Vibrate)).ToList()).ConfigureAwait(continueOnCapturedContext: false); } public async Task VibrateAsync(IEnumerable<ScalarCmd.ScalarCommand> cmds) { await ScalarAsync(cmds.Select((ScalarCmd.ScalarCommand x) => new ScalarCmd.ScalarSubcommand(x.index, x.scalar, ActuatorType.Vibrate)).ToList()).ConfigureAwait(continueOnCapturedContext: false); } public async Task OscillateAsync(double speed) { await ScalarAsync(new ScalarCmd.ScalarSubcommand(uint.MaxValue, speed, ActuatorType.Oscillate)); } public async Task OscillateAsync(IEnumerable<double> cmds) { List<GenericDeviceMessageAttributes> oscillateAttributes = OscillateAttributes; if (cmds.Count() > oscillateAttributes.Count()) { throw new ButtplugDeviceException($"Device {Name} only has {oscillateAttributes.Count()} vibrators, but {cmds.Count()} commands given."); } await ScalarAsync(oscillateAttributes.Select((GenericDeviceMessageAttributes x, int i) => new ScalarCmd.ScalarSubcommand(x.Index, cmds.ElementAt(i), ActuatorType.Oscillate)).ToList()).ConfigureAwait(continueOnCapturedContext: false); } public async Task OscillateAsync(IEnumerable<ScalarCmd.ScalarCommand> cmds) { await ScalarAsync(cmds.Select((ScalarCmd.ScalarCommand x) => new ScalarCmd.ScalarSubcommand(x.index, x.scalar, ActuatorType.Oscillate)).ToList()).ConfigureAwait(continueOnCapturedContext: false); } public async Task RotateAsync(double speed, bool clockwise) { if (!RotateAttributes.Any()) { throw new ButtplugDeviceException("Device " + Name + " does not support rotation"); } RotateCmd rotateCmd = RotateCmd.Create(speed, clockwise, (uint)RotateAttributes.Count); rotateCmd.DeviceIndex = Index; await _handler.SendMessageExpectOk(rotateCmd).ConfigureAwait(continueOnCapturedContext: false); } public async Task RotateAsync(IEnumerable<RotateCmd.RotateCommand> cmds) { if (!RotateAttributes.Any()) { throw new ButtplugDeviceException("Device " + Name + " does not support rotation"); } RotateCmd rotateCmd = RotateCmd.Create(cmds); rotateCmd.DeviceIndex = Index; await _handler.SendMessageExpectOk(rotateCmd).ConfigureAwait(continueOnCapturedContext: false); } public async Task LinearAsync(uint duration, double position) { if (!LinearAttributes.Any()) { throw new ButtplugDeviceException("Device " + Name + " does not support linear position"); } LinearCmd linearCmd = LinearCmd.Create(duration, position, (uint)LinearAttributes.Count); linearCmd.DeviceIndex = Index; await _handler.SendMessageExpectOk(linearCmd).ConfigureAwait(continueOnCapturedContext: false); } public async Task LinearAsync(IEnumerable<LinearCmd.VectorCommand> cmds) { if (!LinearAttributes.Any()) { throw new ButtplugDeviceException("Device " + Name + " does not support linear position"); } LinearCmd linearCmd = LinearCmd.Create(cmds); linearCmd.DeviceIndex = Index; await _handler.SendMessageExpectOk(linearCmd).ConfigureAwait(continueOnCapturedContext: false); } public List<SensorDeviceMessageAttributes> SensorReadAttributes(SensorType sensor) { if (MessageAttributes.SensorReadCmd != null) { return MessageAttributes.SensorReadCmd.Where((SensorDeviceMessageAttributes x) => x.SensorType == sensor).ToList(); } return Enumerable.Empty<SensorDeviceMessageAttributes>().ToList(); } public async Task<double> BatteryAsync() { if (!HasBattery) { throw new ButtplugDeviceException("Device " + Name + " does not have battery capabilities."); } ButtplugMessage buttplugMessage = await _handler.SendMessageAsync(new SensorReadCmd(Index, SensorReadAttributes(SensorType.Battery).ElementAt(0).Index, SensorType.Battery)).ConfigureAwait(continueOnCapturedContext: false); if (!(buttplugMessage is SensorReading sensorReading)) { if (buttplugMessage is Error msg) { throw ButtplugException.FromError(msg); } throw new ButtplugMessageException("Message type " + buttplugMessage.Name + " not handled by BatteryAsync", buttplugMessage.Id); } return (double)sensorReading.data[0] / 100.0; } public async Task Stop() { await _handler.SendMessageExpectOk(new StopDeviceCmd(Index)).ConfigureAwait(continueOnCapturedContext: false); } } internal class ButtplugClientMessageHandler { private IButtplugClientConnector _connector; internal ButtplugClientMessageHandler(IButtplugClientConnector connector) { _connector = connector; } public async Task<ButtplugMessage> SendMessageAsync(ButtplugMessage msg, CancellationToken token = default(CancellationToken)) { if (!_connector.Connected) { throw new ButtplugClientConnectorException("Client not connected."); } return await _connector.SendAsync(msg, token).ConfigureAwait(continueOnCapturedContext: false); } public async Task SendMessageExpectOk(ButtplugMessage msg, CancellationToken token = default(CancellationToken)) { ButtplugMessage buttplugMessage = await SendMessageAsync(msg, token).ConfigureAwait(continueOnCapturedContext: false); if (!(buttplugMessage is Ok)) { if (buttplugMessage is Error msg2) { throw ButtplugException.FromError(msg2); } throw new ButtplugMessageException("Message type " + msg.Name + " not handled by SendMessageExpectOk", msg.Id); } } } public class ButtplugConnectorJSONParser { private readonly ButtplugJsonMessageParser _parser = new ButtplugJsonMessageParser(); public string Serialize(ButtplugMessage msg) { return _parser.Serialize(msg); } public string Serialize(ButtplugMessage[] msgs) { return _parser.Serialize(msgs); } public IEnumerable<ButtplugMessage> Deserialize(string msg) { return _parser.Deserialize(msg); } } public class ButtplugConnectorMessageSorter : IDisposable { private int _counter; private readonly ConcurrentDictionary<uint, TaskCompletionSource<ButtplugMessage>> _waitingMsgs = new ConcurrentDictionary<uint, TaskCompletionSource<ButtplugMessage>>(); public uint NextMsgId => Convert.ToUInt32(Interlocked.Increment(ref _counter)); public Task<ButtplugMessage> PrepareMessage(ButtplugMessage msg) { msg.Id = NextMsgId; TaskCompletionSource<ButtplugMessage> taskCompletionSource = new TaskCompletionSource<ButtplugMessage>(); _waitingMsgs.TryAdd(msg.Id, taskCompletionSource); return taskCompletionSource.Task; } public void CheckMessage(ButtplugMessage msg) { if (msg.Id == 0) { throw new ButtplugMessageException("Cannot sort message with System ID", msg.Id); } if (!_waitingMsgs.TryRemove(msg.Id, out var value)) { throw new ButtplugMessageException("Message with non-matching ID received.", msg.Id); } if (msg is Error msg2) { value.SetException(ButtplugException.FromError(msg2)); } else { value.SetResult(msg); } } protected virtual void Dispose(bool disposing) { foreach (TaskCompletionSource<ButtplugMessage> value in _waitingMsgs.Values) { value.TrySetException(new Exception("Sorter has been destroyed with live tasks still in queue.")); } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } public class ReturnMessage { public readonly string Message; public readonly Task<ButtplugMessage> Promise; public ReturnMessage(string message, Task<ButtplugMessage> promise) { Message = message; Promise = promise; } } public class ButtplugRemoteJSONConnector : IDisposable { private readonly ButtplugConnectorJSONParser _jsonSerializer = new ButtplugConnectorJSONParser(); private readonly ButtplugConnectorMessageSorter _msgSorter = new ButtplugConnectorMessageSorter(); public event EventHandler<MessageReceivedEventArgs> MessageReceived; public event EventHandler<ButtplugExceptionEventArgs> InvalidMessageReceived; public ReturnMessage PrepareMessage(ButtplugMessage msg) { Task<ButtplugMessage> promise = _msgSorter.PrepareMessage(msg); return new ReturnMessage(_jsonSerializer.Serialize(msg), promise); } protected void ReceiveMessages(string jSONMsg) { IEnumerable<ButtplugMessage> enumerable; try { enumerable = _jsonSerializer.Deserialize(jSONMsg); } catch (ButtplugMessageException ex) { this.InvalidMessageReceived?.Invoke(this, new ButtplugExceptionEventArgs(ex)); return; } foreach (ButtplugMessage item in enumerable) { if (item.Id == 0) { this.MessageReceived?.Invoke(this, new MessageReceivedEventArgs(item)); continue; } try { _msgSorter.CheckMessage(item); } catch (ButtplugMessageException ex2) { this.InvalidMessageReceived?.Invoke(this, new ButtplugExceptionEventArgs(ex2)); } } } protected virtual void Dispose(bool disposing) { _msgSorter.Dispose(); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } public class ButtplugWebsocketConnector : ButtplugRemoteJSONConnector, IButtplugClientConnector { private ClientWebSocket _wsClient; private readonly SynchronizationContext _owningDispatcher = SynchronizationContext.Current ?? new SynchronizationContext(); private readonly Uri _uri; private Task _readTask; public bool Connected { get { ClientWebSocket wsClient = _wsClient; if (wsClient == null) { return false; } return wsClient.State == WebSocketState.Open; } } public event EventHandler Disconnected; public ButtplugWebsocketConnector(Uri uri) { _uri = uri; } public async Task ConnectAsync(CancellationToken token = default(CancellationToken)) { if (_wsClient != null) { throw new ButtplugHandshakeException("Websocket connector is already connected."); } try { _wsClient = new ClientWebSocket(); await _wsClient.ConnectAsync(_uri, token).ConfigureAwait(continueOnCapturedContext: false); } catch (Exception inner) { throw new ButtplugClientConnectorException("Websocket Connection Exception! See Inner Exception", inner); } _readTask = Task.Run(async delegate { await RunClientLoop(token).ConfigureAwait(continueOnCapturedContext: false); }, token); } public async Task DisconnectAsync(CancellationToken token = default(CancellationToken)) { if (_wsClient != null && (_wsClient.State == WebSocketState.Connecting || _wsClient.State == WebSocketState.Open)) { await _wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false); } _wsClient?.Dispose(); _wsClient = null; await _readTask.ConfigureAwait(continueOnCapturedContext: false); } public async Task<ButtplugMessage> SendAsync(ButtplugMessage msg, CancellationToken cancellationToken) { if (_wsClient == null) { throw new ButtplugException("Cannot send messages while disconnected", Error.ErrorClass.ERROR_MSG); } ReturnMessage returnMsg = PrepareMessage(msg); await _wsClient.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(returnMsg.Message)), WebSocketMessageType.Text, endOfMessage: true, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); return await returnMsg.Promise.ConfigureAwait(continueOnCapturedContext: false); } private async Task RunClientLoop(CancellationToken token) { try { new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false); byte[] buff = new byte[2048]; while (Connected && !token.IsCancellationRequested) { WebSocketReceiveResult webSocketReceiveResult = await _wsClient.ReceiveAsync(new ArraySegment<byte>(buff), token).ConfigureAwait(continueOnCapturedContext: false); if (webSocketReceiveResult.MessageType == WebSocketMessageType.Text) { string @string = Encoding.Default.GetString(buff, 0, webSocketReceiveResult.Count); ReceiveMessages(@string); } } } catch (Exception) { } finally { if (_wsClient != null) { _wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", token).Dispose(); _wsClient = null; } _owningDispatcher.Send(delegate { Dispose(); }, null); _owningDispatcher.Send(delegate { this.Disconnected?.Invoke(this, EventArgs.Empty); }, null); } } } public class DeviceAddedEventArgs { public readonly ButtplugClientDevice Device; public DeviceAddedEventArgs(ButtplugClientDevice device) { Device = device; } } public class DeviceRemovedEventArgs { public readonly ButtplugClientDevice Device; public DeviceRemovedEventArgs(ButtplugClientDevice device) { Device = device; } } public interface IButtplugClientConnector { bool Connected { get; } event EventHandler<MessageReceivedEventArgs> MessageReceived; event EventHandler<ButtplugExceptionEventArgs> InvalidMessageReceived; event EventHandler Disconnected; Task ConnectAsync(CancellationToken token = default(CancellationToken)); Task DisconnectAsync(CancellationToken token = default(CancellationToken)); Task<ButtplugMessage> SendAsync(ButtplugMessage msg, CancellationToken token = default(CancellationToken)); } }