Decompiled source of SuperhotArchipelago v0.2.2
Mods/SuperhotArchipelago.dll
Decompiled a day agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; using Archipelago.MultiClient.Net; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.Models; using HarmonyLib; using InputSystem; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SuperhotArchipelago.Core; using SuperhotArchipelago.Patches; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Mod), "SuperhotArchipelago", "0.2.2", "My-Key-Morris", null)] [assembly: MelonGame("SUPERHOT_Team", "SUPERHOT")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("SuperhotArchipelago")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+ac2738bb885f50d160aa9e05b060e49b13323e18")] [assembly: AssemblyProduct("SuperhotArchipelago")] [assembly: AssemblyTitle("SuperhotArchipelago")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } 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 SuperhotArchipelago.Patches { [HarmonyPatch(typeof(piOsMenu), "CreateViewFromNode", new Type[] { typeof(XElement), typeof(List<int>), typeof(List<int>), typeof(float), typeof(bool) })] public static class ArchipelagoFolderButtonPatch { private const string ButtonLabel = "ARCHIPELAGO"; private const string SettingsButtonPrefix = "SETTINGS"; public static void Postfix(SHGUIcommanderview ___createdView) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (___createdView != null && ___createdView.isRoot) { SHGUIcommanderbutton val = new SHGUIcommanderbutton("ARCHIPELAGO".PadRight(12, ' ') + "│>FOLDER<", 'w', (Action<SHGUIcommanderbutton>)delegate { OpenFolder(); }).SetListLink(___createdView).SetData("Connect to a server, toggle Archipelago mode, view your check/item history, or configure notification settings."); ___createdView.AddButtonView(val); InsertAboveSettings(___createdView); } } private static void InsertAboveSettings(SHGUIcommanderview view) { List<SHGUIcommanderbutton> buttons = view.buttons; int num = buttons.Count - 1; int num2 = buttons.FindIndex((SHGUIcommanderbutton b) => b.ButtonText.StartsWith("SETTINGS", StringComparison.Ordinal)); if (num2 >= 0 && num2 < num) { SHGUIcommanderbutton item = buttons[num]; buttons.RemoveAt(num); buttons.Insert(num2, item); for (int num3 = 0; num3 < buttons.Count; num3++) { ((SHGUIview)buttons[num3]).y = num3 + 1; } } } private static void OpenFolder() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) SHGUIcommanderview val = new SHGUIcommanderview { isRoot = false, path = "C:\\ARCHIPELAGO\\" }; SHGUIcommanderbutton val2 = new SHGUIcommanderbutton("/.. │<UP-FOL>", 'w', (Action<SHGUIcommanderbutton>)delegate { SHGUI.current.PopView(); }).SetListLink(val).SetData("Go back."); val.AddButtonView(val2); ConnectionButtonPatch.AddTo(val); ArchipelagoModeTogglePatch.AddTo(val); ArchipelagoLogButtonPatch.AddTo(val); NotificationSettingsButtonPatch.AddTo(val); SHGUI.current.AddViewOnTop((SHGUIview)(object)val); } } public static class ArchipelagoLogButtonPatch { private const string ButtonLabel = "AP LOG"; internal static void AddTo(SHGUIcommanderview view) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) SHGUIcommanderbutton val = new SHGUIcommanderbutton("AP LOG".PadRight(12, ' ') + "│VIEW", 'w', (Action<SHGUIcommanderbutton>)delegate { SHGUI.current.AddViewOnTop((SHGUIview)(object)new ArchipelagoLogApp()); }).SetListLink(view).SetData("See a history of items you've received and checks you've sent this run."); view.AddButtonView(val); } } public static class ArchipelagoModeTogglePatch { private const string ButtonLabel = "AP MODE"; private static bool? _lastEnabled; internal static SHGUIcommanderbutton? Button { get; private set; } internal static void AddTo(SHGUIcommanderview view) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) SHGUIcommanderbutton val = new SHGUIcommanderbutton(BuildLabel(), 'w', (Action<SHGUIcommanderbutton>)delegate { Mod.SetEnabled(!Config.Enabled.Value); }).SetListLink(view).SetData("Turn Archipelago mode on or off. Off plays SUPERHOT exactly like vanilla -- no level gating, no hub overlay -- and drops any active connection. Turning back on reconnects and picks up right where you left off."); view.AddButtonView(val); Button = val; _lastEnabled = null; RefreshLabel(); } internal static void RefreshLabel() { if (Button != null) { bool value = Config.Enabled.Value; if (value != _lastEnabled) { _lastEnabled = value; Button.ButtonText = BuildLabel(); Button.RefreshText(); } } } private static string BuildLabel() { string text = (Config.Enabled.Value ? "ON" : "OFF"); return "AP MODE".PadRight(12, ' ') + "│" + text; } } internal static class AutoTransitionCheck { public static void ReportCompletionIfFirstCall(bool alreadyStarted) { if (!alreadyStarted && Mod.IsEnabled) { LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo; if (currentLevelInfo != null) { Mod.Locations?.CheckLocation(currentLevelInfo.ID); } } } public static bool TryBlockAndRedirect() { LevelInfo nextLevelInfo = LevelSetup.GetNextLevelInfo(); if (!LevelAccessGuard.ShouldBlock(nextLevelInfo, out string blockMessage)) { return false; } Instance? log = Mod.Log; if (log != null) { log.Msg("Blocked auto-transition to '" + nextLevelInfo.SceneFileName + "' before any static/camera transition started -- " + blockMessage + ". Returning to hub."); } if ((Object)(object)SHGUI.current != (Object)null) { SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f); } return true; } } [HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevel")] public static class AutoTransitionCheckPatch_LoadNextLevel { public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result) { AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel); if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect()) { return true; } ___loadedNextLevel = true; __result = (Status)1; return false; } } [HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelWithTunnels")] public static class AutoTransitionCheckPatch_LoadNextLevelWithTunnels { public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result) { AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel); if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect()) { return true; } ___loadedNextLevel = true; __result = (Status)1; return false; } } [HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelWithTunnelsWithoutScramble")] public static class AutoTransitionCheckPatch_LoadNextLevelWithTunnelsWithoutScramble { public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result) { AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel); if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect()) { return true; } ___loadedNextLevel = true; __result = (Status)1; return false; } } [HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelWithTunnelsWithScrambleOnEnd")] public static class AutoTransitionCheckPatch_LoadNextLevelWithTunnelsWithScrambleOnEnd { public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result) { AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel); if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect()) { return true; } ___loadedNextLevel = true; __result = (Status)1; return false; } } [HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelInstant")] public static class AutoTransitionCheckPatch_LoadNextLevelInstant { public static bool Prefix() { AutoTransitionCheck.ReportCompletionIfFirstCall(alreadyStarted: false); return !AutoTransitionCheck.TryBlockAndRedirect(); } } public static class ConnectionButtonPatch { private const string ButtonLabel = "CONNECT"; private static string? _lastStatus; internal static SHGUIcommanderbutton? Button { get; private set; } internal static void AddTo(SHGUIcommanderview view) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) SHGUIcommanderbutton val = new SHGUIcommanderbutton("CONNECT".PadRight(12, ' ') + "│OFFLINE", 'w', (Action<SHGUIcommanderbutton>)delegate { SHGUI.current.AddViewOnTop((SHGUIview)(object)new ArchipelagoConnectApp()); }).SetListLink(view).SetData("Set up or check your Archipelago server connection."); view.AddButtonView(val); Button = val; _lastStatus = null; RefreshLabel(); } internal static void RefreshLabel() { if (Button != null) { ArchipelagoConnection? connection = Mod.Connection; string text = ((connection != null && connection.IsConnected) ? "ONLINE" : "OFFLINE"); if (!(text == _lastStatus)) { _lastStatus = text; Button.ButtonText = "CONNECT".PadRight(12, ' ') + "│" + text; Button.RefreshText(); } } } } [HarmonyPatch(typeof(LevelSetup), "LoadNextLevel")] public static class DirectLevelSkipPatch { public static bool Prefix() { LevelInfo nextLevelInfo = LevelSetup.GetNextLevelInfo(); if (!LevelAccessGuard.ShouldBlock(nextLevelInfo, out string blockMessage)) { return true; } Instance? log = Mod.Log; if (log != null) { log.Msg("Blocked direct-continue to '" + nextLevelInfo.SceneFileName + "' -- " + blockMessage + ". Returning to hub instead of leaving the player stuck."); } if ((Object)(object)SHGUI.current != (Object)null) { SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f); } return false; } } internal static class EndingClickBuffer { public static void SuppressIfWithinBuffer(ref InputData inputData, bool endingStarted, float endingStartedTime) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (endingStarted && Time.realtimeSinceStartup - endingStartedTime < Config.EndingClickBufferSeconds.Value) { inputData.skipButton = (ButtonState)1; } } } [HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEnding")] public static class EndingClickBufferPatch_Ending { public static void Prefix(ref InputData ___inputData, bool ___SuperHotEndingStarted, float ___SuperHotEndingStartedTime) { EndingClickBuffer.SuppressIfWithinBuffer(ref ___inputData, ___SuperHotEndingStarted, ___SuperHotEndingStartedTime); } } [HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEndingClickThrough")] public static class EndingClickBufferPatch_EndingClickThrough { public static void Prefix(ref InputData ___inputData, bool ___SuperHotEndingStarted, float ___SuperHotEndingStartedTime) { EndingClickBuffer.SuppressIfWithinBuffer(ref ___inputData, ___SuperHotEndingStarted, ___SuperHotEndingStartedTime); } } [HarmonyPatch(typeof(piOsMenu), "LockUnfinishedLevels")] public static class HubUnlockPatch { private static readonly FieldInfo RecursiveColorSetField = AccessTools.Field(typeof(SHGUIcommanderbutton), "recursiveColorSet"); public static void Postfix() { try { RunPostfixBody(); } catch (Exception arg) { Instance? log = Mod.Log; if (log != null) { log.Error($"HubUnlockPatch.Postfix threw: {arg}"); } } } private static void RunPostfixBody() { if (!Mod.IsEnabled || (Object)(object)SHGUI.current == (Object)null) { return; } SHGUIview interactableView = SHGUI.current.GetInteractableView(); SHGUIcommanderview val = (SHGUIcommanderview)(object)((interactableView is SHGUIcommanderview) ? interactableView : null); if (val == null || val.buttons == null) { return; } foreach (SHGUIcommanderbutton button in val.buttons) { if (button == null || button.LevelToBeLoaded == "") { continue; } LevelInfo levelInfoByUniqueSHName = LevelSetup.GetLevelInfoByUniqueSHName(button.LevelToBeLoadedName); if (string.IsNullOrEmpty(levelInfoByUniqueSHName.SceneFileName) || !LevelCatalog.LevelIdToLevel.TryGetValue(levelInfoByUniqueSHName.ID, out LevelEntry value)) { continue; } bool flag = Mod.Connection != null && Mod.Connection.IsLevelExcluded(value.Order); if (value.HasSecret) { if (flag) { button.AddScrollingNotification("INACTIVE".PadRight(8), false); string text = LocalizationAccessHelperExtensions.T("MENU_SECRETNOTCRACKED", Array.Empty<string>()); string text2 = LocalizationAccessHelperExtensions.T("MENU_SECRETCRACKED", Array.Empty<string>()); if (button.data.Contains(text2)) { button.data = button.data.Replace(text2, "INACTIVE"); } else if (button.data.Contains(text)) { button.data = button.data.Replace(text, "INACTIVE"); } } else { bool flag2 = Mod.Locations?.IsSecretCompleted(levelInfoByUniqueSHName.ID) ?? false; if (flag2) { button.AddScrollingNotification(LocalizationAccessHelperExtensions.T("MENU_CRACKED8CHARS", Array.Empty<string>()).PadRight(8), false); } else { button.RemoveScrollingNotification(); } string text3 = LocalizationAccessHelperExtensions.T("MENU_SECRETNOTCRACKED", Array.Empty<string>()); string text4 = LocalizationAccessHelperExtensions.T("MENU_SECRETCRACKED", Array.Empty<string>()); if (flag2 && button.data.Contains(text3)) { button.data = button.data.Replace(text3, text4); } else if (!flag2 && button.data.Contains(text4)) { button.data = button.data.Replace(text4, text3); } } } bool flag3 = value.Order == LevelCatalog.Levels.Count; int num = 0; int num2 = 0; bool flag4 = false; if (flag3) { num = Mod.Locations?.GetLevelsRequiredForFree() ?? 0; num2 = Mod.Locations?.CountOtherLevelsCompleted() ?? 0; flag4 = num2 < num; if (ButtonTextCache.TryGetData(levelInfoByUniqueSHName.ID, out string cleanData)) { button.data = $"\n\n{num2}/{num} LEVELS COMPLETED\n\n" + cleanData; } } string text5 = (flag3 ? $"{num2}/{num}".PadRight(8) : null); bool flag5 = value.Order == 1 || flag || UnlockState.IsUnlocked(levelInfoByUniqueSHName.ID); if (!ButtonTextCache.TryGet(levelInfoByUniqueSHName.ID, out string cleanName)) { button.SetLocked(!flag5); continue; } if (!flag5) { string scrambledString = StringScrambler.GetScrambledString(cleanName.ToUpper(), 0.9f, "▀▄█▌▐░▒▓■▪01 "); button.ButtonText = scrambledString + "│" + (text5 ?? LocalizationAccessHelperExtensions.T("MENU_LOCKED8CHARS", Array.Empty<string>())); button.RefreshText(); button.SetLocked(true); continue; } bool flag6 = Mod.Locations?.IsLevelCompleted(levelInfoByUniqueSHName.ID) ?? false; if (flag3 && flag4) { button.ButtonText = cleanName + "│" + text5; button.RefreshText(); button.IsLocked = true; ((SHGUIview)button).color = 'z'; ((SHGUIview)button).SetColorRecursive('z'); continue; } string text6 = (flag ? StringScrambler.GetScrambledString(cleanName.ToUpper(), 0.9f, "▀▄█▌▐░▒▓■▪01 ") : cleanName); string text7 = (flag3 ? text5 : (flag ? "INACTIVE" : LocalizationAccessHelperExtensions.T("MENU_LEVEL8CHARS", Array.Empty<string>()))); button.ButtonText = text6 + "│" + text7; button.RefreshText(); button.IsLocked = flag; ((SHGUIview)button).SetColorRecursive(((SHGUIview)button).color = (flag ? 'b' : (flag6 ? 'w' : 'z'))); if (flag) { RecursiveColorSetField.SetValue(button, true); } } } } [HarmonyPatch(typeof(piOsMenu), "PrepareLevelCommanderButtonForLevel")] public static class LevelButtonCapturePatch { public static void Postfix(ref SHGUIcommanderbutton b, LevelInfo element, string customName) { try { if (!(customName != "") && b != null && element != null && LevelCatalog.LevelIdToLevel.ContainsKey(element.ID)) { int num = b.ButtonText.IndexOf('│'); string cleanName = ((num >= 0) ? b.ButtonText.Substring(0, num) : b.ButtonText); ButtonTextCache.Remember(element.ID, cleanName); ButtonTextCache.RememberData(element.ID, b.data); } } catch (Exception arg) { Instance? log = Mod.Log; if (log != null) { log.Error("LevelButtonCapturePatch.Postfix threw for element.ID=" + (element?.ID.ToString() ?? "(null)") + ", customName='" + customName + "', " + string.Format("b.ButtonText='{0}': {1}", b?.ButtonText ?? "(null)", arg)); } } } } [HarmonyPatch(typeof(LevelSetup), "UnlockNextLevel")] public static class LevelCompletePatch { public static void Postfix() { if (Mod.IsEnabled) { LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo; if (currentLevelInfo != null) { Mod.Locations?.CheckLocation(currentLevelInfo.ID); } } } } [HarmonyPatch(typeof(SHGUI), "LaunchLevelAppTunnels", new Type[] { typeof(LevelInfo), typeof(bool), typeof(bool) })] public static class LevelGatePatch { public static bool Prefix(LevelInfo level) { if (!LevelAccessGuard.ShouldBlock(level, out string blockMessage)) { return true; } Instance? log = Mod.Log; if (log != null) { log.Msg("Blocked launch of '" + level.SceneFileName + "' via LaunchLevelAppTunnels -- " + blockMessage); } return false; } } [HarmonyPatch(typeof(piOsMenu), "ShouldBeShown")] public static class MenuVisibilityPatch { public static bool Prefix(ref bool __result) { if (!Mod.IsEnabled) { return true; } __result = true; return false; } } public static class NotificationSettingsButtonPatch { private const string ButtonLabel = "SETTINGS"; internal static void AddTo(SHGUIcommanderview parentView) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) SHGUIcommanderbutton val = new SHGUIcommanderbutton("SETTINGS".PadRight(12, ' ') + "│>FOLDER<", 'w', (Action<SHGUIcommanderbutton>)delegate { OpenFolder(); }).SetListLink(parentView).SetData("Choose which item classes pop up during play. Everything still shows up in AP LOG regardless of these settings."); parentView.AddButtonView(val); } private static void OpenFolder() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) SHGUIcommanderview val = new SHGUIcommanderview { isRoot = false, path = "C:\\ARCHIPELAGO\\SETTINGS\\" }; SHGUIcommanderbutton val2 = new SHGUIcommanderbutton("/.. │<UP-FOL>", 'w', (Action<SHGUIcommanderbutton>)delegate { SHGUI.current.PopView(); }).SetListLink(val).SetData("Go back."); val.AddButtonView(val2); AddToggle(val, "PROGRESSION", Config.NotifyProgression, "Popup when a progression item is sent or received."); AddToggle(val, "USEFUL", Config.NotifyUseful, "Popup when a useful item is sent or received."); AddToggle(val, "NORMAL", Config.NotifyFiller, "Popup for normal (filler) items -- most checks."); AddToggle(val, "TRAP", Config.NotifyTrap, "Popup when a trap item is sent or received."); SHGUI.current.AddViewOnTop((SHGUIview)(object)val); } private static void AddToggle(SHGUIcommanderview view, string label, MelonPreferences_Entry<bool> pref, string description) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) SHGUIcommanderbutton button = null; button = new SHGUIcommanderbutton(BuildLabel(label, pref.Value), 'w', (Action<SHGUIcommanderbutton>)delegate { pref.Value = !pref.Value; Config.Save(); button.ButtonText = BuildLabel(label, pref.Value); button.RefreshText(); }).SetListLink(view).SetData(description); view.AddButtonView(button); } private static string BuildLabel(string label, bool value) { return label.PadRight(12, ' ') + "│" + (value ? "ON" : "OFF"); } } [HarmonyPatch(typeof(TerminalActivator), "OnActivate")] public static class SecretFoundPatch { public static void Prefix(bool ___secretFound, out bool __state) { __state = ___secretFound; } public static void Postfix(bool ___secretFound, bool __state) { if (Mod.IsEnabled && !__state && ___secretFound) { LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo; if (currentLevelInfo != null) { Mod.Locations?.CheckSecretLocation(currentLevelInfo.ID); } } } } [HarmonyPatch(typeof(piOsMenu), "CreateViewFromNode")] public static class StoryLevelsUnlockPatch { public static void Postfix(piOsMenu __instance, XElement e) { try { if (Mod.IsEnabled && e != null && ((XContainer)e).Elements().Any(delegate(XElement child) { if (((object)child.Name).ToString() == "item") { XAttribute obj = child.Attribute(XName.op_Implicit("type")); return ((obj != null) ? obj.Value : null) == "storylevels"; } return false; })) { __instance.LockUnfinishedLevels(); } } catch (Exception arg) { Instance? log = Mod.Log; if (log != null) { log.Error($"StoryLevelsUnlockPatch.Postfix threw: {arg}"); } } } } [HarmonyPatch(typeof(SHGUIcommanderview), "AddButtonView")] public static class SuperhotExeButtonPatch { private const string SuperhotExeCustomName = "superhot.exe"; public static bool Prefix(SHGUIcommanderbutton button) { if (button == null || button.ButtonText == null) { return true; } if (button.ButtonText.StartsWith("superhot.exe")) { return false; } return true; } } internal static class TitleCardGate { public static void SuppressAdvanceIfBlocked(ref InputData inputData) { //IL_0001: 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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) if ((inputData.skipButton & 4) == 0 && (int)inputData.skipButton != 3) { return; } LevelInfo nextLevelInfo = LevelSetup.GetNextLevelInfo(); if (LevelAccessGuard.ShouldBlock(nextLevelInfo, out string blockMessage)) { inputData.skipButton = (ButtonState)1; Instance? log = Mod.Log; if (log != null) { log.Msg("Suppressed SUPERHOT title-card click-through to '" + nextLevelInfo.SceneFileName + "' -- " + blockMessage + ". Returning to hub."); } if ((Object)(object)SHGUI.current != (Object)null) { SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f); } } } } [HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEnding")] public static class TitleCardGatePatch_Ending { public static void Prefix(ref InputData ___inputData) { TitleCardGate.SuppressAdvanceIfBlocked(ref ___inputData); } } [HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEndingClickThrough")] public static class TitleCardGatePatch_EndingClickThrough { public static void Prefix(ref InputData ___inputData) { TitleCardGate.SuppressAdvanceIfBlocked(ref ___inputData); } } [HarmonyPatch(typeof(SHGUI), "LaunchLevelViaApp", new Type[] { typeof(LevelInfo), typeof(float) })] public static class ViaAppGatePatch { public static bool Prefix(LevelInfo level) { if (!LevelAccessGuard.ShouldBlock(level, out string blockMessage)) { return true; } Instance? log = Mod.Log; if (log != null) { log.Msg("Blocked launch of '" + level.SceneFileName + "' via LaunchLevelViaApp -- " + blockMessage + ". Returning to hub."); } if ((Object)(object)SHGUI.current != (Object)null) { SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f); } return false; } } } namespace SuperhotArchipelago.Core { public class ArchipelagoConnectApp : SHGUIappbase { private const int FieldCount = 3; private const int StatusLineWidth = 50; private readonly SHGUItext[] _labels = (SHGUItext[])(object)new SHGUItext[3]; private readonly SHGUItext[] _values = (SHGUItext[])(object)new SHGUItext[3]; private readonly string[] _labelText = new string[3] { "SERVER", "SLOT", "PASSWORD" }; private readonly string[] _buffers = new string[3]; private readonly bool[] _masked = new bool[3] { false, false, true }; private SHGUItext _statusField; private int _focusedField; public ArchipelagoConnectApp() : base("ARCHIPELAGO", true) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected O, but got Unknown _buffers[0] = Config.Server.Value; _buffers[1] = Config.Slot.Value; _buffers[2] = Config.Password.Value; int num = 3; for (int i = 0; i < 3; i++) { ref SHGUItext reference = ref _labels[i]; SHGUIview obj = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext(_labelText[i], 3, num, 'z', false)); reference = (SHGUItext)(object)((obj is SHGUItext) ? obj : null); ref SHGUItext reference2 = ref _values[i]; SHGUIview obj2 = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num + 1, 'w', false)); reference2 = (SHGUItext)(object)((obj2 is SHGUItext) ? obj2 : null); num += 3; } ref SHGUItext statusField = ref _statusField; SHGUIview obj3 = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num, 'z', false)); statusField = (SHGUItext)(object)((obj3 is SHGUItext) ? obj3 : null); num += 5; ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("[TAB] switch field [ENTER] next / connect", 3, num, 'z', false)); RefreshDisplay(); } public override void Update() { ((SHGUIview)this).Update(); if (Input.GetKeyDown((KeyCode)27)) { SHGUI.current.PopView(); Input.ResetInputAxes(); return; } if (Input.GetKeyDown((KeyCode)9)) { _focusedField = (_focusedField + 1) % 3; } string text = _buffers[_focusedField]; if (Input.inputString.Length > 0) { text += Input.inputString; } for (int i = 0; i < text.Length; i++) { if (text[i] == '\b') { text = text.Remove(i--, 1); if (i >= 0) { text = text.Remove(i--, 1); } } } bool flag = false; for (int j = 0; j < text.Length; j++) { if (text[j] == '\r' || text[j] == '\n') { text = text.Remove(j, text.Length - j); flag = true; break; } } _buffers[_focusedField] = text; if (flag) { if (_focusedField < 2) { _focusedField++; } else { Connect(); } } RefreshDisplay(); Input.ResetInputAxes(); } public override void ReactToInputKeyboard(SHGUIinput key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) if ((int)key == 6) { ((SHGUIappbase)this).ReactToInputKeyboard(key); } } private void Connect() { Mod.ApplyConnectionSettingsAndConnect(_buffers[0], _buffers[1], _buffers[2]); RefreshDisplay(); } private void RefreshDisplay() { string text = ((Mathf.Sin(Time.realtimeSinceStartup * 10f) > 0f) ? "_" : ""); for (int i = 0; i < 3; i++) { bool flag = i == _focusedField; ((SHGUIview)_labels[i]).color = (flag ? 'w' : 'z'); string text2 = (_masked[i] ? new string('*', _buffers[i].Length) : _buffers[i]); _values[i].text = text2 + (flag ? text : ""); } _statusField.text = "STATUS: " + StatusText(); _statusField.BreakTextForLineLength(50); } private string StatusText() { ArchipelagoConnection connection = Mod.Connection; if (connection == null) { return "NOT INITIALIZED"; } if (connection.IsConnected) { return "CONNECTED AS '" + Config.Slot.Value + "'"; } if (!string.IsNullOrEmpty(connection.LastError)) { return "ERROR -- " + connection.LastError; } return "NOT CONNECTED"; } } public class ArchipelagoConnection { private readonly Instance _log; public ArchipelagoSession? Session { get; private set; } public bool IsConnected { get; private set; } public string? LastError { get; private set; } public int LevelsRequiredForFree { get; private set; } = 25; public DateTime ConnectedAtUtc { get; private set; } public HashSet<int> ExcludedLevelOrders { get; private set; } = new HashSet<int>(); public event Action? Connected; public ArchipelagoConnection(Instance log) { _log = log; } public void Connect(string server, string slotName, string? password = null) { LastError = null; IsConnected = false; if (string.IsNullOrEmpty(server) || string.IsNullOrEmpty(slotName)) { LastError = "Server and Slot are both required."; _log.Warning("Not connecting: " + LastError); return; } Session = ArchipelagoSessionFactory.CreateSession(server, 38281); try { LoginResult val = Session.TryConnectAndLogin("SUPERHOT", slotName, (ItemsHandlingFlags)7, (Version)null, (string[])null, (string)null, password, true); if (!val.Successful) { LastError = "Login failed -- check server address, slot name, and password."; _log.Error("Failed to connect to Archipelago server: " + server); return; } LoginSuccessful val2 = (LoginSuccessful)(object)((val is LoginSuccessful) ? val : null); if (val2 != null && val2.SlotData.TryGetValue("levels_required_for_free", out var value)) { LevelsRequiredForFree = Convert.ToInt32(value); _log.Msg($"Slot data: {LevelsRequiredForFree} other levels required to enter '34 - Free'."); } else { _log.Warning("Slot data had no 'levels_required_for_free' -- falling back to " + $"{LevelsRequiredForFree}. Expected if this room was generated from an " + "apworld version older than this feature."); } LoginSuccessful val3 = (LoginSuccessful)(object)((val is LoginSuccessful) ? val : null); if (val3 == null || !val3.SlotData.TryGetValue("excluded_level_orders", out var value2)) { goto IL_019c; } JArray val4 = (JArray)((value2 is JArray) ? value2 : null); if (val4 == null) { goto IL_019c; } ExcludedLevelOrders = new HashSet<int>(((JToken)val4).ToObject<List<int>>() ?? new List<int>()); if (ExcludedLevelOrders.Count > 0) { _log.Msg($"Slot data: {ExcludedLevelOrders.Count} level(s) excluded from " + "tracking (ExcludeSlowLevels) -- always unlocked, no checks sent for them."); } goto end_IL_0056; IL_019c: ExcludedLevelOrders = new HashSet<int>(); end_IL_0056:; } catch (Exception ex) { LastError = ex.Message; _log.Error("Failed to connect to Archipelago server '" + server + "': " + ex.Message); return; } IsConnected = true; ConnectedAtUtc = DateTime.UtcNow; NotificationLog.Clear(); UnlockState.Clear(); _log.Msg("Connected to Archipelago as '" + slotName + "'."); if (this.Connected == null) { return; } Delegate[] invocationList = this.Connected.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(); } catch (Exception arg) { _log.Error($"A Connected event handler threw and was skipped: {arg}"); } } } public bool IsLevelExcluded(int order) { return ExcludedLevelOrders.Contains(order); } public void Disconnect() { if (Session == null) { IsConnected = false; return; } try { Session.Socket.DisconnectAsync(); } catch (Exception ex) { _log.Warning("Error while disconnecting from the Archipelago server: " + ex.Message); } Session = null; IsConnected = false; _log.Msg("Disconnected from the Archipelago server."); } } public class ArchipelagoLogApp : SHGUIappbase { private const int VisibleLines = 14; private const int LineWidth = 58; private const int MaxSegmentsPerLine = 6; private const int BaseX = 3; private readonly SHGUItext[][] _lineSegments = new SHGUItext[14][]; private readonly SHGUItext _footer; private int _scrollOffset; public ArchipelagoLogApp() : base("LOG", true) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown int num = 3; for (int i = 0; i < 14; i++) { _lineSegments[i] = (SHGUItext[])(object)new SHGUItext[6]; for (int j = 0; j < 6; j++) { ref SHGUItext reference = ref _lineSegments[i][j]; SHGUIview obj = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num, 'w', false)); reference = (SHGUItext)(object)((obj is SHGUItext) ? obj : null); } num++; } num++; ref SHGUItext footer = ref _footer; SHGUIview obj2 = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num, 'z', false)); footer = (SHGUItext)(object)((obj2 is SHGUItext) ? obj2 : null); RefreshDisplay(); } public override void Update() { ((SHGUIview)this).Update(); if (Input.GetKeyDown((KeyCode)27)) { SHGUI.current.PopView(); Input.ResetInputAxes(); return; } int val = Math.Max(0, NotificationLog.Entries.Count - 14); if (Input.GetKeyDown((KeyCode)274)) { _scrollOffset = Math.Min(val, _scrollOffset + 1); RefreshDisplay(); } else if (Input.GetKeyDown((KeyCode)273)) { _scrollOffset = Math.Max(0, _scrollOffset - 1); RefreshDisplay(); } else if (Input.GetKeyDown((KeyCode)281)) { _scrollOffset = Math.Min(val, _scrollOffset + 14); RefreshDisplay(); } else if (Input.GetKeyDown((KeyCode)280)) { _scrollOffset = Math.Max(0, _scrollOffset - 14); RefreshDisplay(); } } private void RefreshDisplay() { IReadOnlyList<LogSegment[]> entries = NotificationLog.Entries; List<LogSegment[]> list = new List<LogSegment[]>(entries.Count); for (int num = entries.Count - 1; num >= 0; num--) { list.Add(entries[num]); } for (int i = 0; i < 14; i++) { int num2 = _scrollOffset + i; if (num2 < list.Count) { SetRowSegments(i, list[num2]); } else if (num2 == 0) { SetRowSegments(i, new LogSegment[1] { new LogSegment("No notifications yet.", 'w') }); } else { SetRowSegments(i, Array.Empty<LogSegment>()); } } _footer.text = ((list.Count == 0) ? "[ESC] close" : ($"[UP/DOWN] scroll [ESC] close ({_scrollOffset + 1}-" + $"{Math.Min(_scrollOffset + 14, list.Count)} of {list.Count})")); } private void SetRowSegments(int rowIndex, LogSegment[] segments) { SHGUItext[] array = _lineSegments[rowIndex]; int num = 0; int i = 0; for (int j = 0; j < segments.Length; j++) { LogSegment logSegment = segments[j]; if (i >= array.Length) { break; } int num2 = 58 - num; string text = ((num2 > 0) ? ((logSegment.Text.Length > num2) ? ((num2 <= 3) ? logSegment.Text.Substring(0, num2) : (logSegment.Text.Substring(0, num2 - 3) + "...")) : logSegment.Text) : ""); SHGUItext obj = array[i]; ((SHGUIview)obj).x = 3 + num; ((SHGUIview)obj).color = logSegment.Color; obj.text = text; num += text.Length; i++; } for (; i < array.Length; i++) { array[i].text = ""; } } } public static class ButtonTextCache { private static readonly Dictionary<int, string> _cleanNameByLevelId = new Dictionary<int, string>(); private static readonly Dictionary<int, string> _cleanDataByLevelId = new Dictionary<int, string>(); public static void Remember(int levelId, string cleanName) { _cleanNameByLevelId[levelId] = cleanName; } public static bool TryGet(int levelId, out string cleanName) { return _cleanNameByLevelId.TryGetValue(levelId, out cleanName); } public static void RememberData(int levelId, string cleanData) { _cleanDataByLevelId[levelId] = cleanData; } public static bool TryGetData(int levelId, out string cleanData) { return _cleanDataByLevelId.TryGetValue(levelId, out cleanData); } } public static class Config { private static MelonPreferences_Category _category; public static MelonPreferences_Entry<string> Server { get; private set; } public static MelonPreferences_Entry<string> Slot { get; private set; } public static MelonPreferences_Entry<string> Password { get; private set; } public static MelonPreferences_Entry<bool> Enabled { get; private set; } public static MelonPreferences_Entry<bool> NotifyProgression { get; private set; } public static MelonPreferences_Entry<bool> NotifyUseful { get; private set; } public static MelonPreferences_Entry<bool> NotifyFiller { get; private set; } public static MelonPreferences_Entry<bool> NotifyTrap { get; private set; } public static MelonPreferences_Entry<float> EndingClickBufferSeconds { get; private set; } public static bool IsConfigured { get { if (Server.Value != "") { return Slot.Value != ""; } return false; } } public static void Load() { _category = MelonPreferences.CreateCategory("SuperhotArchipelago", "SUPERHOT Archipelago"); Server = _category.CreateEntry<string>("Server", "", "Server", "Archipelago server address, e.g. archipelago.gg:38281 or localhost:38281", false, false, (ValueValidator)null, (string)null); Slot = _category.CreateEntry<string>("Slot", "", "Slot", "Your player/slot name, matching the name in your player YAML.", false, false, (ValueValidator)null, (string)null); Password = _category.CreateEntry<string>("Password", "", "Password", "Room password, if the server has one set. Leave blank otherwise.", false, false, (ValueValidator)null, (string)null); Enabled = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Whether Archipelago mode is on. Turn off to play vanilla SUPERHOT (no level gating, no hub overlay) without uninstalling the mod.", false, false, (ValueValidator)null, (string)null); NotifyProgression = _category.CreateEntry<bool>("NotifyProgression", true, "NotifyProgression", "Popup for progression items.", false, false, (ValueValidator)null, (string)null); NotifyUseful = _category.CreateEntry<bool>("NotifyUseful", true, "NotifyUseful", "Popup for useful items.", false, false, (ValueValidator)null, (string)null); NotifyFiller = _category.CreateEntry<bool>("NotifyFiller", true, "NotifyFiller", "Popup for normal (filler) items -- most checks.", false, false, (ValueValidator)null, (string)null); NotifyTrap = _category.CreateEntry<bool>("NotifyTrap", true, "NotifyTrap", "Popup for trap items.", false, false, (ValueValidator)null, (string)null); EndingClickBufferSeconds = _category.CreateEntry<float>("EndingClickBufferSeconds", 1.5f, "EndingClickBufferSeconds", "Seconds after a level's ending sequence starts before a click can skip through it. Higher values give more protection against finishing-blow clicks accidentally skipping ahead; 0 disables the buffer entirely.", false, false, (ValueValidator)null, (string)null); _category.SaveToFile(false); } public static bool ShouldNotify(NotificationColors.ItemClass itemClass) { return itemClass switch { NotificationColors.ItemClass.Progression => NotifyProgression.Value, NotificationColors.ItemClass.Useful => NotifyUseful.Value, NotificationColors.ItemClass.Trap => NotifyTrap.Value, _ => NotifyFiller.Value, }; } public static void Save() { _category.SaveToFile(false); } } public class ItemManager { private readonly struct QueuedItem { public readonly long ItemId; public readonly long LocationId; public readonly string SenderDisplayName; public readonly int SenderSlot; public readonly bool IsCatchUp; public readonly ItemFlags Flags; public QueuedItem(long itemId, long locationId, string senderDisplayName, int senderSlot, ItemFlags flags, bool isCatchUp) { //IL_001e: 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) ItemId = itemId; LocationId = locationId; SenderDisplayName = senderDisplayName; SenderSlot = senderSlot; Flags = flags; IsCatchUp = isCatchUp; } } private static readonly TimeSpan CatchUpWindow = TimeSpan.FromSeconds(5.0); private readonly ArchipelagoConnection _connection; private readonly Instance _log; private readonly ConcurrentQueue<QueuedItem> _itemQueue = new ConcurrentQueue<QueuedItem>(); public ItemManager(ArchipelagoConnection connection, Instance log) { _connection = connection; _log = log; _connection.Connected += OnConnected; } private void OnConnected() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) try { if (_connection.Session == null) { return; } _connection.Session.Items.ItemReceived += (ItemReceivedHandler)delegate(ReceivedItemsHelper helper) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) try { ItemInfo val2 = helper.DequeueItem(); bool isCatchUp = DateTime.UtcNow - _connection.ConnectedAtUtc < CatchUpWindow; string alias = val2.Player.Alias; _itemQueue.Enqueue(new QueuedItem(val2.ItemId, val2.LocationId, alias, val2.Player.Slot, val2.Flags, isCatchUp)); } catch (Exception arg3) { _log.Error("ItemReceived handler threw for one item -- it may not unlock its " + $"level, but every other item is unaffected: {arg3}"); } }; while (_connection.Session.Items.Any()) { try { ItemInfo val = _connection.Session.Items.DequeueItem(); _itemQueue.Enqueue(new QueuedItem(val.ItemId, val.LocationId, val.Player.Alias, val.Player.Slot, val.Flags, isCatchUp: true)); } catch (Exception arg) { _log.Error("Draining one historical item from the received-items queue threw -- it " + $"may not unlock its level, but every other item is unaffected: {arg}"); } } } catch (Exception arg2) { _log.Error("ItemManager.OnConnected failed -- items received this connection " + $"may not unlock levels correctly: {arg2}"); } } public void ProcessQueue() { QueuedItem result; while (_itemQueue.TryDequeue(out result)) { ApplyItem(result); } } private void ApplyItem(QueuedItem queued) { //IL_006c: 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) long itemId = queued.ItemId; bool isSelfSend = _connection.Session != null && queued.SenderSlot == _connection.Session.ConnectionInfo.Slot; switch (itemId) { case 0L: return; case 3901100L: _log.Msg("Received 'White Space' -- filler, nothing to apply."); Notify("White Space", queued.SenderDisplayName, queued.IsCatchUp, queued.LocationId, isSelfSend, queued.Flags); return; } if (!LevelCatalog.ItemIdToLevel.TryGetValue(itemId, out LevelEntry value)) { _log.Warning($"Received unknown item id {itemId} -- no matching level in LevelCatalog."); return; } UnlockState.Unlock(value.LevelId); _log.Msg($"Unlocked '{value.DisplayName}' (level id {value.LevelId}, scene '{value.SceneName}') from a received item."); Notify(value.DisplayName, queued.SenderDisplayName, queued.IsCatchUp, queued.LocationId, isSelfSend, queued.Flags); } private void Notify(string itemDisplayName, string senderDisplayName, bool isCatchUp, long locationId, bool isSelfSend, ItemFlags flags) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if (!isSelfSend) { char color = NotificationColors.ForItemFlags(flags); LogSegment[] segments = new LogSegment[4] { new LogSegment("Received ", 'w'), new LogSegment(itemDisplayName, color), new LogSegment(" from ", 'w'), new LogSegment(senderDisplayName, 'D') }; string popupText = (Config.ShouldNotify(NotificationColors.Classify(flags)) ? ("Received " + itemDisplayName + " from " + senderDisplayName) : null); if (isCatchUp) { NotificationLog.Add(segments, null, locationId); } else { NotificationLog.Add(segments, popupText); } } } } public static class LevelAccessGuard { public static bool ShouldBlock(LevelInfo level, out string blockMessage) { blockMessage = ""; if (!Mod.IsEnabled) { return false; } if (level == null || string.IsNullOrEmpty(level.SceneFileName)) { return false; } LevelInfo val = ResolveToTrackedLevel(level); if (val == null) { return false; } if (!LevelCatalog.LevelIdToLevel.TryGetValue(val.ID, out LevelEntry value)) { return false; } if (!LevelCatalog.LevelIdToLevel.ContainsKey(level.ID)) { blockMessage = "Return to hub to continue"; return true; } if (value.Order == 1) { return false; } if (Mod.Connection != null && Mod.Connection.IsLevelExcluded(value.Order)) { return false; } if (!UnlockState.IsUnlocked(val.ID)) { blockMessage = "LOCKED: '" + value.DisplayName + "' needs an Archipelago item to unlock"; return true; } if (value.Order == LevelCatalog.Levels.Count) { int num = Mod.Locations?.GetLevelsRequiredForFree() ?? 0; int num2 = Mod.Locations?.CountOtherLevelsCompleted() ?? 0; if (num2 < num) { blockMessage = $"LOCKED: {num2}/{num} levels needed"; return true; } } return false; } private static LevelInfo? ResolveToTrackedLevel(LevelInfo level) { LevelInfo val = level; int num = 0; while (val != null && !string.IsNullOrEmpty(val.SceneFileName) && !LevelCatalog.LevelIdToLevel.ContainsKey(val.ID) && num < 64) { int num2 = LevelSetup.GetLevelIndexByID(val.ID) + 1; val = ((num2 < LevelSetup.Levels.Count) ? LevelSetup.Levels[num2] : null); num++; } return val; } } public class LevelEntry { public int Order; public string SceneName = ""; public string DisplayName = ""; public int LevelId; public bool HasSecret; } public static class LevelCatalog { public const long BaseId = 3891000L; public const long ItemIdOffset = 10000L; public const long SecretLocationIdOffset = 20000L; public const long WhiteSpaceItemId = 3901100L; public static List<LevelEntry> Levels { get; private set; } = new List<LevelEntry>(); public static Dictionary<long, LevelEntry> LocationIdToLevel { get; private set; } = new Dictionary<long, LevelEntry>(); public static Dictionary<long, LevelEntry> ItemIdToLevel { get; private set; } = new Dictionary<long, LevelEntry>(); public static Dictionary<long, LevelEntry> SecretLocationIdToLevel { get; private set; } = new Dictionary<long, LevelEntry>(); public static Dictionary<int, LevelEntry> LevelIdToLevel { get; private set; } = new Dictionary<int, LevelEntry>(); public static void Load(Instance log) { string text = Path.Combine(Path.GetDirectoryName(typeof(LevelCatalog).Assembly.Location), "data", "levels.json"); if (!File.Exists(text)) { log.Error("levels.json not found at '" + text + "' -- location/item id mapping will be empty."); return; } foreach (JToken item in (IEnumerable<JToken>)JObject.Parse(File.ReadAllText(text))["levels"]) { LevelEntry obj = new LevelEntry { Order = Extensions.Value<int>((IEnumerable<JToken>)item[(object)"order"]), SceneName = Extensions.Value<string>((IEnumerable<JToken>)item[(object)"id"]), DisplayName = Extensions.Value<string>((IEnumerable<JToken>)item[(object)"name"]), LevelId = Extensions.Value<int>((IEnumerable<JToken>)item[(object)"gameId"]) }; JToken obj2 = item[(object)"hasSecret"]; obj.HasSecret = obj2 != null && Extensions.Value<bool>((IEnumerable<JToken>)obj2); LevelEntry levelEntry = obj; Levels.Add(levelEntry); long key = 3891000L + (long)levelEntry.Order; long key2 = 3901000L + (long)levelEntry.Order; LocationIdToLevel[key] = levelEntry; ItemIdToLevel[key2] = levelEntry; if (levelEntry.HasSecret) { long key3 = 3911000L + (long)levelEntry.Order; SecretLocationIdToLevel[key3] = levelEntry; } LevelIdToLevel[levelEntry.LevelId] = levelEntry; } log.Msg($"LevelCatalog loaded {Levels.Count} levels from '{text}'."); } public static string? TryGetShortItemDisplayName(long itemId) { if (itemId == 3901100) { return "White Space"; } if (!ItemIdToLevel.TryGetValue(itemId, out LevelEntry value)) { return null; } return value.DisplayName; } } public class LocationManager { private readonly ArchipelagoConnection _connection; private readonly Instance _log; private readonly ConcurrentQueue<(LogSegment[] Segments, string? PopupText, long OrderKey)> _pendingNotifications = new ConcurrentQueue<(LogSegment[], string, long)>(); public LocationManager(ArchipelagoConnection connection, Instance log) { _connection = connection; _log = log; _connection.Connected += OnConnected; } private void OnConnected() { try { OnConnectedCore(); } catch (Exception arg) { _log.Error("LocationManager.OnConnected failed, notification history resync " + $"skipped for this connection: {arg}"); } } private void OnConnectedCore() { if (_connection.Session == null) { return; } long[] array = _connection.Session.Locations.AllLocationsChecked.Where((long id) => LevelCatalog.LocationIdToLevel.ContainsKey(id) || LevelCatalog.SecretLocationIdToLevel.ContainsKey(id)).ToArray(); if (array.Length == 0) { return; } _connection.Session.Locations.ScoutLocationsAsync(array).ContinueWith(delegate(Task<Dictionary<long, ScoutedItemInfo>> task) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (task.IsFaulted || task.IsCanceled || task.Result == null) { _log.Warning("Failed to scout this slot's already-checked locations -- the AP LOG screen won't show full history until the next successful reconnect."); return; } foreach (KeyValuePair<long, ScoutedItemInfo> item2 in task.Result) { try { string text = LevelCatalog.TryGetShortItemDisplayName(((ItemInfo)item2.Value).ItemId) ?? ((ItemInfo)item2.Value).ItemDisplayName; char color = NotificationColors.ForItemFlags(((ItemInfo)item2.Value).Flags); LogSegment[] item = ((_connection.Session == null || item2.Value.Player.Slot != _connection.Session.ConnectionInfo.Slot) ? new LogSegment[5] { new LogSegment("Sent ", 'w'), new LogSegment(text, color), new LogSegment(" to ", 'w'), new LogSegment(item2.Value.Player.Alias, 'D'), new LogSegment(" from " + ResolveLocationDisplayText(item2.Key), 'w') } : new LogSegment[3] { new LogSegment(item2.Value.Player.Alias, 'D'), new LogSegment(" found their ", 'w'), new LogSegment(text, color) }); _pendingNotifications.Enqueue((item, null, item2.Key)); } catch (Exception arg) { _log.Error("Processing one scouted historical location threw -- it may be " + $"missing from the AP LOG, but every other entry is unaffected: {arg}"); } } }); } public void ProcessPendingNotifications() { (LogSegment[], string, long) result; while (_pendingNotifications.TryDequeue(out result)) { NotificationLog.Add(result.Item1, result.Item2, result.Item3); } } public void CheckLocation(int levelId) { if (_connection.Session == null || !_connection.IsConnected) { _log.Warning($"CheckLocation({levelId}) called before connecting -- ignored."); return; } if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value)) { _log.Warning($"Unknown level id {levelId} -- no matching entry in levels.json. " + "Either a level outside our catalog just finished, or levels.json's order/id list is out of sync with the real game."); return; } if (value.Order != LevelCatalog.Levels.Count && !_connection.IsLevelExcluded(value.Order)) { long num = 3891000L + (long)value.Order; if (_connection.Session.Locations.AllLocationsChecked.Contains(num)) { _log.Msg("'" + value.DisplayName + "' already checked -- skipping duplicate check/notification."); } else { _connection.Session.Locations.CompleteLocationChecks(new long[1] { num }); _log.Msg($"Sent check for '{value.DisplayName}' (level id {levelId}, location id {num})."); ScoutAndQueueSentNotification(num, value.DisplayName, isLive: true); } } if (value.Order == LevelCatalog.Levels.Count) { _connection.Session.SetGoalAchieved(); _log.Msg("'" + value.DisplayName + "' was the final level -- reported goal achieved."); SaveManager.Instance.SetValue("storyFinished", (object)true); } } public void CheckSecretLocation(int levelId) { if (_connection.Session == null || !_connection.IsConnected) { _log.Warning($"CheckSecretLocation({levelId}) called before connecting -- ignored."); return; } if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value)) { _log.Warning($"Unknown level id {levelId} for a secret find -- no matching entry in " + "levels.json. Either a level outside our catalog has a secret we don't know about, or levels.json's order/id list is out of sync with the real game."); return; } if (!value.HasSecret) { _log.Warning("Secret found in '" + value.DisplayName + "', but levels.json says this level has none -- sending the check anyway, but this likely means levels.json's hasSecret is out of sync with the real game."); } if (_connection.IsLevelExcluded(value.Order)) { _log.Msg("'" + value.DisplayName + "' secret found, but this level is excluded from tracking (ExcludeSlowLevels) -- no check to send."); return; } long num = 3911000L + (long)value.Order; if (_connection.Session.Locations.AllLocationsChecked.Contains(num)) { _log.Msg("'" + value.DisplayName + "' secret already checked -- skipping duplicate check/notification."); return; } _connection.Session.Locations.CompleteLocationChecks(new long[1] { num }); _log.Msg($"Sent secret check for '{value.DisplayName}' (level id {levelId}, location id {num})."); ScoutAndQueueSentNotification(num, value.DisplayName + " Secret", isLive: true); } private void ScoutAndQueueSentNotification(long locationId, string locationDisplayText, bool isLive) { if (_connection.Session == null) { return; } _connection.Session.Locations.ScoutLocationsAsync(new long[1] { locationId }).ContinueWith(delegate(Task<Dictionary<long, ScoutedItemInfo>> task) { //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) try { ItemFlags? val = null; string text2; LogSegment[] item; if (!task.IsFaulted && !task.IsCanceled && task.Result != null && task.Result.TryGetValue(locationId, out var value)) { val = ((ItemInfo)value).Flags; string text = LevelCatalog.TryGetShortItemDisplayName(((ItemInfo)value).ItemId) ?? ((ItemInfo)value).ItemDisplayName; char color = NotificationColors.ForItemFlags(((ItemInfo)value).Flags); if (_connection.Session != null && value.Player.Slot == _connection.Session.ConnectionInfo.Slot) { text2 = value.Player.Alias + " found their " + text; item = new LogSegment[3] { new LogSegment(value.Player.Alias, 'D'), new LogSegment(" found their ", 'w'), new LogSegment(text, color) }; } else { text2 = "Sent " + text + " to " + value.Player.Alias; item = new LogSegment[5] { new LogSegment("Sent ", 'w'), new LogSegment(text, color), new LogSegment(" to ", 'w'), new LogSegment(value.Player.Alias, 'D'), new LogSegment(" from " + locationDisplayText, 'w') }; } } else { _log.Warning($"Failed to scout location {locationId} for a notification -- " + "showing a generic message instead."); text2 = "Sent check for " + locationDisplayText; item = new LogSegment[1] { new LogSegment(text2, 'w') }; } bool flag = isLive && (!val.HasValue || Config.ShouldNotify(NotificationColors.Classify(val.Value))); _pendingNotifications.Enqueue((item, flag ? text2 : null, long.MaxValue)); } catch (Exception arg) { _log.Error($"Building a notification for location {locationId} threw -- it may be " + $"missing a popup/log entry, but nothing else is affected: {arg}"); } }); } private static string ResolveLocationDisplayText(long locationId) { if (LevelCatalog.LocationIdToLevel.TryGetValue(locationId, out LevelEntry value)) { return value.DisplayName; } if (LevelCatalog.SecretLocationIdToLevel.TryGetValue(locationId, out LevelEntry value2)) { return value2.DisplayName + " Secret"; } return "an unknown location"; } public bool IsLevelCompleted(int levelId) { if (_connection.Session == null || !_connection.IsConnected) { return false; } if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value)) { return false; } if (value.Order == LevelCatalog.Levels.Count) { return UnlockState.IsUnlocked(levelId); } if (_connection.IsLevelExcluded(value.Order)) { return true; } long value2 = 3891000L + (long)value.Order; return _connection.Session.Locations.AllLocationsChecked.Contains(value2); } public bool IsSecretCompleted(int levelId) { if (_connection.Session == null || !_connection.IsConnected) { return false; } if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value)) { return false; } if (_connection.IsLevelExcluded(value.Order)) { return true; } long value2 = 3911000L + (long)value.Order; return _connection.Session.Locations.AllLocationsChecked.Contains(value2); } private IEnumerable<LevelEntry> OtherTrackedLevels() { foreach (LevelEntry level in LevelCatalog.Levels) { if (level.Order != LevelCatalog.Levels.Count && !_connection.IsLevelExcluded(level.Order)) { yield return level; } } } public int CountOtherLevelsCompleted() { int num = 0; foreach (LevelEntry item in OtherTrackedLevels()) { if (IsLevelCompleted(item.LevelId)) { num++; } } return num; } public int GetLevelsRequiredForFree() { return Math.Min(_connection.LevelsRequiredForFree, OtherTrackedLevels().Count()); } } public class Mod : MelonMod { private static Mod? _instance; private static bool _suppressAutoReconnect; public static ArchipelagoConnection? Connection { get; private set; } public static LocationManager? Locations { get; private set; } public static ItemManager? Items { get; private set; } public static Instance? Log { get; private set; } public static bool IsEnabled => Config.Enabled.Value; private static bool IsFullyConnected { get { if (Connection != null) { return Connection.IsConnected; } return false; } } public override void OnInitializeMelon() { _instance = this; Log = ((MelonBase)this).LoggerInstance; ((MelonBase)this).LoggerInstance.Msg("SuperhotArchipelago loading."); Config.Load(); LevelCatalog.Load(((MelonBase)this).LoggerInstance); Connection = new ArchipelagoConnection(((MelonBase)this).LoggerInstance); Locations = new LocationManager(Connection, ((MelonBase)this).LoggerInstance); Items = new ItemManager(Connection, ((MelonBase)this).LoggerInstance); if (!Config.Enabled.Value) { ((MelonBase)this).LoggerInstance.Msg("Not connecting yet: Archipelago mode is turned off. Click the AP MODE icon on the hub's main screen to turn it back on."); } else if (Config.IsConfigured) { TryConnect(); } else { ((MelonBase)this).LoggerInstance.Msg("Not connecting yet: Server/Slot not set. Click the ARCHIPELAGO icon on the hub's main screen to open the connection menu and fill them in, or edit the [SuperhotArchipelago] section of UserData/MelonPreferences.cfg directly (Server/Slot/Password changes there also trigger a reconnect automatically)."); } ((MelonEventBase<LemonAction<string, string>>)(object)Config.Server.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate { TryConnect(); }, 0, false); ((MelonEventBase<LemonAction<string, string>>)(object)Config.Slot.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate { TryConnect(); }, 0, false); ((MelonEventBase<LemonAction<string, string>>)(object)Config.Password.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate { TryConnect(); }, 0, false); ((MelonEventBase<LemonAction<bool, bool>>)(object)Config.Enabled.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate { ApplyEnabledChange(); }, 0, false); } private void TryConnect() { if (!_suppressAutoReconnect && Config.Enabled.Value && Config.IsConfigured) { ((MelonBase)this).LoggerInstance.Msg("Connecting to '" + Config.Server.Value + "' as '" + Config.Slot.Value + "'..."); Connection?.Connect(Config.Server.Value, Config.Slot.Value, (Config.Password.Value == "") ? null : Config.Password.Value); } } public static void SetEnabled(bool enabled) { if (_instance != null) { _suppressAutoReconnect = true; try { Config.Enabled.Value = enabled; Config.Save(); } finally { _suppressAutoReconnect = false; } ApplyEnabledChange(); } } private static void ApplyEnabledChange() { if (_suppressAutoReconnect) { return; } if (Config.Enabled.Value) { Instance? log = Log; if (log != null) { log.Msg("Archipelago mode turned ON."); } _instance?.TryConnect(); } else { Instance? log2 = Log; if (log2 != null) { log2.Msg("Archipelago mode turned OFF -- SUPERHOT will play like vanilla until it's turned back on."); } Connection?.Disconnect(); } } public static void ApplyConnectionSettingsAndConnect(string server, string slot, string password) { if (_instance != null) { _suppressAutoReconnect = true; try { Config.Server.Value = server; Config.Slot.Value = slot; Config.Password.Value = password; Config.Enabled.Value = true; Config.Save(); } finally { _suppressAutoReconnect = false; } _instance.TryConnect(); } } public override void OnUpdate() { RunStep("Items.ProcessQueue", delegate { Items?.ProcessQueue(); }); RunStep("Locations.ProcessPendingNotifications", delegate { Locations?.ProcessPendingNotifications(); }); RunStep("NotificationLog.FlushPendingPopups", NotificationLog.FlushPendingPopups); RunStep("PopupOverlay.Update", PopupOverlay.Update); RunStep("ConnectionButtonPatch.RefreshLabel", ConnectionButtonPatch.RefreshLabel); RunStep("ArchipelagoModeTogglePatch.RefreshLabel", ArchipelagoModeTogglePatch.RefreshLabel); } private void RunStep(string name, Action step) { try { step(); } catch (Exception arg) { ((MelonBase)this).LoggerInstance.Error($"[debug] OnUpdate step '{name}' threw: {arg}"); } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { ((MelonBase)this).LoggerInstance.Msg($"Scene loaded: {sceneName} (buildIndex {buildIndex})"); try { RunSceneWasLoadedBody(sceneName); } catch (Exception arg) { ((MelonBase)this).LoggerInstance.Error($"OnSceneWasLoaded('{sceneName}') threw: {arg}"); } } private void RunSceneWasLoadedBody(string sceneName) { if (IsEnabled && IsFullyConnected && SaveManager.Instance != null && Locations != null) { foreach (LevelEntry level in LevelCatalog.Levels) { if (level.HasSecret) { bool flag = Locations.IsSecretCompleted(level.LevelId); SaveManager.Instance.SetValue(level.SceneName + "1unlocked", (object)flag); } } } LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo; if (sceneName != "SHMenu" && currentLevelInfo != null && LevelAccessGuard.ShouldBlock(currentLevelInfo, out string _)) { ((MelonBase)this).LoggerInstance.Msg("Safety net: scene '" + sceneName + "' resolved to a locked level that no launch-time gate caught -- kicking back to hub."); if ((Object)(object)SHGUI.current != (Object)null) { SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f); } } PopupOverlay.OnSceneLoaded(); } } public static class NotificationColors { public enum ItemClass { Progression, Useful, Trap, Filler } public const char Player = 'D'; public const char ProgressionItem = 'g'; public const char UsefulItem = 'b'; public const char TrapItem = 'r'; public const char FillerItem = 'z'; public const char Default = 'w'; public static ItemClass Classify(ItemFlags flags) { //IL_0000: 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) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (((Enum)flags).HasFlag((Enum)(object)(ItemFlags)4)) { return ItemClass.Trap; } if (((Enum)flags).HasFlag((Enum)(object)(ItemFlags)1)) { return ItemClass.Progression; } if (((Enum)flags).HasFlag((Enum)(object)(ItemFlags)2)) { return ItemClass.Useful; } return ItemClass.Filler; } public static char ForItemFlags(ItemFlags flags) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Classify(flags) switch { ItemClass.Trap => 'r', ItemClass.Progression => 'g', ItemClass.Useful => 'b', _ => 'z', }; } } public readonly struct LogSegment { public readonly string Text; public readonly char Color; public LogSegment(string text, char color) { Text = text; Color = color; } } public static class NotificationLog { private const int MaxEntries = 200; private static readonly List<LogSegment[]> _entries = new List<LogSegment[]>(); private static readonly List<long> _entryOrderKeys = new List<long>(); private static readonly List<string> _pendingPopups = new List<string>(); private const int MaxPendingPopups = 4; public static IReadOnlyList<LogSegment[]> Entries => _entries; public static void Add(LogSegment[] segments, string? popupText) { Add(segments, popupText, long.MaxValue); } public static void Add(string plainText, string? popupText) { Add(new LogSegment[1] { new LogSegment(plainText, 'w') }, popupText, long.MaxValue); } public static void Add(LogSegment[] segments, string? popupText, long orderKey) { int num = _entries.Count; while (num > 0 && _entryOrderKeys[num - 1] > orderKey) { num--; } _entries.Insert(num, segments); _entryOrderKeys.Insert(num, orderKey); while (_entries.Count > 200) { _entries.RemoveAt(0); _entryOrderKeys.RemoveAt(0); } if (popupText != null) { _pendingPopups.Add(popupText); while (_pendingPopups.Count > 4) { _pendingPopups.RemoveAt(0); } } } public static void FlushPendingPopups() { if (_pendingPopups.Count == 0 || TimeControl.forcedTimeScale == 0f) { return; } foreach (string pendingPopup in _pendingPopups) { PopupOverlay.Show(pendingPopup); } _pendingPopups.Clear(); } public static void Clear() { _entries.Clear(); _entryOrderKeys.Clear(); _pendingPopups.Clear(); } } public static class PopupOverlay { private enum State { Hidden, FadingIn, Visible, FadingOut } private const int ReferenceWidth = 1920; private const int ReferenceHeight = 1080; private const float DedupeWindowSeconds = 1f; private static RectTransform? _panel; private static CanvasGroup? _canvasGroup; private static Text? _text; private static readonly Queue<string> _queue = new Queue<string>(); private static float? _fixedCharAdvance; private static float? _fixedLineHeight; private static State _state = State.Hidden; private static float _timer; private static string? _lastShownMessage; private static float _lastShownRealtime = float.NegativeInfinity; private static GameObject? _canvasRoot; private const string GameFontAssetPath = "Fonts/SHGUIFontAsset_EU"; private static readonly string[] PreferredMonospaceFonts = new string[5] { "Consolas", "Courier New", "DejaVu Sans Mono", "Lucida Console", "Monospace" }; private static PopupTuning T => PopupTuning.Current; public static void Show(string text) { bool num = text == _lastShownMessage && Time.unscaledTime - _lastShownRealtime < 1f; _lastShownMessage = text; _lastShownRealtime = Time.unscaledTime; if (!num) { _queue.Enqueue(text); } } public static void OnSceneLoaded() { } public static void Update() { EnsureCanvas(); switch (_state) { case State.Hidden: if (_queue.Count > 0) { DisplayNext(); } break; case State.FadingIn: _timer += Time.unscaledDeltaTime; ApplyTransition(Mathf.Clamp01(_timer / T.TransitionSeconds)); if (_timer >= T.TransitionSeconds) { _state = State.Visible; _timer = T.DisplaySeconds; } break; case State.Visible: _timer -= Time.unscaledDeltaTime; if (_timer <= 0f) { _state = State.FadingOut; _timer = 0f; } break; case State.FadingOut: _timer += Time.unscaledDeltaTime; ApplyTransition(1f - Mathf.Clamp01(_timer / T.TransitionSeconds)); if (_timer >= T.TransitionSeconds) { _state = State.Hidden; ((Component)_panel).gameObject.SetActive(false); } break; } } private static void ApplyTransition(float t) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) _canvasGroup.alpha = t; ((Transform)_panel).localScale = new Vector3(1f, Mathf.Lerp(T.MinScaleY, 1f, t), 1f); } public static void ReloadTuning() { if ((Object)(object)_canvasRoot != (Object)null) { Object.Destroy((Object)(object)_canvasRoot); } _canvasRoot = null; _text = null; _panel = null; _canvasGroup = null; _fixedCharAdvance = null; _fixedLineHeight = null; _state = State.Hidden; _timer = 0f; } private static void EnsureCanvas() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00af: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: 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_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_text != (Object)null)) { PopupTuning.Load(); GameObject val = (_canvasRoot = new GameObject("SuperhotArchipelago_PopupCanvas")); Object.DontDestroyOnLoad((Object)(object)val); Canvas obj = val.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 1000; CanvasScaler obj2 = val.AddComponent<CanvasScaler>(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.screenMatchMode = (ScreenMatchMode)0; obj2.matchWidthOrHeight = 0.5f; GameObject val2 = new GameObject("Panel"); val2.transform.SetParent(val.transform, false); _panel = val2.AddComponent<RectTransform>(); _panel.anchorMin = new Vector2(0.5f, T.TopAnchorY); _panel.anchorMax = new Vector2(0.5f, T.TopAnchorY); _panel.pivot = new Vector2(0.5f, 1f); _canvasGroup = val2.AddComponent<CanvasGroup>(); CreateStretchImage((Transform)(object)_panel, "Background", T.BackgroundColor, 0f); Font val3 = ResolveFont(); GameObject val4 = new GameObject("Text"); val4.transform.SetParent((Transform)(object)_panel, false); RectTransform val5 = val4.AddComponent<RectTransform>(); val5.anchorMin = new Vector2(0.5f, 0.5f); val5.anchorMax = new Vector2(0.5f, 0.5f); val5.pivot = new Vector2(0.5f, 0.5f); ((Transform)val5).localScale = new Vector3(T.PixelScale, T.PixelScale, 1f); _text = val4.AddComponent<Text>(); _text.font = val3; _text.fontSize = T.FontSize; _text.fontStyle = (FontStyle)1; _text.alignment = (TextAnchor)4; _text.horizontalOverflow = (HorizontalWrapMode)1; _text.verticalOverflow = (VerticalWrapMode)1; ((Graphic)_text).color = Color.white; if (_fixedLineHeight.HasValue) { val5.anchoredPosition = new Vector2(0f, (0f - _fixedLineHeight.Value) * T.PixelScale * T.TextVerticalNudgeFraction); } if ((Object)(object)val3.material != (Object)null && (Object)(object)val3.material.mainTexture != (Object)null) { val3.material.mainTexture.filterMode = (FilterMode)0; } val2.SetActive(false); } } private static Image CreateStretchImage(Transform parent, string name, Color color, float inset) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0050: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = Vector2.zero; obj.anchorMax = Vector2.one; obj.offsetMin = new Vector2(inset, inset); obj.offsetMax = new Vector2(0f - inset, 0f - inset); Image obj2 = val.AddComponent<Image>(); ((Graphic)obj2).color = color; return obj2; } private static Font ResolveFont() { Font val = BuildGameFont(); if ((Object)(object)val != (Object)null) { return val; } Font val2 = Font.CreateDynamicFontFromOSFont(PreferredMonospaceFonts, T.FontSize); if ((Object)(object)val2 != (Object)null) { return val2; } Font builtinResource = Resources.GetBuiltinResource<Font>("Arial.ttf"); if ((Object)(object)builtinResource != (Object)null) { return builtinResource; } return Font.CreateDynamicFontFromOSFont("Arial", T.FontSize); } private static Font? BuildGameFont() { //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Expected O, but got Unknown //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Expected O, but got Unknown //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: 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_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) SHGUIFontAsset val = Resources.Load<SHGUIFontAsset>("Fonts/SHGUIFontAsset_EU"); if ((Object)(object)val == (Object)null || (Object)(object)val.FontTexture == (Object)null || (Object)(object)val.FontDescription == (Object)null) { return null; } Texture2D fontTexture = val.FontTexture; float num = ((Texture)fontTexture).width; float num2 = ((Texture)fontTexture).height; List<CharacterInfo> list = new List<CharacterInfo>(); foreach (XElement item2 in ((XContainer)XDocument.Parse(val.FontDescription.text)).Descendants(XName.op_Implicit("char"))) { XAttribute obj = item2.Attribute(XName.op_Implicit("id")); if (!int.TryParse((obj != null) ? obj.Value : null, out var result)) { continue; } XAttribute obj2 = item2.Attribute(XName.op_Implicit("x")); if (!int.TryParse((obj2 != null) ? obj2.Value : null, out var result2)) { continue; } XAttribute obj3 = item2.Attribute(XName.op_Implicit("y")); if (!int.TryParse((obj3 != null) ? obj3.Value : null, out var result3)) { continue; } XAttribute obj4 = item2.Attribute(XName.op_Implicit("width")); if (!int.TryParse((obj4 != null) ? obj4.Value : null, out var result4)) { continue; } XAttribute obj5 = item2.Attribute(XName.op_Implicit("height")); if (int.TryParse((obj5 != null) ? obj5.Value : null, out var result5)) { float num3 = (float)result2 / num; float num4 = (float)(result2 + result4) / num; float num5 = 1f - (float)result3 / num2; float num6 = 1f - (float)(result3 + result5) / num2; float num7 = val.CharSize.y; if (result >= 65 && result <= 90) { num7 -= T.CapitalLetterNudge; } CharacterInfo item = new CharacterInfo { index = result, uv = new Rect(num3, num6, num4 - num3, num5 - num6), vert = new Rect(0f, num7, (float)result4, (float)(-result5)), width = val.CharSize.x }; list.Add(item); } } if (list.Count == 0) { return null; } Font val2 = new Font { material = new Material(Shader.Find("UI/Default")) { mainTexture = (Texture)(object)fontTexture }, characterInfo = list.ToArray() }; _fixedCharAdvance = val.CharSize.x; _fixedLineHeight = val.CharSize.y; return val2; } private static void DisplayNext() { //IL_0171: Unknown result type (might be due to invalid IL or missing references) string text = _queue.Dequeue(); ((Component)_panel).gameObject.SetActive(true); float num = _fixedCharAdvance ?? ((float)T.FontSize * 0.6f); float num2 = _fixedLineHeight ?? ((float)T.FontSize * 1.2f); int maxCols = Mathf.Max(4, Mathf.FloorToInt(T.MaxWidthFraction * 1920f / (num * T.PixelScale)) - T.PaddingHorizontalCells * 2); text = WrapText(text, maxCols); _text.text = text; string[] array = text.Split(new char[1] { '\n' }); int num3 = 0; string[] array2 = array; foreach (string text2 in array2) { num3 = Math.Max(num3, text2.Length); } int num4 = num3 + T.PaddingHorizontalCells * 2; int num5 = array.Length + T.PaddingVerticalCells * 2; _panel.sizeDelta = new Vector2(Mathf.Min((float)num4 * num * T.PixelScale, T.MaxWidthFraction * 1920f), Mathf.Min((float)num5 * num2 * T.PixelScale, T.MaxHeightFraction * 1080f)); _state = State.FadingIn; _timer = 0f; ApplyTransition(0f); } private static string WrapText(string text, int maxCols) { List<string> list = new List<string>(); string[] array = text.Split(new char[1] { '\n' }); foreach (string text2 in array) { if (text2.Length <= maxCols) { list.Add(text2); continue; } StringBuilder stringBuilder = new StringBuilder(); string[] array2 = text2.Split(new char[1] { ' ' }); foreach (string text3 in array2) { if (stringBuilder.Length == 0) { stringBuilder.Append(text3); continue; } if (stringBuilder.Length + 1 + text3.Length <= maxCols) { stringBuilder.Append(' ').Append(text3); continue; } list.Add(stringBuilder.ToString()); stringBuilder.Clear(); stringBuilder.Append(text3); } if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); } } return string.Join("\n", list); } } public class PopupTuning { public float DisplaySeconds = 3f; public float TransitionSeconds = 0.12f; public float MinScaleY = 0.01f; public int FontSize = 16; public float PixelScale = 0.9f; public float TextVerticalNudgeFraction = 0.48f; public float TopAnchorY = 0.95f; public float MaxWidthFraction = 1f; public float MaxHeightFraction = 0.08f; public int PaddingHorizontalCells = 2; public int PaddingVerticalCells = 1; public float CapitalLetterNudge = -1f; public float BackgroundR; public float BackgroundG = 0.01f; public float BackgroundB = 0.02f; public float BackgroundA = 0.75f; [JsonIgnore] public Color BackgroundColor => new Color(BackgroundR, BackgroundG, BackgroundB, BackgroundA); private static string FilePath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "PopupTuning.json"); public static PopupTuning Current { get; private set; } = new PopupTuning(); public static void Load() { try { if (File.Exists(FilePath)) { PopupTuning popupTuning = JsonConvert.DeserializeObject<PopupTuning>(File.ReadAllText(FilePath)); if (popupTuning != null) { Current = popupTuning; return; } } Current = new PopupTuning(); Save(); } catch (Exception arg) { Instance? log = Mod.Log; if (log != null) { log.Error($"PopupTuning.Load failed, using defaults: {arg}"); } Current = new PopupTuning(); } } private static void Save() { File.WriteAllText(FilePath, JsonConvert.SerializeObject((object)Current, (Formatting)1)); } } public static class UnlockState { private static readonly HashSet<int> _unlockedLevelIds = new HashSet<int>(); public static void Unlock(int levelId) { _unlockedLevelIds.Add(levelId); } public static bool IsUnlocked(int levelId) { return _unlockedLevelIds.Contains(levelId); } public static void Clear() { _unlockedLevelIds.Clear(); } } }
UserLibs/Archipelago.MultiClient.Net.dll
Decompiled a day 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; 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(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")] [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; SecurityProtocolType securityProtocolType = SecurityProtocolType.Tls13; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | securityProtocolType; } 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) { Get