Decompiled source of ValheimEnforcer v0.9.1

plugins/ValheimEnforcer.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using ValheimEnforcer;
using ValheimEnforcer.common;
using ValheimEnforcer.modules;
using ValheimEnforcer.modules.character;
using ValheimEnforcer.modules.cheatmonitor;
using ValheimEnforcer.modules.commands;
using ValheimEnforcer.modules.compat;
using ValheimEnforcer.modules.compat.ExtraSlots;
using ValheimEnforcer.modules.notifications;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ValheimEnforcer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ValheimEnforcer")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")]
[assembly: AssemblyFileVersion("0.9.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.9.1.0")]
internal class DeltaChangeTracker : MonoBehaviour
{
	public void Update()
	{
		if (Time.unscaledTime > CharacterDeltaTracker.LastDeltaSyncTime)
		{
			CharacterDeltaTracker.LastDeltaSyncTime = Time.unscaledTime + (float)ValConfig.DeltaSynchronizationFrequencyInSeconds.Value;
			SyncChangesToServer();
		}
		if (Time.unscaledTime > CharacterDeltaTracker.LastFullSaveDataSyncTime)
		{
			CharacterDeltaTracker.LastFullSaveDataSyncTime = Time.unscaledTime + (float)ValConfig.FullSaveDataSynchronizationFrequencyInSeconds.Value;
			Logger.LogDebug("Forcing full save data sync to server.");
			CharacterManager.SavePlayerCharacter(Player.m_localPlayer);
		}
	}

	private static void SyncChangesToServer()
	{
		//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ba: Expected O, but got Unknown
		if (CharacterManager.PlayerCharacter == null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.GetServerPeer() == null)
		{
			return;
		}
		Logger.LogDebug("Checking for character changes to sync to server...");
		List<DataObjects.ItemDelta> list = CharacterDeltaTracker.BuildCharacterItemDeltas();
		Dictionary<string, string> customData = Player.m_localPlayer.m_customData;
		Dictionary<string, string> dictionary = new Dictionary<string, string>();
		List<string> list2 = new List<string>();
		foreach (KeyValuePair<string, string> item in customData)
		{
			if (CharacterManager.PlayerCharacter.PlayerCustomData.ContainsKey(item.Key))
			{
				if (CharacterManager.PlayerCharacter.PlayerCustomData[item.Key] != item.Value)
				{
					dictionary.Add(item.Key, item.Value);
				}
			}
			else
			{
				dictionary.Add(item.Key, item.Value);
			}
		}
		foreach (KeyValuePair<string, string> playerCustomDatum in CharacterManager.PlayerCharacter.PlayerCustomData)
		{
			if (!customData.ContainsKey(playerCustomDatum.Key))
			{
				list2.Add(playerCustomDatum.Key);
			}
		}
		if (list.Count == 0 && dictionary.Count == 0 && list2.Count == 0)
		{
			return;
		}
		Logger.LogDebug("Changes found, syncing deltas.");
		List<DataObjects.PackedItem> list3 = new List<DataObjects.PackedItem>();
		foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems())
		{
			list3.Add(CharacterDeltaTracker.BuildPackedItem(allItem));
		}
		CharacterManager.PlayerCharacter.PlayerItems = list3;
		CharacterManager.PlayerCharacter.PlayerCustomData = customData;
		Dictionary<string, DataObjects.PackedStatusEffect> dictionary2 = new Dictionary<string, DataObjects.PackedStatusEffect>();
		foreach (StatusEffect statusEffect in ((Character)Player.m_localPlayer).GetSEMan().GetStatusEffects())
		{
			dictionary2.Add(((Object)statusEffect).name, new DataObjects.PackedStatusEffect(statusEffect));
		}
		DataObjects.DeltaSummaryUpdate deltaSummaryUpdate = new DataObjects.DeltaSummaryUpdate
		{
			Name = CharacterManager.PlayerCharacter.Name,
			HostID = CharacterManager.PlayerCharacter.HostID,
			ItemModifications = list,
			SkillLevels = ((Character)Player.m_localPlayer).GetSkills().GetSkillList().ToDictionary((Skill s) => s.m_info.m_skill, (Skill s) => s.m_level),
			PlayerCustomDataModifications = dictionary,
			RemovedCustomDataKeys = list2,
			ActiveCharacterEffects = dictionary2
		};
		ZPackage val = new ZPackage();
		val.Write(DataObjects.yamlserializer.Serialize((object)deltaSummaryUpdate));
		ValConfig.ItemDeltaUpdateRPC.SendPackage(ZNet.instance.GetServerPeer().m_uid, val);
		Logger.LogDebug($"Delta flush: {list.Count} items, {dictionary.Count} ({list2.Count} removed) custom data changes. Skill levels updated.");
	}
}
namespace ValheimEnforcer
{
	internal class ValConfig
	{
		public static ConfigFile cfg;

		public static ConfigEntry<bool> EnableDebugMode;

		public static ConfigEntry<bool> UpdateLoadedModsOnStartup;

		public static ConfigEntry<bool> AutoAddModsToRequired;

		public static ConfigEntry<bool> RemoveNontrackedItemsFromJoiningPlayers;

		public static ConfigEntry<bool> AddMissingItemsFromPlayerServerSave;

		public static ConfigEntry<bool> PreventExternalSkillRaises;

		public static ConfigEntry<bool> NewCharactersRemoveExtraItems;

		public static ConfigEntry<bool> NewCharacterSetSkillsToZero;

		public static ConfigEntry<bool> newCharacterClearCustomData;

		public static ConfigEntry<bool> PreventExternalCustomDataChanges;

		public static ConfigEntry<bool> ValidateItemCustomData;

		public static ConfigEntry<bool> ValidateItemDurability;

		public static ConfigEntry<float> ItemValidationDurabilityAllowedVariance;

		public static ConfigEntry<bool> SavePlayerStatusEffectsOnLogout;

		public static ConfigEntry<bool> ItemRemovalForDirtyReconnection;

		public static ConfigEntry<bool> ItemReturnForDirtyReconnection;

		public static ConfigEntry<bool> InternalStorageMode;

		public static ConfigEntry<int> ConfigPollIntervalSeconds;

		public static ConfigEntry<int> DeltaSynchronizationFrequencyInSeconds;

		public static ConfigEntry<int> FullSaveDataSynchronizationFrequencyInSeconds;

		public static ConfigEntry<bool> EnableCheatDetection;

		public static ConfigEntry<bool> DetectCheatEngine;

		public static ConfigEntry<bool> DetectValheimTooler;

		public static ConfigEntry<string> CheatDetectionAction;

		public static ConfigEntry<int> CheatScanIntervalSeconds;

		public static ConfigEntry<string> DiscordWebhookUrl;

		public static ConfigEntry<bool> DiscordNotifyServerStartup;

		public static ConfigEntry<bool> DiscordNotifyServerShutdown;

		public static ConfigEntry<bool> DiscordNotifyPlayerJoined;

		public static ConfigEntry<bool> DiscordNotifyPlayerLeft;

		public static ConfigEntry<bool> DiscordNotifyWrongMods;

		internal const string ModsFileName = "Mods.yaml";

		internal const string ValheimEnforcer = "ValheimEnforcer";

		internal const string CharacterFolder = "Characters";

		internal const string KnownCheatersFileName = "KnownCheaters.yaml";

		internal static string ModsConfigFilePath = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Mods.yaml");

		internal static string CharacterFilePath = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters");

		internal static string KnownCheatersFilePath = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "KnownCheaters.yaml");

		internal static CustomRPC CharacterSaveRPC;

		internal static CustomRPC ReturnConfiscatedItemsRPC;

		internal static CustomRPC CheatDetectionRPC;

		internal static CustomRPC ItemDeltaUpdateRPC;

		internal static CustomRPC ListPlayerRPC;

		internal static CustomRPC ClearConfiscatedRPC;

		public ValConfig(ConfigFile cf)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			//IL_005e: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			//IL_008a: Expected O, but got Unknown
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			//IL_00b6: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_00e2: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Expected O, but got Unknown
			//IL_010e: Expected O, but got Unknown
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			//IL_013a: Expected O, but got Unknown
			cfg = cf;
			cfg.SaveOnConfigSet = true;
			CreateConfigValues(cf);
			Logger.SetDebugLogging(EnableDebugMode.Value);
			ConfigFileWatcher.Initialize();
			SetupMainFileWatcher();
			CharacterSaveRPC = NetworkManager.Instance.AddRPC("VENFORCE_CHAR", new CoroutineHandler(OnServerRecieveCharacter), new CoroutineHandler(OnClientReceiveCharacter));
			ReturnConfiscatedItemsRPC = NetworkManager.Instance.AddRPC("VENFORCE_RETURN_CONFISCATED", new CoroutineHandler(OnServerReturnConfiscatedReceive), new CoroutineHandler(OnClientReceiveConfiscatedItems));
			CheatDetectionRPC = NetworkManager.Instance.AddRPC("VENFORCE_CHEAT", new CoroutineHandler(OnServerReceiveCheatReport), new CoroutineHandler(OnClientReceiveCheatReport));
			ItemDeltaUpdateRPC = NetworkManager.Instance.AddRPC("VENFORCE_ITEMDELTA", new CoroutineHandler(OnServerRecieveDeltaItemUpdate), new CoroutineHandler(OnClientReceiveDeltaItemUpdate));
			ListPlayerRPC = NetworkManager.Instance.AddRPC("VENFORCE_LIST_PLAYER", new CoroutineHandler(OnServerReceiveListPlayer), new CoroutineHandler(OnClientReceiveListPlayer));
			ClearConfiscatedRPC = NetworkManager.Instance.AddRPC("VENFORCE_CLEAR_CONFISCATED", new CoroutineHandler(OnServerRecieveClearConfiscated), new CoroutineHandler(OnClientReceiveClearConfiscated));
			SynchronizationManager.Instance.AddInitialSynchronization(CharacterSaveRPC, (Func<ZNetPeer, ZPackage>)SendSavedCharacter);
			LoadYamlConfigs(new Dictionary<string, Action<string>>
			{
				{ ModsConfigFilePath, CreateModsFile },
				{ KnownCheatersFilePath, CreateKnownCheatersFile }
			});
			KnownCheaterTracker.Initialize();
		}

		private void CreateConfigValues(ConfigFile Config)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			EnableDebugMode = Config.Bind<bool>("Client config", "EnableDebugMode", false, new ConfigDescription("Enables Debug logging.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdvanced = true
			} }));
			EnableDebugMode.SettingChanged += Logger.EnableDebugLogging;
			Logger.CheckEnableDebugLogging();
			UpdateLoadedModsOnStartup = BindServerConfig("Mods", "UpdateLoadedModsOnStartup", value: true, "Whether or not the mod configuration file will update its loaded mods once they are detected.");
			AutoAddModsToRequired = BindServerConfig("Mods", "AutoAddModsToRequired", value: true, "If true, automatically adds mods not found in the optional, admin, or server-only mod lists.");
			RemoveNontrackedItemsFromJoiningPlayers = BindServerConfig("Player Sync", "RemoveNontrackedItemsFromJoiningPlayers", value: true, "If enabled, any items that are not tracked by the server will be removed from joining player's inventories.");
			AddMissingItemsFromPlayerServerSave = BindServerConfig("Player Sync", "AddMissingItemsFromPlayerServerSave", value: true, "If enabled, any items the player does not have that are listed on the server will be given to the player when joining");
			PreventExternalSkillRaises = BindServerConfig("Player Sync", "PreventExternalSkillRaises", value: true, "If enabled, player skill gains outside of the server are removed when connecting.");
			NewCharactersRemoveExtraItems = BindServerConfig("Player Sync", "NewCharactersRemoveExtraItems", value: false, "If enabled, new characters that have no existing character file will have all items removed except for starting items.");
			NewCharacterSetSkillsToZero = BindServerConfig("Player Sync", "NewCharacterSetSkillsToZero", value: false, "If enabled, new characters will have their skills set to zero. Prevents players from raising skills before connecting.");
			PreventExternalCustomDataChanges = BindServerConfig("Player Sync", "PreventExternalCustomDataChanges", value: true, "If enabled, tracks player custom data. Warning: custom data can be large and can impact how other mods function.");
			newCharacterClearCustomData = BindServerConfig("Player Sync", "newCharacterClearCustomData", value: true, "If enabled, new characters will have their custom data cleared.");
			ValidateItemCustomData = BindServerConfig("Player Sync", "ValidateItemCustomData", value: true, "If enabled, custom data on items will be validated.");
			ValidateItemDurability = BindServerConfig("Player Sync", "ValidateItemDurability", value: true, "If enabled, item durability will be validated");
			ItemValidationDurabilityAllowedVariance = BindServerConfig("Player Sync", "ItemValidationDurabilityAllowedVariance", 10f, "Allowed variance for item durability validation.", advanced: true, 0f, 100f);
			SavePlayerStatusEffectsOnLogout = BindServerConfig("Player Sync", "SavePlayerStatusEffectsOnLogout", value: true, "Whether or not to save active character effects on logout and reapply on login");
			ItemRemovalForDirtyReconnection = BindServerConfig("Player Sync", "ItemRemovalForDirtyReconnection", value: false, "If enabled, items will not be removed from the player on a dirty reconnection.");
			ItemReturnForDirtyReconnection = BindServerConfig("Player Sync", "ItemReturnForDirtyReconnection", value: false, "If enabled, items will be returned to the player on a dirty reconnection.");
			InternalStorageMode = BindServerConfig("Advanced", "InternalStorageMode", value: false, "If enabled, player character data will be stored within your world. Enables full portability of the world without having to synchronize configurations.", null, advanced: true);
			ConfigPollIntervalSeconds = BindServerConfig("Advanced", "ConfigPollIntervalSeconds", 30, "How frequently (in seconds) the mod polls config files on disk for changes.", advanced: true, 1, 300);
			DeltaSynchronizationFrequencyInSeconds = BindServerConfig("Advanced", "CharacterDeltaTracker", 60, "How frequently (in seconds) the client sends incremental inventory/skill/custom-data updates to the server.", advanced: true, 30, 300);
			FullSaveDataSynchronizationFrequencyInSeconds = BindServerConfig("Advanced", "FullSaveDataSynchronizationFrequencyInSeconds", 300, "How frequently (in seconds) the client sends a full character save to the server.", advanced: true, 60, 3600);
			EnableCheatDetection = BindServerConfig("Anti-Cheat", "EnableCheatDetection", value: false, "Enable client-side scanning for known cheat tools (Cheat Engine, ValheimTooler). Detections are reported to the server.");
			DetectValheimTooler = BindServerConfig("Anti-Cheat", "DetectValheimTooler", value: true, "Scan loaded assemblies for ValheimTooler. High confidence, very low cost.");
			DetectCheatEngine = BindServerConfig("Anti-Cheat", "DetectCheatEngine", value: true, "Scan for Cheat Engine (processes, windows, injected speedhack/DBK modules, debugger). Note: Cheat Engine has legitimate uses — prefer Log action over Kick/Ban.");
			CheatDetectionAction = BindServerConfig("Anti-Cheat", "ActionOnDetection", "Kick", "Server-side action taken when a client reports a cheat detection.", new AcceptableValueList<string>(new string[3] { "Log", "Kick", "Ban" }));
			CheatScanIntervalSeconds = BindServerConfig("Anti-Cheat", "ScanIntervalSeconds", 5, "Seconds between scans on the client.", advanced: false, 1, 60);
			DiscordWebhookUrl = BindLocalConfig("Discord", "WebhookUrl", "", "Discord webhook URL the server posts notifications to. This is a server-only secret and is never synced to clients. Leave empty to disable. Note: player names are sent to Discord when enabled.");
			DiscordNotifyServerStartup = BindLocalConfig("Discord", "NotifyServerStartup", value: true, "Post a message when the server comes online.");
			DiscordNotifyServerShutdown = BindLocalConfig("Discord", "NotifyServerShutdown", value: true, "Post a message when the server shuts down.");
			DiscordNotifyPlayerJoined = BindLocalConfig("Discord", "NotifyPlayerJoined", value: true, "Post a message when a player joins.");
			DiscordNotifyPlayerLeft = BindLocalConfig("Discord", "NotifyPlayerLeft", value: true, "Post a message when a player leaves, including whether their saved data is up to date.");
			DiscordNotifyWrongMods = BindLocalConfig("Discord", "NotifyWrongMods", value: true, "Post a message when a player is rejected for a mod mismatch, listing the offending mods.");
		}

		internal static void WritePlayerCharacterToSave(string id, DataObjects.Character character)
		{
			if (InternalStorageMode.Value)
			{
				Logger.LogInfo("Saving character with internal storage mode.");
				InternalDataStore.SaveAccountCharacter(character);
			}
			Directory.CreateDirectory(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters"));
			string text = Path.Combine(Directory.CreateDirectory(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", id)).FullName, character.Name + ".yaml");
			Logger.LogInfo("Writing to " + text);
			try
			{
				File.WriteAllText(text, DataObjects.yamlserializer.Serialize((object)character));
			}
			catch (Exception ex)
			{
				Logger.LogWarning("Failed to write character data to disk at " + text + ": " + ex.Message);
			}
		}

		internal static DataObjects.Character LoadCharacterFromSave(string id, string name)
		{
			if (InternalStorageMode.Value)
			{
				Logger.LogInfo("Loading character from internal storage system.");
				DataObjects.Character accountCharacter = InternalDataStore.GetAccountCharacter(id, name);
				if (accountCharacter == null)
				{
					Logger.LogDebug("No character file found for player with " + id + "-" + name + " is this character new?");
				}
				return accountCharacter;
			}
			string path = Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", id, name + ".yaml");
			if (!File.Exists(path))
			{
				Logger.LogDebug("No character file found for player with " + id + "-" + name + " is this character new?");
				return null;
			}
			string text = File.ReadAllText(path);
			return DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(text);
		}

		public static string GetSecondaryConfigDirectoryPath()
		{
			string text = Path.Combine(Paths.ConfigPath, "ValheimEnforcer");
			if (!Directory.Exists(text))
			{
				Directory.CreateDirectory(text);
			}
			return text;
		}

		internal void LoadYamlConfigs(Dictionary<string, Action<string>> configFilesToFind)
		{
			string[] files = Directory.GetFiles(GetSecondaryConfigDirectoryPath());
			List<string> list = new List<string>();
			List<string> list2 = configFilesToFind.Keys.ToList();
			string[] array = files;
			foreach (string text in array)
			{
				if (list2.Contains(text))
				{
					list.Add(text);
					Logger.LogDebug("Found config: " + text);
				}
			}
			foreach (KeyValuePair<string, Action<string>> item in configFilesToFind)
			{
				if (!list.Contains(item.Key))
				{
					configFilesToFind[item.Key](item.Key);
					list.Add(item.Key);
				}
			}
			foreach (string item2 in list)
			{
				string fileName = Path.GetFileName(item2);
				Logger.LogDebug("Setting filewatcher for " + fileName);
				SetupFileWatcher(item2);
			}
		}

		private void SetupFileWatcher(string fullPath)
		{
			ConfigFileWatcher.Register(fullPath, UpdateConfigFileOnChange);
		}

		private static void UpdateConfigFileOnChange(string filepath)
		{
			if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Logger.LogInfo("Player is not an admin, and not allowed to change local configuration. Ignoring.");
			}
			else
			{
				if (!File.Exists(filepath))
				{
					return;
				}
				string text = File.ReadAllText(filepath);
				FileInfo fileInfo = new FileInfo(filepath);
				Logger.LogDebug("Filewatch changes from: (" + fileInfo.Name + ") " + fileInfo.FullName);
				string name = fileInfo.Name;
				if (!(name == "Mods.yaml"))
				{
					if (name == "KnownCheaters.yaml")
					{
						Logger.LogDebug("Triggering KnownCheaters list update.");
						KnownCheaterTracker.LoadFromText(text);
					}
				}
				else
				{
					Logger.LogDebug("Triggering Mod Settings update.");
					ModManager.UpdateModSettingConfigs(text);
				}
			}
		}

		private static void CreateModsFile(string filepath)
		{
			Logger.LogDebug("Loot config missing, recreating.");
			using StreamWriter streamWriter = new StreamWriter(filepath);
			string value = "#################################################\n# Valheim Enforcer - Required, Admin and Optional Mods\n#################################################\n";
			streamWriter.WriteLine(value);
			streamWriter.WriteLine(ModManager.GetDefaultConfig());
		}

		private static void CreateKnownCheatersFile(string filepath)
		{
			Logger.LogDebug("KnownCheaters file missing, recreating.");
			using StreamWriter streamWriter = new StreamWriter(filepath);
			string value = "#################################################\n# Valheim Enforcer - Known Cheaters (server side)\n# Auto-populated when cheaters are banned. Entries: { id, reason }\n#################################################\n";
			streamWriter.WriteLine(value);
		}

		internal static ZPackage SendSavedCharacter(ZNetPeer peer)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Expected O, but got Unknown
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Expected O, but got Unknown
			string endPointString = peer.m_socket.GetEndPointString();
			Logger.LogInfo("Sending saved character data to player " + peer.m_playerName + " with ID: " + endPointString);
			ZPackage val = new ZPackage();
			if (InternalStorageMode.Value)
			{
				Logger.LogInfo("Using internal storage mode to send character data.");
				DataObjects.Character accountCharacter = InternalDataStore.GetAccountCharacter(endPointString, peer.m_playerName);
				if (accountCharacter == null)
				{
					Logger.LogInfo("No character data found for player " + peer.m_playerName + " with ID: " + endPointString + ", no character data will be sent.");
					return new ZPackage();
				}
				string text = DataObjects.yamlserializer.Serialize((object)accountCharacter);
				val.Write(text);
				return val;
			}
			string text2 = Path.Combine(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", endPointString ?? ""), peer.m_playerName + ".yaml");
			if (!File.Exists(text2))
			{
				Logger.LogInfo("path: " + text2 + " does not exist, no character data will be sent.");
				return new ZPackage();
			}
			string text3 = File.ReadAllText(text2);
			val.Write(text3);
			return val;
		}

		public static IEnumerator OnServerRecieveCharacter(long sender, ZPackage package)
		{
			try
			{
				DataObjects.Character character = DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(package.ReadString());
				Logger.LogInfo($"Recieved Player data update for {sender} - {character.Name}|{character.HostID}");
				WritePlayerCharacterToSave(character.HostID, character);
			}
			catch (Exception ex)
			{
				Logger.LogWarning($"Failed to deserialize character data from {sender}: {ex.Message}");
			}
			yield break;
		}

		public static IEnumerator OnServerRecieveClearConfiscated(long sender, ZPackage package)
		{
			DataObjects.RPCServerUpdateData rPCServerUpdateData = DataObjects.yamldeserializer.Deserialize<DataObjects.RPCServerUpdateData>(package.ReadString());
			ZNetPeer peerByPlatformID = GetPeerByPlatformID(rPCServerUpdateData.PlatformID);
			if (peerByPlatformID == null)
			{
				Logger.LogWarning("Could not find peer with PlatformID " + rPCServerUpdateData.PlatformID + " to clear confiscated items.");
				yield break;
			}
			CommandHelpers.ClearSpecifiedPlayerConfiscatedItems(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName, rPCServerUpdateData.ItemPrefabFilter);
			ClearConfiscatedRPC.SendPackage(peerByPlatformID.m_uid, package);
		}

		public static IEnumerator OnClientReceiveClearConfiscated(long sender, ZPackage package)
		{
			DataObjects.RPCServerUpdateData rPCServerUpdateData = DataObjects.yamldeserializer.Deserialize<DataObjects.RPCServerUpdateData>(package.ReadString());
			CommandHelpers.ClearSpecifiedPlayerConfiscatedItems(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName, rPCServerUpdateData.ItemPrefabFilter);
			yield break;
		}

		public static IEnumerator OnClientReceiveCharacter(long sender, ZPackage package)
		{
			DataObjects.Character playerCharacter = DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(package.ReadString());
			Logger.LogDebug("Recieved Player character data from server.");
			CharacterManager.SetPlayerCharacter(playerCharacter);
			yield break;
		}

		public static IEnumerator OnServerReturnConfiscatedReceive(long sender, ZPackage package)
		{
			DataObjects.RPCServerUpdateData rPCServerUpdateData = DataObjects.yamldeserializer.Deserialize<DataObjects.RPCServerUpdateData>(package.ReadString());
			List<DataObjects.PackedItem> list = CommandHelpers.LoadCharacterAndFindItemsToReturn(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName, rPCServerUpdateData.ItemPrefabFilter);
			DataObjects.Character character = LoadCharacterFromSave(rPCServerUpdateData.PlatformID, rPCServerUpdateData.PlayerName);
			ZNetPeer peerByPlatformID = GetPeerByPlatformID(rPCServerUpdateData.PlatformID);
			if (peerByPlatformID == null)
			{
				Logger.LogInfo("Player " + rPCServerUpdateData.PlayerName + " is not currently connected. Moving items to player inventory save so they are restored on next login.");
				foreach (DataObjects.PackedItem item in list)
				{
					character.PlayerItems.Add(item);
				}
				WritePlayerCharacterToSave(rPCServerUpdateData.PlatformID, character);
				if (InternalStorageMode.Value)
				{
					Logger.LogInfo("Also updating character data in internal storage.");
					InternalDataStore.SaveAccountCharacter(character);
				}
			}
			else
			{
				Logger.LogInfo($"Sending {list.Count} confiscated item(s) to player {rPCServerUpdateData.PlayerName}.");
				WritePlayerCharacterToSave(rPCServerUpdateData.PlatformID, character);
				if (InternalStorageMode.Value)
				{
					Logger.LogInfo("Also updating character data in internal storage.");
					InternalDataStore.SaveAccountCharacter(character);
				}
				ZPackage val = new ZPackage();
				val.Write(DataObjects.yamlserializer.Serialize((object)list));
				ReturnConfiscatedItemsRPC.SendPackage(peerByPlatformID.m_uid, val);
				CharacterSaveRPC.SendPackage(peerByPlatformID.m_uid, SendCharacterAsZpackage(character));
			}
			yield break;
		}

		public static IEnumerator OnServerReceiveCheatReport(long sender, ZPackage package)
		{
			string text = package.ReadString();
			DataObjects.CheatSummaryReport cheatSummaryReport;
			try
			{
				cheatSummaryReport = DataObjects.yamldeserializer.Deserialize<DataObjects.CheatSummaryReport>(text);
			}
			catch (Exception ex)
			{
				Logger.LogWarning($"Failed to deserialize cheat report from {sender}: {ex.Message}");
				yield break;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			string playerName = cheatSummaryReport.PlayerName;
			string endPointString = peer.m_socket.GetEndPointString();
			Logger.LogWarning($"Cheat detection from {playerName} ({endPointString}): valheim-tooler: {cheatSummaryReport.ValheimToolerStatus} cheatengine: {cheatSummaryReport.CheatEngineStatus.IsCheatEngineDetected()}");
			string text2 = CheatDetectionAction.Value ?? "Log";
			if (peer == null)
			{
				Logger.LogWarning("Received cheat report for " + playerName + " but could not find corresponding peer. No action will be taken.");
				yield break;
			}
			switch (text2)
			{
			case "Kick":
				Logger.LogWarning("Kicking " + playerName + " for cheat usage.");
				ZNet.instance.Kick(playerName);
				break;
			case "Ban":
				Logger.LogWarning("Banning " + playerName + " for cheat usage.");
				KnownCheaterTracker.AddCheater(peer.m_socket.GetHostName(), BuildCheatReason(cheatSummaryReport));
				ZNet.instance.Ban(playerName);
				break;
			case "Log":
				break;
			}
		}

		private static string BuildCheatReason(DataObjects.CheatSummaryReport summary)
		{
			List<string> list = new List<string>();
			if (summary.ValheimToolerStatus)
			{
				list.Add("ValheimTooler");
			}
			if (summary.CheatEngineStatus != null && summary.CheatEngineStatus.IsCheatEngineDetected())
			{
				list.Add("CheatEngine");
			}
			string text = ((list.Count > 0) ? string.Join(", ", list) : "cheat detected");
			return "Cheat detection: " + text;
		}

		public static IEnumerator OnClientReceiveCheatReport(long sender, ZPackage package)
		{
			yield break;
		}

		public static IEnumerator OnClientReceiveListPlayer(long sender, ZPackage package)
		{
			foreach (KeyValuePair<string, List<string>> item in DataObjects.yamldeserializer.Deserialize<Dictionary<string, List<string>>>(package.ReadString()))
			{
				Logger.LogInfo("AccountID: " + item.Key);
				foreach (string item2 in item.Value)
				{
					Logger.LogInfo("    Character: " + item2);
				}
			}
			yield break;
		}

		public static IEnumerator OnServerReceiveListPlayer(long sender, ZPackage package)
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
			if (InternalStorageMode.Value)
			{
				dictionary = InternalDataStore.GetAccountRegistry();
				ListPlayerRPC.SendPackage(sender, new ZPackage(DataObjects.yamlserializer.Serialize((object)dictionary)));
				yield break;
			}
			foreach (string item in Directory.GetFiles(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters")).ToList())
			{
				List<string> list = Directory.GetFiles(item).ToList();
				string key = item.Split(new char[1] { '/' }).Last();
				List<string> list2 = new List<string>();
				foreach (string item2 in list)
				{
					list2.Add(item2.Split(new char[1] { '/' }).Last());
				}
				dictionary.Add(key, list2);
			}
			ListPlayerRPC.SendPackage(sender, new ZPackage(DataObjects.yamlserializer.Serialize((object)dictionary)));
		}

		public static IEnumerator OnClientReceiveConfiscatedItems(long sender, ZPackage package)
		{
			List<DataObjects.PackedItem> list = DataObjects.yamldeserializer.Deserialize<List<DataObjects.PackedItem>>(package.ReadString());
			Logger.LogInfo($"Received {list.Count} confiscated item(s) returned from server.");
			foreach (DataObjects.PackedItem item in list)
			{
				Logger.LogInfo($"Adding returned confiscated item: {item.prefabName} x{item.m_stack}");
				item.AddToInventory(Player.m_localPlayer, use_position: false);
			}
			yield break;
		}

		internal static IEnumerator OnServerRecieveDeltaItemUpdate(long sender, ZPackage package)
		{
			string text = package.ReadString();
			DataObjects.DeltaSummaryUpdate deltaSummaryUpdate;
			try
			{
				deltaSummaryUpdate = DataObjects.yamldeserializer.Deserialize<DataObjects.DeltaSummaryUpdate>(text);
			}
			catch (Exception ex)
			{
				Logger.LogWarning($"Failed to deserialize delta update from {sender}: {ex.Message}");
				yield break;
			}
			if (string.IsNullOrEmpty(deltaSummaryUpdate.Name) || string.IsNullOrEmpty(deltaSummaryUpdate.HostID))
			{
				Logger.LogWarning($"Malformed delta update from {sender}: missing CharacterName or HostName.");
				yield break;
			}
			string path = Path.Combine(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", deltaSummaryUpdate.HostID), deltaSummaryUpdate.Name + ".yaml");
			DataObjects.Character character;
			try
			{
				if (InternalStorageMode.Value)
				{
					Logger.LogInfo("Loading character for delta update with internal storage mode.");
					character = InternalDataStore.GetAccountCharacter(deltaSummaryUpdate.HostID, deltaSummaryUpdate.Name);
					if (character == null)
					{
						Logger.LogWarning("No character found in internal storage for " + deltaSummaryUpdate.Name + " (" + deltaSummaryUpdate.HostID + "). Delta dropped.");
						yield break;
					}
				}
				else
				{
					character = DataObjects.yamldeserializer.Deserialize<DataObjects.Character>(File.ReadAllText(path));
				}
			}
			catch (Exception ex2)
			{
				Logger.LogWarning("Failed to parse character save for delta update (" + deltaSummaryUpdate.Name + "): " + ex2.Message);
				yield break;
			}
			if (character == null)
			{
				Logger.LogWarning("Server Character does not exist for " + deltaSummaryUpdate.Name + " (" + deltaSummaryUpdate.HostID + "). Delta dropped.");
			}
			else
			{
				Logger.LogInfo($"Received delta update from {deltaSummaryUpdate.Name} ({deltaSummaryUpdate.HostID}): {deltaSummaryUpdate.ItemModifications?.Count ?? 0} item delta(s).");
				UpdatePlayerSaveWithDeltaData(deltaSummaryUpdate, character);
			}
		}

		public static IEnumerator OnClientReceiveDeltaItemUpdate(long sender, ZPackage package)
		{
			yield break;
		}

		internal static void UpdatePlayerSaveWithDeltaData(DataObjects.DeltaSummaryUpdate deltaSummary, DataObjects.Character character)
		{
			foreach (DataObjects.ItemDelta itemModification in deltaSummary.ItemModifications)
			{
				if (itemModification.Item.m_quality != 0)
				{
					_ = itemModification.Item.m_quality;
				}
				switch (itemModification.Op)
				{
				case DataObjects.ItemDeltaChangeType.Added:
					character.PlayerItems.Add(itemModification.Item);
					Logger.LogDebug($"Delta: added {itemModification.Item.prefabName} x{itemModification.Item.m_stack}.");
					break;
				case DataObjects.ItemDeltaChangeType.Removed:
					character.RemoveFromPlayerItems(itemModification.Item);
					break;
				}
			}
			Logger.LogDebug($"Applied {deltaSummary.ItemModifications.Count} item delta(s) for {character.Name}.");
			foreach (string removedCustomDataKey in deltaSummary.RemovedCustomDataKeys)
			{
				character.PlayerCustomData.Remove(removedCustomDataKey);
			}
			foreach (KeyValuePair<string, string> playerCustomDataModification in deltaSummary.PlayerCustomDataModifications)
			{
				if (character.PlayerCustomData.ContainsKey(playerCustomDataModification.Key))
				{
					character.PlayerCustomData[playerCustomDataModification.Key] = playerCustomDataModification.Value;
				}
				else
				{
					character.PlayerCustomData.Add(playerCustomDataModification.Key, playerCustomDataModification.Value);
				}
			}
			Logger.LogDebug("Updated custom data for " + character.Name + ".");
			character.SkillLevels = deltaSummary.SkillLevels;
			Logger.LogDebug("Updated skills for " + character.Name + ".");
			character.ActiveCharacterEffects = deltaSummary.ActiveCharacterEffects;
			if (InternalStorageMode.Value)
			{
				Logger.LogInfo("Saving character with internal storage mode.");
				InternalDataStore.SaveAccountCharacter(character);
			}
			character.LastDisconnect = deltaSummary.DisconnectionState;
			File.WriteAllText(Path.Combine(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters", deltaSummary.HostID), deltaSummary.Name + ".yaml"), DataObjects.yamlserializer.Serialize((object)character));
			Logger.LogInfo("Saved delta update for " + character.Name + ".");
		}

		internal static ZPackage SendCharacterAsZpackage(DataObjects.Character chara)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			string text = DataObjects.yamlserializer.Serialize((object)chara);
			ZPackage val = new ZPackage();
			val.Write(text);
			return val;
		}

		public static ZNetPeer GetPeerByPlatformID(string platformID)
		{
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.IsReady() && peer.m_socket.GetHostName() == platformID)
				{
					return peer;
				}
			}
			return null;
		}

		internal static void SetupMainFileWatcher()
		{
			ConfigFileWatcher.Register(cfg.ConfigFilePath, OnMainConfigFileChanged);
		}

		private static void OnMainConfigFileChanged(string _)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				Logger.LogInfo("Configuration file has been changed, reloading settings.");
				cfg.Reload();
			}
		}

		public static ConfigEntry<string> BindLocalConfig(string catagory, string key, string value, string description, bool advanced = false)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			return cfg.Bind<string>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = false,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<bool> BindLocalConfig(string catagory, string key, bool value, string description, bool advanced = false)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			return cfg.Bind<bool>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = false,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<List<string>> BindServerConfig(string catagory, string key, List<string> value, string description, bool advanced = false)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			return cfg.Bind<List<string>>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<float[]> BindServerConfig(string catagory, string key, float[] value, string description, bool advanced = false, float valmin = 0f, float valmax = 150f)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<float[]>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<bool> BindServerConfig(string catagory, string key, bool value, string description, AcceptableValueBase acceptableValues = null, bool advanced = false)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			return cfg.Bind<bool>(catagory, key, value, new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<int> BindServerConfig(string catagory, string key, int value, string description, bool advanced = false, int valmin = 0, int valmax = 150)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<int>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<float> BindServerConfig(string catagory, string key, float value, string description, bool advanced = false, float valmin = 0f, float valmax = 150f)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<float>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valmin, valmax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<string> BindServerConfig(string catagory, string key, string value, string description, AcceptableValueList<string> acceptableValues = null, bool advanced = false)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			return cfg.Bind<string>(catagory, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}
	}
	internal class Logger
	{
		public static LogLevel Level = (LogLevel)16;

		public static void EnableDebugLogging(object sender, EventArgs e)
		{
			CheckEnableDebugLogging();
		}

		public static void CheckEnableDebugLogging()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (ValConfig.EnableDebugMode.Value)
			{
				Level = (LogLevel)32;
			}
			else
			{
				Level = (LogLevel)16;
			}
		}

		public static void SetDebugLogging(bool state)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (state)
			{
				Level = (LogLevel)32;
			}
			else
			{
				Level = (LogLevel)16;
			}
		}

		public static void LogDebug(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)Level >= 32)
			{
				ValheimEnforcer.Log.LogInfo((object)message);
			}
		}

		public static void LogInfo(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)Level >= 16)
			{
				ValheimEnforcer.Log.LogInfo((object)message);
			}
		}

		public static void LogWarning(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			if ((int)Level >= 4)
			{
				ValheimEnforcer.Log.LogWarning((object)message);
			}
		}

		public static void LogError(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			if ((int)Level >= 2)
			{
				ValheimEnforcer.Log.LogError((object)message);
			}
		}
	}
	[BepInPlugin("MidnightsFX.ValheimEnforcer", "ValheimEnforcer", "0.9.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	internal class ValheimEnforcer : BaseUnityPlugin
	{
		public const string PluginGUID = "MidnightsFX.ValheimEnforcer";

		public const string PluginName = "ValheimEnforcer";

		public const string PluginVersion = "0.9.1";

		internal static ManualLogSource Log;

		internal ValConfig cfg;

		public static CustomLocalization Localization = LocalizationManager.Instance.GetLocalization();

		public static AssetBundle EmbeddedResourceBundle;

		public void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			cfg = new ValConfig(((BaseUnityPlugin)this).Config);
			EmbeddedResourceBundle = AssetUtils.LoadAssetBundleFromResources("ValheimEnforcer.assets.vebundle", typeof(ValheimEnforcer).Assembly);
			PrefabManager.OnPrefabsRegistered += ModManager.SetModsActive;
			ZoneManager.OnLocationsRegistered += InternalDataStore.InstanciateOrLinkMetadataRegistry;
			PrefabManager.OnVanillaPrefabsAvailable += ModManager.SetModsActive;
			GUIManager.OnCustomGUIAvailable += ModManager.AddErrorMessageDetailsForMenu;
			InternalDataStore.RegisterMetadataHolder();
			TerminalCommands.AddCommands();
			MinimapManager.OnVanillaMapDataLoaded += CheatDetector.Initialize;
			MinimapManager.OnVanillaMapDataLoaded += CharacterDeltaTracker.Initialize;
			ModCompatability.CheckModCompat();
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
		}
	}
}
namespace ValheimEnforcer.modules
{
	internal static class InternalDataStore
	{
		private static ZDO MetadataRegistry;

		internal static void SaveAccountCharacter(DataObjects.Character character)
		{
			UpdateAccountRegistry(character.HostID, character.Name);
			string text = MetadataRegistry.GetString(character.HostID, (string)null);
			if (text != null)
			{
				DataObjects.CharacterSaveData characterSaveData = DataObjects.yamldeserializer.Deserialize<DataObjects.CharacterSaveData>(text);
				if (characterSaveData.SavedCharacters.ContainsKey(character.Name))
				{
					characterSaveData.SavedCharacters[character.Name] = character;
				}
				else
				{
					characterSaveData.SavedCharacters.Add(character.Name, character);
				}
				string text2 = DataObjects.yamlserializer.Serialize((object)characterSaveData);
				MetadataRegistry.Set(character.HostID, text2);
			}
			else
			{
				DataObjects.CharacterSaveData characterSaveData2 = new DataObjects.CharacterSaveData
				{
					SavedCharacters = new Dictionary<string, DataObjects.Character> { { character.Name, character } }
				};
				string text3 = DataObjects.yamlserializer.Serialize((object)characterSaveData2);
				MetadataRegistry.Set(character.HostID, text3);
			}
		}

		internal static DataObjects.Character GetAccountCharacter(string accountID, string characterName)
		{
			InstanciateOrLinkMetadataRegistry();
			string text = MetadataRegistry.GetString(accountID, (string)null);
			if (text != null)
			{
				Logger.LogDebug("Character data found " + accountID + "-" + characterName + ".");
				DataObjects.CharacterSaveData characterSaveData = DataObjects.yamldeserializer.Deserialize<DataObjects.CharacterSaveData>(text);
				if (characterSaveData.SavedCharacters.ContainsKey(characterName))
				{
					return characterSaveData.SavedCharacters[characterName];
				}
			}
			return null;
		}

		internal static DataObjects.CharacterSaveData GetAccountData(string accountID)
		{
			InstanciateOrLinkMetadataRegistry();
			string text = MetadataRegistry.GetString(accountID, (string)null);
			if (text != null)
			{
				return DataObjects.yamldeserializer.Deserialize<DataObjects.CharacterSaveData>(text);
			}
			return null;
		}

		internal static void RegisterMetadataHolder()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			CustomPrefab val = new CustomPrefab(ValheimEnforcer.EmbeddedResourceBundle.LoadAsset<GameObject>("VE_METADATA"), false);
			PrefabManager.Instance.AddPrefab(val);
		}

		internal static void InstanciateOrLinkMetadataRegistry()
		{
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			if (MetadataRegistry != null)
			{
				return;
			}
			string text = default(string);
			if (ZoneSystem.instance.GetGlobalKey(DataObjects.CustomDataKey ?? "", ref text))
			{
				string[] array = text.Split(new char[1] { ' ' });
				if (array.Length == 2 && long.TryParse(array[0], out var result) && uint.TryParse(array[1], out var result2))
				{
					ZDOID val = default(ZDOID);
					((ZDOID)(ref val))..ctor(result, result2);
					MetadataRegistry = ZDOMan.instance.GetZDO(val);
				}
			}
			long sessionID = ZDOMan.GetSessionID();
			ZDO val2 = ZDOMan.instance.CreateNewZDO(Vector3.zero, 0);
			val2.Persistent = true;
			val2.SetOwner(sessionID);
			MetadataRegistry = val2;
			ZoneSystem.instance.SetGlobalKey($"{DataObjects.CustomDataKey} {((ZDOID)(ref MetadataRegistry.m_uid)).UserID} {((ZDOID)(ref MetadataRegistry.m_uid)).ID}");
			Logger.LogInfo($"Hooking up Metadata Registry. SessionID:{sessionID} ZDO:{val2.m_uid}");
			Logger.LogInfo($"Setting globalkey: {DataObjects.CustomDataKey} {((ZDOID)(ref MetadataRegistry.m_uid)).UserID} {((ZDOID)(ref MetadataRegistry.m_uid)).ID}");
		}

		internal static void UpdateAccountRegistry(string accountID, string chara = null)
		{
			InstanciateOrLinkMetadataRegistry();
			string text = MetadataRegistry.GetString("VE_ACCOUNTS", (string)null);
			if (text != null)
			{
				Dictionary<string, List<string>> dictionary = DataObjects.yamldeserializer.Deserialize<Dictionary<string, List<string>>>(text);
				if (!dictionary.ContainsKey(accountID))
				{
					if (chara != null)
					{
						dictionary[accountID] = new List<string> { chara };
					}
					else
					{
						dictionary[accountID] = new List<string>();
					}
					string text2 = DataObjects.yamlserializer.Serialize((object)dictionary);
					MetadataRegistry.Set("VE_ACCOUNTS", text2);
				}
			}
			else
			{
				List<string> list = new List<string>();
				if (chara != null)
				{
					list.Add(chara);
				}
				Dictionary<string, List<string>> dictionary2 = new Dictionary<string, List<string>> { { accountID, list } };
				string text3 = DataObjects.yamlserializer.Serialize((object)dictionary2);
				MetadataRegistry.Set("VE_ACCOUNTS", text3);
			}
		}

		internal static Dictionary<string, List<string>> GetAccountRegistry()
		{
			InstanciateOrLinkMetadataRegistry();
			string text = MetadataRegistry.GetString("VE_ACCOUNTS", (string)null);
			if (text != null)
			{
				return DataObjects.yamldeserializer.Deserialize<Dictionary<string, List<string>>>(text);
			}
			return new Dictionary<string, List<string>>();
		}
	}
	internal static class ModManager
	{
		internal static class ValidateMods
		{
			[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
			public static class ZNet_OnNewConnection_Patch
			{
				[HarmonyPrefix]
				[HarmonyPriority(800)]
				private static void Prefix(ZNet __instance, ZNetPeer peer)
				{
					Logger.LogDebug("New Connection, register VE Mod Sync RPC.");
					peer.m_rpc.Register<ZPackage>("RPC_ReceiveModVersionData", (Action<ZRpc, ZPackage>)RPC_ReceiveModVersionData);
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
		public static class ZNet_RPC_ClientHandshake_Patch
		{
			[HarmonyPrefix]
			[HarmonyPriority(800)]
			private static void Prefix(ZNet __instance, ZRpc rpc)
			{
				if (ZNetExtension.IsClientInstance(__instance))
				{
					Logger.LogDebug("Client sending mod version data to server");
					rpc.Invoke("RPC_ReceiveModVersionData", new object[1] { ModSettings.ToZPackage() });
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_ServerHandshake")]
		public static class ZNet_RPC_ServerHandshake_Patch
		{
			[HarmonyPrefix]
			[HarmonyPriority(800)]
			private static void Prefix(ZNet __instance, ZRpc rpc)
			{
				if (__instance.IsServer())
				{
					Logger.LogDebug("Server sending mod version data to client");
					rpc.Invoke("RPC_ReceiveModVersionData", new object[1] { ModSettings.ToZPackage() });
				}
			}
		}

		public class JotunnDetailDisconnectExpansion : MonoBehaviour
		{
			private GameObject ContentView;

			private Text HeaderText;

			private Text FooterText;

			private static string HeaderMessage = "";

			private static string FooterMessage = "";

			private bool textset;

			public void UpdateErrorText(string header, string footer)
			{
				Logger.LogDebug("Set Error results " + header + " " + footer);
				HeaderMessage = header;
				FooterMessage = footer;
				textset = false;
			}

			public void Update()
			{
				if ((Object)(object)GUIManager.CustomGUIFront == (Object)null)
				{
					return;
				}
				Transform val = GUIManager.CustomGUIFront.transform.Find("CompatibilityWindow(Clone)/Scroll View/Viewport/Content");
				if ((Object)(object)val == (Object)null)
				{
					textset = false;
				}
				else if (!textset)
				{
					((Component)GUIManager.CustomGUIFront.transform.Find("CompatibilityWindow(Clone)/Scroll View")).GetComponent<ScrollRect>().scrollSensitivity = 1000f;
					ContentView = ((Component)val).gameObject;
					Transform val2 = ContentView.transform.Find("Failed Connection Text");
					if ((Object)(object)val2 != (Object)null)
					{
						HeaderText = ((Component)val2).GetComponent<Text>();
					}
					else
					{
						Logger.LogDebug("Could not find HeaderText");
					}
					Transform val3 = ContentView.transform.Find("Error Messages Text");
					if ((Object)(object)val3 != (Object)null)
					{
						FooterText = ((Component)val3).GetComponent<Text>();
					}
					else
					{
						Logger.LogDebug("Could not find FooterText");
					}
					if ((Object)(object)HeaderText != (Object)null && !string.IsNullOrEmpty(HeaderMessage))
					{
						HeaderText.text = "<color=#FFA13C>Failed Connection:</color>\n" + HeaderMessage;
					}
					if ((Object)(object)FooterText != (Object)null && !string.IsNullOrEmpty(FooterMessage))
					{
						FooterText.text = "<color=#FFA13C>Further Steps:</color>\n" + FooterMessage;
					}
					Logger.LogDebug("Set error results. H:" + HeaderMessage + " F:" + FooterMessage);
					textset = true;
				}
			}
		}

		internal static Dictionary<string, BaseUnityPlugin> ActiveMods = new Dictionary<string, BaseUnityPlugin>();

		internal static DataObjects.Mods ModSettings { get; set; }

		internal static JotunnDetailDisconnectExpansion DetailsUpdater { get; set; }

		private static string ResolvePeerName(ZRpc rpc)
		{
			ZNet instance = ZNet.instance;
			ZNetPeer val = ((instance != null) ? instance.GetPeer(rpc) : null);
			if (!string.IsNullOrEmpty(val?.m_playerName))
			{
				return val.m_playerName;
			}
			return null;
		}

		internal static void SetModsActive()
		{
			ActiveMods.Clear();
			ActiveMods = BepInExUtils.GetPlugins(true);
			ModSettings = new DataObjects.Mods();
			Logger.LogDebug($"Detected {ActiveMods.Keys.Count} mods.");
			LoadConfig(File.ReadAllText(ValConfig.ModsConfigFilePath));
			ModSettings.ActiveMods.Clear();
			foreach (KeyValuePair<string, BaseUnityPlugin> activeMod in ActiveMods)
			{
				if (!ModSettings.ActiveMods.ContainsKey(activeMod.Key))
				{
					Logger.LogDebug("Adding Mod " + activeMod.Key + " not found in modlist");
					ModSettings.ActiveMods.Add(activeMod.Key, new DataObjects.Mod
					{
						EnforceVersion = true,
						Version = activeMod.Value.Info.Metadata.Version.ToString(),
						PluginID = activeMod.Value.Info.Metadata.GUID,
						Name = activeMod.Value.Info.Metadata.Name
					});
				}
				Logger.LogDebug($"Found active mod: {activeMod.Key} v{activeMod.Value.Info.Metadata.Version}");
				string currentVersion = activeMod.Value.Info.Metadata.Version.ToString();
				if (ModSettings.RequiredMods.ContainsKey(activeMod.Key))
				{
					UpdateModVersionIfChanged(ModSettings.RequiredMods, activeMod.Key, currentVersion);
				}
				else if (ModSettings.AdminOnlyMods.ContainsKey(activeMod.Key))
				{
					UpdateModVersionIfChanged(ModSettings.AdminOnlyMods, activeMod.Key, currentVersion);
				}
				else if (ModSettings.OptionalMods.ContainsKey(activeMod.Key))
				{
					UpdateModVersionIfChanged(ModSettings.OptionalMods, activeMod.Key, currentVersion);
				}
				else if (ModSettings.ServerOnlyMods.ContainsKey(activeMod.Key))
				{
					UpdateModVersionIfChanged(ModSettings.ServerOnlyMods, activeMod.Key, currentVersion);
				}
				else if (ValConfig.AutoAddModsToRequired.Value)
				{
					Logger.LogDebug("Automatically adding " + activeMod.Key + " as a required mod.");
					ModSettings.RequiredMods.Add(activeMod.Key, new DataObjects.Mod
					{
						EnforceVersion = false,
						Version = activeMod.Value.Info.Metadata.Version.ToString(),
						PluginID = activeMod.Value.Info.Metadata.GUID,
						Name = activeMod.Value.Info.Metadata.Name
					});
				}
			}
			if (ValConfig.UpdateLoadedModsOnStartup.Value)
			{
				Logger.LogDebug("Updated Mods.yaml.");
				File.WriteAllText(ValConfig.ModsConfigFilePath, DataObjects.yamlserializer.Serialize((object)ModSettings));
			}
		}

		private static void UpdateModVersionIfChanged(Dictionary<string, DataObjects.Mod> modList, string key, string currentVersion)
		{
			if (modList[key].Version != currentVersion)
			{
				Logger.LogInfo("Updating version for " + key + ": " + modList[key].Version + " -> " + currentVersion);
				modList[key].Version = currentVersion;
			}
		}

		internal static void UpdateModSettingConfigs(string yamlstring)
		{
			try
			{
				ModSettings = DataObjects.yamldeserializer.Deserialize<DataObjects.Mods>(yamlstring);
			}
			catch
			{
				Logger.LogWarning("Failed to deserialize mod configurations.");
			}
		}

		internal static bool ValidateModlist(DataObjects.Mods CheckingMods, DataObjects.Mods AuthoratativeMods, bool isAdmin, bool adminStatusKnown, out string summay, out string details)
		{
			summay = "";
			details = "";
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			List<string> list4 = new List<string>();
			List<string> list5 = AuthoratativeMods.RequiredMods.Keys.Distinct().ToList();
			Logger.LogDebug($"Validating modlist of {CheckingMods.ActiveMods.Count} mods isAdmin? {isAdmin}");
			foreach (KeyValuePair<string, DataObjects.Mod> activeMod in CheckingMods.ActiveMods)
			{
				list5.Remove(activeMod.Key);
				if (AuthoratativeMods.RequiredMods.ContainsKey(activeMod.Key))
				{
					if (!AuthoratativeMods.RequiredMods[activeMod.Key].EnforceVersion || AuthoratativeMods.RequiredMods[activeMod.Key].Version == activeMod.Value.Version)
					{
						continue;
					}
					list2.Add(activeMod.Key);
				}
				if (AuthoratativeMods.AdminOnlyMods.ContainsKey(activeMod.Key))
				{
					if (!adminStatusKnown)
					{
						list4.Add(activeMod.Key);
					}
					else if (isAdmin)
					{
						if (AuthoratativeMods.AdminOnlyMods[activeMod.Key].EnforceVersion && AuthoratativeMods.AdminOnlyMods[activeMod.Key].Version != activeMod.Value.Version)
						{
							list2.Add(activeMod.Key);
						}
					}
					else
					{
						list3.Add(activeMod.Key);
					}
					continue;
				}
				if (AuthoratativeMods.OptionalMods.ContainsKey(activeMod.Key))
				{
					if (!AuthoratativeMods.OptionalMods[activeMod.Key].EnforceVersion || AuthoratativeMods.OptionalMods[activeMod.Key].Version == activeMod.Value.Version)
					{
						continue;
					}
					list2.Add(activeMod.Key);
				}
				list.Add(activeMod.Key);
			}
			if (list2.Count > 0)
			{
				Logger.LogWarning("Mods version mismatch with the server found:");
				summay = "A Mod mismatch was detected. Ensure you have the correct versions and are only using allowed mods.";
			}
			if (list5.Count > 0)
			{
				string text = "\nMissing required mods: " + string.Join(", ", list5);
				summay += text;
				Logger.LogWarning(text);
			}
			if (list.Count > 0)
			{
				string text2 = "\nNon-allowed mods found: " + string.Join(", ", list);
				summay += text2;
				Logger.LogWarning(text2);
			}
			if (list3.Count > 0)
			{
				string text3 = "\nAdmin-only mods not permitted for non-admins: " + string.Join(", ", list3);
				summay += text3;
				Logger.LogWarning(text3);
			}
			if (list4.Count > 0)
			{
				string text4 = "\nThis server restricts some mods to admins; if you are not an admin you will be disconnected: " + string.Join(", ", list4);
				summay += text4;
				Logger.LogInfo(text4);
			}
			if (list2.Count > 0 || list5.Count > 0 || list.Count > 0 || list3.Count > 0 || list4.Count > 0)
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("\n<b>ValheimEnforcer - Mod Validation Failed</b>");
				if (list2.Count > 0)
				{
					stringBuilder.AppendLine("\n<b>Version Mismatches:</b>");
					foreach (string item in list2)
					{
						stringBuilder.AppendLine("  • " + item);
					}
				}
				if (list5.Count > 0)
				{
					stringBuilder.AppendLine("\n<b>Missing Required Mods:</b>");
					foreach (string item2 in list5)
					{
						stringBuilder.AppendLine("  • " + item2);
					}
				}
				if (list.Count > 0)
				{
					stringBuilder.AppendLine("\n<b>Non-Allowed Mods:</b>");
					foreach (string item3 in list)
					{
						stringBuilder.AppendLine("  • " + item3);
					}
				}
				if (list3.Count > 0)
				{
					stringBuilder.AppendLine("\n<b>Admin-Only Mods (not permitted):</b>");
					foreach (string item4 in list3)
					{
						stringBuilder.AppendLine("  • " + item4);
					}
				}
				if (list4.Count > 0)
				{
					stringBuilder.AppendLine("\n<b>Admin-Only Mods (require admin):</b>");
					foreach (string item5 in list4)
					{
						stringBuilder.AppendLine("  • " + item5);
					}
				}
				string text5 = stringBuilder.ToString();
				details = text5;
				return false;
			}
			Logger.LogInfo("Client mod list validated successfully.");
			return true;
		}

		internal static void LoadConfig(string yaml)
		{
			ModSettings = DataObjects.yamldeserializer.Deserialize<DataObjects.Mods>(yaml);
		}

		internal static string GetDefaultConfig()
		{
			if (ModSettings != null)
			{
				return DataObjects.yamlserializer.Serialize((object)ModSettings);
			}
			return DataObjects.yamlserializer.Serialize((object)new DataObjects.Mods());
		}

		private static void RPC_ReceiveModVersionData(ZRpc sender, ZPackage data)
		{
			Logger.LogDebug("Received mod version data from " + sender.m_socket.GetEndPointString());
			string endPointString = sender.m_socket.GetEndPointString();
			if (!ZNet.instance.IsServer())
			{
				DataObjects.Mods mods = new DataObjects.Mods().FromZPackage(data);
				Logger.LogDebug($"Client received server mod data: Required: {mods.RequiredMods.Count}, Optional: {mods.OptionalMods.Count}, AdminOnly: {mods.AdminOnlyMods.Count} mods");
				string summay;
				string details;
				bool num = ValidateModlist(ModSettings, mods, isAdmin: false, adminStatusKnown: false, out summay, out details);
				DetailsUpdater?.UpdateErrorText(summay, details);
				if (!num)
				{
					Logger.LogWarning("Mod compatibility check failed for client.");
				}
				return;
			}
			DataObjects.Mods mods2 = new DataObjects.Mods().FromZPackage(data);
			bool flag = ZNet.instance.IsAdmin(sender.m_socket.GetHostName());
			Logger.LogDebug($"Server received server mod data from {endPointString} Admin?{flag}: Required: {mods2.RequiredMods.Count}, Optional: {mods2.OptionalMods.Count}, AdminOnly: {mods2.AdminOnlyMods.Count} mods");
			if (!ValidateModlist(mods2, ModSettings, flag, adminStatusKnown: true, out var summay2, out var _))
			{
				Logger.LogWarning("Mod compatibility check failed for client at " + endPointString + "\n" + summay2);
				if (ValConfig.DiscordNotifyWrongMods.Value)
				{
					string value = ResolvePeerName(sender) ?? endPointString;
					DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Connection Rejected: Mod Mismatch", summay2.Trim(), 15548997).AddField("Player", value, inline: true).ToMessage());
				}
				sender.Invoke("Error", new object[1] { 3 });
			}
		}

		internal static void AddErrorMessageDetailsForMenu()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).name.Equals("start"))
			{
				DetailsUpdater = GUIManager.CustomGUIFront.AddComponent<JotunnDetailDisconnectExpansion>();
			}
		}
	}
}
namespace ValheimEnforcer.modules.notifications
{
	internal static class DiscordNotifier
	{
		private static readonly HttpClient http = new HttpClient
		{
			Timeout = TimeSpan.FromSeconds(10.0)
		};

		internal static void Initialize()
		{
			string value = ValConfig.DiscordWebhookUrl.Value;
			if (string.IsNullOrWhiteSpace(value))
			{
				Logger.LogInfo("Discord notifications: no webhook URL configured, disabled.");
			}
			else if (!IsValidWebhookUrl(value))
			{
				Logger.LogWarning("Discord notifications: configured webhook URL is invalid, disabled. Expected https://discord.com/api/webhooks/...");
			}
			else
			{
				Logger.LogInfo("Discord notifications enabled.");
			}
		}

		internal static bool IsValidWebhookUrl(string url)
		{
			if (string.IsNullOrWhiteSpace(url))
			{
				return false;
			}
			if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result))
			{
				return false;
			}
			if (result.Scheme != "https")
			{
				return false;
			}
			if (result.Host == "discord.com" || result.Host == "discordapp.com" || result.Host == "ptb.discord.com" || result.Host == "canary.discord.com")
			{
				return result.AbsolutePath.StartsWith("/api/webhooks/", StringComparison.Ordinal);
			}
			return false;
		}

		private static bool IsActive(out string url)
		{
			url = ValConfig.DiscordWebhookUrl.Value;
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				return IsValidWebhookUrl(url);
			}
			return false;
		}

		internal static void SendAsync(DataObjects.DiscordMessage message)
		{
			if (message != null && IsActive(out var url))
			{
				Task.Run(() => Post(url, message));
			}
		}

		internal static void SendSync(DataObjects.DiscordMessage message)
		{
			if (message == null || !IsActive(out var url))
			{
				return;
			}
			try
			{
				Post(url, message).Wait(TimeSpan.FromSeconds(8.0));
			}
			catch (Exception ex)
			{
				Logger.LogWarning("Discord notifications: synchronous send failed: " + ex.Message);
			}
		}

		private static async Task Post(string url, DataObjects.DiscordMessage message)
		{
			try
			{
				StringContent content = new StringContent(message.ToJson(), Encoding.UTF8, "application/json");
				try
				{
					HttpResponseMessage val = await http.PostAsync(url, (HttpContent)(object)content).ConfigureAwait(continueOnCapturedContext: false);
					if (!val.IsSuccessStatusCode)
					{
						Logger.LogWarning($"Discord notifications: webhook returned HTTP {(int)val.StatusCode}.");
					}
				}
				finally
				{
					((IDisposable)content)?.Dispose();
				}
			}
			catch (Exception ex)
			{
				Logger.LogWarning("Discord notifications: send failed: " + ex.Message);
			}
		}
	}
	internal static class NotificationPatches
	{
		[HarmonyPatch(typeof(ZNet), "Start")]
		public static class ZNet_Start_Patch
		{
			[HarmonyPostfix]
			private static void Postfix(ZNet __instance)
			{
				if (__instance.IsServer())
				{
					AnnouncedPeers.Clear();
					DiscordNotifier.Initialize();
					if (ValConfig.DiscordNotifyServerStartup.Value)
					{
						DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Server Online", "The server has started and is accepting connections.", 5763719).ToMessage());
					}
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		public static class ZNet_Shutdown_Patch
		{
			[HarmonyPrefix]
			private static void Prefix(ZNet __instance)
			{
				if (__instance.IsServer() && ValConfig.DiscordNotifyServerShutdown.Value)
				{
					DiscordNotifier.SendSync(new DataObjects.DiscordEmbed("Server Offline", "The server is shutting down.", 9807270).ToMessage());
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		public static class ZNet_RPC_PeerInfo_Patch
		{
			[HarmonyPostfix]
			private static void Postfix(ZNet __instance, ZRpc rpc)
			{
				if (__instance.IsServer())
				{
					ZNetPeer peer = __instance.GetPeer(rpc);
					if (peer != null && !string.IsNullOrEmpty(peer.m_playerName) && AnnouncedPeers.Add(peer.m_uid) && ValConfig.DiscordNotifyPlayerJoined.Value)
					{
						DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Player Joined", null, 5763719).AddField("Player", peer.m_playerName, inline: true).ToMessage());
					}
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		public static class ZNet_Disconnect_Patch
		{
			[HarmonyPrefix]
			private static void Prefix(ZNet __instance, ZNetPeer peer)
			{
				if (__instance.IsServer() && peer != null && AnnouncedPeers.Remove(peer.m_uid) && ValConfig.DiscordNotifyPlayerLeft.Value)
				{
					DataObjects.DisconnectionState num = ResolveSavedDataState(peer);
					int value = ValConfig.DeltaSynchronizationFrequencyInSeconds.Value;
					bool flag = num == DataObjects.DisconnectionState.Clean;
					string value2 = (flag ? "Clean logout" : "Disconnected");
					string value3 = (flag ? "✅ Player Data up to date." : $"⚠\ufe0f Stale — Data outdated by {value}s");
					DiscordNotifier.SendAsync(new DataObjects.DiscordEmbed("Player Left", null, flag ? 5763719 : 16705372).AddField("Player", peer.m_playerName, inline: true).AddField("Disconnect", value2, inline: true).AddField("Saved data", value3)
						.ToMessage());
				}
			}
		}

		private static readonly HashSet<long> AnnouncedPeers = new HashSet<long>();

		private static DataObjects.DisconnectionState ResolveSavedDataState(ZNetPeer peer)
		{
			try
			{
				string text = peer.m_socket.GetHostName();
				if (!string.IsNullOrEmpty(text) && text.Contains(":"))
				{
					text = text.Split(new char[1] { ':' })[0];
				}
				DataObjects.Character character = ValConfig.LoadCharacterFromSave(text, peer.m_playerName);
				if (character == null)
				{
					Logger.LogDebug("Discord notifications: no saved character for " + peer.m_playerName + " (" + text + "); reporting saved data as stale.");
					return DataObjects.DisconnectionState.DirtyDisconnect;
				}
				return character.LastDisconnect;
			}
			catch (Exception ex)
			{
				Logger.LogDebug("Discord notifications: failed to resolve saved-data state for " + peer.m_playerName + ": " + ex.Message);
				return DataObjects.DisconnectionState.DirtyDisconnect;
			}
		}
	}
}
namespace ValheimEnforcer.modules.compat
{
	internal class ModCompatability
	{
		public static bool IsExtraSlotsEnabled;

		public static bool IsTrialsOfToilEnabled;

		internal static void CheckModCompat()
		{
			try
			{
				Dictionary<string, BaseUnityPlugin> plugins = BepInExUtils.GetPlugins(false);
				if (plugins != null)
				{
					if (Enumerable.Contains(plugins.Keys, "shudnal.ExtraSlots"))
					{
						IsExtraSlotsEnabled = API.IsReady();
					}
					if (Enumerable.Contains(plugins.Keys, "maxfoxgaming.environmentalawareness"))
					{
						IsTrialsOfToilEnabled = true;
					}
				}
			}
			catch
			{
				Logger.LogWarning("Unable to check mod compatibility. Ensure that Bepinex can load.");
			}
		}
	}
}
namespace ValheimEnforcer.modules.compat.ExtraSlots
{
	internal static class Extensions
	{
		internal static ExtraSlot ToExtraSlot(this object slot)
		{
			return new ExtraSlot
			{
				_id = () => (string)AccessTools.Property(API._typeSlot, "ID").GetValue(slot),
				_name = () => (string)AccessTools.Property(API._typeSlot, "Name").GetValue(slot),
				_gridPosition = () => (Vector2i)AccessTools.Property(API._typeSlot, "GridPosition").GetValue(slot),
				_item = () => (ItemData)AccessTools.Property(API._typeSlot, "Item").GetValue(slot),
				_itemFits = (ItemData item) => (bool)AccessTools.Method(API._typeSlot, "ItemFits", (Type[])null, (Type[])null).Invoke(slot, new object[1] { item }),
				_isActive = () => (bool)AccessTools.Property(API._typeSlot, "IsActive").GetValue(slot),
				_isFree = () => (bool)AccessTools.Property(API._typeSlot, "IsFree").GetValue(slot),
				_isHotkeySlot = () => (bool)AccessTools.Property(API._typeSlot, "IsHotkeySlot").GetValue(slot),
				_isEquipmentSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsEquipmentSlot").GetValue(slot),
				_isQuickSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsQuickSlot").GetValue(slot),
				_isMiscSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsMiscSlot").GetValue(slot),
				_isAmmoSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsAmmoSlot").GetValue(slot),
				_isFoodSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsFoodSlot").GetValue(slot),
				_isCustomSlot = () => (bool)AccessTools.Property(API._typeSlot, "IsCustomSlot").GetValue(slot),
				_isEmptySlot = () => (bool)AccessTools.Property(API._typeSlot, "IsEmptySlot").GetValue(slot)
			};
		}
	}
	public class ExtraSlot
	{
		internal Func<string> _id;

		internal Func<string> _name;

		internal Func<Vector2i> _gridPosition;

		internal Func<ItemData> _item;

		internal Func<ItemData, bool> _itemFits;

		internal Func<bool> _isActive;

		internal Func<bool> _isFree;

		internal Func<bool> _isHotkeySlot;

		internal Func<bool> _isEquipmentSlot;

		internal Func<bool> _isQuickSlot;

		internal Func<bool> _isMiscSlot;

		internal Func<bool> _isAmmoSlot;

		internal Func<bool> _isFoodSlot;

		internal Func<bool> _isCustomSlot;

		internal Func<bool> _isEmptySlot;

		public static readonly Vector2i emptyPosition = new Vector2i(-1, -1);

		public string ID
		{
			get
			{
				if (_id == null)
				{
					return "";
				}
				return _id();
			}
		}

		public string Name
		{
			get
			{
				if (_name == null)
				{
					return "";
				}
				return _name();
			}
		}

		public Vector2i GridPosition
		{
			get
			{
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				if (_gridPosition == null)
				{
					return emptyPosition;
				}
				return _gridPosition();
			}
		}

		public ItemData Item
		{
			get
			{
				if (_item == null)
				{
					return null;
				}
				return _item();
			}
		}

		public bool IsActive
		{
			get
			{
				if (_isActive != null)
				{
					return _isActive();
				}
				return false;
			}
		}

		public bool IsFree
		{
			get
			{
				if (_isFree != null)
				{
					return _isFree();
				}
				return false;
			}
		}

		public bool IsHotkeySlot
		{
			get
			{
				if (_isHotkeySlot != null)
				{
					return _isHotkeySlot();
				}
				return false;
			}
		}

		public bool IsEquipmentSlot
		{
			get
			{
				if (_isEquipmentSlot != null)
				{
					return _isEquipmentSlot();
				}
				return false;
			}
		}

		public bool IsQuickSlot
		{
			get
			{
				if (_isQuickSlot != null)
				{
					return _isQuickSlot();
				}
				return false;
			}
		}

		public bool IsMiscSlot
		{
			get
			{
				if (_isMiscSlot != null)
				{
					return _isMiscSlot();
				}
				return false;
			}
		}

		public bool IsAmmoSlot
		{
			get
			{
				if (_isAmmoSlot != null)
				{
					return _isAmmoSlot();
				}
				return false;
			}
		}

		public bool IsFoodSlot
		{
			get
			{
				if (_isFoodSlot != null)
				{
					return _isFoodSlot();
				}
				return false;
			}
		}

		public bool IsCustomSlot
		{
			get
			{
				if (_isCustomSlot != null)
				{
					return _isCustomSlot();
				}
				return false;
			}
		}

		public bool IsEmptySlot
		{
			get
			{
				if (_isEmptySlot != null)
				{
					return _isEmptySlot();
				}
				return false;
			}
		}

		public bool ItemFits(ItemData item)
		{
			if (_itemFits != null)
			{
				return _itemFits(item);
			}
			return false;
		}
	}
	public static class API
	{
		private static bool _isNotReady;

		private static readonly List<ItemData> _emptyItemList = new List<ItemData>();

		private static readonly List<ExtraSlot> _emptySlotList = new List<ExtraSlot>();

		internal static Type _typeAPI;

		internal static Type _typeSlot;

		public static bool IsReady()
		{
			if (_isNotReady)
			{
				return false;
			}
			if (_typeAPI != null && _typeSlot != null)
			{
				return true;
			}
			_isNotReady = !Chainloader.PluginInfos.ContainsKey("shudnal.ExtraSlots");
			if (_isNotReady)
			{
				return false;
			}
			if (_typeAPI == null || _typeSlot == null)
			{
				Assembly assembly = Assembly.GetAssembly(((object)Chainloader.PluginInfos["shudnal.ExtraSlots"].Instance).GetType());
				if (assembly == null)
				{
					_isNotReady = true;
					return false;
				}
				_typeAPI = assembly.GetType("ExtraSlots.API");
				_typeSlot = assembly.GetType("ExtraSlots.Slots+Slot");
			}
			if (_typeAPI != null)
			{
				return _typeSlot != null;
			}
			return false;
		}

		public static List<ExtraSlot> GetExtraSlots()
		{
			if (IsReady())
			{
				return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetExtraSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList();
			}
			return _emptySlotList;
		}

		public static List<ExtraSlot> GetEquipmentSlots()
		{
			if (IsReady())
			{
				return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetEquipmentSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList();
			}
			return _emptySlotList;
		}

		public static List<ExtraSlot> GetQuickSlots()
		{
			if (IsReady())
			{
				return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetQuickSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList();
			}
			return _emptySlotList;
		}

		public static List<ExtraSlot> GetFoodSlots()
		{
			if (IsReady())
			{
				return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetFoodSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList();
			}
			return _emptySlotList;
		}

		public static List<ExtraSlot> GetAmmoSlots()
		{
			if (IsReady())
			{
				return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetAmmoSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList();
			}
			return _emptySlotList;
		}

		public static List<ExtraSlot> GetMiscSlots()
		{
			if (IsReady())
			{
				return ((IEnumerable<object>)AccessTools.Method(_typeAPI, "GetMiscSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null)).Select((object slot) => slot.ToExtraSlot()).ToList();
			}
			return _emptySlotList;
		}

		public static ExtraSlot FindSlot(string slotID)
		{
			if (!IsReady())
			{
				return null;
			}
			return AccessTools.Method(_typeAPI, "FindSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { slotID }).ToExtraSlot();
		}

		public static List<ItemData> GetAllExtraSlotsItems()
		{
			if (IsReady())
			{
				return (List<ItemData>)AccessTools.Method(_typeAPI, "GetAllExtraSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return _emptyItemList;
		}

		public static List<ItemData> GetEquipmentSlotsItems()
		{
			if (IsReady())
			{
				return (List<ItemData>)AccessTools.Method(_typeAPI, "GetEquipmentSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return _emptyItemList;
		}

		public static List<ItemData> GetQuickSlotsItems()
		{
			if (IsReady())
			{
				return (List<ItemData>)AccessTools.Method(_typeAPI, "GetQuickSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return _emptyItemList;
		}

		public static List<ItemData> GetFoodSlotsItems()
		{
			if (IsReady())
			{
				return (List<ItemData>)AccessTools.Method(_typeAPI, "GetFoodSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return _emptyItemList;
		}

		public static List<ItemData> GetAmmoSlotsItems()
		{
			if (IsReady())
			{
				return (List<ItemData>)AccessTools.Method(_typeAPI, "GetAmmoSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return _emptyItemList;
		}

		public static List<ItemData> GetMiscSlotsItems()
		{
			if (IsReady())
			{
				return (List<ItemData>)AccessTools.Method(_typeAPI, "GetMiscSlotsItems", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return _emptyItemList;
		}

		public static int GetExtraRows()
		{
			if (IsReady())
			{
				return (int)AccessTools.Method(_typeAPI, "GetExtraRows", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return -1;
		}

		public static int GetInventoryHeightFull()
		{
			if (IsReady())
			{
				return (int)AccessTools.Method(_typeAPI, "GetInventoryHeightFull", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return -1;
		}

		public static int GetInventoryHeightPlayer()
		{
			if (IsReady())
			{
				return (int)AccessTools.Method(_typeAPI, "GetInventoryHeightPlayer", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
			return -1;
		}

		public static bool IsGridPositionASlot(Vector2i gridPos)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "IsGridPositionASlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { gridPos });
		}

		public static bool IsItemInSlot(ItemData item)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "IsItemInSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { item });
		}

		public static bool IsItemInEquipmentSlot(ItemData item)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "IsItemInEquipmentSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { item });
		}

		public static bool IsAnyGlobalKeyActive(string requiredKeys)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "requiredKeys", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { requiredKeys });
		}

		public static bool IsItemTypeKnown(ItemType itemType)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "IsItemTypeKnown", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { itemType });
		}

		public static bool IsAnyMaterialDiscovered(string itemNames)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "IsAnyMaterialDiscovered", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { itemNames });
		}

		public static bool AddSlot(string slotID, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "AddSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, -1, getName, itemIsValid, isActive });
		}

		public static bool AddSlotWithIndex(string slotID, int slotIndex, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "AddSlotWithIndex", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, slotIndex, getName, itemIsValid, isActive });
		}

		public static bool AddSlotBefore(string slotID, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive, params string[] slotIDs)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "AddSlotBefore", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, getName, itemIsValid, isActive, slotIDs });
		}

		public static bool AddSlotAfter(string slotID, Func<string> getName, Func<ItemData, bool> itemIsValid, Func<bool> isActive, params string[] slotIDs)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "AddSlotAfter", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[5] { slotID, getName, itemIsValid, isActive, slotIDs });
		}

		public static bool RemoveSlot(string slotID)
		{
			if (!IsReady())
			{
				return false;
			}
			return (bool)AccessTools.Method(_typeAPI, "RemoveSlot", (Type[])null, (Type[])null).Invoke(_typeAPI, new object[1] { slotID });
		}

		public static void UpdateSlots()
		{
			if (IsReady())
			{
				AccessTools.Method(_typeAPI, "UpdateSlots", (Type[])null, (Type[])null).Invoke(_typeAPI, null);
			}
		}
	}
}
namespace ValheimEnforcer.modules.commands
{
	internal static class CommandHelpers
	{
		public static void ClearSpecifiedPlayerConfiscatedItems(string account, string username, string prefab)
		{
			DataObjects.Character character = ValConfig.LoadCharacterFromSave(account, username);
			Logger.LogInfo($"Found {character.ConfiscatedItems.Count} confiscated items.");
			if (string.Compare(prefab, "all", ignoreCase: true) == 0)
			{
				character.ConfiscatedItems.Clear();
				ValConfig.WritePlayerCharacterToSave(account, character);
				Logger.LogInfo("Cleared all confiscated items.");
				return;
			}
			List<string> targetItems = prefab.Split(new char[1] { ',' }).ToList();
			character.ConfiscatedItems = character.ConfiscatedItems.Where((DataObjects.PackedItem x) => !targetItems.Contains(x.prefabName)).ToList();
			Logger.LogInfo("Removed confiscated item with prefab " + string.Join(",", targetItems) + ".");
		}

		public static List<DataObjects.PackedItem> LoadCharacterAndFindItemsToReturn(string account, string username, string prefabfilter)
		{
			DataObjects.Character character = ValConfig.LoadCharacterFromSave(account, username);
			List<DataObjects.PackedItem> result = new List<DataObjects.PackedItem>();
			if (character == null)
			{
				Logger.LogInfo("Character was not found for the specified account.");
				return result;
			}
			if (character.ConfiscatedItems.Count == 0)
			{
				Logger.LogInfo("Player does not have any confiscated items.");
				return result;
			}
			if (string.Compare(prefabfilter, "all", ignoreCase: true) == 0)
			{
				result = new List<DataObjects.PackedItem>(character.ConfiscatedItems);
				character.ConfiscatedItems.Clear();
			}
			else
			{
				List<string> targetPrefabs = (from s in prefabfilter.Split(new char[1] { ',' })
					select s.Trim()).ToList();
				result = character.ConfiscatedItems.Where((DataObjects.PackedItem i) => targetPrefabs.Contains(i.prefabName)).ToList();
				character.ConfiscatedItems.RemoveAll((DataObjects.PackedItem i) => targetPrefabs.Contains(i.prefabName));
			}
			if (result.Count == 0)
			{
				Logger.LogInfo("No matching confiscated items found for the specified filter.");
				return result;
			}
			return result;
		}
	}
	internal static class TerminalCommands
	{
		internal class ListPlayers : ConsoleCommand
		{
			public override string Name => "Enforcer-List-Players";

			public override bool IsCheat => true;

			public override string Help => "Enforcer-List-Players - Provides a full list of all accounts and Player names stored.";

			public override void Run(string[] args)
			{
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Expected O, but got Unknown
				if (ZNet.instance.IsCurrentServerDedicated())
				{
					ValConfig.ListPlayerRPC.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), new ZPackage());
					Logger.LogInfo("Requesting player list from server...");
					return;
				}
				if (ValConfig.InternalStorageMode.Value)
				{
					foreach (KeyValuePair<string, List<string>> item in InternalDataStore.GetAccountRegistry())
					{
						Logger.LogInfo("Account:" + item.Key);
						foreach (string item2 in item.Value)
						{
							Logger.LogInfo("   " + item2);
						}
					}
					return;
				}
				foreach (string item3 in Directory.GetFiles(Path.Combine(Paths.ConfigPath, "ValheimEnforcer", "Characters")).ToList())
				{
					List<string> list = Directory.GetFiles(item3).ToList();
					Logger.LogInfo("Account:" + item3.Split(new char[1] { '/' }).Last());
					foreach (string item4 in list)
					{
						Logger.LogInfo("   " + item4.Split(new char[1] { '/' }).Last());
					}
				}
			}
		}

		internal class ListPlayerConfiscatedItems : ConsoleCommand
		{
			public override string Name => "Enforcer-List-Confiscated";

			public override bool IsCheat => true;

			public override string Help => "Gets a list of confiscated items, specific to a player/character. Format: enforcer-list-confiscated 99999999 TerryTheTerrible";

			public override void Run(string[] args)
			{
				if (args.Length != 2)
				{
					Logger.LogInfo("Account ID and playername are required. Ensure your command follows the format: enforcer-list-confiscated 99999999 TerryTheTerrible");
					return;
				}
				string id = args[0];
				string name = args[1];
				DataObjects.Character character = ValConfig.LoadCharacterFromSave(id, name);
				if (character.ConfiscatedItems.Count == 0)
				{
					Logger.LogInfo("Player does not have any confiscated items.");
					return;
				}
				Logger.LogInfo($"Found {character.ConfiscatedItems.Count} confiscated items.");
				foreach (DataObjects.PackedItem confiscatedItem in character.ConfiscatedItems)
				{
					Logger.LogInfo($"  {confiscatedItem.prefabName} x {confiscatedItem.m_stack}");
				}
			}
		}

		internal class ClearPlayerConfiscatedItems : ConsoleCommand
		{
			public override string Name => "Enforcer-Clear-Confiscated";

			public override bool IsCheat => true;

			public override string Help => "Clears any confiscated items listed for the specified player Format: enforcer-retrieve-confiscated 99999999 TerryTheTerrible all";

			public override void Run(string[] args)
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_002f: Expected O, but got Unknown
				if (args.Length != 3)
				{
					Logger.LogInfo("Account ID and playername are required. Ensure your command follows the format: enforcer-retrieve-confiscated 99999999 TerryTheTerrible all");
					return;
				}
				string text = args[0];
				string text2 = args[1];
				string text3 = args[2];
				if (ZNet.instance.IsCurrentServerDedicated())
				{
					ZPackage val = new ZPackage();
					DataObjects.RPCServerUpdateData rPCServerUpdateData = new DataObjects.RPCServerUpdateData();
					rPCServerUpdateData.ItemPrefabFilter = text3;
					rPCServerUpdateData.PlatformID = text;
					rPCServerUpdateData.PlayerName = text2;
					val.Write(DataObjects.yamlserializer.Serialize((object)rPCServerUpdateData));
					ValConfig.ClearConfiscatedRPC.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), val);
					Logger.LogInfo("Sending command to clear confiscated items on server...");
				}
				else
				{
					CommandHelpers.ClearSpecifiedPlayerConfiscatedItems(text, text2, text3);
				}
			}
		}

		internal class RestorePlayerConfiscatedItems : ConsoleCommand
		{
			public override string Name => "Enforcer-Admin-Take-Confiscated";

			public override bool IsCheat => true;

			public override string Help => "Gives you player confiscated items, use either item prefab or 'all'. Format: enforcer-admin-take-confiscated 99999999 TerryTheTerrible all";

			public override void Run(string[] args)
			{
				if (args.Length != 3)
				{
					Logger.LogInfo("Account ID and playername are required. Ensure your command follows the format: enforcer-admin-take-confiscated 99999999 TerryTheTerrible all");
					return;
				}
				string id = args[0];
				string name = args[1];
				string text = args[2];
				DataObjects.Character character = ValConfig.LoadCharacterFromSave(id, name);
				if (character == null)
				{
					Logger.LogInfo("Character was not found for the specified account.");
					return;
				}
				if (character.ConfiscatedItems.Count == 0)
				{
					Logger.LogInfo("Player does not have any confiscated items.");
					return;
				}
				Logger.LogInfo($"Found {character.ConfiscatedItems.Count} confiscated items.");
				if (string.Compare(text, "all", ignoreCase: true) == 0)
				{
					Logger.LogInfo("Providing all confiscated items.");
					foreach (DataObjects.PackedItem confiscatedItem in character.ConfiscatedItems)
					{
						confiscatedItem.AddToInventory(Player.m_localPlayer, use_position: false);
					}
					character.ConfiscatedItems.Clear();
					return;
				}
				foreach (DataObjects.PackedItem confiscatedItem2 in character.ConfiscatedItems)
				{
					_ = confiscatedItem2;
					List<string> list = text.Split(new char[1] { ',' }).ToList();
					foreach (DataObjects.PackedItem confiscatedItem3 in character.ConfiscatedItems)
					{
						if (list.Contains(confiscatedItem3.prefabName))
						{
							Logger.LogInfo("Providing " + confiscatedItem3.prefabName);
							confiscatedItem3.AddToInventory(Player.m_localPlayer, use_position: false);
						}
					}
					foreach (string target in list)
					{
						character.ConfiscatedItems.RemoveAll((DataObjects.PackedItem i) => i.prefabName == target);
					}
				}
				ValConfig.WritePlayerCharacterToSave(character.HostID, character);
			}
		}

		internal class ReturnPlayerConfiscatedItems : ConsoleCommand
		{
			public override string Name => "Enforcer-Return-Confiscated";

			public override bool IsCheat => true;

			public override string Help => "Sends confiscated items to a connected player via RPC. Use 'all' or comma-separated prefab names. Format: Enforcer-Return-Confiscated 99999999 TerryTheTerrible all";

			public override void Run(string[] args)
			{
				//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c3: Expected O, but got Unknown
				if (args.Length != 3)
				{
					Logger.LogInfo("Account ID, player name, and item filter are required. Ensure your command follows the format: Enforcer-Return-Confiscated 99999999 TerryTheTerrible all");
					return;
				}
				string text = args[0];
				string text2 = args[1];
				string text3 = args[2];
				if ((Object)(object)Player.m_localPlayer != (Object)null && Player.m_localPlayer.GetPlayerName() == text2 && !ZNet.instance.IsCurrentServerDedicated())
				{
					Logger.LogInfo("Local player is the target, returning player items.");
					List<DataObjects.PackedItem> list = CommandHelpers.LoadCharacterAndFindItemsToReturn(text, text2, text3);
					DataObjects.Character character = ValConfig.LoadCharacterFromSave(text, text2);
					foreach (DataObjects.PackedItem item in list)
					{
						Logger.LogInfo("Providing " + item.prefabName);
						item.AddToInventory(Player.m_localPlayer, use_position: false);
					}
					ValConfig.WritePlayerCharacterToSave(text, character);
				}
				else
				{
					ZPackage val = new ZPackage();
					DataObjects.RPCServerUpdateData rPCServerUpdateData = new DataObjects.RPCServerUpdateData();
					rPCServerUpdateData.PlatformID = text;
					rPCServerUpdateData.ItemPrefabFilter = text3;
					rPCServerUpdateData.PlayerName = text2;
					val.Write(DataObjects.yamlserializer.Serialize((object)rPCServerUpdateData));
					ValConfig.ReturnConfiscatedItemsRPC.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), val);
				}
			}
		}

		internal static void AddCommands()
		{
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ListPlayers());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ClearPlayerConfiscatedItems());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ReturnPlayerConfiscatedItems());
		}
	}
}
namespace ValheimEnforcer.modules.cheatmonitor
{
	internal static class CheatDetector
	{
		private static class NativeWin32
		{
			public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

			[DllImport("user32.dll")]
			public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

			[DllImport("user32.dll", CharSet = CharSet.Unicode)]
			public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

			[DllImport("user32.dll", CharSet = CharSet.Unicode)]
			public static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

			[DllImport("kernel32.dll")]
			public static extern bool IsDebuggerPresent();

			[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
			public static extern bool CheckRemoteDebuggerPresent(IntPtr hProcess, ref bool isDebuggerPresent);
		}

		internal class CheatDetectorBehaviour : MonoBehaviour
		{
			private float nextScan;

			private void Start()
			{
			}

			private void Update()
			{
				if (ValConfig.EnableCheatDetection.Value && !(Time.unscaledTime < nextScan))
				{
					nextScan = Time.unscaledTime + (float)Mathf.Max(1, ValConfig.CheatScanIntervalSeconds.Value);
					RunScan();
				}
			}

			private static void RunScan()
			{
				if (CharacterManager.PlayerCharacter != null)
				{
					DataObjects.CheatSummaryReport cheatSummaryReport = new DataObjects.CheatSummaryReport
					{
						PlayerName = CharacterManager.PlayerCharacter.Name,
						PlatformID = CharacterManager.PlayerCharacter.HostID,
						CheatEngineStatus = new DataObjects.CheatEngineDetector()
					};
					if (ValConfig.DetectValheimTooler.Value && ValheimToolerLoaded())
					{
						cheatSummaryReport.ValheimToolerStatus = true;
					}
					if (ValConfig.DetectCheatEngine.Value && CheatEngineProcessRunning())
					{
						cheatSummaryReport.CheatEngineStatus.CheatEngineProcessDetected = true;
					}
					if (cheatSummaryReport.cheatsDetected())
					{
						ReportCheatScanSummary(cheatSummaryReport);
					}
				}
			}
		}

		private static readonly string[] ToolerAssemblyNames = new string[3] { "ValheimTooler", "ValheimToolerMod", "RapidGUI" };

		private static readonly string[] CheatEngineProcessNames = new string[4] { "cheatengine-x86_64", "cheatengine-i386", "cheatengine-x86_64-sse4-avx2", "cheatengine" };

		internal static void Initialize()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			if ((!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsDedicated()) && ValConfig.EnableCheatDetection.Value)
			{
				GameObject val = new GameObject("VE_CheatDetector");
				Object.DontDestroyOnLoad((Object)val);
				((Object)val).hideFlags = (HideFlags)61;
				val.AddComponent<CheatDetectorBehaviour>();
				Logger.LogDebug("CheatDetector initialized.");
			}
		}

		internal static bool ValheimToolerLoaded()
		{
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					string n = assembly.GetName().Name ?? "";
					if (ToolerAssemblyNames.Any((string t) => t.Equals(n, StringComparison.OrdinalIgnoreCase