using 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.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using DiscordRPC.Converters;
using DiscordRPC.Events;
using DiscordRPC.Exceptions;
using DiscordRPC.Helper;
using DiscordRPC.IO;
using DiscordRPC.Logging;
using DiscordRPC.Message;
using DiscordRPC.RPC;
using DiscordRPC.RPC.Commands;
using DiscordRPC.RPC.Payload;
using DiscordRPC.Registry;
using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Discord RPC")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Discord RPC")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("819d20d6-8d88-45c1-a4d2-aa21f10abd19")]
[assembly: AssemblyFileVersion("1.2.1.24")]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyVersion("1.2.1.24")]
namespace DiscordRPC
{
public class Configuration
{
[JsonProperty("api_endpoint")]
public string ApiEndpoint { get; set; }
[JsonProperty("cdn_host")]
public string CdnHost { get; set; }
[JsonProperty("environment")]
public string Environment { get; set; }
}
public sealed class DiscordRpcClient : IDisposable
{
private ILogger _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 ILogger 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 EventType Subscription { get; private 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 DiscordRpcClient(string applicationID)
: this(applicationID, -1, null, autoEvents: true, null)
{
}
public DiscordRpcClient(string applicationID, int pipe = -1, ILogger logger = null, bool autoEvents = true, INamedPipeClient client = null)
{
if (string.IsNullOrEmpty(applicationID))
{
throw new ArgumentNullException("applicationID");
}
ApplicationID = applicationID.Trim();
TargetPipe = pipe;
ProcessID = Process.GetCurrentProcess().Id;
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 sender, IMessage msg)
{
if (this.OnRpcMessage != null)
{
this.OnRpcMessage(this, msg);
}
if (AutoEvents)
{
ProcessMessage(msg);
}
};
}
public IMessage[] Invoke()
{
if (AutoEvents)
{
Logger.Error("Cannot Invoke client when AutomaticallyInvokeEvents has been set.");
return new IMessage[0];
}
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 MessageType.PresenceUpdate:
lock (_sync)
{
if (message is PresenceMessage presenceMessage)
{
if (presenceMessage.Presence == null)
{
CurrentPresence = null;
}
else if (CurrentPresence == null)
{
CurrentPresence = new RichPresence().Merge(presenceMessage.Presence);
}
else
{
CurrentPresence.Merge(presenceMessage.Presence);
}
presenceMessage.Presence = CurrentPresence;
}
}
if (this.OnPresenceUpdate != null)
{
this.OnPresenceUpdate(this, message as PresenceMessage);
}
break;
case MessageType.Ready:
if (message is ReadyMessage readyMessage)
{
lock (_sync)
{
Configuration = readyMessage.Configuration;
CurrentUser = readyMessage.User;
}
SynchronizeState();
}
if (this.OnReady != null)
{
this.OnReady(this, message as ReadyMessage);
}
break;
case MessageType.Close:
if (this.OnClose != null)
{
this.OnClose(this, message as CloseMessage);
}
break;
case MessageType.Error:
if (this.OnError != null)
{
this.OnError(this, message as ErrorMessage);
}
break;
case MessageType.JoinRequest:
if (Configuration != null && message is JoinRequestMessage joinRequestMessage)
{
joinRequestMessage.User.SetConfiguration(Configuration);
}
if (this.OnJoinRequested != null)
{
this.OnJoinRequested(this, message as JoinRequestMessage);
}
break;
case MessageType.Subscribe:
lock (_sync)
{
SubscribeMessage subscribeMessage = message as SubscribeMessage;
Subscription |= subscribeMessage.Event;
}
if (this.OnSubscribe != null)
{
this.OnSubscribe(this, message as SubscribeMessage);
}
break;
case MessageType.Unsubscribe:
lock (_sync)
{
UnsubscribeMessage unsubscribeMessage = message as UnsubscribeMessage;
Subscription &= ~unsubscribeMessage.Event;
}
if (this.OnUnsubscribe != null)
{
this.OnUnsubscribe(this, message as UnsubscribeMessage);
}
break;
case MessageType.Join:
if (this.OnJoin != null)
{
this.OnJoin(this, message as JoinMessage);
}
break;
case MessageType.Spectate:
if (this.OnSpectate != null)
{
this.OnSpectate(this, message as SpectateMessage);
}
break;
case MessageType.ConnectionEstablished:
if (this.OnConnectionEstablished != null)
{
this.OnConnectionEstablished(this, message as ConnectionEstablishedMessage);
}
break;
case MessageType.ConnectionFailed:
if (this.OnConnectionFailed != null)
{
this.OnConnectionFailed(this, message as ConnectionFailedMessage);
}
break;
default:
Logger.Error("Message was queued with no appropriate handle! {0}", 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 == null)
{
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 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 SetButton(Button button, int index = 0)
{
if (!IsInitialized)
{
throw new UninitializedException();
}
RichPresence richPresence;
lock (_sync)
{
richPresence = ((CurrentPresence != null) ? CurrentPresence.Clone() : new RichPresence());
}
richPresence.Buttons[index] = button;
SetPresence(richPresence);
return richPresence;
}
public RichPresence UpdateDetails(string details)
{
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)
{
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)
{
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)
{
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 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 bool RegisterUriScheme(string steamAppID = null, string executable = null)
{
UriSchemeRegister uriSchemeRegister = new UriSchemeRegister(_logger, ApplicationID, steamAppID, executable);
return HasRegisteredUriScheme = uriSchemeRegister.RegisterUriScheme();
}
public void Subscribe(EventType type)
{
SetSubscription(Subscription | type);
}
[Obsolete("Replaced with Unsubscribe", true)]
public void Unubscribe(EventType type)
{
SetSubscription(Subscription & ~type);
}
public void Unsubscribe(EventType type)
{
SetSubscription(Subscription & ~type);
}
public void SetSubscription(EventType 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(EventType 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 & EventType.Spectate) == EventType.Spectate)
{
connection.EnqueueCommand(new SubscribeCommand
{
Event = ServerEvent.ActivitySpectate,
IsUnsubscribe = isUnsubscribe
});
}
if ((type & EventType.Join) == EventType.Join)
{
connection.EnqueueCommand(new SubscribeCommand
{
Event = ServerEvent.ActivityJoin,
IsUnsubscribe = isUnsubscribe
});
}
if ((type & EventType.JoinRequest) == EventType.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;
}
}
}
[Flags]
public enum EventType
{
None = 0,
Spectate = 1,
Join = 2,
JoinRequest = 4
}
[Serializable]
[JsonObject(/*Could not decode attribute arguments.*/)]
public class BaseRichPresence
{
protected internal string _state;
protected internal string _details;
[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);
}
}
}
[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.*/)]
[Obsolete("This was going to be used, but was replaced by JoinSecret instead")]
private bool Instance { get; set; }
public bool HasTimestamps()
{
if (Timestamps != null)
{
if (!Timestamps.Start.HasValue)
{
return Timestamps.End.HasValue;
}
return true;
}
return false;
}
public bool HasAssets()
{
return Assets != null;
}
public bool HasParty()
{
if (Party != null)
{
return Party.ID != null;
}
return false;
}
public bool HasSecrets()
{
if (Secrets != null)
{
if (Secrets.JoinSecret == null)
{
return Secrets.SpectateSecret != null;
}
return true;
}
return false;
}
internal static bool ValidateString(string str, out string result, int bytes, Encoding encoding)
{
result = str;
if (str == null)
{
return true;
}
string str2 = str.Trim();
if (!str2.WithinLength(bytes, encoding))
{
return false;
}
result = str2.GetNullOrString();
return true;
}
public static implicit operator bool(BaseRichPresence 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.MatchSecret != Secrets.MatchSecret || 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())
{
richPresence.Timestamps = new Timestamps();
if (Timestamps.Start.HasValue)
{
richPresence.Timestamps.Start = Timestamps.Start;
}
if (Timestamps.End.HasValue)
{
richPresence.Timestamps.End = Timestamps.End;
}
}
return richPresence;
}
}
[Serializable]
public class Secrets
{
private string _matchSecret;
private string _joinSecret;
private string _spectateSecret;
[Obsolete("This feature has been deprecated my Mason in issue #152 on the offical library. Was originally used as a Notify Me feature, it has been replaced with Join / Spectate.")]
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string MatchSecret
{
get
{
return _matchSecret;
}
set
{
if (!BaseRichPresence.ValidateString(value, out _matchSecret, 128, Encoding.UTF8))
{
throw new StringOutOfRangeException(128);
}
}
}
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string JoinSecret
{
get
{
return _joinSecret;
}
set
{
if (!BaseRichPresence.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 (!BaseRichPresence.ValidateString(value, out _spectateSecret, 128, Encoding.UTF8))
{
throw new StringOutOfRangeException(128);
}
}
}
public static Encoding Encoding => Encoding.UTF8;
public static int SecretLength => 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 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < SecretLength; i++)
{
stringBuilder.Append(text[random.Next(text.Length)]);
}
return stringBuilder.ToString();
}
}
[Serializable]
public class Assets
{
private string _largeimagekey;
private bool _islargeimagekeyexternal;
private string _largeimagetext;
private string _smallimagekey;
private bool _issmallimagekeyexternal;
private string _smallimagetext;
private ulong? _largeimageID;
private ulong? _smallimageID;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string LargeImageKey
{
get
{
return _largeimagekey;
}
set
{
if (!BaseRichPresence.ValidateString(value, out _largeimagekey, 256, Encoding.UTF8))
{
throw new StringOutOfRangeException(256);
}
_islargeimagekeyexternal = _largeimagekey?.StartsWith("mp:external/") ?? false;
_largeimageID = null;
}
}
[JsonIgnore]
public bool IsLargeImageKeyExternal => _islargeimagekeyexternal;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string LargeImageText
{
get
{
return _largeimagetext;
}
set
{
if (!BaseRichPresence.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 (!BaseRichPresence.ValidateString(value, out _smallimagekey, 256, Encoding.UTF8))
{
throw new StringOutOfRangeException(256);
}
_issmallimagekeyexternal = _smallimagekey?.StartsWith("mp:external/") ?? false;
_smallimageID = null;
}
}
[JsonIgnore]
public bool IsSmallImageKeyExternal => _issmallimagekeyexternal;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string SmallImageText
{
get
{
return _smallimagetext;
}
set
{
if (!BaseRichPresence.ValidateString(value, out _smallimagetext, 128, Encoding.UTF8))
{
throw new StringOutOfRangeException(128);
}
}
}
[JsonIgnore]
public ulong? LargeImageID => _largeimageID;
[JsonIgnore]
public ulong? SmallImageID => _smallimageID;
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;
}
}
[Serializable]
public class Timestamps
{
public static Timestamps Now => new Timestamps(DateTime.UtcNow);
[JsonIgnore]
public DateTime? Start { get; set; }
[JsonIgnore]
public DateTime? End { get; set; }
[JsonProperty(/*Could not decode attribute arguments.*/)]
public ulong? StartUnixMilliseconds
{
get
{
if (!Start.HasValue)
{
return null;
}
return ToUnixMilliseconds(Start.Value);
}
set
{
Start = (value.HasValue ? new DateTime?(FromUnixMilliseconds(value.Value)) : null);
}
}
[JsonProperty(/*Could not decode attribute arguments.*/)]
public ulong? EndUnixMilliseconds
{
get
{
if (!End.HasValue)
{
return null;
}
return ToUnixMilliseconds(End.Value);
}
set
{
End = (value.HasValue ? new DateTime?(FromUnixMilliseconds(value.Value)) : null);
}
}
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 Timestamps()
{
Start = null;
End = null;
}
public Timestamps(DateTime start)
{
Start = start;
End = null;
}
public Timestamps(DateTime start, DateTime end)
{
Start = start;
End = end;
}
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);
}
}
[Serializable]
public class Party
{
public enum PrivacySetting
{
Private,
Public
}
private string _partyid;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string ID
{
get
{
return _partyid;
}
set
{
_partyid = value.GetNullOrString();
}
}
[JsonIgnore]
public int Size { get; set; }
[JsonIgnore]
public int Max { get; set; }
[JsonProperty(/*Could not decode attribute arguments.*/)]
public PrivacySetting Privacy { get; set; }
[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 class Button
{
private string _label;
private string _url;
[JsonProperty("label")]
public string Label
{
get
{
return _label;
}
set
{
if (!BaseRichPresence.ValidateString(value, out _label, 32, Encoding.UTF8))
{
throw new StringOutOfRangeException(32);
}
}
}
[JsonProperty("url")]
public string Url
{
get
{
return _url;
}
set
{
if (!BaseRichPresence.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");
}
}
}
}
public sealed class RichPresence : BaseRichPresence
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public Button[] Buttons { get; set; }
public bool HasButtons()
{
if (Buttons != null)
{
return Buttons.Length != 0;
}
return false;
}
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()
{
return new RichPresence
{
State = ((_state != null) ? (_state.Clone() as string) : null),
Details = ((_details != null) ? (_details.Clone() as string) : null),
Buttons = ((!HasButtons()) ? null : (Buttons.Clone() as Button[])),
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)
}),
Timestamps = ((!HasTimestamps()) ? null : new Timestamps
{
Start = base.Timestamps.Start,
End = base.Timestamps.End
}),
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)
}),
Party = ((!HasParty()) ? null : new Party
{
ID = base.Party.ID,
Size = base.Party.Size,
Max = base.Party.Max,
Privacy = base.Party.Privacy
})
};
}
internal RichPresence Merge(BaseRichPresence 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))
{
return false;
}
if ((Buttons == null) ^ (other.Buttons == null))
{
return false;
}
if (Buttons != null)
{
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;
}
}
internal sealed class RichPresenceResponse : BaseRichPresence
{
[JsonProperty("application_id")]
public string ClientID { get; private set; }
[JsonProperty("name")]
public string Name { get; private set; }
}
public class User
{
public enum AvatarFormat
{
PNG,
JPEG,
WebP,
GIF
}
public enum AvatarSize
{
x16 = 0x10,
x32 = 0x20,
x64 = 0x40,
x128 = 0x80,
x256 = 0x100,
x512 = 0x200,
x1024 = 0x400,
x2048 = 0x800
}
[Flags]
public enum Flag
{
None = 0,
Employee = 1,
Partner = 2,
HypeSquad = 4,
BugHunter = 8,
HouseBravery = 0x40,
HouseBrilliance = 0x80,
HouseBalance = 0x100,
EarlySupporter = 0x200,
TeamUser = 0x400
}
public enum PremiumType
{
None,
NitroClassic,
Nitro
}
[JsonProperty("id")]
public ulong ID { get; private set; }
[JsonProperty("username")]
public string Username { get; private set; }
[JsonProperty("discriminator")]
[Obsolete("Discord no longer uses discriminators.")]
public int Discriminator { get; private set; }
[JsonProperty("global_name")]
public string DisplayName { get; private set; }
[JsonProperty("avatar")]
public string Avatar { get; private set; }
[JsonProperty(/*Could not decode attribute arguments.*/)]
public Flag Flags { get; private set; }
[JsonProperty(/*Could not decode attribute arguments.*/)]
public PremiumType 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 GetAvatarURL(AvatarFormat format)
{
return GetAvatarURL(format, AvatarSize.x128);
}
public string GetAvatarURL(AvatarFormat format, AvatarSize size)
{
string text = $"/avatars/{ID}/{Avatar}";
if (string.IsNullOrEmpty(Avatar))
{
if (format != 0)
{
throw new BadImageFormatException("The user has no avatar and the requested format " + format.ToString() + " is not supported. (Only supports PNG).");
}
int num = (int)((ID >> 22) % 6);
if (Discriminator > 0)
{
num = Discriminator % 5;
}
text = $"/embed/avatars/{num}";
}
return $"https://{CdnEndpoint}{text}{GetAvatarExtension(format)}?size={(int)size}";
}
public string GetAvatarExtension(AvatarFormat format)
{
return "." + format.ToString().ToLowerInvariant();
}
public override string ToString()
{
if (!string.IsNullOrEmpty(DisplayName))
{
return DisplayName;
}
if (Discriminator != 0)
{
return Username + "#" + Discriminator.ToString("D4");
}
return Username;
}
}
}
namespace DiscordRPC.RPC
{
internal class RpcConnection : IDisposable
{
public static readonly int VERSION = 1;
public static readonly int POLL_RATE = 1000;
private static readonly bool CLEAR_ON_SHUTDOWN = true;
private static readonly bool LOCK_STEP = false;
private ILogger _logger;
private RpcState _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 = new object();
private readonly uint _maxRxQueueSize;
private Queue<IMessage> _rxqueue;
private AutoResetEvent queueUpdatedEvent = new AutoResetEvent(initialState: false);
private BackoffDelay delay;
public ILogger Logger
{
get
{
return _logger;
}
set
{
_logger = value;
if (namedPipe != null)
{
namedPipe.Logger = value;
}
}
}
public RpcState State
{
get
{
lock (l_states)
{
return _state;
}
}
}
public Configuration Configuration
{
get
{
Configuration configuration = null;
lock (l_config)
{
return _configuration;
}
}
}
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()
{
nonce++;
return nonce;
}
internal void EnqueueCommand(ICommand command)
{
Logger.Trace("Enqueue Command: {0}", 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
{
if (this.OnRpcMessage != null)
{
this.OnRpcMessage(this, message);
}
}
catch (Exception ex)
{
Logger.Error("Unhandled Exception while processing event: {0}", 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: {0}", 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)
{
if (_rxqueue.Count == 0)
{
return null;
}
return _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.Level <= LogLevel.Trace)
{
Logger.Trace("============================");
Logger.Trace("Assembly: " + Assembly.GetAssembly(typeof(RichPresence)).FullName);
Logger.Trace("Pipe: " + namedPipe.GetType().FullName);
Logger.Trace("Platform: " + Environment.OSVersion.ToString());
Logger.Trace("applicationID: " + applicationID);
Logger.Trace("targetPipe: " + targetPipe);
ILogger logger = Logger;
int pOLL_RATE = POLL_RATE;
logger.Trace("POLL_RATE: " + pOLL_RATE);
ILogger logger2 = Logger;
uint maxRtQueueSize = _maxRtQueueSize;
logger2.Trace("_maxRtQueueSize: " + maxRtQueueSize);
ILogger logger3 = Logger;
maxRtQueueSize = _maxRxQueueSize;
logger3.Trace("_maxRxQueueSize: " + maxRtQueueSize);
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 {0}", 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: {0}", frame.Opcode);
switch (frame.Opcode)
{
case Opcode.Close:
{
ClosePayload @object = frame.GetObject<ClosePayload>();
Logger.Warning("We have been told to terminate by discord: ({0}) {1}", @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! {0}", ex.Message);
Logger.Error("Data: {0}", frame.Message);
}
try
{
if (eventPayload != null)
{
ProcessFrame(eventPayload);
}
}
catch (Exception ex2)
{
Logger.Error("Failed to process event! {0}", ex2.Message);
Logger.Error("Data: {0}", frame.Message);
}
break;
}
default:
Logger.Error("Invalid opcode: {0}", frame.Opcode);
flag = false;
break;
}
}
if (!aborting && namedPipe.IsConnected)
{
ProcessCommandQueue();
queueUpdatedEvent.WaitOne(POLL_RATE);
}
}
Logger.Trace("Left main read loop for some reason. Aborting: {0}, Shutting Down: {1}", aborting, 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 {0}ms before attempting to connect again", num);
Thread.Sleep(delay.NextDelay());
}
}
catch (Exception ex3)
{
Logger.Error("Unhandled Exception: {0}", ex3.GetType().FullName);
Logger.Error(ex3.Message);
Logger.Error(ex3.StackTrace);
}
finally
{
if (namedPipe.IsConnected)
{
Logger.Trace("Closing the named pipe.");
namedPipe.Close();
}
SetConnectionState(RpcState.Disconnected);
}
}
Logger.Trace("Left Main Loop");
if (namedPipe != null)
{
namedPipe.Dispose();
}
Logger.Info("Thread Terminated, no longer performing RPC connection.");
}
private void ProcessFrame(EventPayload response)
{
//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
Logger.Info("Handling Response. Cmd: {0}, Event: {1}", response.Command, 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: ({0}) {1}", @object.Code.ToString(), @object.Message);
EnqueueMessage(@object);
}
else if (State == RpcState.Connecting && response.Command == Command.Dispatch && response.Event.HasValue && response.Event.Value == ServerEvent.Ready)
{
Logger.Info("Connection established with the RPC");
SetConnectionState(RpcState.Connected);
delay.Reset();
ReadyMessage object2 = response.GetObject<ReadyMessage>();
lock (l_config)
{
_configuration = object2.Configuration;
object2.User.SetConfiguration(_configuration);
}
EnqueueMessage(object2);
}
else if (State == RpcState.Connected)
{
switch (response.Command)
{
case Command.Dispatch:
ProcessDispatch(response);
break;
case Command.SetActivity:
{
if (response.Data == null)
{
EnqueueMessage(new PresenceMessage());
break;
}
RichPresenceResponse object3 = response.GetObject<RichPresenceResponse>();
EnqueueMessage(new PresenceMessage(object3));
break;
}
case Command.Subscribe:
case Command.Unsubscribe:
{
((Collection<JsonConverter>)(object)new JsonSerializer().Converters).Add((JsonConverter)(object)new EnumSnakeCaseConverter());
ServerEvent value = response.GetObject<EventPayload>().Event.Value;
if (response.Command == Command.Subscribe)
{
EnqueueMessage(new SubscribeMessage(value));
}
else
{
EnqueueMessage(new UnsubscribeMessage(value));
}
break;
}
case Command.SendActivityJoinInvite:
Logger.Trace("Got invite response ack.");
break;
case Command.CloseActivityJoinRequest:
Logger.Trace("Got invite response reject ack.");
break;
default:
Logger.Error("Unkown frame was received! {0}", response.Command);
break;
}
}
else
{
Logger.Trace("Received a frame while we are disconnected. Ignoring. Cmd: {0}, Event: {1}", response.Command, response.Event);
}
}
private void ProcessDispatch(EventPayload response)
{
if (response.Command == Command.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;
}
default:
Logger.Warning("Ignoring {0}", response.Event.Value);
break;
}
}
}
private void ProcessCommandQueue()
{
if (State != RpcState.Connected)
{
return;
}
if (aborting)
{
Logger.Warning("We have been told to write a queue but we have also been aborted.");
}
bool flag = true;
ICommand command = null;
while (flag && namedPipe.IsConnected)
{
lock (l_rtqueue)
{
flag = _rtqueue.Count > 0;
if (!flag)
{
break;
}
command = _rtqueue.Peek();
}
if (shutdown || (!aborting && LOCK_STEP))
{
flag = false;
}
IPayload payload = command.PreparePayload(GetNextNonce());
Logger.Trace("Attempting to send payload: {0}", 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: {0}", 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 = VERSION,
ClientID = applicationID
})))
{
Logger.Error("Failed to write a handshake.");
}
else
{
SetConnectionState(RpcState.Connecting);
}
}
private void SendHandwave()
{
Logger.Info("Attempting to wave goodbye...");
if (State == RpcState.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 = VERSION,
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(RpcState state)
{
Logger.Trace("Setting the connection state to {0}", state.ToString().ToSnakeCase().ToUpperInvariant());
lock (l_states)
{
_state = state;
}
}
public void Shutdown()
{
Logger.Trace("Initiated shutdown procedure");
shutdown = true;
lock (l_rtqueue)
{
_rtqueue.Clear();
if (CLEAR_ON_SHUTDOWN)
{
_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 RpcState
{
Disconnected,
Connecting,
Connected
}
}
namespace DiscordRPC.RPC.Payload
{
internal class ClosePayload : IPayload
{
[JsonProperty("code")]
public int Code { get; set; }
[JsonProperty("message")]
public string Reason { get; set; }
[JsonConstructor]
public ClosePayload()
{
Code = -1;
Reason = "";
}
}
internal enum Command
{
[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,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
Authorize,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
Authenticate,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
GetGuild,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
GetGuilds,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
GetChannel,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
GetChannels,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
SetUserVoiceSettings,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
SelectVoiceChannel,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
GetSelectedVoiceChannel,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
SelectTextChannel,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
GetVoiceSettings,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
SetVoiceSettings,
[Obsolete("This value is appart of the RPC API and is not supported by this library.", true)]
CaptureShortcut
}
internal abstract class IPayload
{
[JsonProperty("cmd")]
[JsonConverter(typeof(EnumSnakeCaseConverter))]
public Command 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}, Nonce: {Nonce}";
}
}
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 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>()
{
if (Data == null)
{
return default(T);
}
return ((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 DiscordRPC.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 = Command.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 = Command.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 ? Command.SendActivityJoinInvite : Command.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 ? Command.Unsubscribe : Command.Subscribe),
Event = Event
};
}
}
}
namespace DiscordRPC.Registry
{
internal interface IUriSchemeCreator
{
bool RegisterUriScheme(UriSchemeRegister register);
}
internal class MacUriSchemeCreator : IUriSchemeCreator
{
private ILogger logger;
public MacUriSchemeCreator(ILogger 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.");
}
string text2 = "~/Library/Application Support/discord/games";
if (!Directory.CreateDirectory(text2).Exists)
{
logger.Error("Failed to register because {0} does not exist", text2);
return false;
}
string text3 = text2 + "/" + register.ApplicationID + ".json";
File.WriteAllText(text3, "{ \"command\": \"" + text + "\" }");
logger.Trace("Registered {0}, {1}", text3, text);
return true;
}
}
internal class UnixUriSchemeCreator : IUriSchemeCreator
{
private ILogger logger;
public UnixUriSchemeCreator(ILogger 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 = null;
text = ((!register.UsingSteamApp) ? executablePath : ("xdg-open steam://rungameid/" + register.SteamAppID));
string text2 = $"[Desktop Entry]\nName=Game {register.ApplicationID}\nExec={text} %u\nType=Application\nNoDisplay=true\nCategories=Discord;Games;\nMimeType=x-scheme-handler/discord-{register.ApplicationID}";
string text3 = "/discord-" + register.ApplicationID + ".desktop";
string text4 = environmentVariable + "/.local/share/applications";
if (!Directory.CreateDirectory(text4).Exists)
{
logger.Error("Failed to register because {0} does not exist", text4);
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 {0}, {1}, {2}", text4 + text3, text2, text);
return true;
}
private 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;
}
}
internal class UriSchemeRegister
{
private ILogger _logger;
public string ApplicationID { get; set; }
public string SteamAppID { get; set; }
public bool UsingSteamApp
{
get
{
if (!string.IsNullOrEmpty(SteamAppID))
{
return SteamAppID != "";
}
return false;
}
}
public string ExecutablePath { get; set; }
public UriSchemeRegister(ILogger 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 = null;
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:
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_logger.Trace("Creating MacOSX Scheme Creator");
uriSchemeCreator = new MacUriSchemeCreator(_logger);
}
else
{
_logger.Trace("Creating Unix Scheme Creator");
uriSchemeCreator = new UnixUriSchemeCreator(_logger);
}
break;
default:
_logger.Error("Unkown Platform: {0}", Environment.OSVersion.Platform);
throw new PlatformNotSupportedException("Platform does not support registration.");
}
if (uriSchemeCreator.RegisterUriScheme(this))
{
_logger.Info("URI scheme registered.");
return true;
}
return false;
}
public static string GetApplicationLocation()
{
return Process.GetCurrentProcess().MainModule.FileName;
}
}
internal class WindowsUriSchemeCreator : IUriSchemeCreator
{
private ILogger logger;
public WindowsUriSchemeCreator(ILogger logger)
{
this.logger = logger;
}
public bool RegisterUriScheme(UriSchemeRegister register)
{
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.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 defaultIcon = executablePath;
string command = executablePath;
if (register.UsingSteamApp)
{
string steamLocation = GetSteamLocation();
if (steamLocation != null)
{
command = $"\"{steamLocation}\" steam://rungameid/{register.SteamAppID}";
}
}
CreateUriScheme(scheme, friendlyName, defaultIcon, 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 {0}, {1}, {2}", scheme, friendlyName, command);
}
public string GetSteamLocation()
{
using RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\Valve\\Steam");
if (registryKey == null)
{
return null;
}
return registryKey.GetValue("SteamExe") as string;
}
}
}
namespace DiscordRPC.Message
{
public class CloseMessage : IMessage
{
public override MessageType Type => MessageType.Close;
public string Reason { get; internal set; }
public int Code { get; internal set; }
internal CloseMessage()
{
}
internal CloseMessage(string reason)
{
Reason = reason;
}
}
public class ConnectionEstablishedMessage : IMessage
{
public override MessageType Type => MessageType.ConnectionEstablished;
public int ConnectedPipe { get; internal set; }
}
public class ConnectionFailedMessage : IMessage
{
public override MessageType Type => MessageType.ConnectionFailed;
public int FailedPipe { get; internal set; }
}
public class ErrorMessage : IMessage
{
public override MessageType Type => MessageType.Error;
[JsonProperty("code")]
public ErrorCode Code { get; internal set; }
[JsonProperty("message")]
public string Message { get; internal set; }
}
public enum ErrorCode
{
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 MessageType Type { get; }
public DateTime TimeCreated => _timecreated;
public IMessage()
{
_timecreated = DateTime.Now;
}
}
public class JoinMessage : IMessage
{
public override MessageType Type => MessageType.Join;
[JsonProperty("secret")]
public string Secret { get; internal set; }
}
public class JoinRequestMessage : IMessage
{
public override MessageType Type => MessageType.JoinRequest;
[JsonProperty("user")]
public User User { get; internal set; }
}
public enum MessageType
{
Ready,
Close,
Error,
PresenceUpdate,
Subscribe,
Unsubscribe,
Join,
Spectate,
JoinRequest,
ConnectionEstablished,
ConnectionFailed
}
public class PresenceMessage : IMessage
{
public override MessageType Type => MessageType.PresenceUpdate;
public BaseRichPresence Presence { get; internal set; }
public string Name { get; internal set; }
public string ApplicationID { get; internal set; }
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
{
public override MessageType Type => MessageType.Ready;
[JsonProperty("config")]
public Configuration Configuration { get; set; }
[JsonProperty("user")]
public User User { get; set; }
[JsonProperty("v")]
public int Version { get; set; }
}
public class SpectateMessage : JoinMessage
{
public override MessageType Type => MessageType.Spectate;
}
public class SubscribeMessage : IMessage
{
public override MessageType Type => MessageType.Subscribe;
public EventType Event { get; internal set; }
internal SubscribeMessage(ServerEvent evt)
{
switch (evt)
{
default:
Event = EventType.Join;
break;
case ServerEvent.ActivityJoinRequest:
Event = EventType.JoinRequest;
break;
case ServerEvent.ActivitySpectate:
Event = EventType.Spectate;
break;
}
}
}
public class UnsubscribeMessage : IMessage
{
public override MessageType Type => MessageType.Unsubscribe;
public EventType Event { get; internal set; }
internal UnsubscribeMessage(ServerEvent evt)
{
switch (evt)
{
default:
Event = EventType.Join;
break;
case ServerEvent.ActivityJoinRequest:
Event = EventType.JoinRequest;
break;
case ServerEvent.ActivitySpectate:
Event = EventType.Spectate;
break;
}
}
}
}
namespace DiscordRPC.Logging
{
public class ConsoleLogger : ILogger
{
public LogLevel Level { get; set; }
public bool Coloured { get; set; }
[Obsolete("Use Coloured")]
public bool Colored
{
get
{
return Coloured;
}
set
{
Coloured = value;
}
}
public ConsoleLogger()
{
Level = LogLevel.Info;
Coloured = false;
}
public ConsoleLogger(LogLevel level)
: this()
{
Level = level;
}
public ConsoleLogger(LogLevel level, bool coloured)
{
Level = level;
Coloured = coloured;
}
public void Trace(string message, params object[] args)
{
if (Level <= LogLevel.Trace)
{
if (Coloured)
{
Console.ForegroundColor = ConsoleColor.Gray;
}
string text = "TRACE: " + message;
if (args.Length != 0)
{
Console.WriteLine(text, args);
}
else
{
Console.WriteLine(text);
}
}
}
public void Info(string message, params object[] args)
{
if (Level <= LogLevel.Info)
{
if (Coloured)
{
Console.ForegroundColor = ConsoleColor.White;
}
string text = "INFO: " + message;
if (args.Length != 0)
{
Console.WriteLine(text, args);
}
else
{
Console.WriteLine(text);
}
}
}
public void Warning(string message, params object[] args)
{
if (Level <= LogLevel.Warning)
{
if (Coloured)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
string text = "WARN: " + message;
if (args.Length != 0)
{
Console.WriteLine(text, args);
}
else
{
Console.WriteLine(text);
}
}
}
public void Error(string message, params object[] args)
{
if (Level <= LogLevel.Error)
{
if (Coloured)
{
Console.ForegroundColor = ConsoleColor.Red;
}
string text = "ERR : " + message;
if (args.Length != 0)
{
Console.WriteLine(text, args);
}
else
{
Console.WriteLine(text);
}
}
}
}
public class FileLogger : ILogger
{
private object filelock;
public LogLevel Level { get; set; }
public string File { get; set; }
public FileLogger(string path)
: this(path, LogLevel.Info)
{
}
public FileLogger(string path, LogLevel level)
{
Level = level;
File = path;
filelock = new object();
}
public void Trace(string message, params object[] args)
{
if (Level > LogLevel.Trace)
{
return;
}
lock (filelock)
{
System.IO.File.AppendAllText(File, "\r\nTRCE: " + ((args.Length != 0) ? string.Format(message, args) : message));
}
}
public void Info(string message, params object[] args)
{
if (Level > LogLevel.Info)
{
return;
}
lock (filelock)
{
System.IO.File.AppendAllText(File, "\r\nINFO: " + ((args.Length != 0) ? string.Format(message, args) : message));
}
}
public void Warning(string message, params object[] args)
{
if (Level > LogLevel.Warning)
{
return;
}
lock (filelock)
{
System.IO.File.AppendAllText(File, "\r\nWARN: " + ((args.Length != 0) ? string.Format(message, args) : message));
}
}
public void Error(string message, params object[] args)
{
if (Level > LogLevel.Error)
{
return;
}
lock (filelock)
{
System.IO.File.AppendAllText(File, "\r\nERR : " + ((args.Length != 0) ? string.Format(message, args) : message));
}
}
}
public interface ILogger
{
LogLevel Level { get; set; }
void Trace(string message, params object[] args);
void Info(string message, params object[] args);
void Warning(string message, params object[] args);
void Error(string message, params object[] args);
}
public enum LogLevel
{
Trace = 1,
Info = 2,
Warning = 3,
Error = 4,
None = 256
}
public class NullLogger : ILogger
{
public LogLevel Level { get; set; }
public void Trace(string message, params object[] args)
{
}
public void Info(string message, params object[] args)
{
}
public void Warning(string message, params object[] args)
{
}
public void Error(string message, params object[] args)
{
}
}
}
namespace DiscordRPC.IO
{
internal class Handshake
{
[JsonProperty("v")]
public int Version { get; set; }
[JsonProperty("client_id")]
public string ClientID { get; set; }
}
public interface INamedPipeClient : IDisposable
{
ILogger 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 PIPE_NAME = "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 ILogger 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 NullLogger();
_stream = null;
}
public bool Connect(int pipe)
{
Logger.Trace("ManagedNamedPipeClient.Connection({0})", pipe);
if (_isDisposed)
{
throw new ObjectDisposedException("NamedPipe");
}
if (pipe > 9)
{
throw new ArgumentOutOfRangeException("pipe", "Argument cannot be greater than 9");
}
if (pipe < 0)
{
for (int i = 0; i < 10; i++)
{
if (AttemptConnection(i) || AttemptConnection(i, isSandbox: true))
{
BeginReadStream();
return true;
}
}
}
else if (AttemptConnection(pipe) || AttemptConnection(pipe, isSandbox: true))
{
BeginReadStream();
return true;
}
return false;
}
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 {0} ({1})", pipe, text);
string pipeName = GetPipeName(pipe, text);
try
{
lock (l_stream)
{
Logger.Info("Attempting to connect to '{0}'", pipeName);
_stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
_stream.Connect(0);
Logger.Trace("Waiting for connection...");
do
{
Thread.Sleep(10);
}
while (!_stream.IsConnected);
}
Logger.Info("Connected to '{0}'", pipeName);
_connectedPipe = pipe;
_isClosed = false;
}
catch (Exception ex)
{
Logger.Error("Failed connection to {0}. {1}", pipeName, ex.Message);
Close();
}
Logger.Trace("Done. Result: {0}", _isClosed);
return !_isClosed;
}
private void BeginReadStream()
{
if (_isClosed)
{
return;
}
try
{
lock (l_stream)
{
if (_stream != null && _stream.IsConnected)
{
Logger.Trace("Begining Read of {0} bytes", _buffer.Length);
_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: {0}", ex3.Message);
Logger.Error(ex3.StackTrace);
}
}
private void EndReadStream(IAsyncResult callback)
{
Logger.Trace("Ending Read");
int num = 0;
try
{
lock (l_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: {0}", ex4.Message);
Logger.Error(ex4.StackTrace);
return;
}
Logger.Trace("Read {0} bytes", num);
if (num > 0)
{
using MemoryStream stream = new MemoryStream(_buffer, 0, num);
try
{
PipeFrame item = default(PipeFrame);
if (item.ReadStream(stream))
{
Logger.Trace("Read a frame: {0}", 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: {0}", ex5.Message);
Close();
}
}
else if (IsUnix())
{
Logger.Error("Empty frame was read on {0}, aborting.", Environment.OSVersion);
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: {0}", 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)
{
}
_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 GetPipeName(int pipe)
{
return GetPipeName(pipe, "");
}
public static string GetPipeSandbox()
{
if (Environment.OSVersion.Platform != PlatformID.Unix)
{
return null;
}
return "snap.discord/";
}
private static string GetTemporaryDirectory()
{
object obj = null ?? Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR");
if (obj == null)
{
obj = Environment.GetEnvironmentVariable("TMPDIR");
}
if (obj == null)
{
obj = Environment.GetEnvironmentVariable("TMP");
}
if (obj == null)
{
obj = Environment.GetEnvironmentVariable("TEMP");
}
if (obj == null)
{
obj = "/tmp";
}
return (string)obj;
}
public static bool IsUnix()
{
PlatformID platform = Environment.OSVersion.Platform;
if (platform != PlatformID.Unix && platform != PlatformID.MacOSX)
{
return false;
}
return true;
}
}
public enum Opcode : uint
{
Handshake,
Frame,
Close,
Ping,
Pong
}
public struct PipeFrame : IEquatable<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 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)
{
string message = JsonConvert.SerializeObject(obj);
SetMessage(message);
}
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();
uint num2 = (uint)Min(2048, value2);
byte[] array = new byte[num2];
int count;
while ((count = stream.Read(array, 0, Min(array.Length, num))) > 0)
{
num -= num2;
memoryStream.Write(array, 0, count);
}
byte[] array2 = memoryStream.ToArray();
if (array2.LongLength != value2)
{
return false;
}
Opcode = (Opcode)value;
Data = array2;
return true;
}
private int Min(int a, uint b)
{
if (b >= a)
{
return a;
}
return (int)b;
}
private bool TryReadUInt32(Stream stream, out uint value)
{
byte[] array = new byte[4];
if (stream.Read(array, 0, array.Length) != 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);
}
public bool Equals(PipeFrame other)
{
if (Opcode == other.Opcode && Length == other.Length)
{
return Data == other.Data;
}
return false;
}
}
}
namespace DiscordRPC.Helper
{
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 = (float)(Maximum - Minimum) / 100f;
_current = (int)Math.Floor(num * (double)_fails) + Minimum;
return Math.Min(Math.Max(_current, Minimum), Maximum);
}
}
public static class StringTools
{
public static string GetNullOrString(this string str)
{
if (str.Length != 0 && !string.IsNullOrEmpty(str.Trim()))
{
return str;
}
return null;
}
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?.ToLowerInvariant().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;
}
return string.Concat(str.Select((char x, int i) => (i <= 0 || !char.IsUpper(x)) ? x.ToString() : ("_" + x)).ToArray()).ToUpperInvariant();
}
}
}
namespace DiscordRPC.Exceptions
{
public class BadPresenceException : Exception
{
internal BadPresenceException(string message)
: base(message)
{
}
}
public class InvalidConfigurationException : Exception
{
internal InvalidConfigurationException(string message)
: base(message)
{
}
}
[Obsolete("Not actually used anywhere")]
public class InvalidPipeException : Exception
{
internal InvalidPipeException(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 DiscordRPC.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);
}
namespace DiscordRPC.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 = null;
if (TryParseEnum(objectType, (string)reader.Value, out obj))
{
return obj;
}
return 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);
foreach (MemberInfo memberInfo in members)
{
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 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);
foreach (MemberInfo memberInfo in members)
{
object[] customAttributes = memberInfo.GetCustomAttributes(typeof(EnumValueAttribute), inherit: true);
for (int j = 0; j < customAttributes.Length; j++)
{
EnumValueAttribute enumValueAttribute = (EnumValueAttribute)customAttributes[j];
if (str.Equals(enumValueAttribute.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;
}
}
}