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;
}
}