using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using Archipelago.MultiClient.Net; using Archipelago.MultiClient.Net.BounceFeatures.DeathLink; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.MessageLog.Messages; using Archipelago.MultiClient.Net.MessageLog.Parts; using Archipelago.MultiClient.Net.Models; using BepInEx; using BepInEx.Logging; using Characters; using Characters.Gear.Weapons; using Characters.Player; using Data; using HarmonyLib; using Level; using Level.Altars; using Level.Npc; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Scenes; using Services; using Singletons; using SkulAPMod.Helpers; using SkulAPMod.Patches; using TMPro; using UI; using UI.Witch; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("Plugins.Common")] [assembly: IgnoresAccessChecksTo("Plugins.Data")] [assembly: IgnoresAccessChecksTo("Plugins.ObjectPool")] [assembly: IgnoresAccessChecksTo("Plugins.PhysicsUtils")] [assembly: IgnoresAccessChecksTo("Plugins.Singletons")] [assembly: AssemblyCompany("SkulAPMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Skul AP Mod Implementation")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+ee6fe7b2696973526d56fa605e6c875bd6cdec40")] [assembly: AssemblyProduct("SkulAPMod")] [assembly: AssemblyTitle("SkulAPMod")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SkulAPMod { public static class APSessionManager { public static Main PendingMainInstance { get; set; } public static void OnConnected() { if ((Object)(object)PendingMainInstance != (Object)null) { Main capturedInstance = PendingMainInstance; PendingMainInstance = null; SkulAPMod.QueueMainThreadAction(delegate { TitleScreenPatch.Suppress = true; capturedInstance.StartGame(); TitleScreenPatch.Suppress = false; }); } } } public class ArchipelagoClient { private int _itemsToSkip; private DeathLinkService _deathLinkService; private bool _receivingDeathLink; private ArchipelagoSession session { get; set; } public bool DeathLinkEnabled { get; private set; } public bool IsConnected { get { ArchipelagoSession obj = session; if (obj == null) { return false; } return obj.Socket.Connected; } } public string SlotName { get; private set; } public string Seed { get; private set; } public string ServerVersion { get; private set; } public string ApworldVersion { get; private set; } public event Action OnConnected; public event Action OnConnectionFailed; public event Action OnDisconnected; public event Action OnRestartRequired; public void Connect(string hostname, int port, string slotName, string password = "") { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown try { Log.Message($"Attempting to connect to {hostname}:{port} as {slotName}"); SlotName = slotName; session = ArchipelagoSessionFactory.CreateSession(hostname, port); session.Socket.ErrorReceived += new ErrorReceivedHandler(OnError); session.Socket.SocketClosed += new SocketClosedHandler(OnSocketClosed); LoginResult val = session.TryConnectAndLogin("Skul: The Hero Slayer", slotName, (ItemsHandlingFlags)7, new Version(0, 6, 6), (string[])null, (string)null, string.IsNullOrEmpty(password) ? null : password, true); if (val.Successful) { LoginSuccessful val2 = (LoginSuccessful)val; SkulAPMod.sessionSlotData = val2.SlotData; Log.Message($"Connected successfully! Slot: {val2.Slot}"); foreach (KeyValuePair slotDatum in val2.SlotData) { Log.Message($"Slot Data: {slotDatum.Key} = {slotDatum.Value}"); } SlotName = slotName; Seed = session.RoomState.Seed; if (Application_persistentDataPath_Patch.SetSlot(SlotName, Seed)) { Log.Message("[AP] New slot detected — please relaunch the game to load save data."); this.OnRestartRequired?.Invoke(); return; } APSaveManager.Load(SlotName, Seed); ArchipelagoItemHandler.LoadOptions(); InitDeathLink(); session.Items.ItemReceived += new ItemReceivedHandler(OnItemReceived); session.MessageLog.OnMessageReceived += new MessageReceivedHandler(OnMessageReceived); _itemsToSkip = ArchipelagoItemTracker.LoadFromServer(); this.OnConnected?.Invoke(); ArchipelagoItemTracker.LogAllReceivedItems(); ArchipelagoItemTracker.LogAllCheckedLocations(); } else { LoginFailure val3 = (LoginFailure)val; string text = string.Join(", ", val3.Errors); Log.Error("Connection failed: " + text); this.OnConnectionFailed?.Invoke(text); session = null; } } catch (Exception ex) { Log.Error($"Connection exception: {ex}"); this.OnConnectionFailed?.Invoke(ex.Message); session = null; } } public ArchipelagoSession GetSession() { return session; } public void Disconnect() { if (session != null) { session.Socket.DisconnectAsync(); session = null; _deathLinkService = null; Log.Message("Disconnected from Archipelago"); } } private void OnItemReceived(ReceivedItemsHelper helper) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) while (_itemsToSkip > 0) { _itemsToSkip--; helper.DequeueItem(); } ItemInfo val = helper.PeekItem(); string itemName = session.Items.GetItemName(val.ItemId, (string)null); string playerName = session.Players.GetPlayerName(PlayerInfo.op_Implicit(val.Player)); ItemFlags flags = val.Flags; long itemId = val.ItemId; Log.Message("Item Received: " + itemName + " from " + playerName); string itemColor = Utils.GetItemColor(flags); string notification = "Received " + itemName + "\nfrom " + playerName + "!"; SkulAPMod.QueueMainThreadAction(delegate { ArchipelagoItemTracker.AddReceivedItem(itemId); ArchipelagoItemHandler.GrantItem(itemId); GameBase instance = Scene.instance; if (instance != null) { UIManager uiManager = instance.uiManager; if (uiManager != null) { UnlockNotice unlockNotice = uiManager.unlockNotice; if (unlockNotice != null) { unlockNotice.Show(SkulAPMod._archipelagoSprite, notification); } } } }); helper.DequeueItem(); } private void OnMessageReceived(LogMessage message) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); MessagePart[] parts = message.Parts; foreach (MessagePart val in parts) { Color color = val.Color; string text = $"{((Color)(ref color)).R:X2}{((Color)(ref color)).G:X2}{((Color)(ref color)).B:X2}"; stringBuilder.Append("" + val.Text + ""); } string line = stringBuilder.ToString(); SkulAPMod.QueueMainThreadAction(delegate { MessageLogUI.AddMessage(line); }); } private void OnError(Exception ex, string message) { Log.Error("Socket error: " + message + " - " + ex.Message); } private void OnSocketClosed(string reason) { Log.Warning("Socket closed: " + reason); this.OnDisconnected?.Invoke(); } public void SendLocation(long locationId) { if (IsConnected) { ArchipelagoItemTracker.AddCheckedLocation(locationId); session.Locations.CompleteLocationChecks(new long[1] { locationId }); Log.Message($"Sent location check: {locationId}"); ShowLocationSentFloatingText(locationId); } } private void ShowLocationSentFloatingText(long locationId) { ArchipelagoSession capturedSession = session; Task.Run(delegate { //IL_008c: Unknown result type (might be due to invalid IL or missing references) try { ArchipelagoSession obj = capturedSession; ScoutedItemInfo val = ((obj == null) ? null : obj.Locations.ScoutLocationsAsync(false, new long[1] { locationId })?.Result?.Values.FirstOrDefault()); string text = ((val != null) ? ((ItemInfo)val).ItemName : null) ?? "Item"; if (val != null) { capturedSession.Players.GetPlayerName(PlayerInfo.op_Implicit(val.Player)); } string color = ((val != null) ? Utils.GetItemColor(((ItemInfo)val).Flags) : "ffffff"); string displayText = "Sent: " + text + " to " + val.Player.Name; SkulAPMod.QueueMainThreadAction(delegate { //IL_0051: Unknown result type (might be due to invalid IL or missing references) try { Service instance = Singleton.Instance; FloatingTextSpawner val2 = ((instance != null) ? instance.floatingTextSpawner : null); Service instance2 = Singleton.Instance; object obj2; if (instance2 == null) { obj2 = null; } else { LevelManager levelManager = instance2.levelManager; obj2 = ((levelManager != null) ? levelManager.player : null); } Character val3 = (Character)obj2; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val3 == (Object)null)) { val2.SpawnBuff(displayText, ((Component)val3).transform.position, color); } } catch (Exception ex2) { Log.Error("[AP] Floating Text spawn error: " + ex2.Message); } }); } catch (Exception ex) { Log.Error("[AP] Location scout error: " + ex.Message); } }); } private void InitDeathLink() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown _deathLinkService = DeathLinkProvider.CreateDeathLinkService(session); _deathLinkService.OnDeathLinkReceived += new DeathLinkReceivedHandler(OnDeathLinkReceived); string text = null; try { text = GetSlotDataValue("death_link"); } catch { } if (text == "1" || text == "true") { DeathLinkEnabled = true; _deathLinkService.EnableDeathLink(); Log.Message("[AP] DeathLink enabled"); } } public void SetDeathLinkEnabled(bool enabled) { if (_deathLinkService != null) { DeathLinkEnabled = enabled; if (enabled) { _deathLinkService.EnableDeathLink(); } else { _deathLinkService.DisableDeathLink(); } Log.Message("[AP] DeathLink toggled " + (enabled ? "on" : "off")); } } public void SendDeathLink() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown if (_deathLinkService != null && DeathLinkEnabled) { if (_receivingDeathLink) { _receivingDeathLink = false; return; } _deathLinkService.SendDeathLink(new DeathLink(SlotName, SlotName + " was defeated in Skul: The Hero Slayer")); Log.Message("[AP] Sent DeathLink"); } } private void OnDeathLinkReceived(DeathLink deathLink) { string text = deathLink.Source ?? "Unknown"; string text2 = deathLink.Cause ?? (text + " died"); Log.Message("[AP] DeathLink received from " + text + ": " + text2); _receivingDeathLink = true; SkulAPMod.QueueMainThreadAction(delegate { Character player = Singleton.Instance.levelManager.player; if (player != null) { ((Health)player.health).Kill(); } }); } public string GetSlotDataValue(string key) { if (session == null || SkulAPMod.sessionSlotData == null) { return null; } if (SkulAPMod.sessionSlotData.TryGetValue("options", out var value)) { Dictionary dictionary2; if (!(value is Dictionary dictionary)) { JObject val = (JObject)((value is JObject) ? value : null); dictionary2 = ((val == null) ? null : ((JToken)val).ToObject>()); } else { dictionary2 = dictionary; } Dictionary dictionary3 = dictionary2; if (dictionary3 != null && dictionary3.TryGetValue(key, out var value2)) { return value2.ToString(); } } if (SkulAPMod.sessionSlotData.TryGetValue(key, out var value3)) { return value3.ToString(); } throw new SlotDataException("Invalid option requested from apworld: " + key + ". Did you generate on the wrong version?"); } public string GetSeed() { ArchipelagoSession obj = session; if (obj == null) { return null; } IRoomStateHelper roomState = obj.RoomState; if (roomState == null) { return null; } return roomState.Seed; } public ScoutedItemInfo TryScoutLocation(long locationId, bool createHint = false) { return session.Locations.ScoutLocationsAsync(createHint, new long[1] { locationId })?.Result?.Values.First(); } public Dictionary BulkScoutLocations(long[] locationIds) { if (!IsConnected || locationIds.Length == 0) { return new Dictionary(); } return session.Locations.ScoutLocationsAsync(false, locationIds)?.Result ?? new Dictionary(); } } public static class ArchipelagoConstants { public const long MarrowTransplant = 1L; public const long ThickBone = 2L; public const long FatalMind = 3L; public const long QuickDislocation = 4L; public const long FracturePrevention = 5L; public const long AncestralFortitude = 6L; public const long NutritionSupply = 7L; public const long HeavyFrame = 8L; public const long SpiritAcceleration = 9L; public const long ExoskeletonReinforcement = 10L; public const long Reassemble = 11L; public const long AncientAlchemy = 12L; public const long FoxNpc = 13L; public const long OgreNpc = 14L; public const long DruidNpc = 15L; public const long DeathKnightNpc = 16L; public const long ProgressiveStage = 20L; public const long ProgressiveSkullTree = 21L; public const long ProgressiveBoneTree = 22L; public const long ProgressiveSpiritTree = 23L; public const long BoneItem = 30L; public const long DarkQuartzItem = 31L; public const long GoldItem = 32L; public const long DeSkullTrap = 40L; public const int BoneAmount = 10; public const int DarkQuartzAmount = 100; public const int GoldAmount = 200; public const long MarrowTransplant1 = 100L; public const long MarrowTransplant10 = 109L; public const long ThickBone1 = 110L; public const long ThickBone10 = 119L; public const long FatalMind1 = 120L; public const long FatalMind10 = 129L; public const long QuickDislocation1 = 130L; public const long QuickDislocation10 = 139L; public const long FracturePrevention1 = 140L; public const long FracturePrevention10 = 149L; public const long AncestralFortitude1 = 150L; public const long AncestralFortitude10 = 159L; public const long NutritionSupply1 = 160L; public const long NutritionSupply2 = 161L; public const long HeavyFrame1 = 162L; public const long HeavyFrame2 = 163L; public const long SpiritAcceleration1 = 164L; public const long SpiritAcceleration2 = 165L; public const long ExoskeletonReinforcement1 = 166L; public const long ExoskeletonReinforcement2 = 167L; public const long Reassemble1 = 168L; public const long Reassemble2 = 169L; public const long AncientAlchemy1 = 170L; public const long AncientAlchemy2 = 171L; public const long ForestShopItem1 = 196L; public const long ForestShopItem8 = 203L; public const long GrandHallShopItem1 = 204L; public const long GrandHallShopItem8 = 211L; public const long BlackLabShopItem1 = 212L; public const long BlackLabShopItem8 = 219L; public const long FortressShopItem1 = 228L; public const long FortressShopItem8 = 235L; public const long CastleRepair1 = 220L; public const long ForestMiniBossDefeated = 236L; public const long ForestBossDefeated = 237L; public const long GrandHallMiniBossDefeated = 238L; public const long FortressMiniBossDefeated = 239L; public const long GrandHallBossDefeated = 240L; public const long BlackLabMiniBossDefeated = 241L; public const long BlackLabBossDefeated = 242L; public const long FortressBossDefeated = 243L; public const long ForestRoom1Cleared = 500L; public const long GrandHallRoom1Cleared = 600L; public const long BlackLabRoom1Cleared = 700L; public const long FortressRoom1Cleared = 800L; public const long ForestShrine1Broken = 550L; public const long GrandHallShrine1Broken = 650L; public const long BlackLabShrine1Broken = 750L; public const long FortressShrine1Broken = 850L; public static readonly Dictionary BonusKeyToItemId = new Dictionary { ["witch/skull/0"] = 1L, ["witch/skull/1"] = 4L, ["witch/skull/2"] = 7L, ["witch/skull/3"] = 10L, ["witch/body/0"] = 2L, ["witch/body/1"] = 5L, ["witch/body/2"] = 8L, ["witch/body/3"] = 11L, ["witch/soul/0"] = 9L, ["witch/soul/1"] = 6L, ["witch/soul/2"] = 3L, ["witch/soul/3"] = 12L }; public static readonly long[] SkullBonusLocations = new long[4] { 100L, 130L, 160L, 166L }; public static readonly long[] BodyBonusLocations = new long[4] { 110L, 140L, 162L, 168L }; public static readonly long[] SoulBonusLocations = new long[4] { 120L, 150L, 164L, 170L }; public static readonly long[] ChapterRoomBaseLocations = new long[4] { 500L, 600L, 700L, 800L }; public static readonly long[] ChapterShrineBaseLocations = new long[4] { 550L, 650L, 750L, 850L }; public const string QuartzMultOption = "quartz_mult"; public const string ReqRoomCountOption = "req_room_count"; public const string DeathLinkOption = "death_link"; public const string ShrineChecksOption = "shrine_checks_count"; public const string TrapsEnabledOption = "traps_enabled"; } public static class ArchipelagoItemTracker { private static Dictionary ReceivedItems => APSaveManager.SaveData.ReceivedItems; private static HashSet CheckedLocations => APSaveManager.SaveData.CheckedLocations; public static void AddReceivedItem(long itemId) { ReceivedItems[itemId] = ((!ReceivedItems.TryGetValue(itemId, out var value)) ? 1 : (value + 1)); APSaveManager.WriteToDisk(); } public static bool HasItem(long itemId) { return ReceivedItems.ContainsKey(itemId); } public static int AmountOfItem(long itemId) { if (!ReceivedItems.TryGetValue(itemId, out var value)) { return 0; } return value; } public static void AddCheckedLocation(long locationId) { if (CheckedLocations.Add(locationId)) { APSaveManager.WriteToDisk(); } } public static bool HasLocation(long locationId) { return CheckedLocations.Contains(locationId); } public static int GetCheckedLocationCount() { return CheckedLocations.Count; } public static int LoadFromServer() { try { ArchipelagoSession session = SkulAPMod.APClient.GetSession(); if (session == null) { return 0; } ReadOnlyCollection allItemsReceived = session.Items.AllItemsReceived; int num = allItemsReceived?.Count ?? 0; int num2 = ReceivedItems.Values.Sum(); if (num > num2) { Log.Message($"[AP] Granting {num - num2} offline item(s)"); foreach (ItemInfo item in allItemsReceived.Skip(num2)) { long itemId = item.ItemId; SkulAPMod.QueueMainThreadAction(delegate { AddReceivedItem(itemId); ArchipelagoItemHandler.GrantItem(itemId); }); } } IEnumerable allLocationsChecked = session.Locations.AllLocationsChecked; HashSet serverChecked = new HashSet(allLocationsChecked ?? Enumerable.Empty()); long[] array = CheckedLocations.Where((long id) => !serverChecked.Contains(id)).ToArray(); if (array.Length != 0) { Log.Message($"[AP] Re-sending {array.Length} pending location check(s)"); session.Locations.CompleteLocationChecks(array); } return num; } catch (Exception arg) { Log.Error($"[AP] LoadFromServer exception: {arg}"); return 0; } } public static void LogAllReceivedItems() { int num = ReceivedItems.Values.Sum(); Log.Message($"[AP Debug] === Received Items ({num} total) ==="); foreach (KeyValuePair item in ReceivedItems.OrderBy((KeyValuePair kv) => kv.Key)) { Log.Message($"[AP Debug] Item {item.Key}: x{item.Value}"); } } public static void LogAllCheckedLocations() { Log.Message($"[AP Debug] === Checked Locations ({CheckedLocations.Count} total) ==="); foreach (long item in CheckedLocations.OrderBy((long x) => x)) { Log.Message($"[AP Debug] Location {item}"); } } public static int AmountOfWitchBonus(string bonusKey) { if (ArchipelagoConstants.BonusKeyToItemId.TryGetValue(bonusKey, out var value)) { return AmountOfItem(value); } return 0; } public static bool CheckWitchTreeAvailability(string treeName, int indexInTree) { return treeName switch { "Characters.WitchBonus+Skull" => AmountOfItem(21L) >= indexInTree, "Characters.WitchBonus+Body" => AmountOfItem(22L) >= indexInTree, "Characters.WitchBonus+Soul" => AmountOfItem(23L) >= indexInTree, _ => false, }; } } public class ConnectionUI : MonoBehaviour { private bool showUI = true; private string hostname = "archipelago.gg"; private string slotName = ""; private string port = "38281"; private string password = ""; private string statusMessage = ""; private bool _restartRequired; private Rect windowRect = new Rect((float)(Screen.width / 2 - 300), (float)(Screen.height / 2 - 250), 800f, 700f); private ArchipelagoClient apClient; private bool NeedsConnection => !(apClient?.IsConnected ?? false); public void Initialize(ArchipelagoClient client) { apClient = client; apClient.OnConnected += delegate { statusMessage = "Connected successfully!"; }; apClient.OnConnectionFailed += delegate(string error) { statusMessage = "Failed: " + error; }; apClient.OnDisconnected += delegate { statusMessage = "Disconnected"; }; apClient.OnRestartRequired += delegate { _restartRequired = true; statusMessage = "New slot detected. Please restart the game."; }; apClient.OnConnected += delegate { FileWriter fileWriter = Object.FindObjectOfType(); if (!((Object)(object)fileWriter == (Object)null)) { int.TryParse(port, out var result); fileWriter.WriteLastConnection(hostname, result, slotName, password); } }; (string, string, string, string) tuple = FileWriter.ReadLastConnection(); if (!string.IsNullOrEmpty(tuple.Item1)) { (hostname, _, _, _) = tuple; } if (!string.IsNullOrEmpty(tuple.Item2)) { port = tuple.Item2; } if (!string.IsNullOrEmpty(tuple.Item3)) { slotName = tuple.Item3; } if (!string.IsNullOrEmpty(tuple.Item4)) { password = tuple.Item4; } } public void ToggleUI() { showUI = !showUI; } private void Update() { if (_restartRequired) { showUI = true; Time.timeScale = 0f; } else if (NeedsConnection) { showUI = true; Time.timeScale = 0f; } else { showUI = false; Time.timeScale = 1f; } } private void OnGUI() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if (showUI || NeedsConnection) { GUI.skin.label.fontSize = 24; GUI.skin.button.fontSize = 24; GUI.skin.textField.fontSize = 24; Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.5f, 0f, 1f); windowRect = GUI.Window(0, windowRect, new WindowFunction(DrawWindow), "Archipelago Connection"); GUI.backgroundColor = backgroundColor; } } private void DrawWindow(int windowID) { GUILayout.BeginVertical(Array.Empty()); GUILayout.Label("Hostname:", Array.Empty()); hostname = GUILayout.TextField(hostname, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }); GUILayout.Space(10f); GUILayout.Label("Port:", Array.Empty()); port = GUILayout.TextField(port, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }); GUILayout.Space(10f); GUILayout.Label("Slot Name:", Array.Empty()); slotName = GUILayout.TextField(slotName, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }); GUILayout.Space(10f); GUILayout.Label("Password (optional):", Array.Empty()); password = GUILayout.PasswordField(password, '*', (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }); GUILayout.Space(15f); if (!_restartRequired) { ArchipelagoClient archipelagoClient = apClient; if (archipelagoClient != null && archipelagoClient.IsConnected) { if (GUILayout.Button("Disconnect", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) })) { apClient.Disconnect(); } } else if (GUILayout.Button("Connect", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) })) { int result; if (string.IsNullOrEmpty(slotName)) { statusMessage = "Please enter a slot name!"; } else if (int.TryParse(port, out result)) { statusMessage = "Connecting..."; apClient?.Connect(hostname, result, slotName, password); } else { statusMessage = "Invalid port number!"; } } } GUILayout.Space(15f); if (!string.IsNullOrEmpty(statusMessage)) { GUILayout.Label("Status: " + statusMessage, Array.Empty()); } GUILayout.EndVertical(); GUI.DragWindow(); } } public class APSaveData { public Dictionary ReceivedItems = new Dictionary(); public HashSet CheckedLocations = new HashSet(); } public static class APSaveManager { private static string _slotName; private static string _seed; public static APSaveData SaveData { get; private set; } = new APSaveData(); private static string SavePath { get { if (!string.IsNullOrEmpty(_slotName) && !string.IsNullOrEmpty(_seed)) { return Path.Combine(Application.persistentDataPath, _slotName + "_ap.json"); } return null; } } public static void Load(string slotName, string seed) { _slotName = slotName; _seed = seed; string savePath = SavePath; if (savePath != null && File.Exists(savePath)) { try { SaveData = JsonConvert.DeserializeObject(File.ReadAllText(savePath)) ?? new APSaveData(); Log.Message("[APSave] Loaded AP save for slot '" + slotName + "'"); return; } catch (Exception ex) { Log.Error("[APSave] Failed to load: " + ex.Message); SaveData = new APSaveData(); return; } } SaveData = new APSaveData(); Log.Message("[APSave] No existing AP save for '" + slotName + "', starting fresh"); } public static void WriteToDisk() { if (string.IsNullOrEmpty(_slotName)) { return; } try { File.WriteAllText(SavePath, JsonConvert.SerializeObject((object)SaveData, (Formatting)1)); } catch (Exception ex) { Log.Error("[APSave] Write failed: " + ex.Message); } } } public class FileWriter : MonoBehaviour { private const string LastConnectionFileName = "last_connection.txt"; public void WriteLastConnection(string host, int port, string slotName, string password) { try { string path = Application.persistentDataPath + "/last_connection.txt"; List contents = new List { host ?? "", port.ToString(), slotName ?? "", password ?? "" }; File.WriteAllLines(path, contents); } catch (Exception ex) { Debug.LogError((object)("Failed to write last connection: " + ex.Message)); } } public static (string host, string port, string slotName, string password) ReadLastConnection() { try { string path = Application.persistentDataPath + "/last_connection.txt"; if (!File.Exists(path)) { return (null, null, null, null); } string[] array = File.ReadAllLines(path); return ((array.Length != 0) ? array[0] : null, (array.Length > 1) ? array[1] : null, (array.Length > 2) ? array[2] : null, (array.Length > 3) ? array[3] : null); } catch (Exception ex) { Debug.LogError((object)("Failed to read last connection: " + ex.Message)); return (null, null, null, null); } } } public static class ArchipelagoItemHandler { public static float QuartzMultiplier = 1f; public static int ReqRoomCount = 8; public static int ShrineChecksCount = 5; public static bool TrapsEnabled = true; public static void LoadOptions() { if (float.TryParse(SkulAPMod.APClient.GetSlotDataValue("quartz_mult"), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { QuartzMultiplier = result; Log.Message($"[Slot Data] quartz_mult = {QuartzMultiplier}"); } if (int.TryParse(SkulAPMod.APClient.GetSlotDataValue("req_room_count"), out var result2)) { ReqRoomCount = result2; Log.Message($"[Slot Data] req_room_count = {ReqRoomCount}"); } if (int.TryParse(SkulAPMod.APClient.GetSlotDataValue("shrine_checks_count"), out var result3)) { ShrineChecksCount = result3; Log.Message($"[Slot Data] shrine_checks_count = {ShrineChecksCount}"); } string text = null; try { text = SkulAPMod.APClient.GetSlotDataValue("traps_enabled"); } catch { } if (text != null) { TrapsEnabled = text == "1" || text.Equals("true", StringComparison.OrdinalIgnoreCase); Log.Message($"[Slot Data] traps_enabled = {TrapsEnabled}"); } } public static void GrantItem(long itemId) { //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) long num = itemId - 1; if ((ulong)num <= 31uL) { NpcType val; switch (num) { case 31L: Currency.gold.Earn(200); return; case 30L: Currency.darkQuartz.Earn(100); return; case 29L: Currency.bone.Earn(10); return; case 0L: case 1L: case 2L: case 3L: case 4L: case 5L: case 6L: case 7L: case 8L: case 9L: case 10L: case 11L: WitchStatApplicator.Apply(itemId); return; case 12L: case 13L: case 14L: case 15L: { GameDataProgress_SetRescued_Patch.BypassCheck = true; long num2 = itemId - 13; if ((ulong)num2 > 2uL) { goto IL_017e; } switch (num2) { case 0L: break; case 1L: goto IL_0174; case 2L: goto IL_0179; default: goto IL_017e; } val = (NpcType)1; goto IL_0181; } case 16L: case 17L: case 18L: case 19L: case 20L: case 21L: case 22L: case 23L: case 24L: case 25L: case 26L: case 27L: case 28L: return; IL_017e: val = (NpcType)4; goto IL_0181; IL_0179: val = (NpcType)3; goto IL_0181; IL_0174: val = (NpcType)2; goto IL_0181; IL_0181: Progress.SetRescued(val, true); GameDataProgress_SetRescued_Patch.BypassCheck = false; return; } } if (itemId != 40 || !TrapsEnabled) { return; } Character player = Singleton.Instance.levelManager.player; if (!((Object)(object)player == (Object)null)) { WeaponInventory weapon = player.playerComponents.inventory.weapon; int num3 = 1 - weapon.currentIndex; Weapon val2 = weapon.weapons[num3]; if (!((Object)(object)val2 == (Object)null)) { Log.Message($"[AP] De-Skull Trap: removing secondary skull at slot {num3}"); Object.Destroy((Object)(object)((Component)val2).gameObject); weapon.Unequip(val2); } } } } public static class Utils { public static bool IsConnectedAndEnabled => SkulAPMod.APClient?.IsConnected ?? false; public static bool IsTrue(string str) { if (str == "true" || str == "1") { return true; } return false; } public static string GetItemColor(ItemFlags flags) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((flags & 4) != 0) { return "fa8080"; } if ((flags & 1) != 0) { return "9676f5"; } if ((flags & 2) != 0) { return "318ce0"; } return "ffffff"; } public static string GetItemDescText(ItemFlags flags, string playerName = "") { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) string text = ((playerName.Length > 0) ? (" for " + playerName) : ""); if ((flags & 4) != 0) { return "Dangerous one" + text + ", this is..."; } if ((flags & 1) != 0) { return "This trembles with an aura of importance" + text + "..."; } if ((flags & 2) != 0) { return "Appears to be of some use" + text + "..."; } return "Nothing special" + text + ", really..."; } } internal static class Log { private static ManualLogSource _logSource; internal static void Init(ManualLogSource logSource) { _logSource = logSource; } internal static void Debug(object data) { _logSource.LogDebug(data); } internal static void Error(object data) { _logSource.LogError(data); } internal static void Fatal(object data) { _logSource.LogFatal(data); } internal static void Info(object data) { _logSource.LogInfo(data); } internal static void Message(object data) { _logSource.LogMessage(data); } internal static void Warning(object data) { _logSource.LogWarning(data); } } public class MessageLogUI : MonoBehaviour { private static readonly List _messages = new List(); private const int MaxMessages = 50; private const KeyCode ToggleKey = 290; private bool _visible = true; private Vector2 _scrollPos; private GUIStyle _labelStyle; private bool _stylesInitialized; public static void AddMessage(string richText) { if (_messages.Count >= 50) { _messages.RemoveAt(0); } _messages.Add(richText); } private void Update() { if (Input.GetKeyDown((KeyCode)290)) { _visible = !_visible; } } private void InitStyles() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown if (!_stylesInitialized) { _stylesInitialized = true; GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 13, wordWrap = true, richText = true }; val.normal.textColor = Color.white; _labelStyle = val; } } private void OnGUI() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0105: 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_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_0152: 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_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Invalid comparison between Unknown and I4 InitStyles(); float num = 620f; float num2 = 4f; ArchipelagoClient aPClient = SkulAPMod.APClient; string text; Color textColor = default(Color); if (aPClient != null && aPClient.IsConnected) { text = "[F9] Skul 0.1.5 | Connected as " + aPClient.SlotName + " " + (_visible ? "▼" : "▲"); ((Color)(ref textColor))..ctor(0.59f, 0.96f, 0.59f, 0.9f); } else { text = "[F9] AP | Disconnected " + (_visible ? "▼" : "▲"); ((Color)(ref textColor))..ctor(0.96f, 0.4f, 0.4f, 0.9f); } GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 12, fontStyle = (FontStyle)1 }; val.normal.textColor = textColor; GUIStyle val2 = val; GUI.Label(new Rect(num2, 2f, num, 18f), text, val2); if (aPClient != null && aPClient.IsConnected) { GUIStyle val3 = new GUIStyle(GUI.skin.toggle) { fontSize = 12, fontStyle = (FontStyle)1 }; val3.normal.textColor = Color.white; val3.onNormal.textColor = new Color(0.59f, 0.96f, 0.59f); GUIStyle val4 = val3; float x = val2.CalcSize(new GUIContent(text)).x; bool flag = GUI.Toggle(new Rect(num2 + x + 6f, 2f, 110f, 18f), aPClient.DeathLinkEnabled, " DeathLink", val4); if (flag != aPClient.DeathLinkEnabled) { aPClient.SetDeathLinkEnabled(flag); } } if (!_visible) { return; } float num3 = 300f; float num4 = 20f; Rect val5 = new Rect(num2, num4, num, num3); Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.05f, 0.05f, 0.1f, 0.85f); GUI.Box(val5, GUIContent.none); GUI.backgroundColor = backgroundColor; Rect val6 = default(Rect); ((Rect)(ref val6))..ctor(num2 + 4f, num4 + 4f, num - 8f, num3 - 8f); float num5 = Mathf.Max((float)_messages.Count * 20f, ((Rect)(ref val6)).height); Rect val7 = default(Rect); ((Rect)(ref val7))..ctor(0f, 0f, ((Rect)(ref val6)).width - 16f, num5); _scrollPos = GUI.BeginScrollView(val6, _scrollPos, val7); float num6 = num5 - (float)_messages.Count * 20f; foreach (string message in _messages) { GUI.Label(new Rect(2f, num6, ((Rect)(ref val7)).width - 4f, 20f), message, _labelStyle); num6 += 20f; } GUI.EndScrollView(); if ((int)Event.current.type == 8) { _scrollPos.y = float.PositiveInfinity; } } } [BepInPlugin("Jeffdev.SkulAPMod", "SkulAPMod", "0.1.5")] public class SkulAPMod : BaseUnityPlugin { [CompilerGenerated] private sealed class d__29 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public SkulAPMod <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__29(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown int num = <>1__state; SkulAPMod skulAPMod = <>4__this; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; } else { <>1__state = -1; skulAPMod._sendingLocations = true; } long locationId; lock (_locationSendQueue) { if (_locationSendQueue.Count != 0) { locationId = _locationSendQueue.Dequeue(); goto IL_005c; } } skulAPMod._sendingLocations = false; return false; IL_005c: if (!ArchipelagoItemTracker.HasLocation(locationId)) { APClient.SendLocation(locationId); } <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string PluginGuid = "Jeffdev.SkulAPMod"; private const string PluginAuthor = "Jeffdev"; private const string PluginName = "SkulAPMod"; private const string PluginVersion = "0.1.5"; public const string Version = "0.1.5"; private Harmony harmony; public static Dictionary sessionSlotData; public static Sprite _archipelagoSprite; private static GameObject uiObject; private static bool uiCreated; private static readonly Queue _mainThreadQueue = new Queue(); private static readonly Queue _locationSendQueue = new Queue(); private const float LocationSendDelay = 0.5f; private bool _sendingLocations; private FileWriter fileWriter; public static SkulAPMod Instance { get; private set; } public static ArchipelagoClient APClient { get; private set; } public void Awake() { Instance = this; InitializeLogging(); ApplyPatches(); InitializeComponents(); Log.Info("SkulAPMod loaded successfully"); } private void InitializeLogging() { Log.Init(((BaseUnityPlugin)this).Logger); } private void InitializeComponents() { fileWriter = ((Component)this).gameObject.AddComponent(); _archipelagoSprite = LoadEmbeddedSprite("SkulAPMod.Resources.Sprites.skul_ap.png"); APClient = new ArchipelagoClient(); APClient.OnConnected += OnArchipelagoConnected; APClient.OnDisconnected += OnArchipelagoDisconnected; } private static Sprite LoadEmbeddedSprite(string resourceName) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) try { Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); if (manifestResourceStream == null) { Log.Error("Embedded resource not found: " + resourceName); return null; } byte[] array = new byte[manifestResourceStream.Length]; manifestResourceStream.Read(array, 0, array.Length); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); ImageConversion.LoadImage(val, array); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f, 1u, (SpriteMeshType)0); } catch (Exception ex) { Log.Error("Failed to load embedded sprite '" + resourceName + "': " + ex.Message); return null; } } private void ApplyPatches() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_0087: Expected O, but got Unknown harmony = new Harmony("Jeffdev.SkulAPMod"); harmony.PatchAll(Assembly.GetExecutingAssembly()); Type type = AccessTools.TypeByName("Characters.WitchBonus+ReviveOnce"); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "Revive", (Type[])null, (Type[])null) : null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ReviveOnce_Revive_Patch), "Prefix", (Type[])null), new HarmonyMethod(typeof(ReviveOnce_Revive_Patch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Log.Warning("[AP] Could not find WitchBonus+ReviveOnce.Revive — Reassemble AP level and DeathLink guard inactive."); } } public static void EnqueueLocationSend(long locationId) { if (!ArchipelagoItemTracker.HasLocation(locationId)) { lock (_locationSendQueue) { _locationSendQueue.Enqueue(locationId); } if ((Object)(object)Instance != (Object)null && !Instance._sendingLocations) { ((MonoBehaviour)Instance).StartCoroutine(Instance.ProcessLocationSendQueue()); } } } [IteratorStateMachine(typeof(d__29))] private IEnumerator ProcessLocationSendQueue() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__29(0) { <>4__this = this }; } public static void QueueMainThreadAction(Action action) { lock (_mainThreadQueue) { _mainThreadQueue.Enqueue(action); } } public void Update() { while (true) { Action action; lock (_mainThreadQueue) { if (_mainThreadQueue.Count == 0) { break; } action = _mainThreadQueue.Dequeue(); } try { action(); } catch (Exception arg) { Log.Error($"[MainThread] Action threw: {arg}"); } } } private void OnArchipelagoConnected() { Log.Message("Connected to Archipelago - loading items"); uiObject.GetComponent().ToggleUI(); APSessionManager.OnConnected(); PreloadWitchScoutCache(); } private static void PreloadWitchScoutCache() { int num = 72; long[] array = new long[num]; for (int i = 0; i < num; i++) { array[i] = 100L + (long)i; } Dictionary dictionary = APClient.BulkScoutLocations(array); WitchOption_UpdateTexts_Patch.PreloadCache(dictionary); Log.Message($"Pre-scouted {dictionary.Count} witch bonus locations"); } private void OnArchipelagoDisconnected() { Log.Message("Disconnected from Archipelago — returning to title screen"); QueueMainThreadAction(delegate { try { Singleton.Instance.ResetGameScene(); } catch (Exception ex) { Log.Error("Failed to return to title screen: " + ex.Message); } }); } public static void CreateUI() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!uiCreated) { Log.Message("Creating Archipelago UI..."); uiObject = new GameObject("ArchipelagoUI"); Object.DontDestroyOnLoad((Object)(object)uiObject); uiObject.AddComponent().Initialize(APClient); uiObject.AddComponent(); uiCreated = true; } } public void OnDestroy() { APClient?.Disconnect(); if ((Object)(object)uiObject != (Object)null) { Object.Destroy((Object)(object)uiObject); } Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } [Serializable] public class SlotDataException : ApplicationException { public SlotDataException() { } public SlotDataException(string message) : base(message) { } public SlotDataException(string message, Exception innerException) : base(message, innerException) { } } public static class PluginInfo { public const string PLUGIN_GUID = "SkulAPMod"; public const string PLUGIN_NAME = "SkulAPMod"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace SkulAPMod.Patches { [HarmonyPatch(typeof(Chapter), "LoadStage")] public class Chapter_LoadStage_Patch { private static void Postfix(Chapter __instance) { //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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected I4, but got Unknown ? val = __instance.type - 3; Log.Info($"{__instance.type}, {__instance.stageName} {__instance.chapterName}"); StageTracker.Chapter = (int)val; } } [HarmonyPatch(typeof(PlayerKillCounter), "CountKill")] public class PlayerKillCounter_CountKill_Patch { private static void Postfix(ITarget target) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_005b: Expected I4, but got Unknown //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Invalid comparison between Unknown and I4 //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (!SkulAPMod.APClient.IsConnected || ((target != null) ? target.character : null) == null || (int)target.character.type != 3) { return; } Key key = target.character.key; long? num = (key - 2001) switch { 0 => 237L, 3 => 240L, 4 => 242L, 5 => 243L, _ => null, }; if (num.HasValue) { Log.Info($"Boss killed: key={target.character.key}, locationId={num}"); if (!ArchipelagoItemTracker.HasLocation(num.Value)) { SkulAPMod.APClient.SendLocation(num.Value); } } if ((int)target.character.key == 2009) { ArchipelagoGoalManager.CheckAndCompleteGoal(); } } } [HarmonyPatch(typeof(LevelManager), "InvokeOnActivateMapReward")] public class LevelManager_InvokeOnActivateMapReward_Patch { private static void Postfix() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected I4, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Invalid comparison between Unknown and I4 if ((Object)(object)Map.Instance == (Object)null || !SkulAPMod.APClient.IsConnected) { return; } int chapter = StageTracker.Chapter; if (chapter < 0) { return; } Type type = Map.Instance.type; switch ((int)type) { case 0: case 3: if (!((Object)(object)Map.Instance.waveContainer == (Object)null) && Map.Instance.waveContainer.enemyWaves.Length != 0 && chapter < ArchipelagoConstants.ChapterRoomBaseLocations.Length) { int roomChecksSent = GetRoomChecksSent(chapter); if (roomChecksSent < ArchipelagoItemHandler.ReqRoomCount) { Log.Info($"Wave clear: Chapter={chapter}, RoomsSentSoFar={roomChecksSent}"); SendIfNew(ArchipelagoConstants.ChapterRoomBaseLocations[chapter] + roomChecksSent); } } break; case 2: if ((int)Map.Instance.mapReward.type == 4) { long? num = chapter switch { 0 => 236L, 1 => 238L, 2 => 241L, 3 => 239L, _ => null, }; Log.Info($"Mini-boss defeated: Chapter={chapter}, LocationId={num}"); if (num.HasValue) { SendIfNew(num.Value); } } break; case 1: break; } } private static int GetRoomChecksSent(int chapter) { long num = ArchipelagoConstants.ChapterRoomBaseLocations[chapter]; int reqRoomCount = ArchipelagoItemHandler.ReqRoomCount; int num2 = 0; for (int i = 0; i < reqRoomCount; i++) { if (ArchipelagoItemTracker.HasLocation(num + i)) { num2++; } } return num2; } private static void SendIfNew(long locationId) { if (!ArchipelagoItemTracker.HasLocation(locationId)) { SkulAPMod.APClient.SendLocation(locationId); } } } public static class ReviveDetector { public static bool PlayerJustRevived; public static void Prefix() { PlayerJustRevived = true; } } [HarmonyPatch(typeof(LevelManager), "ResetGame", new Type[] { })] public class LevelManager_ResetGame_DeathLink_Patch { private static void Prefix(LevelManager __instance) { if (ReviveDetector.PlayerJustRevived) { ReviveDetector.PlayerJustRevived = false; } else if (SkulAPMod.APClient.IsConnected) { Character player = __instance.player; if (player != null && ((Health)player.health).dead) { SkulAPMod.APClient.SendDeathLink(); } } } } [HarmonyPatch(typeof(LevelManager), "ResetGame", new Type[] { })] public class LevelManager_ResetGame_RegrantFiller_Patch { private static void Postfix() { if (SkulAPMod.APClient.IsConnected) { int num = ArchipelagoItemTracker.AmountOfItem(32L); for (int i = 0; i < num; i++) { ArchipelagoItemHandler.GrantItem(32L); } int num2 = ArchipelagoItemTracker.AmountOfItem(30L); for (int j = 0; j < num2; j++) { ArchipelagoItemHandler.GrantItem(30L); } if (ArchipelagoItemTracker.AmountOfItem(11L) > 0) { Progress.reassembleUsed = false; } } } } [HarmonyPatch(typeof(LevelManager), "ChangeChapter")] public class LevelManager_ChangeChapter_Patch { private static bool Prefix(LevelManager __instance, Type __0) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected I4, but got Unknown if (SkulAPMod.APClient == null || !SkulAPMod.APClient.IsConnected) { return true; } int num = __0 - 3; if ((num <= 0 || num > 3) ? true : false) { return true; } int num2 = ArchipelagoItemTracker.AmountOfItem(20L); if (num <= num2) { return true; } Log.Message($"[AP] Chapter {num} gated - have {num2} Progressive Stage item(s), need {num}."); __instance.ResetGame(); return false; } } [HarmonyPatch(typeof(Altar), "Destroy")] public class Altar_Destroy_Patch { private static void Postfix() { if (!SkulAPMod.APClient.IsConnected) { return; } int chapter = StageTracker.Chapter; if (chapter < 0 || chapter >= ArchipelagoConstants.ChapterShrineBaseLocations.Length) { return; } int shrineChecksCount = ArchipelagoItemHandler.ShrineChecksCount; long num = ArchipelagoConstants.ChapterShrineBaseLocations[chapter]; int num2 = 0; for (int i = 0; i < shrineChecksCount; i++) { if (ArchipelagoItemTracker.HasLocation(num + i)) { num2++; } } if (num2 < shrineChecksCount) { Log.Info($"Shrine destroyed: Chapter={chapter}, ShrinesSentSoFar={num2}"); SkulAPMod.APClient.SendLocation(num + num2); } } } [HarmonyPatch(typeof(Progress), "SetRescued")] public class GameDataProgress_SetRescued_Patch { public static bool BypassCheck; private static bool Prefix(NpcType npcType, bool value) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected I4, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) if (!value) { return true; } if (!SkulAPMod.APClient.IsConnected) { return true; } if (BypassCheck) { return true; } long? num = (npcType - 1) switch { 0 => 13L, 1 => 14L, 2 => 15L, 3 => 16L, _ => null, }; if (!num.HasValue) { return true; } bool num2 = ArchipelagoItemTracker.HasItem(num.Value); if (!num2) { Log.Message($"[AP] Blocked rescue of {npcType} — item not yet received."); } return num2; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public class Progress_HousingPoint_Set_Patch { private static void Postfix() { if (!SkulAPMod.APClient.IsConnected) { return; } int num = 0; for (int i = 0; i < 4; i++) { if (ArchipelagoItemTracker.HasLocation(220L + (long)i)) { num++; } } if (num < 4) { SkulAPMod.APClient.SendLocation(220L + (long)num); } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public class BuildLevel_Cost_Patch { private static void Postfix(ref int __result) { __result = (int)((float)__result * 0.8f); } } public static class OtherPatches { [HarmonyPatch(typeof(Currency), "Earn", new Type[] { typeof(int) })] public class GameDataCurrency_Earn { private static void Prefix(Currency __instance, ref int amount) { if (SkulAPMod.APClient.IsConnected && __instance == Currency.darkQuartz) { amount = (int)((float)amount * ArchipelagoItemHandler.QuartzMultiplier); } } } } public static class StageTracker { public static int Chapter = -1; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static class Application_persistentDataPath_Patch { private static string _originalPath; private static string _slotSubPath; private const string SessionFileName = "archipelago_session.txt"; public static bool SetSlot(string slotName, string seed) { string text = slotName + "_" + seed; bool result = text != _slotSubPath; _slotSubPath = text; if (_originalPath != null) { File.WriteAllText(Path.Combine(_originalPath, "archipelago_session.txt"), _slotSubPath); } return result; } private static void Postfix(ref string __result) { if (_originalPath == null) { _originalPath = __result; string path = Path.Combine(_originalPath, "archipelago_session.txt"); if (File.Exists(path)) { _slotSubPath = File.ReadAllText(path).Trim(); } } string path2 = (string.IsNullOrEmpty(_slotSubPath) ? "archipelago" : Path.Combine("archipelago", _slotSubPath)); __result = Path.Combine(_originalPath, path2); if (!Directory.Exists(__result)) { Directory.CreateDirectory(__result); } } } [HarmonyPatch(typeof(Main), "StartGame")] public static class TitleScreenPatch { internal static bool Suppress; private static bool Prefix(Main __instance) { if (Suppress) { return true; } SkulAPMod.CreateUI(); if (!SkulAPMod.APClient.IsConnected) { APSessionManager.PendingMainInstance = __instance; Log.Message("[AP] Waiting for Archipelago connection"); return false; } return true; } } [HarmonyPatch(typeof(Tutorial), "isPlayed")] public static class Tutorial_isPlayed_Patch { private static void Postfix(ref bool __result) { if (SkulAPMod.APClient.IsConnected) { __result = true; } } } [HarmonyPatch(typeof(UnlockNotice), "Show")] public class UnlockNotice_Show_Patch { [CompilerGenerated] private sealed class d__1 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Animator animator; public UnlockNotice instance; private float 5__2; private float 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: { <>1__state = -1; if ((Object)(object)animator.runtimeAnimatorController != (Object)null) { if (!((Behaviour)animator).enabled) { ((Behaviour)animator).enabled = true; } animator.Play(0, 0, 0f); } AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0); 5__2 = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).length * 0.5f; ((Behaviour)animator).enabled = false; 5__3 = 0f; goto IL_00d6; } case 1: <>1__state = -1; 5__3 += Time.unscaledDeltaTime; goto IL_00d6; case 2: <>1__state = -1; 5__3 += Time.unscaledDeltaTime; goto IL_011a; case 3: { <>1__state = -1; 5__3 += Time.unscaledDeltaTime; break; } IL_00d6: if (5__3 < 5__2) { animator.Update(Time.unscaledDeltaTime); <>2__current = null; <>1__state = 1; return true; } 5__3 = 0f; goto IL_011a; IL_011a: if (5__3 < 4f) { <>2__current = null; <>1__state = 2; return true; } 5__3 = 0f; break; } if (5__3 < 5__2) { animator.Update(Time.unscaledDeltaTime); <>2__current = null; <>1__state = 3; return true; } ((Component)instance).gameObject.SetActive(false); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool Prefix(UnlockNotice __instance, Sprite icon, string name, Image ____icon, TextMeshProUGUI ____name, Animator ____animator) { ____icon.sprite = icon; ((Graphic)____icon).SetNativeSize(); ((TMP_Text)____name).text = name; ((TMP_Text)____name).enableAutoSizing = true; ((TMP_Text)____name).fontSizeMin = 8f; ((TMP_Text)____name).fontSizeMax = 36f; ((Component)__instance).gameObject.SetActive(true); if (((Component)__instance).gameObject.activeInHierarchy) { ((MonoBehaviour)__instance).StopAllCoroutines(); ((MonoBehaviour)__instance).StartCoroutine(CustomFadeInOut(__instance, ____animator)); } return false; } [IteratorStateMachine(typeof(d__1))] private static IEnumerator CustomFadeInOut(UnlockNotice instance, Animator animator) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(0) { instance = instance, animator = animator }; } } [HarmonyPatch(typeof(WitchMastery), "Save")] public class WitchMastery_SaveJson_Patch { private static void Postfix(WitchMastery __instance) { if (SkulAPMod.APClient.IsConnected) { SendWitchLocationChecks(__instance); } } private static void SendWitchLocationChecks(WitchMastery wm) { for (int i = 0; i < 4; i++) { CheckTree(ArchipelagoConstants.SkullBonusLocations[i], ((Data)(object)wm.skull[i]).value); CheckTree(ArchipelagoConstants.BodyBonusLocations[i], ((Data)(object)wm.body[i]).value); CheckTree(ArchipelagoConstants.SoulBonusLocations[i], ((Data)(object)wm.soul[i]).value); } } private static void CheckTree(long baseId, int level) { for (int i = 0; i < level; i++) { if (!ArchipelagoItemTracker.HasLocation(baseId + i)) { SkulAPMod.EnqueueLocationSend(baseId + i); } } } } public static class WitchLevelOverride { [ThreadStatic] public static int? Value; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public class WitchBonus_Level_Get_Patch { private static bool Prefix(ref int __result) { if (WitchLevelOverride.Value.HasValue) { __result = WitchLevelOverride.Value.Value; return false; } return true; } } [HarmonyPatch(typeof(Bonus), "LevelUp")] public class WitchBonus_LevelUp_Patch { private static bool Prefix(Bonus __instance, ref bool __result) { if (!SkulAPMod.APClient.IsConnected) { return true; } if (!__instance.ready || __instance.level == __instance.maxLevel || !Currency.darkQuartz.Consume(__instance.levelUpCost)) { __result = false; return false; } Data obj = (Data)(object)__instance._data; int value = obj.value; obj.value = value + 1; Currency.SaveAll(); Progress.SaveAll(); __result = true; return false; } } [HarmonyPatch(typeof(Bonus), "Initialize")] public class WitchBonus_Initialize_Patch { private static bool Prefix(Bonus __instance) { if (!SkulAPMod.APClient.IsConnected) { return true; } int num = ArchipelagoItemTracker.AmountOfWitchBonus(__instance._key); WitchLevelOverride.Value = num; try { if (num > 0) { __instance.Attach(); } __instance.Update(); } finally { WitchLevelOverride.Value = null; } return false; } } public static class WitchStatApplicator { public static void Apply(long itemId) { WitchBonus instance = WitchBonus.instance; if (((instance != null) ? instance.skull : null) == null) { return; } long num = itemId - 1; if ((ulong)num > 11uL) { goto IL_0111; } switch (num) { case 0L: break; case 3L: goto IL_0071; case 6L: goto IL_0082; case 9L: goto IL_0093; case 1L: goto IL_00a1; case 4L: goto IL_00af; case 7L: goto IL_00bd; case 10L: goto IL_00cb; case 2L: goto IL_00d9; case 8L: goto IL_00e7; case 5L: goto IL_00f5; case 11L: goto IL_0103; default: goto IL_0111; } Bonus val = (Bonus)(object)instance.skull.marrowImplant; goto IL_0113; IL_0071: val = (Bonus)(object)instance.skull.fastDislocation; goto IL_0113; IL_00d9: val = (Bonus)(object)instance.soul.fatalMind; goto IL_0113; IL_00af: val = (Bonus)(object)instance.body.fractureImmunity; goto IL_0113; IL_00a1: val = (Bonus)(object)instance.body.strongBone; goto IL_0113; IL_0113: Bonus val2 = val; if (val2 != null) { val2.Initialize(); } return; IL_0111: val = null; goto IL_0113; IL_0103: val = (Bonus)(object)instance.soul.ancientAlchemy; goto IL_0113; IL_00cb: val = (Bonus)(object)instance.body.reassemble; goto IL_0113; IL_0093: val = (Bonus)(object)instance.skull.enhanceExoskeleton; goto IL_0113; IL_00e7: val = (Bonus)(object)instance.soul.soulAcceleration; goto IL_0113; IL_00bd: val = (Bonus)(object)instance.body.heavyFrame; goto IL_0113; IL_0082: val = (Bonus)(object)instance.skull.nutritionSupply; goto IL_0113; IL_00f5: val = (Bonus)(object)instance.soul.willOfAncestor; goto IL_0113; } } [HarmonyPatch(typeof(TreeElement), "Set")] public class WitchTreeElement_Set_Patch { private static void Postfix(ref TreeElement __instance) { bool flag = ArchipelagoItemTracker.CheckWitchTreeAvailability(((object)__instance._bonus.tree).ToString(), __instance._bonus.indexInTree); Log.Info(flag); __instance.interactable = flag; } } [HarmonyPatch(typeof(Option), "UpdateTexts")] public class WitchOption_UpdateTexts_Patch { internal static readonly Dictionary _scoutCache = new Dictionary(); internal static void PreloadCache(Dictionary data) { foreach (KeyValuePair datum in data) { _scoutCache[datum.Key] = datum.Value; } } private static void Postfix(Bonus ____bonus, TMP_Text ____name, TMP_Text ____level, TMP_Text ____description, TMP_Text ____nextLevelDescription, GameObject ____nextLevelContainer) { //IL_0094: 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) if (!SkulAPMod.APClient.IsConnected) { return; } long? baseLocationId = GetBaseLocationId(____bonus); if (baseLocationId.HasValue && ____bonus.level < ____bonus.maxLevel) { bool flag = ArchipelagoItemTracker.CheckWitchTreeAvailability(((object)____bonus.tree).ToString(), ____bonus.indexInTree); ScoutedItemInfo scoutInfo = GetScoutInfo(baseLocationId.Value + ____bonus.level); ____level.text = $"Checks Sent: {____bonus.level}/{____bonus.maxLevel}"; if (scoutInfo != null) { string text = (flag ? Utils.GetItemColor(((ItemInfo)scoutInfo).Flags) : "ff0000"); string text2 = (flag ? ("" + ((ItemInfo)scoutInfo).ItemName + "") : ("Locked")); string text3 = (flag ? Utils.GetItemDescText(((ItemInfo)scoutInfo).Flags, scoutInfo.Player.Name) : "You need more of this Progressive Tree to view this!"); int num = ArchipelagoItemTracker.AmountOfWitchBonus(____bonus._key); string text4 = $"{____bonus.displayName} {____bonus.level + 1} (You currently have {num} of {____bonus.displayName} sent to you.)"; ____name.text = text2; ____description.text = text3; if (____nextLevelContainer.activeSelf) { ____nextLevelDescription.text = text4; } UpdateAPIcon(____name, visible: true); return; } } int num2 = ArchipelagoItemTracker.AmountOfWitchBonus(____bonus._key); ____description.text = $"{____description.text}\nYou have sent all checks attached to this. You currently have {num2} of {____bonus.displayName} sent to you."; if (____nextLevelContainer.activeSelf) { ____nextLevelDescription.text = "???"; } UpdateAPIcon(____name, visible: false); } private static void UpdateAPIcon(TMP_Text nameText, bool visible) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) Transform val = nameText.transform.Find("APIcon"); RectTransform component; if ((Object)(object)val == (Object)null) { if (!visible) { return; } GameObject val2 = new GameObject("APIcon"); val2.transform.SetParent(nameText.transform, false); Image obj = val2.AddComponent(); obj.sprite = SkulAPMod._archipelagoSprite; obj.preserveAspect = true; component = val2.GetComponent(); component.sizeDelta = new Vector2(36f, 36f); RectTransform obj2 = component; RectTransform obj3 = component; Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 0.5f); obj3.anchorMax = val3; obj2.anchorMin = val3; component.pivot = new Vector2(0f, 0.5f); } else { ((Component)val).gameObject.SetActive(visible); if (!visible) { return; } component = ((Component)val).GetComponent(); } component.anchoredPosition = new Vector2(nameText.preferredWidth + 10f, 0f); } private static ScoutedItemInfo GetScoutInfo(long locationId) { if (_scoutCache.TryGetValue(locationId, out var value)) { return value; } ScoutedItemInfo val = SkulAPMod.APClient.TryScoutLocation(locationId); if (val != null) { _scoutCache[locationId] = val; } return val; } private static long? GetBaseLocationId(Bonus bonus) { WitchBonus instance = WitchBonus.instance; if (instance == null || bonus == null) { return null; } int indexInTree = bonus.indexInTree; if (indexInTree < 0 || indexInTree >= 4) { return null; } if (bonus.tree == instance.skull) { return ArchipelagoConstants.SkullBonusLocations[indexInTree]; } if (bonus.tree == instance.body) { return ArchipelagoConstants.BodyBonusLocations[indexInTree]; } if (bonus.tree == instance.soul) { return ArchipelagoConstants.SoulBonusLocations[indexInTree]; } return null; } } public static class ReviveOnce_Revive_Patch { public static void Prefix(Bonus __instance) { ReviveDetector.PlayerJustRevived = true; if (SkulAPMod.APClient.IsConnected) { int value = ArchipelagoItemTracker.AmountOfWitchBonus(__instance._key); WitchLevelOverride.Value = value; } } public static void Postfix() { WitchLevelOverride.Value = null; } } } namespace SkulAPMod.Helpers { public static class ArchipelagoGoalManager { public static long GetGoalId() { long.TryParse(SkulAPMod.APClient.GetSlotDataValue("goal"), out var result); return result; } public static void CheckAndCompleteGoal() { if (Utils.IsConnectedAndEnabled) { CompleteGoal(); } } private static void CompleteGoal() { SkulAPMod.APClient.GetSession().SetGoalAchieved(); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }