Decompiled source of GyattAntiCheat v1.3.1

GyattAntiCheat.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("GyattAntiCheat")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GyattAntiCheat")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("1eb59790-439f-447a-a5d8-4e23477b83d0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace GyattAntiCheat;

[BepInPlugin("com.valheim.gyattanticheat", "GyattAntiCheat", "1.3.1")]
public class GyattAntiCheatPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(Skills), "RaiseSkill")]
	public static class Skills_RaiseSkill_Patch
	{
		private static void Postfix(Skills __instance, SkillType skillType)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || IsLocalAdmin || (Object)(object)__instance != (Object)(object)((Character)Player.m_localPlayer).GetSkills())
			{
				return;
			}
			Skill val = ((IEnumerable<Skill>)__instance.GetSkillList()).FirstOrDefault((Func<Skill, bool>)((Skill s) => s.m_info != null && s.m_info.m_skill == skillType));
			if (val == null)
			{
				return;
			}
			if (LastSkillLevels.TryGetValue(skillType, out var value) && val.m_level > value)
			{
				float time = Time.time;
				if (LastSkillLevelUpTimes.TryGetValue(skillType, out var value2) && val.m_level > 10f && time - value2 < 1.5f)
				{
					_violationQueue.Enqueue($"Fast Skill Level Up Trainer Detected ({val.m_info.m_skill}: Lv {value:F0} -> {val.m_level:F0} in {time - value2:F1}s)");
				}
				LastSkillLevelUpTimes[skillType] = time;
			}
			LastSkillLevels[skillType] = val.m_level;
			if (val.m_level > 100.1f)
			{
				_violationQueue.Enqueue($"Illegal Skill Level ({val.m_info.m_skill}: {val.m_level:F0} > 100)");
			}
		}
	}

	private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	public static class ZNet_OnNewConnection_Patch
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Action<ZRpc, bool> <>9__0_2;

			public static Action<ZRpc, string> <>9__0_3;

			public static Method <>9__0_4;

			internal void <Postfix>b__0_2(ZRpc rpc, bool isAdmin)
			{
				OnClientReceiveAdminStatus(isAdmin);
			}

			internal void <Postfix>b__0_3(ZRpc rpc, string msg)
			{
				OnAdminReceiveNotification(msg);
			}

			internal void <Postfix>b__0_4(ZRpc rpc)
			{
				TriggerClientSave();
			}
		}

		private static void Postfix(ZNet __instance, ZNetPeer peer)
		{
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Expected O, but got Unknown
			if (peer == null || peer.m_rpc == null)
			{
				return;
			}
			if (__instance.IsServer())
			{
				CacheServerPluginFiles();
				peer.m_rpc.Register<string, string>("GyattAC_Handshake", (Action<ZRpc, string, string>)delegate(ZRpc val2, string pName, string mods)
				{
					OnServerReceiveHandshake(peer, pName, mods);
				});
				peer.m_rpc.Register<string, string>("GyattAC_ReportViolation", (Action<ZRpc, string, string>)delegate(ZRpc val2, string pName, string reason)
				{
					OnServerReceiveViolation(peer, pName, reason);
				});
				if (RequireModToJoin.Value)
				{
					((MonoBehaviour)__instance).StartCoroutine(CheckPlayerHandshake(peer));
				}
				return;
			}
			peer.m_rpc.Register<bool>("GyattAC_SetAdminStatus", (Action<ZRpc, bool>)delegate(ZRpc val2, bool isAdmin)
			{
				OnClientReceiveAdminStatus(isAdmin);
			});
			peer.m_rpc.Register<string>("GyattAC_NotifyAdmin", (Action<ZRpc, string>)delegate(ZRpc val2, string msg)
			{
				OnAdminReceiveNotification(msg);
			});
			ZRpc rpc = peer.m_rpc;
			object obj = <>c.<>9__0_4;
			if (obj == null)
			{
				Method val = delegate
				{
					TriggerClientSave();
				};
				<>c.<>9__0_4 = val;
				obj = (object)val;
			}
			rpc.Register("GyattAC_ForceSave", (Method)obj);
		}

		private static IEnumerator CheckPlayerHandshake(ZNetPeer peer)
		{
			yield return (object)new WaitForSeconds(30f);
			if (peer != null && peer.IsReady() && !IsPeerAdmin(peer))
			{
				string steamID = peer.m_socket.GetHostName();
				if (!VerifiedPeers.Contains(steamID))
				{
					Log.LogWarning((object)("[VIOLATION] Kicking '" + peer.m_playerName + "' (ID: " + steamID + ") - Missing GyattAntiCheat client mod."));
					ZNet.instance.Kick(peer.m_playerName);
				}
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	public static class ZNet_RPC_PeerInfo_Patch
	{
		private static void Postfix(ZNet __instance, ZRpc rpc)
		{
			if (__instance.IsServer())
			{
				return;
			}
			List<string> list = new List<string>();
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				list.Add(value.Metadata.GUID + "|" + value.Metadata.Name);
			}
			string text = string.Join(";", list);
			string text2 = (((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null) ? Game.instance.GetPlayerProfile().GetName() : "Player");
			rpc.Invoke("GyattAC_Handshake", new object[2] { text2, text });
		}
	}

	[HarmonyPatch(typeof(Game), "Logout")]
	public static class Game_Logout_Patch
	{
		private static void Postfix()
		{
			VerifiedPeers.Clear();
			_loggedViolations.Clear();
			_pendingKicks.Clear();
			LastSkillLevels.Clear();
			LastSkillLevelUpTimes.Clear();
			IsLocalAdmin = false;
		}
	}

	public const string ModGUID = "com.valheim.gyattanticheat";

	public const string ModName = "GyattAntiCheat";

	public const string ModVersion = "1.3.1";

	public static ManualLogSource Log;

	private Harmony _harmony;

	public static ConfigEntry<bool> EnforceExactServerMods;

	public static ConfigEntry<bool> CheckMissingServerMods;

	public static ConfigEntry<string> AllowedClientOnlyMods;

	public static ConfigEntry<string> ServerOnlyMods;

	public static ConfigEntry<string> BlacklistedWords;

	public static ConfigEntry<bool> AutoKick;

	public static ConfigEntry<bool> RequireModToJoin;

	private static readonly FieldInfo fNoPlacementCost = AccessTools.Field(typeof(Player), "m_noPlacementCost");

	private static readonly ConcurrentQueue<string> _violationQueue = new ConcurrentQueue<string>();

	private static bool _isScanningProcesses = false;

	public static bool IsLocalAdmin = false;

	private float _stateCheckTimer = 0f;

	private float _processCheckTimer = 0f;

	public static HashSet<string> VerifiedPeers = new HashSet<string>();

	private static HashSet<string> _loggedViolations = new HashSet<string>();

	private static HashSet<string> _pendingKicks = new HashSet<string>();

	private static List<string> _serverPluginFilesData = null;

	public static Dictionary<SkillType, float> LastSkillLevels = new Dictionary<SkillType, float>();

	public static Dictionary<SkillType, float> LastSkillLevelUpTimes = new Dictionary<SkillType, float>();

	private void Awake()
	{
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		EnforceExactServerMods = ((BaseUnityPlugin)this).Config.Bind<bool>("ModSync", "EnforceServerModWhitelist", true, "If true, players cannot use any extra mod that is not installed on the server (except AllowedClientOnlyMods).");
		CheckMissingServerMods = ((BaseUnityPlugin)this).Config.Bind<bool>("ModSync", "CheckMissingServerMods", true, "If true, players must have all active mods that are loaded on the server (except ServerOnlyMods).");
		AllowedClientOnlyMods = ((BaseUnityPlugin)this).Config.Bind<string>("ModSync", "AllowedClientOnlyMods", "org.bepinex.plugins.configurationmanager,com.bepinex.configurationmanager", "Comma-separated list of Mod GUIDs/Names allowed on clients even if not installed on the server.");
		ServerOnlyMods = ((BaseUnityPlugin)this).Config.Bind<string>("ModSync", "ServerOnlyMods", "discordconnector,server_devcommands", "Comma-separated list of Mod GUIDs/Names installed ONLY on the server that clients are not required to have.");
		BlacklistedWords = ((BaseUnityPlugin)this).Config.Bind<string>("Security", "BlacklistedKeywords", "wemod,cheatengine,cheat,trainer,fling,mrantifun,sktoolbox,speedhack,xenos,injector,artmoney", "Comma-separated list of restricted process names, window titles, or mod keywords.");
		AutoKick = ((BaseUnityPlugin)this).Config.Bind<bool>("Security", "AutoKickCheater", true, "Automatically kick non-admin players when a violation or mod mismatch is detected (Server only).");
		RequireModToJoin = ((BaseUnityPlugin)this).Config.Bind<bool>("Security", "RequireGyattAntiCheat", true, "Require clients to have GyattAntiCheat installed to stay connected (Server only).");
		_harmony = new Harmony("com.valheim.gyattanticheat");
		_harmony.PatchAll();
		Log.LogInfo((object)"GyattAntiCheat v1.3.1 Loaded!");
	}

	private static void CacheServerPluginFiles()
	{
		if (_serverPluginFilesData != null)
		{
			return;
		}
		_serverPluginFilesData = new List<string>();
		try
		{
			if (!Directory.Exists(Paths.PluginPath))
			{
				return;
			}
			string[] files = Directory.GetFiles(Paths.PluginPath, "*.dll", SearchOption.AllDirectories);
			string[] array = files;
			foreach (string path in array)
			{
				try
				{
					_serverPluginFilesData.Add(Path.GetFileNameWithoutExtension(path).ToLowerInvariant());
					byte[] bytes = File.ReadAllBytes(path);
					_serverPluginFilesData.Add(Encoding.ASCII.GetString(bytes).ToLowerInvariant());
				}
				catch
				{
				}
			}
			Log.LogInfo((object)$"[MOD SYNC] Cached {files.Length} plugin DLL files from server plugins directory.");
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("[MOD SYNC] Failed to scan plugin directory: " + ex.Message));
		}
	}

	private static bool IsModInServerPluginFolder(string guid, string name)
	{
		CacheServerPluginFiles();
		string value = guid.ToLowerInvariant();
		string value2 = name.ToLowerInvariant();
		foreach (string serverPluginFilesDatum in _serverPluginFilesData)
		{
			if (serverPluginFilesDatum.Contains(value) || serverPluginFilesDatum.Contains(value2))
			{
				return true;
			}
		}
		return false;
	}

	private void Update()
	{
		if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return;
		}
		if (IsLocalAdmin || ZNet.instance.IsServer())
		{
			string result;
			while (_violationQueue.TryDequeue(out result))
			{
			}
			return;
		}
		string result2;
		while (_violationQueue.TryDequeue(out result2))
		{
			TriggerClientSave();
			SendViolationToServer(result2);
		}
		_stateCheckTimer += Time.deltaTime;
		if (_stateCheckTimer >= 3f)
		{
			_stateCheckTimer = 0f;
			CheckLightweightPlayerState(Player.m_localPlayer);
		}
		_processCheckTimer += Time.deltaTime;
		if (_processCheckTimer >= 30f)
		{
			_processCheckTimer = 0f;
			RunBackgroundProcessScan();
		}
	}

	private void CheckLightweightPlayerState(Player p)
	{
		if (((Character)p).IsDead())
		{
			return;
		}
		if (((Character)p).InGodMode())
		{
			_violationQueue.Enqueue("God Mode Active (InGodMode)");
		}
		if (((Character)p).InGhostMode())
		{
			_violationQueue.Enqueue("Ghost Mode Active");
		}
		if (((Character)p).IsDebugFlying() || ((Character)p).m_flying)
		{
			_violationQueue.Enqueue("Debug Fly / Free Fly Active");
		}
		if (fNoPlacementCost != null && (bool)fNoPlacementCost.GetValue(p))
		{
			_violationQueue.Enqueue("No Crafting Req / No Placement Cost Active");
		}
		if (p.GetMaxCarryWeight() > 1200f)
		{
			_violationQueue.Enqueue($"Illegal Carry Weight ({p.GetMaxCarryWeight():F0} > 1200)");
		}
		float health = ((Character)p).GetHealth();
		float maxHealth = ((Character)p).GetMaxHealth();
		if (health > 450f || maxHealth > 450f)
		{
			_violationQueue.Enqueue($"Illegal HP Stat via Trainer ({health:F0}/{maxHealth:F0})");
		}
		else if (health > maxHealth + 10f)
		{
			_violationQueue.Enqueue($"Frozen Health Cheat Detected ({health:F0}/{maxHealth:F0})");
		}
		float stamina = p.GetStamina();
		float maxStamina = ((Character)p).GetMaxStamina();
		if (stamina > 380f || maxStamina > 380f)
		{
			_violationQueue.Enqueue($"Illegal Stamina Stat via Trainer ({stamina:F0}/{maxStamina:F0})");
		}
		else if (stamina > maxStamina + 10f)
		{
			_violationQueue.Enqueue($"Frozen Stamina Cheat Detected ({stamina:F0}/{maxStamina:F0})");
		}
		float eitr = p.GetEitr();
		float maxEitr = ((Character)p).GetMaxEitr();
		if (maxEitr <= 0f && eitr > 5f)
		{
			_violationQueue.Enqueue($"Illegal Eitr Generated Without Food ({eitr:F0})");
		}
		else if (eitr > 350f || maxEitr > 350f)
		{
			_violationQueue.Enqueue($"Illegal Eitr Stat via Trainer ({eitr:F0}/{maxEitr:F0})");
		}
		else if (maxEitr > 0f && eitr > maxEitr + 10f)
		{
			_violationQueue.Enqueue($"Frozen Eitr Cheat Detected ({eitr:F0}/{maxEitr:F0})");
		}
		if (((Humanoid)p).GetInventory() != null)
		{
			foreach (ItemData allItem in ((Humanoid)p).GetInventory().GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null && allItem.m_shared.m_useDurability)
				{
					float maxDurability = allItem.GetMaxDurability();
					if (allItem.m_durability > maxDurability + 10f)
					{
						_violationQueue.Enqueue($"Infinite Durability Cheat ({allItem.m_shared.m_name}: {allItem.m_durability:F0}/{maxDurability:F0})");
						break;
					}
				}
			}
		}
		if (p.GetFoods() != null)
		{
			foreach (Food food in p.GetFoods())
			{
				if (food.m_time > 3300f)
				{
					_violationQueue.Enqueue($"Infinite Food Duration Cheat ({food.m_time:F0}s > 3300s)");
					break;
				}
			}
		}
		if (((Character)p).GetSEMan() == null)
		{
			return;
		}
		foreach (StatusEffect statusEffect in ((Character)p).GetSEMan().GetStatusEffects())
		{
			if ((Object)(object)statusEffect != (Object)null && ((Object)statusEffect).name.Contains("Rested") && statusEffect.GetRemaningTime() > 2700f)
			{
				_violationQueue.Enqueue($"Infinite Rested Duration Cheat ({statusEffect.GetRemaningTime():F0}s > 2700s)");
				break;
			}
		}
	}

	[DllImport("user32.dll", CharSet = CharSet.Unicode)]
	private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);

	[DllImport("user32.dll", CharSet = CharSet.Unicode)]
	private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);

	[DllImport("user32.dll")]
	private static extern bool IsWindowVisible(IntPtr hWnd);

	private void RunBackgroundProcessScan()
	{
		if (_isScanningProcesses || IsLocalAdmin)
		{
			return;
		}
		_isScanningProcesses = true;
		string text = BlacklistedWords.Value.Replace("cheat,", "cheat engine,").Replace(",cheat", ",cheat engine");
		string[] badWords = (from s in (text + ",fling").ToLower().Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
			select s.Trim()).ToArray();
		Task.Run(delegate
		{
			try
			{
				EnumWindows(delegate(IntPtr hWnd, IntPtr lParam)
				{
					if (IsWindowVisible(hWnd))
					{
						StringBuilder stringBuilder = new StringBuilder(256);
						if (GetWindowText(hWnd, stringBuilder, 256) > 0)
						{
							string text3 = stringBuilder.ToString().ToLower();
							if (text3.Contains("anti-cheat") || text3.Contains("anticheat") || text3.Contains("google chrome") || text3.Contains("edge") || text3.Contains("firefox") || text3.Contains("opera") || text3.Contains("brave"))
							{
								return true;
							}
							string[] array3 = badWords;
							foreach (string value2 in array3)
							{
								if (text3.Contains(value2))
								{
									_violationQueue.Enqueue("Restricted Window Detected: " + stringBuilder.ToString());
									return false;
								}
							}
						}
					}
					return true;
				}, IntPtr.Zero);
				Process[] processes = Process.GetProcesses();
				Process[] array = processes;
				foreach (Process process in array)
				{
					try
					{
						string text2 = process.ProcessName.ToLower();
						if (!text2.Contains("chrome") && !text2.Contains("msedge") && !text2.Contains("firefox") && !text2.Contains("opera") && !text2.Contains("brave"))
						{
							string[] array2 = badWords;
							foreach (string value in array2)
							{
								if (text2.Contains(value))
								{
									_violationQueue.Enqueue("Restricted Process Detected: " + process.ProcessName);
									break;
								}
							}
						}
					}
					catch
					{
					}
					finally
					{
						process.Dispose();
					}
				}
			}
			catch
			{
			}
			finally
			{
				_isScanningProcesses = false;
			}
		});
	}

	private void SendViolationToServer(string reason)
	{
		if (!((Object)(object)ZNet.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && !IsLocalAdmin)
		{
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer != null && serverPeer.m_rpc != null)
			{
				serverPeer.m_rpc.Invoke("GyattAC_ReportViolation", new object[2]
				{
					Player.m_localPlayer.GetPlayerName(),
					reason
				});
			}
		}
	}

	private static void TriggerClientSave()
	{
		try
		{
			if ((Object)(object)Game.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				Game.instance.SavePlayerProfile(false, false);
				Log.LogInfo((object)"[GYATT-AC] Profile saved to prevent item duplication.");
			}
		}
		catch
		{
		}
	}

	private static void OnClientReceiveAdminStatus(bool isAdmin)
	{
		IsLocalAdmin = isAdmin;
		if (isAdmin)
		{
			Log.LogInfo((object)"[GYATT-AC] Admin privileges granted by server. Anti-cheat checks bypassed.");
		}
	}

	private static bool IsPeerAdmin(ZNetPeer peer)
	{
		if (peer == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.GetAdminList() == null)
		{
			return false;
		}
		string hostName = peer.m_socket.GetHostName();
		string cleanId = hostName.Replace("Steam_", "").Trim();
		if (string.IsNullOrEmpty(cleanId))
		{
			return false;
		}
		List<string> adminList = ZNet.instance.GetAdminList();
		return adminList.Any((string entry) => !string.IsNullOrWhiteSpace(entry) && entry.Trim().Contains(cleanId));
	}

	private static void OnServerReceiveHandshake(ZNetPeer peer, string playerName, string modListRaw)
	{
		if (!ZNet.instance.IsServer() || peer == null)
		{
			return;
		}
		string hostName = peer.m_socket.GetHostName();
		VerifiedPeers.Add(hostName);
		if (string.IsNullOrEmpty(playerName) && !string.IsNullOrEmpty(peer.m_playerName))
		{
			playerName = peer.m_playerName;
		}
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		string[] array = modListRaw.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
		foreach (string text in array)
		{
			string[] array2 = text.Split(new char[1] { '|' });
			if (array2.Length >= 2)
			{
				dictionary[array2[0].Trim()] = array2[1].Trim();
			}
			else
			{
				dictionary[text.Trim()] = text.Trim();
			}
		}
		Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		foreach (PluginInfo value in Chainloader.PluginInfos.Values)
		{
			dictionary2[value.Metadata.GUID] = value.Metadata.Name;
		}
		if (IsPeerAdmin(peer))
		{
			Log.LogInfo((object)string.Format("[ADMIN JOIN] Admin '{0}' (ID: {1}) connected with {2} mods: [{3}] -> Bypassed all checks.", playerName, hostName, dictionary.Count, string.Join(", ", dictionary.Values)));
			ZRpc rpc = peer.m_rpc;
			if (rpc != null)
			{
				rpc.Invoke("GyattAC_SetAdminStatus", new object[1] { true });
			}
			return;
		}
		Log.LogInfo((object)string.Format("[PLAYER JOIN] Player '{0}' (ID: {1}) connected with {2} mods: [{3}]", playerName, hostName, dictionary.Count, string.Join(", ", dictionary.Values)));
		string[] source = (from s in AllowedClientOnlyMods.Value.ToLower().Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
			select s.Trim()).ToArray();
		string[] source2 = (from s in ServerOnlyMods.Value.ToLower().Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
			select s.Trim()).ToArray();
		if (EnforceExactServerMods.Value)
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				string guid = item.Key;
				string name = item.Value;
				if (!dictionary2.ContainsKey(guid) && !IsModInServerPluginFolder(guid, name) && !source.Any((string allow) => guid.ToLower().Contains(allow) || name.ToLower().Contains(allow)))
				{
					list.Add(name + " (" + guid + ")");
				}
			}
			if (list.Count > 0)
			{
				OnServerReceiveViolation(peer, playerName, "Unauthorized Extra Mods (Not on Server): " + string.Join(", ", list));
				return;
			}
		}
		if (CheckMissingServerMods.Value)
		{
			List<string> list2 = new List<string>();
			foreach (KeyValuePair<string, string> item2 in dictionary2)
			{
				string guid2 = item2.Key;
				string name2 = item2.Value;
				if (!dictionary.ContainsKey(guid2) && !source2.Any((string srvOnly) => guid2.ToLower().Contains(srvOnly) || name2.ToLower().Contains(srvOnly)))
				{
					list2.Add(name2 + " (" + guid2 + ")");
				}
			}
			if (list2.Count > 0)
			{
				OnServerReceiveViolation(peer, playerName, "Missing Required Server Mods: " + string.Join(", ", list2));
				return;
			}
		}
		string[] array3 = BlacklistedWords.Value.ToLower().Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
		foreach (KeyValuePair<string, string> item3 in dictionary)
		{
			if (dictionary2.ContainsKey(item3.Key) || IsModInServerPluginFolder(item3.Key, item3.Value))
			{
				continue;
			}
			string text2 = (item3.Value + " " + item3.Key).ToLower();
			string[] array4 = array3;
			foreach (string text3 in array4)
			{
				if (!string.IsNullOrWhiteSpace(text3) && text2.Contains(text3.Trim()))
				{
					OnServerReceiveViolation(peer, playerName, "Blacklisted Mod Installed: " + item3.Value);
					return;
				}
			}
		}
		Log.LogInfo((object)("[MOD SYNC OK] Player '" + playerName + "' (ID: " + hostName + ") passed all mod verification checks."));
	}

	private static void OnServerReceiveViolation(ZNetPeer offenderPeer, string playerName, string violationReason)
	{
		if (!ZNet.instance.IsServer() || offenderPeer == null || IsPeerAdmin(offenderPeer))
		{
			return;
		}
		string hostName = offenderPeer.m_socket.GetHostName();
		if (string.IsNullOrEmpty(playerName))
		{
			playerName = offenderPeer.m_playerName;
		}
		string item = hostName + "_" + violationReason;
		if (!_loggedViolations.Contains(item))
		{
			_loggedViolations.Add(item);
			string text = "[VIOLATION] Player: '" + playerName + "' (ID: " + hostName + ") | Reason: " + violationReason;
			Log.LogWarning((object)text);
			OnAdminReceiveNotification(text);
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (IsPeerAdmin(peer))
				{
					ZRpc rpc = peer.m_rpc;
					if (rpc != null)
					{
						rpc.Invoke("GyattAC_NotifyAdmin", new object[1] { text });
					}
				}
			}
		}
		if (AutoKick.Value && !_pendingKicks.Contains(hostName))
		{
			_pendingKicks.Add(hostName);
			Log.LogWarning((object)("[ACTION] Triggering save and delaying kick for '" + playerName + "' (ID: " + hostName + ") to prevent dupe..."));
			ZRpc rpc2 = offenderPeer.m_rpc;
			if (rpc2 != null)
			{
				rpc2.Invoke("GyattAC_ForceSave", Array.Empty<object>());
			}
			((MonoBehaviour)ZNet.instance).StartCoroutine(ExecuteDelayedKick(offenderPeer, playerName, hostName));
		}
	}

	private static IEnumerator ExecuteDelayedKick(ZNetPeer peer, string playerName, string steamID)
	{
		yield return (object)new WaitForSeconds(2f);
		_pendingKicks.Remove(steamID);
		if (peer != null && (Object)(object)ZNet.instance != (Object)null)
		{
			Log.LogWarning((object)("[ACTION] Kicking '" + playerName + "' (ID: " + steamID + ") now (Save complete)."));
			ZNet.instance.Kick(playerName);
		}
	}

	private static void OnAdminReceiveNotification(string message)
	{
		if ((IsLocalAdmin || (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())) && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			((Character)Player.m_localPlayer).Message((MessageType)1, "<color=red>" + message + "</color>", 0, (Sprite)null, false);
			Chat instance = Chat.instance;
			if (instance != null)
			{
				((Terminal)instance).AddString("[GYATT-AC]", "<color=red>" + message + "</color>", (Type)1, false);
			}
		}
	}
}