Some mods may be broken due to the recent Alloyed Collective update.
Decompiled source of RumbleRain v0.5.0
plugins/RumbleRain/Buttplug.dll
Decompiled a year 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)); } }
plugins/RumbleRain/RumbleRain.dll
Decompiled a year 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("RumbleRain")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+07ff99695129e2116e6c32ba63c51895dd52eeb1")] [assembly: AssemblyProduct("RumbleRain")] [assembly: AssemblyTitle("RumbleRain")] [assembly: AssemblyVersion("1.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 } 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."); } } internal IEnumerator PollVibrations() { Log.Info($"Beginning polling every {ConfigManager.PollingRateSeconds.Value} seconds"); while (true) { float secondsToWaitFor = ConfigManager.PollingRateSeconds.Value; yield return (object)new WaitForSeconds(secondsToWaitFor); if (ButtplugClient.Connected && State == DeviceState.Active) { if (InfoProvider.Info.IsImpotent() && State == DeviceState.Active) { StopConnectedDevices(); continue; } InfoProvider.UpdateVibrationInfo(TimeSpan.FromSeconds(secondsToWaitFor)); VibrateConnectedDevices(InfoProvider.Info.Intensity); } } } 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.0")] public class RumbleRain : BaseUnityPlugin { public const string PluginGUID = "quasikyo.RumbleRain"; public const string PluginAuthor = "quasikyo"; public const string PluginName = "RumbleRain"; public const string PluginVersion = "0.5.0"; 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; } }