NAK_CVR_Mods/.Deprecated/BullshitWatcher/Main.cs

244 lines
No EOL
9.3 KiB
C#

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
}