using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.WebSockets; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using Archipelago.MultiClient.Net.Colors; using Archipelago.MultiClient.Net.ConcurrentCollection; using Archipelago.MultiClient.Net.Converters; using Archipelago.MultiClient.Net.DataPackage; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Exceptions; using Archipelago.MultiClient.Net.Extensions; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.MessageLog.Messages; using Archipelago.MultiClient.Net.MessageLog.Parts; using Archipelago.MultiClient.Net.Models; using Archipelago.MultiClient.Net.Packets; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(false)] [assembly: Guid("35a803ad-85ed-42e9-b1e3-c6b72096f0c1")] [assembly: InternalsVisibleTo("Archipelago.MultiClient.Net.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Jarno Westhof, Hussein Farran, Zach Parks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyDescription("A client library for use with .NET based prog-langs for interfacing with Archipelago hosts.")] [assembly: AssemblyFileVersion("6.7.1.0")] [assembly: AssemblyInformationalVersion("6.7.1+0c57591db30f2497b0b4fef87164aa2bbe2e51b2")] [assembly: AssemblyProduct("Archipelago.MultiClient.Net")] [assembly: AssemblyTitle("Archipelago.MultiClient.Net")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/ArchipelagoMW/Archipelago.MultiClient.Net")] [assembly: AssemblyVersion("6.7.1.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } internal interface IConcurrentHashSet { bool TryAdd(T item); bool Contains(T item); void UnionWith(T[] otherSet); T[] ToArray(); ReadOnlyCollection AsToReadOnlyCollection(); ReadOnlyCollection AsToReadOnlyCollectionExcept(IConcurrentHashSet otherSet); } public class AttemptingStringEnumConverter : StringEnumConverter { public AttemptingStringEnumConverter() { } public AttemptingStringEnumConverter(Type namingStrategyType) : base(namingStrategyType) { } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { try { return ((StringEnumConverter)this).ReadJson(reader, objectType, existingValue, serializer); } catch (JsonSerializationException) { return objectType.IsValueType ? Activator.CreateInstance(objectType) : null; } } } namespace Archipelago.MultiClient.Net { [Serializable] public abstract class ArchipelagoPacketBase { [JsonIgnore] internal JObject jobject; [JsonProperty("cmd")] [JsonConverter(typeof(StringEnumConverter))] public abstract ArchipelagoPacketType PacketType { get; } public JObject ToJObject() { return jobject; } } public interface IArchipelagoSession : IArchipelagoSessionActions { IArchipelagoSocketHelper Socket { get; } IReceivedItemsHelper Items { get; } ILocationCheckHelper Locations { get; } IPlayerHelper Players { get; } IDataStorageHelper DataStorage { get; } IConnectionInfoProvider ConnectionInfo { get; } IRoomStateHelper RoomState { get; } IMessageLogHelper MessageLog { get; } IHintsHelper Hints { get; } Task ConnectAsync(); Task LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true); LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true); } public class ArchipelagoSession : IArchipelagoSession, IArchipelagoSessionActions { private const int ArchipelagoConnectionTimeoutInSeconds = 4; private ConnectionInfoHelper connectionInfo; private TaskCompletionSource loginResultTask = new TaskCompletionSource(); private TaskCompletionSource roomInfoPacketTask = new TaskCompletionSource(); public IArchipelagoSocketHelper Socket { get; } public IReceivedItemsHelper Items { get; } public ILocationCheckHelper Locations { get; } public IPlayerHelper Players { get; } public IDataStorageHelper DataStorage { get; } public IConnectionInfoProvider ConnectionInfo => connectionInfo; public IRoomStateHelper RoomState { get; } public IMessageLogHelper MessageLog { get; } public IHintsHelper Hints { get; } internal ArchipelagoSession(IArchipelagoSocketHelper socket, IReceivedItemsHelper items, ILocationCheckHelper locations, IPlayerHelper players, IRoomStateHelper roomState, ConnectionInfoHelper connectionInfoHelper, IDataStorageHelper dataStorage, IMessageLogHelper messageLog, IHintsHelper createHints) { Socket = socket; Items = items; Locations = locations; Players = players; RoomState = roomState; connectionInfo = connectionInfoHelper; DataStorage = dataStorage; MessageLog = messageLog; Hints = createHints; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket) && !(packet is ConnectionRefusedPacket)) { if (packet is RoomInfoPacket result) { roomInfoPacketTask.TrySetResult(result); } } else { loginResultTask.TrySetResult(LoginResult.FromPacket(packet)); } } public Task ConnectAsync() { roomInfoPacketTask = new TaskCompletionSource(); Task.Factory.StartNew(delegate { try { Task task = Socket.ConnectAsync(); task.Wait(TimeSpan.FromSeconds(4.0)); if (!task.IsCompleted) { roomInfoPacketTask.TrySetCanceled(); } } catch (AggregateException) { roomInfoPacketTask.TrySetCanceled(); } }); return roomInfoPacketTask.Task; } public Task LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true) { loginResultTask = new TaskCompletionSource(); if (!roomInfoPacketTask.Task.IsCompleted) { loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first")); return loginResultTask.Task; } connectionInfo.SetConnectionParameters(game, tags, itemsHandlingFlags, uuid); try { Socket.SendPacket(BuildConnectPacket(name, password, version, requestSlotData)); } catch (ArchipelagoSocketClosedException) { loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first")); return loginResultTask.Task; } SetResultAfterTimeout(loginResultTask, 4, new LoginFailure("Connection timed out.")); return loginResultTask.Task; } private static void SetResultAfterTimeout(TaskCompletionSource task, int timeoutInSeconds, T result) { new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)).Token.Register(delegate { task.TrySetResult(result); }); } public LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true) { Task task = ConnectAsync(); try { task.Wait(TimeSpan.FromSeconds(4.0)); } catch (AggregateException ex) { if (ex.GetBaseException() is OperationCanceledException) { return new LoginFailure("Connection timed out."); } return new LoginFailure(ex.GetBaseException().Message); } if (!task.IsCompleted) { return new LoginFailure("Connection timed out."); } return LoginAsync(game, name, itemsHandlingFlags, version, tags, uuid, password, requestSlotData).Result; } private ConnectPacket BuildConnectPacket(string name, string password, Version version, bool requestSlotData) { return new ConnectPacket { Game = ConnectionInfo.Game, Name = name, Password = password, Tags = ConnectionInfo.Tags, Uuid = ConnectionInfo.Uuid, Version = ((version != null) ? new NetworkVersion(version) : new NetworkVersion(0, 6, 0)), ItemsHandling = ConnectionInfo.ItemsHandlingFlags, RequestSlotData = requestSlotData }; } public void Say(string message) { Socket.SendPacket(new SayPacket { Text = message }); } public void SetClientState(ArchipelagoClientState state) { Socket.SendPacket(new StatusUpdatePacket { Status = state }); } public void SetGoalAchieved() { SetClientState(ArchipelagoClientState.ClientGoal); } } public interface IArchipelagoSessionActions { void Say(string message); void SetClientState(ArchipelagoClientState state); void SetGoalAchieved(); } public static class ArchipelagoSessionFactory { public static ArchipelagoSession CreateSession(Uri uri) { ArchipelagoSocketHelper socket = new ArchipelagoSocketHelper(uri); DataPackageCache cache = new DataPackageCache(socket); ConnectionInfoHelper connectionInfoHelper = new ConnectionInfoHelper(socket); PlayerHelper playerHelper = new PlayerHelper(socket, connectionInfoHelper); ItemInfoResolver itemInfoResolver = new ItemInfoResolver(cache, connectionInfoHelper); LocationCheckHelper locationCheckHelper = new LocationCheckHelper(socket, itemInfoResolver, connectionInfoHelper, playerHelper); ReceivedItemsHelper items = new ReceivedItemsHelper(socket, locationCheckHelper, itemInfoResolver, connectionInfoHelper, playerHelper); RoomStateHelper roomStateHelper = new RoomStateHelper(socket, locationCheckHelper); DataStorageHelper dataStorageHelper = new DataStorageHelper(socket, connectionInfoHelper); MessageLogHelper messageLog = new MessageLogHelper(socket, itemInfoResolver, playerHelper, connectionInfoHelper); HintsHelper createHints = new HintsHelper(socket, playerHelper, locationCheckHelper, roomStateHelper, dataStorageHelper); return new ArchipelagoSession(socket, items, locationCheckHelper, playerHelper, roomStateHelper, connectionInfoHelper, dataStorageHelper, messageLog, createHints); } public static ArchipelagoSession CreateSession(string hostname, int port = 38281) { return CreateSession(ParseUri(hostname, port)); } internal static Uri ParseUri(string hostname, int port) { string text = hostname; if (!text.StartsWith("ws://") && !text.StartsWith("wss://")) { text = "unspecified://" + text; } if (!text.Substring(text.IndexOf("://", StringComparison.Ordinal) + 3).Contains(":")) { text += $":{port}"; } if (text.EndsWith(":")) { text += port; } return new Uri(text); } } public abstract class LoginResult { public abstract bool Successful { get; } public static LoginResult FromPacket(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (packet is ConnectionRefusedPacket connectionRefusedPacket) { return new LoginFailure(connectionRefusedPacket); } throw new ArgumentOutOfRangeException("packet", "packet is not a connection result packet"); } return new LoginSuccessful(connectedPacket); } } public class LoginSuccessful : LoginResult { public override bool Successful => true; public int Team { get; } public int Slot { get; } public Dictionary SlotData { get; } public LoginSuccessful(ConnectedPacket connectedPacket) { Team = connectedPacket.Team; Slot = connectedPacket.Slot; SlotData = connectedPacket.SlotData; } } public class LoginFailure : LoginResult { public override bool Successful => false; public ConnectionRefusedError[] ErrorCodes { get; } public string[] Errors { get; } public LoginFailure(ConnectionRefusedPacket connectionRefusedPacket) { if (connectionRefusedPacket.Errors != null) { ErrorCodes = connectionRefusedPacket.Errors.ToArray(); Errors = ErrorCodes.Select(GetErrorMessage).ToArray(); } else { ErrorCodes = new ConnectionRefusedError[0]; Errors = new string[0]; } } public LoginFailure(string message) { ErrorCodes = new ConnectionRefusedError[0]; Errors = new string[1] { message }; } private static string GetErrorMessage(ConnectionRefusedError errorCode) { return errorCode switch { ConnectionRefusedError.InvalidSlot => "The slot name did not match any slot on the server.", ConnectionRefusedError.InvalidGame => "The slot is set to a different game on the server.", ConnectionRefusedError.SlotAlreadyTaken => "The slot already has a connection with a different uuid established.", ConnectionRefusedError.IncompatibleVersion => "The client and server version mismatch.", ConnectionRefusedError.InvalidPassword => "The password is invalid.", ConnectionRefusedError.InvalidItemsHandling => "The item handling flags provided are invalid.", _ => $"Unknown error: {errorCode}.", }; } } internal class TwoWayLookup : IEnumerable>, IEnumerable { private readonly Dictionary aToB = new Dictionary(); private readonly Dictionary bToA = new Dictionary(); public TA this[TB b] => bToA[b]; public TB this[TA a] => aToB[a]; public void Add(TA a, TB b) { aToB[a] = b; bToA[b] = a; } public void Add(TB b, TA a) { Add(a, b); } public bool TryGetValue(TA a, out TB b) { return aToB.TryGetValue(a, out b); } public bool TryGetValue(TB b, out TA a) { return bToA.TryGetValue(b, out a); } public IEnumerator> GetEnumerator() { return bToA.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } } namespace Archipelago.MultiClient.Net.Packets { public class BouncedPacket : BouncePacket { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounced; } public class BouncePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounce; [JsonProperty("games")] public List Games { get; set; } = new List(); [JsonProperty("slots")] public List Slots { get; set; } = new List(); [JsonProperty("tags")] public List Tags { get; set; } = new List(); [JsonProperty("data")] public Dictionary Data { get; set; } } public class ConnectedPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connected; [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("players")] public NetworkPlayer[] Players { get; set; } [JsonProperty("missing_locations")] public long[] MissingChecks { get; set; } [JsonProperty("checked_locations")] public long[] LocationsChecked { get; set; } [JsonProperty("slot_data")] public Dictionary SlotData { get; set; } [JsonProperty("slot_info")] public Dictionary SlotInfo { get; set; } [JsonProperty("hint_points")] public int? HintPoints { get; set; } } public class ConnectionRefusedPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectionRefused; [JsonProperty("errors", ItemConverterType = typeof(AttemptingStringEnumConverter))] public ConnectionRefusedError[] Errors { get; set; } } public class ConnectPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connect; [JsonProperty("password")] public string Password { get; set; } [JsonProperty("game")] public string Game { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("uuid")] public string Uuid { get; set; } [JsonProperty("version")] public NetworkVersion Version { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("items_handling")] public ItemsHandlingFlags ItemsHandling { get; set; } [JsonProperty("slot_data")] public bool RequestSlotData { get; set; } } public class ConnectUpdatePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectUpdate; [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("items_handling")] public ItemsHandlingFlags? ItemsHandling { get; set; } } public class CreateHintsPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.CreateHints; [JsonProperty("locations")] public long[] Locations { get; set; } [JsonProperty("player")] public int Player { get; set; } [JsonProperty("status")] public HintStatus Status { get; set; } } public class DataPackagePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.DataPackage; [JsonProperty("data")] public Archipelago.MultiClient.Net.Models.DataPackage DataPackage { get; set; } } public class GetDataPackagePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.GetDataPackage; [JsonProperty("games")] public string[] Games { get; set; } } public class GetPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Get; [JsonProperty("keys")] public string[] Keys { get; set; } } public class InvalidPacketPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.InvalidPacket; [JsonProperty("type")] public InvalidPacketErrorType ErrorType { get; set; } [JsonProperty("text")] public string ErrorText { get; set; } [JsonProperty("original_cmd")] public ArchipelagoPacketType OriginalCmd { get; set; } } public class LocationChecksPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationChecks; [JsonProperty("locations")] public long[] Locations { get; set; } } public class LocationInfoPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationInfo; [JsonProperty("locations")] public NetworkItem[] Locations { get; set; } } public class LocationScoutsPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationScouts; [JsonProperty("locations")] public long[] Locations { get; set; } [JsonProperty("create_as_hint")] public int CreateAsHint { get; set; } } public class PrintJsonPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.PrintJSON; [JsonProperty("data")] public JsonMessagePart[] Data { get; set; } [JsonProperty("type")] [JsonConverter(typeof(AttemptingStringEnumConverter))] public JsonMessageType? MessageType { get; set; } } public class ItemPrintJsonPacket : PrintJsonPacket { [JsonProperty("receiving")] public int ReceivingPlayer { get; set; } [JsonProperty("item")] public NetworkItem Item { get; set; } } public class ItemCheatPrintJsonPacket : PrintJsonPacket { [JsonProperty("receiving")] public int ReceivingPlayer { get; set; } [JsonProperty("item")] public NetworkItem Item { get; set; } [JsonProperty("team")] public int Team { get; set; } } public class HintPrintJsonPacket : PrintJsonPacket { [JsonProperty("receiving")] public int ReceivingPlayer { get; set; } [JsonProperty("item")] public NetworkItem Item { get; set; } [JsonProperty("found")] public bool? Found { get; set; } } public class JoinPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } } public class LeavePrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class ChatPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("message")] public string Message { get; set; } } public class ServerChatPrintJsonPacket : PrintJsonPacket { [JsonProperty("message")] public string Message { get; set; } } public class TutorialPrintJsonPacket : PrintJsonPacket { } public class TagsChangedPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } } public class CommandResultPrintJsonPacket : PrintJsonPacket { } public class AdminCommandResultPrintJsonPacket : PrintJsonPacket { } public class GoalPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class ReleasePrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class CollectPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class CountdownPrintJsonPacket : PrintJsonPacket { [JsonProperty("countdown")] public int RemainingSeconds { get; set; } } public class ReceivedItemsPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ReceivedItems; [JsonProperty("index")] public int Index { get; set; } [JsonProperty("items")] public NetworkItem[] Items { get; set; } } public class RetrievedPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Retrieved; [JsonProperty("keys")] public Dictionary Data { get; set; } } public class RoomInfoPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomInfo; [JsonProperty("version")] public NetworkVersion Version { get; set; } [JsonProperty("generator_version")] public NetworkVersion GeneratorVersion { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("password")] public bool Password { get; set; } [JsonProperty("permissions")] public Dictionary Permissions { get; set; } [JsonProperty("hint_cost")] public int HintCostPercentage { get; set; } [JsonProperty("location_check_points")] public int LocationCheckPoints { get; set; } [JsonProperty("players")] public NetworkPlayer[] Players { get; set; } [JsonProperty("games")] public string[] Games { get; set; } [JsonProperty("datapackage_checksums")] public Dictionary DataPackageChecksums { get; set; } [JsonProperty("seed_name")] public string SeedName { get; set; } [JsonProperty("time")] public double Timestamp { get; set; } } public class RoomUpdatePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomUpdate; [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("password")] public bool? Password { get; set; } [JsonProperty("permissions")] public Dictionary Permissions { get; set; } = new Dictionary(); [JsonProperty("hint_cost")] public int? HintCostPercentage { get; set; } [JsonProperty("location_check_points")] public int? LocationCheckPoints { get; set; } [JsonProperty("players")] public NetworkPlayer[] Players { get; set; } [JsonProperty("hint_points")] public int? HintPoints { get; set; } [JsonProperty("checked_locations")] public long[] CheckedLocations { get; set; } } public class SayPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Say; [JsonProperty("text")] public string Text { get; set; } } public class SetNotifyPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetNotify; [JsonProperty("keys")] public string[] Keys { get; set; } } public class SetPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Set; [JsonProperty("key")] public string Key { get; set; } [JsonProperty("default")] public JToken DefaultValue { get; set; } [JsonProperty("operations")] public OperationSpecification[] Operations { get; set; } [JsonProperty("want_reply")] public bool WantReply { get; set; } [JsonExtensionData] public Dictionary AdditionalArguments { get; set; } [OnDeserialized] internal void OnDeserializedMethod(StreamingContext context) { AdditionalArguments?.Remove("cmd"); } } public class SetReplyPacket : SetPacket { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetReply; [JsonProperty("value")] public JToken Value { get; set; } [JsonProperty("original_value")] public JToken OriginalValue { get; set; } } public class StatusUpdatePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.StatusUpdate; [JsonProperty("status")] public ArchipelagoClientState Status { get; set; } } public class SyncPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Sync; } internal class UnknownPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Unknown; } public class UpdateHintPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.UpdateHint; [JsonProperty("player")] public int Player { get; set; } [JsonProperty("location")] public long Location { get; set; } [JsonProperty("status")] public HintStatus Status { get; set; } } } namespace Archipelago.MultiClient.Net.Models { public struct Color : IEquatable { public static Color Red = new Color(byte.MaxValue, 0, 0); public static Color Green = new Color(0, 128, 0); public static Color Yellow = new Color(byte.MaxValue, byte.MaxValue, 0); public static Color Blue = new Color(0, 0, byte.MaxValue); public static Color Magenta = new Color(byte.MaxValue, 0, byte.MaxValue); public static Color Cyan = new Color(0, byte.MaxValue, byte.MaxValue); public static Color Black = new Color(0, 0, 0); public static Color White = new Color(byte.MaxValue, byte.MaxValue, byte.MaxValue); public static Color SlateBlue = new Color(106, 90, 205); public static Color Salmon = new Color(250, 128, 114); public static Color Plum = new Color(221, 160, 221); public byte R { get; set; } public byte G { get; set; } public byte B { get; set; } public Color(byte r, byte g, byte b) { R = r; G = g; B = b; } public override bool Equals(object obj) { if (obj is Color color && R == color.R && G == color.G) { return B == color.B; } return false; } public bool Equals(Color other) { if (R == other.R && G == other.G) { return B == other.B; } return false; } public override int GetHashCode() { return ((-1520100960 * -1521134295 + R.GetHashCode()) * -1521134295 + G.GetHashCode()) * -1521134295 + B.GetHashCode(); } public static bool operator ==(Color left, Color right) { return left.Equals(right); } public static bool operator !=(Color left, Color right) { return !(left == right); } } public class DataPackage { [JsonProperty("games")] public Dictionary Games { get; set; } = new Dictionary(); } public class DataStorageElement { internal DataStorageElementContext Context; internal List Operations = new List(0); internal DataStorageHelper.DataStorageUpdatedHandler Callbacks; internal Dictionary AdditionalArguments = new Dictionary(0); private JToken cachedValue; public event DataStorageHelper.DataStorageUpdatedHandler OnValueChanged { add { Context.AddHandler(Context.Key, value); } remove { Context.RemoveHandler(Context.Key, value); } } internal DataStorageElement(DataStorageElementContext context) { Context = context; } internal DataStorageElement(OperationType operationType, JToken value) { Operations = new List(1) { new OperationSpecification { OperationType = operationType, Value = value } }; } internal DataStorageElement(DataStorageElement source, OperationType operationType, JToken value) : this(source.Context) { Operations = source.Operations.ToList(); Callbacks = source.Callbacks; AdditionalArguments = source.AdditionalArguments; Operations.Add(new OperationSpecification { OperationType = operationType, Value = value }); } internal DataStorageElement(DataStorageElement source, Callback callback) : this(source.Context) { Operations = source.Operations.ToList(); Callbacks = source.Callbacks; AdditionalArguments = source.AdditionalArguments; Callbacks = (DataStorageHelper.DataStorageUpdatedHandler)Delegate.Combine(Callbacks, callback.Method); } internal DataStorageElement(DataStorageElement source, AdditionalArgument additionalArgument) : this(source.Context) { Operations = source.Operations.ToList(); Callbacks = source.Callbacks; AdditionalArguments = source.AdditionalArguments; AdditionalArguments[additionalArgument.Key] = additionalArgument.Value; } public static DataStorageElement operator ++(DataStorageElement a) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(1)); } public static DataStorageElement operator --(DataStorageElement a) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(-1)); } public static DataStorageElement operator +(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, string b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, JToken b) { return new DataStorageElement(a, OperationType.Add, b); } public static DataStorageElement operator +(DataStorageElement a, IEnumerable b) { return new DataStorageElement(a, OperationType.Add, (JToken)(object)JArray.FromObject((object)b)); } public static DataStorageElement operator +(DataStorageElement a, OperationSpecification s) { return new DataStorageElement(a, s.OperationType, s.Value); } public static DataStorageElement operator +(DataStorageElement a, Callback c) { return new DataStorageElement(a, c); } public static DataStorageElement operator +(DataStorageElement a, AdditionalArgument arg) { return new DataStorageElement(a, arg); } public static DataStorageElement operator *(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator -(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b))); } public static DataStorageElement operator -(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b))); } public static DataStorageElement operator -(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0f - b))); } public static DataStorageElement operator -(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0.0 - b))); } public static DataStorageElement operator -(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b))); } public static DataStorageElement operator /(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b))); } public static DataStorageElement operator /(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b))); } public static DataStorageElement operator /(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / (double)b))); } public static DataStorageElement operator /(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / b))); } public static DataStorageElement operator /(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / b))); } public static implicit operator DataStorageElement(bool b) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(b)); } public static implicit operator DataStorageElement(int i) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(i)); } public static implicit operator DataStorageElement(long l) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(l)); } public static implicit operator DataStorageElement(decimal m) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(m)); } public static implicit operator DataStorageElement(double d) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(d)); } public static implicit operator DataStorageElement(float f) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(f)); } public static implicit operator DataStorageElement(string s) { if (s != null) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(s)); } return new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull()); } public static implicit operator DataStorageElement(JToken o) { return new DataStorageElement(OperationType.Replace, o); } public static implicit operator DataStorageElement(Array a) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)a)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator bool(DataStorageElement e) { return RetrieveAndReturnBoolValue(e); } public static implicit operator bool?(DataStorageElement e) { return RetrieveAndReturnBoolValue(e); } public static implicit operator int(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator int?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator long(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator long?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator float(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator float?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator double(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator double?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator decimal(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator decimal?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator string(DataStorageElement e) { return RetrieveAndReturnStringValue(e); } public static implicit operator bool[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator int[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator long[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator decimal[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator double[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator float[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator string[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator object[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator Array(DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator JArray(DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator JToken(DataStorageElement e) { return e.Context.GetData(e.Context.Key); } public static DataStorageElement operator +(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Add, JToken.Parse(b.ToString())); } public static DataStorageElement operator *(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Mul, JToken.Parse(b.ToString())); } public static DataStorageElement operator %(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Mod, JToken.Parse(b.ToString())); } public static DataStorageElement operator ^(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Pow, JToken.Parse(b.ToString())); } public static DataStorageElement operator -(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Add, JToken.Parse((-b).ToString())); } public static DataStorageElement operator /(DataStorageElement a, BigInteger b) { throw new InvalidOperationException("DataStorage[Key] / BigInterger is not supported, due to loss of precision when using integer division"); } public static implicit operator DataStorageElement(BigInteger bi) { return new DataStorageElement(OperationType.Replace, JToken.Parse(bi.ToString())); } public static implicit operator BigInteger(DataStorageElement e) { return RetrieveAndReturnBigIntegerValue(e); } public static implicit operator BigInteger?(DataStorageElement e) { return RetrieveAndReturnBigIntegerValue(e); } private static T RetrieveAndReturnBigIntegerValue(DataStorageElement e) { if (e.cachedValue != null) { if (!BigInteger.TryParse(((object)e.cachedValue).ToString(), out var result)) { return default(T); } return (T)Convert.ChangeType(result, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } BigInteger result2; BigInteger? bigInteger = (BigInteger.TryParse(((object)e.Context.GetData(e.Context.Key)).ToString(), out result2) ? new BigInteger?(result2) : null); if (!bigInteger.HasValue && !IsNullable()) { bigInteger = Activator.CreateInstance(); } foreach (OperationSpecification operation in e.Operations) { if (operation.OperationType == OperationType.Floor || operation.OperationType == OperationType.Ceil) { continue; } if (!BigInteger.TryParse(((object)operation.Value).ToString(), NumberStyles.AllowLeadingSign, null, out var result3)) { throw new InvalidOperationException($"DataStorage[Key] cannot be converted to BigInterger as its value its not an integer number, value: {operation.Value}"); } switch (operation.OperationType) { case OperationType.Replace: bigInteger = result3; break; case OperationType.Add: bigInteger += result3; break; case OperationType.Mul: bigInteger *= result3; break; case OperationType.Mod: bigInteger %= result3; break; case OperationType.Pow: bigInteger = BigInteger.Pow(bigInteger.Value, (int)operation.Value); break; case OperationType.Max: { BigInteger value = result3; BigInteger? bigInteger2 = bigInteger; if (value > bigInteger2) { bigInteger = result3; } break; } case OperationType.Min: { BigInteger value = result3; BigInteger? bigInteger2 = bigInteger; if (value < bigInteger2) { bigInteger = result3; } break; } case OperationType.Xor: bigInteger ^= result3; break; case OperationType.Or: bigInteger |= result3; break; case OperationType.And: bigInteger &= result3; break; case OperationType.LeftShift: bigInteger <<= (int)operation.Value; break; case OperationType.RightShift: bigInteger >>= (int)operation.Value; break; } } e.cachedValue = JToken.Parse(bigInteger.ToString()); if (!bigInteger.HasValue) { return default(T); } return (T)Convert.ChangeType(bigInteger.Value, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } public void Initialize(JToken value) { Context.Initialize(Context.Key, value); } public void Initialize(IEnumerable value) { Context.Initialize(Context.Key, (JToken)(object)JArray.FromObject((object)value)); } public Task GetAsync() { return GetAsync().ContinueWith((Task r) => r.Result.ToObject()); } public Task GetAsync() { return Context.GetAsync(Context.Key); } private static T RetrieveAndReturnArrayValue(DataStorageElement e) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_007c: Invalid comparison between Unknown and I4 //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Invalid comparison between Unknown and I4 //IL_0120: Unknown result type (might be due to invalid IL or missing references) if (e.cachedValue != null) { return ((JToken)(JArray)e.cachedValue).ToObject(); } JArray val = (JArray)(((object)e.Context.GetData(e.Context.Key).ToObject()) ?? ((object)new JArray())); foreach (OperationSpecification operation in e.Operations) { switch (operation.OperationType) { case OperationType.Add: if ((int)operation.Value.Type != 2) { throw new InvalidOperationException($"Cannot perform operation {1} on Array value, with a non Array value: {operation.Value}"); } ((JContainer)val).Merge((object)operation.Value); break; case OperationType.Replace: if ((int)operation.Value.Type != 2) { throw new InvalidOperationException($"Cannot replace Array value, with a non Array value: {operation.Value}"); } val = (JArray)(((object)operation.Value.ToObject()) ?? ((object)new JArray())); break; default: throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on Array value"); } } e.cachedValue = (JToken)(object)val; return ((JToken)val).ToObject(); } private static string RetrieveAndReturnStringValue(DataStorageElement e) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Invalid comparison between Unknown and I4 if (e.cachedValue != null) { return (string)e.cachedValue; } JToken val = e.Context.GetData(e.Context.Key); string text = (((int)val.Type == 10) ? null : ((object)val).ToString()); foreach (OperationSpecification operation in e.Operations) { switch (operation.OperationType) { case OperationType.Add: text += (string)operation.Value; break; case OperationType.Mul: if ((int)operation.Value.Type != 6) { throw new InvalidOperationException($"Cannot perform operation {2} on string value, with a non interger value: {operation.Value}"); } text = string.Concat(Enumerable.Repeat(text, (int)operation.Value)); break; case OperationType.Replace: text = (string)operation.Value; break; default: throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on string value"); } } if (text == null) { e.cachedValue = (JToken)(object)JValue.CreateNull(); } else { e.cachedValue = JToken.op_Implicit(text); } return (string)e.cachedValue; } private static T RetrieveAndReturnBoolValue(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject(); } bool? flag = e.Context.GetData(e.Context.Key).ToObject() ?? ((bool?)Activator.CreateInstance(typeof(T))); foreach (OperationSpecification operation in e.Operations) { if (operation.OperationType == OperationType.Replace) { flag = (bool?)operation.Value; continue; } throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on boolean value"); } e.cachedValue = JToken.op_Implicit(flag); if (!flag.HasValue) { return default(T); } return (T)Convert.ChangeType(flag.Value, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static T RetrieveAndReturnDecimalValue(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject(); } decimal? num = e.Context.GetData(e.Context.Key).ToObject(); if (!num.HasValue && !IsNullable()) { num = Activator.CreateInstance(); } foreach (OperationSpecification operation in e.Operations) { switch (operation.OperationType) { case OperationType.Replace: num = (decimal)operation.Value; break; case OperationType.Add: num += (decimal?)(decimal)operation.Value; break; case OperationType.Mul: num *= (decimal?)(decimal)operation.Value; break; case OperationType.Mod: num %= (decimal?)(decimal)operation.Value; break; case OperationType.Pow: num = (decimal)Math.Pow((double)num.Value, (double)operation.Value); break; case OperationType.Max: num = Math.Max(num.Value, (decimal)operation.Value); break; case OperationType.Min: num = Math.Min(num.Value, (decimal)operation.Value); break; case OperationType.Xor: num = (long)num.Value ^ (long)operation.Value; break; case OperationType.Or: num = (long)num.Value | (long)operation.Value; break; case OperationType.And: num = (long)num.Value & (long)operation.Value; break; case OperationType.LeftShift: num = (long)num.Value << (int)operation.Value; break; case OperationType.RightShift: num = (long)num.Value >> (int)operation.Value; break; case OperationType.Floor: num = Math.Floor(num.Value); break; case OperationType.Ceil: num = Math.Ceiling(num.Value); break; } } e.cachedValue = JToken.op_Implicit(num); if (!num.HasValue) { return default(T); } return (T)Convert.ChangeType(num.Value, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static bool IsNullable() { if (typeof(T).IsGenericType) { return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } return false; } public T To() { if (Operations.Count != 0) { throw new InvalidOperationException("DataStorageElement.To() cannot be used together with other operations on the DataStorageElement"); } return Context.GetData(Context.Key).ToObject(); } public override string ToString() { return (Context?.ToString() ?? "(null)") + ", (" + ListOperations() + ")"; } private string ListOperations() { if (Operations != null) { return string.Join(", ", Operations.Select((OperationSpecification o) => o.ToString()).ToArray()); } return "none"; } } internal class DataStorageElementContext { internal string Key { get; set; } internal Action AddHandler { get; set; } internal Action RemoveHandler { get; set; } internal Func GetData { get; set; } internal Action Initialize { get; set; } internal Func> GetAsync { get; set; } public override string ToString() { return "Key: " + Key; } } public class GameData { [JsonProperty("location_name_to_id")] public Dictionary LocationLookup { get; set; } = new Dictionary(); [JsonProperty("item_name_to_id")] public Dictionary ItemLookup { get; set; } = new Dictionary(); [Obsolete("use Checksum instead")] [JsonProperty("version")] public int Version { get; set; } [JsonProperty("checksum")] public string Checksum { get; set; } } public class Hint { [JsonProperty("receiving_player")] public int ReceivingPlayer { get; set; } [JsonProperty("finding_player")] public int FindingPlayer { get; set; } [JsonProperty("item")] public long ItemId { get; set; } [JsonProperty("location")] public long LocationId { get; set; } [JsonProperty("item_flags")] public ItemFlags ItemFlags { get; set; } [JsonProperty("found")] public bool Found { get; set; } [JsonProperty("entrance")] public string Entrance { get; set; } [JsonProperty("status")] public HintStatus Status { get; set; } } public class ItemInfo { private readonly IItemInfoResolver itemInfoResolver; public long ItemId { get; } public long LocationId { get; } public PlayerInfo Player { get; } public ItemFlags Flags { get; } public string ItemName => itemInfoResolver.GetItemName(ItemId, ItemGame); public string ItemDisplayName => ItemName ?? $"Item: {ItemId}"; public string LocationName => itemInfoResolver.GetLocationName(LocationId, LocationGame); public string LocationDisplayName => LocationName ?? $"Location: {LocationId}"; public string ItemGame { get; } public string LocationGame { get; } public ItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, PlayerInfo player) { this.itemInfoResolver = itemInfoResolver; ItemGame = receiverGame; LocationGame = senderGame; ItemId = item.Item; LocationId = item.Location; Flags = item.Flags; Player = player; } public SerializableItemInfo ToSerializable() { return new SerializableItemInfo { IsScout = (GetType() == typeof(ScoutedItemInfo)), ItemId = ItemId, LocationId = LocationId, PlayerSlot = Player, Player = Player, Flags = Flags, ItemGame = ItemGame, ItemName = ItemName, LocationGame = LocationGame, LocationName = LocationName }; } } public class ScoutedItemInfo : ItemInfo { public new PlayerInfo Player => base.Player; public bool IsReceiverRelatedToActivePlayer { get; } public ScoutedItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, IPlayerHelper players, PlayerInfo player) : base(item, receiverGame, senderGame, itemInfoResolver, player) { IsReceiverRelatedToActivePlayer = (players.ActivePlayer ?? new PlayerInfo()).IsRelatedTo(player); } } public class JsonMessagePart { [JsonProperty("type")] [JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })] public JsonMessagePartType? Type { get; set; } [JsonProperty("color")] [JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })] public JsonMessagePartColor? Color { get; set; } [JsonProperty("text")] public string Text { get; set; } [JsonProperty("player")] public int? Player { get; set; } [JsonProperty("flags")] public ItemFlags? Flags { get; set; } [JsonProperty("hint_status")] public HintStatus? HintStatus { get; set; } } public struct NetworkItem { [JsonProperty("item")] public long Item { get; set; } [JsonProperty("location")] public long Location { get; set; } [JsonProperty("player")] public int Player { get; set; } [JsonProperty("flags")] public ItemFlags Flags { get; set; } } public struct NetworkPlayer { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("alias")] public string Alias { get; set; } [JsonProperty("name")] public string Name { get; set; } } public struct NetworkSlot { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("game")] public string Game { get; set; } [JsonProperty("type")] public SlotType Type { get; set; } [JsonProperty("group_members")] public int[] GroupMembers { get; set; } } public class NetworkVersion { [JsonProperty("major")] public int Major { get; set; } [JsonProperty("minor")] public int Minor { get; set; } [JsonProperty("build")] public int Build { get; set; } [JsonProperty("class")] public string Class => "Version"; public NetworkVersion() { } public NetworkVersion(int major, int minor, int build) { Major = major; Minor = minor; Build = build; } public NetworkVersion(Version version) { Major = version.Major; Minor = version.Minor; Build = version.Build; } public Version ToVersion() { return new Version(Major, Minor, Build); } } public class OperationSpecification { [JsonProperty("operation")] [JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })] public OperationType OperationType; [JsonProperty("value")] public JToken Value { get; set; } public override string ToString() { return $"{OperationType}: {Value}"; } } public static class Operation { public static OperationSpecification Min(int i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(long i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(float i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(double i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(decimal i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(JToken i) { return new OperationSpecification { OperationType = OperationType.Min, Value = i }; } public static OperationSpecification Min(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification Max(int i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(long i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(float i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(double i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(decimal i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(JToken i) { return new OperationSpecification { OperationType = OperationType.Max, Value = i }; } public static OperationSpecification Max(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification Remove(JToken value) { return new OperationSpecification { OperationType = OperationType.Remove, Value = value }; } public static OperationSpecification Pop(int value) { return new OperationSpecification { OperationType = OperationType.Pop, Value = JToken.op_Implicit(value) }; } public static OperationSpecification Pop(JToken value) { return new OperationSpecification { OperationType = OperationType.Pop, Value = value }; } public static OperationSpecification Update(IDictionary dictionary) { return new OperationSpecification { OperationType = OperationType.Update, Value = (JToken)(object)JObject.FromObject((object)dictionary) }; } public static OperationSpecification Floor() { return new OperationSpecification { OperationType = OperationType.Floor, Value = null }; } public static OperationSpecification Ceiling() { return new OperationSpecification { OperationType = OperationType.Ceil, Value = null }; } } public static class Bitwise { public static OperationSpecification Xor(long i) { return new OperationSpecification { OperationType = OperationType.Xor, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Xor(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Xor, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification Or(long i) { return new OperationSpecification { OperationType = OperationType.Or, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Or(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Or, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification And(long i) { return new OperationSpecification { OperationType = OperationType.And, Value = JToken.op_Implicit(i) }; } public static OperationSpecification And(BigInteger i) { return new OperationSpecification { OperationType = OperationType.And, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification LeftShift(long i) { return new OperationSpecification { OperationType = OperationType.LeftShift, Value = JToken.op_Implicit(i) }; } public static OperationSpecification RightShift(long i) { return new OperationSpecification { OperationType = OperationType.RightShift, Value = JToken.op_Implicit(i) }; } } public class Callback { internal DataStorageHelper.DataStorageUpdatedHandler Method { get; set; } private Callback() { } public static Callback Add(DataStorageHelper.DataStorageUpdatedHandler callback) { return new Callback { Method = callback }; } } public class AdditionalArgument { internal string Key { get; set; } internal JToken Value { get; set; } private AdditionalArgument() { } public static AdditionalArgument Add(string name, JToken value) { return new AdditionalArgument { Key = name, Value = value }; } } public class MinimalSerializableItemInfo { public long ItemId { get; set; } public long LocationId { get; set; } public int PlayerSlot { get; set; } public ItemFlags Flags { get; set; } public string ItemGame { get; set; } public string LocationGame { get; set; } } public class SerializableItemInfo : MinimalSerializableItemInfo { public bool IsScout { get; set; } public PlayerInfo Player { get; set; } public string ItemName { get; set; } public string LocationName { get; set; } [JsonIgnore] public string ItemDisplayName => ItemName ?? $"Item: {base.ItemId}"; [JsonIgnore] public string LocationDisplayName => LocationName ?? $"Location: {base.LocationId}"; public string ToJson(bool full = false) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0071: Expected O, but got Unknown MinimalSerializableItemInfo minimalSerializableItemInfo = this; if (!full) { minimalSerializableItemInfo = new MinimalSerializableItemInfo { ItemId = base.ItemId, LocationId = base.LocationId, PlayerSlot = base.PlayerSlot, Flags = base.Flags }; if (IsScout) { minimalSerializableItemInfo.ItemGame = base.ItemGame; } else { minimalSerializableItemInfo.LocationGame = base.LocationGame; } } JsonSerializerSettings val = new JsonSerializerSettings { NullValueHandling = (NullValueHandling)1, Formatting = (Formatting)0 }; return JsonConvert.SerializeObject((object)minimalSerializableItemInfo, val); } public static SerializableItemInfo FromJson(string json, IArchipelagoSession session = null) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown ItemInfoStreamingContext additional = ((session != null) ? new ItemInfoStreamingContext { Items = session.Items, Locations = session.Locations, PlayerHelper = session.Players, ConnectionInfo = session.ConnectionInfo } : null); JsonSerializerSettings val = new JsonSerializerSettings { Context = new StreamingContext(StreamingContextStates.Other, additional) }; return JsonConvert.DeserializeObject(json, val); } [OnDeserialized] internal void OnDeserializedMethod(StreamingContext streamingContext) { if (base.ItemGame == null && base.LocationGame != null) { IsScout = false; } else if (base.ItemGame != null && base.LocationGame == null) { IsScout = true; } if (streamingContext.Context is ItemInfoStreamingContext itemInfoStreamingContext) { if (IsScout && base.LocationGame == null) { base.LocationGame = itemInfoStreamingContext.ConnectionInfo.Game; } else if (!IsScout && base.ItemGame == null) { base.ItemGame = itemInfoStreamingContext.ConnectionInfo.Game; } if (ItemName == null) { ItemName = itemInfoStreamingContext.Items.GetItemName(base.ItemId, base.ItemGame); } if (LocationName == null) { LocationName = itemInfoStreamingContext.Locations.GetLocationNameFromId(base.LocationId, base.LocationGame); } if (Player == null) { Player = itemInfoStreamingContext.PlayerHelper.GetPlayerInfo(base.PlayerSlot); } } } } internal class ItemInfoStreamingContext { public IReceivedItemsHelper Items { get; set; } public ILocationCheckHelper Locations { get; set; } public IPlayerHelper PlayerHelper { get; set; } public IConnectionInfoProvider ConnectionInfo { get; set; } } } namespace Archipelago.MultiClient.Net.MessageLog.Parts { public class EntranceMessagePart : MessagePart { internal EntranceMessagePart(JsonMessagePart messagePart) : base(MessagePartType.Entrance, messagePart, Archipelago.MultiClient.Net.Colors.PaletteColor.Blue) { base.Text = messagePart.Text; } } public class HintStatusMessagePart : MessagePart { internal HintStatusMessagePart(JsonMessagePart messagePart) : base(MessagePartType.HintStatus, messagePart) { base.Text = messagePart.Text; if (messagePart.HintStatus.HasValue) { base.PaletteColor = ColorUtils.GetColor(messagePart.HintStatus.Value); } } } public class ItemMessagePart : MessagePart { public ItemFlags Flags { get; } public long ItemId { get; } public int Player { get; } internal ItemMessagePart(IPlayerHelper players, IItemInfoResolver items, JsonMessagePart part) : base(MessagePartType.Item, part) { Flags = part.Flags.GetValueOrDefault(); base.PaletteColor = ColorUtils.GetColor(Flags); Player = part.Player.GetValueOrDefault(); string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game; JsonMessagePartType? type = part.Type; if (type.HasValue) { switch (type.GetValueOrDefault()) { case JsonMessagePartType.ItemId: ItemId = long.Parse(part.Text); base.Text = items.GetItemName(ItemId, game) ?? $"Item: {ItemId}"; break; case JsonMessagePartType.ItemName: ItemId = 0L; base.Text = part.Text; break; } } } } public class LocationMessagePart : MessagePart { public long LocationId { get; } public int Player { get; } internal LocationMessagePart(IPlayerHelper players, IItemInfoResolver itemInfoResolver, JsonMessagePart part) : base(MessagePartType.Location, part, Archipelago.MultiClient.Net.Colors.PaletteColor.Green) { Player = part.Player.GetValueOrDefault(); string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game; JsonMessagePartType? type = part.Type; if (type.HasValue) { switch (type.GetValueOrDefault()) { case JsonMessagePartType.LocationId: LocationId = long.Parse(part.Text); base.Text = itemInfoResolver.GetLocationName(LocationId, game) ?? $"Location: {LocationId}"; break; case JsonMessagePartType.LocationName: LocationId = itemInfoResolver.GetLocationId(part.Text, game); base.Text = part.Text; break; } } } } public class MessagePart { public string Text { get; internal set; } public MessagePartType Type { get; internal set; } public Color Color => GetColor(BuiltInPalettes.Dark); public PaletteColor? PaletteColor { get; protected set; } public bool IsBackgroundColor { get; internal set; } internal MessagePart(MessagePartType type, JsonMessagePart messagePart, PaletteColor? color = null) { Type = type; Text = messagePart.Text; if (color.HasValue) { PaletteColor = color.Value; } else if (messagePart.Color.HasValue) { PaletteColor = ColorUtils.GetColor(messagePart.Color.Value); IsBackgroundColor = messagePart.Color.Value >= JsonMessagePartColor.BlackBg; } else { PaletteColor = null; } } public T GetColor(Palette palette) { return palette[PaletteColor]; } public override string ToString() { return Text; } } public enum MessagePartType { Text, Player, Item, Location, Entrance, HintStatus } public class PlayerMessagePart : MessagePart { public bool IsActivePlayer { get; } public int SlotId { get; } internal PlayerMessagePart(IPlayerHelper players, IConnectionInfoProvider connectionInfo, JsonMessagePart part) : base(MessagePartType.Player, part) { switch (part.Type) { case JsonMessagePartType.PlayerId: SlotId = int.Parse(part.Text); IsActivePlayer = SlotId == connectionInfo.Slot; base.Text = players.GetPlayerAlias(SlotId) ?? $"Player {SlotId}"; break; case JsonMessagePartType.PlayerName: SlotId = 0; IsActivePlayer = false; base.Text = part.Text; break; } base.PaletteColor = (IsActivePlayer ? Archipelago.MultiClient.Net.Colors.PaletteColor.Magenta : Archipelago.MultiClient.Net.Colors.PaletteColor.Yellow); } } } namespace Archipelago.MultiClient.Net.MessageLog.Messages { public class AdminCommandResultLogMessage : LogMessage { internal AdminCommandResultLogMessage(MessagePart[] parts) : base(parts) { } } public class ChatLogMessage : PlayerSpecificLogMessage { public string Message { get; } internal ChatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string message) : base(parts, players, team, slot) { Message = message; } } public class CollectLogMessage : PlayerSpecificLogMessage { internal CollectLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class CommandResultLogMessage : LogMessage { internal CommandResultLogMessage(MessagePart[] parts) : base(parts) { } } public class CountdownLogMessage : LogMessage { public int RemainingSeconds { get; } internal CountdownLogMessage(MessagePart[] parts, int remainingSeconds) : base(parts) { RemainingSeconds = remainingSeconds; } } public class GoalLogMessage : PlayerSpecificLogMessage { internal GoalLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class HintItemSendLogMessage : ItemSendLogMessage { public bool IsFound { get; } internal HintItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, bool found, IItemInfoResolver itemInfoResolver) : base(parts, players, receiver, sender, item, itemInfoResolver) { IsFound = found; } } public class ItemCheatLogMessage : ItemSendLogMessage { internal ItemCheatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, NetworkItem item, IItemInfoResolver itemInfoResolver) : base(parts, players, slot, 0, item, team, itemInfoResolver) { } } public class ItemSendLogMessage : LogMessage { private PlayerInfo ActivePlayer { get; } public PlayerInfo Receiver { get; } public PlayerInfo Sender { get; } public bool IsReceiverTheActivePlayer => Receiver == ActivePlayer; public bool IsSenderTheActivePlayer => Sender == ActivePlayer; public bool IsRelatedToActivePlayer { get { if (!ActivePlayer.IsRelatedTo(Receiver)) { return ActivePlayer.IsRelatedTo(Sender); } return true; } } public ItemInfo Item { get; } internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, IItemInfoResolver itemInfoResolver) : this(parts, players, receiver, sender, item, players.ActivePlayer.Team, itemInfoResolver) { } internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, int team, IItemInfoResolver itemInfoResolver) : base(parts) { ActivePlayer = players.ActivePlayer ?? new PlayerInfo(); Receiver = players.GetPlayerInfo(team, receiver) ?? new PlayerInfo(); Sender = players.GetPlayerInfo(team, sender) ?? new PlayerInfo(); PlayerInfo player = players.GetPlayerInfo(team, item.Player) ?? new PlayerInfo(); Item = new ItemInfo(item, Receiver.Game, Sender.Game, itemInfoResolver, player); } } public class JoinLogMessage : PlayerSpecificLogMessage { public string[] Tags { get; } internal JoinLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags) : base(parts, players, team, slot) { Tags = tags; } } public class LeaveLogMessage : PlayerSpecificLogMessage { internal LeaveLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class LogMessage { public MessagePart[] Parts { get; } internal LogMessage(MessagePart[] parts) { Parts = parts; } public override string ToString() { if (Parts.Length == 1) { return Parts[0].Text; } StringBuilder stringBuilder = new StringBuilder(); MessagePart[] parts = Parts; foreach (MessagePart messagePart in parts) { stringBuilder.Append(messagePart.Text); } return stringBuilder.ToString(); } } public abstract class PlayerSpecificLogMessage : LogMessage { private PlayerInfo ActivePlayer { get; } public PlayerInfo Player { get; } public bool IsActivePlayer => Player == ActivePlayer; public bool IsRelatedToActivePlayer => ActivePlayer.IsRelatedTo(Player); internal PlayerSpecificLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts) { ActivePlayer = players.ActivePlayer ?? new PlayerInfo(); Player = players.GetPlayerInfo(team, slot) ?? new PlayerInfo(); } } public class ReleaseLogMessage : PlayerSpecificLogMessage { internal ReleaseLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class ServerChatLogMessage : LogMessage { public string Message { get; } internal ServerChatLogMessage(MessagePart[] parts, string message) : base(parts) { Message = message; } } public class TagsChangedLogMessage : PlayerSpecificLogMessage { public string[] Tags { get; } internal TagsChangedLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags) : base(parts, players, team, slot) { Tags = tags; } } public class TutorialLogMessage : LogMessage { internal TutorialLogMessage(MessagePart[] parts) : base(parts) { } } } namespace Archipelago.MultiClient.Net.Helpers { public class ArchipelagoSocketHelper : BaseArchipelagoSocketHelper, IArchipelagoSocketHelper { public Uri Uri { get; } internal ArchipelagoSocketHelper(Uri hostUri) : base(CreateWebSocket(), 1024) { Uri = hostUri; } private static ClientWebSocket CreateWebSocket() { ClientWebSocket clientWebSocket = new ClientWebSocket(); clientWebSocket.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); return clientWebSocket; } public async Task ConnectAsync() { await ConnectToProvidedUri(Uri); StartPolling(); } private async Task ConnectToProvidedUri(Uri uri) { if (uri.Scheme != "unspecified") { try { await Socket.ConnectAsync(uri, CancellationToken.None); return; } catch (Exception e) { OnError(e); throw; } } List errors = new List(0); try { await Socket.ConnectAsync(uri.AsWss(), CancellationToken.None); if (Socket.State == WebSocketState.Open) { return; } } catch (Exception item) { errors.Add(item); Socket = CreateWebSocket(); } try { await Socket.ConnectAsync(uri.AsWs(), CancellationToken.None); } catch (Exception item2) { errors.Add(item2); OnError(new AggregateException(errors)); throw; } } } public class BaseArchipelagoSocketHelper where T : WebSocket { private static readonly ArchipelagoPacketConverter Converter = new ArchipelagoPacketConverter(); private readonly BlockingCollection>> sendQueue = new BlockingCollection>>(); internal T Socket; private readonly int bufferSize; public bool Connected { get { if (Socket.State != WebSocketState.Open) { return Socket.State == WebSocketState.CloseReceived; } return true; } } public event ArchipelagoSocketHelperDelagates.PacketReceivedHandler PacketReceived; public event ArchipelagoSocketHelperDelagates.PacketsSentHandler PacketsSent; public event ArchipelagoSocketHelperDelagates.ErrorReceivedHandler ErrorReceived; public event ArchipelagoSocketHelperDelagates.SocketClosedHandler SocketClosed; public event ArchipelagoSocketHelperDelagates.SocketOpenedHandler SocketOpened; internal BaseArchipelagoSocketHelper(T socket, int bufferSize = 1024) { Socket = socket; this.bufferSize = bufferSize; } internal void StartPolling() { if (this.SocketOpened != null) { this.SocketOpened(); } Task.Run((Func)PollingLoop); Task.Run((Func)SendLoop); } private async Task PollingLoop() { byte[] buffer = new byte[bufferSize]; while (Socket.State == WebSocketState.Open) { string message = null; try { message = await ReadMessageAsync(buffer); } catch (Exception e) { OnError(e); } OnMessageReceived(message); } } private async Task SendLoop() { while (Socket.State == WebSocketState.Open) { try { await HandleSendBuffer(); } catch (Exception e) { OnError(e); } await Task.Delay(20); } } private async Task ReadMessageAsync(byte[] buffer) { using MemoryStream readStream = new MemoryStream(buffer.Length); WebSocketReceiveResult result; do { result = await Socket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Close) { try { await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); } catch { } OnSocketClosed(); } else { readStream.Write(buffer, 0, result.Count); } } while (!result.EndOfMessage); return Encoding.UTF8.GetString(readStream.ToArray()); } public async Task DisconnectAsync() { await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closure requested by client", CancellationToken.None); OnSocketClosed(); } public void SendPacket(ArchipelagoPacketBase packet) { SendMultiplePackets(new List { packet }); } public void SendMultiplePackets(List packets) { SendMultiplePackets(packets.ToArray()); } public void SendMultiplePackets(params ArchipelagoPacketBase[] packets) { SendMultiplePacketsAsync(packets).Wait(); } public Task SendPacketAsync(ArchipelagoPacketBase packet) { return SendMultiplePacketsAsync(new List { packet }); } public Task SendMultiplePacketsAsync(List packets) { return SendMultiplePacketsAsync(packets.ToArray()); } public Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets) { TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); foreach (ArchipelagoPacketBase item in packets) { sendQueue.Add(new Tuple>(item, taskCompletionSource)); } return taskCompletionSource.Task; } private async Task HandleSendBuffer() { List list = new List(); List> tasks = new List>(); Tuple> tuple = sendQueue.Take(); list.Add(tuple.Item1); tasks.Add(tuple.Item2); Tuple> item; while (sendQueue.TryTake(out item)) { list.Add(item.Item1); tasks.Add(item.Item2); } if (!list.Any()) { return; } if (Socket.State != WebSocketState.Open) { throw new ArchipelagoSocketClosedException(); } ArchipelagoPacketBase[] packets = list.ToArray(); string s = JsonConvert.SerializeObject((object)packets); byte[] messageBuffer = Encoding.UTF8.GetBytes(s); int messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / (double)bufferSize); for (int i = 0; i < messagesCount; i++) { int num = bufferSize * i; int num2 = bufferSize; bool endOfMessage = i + 1 == messagesCount; if (num2 * (i + 1) > messageBuffer.Length) { num2 = messageBuffer.Length - num; } await Socket.SendAsync(new ArraySegment(messageBuffer, num, num2), WebSocketMessageType.Text, endOfMessage, CancellationToken.None); } foreach (TaskCompletionSource item2 in tasks) { item2.TrySetResult(result: true); } OnPacketSend(packets); } private void OnPacketSend(ArchipelagoPacketBase[] packets) { try { if (this.PacketsSent != null) { this.PacketsSent(packets); } } catch (Exception e) { OnError(e); } } private void OnSocketClosed() { try { if (this.SocketClosed != null) { this.SocketClosed(""); } } catch (Exception e) { OnError(e); } } private void OnMessageReceived(string message) { try { if (string.IsNullOrEmpty(message) || this.PacketReceived == null) { return; } List list = null; try { list = JsonConvert.DeserializeObject>(message, (JsonConverter[])(object)new JsonConverter[1] { Converter }); } catch (Exception e) { OnError(e); } if (list == null) { return; } foreach (ArchipelagoPacketBase item in list) { this.PacketReceived(item); } } catch (Exception e2) { OnError(e2); } } protected void OnError(Exception e) { try { if (this.ErrorReceived != null) { this.ErrorReceived(e, e.Message); } } catch (Exception ex) { Console.Out.WriteLine($"Error occured during reporting of errorOuter Errror: {e.Message} {e.StackTrace}Inner Errror: {ex.Message} {ex.StackTrace}"); } } } public interface IConnectionInfoProvider { string Game { get; } int Team { get; } int Slot { get; } string[] Tags { get; } ItemsHandlingFlags ItemsHandlingFlags { get; } string Uuid { get; } void UpdateConnectionOptions(string[] tags); void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags); void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags); } public class ConnectionInfoHelper : IConnectionInfoProvider { private readonly IArchipelagoSocketHelper socket; public string Game { get; private set; } public int Team { get; private set; } public int Slot { get; private set; } public string[] Tags { get; internal set; } public ItemsHandlingFlags ItemsHandlingFlags { get; internal set; } public string Uuid { get; private set; } internal ConnectionInfoHelper(IArchipelagoSocketHelper socket) { this.socket = socket; Reset(); socket.PacketReceived += PacketReceived; } private void PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (packet is ConnectionRefusedPacket) { Reset(); } return; } Team = connectedPacket.Team; Slot = connectedPacket.Slot; if (connectedPacket.SlotInfo != null && connectedPacket.SlotInfo.ContainsKey(Slot)) { Game = connectedPacket.SlotInfo[Slot].Game; } } internal void SetConnectionParameters(string game, string[] tags, ItemsHandlingFlags itemsHandlingFlags, string uuid) { Game = game; Tags = tags ?? new string[0]; ItemsHandlingFlags = itemsHandlingFlags; Uuid = uuid ?? Guid.NewGuid().ToString(); } private void Reset() { Game = null; Team = -1; Slot = -1; Tags = new string[0]; ItemsHandlingFlags = ItemsHandlingFlags.NoItems; Uuid = null; } public void UpdateConnectionOptions(string[] tags) { UpdateConnectionOptions(tags, ItemsHandlingFlags); } public void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags) { UpdateConnectionOptions(Tags, itemsHandlingFlags); } public void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags) { SetConnectionParameters(Game, tags, itemsHandlingFlags, Uuid); socket.SendPacket(new ConnectUpdatePacket { Tags = Tags, ItemsHandling = ItemsHandlingFlags }); } } public interface IDataStorageHelper : IDataStorageWrapper { DataStorageElement this[Scope scope, string key] { get; set; } DataStorageElement this[string key] { get; set; } } public class DataStorageHelper : IDataStorageHelper, IDataStorageWrapper { public delegate void DataStorageUpdatedHandler(JToken originalValue, JToken newValue, Dictionary additionalArguments); private readonly Dictionary onValueChangedEventHandlers = new Dictionary(); private readonly Dictionary operationSpecificCallbacks = new Dictionary(); private readonly Dictionary> asyncRetrievalTasks = new Dictionary>(); private readonly IArchipelagoSocketHelper socket; private readonly IConnectionInfoProvider connectionInfoProvider; public DataStorageElement this[Scope scope, string key] { get { return this[AddScope(scope, key)]; } set { this[AddScope(scope, key)] = value; } } public DataStorageElement this[string key] { get { return new DataStorageElement(GetContextForKey(key)); } set { SetValue(key, value); } } internal DataStorageHelper(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider) { this.socket = socket; this.connectionInfoProvider = connectionInfoProvider; socket.PacketReceived += OnPacketReceived; } private void OnPacketReceived(ArchipelagoPacketBase packet) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Invalid comparison between Unknown and I4 if (!(packet is RetrievedPacket retrievedPacket)) { if (packet is SetReplyPacket setReplyPacket) { if (setReplyPacket.AdditionalArguments != null && setReplyPacket.AdditionalArguments.ContainsKey("Reference") && (int)setReplyPacket.AdditionalArguments["Reference"].Type == 8 && ((string)setReplyPacket.AdditionalArguments["Reference"]).TryParseNGuid(out var g) && operationSpecificCallbacks.TryGetValue(g, out var value)) { value(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments); operationSpecificCallbacks.Remove(g); } if (onValueChangedEventHandlers.TryGetValue(setReplyPacket.Key, out var value2)) { value2(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments); } } return; } foreach (KeyValuePair datum in retrievedPacket.Data) { if (asyncRetrievalTasks.TryGetValue(datum.Key, out var value3)) { value3.TrySetResult(datum.Value); asyncRetrievalTasks.Remove(datum.Key); } } } private Task GetAsync(string key) { if (asyncRetrievalTasks.TryGetValue(key, out var value)) { return value.Task; } TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); asyncRetrievalTasks[key] = taskCompletionSource; socket.SendPacketAsync(new GetPacket { Keys = new string[1] { key } }); return taskCompletionSource.Task; } private void Initialize(string key, JToken value) { socket.SendPacketAsync(new SetPacket { Key = key, DefaultValue = value, Operations = new OperationSpecification[1] { new OperationSpecification { OperationType = OperationType.Default } } }); } private JToken GetValue(string key) { Task async = GetAsync(key); if (!async.Wait(TimeSpan.FromSeconds(2.0))) { throw new TimeoutException($"Timed out retrieving data for key `{key}`. This may be due to an attempt to retrieve a value from the DataStorageHelper in a synchronous fashion from within a PacketReceived handler. When using the DataStorageHelper from within code which runs on the websocket thread then use the asynchronous getters. Ex: `DataStorageHelper[\"{key}\"].GetAsync().ContinueWith(x => {{}});`Be aware that DataStorageHelper calls tend to cause packet responses, so making a call from within a PacketReceived handler may cause an infinite loop."); } return async.Result; } private void SetValue(string key, DataStorageElement e) { if (key.StartsWith("_read_")) { throw new InvalidOperationException("DataStorage write operation on readonly key '" + key + "' is not allowed"); } if (e == null) { e = new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull()); } if (e.Context == null) { e.Context = GetContextForKey(key); } else if (e.Context.Key != key) { e.Operations.Insert(0, new OperationSpecification { OperationType = OperationType.Replace, Value = GetValue(e.Context.Key) }); } Dictionary dictionary = e.AdditionalArguments ?? new Dictionary(0); if (e.Callbacks != null) { Guid key2 = Guid.NewGuid(); operationSpecificCallbacks[key2] = e.Callbacks; dictionary["Reference"] = JToken.op_Implicit(key2.ToString("N")); socket.SendPacketAsync(new SetPacket { Key = key, Operations = e.Operations.ToArray(), WantReply = true, AdditionalArguments = dictionary }); } else { socket.SendPacketAsync(new SetPacket { Key = key, Operations = e.Operations.ToArray(), AdditionalArguments = dictionary }); } } private DataStorageElementContext GetContextForKey(string key) { return new DataStorageElementContext { Key = key, GetData = GetValue, GetAsync = GetAsync, Initialize = Initialize, AddHandler = AddHandler, RemoveHandler = RemoveHandler }; } private void AddHandler(string key, DataStorageUpdatedHandler handler) { if (onValueChangedEventHandlers.ContainsKey(key)) { Dictionary dictionary = onValueChangedEventHandlers; dictionary[key] = (DataStorageUpdatedHandler)Delegate.Combine(dictionary[key], handler); } else { onValueChangedEventHandlers[key] = handler; } socket.SendPacketAsync(new SetNotifyPacket { Keys = new string[1] { key } }); } private void RemoveHandler(string key, DataStorageUpdatedHandler handler) { if (onValueChangedEventHandlers.ContainsKey(key)) { Dictionary dictionary = onValueChangedEventHandlers; dictionary[key] = (DataStorageUpdatedHandler)Delegate.Remove(dictionary[key], handler); if (onValueChangedEventHandlers[key] == null) { onValueChangedEventHandlers.Remove(key); } } } private string AddScope(Scope scope, string key) { return scope switch { Scope.Global => key, Scope.Game => $"{scope}:{connectionInfoProvider.Game}:{key}", Scope.Team => $"{scope}:{connectionInfoProvider.Team}:{key}", Scope.Slot => $"{scope}:{connectionInfoProvider.Slot}:{key}", Scope.ReadOnly => "_read_" + key, _ => throw new ArgumentOutOfRangeException("scope", scope, "Invalid scope for key " + key), }; } private DataStorageElement GetHintsElement(int? slot = null, int? team = null) { return this[Scope.ReadOnly, $"hints_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"]; } private DataStorageElement GetSlotDataElement(int? slot = null) { return this[Scope.ReadOnly, $"slot_data_{slot ?? connectionInfoProvider.Slot}"]; } private DataStorageElement GetItemNameGroupsElement(string game = null) { return this[Scope.ReadOnly, "item_name_groups_" + (game ?? connectionInfoProvider.Game)]; } private DataStorageElement GetLocationNameGroupsElement(string game = null) { return this[Scope.ReadOnly, "location_name_groups_" + (game ?? connectionInfoProvider.Game)]; } private DataStorageElement GetClientStatusElement(int? slot = null, int? team = null) { return this[Scope.ReadOnly, $"client_status_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"]; } private DataStorageElement GetRaceModeElement() { return this[Scope.ReadOnly, "race_mode"]; } public Hint[] GetHints(int? slot = null, int? team = null) { return GetHintsElement(slot, team).To(); } public Task GetHintsAsync(int? slot = null, int? team = null) { return GetHintsElement(slot, team).GetAsync(); } public void TrackHints(Action onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null) { GetHintsElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary x) { onHintsUpdated(newValue.ToObject()); }; if (retrieveCurrentlyUnlockedHints) { GetHintsAsync(slot, team).ContinueWith(delegate(Task t) { onHintsUpdated(t.Result); }); } } public Dictionary GetSlotData(int? slot = null) { return GetSlotData>(slot); } public T GetSlotData(int? slot = null) where T : class { return GetSlotDataElement(slot).To(); } public Task> GetSlotDataAsync(int? slot = null) { return GetSlotDataAsync>(slot); } public Task GetSlotDataAsync(int? slot = null) where T : class { return GetSlotDataElement(slot).GetAsync(); } public Dictionary GetItemNameGroups(string game = null) { return GetItemNameGroupsElement(game).To>(); } public Task> GetItemNameGroupsAsync(string game = null) { return GetItemNameGroupsElement(game).GetAsync>(); } public Dictionary GetLocationNameGroups(string game = null) { return GetLocationNameGroupsElement(game).To>(); } public Task> GetLocationNameGroupsAsync(string game = null) { return GetLocationNameGroupsElement(game).GetAsync>(); } public ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).To().GetValueOrDefault(); } public Task GetClientStatusAsync(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).GetAsync().ContinueWith((Task r) => r.Result.GetValueOrDefault()); } public void TrackClientStatus(Action onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null) { GetClientStatusElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary x) { onStatusUpdated(newValue.ToObject()); }; if (retrieveCurrentClientStatus) { GetClientStatusAsync(slot, team).ContinueWith(delegate(Task t) { onStatusUpdated(t.Result); }); } } public bool GetRaceMode() { return GetRaceModeElement().To().GetValueOrDefault() > 0; } public Task GetRaceModeAsync() { return GetRaceModeElement().GetAsync().ContinueWith((Task t) => t.Result.GetValueOrDefault() > 0); } } public interface IDataStorageWrapper { Hint[] GetHints(int? slot = null, int? team = null); Task GetHintsAsync(int? slot = null, int? team = null); void TrackHints(Action onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null); Dictionary GetSlotData(int? slot = null); T GetSlotData(int? slot = null) where T : class; Task> GetSlotDataAsync(int? slot = null); Task GetSlotDataAsync(int? slot = null) where T : class; Dictionary GetItemNameGroups(string game = null); Task> GetItemNameGroupsAsync(string game = null); Dictionary GetLocationNameGroups(string game = null); Task> GetLocationNameGroupsAsync(string game = null); ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null); Task GetClientStatusAsync(int? slot = null, int? team = null); void TrackClientStatus(Action onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null); bool GetRaceMode(); Task GetRaceModeAsync(); } public interface IHintsHelper { void CreateHints(int player, HintStatus hintStatus = HintStatus.Unspecified, params long[] locationIds); void CreateHints(HintStatus hintStatus = HintStatus.Unspecified, params long[] locationIds); void UpdateHintStatus(int player, long locationId, HintStatus newHintStatus); Hint[] GetHints(int? slot = null, int? team = null); Task GetHintsAsync(int? slot = null, int? team = null); void TrackHints(Action onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null); } public class HintsHelper : IHintsHelper { private readonly IArchipelagoSocketHelper socket; private readonly ILocationCheckHelper locationCheckHelper; private readonly IRoomStateHelper roomStateHelper; private readonly IPlayerHelper players; private readonly IDataStorageHelper dataStorageHelper; internal HintsHelper(IArchipelagoSocketHelper socket, IPlayerHelper players, ILocationCheckHelper locationCheckHelper, IRoomStateHelper roomStateHelper, IDataStorageHelper dataStorageHelper) { this.socket = socket; this.players = players; this.locationCheckHelper = locationCheckHelper; this.roomStateHelper = roomStateHelper; this.dataStorageHelper = dataStorageHelper; } public void CreateHints(int player, HintStatus hintStatus = HintStatus.Unspecified, params long[] locationIds) { if (roomStateHelper.Version < new Version(0, 6, 2)) { locationCheckHelper.ScoutLocationsAsync(createAsHint: true, locationIds); return; } socket.SendPacket(new CreateHintsPacket { Locations = locationIds, Player = player, Status = hintStatus }); } public void CreateHints(HintStatus hintStatus = HintStatus.Unspecified, params long[] ids) { int slot = players.ActivePlayer.Slot; CreateHints(slot, hintStatus, ids); } public void UpdateHintStatus(int player, long locationId, HintStatus newHintStatus) { socket.SendPacket(new UpdateHintPacket { Location = locationId, Player = player, Status = newHintStatus }); } public Hint[] GetHints(int? slot = null, int? team = null) { return dataStorageHelper.GetHints(slot, team); } public Task GetHintsAsync(int? slot = null, int? team = null) { return dataStorageHelper.GetHintsAsync(slot, team); } public void TrackHints(Action onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null) { dataStorageHelper.TrackHints(onHintsUpdated, retrieveCurrentlyUnlockedHints, slot, team); } } public class ArchipelagoSocketHelperDelagates { public delegate void PacketReceivedHandler(ArchipelagoPacketBase packet); public delegate void PacketsSentHandler(ArchipelagoPacketBase[] packets); public delegate void ErrorReceivedHandler(Exception e, string message); public delegate void SocketClosedHandler(string reason); public delegate void SocketOpenedHandler(); } public interface IArchipelagoSocketHelper { Uri Uri { get; } bool Connected { get; } event ArchipelagoSocketHelperDelagates.PacketReceivedHandler PacketReceived; event ArchipelagoSocketHelperDelagates.PacketsSentHandler PacketsSent; event ArchipelagoSocketHelperDelagates.ErrorReceivedHandler ErrorReceived; event ArchipelagoSocketHelperDelagates.SocketClosedHandler SocketClosed; event ArchipelagoSocketHelperDelagates.SocketOpenedHandler SocketOpened; void SendPacket(ArchipelagoPacketBase packet); void SendMultiplePackets(List packets); void SendMultiplePackets(params ArchipelagoPacketBase[] packets); Task ConnectAsync(); Task DisconnectAsync(); Task SendPacketAsync(ArchipelagoPacketBase packet); Task SendMultiplePacketsAsync(List packets); Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets); } public interface ILocationCheckHelper { ReadOnlyCollection AllLocations { get; } ReadOnlyCollection AllLocationsChecked { get; } ReadOnlyCollection AllMissingLocations { get; } event LocationCheckHelper.CheckedLocationsUpdatedHandler CheckedLocationsUpdated; void CompleteLocationChecks(params long[] ids); Task CompleteLocationChecksAsync(params long[] ids); Task> ScoutLocationsAsync(HintCreationPolicy hintCreationPolicy, params long[] ids); Task> ScoutLocationsAsync(bool createAsHint, params long[] ids); Task> ScoutLocationsAsync(params long[] ids); long GetLocationIdFromName(string game, string locationName); string GetLocationNameFromId(long locationId, string game = null); } public class LocationCheckHelper : ILocationCheckHelper { public delegate void CheckedLocationsUpdatedHandler(ReadOnlyCollection newCheckedLocations); private readonly IConcurrentHashSet allLocations = new ConcurrentHashSet(); private readonly IConcurrentHashSet locationsChecked = new ConcurrentHashSet(); private readonly IConcurrentHashSet serverConfirmedChecks = new ConcurrentHashSet(); private ReadOnlyCollection missingLocations = new ReadOnlyCollection(new long[0]); private readonly IArchipelagoSocketHelper socket; private readonly IItemInfoResolver itemInfoResolver; private readonly IConnectionInfoProvider connectionInfoProvider; private readonly IPlayerHelper players; private bool awaitingLocationInfoPacket; private TaskCompletionSource> locationInfoPacketCallbackTask; public ReadOnlyCollection AllLocations => allLocations.AsToReadOnlyCollection(); public ReadOnlyCollection AllLocationsChecked => locationsChecked.AsToReadOnlyCollection(); public ReadOnlyCollection AllMissingLocations => missingLocations; public event CheckedLocationsUpdatedHandler CheckedLocationsUpdated; internal LocationCheckHelper(IArchipelagoSocketHelper socket, IItemInfoResolver itemInfoResolver, IConnectionInfoProvider connectionInfoProvider, IPlayerHelper players) { this.socket = socket; this.itemInfoResolver = itemInfoResolver; this.connectionInfoProvider = connectionInfoProvider; this.players = players; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (!(packet is RoomUpdatePacket roomUpdatePacket)) { if (!(packet is LocationInfoPacket locationInfoPacket)) { if (packet is InvalidPacketPacket invalidPacketPacket && awaitingLocationInfoPacket && invalidPacketPacket.OriginalCmd == ArchipelagoPacketType.LocationScouts) { locationInfoPacketCallbackTask.TrySetException(new ArchipelagoServerRejectedPacketException(invalidPacketPacket.OriginalCmd, invalidPacketPacket.ErrorType, "location scout rejected by the server: " + invalidPacketPacket.ErrorText)); awaitingLocationInfoPacket = false; locationInfoPacketCallbackTask = null; } } else { if (!awaitingLocationInfoPacket) { return; } if (locationInfoPacketCallbackTask != null) { Dictionary result = locationInfoPacket.Locations.ToDictionary((NetworkItem item) => item.Location, delegate(NetworkItem item) { PlayerInfo playerInfo = players.GetPlayerInfo(item.Player) ?? new PlayerInfo(); return new ScoutedItemInfo(item, playerInfo.Game, connectionInfoProvider.Game, itemInfoResolver, players, playerInfo); }); locationInfoPacketCallbackTask.TrySetResult(result); } awaitingLocationInfoPacket = false; locationInfoPacketCallbackTask = null; } } else { CheckLocations(roomUpdatePacket.CheckedLocations); if (roomUpdatePacket.CheckedLocations != null) { serverConfirmedChecks.UnionWith(roomUpdatePacket.CheckedLocations); } } } else { allLocations.UnionWith(connectedPacket.LocationsChecked); allLocations.UnionWith(connectedPacket.MissingChecks); serverConfirmedChecks.UnionWith(connectedPacket.LocationsChecked); missingLocations = new ReadOnlyCollection(connectedPacket.MissingChecks); CheckLocations(connectedPacket.LocationsChecked); } } public void CompleteLocationChecks(params long[] ids) { CheckLocations(ids); LocationChecksPacket locationChecksPacket = GetLocationChecksPacket(); if (locationChecksPacket.Locations.Any()) { socket.SendPacket(locationChecksPacket); } } public Task CompleteLocationChecksAsync(params long[] ids) { return Task.Factory.StartNew((Func)async delegate { CheckLocations(ids); LocationChecksPacket locationChecksPacket = GetLocationChecksPacket(); if (locationChecksPacket.Locations.Any()) { await socket.SendPacketAsync(locationChecksPacket); } }); } private LocationChecksPacket GetLocationChecksPacket() { return new LocationChecksPacket { Locations = locationsChecked.AsToReadOnlyCollectionExcept(serverConfirmedChecks).ToArray() }; } public Task> ScoutLocationsAsync(HintCreationPolicy hintCreationPolicy, params long[] ids) { long[] array = ids.Where((long i) => allLocations.Contains(i)).ToArray(); if (!array.Any()) { return Task.FromResult(new Dictionary()); } locationInfoPacketCallbackTask = new TaskCompletionSource>(); awaitingLocationInfoPacket = true; socket.SendPacketAsync(new LocationScoutsPacket { Locations = array, CreateAsHint = (int)hintCreationPolicy }); return locationInfoPacketCallbackTask.Task; } public Task> ScoutLocationsAsync(bool createAsHint, params long[] ids) { return ScoutLocationsAsync(createAsHint ? HintCreationPolicy.CreateAndAnnounce : HintCreationPolicy.None, ids); } public Task> ScoutLocationsAsync(params long[] ids) { return ScoutLocationsAsync(createAsHint: false, ids); } public long GetLocationIdFromName(string game, string locationName) { return itemInfoResolver.GetLocationId(locationName, game); } public string GetLocationNameFromId(long locationId, string game = null) { return itemInfoResolver.GetLocationName(locationId, game); } private void CheckLocations(ICollection locationIds) { if (locationIds == null || !locationIds.Any()) { return; } List list = new List(); foreach (long locationId in locationIds) { if (allLocations.Contains(locationId) && locationsChecked.TryAdd(locationId)) { list.Add(locationId); } } missingLocations = allLocations.AsToReadOnlyCollectionExcept(locationsChecked); if (list.Any()) { this.CheckedLocationsUpdated?.Invoke(new ReadOnlyCollection(list)); } } } public interface IMessageLogHelper { event MessageLogHelper.MessageReceivedHandler OnMessageReceived; } public class MessageLogHelper : IMessageLogHelper { public delegate void MessageReceivedHandler(LogMessage message); private readonly IItemInfoResolver itemInfoResolver; private readonly IPlayerHelper players; private readonly IConnectionInfoProvider connectionInfo; public event MessageReceivedHandler OnMessageReceived; internal MessageLogHelper(IArchipelagoSocketHelper socket, IItemInfoResolver itemInfoResolver, IPlayerHelper players, IConnectionInfoProvider connectionInfo) { this.itemInfoResolver = itemInfoResolver; this.players = players; this.connectionInfo = connectionInfo; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (this.OnMessageReceived != null && packet is PrintJsonPacket printJsonPacket) { TriggerOnMessageReceived(printJsonPacket); } } private void TriggerOnMessageReceived(PrintJsonPacket printJsonPacket) { foreach (PrintJsonPacket item in SplitPacketsPerLine(printJsonPacket)) { MessagePart[] parsedData = GetParsedData(item); LogMessage message = ((item is ItemPrintJsonPacket itemPrintJsonPacket) ? new ItemSendLogMessage(parsedData, players, itemPrintJsonPacket.ReceivingPlayer, itemPrintJsonPacket.Item.Player, itemPrintJsonPacket.Item, itemInfoResolver) : ((item is ItemCheatPrintJsonPacket itemCheatPrintJsonPacket) ? new ItemCheatLogMessage(parsedData, players, itemCheatPrintJsonPacket.Team, itemCheatPrintJsonPacket.ReceivingPlayer, itemCheatPrintJsonPacket.Item, itemInfoResolver) : ((item is HintPrintJsonPacket hintPrintJsonPacket) ? new HintItemSendLogMessage(parsedData, players, hintPrintJsonPacket.ReceivingPlayer, hintPrintJsonPacket.Item.Player, hintPrintJsonPacket.Item, hintPrintJsonPacket.Found.HasValue && hintPrintJsonPacket.Found.Value, itemInfoResolver) : ((item is JoinPrintJsonPacket joinPrintJsonPacket) ? new JoinLogMessage(parsedData, players, joinPrintJsonPacket.Team, joinPrintJsonPacket.Slot, joinPrintJsonPacket.Tags) : ((item is LeavePrintJsonPacket leavePrintJsonPacket) ? new LeaveLogMessage(parsedData, players, leavePrintJsonPacket.Team, leavePrintJsonPacket.Slot) : ((item is ChatPrintJsonPacket chatPrintJsonPacket) ? new ChatLogMessage(parsedData, players, chatPrintJsonPacket.Team, chatPrintJsonPacket.Slot, chatPrintJsonPacket.Message) : ((item is ServerChatPrintJsonPacket serverChatPrintJsonPacket) ? new ServerChatLogMessage(parsedData, serverChatPrintJsonPacket.Message) : ((item is TutorialPrintJsonPacket) ? new TutorialLogMessage(parsedData) : ((item is TagsChangedPrintJsonPacket tagsChangedPrintJsonPacket) ? new TagsChangedLogMessage(parsedData, players, tagsChangedPrintJsonPacket.Team, tagsChangedPrintJsonPacket.Slot, tagsChangedPrintJsonPacket.Tags) : ((item is CommandResultPrintJsonPacket) ? new CommandResultLogMessage(parsedData) : ((item is AdminCommandResultPrintJsonPacket) ? new AdminCommandResultLogMessage(parsedData) : ((item is GoalPrintJsonPacket goalPrintJsonPacket) ? new GoalLogMessage(parsedData, players, goalPrintJsonPacket.Team, goalPrintJsonPacket.Slot) : ((item is ReleasePrintJsonPacket releasePrintJsonPacket) ? new ReleaseLogMessage(parsedData, players, releasePrintJsonPacket.Team, releasePrintJsonPacket.Slot) : ((item is CollectPrintJsonPacket collectPrintJsonPacket) ? new CollectLogMessage(parsedData, players, collectPrintJsonPacket.Team, collectPrintJsonPacket.Slot) : ((!(item is CountdownPrintJsonPacket countdownPrintJsonPacket)) ? new LogMessage(parsedData) : new CountdownLogMessage(parsedData, countdownPrintJsonPacket.RemainingSeconds)))))))))))))))); this.OnMessageReceived?.Invoke(message); } } private static IEnumerable SplitPacketsPerLine(PrintJsonPacket printJsonPacket) { List list = new List(); List list2 = new List(); JsonMessagePart[] data = printJsonPacket.Data; foreach (JsonMessagePart jsonMessagePart in data) { string[] array = jsonMessagePart.Text.Split('\n'); for (int j = 0; j < array.Length; j++) { string text = array[j]; list2.Add(new JsonMessagePart { Text = text, Type = jsonMessagePart.Type, Color = jsonMessagePart.Color, Flags = jsonMessagePart.Flags, Player = jsonMessagePart.Player, HintStatus = jsonMessagePart.HintStatus }); if (j < array.Length - 1) { PrintJsonPacket printJsonPacket2 = CloneWithoutData(printJsonPacket); printJsonPacket2.Data = list2.ToArray(); list.Add(printJsonPacket2); list2 = new List(); } } } PrintJsonPacket printJsonPacket3 = CloneWithoutData(printJsonPacket); printJsonPacket3.Data = list2.ToArray(); list.Add(printJsonPacket3); return list; } private static PrintJsonPacket CloneWithoutData(PrintJsonPacket source) { if (!(source is ItemPrintJsonPacket itemPrintJsonPacket)) { if (!(source is ItemCheatPrintJsonPacket itemCheatPrintJsonPacket)) { if (!(source is HintPrintJsonPacket hintPrintJsonPacket)) { if (!(source is JoinPrintJsonPacket joinPrintJsonPacket)) { if (!(source is LeavePrintJsonPacket leavePrintJsonPacket)) { if (!(source is ChatPrintJsonPacket chatPrintJsonPacket)) { if (!(source is ServerChatPrintJsonPacket serverChatPrintJsonPacket)) { if (!(source is TutorialPrintJsonPacket tutorialPrintJsonPacket)) { if (!(source is TagsChangedPrintJsonPacket tagsChangedPrintJsonPacket)) { if (!(source is CommandResultPrintJsonPacket commandResultPrintJsonPacket)) { if (!(source is AdminCommandResultPrintJsonPacket adminCommandResultPrintJsonPacket)) { if (!(source is GoalPrintJsonPacket goalPrintJsonPacket)) { if (!(source is ReleasePrintJsonPacket releasePrintJsonPacket)) { if (!(source is CollectPrintJsonPacket collectPrintJsonPacket)) { if (source is CountdownPrintJsonPacket countdownPrintJsonPacket) { return new CountdownPrintJsonPacket { RemainingSeconds = countdownPrintJsonPacket.RemainingSeconds }; } return new PrintJsonPacket { MessageType = source.MessageType }; } return new CollectPrintJsonPacket { MessageType = collectPrintJsonPacket.MessageType, Team = collectPrintJsonPacket.Team, Slot = collectPrintJsonPacket.Slot }; } return new ReleasePrintJsonPacket { MessageType = releasePrintJsonPacket.MessageType, Team = releasePrintJsonPacket.Team, Slot = releasePrintJsonPacket.Slot }; } return new GoalPrintJsonPacket { MessageType = goalPrintJsonPacket.MessageType, Team = goalPrintJsonPacket.Team, Slot = goalPrintJsonPacket.Slot }; } return new AdminCommandResultPrintJsonPacket { MessageType = adminCommandResultPrintJsonPacket.MessageType }; } return new CommandResultPrintJsonPacket { MessageType = commandResultPrintJsonPacket.MessageType }; } return new TagsChangedPrintJsonPacket { MessageType = tagsChangedPrintJsonPacket.MessageType, Team = tagsChangedPrintJsonPacket.Team, Slot = tagsChangedPrintJsonPacket.Slot, Tags = tagsChangedPrintJsonPacket.Tags }; } return new TutorialPrintJsonPacket { MessageType = tutorialPrintJsonPacket.MessageType }; } return new ServerChatPrintJsonPacket { MessageType = serverChatPrintJsonPacket.MessageType, Message = serverChatPrintJsonPacket.Message }; } return new ChatPrintJsonPacket { MessageType = chatPrintJsonPacket.MessageType, Team = chatPrintJsonPacket.Team, Slot = chatPrintJsonPacket.Slot, Message = chatPrintJsonPacket.Message }; } return new LeavePrintJsonPacket { MessageType = leavePrintJsonPacket.MessageType, Team = leavePrintJsonPacket.Team, Slot = leavePrintJsonPacket.Slot }; } return new JoinPrintJsonPacket { MessageType = joinPrintJsonPacket.MessageType, Team = joinPrintJsonPacket.Team, Slot = joinPrintJsonPacket.Slot, Tags = joinPrintJsonPacket.Tags }; } return new HintPrintJsonPacket { MessageType = hintPrintJsonPacket.MessageType, ReceivingPlayer = hintPrintJsonPacket.ReceivingPlayer, Item = hintPrintJsonPacket.Item, Found = hintPrintJsonPacket.Found }; } return new ItemCheatPrintJsonPacket { MessageType = itemCheatPrintJsonPacket.MessageType, ReceivingPlayer = itemCheatPrintJsonPacket.ReceivingPlayer, Item = itemCheatPrintJsonPacket.Item, Team = itemCheatPrintJsonPacket.Team }; } return new ItemPrintJsonPacket { MessageType = itemPrintJsonPacket.MessageType, ReceivingPlayer = itemPrintJsonPacket.ReceivingPlayer, Item = itemPrintJsonPacket.Item }; } internal MessagePart[] GetParsedData(PrintJsonPacket packet) { return packet.Data.Select(GetMessagePart).ToArray(); } private MessagePart GetMessagePart(JsonMessagePart part) { switch (part.Type) { case JsonMessagePartType.ItemId: case JsonMessagePartType.ItemName: return new ItemMessagePart(players, itemInfoResolver, part); case JsonMessagePartType.PlayerId: case JsonMessagePartType.PlayerName: return new PlayerMessagePart(players, connectionInfo, part); case JsonMessagePartType.LocationId: case JsonMessagePartType.LocationName: return new LocationMessagePart(players, itemInfoResolver, part); case JsonMessagePartType.EntranceName: return new EntranceMessagePart(part); case JsonMessagePartType.HintStatus: return new HintStatusMessagePart(part); default: return new MessagePart(MessagePartType.Text, part); } } } public interface IPlayerHelper { ReadOnlyDictionary> Players { get; } IEnumerable AllPlayers { get; } PlayerInfo ActivePlayer { get; } string GetPlayerAlias(int slot); string GetPlayerName(int slot); string GetPlayerAliasAndName(int slot); PlayerInfo GetPlayerInfo(int team, int slot); PlayerInfo GetPlayerInfo(int slot); } public class PlayerHelper : IPlayerHelper { private readonly IConnectionInfoProvider connectionInfo; private ReadOnlyDictionary> players = new ReadOnlyDictionary>(new Dictionary>(0)); public ReadOnlyDictionary> Players => players; public IEnumerable AllPlayers => players.SelectMany((KeyValuePair> kvp) => kvp.Value); public PlayerInfo ActivePlayer => GetPlayerInfo(connectionInfo.Team, connectionInfo.Slot); public PlayerInfo GetPlayerInfo(int team, int slot) { if (team < 0 || slot < 0 || players.Count <= team || players[team].Count <= slot) { return null; } return players[team][slot]; } public PlayerInfo GetPlayerInfo(int slot) { return GetPlayerInfo(connectionInfo.Team, slot); } internal PlayerHelper(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfo) { this.connectionInfo = connectionInfo; socket.PacketReceived += PacketReceived; } public string GetPlayerAlias(int slot) { if (players == null) { return null; } return players[connectionInfo.Team].FirstOrDefault((PlayerInfo p) => p.Slot == slot)?.Alias; } public string GetPlayerName(int slot) { if (players == null) { return null; } return players[connectionInfo.Team].FirstOrDefault((PlayerInfo p) => p.Slot == slot)?.Name; } public string GetPlayerAliasAndName(int slot) { if (players == null) { return null; } PlayerInfo playerInfo = players[connectionInfo.Team].FirstOrDefault((PlayerInfo p) => p.Slot == slot); if (playerInfo == null) { return null; } return playerInfo.Alias + " (" + playerInfo.Name + ")"; } private void PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (packet is RoomUpdatePacket roomUpdatePacket) { UpdatePlayerInfo(roomUpdatePacket.Players); } } else { CreatePlayerInfo(connectedPacket.Players, connectedPacket.SlotInfo); } } private void CreatePlayerInfo(NetworkPlayer[] networkPlayers, Dictionary slotInfos) { NetworkSlot[] source = ((slotInfos == null) ? new NetworkSlot[0] : slotInfos.Values.Where((NetworkSlot s) => s.Type == SlotType.Group).ToArray()); int num = 0; int num2 = 0; NetworkPlayer[] array = networkPlayers; for (int i = 0; i < array.Length; i++) { NetworkPlayer networkPlayer = array[i]; if (networkPlayer.Team > num) { num = networkPlayer.Team; } if (networkPlayer.Slot > num2) { num2 = networkPlayer.Slot; } } Dictionary dictionary = new Dictionary(num); for (int j = 0; j <= num; j++) { dictionary[j] = new PlayerInfo[num2 + 1]; dictionary[j][0] = new PlayerInfo { Team = j, Slot = 0, Name = "Server", Alias = "Server", Game = "Archipelago", Groups = new NetworkSlot[0] }; } array = networkPlayers; for (int i = 0; i < array.Length; i++) { NetworkPlayer p = array[i]; dictionary[p.Team][p.Slot] = new PlayerInfo { Team = p.Team, Slot = p.Slot, Name = p.Name, Alias = p.Alias, Game = slotInfos?[p.Slot].Game, Groups = source.Where((NetworkSlot g) => g.GroupMembers.Contains(p.Slot)).ToArray(), GroupMembers = slotInfos?[p.Slot].GroupMembers }; } Dictionary> dictionary2 = new Dictionary>(dictionary.Count); foreach (KeyValuePair item in dictionary) { dictionary2[item.Key] = new ReadOnlyCollection(item.Value); } players = new ReadOnlyDictionary>(dictionary2); } private void UpdatePlayerInfo(NetworkPlayer[] networkPlayers) { if (networkPlayers != null && networkPlayers.Length != 0) { for (int i = 0; i < networkPlayers.Length; i++) { NetworkPlayer networkPlayer = networkPlayers[i]; players[networkPlayer.Team][networkPlayer.Slot].Name = networkPlayer.Name; players[networkPlayer.Team][networkPlayer.Slot].Alias = networkPlayer.Alias; } } } } public class PlayerInfo : IEquatable { public int Team { get; internal set; } public int Slot { get; internal set; } public string Alias { get; internal set; } public string Name { get; internal set; } public string Game { get; internal set; } public NetworkSlot[] Groups { get; internal set; } internal int[] GroupMembers { get; set; } public bool IsGroup { get { if (GroupMembers != null) { return GroupMembers.Length != 0; } return false; } } [Obsolete] public bool IsSharingGroupWith(int team, int slot) { if (Team != team || Slot != slot) { if (Groups != null) { return Groups.Any((NetworkSlot g) => g.GroupMembers.Contains(slot)); } return false; } return true; } public IEnumerable GetGroupMembers(IPlayerHelper playerHelper) { if (!IsGroup) { return null; } return GroupMembers.Select((int g) => playerHelper.GetPlayerInfo(Team, g)); } public bool IsRelatedTo(PlayerInfo other) { if (!(this == other)) { if (Team == other.Team) { if (other.GroupMembers == null || !other.GroupMembers.Contains(this)) { if (GroupMembers != null) { return GroupMembers.Contains(other); } return false; } return true; } return false; } return true; } public static implicit operator int(PlayerInfo p) { return p.Slot; } public static bool operator ==(PlayerInfo lhs, PlayerInfo rhs) { return lhs?.Equals(rhs) ?? ((object)rhs == null); } public static bool operator !=(PlayerInfo lhs, PlayerInfo rhs) { return !(lhs == rhs); } public override string ToString() { return Alias ?? Name ?? $"Player: {Slot}"; } public PlayerInfo() { } [JsonConstructor] public PlayerInfo(int team, int slot, string name, string alias, string game, NetworkSlot[] groups, int[] groupMembers) { Team = team; Slot = slot; Name = name; Alias = alias; Game = game; Groups = groups; GroupMembers = groupMembers; } public bool Equals(PlayerInfo other) { if ((object)other == null) { return false; } if ((object)this == other) { return true; } if (Team == other.Team) { return Slot == other.Slot; } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((PlayerInfo)obj); } public override int GetHashCode() { return (Team * 397) ^ Slot; } } public interface IReceivedItemsHelper { int Index { get; } ReadOnlyCollection AllItemsReceived { get; } event ReceivedItemsHelper.ItemReceivedHandler ItemReceived; string GetItemName(long id, string game = null); bool Any(); ItemInfo PeekItem(); ItemInfo DequeueItem(); } public class ReceivedItemsHelper : IReceivedItemsHelper { public delegate void ItemReceivedHandler(ReceivedItemsHelper helper); private readonly IArchipelagoSocketHelper socket; private readonly ILocationCheckHelper locationsHelper; private readonly IItemInfoResolver itemInfoResolver; private readonly IConnectionInfoProvider connectionInfoProvider; private readonly IPlayerHelper playerHelper; private ConcurrentQueue itemQueue; private readonly IConcurrentList allItemsReceived; private ReadOnlyCollection cachedReceivedItems; public int Index => cachedReceivedItems.Count; public ReadOnlyCollection AllItemsReceived => cachedReceivedItems; public event ItemReceivedHandler ItemReceived; internal ReceivedItemsHelper(IArchipelagoSocketHelper socket, ILocationCheckHelper locationsHelper, IItemInfoResolver itemInfoResolver, IConnectionInfoProvider connectionInfoProvider, IPlayerHelper playerHelper) { this.socket = socket; this.locationsHelper = locationsHelper; this.itemInfoResolver = itemInfoResolver; this.connectionInfoProvider = connectionInfoProvider; this.playerHelper = playerHelper; itemQueue = new ConcurrentQueue(); allItemsReceived = new ConcurrentList(); cachedReceivedItems = allItemsReceived.AsReadOnlyCollection(); socket.PacketReceived += Socket_PacketReceived; } public bool Any() { return !itemQueue.IsEmpty; } public ItemInfo PeekItem() { itemQueue.TryPeek(out var result); return result; } public ItemInfo DequeueItem() { itemQueue.TryDequeue(out var result); return result; } public string GetItemName(long id, string game = null) { return itemInfoResolver.GetItemName(id, game); } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (packet.PacketType != ArchipelagoPacketType.ReceivedItems) { return; } ReceivedItemsPacket receivedItemsPacket = (ReceivedItemsPacket)packet; if (receivedItemsPacket.Index == 0) { PerformResynchronization(receivedItemsPacket); return; } if (allItemsReceived.Count != receivedItemsPacket.Index) { socket.SendPacket(new SyncPacket()); locationsHelper.CompleteLocationChecks(); return; } NetworkItem[] items = receivedItemsPacket.Items; for (int i = 0; i < items.Length; i++) { NetworkItem item = items[i]; PlayerInfo playerInfo = playerHelper.GetPlayerInfo(item.Player) ?? new PlayerInfo(); ItemInfo item2 = new ItemInfo(item, connectionInfoProvider.Game, playerInfo.Game, itemInfoResolver, playerInfo); allItemsReceived.Add(item2); itemQueue.Enqueue(item2); cachedReceivedItems = allItemsReceived.AsReadOnlyCollection(); this.ItemReceived?.Invoke(this); } } private void PerformResynchronization(ReceivedItemsPacket receivedItemsPacket) { ReadOnlyCollection readOnlyCollection = allItemsReceived.AsReadOnlyCollection(); itemQueue = new ConcurrentQueue(); allItemsReceived.Clear(); NetworkItem[] items = receivedItemsPacket.Items; for (int i = 0; i < items.Length; i++) { NetworkItem item = items[i]; PlayerInfo playerInfo = playerHelper.GetPlayerInfo(item.Player) ?? new PlayerInfo(); ItemInfo itemInfo = new ItemInfo(item, connectionInfoProvider.Game, playerInfo.Game, itemInfoResolver, playerInfo); itemQueue.Enqueue(itemInfo); allItemsReceived.Add(itemInfo); cachedReceivedItems = allItemsReceived.AsReadOnlyCollection(); if (this.ItemReceived != null && !readOnlyCollection.Contains(itemInfo)) { this.ItemReceived(this); } } } } public interface IRoomStateHelper { int HintCost { get; } int HintCostPercentage { get; } int LocationCheckPoints { get; } int HintPoints { get; } Version Version { get; } Version GeneratorVersion { get; } bool HasPassword { get; } Permissions ReleasePermissions { get; } Permissions ForfeitPermissions { get; } Permissions CollectPermissions { get; } Permissions RemainingPermissions { get; } string Seed { get; } DateTime RoomInfoSendTime { get; } ReadOnlyCollection ServerTags { get; } } public class RoomStateHelper : IRoomStateHelper { private readonly ILocationCheckHelper locationCheckHelper; private string[] tags; public int HintCost { get; private set; } public int HintCostPercentage { get; private set; } public int LocationCheckPoints { get; private set; } public int HintPoints { get; private set; } public Version Version { get; private set; } public Version GeneratorVersion { get; private set; } public bool HasPassword { get; private set; } public Permissions ReleasePermissions { get; private set; } public Permissions ForfeitPermissions => ReleasePermissions; public Permissions CollectPermissions { get; private set; } public Permissions RemainingPermissions { get; private set; } public string Seed { get; private set; } public DateTime RoomInfoSendTime { get; private set; } public ReadOnlyCollection ServerTags { get { if (tags != null) { return new ReadOnlyCollection(tags); } return null; } } internal RoomStateHelper(IArchipelagoSocketHelper socket, ILocationCheckHelper locationCheckHelper) { this.locationCheckHelper = locationCheckHelper; socket.PacketReceived += PacketReceived; } private void PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket packet2)) { if (!(packet is RoomUpdatePacket packet3)) { if (packet is RoomInfoPacket packet4) { OnRoomInfoPacketReceived(packet4); } } else { OnRoomUpdatedPacketReceived(packet3); } } else { OnConnectedPacketReceived(packet2); } } private void OnConnectedPacketReceived(ConnectedPacket packet) { if (packet.HintPoints.HasValue) { HintPoints = packet.HintPoints.Value; } int num = packet.LocationsChecked.Length + packet.MissingChecks.Length; HintCost = (int)Math.Max(0m, (decimal)num * 0.01m * (decimal)HintCostPercentage); } private void OnRoomInfoPacketReceived(RoomInfoPacket packet) { HintCostPercentage = packet.HintCostPercentage; LocationCheckPoints = packet.LocationCheckPoints; Version = packet.Version?.ToVersion(); GeneratorVersion = packet.GeneratorVersion?.ToVersion(); HasPassword = packet.Password; Seed = packet.SeedName; RoomInfoSendTime = UnixTimeConverter.UnixTimeStampToDateTime(packet.Timestamp); tags = packet.Tags; if (packet.Permissions != null) { Permissions value2; if (packet.Permissions.TryGetValue("release", out var value)) { ReleasePermissions = value; } else if (packet.Permissions.TryGetValue("forfeit", out value2)) { ReleasePermissions = value2; } if (packet.Permissions.TryGetValue("collect", out var value3)) { CollectPermissions = value3; } if (packet.Permissions.TryGetValue("remaining", out var value4)) { RemainingPermissions = value4; } } } private void OnRoomUpdatedPacketReceived(RoomUpdatePacket packet) { if (packet.HintCostPercentage.HasValue) { HintCostPercentage = packet.HintCostPercentage.Value; HintCost = (int)Math.Max(0m, (decimal)locationCheckHelper.AllLocations.Count * 0.01m * (decimal)packet.HintCostPercentage.Value); } if (packet.LocationCheckPoints.HasValue) { LocationCheckPoints = packet.LocationCheckPoints.Value; } if (packet.HintPoints.HasValue) { HintPoints = packet.HintPoints.Value; } if (packet.Tags != null) { tags = packet.Tags; } if (packet.Password.HasValue) { HasPassword = packet.Password.Value; } if (packet.Permissions != null) { Permissions value2; if (packet.Permissions.TryGetValue("release", out var value)) { ReleasePermissions = value; } else if (packet.Permissions.TryGetValue("forfeit", out value2)) { ReleasePermissions = value2; } if (packet.Permissions.TryGetValue("collect", out var value3)) { CollectPermissions = value3; } if (packet.Permissions.TryGetValue("remaining", out var value4)) { RemainingPermissions = value4; } } } } } namespace Archipelago.MultiClient.Net.Extensions { internal static class GuidExtensions { internal static bool TryParseNGuid(this string s, out Guid g) { return Guid.TryParseExact(s, "N", out g); } } internal static class UriExtensions { private static readonly string UriSchemeWss = Uri.UriSchemeWss; private static readonly string UriSchemeWs = Uri.UriSchemeWs; public static Uri WithSchema(this Uri uri, string schema) { return new UriBuilder(uri) { Scheme = schema }.Uri; } public static Uri AsWss(this Uri uri) { return uri.WithSchema(UriSchemeWss); } public static Uri AsWs(this Uri uri) { return uri.WithSchema(UriSchemeWs); } } } namespace Archipelago.MultiClient.Net.Exceptions { public class ArchipelagoServerRejectedPacketException : Exception { public ArchipelagoPacketType FaultyPacketType { get; } public InvalidPacketErrorType ErrorType { get; } public ArchipelagoServerRejectedPacketException(ArchipelagoPacketType faultyPacketType, InvalidPacketErrorType errorType, string message) : base(message) { FaultyPacketType = faultyPacketType; ErrorType = errorType; } } public class ArchipelagoSocketClosedException : Exception { public ArchipelagoSocketClosedException() { } public ArchipelagoSocketClosedException(string message) : base(message) { } public ArchipelagoSocketClosedException(string message, Exception innerException) : base(message, innerException) { } protected ArchipelagoSocketClosedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } namespace Archipelago.MultiClient.Net.Enums { public enum ArchipelagoClientState { ClientUnknown = 0, ClientConnected = 5, ClientReady = 10, ClientPlaying = 20, ClientGoal = 30 } public enum ArchipelagoPacketType { RoomInfo, ConnectionRefused, Connected, ReceivedItems, LocationInfo, RoomUpdate, PrintJSON, Connect, ConnectUpdate, LocationChecks, LocationScouts, CreateHints, StatusUpdate, Say, GetDataPackage, DataPackage, Sync, Bounced, Bounce, InvalidPacket, Get, Retrieved, Set, SetReply, SetNotify, UpdateHint, Unknown } public enum ConnectionRefusedError { UnknownError, InvalidSlot, InvalidGame, SlotAlreadyTaken, IncompatibleVersion, InvalidPassword, InvalidItemsHandling } public enum Scope { Global, Game, Team, Slot, ReadOnly } public enum HintCreationPolicy { None, CreateAndAnnounce, CreateAndAnnounceOnce } public enum HintStatus { Found = 40, Unspecified = 0, NoPriority = 10, Avoid = 20, Priority = 30 } public enum InvalidPacketErrorType { Cmd, Arguments } [Flags] public enum ItemFlags { None = 0, Advancement = 1, NeverExclude = 2, Trap = 4 } [Flags] public enum ItemsHandlingFlags { NoItems = 0, RemoteItems = 1, IncludeOwnItems = 3, IncludeStartingInventory = 5, AllItems = 7 } public enum JsonMessagePartColor { Bold, Underline, Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, BlackBg, RedBg, GreenBg, YellowBg, BlueBg, MagentaBg, CyanBg, WhiteBg } public enum JsonMessagePartType { Text, PlayerId, PlayerName, ItemId, ItemName, LocationId, LocationName, EntranceName, Color, HintStatus } public enum JsonMessageType { ItemSend, ItemCheat, Hint, Join, Part, Chat, ServerChat, Tutorial, TagsChanged, CommandResult, AdminCommandResult, Goal, Release, Collect, Countdown } public enum OperationType { Unknown, Add, Mul, Max, Min, Replace, Default, Mod, Pow, Xor, Or, And, LeftShift, RightShift, Remove, Pop, Update, Floor, Ceil } [JsonConverter(typeof(PermissionsEnumConverter))] [Flags] public enum Permissions { Disabled = 0, Enabled = 1, Goal = 2, Auto = 6, AutoEnabled = 7 } [Flags] public enum SlotType { Spectator = 0, Player = 1, Group = 2 } } namespace Archipelago.MultiClient.Net.DataPackage { internal class DataPackageCache : IDataPackageCache { private readonly IArchipelagoSocketHelper socket; private readonly Dictionary inMemoryCache = new Dictionary(); internal IFileSystemDataPackageProvider FileSystemDataPackageProvider; public DataPackageCache(IArchipelagoSocketHelper socket) { this.socket = socket; socket.PacketReceived += Socket_PacketReceived; } public DataPackageCache(IArchipelagoSocketHelper socket, IFileSystemDataPackageProvider fileSystemProvider) { this.socket = socket; FileSystemDataPackageProvider = fileSystemProvider; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is RoomInfoPacket packet2)) { if (packet is DataPackagePacket dataPackagePacket) { UpdateDataPackageFromServer(dataPackagePacket.DataPackage); } return; } if (FileSystemDataPackageProvider == null) { FileSystemDataPackageProvider = new FileSystemCheckSumDataPackageProvider(); } List cacheInvalidatedGamesByChecksum = GetCacheInvalidatedGamesByChecksum(packet2); if (cacheInvalidatedGamesByChecksum.Any()) { IEnumerable source = cacheInvalidatedGamesByChecksum.Select((string game) => new GetDataPackagePacket { Games = new string[1] { game } }); socket.SendMultiplePackets(source.Cast().ToArray()); } } public bool TryGetDataPackageFromCache(out Dictionary gameData) { gameData = inMemoryCache; return inMemoryCache.Any(); } public bool TryGetGameDataFromCache(string game, out IGameDataLookup gameData) { if (inMemoryCache.TryGetValue(game, out var value)) { gameData = value; return true; } gameData = null; return false; } internal void UpdateDataPackageFromServer(Archipelago.MultiClient.Net.Models.DataPackage package) { foreach (KeyValuePair game in package.Games) { inMemoryCache[game.Key] = new GameDataLookup(game.Value); FileSystemDataPackageProvider.SaveDataPackageToFile(game.Key, game.Value); } } private List GetCacheInvalidatedGamesByChecksum(RoomInfoPacket packet) { List list = new List(); string[] games = packet.Games; foreach (string text in games) { if (packet.DataPackageChecksums != null && packet.DataPackageChecksums.TryGetValue(text, out var value) && FileSystemDataPackageProvider.TryGetDataPackage(text, value, out var gameData) && gameData.Checksum == value) { inMemoryCache[text] = new GameDataLookup(gameData); } else { list.Add(text); } } return list; } } internal class FileSystemCheckSumDataPackageProvider : IFileSystemDataPackageProvider { private static readonly string CacheFolder; static FileSystemCheckSumDataPackageProvider() { CacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Path.Combine("Archipelago", Path.Combine("Cache", "datapackage"))); } public bool TryGetDataPackage(string game, string checksum, out GameData gameData) { string path = Path.Combine(Path.Combine(CacheFolder, GetFileSystemSafeFileName(game)), checksum + ".json"); if (!File.Exists(path)) { gameData = null; return false; } try { string text = File.ReadAllText(path); gameData = JsonConvert.DeserializeObject(text); return true; } catch { gameData = null; return false; } } public void SaveDataPackageToFile(string game, GameData gameData) { string text = Path.Combine(CacheFolder, GetFileSystemSafeFileName(game)); string path = Path.Combine(text, GetFileSystemSafeFileName(gameData.Checksum) + ".json"); try { Directory.CreateDirectory(text); string contents = JsonConvert.SerializeObject((object)gameData); File.WriteAllText(path, contents); } catch { } } private string GetFileSystemSafeFileName(string gameName) { string result = gameName; char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char c in invalidFileNameChars) { gameName = gameName.Replace(c.ToString(), string.Empty); } return result; } } internal interface IGameDataLookup { TwoWayLookup Locations { get; } TwoWayLookup Items { get; } string Checksum { get; } } internal class GameDataLookup : IGameDataLookup { public TwoWayLookup Locations { get; } public TwoWayLookup Items { get; } public string Checksum { get; } public GameDataLookup(GameData gameData) { Locations = new TwoWayLookup(); Items = new TwoWayLookup(); Checksum = gameData.Checksum; foreach (KeyValuePair item in gameData.LocationLookup) { Locations.Add(item.Value, item.Key); } foreach (KeyValuePair item2 in gameData.ItemLookup) { Items.Add(item2.Value, item2.Key); } } } internal interface IDataPackageCache { bool TryGetGameDataFromCache(string game, out IGameDataLookup gameData); bool TryGetDataPackageFromCache(out Dictionary gameData); } internal interface IFileSystemDataPackageProvider { bool TryGetDataPackage(string game, string checksum, out GameData gameData); void SaveDataPackageToFile(string game, GameData gameData); } public interface IItemInfoResolver { string GetItemName(long itemId, string game = null); string GetLocationName(long locationId, string game = null); long GetLocationId(string locationName, string game = null); } internal class ItemInfoResolver : IItemInfoResolver { private readonly IDataPackageCache cache; private readonly IConnectionInfoProvider connectionInfoProvider; public ItemInfoResolver(IDataPackageCache cache, IConnectionInfoProvider connectionInfoProvider) { this.cache = cache; this.connectionInfoProvider = connectionInfoProvider; } public string GetItemName(long itemId, string game = null) { if (game == null) { game = connectionInfoProvider.Game ?? "Archipelago"; } if (itemId < 0) { game = "Archipelago"; } if (!cache.TryGetGameDataFromCache(game, out var gameData)) { return null; } if (!gameData.Items.TryGetValue(itemId, out var b)) { return null; } return b; } public string GetLocationName(long locationId, string game = null) { if (game == null) { game = connectionInfoProvider.Game ?? "Archipelago"; } if (locationId < 0) { game = "Archipelago"; } if (!cache.TryGetGameDataFromCache(game, out var gameData)) { return null; } if (!gameData.Locations.TryGetValue(locationId, out var b)) { return null; } return b; } public long GetLocationId(string locationName, string game = null) { if (game == null) { game = connectionInfoProvider.Game ?? "Archipelago"; } if (cache.TryGetGameDataFromCache(game, out var gameData) && gameData.Locations.TryGetValue(locationName, out var a)) { return a; } if (cache.TryGetGameDataFromCache("Archipelago", out var gameData2) && gameData2.Locations.TryGetValue(locationName, out var a2)) { return a2; } return -1L; } } } namespace Archipelago.MultiClient.Net.Converters { public class ArchipelagoPacketConverter : JsonConverter { private static readonly Dictionary> PacketDeserializationMap; public override bool CanWrite => false; public override bool CanConvert(Type objectType) { return objectType.IsAssignableFrom(typeof(ArchipelagoPacketBase)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject val = JObject.Load(reader); ArchipelagoPacketType result; ArchipelagoPacketBase archipelagoPacketBase = ((!EnumTryParse(((object)val["cmd"])?.ToString(), out result) || !PacketDeserializationMap.ContainsKey(result)) ? new UnknownPacket() : PacketDeserializationMap[result](val)); archipelagoPacketBase.jobject = val; return archipelagoPacketBase; } private static ArchipelagoPacketBase DeserializePrintJsonPacket(JObject obj) { JToken val = default(JToken); if (obj.TryGetValue("type", ref val)) { if (EnumTryParse(((object)val).ToString(), out var result)) { switch (result) { case JsonMessageType.ItemSend: return ((JToken)obj).ToObject(); case JsonMessageType.ItemCheat: return ((JToken)obj).ToObject(); case JsonMessageType.Hint: return ((JToken)obj).ToObject(); case JsonMessageType.Join: return ((JToken)obj).ToObject(); case JsonMessageType.Part: return ((JToken)obj).ToObject(); case JsonMessageType.Chat: return ((JToken)obj).ToObject(); case JsonMessageType.ServerChat: return ((JToken)obj).ToObject(); case JsonMessageType.Tutorial: return ((JToken)obj).ToObject(); case JsonMessageType.TagsChanged: return ((JToken)obj).ToObject(); case JsonMessageType.CommandResult: return ((JToken)obj).ToObject(); case JsonMessageType.AdminCommandResult: return ((JToken)obj).ToObject(); case JsonMessageType.Goal: return ((JToken)obj).ToObject(); case JsonMessageType.Release: return ((JToken)obj).ToObject(); case JsonMessageType.Collect: return ((JToken)obj).ToObject(); case JsonMessageType.Countdown: return ((JToken)obj).ToObject(); } } obj["type"] = null; } return ((JToken)obj).ToObject(); } private static bool EnumTryParse(string value, out TEnum result) where TEnum : struct, IConvertible { return Enum.TryParse(value, out result); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } static ArchipelagoPacketConverter() { Dictionary> dictionary = new Dictionary>(25); dictionary[ArchipelagoPacketType.RoomInfo] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.ConnectionRefused] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Connected] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.ReceivedItems] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.LocationInfo] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.RoomUpdate] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.PrintJSON] = DeserializePrintJsonPacket; dictionary[ArchipelagoPacketType.Connect] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.ConnectUpdate] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.LocationChecks] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.LocationScouts] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.CreateHints] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.StatusUpdate] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Say] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.GetDataPackage] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.DataPackage] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Sync] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Bounced] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Bounce] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.InvalidPacket] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Get] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Retrieved] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.Set] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.SetNotify] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.SetReply] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.SetNotify] = (JObject obj) => ((JToken)obj).ToObject(); dictionary[ArchipelagoPacketType.UpdateHint] = (JObject obj) => ((JToken)obj).ToObject(); PacketDeserializationMap = dictionary; } } public class PermissionsEnumConverter : JsonConverter { public override bool CanConvert(Type objectType) { if (!(objectType == typeof(string)) && !(objectType == typeof(Permissions))) { return objectType == typeof(int); } return true; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { string text = reader.Value.ToString(); if (int.TryParse(text, out var result)) { return (Permissions)result; } Permissions permissions = Permissions.Disabled; if (text.Contains("enabled")) { permissions |= Permissions.Enabled; } if (text.Contains("auto")) { permissions |= Permissions.Auto; } if (text.Contains("goal")) { permissions |= Permissions.Goal; } return permissions; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Permissions permissions = (Permissions)value; writer.WriteValue((int)permissions); } } public static class UnixTimeConverter { private static DateTime UtcEpoch => DateTime.UnixEpoch; public static DateTime UnixTimeStampToDateTime(double unixTimeStamp) { if (unixTimeStamp > 1000000000000.0) { unixTimeStamp /= 1000.0; } return UtcEpoch.AddSeconds(unixTimeStamp); } public static double ToUnixTimeStamp(this DateTime dateTime) { return (dateTime - UtcEpoch).TotalMilliseconds / 1000.0; } } } namespace Archipelago.MultiClient.Net.ConcurrentCollection { internal class ConcurrentHashSet : IConcurrentHashSet { private readonly ConcurrentDictionary set = new ConcurrentDictionary(); public bool TryAdd(T item) { return set.TryAdd(item, 0); } public bool Contains(T item) { return set.ContainsKey(item); } public void UnionWith(T[] otherSet) { foreach (T key in otherSet) { set.TryAdd(key, 0); } } public T[] ToArray() { return set.Keys.ToArray(); } public ReadOnlyCollection AsToReadOnlyCollection() { return (ReadOnlyCollection)set.Keys; } public ReadOnlyCollection AsToReadOnlyCollectionExcept(IConcurrentHashSet otherSet) { ICollection keys = set.Keys; List list = new List(keys.Count); foreach (T item in keys) { if (!otherSet.Contains(item)) { list.Add(item); } } return new ReadOnlyCollection(list); } } internal class ConcurrentList : IConcurrentList { private readonly ConcurrentDictionary list = new ConcurrentDictionary(); public int Count => list.Count; public void Add(T item) { list.TryAdd(list.Count, item); } public void Clear() { list.Clear(); } public ReadOnlyCollection AsReadOnlyCollection() { return (ReadOnlyCollection)list.Values; } } internal interface IConcurrentList { int Count { get; } void Add(T item); void Clear(); ReadOnlyCollection AsReadOnlyCollection(); } } namespace Archipelago.MultiClient.Net.Colors { public static class BuiltInPalettes { public static readonly Palette Dark = new Palette(Color.White, new Dictionary { [PaletteColor.White] = Color.White, [PaletteColor.Black] = Color.Black, [PaletteColor.Red] = Color.Red, [PaletteColor.Green] = Color.Green, [PaletteColor.Blue] = Color.Blue, [PaletteColor.Cyan] = Color.Cyan, [PaletteColor.Magenta] = Color.Magenta, [PaletteColor.Yellow] = Color.Yellow, [PaletteColor.SlateBlue] = Color.SlateBlue, [PaletteColor.Salmon] = Color.Salmon, [PaletteColor.Plum] = Color.Plum }); } public static class ColorUtils { public const PaletteColor ActivePlayerColor = PaletteColor.Magenta; public const PaletteColor NonActivePlayerColor = PaletteColor.Yellow; public static PaletteColor? GetColor(JsonMessagePartColor color) { switch (color) { case JsonMessagePartColor.Red: case JsonMessagePartColor.RedBg: return PaletteColor.Red; case JsonMessagePartColor.Green: case JsonMessagePartColor.GreenBg: return PaletteColor.Green; case JsonMessagePartColor.Yellow: case JsonMessagePartColor.YellowBg: return PaletteColor.Yellow; case JsonMessagePartColor.Blue: case JsonMessagePartColor.BlueBg: return PaletteColor.Blue; case JsonMessagePartColor.Magenta: case JsonMessagePartColor.MagentaBg: return PaletteColor.Magenta; case JsonMessagePartColor.Cyan: case JsonMessagePartColor.CyanBg: return PaletteColor.Cyan; case JsonMessagePartColor.Black: case JsonMessagePartColor.BlackBg: return PaletteColor.Black; case JsonMessagePartColor.White: case JsonMessagePartColor.WhiteBg: return PaletteColor.White; default: return null; } } public static PaletteColor? GetColor(HintStatus status) { return status switch { HintStatus.Found => PaletteColor.Green, HintStatus.NoPriority => PaletteColor.SlateBlue, HintStatus.Avoid => PaletteColor.Salmon, HintStatus.Priority => PaletteColor.Plum, _ => null, }; } public static PaletteColor? GetColor(Hint hint) { return GetColor(hint.Status); } public static PaletteColor? GetColor(ItemFlags flags) { if (HasFlag(flags, ItemFlags.Advancement)) { return PaletteColor.Plum; } if (HasFlag(flags, ItemFlags.NeverExclude)) { return PaletteColor.SlateBlue; } if (HasFlag(flags, ItemFlags.Trap)) { return PaletteColor.Salmon; } return PaletteColor.Cyan; } public static PaletteColor? GetColor(ItemInfo item) { return GetColor(item.Flags); } public static PaletteColor? GetColor(PlayerInfo player, IConnectionInfoProvider connectionInfo) { return (player.Slot == connectionInfo.Slot) ? PaletteColor.Magenta : PaletteColor.Yellow; } private static bool HasFlag(ItemFlags flags, ItemFlags flag) { return flags.HasFlag(flag); } } public class Palette { private readonly Dictionary palette; public T DefaultColor { get; private set; } public T this[PaletteColor? color] { get { if (color.HasValue && palette.TryGetValue(color.Value, out var value)) { return value; } return DefaultColor; } } public Palette(T defaultColor, Dictionary palette) { DefaultColor = defaultColor; this.palette = new Dictionary(palette); } public Palette Transform(Func transform) { U defaultColor = transform(DefaultColor); Dictionary dictionary = new Dictionary(palette.Count); foreach (KeyValuePair item in palette) { dictionary[item.Key] = transform(item.Value); } return new Palette(defaultColor, dictionary); } public Palette Edit(T newDefault, Dictionary paletteEdits) { Dictionary dictionary = new Dictionary(palette); foreach (KeyValuePair paletteEdit in paletteEdits) { dictionary[paletteEdit.Key] = paletteEdit.Value; } return new Palette(newDefault, dictionary); } public Palette Edit(Dictionary paletteEdits) { return Edit(DefaultColor, paletteEdits); } } public enum PaletteColor { White, Black, Red, Green, Blue, Cyan, Magenta, Yellow, SlateBlue, Salmon, Plum } } namespace Archipelago.MultiClient.Net.BounceFeatures.DeathLink { public class DeathLink : IEquatable { public DateTime Timestamp { get; internal set; } public string Source { get; } public string Cause { get; } public DeathLink(string sourcePlayer, string cause = null) { Timestamp = DateTime.UtcNow; Source = sourcePlayer; Cause = cause; } internal static bool TryParse(Dictionary data, out DeathLink deathLink) { try { if (!data.TryGetValue("time", out var value) || !data.TryGetValue("source", out var value2)) { deathLink = null; return false; } string cause = null; if (data.TryGetValue("cause", out var value3)) { cause = ((object)value3).ToString(); } deathLink = new DeathLink(((object)value2).ToString(), cause) { Timestamp = UnixTimeConverter.UnixTimeStampToDateTime(value.ToObject()) }; return true; } catch { deathLink = null; return false; } } public bool Equals(DeathLink other) { if ((object)other == null) { return false; } if ((object)this == other) { return true; } if (Source == other.Source && Timestamp.Date.Equals(other.Timestamp.Date) && Timestamp.Hour == other.Timestamp.Hour && Timestamp.Minute == other.Timestamp.Minute) { return Timestamp.Second == other.Timestamp.Second; } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((DeathLink)obj); } public override int GetHashCode() { return (Timestamp.GetHashCode() * 397) ^ ((Source != null) ? Source.GetHashCode() : 0); } public static bool operator ==(DeathLink lhs, DeathLink rhs) { return lhs?.Equals(rhs) ?? ((object)rhs == null); } public static bool operator !=(DeathLink lhs, DeathLink rhs) { return !(lhs == rhs); } } public static class DeathLinkProvider { public static DeathLinkService CreateDeathLinkService(this ArchipelagoSession session) { return new DeathLinkService(session.Socket, session.ConnectionInfo); } } public class DeathLinkService { public delegate void DeathLinkReceivedHandler(DeathLink deathLink); private readonly IArchipelagoSocketHelper socket; private readonly IConnectionInfoProvider connectionInfoProvider; private DeathLink lastSendDeathLink; public event DeathLinkReceivedHandler OnDeathLinkReceived; internal DeathLinkService(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider) { this.socket = socket; this.connectionInfoProvider = connectionInfoProvider; socket.PacketReceived += OnPacketReceived; } private void OnPacketReceived(ArchipelagoPacketBase packet) { if (packet is BouncedPacket bouncedPacket && bouncedPacket.Tags.Contains("DeathLink") && DeathLink.TryParse(bouncedPacket.Data, out var deathLink) && (!(lastSendDeathLink != null) || !(lastSendDeathLink == deathLink)) && this.OnDeathLinkReceived != null) { this.OnDeathLinkReceived(deathLink); } } public void SendDeathLink(DeathLink deathLink) { BouncePacket bouncePacket = new BouncePacket { Tags = new List { "DeathLink" }, Data = new Dictionary { { "time", JToken.op_Implicit(deathLink.Timestamp.ToUnixTimeStamp()) }, { "source", JToken.op_Implicit(deathLink.Source) } } }; if (deathLink.Cause != null) { bouncePacket.Data.Add("cause", JToken.op_Implicit(deathLink.Cause)); } lastSendDeathLink = deathLink; socket.SendPacketAsync(bouncePacket); } public void EnableDeathLink() { if (Array.IndexOf(connectionInfoProvider.Tags, "DeathLink") == -1) { connectionInfoProvider.UpdateConnectionOptions(connectionInfoProvider.Tags.Concat(new string[1] { "DeathLink" }).ToArray()); } } public void DisableDeathLink() { if (Array.IndexOf(connectionInfoProvider.Tags, "DeathLink") != -1) { connectionInfoProvider.UpdateConnectionOptions(connectionInfoProvider.Tags.Where((string t) => t != "DeathLink").ToArray()); } } } }