Decompiled source of KillMeForMyPower v2.1.1
KillMeForMyPower.dll
Decompiled 21 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using KillMeForMyPower.Commands; using KillMeForMyPower.Managers; using KillMeForMyPower.Restrictions.BossNameManagement; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("PowersForbiddenForNewbies")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("PowersForbiddenForNewbies")] [assembly: AssemblyCopyright("Copyright © 2024")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e78efca2-b045-4686-8901-4fedba31614f")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace KillMeForMyPower { public class KillMeForMyPowerUtils { private static BossNameEnum parseBossName(string value) { Logger.Log("Parsing value: " + value); return (BossNameEnum)Enum.Parse(typeof(BossNameEnum), value, ignoreCase: true); } public static BossNameEnum findBossNameByPrefabName(string value, bool logSilent = false) { Logger.Log("Parsing prefab bossName " + value + " into Enum"); foreach (BossNameEnum value2 in Enum.GetValues(typeof(BossNameEnum))) { if (value2.GetFightBossname() == value || value2.ToString() == value) { return value2; } } if (!logSilent) { Logger.LogWarning(value + " not a vanilla/monstrum boss name."); } return BossNameEnum.None; } public static string getBossNameTranslation(BossNameEnum bossName) { return bossName.GetTranslationKey(); } public static bool HasDefeatedBossNameStr(string bossName) { BossNameEnum bossNameEnum; try { bossNameEnum = parseBossName(bossName); } catch (Exception ex) { Logger.LogError("Error in HasDefeatedBossName with " + bossName + " in parseBossName. " + ex); return false; } return HasDefeatedBossName(bossNameEnum); } public static bool HasDefeatedBossName(BossNameEnum bossNameEnum) { bool flag = BossNameUtils.IsBossPowerGrantedForPlayer(bossNameEnum, Player.m_localPlayer); Logger.Log($"hasDefeated(1) for player {Player.m_localPlayer.GetPlayerName()}: {flag}"); if (!flag) { flag = ConfigurationFile.activateMidPlayDetection.Value && ((Humanoid)Player.m_localPlayer).HaveUniqueKey(bossNameEnum.GetPowerKey()); Logger.Log($"hasDefeated(2) for player {Player.m_localPlayer.GetPlayerName()}: {flag}"); if (flag) { Logger.Log("Learned the power before. Granting!"); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "RPC_BossPowerGrantServer", new object[2] { bossNameEnum.ToString(), Player.m_localPlayer.GetPlayerName() }); } } return flag; } public static int GetCurrentDay() { return (int)GameManager.GetPrivateMethod(EnvMan.instance, "GetCurrentDay"); } public static int GetBossMinimumDayForPower(string bossName) { return parseBossName(bossName) switch { BossNameEnum.Eikthyr => ConfigurationFile.daysBossEikthyr.Value, BossNameEnum.TheElder => ConfigurationFile.daysBossElder.Value, BossNameEnum.Bonemass => ConfigurationFile.daysBossBonemass.Value, BossNameEnum.Moder => ConfigurationFile.daysBossModer.Value, BossNameEnum.Yagluth => ConfigurationFile.daysBossYagluth.Value, BossNameEnum.Queen => ConfigurationFile.daysBossQueen.Value, BossNameEnum.Fader => ConfigurationFile.daysBossFader.Value, _ => ConfigurationFile.daysBossModded.Value, }; } public static bool bossIsKilled(string bossToCheck) { return bossIsKilled(parseBossName(bossToCheck)); } public static bool bossIsKilled(BossNameEnum bossToCheck) { return HasDefeatedBossName(bossToCheck); } } internal class ConfigurationFile { private static ConfigEntry<bool> _serverConfigLocked; public static ConfigEntry<bool> debug; public static ConfigEntry<bool> activateMidPlayDetection; public static ConfigEntry<string> powerCommandsAdminPlayersList; public static ConfigEntry<string> forbiddenMessage; public static ConfigEntry<bool> vendorLocalRestrictions; public static ConfigEntry<BossNameEnum> vendorHaldorBossToKill; public static ConfigEntry<string> vendorHaldorRestrictions; public static ConfigEntry<BossNameEnum> vendorHildirBossToKill; public static ConfigEntry<string> vendorHildirRestrictions; public static ConfigEntry<BossNameEnum> vendorBogWitchBossToKill; public static ConfigEntry<string> vendorBogWitchRestrictions; public static ConfigEntry<string> forbiddenVendorMessage; public static ConfigEntry<bool> restrictUsingKeyItems; public static ConfigEntry<string> restrictUsingKeyItemsMessage; public static ConfigEntry<string> itemRestrictionAvailableTooltipMessage; public static ConfigEntry<string> itemRestrictionAvailableTooltipYes; public static ConfigEntry<string> itemRestrictionAvailableTooltipNo; public static ConfigEntry<bool> grantKillToNearbyPlayers; public static ConfigEntry<int> daysBossEikthyr; public static ConfigEntry<int> daysBossElder; public static ConfigEntry<int> daysBossBonemass; public static ConfigEntry<int> daysBossModer; public static ConfigEntry<int> daysBossYagluth; public static ConfigEntry<int> daysBossQueen; public static ConfigEntry<int> daysBossFader; public static ConfigEntry<int> daysBossModded; public static ConfigEntry<float> maxLevelBeforeBoss1Eikthyr; public static ConfigEntry<float> maxLevelBeforeBoss2TheElder; public static ConfigEntry<float> maxLevelBeforeBoss3Bonemass; public static ConfigEntry<float> maxLevelBeforeBoss4Moder; public static ConfigEntry<float> maxLevelBeforeBoss5Yagluth; public static ConfigEntry<float> maxLevelBeforeBoss6Queen; public static ConfigEntry<float> maxLevelBeforeBoss7Fader; public static ConfigEntry<string> playerListForBoss1EikthyrPower; public static ConfigEntry<string> playerListForBoss2TheElderPower; public static ConfigEntry<string> playerListForBoss3BonemassPower; public static ConfigEntry<string> playerListForBoss4ModerPower; public static ConfigEntry<string> playerListForBoss5YagluthPower; public static ConfigEntry<string> playerListForBoss6QueenPower; public static ConfigEntry<string> playerListForBoss7FaderPower; public static ConfigEntry<string> playerListForBoss8TherzieGorrPower; public static ConfigEntry<string> playerListForBoss8TherzieBrutalisPower; public static ConfigEntry<string> playerListForBoss8TherzieStormHeraldPower; public static ConfigEntry<string> playerListForBoss8TherzieSythrakPower; private static ConfigFile configFile; private static readonly ConfigSync ConfigSync = new ConfigSync("Turbero.KillMeForMyPower") { DisplayName = "Kill Me For My Power", CurrentVersion = "2.1.1", MinimumRequiredVersion = "2.1.1" }; internal static void LoadConfig(BaseUnityPlugin plugin) { configFile = plugin.Config; _serverConfigLocked = config("1 - General", "Lock Configuration", value: true, "If on, the configuration is locked and can be changed by server admins only."); ConfigSync.AddLockingConfigEntry<bool>(_serverConfigLocked); debug = config("1 - General", "DebugMode", value: false, "Enabling/Disabling the debugging in the console (default = false)", synchronizedSetting: false); activateMidPlayDetection = config("1 - General", "ActivateMidPlayDetection", value: true, "Adds boss power detection to identify if the player had used the power before installing the mod in a mid-play (default = false)"); powerCommandsAdminPlayersList = config("1 - General", "Power Commands Admin Players List", "", "List of additional player names that can help admin servers to assign powers to other players or themselves using the mod commands."); forbiddenMessage = config("2 - Config", "ForbiddenMessage", "Kill the forsaken first!", "Message to show when you cannot obtain the forsaken power"); vendorLocalRestrictions = config("2 - Config", "VendorLocalRestrictions", value: true, "Vendors allow buying items based on personal progress, not global (default = true)"); vendorHaldorBossToKill = config("2 - Config", "VendorHaldorBossToKill", BossNameEnum.Eikthyr, "Boss to be killed before being able to talk to Haldor (default = Eikthyr). Set to 'None' to remove this restriction. Possible values: Eikthyr,TheElder,Bonemass,Moder,Yagluth,Queen,Fader,None"); vendorHaldorRestrictions = config("2 - Config", "VendorHaldorRestrictions", "BeltStrength,Eikthyr;YmirRemains,TheElder;Thunderstone,TheElder;ChickenEgg,Yagluth", "Restricted items for Haldor split by comma and semicolon. Ex: BeltStrength,Eikthyr;YmirRemains,TheElder;Thunderstone,TheElder;ChickenEgg,Yagluth. Available boss names: Eikthyr,TheElder,Bonemass,Moder,Yagluth,Queen,Fader (empty = nothing to restrict)"); vendorHildirBossToKill = config("2 - Config", "VendorHildirBossToKill", BossNameEnum.Eikthyr, "Boss to be killed before being able to talk to Haldor (default = Eikthyr). Set to 'None' to remove this restriction. Possible values: Eikthyr,TheElder,Bonemass,Moder,Yagluth,Queen,Fader,None"); vendorHildirRestrictions = config("2 - Config", "VendorHildirRestrictions", "", "Restricted items for Hildir split by comma and semicolon. Ex: Ironpit,Moder. Available boss names: Eikthyr,TheElder,Bonemass,Moder,Yagluth,Queen,Fader (empty = nothing to restrict)"); vendorBogWitchBossToKill = config("2 - Config", "VendorBogWitchBossToKill", BossNameEnum.TheElder, "Boss to be killed before being able to talk to Haldor (default = TheElder). Set to 'None' to remove this restriction. Possible values: Eikthyr,TheElder,Bonemass,Moder,Yagluth,Queen,Fader,None"); vendorBogWitchRestrictions = config("2 - Config", "VendorBogWitchRestrictions", "", "Restricted items for BogWitch split by comma. Ex: MeadTrollPheromones,TheElder;SpicePlains,Moder;SpiceAshlands,Queen. Available boss names: Eikthyr,TheElder,Bonemass,Moder,Yagluth,Queen,Fader (empty = nothing to restrict)"); forbiddenVendorMessage = config("2 - Config", "ForbiddenVendorMessage", "You have not killed {0} yet to buy my stuff!", "Message to show when you cannot buy from a NPC"); restrictUsingKeyItems = config("2 - Config", "RestrictUsingKeyItems", value: true, "Restricts using crypt key, wishbone and wisplight until you kill the previous boss even if you get them from someone else. (default = true)"); restrictUsingKeyItemsMessage = config("2 - Config", "RestrictUsingKeyItemsMessage", "You must kill {0} before doing that action!", "Message to show when you cannot equip or do an action with an important key item"); itemRestrictionAvailableTooltipMessage = config("2 - Config", "ItemRestrictionAvailableTooltipMessage", "Available to use", "Message to show in item descriptions to know when you can start using them"); itemRestrictionAvailableTooltipYes = config("2 - Config", "ItemRestrictionAvailableTooltipMessageYes", "YES", "Message to show in item descriptions confirming when you can use them"); itemRestrictionAvailableTooltipNo = config("2 - Config", "ItemRestrictionAvailableTooltipMessageNo", "NO", "Message to show in item descriptions confirming when you cannot use them"); grantKillToNearbyPlayers = config("2 - Config", "GrantKillToNearbyPlayers", value: true, "Allows nearby players to grant the boss kill (default = true)"); daysBossEikthyr = config("3 - Days", "DaysBossEikthyr", 100000, "Minimum number of days until the Eikthyr power cannot be obtained without killing him (default = 10000)"); daysBossElder = config("3 - Days", "DaysBossElder", 100000, "Minimum number of days until the Elder power cannot be obtained without killing him (default = 10000)"); daysBossBonemass = config("3 - Days", "DaysBossBonemass", 100000, "Minimum number of days until the Bonemass power cannot be obtained without killing him (default = 10000)"); daysBossModer = config("3 - Days", "DaysBossModer", 100000, "Minimum number of days until the Moder power cannot be obtained without killing him (default = 10000)"); daysBossYagluth = config("3 - Days", "DaysBossYagluth", 100000, "Minimum number of days until the Yagluth power cannot be obtained without killing him (default = 10000)"); daysBossQueen = config("3 - Days", "DaysBossQueen", 100000, "Minimum number of days until the Queen power cannot be obtained without killing him (default = 10000)"); daysBossFader = config("3 - Days", "DaysBossFader", 100000, "Minimum number of days until the Fader power cannot be obtained without killing him (default = 10000)"); daysBossModded = config("3 - Days", "DaysBossModded", 100000, "Minimum number of days until any modded boss power cannot be obtained without killing him (default = 10000)"); maxLevelBeforeBoss1Eikthyr = config("4 - Max levels", "MaxLevelBeforeBoss1Eikthyr", 100f, "Maximum skill level that player can level up skills before killing Eikthyr (default = 100)"); maxLevelBeforeBoss2TheElder = config("4 - Max levels", "MaxLevelBeforeBoss2TheElder", 100f, "Maximum skill level that player can level up skills before killing The Elder (default = 100)"); maxLevelBeforeBoss3Bonemass = config("4 - Max levels", "MaxLevelBeforeBoss3Bonemass", 100f, "Maximum skill level that player can level up skills before killing Bonemass (default = 100)"); maxLevelBeforeBoss4Moder = config("4 - Max levels", "MaxLevelBeforeBoss4Moder", 100f, "Maximum skill level that player can level up skills before killing Moder (default = 100)"); maxLevelBeforeBoss5Yagluth = config("4 - Max levels", "MaxLevelBeforeBoss5Yagluth", 100f, "Maximum skill level that player can level up skills before killing Yagluth (default = 100)"); maxLevelBeforeBoss6Queen = config("4 - Max levels", "MaxLevelBeforeBoss6Queen", 100f, "Maximum skill level that player can level up skills before killing The Queen (default = 100)"); maxLevelBeforeBoss7Fader = config("4 - Max levels", "MaxLevelBeforeBoss7Fader", 100f, "Maximum skill level that player can level up skills before killing Fader (default = 100)"); playerListForBoss1EikthyrPower = config("5 - Power Granted player lists", "Player List for Boss 1 - Eikthyr power", "", "List of player names that can use Eikthyr power after defeating him"); playerListForBoss2TheElderPower = config("5 - Power Granted player lists", "Player List for Boss 2 - The Elder power", "", "List of player names that can use The Elder power after defeating him"); playerListForBoss3BonemassPower = config("5 - Power Granted player lists", "Player List for Boss 3 - Bonemass power", "", "List of player names that can use Bonemass power after defeating him"); playerListForBoss4ModerPower = config("5 - Power Granted player lists", "Player List for Boss 4 - Moder power", "", "List of player names that can use Moder power after defeating her"); playerListForBoss5YagluthPower = config("5 - Power Granted player lists", "Player List for Boss 5 - Yagluth power", "", "List of player names that can use Yagluth power after defeating him"); playerListForBoss6QueenPower = config("5 - Power Granted player lists", "Player List for Boss 6 - Queen power", "", "List of player names that can use Queen power after defeating her"); playerListForBoss7FaderPower = config("5 - Power Granted player lists", "Player List for Boss 7 - Fader power", "", "List of player names that can use Fader power after defeating him"); playerListForBoss8TherzieGorrPower = config("5 - Power Granted player lists", "Player List for Therzie Boss - Gorr power", "", "List of player names that can use Gorr power after defeating him"); playerListForBoss8TherzieBrutalisPower = config("5 - Power Granted player lists", "Player List for Therzie Boss - Brutalis power", "", "List of player names that can use Brutalis power after defeating him"); playerListForBoss8TherzieStormHeraldPower = config("5 - Power Granted player lists", "Player List for Therzie Boss - StormHerald power", "", "List of player names that can use StormHerald power after defeating him"); playerListForBoss8TherzieSythrakPower = config("5 - Power Granted player lists", "Player List for Therzie Boss - Sythrak power", "", "List of player names that can use Sythrak power after defeating him"); } private static ConfigEntry<T> config<T>(string group, string name, T value, string description, bool synchronizedSetting = true) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting); } private static ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, description.Tags); ConfigEntry<T> val2 = configFile.Bind<T>(group, name, value, val); SyncedConfigEntry<T> syncedConfigEntry = ConfigSync.AddConfigEntry<T>(val2); syncedConfigEntry.SynchronizedConfig = synchronizedSetting; return val2; } } [BepInPlugin("Turbero.KillMeForMyPower", "Kill Me For My Power", "2.1.1")] public class KillMeForMyPower : BaseUnityPlugin { public const string GUID = "Turbero.KillMeForMyPower"; public const string NAME = "Kill Me For My Power"; public const string VERSION = "2.1.1"; private readonly Harmony harmony = new Harmony("Turbero.KillMeForMyPower"); private void Awake() { ConfigurationFile.LoadConfig((BaseUnityPlugin)(object)this); harmony.PatchAll(); } private void Start() { ((MonoBehaviour)this).StartCoroutine(WaitForNetworking()); } private IEnumerator WaitForNetworking() { while (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null) { yield return (object)new WaitForSeconds(1f); } PowerManagementCommands.RegisterConsoleCommand(); CheckBossesCommand.RegisterConsoleCommand(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Console commands registered successfully. IsServer: " + ZNet.instance.IsServer().ToString().ToUpperInvariant())); } private void onDestroy() { harmony.UnpatchSelf(); } } public static class Logger { public static ManualLogSource logger = Logger.CreateLogSource("Kill Me For My Power"); internal static void Log(object s) { if (ConfigurationFile.debug.Value) { logger.LogInfo((object)s?.ToString()); } } internal static void LogInfo(object s) { logger.LogInfo((object)s?.ToString()); } internal static void LogWarning(object s) { logger.LogWarning((object)s?.ToString()); } internal static void LogError(object s) { logger.LogError((object)s?.ToString()); } } } namespace KillMeForMyPower.Restrictions { [HarmonyPatch(typeof(Character), "OnDeath")] public class RegisterBossDefeatPatch { public static void Postfix(Character __instance) { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance != (Object)null) || !__instance.IsBoss()) { return; } string text = ((Object)__instance).name.Replace("(Clone)", ""); Player localPlayer = Player.m_localPlayer; List<string> list = new List<string>(); list.Add(localPlayer.GetPlayerName()); if (ConfigurationFile.grantKillToNearbyPlayers.Value) { Logger.LogInfo("Finding nearby players to grant the kill"); BaseAI component = ((Component)__instance).GetComponent<BaseAI>(); float num = (Object.op_Implicit((Object)(object)component) ? component.m_viewRange : 0f); Logger.LogInfo(string.Format("Detection boss range for {0} is {1} meters", ((Object)__instance).name.Replace("(Clone)", ""), num)); Vector3 position = ((Component)__instance).transform.position; List<Player> allPlayers = Player.GetAllPlayers(); foreach (Player item in allPlayers) { if (!((Object)(object)item == (Object)null) && !(item.GetPlayerName() == localPlayer.GetPlayerName()) && Vector3.Distance(((Component)item).transform.position, position) <= num) { list.Add(item.GetPlayerName()); } } } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "RPC_BossPowerGrantServer", new object[2] { text, string.Join(",", list) }); } } [HarmonyPatch(typeof(Player), "Save")] public class Player_Save_Null_Key_Clean_Patch { [HarmonyPrefix] public static void Prefix(Player __instance) { List<string> list = __instance.m_customData.Keys.Where((string k) => string.IsNullOrEmpty(k) || k == "null").ToList(); foreach (string item in list) { Logger.LogWarning("Removing null key from player " + __instance.GetPlayerName()); ((Humanoid)__instance).RemoveUniqueKey(item); } } } public class RPC_BossPowerGrantCalls { public static void RPC_BossPowerGrantServer(long sender, string bossEnumStr, string playersToGrant) { BossNameEnum bossNameEnum = KillMeForMyPowerUtils.findBossNameByPrefabName(bossEnumStr, logSilent: true); if (bossNameEnum != BossNameEnum.None) { Logger.Log($"[RPC_BossPowerGrantServer] RPC sent from sender {sender} with {bossEnumStr} ({bossNameEnum}) and {playersToGrant}"); string[] array = playersToGrant.Split(new char[1] { ',' }); string[] array2 = array; foreach (string playerName in array2) { BossNameUtils.GrantBossPowerToPlayer(bossNameEnum, playerName, isAdd: true); } } } public static void RPC_BossPowerRemoveGrantServer(long sender, string bossEnumStr, string playersToRemoveGrant) { BossNameEnum bossNameEnum = KillMeForMyPowerUtils.findBossNameByPrefabName(bossEnumStr); if (bossNameEnum != BossNameEnum.None) { Logger.Log($"[RPC_BossPowerRemoveGrantServer] RPC sent from sender {sender} with {bossEnumStr} ({bossNameEnum}) and {playersToRemoveGrant}"); string[] array = playersToRemoveGrant.Split(new char[1] { ',' }); string[] array2 = array; foreach (string playerName in array2) { BossNameUtils.GrantBossPowerToPlayer(bossNameEnum, playerName, isAdd: false); } } } } public class BossPowerRestriction { [HarmonyPatch(typeof(Player), "ActivateGuardianPower")] public static class ActivateGuardianPowerPatch { public static bool Prefix(Player __instance, ref bool __result) { string guardianPowerName = __instance.GetGuardianPowerName(); if (guardianPowerName == null || guardianPowerName.Equals("")) { return true; } string text = guardianPowerName.Replace("GP_", ""); Logger.Log("ActivateGuardianPower - guardianPowerName: " + text); if (!KillMeForMyPowerUtils.HasDefeatedBossNameStr(text) && KillMeForMyPowerUtils.GetCurrentDay() < KillMeForMyPowerUtils.GetBossMinimumDayForPower(text)) { ((Character)__instance).Message((MessageType)2, ConfigurationFile.forbiddenMessage.Value, 0, (Sprite)null); ApplyBlockedEffect(text); __result = false; return false; } return true; } } [HarmonyPatch(typeof(ItemStand), "DelayedPowerActivation")] public static class DelayedPowerActivationPatch { public static bool Prefix(ItemStand __instance) { StatusEffect guardianPower = __instance.m_guardianPower; string text = ((guardianPower != null) ? ((Object)guardianPower).name.Replace("GP_", "") : null); Logger.Log("DelayedPowerActivation - ItemStand name: " + text); if (!KillMeForMyPowerUtils.HasDefeatedBossNameStr(text) && KillMeForMyPowerUtils.GetCurrentDay() < KillMeForMyPowerUtils.GetBossMinimumDayForPower(text)) { ((Character)Player.m_localPlayer).Message((MessageType)2, ConfigurationFile.forbiddenMessage.Value, 0, (Sprite)null); ApplyBlockedEffect(text); return false; } return true; } } private static void ApplyBlockedEffect(string bossName) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Expected O, but got Unknown //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Expected O, but got Unknown //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Expected O, but got Unknown //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Expected O, but got Unknown SEMan sEMan = ((Character)Player.m_localPlayer).GetSEMan(); switch ((BossNameEnum)Enum.Parse(typeof(BossNameEnum), bossName, ignoreCase: true)) { case BossNameEnum.Eikthyr: { StatusEffect val10 = ((sEMan != null) ? sEMan.AddStatusEffect("Lightning".GetHashCode(), false, 0, 0f) : null); val10.m_ttl = 5f; break; } case BossNameEnum.TheElder: { SE_Burning val3 = (SE_Burning)((sEMan != null) ? sEMan.AddStatusEffect("Burning".GetHashCode(), false, 0, 0f) : null); val3.AddFireDamage(Math.Max(2f, ((Character)Player.m_localPlayer).GetHealth() - 10f)); break; } case BossNameEnum.Bonemass: { SE_Poison val11 = (SE_Poison)((sEMan != null) ? sEMan.AddStatusEffect("Poison".GetHashCode(), false, 0, 0f) : null); val11.AddDamage(Math.Max(1f, ((Character)Player.m_localPlayer).GetHealth() - 10f)); break; } case BossNameEnum.Moder: { SE_Frost val8 = (SE_Frost)((sEMan != null) ? sEMan.AddStatusEffect("Frost".GetHashCode(), false, 0, 0f) : null); ((StatusEffect)val8).m_ttl = 5f; break; } case BossNameEnum.Yagluth: { SE_Burning val4 = (SE_Burning)((sEMan != null) ? sEMan.AddStatusEffect("Burning".GetHashCode(), false, 0, 0f) : null); val4.AddFireDamage(Math.Max(2f, ((Character)Player.m_localPlayer).GetHealth() - 10f)); break; } case BossNameEnum.Queen: { SE_Poison val6 = (SE_Poison)((sEMan != null) ? sEMan.AddStatusEffect("Poison".GetHashCode(), false, 0, 0f) : null); val6.AddDamage(Math.Max(1f, ((Character)Player.m_localPlayer).GetHealth() - 10f)); break; } case BossNameEnum.Fader: { SE_Burning val7 = (SE_Burning)((sEMan != null) ? sEMan.AddStatusEffect("Burning".GetHashCode(), false, 0, 0f) : null); val7.AddFireDamage(Math.Max(2f, ((Character)Player.m_localPlayer).GetHealth() - 10f)); break; } case BossNameEnum.SE_Boss_Gorr: { SE_Frost val2 = (SE_Frost)((sEMan != null) ? sEMan.AddStatusEffect("Frost".GetHashCode(), false, 0, 0f) : null); ((StatusEffect)val2).m_ttl = 5f; break; } case BossNameEnum.SE_Boss_Brutalis: { SE_Burning val9 = (SE_Burning)((sEMan != null) ? sEMan.AddStatusEffect("Burning".GetHashCode(), false, 0, 0f) : null); val9.AddFireDamage(Math.Max(2f, ((Character)Player.m_localPlayer).GetHealth() - 10f)); break; } case BossNameEnum.SE_Boss_Sythrak: { SE_Burning val5 = (SE_Burning)((sEMan != null) ? sEMan.AddStatusEffect("Burning".GetHashCode(), false, 0, 0f) : null); val5.AddFireDamage(Math.Max(2f, ((Character)Player.m_localPlayer).GetHealth() - 10f)); break; } case BossNameEnum.SE_Boss_StormHerald: { StatusEffect val = ((sEMan != null) ? sEMan.AddStatusEffect("Lightning".GetHashCode(), false, 0, 0f) : null); val.m_ttl = 5f; break; } } } } public class Effects { public static void scareEffect() { SEMan sEMan = ((Character)Player.m_localPlayer).GetSEMan(); StatusEffect val = ((sEMan != null) ? sEMan.AddStatusEffect("Lightning".GetHashCode(), false, 0, 0f) : null); val.m_ttl = 3f; } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] public class PlayerEquipItemPatch { [HarmonyPrefix] public static bool EquipItemPrefix(Humanoid __instance, ItemData item, bool triggerEquipEffects, ref bool __result) { if (!ConfigurationFile.restrictUsingKeyItems.Value) { return true; } if (__instance is Player) { if (item.m_shared.m_name == "$item_wishbone" && !KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Bonemass)) { ((Character)__instance).Message((MessageType)2, ConfigurationFile.restrictUsingKeyItemsMessage.Value.Replace("{0}", BossNameEnum.Bonemass.GetTranslationKey()), 0, (Sprite)null); Effects.scareEffect(); __result = false; return false; } if (item.m_shared.m_name == "$item_demister" && !KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Yagluth)) { ((Character)__instance).Message((MessageType)2, ConfigurationFile.restrictUsingKeyItemsMessage.Value.Replace("{0}", BossNameEnum.Yagluth.GetTranslationKey()), 0, (Sprite)null); Effects.scareEffect(); __result = false; return false; } } return true; } } [HarmonyPatch(typeof(Door), "Interact")] public class SwampKeyRestrictionPatch { [HarmonyPrefix] public static bool InteractPrefix(Door __instance, Humanoid character, bool hold, ref bool __result) { if (!ConfigurationFile.restrictUsingKeyItems.Value) { return true; } if (((Object)__instance).name.Contains("sunken_crypt_gate") && character is Player && !KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.TheElder)) { ((Character)character).Message((MessageType)2, ConfigurationFile.restrictUsingKeyItemsMessage.Value.Replace("{0}", BossNameEnum.TheElder.GetTranslationKey()), 0, (Sprite)null); Effects.scareEffect(); __result = false; return false; } return true; } } [HarmonyPatch(typeof(UITooltip), "UpdateTextElements")] public class UITooltipPatch { public static void Postfix(UITooltip __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown GameObject val = (GameObject)GameManager.GetPrivateValue(__instance, "m_tooltip", BindingFlags.Static | BindingFlags.NonPublic); if (!((Object)(object)val != (Object)null)) { return; } Transform val2 = Utils.FindChild(val.transform, "Text", (IterativeSearchType)0); if ((Object)(object)val2 != (Object)null && __instance.m_topic != null) { if (__instance.m_topic == "$item_demister") { updateTooltipText(__instance, val2, BossNameEnum.Yagluth); } else if (__instance.m_topic == "$item_wishbone") { updateTooltipText(__instance, val2, BossNameEnum.Bonemass); } else if (__instance.m_topic == "$item_cryptkey") { updateTooltipText(__instance, val2, BossNameEnum.TheElder); } } } private static void updateTooltipText(UITooltip __instance, Transform transform, BossNameEnum bossToCheck) { Logger.Log("tooltip topic: " + __instance.m_topic); bool flag = KillMeForMyPowerUtils.HasDefeatedBossName(bossToCheck); string text = (flag ? ConfigurationFile.itemRestrictionAvailableTooltipYes.Value : ConfigurationFile.itemRestrictionAvailableTooltipNo.Value); string text2 = (flag ? "green" : "red"); string text3 = ConfigurationFile.itemRestrictionAvailableTooltipMessage.Value + ": <color=" + text2 + ">" + text + "</color>"; __instance.m_text = __instance.m_text.Replace("_description", "_description\n" + text3); ((Component)transform).GetComponent<TMP_Text>().text = Localization.instance.Localize(__instance.m_text); string[] array = ((Component)transform).GetComponent<TMP_Text>().text.Split(new char[1] { '\n' }); ((Component)transform).GetComponent<TMP_Text>().text = string.Join("\n", array, 0, array.Length - 1); } } public class LevelCalculation { public static bool canSkillUp(float skillLevelValue) { return (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Eikthyr) || !(skillLevelValue >= ConfigurationFile.maxLevelBeforeBoss1Eikthyr.Value)) && (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.TheElder) || !(skillLevelValue >= ConfigurationFile.maxLevelBeforeBoss2TheElder.Value)) && (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Bonemass) || !(skillLevelValue >= ConfigurationFile.maxLevelBeforeBoss3Bonemass.Value)) && (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Moder) || !(skillLevelValue >= ConfigurationFile.maxLevelBeforeBoss4Moder.Value)) && (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Yagluth) || !(skillLevelValue >= ConfigurationFile.maxLevelBeforeBoss5Yagluth.Value)) && (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Queen) || !(skillLevelValue >= ConfigurationFile.maxLevelBeforeBoss6Queen.Value)) && (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Fader) || !(skillLevelValue >= ConfigurationFile.maxLevelBeforeBoss7Fader.Value)); } private static float currentMaxSkillLevel() { if (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Fader)) { return float.MaxValue; } if (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Queen)) { return ConfigurationFile.maxLevelBeforeBoss7Fader.Value; } if (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Yagluth)) { return ConfigurationFile.maxLevelBeforeBoss6Queen.Value; } if (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Moder)) { return ConfigurationFile.maxLevelBeforeBoss5Yagluth.Value; } if (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Bonemass)) { return ConfigurationFile.maxLevelBeforeBoss4Moder.Value; } if (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.TheElder)) { return ConfigurationFile.maxLevelBeforeBoss3Bonemass.Value; } if (KillMeForMyPowerUtils.HasDefeatedBossName(BossNameEnum.Eikthyr)) { return ConfigurationFile.maxLevelBeforeBoss2TheElder.Value; } return ConfigurationFile.maxLevelBeforeBoss1Eikthyr.Value; } public static bool reviewAndUpdateSkill(Player player, SkillType skillType, string buffName) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) Skill val = ((Character)player).GetSkills().GetSkillList().Find((Skill s) => s.m_info.m_skill == skillType); if (val == null) { return true; } int num = (int)val.m_level; float num2 = (float)num + val.GetLevelPercentage(); float num3 = currentMaxSkillLevel(); bool flag = num2 < num3; Logger.Log($"Decision with skill {skillType}, skillLevel {num}, percentage {val.GetLevelPercentage()} and currentLevel {num2}: {flag}"); if (!flag) { val.m_level = Math.Min(num, num3); val.m_accumulator = 0f; Logger.Log($"{skillType} rounded to max allowed value: {val.m_level}"); if (GameManager.isDetailedLevelsInstalled()) { Logger.Log("Checking DetailedLevels buff..."); List<StatusEffect> list = (List<StatusEffect>)GameManager.GetPrivateValue(((Character)Player.m_localPlayer).GetSEMan(), "m_statusEffects"); StatusEffect val2 = list.Find((StatusEffect effect) => effect.m_name.Contains(buffName)); if ((Object)(object)val2 != (Object)null) { Logger.Log("Fixing value in buff"); val2.m_name = $"{buffName}: {val.m_level}"; } } } return flag; } } [HarmonyPatch(typeof(Player), "RaiseSkill")] public class Player_RaiseSkill_Prefix_Patch { private static bool Prefix(Player __instance, SkillType skill, float value) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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) if ((Object)(object)__instance != (Object)null) { float skillLevel = ((Character)__instance).GetSkills().GetSkillLevel(skill); Logger.Log($"Checking skill level availability {skill} with {skillLevel}..."); if (skillLevel < 100f) { return LevelCalculation.reviewAndUpdateSkill(__instance, skill, "$skill_" + ((object)(SkillType)(ref skill)).ToString().ToLower()); } } return true; } } [HarmonyPatch] [HarmonyPriority(700)] public class Dodge_ManualUpdate_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "Dodge", (Type[])null, (Type[])null); } private static void Postfix(ref Player __instance, Vector3 dodgeDir) { Logger.Log("Checking dodge skill here since it doesn't seem to go through Player.RaiseSkill..."); WaitForSecondsAsync(__instance, 0.1f); } private static async Task WaitForSecondsAsync(Player player, float seconds) { await Task.Delay((int)(Math.Max(0f, seconds) * 1000f)); LevelCalculation.reviewAndUpdateSkill(player, (SkillType)108, "$skill_dodge"); } } [HarmonyPatch(typeof(Character), "Damage")] public class BloodMagic_ManualUpdate_Patch { private static void Postfix(Character __instance, HitData hit) { if (!((Object)(object)__instance != (Object)null)) { return; } Character attacker = hit.GetAttacker(); if (!((Object)(object)attacker != (Object)null)) { return; } if (attacker.IsTamed() && __instance.IsMonsterFaction(0f)) { WaitForSecondsAsync(null, 0.1f); } else if (attacker.IsMonsterFaction(0f) && ((object)__instance).GetType() == typeof(Player)) { WaitForSecondsAsync((Player)(object)((__instance is Player) ? __instance : null), 0.1f); } else if (((Object)attacker).name.Contains("Troll_Summoned")) { if (((object)__instance).GetType() == typeof(Player)) { WaitForSecondsAsync((Player)(object)((__instance is Player) ? __instance : null), 0.1f); } else if (__instance.IsTamed()) { WaitForSecondsAsync(null, 0.1f); } } } private static async Task WaitForSecondsAsync(Player player, float seconds) { Logger.Log("Checking blood magic here since it doesn't seem to go through Player.RaiseSkill..."); await Task.Delay((int)(Math.Max(0f, seconds) * 1000f)); LevelCalculation.reviewAndUpdateSkill(((Object)(object)player == (Object)null) ? Player.m_localPlayer : player, (SkillType)10, "$skill_bloodmagic"); } } [HarmonyPatch(typeof(SkillsDialog), "Setup")] [HarmonyPriority(100)] public class SkillsDialogAdditions_Patch { private static void Postfix(SkillsDialog __instance, ref Player player, ref List<GameObject> ___m_elements) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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) List<Skill> skillList = ((Character)player).GetSkills().GetSkillList(); for (int i = 0; i < skillList.Count; i++) { GameObject val = ___m_elements[i]; Skill val2 = skillList[i]; Color color = (LevelCalculation.canSkillUp((int)val2.m_level) ? Color.white : Color.red); ((Component)Utils.FindChild(val.transform, "leveltext", (IterativeSearchType)0)).GetComponent<TMP_Text>().color = color; } } } public class TraderSetup { [HarmonyPatch(typeof(Trader), "Interact")] public class TraderInteractPatch { [HarmonyPrefix] public static bool InteractPrefix(Trader __instance, Humanoid character, bool hold, bool alt) { Logger.Log("**Trader.Interact called for " + ((Object)((Component)__instance).gameObject).name); if (!ConfigurationFile.vendorLocalRestrictions.Value) { return true; } if (!grantedVendor(__instance)) { string mandatoryBossToKill = getMandatoryBossToKill(__instance); if (mandatoryBossToKill != null) { ((Character)character).Message((MessageType)2, ConfigurationFile.forbiddenVendorMessage.Value.Replace("{0}", mandatoryBossToKill), 0, (Sprite)null); return false; } } return true; } private static string getMandatoryBossToKill(Trader trader) { if (((Object)((Component)trader).gameObject).name.StartsWith("Haldor") && ConfigurationFile.vendorHaldorBossToKill.Value != BossNameEnum.None) { return Localization.instance.Localize(KillMeForMyPowerUtils.getBossNameTranslation(ConfigurationFile.vendorHaldorBossToKill.Value)); } if (((Object)((Component)trader).gameObject).name.StartsWith("Hildir") && ConfigurationFile.vendorHildirBossToKill.Value != BossNameEnum.None) { return Localization.instance.Localize(KillMeForMyPowerUtils.getBossNameTranslation(ConfigurationFile.vendorHildirBossToKill.Value)); } if (((Object)((Component)trader).gameObject).name.StartsWith("BogWitch") && ConfigurationFile.vendorBogWitchBossToKill.Value != BossNameEnum.None) { return Localization.instance.Localize(KillMeForMyPowerUtils.getBossNameTranslation(ConfigurationFile.vendorBogWitchBossToKill.Value)); } return null; } private static bool grantedVendor(Trader trader) { bool flag = ((Object)((Component)trader).gameObject).name.StartsWith("Haldor") && KillMeForMyPowerUtils.bossIsKilled(ConfigurationFile.vendorHaldorBossToKill.Value); bool flag2 = ((Object)((Component)trader).gameObject).name.StartsWith("Hildir") && KillMeForMyPowerUtils.bossIsKilled(ConfigurationFile.vendorHildirBossToKill.Value); bool flag3 = ((Object)((Component)trader).gameObject).name.StartsWith("BogWitch") && KillMeForMyPowerUtils.bossIsKilled(ConfigurationFile.vendorBogWitchBossToKill.Value); return flag || flag2 || flag3; } } [HarmonyPatch(typeof(Trader), "GetAvailableItems")] public class TraderGetAvailableItemsPatch { [HarmonyPostfix] public static void GetAvailableItemsPostfix(Trader __instance, ref List<TradeItem> __result) { Logger.Log("**Trader.GetAvailableItems called for " + ((Object)((Component)__instance).gameObject).name); if (!ConfigurationFile.vendorLocalRestrictions.Value) { return; } List<TradeItem> list = new List<TradeItem>(); if (((Object)__instance).name.Contains("Haldor")) { addCustomItemsToRemove(__result, list, ConfigurationFile.vendorHaldorRestrictions.Value); } else if (((Object)__instance).name.Contains("Hildir")) { addCustomItemsToRemove(__result, list, ConfigurationFile.vendorHildirRestrictions.Value); } else if (((Object)__instance).name.Contains("BogWitch")) { addCustomItemsToRemove(__result, list, ConfigurationFile.vendorBogWitchRestrictions.Value); } foreach (TradeItem item in list) { __result.Remove(item); } } private static void addCustomItemsToRemove(List<TradeItem> __result, List<TradeItem> customItemsToRemove, string vendorRestrictions) { if (string.IsNullOrEmpty(vendorRestrictions)) { return; } string[] array = vendorRestrictions.Split(new char[1] { ';' }); string[] array2 = array; foreach (string text in array2) { string[] array3 = text.Split(new char[1] { ',' }); Logger.Log("Checking vendor item " + array3[0] + " against " + array3[1]); foreach (TradeItem item in __result) { Logger.Log("- Checking " + ((Object)((Component)item.m_prefab).gameObject).name + " from the vendor list..."); if (((Object)((Component)item.m_prefab).gameObject).name == array3[0]) { if (!KillMeForMyPowerUtils.bossIsKilled(array3[1])) { customItemsToRemove.Add(item); Logger.Log(array3[1] + " will be excluded."); } else { Logger.Log(array3[1] + " will NOT be excluded."); } break; } } } } } [HarmonyPatch(typeof(Vegvisir), "Interact")] public class HildirMapTablePatch { public static bool Prefix(Vegvisir __instance, Humanoid character, bool hold, bool alt, ref bool __result) { if (character is Player && __instance.m_locations.FindAll((VegvisrLocation loc) => (int)loc.m_pinType == 14 || (int)loc.m_pinType == 15 || (int)loc.m_pinType == 16).Count > 0 && !KillMeForMyPowerUtils.HasDefeatedBossName(ConfigurationFile.vendorHildirBossToKill.Value)) { ((Character)character).Message((MessageType)2, ConfigurationFile.restrictUsingKeyItemsMessage.Value.Replace("{0}", ConfigurationFile.vendorHildirBossToKill.Value.GetTranslationKey()), 0, (Sprite)null); Effects.scareEffect(); __result = false; return false; } return true; } } } } namespace KillMeForMyPower.Restrictions.BossNameManagement { public enum BossNameEnum { [BossNameAttr("Eikthyr", "GP_Eikthyr", "$enemy_eikthyr", "playerListForBoss1EikthyrPower")] Eikthyr, [BossNameAttr("gd_king", "GP_TheElder", "$enemy_gdking", "playerListForBoss2TheElderPower")] TheElder, [BossNameAttr("Bonemass", "GP_Bonemass", "$enemy_bonemass", "playerListForBoss3BonemassPower")] Bonemass, [BossNameAttr("Dragon", "GP_Moder", "$enemy_dragon", "playerListForBoss4ModerPower")] Moder, [BossNameAttr("GoblinKing", "GP_Yagluth", "$enemy_goblinking", "playerListForBoss5YagluthPower")] Yagluth, [BossNameAttr("SeekerQueen", "GP_Queen", "$enemy_seekerqueen", "playerListForBoss6QueenPower")] Queen, [BossNameAttr("Fader", "GP_Fader", "$enemy_fader", "playerListForBoss7FaderPower")] Fader, [BossNameAttr(null, null, null, null)] None, [BossNameAttr("BossGorr_TW", "SE_Boss_Gorr", "Gorr", "playerListForBoss8TherzieGorrPower")] SE_Boss_Gorr, [BossNameAttr("BossBrutalis_TW", "SE_Boss_Brutalis", "Brutalis", "playerListForBoss8TherzieBrutalisPower")] SE_Boss_Brutalis, [BossNameAttr("BossStormHerald_TW", "SE_Boss_StormHerald", "StormHerald", "playerListForBoss8TherzieStormHeraldPower")] SE_Boss_StormHerald, [BossNameAttr("BossSythrak_TW", "SE_Boss_Sythrak", "Sythrak", "playerListForBoss8TherzieSythrakPower")] SE_Boss_Sythrak } internal class BossNameAttr : Attribute { public string prefabBossName { get; private set; } public string translationKey { get; private set; } public string powerKey { get; private set; } public string configurationListName { get; private set; } internal BossNameAttr(string prefabBossName, string powerKey, string translationKey, string configurationListName) { this.prefabBossName = prefabBossName; this.powerKey = powerKey; this.translationKey = translationKey; this.configurationListName = configurationListName; } } public static class BossNameFields { public static string GetGrantedPlayerNamesList(this BossNameEnum p) { BossNameAttr attr = GetAttr(p); return ((ConfigEntry<string>)(typeof(ConfigurationFile).GetField(attr.configurationListName, BindingFlags.Static | BindingFlags.Public)?.GetValue(null)))?.Value; } public static string GetFightBossname(this BossNameEnum p) { BossNameAttr attr = GetAttr(p); return attr.prefabBossName; } public static string GetTranslationKey(this BossNameEnum p) { BossNameAttr attr = GetAttr(p); return attr.translationKey; } public static string GetPowerKey(this BossNameEnum p) { BossNameAttr attr = GetAttr(p); return attr.powerKey; } public static string GetConfigurationListName(this BossNameEnum p) { BossNameAttr attr = GetAttr(p); return attr.configurationListName; } private static BossNameAttr GetAttr(BossNameEnum p) { return (BossNameAttr)Attribute.GetCustomAttribute(ForValue(p), typeof(BossNameAttr)); } private static MemberInfo ForValue(BossNameEnum p) { return typeof(BossNameEnum).GetField(Enum.GetName(typeof(BossNameEnum), p)); } } public static class BossNameUtils { public static void GrantBossPowerToPlayer(BossNameEnum bossNameEnum, string playerName, bool isAdd) { if (bossNameEnum != BossNameEnum.None) { UpdateConfigurationGrantedPlayersList(bossNameEnum, playerName, isAdd); Logger.LogInfo("Player " + playerName + " " + (isAdd ? "defeated" : "removed") + $" {bossNameEnum}."); } } public static bool IsBossPowerGrantedForPlayer(BossNameEnum bossNameEnum, Player player) { string grantedPlayerNamesList = bossNameEnum.GetGrantedPlayerNamesList(); return grantedPlayerNamesList.Contains(player.GetPlayerName()); } [MethodImpl(MethodImplOptions.Synchronized)] public static void UpdateConfigurationGrantedPlayersList(BossNameEnum bossNameEnum, string playerName, bool isAdd) { string grantedPlayerNamesList = bossNameEnum.GetGrantedPlayerNamesList(); if ((isAdd && grantedPlayerNamesList.Contains(playerName)) || (!isAdd && !grantedPlayerNamesList.Contains(playerName))) { return; } string text; if (!string.IsNullOrEmpty(grantedPlayerNamesList)) { string[] collection = grantedPlayerNamesList.Split(new char[1] { ',' }); List<string> list = new List<string>(collection); if (isAdd) { list.Add(playerName); } else { list.Remove(playerName); } list.Sort(); text = string.Join(",", list.ToArray()); } else { text = (isAdd ? playerName : ""); } Logger.Log("Result to save: " + text); switch (bossNameEnum) { case BossNameEnum.Eikthyr: ConfigurationFile.playerListForBoss1EikthyrPower.Value = text; break; case BossNameEnum.TheElder: ConfigurationFile.playerListForBoss2TheElderPower.Value = text; break; case BossNameEnum.Bonemass: ConfigurationFile.playerListForBoss3BonemassPower.Value = text; break; case BossNameEnum.Moder: ConfigurationFile.playerListForBoss4ModerPower.Value = text; break; case BossNameEnum.Yagluth: ConfigurationFile.playerListForBoss5YagluthPower.Value = text; break; case BossNameEnum.Queen: ConfigurationFile.playerListForBoss6QueenPower.Value = text; break; case BossNameEnum.Fader: ConfigurationFile.playerListForBoss7FaderPower.Value = text; break; case BossNameEnum.SE_Boss_Gorr: ConfigurationFile.playerListForBoss8TherzieGorrPower.Value = text; break; case BossNameEnum.SE_Boss_Brutalis: ConfigurationFile.playerListForBoss8TherzieBrutalisPower.Value = text; break; case BossNameEnum.SE_Boss_StormHerald: ConfigurationFile.playerListForBoss8TherzieStormHeraldPower.Value = text; break; case BossNameEnum.SE_Boss_Sythrak: ConfigurationFile.playerListForBoss8TherzieSythrakPower.Value = text; break; default: Logger.LogWarning("Not saved, boss not found!"); break; } } } [HarmonyPatch(typeof(Game), "Start")] public class GameStartPatch { private static void Prefix() { ZRoutedRpc.instance.Register<string, string>("RPC_BossPowerGrantServer", (Action<long, string, string>)RPC_BossPowerGrantCalls.RPC_BossPowerGrantServer); ZRoutedRpc.instance.Register<string, string>("RPC_BossPowerRemoveGrantServer", (Action<long, string, string>)RPC_BossPowerGrantCalls.RPC_BossPowerRemoveGrantServer); } } } namespace KillMeForMyPower.Managers { public class GameManager { public static object GetPrivateValue(object obj, string name, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic) { return obj.GetType().GetField(name, bindingAttr)?.GetValue(obj); } public static object GetPrivateMethod(object obj, string name, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic) { return obj.GetType().GetMethod(name, bindingAttr)?.Invoke(obj, null); } public static bool isDetailedLevelsInstalled() { return Chainloader.PluginInfos.ContainsKey("Turbero.DetailedLevels"); } public static bool IsAdmin(Player player) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) Logger.Log("[IsAdmin] Checking powerCommandsAdminPlayersList..."); string playerName = player.GetPlayerName(); if (ConfigurationFile.powerCommandsAdminPlayersList.Value.Contains(playerName)) { return true; } Logger.Log("[IsAdmin] Finding All PlayerInfo..."); List<PlayerInfo> list = ZNet.instance.GetPlayerList().FindAll((PlayerInfo p) => p.m_name == playerName); if (list.Count == 0) { return false; } string userID = list[0].m_userInfo.m_id.m_userID; Logger.Log("[IsAdmin] Matching steamID " + userID + " in adminList..."); return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetAdminList() != null && ZNet.instance.GetAdminList().Contains(userID); } } } namespace KillMeForMyPower.Commands { public class PowerManagementCommands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; internal void <RegisterConsoleCommand>b__0_0(ConsoleEventArgs args) { if (!GameManager.IsAdmin(Player.m_localPlayer)) { args.Context.AddString("You are not an admin."); return; } if (args.Args.Length < 3) { args.Context.AddString("Usage: add_power <name> <boss_name>"); return; } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "RPC_BossPowerGrantServer", new object[2] { args.Args[2], args.Args[1] }); } internal void <RegisterConsoleCommand>b__0_1(ConsoleEventArgs args) { if (!GameManager.IsAdmin(Player.m_localPlayer)) { args.Context.AddString("You are not an admin."); return; } if (args.Args.Length < 3) { args.Context.AddString("Usage: add_power <name> <boss_name>"); return; } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "RPC_BossPowerRemoveGrantServer", new object[2] { args.Args[2], args.Args[1] }); } } public static void RegisterConsoleCommand() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_006b: 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) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if (!GameManager.IsAdmin(Player.m_localPlayer)) { args.Context.AddString("You are not an admin."); } else if (args.Args.Length < 3) { args.Context.AddString("Usage: add_power <name> <boss_name>"); } else { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "RPC_BossPowerGrantServer", new object[2] { args.Args[2], args.Args[1] }); } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("add_power", "[player] [boss_name]", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { if (!GameManager.IsAdmin(Player.m_localPlayer)) { args.Context.AddString("You are not an admin."); } else if (args.Args.Length < 3) { args.Context.AddString("Usage: add_power <name> <boss_name>"); } else { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "RPC_BossPowerRemoveGrantServer", new object[2] { args.Args[2], args.Args[1] }); } }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("remove_power", "[boss_order] [player]", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } public class CheckBossesCommand { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; internal void <RegisterConsoleCommand>b__0_0(ConsoleEventArgs args) { bool flag = false; string playerName = Player.m_localPlayer.GetPlayerName(); args.Context.AddString("=== Bosses killed and allowed to use their powers ==="); foreach (BossNameEnum value in Enum.GetValues(typeof(BossNameEnum))) { if (!string.IsNullOrEmpty(value.GetConfigurationListName())) { string grantedPlayerNamesList = value.GetGrantedPlayerNamesList(); if ((!string.IsNullOrEmpty(grantedPlayerNamesList) && grantedPlayerNamesList.Contains(playerName)) || (ConfigurationFile.activateMidPlayDetection.Value && ((Humanoid)Player.m_localPlayer).HaveUniqueKey(value.GetPowerKey()))) { args.Context.AddString("- " + Localization.instance.Localize(value.GetTranslationKey())); flag = true; } } } if (!flag) { args.Context.AddString("NONE"); } } } public static void RegisterConsoleCommand() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { bool flag = false; string playerName = Player.m_localPlayer.GetPlayerName(); args.Context.AddString("=== Bosses killed and allowed to use their powers ==="); foreach (BossNameEnum value in Enum.GetValues(typeof(BossNameEnum))) { if (!string.IsNullOrEmpty(value.GetConfigurationListName())) { string grantedPlayerNamesList = value.GetGrantedPlayerNamesList(); if ((!string.IsNullOrEmpty(grantedPlayerNamesList) && grantedPlayerNamesList.Contains(playerName)) || (ConfigurationFile.activateMidPlayDetection.Value && ((Humanoid)Player.m_localPlayer).HaveUniqueKey(value.GetPowerKey()))) { args.Context.AddString("- " + Localization.instance.Localize(value.GetTranslationKey())); flag = true; } } } if (!flag) { args.Context.AddString("NONE"); } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("bosses_permissions", "", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } } namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T> : OwnConfigEntryBase { public readonly ConfigEntry<T> SourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public SyncedConfigEntry(ConfigEntry<T> sourceConfig) { SourceConfig = sourceConfig; } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (__instance.IsServer()) { BufferingSocket value = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, value); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, value); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = value; } } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZRpc rpc2 = rpc; ZNet __instance2 = __instance; Dictionary<Assembly, BufferingSocket> __state2 = __state; ZNetPeer peer; if (__instance2.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance2).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc2.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc2, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state2[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc2.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc2.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance2).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag.GetValueOrDefault(); } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0053: return (byte)result != 0; IL_0052: result = 0; goto IL_0053; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { ConfigEntry<T> configEntry2 = configEntry; OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry2); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry2); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry2).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry2).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry2.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry2); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { CustomSyncedValueBase customValue2 = customValue; if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue2.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue2); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue2.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue2); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2u) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4u) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int i = 0; i < num; i++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { OwnConfigEntryBase config2 = config; ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config2)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config2.SynchronizedConfig || config2.LocalBaseValue == null || (!configSync.IsLocked && (config2 != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZNetPeer peer2 = peer; ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer2.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer2.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer2.m_server ? 0 : peer2.m_uid, text, new object[1] { pkg }); } } IEnumerable<bool> waitForQueue() { float timeout = Time.time + 30f; while (peer2.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer2.m_uid} after 30 seconds config sending timeout"); peer2.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer2); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty<object>().GetEnumerator(); } List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package) { ZPackage package2 = package; if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package2.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write((byte)4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package2 = compressedPackage; } List<IEnumerator<bool>> writers = (from peer in peers where peer.IsReady() select peer into p select distributeConfigToPeers(p, package2)).ToList(); writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault(); } public static SyncedConfigEntry<T>? ConfigData<T>(ConfigEntry<T> config) { return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault(); } private static T configAttribute<T>(ConfigEntryBase config) { return config.Description.Tags.OfType<T>().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { return type.IsEnum ? Enum.GetUnderlyingType(type) : type; } private static ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase>? configs = null, IEnumerable<CustomSyncedValueBase>? customValues = null, IEnumerable<PackageEntry>? packageEntries = null, bool partial = true) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List<ConfigEntryBase> list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>(); List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>(); ZPackage val = new ZPackage(); val.Write((byte)(partial ? 1 : 0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty<PackageEntry>()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List<string>) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List<object> source = new List<object>(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] internal class VersionCheck { private static readonly HashSet<VersionCheck> versionChecks; private static readonly Dictionary<string, string> notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List<ZRpc> ValidatedClients = new List<ZRpc>(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { return minimumRequiredVersion ?? (ModRequired ? CurrentVersion : "0.0.0"); } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.