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.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;
}
}
}