Decompiled source of CheatTagEnforcement v0.1.1

plugins/Blackheim.CheatTagEnforcement.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Blackheim.CheatTagEnforcement")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1")]
[assembly: AssemblyProduct("Blackheim.CheatTagEnforcement")]
[assembly: AssemblyTitle("Blackheim.CheatTagEnforcement")]
[assembly: AssemblyVersion("0.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Blackheim.CheatTagEnforcement
{
	[BepInPlugin("blackheim.cheattagenforcement", "Blackheim Cheat Tag Enforcement", "0.1.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class Session
		{
			internal float Started;

			internal float LastPong = Time.realtimeSinceStartup;

			internal float NextHello;

			internal float NextPing;

			internal float KickAt;

			internal bool Accepted;

			internal bool Kicked;

			internal string Challenge = "";
		}

		[HarmonyPatch(typeof(Inventory), "Changed", new Type[]
		{
			typeof(bool),
			typeof(bool)
		})]
		private static class InventoryChanged
		{
			private static void Postfix(Inventory __instance)
			{
				if ((Object)(object)Player.m_localPlayer != (Object)null && __instance == ((Humanoid)Player.m_localPlayer).GetInventory())
				{
					Instance?.CleanLocal();
				}
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		private static class Spawned
		{
			[HarmonyPriority(800)]
			private static void Prefix(Player __instance)
			{
				if (__instance == Player.m_localPlayer)
				{
					Instance?.CleanLocal();
				}
			}
		}

		[HarmonyPatch(typeof(Player), "Save")]
		private static class Saving
		{
			[HarmonyPriority(800)]
			private static void Prefix(Player __instance)
			{
				if (__instance == Player.m_localPlayer)
				{
					Instance?.CleanLocal();
				}
			}
		}

		public const string Id = "blackheim.cheattagenforcement";

		public const string Release = "0.1.1";

		private const string Hello = "BlackheimCT_Hello_v1";

		private const string Policy = "BlackheimCT_Policy_v1";

		private const string Report = "BlackheimCT_Report_v1";

		private const string Notice = "BlackheimCT_Notice_v1";

		private const string Ping = "BlackheimCT_Ping_v1";

		private const string Pong = "BlackheimCT_Pong_v1";

		private const string Failure = "BlackheimCT_Failure_v1";

		internal static Plugin Instance;

		private Harmony harmony;

		private readonly Dictionary<ZNetPeer, Session> sessions = new Dictionary<ZNetPeer, Session>();

		private ZNetPeer policyPeer;

		private bool sweeping;

		private float nextSweep;

		private string banner = "";

		private float bannerUntil;

		private int pendingStacks;

		private string pendingDescription = "";

		private float nextClientReport;

		private static readonly MethodInfo KickPeer = AccessTools.Method(typeof(ZNet), "InternalKick", new Type[1] { typeof(ZNetPeer) }, (Type[])null);

		private bool ActiveClient
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && policyPeer != null)
				{
					return policyPeer == ZNet.instance.GetServerPeer();
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			if (KickPeer == null)
			{
				throw new MissingMethodException("Required ZNet.InternalKick(ZNetPeer) unavailable");
			}
			Instance = this;
			harmony = new Harmony("blackheim.cheattagenforcement");
			harmony.PatchAll(typeof(Plugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Blackheim tag enforcement 0.1.1 loaded: native tagged items are deleted on the dedicated server or a connected policy client; carried-item reports trigger warning and kick.");
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			Instance = null;
		}

		private void Update()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				sessions.Clear();
				policyPeer = null;
				pendingStacks = 0;
				pendingDescription = "";
				return;
			}
			ZNetPeer[] peers = ZNet.instance.GetPeers().ToArray();
			ZNetPeer[] array = sessions.Keys.Where((ZNetPeer p) => !peers.Contains(p)).ToArray();
			foreach (ZNetPeer key in array)
			{
				sessions.Remove(key);
			}
			array = peers;
			foreach (ZNetPeer val in array)
			{
				if (!sessions.ContainsKey(val))
				{
					Register(val);
				}
				if (!val.IsReady())
				{
					continue;
				}
				Session session = sessions[val];
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (session.Started == 0f)
				{
					session.Started = realtimeSinceStartup;
				}
				if (ZNet.instance.IsServer())
				{
					if ((!session.Accepted && realtimeSinceStartup - session.Started > 30f) || (session.Accepted && realtimeSinceStartup - session.LastPong > 40f))
					{
						ScheduleKick(val, "Required Blackheim enforcement client is missing or stopped responding.", 0f);
					}
					if (session.Accepted && realtimeSinceStartup >= session.NextPing && session.Challenge.Length == 0)
					{
						session.Challenge = Guid.NewGuid().ToString("N");
						val.m_rpc.Invoke("BlackheimCT_Ping_v1", new object[1] { session.Challenge });
						session.NextPing = realtimeSinceStartup + 10f;
					}
					if (!session.Kicked && session.KickAt > 0f && realtimeSinceStartup >= session.KickAt)
					{
						session.Kicked = true;
						KickPeer.Invoke(ZNet.instance, new object[1] { val });
						((BaseUnityPlugin)this).Logger.LogWarning((object)("[kick] connection=" + val.m_uid + " player=" + Safe(val.m_playerName)));
					}
				}
				else if (val == ZNet.instance.GetServerPeer() && realtimeSinceStartup >= session.NextHello)
				{
					val.m_rpc.Invoke("BlackheimCT_Hello_v1", new object[1] { "0.1.1" });
					session.NextHello = realtimeSinceStartup + 5f;
				}
			}
			if (ActiveClient && pendingStacks > 0 && Time.realtimeSinceStartup >= nextClientReport)
			{
				CleanLocal();
				if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Game.instance != (Object)null)
				{
					try
					{
						Game.instance.SavePlayerProfile(false, true);
						policyPeer.m_rpc.Invoke("BlackheimCT_Report_v1", new object[2] { pendingStacks, pendingDescription });
						pendingStacks = 0;
						pendingDescription = "";
					}
					catch (Exception error)
					{
						FailClient("Could not save cleaned inventory", error);
					}
					nextClientReport = Time.realtimeSinceStartup + 1f;
				}
			}
			if (Time.realtimeSinceStartup >= nextSweep)
			{
				nextSweep = Time.realtimeSinceStartup + 2f;
				if (ActiveClient)
				{
					CleanLocal();
				}
				if (ZNet.instance.IsDedicated() || ActiveClient)
				{
					SweepOwnedWorld();
				}
			}
		}

		private void Register(ZNetPeer peer)
		{
			sessions[peer] = new Session();
			peer.m_rpc.Register<string>("BlackheimCT_Hello_v1", (Action<ZRpc, string>)delegate(ZRpc rpc, string version)
			{
				if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && rpc == peer.m_rpc)
				{
					if (version != "0.1.1")
					{
						ScheduleKick(peer, "Install Blackheim Cheat Tag Enforcement 0.1.1", 0f);
					}
					else
					{
						Session session = sessions[peer];
						if (!session.Accepted)
						{
							session.LastPong = Time.realtimeSinceStartup;
						}
						session.Accepted = true;
						rpc.Invoke("BlackheimCT_Policy_v1", new object[1] { "0.1.1" });
					}
				}
			});
			peer.m_rpc.Register<string>("BlackheimCT_Policy_v1", (Action<ZRpc, string>)delegate(ZRpc rpc, string version)
			{
				if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && !(version != "0.1.1") && peer == ZNet.instance.GetServerPeer())
				{
					policyPeer = peer;
					CleanLocal();
				}
			});
			peer.m_rpc.Register<string>("BlackheimCT_Ping_v1", (Action<ZRpc, string>)delegate(ZRpc rpc, string nonce)
			{
				if (ActiveClient && peer == policyPeer)
				{
					rpc.Invoke("BlackheimCT_Pong_v1", new object[1] { nonce });
				}
			});
			peer.m_rpc.Register<string>("BlackheimCT_Pong_v1", (Action<ZRpc, string>)delegate(ZRpc rpc, string nonce)
			{
				if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
				{
					Session session = sessions[peer];
					if (session.Accepted && session.Challenge.Length > 0 && nonce == session.Challenge)
					{
						session.LastPong = Time.realtimeSinceStartup;
						session.Challenge = "";
					}
				}
			});
			peer.m_rpc.Register<int, string>("BlackheimCT_Report_v1", (Action<ZRpc, int, string>)delegate(ZRpc rpc, int count, string description)
			{
				if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && sessions[peer].Accepted && count > 0)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("[client-deletion-report] connection=" + peer.m_uid + " player=" + Safe(peer.m_playerName) + " stacks=" + count + " items=" + Safe(description)));
					Type type = AccessTools.TypeByName("ValheimEnforcer.modules.character.FullSyncSchedulerBehaviour");
					MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "RequestFullSync", (Type[])null, (Type[])null));
					try
					{
						if (methodInfo != null && methodInfo.IsStatic)
						{
							methodInfo.Invoke(null, new object[1] { peer });
						}
						else
						{
							((BaseUnityPlugin)this).Logger.LogWarning((object)"Full-sync request unavailable; client save sent, server persistence must be checked.");
						}
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogError((object)("Full-sync request failed: " + ex.Message));
					}
					ScheduleKick(peer, "Cheat-tagged items removed. You will be disconnected. Rejoin with clean items only.", 8f);
				}
			});
			peer.m_rpc.Register<string>("BlackheimCT_Notice_v1", (Action<ZRpc, string>)delegate(ZRpc rpc, string message)
			{
				if (ActiveClient && peer == policyPeer)
				{
					ShowNotice(message);
				}
			});
			peer.m_rpc.Register<string>("BlackheimCT_Failure_v1", (Action<ZRpc, string>)delegate(ZRpc rpc, string reason)
			{
				if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && sessions[peer].Accepted)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("[client-enforcement-failure] connection=" + peer.m_uid + " " + Safe(reason)));
					ScheduleKick(peer, "Item enforcement could not complete. Connection refused until the client is repaired.", 0f);
				}
			});
		}

		private void ScheduleKick(ZNetPeer peer, string reason, float delay)
		{
			if (sessions.TryGetValue(peer, out var value) && !(value.KickAt > 0f))
			{
				peer.m_rpc.Invoke("BlackheimCT_Notice_v1", new object[1] { reason });
				value.KickAt = Time.realtimeSinceStartup + Math.Max(0.01f, delay);
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[warning] connection=" + peer.m_uid + " player=" + Safe(peer.m_playerName) + " " + reason));
			}
		}

		private void ShowNotice(string message)
		{
			banner = message;
			bannerUntil = Time.realtimeSinceStartup + 45f;
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null, false);
			}
			((BaseUnityPlugin)this).Logger.LogWarning((object)message);
		}

		private void OnGUI()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (banner.Length != 0 && !(Time.realtimeSinceStartup > bannerUntil))
			{
				GUI.Box(new Rect(20f, 20f, (float)Math.Min(Screen.width - 40, 850), 75f), "BLACKHEIM ENFORCEMENT\n" + banner);
			}
		}

		internal void CleanLocal()
		{
			if (ActiveClient && !((Object)(object)Player.m_localPlayer == (Object)null))
			{
				CleanInventory(((Humanoid)Player.m_localPlayer).GetInventory(), "carried", Player.m_localPlayer);
			}
		}

		private void CleanInventory(Inventory inventory, string location, Player player = null)
		{
			if (sweeping || inventory == null)
			{
				return;
			}
			sweeping = true;
			try
			{
				ItemData[] array = (from i in inventory.GetAllItems()
					where i.m_cheated
					select i).ToArray();
				foreach (ItemData val in array)
				{
					Receipt(val, location);
					if ((Object)(object)player != (Object)null && val.m_equipped)
					{
						((Humanoid)player).UnequipItem(val, false);
					}
					inventory.RemoveItem(val);
					if ((Object)(object)player != (Object)null)
					{
						pendingStacks++;
						if (pendingDescription.Length < 1400)
						{
							pendingDescription = pendingDescription + ItemName(val) + " x" + val.m_stack + "; ";
						}
					}
				}
				if ((Object)(object)player != (Object)null && pendingStacks > 0)
				{
					ShowNotice("Cheat-tagged items deleted. This server removes tagged items and kicks on possession.");
				}
			}
			catch (Exception ex)
			{
				if ((Object)(object)player != (Object)null)
				{
					FailClient("Could not remove tagged inventory items", ex);
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("[world-enforcement-failure] " + Safe(location) + " " + ex.Message));
				}
			}
			finally
			{
				sweeping = false;
			}
		}

		private void FailClient(string reason, Exception error)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)(reason + ": " + error.Message));
			ShowNotice("Item enforcement failed; disconnecting. " + reason);
			if (ActiveClient)
			{
				policyPeer.m_rpc.Invoke("BlackheimCT_Failure_v1", new object[1] { reason });
			}
		}

		private void SweepOwnedWorld()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (Container val in array)
			{
				ZNetView component = ((Component)val).GetComponent<ZNetView>();
				if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner())
				{
					CleanInventory(val.GetInventory(), "container " + ((object)((Component)val).transform.position/*cast due to .constrained prefix*/).ToString());
				}
			}
			ItemDrop[] array2 = Object.FindObjectsByType<ItemDrop>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (ItemDrop val2 in array2)
			{
				ZNetView component2 = ((Component)val2).GetComponent<ZNetView>();
				if (!((Object)(object)component2 == (Object)null) && component2.IsValid() && component2.IsOwner())
				{
					if (!val2.m_itemData.m_cheated)
					{
						val2.Load();
					}
					if (val2.m_itemData.m_cheated)
					{
						Receipt(val2.m_itemData, "ground " + ((object)((Component)val2).transform.position/*cast due to .constrained prefix*/).ToString());
						component2.Destroy();
					}
				}
			}
			ItemStand[] array3 = Object.FindObjectsByType<ItemStand>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (ItemStand val3 in array3)
			{
				ZNetView component3 = ((Component)val3).GetComponent<ZNetView>();
				if (!((Object)(object)component3 == (Object)null) && component3.IsValid() && component3.IsOwner() && val3.HaveAttachment())
				{
					ItemData val4 = ReadAttachment(component3.GetZDO(), val3.GetAttachedItem(), -1);
					if (val4 != null && val4.m_cheated)
					{
						Receipt(val4, "item stand " + ((object)((Component)val3).transform.position/*cast due to .constrained prefix*/).ToString());
						val3.DestroyAttachment();
					}
				}
			}
			ArmorStand[] array4 = Object.FindObjectsByType<ArmorStand>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (ArmorStand val5 in array4)
			{
				ZNetView component4 = ((Component)val5).GetComponent<ZNetView>();
				if ((Object)(object)component4 == (Object)null || !component4.IsValid() || !component4.IsOwner())
				{
					continue;
				}
				for (int j = 0; j < val5.m_slots.Count; j++)
				{
					if (val5.HaveAttachment(j))
					{
						ItemData val6 = ReadAttachment(component4.GetZDO(), val5.GetAttachedItem(j), j);
						if (val6 != null && val6.m_cheated)
						{
							Receipt(val6, "armor stand " + ((object)((Component)val5).transform.position/*cast due to .constrained prefix*/).ToString() + " slot=" + j);
							val5.DestroyAttachment(j);
						}
					}
				}
			}
		}

		private static ItemData ReadAttachment(ZDO record, int prefabHash, int slot)
		{
			ObjectDB instance = ObjectDB.instance;
			GameObject val = ((instance != null) ? instance.GetItemPrefab(prefabHash) : null);
			ItemDrop val2 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<ItemDrop>());
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			ItemData obj = val2.m_itemData.Clone();
			obj.m_dropPrefab = val;
			ItemDrop.LoadFromZDO(obj, record, slot);
			return obj;
		}

		private void Receipt(ItemData item, string location)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			item.Save(val);
			string text = DateTime.UtcNow.ToString("O") + "\t" + Safe(location) + "\t" + Safe(ItemName(item)) + "\t" + item.m_stack + "\t" + Convert.ToBase64String(val.GetArray());
			string text2 = Path.Combine(Paths.ConfigPath, "BlackheimCheatTagEnforcement");
			Directory.CreateDirectory(text2);
			File.AppendAllText(Path.Combine(text2, "deleted-" + DateTime.UtcNow.ToString("yyyy-MM-dd") + ".tsv"), text + Environment.NewLine);
			((BaseUnityPlugin)this).Logger.LogWarning((object)("[delete-tagged] " + Safe(location) + " " + Safe(ItemName(item)) + " x" + item.m_stack));
		}

		private static string ItemName(ItemData item)
		{
			object obj;
			if (!((Object)(object)item.m_dropPrefab != (Object)null))
			{
				obj = item.m_shared?.m_name;
				if (obj == null)
				{
					return "unknown";
				}
			}
			else
			{
				obj = ((Object)item.m_dropPrefab).name;
			}
			return (string)obj;
		}

		private static string Safe(string text)
		{
			return (text ?? "").Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ')
				.Substring(0, Math.Min((text ?? "").Length, 1600));
		}
	}
}