NAK_CVR_Mods/.Experimental/BullshitWatcher/Main.cs
2026-06-18 22:20:56 -05:00

457 lines
No EOL
19 KiB
C#

using System.Reflection;
using System.Runtime.CompilerServices;
using ABI_RC.Core.Player;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
using Object = UnityEngine.Object;
namespace NAK.BullshitWatcher;
public class BullshitWatcherMod : MelonMod
{
private const float MaxAllowedValueTop = 3.402823E+7f;
private const float MaxAllowedValueBottom = -3.402823E+7f;
private const float MinAvatarHeight = 0.005f;
public override void OnInitializeMelon()
{
PatchSetterVector3(typeof(Transform), nameof(Transform.position));
PatchSetterVector3(typeof(Transform), nameof(Transform.localPosition));
PatchSetterVector3(typeof(Transform), nameof(Transform.localScale));
PatchSetterVector3(typeof(Transform), nameof(Transform.right));
PatchSetterVector3(typeof(Transform), nameof(Transform.up));
PatchSetterVector3(typeof(Transform), nameof(Transform.forward));
PatchSetterQuaternion(typeof(Transform), nameof(Transform.rotation));
PatchSetterQuaternion(typeof(Transform), nameof(Transform.localRotation));
PatchMethod(typeof(Transform), nameof(Transform.SetPositionAndRotation),
nameof(OnTransformSetPositionAndRotation),
typeof(Vector3), typeof(Quaternion));
PatchMethod(typeof(Transform), nameof(Transform.SetLocalPositionAndRotation),
nameof(OnTransformSetLocalPositionAndRotation),
typeof(Vector3), typeof(Quaternion));
PatchMethod(typeof(Transform), nameof(Transform.Translate),
nameof(OnTransformTranslateVector3Space),
typeof(Vector3), typeof(Space));
PatchMethod(typeof(Transform), nameof(Transform.Translate),
nameof(OnTransformTranslateVector3),
typeof(Vector3));
PatchMethod(typeof(Transform), nameof(Transform.Translate),
nameof(OnTransformTranslateVector3Transform),
typeof(Vector3), typeof(Transform));
PatchMethod(typeof(Transform), nameof(Transform.Rotate),
nameof(OnTransformRotateVector3Space),
typeof(Vector3), typeof(Space));
PatchMethod(typeof(Transform), nameof(Transform.Rotate),
nameof(OnTransformRotateVector3),
typeof(Vector3));
PatchMethod(typeof(Transform), nameof(Transform.Rotate),
nameof(OnTransformRotateAxisAngleSpace),
typeof(Vector3), typeof(float), typeof(Space));
PatchMethod(typeof(Transform), nameof(Transform.RotateAround),
nameof(OnTransformRotateAround),
typeof(Vector3), typeof(Vector3), typeof(float));
PatchMethod(typeof(Transform), nameof(Transform.LookAt),
nameof(OnTransformLookAt),
typeof(Vector3), typeof(Vector3));
PatchSetterVector3(typeof(Rigidbody), nameof(Rigidbody.position));
PatchSetterVector3(typeof(Rigidbody), nameof(Rigidbody.velocity));
PatchSetterVector3(typeof(Rigidbody), nameof(Rigidbody.angularVelocity));
PatchSetterVector3(typeof(Rigidbody), nameof(Rigidbody.centerOfMass));
PatchSetterQuaternion(typeof(Rigidbody), nameof(Rigidbody.rotation));
PatchMethod(typeof(Rigidbody), nameof(Rigidbody.MovePosition),
nameof(OnRigidbodyMovePosition),
typeof(Vector3));
PatchMethod(typeof(Rigidbody), nameof(Rigidbody.MoveRotation),
nameof(OnRigidbodyMoveRotation),
typeof(Quaternion));
PatchMethod(typeof(Rigidbody), nameof(Rigidbody.AddForce),
nameof(OnRigidbodyAddForce),
typeof(Vector3), typeof(ForceMode));
PatchMethod(typeof(Rigidbody), nameof(Rigidbody.AddRelativeForce),
nameof(OnRigidbodyAddRelativeForce),
typeof(Vector3), typeof(ForceMode));
PatchMethod(typeof(Rigidbody), nameof(Rigidbody.AddTorque),
nameof(OnRigidbodyAddTorque),
typeof(Vector3), typeof(ForceMode));
PatchMethod(typeof(Rigidbody), nameof(Rigidbody.AddRelativeTorque),
nameof(OnRigidbodyAddRelativeTorque),
typeof(Vector3), typeof(ForceMode));
PatchMethod(typeof(Rigidbody), nameof(Rigidbody.AddForceAtPosition),
nameof(OnRigidbodyAddForceAtPosition),
typeof(Vector3), typeof(Vector3), typeof(ForceMode));
PatchMethod(typeof(Object), nameof(Object.Instantiate),
nameof(OnInstantiatePositionRotation),
typeof(Object), typeof(Vector3), typeof(Quaternion));
PatchMethod(typeof(Object), nameof(Object.Instantiate),
nameof(OnInstantiatePositionRotationParent),
typeof(Object), typeof(Vector3), typeof(Quaternion), typeof(Transform));
PatchSetterAvatarHeight(typeof(PlayerSetup), nameof(PlayerSetup.AvatarHeight));
}
private void PatchSetterVector3(Type type, string propertyName)
=> HarmonyInstance.Patch(
AccessTools.Property(type, propertyName).SetMethod,
new HarmonyMethod(GetPrefix(nameof(OnSetVector3))));
private void PatchSetterQuaternion(Type type, string propertyName)
=> HarmonyInstance.Patch(
AccessTools.Property(type, propertyName).SetMethod,
new HarmonyMethod(GetPrefix(nameof(OnSetQuaternion))));
private void PatchSetterAvatarHeight(Type type, string propertyName)
=> HarmonyInstance.Patch(
AccessTools.Property(type, propertyName).SetMethod,
new HarmonyMethod(GetPrefix(nameof(OnSetAvatarHeight))));
private void PatchMethod(Type type, string methodName, string prefixName, params Type[] parameterTypes)
=> HarmonyInstance.Patch(
AccessTools.Method(type, methodName, parameterTypes),
new HarmonyMethod(GetPrefix(prefixName)));
private static MethodInfo GetPrefix(string name)
=> typeof(BullshitWatcherMod).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe bool IsBullshit(float value)
=> (((*(int*)&value) & int.MaxValue) >= 0x7F800000)
|| value <= MaxAllowedValueBottom
|| value >= MaxAllowedValueTop;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsBullshit(Vector3 value)
=> IsBullshit(value.x) || IsBullshit(value.y) || IsBullshit(value.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsBullshit(Quaternion value)
=> IsBullshit(value.x) || IsBullshit(value.y) || IsBullshit(value.z) || IsBullshit(value.w);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsBullshit(Vector3 a, Vector3 b)
=> IsBullshit(a.x) || IsBullshit(a.y) || IsBullshit(a.z)
|| IsBullshit(b.x) || IsBullshit(b.y) || IsBullshit(b.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsBullshit(Vector3 vector, Quaternion quaternion)
=> IsBullshit(vector.x) || IsBullshit(vector.y) || IsBullshit(vector.z)
|| IsBullshit(quaternion.x) || IsBullshit(quaternion.y) || IsBullshit(quaternion.z) || IsBullshit(quaternion.w);
// --- Sanitizers --------------------------------------------------------
// NaN -> 0 (NaN can't be clamped meaningfully).
// +/-Infinity and out-of-range finite values -> clamped to the allowed bound.
// A bullshit Quaternion -> identity, because zeroing it produces a degenerate
// (0,0,0,0) rotation that just turns back into NaN the moment Unity normalizes it.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Sanitize(float value)
{
if (float.IsNaN(value))
return 0f;
if (value >= MaxAllowedValueTop) // also catches +Infinity
return MaxAllowedValueTop;
if (value <= MaxAllowedValueBottom) // also catches -Infinity
return MaxAllowedValueBottom;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector3 Sanitize(Vector3 value)
=> new Vector3(Sanitize(value.x), Sanitize(value.y), Sanitize(value.z));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Quaternion Sanitize(Quaternion value)
=> IsBullshit(value) ? Quaternion.identity : value;
// -----------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogBullshitValue(Component instance, MethodBase method, object value)
{
string member = method.Name.Replace("set_", "");
MelonLogger.Error($"Bullshit {instance.GetType().Name}.{member} = {value} on '{GetPath(instance.transform)}' (sanitized)");
MelonLogger.Error(Environment.StackTrace);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogBullshitArgs1(Component instance, MethodBase method, object arg0)
{
MelonLogger.Error($"Bullshit args in {instance.GetType().Name}.{method.Name}() on '{GetPath(instance.transform)}' (sanitized)");
MelonLogger.Error($" arg0: {arg0}");
MelonLogger.Error(Environment.StackTrace);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogBullshitArgs2(Component instance, MethodBase method, object arg0, object arg1)
{
MelonLogger.Error($"Bullshit args in {instance.GetType().Name}.{method.Name}() on '{GetPath(instance.transform)}' (sanitized)");
MelonLogger.Error($" arg0: {arg0}");
MelonLogger.Error($" arg1: {arg1}");
MelonLogger.Error(Environment.StackTrace);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogBullshitArgs3(Component instance, MethodBase method, object arg0, object arg1, object arg2)
{
MelonLogger.Error($"Bullshit args in {instance.GetType().Name}.{method.Name}() on '{GetPath(instance.transform)}' (sanitized)");
MelonLogger.Error($" arg0: {arg0}");
MelonLogger.Error($" arg1: {arg1}");
MelonLogger.Error($" arg2: {arg2}");
MelonLogger.Error(Environment.StackTrace);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogBullshitInstantiate(MethodBase method, Object original, Vector3 position, Quaternion rotation, Transform parent)
{
string originalName = original != null ? original.name : "null";
string parentPath = parent != null ? GetPath(parent) : "<none>";
MelonLogger.Error($"Bullshit args in Object.{method.Name}() original='{originalName}' parent='{parentPath}' (sanitized)");
MelonLogger.Error($" position: {position}");
MelonLogger.Error($" rotation: {rotation}");
MelonLogger.Error(Environment.StackTrace);
}
private static void OnSetVector3(ref Vector3 value, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(value))
return;
LogBullshitValue(__instance, __originalMethod, value);
value = Sanitize(value);
}
private static void OnSetQuaternion(ref Quaternion value, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(value))
return;
LogBullshitValue(__instance, __originalMethod, value);
value = Sanitize(value);
}
private static void OnSetAvatarHeight(ref float value, Component __instance, MethodBase __originalMethod)
{
float clamped;
if (float.IsNaN(value) || value < MinAvatarHeight) // also catches 0, negatives, -Infinity
clamped = MinAvatarHeight;
else if (value > MaxAllowedValueTop) // also catches +Infinity
clamped = MaxAllowedValueTop;
else
return; // height is fine, leave it alone
LogBullshitValue(__instance, __originalMethod, value);
value = clamped;
}
private static void OnTransformSetPositionAndRotation(ref Vector3 position, ref Quaternion rotation, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(position, rotation))
return;
LogBullshitArgs2(__instance, __originalMethod, position, rotation);
position = Sanitize(position);
rotation = Sanitize(rotation);
}
private static void OnTransformSetLocalPositionAndRotation(ref Vector3 localPosition, ref Quaternion localRotation, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(localPosition, localRotation))
return;
LogBullshitArgs2(__instance, __originalMethod, localPosition, localRotation);
localPosition = Sanitize(localPosition);
localRotation = Sanitize(localRotation);
}
private static void OnTransformTranslateVector3Space(ref Vector3 translation, Space relativeTo, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(translation))
return;
LogBullshitArgs2(__instance, __originalMethod, translation, relativeTo);
translation = Sanitize(translation);
}
private static void OnTransformTranslateVector3(ref Vector3 translation, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(translation))
return;
LogBullshitArgs1(__instance, __originalMethod, translation);
translation = Sanitize(translation);
}
private static void OnTransformTranslateVector3Transform(ref Vector3 translation, Transform relativeTo, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(translation))
return;
LogBullshitArgs2(__instance, __originalMethod, translation, relativeTo);
translation = Sanitize(translation);
}
private static void OnTransformRotateVector3Space(ref Vector3 eulers, Space relativeTo, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(eulers))
return;
LogBullshitArgs2(__instance, __originalMethod, eulers, relativeTo);
eulers = Sanitize(eulers);
}
private static void OnTransformRotateVector3(ref Vector3 eulers, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(eulers))
return;
LogBullshitArgs1(__instance, __originalMethod, eulers);
eulers = Sanitize(eulers);
}
private static void OnTransformRotateAxisAngleSpace(ref Vector3 axis, ref float angle, Space relativeTo, Component __instance, MethodBase __originalMethod)
{
if (!(IsBullshit(axis) || IsBullshit(angle)))
return;
LogBullshitArgs3(__instance, __originalMethod, axis, angle, relativeTo);
axis = Sanitize(axis);
angle = Sanitize(angle);
}
private static void OnTransformRotateAround(ref Vector3 point, ref Vector3 axis, ref float angle, Component __instance, MethodBase __originalMethod)
{
if (!(IsBullshit(point, axis) || IsBullshit(angle)))
return;
LogBullshitArgs3(__instance, __originalMethod, point, axis, angle);
point = Sanitize(point);
axis = Sanitize(axis);
angle = Sanitize(angle);
}
private static void OnTransformLookAt(ref Vector3 worldPosition, ref Vector3 worldUp, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(worldPosition, worldUp))
return;
LogBullshitArgs2(__instance, __originalMethod, worldPosition, worldUp);
worldPosition = Sanitize(worldPosition);
// worldUp must stay a usable direction; a zeroed up vector makes LookAt produce garbage.
worldUp = Sanitize(worldUp);
if (worldUp == Vector3.zero)
worldUp = Vector3.up;
}
private static void OnRigidbodyMovePosition(ref Vector3 position, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(position))
return;
LogBullshitArgs1(__instance, __originalMethod, position);
position = Sanitize(position);
}
private static void OnRigidbodyMoveRotation(ref Quaternion rot, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(rot))
return;
LogBullshitArgs1(__instance, __originalMethod, rot);
rot = Sanitize(rot);
}
private static void OnRigidbodyAddForce(ref Vector3 force, ForceMode mode, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(force))
return;
LogBullshitArgs2(__instance, __originalMethod, force, mode);
force = Sanitize(force);
}
private static void OnRigidbodyAddRelativeForce(ref Vector3 force, ForceMode mode, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(force))
return;
LogBullshitArgs2(__instance, __originalMethod, force, mode);
force = Sanitize(force);
}
private static void OnRigidbodyAddTorque(ref Vector3 torque, ForceMode mode, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(torque))
return;
LogBullshitArgs2(__instance, __originalMethod, torque, mode);
torque = Sanitize(torque);
}
private static void OnRigidbodyAddRelativeTorque(ref Vector3 torque, ForceMode mode, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(torque))
return;
LogBullshitArgs2(__instance, __originalMethod, torque, mode);
torque = Sanitize(torque);
}
private static void OnRigidbodyAddForceAtPosition(ref Vector3 force, ref Vector3 position, ForceMode mode, Component __instance, MethodBase __originalMethod)
{
if (!IsBullshit(force, position))
return;
LogBullshitArgs3(__instance, __originalMethod, force, position, mode);
force = Sanitize(force);
position = Sanitize(position);
}
private static void OnInstantiatePositionRotation(Object original, ref Vector3 position, ref Quaternion rotation, MethodBase __originalMethod)
{
if (!IsBullshit(position, rotation))
return;
LogBullshitInstantiate(__originalMethod, original, position, rotation, null);
position = Sanitize(position);
rotation = Sanitize(rotation);
}
private static void OnInstantiatePositionRotationParent(Object original, ref Vector3 position, ref Quaternion rotation, Transform parent, MethodBase __originalMethod)
{
if (!IsBullshit(position, rotation))
return;
LogBullshitInstantiate(__originalMethod, original, position, rotation, parent);
position = Sanitize(position);
rotation = Sanitize(rotation);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetPath(Transform current)
{
string path = current.name;
for (Transform parent = current.parent; parent != null; parent = parent.parent)
path = parent.name + "/" + path;
return path;
}
}