Decompiled source of AntiItemTeleport v1.1.0

BepInEx/plugins/RepoAntiItemTeleport.dll

Decompiled 7 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using AntiItemTeleport.Core;
using AntiItemTeleport.Patches;
using BepInEx;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.SceneManagement;

[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("RepoAntiItemTeleport")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.6.0.0")]
[assembly: AssemblyInformationalVersion("2.6.0")]
[assembly: AssemblyProduct("Anti Item Teleport")]
[assembly: AssemblyTitle("RepoAntiItemTeleport")]
[assembly: AssemblyVersion("2.6.0.0")]
namespace AntiItemTeleport
{
	[BepInPlugin("com.sunwu.anti_Item_teleport", "Anti Item Teleport", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PatchInstaller.InstallAll(new Harmony("com.sunwu.anti_Item_teleport"));
			SceneManager.sceneLoaded += delegate
			{
				PatchInstaller.TryInstallDispatchGuard();
			};
			Log.LogInfo((object)"Anti Item Teleport 1.0.0 ready - clients cannot change object positions (enforced on the host only).");
		}

		private void Update()
		{
			PatchInstaller.TryInstallDispatchGuard();
		}
	}
	internal static class PluginInfo
	{
		internal const string Guid = "com.sunwu.anti_Item_teleport";

		internal const string Name = "Anti Item Teleport";

		internal const string Version = "1.0.0";
	}
}
namespace AntiItemTeleport.Patches
{
	internal static class GumballRosterFix
	{
		private static bool _resolved;

		private static bool _ready;

		private static FieldRef<GumballValuable, List<GameObject>> _hypnosisLines;

		private static FieldRef<GumballValuable, List<bool>> _playerInSight;

		private static FieldRef<GumballValuable, List<bool>> _previouslySeen;

		internal static bool Resolve()
		{
			if (_resolved)
			{
				return _ready;
			}
			_resolved = true;
			try
			{
				_hypnosisLines = AccessTools.FieldRefAccess<GumballValuable, List<GameObject>>("hypnosisLines");
				_playerInSight = AccessTools.FieldRefAccess<GumballValuable, List<bool>>("playerInSight");
				_previouslySeen = AccessTools.FieldRefAccess<GumballValuable, List<bool>>("previouslySeenList");
				_ready = true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("GumballValuable internal fields not found, disabling the gumball fix: " + ex.Message));
			}
			return _ready;
		}

		internal static bool Prefix(GumballValuable __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			try
			{
				List<PlayerAvatar> list = SemiFunc.PlayerGetList();
				List<GameObject> list2 = _hypnosisLines.Invoke(__instance);
				if (list == null || list2 == null)
				{
					return true;
				}
				int count = list.Count;
				if (count <= list2.Count)
				{
					return true;
				}
				Grow(_playerInSight.Invoke(__instance), count);
				Grow(_previouslySeen.Invoke(__instance), count);
				while (list2.Count < count)
				{
					GameObject val = CreateHypnosisLine(__instance, list[list2.Count]);
					if ((Object)(object)val == (Object)null)
					{
						GuardLog.Warn("gumball", $"[Gumball] Could not create a hypnosis line (need {count}, have {list2.Count})." + " Skipping the roster update.");
						return false;
					}
					list2.Add(val);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Gumball list repair failed, skipping this frame: " + ex.Message));
				return false;
			}
			return true;
		}

		private static void Grow(List<bool> list, int need)
		{
			if (list != null)
			{
				while (list.Count < need)
				{
					list.Add(item: false);
				}
			}
		}

		private static GameObject CreateHypnosisLine(GumballValuable gumball, PlayerAvatar player)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)gumball.hypnosisLine == (Object)null || (Object)(object)player == (Object)null || (Object)(object)player.localCamera == (Object)null)
			{
				return null;
			}
			Transform overrideTransform = player.localCamera.GetOverrideTransform();
			if ((Object)(object)overrideTransform == (Object)null)
			{
				return null;
			}
			GameObject val = Object.Instantiate<GameObject>(gumball.hypnosisLine, ((Component)gumball).transform.position, Quaternion.identity);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			val.transform.SetParent(overrideTransform);
			val.transform.localPosition = Vector3.zero;
			val.transform.localRotation = Quaternion.identity;
			EyeLines component = val.GetComponent<EyeLines>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val);
				return null;
			}
			component.InitializeLine(player);
			val.transform.SetParent(gumball.HypnosisLinesParenTransform);
			val.SetActive(false);
			return val;
		}
	}
	internal static class RpcDispatchGuard
	{
		private const string TargetMethod = "SetPositionRPC";

		private static readonly object KeyViewId = (byte)0;

		private static readonly object KeyMethodName = (byte)3;

		private static readonly object KeyParameters = (byte)4;

		private static readonly object KeyRpcIndex = (byte)5;

		internal static bool ShouldDrop(Hashtable rpcData, Player sender)
		{
			if (rpcData == null)
			{
				return false;
			}
			if (sender != null && sender == PhotonNetwork.LocalPlayer)
			{
				return false;
			}
			if (!RunGate.HostInGame)
			{
				return false;
			}
			try
			{
				if (ResolveMethodName(rpcData) != "SetPositionRPC")
				{
					return false;
				}
				PhotonView val = FindView(rpcData);
				string text = (((Object)(object)val != (Object)null) ? $"{((Object)val).name} (View {val.ViewID})" : "(unknown)");
				GuardLog.Warn("drop", "[Blocked] Dropped a client object position request | target: " + text + " | position: " + DescribePosition(rpcData) + " | from: " + RpcOrigin.DescribeSender(sender));
				if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).GetComponent<ValuableObject>() != (Object)null)
				{
					CheatReport.Broadcast(sender, ((Object)val).name);
				}
				return true;
			}
			catch (Exception ex)
			{
				GuardLog.Warn("dispatch-error", "Error while screening an inbound RPC, letting it through: " + ex.Message);
				return false;
			}
		}

		private static string ResolveMethodName(Hashtable rpcData)
		{
			if (!((Dictionary<object, object>)(object)rpcData).ContainsKey(KeyRpcIndex))
			{
				return rpcData[KeyMethodName] as string;
			}
			int num = (short)rpcData[KeyRpcIndex];
			List<string> rpcList = PhotonNetwork.PhotonServerSettings.RpcList;
			if (rpcList == null || num < 0 || num >= rpcList.Count)
			{
				return null;
			}
			return rpcList[num];
		}

		private static PhotonView FindView(Hashtable rpcData)
		{
			object obj = (((Dictionary<object, object>)(object)rpcData).ContainsKey(KeyViewId) ? rpcData[KeyViewId] : null);
			int num = ((obj is int) ? ((int)obj) : 0);
			if (num == 0)
			{
				return null;
			}
			try
			{
				return PhotonView.Find(num);
			}
			catch
			{
				return null;
			}
		}

		private static string DescribePosition(Hashtable rpcData)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			object[] array = (((Dictionary<object, object>)(object)rpcData).ContainsKey(KeyParameters) ? (rpcData[KeyParameters] as object[]) : null);
			if (array == null || array.Length < 1 || !(array[0] is Vector3))
			{
				return "(unknown)";
			}
			return ((object)(Vector3)array[0]/*cast due to .constrained prefix*/).ToString();
		}
	}
	internal static class SetPositionRpcGuard
	{
		internal static bool Prefix(PhysGrabObject __instance, Vector3 __0)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			if (!RunGate.HostInGame)
			{
				return true;
			}
			if (TeleportOrigin.InLocalCall)
			{
				return true;
			}
			if (RpcOrigin.Available && !RpcOrigin.InDispatch)
			{
				return true;
			}
			string text = (((Object)(object)__instance != (Object)null) ? ((Object)__instance).name : "(unknown)");
			GuardLog.Warn("deny", "[Blocked] Refused a client object position request | target: " + text + $" | position: {__0}" + " | from: " + RpcOrigin.DescribeSender());
			return false;
		}
	}
	internal static class VoidRescue
	{
		private const float VanillaVoidY = -50f;

		private const float PerObjectCooldown = 2f;

		private static readonly Dictionary<int, float> LastRescue = new Dictionary<int, float>();

		private static float _nextPrune;

		private static bool _valuableRefResolved;

		private static FieldRef<PhysGrabObject, bool> _isValuable;

		internal static void Prefix(PhysGrabObject __instance)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			if (!RunGate.CanMoveObjects || (Object)(object)__instance == (Object)null || __instance.dead || !__instance.spawned)
			{
				return;
			}
			Vector3 position = ((Component)__instance).transform.position;
			if (position.y >= -50f || !IsValuable(__instance))
			{
				return;
			}
			TruckSafetySpawnPoint instance = TruckSafetySpawnPoint.instance;
			if (!((Object)(object)instance == (Object)null) && PassesCooldown(((Object)__instance).GetInstanceID()))
			{
				if ((Object)(object)__instance.rb != (Object)null)
				{
					__instance.rb.velocity = Vector3.zero;
					__instance.rb.angularVelocity = Vector3.zero;
				}
				__instance.Teleport(((Component)instance).transform.position, ((Component)instance).transform.rotation);
				GuardLog.Warn("rescue", "[Rescued] Returned a valuable that fell out of the world to the truck safety spawn" + $" - {((Object)__instance).name} (Y {position.y:F1})");
			}
		}

		private static bool IsValuable(PhysGrabObject target)
		{
			if (!_valuableRefResolved)
			{
				_valuableRefResolved = true;
				try
				{
					_isValuable = AccessTools.FieldRefAccess<PhysGrabObject, bool>("isValuable");
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("PhysGrabObject.isValuable not accessible, falling back to GetComponent: " + ex.Message));
				}
			}
			if (_isValuable == null)
			{
				return (Object)(object)((Component)target).GetComponent<ValuableObject>() != (Object)null;
			}
			return _isValuable.Invoke(target);
		}

		private static bool PassesCooldown(int id)
		{
			float unscaledTime = Time.unscaledTime;
			if (LastRescue.TryGetValue(id, out var value) && unscaledTime - value < 2f)
			{
				return false;
			}
			LastRescue[id] = unscaledTime;
			if (unscaledTime >= _nextPrune || LastRescue.Count >= 1024)
			{
				_nextPrune = unscaledTime + 30f;
				List<int> list = new List<int>();
				foreach (KeyValuePair<int, float> item in LastRescue)
				{
					if (unscaledTime - item.Value > 30f)
					{
						list.Add(item.Key);
					}
				}
				for (int i = 0; i < list.Count; i++)
				{
					LastRescue.Remove(list[i]);
				}
			}
			return true;
		}
	}
}
namespace AntiItemTeleport.Core
{
	internal static class CheatReport
	{
		private const byte EventCode = 171;

		private static bool _listening;

		internal static void StartListening()
		{
			if (_listening)
			{
				return;
			}
			try
			{
				PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
				_listening = true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not subscribe to host block reports: " + ex.Message));
			}
		}

		internal static void Broadcast(Player culprit, string targetName)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if (culprit == null)
			{
				return;
			}
			try
			{
				if (PhotonNetwork.InRoom)
				{
					object[] array = new object[3]
					{
						culprit.ActorNumber,
						RpcOrigin.DescribeSender(culprit),
						targetName
					};
					RaiseEventOptions val = new RaiseEventOptions
					{
						Receivers = (ReceiverGroup)0
					};
					PhotonNetwork.RaiseEvent((byte)171, (object)array, val, SendOptions.SendReliable);
				}
			}
			catch (Exception ex)
			{
				GuardLog.Warn("report-send", "Could not broadcast the block report: " + ex.Message);
			}
		}

		private static void OnEvent(EventData photonEvent)
		{
			if (photonEvent.Code != 171)
			{
				return;
			}
			Player masterClient = PhotonNetwork.MasterClient;
			if (masterClient != null && photonEvent.Sender == masterClient.ActorNumber && photonEvent.CustomData is object[] array && array.Length >= 3)
			{
				string text = array[1] as string;
				string text2 = array[2] as string;
				if (!string.IsNullOrEmpty(text))
				{
					GuardLog.Warn("report", "[Host report] The host blocked a valuable position request | from: " + text + " | target: " + text2);
				}
			}
		}
	}
	internal static class GuardLog
	{
		private struct Bucket
		{
			public float NextEmit;

			public int Suppressed;
		}

		private const float Window = 1f;

		private static readonly Dictionary<string, Bucket> Buckets = new Dictionary<string, Bucket>();

		internal static void Warn(string key, string message)
		{
			float unscaledTime = Time.unscaledTime;
			if (!Buckets.TryGetValue(key, out var value))
			{
				value = default(Bucket);
			}
			if (unscaledTime < value.NextEmit)
			{
				value.Suppressed++;
				Buckets[key] = value;
				return;
			}
			if (value.Suppressed > 0)
			{
				message += $"  ({value.Suppressed} more of the same suppressed in the last second)";
			}
			value.Suppressed = 0;
			value.NextEmit = unscaledTime + 1f;
			Buckets[key] = value;
			Plugin.Log.LogWarning((object)message);
		}
	}
	internal static class PatchInstaller
	{
		private static Harmony _harmony;

		private static bool _dispatchGuardDone;

		internal static void InstallAll(Harmony harmony)
		{
			_harmony = harmony;
			if (Apply(harmony, "Call origin marker (PhysGrabObject.Teleport)", AccessTools.Method(typeof(PhysGrabObject), "Teleport", new Type[2]
			{
				typeof(Vector3),
				typeof(Quaternion)
			}, (Type[])null), Method(typeof(TeleportOrigin), "Enter"), Method(typeof(TeleportOrigin), "Leave")))
			{
				Apply(harmony, "Second layer (PhysGrabObject.SetPositionRPC)", AccessTools.Method(typeof(PhysGrabObject), "SetPositionRPC", (Type[])null, (Type[])null), Method(typeof(SetPositionRpcGuard), "Prefix"));
			}
			else
			{
				Plugin.Log.LogError((object)"PhysGrabObject.Teleport not found. Cannot tell legitimate moves from cheats, so the second layer is not installed.");
			}
			Apply(harmony, "Fallen valuable rescue (PhysGrabObject.Update)", AccessTools.Method(typeof(PhysGrabObject), "Update", (Type[])null, (Type[])null), Method(typeof(VoidRescue), "Prefix"));
			if (GumballRosterFix.Resolve())
			{
				Apply(harmony, "Gumball roster fix (GumballValuable.CheckForLeftPlayers)", AccessTools.Method(typeof(GumballValuable), "CheckForLeftPlayers", (Type[])null, (Type[])null), Method(typeof(GumballRosterFix), "Prefix"));
			}
		}

		internal static void TryInstallDispatchGuard()
		{
			if (!_dispatchGuardDone && _harmony != null && !((Object)(object)GameObject.Find("PhotonMono") == (Object)null))
			{
				_dispatchGuardDone = true;
				CheatReport.StartListening();
				try
				{
					RpcOrigin.Available = Apply(_harmony, "First layer (PhotonNetwork.ExecuteRpc)", AccessTools.Method(typeof(PhotonNetwork), "ExecuteRpc", (Type[])null, (Type[])null), Method(typeof(RpcOrigin), "Enter"), Method(typeof(RpcOrigin), "Leave"));
				}
				catch (Exception ex)
				{
					RpcOrigin.Available = false;
					Plugin.Log.LogError((object)("Failed to install the first layer: " + ex.Message));
				}
				if (!RpcOrigin.Available)
				{
					Plugin.Log.LogError((object)"Running on the second layer (direct SetPositionRPC check) only.");
				}
			}
		}

		private static bool Apply(Harmony harmony, string label, MethodBase target, HarmonyMethod prefix = null, HarmonyMethod finalizer = null)
		{
			if (target == null)
			{
				Plugin.Log.LogWarning((object)("Target not found, skipping - " + label));
				return false;
			}
			try
			{
				harmony.Patch(target, prefix, (HarmonyMethod)null, (HarmonyMethod)null, finalizer, (HarmonyMethod)null);
				Plugin.Log.LogInfo((object)("Installed - " + label));
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to install - " + label + ": " + ex.Message));
				return false;
			}
		}

		private static HarmonyMethod Method(Type type, string name)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(type, name, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				throw new InvalidOperationException("Patch method not found: " + type.Name + "." + name);
			}
			return new HarmonyMethod(methodInfo);
		}
	}
	internal static class RpcOrigin
	{
		private const int SanityLimit = 64;

		internal static bool Available;

		private static readonly List<Player> Stack = new List<Player>(8);

		private static bool _warned;

		internal static bool InDispatch => Stack.Count > 0;

		internal static Player CurrentSender
		{
			get
			{
				if (Stack.Count <= 0)
				{
					return null;
				}
				return Stack[Stack.Count - 1];
			}
		}

		internal static bool Enter(Hashtable rpcData, Player sender)
		{
			Stack.Add(sender);
			if (Stack.Count > 64)
			{
				Stack.Clear();
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogWarning((object)"Inbound RPC sender stack grew abnormally deep and was reset.");
				}
				return true;
			}
			return !RpcDispatchGuard.ShouldDrop(rpcData, sender);
		}

		internal static void Leave()
		{
			int num = Stack.Count - 1;
			if (num >= 0)
			{
				Stack.RemoveAt(num);
			}
		}

		internal static string DescribeSender()
		{
			return DescribeSender(CurrentSender);
		}

		internal static string DescribeSender(Player sender)
		{
			if (sender == null)
			{
				return "unknown client";
			}
			try
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonPlayer(sender);
				if ((Object)(object)val != (Object)null)
				{
					return $"{SemiFunc.PlayerGetName(val)} (SteamID {SemiFunc.PlayerGetSteamID(val)}, Actor {sender.ActorNumber})";
				}
			}
			catch
			{
			}
			return $"{sender.NickName} (Actor {sender.ActorNumber})";
		}
	}
	internal static class RunGate
	{
		private static int _frame = -1;

		private static bool _inGame;

		private static bool _host;

		private static bool _levelReady;

		internal static bool HostInGame
		{
			get
			{
				Refresh();
				if (_inGame)
				{
					return _host;
				}
				return false;
			}
		}

		internal static bool CanMoveObjects
		{
			get
			{
				Refresh();
				if (_inGame && _host)
				{
					return _levelReady;
				}
				return false;
			}
		}

		private static void Refresh()
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Invalid comparison between Unknown and I4
			int frameCount = Time.frameCount;
			if (frameCount == _frame)
			{
				return;
			}
			_frame = frameCount;
			_inGame = false;
			_host = false;
			_levelReady = false;
			try
			{
				if ((Object)(object)RunManager.instance == (Object)null || SemiFunc.MenuLevel())
				{
					return;
				}
				_inGame = true;
				_host = SemiFunc.IsMasterClientOrSingleplayer();
				_levelReady = (Object)(object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated && (Object)(object)GameDirector.instance != (Object)null && (int)GameDirector.instance.currentState == 2;
			}
			catch (Exception)
			{
				_inGame = false;
				_host = false;
				_levelReady = false;
			}
			PatchInstaller.TryInstallDispatchGuard();
		}
	}
	internal static class TeleportOrigin
	{
		private const int SanityLimit = 64;

		private static int _depth;

		private static bool _warned;

		internal static bool InLocalCall => _depth > 0;

		internal static void Enter()
		{
			_depth++;
			if (_depth > 64)
			{
				_depth = 0;
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogWarning((object)"Teleport call depth grew abnormally large and was reset.");
				}
			}
		}

		internal static void Leave()
		{
			if (_depth > 0)
			{
				_depth--;
			}
		}
	}
}