The BepInEx console will not appear when launching like it does for other games on Thunderstore. This is normal (and helps prevent crashes during startup). You can turn it back on in your BepInEx.cfg file.
Decompiled source of DiscordRPC v1.1.0
DiscordRPC.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.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.3.0.28")] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyVersion("1.3.0.28")] 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 Update(Action<RichPresence> func) { if (!IsInitialized) { throw new UninitializedException(); } RichPresence richPresence; lock (_sync) { richPresence = ((CurrentPresence == null) ? new RichPresence() : CurrentPresence.Clone()); } func(richPresence); SetPresence(richPresence); return richPresence; } public RichPresence UpdateType(ActivityType type) { return Update(delegate(RichPresence p) { p.Type = type; }); } public RichPresence UpdateButtons(Button[] buttons = null) { return Update(delegate(RichPresence p) { p.Buttons = buttons; }); } public RichPresence SetButton(Button button, int index = 0) { return Update(delegate(RichPresence p) { p.Buttons[index] = button; }); } public RichPresence UpdateDetails(string details) { return Update(delegate(RichPresence p) { p.Details = details; }); } public RichPresence UpdateState(string state) { return Update(delegate(RichPresence p) { p.State = state; }); } public RichPresence UpdateParty(Party party) { return Update(delegate(RichPresence p) { p.Party = party; }); } public RichPresence UpdatePartySize(int size) { return Update(delegate(RichPresence p) { if (p.Party == null) { throw new BadPresenceException("Cannot set the size of the party if the party does not exist"); } p.Party.Size = size; }); } public RichPresence UpdatePartySize(int size, int max) { return Update(delegate(RichPresence p) { if (p.Party == null) { throw new BadPresenceException("Cannot set the size of the party if the party does not exist"); } p.Party.Size = size; p.Party.Max = max; }); } public RichPresence UpdateLargeAsset(string key = null, string tooltip = null) { return Update(delegate(RichPresence p) { if (p.Assets == null) { p.Assets = new Assets(); } p.Assets.LargeImageKey = key ?? p.Assets.LargeImageKey; p.Assets.LargeImageText = tooltip ?? p.Assets.LargeImageText; }); } public RichPresence UpdateSmallAsset(string key = null, string tooltip = null) { return Update(delegate(RichPresence p) { if (p.Assets == null) { p.Assets = new Assets(); } p.Assets.SmallImageKey = key ?? p.Assets.SmallImageKey; p.Assets.SmallImageText = tooltip ?? p.Assets.SmallImageText; }); } public RichPresence UpdateSecrets(Secrets secrets) { return Update(delegate(RichPresence p) { p.Secrets = secrets; }); } public RichPresence UpdateStartTime() { return UpdateStartTime(DateTime.UtcNow); } public RichPresence UpdateStartTime(DateTime time) { return Update(delegate(RichPresence p) { if (p.Timestamps == null) { p.Timestamps = new Timestamps(); } p.Timestamps.Start = time; }); } public RichPresence UpdateEndTime() { return UpdateEndTime(DateTime.UtcNow); } public RichPresence UpdateEndTime(DateTime time) { return Update(delegate(RichPresence p) { if (p.Timestamps == null) { p.Timestamps = new Timestamps(); } p.Timestamps.End = time; }); } public RichPresence UpdateClearTime() { return Update(delegate(RichPresence p) { p.Timestamps = null; }); } 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.*/)] public ActivityType Type { 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 || Type != other.Type) { 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.Type = Type; 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 enum ActivityType { Playing = 0, Listening = 2, Watching = 3, Competing = 5 } 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 WithType(ActivityType type) { base.Type = type; 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), Type = base.Type, 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.Type = presence.Type; 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; } } }
DiscordRPCMod.dll
Decompiled 5 days agousing System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using DiscordRPC.IO; using DiscordRPC.Logging; using UnityEngine; using UnityEngine.SceneManagement; using Zorro.Core; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("ClassLibrary2")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ClassLibrary2")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("bef3c018-0e56-4ba4-a07a-5391071d7533")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace DiscordRPC; [BepInPlugin("com.hiccup444.drpcmod", "Discord RPC Mod", "1.0.0")] public class DRPCMod : BaseUnityPlugin { private DiscordRpcClient client; private float updateTimer; private Timestamps sessionStart; private void Start() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_009e: Expected O, but got Unknown client = new DiscordRpcClient("1387553433846026260", -1, (ILogger)null, true, (INamedPipeClient)null); client.Logger = (ILogger)new ConsoleLogger { Level = (LogLevel)3 }; client.Initialize(); sessionStart = Timestamps.Now; client.SetPresence(new RichPresence { Details = "In Main Menu", State = "Preparing for adventure…", Timestamps = sessionStart, Assets = new Assets { LargeImageKey = "icon", LargeImageText = "PEAK" } }); } private void Update() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_009f: Expected O, but got Unknown if (client != null) { updateTimer += Time.deltaTime; if (!(updateTimer < 15f)) { updateTimer = 0f; string statusDescription = GetStatusDescription(); client.SetPresence(new RichPresence { Details = statusDescription, State = "Online", Timestamps = sessionStart, Assets = new Assets { LargeImageKey = "icon", LargeImageText = "PEAK" } }); } } } private string GetStatusDescription() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected I4, but got Unknown Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (name != "Airport" && name != "Level_11") { return "In Main Menu"; } if (name == "Airport") { return "Boarding the Plane"; } MapHandler val = null; if (typeof(Singleton<MapHandler>) != null) { val = Singleton<MapHandler>.Instance; } if ((Object)(object)val == (Object)null) { val = Object.FindObjectOfType<MapHandler>(); } if ((Object)(object)val == (Object)null) { return "Exploring the Mountain"; } Segment currentSegment = val.GetCurrentSegment(); Segment val2 = currentSegment; Segment val3 = val2; return (int)val3 switch { 0 => "Relaxing on the Beach", 1 => "Exploring the Tropics", 2 => "Climbing the Alpine", 3 => "Hiking the Caldera", 4 => "Braving The Kiln", 5 => "Scaling the Peak", _ => "Exploring the Mountain", }; } private void OnDestroy() { DiscordRpcClient obj = client; if (obj != null) { obj.Dispose(); } } }