Decompiled source of RustyHeathensControllerAudit v0.1.0

RustyHeathensControllerAudit.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("RustyHeathensControllerAudit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+53644460c4405c784c83f30f8a806ea966ac4b58")]
[assembly: AssemblyProduct("RustyHeathensControllerAudit")]
[assembly: AssemblyTitle("RustyHeathensControllerAudit")]
[assembly: AssemblyVersion("0.1.0.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 RustyHeathens.ControllerAudit
{
	[BepInPlugin("com.rustylasergaming.controlleraudit", "Rusty Heathens Controller Audit", "0.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class DeviceSnapshot
		{
			public List<string> Legacy = new List<string>();

			public List<string> InputSystem = new List<string>();

			public bool InputSystemUpdatedThisFrame;

			public bool Detected
			{
				get
				{
					if (Legacy.Count <= 0)
					{
						return InputSystem.Count > 0;
					}
					return true;
				}
			}

			public string Fingerprint => string.Join(";;", Legacy.ToArray()) + "##" + string.Join(";;", InputSystem.ToArray());

			public static DeviceSnapshot Capture()
			{
				DeviceSnapshot deviceSnapshot = new DeviceSnapshot();
				try
				{
					string[] array = Input.GetJoystickNames() ?? new string[0];
					for (int i = 0; i < array.Length; i++)
					{
						string text = Safe(array[i], 160).Trim();
						if (!string.IsNullOrEmpty(text) && !deviceSnapshot.Legacy.Contains(text))
						{
							deviceSnapshot.Legacy.Add(text);
						}
					}
				}
				catch
				{
				}
				CaptureInputSystem(deviceSnapshot);
				deviceSnapshot.Legacy.Sort(StringComparer.OrdinalIgnoreCase);
				deviceSnapshot.InputSystem.Sort(StringComparer.OrdinalIgnoreCase);
				return deviceSnapshot;
			}

			private static void CaptureInputSystem(DeviceSnapshot snapshot)
			{
				Type type = FindType("UnityEngine.InputSystem.Gamepad");
				if (type == null)
				{
					return;
				}
				try
				{
					PropertyInfo property = type.GetProperty("all", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					object obj = ((property == null) ? null : property.GetValue(null, null));
					if (obj == null)
					{
						return;
					}
					foreach (object item2 in EnumerateReadOnlyArray(obj))
					{
						if (item2 == null)
						{
							continue;
						}
						string value = Convert.ToString(ReadMember(item2, "displayName")) ?? "";
						string value2 = Convert.ToString(ReadMember(item2, "name")) ?? "";
						string value3 = Convert.ToString(ReadMember(item2, "layout")) ?? "";
						string value4 = Convert.ToString(ReadMember(item2, "deviceId")) ?? "";
						object instance = ReadMember(item2, "description");
						string value5 = Convert.ToString(ReadMember(instance, "product")) ?? "";
						string value6 = Convert.ToString(ReadMember(instance, "manufacturer")) ?? "";
						string value7 = Convert.ToString(ReadMember(instance, "interfaceName")) ?? "";
						string capabilities = Convert.ToString(ReadMember(instance, "capabilities")) ?? "";
						string text = MatchId(VendorRegex, capabilities);
						string text2 = MatchId(ProductRegex, capabilities);
						bool flag = false;
						object obj2 = ReadMember(item2, "wasUpdatedThisFrame");
						if (obj2 != null)
						{
							try
							{
								flag = Convert.ToBoolean(obj2);
							}
							catch
							{
							}
						}
						if (flag)
						{
							snapshot.InputSystemUpdatedThisFrame = true;
						}
						string item = "display=" + Safe(value, 120) + ";product=" + Safe(value5, 120) + ";manufacturer=" + Safe(value6, 100) + ";interface=" + Safe(value7, 80) + ";layout=" + Safe(value3, 80) + ";name=" + Safe(value2, 80) + ";deviceId=" + Safe(value4, 20) + ";vendorId=" + text + ";productId=" + text2;
						if (!snapshot.InputSystem.Contains(item))
						{
							snapshot.InputSystem.Add(item);
						}
					}
				}
				catch
				{
				}
			}

			private static IEnumerable EnumerateReadOnlyArray(object value)
			{
				if (value is IEnumerable result)
				{
					return result;
				}
				List<object> list = new List<object>();
				Type type = value.GetType();
				object value2 = ReadMember(value, "Count");
				int num = 0;
				try
				{
					num = Convert.ToInt32(value2);
				}
				catch
				{
				}
				PropertyInfo property = type.GetProperty("Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null)
				{
					for (int i = 0; i < num; i++)
					{
						try
						{
							list.Add(property.GetValue(value, new object[1] { i }));
						}
						catch
						{
						}
					}
				}
				return list;
			}

			private static string MatchId(Regex regex, string capabilities)
			{
				Match match = regex.Match(capabilities ?? "");
				if (!match.Success)
				{
					return "";
				}
				return match.Groups[1].Value;
			}

			public string ToPayload(bool activitySeen)
			{
				return "protocol=1|detected=" + (Detected ? "1" : "0") + "|activity=" + (activitySeen ? "1" : "0") + "|legacy=" + Safe(string.Join(" ;; ", Legacy.ToArray()), 800) + "|input=" + Safe(string.Join(" ;; ", InputSystem.ToArray()), 1800);
			}

			public string ToLocalSummary()
			{
				return "legacy=[" + Safe(string.Join(" ;; ", Legacy.ToArray()), 500) + "] input_system=[" + Safe(string.Join(" ;; ", InputSystem.ToArray()), 900) + "]";
			}
		}

		private sealed class AuditReport
		{
			public bool Detected;

			public bool ActivitySeen;

			public string Legacy = "";

			public string InputSystem = "";

			public static AuditReport Parse(string payload)
			{
				AuditReport auditReport = new AuditReport();
				string[] array = (payload ?? "").Split(new char[1] { '|' });
				foreach (string text in array)
				{
					int num = text.IndexOf('=');
					if (num > 0)
					{
						string text2 = text.Substring(0, num);
						string text3 = text.Substring(num + 1);
						switch (text2)
						{
						case "detected":
							auditReport.Detected = text3 == "1";
							break;
						case "activity":
							auditReport.ActivitySeen = text3 == "1";
							break;
						case "legacy":
							auditReport.Legacy = text3;
							break;
						case "input":
							auditReport.InputSystem = text3;
							break;
						}
					}
				}
				return auditReport;
			}
		}

		public const string PluginGuid = "com.rustylasergaming.controlleraudit";

		public const string PluginName = "Rusty Heathens Controller Audit";

		public const string PluginVersion = "0.1.0";

		private const string RpcName = "RHCA_ControllerAudit_v1";

		private ConfigEntry<bool> _enabled;

		private ConfigEntry<float> _reportEverySeconds;

		private ConfigEntry<bool> _logClientChanges;

		private object _lastZNet;

		private bool _rpcRegistered;

		private float _nextRpcCheck;

		private float _nextReport;

		private string _lastFingerprint = "";

		private bool _activitySeen;

		private static readonly Regex VendorRegex = new Regex("\\\"vendorId\\\"\\s*:\\s*(\\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Regex ProductRegex = new Regex("\\\"productId\\\"\\s*:\\s*(\\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private void Awake()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Audit", "Enabled", true, "Audit only. Detects controller/gamepad device names and reports them to the server. No controller input is blocked.");
			_reportEverySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Audit", "ReportEverySeconds", 15f, new ConfigDescription("How often a connected client sends a controller audit heartbeat. Device changes send immediately.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 120f), Array.Empty<object>()));
			_logClientChanges = ((BaseUnityPlugin)this).Config.Bind<bool>("Audit", "LogClientChanges", true, "Write a local log line when the detected controller list changes.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[RHCA] 0.1.0 loaded. AUDIT ONLY: no input is blocked and no serial numbers are collected.");
		}

		private void Update()
		{
			if (!_enabled.Value)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup >= _nextRpcCheck)
			{
				_nextRpcCheck = realtimeSinceStartup + 2f;
				EnsureRpc();
			}
			if (!_rpcRegistered || IsServer())
			{
				return;
			}
			if (LegacyControllerButtonPressed())
			{
				_activitySeen = true;
			}
			DeviceSnapshot deviceSnapshot = DeviceSnapshot.Capture();
			if (deviceSnapshot.InputSystemUpdatedThisFrame)
			{
				_activitySeen = true;
			}
			string fingerprint = deviceSnapshot.Fingerprint;
			if (!string.Equals(fingerprint, _lastFingerprint, StringComparison.Ordinal))
			{
				_lastFingerprint = fingerprint;
				_nextReport = 0f;
				if (_logClientChanges.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("[RHCA] Controller devices changed: " + deviceSnapshot.ToLocalSummary()));
				}
			}
			if (realtimeSinceStartup < _nextReport)
			{
				return;
			}
			long serverPeerUid = GetServerPeerUid();
			if (serverPeerUid != 0L)
			{
				_nextReport = realtimeSinceStartup + Mathf.Clamp(_reportEverySeconds.Value, 5f, 120f);
				string payload = deviceSnapshot.ToPayload(_activitySeen);
				if (InvokeRoutedRpc(serverPeerUid, "RHCA_ControllerAudit_v1", payload))
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("[RHCA] Controller audit sent. detected=" + deviceSnapshot.Detected + " activity_seen=" + _activitySeen));
					_activitySeen = false;
				}
			}
		}

		private void EnsureRpc()
		{
			object staticInstance = GetStaticInstance(FindType("ZNet"));
			if (staticInstance == null)
			{
				_rpcRegistered = false;
				_lastZNet = null;
				return;
			}
			if (staticInstance != _lastZNet)
			{
				_lastZNet = staticInstance;
				_rpcRegistered = false;
			}
			if (_rpcRegistered)
			{
				return;
			}
			Type type = FindType("ZRoutedRpc");
			object staticInstance2 = GetStaticInstance(type);
			if (staticInstance2 == null || type == null)
			{
				return;
			}
			try
			{
				MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Register" && m.IsGenericMethodDefinition && m.GetParameters().Length == 2);
				if (!(methodInfo == null))
				{
					MethodInfo methodInfo2 = methodInfo.MakeGenericMethod(typeof(string));
					Action<long, string> action = OnAuditReport;
					methodInfo2.Invoke(staticInstance2, new object[2] { "RHCA_ControllerAudit_v1", action });
					_rpcRegistered = true;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[RHCA] Routed controller-audit RPC registered.");
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[RHCA] Could not register audit RPC: " + ex.GetType().Name));
			}
		}

		private void OnAuditReport(long sender, string payload)
		{
			if (!IsServer())
			{
				return;
			}
			try
			{
				AuditReport auditReport = AuditReport.Parse(payload);
				string value = FindPeerName(sender);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[RHCA] CONTROLLER_AUDIT player=\\\"" + Safe(value, 80) + "\\\" uid=" + sender + " detected=" + auditReport.Detected + " activity_seen=" + auditReport.ActivitySeen + " legacy=[" + Safe(auditReport.Legacy, 700) + "] input_system=[" + Safe(auditReport.InputSystem, 1400) + "]"));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[RHCA] Controller audit from uid=" + sender + " could not be parsed: " + ex.GetType().Name));
			}
		}

		private static bool LegacyControllerButtonPressed()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				for (int i = 0; i <= 19; i++)
				{
					if (Enum.TryParse<KeyCode>("JoystickButton" + i, out KeyCode result) && Input.GetKey(result))
					{
						return true;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private bool IsServer()
		{
			object staticInstance = GetStaticInstance(FindType("ZNet"));
			if (staticInstance == null)
			{
				return false;
			}
			try
			{
				MethodInfo method = staticInstance.GetType().GetMethod("IsServer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				return method != null && Convert.ToBoolean(method.Invoke(staticInstance, null));
			}
			catch
			{
				return false;
			}
		}

		private long GetServerPeerUid()
		{
			object staticInstance = GetStaticInstance(FindType("ZNet"));
			if (staticInstance == null)
			{
				return 0L;
			}
			try
			{
				MethodInfo method = staticInstance.GetType().GetMethod("GetServerPeer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				return ConvertToLong(ReadMember((method == null) ? null : method.Invoke(staticInstance, null), "m_uid"));
			}
			catch
			{
				return 0L;
			}
		}

		private string FindPeerName(long uid)
		{
			object staticInstance = GetStaticInstance(FindType("ZNet"));
			if (staticInstance == null)
			{
				return "unknown";
			}
			try
			{
				MethodInfo method = staticInstance.GetType().GetMethod("GetPeers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (!(((method == null) ? ReadMember(staticInstance, "m_peers") : method.Invoke(staticInstance, null)) is IEnumerable enumerable))
				{
					return "unknown";
				}
				foreach (object item in enumerable)
				{
					if (ConvertToLong(ReadMember(item, "m_uid")) == uid)
					{
						string text = Convert.ToString(ReadMember(item, "m_playerName")) ?? Convert.ToString(ReadMember(item, "m_playerNameCached"));
						return string.IsNullOrWhiteSpace(text) ? "unknown" : text;
					}
				}
			}
			catch
			{
			}
			return "unknown";
		}

		private bool InvokeRoutedRpc(long target, string methodName, string payload)
		{
			Type type = FindType("ZRoutedRpc");
			object staticInstance = GetStaticInstance(type);
			if (type == null || staticInstance == null)
			{
				return false;
			}
			try
			{
				MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo m)
				{
					if (m.Name != "InvokeRoutedRPC")
					{
						return false;
					}
					ParameterInfo[] parameters = m.GetParameters();
					return parameters.Length == 3 && parameters[0].ParameterType == typeof(long) && parameters[1].ParameterType == typeof(string);
				});
				if (methodInfo == null)
				{
					return false;
				}
				methodInfo.Invoke(staticInstance, new object[3]
				{
					target,
					methodName,
					new object[1] { payload }
				});
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[RHCA] Audit send failed: " + ex.GetType().Name));
				return false;
			}
		}

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

		private static object GetStaticInstance(Type type)
		{
			if (type == null)
			{
				return null;
			}
			PropertyInfo property = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				return property.GetValue(null, null);
			}
			FieldInfo field = type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(null);
			}
			return null;
		}

		private static object ReadMember(object instance, string name)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				try
				{
					return property.GetValue(instance, null);
				}
				catch
				{
				}
			}
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				try
				{
					return field.GetValue(instance);
				}
				catch
				{
				}
			}
			return null;
		}

		private static long ConvertToLong(object value)
		{
			if (value == null)
			{
				return 0L;
			}
			try
			{
				return Convert.ToInt64(value);
			}
			catch
			{
				Match match = Regex.Match(Convert.ToString(value) ?? "", "(\\d{5,})");
				long result;
				return (match.Success && long.TryParse(match.Groups[1].Value, out result)) ? result : 0;
			}
		}

		private static string Safe(string value, int max)
		{
			string text = (value ?? "").Replace("\\r", " ").Replace("\\n", " ").Replace("|", "/");
			if (text.Length > max)
			{
				text = text.Substring(0, max);
			}
			return text;
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "RustyHeathensControllerAudit";

		public const string PLUGIN_NAME = "RustyHeathensControllerAudit";

		public const string PLUGIN_VERSION = "0.1.0";
	}
}