Decompiled source of LabPresence v1.0.0
Mods/LabPresence.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BoneLib; using BoneLib.Notifications; using DiscordRPC; using DiscordRPC.Converters; 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 Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.SceneStreaming; using Il2CppSLZ.Marrow.Utilities; using Il2CppSLZ.Marrow.Warehouse; using Il2CppSystem; using LabFusion.Data; using LabFusion.Entities; using LabFusion.Network; using LabFusion.Player; using LabFusion.SDK.Gamemodes; using LabFusion.SDK.Metadata; using LabFusion.Utilities; using LabPresence; using LabPresence.Config; using LabPresence.Helper; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Tomlet.Attributes; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "LabPresence", "1.0.0", "HAHOOS", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: MelonPriority(-1000)] [assembly: MelonOptionalDependencies(new string[] { "LabFusion" })] [assembly: AssemblyTitle("Adds Discord RPC to BONELAB")] [assembly: AssemblyDescription("Adds Discord RPC to BONELAB")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("HAHOOS")] [assembly: AssemblyProduct("LabPresence")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } 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 readonly RpcConnection connection; private bool _shutdownOnly = true; private readonly object _sync = new object(); public bool HasRegisteredUriScheme { get; private set; } public string ApplicationID { get; } public string SteamID { get; } public int ProcessID { get; } public int MaxQueueSize { get; } 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; } public bool SkipIdenticalPresence { get; set; } public int TargetPipe { get; } 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 EventHandler<ReadyMessage> OnReady; public event EventHandler<CloseMessage> OnClose; public event EventHandler<ErrorMessage> OnError; public event EventHandler<PresenceMessage> OnPresenceUpdate; public event EventHandler<SubscribeMessage> OnSubscribe; public event EventHandler<UnsubscribeMessage> OnUnsubscribe; public event EventHandler<JoinMessage> OnJoin; public event EventHandler<SpectateMessage> OnSpectate; public event EventHandler<JoinRequestMessage> OnJoinRequested; public event EventHandler<ConnectionEstablishedMessage> OnConnectionEstablished; public event EventHandler<ConnectionFailedMessage> OnConnectionFailed; public event EventHandler<IMessage> 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 = 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 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; } } this.OnPresenceUpdate?.Invoke(this, message as PresenceMessage); break; case MessageType.Ready: if (message is ReadyMessage readyMessage) { lock (_sync) { Configuration = readyMessage.Configuration; CurrentUser = readyMessage.User; } SynchronizeState(); } this.OnReady?.Invoke(this, message as ReadyMessage); break; case MessageType.Close: this.OnClose?.Invoke(this, message as CloseMessage); break; case MessageType.Error: this.OnError?.Invoke(this, message as ErrorMessage); break; case MessageType.JoinRequest: if (Configuration != null && message is JoinRequestMessage joinRequestMessage) { joinRequestMessage.User.SetConfiguration(Configuration); } this.OnJoinRequested?.Invoke(this, message as JoinRequestMessage); break; case MessageType.Subscribe: lock (_sync) { SubscribeMessage subscribeMessage = message as SubscribeMessage; Subscription |= subscribeMessage.Event; } this.OnSubscribe?.Invoke(this, message as SubscribeMessage); break; case MessageType.Unsubscribe: lock (_sync) { UnsubscribeMessage unsubscribeMessage = message as UnsubscribeMessage; Subscription &= ~unsubscribeMessage.Event; } this.OnUnsubscribe?.Invoke(this, message as UnsubscribeMessage); break; case MessageType.Join: this.OnJoin?.Invoke(this, message as JoinMessage); break; case MessageType.Spectate: this.OnSpectate?.Invoke(this, message as SpectateMessage); break; case MessageType.ConnectionEstablished: this.OnConnectionEstablished?.Invoke(this, message as ConnectionEstablishedMessage); break; case MessageType.ConnectionFailed: this.OnConnectionFailed?.Invoke(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 UpdateName(string name) { return Update(delegate(RichPresence p) { p.Name = name; }); } 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) { Assets assets2 = (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) { Assets assets2 = (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) { Timestamps timestamps2 = (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) { Timestamps timestamps2 = (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 _name; protected internal string _state; protected internal string _details; [JsonProperty(/*Could not decode attribute arguments.*/)] public string Name { get { return _name; } set { if (!ValidateString(value, out _name, 128, Encoding.UTF8)) { throw new StringOutOfRangeException("Name", 0, 128); } } } [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() { return Timestamps != null && (Timestamps.Start.HasValue || Timestamps.End.HasValue); } public bool HasAssets() { return Assets != null; } public bool HasParty() { return Party?.ID != null; } public bool HasSecrets() { return Secrets != null && (Secrets.JoinSecret != null || Secrets.SpectateSecret != null); } 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 { State = State, Details = Details, Type = Type, Party = ((!HasParty()) ? Party : null), 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) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < SecretLength; i++) { stringBuilder.Append("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[random.Next("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".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 { 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 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() { Button[] buttons = Buttons; return buttons != null && buttons.Length != 0; } public RichPresence WithState(string state) { base.State = state; return this; } public RichPresence WithName(string name) { base.Name = name; 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() { RichPresence richPresence = new RichPresence(); richPresence.Name = ((_name != null) ? (_name.Clone() as string) : null); richPresence.State = ((_state != null) ? (_state.Clone() as string) : null); richPresence.Details = ((_details != null) ? (_details.Clone() as string) : null); richPresence.Type = base.Type; 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(BaseRichPresence presence) { _name = presence.Name; _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 new 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 value = (int)((ID >> 22) % 6); if (Discriminator > 0) { value = Discriminator % 5; } text = $"/embed/avatars/{value}"; } return $"https://{CdnEndpoint}{text}{GetAvatarExtension(format)}?size={(int)size}"; } public static 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 const bool CLEAR_ON_SHUTDOWN = true; private const bool LOCK_STEP = false; private ILogger _logger; private RpcState _state; private readonly object l_states = new object(); private Configuration _configuration = null; private readonly object l_config = new object(); private volatile bool aborting = false; private volatile bool shutdown = false; private readonly string applicationID; private readonly int processID; private long nonce; private Thread thread; private readonly INamedPipeClient namedPipe; private readonly int targetPipe; private readonly object l_rtqueue = new object(); private readonly uint _maxRtQueueSize; private readonly Queue<ICommand> _rtqueue; private readonly object l_rxqueue = new object(); private readonly uint _maxRxQueueSize; private readonly Queue<IMessage> _rxqueue; private readonly AutoResetEvent queueUpdatedEvent = new AutoResetEvent(initialState: false); private readonly 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 result = null; lock (l_config) { result = _configuration; } return result; } } public bool IsRunning => thread != null; public bool ShutdownOnly { get; set; } public event EventHandler<IMessage> 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 { this.OnRpcMessage?.Invoke(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) { Queue<IMessage> rxqueue = _rxqueue; int num = 0; IMessage[] array = new IMessage[rxqueue.Count]; foreach (IMessage item in rxqueue) { array[num] = item; num++; } IMessage[] result = array; _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); Logger.Trace("applicationID: " + applicationID); Logger.Trace("targetPipe: " + targetPipe); Logger.Trace("POLL_RATE: " + POLL_RATE); 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 {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"); namedPipe?.Dispose(); Logger.Info("Thread Terminated, no longer performing RPC connection."); } private void ProcessFrame(EventPayload response) { //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Expected O, but got Unknown Logger.Info("Handling Response. Cmd: {0}, Event: {1}", response.Command, response.Event); if (response.Event.GetValueOrDefault() == 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: { JsonSerializer val = new JsonSerializer(); ((Collection<JsonConverter>)(object)val.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) { if (aborting) { } } else if (true) { 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) { Name = "Discord IPC 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(); 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 RpcState { Disconnected, Connecting, Connected } } namespace DiscordRPC.RPC.Payload { internal class ClosePayload : IPayload { [JsonProperty("code")] public int Code { get; set; } = -1; [JsonProperty("message")] public string Reason { get; set; } = ""; [JsonConstructor] public ClosePayload() { } } 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 readonly ILogger logger; public MacUriSchemeCreator(ILogger logger) { this.logger = logger; base..ctor(); } 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 {0} does not exist", "~/Library/Application Support/discord/games"); return false; } string text2 = "~/Library/Application Support/discord/games/" + register.ApplicationID + ".json"; File.WriteAllText(text2, "{ \"command\": \"" + text + "\" }"); logger.Trace("Registered {0}, {1}", text2, text); return true; } } internal class UnixUriSchemeCreator : IUriSchemeCreator { private readonly ILogger logger; public UnixUriSchemeCreator(ILogger logger) { this.logger = logger; base..ctor(); } 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]\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"; DirectoryInfo directoryInfo = Directory.CreateDirectory(text4); if (!directoryInfo.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 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; } } internal class UriSchemeRegister { private readonly ILogger _logger; public string ApplicationID { get; set; } = applicationID.Trim(); public string SteamAppID { get; set; } = steamAppID?.Trim(); public bool UsingSteamApp => !string.IsNullOrEmpty(SteamAppID) && SteamAppID != ""; public string ExecutablePath { get; set; } = executable ?? GetApplicationLocation(); public UriSchemeRegister(ILogger logger, string applicationID, string steamAppID = null, string executable = null) { _logger = logger; base..ctor(); } 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: _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; 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 Environment.ProcessPath; } } internal class WindowsUriSchemeCreator : IUriSchemeCreator { private readonly ILogger logger; public WindowsUriSchemeCreator(ILogger logger) { this.logger = logger; base..ctor(); } 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 static 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 { public abstract MessageType Type { get; } public DateTime TimeCreated { get; } 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) { if (1 == 0) { } EventType @event = evt switch { ServerEvent.ActivityJoinRequest => EventType.JoinRequest, ServerEvent.ActivitySpectate => EventType.Spectate, _ => EventType.Join, }; if (1 == 0) { } Event = @event; } } public class UnsubscribeMessage : IMessage { public override MessageType Type => MessageType.Unsubscribe; public EventType Event { get; internal set; } internal UnsubscribeMessage(ServerEvent evt) { if (1 == 0) { } EventType @event = evt switch { ServerEvent.ActivityJoinRequest => EventType.JoinRequest, ServerEvent.ActivitySpectate => EventType.Spectate, _ => EventType.Join, }; if (1 == 0) { } Event = @event; } } } 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 readonly object filelock; public LogLevel Level { get; set; } public string File { get; set; } public FileLogger(string path, LogLevel level) { Level = level; File = path; filelock = new object(); base..ctor(); } public FileLogger(string path) : this(path, LogLevel.Info) { } 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 readonly byte[] _buffer = new byte[PipeFrame.MAX_SIZE]; private readonly Queue<PipeFrame> _framequeue = new Queue<PipeFrame>(); private readonly object _framequeuelock = new object(); private volatile bool _isDisposed = false; private volatile bool _isClosed = true; private readonly object l_stream = new object(); public ILogger Logger { get; set; } public bool IsConnected { get { if (_isClosed) { return false; } lock (l_stream) { return _stream?.IsConnected ?? false; } } } 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) { NamedPipeClientStream stream = _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) { 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: {0}", ex4.Message); Logger.Error(ex4.StackTrace); return; } Logger.Trace("Read {0} bytes", num); 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: {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() { 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; if (text == null) { text = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); } if (text == null) { text = Environment.GetEnvironmentVariable("TMPDIR"); } if (text == null) { text = Environment.GetEnvironmentVariable("TMP"); } if (text == null) { text = Environment.GetEnvironmentVariable("TEMP"); } if (text == null) { text = "/tmp"; } return text; } public static bool IsUnix() { PlatformID platform = Environment.OSVersion.Platform; if (1 == 0) { } bool result = ((platform == PlatformID.Unix || platform == PlatformID.MacOSX) ? true : false); if (1 == 0) { } return result; } } 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 readonly uint Length => (uint)Data.Length; public byte[] Data { get; set; } public string Message { readonly 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 readonly 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 readonly T GetObject<T>() { string message = GetMessage(); return JsonConvert.DeserializeObject<T>(message); } 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 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 readonly 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 readonly bool Equals(PipeFrame other) { return Opcode == other.Opcode && Length == other.Length && Data == other.Data; } public override readonly bool Equals(object obj) { return obj is PipeFrame other && Equals(other); } public static bool operator ==(PipeFrame left, PipeFrame right) { return left.Equals(right); } public static bool operator !=(PipeFrame left, PipeFrame right) { return !(left == right); } } } namespace DiscordRPC.Helper { internal class BackoffDelay { private int _current; private int _fails; public int Maximum { get; } public int Minimum { get; } 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) { 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?.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; } string text = string.Concat(str.Select((char x, int i) => (i > 0 && char.IsUpper(x)) ? ("_" + x) : x.ToString()).ToArray()); return text.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; } if (TryParseEnum(objectType, (string)reader.Value, out var 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); 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); object[] array2 = customAttributes; foreach (object obj2 in array2) { EnumValueAttribute enumValueAttribute = (EnumValueAttribute)obj2; 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; } } } namespace LabPresence { public class Core : MelonMod { [CompilerGenerated] private sealed class <AfterLevelLoaded>d__23 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Action callback; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <AfterLevelLoaded>d__23(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } StreamSession session = SceneStreamer.Session; if (session == null || (int)session.Status != 2) { <>2__current = null; <>1__state = 1; return true; } callback?.Invoke(); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string Version = "1.0.0"; private const string ClientID = "1338522973421965382"; private static float _elapsedSeconds; private string lastState; private string lastDetails; private float delay; public static DiscordRpcClient Client { get; private set; } internal static Instance Logger { get; private set; } internal static MelonPreferences_ReflectiveCategory Category { get; private set; } internal static MelonPreferences_ReflectiveCategory FusionCategory { get; private set; } internal static DefaultConfig Config { get { MelonPreferences_ReflectiveCategory category = Category; return (category != null) ? category.GetValue<DefaultConfig>() : null; } } internal static FusionConfig FusionConfig { get { MelonPreferences_ReflectiveCategory fusionCategory = FusionCategory; return (fusionCategory != null) ? fusionCategory.GetValue<FusionConfig>() : null; } } public override void OnInitializeMelon() { if (HelperMethods.IsAndroid()) { ((MelonBase)this).LoggerInstance.Error("This mod is not supported as it is unlikely for it to actually work."); ((MelonBase)this).Unregister("Unsupported platform", false); return; } Logger = ((MelonBase)this).LoggerInstance; ((MelonBase)this).LoggerInstance.Msg("Creating preferences"); DirectoryInfo directoryInfo = Directory.CreateDirectory(Path.Combine(MelonEnvironment.UserDataDirectory, "LabPresence")); Category = MelonPreferences.CreateCategory<DefaultConfig>("LabPresenceConfig", "Lab Presence Config"); Category.SetFilePath(Path.Combine(directoryInfo.FullName, "default.cfg"), true, false); Category.SaveToFile(false); FusionCategory = MelonPreferences.CreateCategory<FusionConfig>("Fusion_LabPresenceConfig", "Fusion | Lab Presence Config"); FusionCategory.SetFilePath(Path.Combine(directoryInfo.FullName, "fusion.cfg"), true, false); FusionCategory.SaveToFile(false); try { ((MelonBase)this).LoggerInstance.Msg("Adding README.txt"); Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = executingAssembly?.GetName()?.Name; if (!string.IsNullOrWhiteSpace(text)) { using Stream stream = executingAssembly.GetManifestResourceStream($"{text}.Embedded.README.txt"); using StreamReader streamReader = new StreamReader(stream); string value = streamReader.ReadToEnd(); using StreamWriter streamWriter = File.CreateText(Path.Combine(directoryInfo.FullName, "README.txt")); streamWriter.Write(value); streamWriter.Flush(); } else { ((MelonBase)this).LoggerInstance.Warning("The assembly name could not be found! Cannot add README.txt"); } } catch (Exception value2) { ((MelonBase)this).LoggerInstance.Error($"An unexpected error has occurred while creating README.txt, exception:\n{value2}"); } Fusion.Init(); ((MelonBase)this).LoggerInstance.Msg("Adding placeholders"); AddDefaultPlaceholders(); ((MelonBase)this).LoggerInstance.Msg("Initializing..."); Client = new DiscordRpcClient("1338522973421965382", -1, null, autoEvents: false) { Logger = new MLLogger(((MelonBase)this).LoggerInstance, Config.RPCLogLevel) }; Client.OnReady += delegate(object? _, ReadyMessage e) { ((MelonBase)this).LoggerInstance.Msg("User @" + e.User.Username + " is ready"); ((MelonBase)this).LoggerInstance.Msg("Registering URI Scheme"); RegisterURIScheme(); Client.SynchronizeState(); }; Client.OnConnectionEstablished += delegate(object? _, ConnectionEstablishedMessage e) { ((MelonBase)this).LoggerInstance.Msg($"Successfully established connection to pipe {e.ConnectedPipe}"); }; Client.OnConnectionFailed += delegate(object? _, ConnectionFailedMessage e) { ((MelonBase)this).LoggerInstance.Error($"Failed to establish connection with pipe {e.FailedPipe}"); }; Client.OnError += delegate(object? _, ErrorMessage e) { ((MelonBase)this).LoggerInstance.Error("An unexpected error has occurred when sending a message, error: " + e.Message); }; Client.OnJoin += delegate(object? _, JoinMessage e) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown try { ((MelonBase)this).LoggerInstance.Msg("Received Join Request"); string text2 = Decrypt(e.Secret); string[] array = text2.Split("|"); if (array.Length <= 1) { throw new Exception("Secret provided to join the lobby did not include all of the necessary info"); } string layer = array[0]; string code = array[1]; MelonCoroutines.Start(AfterLevelLoaded(join)); void join() { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown re