Decompiled source of RepUtilsDefense v1.0.0

RepUtilsDefense.dll

Decompiled a day 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 System.Security.Cryptography;
using System.Text;
using HarmonyLib;
using MelonLoader;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("RepUtilsDefense")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RepUtilsDefense")]
[assembly: AssemblyTitle("RepUtilsDefense")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace RepUtilsDefense;

public sealed class RepUtilsDefenseMod : MelonMod
{
	public override void OnInitializeMelon()
	{
		DetectionResult detectionResult = RepUtilsDetector.Scan();
		if (detectionResult.IsMatch)
		{
			MelonLogger.Warning("RepUtilsV5 fingerprint detected: " + detectionResult.Reason);
			MelonLogger.Warning("RepUtils defensive containment is active; host-side validation is still required.");
			RepUtilsDiagnostics.Start(detectionResult.Reason);
			FusionRuntimeProbe.Report();
			FusionCallGuard.Install();
			CrashCallGuard.Install();
		}
	}
}
internal static class RepUtilsDiagnostics
{
	private static bool started;

	private static readonly object FileGate = new object();

	public static void Start(string reason)
	{
		if (!started)
		{
			started = true;
			MelonLogger.Msg("[RepUtilsDefense][Debug] tracing confirmed Reputils activity; detection reason: " + reason);
			Write("START detection reason=" + reason);
		}
	}

	public static void Trace(string category, MethodBase method, object[] args, string reason)
	{
		string text = ((method == null) ? "unknown method" : (method.DeclaringType?.ToString() + "." + method.Name));
		string text2 = category + " blocked: " + text + "; reason=" + reason + "; args=" + DescribeArgs(args);
		MelonLogger.Warning("[RepUtilsDefense][Debug] " + text2);
		Write(text2);
	}

	public static void Trace(string category, string reason)
	{
		MelonLogger.Warning("[RepUtilsDefense][Debug] " + category + ": " + reason);
		Write(category + ": " + reason);
	}

	private static void Write(string message)
	{
		try
		{
			string text = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UserData");
			Directory.CreateDirectory(text);
			string path = Path.Combine(text, "RepUtilsDefense-debug.log");
			lock (FileGate)
			{
				File.AppendAllText(path, DateTime.UtcNow.ToString("O") + " " + message + Environment.NewLine, Encoding.UTF8);
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("[RepUtilsDefense] Could not write UserData debug log: " + ex.GetType().Name);
		}
	}

	private static string DescribeArgs(object[] args)
	{
		if (args == null || args.Length == 0)
		{
			return "none";
		}
		return string.Join(",", args.Select((object value) => (value != null) ? value.GetType().FullName : "null"));
	}
}
public sealed class DetectionResult
{
	public bool IsMatch { get; set; }

	public string Reason { get; set; }
}
public static class RepUtilsDetector
{
	private const string KnownSha256 = "37D3F3FC06D80E50D96F61E8ABFC08CEF64B60F8926CF02F7BBF58B3663C063E";

	public static DetectionResult Scan()
	{
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			string text = assembly.GetName().Name ?? string.Empty;
			if (text.Equals("RepUtilsV5", StringComparison.OrdinalIgnoreCase) || text.Equals("melonmenu", StringComparison.OrdinalIgnoreCase))
			{
				return new DetectionResult
				{
					IsMatch = true,
					Reason = "assembly name: " + text
				};
			}
			string text2 = SafeLocation(assembly);
			if (!string.IsNullOrEmpty(text2) && File.Exists(text2) && Sha256(text2).Equals("37D3F3FC06D80E50D96F61E8ABFC08CEF64B60F8926CF02F7BBF58B3663C063E", StringComparison.OrdinalIgnoreCase))
			{
				return new DetectionResult
				{
					IsMatch = true,
					Reason = "known SHA-256 match"
				};
			}
			Type[] source = SafeTypes(assembly);
			bool flag = source.Any((Type t) => t.FullName == "A.C");
			bool flag2 = source.Any((Type t) => t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Any((MethodInfo m) => m.Name == "OnOwnedUpdate"));
			if (flag && flag2)
			{
				return new DetectionResult
				{
					IsMatch = true,
					Reason = "obfuscated RepUtils type fingerprint"
				};
			}
		}
		return new DetectionResult
		{
			IsMatch = false,
			Reason = string.Empty
		};
	}

	private static string SafeLocation(Assembly assembly)
	{
		try
		{
			return assembly.Location;
		}
		catch
		{
			return string.Empty;
		}
	}

	private static Type[] SafeTypes(Assembly assembly)
	{
		try
		{
			return assembly.GetTypes();
		}
		catch (ReflectionTypeLoadException ex)
		{
			return ex.Types.Where((Type t) => t != null).ToArray();
		}
		catch
		{
			return new Type[0];
		}
	}

	private static string Sha256(string path)
	{
		using SHA256 sHA = SHA256.Create();
		using FileStream inputStream = File.OpenRead(path);
		return string.Concat(from b in sHA.ComputeHash(inputStream)
			select b.ToString("X2"));
	}
}
internal static class FusionRuntimeProbe
{
	public static void Report()
	{
		Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => (a.GetName().Name ?? string.Empty).Equals("LabFusion", StringComparison.OrdinalIgnoreCase));
		if (assembly == null)
		{
			MelonLogger.Warning("[RepUtilsDefense] LabFusion was not loaded; network containment is unavailable.");
			return;
		}
		string text = assembly.GetName().Version?.ToString() ?? "unknown";
		bool flag = HasType(assembly, "LabFusion.Network.EOSRuntime") || HasType(assembly, "LabFusion.Network.EOSInterface");
		bool flag2 = HasType(assembly, "LabFusion.Network.SteamMatchmaker") || HasType(assembly, "LabFusion.Network.SteamNetworkLayer");
		string text2 = ((flag && flag2) ? "EOS + Steam" : (flag ? "EOS" : (flag2 ? "Steam" : "unknown backend")));
		MelonLogger.Msg("[RepUtilsDefense] LabFusion " + text + " detected (" + text2 + ").");
	}

	private static bool HasType(Assembly assembly, string fullName)
	{
		try
		{
			return assembly.GetType(fullName, throwOnError: false) != null;
		}
		catch
		{
			return false;
		}
	}
}
internal static class FusionCallGuard
{
	private static Harmony harmony;

	private static int blockedCalls;

	private static readonly object Gate = new object();

	private static readonly Queue<long> RecentCalls = new Queue<long>();

	private const int MaxCallsPerSecond = 40;

	public static void Install()
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Expected O, but got Unknown
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Expected O, but got Unknown
		if (harmony != null)
		{
			return;
		}
		Type type = FindType("LabFusion.RPC.NetworkAssetSpawner") ?? FindType("LabFusion.Network.NetworkAssetSpawner");
		if (type == null)
		{
			MelonLogger.Warning("LabFusion.RPC.NetworkAssetSpawner was not found; local spawn protection is unavailable.");
			return;
		}
		harmony = new Harmony("rep.utils.defense");
		MethodInfo method = typeof(FusionCallGuard).GetMethod("BlockRepUtilsCall", BindingFlags.Static | BindingFlags.NonPublic);
		int num = 0;
		MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (MethodInfo methodInfo in methods)
		{
			if (!(methodInfo.Name != "Spawn") || !(methodInfo.Name != "Despawn"))
			{
				try
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("Could not guard Fusion method " + methodInfo?.ToString() + ": " + ex.GetType().Name);
				}
			}
		}
		MelonLogger.Msg("RepUtils local Fusion guard installed on " + num + " spawn/despawn entry points.");
	}

	private static bool BlockRepUtilsCall(MethodBase __originalMethod, object[] __args)
	{
		bool flag = CalledByRepUtils();
		bool flag2 = !flag && ExceedsRateLimit();
		if (!flag && !flag2)
		{
			return true;
		}
		blockedCalls++;
		if (flag)
		{
			RepUtilsDiagnostics.Trace("Fusion call", __originalMethod, __args, "caller assembly matched RepUtilsV5/melonmenu");
		}
		if (blockedCalls == 1 || blockedCalls % 50 == 0)
		{
			MelonLogger.Warning(flag ? ("Blocked RepUtils Fusion spawn/despawn call #" + blockedCalls + ".") : ("Blocked an excessive Fusion spawn/despawn burst #" + blockedCalls + "."));
		}
		HostKick.TryKickSender(__args, flag ? "RepUtils network call" : "spawn/despawn rate limit");
		return false;
	}

	private static bool ExceedsRateLimit()
	{
		long timestamp = Stopwatch.GetTimestamp();
		long frequency = Stopwatch.Frequency;
		lock (Gate)
		{
			while (RecentCalls.Count > 0 && timestamp - RecentCalls.Peek() > frequency)
			{
				RecentCalls.Dequeue();
			}
			RecentCalls.Enqueue(timestamp);
			return RecentCalls.Count > 40;
		}
	}

	private static bool CalledByRepUtils()
	{
		StackFrame[] frames = new StackTrace().GetFrames();
		if (frames == null)
		{
			return false;
		}
		StackFrame[] array = frames;
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].GetMethod()?.DeclaringType?.Assembly?.GetName()?.Name;
			if (text != null && (text.Equals("RepUtilsV5", StringComparison.OrdinalIgnoreCase) || text.Equals("melonmenu", StringComparison.OrdinalIgnoreCase)))
			{
				return true;
			}
		}
		return false;
	}

	private static Type FindType(string fullName)
	{
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		for (int i = 0; i < assemblies.Length; i++)
		{
			Type type = assemblies[i].GetType(fullName, throwOnError: false);
			if (type != null)
			{
				return type;
			}
		}
		return null;
	}
}
internal static class CrashCallGuard
{
	private static Harmony harmony;

	private static int blockedCrashCalls;

	public static void Install()
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		if (harmony == null)
		{
			harmony = new Harmony("rep.utils.defense.crash");
			int num = 0;
			num += PatchKnownMethod("B.BC", "c");
			num += PatchByName("UnityEngine.Diagnostics.Utils", "ForceCrash");
			MelonLogger.Msg("RepUtils crash containment installed on " + (num + PatchByName("UnityEngine.Application", "Quit")) + " entry points.");
		}
	}

	private static int PatchKnownMethod(string typeName, string methodName)
	{
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Expected O, but got Unknown
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			Type type = null;
			try
			{
				type = assembly.GetType(typeName, throwOnError: false);
			}
			catch
			{
			}
			if (type == null)
			{
				continue;
			}
			MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (!(method == null))
			{
				try
				{
					harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(CrashCallGuard), "BlockRepUtilsCrash", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					return 1;
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("Could not guard " + typeName + "." + methodName + ": " + ex.GetType().Name);
				}
			}
		}
		return 0;
	}

	private static int PatchByName(string typeName, string methodName)
	{
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Expected O, but got Unknown
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			Type type = null;
			try
			{
				type = assembly.GetType(typeName, throwOnError: false);
			}
			catch
			{
			}
			if (type == null)
			{
				continue;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name.Equals(methodName, StringComparison.Ordinal))
				{
					try
					{
						harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(CrashCallGuard), "BlockRepUtilsCrash", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						return 1;
					}
					catch (Exception ex)
					{
						MelonLogger.Warning("Could not guard " + typeName + "." + methodName + ": " + ex.GetType().Name);
					}
				}
			}
		}
		return 0;
	}

	private static bool BlockRepUtilsCrash()
	{
		if (!CalledByRepUtils())
		{
			return true;
		}
		blockedCrashCalls++;
		RepUtilsDiagnostics.Trace("Crash/quit call", "caller assembly matched RepUtilsV5/melonmenu");
		if (blockedCrashCalls == 1 || blockedCrashCalls % 20 == 0)
		{
			MelonLogger.Warning("Blocked RepUtils crash/quit call #" + blockedCrashCalls + ".");
		}
		return false;
	}

	private static bool CalledByRepUtils()
	{
		StackFrame[] frames = new StackTrace().GetFrames();
		if (frames == null)
		{
			return false;
		}
		StackFrame[] array = frames;
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].GetMethod()?.DeclaringType?.Assembly?.GetName()?.Name;
			if (text != null && (text.Equals("RepUtilsV5", StringComparison.OrdinalIgnoreCase) || text.Equals("melonmenu", StringComparison.OrdinalIgnoreCase)))
			{
				return true;
			}
		}
		return false;
	}
}
internal static class HostKick
{
	private static bool warnedUnavailable;

	private static readonly HashSet<object> Kicked = new HashSet<object>();

	public static void TryKickSender(object[] args, string reason)
	{
		try
		{
			object obj = FindPlayerId(args, 0, new HashSet<object>());
			if (obj == null)
			{
				if (!warnedUnavailable)
				{
					warnedUnavailable = true;
					MelonLogger.Warning("[RepUtilsDefense] Abuse blocked, but Fusion did not expose a sender PlayerID. No player identity was guessed.");
				}
				return;
			}
			MelonLogger.Warning("[RepUtilsDefense] Suspected Reputils user detected: " + Describe(obj) + ". Tell the Fusion host to kick this player.");
			PropertyInfo propertyInfo = FindType("LabFusion.Network.NetworkInfo")?.GetProperty("IsHost", BindingFlags.Static | BindingFlags.Public);
			if (propertyInfo == null || !(bool)propertyInfo.GetValue(null))
			{
				return;
			}
			PropertyInfo property = obj.GetType().GetProperty("IsHost", BindingFlags.Instance | BindingFlags.Public);
			if ((!(property != null) || !(property.PropertyType == typeof(bool)) || !(bool)property.GetValue(obj)) && Kicked.Add(obj))
			{
				MethodInfo methodInfo = (FindType("LabFusion.Utilities.NetworkHelper") ?? FindType("LabFusion.Network.NetworkHelper"))?.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault((MethodInfo m) => m.Name == "KickUser" && m.GetParameters().Length == 1);
				if (methodInfo == null)
				{
					MelonLogger.Warning("Fusion host kick API was not found; abuse was blocked locally.");
					return;
				}
				methodInfo.Invoke(null, new object[1] { obj });
				MelonLogger.Warning("Kicked a Fusion player after blocking " + reason + ".");
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("Could not kick the abusive Fusion sender: " + ex.GetType().Name);
		}
	}

	private static object FindPlayerId(object[] args, int depth, HashSet<object> visited)
	{
		if (args == null)
		{
			return null;
		}
		for (int i = 0; i < args.Length; i++)
		{
			object obj = FindPlayerId(args[i], depth, visited);
			if (obj != null)
			{
				return obj;
			}
		}
		return null;
	}

	private static object FindPlayerId(object value, int depth, HashSet<object> visited)
	{
		if (value == null || depth > 2 || value is string || value.GetType().IsPrimitive)
		{
			return null;
		}
		Type type = value.GetType();
		if (type.FullName != null && type.FullName.EndsWith(".PlayerID", StringComparison.Ordinal))
		{
			return value;
		}
		if (!visited.Add(value))
		{
			return null;
		}
		PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
		foreach (PropertyInfo propertyInfo in properties)
		{
			if (propertyInfo.GetIndexParameters().Length != 0 || (!propertyInfo.Name.Equals("PlayerID", StringComparison.OrdinalIgnoreCase) && !propertyInfo.Name.Equals("Sender", StringComparison.OrdinalIgnoreCase) && !propertyInfo.Name.Equals("SenderID", StringComparison.OrdinalIgnoreCase) && !propertyInfo.Name.Equals("OwnerID", StringComparison.OrdinalIgnoreCase) && !propertyInfo.Name.Equals("Owner", StringComparison.OrdinalIgnoreCase)))
			{
				continue;
			}
			try
			{
				object obj = FindPlayerId(propertyInfo.GetValue(value), depth + 1, visited);
				if (obj != null)
				{
					return obj;
				}
			}
			catch
			{
			}
		}
		return null;
	}

	private static string Describe(object playerId)
	{
		string text = ReadProperty(playerId, "PlatformID") ?? ReadProperty(playerId, "PlatformId") ?? "unknown platform ID";
		string text2 = ReadProperty(playerId, "Username") ?? ReadProperty(playerId, "UserName") ?? ReadProperty(playerId, "Name");
		if (!string.IsNullOrEmpty(text2))
		{
			return text2 + " (" + text + ")";
		}
		return text;
	}

	private static string ReadProperty(object value, string name)
	{
		try
		{
			return (value.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public)?.GetValue(value))?.ToString();
		}
		catch
		{
			return null;
		}
	}

	private static Type FindType(string fullName)
	{
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			try
			{
				Type type = assembly.GetType(fullName, throwOnError: false);
				if (type != null)
				{
					return type;
				}
			}
			catch
			{
			}
		}
		return null;
	}
}