RUMBLE does not support other mod managers. If you want to use a manager, you must use the RUMBLE Mod Manager, a manager specifically designed for this game.
Decompiled source of DiscordRichPresence v1.0.0
Mods/DiscordRichPresence.dll
Decompiled 8 months agousing System; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using DiscordRichPresence; using Il2CppRUMBLE.Managers; using Il2CppRUMBLE.Utilities; using MelonLoader; using Microsoft.CodeAnalysis; using NetDiscordRpc; using NetDiscordRpc.Core.Logger; using NetDiscordRpc.RPC; using RumbleModdingAPI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Class1), "DiscordRichPresence", "1.0.0", "PeppaStone", null)] [assembly: MelonGame("Buckethead Entertainment", "RUMBLE")] [assembly: MelonColor(255, 255, 51, 238)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("DiscordRichPresence")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("DiscordRichPresence")] [assembly: AssemblyTitle("DiscordRichPresence")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace DiscordRichPresence { public class Class1 : MelonMod { public static DiscordRPC DiscordRpc; private string currentScene; public override void OnSceneWasLoaded(int buildIndex, string sceneName) { currentScene = sceneName; } public void SceneLoaded() { switch (currentScene) { case "Gym": DiscordRpc.UpdateState(""); DiscordRpc.UpdateDetails("In the Gym"); break; case "Map0": if (Singleton<PlayerManager>.Instance.AllPlayers.Count >= 2) { DiscordRpc.UpdateState("Fighting " + RemoveTags(Singleton<PlayerManager>.Instance.AllPlayers[1].Data.GeneralData.PublicUsername)); } else { DiscordRpc.UpdateState("Alone"); } DiscordRpc.UpdateDetails("In the Ring"); break; case "Map1": if (Singleton<PlayerManager>.Instance.AllPlayers.Count >= 2) { DiscordRpc.UpdateState("Fighting " + RemoveTags(Singleton<PlayerManager>.Instance.AllPlayers[1].Data.GeneralData.PublicUsername)); } else { DiscordRpc.UpdateState("Alone"); } DiscordRpc.UpdateDetails("In the Pit"); break; case "Park": DiscordRpc.UpdateState(""); DiscordRpc.UpdateDetails("In the Park"); break; } } public static string RemoveTags(string input) { string pattern = "<#.*?>"; return Regex.Replace(input, pattern, string.Empty); } public override void OnLateInitializeMelon() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown DiscordRpc = new DiscordRPC("1319243922274390027"); DiscordRpc.Logger = (IConsoleLogger)new ConsoleLogger(); DiscordRpc.Logger = (IConsoleLogger)new NullLogger(); DiscordRpc.Initialize(); DiscordRPC discordRpc = DiscordRpc; RichPresence val = new RichPresence(); ((RichPresenceBase)val).Details = "In the Gym"; ((RichPresenceBase)val).State = ""; ((RichPresenceBase)val).Assets = new Assets { LargeImageKey = "largeimage", LargeImageText = "RUMBLE", SmallImageKey = "smallimage", SmallImageText = "RUMBLE" }; ((RichPresenceBase)val).Timestamps = Timestamps.Now; val.Buttons = (Button[])(object)new Button[1] { new Button { Label = "Get RUMBLE", Url = "https://store.steampowered.com/app/890550/RUMBLE/" } }; discordRpc.SetPresence(val); DiscordRpc.Invoke(); Calls.onMapInitialized += SceneLoaded; } } }
UserLibs/NetDiscordRpc.dll
Decompiled 8 months agousing System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using Microsoft.Win32; using NetDiscordRpc.Core.Converters; using NetDiscordRpc.Core.Exceptions; using NetDiscordRpc.Core.Helpers; using NetDiscordRpc.Core.IO; using NetDiscordRpc.Core.Logger; using NetDiscordRpc.Core.Registry; using NetDiscordRpc.Events; using NetDiscordRpc.Message; using NetDiscordRpc.Message.Messages; using NetDiscordRpc.RPC; using NetDiscordRpc.RPC.Commands; using NetDiscordRpc.RPC.Payload; using NetDiscordRpc.Users; using Newtonsoft.Json; using Newtonsoft.Json.Linq; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETCoreApp,Version=v5.0", FrameworkDisplayName = "")] [assembly: AssemblyVersion("0.0.0.0")] namespace NetDiscordRpc { public class Configuration { [JsonProperty("api_endpoint")] public string ApiEndpoint { get; set; } [JsonProperty("cdn_host")] public string CdnHost { get; set; } [JsonProperty("enviroment")] public string Enviroment { get; set; } } public sealed class DiscordRPC : IDisposable { private IConsoleLogger _logger; private RpcConnection connection; private bool _shutdownOnly = true; private object _sync = new object(); public bool HasRegisteredUriScheme { get; private set; } public string ApplicationID { get; private set; } public string SteamID { get; private set; } public int ProcessID { get; private set; } public int MaxQueueSize { get; private set; } public bool IsDisposed { get; private set; } public IConsoleLogger Logger { get { return _logger; } set { _logger = value; if (connection != null) { connection.Logger = value; } } } public bool AutoEvents { get; private set; } public bool SkipIdenticalPresence { get; set; } public int TargetPipe { get; private set; } public RichPresence CurrentPresence { get; private set; } public EventTypes Subscription { get; private set; } public Button[] Buttons { get; set; } public User CurrentUser { get; private set; } public Configuration Configuration { get; private set; } public bool IsInitialized { get; private set; } public bool ShutdownOnly { get { return _shutdownOnly; } set { _shutdownOnly = value; if (connection != null) { connection.ShutdownOnly = value; } } } public event OnReadyEvent OnReady; public event OnCloseEvent OnClose; public event OnErrorEvent OnError; public event OnPresenceUpdateEvent OnPresenceUpdate; public event OnSubscribeEvent OnSubscribe; public event OnUnsubscribeEvent OnUnsubscribe; public event OnJoinEvent OnJoin; public event OnSpectateEvent OnSpectate; public event OnJoinRequestedEvent OnJoinRequested; public event OnConnectionEstablishedEvent OnConnectionEstablished; public event OnConnectionFailedEvent OnConnectionFailed; public event OnRpcMessageEvent OnRpcMessage; public DiscordRPC(string applicationID) : this(applicationID, -1, null, autoEvents: true, null) { } public DiscordRPC(string applicationID, int pipe = -1, IConsoleLogger logger = null, bool autoEvents = true, INamedPipeClient client = null) { if (string.IsNullOrEmpty(applicationID)) { throw new ArgumentNullException("applicationID"); } Type typeFromHandle = typeof(JsonConverter); if (typeFromHandle == null) { throw new Exception("JsonConverter Type Not Found"); } ApplicationID = applicationID.Trim(); TargetPipe = pipe; ProcessID = Environment.ProcessId; HasRegisteredUriScheme = false; AutoEvents = autoEvents; SkipIdenticalPresence = true; _logger = logger ?? new NullLogger(); connection = new RpcConnection(ApplicationID, ProcessID, TargetPipe, client ?? new ManagedNamedPipeClient(), (!autoEvents) ? 128u : 0u) { ShutdownOnly = _shutdownOnly, Logger = _logger }; connection.OnRpcMessage += delegate(object _, IMessage msg) { this.OnRpcMessage?.Invoke(this, msg); if (AutoEvents) { ProcessMessage(msg); } }; } public IMessage[] Invoke() { if (AutoEvents) { Logger.Error("Cannot Invoke client when AutomaticallyInvokeEvents has been set."); return Array.Empty<IMessage>(); } IMessage[] array = connection.DequeueMessages(); foreach (IMessage message in array) { ProcessMessage(message); } return array; } private void ProcessMessage(IMessage message) { if (message == null) { return; } switch (message.Type) { case MessageTypes.PresenceUpdate: lock (_sync) { if (message is PresenceMessage presenceMessage) { if (CurrentPresence == null) { CurrentPresence = new RichPresence().Merge(presenceMessage.Presence); } else if (presenceMessage.Presence == null) { CurrentPresence = null; } else { CurrentPresence.Merge(presenceMessage.Presence); } presenceMessage.Presence = CurrentPresence; } } break; case MessageTypes.Ready: if (message is ReadyMessage readyMessage) { lock (_sync) { Configuration = readyMessage.Configuration; CurrentUser = readyMessage.User; } SynchronizeState(); } break; case MessageTypes.JoinRequest: if (Configuration != null && message is JoinRequestMessage joinRequestMessage) { joinRequestMessage.User.SetConfiguration(Configuration); } break; case MessageTypes.Subscribe: lock (_sync) { SubscribeMessage subscribeMessage = message as SubscribeMessage; Subscription |= subscribeMessage.Event; } break; case MessageTypes.Unsubscribe: lock (_sync) { UnsubscribeMessage unsubscribeMessage = message as UnsubscribeMessage; Subscription &= ~unsubscribeMessage.Event; } break; } switch (message.Type) { case MessageTypes.Ready: this.OnReady?.Invoke(this, message as ReadyMessage); break; case MessageTypes.Close: this.OnClose?.Invoke(this, message as CloseMessage); break; case MessageTypes.Error: this.OnError?.Invoke(this, message as ErrorMessage); break; case MessageTypes.PresenceUpdate: this.OnPresenceUpdate?.Invoke(this, message as PresenceMessage); break; case MessageTypes.Subscribe: this.OnSubscribe?.Invoke(this, message as SubscribeMessage); break; case MessageTypes.Unsubscribe: this.OnUnsubscribe?.Invoke(this, message as UnsubscribeMessage); break; case MessageTypes.Join: this.OnJoin?.Invoke(this, message as JoinMessage); break; case MessageTypes.Spectate: this.OnSpectate?.Invoke(this, message as SpectateMessage); break; case MessageTypes.JoinRequest: this.OnJoinRequested?.Invoke(this, message as JoinRequestMessage); break; case MessageTypes.ConnectionEstablished: this.OnConnectionEstablished?.Invoke(this, message as ConnectionEstablishedMessage); break; case MessageTypes.ConnectionFailed: this.OnConnectionFailed?.Invoke(this, message as ConnectionFailedMessage); break; default: Logger.Error($"Message was queued with no appropriate handle! {message.Type}"); break; } } public void Respond(JoinRequestMessage request, bool acceptRequest) { if (IsDisposed) { throw new ObjectDisposedException("Discord IPC Client"); } if (connection == null) { throw new ObjectDisposedException("Connection", "Cannot initialize as the connection has been deinitialized"); } if (!IsInitialized) { throw new UninitializedException(); } connection.EnqueueCommand(new RespondCommand { Accept = acceptRequest, UserID = request.User.ID.ToString() }); } public void SetPresence(RichPresence presence) { if (IsDisposed) { throw new ObjectDisposedException("Discord IPC Client"); } if (connection == null) { throw new ObjectDisposedException("Connection", "Cannot initialize as the connection has been deinitialized"); } if (!IsInitialized) { Logger.Warning("The client is not yet initialized, storing the presence as a state instead."); } if (!presence) { if (!SkipIdenticalPresence || CurrentPresence != null) { connection.EnqueueCommand(new PresenceCommand { PID = ProcessID, Presence = null }); } } else { if (presence.HasSecrets() && !HasRegisteredUriScheme) { throw new BadPresenceException("Cannot send a presence with secrets as this object has not registered a URI scheme. Please enable the uri scheme registration in the DiscordRpcClient constructor."); } if (presence.HasParty() && presence.Party.Max < presence.Party.Size) { throw new BadPresenceException("Presence maximum party size cannot be smaller than the current size."); } if (presence.HasSecrets() && !presence.HasParty()) { Logger.Warning("The presence has set the secrets but no buttons will show as there is no party available."); } if (!SkipIdenticalPresence || !presence.Matches(CurrentPresence)) { connection.EnqueueCommand(new PresenceCommand { PID = ProcessID, Presence = presence.Clone() }); } } lock (_sync) { CurrentPresence = presence?.Clone(); } } public RichPresence UpdateDetails(string details = null) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } richPresence.Details = details; SetPresence(richPresence); return richPresence; } public RichPresence UpdateState(string state = null) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } richPresence.State = state; SetPresence(richPresence); return richPresence; } public RichPresence UpdateParty(Party party = null) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } richPresence.Party = party; SetPresence(richPresence); return richPresence; } public RichPresence UpdatePartySize(int size) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Party == null) { throw new BadPresenceException("Cannot set the size of the party if the party does not exist"); } richPresence.Party.Size = size; SetPresence(richPresence); return richPresence; } public RichPresence UpdatePartySize(int size, int max) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Party == null) { throw new BadPresenceException("Cannot set the size of the party if the party does not exist"); } richPresence.Party.Size = size; richPresence.Party.Max = max; SetPresence(richPresence); return richPresence; } public RichPresence UpdateLargeAsset(string key = null, string tooltip = null) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Assets == null) { richPresence.Assets = new Assets(); } richPresence.Assets.LargeImageKey = key ?? richPresence.Assets.LargeImageKey; richPresence.Assets.LargeImageText = tooltip ?? richPresence.Assets.LargeImageText; SetPresence(richPresence); return richPresence; } public RichPresence UpdateSmallAsset(string key = null, string tooltip = null) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Assets == null) { richPresence.Assets = new Assets(); } richPresence.Assets.SmallImageKey = key ?? richPresence.Assets.SmallImageKey; richPresence.Assets.SmallImageText = tooltip ?? richPresence.Assets.SmallImageText; SetPresence(richPresence); return richPresence; } public RichPresence UpdateSecrets(Secrets secrets = null) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } richPresence.Secrets = secrets; SetPresence(richPresence); return richPresence; } public RichPresence UpdateStartTime() { return UpdateStartTime(DateTime.UtcNow); } public RichPresence UpdateTimestamps(Timestamps timestamps) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Timestamps == null) { richPresence.Timestamps = new Timestamps(); } richPresence.Timestamps = timestamps; SetPresence(richPresence); return richPresence; } public RichPresence UpdateStartTime(DateTime time) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Timestamps == null) { richPresence.Timestamps = new Timestamps(); } richPresence.Timestamps.Start = time; SetPresence(richPresence); return richPresence; } public RichPresence UpdateEndTime() { return UpdateEndTime(DateTime.UtcNow); } public RichPresence UpdateEndTime(DateTime time) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Timestamps == null) { richPresence.Timestamps = new Timestamps(); } richPresence.Timestamps.End = time; SetPresence(richPresence); return richPresence; } public RichPresence UpdateClearTime() { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } richPresence.Timestamps = null; SetPresence(richPresence); return richPresence; } public void ClearPresence() { if (IsDisposed) { throw new ObjectDisposedException("Discord IPC Client"); } if (!IsInitialized) { throw new UninitializedException(); } if (connection == null) { throw new ObjectDisposedException("Connection", "Cannot initialize as the connection has been deinitialized"); } SetPresence(null); } public RichPresence UpdateButtons(Button[] button = null) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } richPresence.Buttons = button; SetPresence(richPresence); return richPresence; } public RichPresence UpdateButtons(Button[] button, int buttonId) { if (!IsInitialized) { throw new UninitializedException(); } int num = buttonId - 1; RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } richPresence.Buttons[num] = button[num]; SetPresence(richPresence); return richPresence; } public RichPresence RemoveLargeAsset() { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Assets == null) { richPresence.Assets = new Assets(); } richPresence.Assets.LargeImageKey = null; richPresence.Assets.LargeImageText = null; SetPresence(richPresence); return richPresence; } public RichPresence RemoveSmallAsset() { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence()); } if (richPresence.Assets == null) { richPresence.Assets = new Assets(); } richPresence.Assets.LargeImageKey = null; richPresence.Assets.LargeImageText = null; SetPresence(richPresence); return richPresence; } public bool RegisterUriScheme(string steamAppID = null, string executable = null) { UriSchemeRegister uriSchemeRegister = new UriSchemeRegister(_logger, ApplicationID, steamAppID, executable); return HasRegisteredUriScheme = uriSchemeRegister.RegisterUriScheme(); } public void Subscribe(EventTypes type) { SetSubscription(Subscription | type); } public void Unsubscribe(EventTypes type) { SetSubscription(Subscription & ~type); } public void SetSubscription(EventTypes type) { if (IsInitialized) { SubscribeToTypes(Subscription & ~type, isUnsubscribe: true); SubscribeToTypes(~Subscription & type, isUnsubscribe: false); } else { Logger.Warning("Client has not yet initialized, but events are being subscribed too. Storing them as state instead."); } lock (_sync) { Subscription = type; } } private void SubscribeToTypes(EventTypes type, bool isUnsubscribe) { if (type != 0) { if (IsDisposed) { throw new ObjectDisposedException("Discord IPC Client"); } if (!IsInitialized) { throw new UninitializedException(); } if (connection == null) { throw new ObjectDisposedException("Connection", "Cannot initialize as the connection has been deinitialized"); } if (!HasRegisteredUriScheme) { throw new InvalidConfigurationException("Cannot subscribe/unsubscribe to an event as this application has not registered a URI Scheme. Call RegisterUriScheme()."); } if ((type & EventTypes.Spectate) == EventTypes.Spectate) { connection.EnqueueCommand(new SubscribeCommand { Event = ServerEvent.ActivitySpectate, IsUnsubscribe = isUnsubscribe }); } if ((type & EventTypes.Join) == EventTypes.Join) { connection.EnqueueCommand(new SubscribeCommand { Event = ServerEvent.ActivityJoin, IsUnsubscribe = isUnsubscribe }); } if ((type & EventTypes.JoinRequest) == EventTypes.JoinRequest) { connection.EnqueueCommand(new SubscribeCommand { Event = ServerEvent.ActivityJoinRequest, IsUnsubscribe = isUnsubscribe }); } } } public void SynchronizeState() { if (!IsInitialized) { throw new UninitializedException(); } SetPresence(CurrentPresence); if (HasRegisteredUriScheme) { SubscribeToTypes(Subscription, isUnsubscribe: false); } } public bool Initialize() { if (IsDisposed) { throw new ObjectDisposedException("Discord IPC Client"); } if (IsInitialized) { throw new UninitializedException("Cannot initialize a client that is already initialized"); } if (connection == null) { throw new ObjectDisposedException("Connection", "Cannot initialize as the connection has been deinitialized"); } return IsInitialized = connection.AttemptConnection(); } public void Deinitialize() { if (!IsInitialized) { throw new UninitializedException("Cannot deinitialize a client that has not been initalized."); } connection.Close(); IsInitialized = false; } public void Dispose() { if (!IsDisposed) { if (IsInitialized) { Deinitialize(); } IsDisposed = true; } } } } namespace NetDiscordRpc.Users { public enum AvatarFormats { PNG, JPEG, WebP, GIF } public enum AvatarSizes { x16 = 0x10, x32 = 0x20, x64 = 0x40, x128 = 0x80, x256 = 0x100, x512 = 0x200, x1024 = 0x400, x2048 = 0x800 } public enum PremiumTypes { None, NitroClassic, Nitro } public class User { [JsonProperty("id")] public ulong ID { get; private set; } [JsonProperty("username")] public string Username { get; private set; } [JsonProperty("discriminator")] public int Discriminator { get; private set; } [JsonProperty("avatar")] public string Avatar { get; private set; } [JsonProperty("flags")] public UserFlags Flags { get; private set; } [JsonProperty("premium_type")] public PremiumTypes Premium { get; private set; } public string CdnEndpoint { get; private set; } internal User() { CdnEndpoint = "cdn.discordapp.com"; } internal void SetConfiguration(Configuration configuration) { CdnEndpoint = configuration.CdnHost; } public string GetAvatar(AvatarFormats format, AvatarSizes size = AvatarSizes.x128) { string text = $"/avatars/{ID}/{Avatar}"; if (!string.IsNullOrEmpty(Avatar)) { return $"{CdnEndpoint}/{text}.{GetAvatarExtension(format)}?size={size}"; } if (format != 0) { throw new BadImageFormatException("The user has no avatar and the requested format " + format.ToString() + " is not supported. (Only supports PNG)."); } text = $"/embed/avatars/{Discriminator % 5}"; return $"https://{CdnEndpoint}/{text}.{GetAvatarExtension(format)}?size={size}"; } public static string GetAvatarExtension(AvatarFormats format) { return format.ToString().ToLowerInvariant(); } public override string ToString() { return Username + "#" + Discriminator.ToString("D4"); } } public enum UserFlags { None = 0, Employee = 1, Partner = 2, HypeSquad = 4, BugHunter = 8, HouseBravery = 0x40, HouseBrilliance = 0x80, HouseBalance = 0x100, EarlySupporter = 0x200, TeamUser = 0x400 } } namespace NetDiscordRpc.RPC { [Serializable] public class Assets { private string _largeimagekey; private string _largeimagetext; private string _smallimagekey; private string _smallimagetext; private ulong? _largeimageID; private ulong? _smallimageID; [JsonIgnore] public ulong? LargeImageID => _largeimageID; [JsonIgnore] public ulong? SmallImageID => _smallimageID; [JsonProperty(/*Could not decode attribute arguments.*/)] public string LargeImageKey { get { return _largeimagekey; } set { if (!RichPresenceBase.ValidateString(value, out _largeimagekey, 32, Encoding.UTF8)) { throw new StringOutOfRangeException(32); } _largeimageID = null; } } [JsonProperty(/*Could not decode attribute arguments.*/)] public string LargeImageText { get { return _largeimagetext; } set { if (!RichPresenceBase.ValidateString(value, out _largeimagetext, 128, Encoding.UTF8)) { throw new StringOutOfRangeException(128); } } } [JsonProperty(/*Could not decode attribute arguments.*/)] public string SmallImageKey { get { return _smallimagekey; } set { if (!RichPresenceBase.ValidateString(value, out _smallimagekey, 32, Encoding.UTF8)) { throw new StringOutOfRangeException(32); } _smallimageID = null; } } [JsonProperty(/*Could not decode attribute arguments.*/)] public string SmallImageText { get { return _smallimagetext; } set { if (!RichPresenceBase.ValidateString(value, out _smallimagetext, 128, Encoding.UTF8)) { throw new StringOutOfRangeException(128); } } } internal void Merge(Assets other) { _smallimagetext = other._smallimagetext; _largeimagetext = other._largeimagetext; if (ulong.TryParse(other._largeimagekey, out var result)) { _largeimageID = result; } else { _largeimagekey = other._largeimagekey; _largeimageID = null; } if (ulong.TryParse(other._smallimagekey, out var result2)) { _smallimageID = result2; return; } _smallimagekey = other._smallimagekey; _smallimageID = null; } } public class Button { private string _label; private string _url; [JsonProperty("label")] public string Label { get { return _label; } set { if (!RichPresenceBase.ValidateString(value, out _label, 32, Encoding.UTF8)) { throw new StringOutOfRangeException(512); } } } [JsonProperty("url")] public string Url { get { return _url; } set { if (!RichPresenceBase.ValidateString(value, out _url, 512, Encoding.UTF8)) { throw new StringOutOfRangeException(512); } if (!Uri.TryCreate(_url, UriKind.Absolute, out Uri _)) { throw new ArgumentException("Url must be a valid URI"); } } } } [Serializable] public class Party { private string _partyid; [JsonIgnore] public int Size { get; set; } [JsonIgnore] public int Max { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public PartyPrivacySettings Privacy { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string ID { get { return _partyid; } set { _partyid = value.GetNullOrString(); } } [JsonProperty(/*Could not decode attribute arguments.*/)] private int[] _size { get { int num = Math.Max(1, Size); return new int[2] { num, Math.Max(num, Max) }; } set { if (value.Length != 2) { Size = 0; Max = 0; } else { Size = value[0]; Max = value[1]; } } } } public enum PartyPrivacySettings { Private, Public } public sealed class RichPresence : RichPresenceBase { [JsonProperty(/*Could not decode attribute arguments.*/)] public Button[] Buttons { get; set; } public bool HasButtons() { return Buttons != null && Buttons.Length != 0; } public RichPresence WithState(string state) { base.State = state; return this; } public RichPresence WithDetails(string details) { base.Details = details; return this; } public RichPresence WithTimestamps(Timestamps timestamps) { base.Timestamps = timestamps; return this; } public RichPresence WithAssets(Assets assets) { base.Assets = assets; return this; } public RichPresence WithParty(Party party) { base.Party = party; return this; } public RichPresence WithSecrets(Secrets secrets) { base.Secrets = secrets; return this; } public RichPresence Clone() { RichPresence richPresence = new RichPresence(); richPresence.State = ((_state != null) ? (_state.Clone() as string) : null); richPresence.Details = ((_details != null) ? (_details.Clone() as string) : null); richPresence.Buttons = ((!HasButtons()) ? null : (Buttons.Clone() as Button[])); richPresence.Secrets = ((!HasSecrets()) ? null : new Secrets { JoinSecret = ((base.Secrets.JoinSecret != null) ? (base.Secrets.JoinSecret.Clone() as string) : null), SpectateSecret = ((base.Secrets.SpectateSecret != null) ? (base.Secrets.SpectateSecret.Clone() as string) : null) }); richPresence.Timestamps = ((!HasTimestamps()) ? null : new Timestamps { Start = base.Timestamps.Start, End = base.Timestamps.End }); richPresence.Assets = ((!HasAssets()) ? null : new Assets { LargeImageKey = ((base.Assets.LargeImageKey != null) ? (base.Assets.LargeImageKey.Clone() as string) : null), LargeImageText = ((base.Assets.LargeImageText != null) ? (base.Assets.LargeImageText.Clone() as string) : null), SmallImageKey = ((base.Assets.SmallImageKey != null) ? (base.Assets.SmallImageKey.Clone() as string) : null), SmallImageText = ((base.Assets.SmallImageText != null) ? (base.Assets.SmallImageText.Clone() as string) : null) }); richPresence.Party = ((!HasParty()) ? null : new Party { ID = base.Party.ID, Size = base.Party.Size, Max = base.Party.Max, Privacy = base.Party.Privacy }); return richPresence; } internal RichPresence Merge(RichPresenceBase presence) { _state = presence.State; _details = presence.Details; base.Party = presence.Party; base.Timestamps = presence.Timestamps; base.Secrets = presence.Secrets; if (presence.HasAssets()) { if (!HasAssets()) { base.Assets = presence.Assets; } else { base.Assets.Merge(presence.Assets); } } else { base.Assets = null; } return this; } internal override bool Matches(RichPresence other) { if (!base.Matches(other) || ((Buttons == null) ^ (other.Buttons == null))) { return false; } if (Buttons == null) { return true; } if (Buttons.Length != other.Buttons.Length) { return false; } for (int i = 0; i < Buttons.Length; i++) { Button button = Buttons[i]; Button button2 = other.Buttons[i]; if (button.Label != button2.Label || button.Url != button2.Url) { return false; } } return true; } public static implicit operator bool(RichPresence presesnce) { return presesnce != null; } } [Serializable] [JsonObject(/*Could not decode attribute arguments.*/)] public class RichPresenceBase { protected internal string _state; protected internal string _details; [JsonProperty(/*Could not decode attribute arguments.*/)] public Timestamps Timestamps { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public Assets Assets { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public Party Party { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public Secrets Secrets { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] private bool Instance { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string State { get { return _state; } set { if (!ValidateString(value, out _state, 128, Encoding.UTF8)) { throw new StringOutOfRangeException("State", 0, 128); } } } [JsonProperty(/*Could not decode attribute arguments.*/)] public string Details { get { return _details; } set { if (!ValidateString(value, out _details, 128, Encoding.UTF8)) { throw new StringOutOfRangeException(128); } } } internal static bool ValidateString(string text, out string result, int bytes, Encoding encoding) { result = text; if (text == null) { return true; } string str = text.Trim(); if (!str.WithinLength(bytes, encoding)) { return false; } result = str.GetNullOrString(); return true; } public static implicit operator bool(RichPresenceBase presesnce) { return presesnce != null; } internal virtual bool Matches(RichPresence other) { if (other == null) { return false; } if (State != other.State || Details != other.Details) { return false; } if (Timestamps == null) { if (other.Timestamps == null || other.Timestamps.StartUnixMilliseconds != Timestamps.StartUnixMilliseconds || other.Timestamps.EndUnixMilliseconds != Timestamps.EndUnixMilliseconds) { return false; } } else if (other.Timestamps != null) { return false; } if (Secrets != null) { if (other.Secrets == null || other.Secrets.JoinSecret != Secrets.JoinSecret || other.Secrets.SpectateSecret != Secrets.SpectateSecret) { return false; } } else if (other.Secrets != null) { return false; } if (Party != null) { if (other.Party == null || other.Party.ID != Party.ID || other.Party.Max != Party.Max || other.Party.Size != Party.Size || other.Party.Privacy != Party.Privacy) { return false; } } else if (other.Party != null) { return false; } if (Assets != null) { if (other.Assets == null || other.Assets.LargeImageKey != Assets.LargeImageKey || other.Assets.LargeImageText != Assets.LargeImageText || other.Assets.SmallImageKey != Assets.SmallImageKey || other.Assets.SmallImageText != Assets.SmallImageText) { return false; } } else if (other.Assets != null) { return false; } return Instance == other.Instance; } public RichPresence ToRichPresence() { RichPresence richPresence = new RichPresence(); richPresence.State = State; richPresence.Details = Details; richPresence.Party = ((!HasParty()) ? Party : null); richPresence.Secrets = ((!HasSecrets()) ? Secrets : null); if (HasAssets()) { richPresence.Assets = new Assets { SmallImageKey = Assets.SmallImageKey, SmallImageText = Assets.SmallImageText, LargeImageKey = Assets.LargeImageKey, LargeImageText = Assets.LargeImageText }; } if (!HasTimestamps()) { return richPresence; } richPresence.Timestamps = new Timestamps(); if (Timestamps.Start.HasValue) { richPresence.Timestamps.Start = Timestamps.Start; } if (Timestamps.End.HasValue) { richPresence.Timestamps.End = Timestamps.End; } return richPresence; } public bool HasTimestamps() { return Timestamps != null && (Timestamps.Start.HasValue || Timestamps.End.HasValue); } public bool HasAssets() { return Assets != null; } public bool HasParty() { return Party != null && Party.ID != null; } public bool HasSecrets() { return Secrets != null && (Secrets.JoinSecret != null || Secrets.SpectateSecret != null); } } internal sealed class RichPresenceResponse : RichPresenceBase { [JsonProperty("application_id")] public string ClientID { get; private set; } [JsonProperty("name")] public string Name { get; private set; } } internal class RpcConnection : IDisposable { public const int VERSION = 1; public const int POLL_RATE = 1000; private const bool CLEAR_ON_SHUTDOWN = true; private const bool LOCK_STEP = false; private IConsoleLogger _logger; private RpcStates _state; private readonly object l_states = new object(); private Configuration _configuration; private readonly object l_config = new object(); private volatile bool aborting; private volatile bool shutdown; private string applicationID; private int processID; private long nonce; private Thread thread; private INamedPipeClient namedPipe; private int targetPipe; private readonly object l_rtqueue = new object(); private readonly uint _maxRtQueueSize; private Queue<ICommand> _rtqueue; private readonly object l_rxqueue; private readonly uint _maxRxQueueSize; private Queue<IMessage> _rxqueue; private AutoResetEvent queueUpdatedEvent = new AutoResetEvent(initialState: false); private BackoffDelay delay; public IConsoleLogger Logger { get { return _logger; } set { _logger = value; if (namedPipe != null) { namedPipe.Logger = value; } } } public RpcStates State { get { RpcStates result = RpcStates.Disconnected; lock (l_states) { result = _state; } return result; } } public Configuration Configuration { get { Configuration result = null; lock (l_config) { result = _configuration; } return result; } } public bool IsRunning => thread != null; public bool ShutdownOnly { get; set; } public event OnRpcMessageEvent OnRpcMessage; public RpcConnection(string applicationID, int processID, int targetPipe, INamedPipeClient client, uint maxRxQueueSize = 128u, uint maxRtQueueSize = 512u) { this.applicationID = applicationID; this.processID = processID; this.targetPipe = targetPipe; namedPipe = client; ShutdownOnly = true; Logger = new ConsoleLogger(); delay = new BackoffDelay(500, 60000); _maxRtQueueSize = maxRtQueueSize; _rtqueue = new Queue<ICommand>((int)(_maxRtQueueSize + 1)); _maxRxQueueSize = maxRxQueueSize; _rxqueue = new Queue<IMessage>((int)(_maxRxQueueSize + 1)); nonce = 0L; } private long GetNextNonce() { return ++nonce; } internal void EnqueueCommand(ICommand command) { Logger.Trace("Enqueue Command: " + command.GetType().FullName); if (aborting || shutdown) { return; } lock (l_rtqueue) { if (_rtqueue.Count == _maxRtQueueSize) { Logger.Error("Too many enqueued commands, dropping oldest one. Maybe you are pushing new presences to fast?"); _rtqueue.Dequeue(); } _rtqueue.Enqueue(command); } } private void EnqueueMessage(IMessage message) { try { this.OnRpcMessage?.Invoke(this, message); } catch (Exception ex) { Logger.Error("Unhandled Exception while processing event: " + ex.GetType().FullName); Logger.Error(ex.Message); Logger.Error(ex.StackTrace); } if (_maxRxQueueSize == 0) { Logger.Trace("Enqueued Message, but queue size is 0."); return; } Logger.Trace($"Enqueue Message: {message.Type}"); lock (l_rxqueue) { if (_rxqueue.Count == _maxRxQueueSize) { Logger.Warning("Too many enqueued messages, dropping oldest one."); _rxqueue.Dequeue(); } _rxqueue.Enqueue(message); } } internal IMessage DequeueMessage() { lock (l_rxqueue) { return (_rxqueue.Count == 0) ? null : _rxqueue.Dequeue(); } } internal IMessage[] DequeueMessages() { lock (l_rxqueue) { IMessage[] result = _rxqueue.ToArray(); _rxqueue.Clear(); return result; } } private void MainLoop() { Logger.Info("RPC Connection Started"); if (Logger != null) { Logger.Trace("============================"); Logger.Trace("Assembly: " + Assembly.GetAssembly(typeof(RichPresence)).FullName); Logger.Trace("Pipe: " + namedPipe.GetType().FullName); Logger.Trace($"Platform: {Environment.OSVersion}"); Logger.Trace("applicationID: " + applicationID); Logger.Trace($"targetPipe: {targetPipe}"); Logger.Trace($"POLL_RATE: {1000}"); Logger.Trace($"_maxRtQueueSize: {_maxRtQueueSize}"); Logger.Trace($"_maxRxQueueSize: {_maxRxQueueSize}"); Logger.Trace("============================"); } while (!aborting && !shutdown) { try { if (namedPipe == null) { Logger.Error("Something bad has happened with our pipe client!"); aborting = true; return; } Logger.Trace("Connecting to the pipe through the " + namedPipe.GetType().FullName); if (namedPipe.Connect(targetPipe)) { Logger.Trace("Connected to the pipe. Attempting to establish handshake..."); EnqueueMessage(new ConnectionEstablishedMessage { ConnectedPipe = namedPipe.ConnectedPipe }); EstablishHandshake(); Logger.Trace("Connection Established. Starting reading loop..."); bool flag = true; while (flag && !aborting && !shutdown && namedPipe.IsConnected) { if (namedPipe.ReadFrame(out var frame)) { Logger.Trace($"Read Payload: {frame.Opcode}"); switch (frame.Opcode) { case OpCode.Close: { ClosePayload @object = frame.GetObject<ClosePayload>(); Logger.Warning($"We have been told to terminate by discord: ({@object.Code}) {@object.Reason}"); EnqueueMessage(new CloseMessage { Code = @object.Code, Reason = @object.Reason }); flag = false; break; } case OpCode.Ping: Logger.Trace("PING"); frame.Opcode = OpCode.Pong; namedPipe.WriteFrame(frame); break; case OpCode.Pong: Logger.Trace("PONG"); break; case OpCode.Frame: { if (shutdown) { Logger.Warning("Skipping frame because we are shutting down."); break; } if (frame.Data == null) { Logger.Error("We received no data from the frame so we cannot get the event payload!"); break; } EventPayload eventPayload = null; try { eventPayload = frame.GetObject<EventPayload>(); } catch (Exception ex) { Logger.Error("Failed to parse event! " + ex.Message); Logger.Error("Data: " + frame.Message); } try { if (eventPayload != null) { ProcessFrame(eventPayload); } } catch (Exception ex2) { Logger.Error("Failed to process event! " + ex2.Message); Logger.Error("Data: " + frame.Message); } break; } default: Logger.Error($"Invalid opcode: {frame.Opcode}"); flag = false; break; } } if (!aborting && namedPipe.IsConnected) { ProcessCommandQueue(); queueUpdatedEvent.WaitOne(1000); } } Logger.Trace($"Left main read loop for some reason. Aborting: {aborting}, Shutting Down: {shutdown}"); } else { Logger.Error("Failed to connect for some reason."); EnqueueMessage(new ConnectionFailedMessage { FailedPipe = targetPipe }); } if (!aborting && !shutdown) { long num = delay.NextDelay(); Logger.Trace($"Waiting {num}ms before attempting to connect again"); Thread.Sleep(delay.NextDelay()); } } catch (Exception ex3) { Logger.Error("Unhandled Exception: " + ex3.GetType().FullName); Logger.Error(ex3.Message); Logger.Error(ex3.StackTrace); } finally { if (namedPipe.IsConnected) { Logger.Trace("Closing the named pipe."); namedPipe.Close(); } SetConnectionState(RpcStates.Disconnected); } } Logger.Trace("Left Main Loop"); namedPipe?.Dispose(); Logger.Info("Thread Terminated, no longer performing RPC connection."); } private void ProcessFrame(EventPayload response) { //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown Logger.Info($"Handling Response. Cmd: {response.Command}, Event: {response.Event}"); if (response.Event.HasValue && response.Event.Value == ServerEvent.Error) { Logger.Error("Error received from the RPC"); ErrorMessage @object = response.GetObject<ErrorMessage>(); Logger.Error("Server responded with an error message: (" + @object.Code.ToString() + ") " + @object.Message); EnqueueMessage(@object); } else if (State == RpcStates.Connecting && response.Command == NetDiscordRpc.RPC.Payload.Commands.Dispatch && response.Event.HasValue && response.Event.Value == ServerEvent.Ready) { Logger.Info("Connection established with the RPC"); SetConnectionState(RpcStates.Connected); delay.Reset(); ReadyMessage object2 = response.GetObject<ReadyMessage>(); lock (l_config) { _configuration = object2.Configuration; object2.User.SetConfiguration(_configuration); } EnqueueMessage(object2); } else if (State == RpcStates.Connected) { switch (response.Command) { case NetDiscordRpc.RPC.Payload.Commands.Dispatch: ProcessDispatch(response); break; case NetDiscordRpc.RPC.Payload.Commands.SetActivity: if (response.Data == null) { EnqueueMessage(new PresenceMessage()); } else { EnqueueMessage(new PresenceMessage(response.GetObject<RichPresenceResponse>())); } break; case NetDiscordRpc.RPC.Payload.Commands.Subscribe: case NetDiscordRpc.RPC.Payload.Commands.Unsubscribe: { JsonSerializer val = new JsonSerializer(); ((Collection<JsonConverter>)(object)val.Converters).Add((JsonConverter)(object)new EnumSnakeCaseConverter()); ServerEvent value = response.GetObject<EventPayload>().Event.Value; if (response.Command == NetDiscordRpc.RPC.Payload.Commands.Subscribe) { EnqueueMessage(new SubscribeMessage(value)); } else { EnqueueMessage(new UnsubscribeMessage(value)); } break; } case NetDiscordRpc.RPC.Payload.Commands.SendActivityJoinInvite: Logger.Trace("Got invite response ack."); break; case NetDiscordRpc.RPC.Payload.Commands.CloseActivityJoinRequest: Logger.Trace("Got invite response reject ack."); break; default: Logger.Error($"Unkown frame was received! {response.Command}"); break; } } else { Logger.Trace($"Received a frame while we are disconnected. Ignoring. Cmd: {response.Command}, Event: {response.Event}"); } } private void ProcessDispatch(EventPayload response) { if (response.Command == NetDiscordRpc.RPC.Payload.Commands.Dispatch && response.Event.HasValue) { switch (response.Event.Value) { case ServerEvent.ActivitySpectate: { SpectateMessage object3 = response.GetObject<SpectateMessage>(); EnqueueMessage(object3); break; } case ServerEvent.ActivityJoin: { JoinMessage object2 = response.GetObject<JoinMessage>(); EnqueueMessage(object2); break; } case ServerEvent.ActivityJoinRequest: { JoinRequestMessage @object = response.GetObject<JoinRequestMessage>(); EnqueueMessage(@object); break; } case ServerEvent.Ready: case ServerEvent.Error: break; default: Logger.Warning($"Ignoring {response.Event.Value}"); break; } } } private void ProcessCommandQueue() { if (State != RpcStates.Connected) { return; } if (aborting) { Logger.Warning("We have been told to write a queue but we have also been aborted."); } bool flag = true; while (flag && namedPipe.IsConnected) { ICommand command; lock (l_rtqueue) { flag = _rtqueue.Count > 0; if (!flag) { break; } command = _rtqueue.Peek(); } if (!shutdown) { if (aborting) { } } else if (true) { flag = false; } IPayload payload = command.PreparePayload(GetNextNonce()); Logger.Trace($"Attempting to send payload: {payload.Command}"); PipeFrame frame = default(PipeFrame); if (command is CloseCommand) { SendHandwave(); Logger.Trace("Handwave sent, ending queue processing."); lock (l_rtqueue) { _rtqueue.Dequeue(); break; } } if (aborting) { Logger.Warning("- skipping frame because of abort."); lock (l_rtqueue) { _rtqueue.Dequeue(); } continue; } frame.SetObject(OpCode.Frame, payload); Logger.Trace($"Sending payload: {payload.Command}"); if (namedPipe.WriteFrame(frame)) { Logger.Trace("Sent Successfully."); lock (l_rtqueue) { _rtqueue.Dequeue(); } continue; } Logger.Warning("Something went wrong during writing!"); break; } } private void EstablishHandshake() { Logger.Trace("Attempting to establish a handshake..."); if (State != 0) { Logger.Error("State must be disconnected in order to start a handshake!"); return; } Logger.Trace("Sending Handshake..."); if (!namedPipe.WriteFrame(new PipeFrame(OpCode.Handshake, new Handshake { Version = 1, ClientID = applicationID }))) { Logger.Error("Failed to write a handshake."); } else { SetConnectionState(RpcStates.Connecting); } } private void SendHandwave() { Logger.Info("Attempting to wave goodbye..."); if (State == RpcStates.Disconnected) { Logger.Error("State must NOT be disconnected in order to send a handwave!"); } else if (!namedPipe.WriteFrame(new PipeFrame(OpCode.Close, new Handshake { Version = 1, ClientID = applicationID }))) { Logger.Error("failed to write a handwave."); } } public bool AttemptConnection() { Logger.Info("Attempting a new connection"); if (thread != null) { Logger.Error("Cannot attempt a new connection as the previous connection thread is not null!"); return false; } if (State != 0) { Logger.Warning("Cannot attempt a new connection as the previous connection hasn't changed state yet."); return false; } if (aborting) { Logger.Error("Cannot attempt a new connection while aborting!"); return false; } thread = new Thread(MainLoop); thread.Name = "Discord IPC Thread"; thread.IsBackground = true; thread.Start(); return true; } private void SetConnectionState(RpcStates state) { Logger.Trace("Setting the connection state to " + state.ToString().ToSnakeCase().ToUpperInvariant()); lock (l_states) { _state = state; } } public void Shutdown() { Logger.Trace("Initiated shutdown procedure"); shutdown = true; lock (l_rtqueue) { _rtqueue.Clear(); bool flag = true; _rtqueue.Enqueue(new PresenceCommand { PID = processID, Presence = null }); _rtqueue.Enqueue(new CloseCommand()); } queueUpdatedEvent.Set(); } public void Close() { if (thread == null) { Logger.Error("Cannot close as it is not available!"); return; } if (aborting) { Logger.Error("Cannot abort as it has already been aborted"); return; } if (ShutdownOnly) { Shutdown(); return; } Logger.Trace("Updating Abort State..."); aborting = true; queueUpdatedEvent.Set(); } public void Dispose() { ShutdownOnly = false; Close(); } } internal enum RpcStates { Disconnected, Connecting, Connected } [Serializable] public class Secrets { private string _joinSecret; private string _spectateSecret; public static Encoding Encoding => Encoding.UTF8; public static int SecretLength => 128; [JsonProperty(/*Could not decode attribute arguments.*/)] public string JoinSecret { get { return _joinSecret; } set { if (!RichPresenceBase.ValidateString(value, out _joinSecret, 128, Encoding.UTF8)) { throw new StringOutOfRangeException(128); } } } [JsonProperty(/*Could not decode attribute arguments.*/)] public string SpectateSecret { get { return _spectateSecret; } set { if (!RichPresenceBase.ValidateString(value, out _spectateSecret, 128, Encoding.UTF8)) { throw new StringOutOfRangeException(128); } } } public static string CreateSecret(Random random) { byte[] array = new byte[SecretLength]; random.NextBytes(array); return Encoding.GetString(array); } public static string CreateFriendlySecret(Random random) { string text = ""; for (int i = 0; i < SecretLength; i++) { text += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[random.Next("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".Length)]; } return text; } } [Serializable] public class Timestamps { [JsonIgnore] public DateTime? Start { get; set; } [JsonIgnore] public DateTime? End { get; set; } public static Timestamps Now => new Timestamps(DateTime.UtcNow); [JsonProperty(/*Could not decode attribute arguments.*/)] public ulong? StartUnixMilliseconds { get { return Start.HasValue ? new ulong?(ToUnixMilliseconds(Start.Value)) : null; } set { Start = (value.HasValue ? new DateTime?(FromUnixMilliseconds(value.Value)) : null); } } [JsonProperty(/*Could not decode attribute arguments.*/)] public ulong? EndUnixMilliseconds { get { return End.HasValue ? new ulong?(ToUnixMilliseconds(End.Value)) : null; } set { End = (value.HasValue ? new DateTime?(FromUnixMilliseconds(value.Value)) : null); } } public Timestamps() { Start = null; End = null; } public Timestamps(DateTime start, DateTime? end = null) { Start = start; End = end; } public static Timestamps FromTimeSpan(double seconds) { return FromTimeSpan(TimeSpan.FromSeconds(seconds)); } public static Timestamps FromTimeSpan(TimeSpan timespan) { return new Timestamps { Start = DateTime.UtcNow, End = DateTime.UtcNow + timespan }; } public static DateTime FromUnixMilliseconds(ulong unixTime) { return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(Convert.ToDouble(unixTime)); } public static ulong ToUnixMilliseconds(DateTime date) { DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); return Convert.ToUInt64((date - dateTime).TotalMilliseconds); } } } namespace NetDiscordRpc.RPC.Payload { internal class ArgumentPayload : IPayload { [JsonProperty(/*Could not decode attribute arguments.*/)] public JObject Arguments { get; set; } public ArgumentPayload() { Arguments = null; } public ArgumentPayload(long nonce) : base(nonce) { Arguments = null; } public ArgumentPayload(object args, long nonce) : base(nonce) { SetObject(args); } public void SetObject(object obj) { Arguments = JObject.FromObject(obj); } public T GetObject<T>() { return ((JToken)Arguments).ToObject<T>(); } public override string ToString() { return "Argument " + base.ToString(); } } internal class ClosePayload : IPayload { [JsonProperty("code")] public int Code { get; set; } [JsonProperty("message")] public string Reason { get; set; } } public enum Commands { [EnumValue("DISPATCH")] Dispatch, [EnumValue("SET_ACTIVITY")] SetActivity, [EnumValue("SUBSCRIBE")] Subscribe, [EnumValue("UNSUBSCRIBE")] Unsubscribe, [EnumValue("SEND_ACTIVITY_JOIN_INVITE")] SendActivityJoinInvite, [EnumValue("CLOSE_ACTIVITY_JOIN_REQUEST")] CloseActivityJoinRequest } internal abstract class IPayload { [JsonProperty("cmd")] [JsonConverter(typeof(EnumSnakeCaseConverter))] public Commands Command { get; set; } [JsonProperty("nonce")] public string Nonce { get; set; } protected IPayload() { } protected IPayload(long nonce) { Nonce = nonce.ToString(); } public override string ToString() { return "Payload || Command: " + Command.ToString() + ", Nonce: " + ((Nonce != null) ? Nonce : "NULL"); } } internal class EventPayload : IPayload { [JsonProperty(/*Could not decode attribute arguments.*/)] public JObject Data { get; set; } [JsonProperty("evt")] [JsonConverter(typeof(EnumSnakeCaseConverter))] public ServerEvent? Event { get; set; } public EventPayload() { Data = null; } public EventPayload(long nonce) : base(nonce) { Data = null; } public T GetObject<T>() { return (Data == null) ? default(T) : ((JToken)Data).ToObject<T>(); } public override string ToString() { return "Event " + base.ToString() + ", Event: " + (Event.HasValue ? Event.ToString() : "N/A"); } } internal enum ServerEvent { [EnumValue("READY")] Ready, [EnumValue("ERROR")] Error, [EnumValue("ACTIVITY_JOIN")] ActivityJoin, [EnumValue("ACTIVITY_SPECTATE")] ActivitySpectate, [EnumValue("ACTIVITY_JOIN_REQUEST")] ActivityJoinRequest } } namespace NetDiscordRpc.RPC.Commands { internal class CloseCommand : ICommand { [JsonProperty("close_reason")] public string value = "Unity 5.5 doesn't handle thread aborts. Can you please close me discord?"; [JsonProperty("pid")] public int PID { get; set; } public IPayload PreparePayload(long nonce) { return new ArgumentPayload { Command = NetDiscordRpc.RPC.Payload.Commands.Dispatch, Nonce = null, Arguments = null }; } } internal interface ICommand { IPayload PreparePayload(long nonce); } internal class PresenceCommand : ICommand { [JsonProperty("pid")] public int PID { get; set; } [JsonProperty("activity")] public RichPresence Presence { get; set; } public IPayload PreparePayload(long nonce) { return new ArgumentPayload(this, nonce) { Command = NetDiscordRpc.RPC.Payload.Commands.SetActivity }; } } internal class RespondCommand : ICommand { [JsonProperty("user_id")] public string UserID { get; set; } [JsonIgnore] public bool Accept { get; set; } public IPayload PreparePayload(long nonce) { return new ArgumentPayload(this, nonce) { Command = (Accept ? NetDiscordRpc.RPC.Payload.Commands.SendActivityJoinInvite : NetDiscordRpc.RPC.Payload.Commands.CloseActivityJoinRequest) }; } } internal class SubscribeCommand : ICommand { public ServerEvent Event { get; set; } public bool IsUnsubscribe { get; set; } public IPayload PreparePayload(long nonce) { return new EventPayload(nonce) { Command = (IsUnsubscribe ? NetDiscordRpc.RPC.Payload.Commands.Unsubscribe : NetDiscordRpc.RPC.Payload.Commands.Subscribe), Event = Event }; } } } namespace NetDiscordRpc.Message { public enum ErrorCodes { Success = 0, PipeException = 1, ReadCorrupt = 2, NotImplemented = 10, UnkownError = 1000, InvalidPayload = 4000, InvalidCommand = 4002, InvalidEvent = 4004 } public abstract class IMessage { private DateTime _timecreated; public abstract MessageTypes Type { get; } public DateTime TimeCreated => _timecreated; public IMessage() { _timecreated = DateTime.Now; } } public enum MessageTypes { Ready, Close, Error, PresenceUpdate, Subscribe, Unsubscribe, Join, Spectate, JoinRequest, ConnectionEstablished, ConnectionFailed } } namespace NetDiscordRpc.Message.Messages { public class CloseMessage : IMessage { public string Reason { get; internal set; } public int Code { get; internal set; } public override MessageTypes Type => MessageTypes.Close; internal CloseMessage() { } internal CloseMessage(string reason) { Reason = reason; } } public class ConnectionEstablishedMessage : IMessage { public int ConnectedPipe { get; internal set; } public override MessageTypes Type => MessageTypes.ConnectionEstablished; } public class ConnectionFailedMessage : IMessage { public int FailedPipe { get; internal set; } public override MessageTypes Type => MessageTypes.ConnectionFailed; } public class ErrorMessage : IMessage { [JsonProperty("code")] public ErrorCodes Code { get; internal set; } [JsonProperty("message")] public string Message { get; internal set; } public override MessageTypes Type => MessageTypes.Error; } public class JoinMessage : IMessage { [JsonProperty("secret")] public string Secret { get; internal set; } public override MessageTypes Type => MessageTypes.Join; } public class JoinRequestMessage : IMessage { [JsonProperty("user")] public User User { get; internal set; } public override MessageTypes Type => MessageTypes.JoinRequest; } public class PresenceMessage : IMessage { public string Name { get; internal set; } public string ApplicationID { get; internal set; } public RichPresenceBase Presence { get; internal set; } public override MessageTypes Type => MessageTypes.PresenceUpdate; internal PresenceMessage() : this(null) { } internal PresenceMessage(RichPresenceResponse rpr) { if (rpr == null) { Presence = null; Name = "No Rich Presence"; ApplicationID = ""; } else { Presence = rpr; Name = rpr.Name; ApplicationID = rpr.ClientID; } } } public class ReadyMessage : IMessage { [JsonProperty("config")] public Configuration Configuration { get; set; } [JsonProperty("user")] public User User { get; set; } [JsonProperty("v")] public int Version { get; set; } public override MessageTypes Type => MessageTypes.Ready; } public class SpectateMessage : IMessage { public override MessageTypes Type => MessageTypes.Spectate; } public class SubscribeMessage : IMessage { public EventTypes Event { get; internal set; } public override MessageTypes Type => MessageTypes.Subscribe; internal SubscribeMessage(ServerEvent evt) { if (1 == 0) { } EventTypes @event = evt switch { ServerEvent.ActivityJoin => EventTypes.Join, ServerEvent.ActivityJoinRequest => EventTypes.JoinRequest, ServerEvent.ActivitySpectate => EventTypes.Spectate, _ => Event, }; if (1 == 0) { } Event = @event; } } public class UnsubscribeMessage : IMessage { public EventTypes Event { get; internal set; } public override MessageTypes Type => MessageTypes.Unsubscribe; internal UnsubscribeMessage(ServerEvent evt) { if (1 == 0) { } EventTypes @event = evt switch { ServerEvent.ActivityJoin => EventTypes.Join, ServerEvent.ActivityJoinRequest => EventTypes.JoinRequest, ServerEvent.ActivitySpectate => EventTypes.Spectate, _ => Event, }; if (1 == 0) { } Event = @event; } } } namespace NetDiscordRpc.Events { public delegate void OnReadyEvent(object sender, ReadyMessage args); public delegate void OnCloseEvent(object sender, CloseMessage args); public delegate void OnErrorEvent(object sender, ErrorMessage args); public delegate void OnPresenceUpdateEvent(object sender, PresenceMessage args); public delegate void OnSubscribeEvent(object sender, SubscribeMessage args); public delegate void OnUnsubscribeEvent(object sender, UnsubscribeMessage args); public delegate void OnJoinEvent(object sender, JoinMessage args); public delegate void OnSpectateEvent(object sender, SpectateMessage args); public delegate void OnJoinRequestedEvent(object sender, JoinRequestMessage args); public delegate void OnConnectionEstablishedEvent(object sender, ConnectionEstablishedMessage args); public delegate void OnConnectionFailedEvent(object sender, ConnectionFailedMessage args); public delegate void OnRpcMessageEvent(object sender, IMessage msg); [Flags] public enum EventTypes { None = 0, Spectate = 1, Join = 2, JoinRequest = 4 } } namespace NetDiscordRpc.Core.Registry { public interface IUriSchemeCreator { bool RegisterUriScheme(UriSchemeRegister register); } internal class MacUriSchemeCreator : IUriSchemeCreator { private IConsoleLogger logger; public MacUriSchemeCreator(IConsoleLogger logger) { this.logger = logger; } public bool RegisterUriScheme(UriSchemeRegister register) { string executablePath = register.ExecutablePath; if (string.IsNullOrEmpty(executablePath)) { logger.Error("Failed to register because the application could not be located."); return false; } logger.Trace("Registering Steam Command"); string text = executablePath; if (register.UsingSteamApp) { text = "steam://rungameid/" + register.SteamAppID; } else { logger.Warning("This library does not fully support MacOS URI Scheme Registration."); } DirectoryInfo directoryInfo = Directory.CreateDirectory("~/Library/Application Support/discord/games"); if (!directoryInfo.Exists) { logger.Error("Failed to register because ~/Library/Application Support/discord/games does not exist"); return false; } File.WriteAllText("~/Library/Application Support/discord/games/" + register.ApplicationID + ".json", "{ \"command\": \"" + text + "\" }"); logger.Trace("Registered ~/Library/Application Support/discord/games/" + register.ApplicationID + ".json, " + text); return true; } } internal class UnixUriSchemeCreator : IUriSchemeCreator { private IConsoleLogger logger; public UnixUriSchemeCreator(IConsoleLogger logger) { this.logger = logger; } public bool RegisterUriScheme(UriSchemeRegister register) { string environmentVariable = Environment.GetEnvironmentVariable("HOME"); if (string.IsNullOrEmpty(environmentVariable)) { logger.Error("Failed to register because the HOME variable was not set."); return false; } string executablePath = register.ExecutablePath; if (string.IsNullOrEmpty(executablePath)) { logger.Error("Failed to register because the application was not located."); return false; } string text = ((!register.UsingSteamApp) ? executablePath : ("xdg-open steam://rungameid/" + register.SteamAppID)); string text2 = $"[Desktop Entry]Name=Game {register.ApplicationID}Exec={text} %uType=ApplicationNoDisplay=trueCategories=Discord;Games;MimeType=x-scheme-handler/discord-{register.ApplicationID}"; string text3 = "/discord-" + register.ApplicationID + ".desktop"; string text4 = environmentVariable + "/.local/share/applications"; DirectoryInfo directoryInfo = Directory.CreateDirectory(text4); if (!directoryInfo.Exists) { logger.Error("Failed to register because " + text4 + " does not exist"); return false; } File.WriteAllText(text4 + text3, text2); if (!RegisterMime(register.ApplicationID)) { logger.Error("Failed to register because the Mime failed."); return false; } logger.Trace("Registered " + text4 + text3 + ", " + text2 + ", " + text); return true; } private static bool RegisterMime(string appid) { string arguments = string.Format("default discord-{0}.desktop x-scheme-handler/discord-{0}", appid); Process process = Process.Start("xdg-mime", arguments); process.WaitForExit(); return process.ExitCode >= 0; } } public class UriSchemeRegister { private IConsoleLogger _logger; public string ApplicationID { get; set; } public string SteamAppID { get; set; } public bool UsingSteamApp => !string.IsNullOrEmpty(SteamAppID) && SteamAppID != ""; public string ExecutablePath { get; set; } public UriSchemeRegister(IConsoleLogger logger, string applicationID, string steamAppID = null, string executable = null) { _logger = logger; ApplicationID = applicationID.Trim(); SteamAppID = steamAppID?.Trim(); ExecutablePath = executable ?? GetApplicationLocation(); } public bool RegisterUriScheme() { IUriSchemeCreator uriSchemeCreator; switch (Environment.OSVersion.Platform) { case PlatformID.Win32S: case PlatformID.Win32Windows: case PlatformID.Win32NT: case PlatformID.WinCE: _logger.Trace("Creating Windows Scheme Creator"); uriSchemeCreator = new WindowsUriSchemeCreator(_logger); break; case PlatformID.Unix: _logger.Trace("Creating Unix Scheme Creator"); uriSchemeCreator = new UnixUriSchemeCreator(_logger); break; case PlatformID.MacOSX: _logger.Trace("Creating MacOSX Scheme Creator"); uriSchemeCreator = new MacUriSchemeCreator(_logger); break; case PlatformID.Xbox: case PlatformID.Other: _logger.Error($"Unkown Platform: {Environment.OSVersion.Platform}"); throw new PlatformNotSupportedException("Platform does not support registration."); default: _logger.Error($"Unkown Platform: {Environment.OSVersion.Platform}"); throw new PlatformNotSupportedException("Platform does not support registration."); } if (!uriSchemeCreator.RegisterUriScheme(this)) { return false; } _logger.Info("URI scheme registered."); return true; } public static string GetApplicationLocation() { return Process.GetCurrentProcess().MainModule.FileName; } } internal class WindowsUriSchemeCreator : IUriSchemeCreator { private IConsoleLogger logger; public WindowsUriSchemeCreator(IConsoleLogger logger) { this.logger = logger; } public bool RegisterUriScheme(UriSchemeRegister register) { PlatformID platform = Environment.OSVersion.Platform; if (platform == PlatformID.Unix || platform == PlatformID.MacOSX) { throw new PlatformNotSupportedException("URI schemes can only be registered on Windows"); } string executablePath = register.ExecutablePath; if (executablePath == null) { logger.Error("Failed to register application because the location was null."); return false; } string scheme = "discord-" + register.ApplicationID; string friendlyName = "Run game " + register.ApplicationID + " protocol"; string command = executablePath; if (register.UsingSteamApp) { string steamLocation = GetSteamLocation(); if (steamLocation != null) { command = "\"" + steamLocation + "\" steam://rungameid/" + register.SteamAppID; } } CreateUriScheme(scheme, friendlyName, executablePath, command); return true; } private void CreateUriScheme(string scheme, string friendlyName, string defaultIcon, string command) { using (RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("SOFTWARE\\Classes\\" + scheme)) { registryKey.SetValue("", "URL:" + friendlyName); registryKey.SetValue("URL Protocol", ""); using (RegistryKey registryKey2 = registryKey.CreateSubKey("DefaultIcon")) { registryKey2.SetValue("", defaultIcon); } using RegistryKey registryKey3 = registryKey.CreateSubKey("shell\\open\\command"); registryKey3.SetValue("", command); } logger.Trace("Registered " + scheme + ", " + friendlyName + ", " + command); } public static string GetSteamLocation() { using RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\Valve\\Steam"); return registryKey?.GetValue("SteamExe") as string; } } } namespace NetDiscordRpc.Core.Logger { public class ConsoleLogger : IConsoleLogger { private static string GetLogType(ConsoleLogLevel level) { if (1 == 0) { } string result = level switch { ConsoleLogLevel.Info => "info ", ConsoleLogLevel.Warning => "warn ", ConsoleLogLevel.Error => "error", ConsoleLogLevel.Trace => "trace", ConsoleLogLevel.None => "none ", _ => throw new ArgumentOutOfRangeException("level"), }; if (1 == 0) { } return result; } public void Trace(string message) { Log(ConsoleLogLevel.Trace, ConsoleColor.Gray, message); } public void Info(string message) { Log(ConsoleLogLevel.Info, ConsoleColor.White, message); } public void Warning(string message) { Log(ConsoleLogLevel.Warning, ConsoleColor.Yellow, message); } public void Error(string message) { Log(ConsoleLogLevel.Error, ConsoleColor.Red, message); } private static void Log(ConsoleLogLevel level, ConsoleColor color, string log) { Console.ForegroundColor = color; Console.WriteLine($"[{DateTime.Now}] {GetLogType(level)}: {log}"); Console.ResetColor(); } } public enum ConsoleLogLevel { Trace = 1, Info = 2, Warning = 3, Error = 4, None = 256 } public interface IConsoleLogger { void Trace(string message); void Info(string message); void Warning(string message); void Error(string message); } public class NullLogger : IConsoleLogger { public void Trace(string message) { } public void Info(string message) { } public void Warning(string message) { } public void Error(string message) { } } } namespace NetDiscordRpc.Core.IO { internal class Handshake { [JsonProperty("v")] public int Version { get; set; } [JsonProperty("client_id")] public string ClientID { get; set; } } public interface INamedPipeClient : IDisposable { IConsoleLogger Logger { get; set; } bool IsConnected { get; } int ConnectedPipe { get; } bool Connect(int pipe); bool ReadFrame(out PipeFrame frame); bool WriteFrame(PipeFrame frame); void Close(); } public sealed class ManagedNamedPipeClient : INamedPipeClient, IDisposable { private const string PipeName = "discord-ipc-{0}"; private int _connectedPipe; private NamedPipeClientStream _stream; private byte[] _buffer = new byte[PipeFrame.MAX_SIZE]; private Queue<PipeFrame> _framequeue = new Queue<PipeFrame>(); private object _framequeuelock = new object(); private volatile bool _isDisposed; private volatile bool _isClosed = true; private object l_stream = new object(); public IConsoleLogger Logger { get; set; } public bool IsConnected { get { if (_isClosed) { return false; } lock (l_stream) { return _stream != null && _stream.IsConnected; } } } public int ConnectedPipe => _connectedPipe; public ManagedNamedPipeClient() { _buffer = new byte[PipeFrame.MAX_SIZE]; Logger = new ConsoleLogger(); _stream = null; } public bool Connect(int pipe) { Logger.Trace($"ManagedNamedPipeClient.Connection({pipe})"); if (_isDisposed) { throw new ObjectDisposedException("NamedPipe"); } if (pipe <= 9) { if (pipe < 0) { for (int i = 0; i < 10; i++) { if (AttemptConnection(i) || AttemptConnection(i, isSandbox: true)) { BeginReadStream(); return true; } } return false; } if (!AttemptConnection(pipe) && !AttemptConnection(pipe, isSandbox: true)) { return false; } BeginReadStream(); return true; } throw new ArgumentOutOfRangeException("pipe", "Argument cannot be greater than 9"); } private bool AttemptConnection(int pipe, bool isSandbox = false) { if (_isDisposed) { throw new ObjectDisposedException("_stream"); } string text = (isSandbox ? GetPipeSandbox() : ""); if (isSandbox && text == null) { Logger.Trace("Skipping sandbox connection."); return false; } Logger.Trace($"Connection Attempt {pipe} ({text})"); string pipeName = GetPipeName(pipe, text); try { lock (l_stream) { Logger.Info("Attempting to connect to " + pipeName); _stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); _stream.Connect(1000); Logger.Trace("Waiting for connection..."); do { Thread.Sleep(10); } while (!_stream.IsConnected); } Logger.Info("Connected to " + pipeName); _connectedPipe = pipe; _isClosed = false; } catch (Exception ex) { Logger.Error("Failed connection to " + pipeName + ". " + ex.Message); Close(); } Logger.Trace($"Done. Result: {_isClosed}"); return !_isClosed; } private void BeginReadStream() { if (_isClosed) { return; } try { lock (l_stream) { NamedPipeClientStream stream = _stream; if (stream != null && stream.IsConnected) { Logger.Trace($"Begining Read of {_buffer.Length} bytes"); _stream.BeginRead(_buffer, 0, _buffer.Length, EndReadStream, _stream.IsConnected); } } } catch (ObjectDisposedException) { Logger.Warning("Attempted to start reading from a disposed pipe"); } catch (InvalidOperationException) { Logger.Warning("Attempted to start reading from a closed pipe"); } catch (Exception ex3) { Logger.Error("An exception occured while starting to read a stream: " + ex3.Message); Logger.Error(ex3.StackTrace); } } private void EndReadStream(IAsyncResult callback) { Logger.Trace("Ending Read"); int num; try { lock (l_stream) { NamedPipeClientStream stream = _stream; if (stream == null || !stream.IsConnected) { return; } num = _stream.EndRead(callback); } } catch (IOException) { Logger.Warning("Attempted to end reading from a closed pipe"); return; } catch (NullReferenceException) { Logger.Warning("Attempted to read from a null pipe"); return; } catch (ObjectDisposedException) { Logger.Warning("Attemped to end reading from a disposed pipe"); return; } catch (Exception ex4) { Logger.Error($"An exception occured while ending a read of a stream: {ex4.Message}"); Logger.Error(ex4.StackTrace); return; } Logger.Trace($"Read {num} bytes"); if (num > 0) { using MemoryStream stream2 = new MemoryStream(_buffer, 0, num); try { PipeFrame item = default(PipeFrame); if (item.ReadStream(stream2)) { Logger.Trace($"Read a frame: {item.Opcode}"); lock (_framequeuelock) { _framequeue.Enqueue(item); } } else { Logger.Error("Pipe failed to read from the data received by the stream."); Close(); } } catch (Exception ex5) { Logger.Error("A exception has occured while trying to parse the pipe data: " + ex5.Message); Close(); } } else if (IsUnix()) { Logger.Error($"Empty frame was read on {Environment.OSVersion} aborting."); Close(); } else { Logger.Warning("Empty frame was read. Please send report to Lachee."); } if (!_isClosed && IsConnected) { Logger.Trace("Starting another read"); BeginReadStream(); } } public bool ReadFrame(out PipeFrame frame) { if (_isDisposed) { throw new ObjectDisposedException("_stream"); } lock (_framequeuelock) { if (_framequeue.Count == 0) { frame = default(PipeFrame); return false; } frame = _framequeue.Dequeue(); return true; } } public bool WriteFrame(PipeFrame frame) { if (_isDisposed) { throw new ObjectDisposedException("_stream"); } if (_isClosed || !IsConnected) { Logger.Error("Failed to write frame because the stream is closed"); return false; } try { frame.WriteStream(_stream); return true; } catch (IOException ex) { Logger.Error("Failed to write frame because of a IO Exception: " + ex.Message); } catch (ObjectDisposedException) { Logger.Warning("Failed to write frame as the stream was already disposed"); } catch (InvalidOperationException) { Logger.Warning("Failed to write frame because of a invalid operation"); } return false; } public void Close() { if (_isClosed) { Logger.Warning("Tried to close a already closed pipe."); return; } try { lock (l_stream) { if (_stream != null) { try { _stream.Flush(); _stream.Dispose(); } catch (Exception ex) { Logger.Error("Something went wrong when closing the stream: [" + ex.Message + "]"); } _stream = null; _isClosed = true; } else { Logger.Warning("Stream was closed, but no stream was available to begin with!"); } } } catch (ObjectDisposedException) { Logger.Warning("Tried to dispose already disposed stream"); } finally { _isClosed = true; _connectedPipe = -1; } } public void Dispose() { if (_isDisposed) { return; } if (!_isClosed) { Close(); } lock (l_stream) { if (_stream != null) { _stream.Dispose(); _stream = null; } } _isDisposed = true; } public static string GetPipeName(int pipe, string sandbox = "") { if (!IsUnix()) { return sandbox + $"discord-ipc-{pipe}"; } return Path.Combine(GetTemporaryDirectory(), sandbox + $"discord-ipc-{pipe}"); } public static string GetPipeSandbox() { PlatformID platform = Environment.OSVersion.Platform; if (1 == 0) { } string result = ((platform != PlatformID.Unix) ? null : "snap.discord/"); if (1 == 0) { } return result; } private static string GetTemporaryDirectory() { string text = null; text = text ?? Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); text = text ?? Environment.GetEnvironmentVariable("TMPDIR"); text = text ?? Environment.GetEnvironmentVariable("TMP"); text = text ?? Environment.GetEnvironmentVariable("TEMP"); return text ?? "/tmp"; } public static bool IsUnix() { switch (Environment.OSVersion.Platform) { case PlatformID.Win32S: case PlatformID.Win32Windows: case PlatformID.Win32NT: case PlatformID.WinCE: case PlatformID.Xbox: case PlatformID.Other: return false; default: return false; case PlatformID.Unix: case PlatformID.MacOSX: return true; } } } public enum OpCode : uint { Handshake, Frame, Close, Ping, Pong } public struct PipeFrame { public static readonly int MAX_SIZE = 16384; public OpCode Opcode { get; set; } public uint Length => (uint)Data.Length; public byte[] Data { get; set; } public string Message { get { return GetMessage(); } set { SetMessage(value); } } public static Encoding MessageEncoding => Encoding.UTF8; public PipeFrame(OpCode opcode, object data) { Opcode = opcode; Data = null; SetObject(data); } private void SetMessage(string str) { Data = MessageEncoding.GetBytes(str); } private string GetMessage() { return MessageEncoding.GetString(Data); } public void SetObject(object obj) { SetMessage(JsonConvert.SerializeObject(obj)); } public void SetObject(OpCode opcode, object obj) { Opcode = opcode; SetObject(obj); } public T GetObject<T>() { return JsonConvert.DeserializeObject<T>(GetMessage()); } public bool ReadStream(Stream stream) { if (!TryReadUInt32(stream, out var value)) { return false; } if (!TryReadUInt32(stream, out var value2)) { return false; } uint num = value2; using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[Min(2048, value2)]; int count; while ((count = stream.Read(array, 0, Min(array.Length, num))) > 0) { num -= value2; memoryStream.Write(array, 0, count); } byte[] array2 = memoryStream.ToArray(); if (array2.LongLength != value2) { return false; } Opcode = (OpCode)value; Data = array2; return true; } private static int Min(int a, uint b) { if (b >= a) { return a; } return (int)b; } private static bool TryReadUInt32(Stream stream, out uint value) { byte[] array = new byte[4]; int num = stream.Read(array, 0, array.Length); if (num != 4) { value = 0u; return false; } value = BitConverter.ToUInt32(array, 0); return true; } public void WriteStream(Stream stream) { byte[] bytes = BitConverter.GetBytes((uint)Opcode); byte[] bytes2 = BitConverter.GetBytes(Length); byte[] array = new byte[bytes.Length + bytes2.Length + Data.Length]; bytes.CopyTo(array, 0); bytes2.CopyTo(array, bytes.Length); Data.CopyTo(array, bytes.Length + bytes2.Length); stream.Write(array, 0, array.Length); } } } namespace NetDiscordRpc.Core.Helpers { internal class BackoffDelay { private int _current; private int _fails; public int Maximum { get; private set; } public int Minimum { get; private set; } public int Current => _current; public int Fails => _fails; public Random Random { get; set; } private BackoffDelay() { } public BackoffDelay(int min, int max) : this(min, max, new Random()) { } public BackoffDelay(int min, int max, Random random) { Minimum = min; Maximum = max; _current = min; _fails = 0; Random = random; } public void Reset() { _fails = 0; _current = Minimum; } public int NextDelay() { _fails++; double num = (double)(Maximum - Minimum) / 100.0; _current = (int)Math.Floor(num * (double)_fails) + Minimum; return Math.Min(Math.Max(_current, Minimum), Maximum); } } public static class StringHelper { public static string GetNullOrString(this string str) { return (str.Length == 0 || string.IsNullOrEmpty(str.Trim())) ? null : str; } public static bool WithinLength(this string str, int bytes) { return str.WithinLength(bytes, Encoding.UTF8); } public static bool WithinLength(this string str, int bytes, Encoding encoding) { return encoding.GetByteCount(str) <= bytes; } public static string ToCamelCase(this string str) { return (from s in str?.ToLower().Split(new string[2] { "_", " " }, StringSplitOptions.RemoveEmptyEntries) select char.ToUpper(s[0]) + s.Substring(1, s.Length - 1)).Aggregate(string.Empty, (string s1, string s2) => s1 + s2); } public static string ToSnakeCase(this string str) { if (str == null) { return null; } string text = string.Concat(str.Select((char x, int i) => (i > 0 && char.IsUpper(x)) ? ("_" + x) : x.ToString()).ToArray()); return text.ToUpper(); } } } namespace NetDiscordRpc.Core.Exceptions { public class BadPresenceException : Exception { internal BadPresenceException(string message) : base(message) { } } public class InvalidConfigurationException : Exception { internal InvalidConfigurationException(string message) : base(message) { } } public class NetDiscordRpcException : Exception { public NetDiscordRpcException(string message) : base(message) { } } public class StringOutOfRangeException : Exception { public int MaximumLength { get; private set; } public int MinimumLength { get; private set; } internal StringOutOfRangeException(string message, int min, int max) : base(message) { MinimumLength = min; MaximumLength = max; } internal StringOutOfRangeException(int minumum, int max) : this("Length of string is out of range. Expected a value between " + minumum + " and " + max, minumum, max) { } internal StringOutOfRangeException(int max) : this("Length of string is out of range. Expected a value with a maximum length of " + max, 0, max) { } } public class UninitializedException : Exception { internal UninitializedException(string message) : base(message) { } internal UninitializedException() : this("Cannot perform action because the client has not been initialized yet or has been deinitialized.") { } } } namespace NetDiscordRpc.Core.Converters { internal class EnumSnakeCaseConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType.IsEnum; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.Value == null) { return null; } object obj; return TryParseEnum(objectType, (string)reader.Value, out obj) ? obj : existingValue; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Type type = value.GetType(); string text = Enum.GetName(type, value); MemberInfo[] members = type.GetMembers(BindingFlags.Static | BindingFlags.Public); MemberInfo[] array = members; foreach (MemberInfo memberInfo in array) { if (memberInfo.Name.Equals(text)) { object[] customAttributes = memberInfo.GetCustomAttributes(typeof(EnumValueAttribute), inherit: true); if (customAttributes.Length != 0) { text = ((EnumValueAttribute)customAttributes[0]).Value; } } } writer.WriteValue(text); } public static bool TryParseEnum(Type enumType, string str, out object obj) { if (str == null) { obj = null; return false; } Type type = enumType; if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { type = type.GetGenericArguments().First(); } if (!type.IsEnum) { obj = null; return false; } MemberInfo[] members = type.GetMembers(BindingFlags.Static | BindingFlags.Public); MemberInfo[] array = members; foreach (MemberInfo memberInfo in array) { object[] customAttributes = memberInfo.GetCustomAttributes(typeof(EnumValueAttribute), inherit: true); if (customAttributes.Cast<EnumValueAttribute>().Any((EnumValueAttribute enumval) => str.Equals(enumval.Value))) { obj = Enum.Parse(type, memberInfo.Name, ignoreCase: true); return true; } } obj = null; return false; } } internal class EnumValueAttribute : Attribute { public string Value { get; set; } public EnumValueAttribute(string value) { Value = value; } } }