From 3d6b1bbd59d23be19fe3594e104ad26e4ac0adcd Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 27 Dec 2024 22:01:03 -0600 Subject: [PATCH] BullshitWatcher: lame --- BullshitWatcher/BullshitWatcher.csproj | 11 + BullshitWatcher/Main.cs | 244 +++++++++++++++++++++ BullshitWatcher/Properties/AssemblyInfo.cs | 32 +++ 3 files changed, 287 insertions(+) create mode 100644 BullshitWatcher/BullshitWatcher.csproj create mode 100644 BullshitWatcher/Main.cs create mode 100644 BullshitWatcher/Properties/AssemblyInfo.cs diff --git a/BullshitWatcher/BullshitWatcher.csproj b/BullshitWatcher/BullshitWatcher.csproj new file mode 100644 index 0000000..4e44ed3 --- /dev/null +++ b/BullshitWatcher/BullshitWatcher.csproj @@ -0,0 +1,11 @@ + + + + net48 + + + + ..\.ManagedLibs\TheClapper.dll + + + diff --git a/BullshitWatcher/Main.cs b/BullshitWatcher/Main.cs new file mode 100644 index 0000000..aa46305 --- /dev/null +++ b/BullshitWatcher/Main.cs @@ -0,0 +1,244 @@ +using System.Reflection; +using HarmonyLib; +using MelonLoader; +using UnityEngine; + +namespace NAK.BullshitWatcher; + +// Slapped together to log where bullshit values are coming from, +// instead of creating the same Unity Explorer patch for the 100th time + +public class BullshitWatcherMod : MelonMod +{ + #region Initialize + + public override void OnInitializeMelon() + { + #region Transform Patches + + Type transformType = typeof(Transform); + + // Properties + PatchProperty(transformType, nameof(Transform.position)); + PatchProperty(transformType, nameof(Transform.localPosition)); + PatchProperty(transformType, nameof(Transform.rotation)); + PatchProperty(transformType, nameof(Transform.localRotation)); + PatchProperty(transformType, nameof(Transform.localScale)); + PatchProperty(transformType, nameof(Transform.right)); + PatchProperty(transformType, nameof(Transform.up)); + PatchProperty(transformType, nameof(Transform.forward)); + + // Methods + PatchMethod(transformType, nameof(Transform.SetPositionAndRotation)); + PatchMethod(transformType, nameof(Transform.SetLocalPositionAndRotation)); + PatchMethod(transformType, nameof(Transform.Translate), new[] { typeof(Vector3), typeof(Space) }); + PatchMethod(transformType, nameof(Transform.Translate), new[] { typeof(Vector3) }); + PatchMethod(transformType, nameof(Transform.Translate), new[] { typeof(Vector3), typeof(Transform) }); + PatchMethod(transformType, nameof(Transform.Rotate), new[] { typeof(Vector3), typeof(Space) }); + PatchMethod(transformType, nameof(Transform.Rotate), new[] { typeof(Vector3) }); + PatchMethod(transformType, nameof(Transform.Rotate), new[] { typeof(Vector3), typeof(float), typeof(Space) }); + PatchMethod(transformType, nameof(Transform.RotateAround)); + PatchMethod(transformType, nameof(Transform.LookAt), new[] { typeof(Vector3), typeof(Vector3) }); + + #endregion + + #region Rigidbody Patches + + Type rigidbodyType = typeof(Rigidbody); + + // Properties + PatchProperty(rigidbodyType, nameof(Rigidbody.position)); + PatchProperty(rigidbodyType, nameof(Rigidbody.rotation)); + PatchProperty(rigidbodyType, nameof(Rigidbody.velocity)); + PatchProperty(rigidbodyType, nameof(Rigidbody.angularVelocity)); + PatchProperty(rigidbodyType, nameof(Rigidbody.centerOfMass)); + + // Methods + PatchMethod(rigidbodyType, nameof(Rigidbody.MovePosition)); + PatchMethod(rigidbodyType, nameof(Rigidbody.MoveRotation)); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddForce), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddRelativeForce), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddTorque), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddRelativeTorque), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddForceAtPosition), new[] { typeof(Vector3), typeof(Vector3), typeof(ForceMode) }); + + #endregion + } + + private void PatchProperty(Type type, string propertyName) + { + PropertyInfo property = type.GetProperty(propertyName); + if (property!.SetMethod != null) + { + HarmonyInstance.Patch( + property.SetMethod, + prefix: new HarmonyMethod(typeof(BullshitWatcherMod).GetMethod(nameof(OnSetValue), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + } + + private void PatchMethod(Type type, string methodName, Type[] parameters = null) + { + MethodInfo method; + if (parameters != null) + { + method = type.GetMethod(methodName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy, + null, + parameters, + null); + } + else + { + var methods = type.GetMethods() + .Where(m => m.Name == methodName && !m.IsGenericMethod) + .ToArray(); + + // If there's only one method with this name, use it + if (methods.Length == 1) + { + method = methods[0]; + } + else + { + // This is fine :) + LoggerInstance.Error($"Multiple methods found for {type.Name}.{methodName}, skipping ambiguous patch"); + return; + } + } + + if (method != null) + { + HarmonyInstance.Patch( + method, + prefix: new HarmonyMethod(typeof(BullshitWatcherMod).GetMethod(nameof(OnMethodCall), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + else + { + MelonLogger.Warning($"Could not find method {type.Name}.{methodName}"); + } + } + + #endregion + + #region Validation Methods + + private static bool ContainsBullshitValue(Vector3 vector) + { + return float.IsNaN(vector.x) || float.IsNaN(vector.y) || float.IsNaN(vector.z) || + float.IsInfinity(vector.x) || float.IsInfinity(vector.y) || float.IsInfinity(vector.z) || + float.IsNegativeInfinity(vector.x) || float.IsNegativeInfinity(vector.y) || float.IsNegativeInfinity(vector.z); + } + + private static bool ContainsBullshitValue(Quaternion quaternion) + { + return float.IsNaN(quaternion.x) || float.IsNaN(quaternion.y) || float.IsNaN(quaternion.z) || float.IsNaN(quaternion.w) || + float.IsInfinity(quaternion.x) || float.IsInfinity(quaternion.y) || float.IsInfinity(quaternion.z) || float.IsInfinity(quaternion.w) || + float.IsNegativeInfinity(quaternion.x) || float.IsNegativeInfinity(quaternion.y) || + float.IsNegativeInfinity(quaternion.z) || float.IsNegativeInfinity(quaternion.w); + } + + private static bool ContainsBullshitValue(float value) + { + return float.IsNaN(value) || float.IsInfinity(value) || float.IsNegativeInfinity(value); + } + + private static bool ContainsBullshitValues(object[] values) + { + foreach (var value in values) + { + if (value == null) continue; + + switch (value) + { + case Vector3 v3: + if (ContainsBullshitValue(v3)) return true; + break; + case Quaternion q: + if (ContainsBullshitValue(q)) return true; + break; + case float f: + if (ContainsBullshitValue(f)) return true; + break; + } + } + return false; + } + + #endregion + + #region Logging Methods + + private static void LogBullshitValue(string componentType, string propertyName, Component component, object value) + { + MelonLogger.Error($"Bullshit {componentType}.{propertyName} value detected on GameObject '{GetGameObjectPath(component.gameObject)}': {value}"); + MelonLogger.Error(Environment.StackTrace); + } + + private static void LogBullshitMethod(string componentType, string methodName, Component component, object[] parameters) + { + MelonLogger.Error($"Bullshit parameters in {componentType}.{methodName} call on GameObject '{GetGameObjectPath(component.gameObject)}'"); + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i] != null) + MelonLogger.Error($" Parameter {i}: {parameters[i]}"); + } + MelonLogger.Error(Environment.StackTrace); + } + + private static string GetGameObjectPath(GameObject obj) + { + string path = obj.name; + Transform parent = obj.transform.parent; + + while (parent != null) + { + path = $"{parent.name}/{path}"; + parent = parent.parent; + } + + return path; + } + + #endregion + + #region Harmony Patches + + private static void OnSetValue(object value, Component __instance) + { + if (value == null) return; + + var componentType = __instance.GetType().Name; + var propertyName = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name.Replace("set_", ""); + + switch (value) + { + case Vector3 v3 when ContainsBullshitValue(v3): + LogBullshitValue(componentType, propertyName, __instance, v3); + break; + case Quaternion q when ContainsBullshitValue(q): + LogBullshitValue(componentType, propertyName, __instance, q); + break; + case float f when ContainsBullshitValue(f): + LogBullshitValue(componentType, propertyName, __instance, f); + break; + } + } + + private static void OnMethodCall(object[] __args, Component __instance) + { + if (__args == null || __args.Length == 0) return; + + if (ContainsBullshitValues(__args)) + { + var componentType = __instance.GetType().Name; + var methodName = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name; + LogBullshitMethod(componentType, methodName, __instance, __args); + } + } + + #endregion +} \ No newline at end of file diff --git a/BullshitWatcher/Properties/AssemblyInfo.cs b/BullshitWatcher/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c78d2a8 --- /dev/null +++ b/BullshitWatcher/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.BullshitWatcher.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.BullshitWatcher))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.BullshitWatcher))] + +[assembly: MelonInfo( + typeof(NAK.BullshitWatcher.BullshitWatcherMod), + nameof(NAK.BullshitWatcher), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/BullshitWatcher" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.BullshitWatcher.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file