Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValheimEnforcer v0.9.1
plugins/ValheimEnforcer.dll
Decompiled a week 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.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using ValheimEnforcer; using ValheimEnforcer.common; using ValheimEnforcer.modules; using ValheimEnforcer.modules.character; using ValheimEnforcer.modules.cheatmonitor; using ValheimEnforcer.modules.commands; using ValheimEnforcer.modules.compat; using ValheimEnforcer.modules.compat.ExtraSlots; using ValheimEnforcer.modules.notifications; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ValheimEnforcer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ValheimEnforcer")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: AssemblyFileVersion("0.9.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.9.1.0")] internal class DeltaChangeTracker : MonoBehaviour { public void Update() { if (Time.unscaledTime > CharacterDeltaTracker.LastDeltaSyncTime) { CharacterDeltaTracker.LastDeltaSyncTime = Time.unscaledTime + (float)ValConfig.DeltaSynchronizationFrequencyInSeconds.Value; SyncChangesToServer(); } if (Time.unscaledTime > CharacterDeltaTracker.LastFullSaveDataSyncTime) { CharacterDeltaTracker.LastFullSaveDataSyncTime = Time.unscaledTime + (float)ValConfig.FullSaveDataSynchronizationFrequencyInSeconds.Value; Logger.LogDebug("Forcing full save data sync to server."); CharacterManager.SavePlayerCharacter(Player.m_localPlayer); } } private static void SyncChangesToServer() { //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Expected O, but got Unknown if (CharacterManager.PlayerCharacter == null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.GetServerPeer() == null) { return; } Logger.LogDebug("Checking for character changes to sync to server..."); List<DataObjects.ItemDelta> list = CharacterDeltaTracker.BuildCharacterItemDeltas(); Dictionary<string, string> customData = Player.m_localPlayer.m_customData; Dictionary<string, string> dictionary = new Dictionary<string, string>(); List<string> list2 = new List<string>(); foreach (KeyValuePair<string, string> item in customData) { if (CharacterManager.PlayerCharacter.PlayerCustomData.ContainsKey(item.Key)) { if (CharacterManager.PlayerCharacter.PlayerCustomData[item.Key] != item.Value) { dictionary.Add(item.Key, item.Value); } } else { dictionary.Add(item.Key, item.Value); } } foreach (KeyValuePair<string, string> playerCustomDatum in CharacterManager.PlayerCharacter.PlayerCustomData) { if (!customData.ContainsKey(playerCustomDatum.Key)) { list2.Add(playerCustomDatum.Key); } } if (list.Count == 0 && dictionary.Count == 0 && list2.Count == 0) { return; } Logger.LogDebug("Changes found, syncing deltas."); List<DataObjects.PackedItem> list3 = new List<DataObjects.PackedItem>(); foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems()) { list3.Add(CharacterDeltaTracker.BuildPackedItem(allItem)); } CharacterManager.PlayerCharacter.PlayerItems = list3; CharacterManager.PlayerCharacter.PlayerCustomData = customData; Dictionary<string, DataObjects.PackedStatusEffect> dictionary2 = new Dictionary<string, DataObjects.PackedStatusEffect>(); foreach (StatusEffect statusEffect in ((Character)Player.m_localPlayer).GetSEMan().GetStatusEffects()) { dictionary2.Add(((Object)statusEffect).name, new DataObjects.PackedStatusEffect(statusEffect)); } DataObjects.DeltaSummaryUpdate deltaSummaryUpdate = new DataObjects.DeltaSummaryUpdate { Name = CharacterManager.PlayerCharacter.Name, HostID = CharacterManager.PlayerCharacter.HostID, ItemModifications = list, SkillLevels = ((Character)Player.m_localPlayer).GetSkills().GetSkillList().ToDictionary((Skill s) => s.m_info.m_skill, (Skill s) => s.m_level), PlayerCustomDataModifications = dictionary, RemovedCustomDataKeys = list2, ActiveCharacterEffects = dictionary2 }; ZPackage val = new ZPackage(); val.Write(DataObjects.yamlserializer.Serialize((object)deltaSummaryUpdate)); ValConfig.ItemDeltaUpdateRPC.SendPackage(ZNet.instance.GetServerPeer().m_uid, val); Logger.LogDebug($"Delta flush: {list.Count} items, {dictionary.Count} ({list2.Count} removed) custom data changes. Skill levels updated."); } } namespace ValheimEnforcer { internal class ValConfig { public static ConfigFile cfg; public static ConfigEntry<bool> EnableDebugMode; public static ConfigEntry<bool> UpdateLoadedModsOnStartup; public static ConfigEntry<bool> AutoAddModsToRequired; public static ConfigEntry<bool> RemoveNontrackedItemsFromJoiningPlayers; public static ConfigEntry<bool> AddMissingItemsFromPlayerServerSave; public static ConfigEntry<bool> PreventExternalSkillRaises; public static ConfigEntry<bool> NewCharactersRemoveExtraItems; public static ConfigEntry<bool> NewCharacterSetSkillsToZero; public static ConfigEntry<bool> newCharacterClearCustomData; public static ConfigEntry<bool> PreventExternalCustomDataChanges; public static ConfigEntry<bool> ValidateItemCustomData; public static ConfigEntry<bool> ValidateItemDurability; public static ConfigEntry<float> ItemValidationDurabilityAllowedVariance; public static ConfigEntry<bool> SavePlayerStatusEffectsOnLogout; public static ConfigEntry<bool> ItemRemovalForDirtyReconnection; public static ConfigEntry<bool> ItemReturnForDirtyReconnection; public static ConfigEntry<bool> InternalStorageMode; public static ConfigEntry<int> ConfigPollIntervalSeconds; public static ConfigEntry<int> DeltaSynchronizationFrequencyInSeconds; public static ConfigEntry<int> FullSaveDataSynchronizationFrequencyInSeconds; public static ConfigEntry<bool> EnableCheatDetection; public static ConfigEntry<bool> DetectCheatEngine; public static ConfigEntry<bool> DetectValheimTooler; public static ConfigEntry<string> CheatDetectionAction; public static ConfigEntry<int> CheatScanIntervalSeconds; public static ConfigEntry<string> DiscordWebhookUrl; public static ConfigEntry<bool> DiscordNotifyServerStartup; public static ConfigEntry<bool> DiscordNotifyServerShutdown; public static ConfigEntry<bool> DiscordNotifyPlayerJoined; public static ConfigEntry<bool> DiscordNotifyPlayerLeft; public static ConfigEntry<bool> DiscordNotifyWrongMods; internal const string ModsFileName = "Mods.yaml"; internal const string ValheimEnforcer = "ValheimEnforcer"; internal const string CharacterFolder = "Characters"; internal const string KnownCheatersFileName = "KnownCheaters.yaml"; internal static string ModsConfigFilePath = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Mods.yaml"); internal static string CharacterFilePath = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters"); internal static string KnownCheatersFilePath = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "KnownCheaters.yaml"); internal static CustomRPC CharacterSaveRPC; internal static CustomRPC ReturnConfiscatedItemsRPC; internal static CustomRPC CheatDetectionRPC; internal static CustomRPC ItemDeltaUpdateRPC; internal static CustomRPC ListPlayerRPC; internal static CustomRPC ClearConfiscatedRPC; public ValConfig(ConfigFile cf) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_005e: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_008a: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00b6: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown //IL_00e2: Expected O, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown //IL_010e: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_013a: Expected O, but got Unknown cfg = cf; cfg.SaveOnConfigSet = true; CreateConfigValues(cf); Logger.SetDebugLogging(EnableDebugMode.Value); ConfigFileWatcher.Initialize(); SetupMainFileWatcher(); CharacterSaveRPC = NetworkManager.Instance.AddRPC("VENFORCE_CHAR", new CoroutineHandler(OnServerRecieveCharacter), new CoroutineHandler(OnClientReceiveCharacter)); ReturnConfiscatedItemsRPC = NetworkManager.Instance.AddRPC("VENFORCE_RETURN_CONFISCATED", new CoroutineHandler(OnServerReturnConfiscatedReceive), new CoroutineHandler(OnClientReceiveConfiscatedItems)); CheatDetectionRPC = NetworkManager.Instance.AddRPC("VENFORCE_CHEAT", new CoroutineHandler(OnServerReceiveCheatReport), new CoroutineHandler(OnClientReceiveCheatReport)); ItemDeltaUpdateRPC = NetworkManager.Instance.AddRPC("VENFORCE_ITEMDELTA", new CoroutineHandler(OnServerRecieveDeltaItemUpdate), new CoroutineHandler(OnClientReceiveDeltaItemUpdate)); ListPlayerRPC = NetworkManager.Instance.AddRPC("VENFORCE_LIST_PLAYER", new CoroutineHandler(OnServerReceiveListPlayer), new CoroutineHandler(OnClientReceiveListPlayer)); ClearConfiscatedRPC = NetworkManager.Instance.AddRPC("VENFORCE_CLEAR_CONFISCATED", new CoroutineHandler(OnServerRecieveClearConfiscated), new CoroutineHandler(OnClientReceiveClearConfiscated)); SynchronizationManager.Instance.AddInitialSynchronization(CharacterSaveRPC, (Func<ZNetPeer, ZPackage>)SendSavedCharacter); LoadYamlConfigs(new Dictionary<string, Action<string>> { { ModsConfigFilePath, CreateModsFile }, { KnownCheatersFilePath, CreateKnownCheatersFile } }); KnownCheaterTracker.Initialize(); } private void CreateConfigValues(ConfigFile Config) { //IL_001a: 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_002c: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown EnableDebugMode = Config.Bind<bool>("Client config", "EnableDebugMode", false, new ConfigDescription("Enables Debug logging.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdvanced = true } })); EnableDebugMode.SettingChanged += Logger.EnableDebugLogging; Logger.CheckEnableDebugLogging(); UpdateLoadedModsOnStartup = BindServerConfig("Mods", "UpdateLoadedModsOnStartup", value: true, "Whether or not the mod configuration file will update its loaded mods once they are detected."); AutoAddModsToRequired = BindServerConfig("Mods", "AutoAddModsToRequired", value: true, "If true, automatically adds mods not found in the optional, admin, or server-only mod lists."); RemoveNontrackedItemsFromJoiningPlayers = BindServerConfig("Player Sync", "RemoveNontrackedItemsFromJoiningPlayers", value: true, "If enabled, any items that are not tracked by the server will be removed from joining player's inventories."); AddMissingItemsFromPlayerServerSave = BindServerConfig("Player Sync", "AddMissingItemsFromPlayerServerSave", value: true, "If enabled, any items the player does not have that are listed on the server will be given to the player when joining"); PreventExternalSkillRaises = BindServerConfig("Player Sync", "PreventExternalSkillRaises", value: true, "If enabled, player skill gains outside of the server are removed when connecting."); NewCharactersRemoveExtraItems = BindServerConfig("Player Sync", "NewCharactersRemoveExtraItems", value: false, "If enabled, new characters that have no existing character file will have all items removed except for starting items."); NewCharacterSetSkillsToZero = BindServerConfig("Player Sync", "NewCharacterSetSkillsToZero", value: false, "If enabled, new characters will have their skills set to zero. Prevents players from raising skills before connecting."); PreventExternalCustomDataChanges = BindServerConfig("Player Sync", "PreventExternalCustomDataChanges", value: true, "If enabled, tracks player custom data. Warning: custom data can be large and can impact how other mods function."); newCharacterClearCustomData = BindServerConfig("Player Sync", "newCharacterClearCustomData", value: true, "If enabled, new characters will have their custom data cleared."); ValidateItemCustomData = BindServerConfig("Player Sync", "ValidateItemCustomData", value: true, "If enabled, custom data on items will be validated."); ValidateItemDurability = BindServerConfig("Player Sync", "ValidateItemDurability", value: true, "If enabled, item durability will be validated"); ItemValidationDurabilityAllowedVariance = BindServerConfig("Player Sync", "ItemValidationDurabilityAllowedVariance", 10f, "Allowed variance for item durability validation.", advanced: true, 0f, 100f); SavePlayerStatusEffectsOnLogout = BindServerConfig("Player Sync", "SavePlayerStatusEffectsOnLogout", value: true, "Whether or not to save active character effects on logout and reapply on login"); ItemRemovalForDirtyReconnection = BindServerConfig("Player Sync", "ItemRemovalForDirtyReconnection", value: false, "If enabled, items will not be removed from the player on a dirty reconnection."); ItemReturnForDirtyReconnection = BindServerConfig("Player Sync", "ItemReturnForDirtyReconnection", value: false, "If enabled, items will be returned to the player on a dirty reconnection."); InternalStorageMode = BindServerConfig("Advanced", "InternalStorageMode", value: false, "If enabled, player character data will be stored within your world. Enables full portability of the world without having to synchronize configurations.", null, advanced: true); ConfigPollIntervalSeconds = BindServerConfig("Advanced", "ConfigPollIntervalSeconds", 30, "How frequently (in seconds) the mod polls config files on disk for changes.", advanced: true, 1, 300); DeltaSynchronizationFrequencyInSeconds = BindServerConfig("Advanced", "CharacterDeltaTracker", 60, "How frequently (in seconds) the client sends incremental inventory/skill/custom-data updates to the server.", advanced: true, 30, 300); FullSaveDataSynchronizationFrequencyInSeconds = BindServerConfig("Advanced", "FullSaveDataSynchronizationFrequencyInSeconds", 300, "How frequently (in seconds) the client sends a full character save to the server.", advanced: true, 60, 3600); EnableCheatDetection = BindServerConfig("Anti-Cheat", "EnableCheatDetection", value: false, "Enable client-side scanning for known cheat tools (Cheat Engine, ValheimTooler). Detections are reported to the server."); DetectValheimTooler = BindServerConfig("Anti-Cheat", "DetectValheimTooler", value: true, "Scan loaded assemblies for ValheimTooler. High confidence, very low cost."); DetectCheatEngine = BindServerConfig("Anti-Cheat", "DetectCheatEngine", value: true, "Scan for Cheat Engine (processes, windows, injected speedhack/DBK modules, debugger). Note: Cheat Engine has legitimate uses — prefer Log action over Kick/Ban."); CheatDetectionAction = BindServerConfig("Anti-Cheat", "ActionOnDetection", "Kick", "Server-side action taken when a client reports a cheat detection.", new AcceptableValueList<string>(new string[3] { "Log", "Kick", "Ban" })); CheatScanIntervalSeconds = BindServerConfig("Anti-Cheat", "ScanIntervalSeconds", 5, "Seconds between scans on the client.", advanced: false, 1, 60); DiscordWebhookUrl = BindLocalConfig("Discord", "WebhookUrl", "", "Discord webhook URL the server posts notifications to. This is a server-only secret and is never synced to clients. Leave empty to disable. Note: player names are sent to Discord when enabled."); DiscordNotifyServerStartup = BindLocalConfig("Discord", "NotifyServerStartup", value: true, "Post a message when the server comes online."); DiscordNotifyServerShutdown = BindLocalConfig("Discord", "NotifyServerShutdown", value: true, "Post a message when the server shuts down."); DiscordNotifyPlayerJoined = BindLocalConfig("Discord", "NotifyPlayerJoined", value: true, "Post a message when a player joins."); DiscordNotifyPlayerLeft = BindLocalConfig("Discord", "NotifyPlayerLeft", value: true, "Post a message when a player leaves, including whether their saved data is up to date."); DiscordNotifyWrongMods = BindLocalConfig("Discord", "NotifyWrongMods", value: true, "Post a message when a player is rejected for a mod mismatch, listing the offending mods."); } internal static void WritePlayerCharacterToSave(string id, DataObjects.Character character) { if (InternalStorageMode.Value) { Logger.LogInfo("Saving character with internal storage mode."); InternalDataStore.SaveAccountCharacter(character); } Directory.CreateDirectory(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters")); string text = Path.Combine(Directory.CreateDirectory(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", id)).FullName, character.Name + ".yaml"); Logger.LogInfo("Writing to " + text); try { File.WriteAllText(text, DataObjects.yamlserializer.Serialize((object)character)); } catch (Exception ex) { Logger.LogWarning("Failed to write character data to disk at " + text + ": " + ex.Message); } } internal static DataObjects.Character LoadCharacterFromSave(string id, string name) { if (InternalStorageMode.Value) { Logger.LogInfo("Loading character from internal storage system."); DataObjects.Character accountCharacter = InternalDataStore.GetAccountCharacter(id, name); if (accountCharacter == null) { Logger.LogDebug("No character file found for player with " + id + "-" + name + " is this character new?"); } return accountCharacter; } string path = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", id, name + ".yaml"); if (!File.Exists(path)) { Logger.LogDebug("No character file found for player with " + id + "-" + name + " is this character new?"); return null; } string text = File.ReadAllText(path); return DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(text); } public static string GetSecondaryConfigDirectoryPath() { string text = Path.Combine(Paths.ConfigPath, "ValheimEnforcer"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } return text; } internal void LoadYamlConfigs(Dictionary<string, Action<string>> configFilesToFind) { string[] files = Directory.GetFiles(GetSecondaryConfigDirectoryPath()); List<string> list = new List<string>(); List<string> list2 = configFilesToFind.Keys.ToList(); string[] array = files; foreach (string text in array) { if (list2.Contains(text)) { list.Add(text); Logger.LogDebug("Found config: " + text); } } foreach (KeyValuePair<string, Action<string>> item in configFilesToFind) { if (!list.Contains(item.Key)) { configFilesToFind[item.Key](item.Key); list.Add(item.Key); } } foreach (string item2 in list) { string fileName = Path.GetFileName(item2); Logger.LogDebug("Setting filewatcher for " + fileName); SetupFileWatcher(item2); } } private void SetupFileWatcher(string fullPath) { ConfigFileWatcher.Register(fullPath, UpdateConfigFileOnChange); } private static void UpdateConfigFileOnChange(string filepath) { if (!SynchronizationManager.Instance.PlayerIsAdmin) { Logger.LogInfo("Player is not an admin, and not allowed to change local configuration. Ignoring."); } else { if (!File.Exists(filepath)) { return; } string text = File.ReadAllText(filepath); FileInfo fileInfo = new FileInfo(filepath); Logger.LogDebug("Filewatch changes from: (" + fileInfo.Name + ") " + fileInfo.FullName); string name = fileInfo.Name; if (!(name == "Mods.yaml")) { if (name == "KnownCheaters.yaml") { Logger.LogDebug("Triggering KnownCheaters list update."); KnownCheaterTracker.LoadFromText(text); } } else { Logger.LogDebug("Triggering Mod Settings update."); ModManager.UpdateModSettingConfigs(text); } } } private static void CreateModsFile(string filepath) { Logger.LogDebug("Loot config missing, recreating."); using StreamWriter streamWriter = new StreamWriter(filepath); string value = "#################################################\n# Valheim Enforcer - Required, Admin and Optional Mods\n#################################################\n"; streamWriter.WriteLine(value); streamWriter.WriteLine(ModManager.GetDefaultConfig()); } private static void CreateKnownCheatersFile(string filepath) { Logger.LogDebug("KnownCheaters file missing, recreating."); using StreamWriter streamWriter = new StreamWriter(filepath); string value = "#################################################\n# Valheim Enforcer - Known Cheaters (server side)\n# Auto-populated when cheaters are banned. Entries: { id, reason }\n#################################################\n"; streamWriter.WriteLine(value); } internal static ZPackage SendSavedCharacter(ZNetPeer peer) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown string endPointString = peer.m_socket.GetEndPointString(); Logger.LogInfo("Sending saved character data to player " + peer.m_playerName + " with ID: " + endPointString); ZPackage val = new ZPackage(); if (InternalStorageMode.Value) { Logger.LogInfo("Using internal storage mode to send character data."); DataObjects.Character accountCharacter = InternalDataStore.GetAccountCharacter(endPointString, peer.m_playerName); if (accountCharacter == null) { Logger.LogInfo("No character data found for player " + peer.m_playerName + " with ID: " + endPointString + ", no character data will be sent."); return new ZPackage(); } string text = DataObjects.yamlserializer.Serialize((object)accountCharacter); val.Write(text); return val; } string text2 = Path.Combine(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", endPointString ?? ""), peer.m_playerName + ".yaml"); if (!File.Exists(text2)) { Logger.LogInfo("path: " + text2 + " does not exist, no character data will be sent."); return new ZPackage(); } string text3 = File.ReadAllText(text2); val.Write(text3); return val; } public static IEnumerator OnServerRecieveCharacter(long sender, ZPackage package) { try { DataObjects.Character character = DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(package.ReadString()); Logger.LogInfo($"Recieved Player data update for {sender} - {character.Name}|{character.HostID}"); WritePlayerCharacterToSave(character.HostID, character); } catch (Exception ex) { Logger.LogWarning($"Failed to deserialize character data from {sender}: {ex.Message}"); } yield break; } public static IEnumerator OnServerRecieveClearConfiscated(long sender, ZPackage package) { DataObjects.RPCServerUpdateData rPCServerUpdateData = DataObjects.yamldeserializer.Deserialize<DataObjects.RPCServerUpdateData>(package.ReadString()); ZNetPeer peerByPlatformID = GetPeerByPlatformID(rPCServerUpdateData.PlatformID); if (peerByPlatformID == null) { Logger.LogWarning("Could not find peer with PlatformID " + rPCServerUpdateData.PlatformID + " to clear confiscated items."); yield break; } CommandHelpers.ClearSpecifiedPlayerConfiscatedItems(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName, rPCServerUpdateData.ItemPrefabFilter); ClearConfiscatedRPC.SendPackage(peerByPlatformID.m_uid, package); } public static IEnumerator OnClientReceiveClearConfiscated(long sender, ZPackage package) { DataObjects.RPCServerUpdateData rPCServerUpdateData = DataObjects.yamldeserializer.Deserialize<DataObjects.RPCServerUpdateData>(package.ReadString()); CommandHelpers.ClearSpecifiedPlayerConfiscatedItems(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName, rPCServerUpdateData.ItemPrefabFilter); yield break; } public static IEnumerator OnClientReceiveCharacter(long sender, ZPackage package) { DataObjects.Character playerCharacter = DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(package.ReadString()); Logger.LogDebug("Recieved Player character data from server."); CharacterManager.SetPlayerCharacter(playerCharacter); yield break; } public static IEnumerator OnServerReturnConfiscatedReceive(long sender, ZPackage package) { DataObjects.RPCServerUpdateData rPCServerUpdateData = DataObjects.yamldeserializer.Deserialize<DataObjects.RPCServerUpdateData>(package.ReadString()); List<DataObjects.PackedItem> list = CommandHelpers.LoadCharacterAndFindItemsToReturn(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName, rPCServerUpdateData.ItemPrefabFilter); DataObjects.Character character = LoadCharacterFromSave(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName); ZNetPeer peerByPlatformID = GetPeerByPlatformID(rPCServerUpdateData.PlatformID); if (peerByPlatformID == null) { Logger.LogInfo("Player " + rPCServerUpdateData.PlayerName + " is not currently connected. Moving items to player inventory save so they are restored on next login."); foreach (DataObjects.PackedItem item in list) { character.PlayerItems.Add(item); } WritePlayerCharacterToSave(rPCServerUpdateData.PlatformID, character); if (InternalStorageMode.Value) { Logger.LogInfo("Also updating character data in internal storage."); InternalDataStore.SaveAccountCharacter(character); } } else { Logger.LogInfo($"Sending {list.Count} confiscated item(s) to player {rPCServerUpdateData.PlayerName}."); WritePlayerCharacterToSave(rPCServerUpdateData.PlatformID, character); if (InternalStorageMode.Value) { Logger.LogInfo("Also updating character data in internal storage."); InternalDataStore.SaveAccountCharacter(character); } ZPackage val = new ZPackage(); val.Write(DataObjects.yamlserializer.Serialize((object)list)); ReturnConfiscatedItemsRPC.SendPackage(peerByPlatformID.m_uid, val); CharacterSaveRPC.SendPackage(peerByPlatformID.m_uid, SendCharacterAsZpackage(character)); } yield break; } public static IEnumerator OnServerReceiveCheatReport(long sender, ZPackage package) { string text = package.ReadString(); DataObjects.CheatSummaryReport cheatSummaryReport; try { cheatSummaryReport = DataObjects.yamldeserializer.Deserialize<DataObjects.CheatSummaryReport>(text); } catch (Exception ex) { Logger.LogWarning($"Failed to deserialize cheat report from {sender}: {ex.Message}"); yield break; } ZNetPeer peer = ZNet.instance.GetPeer(sender); string playerName = cheatSummaryReport.PlayerName; string endPointString = peer.m_socket.GetEndPointString(); Logger.LogWarning($"Cheat detection from {playerName} ({endPointString}): valheim-tooler: {cheatSummaryReport.ValheimToolerStatus} cheatengine: {cheatSummaryReport.CheatEngineStatus.IsCheatEngineDetected()}"); string text2 = CheatDetectionAction.Value ?? "Log"; if (peer == null) { Logger.LogWarning("Received cheat report for " + playerName + " but could not find corresponding peer. No action will be taken."); yield break; } switch (text2) { case "Kick": Logger.LogWarning("Kicking " + playerName + " for cheat usage."); ZNet.instance.Kick(playerName); break; case "Ban": Logger.LogWarning("Banning " + playerName + " for cheat usage."); KnownCheaterTracker.AddCheater(peer.m_socket.GetHostName(), BuildCheatReason(cheatSummaryReport)); ZNet.instance.Ban(playerName); break; case "Log": break; } } private static string BuildCheatReason(DataObjects.CheatSummaryReport summary) { List<string> list = new List<string>(); if (summary.ValheimToolerStatus) { list.Add("ValheimTooler"); } if (summary.CheatEngineStatus != null && summary.CheatEngineStatus.IsCheatEngineDetected()) { list.Add("CheatEngine"); } string text = ((list.Count > 0) ? string.Join(", ", list) : "cheat detected"); return "Cheat detection: " + text; } public static IEnumerator OnClientReceiveCheatReport(long sender, ZPackage package) { yield break; } public static IEnumerator OnClientReceiveListPlayer(long sender, ZPackage package) { foreach (KeyValuePair<string, List<string>> item in DataObjects.yamldeserializer.Deserialize<Dictionary<string, List<string>>>(package.ReadString())) { Logger.LogInfo("AccountID: " + item.Key); foreach (string item2 in item.Value) { Logger.LogInfo(" Character: " + item2); } } yield break; } public static IEnumerator OnServerReceiveListPlayer(long sender, ZPackage package) { Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(); if (InternalStorageMode.Value) { dictionary = InternalDataStore.GetAccountRegistry(); ListPlayerRPC.SendPackage(sender, new ZPackage(DataObjects.yamlserializer.Serialize((object)dictionary))); yield break; } foreach (string item in Directory.GetFiles(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters")).ToList()) { List<string> list = Directory.GetFiles(item).ToList(); string key = item.Split(new char[1] { '/' }).Last(); List<string> list2 = new List<string>(); foreach (string item2 in list) { list2.Add(item2.Split(new char[1] { '/' }).Last()); } dictionary.Add(key, list2); } ListPlayerRPC.SendPackage(sender, new ZPackage(DataObjects.yamlserializer.Serialize((object)dictionary))); } public static IEnumerator OnClientReceiveConfiscatedItems(long sender, ZPackage package) { List<DataObjects.PackedItem> list = DataObjects.yamldeserializer.Deserialize<List<DataObjects.PackedItem>>(package.ReadString()); Logger.LogInfo($"Received {list.Count} confiscated item(s) returned from server."); foreach (DataObjects.PackedItem item in list) { Logger.LogInfo($"Adding returned confiscated item: {item.prefabName} x{item.m_stack}"); item.AddToInventory(Player.m_localPlayer, use_position: false); } yield break; } internal static IEnumerator OnServerRecieveDeltaItemUpdate(long sender, ZPackage package) { string text = package.ReadString(); DataObjects.DeltaSummaryUpdate deltaSummaryUpdate; try { deltaSummaryUpdate = DataObjects.yamldeserializer.Deserialize<DataObjects.DeltaSummaryUpdate>(text); } catch (Exception ex) { Logger.LogWarning($"Failed to deserialize delta update from {sender}: {ex.Message}"); yield break; } if (string.IsNullOrEmpty(deltaSummaryUpdate.Name) || string.IsNullOrEmpty(deltaSummaryUpdate.HostID)) { Logger.LogWarning($"Malformed delta update from {sender}: missing CharacterName or HostName."); yield break; } string path = Path.Combine(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", deltaSummaryUpdate.HostID), deltaSummaryUpdate.Name + ".yaml"); DataObjects.Character character; try { if (InternalStorageMode.Value) { Logger.LogInfo("Loading character for delta update with internal storage mode."); character = InternalDataStore.GetAccountCharacter(deltaSummaryUpdate.HostID, deltaSummaryUpdate.Name); if (character == null) { Logger.LogWarning("No character found in internal storage for " + deltaSummaryUpdate.Name + " (" + deltaSummaryUpdate.HostID + "). Delta dropped."); yield break; } } else { character = DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(File.ReadAllText(path)); } } catch (Exception ex2) { Logger.LogWarning("Failed to parse character save for delta update (" + deltaSummaryUpdate.Name + "): " + ex2.Message); yield break; } if (character == null) { Logger.LogWarning("Server Character does not exist for " + deltaSummaryUpdate.Name + " (" + deltaSummaryUpdate.HostID + "). Delta dropped."); } else { Logger.LogInfo($"Received delta update from {deltaSummaryUpdate.Name} ({deltaSummaryUpdate.HostID}): {deltaSummaryUpdate.ItemModifications?.Count ?? 0} item delta(s)."); UpdatePlayerSaveWithDeltaData(deltaSummaryUpdate, character); } } public static IEnumerator OnClientReceiveDeltaItemUpdate(long sender, ZPackage package) { yield break; } internal static void UpdatePlayerSaveWithDeltaData(DataObjects.DeltaSummaryUpdate deltaSummary, DataObjects.Character character) { foreach (DataObjects.ItemDelta itemModification in deltaSummary.ItemModifications) { if (itemModification.Item.m_quality != 0) { _ = itemModification.Item.m_quality; } switch (itemModification.Op) { case DataObjects.ItemDeltaChangeType.Added: character.PlayerItems.Add(itemModification.Item); Logger.LogDebug($"Delta: added {itemModification.Item.prefabName} x{itemModification.Item.m_stack}."); break; case DataObjects.ItemDeltaChangeType.Removed: character.RemoveFromPlayerItems(itemModification.Item); break; } } Logger.LogDebug($"Applied {deltaSummary.ItemModifications.Count} item delta(s) for {character.Name}."); foreach (string removedCustomDataKey in deltaSummary.RemovedCustomDataKeys) { character.PlayerCustomData.Remove(removedCustomDataKey); } foreach (KeyValuePair<string, string> playerCustomDataModification in deltaSummary.PlayerCustomDataModifications) { if (character.PlayerCustomData.ContainsKey(playerCustomDataModification.Key)) { character.PlayerCustomData[playerCustomDataModification.Key] = playerCustomDataModification.Value; } else { character.PlayerCustomData.Add(playerCustomDataModification.Key, playerCustomDataModification.Value); } } Logger.LogDebug("Updated custom data for " + character.Name + "."); character.SkillLevels = deltaSummary.SkillLevels; Logger.LogDebug("Updated skills for " + character.Name + "."); character.ActiveCharacterEffects = deltaSummary.ActiveCharacterEffects; if (InternalStorageMode.Value) { Logger.LogInfo("Saving character with internal storage mode."); InternalDataStore.SaveAccountCharacter(character); } character.LastDisconnect = deltaSummary.DisconnectionState; File.WriteAllText(Path.Combine(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", deltaSummary.HostID), deltaSummary.Name + ".yaml"), DataObjects.yamlserializer.Serialize((object)character)); Logger.LogInfo("Saved delta update for " + character.Name + "."); } internal static ZPackage SendCharacterAsZpackage(DataObjects.Character chara) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown string text = DataObjects.yamlserializer.Serialize((object)chara); ZPackage val = new ZPackage(); val.Write(text); return val; } public static ZNetPeer GetPeerByPlatformID(string platformID) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && peer.m_socket.GetHostName() == platformID) { return peer; } } return null; } internal static void SetupMainFileWatcher() { ConfigFileWatcher.Register(cfg.ConfigFilePath, OnMainConfigFileChanged); } private static void OnMainConfigFileChanged(string _) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { Logger.LogInfo("Configuration file has been changed, reloading settings."); cfg.Reload(); } } public static ConfigEntry<string> BindLocalConfig(string catagory, string key, string value, string description, bool advanced = false) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown return cfg.Bind<string>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = false, IsAdvanced = advanced } })); } public static ConfigEntry<bool> BindLocalConfig(string catagory, string key, bool value, string description, bool advanced = false) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown return cfg.Bind<bool>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = false, IsAdvanced = advanced } })); } public static ConfigEntry<List<string>> BindServerConfig(string catagory, string key, List<string> value, string description, bool advanced = false) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown return cfg.Bind<List<string>>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<float[]> BindServerConfig(string catagory, string key, float[] value, string description, bool advanced = false, float valmin = 0f, float valmax = 150f) { //IL_001a: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<float[]>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<bool> BindServerConfig(string catagory, string key, bool value, string description, AcceptableValueBase acceptableValues = null, bool advanced = false) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown return cfg.Bind<bool>(catagory, key, value, new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<int> BindServerConfig(string catagory, string key, int value, string description, bool advanced = false, int valmin = 0, int valmax = 150) { //IL_001a: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<int>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<float> BindServerConfig(string catagory, string key, float value, string description, bool advanced = false, float valmin = 0f, float valmax = 150f) { //IL_001a: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<float>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<string> BindServerConfig(string catagory, string key, string value, string description, AcceptableValueList<string> acceptableValues = null, bool advanced = false) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown return cfg.Bind<string>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } } internal class Logger { public static LogLevel Level = (LogLevel)16; public static void EnableDebugLogging(object sender, EventArgs e) { CheckEnableDebugLogging(); } public static void CheckEnableDebugLogging() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ValConfig.EnableDebugMode.Value) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void SetDebugLogging(bool state) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (state) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void LogDebug(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 32) { ValheimEnforcer.Log.LogInfo((object)message); } } public static void LogInfo(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 16) { ValheimEnforcer.Log.LogInfo((object)message); } } public static void LogWarning(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 4) { ValheimEnforcer.Log.LogWarning((object)message); } } public static void LogError(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 2) { ValheimEnforcer.Log.LogError((object)message); } } } [BepInPlugin("MidnightsFX.ValheimEnforcer", "ValheimEnforcer", "0.9.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] internal class ValheimEnforcer : BaseUnityPlugin { public const string PluginGUID = "MidnightsFX.ValheimEnforcer"; public const string PluginName = "ValheimEnforcer"; public const string PluginVersion = "0.9.1"; internal static ManualLogSource Log; internal ValConfig cfg; public static CustomLocalization Localization = LocalizationManager.Instance.GetLocalization(); public static AssetBundle EmbeddedResourceBundle; public void Awake() { Log = ((BaseUnityPlugin)this).Logger; cfg = new ValConfig(((BaseUnityPlugin)this).Config); EmbeddedResourceBundle = AssetUtils.LoadAssetBundleFromResources("ValheimEnforcer.assets.vebundle", typeof(ValheimEnforcer).Assembly); PrefabManager.OnPrefabsRegistered += ModManager.SetModsActive; ZoneManager.OnLocationsRegistered += InternalDataStore.InstanciateOrLinkMetadataRegistry; PrefabManager.OnVanillaPrefabsAvailable += ModManager.SetModsActive; GUIManager.OnCustomGUIAvailable += ModManager.AddErrorMessageDetailsForMenu; InternalDataStore.RegisterMetadataHolder(); TerminalCommands.AddCommands(); MinimapManager.OnVanillaMapDataLoaded += CheatDetector.Initialize; MinimapManager.OnVanillaMapDataLoaded += CharacterDeltaTracker.Initialize; ModCompatability.CheckModCompat(); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null); } } } namespace ValheimEnforcer.modules { internal static class InternalDataStore { private static ZDO MetadataRegistry; internal static void SaveAccountCharacter(DataObjects.Character character) { UpdateAccountRegistry(character.HostID, character.Name); string text = MetadataRegistry.GetString(character.HostID, (string)null); if (text != null) { DataObjects.CharacterSaveData characterSaveData = DataObjects.yamldeserializer.Deserialize<DataObjects.CharacterSaveData>(text); if (characterSaveData.SavedCharacters.ContainsKey(character.Name)) { characterSaveData.SavedCharacters[character.Name] = character; } else { characterSaveData.SavedCharacters.Add(character.Name, character); } string text2 = DataObjects.yamlserializer.Serialize((object)characterSaveData); MetadataRegistry.Set(character.HostID, text2); } else { DataObjects.CharacterSaveData characterSaveData2 = new DataObjects.CharacterSaveData { SavedCharacters = new Dictionary<string, DataObjects.Character> { { character.Name, character } } }; string text3 = DataObjects.yamlserializer.Serialize((object)characterSaveData2); MetadataRegistry.Set(character.HostID, text3); } } internal static DataObjects.Character GetAccountCharacter(string accountID, string characterName) { InstanciateOrLinkMetadataRegistry(); string text = MetadataRegistry.GetString(accountID, (string)null); if (text != null) { Logger.LogDebug("Character data found " + accountID + "-" + characterName + "."); DataObjects.CharacterSaveData characterSaveData = DataObjects.yamldeserializer.Deserialize<DataObjects.CharacterSaveData>(text); if (characterSaveData.SavedCharacters.ContainsKey(characterName)) { return characterSaveData.SavedCharacters[characterName]; } } return null; } internal static DataObjects.CharacterSaveData GetAccountData(string accountID) { InstanciateOrLinkMetadataRegistry(); string text = MetadataRegistry.GetString(accountID, (string)null); if (text != null) { return DataObjects.yamldeserializer.Deserialize<DataObjects.CharacterSaveData>(text); } return null; } internal static void RegisterMetadataHolder() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown CustomPrefab val = new CustomPrefab(ValheimEnforcer.EmbeddedResourceBundle.LoadAsset<GameObject>("VE_METADATA"), false); PrefabManager.Instance.AddPrefab(val); } internal static void InstanciateOrLinkMetadataRegistry() { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (MetadataRegistry != null) { return; } string text = default(string); if (ZoneSystem.instance.GetGlobalKey(DataObjects.CustomDataKey ?? "", ref text)) { string[] array = text.Split(new char[1] { ' ' }); if (array.Length == 2 && long.TryParse(array[0], out var result) && uint.TryParse(array[1], out var result2)) { ZDOID val = default(ZDOID); ((ZDOID)(ref val))..ctor(result, result2); MetadataRegistry = ZDOMan.instance.GetZDO(val); } } long sessionID = ZDOMan.GetSessionID(); ZDO val2 = ZDOMan.instance.CreateNewZDO(Vector3.zero, 0); val2.Persistent = true; val2.SetOwner(sessionID); MetadataRegistry = val2; ZoneSystem.instance.SetGlobalKey($"{DataObjects.CustomDataKey} {((ZDOID)(ref MetadataRegistry.m_uid)).UserID} {((ZDOID)(ref MetadataRegistry.m_uid)).ID}"); Logger.LogInfo($"Hooking up Metadata Registry. SessionID:{sessionID} ZDO:{val2.m_uid}"); Logger.LogInfo($"Setting globalkey: {DataObjects.CustomDataKey} {((ZDOID)(ref MetadataRegistry.m_uid)).UserID} {((ZDOID)(ref MetadataRegistry.m_uid)).ID}"); } internal static void UpdateAccountRegistry(string accountID, string chara = null) { InstanciateOrLinkMetadataRegistry(); string text = MetadataRegistry.GetString("VE_ACCOUNTS", (string)null); if (text != null) { Dictionary<string, List<string>> dictionary = DataObjects.yamldeserializer.Deserialize<Dictionary<string, List<string>>>(text); if (!dictionary.ContainsKey(accountID)) { if (chara != null) { dictionary[accountID] = new List<string> { chara }; } else { dictionary[accountID] = new List<string>(); } string text2 = DataObjects.yamlserializer.Serialize((object)dictionary); MetadataRegistry.Set("VE_ACCOUNTS", text2); } } else { List<string> list = new List<string>(); if (chara != null) { list.Add(chara); } Dictionary<string, List<string>> dictionary2 = new Dictionary<string, List<string>> { { accountID, list } }; string text3 = DataObjects.yamlserializer.Serialize((object)dictionary2); MetadataRegistry.Set("VE_ACCOUNTS", text3); } } internal static Dictionary<string, List<string>> GetAccountRegistry() { InstanciateOrLinkMetadataRegistry(); string text = MetadataRegistry.GetString("VE_ACCOUNTS", (string)null); if (text != null) { return DataObjects.yamldeserializer.Deserialize<Dictionary<string, List<string>>>(text); } return new Dictionary<string, List<string>>(); } } internal static class ModManager { internal static class ValidateMods { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] public static class ZNet_OnNewConnection_Patch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ZNet __instance, ZNetPeer peer) { Logger.LogDebug("New Connection, register VE Mod Sync RPC."); peer.m_rpc.Register<ZPackage>("RPC_ReceiveModVersionData", (Action<ZRpc, ZPackage>)RPC_ReceiveModVersionData); } } } [HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")] public static class ZNet_RPC_ClientHandshake_Patch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ZNet __instance, ZRpc rpc) { if (ZNetExtension.IsClientInstance(__instance)) { Logger.LogDebug("Client sending mod version data to server"); rpc.Invoke("RPC_ReceiveModVersionData", new object[1] { ModSettings.ToZPackage() }); } } } [HarmonyPatch(typeof(ZNet), "RPC_ServerHandshake")] public static class ZNet_RPC_ServerHandshake_Patch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ZNet __instance, ZRpc rpc) { if (__instance.IsServer()) { Logger.LogDebug("Server sending mod version data to client"); rpc.Invoke("RPC_ReceiveModVersionData", new object[1] { ModSettings.ToZPackage() }); } } } public class JotunnDetailDisconnectExpansion : MonoBehaviour { private GameObject ContentView; private Text HeaderText; private Text FooterText; private static string HeaderMessage = ""; private static string FooterMessage = ""; private bool textset; public void UpdateErrorText(string header, string footer) { Logger.LogDebug("Set Error results " + header + " " + footer); HeaderMessage = header; FooterMessage = footer; textset = false; } public void Update() { if ((Object)(object)GUIManager.CustomGUIFront == (Object)null) { return; } Transform val = GUIManager.CustomGUIFront.transform.Find("CompatibilityWindow(Clone)/Scroll View/Viewport/Content"); if ((Object)(object)val == (Object)null) { textset = false; } else if (!textset) { ((Component)GUIManager.CustomGUIFront.transform.Find("CompatibilityWindow(Clone)/Scroll View")).GetComponent<ScrollRect>().scrollSensitivity = 1000f; ContentView = ((Component)val).gameObject; Transform val2 = ContentView.transform.Find("Failed Connection Text"); if ((Object)(object)val2 != (Object)null) { HeaderText = ((Component)val2).GetComponent<Text>(); } else { Logger.LogDebug("Could not find HeaderText"); } Transform val3 = ContentView.transform.Find("Error Messages Text"); if ((Object)(object)val3 != (Object)null) { FooterText = ((Component)val3).GetComponent<Text>(); } else { Logger.LogDebug("Could not find FooterText"); } if ((Object)(object)HeaderText != (Object)null && !string.IsNullOrEmpty(HeaderMessage)) { HeaderText.text = "<color=#FFA13C>Failed Connection:</color>\n" + HeaderMessage; } if ((Object)(object)FooterText != (Object)null && !string.IsNullOrEmpty(FooterMessage)) { FooterText.text = "<color=#FFA13C>Further Steps:</color>\n" + FooterMessage; } Logger.LogDebug("Set error results. H:" + HeaderMessage + " F:" + FooterMessage); textset = true; } } } internal static Dictionary<string, BaseUnityPlugin> ActiveMods = new Dictionary<string, BaseUnityPlugin>(); internal static DataObjects.Mods ModSettings { get; set; } internal static JotunnDetailDisconnectExpansion DetailsUpdater { get; set; } private static string ResolvePeerName(ZRpc rpc) { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(rpc) : null); if (!string.IsNullOrEmpty(val?.m_playerName)) { return val.m_playerName; } return null; } internal static void SetModsActive() { ActiveMods.Clear(); ActiveMods = BepInExUtils.GetPlugins(true); ModSettings = new DataObjects.Mods(); Logger.LogDebug($"Detected {ActiveMods.Keys.Count} mods."); LoadConfig(File.ReadAllText(ValConfig.ModsConfigFilePath)); ModSettings.ActiveMods.Clear(); foreach (KeyValuePair<string, BaseUnityPlugin> activeMod in ActiveMods) { if (!ModSettings.ActiveMods.ContainsKey(activeMod.Key)) { Logger.LogDebug("Adding Mod " + activeMod.Key + " not found in modlist"); ModSettings.ActiveMods.Add(activeMod.Key, new DataObjects.Mod { EnforceVersion = true, Version = activeMod.Value.Info.Metadata.Version.ToString(), PluginID = activeMod.Value.Info.Metadata.GUID, Name = activeMod.Value.Info.Metadata.Name }); } Logger.LogDebug($"Found active mod: {activeMod.Key} v{activeMod.Value.Info.Metadata.Version}"); string currentVersion = activeMod.Value.Info.Metadata.Version.ToString(); if (ModSettings.RequiredMods.ContainsKey(activeMod.Key)) { UpdateModVersionIfChanged(ModSettings.RequiredMods, activeMod.Key, currentVersion); } else if (ModSettings.AdminOnlyMods.ContainsKey(activeMod.Key)) { UpdateModVersionIfChanged(ModSettings.AdminOnlyMods, activeMod.Key, currentVersion); } else if (ModSettings.OptionalMods.ContainsKey(activeMod.Key)) { UpdateModVersionIfChanged(ModSettings.OptionalMods, activeMod.Key, currentVersion); } else if (ModSettings.ServerOnlyMods.ContainsKey(activeMod.Key)) { UpdateModVersionIfChanged(ModSettings.ServerOnlyMods, activeMod.Key, currentVersion); } else if (ValConfig.AutoAddModsToRequired.Value) { Logger.LogDebug("Automatically adding " + activeMod.Key + " as a required mod."); ModSettings.RequiredMods.Add(activeMod.Key, new DataObjects.Mod { EnforceVersion = false, Version = activeMod.Value.Info.Metadata.Version.ToString(), PluginID = activeMod.Value.Info.Metadata.GUID, Name = activeMod.Value.Info.Metadata.Name }); } } if (ValConfig.UpdateLoadedModsOnStartup.Value) { Logger.LogDebug("Updated Mods.yaml."); File.WriteAllText(ValConfig.ModsConfigFilePath, DataObjects.yamlserializer.Serialize((object)ModSettings)); } } private static void UpdateModVersionIfChanged(Dictionary<string, DataObjects.Mod> modList, string key, string currentVersion) { if (modList[key].Version != currentVersion) { Logger.LogInfo("Updating version for " + key + ": " + modList[key].Version + " -> " + currentVersion); modList[key].Version = currentVersion; } } internal static void UpdateModSettingConfigs(string yamlstring) { try { ModSettings = DataObjects.yamldeserializer.Deserialize<DataObjects.Mods>(yamlstring); } catch { Logger.LogWarning("Failed to deserialize mod configurations."); } } internal static bool ValidateModlist(DataObjects.Mods CheckingMods, DataObjects.Mods AuthoratativeMods, bool isAdmin, bool adminStatusKnown, out string summay, out string details) { summay = ""; details = ""; List<string> list = new List<string>(); List<string> list2 = new List<string>(); List<string> list3 = new List<string>(); List<string> list4 = new List<string>(); List<string> list5 = AuthoratativeMods.RequiredMods.Keys.Distinct().ToList(); Logger.LogDebug($"Validating modlist of {CheckingMods.ActiveMods.Count} mods isAdmin? {isAdmin}"); foreach (KeyValuePair<string, DataObjects.Mod> activeMod in CheckingMods.ActiveMods) { list5.Remove(activeMod.Key); if (AuthoratativeMods.RequiredMods.ContainsKey(activeMod.Key)) { if (!AuthoratativeMods.RequiredMods[activeMod.Key].EnforceVersion || AuthoratativeMods.RequiredMods[activeMod.Key].Version == activeMod.Value.Version) { continue; } list2.Add(activeMod.Key); } if (AuthoratativeMods.AdminOnlyMods.ContainsKey(activeMod.Key)) { if (!adminStatusKnown) { list4.Add(activeMod.Key); } else if (isAdmin) { if (AuthoratativeMods.AdminOnlyMods[activeMod.Key].EnforceVersion && AuthoratativeMods.AdminOnlyMods[activeMod.Key].Version != activeMod.Value.Version) { list2.Add(activeMod.Key); } } else { list3.Add(activeMod.Key); } continue; } if (AuthoratativeMods.OptionalMods.ContainsKey(activeMod.Key)) { if (!AuthoratativeMods.OptionalMods[activeMod.Key].EnforceVersion || AuthoratativeMods.OptionalMods[activeMod.Key].Version == activeMod.Value.Version) { continue; } list2.Add(activeMod.Key); } list.Add(activeMod.Key); } if (list2.Count > 0) { Logger.LogWarning("Mods version mismatch with the server found:"); summay = "A Mod mismatch was detected. Ensure you have the correct versions and are only using allowed mods."; } if (list5.Count > 0) { string text = "\nMissing required mods: " + string.Join(", ", list5); summay += text; Logger.LogWarning(text); } if (list.Count > 0) { string text2 = "\nNon-allowed mods found: " + string.Join(", ", list); summay += text2; Logger.LogWarning(text2); } if (list3.Count > 0) { string text3 = "\nAdmin-only mods not permitted for non-admins: " + string.Join(", ", list3); summay += text3; Logger.LogWarning(text3); } if (list4.Count > 0) { string text4 = "\nThis server restricts some mods to admins; if you are not an admin you will be disconnected: " + string.Join(", ", list4); summay += text4; Logger.LogInfo(text4); } if (list2.Count > 0 || list5.Count > 0 || list.Count > 0 || list3.Count > 0 || list4.Count > 0) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("\n<b>ValheimEnforcer - Mod Validation Failed</b>"); if (list2.Count > 0) { stringBuilder.AppendLine("\n<b>Version Mismatches:</b>"); foreach (string item in list2) { stringBuilder.AppendLine(" • " + item); } } if (list5.Count > 0) { stringBuilder.AppendLine("\n<b>Missing Required Mods:</b>"); foreach (string item2 in list5) { stringBuilder.AppendLine(" • " + item2); } } if (list.Count > 0) { stringBuilder.AppendLine("\n<b>Non-Allowed Mods:</b>"); foreach (string item3 in list) { stringBuilder.AppendLine(" • " + item3); } } if (list3.Count > 0) { stringBuilder.AppendLine("\n<b>Admin-Only Mods (not permitted):</b>"); foreach (string item4 in list3) { stringBuilder.AppendLine(" • " + item4); } } if (list4.Count > 0) { stringBuilder.AppendLine("\n<b>Admin-Only Mods (require admin):</b>"); foreach (string item5 in list4) { stringBuilder.AppendLine(" • " + item5); } } string text5 = stringBuilder.ToString(); details = text5; return false; } Logger.LogInfo("Client mod list validated successfully."); return true; } internal static void LoadConfig(string yaml) { ModSettings = DataObjects.yamldeserializer.Deserialize<DataObjects.Mods>(yaml); } internal static string GetDefaultConfig() { if (ModSettings != null) { return DataObjects.yamlserializer.Serialize((object)ModSettings); } return DataObjects.yamlserializer.Serialize((object)new DataObjects.Mods()); } private static void RPC_ReceiveModVersionData(ZRpc sender, ZPackage data) { Logger.LogDebug("Received mod version data from " + sender.m_socket.GetEndPointString()); string endPointString = sender.m_socket.GetEndPointString(); if (!ZNet.instance.IsServer()) { DataObjects.Mods mods = new DataObjects.Mods().FromZPackage(data); Logger.LogDebug($"Client received server mod data: Required: {mods.RequiredMods.Count}, Optional: {mods.OptionalMods.Count}, AdminOnly: {mods.AdminOnlyMods.Count} mods"); string summay; string details; bool num = ValidateModlist(ModSettings, mods, isAdmin: false, adminStatusKnown: false, out summay, out details); DetailsUpdater?.UpdateErrorText(summay, details); if (!num) { Logger.LogWarning("Mod compatibility check failed for client."); } return; } DataObjects.Mods mods2 = new DataObjects.Mods().FromZPackage(data); bool flag = ZNet.instance.IsAdmin(sender.m_socket.GetHostName()); Logger.LogDebug($"Server received server mod data from {endPointString} Admin?{flag}: Required: {mods2.RequiredMods.Count}, Optional: {mods2.OptionalMods.Count}, AdminOnly: {mods2.AdminOnlyMods.Count} mods"); if (!ValidateModlist(mods2, ModSettings, flag, adminStatusKnown: true, out var summay2, out var _)) { Logger.LogWarning("Mod compatibility check failed for client at " + endPointString + "\n" + summay2); if (ValConfig.DiscordNotifyWrongMods.Value) { string value = ResolvePeerName(sender) ?? endPointString; DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Connection Rejected: Mod Mismatch", summay2.Trim(), 15548997).AddField("Player", value, inline: true).ToMessage()); } sender.Invoke("Error", new object[1] { 3 }); } } internal static void AddErrorMessageDetailsForMenu() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name.Equals("start")) { DetailsUpdater = GUIManager.CustomGUIFront.AddComponent<JotunnDetailDisconnectExpansion>(); } } } } namespace ValheimEnforcer.modules.notifications { internal static class DiscordNotifier { private static readonly HttpClient http = new HttpClient { Timeout = TimeSpan.FromSeconds(10.0) }; internal static void Initialize() { string value = ValConfig.DiscordWebhookUrl.Value; if (string.IsNullOrWhiteSpace(value)) { Logger.LogInfo("Discord notifications: no webhook URL configured, disabled."); } else if (!IsValidWebhookUrl(value)) { Logger.LogWarning("Discord notifications: configured webhook URL is invalid, disabled. Expected https://discord.com/api/webhooks/..."); } else { Logger.LogInfo("Discord notifications enabled."); } } internal static bool IsValidWebhookUrl(string url) { if (string.IsNullOrWhiteSpace(url)) { return false; } if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } if (result.Scheme != "https") { return false; } if (result.Host == "discord.com" || result.Host == "discordapp.com" || result.Host == "ptb.discord.com" || result.Host == "canary.discord.com") { return result.AbsolutePath.StartsWith("/api/webhooks/", StringComparison.Ordinal); } return false; } private static bool IsActive(out string url) { url = ValConfig.DiscordWebhookUrl.Value; if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return IsValidWebhookUrl(url); } return false; } internal static void SendAsync(DataObjects.DiscordMessage message) { if (message != null && IsActive(out var url)) { Task.Run(() => Post(url, message)); } } internal static void SendSync(DataObjects.DiscordMessage message) { if (message == null || !IsActive(out var url)) { return; } try { Post(url, message).Wait(TimeSpan.FromSeconds(8.0)); } catch (Exception ex) { Logger.LogWarning("Discord notifications: synchronous send failed: " + ex.Message); } } private static async Task Post(string url, DataObjects.DiscordMessage message) { try { StringContent content = new StringContent(message.ToJson(), Encoding.UTF8, "application/json"); try { HttpResponseMessage val = await http.PostAsync(url, (HttpContent)(object)content).ConfigureAwait(continueOnCapturedContext: false); if (!val.IsSuccessStatusCode) { Logger.LogWarning($"Discord notifications: webhook returned HTTP {(int)val.StatusCode}."); } } finally { ((IDisposable)content)?.Dispose(); } } catch (Exception ex) { Logger.LogWarning("Discord notifications: send failed: " + ex.Message); } } } internal static class NotificationPatches { [HarmonyPatch(typeof(ZNet), "Start")] public static class ZNet_Start_Patch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { if (__instance.IsServer()) { AnnouncedPeers.Clear(); DiscordNotifier.Initialize(); if (ValConfig.DiscordNotifyServerStartup.Value) { DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Server Online", "The server has started and is accepting connections.", 5763719).ToMessage()); } } } } [HarmonyPatch(typeof(ZNet), "Shutdown")] public static class ZNet_Shutdown_Patch { [HarmonyPrefix] private static void Prefix(ZNet __instance) { if (__instance.IsServer() && ValConfig.DiscordNotifyServerShutdown.Value) { DiscordNotifier.SendSync(new DataObjects.DiscordEmbed("Server Offline", "The server is shutting down.", 9807270).ToMessage()); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] public static class ZNet_RPC_PeerInfo_Patch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZRpc rpc) { if (__instance.IsServer()) { ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null && !string.IsNullOrEmpty(peer.m_playerName) && AnnouncedPeers.Add(peer.m_uid) && ValConfig.DiscordNotifyPlayerJoined.Value) { DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Player Joined", null, 5763719).AddField("Player", peer.m_playerName, inline: true).ToMessage()); } } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] public static class ZNet_Disconnect_Patch { [HarmonyPrefix] private static void Prefix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer() && peer != null && AnnouncedPeers.Remove(peer.m_uid) && ValConfig.DiscordNotifyPlayerLeft.Value) { DataObjects.DisconnectionState num = ResolveSavedDataState(peer); int value = ValConfig.DeltaSynchronizationFrequencyInSeconds.Value; bool flag = num == DataObjects.DisconnectionState.Clean; string value2 = (flag ? "Clean logout" : "Disconnected"); string value3 = (flag ? "✅ Player Data up to date." : $"⚠\ufe0f Stale — Data outdated by {value}s"); DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Player Left", null, flag ? 5763719 : 16705372).AddField("Player", peer.m_playerName, inline: true).AddField("Disconnect", value2, inline: true).AddField("Saved data", value3) .ToMessage()); } } } private static readonly HashSet<long> AnnouncedPeers = new HashSet<long>(); private static DataObjects.DisconnectionState ResolveSavedDataState(ZNetPeer peer) { try { string text = peer.m_socket.GetHostName(); if (!string.IsNullOrEmpty(text) && text.Contains(":")) { text = text.Split(new char[1] { ':' })[0]; } DataObjects.Character character = ValConfig.LoadCharacterFromSave(text, peer.m_playerName); if (character == null) { Logger.LogDebug("Discord notifications: no saved character for " + peer.m_playerName + " (" + text + "); reporting saved data as stale."); return DataObjects.DisconnectionState.DirtyDisconnect; } return character.LastDisconnect; } catch (Exception ex) { Logger.LogDebug("Discord notifications: failed to resolve saved-data state for " + peer.m_playerName + ": " + ex.Message); return DataObjects.DisconnectionState.DirtyDisconnect; } } } } namespace ValheimEnforcer.modules.compat { internal class ModCompatability { public static bool IsExtraSlotsEnabled; public static bool IsTrialsOfToilEnabled; internal static void CheckModCompat() { try { Dictionary<string, BaseUnityPlugin> plugins = BepInExUtils.GetPlugins(false); if (plugins != null) { if (Enumerable.Contains(plugins.Keys, "shudnal.ExtraSlots")) { IsExtraSlotsEnabled = API.IsReady(); } if (Enumerable.Contains(plugins.Keys, "maxfoxgaming.environmentalawareness")) { IsTrialsOfToilEnabled = true; } } } catch { Logger.LogWarning("Unable to check mod compatibility. Ensure that Bepinex can load."); } } } } namespace ValheimEnforcer.modules.compat.ExtraSlots { internal static class Extensions { internal static ExtraSlot ToExtraSlot(this object slot) { return new ExtraSlot { _id = () => (string)AccessTools.Property(API._typeSlot, "ID").GetValue(slot), _name = () => (string)AccessTools.Property(API._typeSlot, "Name").GetValue(slot), _gridPosition = () => (Vector2i)AccessTools.Property(API._typeSlot, "GridPosition").GetValue(slot), _item = () => (ItemData)AccessTools.Property(API._typeSlot, "Item").GetValue(slot), _itemFits = (ItemData item) => (bool)AccessTools.Method(API._typeSlot, "ItemFits", (Type[])null, (Type[])null).Invoke(slot, new object[1] { item }), _isActive = () => (bool)AccessTools.Property(API._typeSlot, "IsActive").GetValue(slot), _isFree = () => (bool)AccessTools.Property(API._typeSlot, "IsFree").GetValue(slot), _isHotkeySlot = () => (bool)AccessTools.Property(API._typeSlot, "IsHotkeySlot").GetValue(slot), _isEquipmentSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsEquipmentSlot").GetValue(slot), _isQuickSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsQuickSlot").GetValue(slot), _isMiscSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsMiscSlot").GetValue(slot), _isAmmoSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsAmmoSlot").GetValue(slot), _isFoodSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsFoodSlot").GetValue(slot), _isCustomSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsCustomSlot").GetValue(slot), _isEmptySlot = () => (bool)AccessTools.Property(API._typeSlot, "IsEmptySlot").GetValue(slot) }; } } public class ExtraSlot { internal Func<string> _id; internal Func<string> _name; internal Func<Vector2i> _gridPosition; internal Func<ItemData> _item; internal Func<ItemData, bool> _itemFits; internal Func<bool> _isActive; internal Func<bool> _isFree; internal Func<bool> _isHotkeySlot; internal Func<bool> _isEquipmentSlot; internal Func<bool> _isQuickSlot; internal Func<bool> _isMiscSlot; internal Func<bool> _isAmmoSlot; internal Func<bool> _isFoodSlot; internal Func<bool> _isCustomSlot; internal Func<bool> _isEmptySlot; public static readonly Vector2i emptyPosition = new Vector2i(-1, -1); public string ID { get { if (_id == null) { return ""; } return _id(); } } public string Name { get { if (_name == null) { return ""; } return _name(); } } public Vector2i GridPosition { get { //IL_0014: 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) if (_gridPosition == null) { return emptyPosition; } return _gridPosition(); } } public ItemData Item { get { if (_item == null) { return null; } return _item(); } } public bool IsActive { get { if (_isActive != null) { return _isActive(); } return false; } } public bool IsFree { get { if (_isFree != null) { return _isFree(); } return false; } } public bool IsHotkeySlot { get { if (_isHotkeySlot != null) { return _isHotkeySlot(); } return false; } } public bool IsEquipmentSlot { get { if (_isEquipmentSlot != null) { return _isEquipmentSlot(); } return false; } } public bool IsQuickSlot { get { if (_isQuickSlot != null) { return _isQuickSlot(); } return false; } } public bool IsMiscSlot { get { if (_isMiscSlot != null) { return _isMiscSlot(); } return false; } } public bool IsAmmoSlot { get { if (_isAmmoSlot != null) { return _isAmmoSlot(); } return false; } } public bool IsFoodSlot { get { if (_isFoodSlot != null) { return _isFoodSlot(); } return false; } } public bool IsCustomSlot { get { if (_isCustomSlot != null) { return _isCustomSlot(); } return false; } } public bool IsEmptySlot { get { if (_isEmptySlot != null) { return _isEmptySlot(); } return false; } } public bool ItemFits(ItemData item) { if (_itemFits != null) { return _itemFits(item); } return false; } } public static class API { private static bool _isNotReady; private static readonly List<ItemData> _emptyItemList = new List<ItemData>(); private static readonly List<ExtraSlot> _emptySlotList = new List<ExtraSlot>(); internal static Type _typeAPI; internal static Type _typeSlot; public static bool IsReady() { if (_isNotReady) { return false; } if (_typeAPI != null && _typeSlot != null) { return true; } _isNotReady = !Chainloader.PluginInfos.ContainsKey("shudnal.ExtraSlots"); if (_isNotReady) { return false; } if (_typeAPI == null || _typeSlot == null) { Assembly assembly = Assembly.GetAssembly(((object)Chainloader.PluginInfos["shudnal.ExtraSlots"].Instance).GetType()); if (assembly == null) { _isNotReady = true; return false; } _typeAPI = assembly.GetType("ExtraSlots.API"); _typeSlot = assembly.GetType("ExtraSlots.Slots+Slot"); } if (_typeAPI != null) { return _typeSlot != null; } return false; } public static List<ExtraSlot> GetExtraSlots() { if (IsReady()) { return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetExtraSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List<ExtraSlot> GetEquipmentSlots() { if (IsReady()) { return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetEquipmentSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List<ExtraSlot> GetQuickSlots() { if (IsReady()) { return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetQuickSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List<ExtraSlot> GetFoodSlots() { if (IsReady()) { return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetFoodSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List<ExtraSlot> GetAmmoSlots() { if (IsReady()) { return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetAmmoSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List<ExtraSlot> GetMiscSlots() { if (IsReady()) { return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetMiscSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static ExtraSlot FindSlot(string slotID) { if (!IsReady()) { return null; } return AccessTools.Method(_typeAPI, "FindSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { slotID }).ToExtraSlot(); } public static List<ItemData> GetAllExtraSlotsItems() { if (IsReady()) { return (List<ItemData>)AccessTools.Method(_typeAPI, "GetAllExtraSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List<ItemData> GetEquipmentSlotsItems() { if (IsReady()) { return (List<ItemData>)AccessTools.Method(_typeAPI, "GetEquipmentSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List<ItemData> GetQuickSlotsItems() { if (IsReady()) { return (List<ItemData>)AccessTools.Method(_typeAPI, "GetQuickSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List<ItemData> GetFoodSlotsItems() { if (IsReady()) { return (List<ItemData>)AccessTools.Method(_typeAPI, "GetFoodSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List<ItemData> GetAmmoSlotsItems() { if (IsReady()) { return (List<ItemData>)AccessTools.Method(_typeAPI, "GetAmmoSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List<ItemData> GetMiscSlotsItems() { if (IsReady()) { return (List<ItemData>)AccessTools.Method(_typeAPI, "GetMiscSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static int GetExtraRows() { if (IsReady()) { return (int)AccessTools.Method(_typeAPI, "GetExtraRows", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return -1; } public static int GetInventoryHeightFull() { if (IsReady()) { return (int)AccessTools.Method(_typeAPI, "GetInventoryHeightFull", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return -1; } public static int GetInventoryHeightPlayer() { if (IsReady()) { return (int)AccessTools.Method(_typeAPI, "GetInventoryHeightPlayer", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return -1; } public static bool IsGridPositionASlot(Vector2i gridPos) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "IsGridPositionASlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { gridPos }); } public static bool IsItemInSlot(ItemData item) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "IsItemInSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { item }); } public static bool IsItemInEquipmentSlot(ItemData item) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "IsItemInEquipmentSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { item }); } public static bool IsAnyGlobalKeyActive(string requiredKeys) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "requiredKeys", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { requiredKeys }); } public static bool IsItemTypeKnown(ItemType itemType) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "IsItemTypeKnown", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { itemType }); } public static bool IsAnyMaterialDiscovered(string itemNames) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "IsAnyMaterialDiscovered", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { itemNames }); } public static bool AddSlot(string slotID, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "AddSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, -1, getName, itemIsValid, isActive }); } public static bool AddSlotWithIndex(string slotID, int slotIndex, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "AddSlotWithIndex", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, slotIndex, getName, itemIsValid, isActive }); } public static bool AddSlotBefore(string slotID, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive, params string[] slotIDs) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "AddSlotBefore", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, getName, itemIsValid, isActive, slotIDs }); } public static bool AddSlotAfter(string slotID, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive, params string[] slotIDs) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "AddSlotAfter", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, getName, itemIsValid, isActive, slotIDs }); } public static bool RemoveSlot(string slotID) { if (!IsReady()) { return false; } return (bool)AccessTools.Method(_typeAPI, "RemoveSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { slotID }); } public static void UpdateSlots() { if (IsReady()) { AccessTools.Method(_typeAPI, "UpdateSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } } } } namespace ValheimEnforcer.modules.commands { internal static class CommandHelpers { public static void ClearSpecifiedPlayerConfiscatedItems(string account, string username, string prefab) { DataObjects.Character character = ValConfig.LoadCharacterFromSave(account, username); Logger.LogInfo($"Found {character.ConfiscatedItems.Count} confiscated items."); if (string.Compare(prefab, "all", ignoreCase: true) == 0) { character.ConfiscatedItems.Clear(); ValConfig.WritePlayerCharacterToSave(account, character); Logger.LogInfo("Cleared all confiscated items."); return; } List<string> targetItems = prefab.Split(new char[1] { ',' }).ToList(); character.ConfiscatedItems = character.ConfiscatedItems.Where((DataObjects.PackedItem x) => !targetItems.Contains(x.prefabName)).ToList(); Logger.LogInfo("Removed confiscated item with prefab " + string.Join(",", targetItems) + "."); } public static List<DataObjects.PackedItem> LoadCharacterAndFindItemsToReturn(string account, string username, string prefabfilter) { DataObjects.Character character = ValConfig.LoadCharacterFromSave(account, username); List<DataObjects.PackedItem> result = new List<DataObjects.PackedItem>(); if (character == null) { Logger.LogInfo("Character was not found for the specified account."); return result; } if (character.ConfiscatedItems.Count == 0) { Logger.LogInfo("Player does not have any confiscated items."); return result; } if (string.Compare(prefabfilter, "all", ignoreCase: true) == 0) { result = new List<DataObjects.PackedItem>(character.ConfiscatedItems); character.ConfiscatedItems.Clear(); } else { List<string> targetPrefabs = (from s in prefabfilter.Split(new char[1] { ',' }) select s.Trim()).ToList(); result = character.ConfiscatedItems.Where((DataObjects.PackedItem i) => targetPrefabs.Contains(i.prefabName)).ToList(); character.ConfiscatedItems.RemoveAll((DataObjects.PackedItem i) => targetPrefabs.Contains(i.prefabName)); } if (result.Count == 0) { Logger.LogInfo("No matching confiscated items found for the specified filter."); return result; } return result; } } internal static class TerminalCommands { internal class ListPlayers : ConsoleCommand { public override string Name => "Enforcer-List-Players"; public override bool IsCheat => true; public override string Help => "Enforcer-List-Players - Provides a full list of all accounts and Player names stored."; public override void Run(string[] args) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown if (ZNet.instance.IsCurrentServerDedicated()) { ValConfig.ListPlayerRPC.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), new ZPackage()); Logger.LogInfo("Requesting player list from server..."); return; } if (ValConfig.InternalStorageMode.Value) { foreach (KeyValuePair<string, List<string>> item in InternalDataStore.GetAccountRegistry()) { Logger.LogInfo("Account:" + item.Key); foreach (string item2 in item.Value) { Logger.LogInfo(" " + item2); } } return; } foreach (string item3 in Directory.GetFiles(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters")).ToList()) { List<string> list = Directory.GetFiles(item3).ToList(); Logger.LogInfo("Account:" + item3.Split(new char[1] { '/' }).Last()); foreach (string item4 in list) { Logger.LogInfo(" " + item4.Split(new char[1] { '/' }).Last()); } } } } internal class ListPlayerConfiscatedItems : ConsoleCommand { public override string Name => "Enforcer-List-Confiscated"; public override bool IsCheat => true; public override string Help => "Gets a list of confiscated items, specific to a player/character. Format: enforcer-list-confiscated 99999999 TerryTheTerrible"; public override void Run(string[] args) { if (args.Length != 2) { Logger.LogInfo("Account ID and playername are required. Ensure your command follows the format: enforcer-list-confiscated 99999999 TerryTheTerrible"); return; } string id = args[0]; string name = args[1]; DataObjects.Character character = ValConfig.LoadCharacterFromSave(id, name); if (character.ConfiscatedItems.Count == 0) { Logger.LogInfo("Player does not have any confiscated items."); return; } Logger.LogInfo($"Found {character.ConfiscatedItems.Count} confiscated items."); foreach (DataObjects.PackedItem confiscatedItem in character.ConfiscatedItems) { Logger.LogInfo($" {confiscatedItem.prefabName} x {confiscatedItem.m_stack}"); } } } internal class ClearPlayerConfiscatedItems : ConsoleCommand { public override string Name => "Enforcer-Clear-Confiscated"; public override bool IsCheat => true; public override string Help => "Clears any confiscated items listed for the specified player Format: enforcer-retrieve-confiscated 99999999 TerryTheTerrible all"; public override void Run(string[] args) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown if (args.Length != 3) { Logger.LogInfo("Account ID and playername are required. Ensure your command follows the format: enforcer-retrieve-confiscated 99999999 TerryTheTerrible all"); return; } string text = args[0]; string text2 = args[1]; string text3 = args[2]; if (ZNet.instance.IsCurrentServerDedicated()) { ZPackage val = new ZPackage(); DataObjects.RPCServerUpdateData rPCServerUpdateData = new DataObjects.RPCServerUpdateData(); rPCServerUpdateData.ItemPrefabFilter = text3; rPCServerUpdateData.PlatformID = text; rPCServerUpdateData.PlayerName = text2; val.Write(DataObjects.yamlserializer.Serialize((object)rPCServerUpdateData)); ValConfig.ClearConfiscatedRPC.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), val); Logger.LogInfo("Sending command to clear confiscated items on server..."); } else { CommandHelpers.ClearSpecifiedPlayerConfiscatedItems(text, text2, text3); } } } internal class RestorePlayerConfiscatedItems : ConsoleCommand { public override string Name => "Enforcer-Admin-Take-Confiscated"; public override bool IsCheat => true; public override string Help => "Gives you player confiscated items, use either item prefab or 'all'. Format: enforcer-admin-take-confiscated 99999999 TerryTheTerrible all"; public override void Run(string[] args) { if (args.Length != 3) { Logger.LogInfo("Account ID and playername are required. Ensure your command follows the format: enforcer-admin-take-confiscated 99999999 TerryTheTerrible all"); return; } string id = args[0]; string name = args[1]; string text = args[2]; DataObjects.Character character = ValConfig.LoadCharacterFromSave(id, name); if (character == null) { Logger.LogInfo("Character was not found for the specified account."); return; } if (character.ConfiscatedItems.Count == 0) { Logger.LogInfo("Player does not have any confiscated items."); return; } Logger.LogInfo($"Found {character.ConfiscatedItems.Count} confiscated items."); if (string.Compare(text, "all", ignoreCase: true) == 0) { Logger.LogInfo("Providing all confiscated items."); foreach (DataObjects.PackedItem confiscatedItem in character.ConfiscatedItems) { confiscatedItem.AddToInventory(Player.m_localPlayer, use_position: false); } character.ConfiscatedItems.Clear(); return; } foreach (DataObjects.PackedItem confiscatedItem2 in character.ConfiscatedItems) { _ = confiscatedItem2; List<string> list = text.Split(new char[1] { ',' }).ToList(); foreach (DataObjects.PackedItem confiscatedItem3 in character.ConfiscatedItems) { if (list.Contains(confiscatedItem3.prefabName)) { Logger.LogInfo("Providing " + confiscatedItem3.prefabName); confiscatedItem3.AddToInventory(Player.m_localPlayer, use_position: false); } } foreach (string target in list) { character.ConfiscatedItems.RemoveAll((DataObjects.PackedItem i) => i.prefabName == target); } } ValConfig.WritePlayerCharacterToSave(character.HostID, character); } } internal class ReturnPlayerConfiscatedItems : ConsoleCommand { public override string Name => "Enforcer-Return-Confiscated"; public override bool IsCheat => true; public override string Help => "Sends confiscated items to a connected player via RPC. Use 'all' or comma-separated prefab names. Format: Enforcer-Return-Confiscated 99999999 TerryTheTerrible all"; public override void Run(string[] args) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown if (args.Length != 3) { Logger.LogInfo("Account ID, player name, and item filter are required. Ensure your command follows the format: Enforcer-Return-Confiscated 99999999 TerryTheTerrible all"); return; } string text = args[0]; string text2 = args[1]; string text3 = args[2]; if ((Object)(object)Player.m_localPlayer != (Object)null && Player.m_localPlayer.GetPlayerName() == text2 && !ZNet.instance.IsCurrentServerDedicated()) { Logger.LogInfo("Local player is the target, returning player items."); List<DataObjects.PackedItem> list = CommandHelpers.LoadCharacterAndFindItemsToReturn(text, text2, text3); DataObjects.Character character = ValConfig.LoadCharacterFromSave(text, text2); foreach (DataObjects.PackedItem item in list) { Logger.LogInfo("Providing " + item.prefabName); item.AddToInventory(Player.m_localPlayer, use_position: false); } ValConfig.WritePlayerCharacterToSave(text, character); } else { ZPackage val = new ZPackage(); DataObjects.RPCServerUpdateData rPCServerUpdateData = new DataObjects.RPCServerUpdateData(); rPCServerUpdateData.PlatformID = text; rPCServerUpdateData.ItemPrefabFilter = text3; rPCServerUpdateData.PlayerName = text2; val.Write(DataObjects.yamlserializer.Serialize((object)rPCServerUpdateData)); ValConfig.ReturnConfiscatedItemsRPC.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), val); } } } internal static void AddCommands() { CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ListPlayers()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ClearPlayerConfiscatedItems()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ReturnPlayerConfiscatedItems()); } } } namespace ValheimEnforcer.modules.cheatmonitor { internal static class CheatDetector { private static class NativeWin32 { public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("kernel32.dll")] public static extern bool IsDebuggerPresent(); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] public static extern bool CheckRemoteDebuggerPresent(IntPtr hProcess, ref bool isDebuggerPresent); } internal class CheatDetectorBehaviour : MonoBehaviour { private float nextScan; private void Start() { } private void Update() { if (ValConfig.EnableCheatDetection.Value && !(Time.unscaledTime < nextScan)) { nextScan = Time.unscaledTime + (float)Mathf.Max(1, ValConfig.CheatScanIntervalSeconds.Value); RunScan(); } } private static void RunScan() { if (CharacterManager.PlayerCharacter != null) { DataObjects.CheatSummaryReport cheatSummaryReport = new DataObjects.CheatSummaryReport { PlayerName = CharacterManager.PlayerCharacter.Name, PlatformID = CharacterManager.PlayerCharacter.HostID, CheatEngineStatus = new DataObjects.CheatEngineDetector() }; if (ValConfig.DetectValheimTooler.Value && ValheimToolerLoaded()) { cheatSummaryReport.ValheimToolerStatus = true; } if (ValConfig.DetectCheatEngine.Value && CheatEngineProcessRunning()) { cheatSummaryReport.CheatEngineStatus.CheatEngineProcessDetected = true; } if (cheatSummaryReport.cheatsDetected()) { ReportCheatScanSummary(cheatSummaryReport); } } } } private static readonly string[] ToolerAssemblyNames = new string[3] { "ValheimTooler", "ValheimToolerMod", "RapidGUI" }; private static readonly string[] CheatEngineProcessNames = new string[4] { "cheatengine-x86_64", "cheatengine-i386", "cheatengine-x86_64-sse4-avx2", "cheatengine" }; internal static void Initialize() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) if ((!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsDedicated()) && ValConfig.EnableCheatDetection.Value) { GameObject val = new GameObject("VE_CheatDetector"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent<CheatDetectorBehaviour>(); Logger.LogDebug("CheatDetector initialized."); } } internal static bool ValheimToolerLoaded() { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string n = assembly.GetName().Name ?? ""; if (ToolerAssemblyNames.Any((string t) => t.Equals(n, StringComparison.OrdinalIgnoreCase