Decompiled source of Archipelago v0.6.13
plugins/gg.archipelago.rhythmdoctor/RhythmDoctor.Archipelago.dll
Decompiled 14 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Archipelago.MultiClient.Net; using Archipelago.MultiClient.Net.BounceFeatures.DeathLink; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.MessageLog.Messages; using Archipelago.MultiClient.Net.Models; using Archipelago.MultiClient.Net.Packets; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json.Linq; using PulseLib; using PulseLib.Extensions; using PulseLib.Localization; using RDLevelEditor; using RhythmDoctor.Archipelago.Client; using RhythmDoctor.Archipelago.Extensions; using RhythmDoctor.Archipelago.Helpers; using RhythmDoctor.Archipelago.Interfaces; using RhythmDoctor.Archipelago.Patches; using RhythmDoctor.Archipelago.Patches.Gameplay; using RhythmDoctor.Archipelago.Patches.Gameplay.Powerups; using RhythmDoctor.Archipelago.Patches.Gameplay.Traps; using RhythmDoctor.Archipelago.Patches.Menu; using RhythmDoctor.Archipelago.World; using RhythmDoctor.Archipelago.World.Data; using RhythmWeightlifter; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("gg.archipelago.rhythmdoctor")] [assembly: AssemblyConfiguration("Release-BepInEx5")] [assembly: AssemblyFileVersion("0.6.13.0")] [assembly: AssemblyInformationalVersion("0.6.13+71162631dc2f6e169ab8e9362130d32633f21ee2")] [assembly: AssemblyProduct("RhythmDoctor.Archipelago")] [assembly: AssemblyTitle("gg.archipelago.rhythmdoctor")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.6.13.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] [CompilerGenerated] internal sealed class <>z__ReadOnlyArray<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T> { int ICollection.Count => _items.Length; bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; object IList.this[int index] { get { return _items[index]; } set { throw new NotSupportedException(); } } bool IList.IsFixedSize => true; bool IList.IsReadOnly => true; int IReadOnlyCollection<T>.Count => _items.Length; T IReadOnlyList<T>.this[int index] => _items[index]; int ICollection<T>.Count => _items.Length; bool ICollection<T>.IsReadOnly => true; T IList<T>.this[int index] { get { return _items[index]; } set { throw new NotSupportedException(); } } public <>z__ReadOnlyArray(T[] items) { _items = items; } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_items).GetEnumerator(); } void ICollection.CopyTo(Array array, int index) { ((ICollection)_items).CopyTo(array, index); } int IList.Add(object value) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } bool IList.Contains(object value) { return ((IList)_items).Contains(value); } int IList.IndexOf(object value) { return ((IList)_items).IndexOf(value); } void IList.Insert(int index, object value) { throw new NotSupportedException(); } void IList.Remove(object value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return ((IEnumerable<T>)_items).GetEnumerator(); } void ICollection<T>.Add(T item) { throw new NotSupportedException(); } void ICollection<T>.Clear() { throw new NotSupportedException(); } bool ICollection<T>.Contains(T item) { return ((ICollection<T>)_items).Contains(item); } void ICollection<T>.CopyTo(T[] array, int arrayIndex) { ((ICollection<T>)_items).CopyTo(array, arrayIndex); } bool ICollection<T>.Remove(T item) { throw new NotSupportedException(); } int IList<T>.IndexOf(T item) { return ((IList<T>)_items).IndexOf(item); } void IList<T>.Insert(int index, T item) { throw new NotSupportedException(); } void IList<T>.RemoveAt(int index) { throw new NotSupportedException(); } } [CompilerGenerated] internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T> { private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T> { object IEnumerator.Current => _item; T IEnumerator<T>.Current => _item; public Enumerator(T item) { _item = item; } bool IEnumerator.MoveNext() { if (!_moveNextCalled) { return _moveNextCalled = true; } return false; } void IEnumerator.Reset() { _moveNextCalled = false; } void IDisposable.Dispose() { } } int ICollection.Count => 1; bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; object IList.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } set { throw new NotSupportedException(); } } bool IList.IsFixedSize => true; bool IList.IsReadOnly => true; int IReadOnlyCollection<T>.Count => 1; T IReadOnlyList<T>.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } } int ICollection<T>.Count => 1; bool ICollection<T>.IsReadOnly => true; T IList<T>.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } set { throw new NotSupportedException(); } } public <>z__ReadOnlySingleElementList(T item) { _item = item; } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(_item); } void ICollection.CopyTo(Array array, int index) { array.SetValue(_item, index); } int IList.Add(object value) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } bool IList.Contains(object value) { return EqualityComparer<T>.Default.Equals(_item, (T)value); } int IList.IndexOf(object value) { if (!EqualityComparer<T>.Default.Equals(_item, (T)value)) { return -1; } return 0; } void IList.Insert(int index, object value) { throw new NotSupportedException(); } void IList.Remove(object value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return new Enumerator(_item); } void ICollection<T>.Add(T item) { throw new NotSupportedException(); } void ICollection<T>.Clear() { throw new NotSupportedException(); } bool ICollection<T>.Contains(T item) { return EqualityComparer<T>.Default.Equals(_item, item); } void ICollection<T>.CopyTo(T[] array, int arrayIndex) { array[arrayIndex] = _item; } bool ICollection<T>.Remove(T item) { throw new NotSupportedException(); } int IList<T>.IndexOf(T item) { if (!EqualityComparer<T>.Default.Equals(_item, item)) { return -1; } return 0; } void IList<T>.Insert(int index, T item) { throw new NotSupportedException(); } void IList<T>.RemoveAt(int index) { throw new NotSupportedException(); } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Microsoft.CodeAnalysis { [Embedded] [AttributeUsage(AttributeTargets.All)] [ExcludeFromCodeCoverage] internal sealed class EmbeddedAttribute : Attribute { } } namespace Archipelago.MultiClient.Net { [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] internal sealed class DataStoragePropertyAttribute : Attribute { public string? SessionVariable { get; } public Scope Scope { get; } public string Key { get; } public DataStoragePropertyAttribute(string sessionVariable, Scope scope, string key) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) SessionVariable = sessionVariable; Scope = scope; Key = key; } public DataStoragePropertyAttribute(string sessionVariable, string key) : this(sessionVariable, (Scope)0, key) { } } } namespace RhythmDoctor.Archipelago { internal static class Configuration { internal enum DeathLinkConfig { FollowSlot, On, Off } private static ConfigEntry<DeathLinkConfig> _deathLink; private static ConfigEntry<int> _autoReconnectMaxRetries; private static ConfigEntry<int> _remoteTrapClearsTimeout; private static ConfigEntry<int> _slotToUse; internal static void Bind(ConfigFile config) { Plugin.Logger.LogInfo((object)"Binding configuration"); _deathLink = config.Bind<DeathLinkConfig>("Gameplay", "DeathLink", DeathLinkConfig.FollowSlot, "Whether to enable DeathLink when connected to an Archipelago slot.\nSee the respective Game Page in Archipelago for more info."); _autoReconnectMaxRetries = config.Bind<int>("zzzDebugDoNotTouchUnlessAsked", "AutoReconnectMaxRetries", 3, "How many times to attempt to reconnect to an Archipelago server before the client is considered unsalvagable and the Client is abandoned."); _remoteTrapClearsTimeout = config.Bind<int>("zzzDebugDoNotTouchUnlessAsked", "RemoteTrapClearsTimeout", 3000, "How long to wait in milliseconds until getting remote trap clear status times out and defaults to 0/last known good value."); _slotToUse = config.Bind<int>("zzzDebugDoNotTouchUnlessAsked", "SlotToUse", 0, "Slot to use for Archipelago."); } internal static async Task<DeathLinkConfig> GetDeathLink() { if (Plugin.Client == null || Plugin.Client.Session == null) { return _deathLink.Value; } return (!(await ((IDataStorageWrapper)Plugin.Client.Session.DataStorage).GetRaceModeAsync())) ? _deathLink.Value : DeathLinkConfig.FollowSlot; } internal static int GetSlotToUse() { return _slotToUse.Value; } internal static int GetAutoReconnectMaxRetries() { return _autoReconnectMaxRetries.Value; } internal static int GetRemoteTrapClearsTimeout() { return _remoteTrapClearsTimeout.Value; } } internal static class Paths { internal static string Assembly; internal static string Assets; internal static string Localization; internal static string WardIcons; internal static void PopulatePaths() { Assembly = Path.GetDirectoryName(((BaseUnityPlugin)Plugin.Instance).Info.Location); Assets = Path.Combine(Assembly, "Assets"); Localization = Path.Combine(Assets, "Localization"); WardIcons = Path.Combine(Assets, "WardIcons"); } } [BepInPlugin("gg.archipelago.rhythmdoctor", "RhythmDoctor.Archipelago", "0.6.13")] [BepInDependency("io.github.nonperforming.pulse", "0.0.6")] [BepInProcess("Rhythm Doctor.exe")] public class Plugin : BaseUnityPlugin { internal static RhythmDoctor.Archipelago.Client.Client Client = null; internal static ManualLogSource Logger = null; internal static readonly Random Random = new Random(); internal static readonly ConcurrentQueue<Action> ToExecuteOnMainThread = new ConcurrentQueue<Action>(); internal static Plugin Instance = null; internal const string PATCH_ID_ALWAYS_ACTIVE = "gg.archipelago.rhythmdoctor"; internal const string PATCH_ID_ARCHIPELAGO_MENU = "gg.archipelago.rhythmdoctor.cls"; internal const string PATCH_ID_POST_LOGIN = "gg.archipelago.rhythmdoctor.post"; internal const string PATCH_ID_TRAP = "gg.archipelago.rhythmdoctor.trap"; internal const string PATCH_ID_SLEEVE_PAINT = "gg.archipelago.rhythmdoctor.sleevepaint"; private static readonly Type[] AlwaysActivePatches = new Type[3] { typeof(ArchipelagoMainMenuOptionPatch), typeof(VersionTextPatch), typeof(scnGameExtensions) }; private static readonly Type[] PostLoginPatches = new Type[15] { typeof(Act5Patch), typeof(ClearLocationPatch), typeof(DeathLinkPatch), typeof(LevelSelectVisualFixesPatch), typeof(RhythmDogtorLevelPatch), typeof(RhythmWeightlifterPatch), typeof(RunningCharactersPatch), typeof(SkipCutscenePatch), typeof(SkipTutorialPatch), typeof(StateReplicationPatch), typeof(TrapManagerPatch), typeof(UnlockItemPatch), typeof(WelcomeBackPatch), typeof(SavingPatch), typeof(UnapplyPatchesPatch) }; private static readonly Type CustomLoginScreenPatch = typeof(ArchipelagoLoginPatch); private bool _quitting; private void Awake() { Instance = this; Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"gg.archipelago.rhythmdoctor v0.6.13 loading"); Configuration.Bind(((BaseUnityPlugin)this).Config); Logger.LogInfo((object)"Setting up paths"); Paths.PopulatePaths(); Logger.LogInfo((object)("Registering custom localization (" + Paths.Localization + ")")); CustomLocalizationHelper.SearchAndRegisterDirectory(Paths.Localization); Logger.LogInfo((object)"Applying always active patches"); ApplyPatches("gg.archipelago.rhythmdoctor", AlwaysActivePatches); Logger.LogInfo((object)"gg.archipelago.rhythmdoctor v0.6.13 loaded"); } private void Start() { Version version = new Version("1.1.1"); Version version2 = new Version(Application.version); if (Releases.releaseNumber < 42 || version2 < version) { Logger.LogWarning((object)"================================================================================"); Logger.LogWarning((object)"| ╭─╮╷ ╭─╴╭─╮╭─╮╭─╴ ╷ ╷╭─╮╶┬╮╭─╮╶┬╴╭─╴ ╷ ╷╭─╮╷ ╷╭─╮ ╭─╴╭─╮╭┬╮╭─╴╷╷╷ |"); Logger.LogWarning((object)"| ├─╯│ ├╴ ├─┤╰─╮├╴ │ │├─╯ ││├─┤ │ ├╴ ╰┬╯│ ││ │├┬╯ │╶╮├─┤│││├╴ ╵╵╵ |"); Logger.LogWarning((object)"| ╵ ╰─╴╰─╴╵ ╵╰─╯╰─╴ ╰─╯╵ ╶┴╯╵ ╵ ╵ ╰─╴ ╵ ╰─╯╰─╯╵╰╴ ╰─╯╵ ╵╵ ╵╰─╴╵╵╵ |"); Logger.LogWarning((object)"================================================================================"); Logger.LogWarning((object)string.Format("This version of the mod (v{0}) was built for: v{1} (release {2}, commit {3}, date {4})", "0.6.13", version, 42, "e43207b", "2026/06/15 12:42 AM")); Logger.LogWarning((object)$"Your version is: v{Application.version} (release {Releases.releaseNumber}, commit {Releases.buildCommit}, date {Releases.buildDate})"); Logger.LogWarning((object)"================================================================================"); } else if (Releases.releaseNumber > 42 || version2 > version) { Logger.LogWarning((object)"================================================================================="); Logger.LogWarning((object)"| ╭─╮╷ ╭─╴╭─╮╭─╮╭─╴ ╭─╮╭─╮╭─╴╭╮╷ ╭─╮╭╮╷ ╷╭─╮╭─╮╷ ╷╭─╴╷╷╷ |"); Logger.LogWarning((object)"| ├─╯│ ├╴ ├─┤╰─╮├╴ │ │├─╯├╴ │╰┤ ├─┤│╰┤ │╰─╮╰─╮│ │├╴ ╵╵╵ |"); Logger.LogWarning((object)"| ╵ ╰─╴╰─╴╵ ╵╰─╯╰─╴ ╰─╯╵ ╰─╴╵ ╵ ╵ ╵╵ ╵ ╵╰─╯╰─╯╰─╯╰─╴╵╵╵ |"); Logger.LogWarning((object)"================================================================================="); Logger.LogWarning((object)string.Format("This version of the mod (v{0}) was built for: v{1} (release {2}, commit {3}, date {4})", "0.6.13", version, 42, "e43207b", "2026/06/15 12:42 AM")); Logger.LogWarning((object)$"Your version is: v{Application.version} (release {Releases.releaseNumber}, commit {Releases.buildCommit}, date {Releases.buildDate})"); Logger.LogWarning((object)null); Logger.LogWarning((object)"First, please check that there is an updated version here: https://github.com/nonperforming/RhythmDoctor.Archipelago/releases. Remember to update Pulse (https://github.com/nonperforming/Pulse/releases) as well."); Logger.LogWarning((object)$"If there is no update yet, please open an issue at https://github.com/nonperforming/RhythmDoctor.Archipelago/issues/new?labels=\"plugin outdated\"&title=v{Application.version}+support&body=Release:+{Releases.releaseNumber}+/+Commit:+{Releases.buildCommit}+/+Date:+{Releases.buildDate}&assignees=nonperforming"); Logger.LogWarning((object)"================================================================================="); } } private void Update() { Action result; while (ToExecuteOnMainThread.TryDequeue(out result)) { result(); } } private static void ApplyPatches(string id, params Type[] patches) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown Logger.LogInfo((object)("Applying patches as " + id)); Harmony val = new Harmony(id); foreach (Type type in patches) { Logger.LogInfo((object)("Applying " + type.Name)); val.PatchAll(type); } } internal static void ApplyGameplayPatches() { Logger.LogInfo((object)"Applying gameplay patches"); ApplyPatches("gg.archipelago.rhythmdoctor.post", PostLoginPatches); ApplyPatches("gg.archipelago.rhythmdoctor.sleevepaint", typeof(LockSleevePaintPatch)); } internal static void UnapplyGameplayPatches() { Logger.LogInfo((object)"Unapplying gameplay patches"); Harmony.UnpatchID("gg.archipelago.rhythmdoctor.post"); Harmony.UnpatchID("gg.archipelago.rhythmdoctor.sleevepaint"); } internal static void ApplyArchipelagoMenuPatch() { Logger.LogInfo((object)"Applying Archipelago menu patch"); Harmony.CreateAndPatchAll(CustomLoginScreenPatch, "gg.archipelago.rhythmdoctor.cls"); } internal static void UnapplyArchipelagoMenuPatch() { Logger.LogInfo((object)"Unapplying Archipelago menu patch"); Harmony.UnpatchID("gg.archipelago.rhythmdoctor.cls"); } private void OnApplicationQuit() { Logger.LogDebug((object)"Quitting..."); _quitting = true; } private void OnDestroy() { if (!_quitting) { Logger.LogWarning((object)"Tearing down plugin. This is unsupported!"); UnapplyPatchesPatch.TearDownClientPluginPatch(); Harmony.UnpatchID("gg.archipelago.rhythmdoctor"); Harmony.UnpatchID("gg.archipelago.rhythmdoctor.cls"); Harmony.UnpatchID("gg.archipelago.rhythmdoctor.post"); Harmony.UnpatchID("gg.archipelago.rhythmdoctor.trap"); } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "gg.archipelago.rhythmdoctor"; public const string PLUGIN_NAME = "RhythmDoctor.Archipelago"; public const string PLUGIN_VERSION = "0.6.13"; } } namespace RhythmDoctor.Archipelago.World { internal enum Act { None, Act1, Act2, Act3, Act4, Act5, Act6, Act7 } internal static class Bindings { internal const string GAME = "Rhythm Doctor"; internal const long SLEEVE_PAINT_ITEM_ID = 56L; internal const Level RHYTHM_DOGTOR_LEVEL = (Level)84; internal static readonly Dictionary<Level, BaseStage> LevelToStage = new Dictionary<Level, BaseStage> { { (Level)2, new RegularStage(Act.Act1, 1L, 2L, 3L) }, { (Level)3, new RegularStage(Act.Act1, 4L, 5L, 6L) }, { (Level)4, new RegularStage(Act.Act1, 7L, 8L, 9L) }, { (Level)5, new RegularStage(Act.Act1, 10L, 11L, 12L) }, { (Level)53, new RegularStage(Act.Act1, 13L, 14L, 15L) }, { (Level)54, new RegularStage(Act.Act1, 16L, 17L, 18L) }, { (Level)18, new RegularStage(Act.Act3, 21L, 22L, 23L) }, { (Level)19, new RegularStage(Act.Act3, 24L, 25L, 26L) }, { (Level)20, new RegularStage(Act.Act3, 27L, 28L, 29L) }, { (Level)21, new RegularStage(Act.Act3, 30L, 31L, 32L) }, { (Level)22, new RegularStage(Act.Act3, 33L, 34L, 35L) }, { (Level)23, new RegularStage(Act.Act3, 36L, 37L, 38L) }, { (Level)26, new BossStage(Act.Act3, 39L, null, 40L) }, { (Level)84, new BossStage(Act.Act3, 41L, null, 42L) }, { (Level)8, new RegularStage(Act.Act2, 43L, 44L, 45L) }, { (Level)9, new RegularStage(Act.Act2, 46L, 47L, 48L) }, { (Level)10, new RegularStage(Act.Act2, 49L, 50L, 51L) }, { (Level)11, new RegularStage(Act.Act2, 52L, 53L, 54L) }, { (Level)12, new RegularStage(Act.Act2, 55L, 56L, 57L) }, { (Level)13, new RegularStage(Act.Act2, 58L, 59L, 60L) }, { (Level)14, new RegularStage(Act.Act2, 171L, 172L, 61L) }, { (Level)15, new RegularStage(Act.Act2, 173L, 174L, 62L) }, { (Level)17, new RegularStage(Act.Act2, 63L, 64L, 65L) }, { (Level)48, new BossStage(Act.Act7, 66L, 67L, 68L) }, { (Level)27, new RegularStage(Act.Act4, 71L, 72L, 73L) }, { (Level)28, new RegularStage(Act.Act4, 74L, 75L, 76L) }, { (Level)29, new RegularStage(Act.Act4, 77L, 78L, 79L) }, { (Level)30, new RegularStage(Act.Act4, 80L, 81L, 82L) }, { (Level)31, new RegularStage(Act.Act4, 83L, 84L, 85L) }, { (Level)32, new RegularStage(Act.Act4, 86L, 87L, 88L) }, { (Level)33, new RegularStage(Act.Act4, 89L, 90L, 91L) }, { (Level)34, new RegularStage(Act.Act4, 92L, 93L, 94L) }, { (Level)35, new RegularStage(Act.Act5, 97L, 98L, 99L) }, { (Level)36, new RegularStage(Act.Act5, 100L, 101L, 102L) }, { (Level)37, new RegularStage(Act.Act5, 103L, 104L, 105L) }, { (Level)38, new RegularStage(Act.Act5, 106L, 107L, 108L) }, { (Level)39, new RegularStage(Act.Act5, 175L, 176L, 109L) }, { (Level)40, new RegularStage(Act.Act5, 110L, 111L, 112L) }, { (Level)43, new RegularStage(Act.Act6, 126L, 127L, 128L) }, { (Level)45, new RegularStage(Act.Act6, 129L, 130L, 131L) }, { (Level)47, new RegularStage(Act.Act7, 135L, 136L, 137L) }, { (Level)58, new RegularStage(Act.None, 144L, 145L, 146L) }, { (Level)59, new RegularStage(Act.None, 147L, 148L, 149L) }, { (Level)56, new RegularStage(Act.None, 150L, 151L, 152L) }, { (Level)57, new RegularStage(Act.None, 153L, 154L, 155L) }, { (Level)61, new RegularStage(Act.None, 156L, 157L, 158L) }, { (Level)62, new RegularStage(Act.None, 159L, 160L, 161L) }, { (Level)63, new RegularStage(Act.None, 162L, 163L, 164L) }, { (Level)55, new RegularStage(Act.None, 165L, 166L, 167L) }, { (Level)25, new RegularStage(Act.None, 168L, 169L, 170L) }, { (Level)60, new RegularStage(Act.None, 177L, 178L, 179L) }, { (Level)6, new BossStage(Act.Act1, 19L, null, 20L) }, { (Level)16, new BossStage(Act.Act2, 69L, null, 70L) }, { (Level)7, new BossStage(Act.Act4, 95L, null, 96L) }, { (Level)42, new BossStage(Act.Act5, 123L, 124L, 125L) }, { (Level)46, new BossStage(Act.Act6, 132L, null, 134L) }, { (Level)49, new BossStage(Act.Act7, 138L, 139L, 140L) }, { (Level)50, new BossStage(Act.Act7, 141L, 142L, 143L) } }; internal static readonly Dictionary<long, Level> ItemIdToLevel = new Dictionary<long, Level> { { 1L, (Level)2 }, { 2L, (Level)3 }, { 3L, (Level)4 }, { 4L, (Level)5 }, { 5L, (Level)53 }, { 6L, (Level)54 }, { 7L, (Level)18 }, { 8L, (Level)19 }, { 9L, (Level)20 }, { 10L, (Level)21 }, { 11L, (Level)22 }, { 12L, (Level)23 }, { 13L, (Level)8 }, { 14L, (Level)9 }, { 15L, (Level)10 }, { 16L, (Level)11 }, { 17L, (Level)12 }, { 18L, (Level)13 }, { 19L, (Level)14 }, { 20L, (Level)15 }, { 21L, (Level)17 }, { 22L, (Level)48 }, { 23L, (Level)27 }, { 24L, (Level)28 }, { 25L, (Level)29 }, { 26L, (Level)30 }, { 27L, (Level)31 }, { 28L, (Level)32 }, { 29L, (Level)33 }, { 30L, (Level)34 }, { 31L, (Level)35 }, { 32L, (Level)36 }, { 33L, (Level)37 }, { 34L, (Level)38 }, { 35L, (Level)39 }, { 36L, (Level)40 }, { 37L, (Level)41 }, { 38L, (Level)43 }, { 39L, (Level)45 }, { 40L, (Level)47 }, { 41L, (Level)58 }, { 42L, (Level)59 }, { 43L, (Level)56 }, { 44L, (Level)57 }, { 45L, (Level)61 }, { 46L, (Level)62 }, { 47L, (Level)63 }, { 48L, (Level)55 }, { 49L, (Level)25 }, { 67L, (Level)60 } }; internal static readonly Dictionary<long, Type> TrapItemIdToLevel = new Dictionary<long, Type> { { 60L, typeof(FragileHeartTrapPatch) }, { 61L, typeof(HardDifficultyTrapPatch) }, { 62L, typeof(ScrambleCharactersTrapPatch) }, { 63L, typeof(ScrambleBeatsoundsTrapPatch) }, { 64L, typeof(ScrambleHitsoundsTrapPatch) }, { 65L, typeof(ChilliSpeedTrapPatch) }, { 66L, typeof(GhostTapTrapPatch) }, { 57L, typeof(StrongHeartPowerupPatch) }, { 58L, typeof(EasyDifficultyPowerupPatch) }, { 59L, typeof(IceSpeedTrapPatch) } }; internal static readonly Dictionary<long, Region> KeyItemIdToWard = new Dictionary<long, Region> { { 50L, Region.SVTWard }, { 51L, Region.Train }, { 52L, Region.PhysiotherapyWard }, { 55L, Region.RecordsRoom }, { 53L, Region.Basement }, { 54L, Region.GardenRoom } }; internal static readonly long[] RhythmWeightlifterStageToLocationID = new long[12] { 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 180L, 181L }; internal static readonly IReadOnlyCollection<Type> Traps = TrapItemIdToLevel.Values; internal static readonly IReadOnlyCollection<Level> Levels = LevelToStage.Keys; internal static readonly Dictionary<Level, Act> LevelToAct = LevelToStage.ToDictionary<KeyValuePair<Level, BaseStage>, Level, Act>((KeyValuePair<Level, BaseStage> pair) => pair.Key, (KeyValuePair<Level, BaseStage> pair) => pair.Value.StageAct); internal static readonly Dictionary<Region, long> RegionToKeyID = KeyItemIdToWard.ToDictionary((KeyValuePair<long, Region> pair) => pair.Value, (KeyValuePair<long, Region> pair) => pair.Key); internal static readonly Dictionary<Act, IEnumerable<Level>> LevelsInAct = (from pair in LevelToStage group pair by pair.Value.StageAct).ToDictionary((IGrouping<Act, KeyValuePair<Level, BaseStage>> grouping) => grouping.Key, (IGrouping<Act, KeyValuePair<Level, BaseStage>> grouping) => grouping.Select((KeyValuePair<Level, BaseStage> c) => c.Key)); internal static readonly Level[] LevelsThatDoNotUnlockBoss = (Level[])(object)new Level[1] { (Level)41 }; internal static readonly Dictionary<Act, Level[]> ActBoss = new Dictionary<Act, Level[]> { { Act.Act1, (Level[])(object)new Level[1] { (Level)6 } }, { Act.Act2, (Level[])(object)new Level[1] { (Level)16 } }, { Act.Act3, (Level[])(object)new Level[2] { (Level)26, (Level)84 } }, { Act.Act4, (Level[])(object)new Level[1] { (Level)7 } }, { Act.Act5, (Level[])(object)new Level[1] { (Level)42 } }, { Act.Act6, (Level[])(object)new Level[1] { (Level)46 } }, { Act.Act7, (Level[])(object)new Level[2] { (Level)49, (Level)50 } } }; } internal enum Region { MainWard, SVTWard, Train, PhysiotherapyWard, RecordsRoom, Basement, GardenRoom } } namespace RhythmDoctor.Archipelago.World.Data { internal abstract class BaseStage { internal Act StageAct { get; init; } internal abstract IEnumerable<long> GetLocationsToClear(Rank rank); } internal class BossStage : BaseStage { internal long ClearLocation { get; init; } internal long? CompletePlusLocation { get; init; } internal long PerfectLocation { get; init; } internal BossStage(Act act, long clearLocation, long? completePlusLocation, long perfectLocation) { base.StageAct = act; ClearLocation = clearLocation; CompletePlusLocation = completePlusLocation; PerfectLocation = perfectLocation; } internal override IEnumerable<long> GetLocationsToClear(Rank rank) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogDebug((object)$"Getting locations to clear for rank {rank.internalValue}"); List<long> list = new List<long>(); if (((Rank)(ref rank)).perfected) { list.Add(PerfectLocation); } if (CompletePlusLocation.HasValue && (((Rank)(ref rank)).perfected || scnGame.instance.GetPassedLevelWithoutCheckpoints())) { list.Add(CompletePlusLocation.Value); } list.Add(ClearLocation); return list.AsReadOnly(); } } internal class RegularStage : BaseStage { internal long? BRankLocation { get; init; } internal long? ARankLocation { get; init; } internal long SRankLocation { get; init; } internal RegularStage(Act act, long? bRankLocation, long? aRankLocation, long sRankLocation) { base.StageAct = act; BRankLocation = bRankLocation; ARankLocation = aRankLocation; SRankLocation = sRankLocation; } internal override IEnumerable<long> GetLocationsToClear(Rank rank) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogDebug((object)$"Getting locations to clear for rank {rank.internalValue}"); List<long> list = new List<long>(); switch (Rank.op_Implicit(rank)) { case -15: case 5: case 15: list.Add(SRankLocation); goto case -14; case -14: case 4: case 14: if (ARankLocation.HasValue) { list.Add(ARankLocation.Value); } goto case -13; case -13: case 3: case 13: if (BRankLocation.HasValue) { list.Add(BRankLocation.Value); } break; } return list.AsReadOnly(); } } } namespace RhythmDoctor.Archipelago.Patches { [HarmonyPatch(typeof(ArmSkin))] internal static class LockSleevePaintPatch { [HarmonyPatch("Load")] [HarmonyPrefix] private static void LoadDefaultSleevePatch(ref ArmSkin __instance, ref bool __runOriginal) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) __runOriginal = false; int num = Plugin.Random.Next(0, 2); RDPlayer player = __instance.player; if ((int)player != 0) { if ((int)player == 1) { __instance.skinColor = RDConstants.data.skinColors[RDConstants.data.defaultP2SkinColor[num]]; __instance.sleeveColor = RDConstants.data.sleeveColors[RDConstants.data.defaultP2SleeveColor[num]]; __instance.nailsColor = RDConstants.data.nailColors[RDConstants.data.defaultP2NailColor[num]]; __instance.palmLightness = RDConstants.data.defaultP2PalmLightness[num]; __instance.drawing = RDConstants.data.emptyTexture; } } else { __instance.skinColor = RDConstants.data.skinColors[RDConstants.data.defaultP1SkinColor[num]]; __instance.sleeveColor = RDConstants.data.sleeveColors[RDConstants.data.defaultP1SleeveColor[num]]; __instance.nailsColor = RDConstants.data.nailColors[RDConstants.data.defaultP1NailColor[num]]; __instance.palmLightness = RDConstants.data.defaultP1PalmLightness[num]; __instance.drawing = RDConstants.data.emptyTexture; } } } [HarmonyPatch(typeof(RDVersionText))] internal static class VersionTextPatch { [HarmonyPatch("SetPage")] [HarmonyPostfix] private static void AppendClientVersionPatch(RDVersionText __instance) { Text text = __instance.text; string text2 = (text.text += " / Archipelago v0.6.13"); string text4 = text2; __instance.text.text = text4; } } [HarmonyPatch] internal static class SavingPatch { [HarmonyPatch(typeof(PlayerPrefsJson), "Save")] [HarmonyPatch(typeof(PlayerPrefsJson), "SaveBackup")] [HarmonyPrefix] private static void DisableSavingToFilePatch(ref bool __runOriginal, ref PlayerPrefsJson __instance) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 __runOriginal = (int)__instance.fileType == 0; } [HarmonyPatch(typeof(ArmSkin), "GetDrawingPath")] [HarmonyPrefix] private unsafe static void RedirectSleevePaintFilePatch(RDPlayer player, ref string __result, ref bool __runOriginal) { __runOriginal = false; __result = Path.Combine(PlayerPrefsJson.GetFileFolderPath(), "scribble" + ((object)(*(RDPlayer*)(&player))/*cast due to .constrained prefix*/).ToString() + "_AP.png"); } } [HarmonyPatch] internal static class UnapplyPatchesPatch { [HarmonyPatch(typeof(scnMenu), "Awake")] [HarmonyPatch(typeof(scnBase), "GoToMainMenu")] [HarmonyPrefix] internal static void TearDownClientPluginPatch() { Plugin.Logger.LogInfo((object)"Tearing down client plugin"); Plugin.UnapplyGameplayPatches(); Persistence.Load(); try { scnMenu.instance.slots[Configuration.GetSlotToUse()].LoadSlotData(); Plugin.Logger.LogDebug((object)"Reloaded slot data"); } catch (NullReferenceException) { Plugin.Logger.LogDebug((object)"Not in main menu, could not load slot data"); } Plugin.Client?.Dispose(); Plugin.Client = null; } } } namespace RhythmDoctor.Archipelago.Patches.Menu { [HarmonyPatch] internal static class ArchipelagoLoginPatch { [HarmonyPatch(typeof(scnCLS), "Start")] [HarmonyPostfix] private static void ConstructArchipelagoMenuPatch(scnCLS __instance) { Plugin.Logger.LogInfo((object)"Renaming custom level ward items"); Plugin.Logger.LogInfo((object)"Renaming LED sign"); GameObject.Find("Canvas/Ward Container/LEDSign Container/WardTitle Text").GetComponent<Text>().text = RDString.Get("archipelago.connectScreenTitle"); Plugin.Logger.LogInfo((object)"Renaming ward options"); WardOption val = __instance.wardOptions.Find((WardOption wardOption) => (int)wardOption.name == 0); WardOption val2 = __instance.wardOptions.Find((WardOption wardOption) => (int)wardOption.name == 1); WardOption? obj = __instance.wardOptions.Find((WardOption wardOption) => (int)wardOption.name == 2); __instance.ChangeToImportOption(); if (val != null) { RectTransform rect = val.rect; if (rect != null) { Transform parent = ((Component)rect).transform.parent; if (parent != null) { ((Component)parent).gameObject.SetActive(false); } } __instance.wardOptions.Remove(val); } if (val2 != null) { RectTransform rect2 = val2.rect; if (rect2 != null) { Transform parent2 = ((Component)rect2).transform.parent; if (parent2 != null) { ((Component)parent2).gameObject.SetActive(false); } } __instance.wardOptions.Remove(val2); } Plugin.Logger.LogInfo((object)"Renaming Import to Archipelago option"); Transform obj2 = ((Transform)obj.rect).Find("Button"); ((Component)obj2.Find("Icon Image")).GetComponent<Image>().sprite = AssetHelper.LoadSprite("WardIcons", "Archipelago.png"); ((Component)obj2.Find("Text")).GetComponent<Text>().text = RDString.Get("archipelago.loginButton"); Plugin.Logger.LogInfo((object)"Disabling DragAndDrop"); __instance.levelImporter.dragAndDrop.SetActive(false); __instance._currentWardOptionIndex = 0; } [HarmonyPatch(typeof(LevelImporter), "Install")] [HarmonyPostfix] private static IEnumerator OverrideInstallButtonPatch(IEnumerator result, LevelImporter __instance) { Plugin.Logger.LogInfo((object)"Locking input"); ((RDBase)__instance).cls.CLSPlaySound("sndImportInstallButtonClick", 1f, "CustomLevelSelect", 1f, 0f); __instance.ToggleInsertUrlContainer(false, true, false); __instance.CurrentContentName = (ContentName)2; __instance.stoppedInstallCoroutine = false; __instance.CanToggleClearButton = false; __instance.levelsToInstall = new List<ImportLevel>(__instance.toInstallIS.levels); foreach (ImportLevel item in __instance.levelsToInstall) { item.canToggleRemoveButton = false; } yield return null; ((Selectable)__instance.stopButton).interactable = false; string text = ((Component)((Component)__instance).transform.Find("screen/Contents/InsertURL Container/URL InputField/Text")).GetComponent<Text>().text; string[] array = (from text2 in text.Split('\n') select text2.Trim()).ToArray(); Plugin.Logger.LogInfo((object)("Input: '" + GeneralExtensions.Join<string>((IEnumerable<string>)array, (Func<string, string>)null, ", ") + "' (raw: " + Convert.ToBase64String(Encoding.UTF8.GetBytes(text)))); if (array.Length < 2) { Plugin.Logger.LogError((object)"URL or Name not input, bailing out"); __instance.CurrentContentName = (ContentName)3; ((RDBase)__instance).cls.CLSPlaySound("sndImportInstallFinish", 1f, "CustomLevelSelect", 1f, 0f); __instance.AddLevelToErrorSection(__instance.levelsToInstall[0], RDString.Get("archipelago.login.fail.missingInformation")); yield break; } string url = array[0]; string name = array[1]; string password = null; try { password = array[2]; } catch (IndexOutOfRangeException) { } Plugin.Logger.LogInfo((object)("URL: " + url + ", Slot Name: " + name)); if (Utility.IsNullOrWhiteSpace(url) || Utility.IsNullOrWhiteSpace(name)) { Plugin.Logger.LogError((object)"Invalid information, bailing out"); } else { Plugin.Logger.LogInfo((object)"Creating client"); Plugin.Client = new RhythmDoctor.Archipelago.Client.Client(); Task<LoginResult> login = Task.Run(() => Plugin.Client.CreateSessionAndConnect(url, name, password)); yield return (object)new WaitUntil((Func<bool>)(() => login.IsCompleted)); if (login.IsCanceled || login.IsFaulted) { string arg = (login.IsFaulted ? login.Exception.ToString() : "false"); Plugin.Logger.LogError((object)$"Login has cancelled or faulted (cancel: {login.IsCanceled} / fault: {arg})"); } else { LoginResult result2 = login.Result; if (result2 is LoginSuccessful) { Plugin.Logger.LogInfo((object)"Logged in!"); ((RDBase)__instance).cls.CLSPlaySound("sndImportInstallFinish", 1f, "CustomLevelSelect", 1f, 0f); yield return null; UnpatchMenuPatch(); while (!Plugin.Client.Setup) { yield return (object)new WaitForSecondsRealtime(1f); } Plugin.Logger.LogInfo((object)"Heading to Level Select..."); scnBase.GoToScene("scnLevelSelect"); yield break; } LoginFailure val = (LoginFailure)(object)((result2 is LoginFailure) ? result2 : null); if (val == null) { Plugin.Logger.LogWarning((object)"Got to the end of OverrideInstallButtonPatch - this should never happen!"); yield break; } Plugin.Logger.LogError((object)("Got LoginFailure: " + string.Join(", ", val.ErrorCodes) + " / " + string.Join(", ", val.Errors))); } Plugin.Logger.LogError((object)"Login failed (Login)"); UnapplyPatchesPatch.TearDownClientPluginPatch(); Plugin.Client?.Dispose(); Plugin.Client = null; } __instance.CurrentContentName = (ContentName)3; ((RDBase)__instance).cls.CLSPlaySound("sndImportInstallFinish", 1f, "CustomLevelSelect", 1f, 0f); ((Component)((Component)__instance).transform.Find("screen/Contents/Top Panel/Title Text")).GetComponent<Text>().text = RDString.Get("archipelago.login.fail.title"); } [HarmonyPatch(typeof(scnCLS), "Exit")] [HarmonyPostfix] private static void UnpatchMenuPatch() { Plugin.UnapplyArchipelagoMenuPatch(); } [HarmonyPatch(typeof(scnCLS), "SelectWardOption")] [HarmonyPostfix] private static void CustomSelectOptionPatch(ref bool __runOriginal, scnCLS __instance) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 __runOriginal = false; WardOptionName name = __instance.CurrentWardOption.name; if ((int)name <= 1) { Plugin.Logger.LogWarning((object)"Library/Steam Workshop selected in Archipelago login screen"); } else { __runOriginal = true; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void ArchipelagoImportScreenPatch() { GameObject gameObject = ((Component)((Component)((Component)scnCLS.instance.levelImporter).gameObject.transform.Find("screen")).gameObject.transform.Find("Contents")).gameObject; GameObject gameObject2 = ((Component)gameObject.transform.Find("InsertURL Container")).gameObject; GameObject gameObject3 = ((Component)gameObject2.transform.Find("Add Button")).gameObject; Text component = ((Component)((Component)gameObject2.transform.Find("URL InputField")).gameObject.transform.Find("Placeholder")).gameObject.GetComponent<Text>(); Text component2 = ((Component)gameObject2.transform.Find("Instructions")).GetComponent<Text>(); Text component3 = ((Component)gameObject3.transform.Find("Text")).GetComponent<Text>(); ((UnityEvent)((Component)gameObject.transform.Find("Draggable Content/AddURL Button")).GetComponent<Button>().onClick).Invoke(); ((Component)gameObject.transform.Find("Top Panel/Title Text")).GetComponent<Text>().text = RDString.Get("archipelago.login.menu.title"); ((Component)gameObject2.transform.Find("Cancel Button")).gameObject.SetActive(false); component2.text = RDString.Get("archipelago.login.menu.instructions"); component.text = RDString.Get("archipelago.login.menu.hint"); component3.text = RDString.Get("archipelago.login.menu.connectButton"); } [HarmonyPatch(typeof(LevelImporter), "ValidateUrl")] [HarmonyPrefix] private static void StubValidateUrlPatch(ref bool __runOriginal, LevelImporter __instance) { __runOriginal = false; __instance.Install_Public(); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static void DisableDragAndDropInstallGetPatch(ref bool __result, ref bool __runOriginal) { __runOriginal = false; __result = false; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static void DisableDragAndDropInstallSetPatch(ref bool __runOriginal) { __runOriginal = false; } } [HarmonyPatch(typeof(scnMenu))] internal static class ArchipelagoMainMenuOptionPatch { private const string ARCHIPELAGO_OBJECT_NAME = "archipelago"; [HarmonyPatch("Awake")] [HarmonyPrefix] private static void RenameMusicOptionPatch(scnMenu __instance) { ((Object)((Component)((Component)__instance).transform.Find("mainMenu/options/optionsContainer/music")).gameObject).name = "archipelago"; } [HarmonyPatch("SelectOption")] [HarmonyPrefix] private static void HandleArchipelagoOptionSelectedPatch(ref bool __runOriginal, scnMenu __instance) { if (((Object)((Component)__instance.optionsText[__instance.currentOption]).gameObject).name == "archipelago") { __runOriginal = false; Plugin.ApplyArchipelagoMenuPatch(); __instance.PlayConfirmSound(); __instance.TransitionToScene("scnCLS"); } } } } namespace RhythmDoctor.Archipelago.Patches.Gameplay { [HarmonyPatch(typeof(scnLevelSelect))] internal static class Act5Patch { [HarmonyPatch("PerformEntityAction")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> NicoleBlockageInteractionPatch(IEnumerable<CodeInstruction> instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(true, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldstr, (object)"GoToAthleteWard", (string)null) }).Advance(3).RemoveInstructions(8) .SetOpcodeAndAdvance(OpCodes.Ldc_I4_0) .InstructionEnumeration(); } [HarmonyPatch("Start")] [HarmonyPostfix] private static void DematerializeNicolePatch(scnLevelSelect __instance) { ((Entity)__instance.nicoleAct5Blockage).visible = false; } [HarmonyPatch("PrepareAthleteWardTransition")] [HarmonyPrefix] private static void DoNotShowDreamBubblesPatch(ref bool __runOriginal) { __runOriginal = false; } } [HarmonyPatch] internal static class ClearLocationPatch { private static Dictionary<long, ScoutedItemInfo> ItemsToSend = new Dictionary<long, ScoutedItemInfo>(); private static long[] JustSentLocations = Array.Empty<long>(); [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static void ScoutItemsSentPatch(LevelBase __instance) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogDebug((object)("Scouting locations to send for " + scnGame.internalIdentifier)); ItemsToSend.Clear(); JustSentLocations = Array.Empty<long>(); if (!Enum.TryParse<Level>(scnGame.internalIdentifier, out Level result)) { Plugin.Logger.LogError((object)("Couldn't find Level. Level identifier: " + scnGame.internalIdentifier)); Plugin.Client.TrapManager.ClearActiveTraps(returnToQueue: false); return; } if ((int)result == 26 && (__instance.dogMode || scnGame.loadDogMode)) { result = (Level)84; } IEnumerable<long> locationsToClear = Bindings.LevelToStage[result].GetLocationsToClear(Rank.op_Implicit(5)); ((MonoBehaviour)Plugin.Instance).StartCoroutine(ScoutLocationChecks(locationsToClear.ToArray())); } [HarmonyPatch(typeof(Rankscreen), "ShowAndSaveRank")] [HarmonyPrefix] private static void CustomClearLocationPatch(bool bossLevelFailed, bool onlySavePersistence, Rankscreen __instance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) Level currentLevel = GetCurrentLevel(); Rank rankFromMistakes = scnGame.instance.currentLevel.GetRankFromMistakes(); SendLocations(currentLevel, rankFromMistakes, bossLevelFailed); } [HarmonyPatch(typeof(Level), "GetRank")] [HarmonyPostfix] private static void RhythmWeightlifterClearLocationPatch(string __result) { if (!(__result == "-")) { Plugin.Client.Session.Locations.CompleteLocationChecks(new long[1] { Bindings.RhythmWeightlifterStageToLocationID[scnRhythmWeightlifter.gameInstance.LevelIndex] }); } } [HarmonyPatch(typeof(Rankscreen), "ShowRankDescription")] [HarmonyPostfix] private static void ShowSentItemsPatch(Rankscreen __instance) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) if (!ItemsToSend.Any()) { Plugin.Logger.LogWarning((object)"Couldn't get items sent, possibly due to a network issue."); __instance.description.text = "<color=red>" + RDString.Get("archipelago.rankscreen.error") + "</color>"; } else { Rank rankFromMistakes = scnGame.instance.currentLevel.GetRankFromMistakes(); IEnumerable<long> stageLocationIDsToClear = GetStageLocationIDsToClear(GetCurrentLevel(), rankFromMistakes); long[] array = stageLocationIDsToClear.Where((long id) => !Plugin.Client.Session.Locations.AllLocationsChecked.Contains(id) || JustSentLocations.Contains(id)).ToArray(); Plugin.Logger.LogDebug((object)("IDs: " + string.Join(", ", stageLocationIDsToClear) + " (of which " + string.Join(", ", array) + " are new)")); if (!stageLocationIDsToClear.Any()) { __instance.description.text = RDString.Get("archipelago.rankscreen.nothing"); return; } if (array.Length == 0) { __instance.description.text = RDString.Get("archipelago.rankscreen.nothingNew"); return; } __instance.description.text = ""; __instance.description.fontSize = 10; long[] array2 = array; foreach (long num2 in array2) { if (!ItemsToSend.TryGetValue(num2, out ScoutedItemInfo value)) { Plugin.Logger.LogWarning((object)($"Could not get scouted item information for location ID {num2}. " + "This is normal if this ID is tied to a Perfect rank, and Perfect locations are disabled.")); continue; } string text = "silver"; if (((Enum)((ItemInfo)value).Flags).HasFlag((Enum)(object)(ItemFlags)1) || ((Enum)((ItemInfo)value).Flags).HasFlag((Enum)(object)(ItemFlags)2)) { text = "yellow"; } else if (((Enum)((ItemInfo)value).Flags).HasFlag((Enum)(object)(ItemFlags)4)) { text = "red"; } if (value.IsReceiverRelatedToActivePlayer) { Text description = __instance.description; description.text += RDStringExtensions.GetWithSubstitution("archipelago.rankscreen.found", new KeyValuePair<string, string>[1] { new KeyValuePair<string, string>("item", "<color=" + text + ">" + ((ItemInfo)value).ItemDisplayName + "</color>") }); } else { Text description2 = __instance.description; description2.text += RDStringExtensions.GetWithSubstitution("archipelago.rankscreen.sentTo", new KeyValuePair<string, string>[2] { new KeyValuePair<string, string>("item", "<color=" + text + ">" + ((ItemInfo)value).ItemDisplayName + "</color>"), new KeyValuePair<string, string>("player", value.Player.Alias) }); } } } ItemsToSend.Clear(); JustSentLocations = Array.Empty<long>(); } [HarmonyPatch(typeof(Level_Montage), "SetLevelAsPassed")] [HarmonyPostfix] private static void MiracleDefibrillatorClearLocationPatch(Level_Montage __instance) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) bool flag = Plugin.Client.TrapManager.IsTrapActive(ScrambleCharactersTrapPatch.name); int num = ((!__instance.missedOnce && !((RDClass)__instance).game.GetPassedLevelWithoutCheckpoints()) ? 5 : ((!((RDClass)__instance).game.GetPassedLevelWithoutCheckpoints()) ? 4 : 14)); IEnumerable<long> enumerable = SendLocations((Level)49, Rank.op_Implicit(num)); if (!ItemsToSend.Any()) { Plugin.Logger.LogWarning((object)"Couldn't get items sent, possibly due to a network issue."); ((RDClass)__instance).game.statusText.SetStatusText("Couldn't get items sent.", (Color?)Color.red, 10f, false, true); } else { long[] array = enumerable.Where((long id) => !Plugin.Client.Session.Locations.AllLocationsChecked.Contains(id) || JustSentLocations.Contains(id)).ToArray(); Plugin.Logger.LogDebug((object)("IDs: " + string.Join(", ", enumerable) + " (of which " + string.Join(", ", array) + " are new)")); if (!enumerable.Any()) { ((RDClass)__instance).game.statusText.SetStatusText(RDString.Get("archipelago.rankscreen.nothing"), (Color?)null, 4f, false, false); return; } if (array.Length == 0) { ((RDClass)__instance).game.statusText.SetStatusText(RDString.Get("archipelago.rankscreen.nothingNew"), (Color?)null, 4f, false, false); return; } IEnumerable<string> values = array.Select((long id) => ((ItemInfo)ItemsToSend[id]).ItemDisplayName); ((RDClass)__instance).game.statusText.SetStatusText("Found " + string.Join(", ", values), (Color?)null, 10f, false, true); } if (flag) { Plugin.Logger.LogInfo((object)"Setting Room 1, Row 2 to Cole"); ((RDClass)__instance).game.rows[1].ent.character.ChangeCharacter((Character)13, (string)null); } } private static IEnumerable<long> SendLocations(Level level, Rank rank, bool bossLevelFailed = false) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_0040: Invalid comparison between Unknown and I4 //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) if (bossLevelFailed) { Plugin.Client.TrapManager.ClearActiveTraps(returnToQueue: false); return Array.Empty<long>(); } IEnumerable<long> ids = GetStageLocationIDsToClear(level, rank); if (Plugin.Client.Slot.endGoal == SlotData.EndGoal.HelpingHands && (int)level == 55 && ((Rank)(ref rank)).passed) { Plugin.Logger.LogInfo((object)"Setting goal achieved - Helping Hands"); Plugin.Client.Session.SetGoalAchieved(); } else if (Plugin.Client.Slot.endGoal != SlotData.EndGoal.HelpingHands) { bool flag = true; Rank val = Rank.op_Implicit(Plugin.Client.Slot.endGoal switch { SlotData.EndGoal.PerfectAll => 5, SlotData.EndGoal.ARankAll => 4, SlotData.EndGoal.BRankAll => 3, _ => throw new ArgumentOutOfRangeException($"End Goal ({Plugin.Client.Slot.endGoal}) not valid value."), }); foreach (Level level2 in Bindings.Levels) { Rank levelRank = Persistence.GetLevelRank(level2, false, -1); if (Rank.op_Implicit(val) > Rank.op_Implicit(((Rank)(ref levelRank)).ToNormal())) { flag = false; break; } } if (flag) { Plugin.Logger.LogInfo((object)"Setting goal achieved - Cleared all"); Plugin.Client.Session.SetGoalAchieved(); } } if (ids.Any((long id) => !Plugin.Client.Session.Locations.AllLocationsChecked.Contains(id))) { JustSentLocations = ids.Where((long id) => !Plugin.Client.Session.Locations.AllLocationsChecked.Contains(id)).ToArray(); Task.Run(() => Plugin.Client.Session.Locations.CompleteLocationChecksAsync(ids.ToArray())); Plugin.Client.TrapManager.ClearActiveTraps(returnToQueue: false); } else { Plugin.Client.TrapManager.ClearActiveTraps(returnToQueue: true); } return ids; } private static IEnumerator ScoutLocationChecks(long[] ids, int retries = 0) { Plugin.Logger.LogInfo((object)string.Format("Scouting location checks for ids {0}... (try {1})", string.Join(", ", ids), retries)); Task<Dictionary<long, ScoutedItemInfo>> scout = Task.Run(() => Plugin.Client.Session.Locations.ScoutLocationsAsync((HintCreationPolicy)0, ids)); yield return (object)new WaitUntil((Func<bool>)(() => scout.IsCompleted)); Plugin.Logger.LogInfo((object)"Completed scouting"); if (!scout.IsCompletedSuccessfully) { if (retries >= 3) { Plugin.Logger.LogError((object)$"Couldn't scout locations - on try #{retries}, retrying"); ((MonoBehaviour)Plugin.Instance).StartCoroutine(ScoutLocationChecks(ids, retries + 1)); } else { Plugin.Logger.LogError((object)"Couldn't scout locations"); } } else { ItemsToSend = scout.Result; } } private static Level GetCurrentLevel() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Invalid comparison between Unknown and I4 //IL_0064: Unknown result type (might be due to invalid IL or missing references) if (!Enum.TryParse<Level>(scnGame.internalIdentifier, out Level result)) { Plugin.Logger.LogError((object)("Couldn't find Level. Level identifier: " + scnGame.internalIdentifier)); Plugin.Client.TrapManager.ClearActiveTraps(returnToQueue: false); throw new ArgumentOutOfRangeException("Couldn't find level " + scnGame.internalIdentifier); } if (scnGame.instance.currentLevel.dogMode && (int)result == 26) { return (Level)84; } return result; } private static IEnumerable<long> GetStageLocationIDsToClear(Level level, Rank rank) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogDebug((object)"Getting locations to clear"); IEnumerable<long> locationsToClear = Bindings.LevelToStage[level].GetLocationsToClear(rank); Plugin.Logger.LogDebug((object)string.Format("Stage to clear: {0} with rank {1} ({2})", level, rank.internalValue, string.Join(", ", locationsToClear))); return locationsToClear; } } [HarmonyPatch] internal static class DeathLinkPatch { internal static bool enabled = true; [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static void ResetDeathLinkPatch() { Plugin.Logger.LogDebug((object)"Enabling DeathLink patch"); enabled = true; } [HarmonyPatch(typeof(RowEntity), "CrackAdvance")] [HarmonyPostfix] private static void SendDeathLinkOnCrackedHeartPatch(RowEntity __instance) { if (Plugin.Client.DeathLink != null && enabled && !(__instance.rowMisses < ((RDBase)__instance).game.currentLevel.missesToCrackHeart)) { Plugin.Logger.LogInfo((object)"Sending CrackAdvance death"); enabled = false; Plugin.Client.SendDeathLink(); } } [HarmonyPatch(typeof(scnGame), "FailLevel")] [HarmonyPostfix] private static void SendDeathLinkOnLevelFailPatch() { if (enabled) { Plugin.Logger.LogInfo((object)"Sending FailLevel death"); enabled = false; Plugin.Client.SendDeathLink(); } } [HarmonyPatch(typeof(scnGame), "FailLevelLite")] [HarmonyPostfix] private static void SendDeathLinkOnLevelFailLitePatch(scnGame __instance) { if (enabled && !(__instance.levelIdentifier == "Montage")) { Plugin.Logger.LogInfo((object)"Sending FailLevelLite death"); enabled = false; Plugin.Client.SendDeathLink(); } } [HarmonyPatch(typeof(LevelBase), "RunTag")] [HarmonyPostfix] private static void SendDeathLinkOnBeansHopperLossPatch(string tag) { if (enabled && scnGame.internalIdentifier == "BeansHopper" && tag == "miss" && scnGame.instance.currentLevel.i1 < 30) { Plugin.Logger.LogInfo((object)"Sending Beans Hopper death"); enabled = false; Plugin.Client.SendDeathLink(); } } } [HarmonyPatch(typeof(scnLevelSelect))] internal static class JanitorPatch { [HarmonyPatch("PerformEntityAction")] [HarmonyPrefix] private static void InteractJanitorPatch(scnLevelSelect __instance, ref bool __runOriginal) { SelectableEntity selectedEntity = __instance.selectedEntity; SelectableObject val = (SelectableObject)(object)((selectedEntity is SelectableObject) ? selectedEntity : null); if (val == null) { Plugin.Logger.LogWarning((object)"Current selectable object is null"); } else if (val.action != "talkToJanitor") { Plugin.Logger.LogDebug((object)"Current selectable object's action is not talkToJanitor"); } else { Plugin.Logger.LogError((object)"TODO: Implement janitor menu patch"); } } [HarmonyPatch("ShowRanksText")] [HarmonyPrefix] private static void TextJanitorPatch(int index, scnLevelSelect __instance, ref bool __runOriginal) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) SelectableEntity obj = __instance.selectableEntities[index]; SelectableObject val = (SelectableObject)(object)((obj is SelectableObject) ? obj : null); if (val == null) { Plugin.Logger.LogDebug((object)"Selectable entities is null"); return; } if (!((SelectableEntity)val).id.StartsWith("TalkToJanitor")) { Plugin.Logger.LogDebug((object)"Selected object is not Janitor, doing nothing"); return; } Plugin.Logger.LogDebug((object)"Overriding Janitor text"); __runOriginal = false; __instance.ChangeTextOutline(RDConstants.data.levelSelect_notPassedLevelTextOutline); __instance.SetDifficultyArrowsVisible(false); __instance.description.text = RDString.Get("mainMenu.Archipelago"); } [HarmonyPatch("PlaceJanitor")] [HarmonyPrefix] private static void PlaceJanitorPatch(ref bool __runOriginal) { Plugin.Logger.LogDebug((object)"Bypassing PlaceJanitor"); __runOriginal = false; } [HarmonyPatch("HideJanitor")] [HarmonyPrefix] private static void HideJanitorPatch(ref bool __runOriginal) { Plugin.Logger.LogWarning((object)"Bypassing HideJanitor. This should never be called assuming PlaceJanitor was bypassed!"); __runOriginal = false; } } [HarmonyPatch(typeof(scnLevelSelect))] internal static class LevelSelectVisualFixesPatch { private static readonly Level[] ForceDisplayAsRegularLevel; private static readonly Level[] ForceDisplayAsBossLevel; [HarmonyPatch("ShowRanksText")] [HarmonyPrefix] private static void ModifyLabelVisibilitiesPatch(int index, scnLevelSelect __instance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) SelectableEntity obj = __instance.selectableEntities[index]; SelectableCharacter val = (SelectableCharacter)(object)((obj is SelectableCharacter) ? obj : null); if (val != null && val.levels.TryGetValue(__instance.currentDifficulty, out var value)) { if (ForceDisplayAsRegularLevel.Contains(value)) { ((SelectableEntity)val).levelType = (LevelType)1; } else if (ForceDisplayAsBossLevel.Contains(value)) { ((SelectableEntity)val).levelType = (LevelType)2; } } } [HarmonyPatch("ShowRanksText")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> DoNotShowUnlockHintsPatch(IEnumerable<CodeInstruction> instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Expected O, but got Unknown return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldstr, (object)"levelSelect.condition.{0}", (string)null) }).SetOpcodeAndAdvance(OpCodes.Nop).SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .MatchForward(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldfld, (object)AccessTools.Field(typeof(scnLevelSelect), "currentDifficulty"), (string)null) }) .Advance(-1) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Nop) .SetOpcodeAndAdvance(OpCodes.Br_S) .MatchForward(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldstr, (object)"", (string)null) }) .InstructionEnumeration(); } static LevelSelectVisualFixesPatch() { Level[] array = new Level[6]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); ForceDisplayAsRegularLevel = (Level[])(object)array; ForceDisplayAsBossLevel = (Level[])(object)new Level[1] { (Level)16 }; } } [HarmonyPatch] internal static class RhythmDogtorLevelPatch { [HarmonyPatch(typeof(scnLevelSelect), "LoadLevelData")] [HarmonyPostfix] private static void AddRhythmDogtorPatch(scnLevelSelect __instance) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogDebug((object)"Creating Rhythm Dogtor entity"); SelectableCharacter val = (SelectableCharacter)__instance.FindSelectableEntity("3-X"); val.bpmHard = val.bpmNormal; ((SelectableEntity)val).hardEnabled = true; val.charactersHard = (Character[])(object)new Character[1] { (Character)22 }; val.levels[(Difficulty)2] = (Level)84; val.portraitsHard = (Character[])(object)new Character[2] { (Character)23, (Character)22 }; if (!GC.levelMetadata.ContainsKey((Level)84)) { Plugin.Logger.LogDebug((object)"Creating Rhythm Dogtor metadata"); LevelMetadata val2 = GC.levelMetadata[(Level)26]; GuestData guest = new GuestData { link = val2.guest1.link, linkType = val2.guest1.linkType, name = val2.guest1.name, type = "Mix" }; GuestData guest2 = new GuestData { link = "https://x.com/Morphious86", linkType = "Twitter", name = "Morphious86", type = "VocalsAndArt" }; LevelMetadata val3 = new LevelMetadata(); val3.id = "3-DOG"; val3.type = val2.type; val3.portraits = (Character[])(object)new Character[2] { (Character)23, (Character)22 }; val3.characters = (Character[])(object)new Character[1] { (Character)22 }; val3.guest1 = guest; val3.guest2 = guest2; val3.guest3 = null; val3.guest4 = null; LevelMetadata value = val3; GC.levelMetadata.Add((Level)84, value); } } [HarmonyPatch(typeof(scnLevelSelect), "GoToLevelSequence")] [HarmonyPrefix] private static void GoToRhythmDogtorPatch(ref string levelToGo, scnLevelSelect __instance) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 if ((int)__instance.currentDifficulty == 2 && (int)RDUtils.ParseEnum<Level>(levelToGo, (Level)0) == 84) { Plugin.Logger.LogInfo((object)"Going to Rhythm Dogtor..."); Persistence.SetLastPlayedLevel((Level)84); levelToGo = "Lesmis"; scnGame.loadDogMode = true; } } [HarmonyPatch(typeof(Rankscreen), "ShowAndSaveRank")] [HarmonyPrefix] private static void SaveRhythmDogtorRankPatch(bool bossLevelFailed, Rankscreen __instance) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (!bossLevelFailed && !(((RDBase)__instance).game.levelIdentifier != "Lesmis") && ((RDBase)__instance).game.currentLevel.dogMode) { Plugin.Logger.LogInfo((object)"Saving Rhythm Dogtor rank..."); Persistence.SetLevelRank((Level)84, ((RDBase)__instance).game.currentLevel.GetRankFromMistakes(), false, true); } } [HarmonyPatch(typeof(scnLevelSelect), "SetDifficulty")] [HarmonyPrefix] private static void ShowAdogPatch(Difficulty newDifficulty, scnLevelSelect __instance) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 Rank levelRank = Persistence.GetLevelRank((Level)26, false, -1); bool passed = ((Rank)(ref levelRank)).passed; if ((int)newDifficulty != 1) { if ((int)newDifficulty == 2) { __instance.FindSelectableEntity("3-X").gameObject.SetActive(true); HideSleepingPaige(); } } else if (passed) { __instance.FindSelectableEntity("3-X").gameObject.SetActive(false); HideSleepingPaige(); } void HideSleepingPaige() { if (((InvisibleEntity)__instance.sleepingPaige).shaderRenderer != null) { ((Entity)__instance.sleepingPaige).visible = false; } else { Plugin.Logger.LogWarning((object)"[RhythmDogtorLevelPatch] Sleeping Paige shader renderer is null"); } } } } [HarmonyPatch(typeof(scnRhythmWeightlifter))] internal static class RhythmWeightlifterPatch { [HarmonyPatch("PlayLevelDialogue")] [HarmonyPrefix] private static void DoNotShowDialoguePatch(ref bool __runOriginal) { __runOriginal = false; } } [HarmonyPatch(typeof(WalkingSelectableCharacter))] internal static class RunningCharactersPatch { [HarmonyPatch("UpdateCharacter")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> WalkingSelectableCharacterOverLockedLevelsPatch(IEnumerable<CodeInstruction> instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null); foreach (CodeInstruction item in val.Instructions()) { if (item.opcode == OpCodes.Call && (MethodInfo)item.operand == AccessTools.Method(typeof(RDUtils), "Locked", new Type[1] { typeof(Level) }, (Type[])null)) { item.opcode = OpCodes.Ldc_I4_0; } } return val.InstructionEnumeration(); } } [HarmonyPatch] internal static class SkipCutscenePatch { [HarmonyPatch(typeof(Persistence), "GetPlayedPassedLevelCutscene")] [HarmonyPatch(typeof(Persistence), "GetPlayedPostAct2Cutscene")] [HarmonyPatch(typeof(Persistence), "GetPlayedPostAct3Cutscene")] [HarmonyPatch(typeof(Persistence), "GetPlayedPostAct4Cutscene")] [HarmonyPatch(typeof(Persistence), "GetPlayedPreAct5Cutscene")] [HarmonyPatch(typeof(Persistence), "GetPlayedPreAct6Cutscene")] [HarmonyPatch(typeof(Persistence), "GetPlayedHaileyDuetIntroduction")] [HarmonyPatch(typeof(Persistence), "GetPlayedPreBitternessCutscene")] [HarmonyPatch(typeof(Persistence), "GetPlayedRooftopCutscene")] [HarmonyPrefix] private static void PersistenceDoNotPlayCutscenePatch(ref bool __result, ref bool __runOriginal) { __result = true; __runOriginal = false; } [HarmonyPatch(typeof(scnLevelSelect), "MovePaigeToVendingMachine")] [HarmonyPatch(typeof(scnLevelSelect), "PlayAct5Intro")] [HarmonyPatch(typeof(scnLevelSelect), "CheckForCutscene")] [HarmonyPatch(typeof(scnBase), "GoToCutscene")] [HarmonyPrefix] private static void LevelSelectDoNotPlayCutscenePatch(ref bool __runOriginal) { __runOriginal = false; } [HarmonyPatch(typeof(scnLevelSelect), "Start")] [HarmonyPostfix] private static void SkipStoryAndCutscenePatch(scnLevelSelect __instance) { scnLevelSelect.bitternessWarningPlayed = true; __instance.voidItemProgress = 3; __instance.selectableEntities.Find((SelectableEntity entity) => entity.id == "VoidItem1").normalEnabled = true; __instance.selectableEntities.Find((SelectableEntity entity) => entity.id == "VoidItem2").normalEnabled = true; __instance.selectableEntities.Find((SelectableEntity entity) => entity.id == "VoidItem3").normalEnabled = true; } } [HarmonyPatch(typeof(scnGame))] internal static class SkipTutorialPatch { [HarmonyPatch("Start")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> FixLesmisPatch(IEnumerable<CodeInstruction> instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldstr, (object)"Level_", (string)null) }).Advance(3).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Ldstr, (object)", Assembly-CSharp"), new CodeInstruction(OpCodes.Call, (object)AccessTools.Method("System.String:Concat", new Type[2] { typeof(string), typeof(string) }, (Type[])null)) }) .InstructionEnumeration(); } [HarmonyPatch("Start")] [HarmonyPrefix] private static void DoNotLoadTutorialPatch() { if (scnGame.internalIdentifier == "Montage") { Plugin.Logger.LogDebug((object)"Level is 7-X, not skipping tutorial"); return; } Plugin.Logger.LogDebug((object)$"Level {scnGame.internalIdentifier}: Forcing attemptToLoadTutorial from {scnGame.attemptToLoadTutorial} to false"); scnGame.attemptToLoadTutorial = false; } [HarmonyPatch(typeof(Persistence), "GetFirstTimePlaying")] [HarmonyPrefix] private static void DoNotLoadIntroPatch(ref bool __result, ref bool __runOriginal) { Plugin.Logger.LogDebug((object)"Forcing first_time to false"); __runOriginal = false; __result = false; } } [HarmonyPatch(typeof(Persistence))] internal static class StateReplicationPatch { [HarmonyPatch("SetPaigeEnding")] [HarmonyPostfix] private static void ReplicatePaigeStaysPatch(bool paigeStays) { Plugin.Logger.LogInfo((object)string.Format("Replicating {0} to {1}", "paige_stays", paigeStays)); Plugin.Client.Session.DataStorage[(Scope)3, "paige_stays"] = DataStorageElement.op_Implicit(paigeStays); } [HarmonyPatch("SetIanDesktopLogin")] [HarmonyPostfix] private static void ReplicateIansDesktopUnlockedPatch(bool ianDesktopLogin) { Plugin.Logger.LogInfo((object)string.Format("Replicating {0} to {1}", "ian_desktop_login", ianDesktopLogin)); Plugin.Client.Session.DataStorage[(Scope)3, "ian_desktop_login"] = DataStorageElement.op_Implicit(ianDesktopLogin); } internal static async Task InitializeSync() { Task task = InitializeKey<bool>("paige_stays", Persistence.SetPaigeEnding, delegate { bool flag = Plugin.Random.Next() % 2 == 1; Plugin.Client.Session.DataStorage[(Scope)3, "paige_stays"] = DataStorageElement.op_Implicit(flag); return flag; }); Task task2 = InitializeKey<bool>("ian_desktop_login", Persistence.SetIanDesktopLogin, delegate { Plugin.Client.Session.DataStorage[(Scope)3, "ian_desktop_login"] = DataStorageElement.op_Implicit(false); return false; }); Plugin.Logger.LogInfo((object)"Waiting for initialization to complete..."); await Task.WhenAll(task, task2); Plugin.Logger.LogInfo((object)"Initialization completed!"); static async Task InitializeKey<T>(string key, Action<T> executeExisting, Func<T> valueDoesntExist) where T : notnull { JToken val = await Plugin.Client.Session.DataStorage[(Scope)3, key].GetAsync(); if ((int)val.Type != 10 && (int)val.Type != 11) { T val2 = val.ToObject<T>(); Plugin.Logger.LogInfo((object)$"Found {key} state: {val2}"); executeExisting(val2); } else { T val3 = valueDoesntExist(); Plugin.Logger.LogInfo((object)$"Setting initial {key} state to {val3}"); } } } } [HarmonyPatch] internal static class TrapManagerPatch { [HarmonyPatch(typeof(scnLevelSelect), "SelectCharacter")] [HarmonyPrefix] private static void ApplyApplicableTrapPreviewPatch(scnLevelSelect __instance) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) SelectableEntity selectedEntity = __instance.selectedEntity; SelectableCharacter val = (SelectableCharacter)(object)((selectedEntity is SelectableCharacter) ? selectedEntity : null); if (val != null) { Level level = val.levels[__instance.currentDifficulty]; Plugin.Client.TrapManager.ApplyApplicableTraps(level); } } [HarmonyPatch(typeof(HeartMonitor), "Show")] [HarmonyPostfix] private static void ShowTrapNameOnPhonePatch(HeartMonitor __instance) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown IEnumerable<string> previewTrapNames = Plugin.Client.TrapManager.GetPreviewTrapNames(); if (!previewTrapNames.Any()) { return; } Plugin.Logger.LogDebug((object)"Instantiating guest credit for preview traps"); __instance.isGuestCreditShown = true; foreach (string item in previewTrapNames) { Plugin.Logger.LogDebug((object)("Creating guest credit for " + item)); GuestData val = new GuestData { type = null, link = null, linkType = "other-unused", name = item }; GameObject val2 = Object.Instantiate<GameObject>(__instance.guestPrefab, (Transform)(object)__instance.creditsContainer); HeartMonitorGuest component = val2.GetComponent<HeartMonitorGuest>(); component.Setup(val); __instance.creditStrings.Add(val.name); __instance.links.Add(val.link); __instance.creditsElements.Add(val2); component.nameText.text = item; } } [HarmonyPatch(typeof(scnLevelSelect), "GoToLevelSequence")] [HarmonyPostfix] private static void ApplyApplicableTrapsPatch(string levelToGo, scnLevelSelect __instance) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogInfo((object)"Promoting preview traps to active traps"); if ((levelToGo == "Lesmis" || levelToGo == "RhythmWeightlifter") ? true : false) { Plugin.Logger.LogInfo((object)"Going to Rhythm Dogtor/Rhythm Weightlifter (dog) - applying preview traps now"); Level level = RDUtils.ParseEnum<Level>(levelToGo, (Level)0); Plugin.Client.TrapManager.ApplyApplicableTraps(level); } Plugin.Client.TrapManager.PromotePreviewTrapsToActiveTraps(); } [HarmonyPatch(typeof(scnBase), "GoToLevel")] [HarmonyPostfix] private static void ApplyApplicableTrapsOnLevelChangePatch(string path) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) Level val = RDUtils.ParseEnum<Level>(path, (Level)0); if ((int)val == 50) { Plugin.Logger.LogWarning((object)$"Applying traps for {val} immediately"); Plugin.Client.TrapManager.ApplyApplicableTraps(val); Plugin.Client.TrapManager.PromotePreviewTrapsToActiveTraps(); } } [HarmonyPatch(typeof(scnGame), "Quit")] [HarmonyPostfix] private static void RestoreActiveTrapsOnAbandonPatch() { Plugin.Logger.LogInfo((object)"Clearing active traps (returning to queue)"); Plugin.Client.TrapManager.ClearActiveTraps(returnToQueue: true); } } [HarmonyPatch(typeof(scnLevelSelect))] internal static class UnlockItemPatch { [HarmonyPatch("LoadLevelData")] [HarmonyPostfix] private static void UnlockEntitiesWithItemsPatch(scnLevelSelect __instance) { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_057f: Unknown result type (might be due to invalid IL or missing references) //IL_05aa: Unknown result type (might be due to invalid IL or missing references) //IL_05d5: Unknown result type (might be due to invalid IL or missing references) //IL_05f3: Unknown result type (might be due to invalid IL or missing references) //IL_0604: Unknown result type (might be due to invalid IL or missing references) //IL_0609: Unknown result type (might be due to invalid IL or missing references) //IL_0628: Unknown result type (might be due to invalid IL or missing references) //IL_06d3: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogInfo((object)"Checking for extra unlocks"); Plugin.Logger.LogInfo((object)"Checking for regions to unlock"); bool flag = false; foreach (ItemInfo item in Plugin.Client.Session.Items.AllItemsReceived) { Plugin.Logger.LogDebug((object)$"Processing item {item.ItemName} ({item.ItemId})"); Level value2; if (Bindings.KeyItemIdToWard.TryGetValue(item.ItemId, out var value)) { if (value == Region.Basement) { flag = true; } Plugin.Logger.LogInfo((object)$"Unlocking entrance {value}"); scnLevelSelect.instance.UnlockEntrance(value); } else if (Bindings.ItemIdToLevel.TryGetValue(item.ItemId, out value2)) { Plugin.Logger.LogInfo((object)string.Format("[{0}] Unlocking stage item {1} ({2}, {3})", "UnlockItemPatch", item.ItemName, item.ItemId, value2)); Persistence.SetLevelRank(value2, Rank.op_Implicit(-1), false, false); } } if (!flag) { Plugin.Logger.LogInfo((object)"Locking basement"); __instance.LockEntrance("GoToBasement"); } Plugin.Logger.LogInfo((object)"Moving 1-CNY"); __instance.FindSelectableEntity("1-CNY").gamePosition.x = -564f; if (Plugin.Client.Slot.endGoal == SlotData.EndGoal.HelpingHands) { Plugin.Logger.LogInfo((object)"Moving X-1 to the basement"); SelectableEntity val = __instance.FindSelectableEntity("X-1"); val.group = 3; int index = __instance.selectableEntities.FindIndex((SelectableEntity entity) => ((Object)entity.gameObject).name == "BasementComputer") + 1; val.gamePosition = new Vector2(2480f, 53f); __instance.selectableEntities.Remove(val); __instance.selectableEntities.Insert(index, val); if (Bindings.ActBoss.Values.All((Level[] levels) => levels.All(delegate(Level level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Rank levelRank2 = Persistence.GetLevelRank(level, false, -1); return ((Rank)(ref levelRank2)).passed; }))) { Plugin.Logger.LogInfo((object)"Unlocking Art Room and X-0 - Helping Hands"); __instance.UnlockEntrance(__instance.FindSelectableEntity("GoToArtRoom")); Persistence.SetLevelRank((Level)55, Rank.op_Implicit(-1), false, false); } } GameObject.Find("/Scene/Corridor/VendingMachinePaige").gameObject.SetActive(false); foreach (SelectableEntity item2 in __instance.selectableEntities.Where((SelectableEntity entity) => entity.id.StartsWith("X-") && !Regex.IsMatch(entity.id, "/[0-9]/"))) { item2.normalEnabled = true; } __instance.GetSelectableEntity("2-B1").normalEnabled = true; __instance.GetSelectableEntity("5-B1").normalEnabled = true; foreach (SelectableEntity item3 in __instance.selectableEntities.Where((SelectableEntity entity) => entity.id.StartsWith("3-"))) { item3.normalEnabled = true; if (item3.id != "3-X") { item3.hardEnabled = true; } } foreach (SelectableEntity item4 in __instance.selectableEntities.Where((SelectableEntity entity) => entity.id.StartsWith("5-"))) { item4.normalEnabled = true; bool flag2; switch (item4.id) { case "5-1": case "5-2": case "5-3": flag2 = true; break; default: flag2 = false; break; } if (flag2) { item4.hardEnabled = true; } } GameObject.Find("/Scene/Levels Container/Vertical Movement/5-2").SetActive(true); GameObject.Find("/Scene/Levels Container/Vertical Movement/5-3").SetActive(true); GameObject.Find("/Scene/Levels Container/Vertical Movement/5-X").SetActive(true); SelectableEntity selectableEntity = __instance.GetSelectableEntity("1-CNY"); selectableEntity.normalEnabled = true; selectableEntity.gameObject.SetActive(true); SelectableEntity selectableEntity2 = __instance.GetSelectableEntity("1-BOO"); selectableEntity2.normalEnabled = true; selectableEntity2.gameObject.SetActive(true); __instance.GetSelectableEntity("MD-1").normalEnabled = true; __instance.GetSelectableEntity("1-X").hardEnabled = true; SelectableEntity selectableEntity3 = __instance.GetSelectableEntity("2-X"); selectableEntity3.normalEnabled = true; selectableEntity3.hardEnabled = true; SelectableEntity selectableEntity4 = __instance.GetSelectableEntity("5-1"); selectableEntity4.normalEnabled = true; selectableEntity4.hardEnabled = true; SelectableEntity[] array = (SelectableEntity[])(object)new SelectableEntity[3] { __instance.FindSelectableEntity("6-2"), __instance.FindSelectableEntity("6-X"), __instance.FindSelectableEntity("7-1") }; for (int num = 0; num < array.Length; num++) { array[num].normalEnabled = true; } GameObject.Find("/Scene/Levels Container/Vertical Movement/6-2").SetActive(true); __instance.GetSelectableEntity("VoidItem1").normalEnabled = true; __instance.GetSelectableEntity("VoidItem2").normalEnabled = true; __instance.GetSelectableEntity("VoidItem3").normalEnabled = true; ((tk2dBaseSprite)__instance.abandonedWard.voidItems[0]).color = new Color(1f, 1f, 1f, 1f); ((tk2dBaseSprite)__instance.abandonedWard.voidItems[1]).color = new Color(1f, 1f, 1f, 1f); ((tk2dBaseSprite)__instance.abandonedWard.voidItems[2]).color = new Color(1f, 1f, 1f, 1f); __instance.GetSelectableEntity("7-X").normalEnabled = true; Persistence.SetLevelRank((Level)49, Rank.op_Implicit(-1), false, true); Rank levelRank = Persistence.GetLevelRank((Level)49, false, -1); if (((Rank)(ref levelRank)).passed) { __instance.GetSelectableEntity("7-X2").normalEnabled = true; Persistence.SetLevelRank((Level)50, Rank.op_Implicit(-1), false, true); } SelectableEntity selectableEntity5 = __instance.GetSelectableEntity("X-0"); selectableEntity5.normalEnabled = true; selectableEntity5.gameObject.SetActive(true); SelectableEntity selectableEntity6 = __instance.GetSelectableEntity("X-1"); selectableEntity6.normalEnabled = true; selectableEntity6.gameObject.SetActive(true); foreach (Act value3 in Enum.GetValues(typeof(Act))) { if (!HasUnlockedBossSong(value3)) { continue; } Plugin.Logger.LogDebug((object)$"Unlocking act {value3}"); Level[] array2 = Bindings.ActBoss[value3]; for (int num = 0; num < array2.Length; num++) { int num2 = (int)array2[num]; Persistence.SetLevelRank((Level)num2, Rank.op_Implicit(-1), false, false); if (num2 == 49) { __instance.UnlockEntrance(Region.RecordsRoom); } } } } [HarmonyPatch("LoadLevelData")] [HarmonyPatch("LevelJustPassedSequenceCo")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> DoNotUnlockEntrancesPatch(IEnumerable<CodeInstruction> instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null); foreach (CodeInstruction item in val.Instructions()) { if (item.opcode == OpCodes.Call && (MethodInfo)item.operand == AccessTools.Method(typeof(scnLevelSelect), "UnlockEntrance", new Type[1] { typeof(SelectableEntity) }, (Type[])null)) { item.opcode = OpCodes.Nop; } } return val.InstructionEnumeration(); } [HarmonyPatch("UnlockNightShiftLevel")] [HarmonyPrefix] private static void DoNotUnlockNightShiftLevelPatch(ref bool __runOriginal) { __runOriginal = false; } [HarmonyPatch("LoadLevelData")] [HarmonyPatch("Awake")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> DoNotUnlockLevelsPatch(IEnumerable<CodeInstruction> instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null); List<CodeInstruction> list = val.Instructions(); for (int i = 0; i < list.Count; i++) { CodeInstruction val2 = list[i]; if (!(val2.opcode == OpCodes.Call)) { continue; } int num; if ((MethodInfo)val2.operand == AccessTools.Method(typeof(Persistence), "SetLevelRank", new Type[4] { typeof(string), typeof(Rank), typeof(bool), typeof(bool) }, (Type[])null)) { num = -9; } else { if (!((MethodInfo)val2.operand == AccessTools.Method(typeof(Persistence), "SetLevelRank", new Type[4] { typeof(Level), typeof(Rank), typeof(bool), typeof(bool) }, (Type[])null))) { continue; } num = -5; } for (int j = num; j <= 1; j++) { list[i + j].opcode = OpCodes.Nop; } } return val.InstructionEnumeration(); } [HarmonyPatch("ShowRanksText")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> CustomPopulateSelectedVerticalDestinationsPatch(IEnumerable<CodeInstruction> instructions) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown int match = 0; return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(true, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Stfld, (object)AccessTools.Field(typeof(scnLevelSelect), "selectedVerticalDestinations"), (string)null) }).Repeat((Action<CodeMatcher>)delegate(CodeMatcher codeMatcher) { codeMatcher.Advance(1); switch (match) { case 0: codeMatcher.Insert((CodeInstruction[])(object)new CodeInstruction[1] { Transpilers.EmitDelegate<Action>((Action)delegate { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown Plugin.Logger.LogInfo((object)"Setting Ian's laptop vertical destinations"); scnLevelSelect.instance.selectedVerticalDestinations = new List<LevelSelectDestination>(); if (Plugin.Client.Session.Items.AllItemsReceived.Any((ItemInfo item) => item.ItemId == 56)) { Plugin.Logger.LogInfo((object)"Adding sleevePaint"); scnLevelSelect.instance.selectedVerticalDestinations.Add(new LevelSelectDestination("sleevePaint", RDString.Get("levelSelect.SleevePaint"), RDString.Get("levelSelect.SleevePaint.details"))); } Plugin.Logger.LogInfo((object)"Adding IanDesktop"); scnLevelSelect.instance.selectedVerticalDestinations.Add(new LevelSelectDestination("IanDesktop", RDString.Get("levelSelect.IanDesktop"), RDString.Get($"levelSelect.IanDesktop.details.{((!Persistence.GetIanDesktopLogin()) ? 1 : 2)}"))); }) }); break; case 1: codeMatcher.Insert((CodeInstruction[])(object)new CodeInstruction[1] { Transpilers.EmitDelegate<Action>((Action)delegate { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown Plugin.Logger.LogInfo((object)"Setting main elevator vertical destinations"); scnLevelSelect.instance.selectedVerticalDestinations = new List<LevelSelectDestination>(); if (Plugin.Client.Session.Items.AllItemsReceived.Any((ItemInfo item) => item.ItemId == Bindings.RegionToKeyID[Region.RecordsRoom])) { Plugin.Logger.LogInfo((object)"Adding ExitRecordsRoom"); scnLevelSelect.instance.selectedVerticalDestinations.Add(new LevelSelectDestination("ExitRecordsRoom", RDString.Get("levelSelect.act6"), RDString.Get("levelSelect.GoToRecordsRoom.day"))); } if (HasUnlockedBossSong(Act.Act7)) { Plugin.Logger.LogInfo((object)"Adding ExitVoid"); scnLevelSelect.instance.selectedVerticalDestinations.Add(new LevelSelectDestination("ExitVoid", RDString.Get("levelSelect.finale"), RDString.Get("levelSelect.GoToVoid.day"))); } }) }); break; default: Plugin.Logger.LogWarning((object)"Matched more than two vertical destinations - plugin may need update!!"); break; } match++; }, (Action<string>)null).Start() .MatchForward(true, (CodeMatch[])(object)new CodeMatch[3] { new CodeMatch((OpCode?)OpCodes.Ldc_I4_S, (object)(sbyte)48, (string)null), new CodeMatch((OpCode?)OpCodes.Call, (object)AccessTools.Method(typeof(RDUtils), "Passed", (Type[])null, (Type[])null), (string)null), new CodeMatch((OpCode?)OpCodes.Brfalse, (object)null, (string)null) }) .ThrowIfInvalid("Couldn't find Level.Bitterness.Passed()") .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Pop, (object)null) }) .SetOpcodeAndAdvance(OpCodes.Br) .InstructionEnumeration(); } [HarmonyPatch("UpdateCharacters")] [HarmonyPostfix] private static void UpdateCharactersPatch(scnLe
plugins/gg.archipelago.rhythmdoctor/Archipelago.MultiClient.Net.dll
Decompiled 14 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.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 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(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.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")] internal interface IConcurrentHashSet<T> { bool TryAdd(T item); bool Contains(T item); void UnionWith(T[] otherSet); T[] ToArray(); ReadOnlyCollection<T> AsToReadOnlyCollection(); ReadOnlyCollection<T> AsToReadOnlyCollectionExcept(IConcurrentHashSet<T> 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<RoomInfoPacket> ConnectAsync(); Task<LoginResult> 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<LoginResult> loginResultTask = new TaskCompletionSource<LoginResult>(); private TaskCompletionSource<RoomInfoPacket> roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>(); 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<RoomInfoPacket> ConnectAsync() { roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>(); 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<LoginResult> 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<LoginResult>(); 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<T>(TaskCompletionSource<T> 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<RoomInfoPacket> 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<string, object> 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<TA, TB> : IEnumerable<KeyValuePair<TB, TA>>, IEnumerable { private readonly Dictionary<TA, TB> aToB = new Dictionary<TA, TB>(); private readonly Dictionary<TB, TA> bToA = new Dictionary<TB, TA>(); 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<KeyValuePair<TB, TA>> 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<string> Games { get; set; } = new List<string>(); [JsonProperty("slots")] public List<int> Slots { get; set; } = new List<int>(); [JsonProperty("tags")] public List<string> Tags { get; set; } = new List<string>(); [JsonProperty("data")] public Dictionary<string, JToken> 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<string, object> SlotData { get; set; } [JsonProperty("slot_info")] public Dictionary<int, NetworkSlot> 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<string, JToken> 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<string, Permissions> 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<string, string> 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<string, Permissions> Permissions { get; set; } = new Dictionary<string, Permissions>(); [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<string, JToken> 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<Color> { 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<string, GameData> Games { get; set; } = new Dictionary<string, GameData>(); } public class DataStorageElement { internal DataStorageElementContext Context; internal List<OperationSpecification> Operations = new List<OperationSpecification>(0); internal DataStorageHelper.DataStorageUpdatedHandler Callbacks; internal Dictionary<string, JToken> AdditionalArguments = new Dictionary<string, JToken>(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<OperationSpecification>(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<bool> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<int> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<long> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<decimal> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<double> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<float> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<string> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<object> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator bool(DataStorageElement e) { return RetrieveAndReturnBoolValue<bool>(e); } public static implicit operator bool?(DataStorageElement e) { return RetrieveAndReturnBoolValue<bool?>(e); } public static implicit operator int(DataStorageElement e) { return RetrieveAndReturnDecimalValue<int>(e); } public static implicit operator int?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<int?>(e); } public static implicit operator long(DataStorageElement e) { return RetrieveAndReturnDecimalValue<long>(e); } public static implicit operator long?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<long?>(e); } public static implicit operator float(DataStorageElement e) { return RetrieveAndReturnDecimalValue<float>(e); } public static implicit operator float?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<float?>(e); } public static implicit operator double(DataStorageElement e) { return RetrieveAndReturnDecimalValue<double>(e); } public static implicit operator double?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<double?>(e); } public static implicit operator decimal(DataStorageElement e) { return RetrieveAndReturnDecimalValue<decimal>(e); } public static implicit operator decimal?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<decimal?>(e); } public static implicit operator string(DataStorageElement e) { return RetrieveAndReturnStringValue(e); } public static implicit operator bool[](DataStorageElement e) { return RetrieveAndReturnArrayValue<bool[]>(e); } public static implicit operator int[](DataStorageElement e) { return RetrieveAndReturnArrayValue<int[]>(e); } public static implicit operator long[](DataStorageElement e) { return RetrieveAndReturnArrayValue<long[]>(e); } public static implicit operator decimal[](DataStorageElement e) { return RetrieveAndReturnArrayValue<decimal[]>(e); } public static implicit operator double[](DataStorageElement e) { return RetrieveAndReturnArrayValue<double[]>(e); } public static implicit operator float[](DataStorageElement e) { return RetrieveAndReturnArrayValue<float[]>(e); } public static implicit operator string[](DataStorageElement e) { return RetrieveAndReturnArrayValue<string[]>(e); } public static implicit operator object[](DataStorageElement e) { return RetrieveAndReturnArrayValue<object[]>(e); } public static implicit operator List<bool>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<bool>>(e); } public static implicit operator List<int>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<int>>(e); } public static implicit operator List<long>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<long>>(e); } public static implicit operator List<decimal>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<decimal>>(e); } public static implicit operator List<double>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<double>>(e); } public static implicit operator List<float>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<float>>(e); } public static implicit operator List<string>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<string>>(e); } public static implicit operator List<object>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<object>>(e); } public static implicit operator Array(DataStorageElement e) { return RetrieveAndReturnArrayValue<Array>(e); } public static implicit operator JArray(DataStorageElement e) { return RetrieveAndReturnArrayValue<JArray>(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<BigInteger>(e); } public static implicit operator BigInteger?(DataStorageElement e) { return RetrieveAndReturnBigIntegerValue<BigInteger?>(e); } private static T RetrieveAndReturnBigIntegerValue<T>(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<T>() ? 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<T>()) { bigInteger = Activator.CreateInstance<BigInteger>(); } 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<T>() ? 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<T> GetAsync<T>() { return GetAsync().ContinueWith((Task<JToken> r) => r.Result.ToObject<T>()); } public Task<JToken> GetAsync() { return Context.GetAsync(Context.Key); } private static T RetrieveAndReturnArrayValue<T>(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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Invalid comparison between Unknown and I4 //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if (e.cachedValue != null) { return ((JToken)(JArray)e.cachedValue).ToObject<T>(); } JArray val = (JArray)(((object)e.Context.GetData(e.Context.Key).ToObject<JArray>()) ?? ((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 {OperationType.Add} 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<JArray>()) ?? ((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<T>(); } 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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: 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 {OperationType.Mul} 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<T>(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject<T>(); } bool? flag = e.Context.GetData(e.Context.Key).ToObject<bool?>() ?? ((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<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static T RetrieveAndReturnDecimalValue<T>(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject<T>(); } decimal? num = e.Context.GetData(e.Context.Key).ToObject<decimal?>(); if (!num.HasValue && !IsNullable<T>()) { num = Activator.CreateInstance<decimal>(); } 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<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static bool IsNullable<T>() { if (typeof(T).IsGenericType) { return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } return false; } public T To<T>() { if (Operations.Count != 0) { throw new InvalidOperationException("DataStorageElement.To<T>() cannot be used together with other operations on the DataStorageElement"); } return Context.GetData(Context.Key).ToObject<T>(); } 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<string, DataStorageHelper.DataStorageUpdatedHandler> AddHandler { get; set; } internal Action<string, DataStorageHelper.DataStorageUpdatedHandler> RemoveHandler { get; set; } internal Func<string, JToken> GetData { get; set; } internal Action<string, JToken> Initialize { get; set; } internal Func<string, Task<JToken>> GetAsync { get; set; } public override string ToString() { return "Key: " + Key; } } public class GameData { [JsonProperty("location_name_to_id")] public Dictionary<string, long> LocationLookup { get; set; } = new Dictionary<string, long>(); [JsonProperty("item_name_to_id")] public Dictionary<string, long> ItemLookup { get; set; } = new Dictionary<string, long>(); [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<SerializableItemInfo>(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<T>(Palette<T> 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<ClientWebSocket>, IArchipelagoSocketHelper { public Uri Uri { get; } internal ArchipelagoSocketHelper(Uri hostUri) : base(CreateWebSocket(), 1024) { Uri = hostUri; } private static ClientWebSocket CreateWebSocket() { return new 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<Exception> errors = new List<Exception>(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<T> where T : WebSocket { private static readonly ArchipelagoPacketConverter Converter = new ArchipelagoPacketConverter(); private readonly BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>> sendQueue = new BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>>(); 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<Task?>)PollingLoop); Task.Run((Func<Task?>)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<string> ReadMessageAsync(byte[] buffer) { using MemoryStream readStream = new MemoryStream(buffer.Length); WebSocketReceiveResult result; do { result = await Socket.ReceiveAsync(new ArraySegment<byte>(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<ArchipelagoPacketBase> { packet }); } public void SendMultiplePackets(List<ArchipelagoPacketBase> packets) { SendMultiplePackets(packets.ToArray()); } public void SendMultiplePackets(params ArchipelagoPacketBase[] packets) { SendMultiplePacketsAsync(packets).Wait(); } public Task SendPacketAsync(ArchipelagoPacketBase packet) { return SendMultiplePacketsAsync(new List<ArchipelagoPacketBase> { packet }); } public Task SendMultiplePacketsAsync(List<ArchipelagoPacketBase> packets) { return SendMultiplePacketsAsync(packets.ToArray()); } public Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets) { TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>(); foreach (ArchipelagoPacketBase item in packets) { sendQueue.Add(new Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>(item, taskCompletionSource)); } return taskCompletionSource.Task; } private async Task HandleSendBuffer() { List<ArchipelagoPacketBase> list = new List<ArchipelagoPacketBase>(); List<TaskCompletionSource<bool>> tasks = new List<TaskCompletionSource<bool>>(); Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> tuple = sendQueue.Take(); list.Add(tuple.Item1); tasks.Add(tuple.Item2); Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> 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<byte>(messageBuffer, num, num2), WebSocketMessageType.Text, endOfMessage, CancellationToken.None); } foreach (TaskCompletionSource<bool> 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<ArchipelagoPacketBase> list = null; try { list = JsonConvert.DeserializeObject<List<ArchipelagoPacketBase>>(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<string, JToken> additionalArguments); private readonly Dictionary<string, DataStorageUpdatedHandler> onValueChangedEventHandlers = new Dictionary<string, DataStorageUpdatedHandler>(); private readonly Dictionary<Guid, DataStorageUpdatedHandler> operationSpecificCallbacks = new Dictionary<Guid, DataStorageUpdatedHandler>(); private readonly Dictionary<string, TaskCompletionSource<JToken>> asyncRetrievalTasks = new Dictionary<string, TaskCompletionSource<JToken>>(); 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<string, JToken> datum in retrievedPacket.Data) { if (asyncRetrievalTasks.TryGetValue(datum.Key, out var value3)) { value3.TrySetResult(datum.Value); asyncRetrievalTasks.Remove(datum.Key); } } } private Task<JToken> GetAsync(string key) { if (asyncRetrievalTasks.TryGetValue(key, out var value)) { return value.Task; } TaskCompletionSource<JToken> taskCompletionSource = new TaskCompletionSource<JToken>(); 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<JToken> 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<string, JToken> dictionary = e.AdditionalArguments ?? new Dictionary<string, JToken>(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<string, DataStorageUpdatedHandler> 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<string, DataStorageUpdatedHandler> 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<Hint[]>(); } public Task<Hint[]> GetHintsAsync(int? slot = null, int? team = null) { return GetHintsElement(slot, team).GetAsync<Hint[]>(); } public void TrackHints(Action<Hint[]> onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null) { GetHintsElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary<string, JToken> x) { onHintsUpdated(newValue.ToObject<Hint[]>()); }; if (retrieveCurrentlyUnlockedHints) { GetHintsAsync(slot, team).ContinueWith(delegate(Task<Hint[]> t) { onHintsUpdated(t.Result); }); } } public Dictionary<string, object> GetSlotData(int? slot = null) { return GetSlotData<Dictionary<string, object>>(slot); } public T GetSlotData<T>(int? slot = null) where T : class { return GetSlotDataElement(slot).To<T>(); } public Task<Dictionary<string, object>> GetSlotDataAsync(int? slot = null) { return GetSlotDataAsync<Dictionary<string, object>>(slot); } public Task<T> GetSlotDataAsync<T>(int? slot = null) where T : class { return GetSlotDataElement(slot).GetAsync<T>(); } public Dictionary<string, string[]> GetItemNameGroups(string game = null) { return GetItemNameGroupsElement(game).To<Dictionary<string, string[]>>(); } public Task<Dictionary<string, string[]>> GetItemNameGroupsAsync(string game = null) { return GetItemNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>(); } public Dictionary<string, string[]> GetLocationNameGroups(string game = null) { return GetLocationNameGroupsElement(game).To<Dictionary<string, string[]>>(); } public Task<Dictionary<string, string[]>> GetLocationNameGroupsAsync(string game = null) { return GetLocationNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>(); } public ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).To<ArchipelagoClientState?>().GetValueOrDefault(); } public Task<ArchipelagoClientState> GetClientStatusAsync(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).GetAsync<ArchipelagoClientState?>().ContinueWith((Task<ArchipelagoClientState?> r) => r.Result.GetValueOrDefault()); } public void TrackClientStatus(Action<ArchipelagoClientState> onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null) { GetClientStatusElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary<string, JToken> x) { onStatusUpdated(newValue.ToObject<ArchipelagoClientState>());