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 list = CharacterDeltaTracker.BuildCharacterItemDeltas(); Dictionary customData = Player.m_localPlayer.m_customData; Dictionary dictionary = new Dictionary(); List list2 = new List(); foreach (KeyValuePair 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 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 list3 = new List(); foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems()) { list3.Add(CharacterDeltaTracker.BuildPackedItem(allItem)); } CharacterManager.PlayerCharacter.PlayerItems = list3; CharacterManager.PlayerCharacter.PlayerCustomData = customData; Dictionary dictionary2 = new Dictionary(); 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 EnableDebugMode; public static ConfigEntry UpdateLoadedModsOnStartup; public static ConfigEntry AutoAddModsToRequired; public static ConfigEntry RemoveNontrackedItemsFromJoiningPlayers; public static ConfigEntry AddMissingItemsFromPlayerServerSave; public static ConfigEntry PreventExternalSkillRaises; public static ConfigEntry NewCharactersRemoveExtraItems; public static ConfigEntry NewCharacterSetSkillsToZero; public static ConfigEntry newCharacterClearCustomData; public static ConfigEntry PreventExternalCustomDataChanges; public static ConfigEntry ValidateItemCustomData; public static ConfigEntry ValidateItemDurability; public static ConfigEntry ItemValidationDurabilityAllowedVariance; public static ConfigEntry SavePlayerStatusEffectsOnLogout; public static ConfigEntry ItemRemovalForDirtyReconnection; public static ConfigEntry ItemReturnForDirtyReconnection; public static ConfigEntry InternalStorageMode; public static ConfigEntry ConfigPollIntervalSeconds; public static ConfigEntry DeltaSynchronizationFrequencyInSeconds; public static ConfigEntry FullSaveDataSynchronizationFrequencyInSeconds; public static ConfigEntry EnableCheatDetection; public static ConfigEntry DetectCheatEngine; public static ConfigEntry DetectValheimTooler; public static ConfigEntry CheatDetectionAction; public static ConfigEntry CheatScanIntervalSeconds; public static ConfigEntry DiscordWebhookUrl; public static ConfigEntry DiscordNotifyServerStartup; public static ConfigEntry DiscordNotifyServerShutdown; public static ConfigEntry DiscordNotifyPlayerJoined; public static ConfigEntry DiscordNotifyPlayerLeft; public static ConfigEntry 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)SendSavedCharacter); LoadYamlConfigs(new Dictionary> { { 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("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(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(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> configFilesToFind) { string[] files = Directory.GetFiles(GetSecondaryConfigDirectoryPath()); List list = new List(); List 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> 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(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(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(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(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(package.ReadString()); List 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(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 list = new List(); 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> item in DataObjects.yamldeserializer.Deserialize>>(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> dictionary = new Dictionary>(); 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 list = Directory.GetFiles(item).ToList(); string key = item.Split(new char[1] { '/' }).Last(); List list2 = new List(); 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 list = DataObjects.yamldeserializer.Deserialize>(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(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(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 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 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(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = false, IsAdvanced = advanced } })); } public static ConfigEntry 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(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = false, IsAdvanced = advanced } })); } public static ConfigEntry> BindServerConfig(string catagory, string key, List 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>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry 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(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry 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(catagory, key, value, new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry 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(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry 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(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry BindServerConfig(string catagory, string key, string value, string description, AcceptableValueList 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(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(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 { { 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(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(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("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> dictionary = DataObjects.yamldeserializer.Deserialize>>(text); if (!dictionary.ContainsKey(accountID)) { if (chara != null) { dictionary[accountID] = new List { chara }; } else { dictionary[accountID] = new List(); } string text2 = DataObjects.yamlserializer.Serialize((object)dictionary); MetadataRegistry.Set("VE_ACCOUNTS", text2); } } else { List list = new List(); if (chara != null) { list.Add(chara); } Dictionary> dictionary2 = new Dictionary> { { accountID, list } }; string text3 = DataObjects.yamlserializer.Serialize((object)dictionary2); MetadataRegistry.Set("VE_ACCOUNTS", text3); } } internal static Dictionary> GetAccountRegistry() { InstanciateOrLinkMetadataRegistry(); string text = MetadataRegistry.GetString("VE_ACCOUNTS", (string)null); if (text != null) { return DataObjects.yamldeserializer.Deserialize>>(text); } return new Dictionary>(); } } 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("RPC_ReceiveModVersionData", (Action)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().scrollSensitivity = 1000f; ContentView = ((Component)val).gameObject; Transform val2 = ContentView.transform.Find("Failed Connection Text"); if ((Object)(object)val2 != (Object)null) { HeaderText = ((Component)val2).GetComponent(); } 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(); } else { Logger.LogDebug("Could not find FooterText"); } if ((Object)(object)HeaderText != (Object)null && !string.IsNullOrEmpty(HeaderMessage)) { HeaderText.text = "Failed Connection:\n" + HeaderMessage; } if ((Object)(object)FooterText != (Object)null && !string.IsNullOrEmpty(FooterMessage)) { FooterText.text = "Further Steps:\n" + FooterMessage; } Logger.LogDebug("Set error results. H:" + HeaderMessage + " F:" + FooterMessage); textset = true; } } } internal static Dictionary ActiveMods = new Dictionary(); 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 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 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(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 list = new List(); List list2 = new List(); List list3 = new List(); List list4 = new List(); List list5 = AuthoratativeMods.RequiredMods.Keys.Distinct().ToList(); Logger.LogDebug($"Validating modlist of {CheckingMods.ActiveMods.Count} mods isAdmin? {isAdmin}"); foreach (KeyValuePair 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("\nValheimEnforcer - Mod Validation Failed"); if (list2.Count > 0) { stringBuilder.AppendLine("\nVersion Mismatches:"); foreach (string item in list2) { stringBuilder.AppendLine(" • " + item); } } if (list5.Count > 0) { stringBuilder.AppendLine("\nMissing Required Mods:"); foreach (string item2 in list5) { stringBuilder.AppendLine(" • " + item2); } } if (list.Count > 0) { stringBuilder.AppendLine("\nNon-Allowed Mods:"); foreach (string item3 in list) { stringBuilder.AppendLine(" • " + item3); } } if (list3.Count > 0) { stringBuilder.AppendLine("\nAdmin-Only Mods (not permitted):"); foreach (string item4 in list3) { stringBuilder.AppendLine(" • " + item4); } } if (list4.Count > 0) { stringBuilder.AppendLine("\nAdmin-Only Mods (require admin):"); 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(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(); } } } } 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 AnnouncedPeers = new HashSet(); 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 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 _id; internal Func _name; internal Func _gridPosition; internal Func _item; internal Func _itemFits; internal Func _isActive; internal Func _isFree; internal Func _isHotkeySlot; internal Func _isEquipmentSlot; internal Func _isQuickSlot; internal Func _isMiscSlot; internal Func _isAmmoSlot; internal Func _isFoodSlot; internal Func _isCustomSlot; internal Func _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 _emptyItemList = new List(); private static readonly List _emptySlotList = new List(); 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 GetExtraSlots() { if (IsReady()) { return ((IEnumerable)AccessTools.Method(_typeAPI, "GetExtraSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List GetEquipmentSlots() { if (IsReady()) { return ((IEnumerable)AccessTools.Method(_typeAPI, "GetEquipmentSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List GetQuickSlots() { if (IsReady()) { return ((IEnumerable)AccessTools.Method(_typeAPI, "GetQuickSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List GetFoodSlots() { if (IsReady()) { return ((IEnumerable)AccessTools.Method(_typeAPI, "GetFoodSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List GetAmmoSlots() { if (IsReady()) { return ((IEnumerable)AccessTools.Method(_typeAPI, "GetAmmoSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList(); } return _emptySlotList; } public static List GetMiscSlots() { if (IsReady()) { return ((IEnumerable)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 GetAllExtraSlotsItems() { if (IsReady()) { return (List)AccessTools.Method(_typeAPI, "GetAllExtraSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List GetEquipmentSlotsItems() { if (IsReady()) { return (List)AccessTools.Method(_typeAPI, "GetEquipmentSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List GetQuickSlotsItems() { if (IsReady()) { return (List)AccessTools.Method(_typeAPI, "GetQuickSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List GetFoodSlotsItems() { if (IsReady()) { return (List)AccessTools.Method(_typeAPI, "GetFoodSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List GetAmmoSlotsItems() { if (IsReady()) { return (List)AccessTools.Method(_typeAPI, "GetAmmoSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null); } return _emptyItemList; } public static List GetMiscSlotsItems() { if (IsReady()) { return (List)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 getName, Func itemIsValid, Func 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 getName, Func itemIsValid, Func 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 getName, Func itemIsValid, Func 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 getName, Func itemIsValid, Func 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 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 LoadCharacterAndFindItemsToReturn(string account, string username, string prefabfilter) { DataObjects.Character character = ValConfig.LoadCharacterFromSave(account, username); List result = new List(); 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(character.ConfiscatedItems); character.ConfiscatedItems.Clear(); } else { List 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> 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 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 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 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(); 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))) { return true; } } } catch (Exception ex) { Logger.LogDebug("CheatDetector.ValheimToolerLoaded failed: " + ex.Message); } return false; } internal static bool CheatEngineProcessRunning() { try { Process[] processes = Process.GetProcesses(); foreach (Process process in processes) { string pn = process.ProcessName ?? ""; if (CheatEngineProcessNames.Any((string n) => pn.IndexOf(n, StringComparison.OrdinalIgnoreCase) >= 0)) { return true; } } } catch (Exception ex) { Logger.LogDebug("CheatDetector.CheatEngineProcessRunning failed: " + ex.Message); } return false; } internal static void ReportCheatScanSummary(DataObjects.CheatSummaryReport report) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown try { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetServerPeer() != null && ValConfig.CheatDetectionRPC != null) { string text = DataObjects.yamlserializer.Serialize((object)report); ZPackage val = new ZPackage(); val.Write(text); ValConfig.CheatDetectionRPC.SendPackage(ZNet.instance.GetServerPeer().m_uid, val); } } catch (Exception ex) { Logger.LogDebug("CheatDetector.ReportCheatScanSummary failed: " + ex.Message); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class ZNet_RPC_PeerInfo_BanCheck { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(ZNet __instance, ZRpc rpc) { if (!__instance.IsServer()) { return true; } ISocket socket = rpc.GetSocket(); string text = ((socket != null) ? socket.GetHostName() : null); if (string.IsNullOrEmpty(text) || !KnownCheaterTracker.IsListed(text)) { return true; } Logger.LogWarning("Rejecting known cheater " + text + ": " + KnownCheaterTracker.GetReason(text)); rpc.Invoke("Error", new object[1] { 8 }); return false; } } internal static class KnownCheaterTracker { private const string EmbeddedResourceName = "ValheimEnforcer.assets.KnownCheaters.yaml"; private static readonly Dictionary Cheaters = new Dictionary(); internal static void Initialize() { Cheaters.Clear(); foreach (DataObjects.KnownCheaterEntry item in ReadEmbeddedSeed()) { Upsert(item); } string knownCheatersFilePath = ValConfig.KnownCheatersFilePath; if (File.Exists(knownCheatersFilePath)) { try { foreach (DataObjects.KnownCheaterEntry item2 in Parse(File.ReadAllText(knownCheatersFilePath))) { Upsert(item2); } } catch (Exception ex) { Logger.LogWarning("Failed to read KnownCheaters file at " + knownCheatersFilePath + ": " + ex.Message); } } SaveToDisk(); Logger.LogDebug($"KnownCheaterTracker initialized with {Cheaters.Count} entr(ies)."); } internal static void LoadFromText(string yaml) { Cheaters.Clear(); foreach (DataObjects.KnownCheaterEntry item in ReadEmbeddedSeed()) { Upsert(item); } try { foreach (DataObjects.KnownCheaterEntry item2 in Parse(yaml)) { Upsert(item2); } } catch (Exception ex) { Logger.LogWarning("Failed to parse KnownCheaters update: " + ex.Message); } Logger.LogInfo($"KnownCheaters list reloaded ({Cheaters.Count} entries)."); } internal static void AddCheater(string id, string reason) { if (!string.IsNullOrEmpty(id) && !Cheaters.ContainsKey(id)) { Cheaters[id] = reason ?? ""; Logger.LogInfo("Added " + id + " to the known cheaters list (" + reason + ")."); SaveToDisk(); } } internal static bool IsListed(string hostId) { return FindMatchKey(hostId) != null; } internal static string GetReason(string hostId) { string text = FindMatchKey(hostId); if (text == null) { return null; } return Cheaters[text]; } private static string FindMatchKey(string hostId) { if (string.IsNullOrEmpty(hostId)) { return null; } if (Cheaters.ContainsKey(hostId)) { return hostId; } string text = Normalize(hostId); foreach (string key in Cheaters.Keys) { if (Normalize(key) == text) { return key; } } return null; } private static string Normalize(string id) { if (string.IsNullOrEmpty(id)) { return id; } int num = id.LastIndexOf('_'); if (num < 0 || num >= id.Length - 1) { return id; } return id.Substring(num + 1); } private static void Upsert(DataObjects.KnownCheaterEntry entry) { if (entry != null && !string.IsNullOrEmpty(entry.Id)) { Cheaters[entry.Id] = entry.Reason ?? ""; } } private static List Parse(string yaml) { if (string.IsNullOrWhiteSpace(yaml)) { return new List(); } return DataObjects.yamldeserializer.Deserialize>(yaml) ?? new List(); } private static List ReadEmbeddedSeed() { try { using Stream stream = typeof(ValheimEnforcer).Assembly.GetManifestResourceStream("ValheimEnforcer.assets.KnownCheaters.yaml"); if (stream == null) { Logger.LogWarning("Embedded KnownCheaters seed resource 'ValheimEnforcer.assets.KnownCheaters.yaml' was not found."); return new List(); } using StreamReader streamReader = new StreamReader(stream); return Parse(streamReader.ReadToEnd()); } catch (Exception ex) { Logger.LogWarning("Failed to read embedded KnownCheaters seed: " + ex.Message); return new List(); } } private static void SaveToDisk() { List list = Cheaters.Select((KeyValuePair kvp) => new DataObjects.KnownCheaterEntry { Id = kvp.Key, Reason = kvp.Value }).ToList(); try { ValConfig.GetSecondaryConfigDirectoryPath(); File.WriteAllText(ValConfig.KnownCheatersFilePath, DataObjects.yamlserializer.Serialize((object)list)); } catch (Exception ex) { Logger.LogWarning("Failed to write KnownCheaters file at " + ValConfig.KnownCheatersFilePath + ": " + ex.Message); } } } } namespace ValheimEnforcer.modules.character { internal static class CharacterDeltaTracker { internal static float LastDeltaSyncTime; internal static float LastFullSaveDataSyncTime; internal static DeltaChangeTracker DeltaTracker; 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()) && !((Object)(object)DeltaTracker != (Object)null)) { GameObject val = new GameObject("VE_ItemDeltaTracker"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; DeltaTracker = val.AddComponent(); Logger.LogDebug("ItemDeltaTracker initialized."); } } internal static DataObjects.PackedItem BuildPackedItem(ItemData item) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) return new DataObjects.PackedItem { prefabName = ((Object)item.m_dropPrefab).name, m_stack = item.m_stack, m_durability = Mathf.Clamp(item.m_durability, 0f, item.m_shared.m_maxDurability + item.m_shared.m_durabilityPerLevel * (float)Mathf.Max(item.m_quality, 1)), m_quality = item.m_quality, m_variant = item.m_variant, m_worldlevel = item.m_worldLevel, m_crafterID = item.m_crafterID, m_crafterName = item.m_crafterName, m_customdata = item.m_customData, m_equipped = item.m_equipped, m_gridpos = item.m_gridPos }; } internal static List BuildCharacterItemDeltas() { List list = new List(); if (CharacterManager.PlayerCharacter == null) { return list; } List list2 = new List(); foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems()) { list2.Add(BuildPackedItem(allItem)); } foreach (DataObjects.PackedItem playerItem in CharacterManager.PlayerCharacter.PlayerItems) { list2.Remove(playerItem); } foreach (DataObjects.PackedItem playerItem2 in CharacterManager.PlayerCharacter.PlayerItems) { if (!list2.Contains(playerItem2)) { list.Add(new DataObjects.ItemDelta { Item = playerItem2, Op = DataObjects.ItemDeltaChangeType.Removed }); } } foreach (DataObjects.PackedItem item in list2) { list.Add(new DataObjects.ItemDelta { Item = item, Op = DataObjects.ItemDeltaChangeType.Added }); } return list; } } internal static class CharacterManager { internal static DataObjects.Character PlayerCharacter = null; internal static List staringAllowedPrefabs = new List { "ArmorRagsChest", "ArmorRagsLegs", "Torch" }; internal static void SetPlayerCharacter(DataObjects.Character character) { if (character != null) { Logger.LogDebug("Set character from Saved server data"); PlayerCharacter = character; } } internal static string GetPlayerID(Player player) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) List playerList = ZNet.instance.GetPlayerList(); string text = ""; foreach (PlayerInfo item in playerList) { Logger.LogDebug($"Checking player {item.m_characterID} with ID {item.m_userInfo.m_id.m_userID} against local player {((Character)player).m_nview.GetZDO().m_uid}"); if (item.m_characterID == ((Character)player).m_nview.GetZDO().m_uid) { text = item.m_userInfo.m_id.m_userID; break; } } if (text.Length < 1) { string playerName = player.GetPlayerName(); foreach (PlayerInfo item2 in playerList) { if (item2.m_name == playerName) { text = item2.m_userInfo.m_id.m_userID; Logger.LogDebug("Matched player " + playerName + " by name to ID " + text); break; } } } if (text.Length < 1) { Logger.LogWarning("Failed to find matching player ID for local player " + player.GetPlayerName() + ". Defaulting to ZDO UID as player ID."); text = ((object)Unsafe.As(ref ((Character)player).m_nview.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString(); } if (text.Contains(":")) { Logger.LogDebug("Player ID contained invalid character : removing."); text = text.Split(new char[1] { ':' })[0]; } return text; } internal static void SavePlayerCharacter(Player __instance, DataObjects.DisconnectionState lastDisconnect = DataObjects.DisconnectionState.Clean) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } Scene activeScene = SceneManager.GetActiveScene(); if (!((Scene)(ref activeScene)).name.Equals("main")) { return; } string text = ""; string text2 = ""; DataObjects.Character character = null; if (PlayerCharacter != null) { character = PlayerCharacter; text = PlayerCharacter.HostID; text2 = PlayerCharacter.Name; } else { text = GetPlayerID(__instance); text2 = __instance.GetPlayerName(); } Logger.LogDebug("Saving character for player " + text2 + " with id " + text); if (PlayerCharacter == null) { character = ValConfig.LoadCharacterFromSave(text, text2); } if (character == null) { Logger.LogWarning("Attempted to save character for player " + text2 + " with ID " + text + " but no existing character data was found. Creating new character data."); character = new DataObjects.Character { Name = text2, HostID = text, SkillLevels = ((Character)__instance).GetSkills().GetSkillList().ToDictionary((Skill skill) => skill.m_info.m_skill, (Skill skill) => skill.m_level), ConfiscatedItems = null, LastDisconnect = lastDisconnect }; foreach (ItemData item in ((Humanoid)__instance).GetInventory().GetAllItems().ToList()) { character.AddItemToPlayerItems(item); } if (ValConfig.PreventExternalCustomDataChanges.Value) { character.PlayerCustomData = __instance.m_customData; } if (ValConfig.SavePlayerStatusEffectsOnLogout.Value) { character.ActiveCharacterEffects.Clear(); foreach (StatusEffect statusEffect in ((Character)__instance).GetSEMan().GetStatusEffects()) { Logger.LogDebug("Saving active status effect: " + ((Object)statusEffect).name); if (character.ActiveCharacterEffects.ContainsKey(((Object)statusEffect).name)) { character.ActiveCharacterEffects[((Object)statusEffect).name] = new DataObjects.PackedStatusEffect(statusEffect); } else { character.ActiveCharacterEffects.Add(((Object)statusEffect).name, new DataObjects.PackedStatusEffect(statusEffect)); } } } } else { Logger.LogDebug("Existing character data found for player " + text2 + " with ID " + text + ". Updating character data with current player information."); character.SkillLevels = ((Character)__instance).GetSkills().GetSkillList().ToDictionary((Skill skill) => skill.m_info.m_skill, (Skill skill) => skill.m_level); Logger.LogDebug("Updated player skills for " + text2 + " with ID " + text + "."); if (ValConfig.PreventExternalCustomDataChanges.Value) { character.PlayerCustomData = __instance.m_customData; Logger.LogDebug("Updated player custom data."); } character.PlayerItems.Clear(); foreach (ItemData item2 in ((Humanoid)__instance).GetInventory().GetAllItems().ToList()) { character.AddItemToPlayerItems(item2); } Logger.LogDebug("Updated player Items for " + text2 + " with ID " + text + "."); if (ValConfig.SavePlayerStatusEffectsOnLogout.Value) { character.ActiveCharacterEffects.Clear(); foreach (StatusEffect statusEffect2 in ((Character)__instance).GetSEMan().GetStatusEffects()) { Logger.LogDebug("Saving active status effect: " + ((Object)statusEffect2).name); if (character.ActiveCharacterEffects.ContainsKey(((Object)statusEffect2).name)) { character.ActiveCharacterEffects[((Object)statusEffect2).name] = new DataObjects.PackedStatusEffect(statusEffect2); } else { character.ActiveCharacterEffects.Add(((Object)statusEffect2).name, new DataObjects.PackedStatusEffect(statusEffect2)); } } Logger.LogDebug("Updated player active status effects."); } } if (character == null) { Logger.LogWarning("Savable character was null, not sending network updates."); return; } ValConfig.WritePlayerCharacterToSave(text, character); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetServerPeer() != null) { Logger.LogDebug("Sending updated character data to server."); ValConfig.CharacterSaveRPC.SendPackage(ZNet.instance.GetServerPeer().m_uid, ValConfig.SendCharacterAsZpackage(character)); } else { Logger.LogWarning("Server Disconnected, can't sync player data. This may result in desync issues."); } } internal static void LoadAndValidatePlayer(Player player) { //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) string text; string text2; if (PlayerCharacter != null) { text = PlayerCharacter.HostID; text2 = PlayerCharacter.Name; } else { text = GetPlayerID(player); text2 = player.GetPlayerName(); } Logger.LogInfo("Player " + text2 + " with ID " + text + " validating character data."); DataObjects.Character savableChar = PlayerCharacter; if (savableChar == null) { Logger.LogInfo("No existing character data found for player " + text2 + " with ID " + text + ". Attempting to load from local save."); savableChar = ValConfig.LoadCharacterFromSave(text, text2); if (savableChar == null) { savableChar = new DataObjects.Character { Name = player.GetPlayerName(), HostID = text, SkillLevels = ((Character)player).GetSkills().GetSkillList().ToDictionary((Skill skill) => skill.m_info.m_skill, (Skill skill) => skill.m_level) }; if (ValConfig.NewCharacterSetSkillsToZero.Value) { Logger.LogInfo("New character save for player " + text2 + " with ID " + text + ", skills set to zero."); foreach (SkillType item2 in savableChar.SkillLevels.Keys.ToList()) { savableChar.SkillLevels[item2] = 0f; } } if (ValConfig.NewCharactersRemoveExtraItems.Value) { Logger.LogInfo("New character save for player " + text2 + " with ID " + text + ", checking to removing non-starter items."); List removeItems = new List(); ((Humanoid)player).m_inventory.GetAllItems().ForEach(delegate(ItemData val2) { if (!staringAllowedPrefabs.Contains(((Object)val2.m_dropPrefab).name)) { Logger.LogInfo($"Removing non-starter item {((Object)val2.m_dropPrefab).name}x{val2.m_stack} from new player {savableChar.Name}"); savableChar.AddConfiscatedItem(val2, "New character, non-starter item"); removeItems.Add(val2); } if (val2.m_quality > 1) { Logger.LogInfo($"Removing high quality item {((Object)val2.m_dropPrefab).name}x{val2.m_stack} with quality {val2.m_quality} from new player {savableChar.Name}"); savableChar.AddConfiscatedItem(val2, $"Item quality did not match saved item {val2.m_quality}"); removeItems.Add(val2); } }); foreach (ItemData item3 in removeItems) { ((Humanoid)player).UnequipItem(item3, true); ((Humanoid)player).GetInventory().RemoveItem(item3); } } foreach (ItemData item4 in ((Humanoid)player).GetInventory().GetAllItems().ToList()) { savableChar.AddItemToPlayerItems(item4); if (ValConfig.ValidateItemCustomData.Value) { item4.m_customData.Clear(); } } } } if (ValConfig.RemoveNontrackedItemsFromJoiningPlayers.Value && ValConfig.ItemRemovalForDirtyReconnection.Value && savableChar.LastDisconnect == DataObjects.DisconnectionState.DirtyDisconnect) { new List(); foreach (KeyValuePair item5 in ValidateItems(((Humanoid)player).m_inventory.GetAllItems(), savableChar)) { if (!item5.Value.Validated) { Logger.LogInfo($"Removing item {((Object)item5.Key.m_dropPrefab).name}x{item5.Key.m_stack} from player {savableChar.Name}. Validation message: {item5.Value.ValidationMessage}"); savableChar.AddConfiscatedItem(item5.Key, item5.Value.ValidationMessage); ((Humanoid)player).UnequipItem(item5.Key, true); ((Humanoid)player).GetInventory().RemoveItem(item5.Key); } } } if (ValConfig.AddMissingItemsFromPlayerServerSave.Value && ValConfig.ItemReturnForDirtyReconnection.Value && savableChar.LastDisconnect == DataObjects.DisconnectionState.DirtyDisconnect) { Logger.LogDebug("Checking to restore player items."); List> list = new List>(); foreach (ItemData allItem in ((Humanoid)player).m_inventory.GetAllItems()) { list.Add(new Tuple(((Object)allItem.m_dropPrefab).name, allItem.m_stack)); } foreach (DataObjects.PackedItem playerItem in savableChar.PlayerItems) { Tuple item = new Tuple(playerItem.prefabName, playerItem.m_stack); if (!list.Contains(item)) { Logger.LogInfo($"Adding missing item to players inventory: {playerItem.prefabName}x{playerItem.m_stack}"); playerItem.AddToInventory(player, use_position: false); } } } Logger.LogDebug("Validated player items."); if (ValConfig.PreventExternalSkillRaises.Value) { ((Character)player).GetSkills().GetSkillList().ForEach(delegate(Skill skill) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (savableChar.SkillLevels.TryGetValue(skill.m_info.m_skill, out var value) && skill.m_level > value) { Logger.LogInfo($"Removing external skill gains for {skill.m_info.m_skill} from {value} to {skill.m_level} from player {savableChar.Name}"); skill.m_level = value; } }); } Logger.LogDebug("Validated player skills."); if (ValConfig.SavePlayerStatusEffectsOnLogout.Value && savableChar.ActiveCharacterEffects != null && savableChar.ActiveCharacterEffects.Count > 0) { SEMan sEMan = ((Character)player).GetSEMan(); foreach (KeyValuePair activeCharacterEffect in savableChar.ActiveCharacterEffects) { Logger.LogDebug("Applying status effect: " + activeCharacterEffect.Key); StatusEffect val = activeCharacterEffect.Value.ToStatusEffect(); if (!((Object)(object)val == (Object)null)) { sEMan.AddStatusEffect(val, false, 0, 0f); } } savableChar.ActiveCharacterEffects.Clear(); Logger.LogDebug("Validated saved status effects."); } PlayerCharacter = savableChar; ValConfig.WritePlayerCharacterToSave(text, savableChar); if (ZNet.instance.GetServerPeer() != null) { ValConfig.CharacterSaveRPC.SendPackage(ZNet.instance.GetServerPeer().m_uid, ValConfig.SendCharacterAsZpackage(savableChar)); } } internal static Dictionary ValidateItems(List playerItems, DataObjects.Character savedChar) { Dictionary dictionary = new Dictionary(); Logger.LogInfo($"Player Items: {playerItems.Count} | SavedCharacter Items: {savedChar.PlayerItems.Count}"); foreach (ItemData playerItem in playerItems) { Logger.LogDebug("Checking player item: " + ((Object)playerItem.m_dropPrefab).name); DataObjects.ValidationSummary validationSummary = new DataObjects.ValidationSummary(); dictionary.Add(playerItem, new DataObjects.ItemValidatorResult { CharacterItemRef = playerItem }); string text = ""; foreach (DataObjects.PackedItem playerItem2 in savedChar.PlayerItems) { if (!(playerItem2.prefabName == ((Object)playerItem.m_dropPrefab).name) || playerItem2.m_stack != playerItem.m_stack) { continue; } validationSummary.NameAndStackMatch = true; int num = playerItem2.m_quality; if (num == 0) { num = 1; } if (num == playerItem.m_quality) { validationSummary.QualityMatch = true; text += $"{num} != {playerItem.m_quality} "; } if (ValConfig.ValidateItemDurability.Value && playerItem.m_durability <= playerItem2.m_durability - ValConfig.ItemValidationDurabilityAllowedVariance.Value && playerItem.m_durability >= playerItem2.m_durability + ValConfig.ItemValidationDurabilityAllowedVariance.Value) { validationSummary.DurabilityMatch = false; text += $"Durability mismatch. Expected {playerItem2.m_durability} got {playerItem.m_durability} "; Logger.LogDebug($"Item {((Object)playerItem.m_dropPrefab).name} durability mismatch. Expected {playerItem2.m_durability} got {playerItem.m_durability} | {playerItem.m_durability} >= {playerItem2.m_durability - ValConfig.ItemValidationDurabilityAllowedVariance.Value} && {playerItem.m_durability} <= {playerItem2.m_durability + ValConfig.ItemValidationDurabilityAllowedVariance.Value}"); } else { validationSummary.DurabilityMatch = true; } validationSummary.CustomDataMatch = true; if (ValConfig.ValidateItemCustomData.Value) { foreach (KeyValuePair customDatum in playerItem.m_customData) { if (playerItem2.m_customdata.ContainsKey(customDatum.Key) && playerItem2.m_customdata[customDatum.Key] != customDatum.Value) { validationSummary.CustomDataMatch = false; text = text + "Custom data mismatch on key " + customDatum.Key + ". Expected " + playerItem2.m_customdata[customDatum.Key] + " got " + customDatum.Value + " "; Logger.LogDebug("Item " + ((Object)playerItem.m_dropPrefab).name + " custom data mismatch on key " + customDatum.Key + ". Expected " + playerItem2.m_customdata[customDatum.Key] + " got " + customDatum.Value); } } } if (validationSummary.IsValid()) { Logger.LogDebug("Item " + ((Object)playerItem.m_dropPrefab).name + " passed validation checks against saved character data."); dictionary[playerItem].SavedItemRef = playerItem2; dictionary[playerItem].Validated = true; break; } } dictionary[playerItem].ValidationResult = validationSummary; if (!validationSummary.IsValid()) { dictionary[playerItem].ValidationMessage = "Item " + ((Object)playerItem.m_dropPrefab).name + " failed validation checks against saved character data. " + $"Stack Match: {validationSummary.NameAndStackMatch}, " + $"Quality Match: {validationSummary.QualityMatch}, " + $"Custom Data Match: {validationSummary.CustomDataMatch}, " + $"Durability Match: {validationSummary.DurabilityMatch} | " + text; } } return dictionary; } } internal static class CharacterPatches { [HarmonyPatch(typeof(Game), "SpawnPlayer")] public static class LoadAndValidatePlayerPatch { [HarmonyPostfix] [HarmonyPriority(800)] private static void PlayerSpawn(Game __instance) { CharacterManager.LoadAndValidatePlayer(Player.m_localPlayer); } } [HarmonyPatch(typeof(Game), "Logout")] public static class ClearPlayerCharacterOnLogout { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix() { if (CharacterManager.PlayerCharacter != null) { Logger.LogDebug("Clearing selected save profile for " + CharacterManager.PlayerCharacter.Name + " on logout."); CharacterManager.PlayerCharacter = null; } } } [HarmonyPatch(typeof(Player))] public static class LoadPlayerCustomData { [HarmonyPostfix] [HarmonyPriority(800)] [HarmonyPatch("Load")] private static void Postfix(Player __instance) { DataObjects.Character character = null; string id; string name; if (CharacterManager.PlayerCharacter != null) { character = CharacterManager.PlayerCharacter; id = CharacterManager.PlayerCharacter.HostID; name = CharacterManager.PlayerCharacter.Name; } else { id = CharacterManager.GetPlayerID(__instance); name = __instance.GetPlayerName(); } if (CharacterManager.PlayerCharacter == null) { character = ValConfig.LoadCharacterFromSave(id, name); } if (character == null) { if (ValConfig.PreventExternalCustomDataChanges.Value && ValConfig.newCharacterClearCustomData.Value) { __instance.m_customData.Clear(); } } else if (ValConfig.PreventExternalCustomDataChanges.Value) { __instance.m_customData = character.PlayerCustomData; Logger.LogDebug("Set player custom data."); } } } [HarmonyPatch(typeof(Player), "Save")] public static class SaveSync { [HarmonyPrefix] [HarmonyPriority(0)] private static void PlayerSave(Player __instance) { CharacterManager.SavePlayerCharacter(__instance); } } [HarmonyPatch(typeof(Game), "Logout")] public static class SaveSyncForLogout { [HarmonyPrefix] [HarmonyPriority(800)] private static void PlayerSave() { if ((Object)(object)Player.m_localPlayer != (Object)null) { CharacterManager.SavePlayerCharacter(Player.m_localPlayer); } else { Logger.LogWarning("Player.m_localPlayer was null during logout. Skipping character sync."); } } } } } namespace ValheimEnforcer.common { internal static class ConfigFileWatcher { private class WatchEntry { public Action Callback; public DateTime LastWriteUTC { get; set; } public long FileLength { get; set; } public void Update(DateTime lastwrite, long len) { LastWriteUTC = lastwrite; FileLength = len; } } internal class ConfigFileWatcherBehaviour : MonoBehaviour { private float nextPollTime; public void Update() { if (!(Time.unscaledTime < nextPollTime)) { nextPollTime = Time.unscaledTime + (float)ValConfig.ConfigPollIntervalSeconds.Value; Poll(); } } private static void Poll() { if (WatchedFiles.Count == 0) { return; } foreach (string key in WatchedFiles.Keys) { if (!File.Exists(key)) { continue; } FileInfo fileInfo = new FileInfo(key); DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; long length = fileInfo.Length; WatchEntry watchEntry = WatchedFiles[key]; if (lastWriteTimeUtc == watchEntry.LastWriteUTC && length == watchEntry.FileLength) { continue; } WatchedFiles[key].LastWriteUTC = lastWriteTimeUtc; WatchedFiles[key].FileLength = length; try { if (watchEntry.Callback != null) { watchEntry.Callback(key); } } catch (Exception ex) { Logger.LogWarning("ConfigFileWatcher callback for " + key + " threw: " + ex.Message); } } } } private static Dictionary WatchedFiles = new Dictionary(); private static ConfigFileWatcherBehaviour watchProcess; internal static void Initialize() { //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_001e: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)watchProcess != (Object)null)) { GameObject val = new GameObject("VE_ConfigFileWatcher"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; watchProcess = val.AddComponent(); Logger.LogDebug("ConfigFileWatcher initialized."); } } internal static void Register(string fullPath, Action onChanged) { if (File.Exists(fullPath)) { FileInfo fileInfo = new FileInfo(fullPath); DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; long length = fileInfo.Length; WatchedFiles.Add(fullPath, new WatchEntry { LastWriteUTC = lastWriteTimeUtc, FileLength = length, Callback = onChanged }); } else { WatchedFiles.Add(fullPath, new WatchEntry { LastWriteUTC = DateTime.MinValue, FileLength = 0L, Callback = onChanged }); } Logger.LogDebug("ConfigFileWatcher watching " + fullPath); } } internal static class DataObjects { public enum ItemDeltaChangeType { Added, Removed } public enum DisconnectionState { Clean, DirtyDisconnect } public class RPCServerUpdateData { public string PlatformID { get; set; } public string PlayerName { get; set; } public string ItemPrefabFilter { get; set; } = "All"; } public class Mod { public string PluginID { get; set; } public string Version { get; set; } public string Name { get; set; } [DefaultValue(false)] public bool EnforceVersion { get; set; } [DefaultValue("Minor")] public string VersionStrictness { get; set; } = "Minor"; } public class Mods { public Dictionary ActiveMods { get; set; } = new Dictionary(); public Dictionary RequiredMods { get; set; } = new Dictionary(); public Dictionary OptionalMods { get; set; } = new Dictionary(); public Dictionary AdminOnlyMods { get; set; } = new Dictionary(); public Dictionary ServerOnlyMods { get; set; } = new Dictionary(); public ZPackage ToZPackage() { //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 = yamlserializer.Serialize((object)this); ZPackage val = new ZPackage(); val.Write(text); return val; } public Mods FromZPackage(ZPackage incoming) { Mods mods = yamldeserializer.Deserialize(incoming.ReadString()); ActiveMods = mods.ActiveMods; RequiredMods = mods.RequiredMods; OptionalMods = mods.OptionalMods; AdminOnlyMods = mods.AdminOnlyMods; ServerOnlyMods = mods.ServerOnlyMods; return mods; } } public class KnownCheaterEntry { public string Id { get; set; } public string Reason { get; set; } } public class CheatEngineDetector { public bool CheatEngineModuleLoaded { get; set; } public bool CheatEngineProcessDetected { get; set; } public bool IsCheatEngineDetected() { if (!CheatEngineModuleLoaded) { return CheatEngineProcessDetected; } return true; } } public class CheatSummaryReport { public string PlayerName { get; set; } public string PlatformID { get; set; } public CheatEngineDetector CheatEngineStatus { get; set; } public bool ValheimToolerStatus { get; set; } public bool cheatsDetected() { if (CheatEngineStatus == null || !CheatEngineStatus.IsCheatEngineDetected()) { return ValheimToolerStatus; } return true; } } public class ItemValidatorResult { public PackedItem SavedItemRef { get; set; } public ItemData CharacterItemRef { get; set; } [DefaultValue(false)] public bool Validated { get; set; } public string ValidationMessage { get; set; } public ValidationSummary ValidationResult { get; set; } } public class ValidationSummary { [DefaultValue(false)] public bool NameAndStackMatch { get; set; } [DefaultValue(false)] public bool QualityMatch { get; set; } [DefaultValue(false)] public bool CustomDataMatch { get; set; } [DefaultValue(false)] public bool DurabilityMatch { get; set; } public bool IsValid() { if (NameAndStackMatch && QualityMatch && CustomDataMatch) { return DurabilityMatch; } return false; } } [Serializable] public class PackedStatusEffect { public float TimeRemaining { get; set; } public float Time { get; set; } public int NameHash { get; set; } [DefaultValue(0f)] public float DamageLeft { get; set; } [DefaultValue(0f)] public float DamagePerHit { get; set; } [DefaultValue(0f)] public float FireDamageLeft { get; set; } [DefaultValue(0f)] public float FireDamagePerHit { get; set; } [DefaultValue(0f)] public float SpiritDamageLeft { get; set; } [DefaultValue(0f)] public float SpiritDamagePerHit { get; set; } public PackedStatusEffect() { } public PackedStatusEffect(StatusEffect status) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown NameHash = status.NameHash(); TimeRemaining = status.m_ttl; Time = status.m_time; if (NameHash == Poison) { SE_Poison val = (SE_Poison)status; DamageLeft = val.m_damageLeft; DamagePerHit = val.m_damagePerHit; } else if (NameHash == Burning || NameHash == Spirit) { SE_Burning val2 = (SE_Burning)status; FireDamageLeft = val2.m_fireDamageLeft; FireDamagePerHit = val2.m_fireDamagePerHit; SpiritDamageLeft = val2.m_spiritDamageLeft; SpiritDamagePerHit = val2.m_spiritDamagePerHit; } } public StatusEffect ToStatusEffect() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown StatusEffect statusEffect = ObjectDB.instance.GetStatusEffect(NameHash); if ((Object)(object)statusEffect == (Object)null) { Logger.LogWarning($"Tried to get a status effect which does not exist ID:{NameHash}"); return null; } StatusEffect val = statusEffect.Clone(); if (NameHash == Poison) { SE_Poison val2 = (SE_Poison)val; ((StatusEffect)val2).m_ttl = TimeRemaining; ((StatusEffect)val2).m_time = Time; val2.m_damageLeft = DamageLeft; val2.m_damagePerHit = DamagePerHit; return (StatusEffect)val2; } if (NameHash == Burning || NameHash == Spirit) { SE_Burning val3 = (SE_Burning)val; ((StatusEffect)val3).m_ttl = TimeRemaining; ((StatusEffect)val3).m_time = Time; val3.m_fireDamageLeft = FireDamageLeft; val3.m_fireDamagePerHit = FireDamagePerHit; val3.m_spiritDamageLeft = SpiritDamageLeft; val3.m_spiritDamagePerHit = SpiritDamagePerHit; return (StatusEffect)val3; } return val; } } [Serializable] public class PackedItem { public string prefabName { get; set; } public int m_stack { get; set; } public float m_durability { get; set; } public int m_quality { get; set; } [DefaultValue(0)] public int m_variant { get; set; } [DefaultValue(0)] public int m_worldlevel { get; set; } [DefaultValue(0L)] public long m_crafterID { get; set; } [DefaultValue("")] public string m_crafterName { get; set; } public Dictionary m_customdata { get; set; } [DefaultValue(false)] public bool m_equipped { get; set; } public Vector2i m_gridpos { get; set; } public string confiscatedReason { get; set; } public DateTime confiscatedTime { get; set; } public void AddToInventory(Player player, bool use_position) { //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) Inventory inventory = ((Humanoid)player).GetInventory(); ZNetView.m_forceDisableInit = true; GameObject prefab = PrefabManager.Instance.GetPrefab(prefabName); if ((Object)(object)prefab == (Object)null) { Logger.LogError($"Could not find prefab with name {prefabName} for item with crafter name {m_crafterName} and crafter ID {m_crafterID}. This item will not be added to the inventory."); ZNetView.m_forceDisableInit = false; return; } GameObject obj = Object.Instantiate(prefab); ZNetView.m_forceDisableInit = false; ItemDrop component = obj.GetComponent(); component.m_itemData.m_stack = m_stack; component.m_itemData.m_durability = m_durability; if (m_quality == 0) { component.m_itemData.m_quality = 1; } else { component.m_itemData.m_quality = m_quality; } component.m_itemData.m_variant = m_variant; component.m_itemData.m_worldLevel = m_worldlevel; component.m_itemData.m_crafterID = m_crafterID; if (m_crafterName == null) { component.m_itemData.m_crafterName = ""; } else { component.m_itemData.m_crafterName = m_crafterName; } component.m_itemData.m_customData = m_customdata; component.m_itemData.m_pickedUp = true; if (!inventory.CanAddItem(component.m_itemData, -1)) { if (use_position) { component.m_itemData.m_gridPos = m_gridpos; inventory.AddItem(component.m_itemData, component.m_itemData.m_stack, m_gridpos.x, m_gridpos.y); } else if (ModCompatability.IsExtraSlotsEnabled && API.IsGridPositionASlot(m_gridpos)) { Logger.LogDebug($"Item {prefabName} saved grid position {m_gridpos} maps to an ExtraSlots slot. Placing into that slot."); component.m_itemData.m_gridPos = m_gridpos; inventory.AddItem(component.m_itemData, component.m_itemData.m_stack, m_gridpos.x, m_gridpos.y); } else { inventory.AddItem(component.m_itemData); } } else { Logger.LogDebug("Dropping item " + prefabName + " at player position because it cannot be added to the inventory."); ItemDrop.DropItem(component.m_itemData, component.m_itemData.m_stack, ((Component)player).gameObject.transform.position, ((Component)player).gameObject.transform.rotation); } if (m_equipped) { ((Humanoid)player).EquipItem(component.m_itemData, true); } Object.Destroy((Object)(object)obj); } } public class ItemDelta { public PackedItem Item { get; set; } public ItemDeltaChangeType Op { get; set; } } public class DeltaSummaryUpdate { public string Name { get; set; } public string HostID { get; set; } public DisconnectionState DisconnectionState { get; set; } = DisconnectionState.DirtyDisconnect; public List ItemModifications { get; set; } = new List(); public Dictionary PlayerCustomDataModifications { get; set; } = new Dictionary(); public List RemovedCustomDataKeys { get; set; } = new List(); public Dictionary SkillLevels { get; set; } = new Dictionary(); public Dictionary ActiveCharacterEffects { get; set; } = new Dictionary(); } public class CharacterSaveData { public Dictionary SavedCharacters = new Dictionary(); } public class AccountEntries { public Dictionary> AccountCharacterEntries = new Dictionary>(); } public class Character { public string Name { get; set; } public string HostID { get; set; } public DisconnectionState LastDisconnect { get; set; } public Dictionary SkillLevels { get; set; } = new Dictionary(); public Dictionary PlayerCustomData { get; set; } = new Dictionary(); public Dictionary ActiveCharacterEffects { get; set; } = new Dictionary(); public List PlayerItems { get; set; } = new List(); public List ConfiscatedItems { get; set; } = new List(); public bool RemoveFromPlayerItems(PackedItem packedItem) { bool flag = false; if (PlayerItems != null && PlayerItems.Contains(packedItem)) { flag = PlayerItems.Remove(packedItem); } if (flag) { return true; } if (PlayerItems != null) { foreach (PackedItem playerItem in PlayerItems) { if (packedItem.prefabName == playerItem.prefabName && packedItem.m_stack == playerItem.m_stack && packedItem.m_variant == playerItem.m_variant && packedItem.m_worldlevel == playerItem.m_worldlevel && packedItem.m_crafterID == playerItem.m_crafterID && packedItem.m_crafterName == playerItem.m_crafterName) { flag = PlayerItems.Remove(playerItem); if (flag) { Logger.LogDebug("Removed item " + playerItem.prefabName + " from player items based on a fuzzy match."); break; } } } } return flag; } public void AddItemToPlayerItems(ItemData item) { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) if (PlayerItems == null) { PlayerItems = new List(); } Logger.LogDebug($"Adding saved item {((Object)item.m_dropPrefab).name} with quality - {item.m_quality}"); PlayerItems.Add(new PackedItem { prefabName = ((Object)item.m_dropPrefab).name, m_stack = item.m_stack, m_durability = Mathf.Clamp(item.m_durability, 0f, item.m_shared.m_maxDurability + item.m_shared.m_durabilityPerLevel * (float)Mathf.Max(item.m_quality, 1)), m_quality = item.m_quality, m_variant = item.m_variant, m_worldlevel = item.m_worldLevel, m_crafterID = item.m_crafterID, m_crafterName = item.m_crafterName, m_customdata = item.m_customData, m_equipped = item.m_equipped, m_gridpos = item.m_gridPos }); } public void AddConfiscatedItem(ItemData item, string reason = "") { //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (ConfiscatedItems == null) { ConfiscatedItems = new List(); } PackedItem packedItem = new PackedItem { prefabName = ((Object)item.m_dropPrefab).name, m_stack = item.m_stack, m_durability = item.m_durability, m_quality = item.m_quality, m_variant = item.m_variant, m_worldlevel = item.m_worldLevel, m_crafterID = item.m_crafterID, m_crafterName = item.m_crafterName, m_customdata = item.m_customData, m_equipped = item.m_equipped, m_gridpos = item.m_gridPos }; if (!string.IsNullOrEmpty(reason)) { packedItem.confiscatedReason = reason; } packedItem.confiscatedTime = DateTime.UtcNow; ConfiscatedItems.Add(packedItem); } } internal class DiscordMessage { public List Embeds { get; } = new List(); public DiscordMessage AddEmbed(DiscordEmbed embed) { Embeds.Add(embed); return this; } public string ToJson() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\"embeds\":["); for (int i = 0; i < Embeds.Count; i++) { if (i > 0) { stringBuilder.Append(','); } Embeds[i].AppendJson(stringBuilder); } stringBuilder.Append("]}"); return stringBuilder.ToString(); } internal static string EscapeJson(string value) { if (string.IsNullOrEmpty(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(value.Length + 8); foreach (char c in value) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\b': stringBuilder.Append("\\b"); continue; case '\f': stringBuilder.Append("\\f"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } internal class DiscordEmbed { private const int TitleLimit = 256; private const int DescriptionLimit = 4096; private const int FieldValueLimit = 1024; public string Title { get; set; } public string Description { get; set; } public int Color { get; set; } public string Timestamp { get; set; } public List Fields { get; } = new List(); public DiscordEmbed(string title, string description, int color, string timestamp = null) { Title = title; Description = description; Color = color; if (timestamp != null) { Timestamp = timestamp; } else { Timestamp = DateTime.UtcNow.ToString("o"); } } public DiscordMessage ToMessage() { return new DiscordMessage().AddEmbed(this); } public DiscordEmbed AddField(string name, string value, bool inline = false) { string value2 = (string.IsNullOrEmpty(value) ? "unknown" : value); Fields.Add(new DiscordEmbedField { Name = name, Value = Clamp(value2, 1024), Inline = inline }); return this; } public void AppendJson(StringBuilder sb) { sb.Append('{'); bool first = true; AppendStringProp(sb, "title", Clamp(Title, 256), ref first); AppendStringProp(sb, "description", Clamp(Description, 4096), ref first); if (!first) { sb.Append(','); } sb.Append("\"color\":").Append(Color); first = false; AppendStringProp(sb, "timestamp", Timestamp, ref first); if (Fields.Count > 0) { sb.Append(",\"fields\":["); for (int i = 0; i < Fields.Count; i++) { if (i > 0) { sb.Append(','); } Fields[i].AppendJson(sb); } sb.Append(']'); } sb.Append('}'); } private static void AppendStringProp(StringBuilder sb, string name, string value, ref bool first) { if (!string.IsNullOrEmpty(value)) { if (!first) { sb.Append(','); } sb.Append('"').Append(name).Append("\":\"") .Append(DiscordMessage.EscapeJson(value)) .Append('"'); first = false; } } private static string Clamp(string value, int max) { if (string.IsNullOrEmpty(value) || value.Length <= max) { return value; } return value.Substring(0, max); } } internal class DiscordEmbedField { public string Name { get; set; } public string Value { get; set; } public bool Inline { get; set; } public void AppendJson(StringBuilder sb) { sb.Append("{\"name\":\"").Append(DiscordMessage.EscapeJson(Name)).Append("\",\"value\":\"") .Append(DiscordMessage.EscapeJson(Value)) .Append("\",\"inline\":") .Append(Inline ? "true" : "false") .Append('}'); } } public static IDeserializer yamldeserializer = ((BuilderSkeleton)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); public static ISerializer yamlserializer = ((BuilderSkeleton)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling((DefaultValuesHandling)2).Build(); public static readonly string CustomDataKey = "VE_CUSTOM_DATA"; private static readonly int Poison = StringExtensionMethods.GetStableHashCode("Poison"); private static readonly int Burning = StringExtensionMethods.GetStableHashCode("Burning"); private static readonly int Spirit = StringExtensionMethods.GetStableHashCode("Spirit"); internal const int Green = 5763719; internal const int Grey = 9807270; internal const int Amber = 16705372; internal const int Red = 15548997; } }