Decompiled source of Bloodcleaner v1.0.1

Mods/BloodCleaner (1).dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BloodCleaner;
using BoneLib.BoneMenu;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MelonLoader;
using MelonLoader.Preferences;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Main), "BloodCleaner", "1.0.1", "BlixSVO", "")]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("BloodCleaner")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+685b7f9f941495094deacc3bd0e2d7cd326ea7d6")]
[assembly: AssemblyProduct("BloodCleaner")]
[assembly: AssemblyTitle("BloodCleaner")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace BloodCleaner;

internal static class BloodCleanerMenu
{
	private static readonly Color C_RED = new Color(1f, 0.36f, 0.34f);

	private static readonly Color C_ORANGE = new Color(1f, 0.65f, 0.28f);

	private static readonly Color C_CYAN = new Color(0.45f, 0.9f, 1f);

	private static readonly Color C_WHITE = Color.white;

	public static void Build()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		Page obj = Page.Root.CreatePage("Blood Cleaner", C_RED, 0, true);
		obj.CreateFunction("Clear blood + impact decals", C_RED, (Action)delegate
		{
			DecalCleaner.Begin(includeAllDecals: true);
		});
		obj.CreateFunction("Clear named blood only", C_ORANGE, (Action)delegate
		{
			DecalCleaner.Begin(includeAllDecals: false);
		});
		obj.CreateBool("Auto-clean after map load", C_CYAN, Config.AutoCleanOnLevelLoad.Value, (Action<bool>)delegate(bool value)
		{
			Config.AutoCleanOnLevelLoad.Value = value;
			Config.Save();
		});
		obj.CreateFunction("Fusion status", C_WHITE, (Action)delegate
		{
			MelonLogger.Msg("[BloodCleaner] " + FusionBridge.GetStatus());
		});
	}
}
internal static class Config
{
	private static MelonPreferences_Category _category;

	public static MelonPreferences_Entry<bool> AutoCleanOnLevelLoad;

	public static void Init()
	{
		_category = MelonPreferences.CreateCategory("BloodCleaner", "Blood Cleaner");
		AutoCleanOnLevelLoad = _category.CreateEntry<bool>("AutoCleanOnLevelLoad", false, "Auto-clean on map load", "Clear decals, pose-space hit marks, and named blood splatters five seconds after loading a map.", false, false, (ValueValidator)null, (string)null);
		_category.LoadFromFile(false);
	}

	public static void Save()
	{
		try
		{
			if (_category != null)
			{
				_category.SaveToFile(false);
			}
		}
		catch
		{
		}
	}
}
internal static class DecalCleaner
{
	private const int ComponentsPerFrame = 250;

	private const int RenderersPerFrame = 400;

	private const BindingFlags InstanceMembers = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

	private static readonly string[] BloodMarkers = new string[6] { "blood", "gore", "splat", "splatter", "wound", "bleed" };

	private static readonly List<MonoBehaviour> ComponentWork = new List<MonoBehaviour>(1024);

	private static readonly HashSet<int> SeenComponents = new HashSet<int>();

	private static readonly List<Renderer> RendererWork = new List<Renderer>(1024);

	private static readonly HashSet<int> SeenRenderers = new HashSet<int>();

	private static readonly HashSet<int> HiddenRenderers = new HashSet<int>();

	private static int _componentCursor;

	private static int _rendererCursor;

	private static int _projectorsFound;

	private static int _projectorsHidden;

	private static int _damageControllersReset;

	private static int _impactManagersCleared;

	private static int _impactManagerFailures;

	private static int _bloodRenderersHidden;

	private static int _alreadyHidden;

	private static bool _includeAllDecals;

	private static bool _running;

	public static void Begin(bool includeAllDecals)
	{
		if (_running)
		{
			MelonLogger.Warning("[BloodCleaner] A cleanup pass is already running.");
			return;
		}
		Cancel();
		_includeAllDecals = includeAllDecals;
		_projectorsFound = 0;
		_projectorsHidden = 0;
		_damageControllersReset = 0;
		_impactManagersCleared = 0;
		_impactManagerFailures = 0;
		_bloodRenderersHidden = 0;
		_alreadyHidden = 0;
		try
		{
			Il2CppArrayBase<MonoBehaviour> val = Object.FindObjectsOfType<MonoBehaviour>(true);
			for (int i = 0; i < val.Length; i++)
			{
				AddUnique(val[i]);
			}
			Il2CppArrayBase<Renderer> val2 = Object.FindObjectsOfType<Renderer>(true);
			for (int j = 0; j < val2.Length; j++)
			{
				AddUnique(val2[j]);
			}
			int num = FusionBridge.AppendNetworkPlayerComponents(ComponentWork, SeenComponents);
			_componentCursor = 0;
			_rendererCursor = 0;
			_running = true;
			MelonLogger.Msg("[BloodCleaner] Scan started: " + ComponentWork.Count + " behaviours and " + RendererWork.Count + " renderers queued; " + (includeAllDecals ? "complete decal cleanup" : "named blood only") + ". Fusion rigs included: " + num + ".");
			if (ComponentWork.Count == 0 && RendererWork.Count == 0)
			{
				Finish();
			}
		}
		catch (Exception ex)
		{
			Cancel();
			MelonLogger.Error("[BloodCleaner] Could not scan the loaded scene: " + ex);
		}
	}

	public static void Tick()
	{
		if (!_running)
		{
			return;
		}
		try
		{
			ProcessComponents();
			ProcessRenderers();
			if (_componentCursor >= ComponentWork.Count && _rendererCursor >= RendererWork.Count)
			{
				Finish();
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("[BloodCleaner] Cleanup batch failed: " + ex.Message);
			Finish();
		}
	}

	public static void Cancel()
	{
		_running = false;
		_componentCursor = 0;
		_rendererCursor = 0;
		ComponentWork.Clear();
		SeenComponents.Clear();
		RendererWork.Clear();
		SeenRenderers.Clear();
		HiddenRenderers.Clear();
	}

	private static void ProcessComponents()
	{
		int num = Math.Min(_componentCursor + 250, ComponentWork.Count);
		while (_componentCursor < num)
		{
			MonoBehaviour val = ComponentWork[_componentCursor];
			if (!((Object)(object)val == (Object)null))
			{
				try
				{
					Type type = ((object)val).GetType();
					if (!(type == null))
					{
						if (type.Name.IndexOf("VisualDamageController", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							if (_includeAllDecals)
							{
								ClearVisualDamage(val, type);
							}
						}
						else if (type.Name.IndexOf("DecalProjector", StringComparison.OrdinalIgnoreCase) >= 0 && (_includeAllDecals || LooksLikeBlood(val, type)))
						{
							_projectorsFound++;
							if (HideProjector(val))
							{
								_projectorsHidden++;
							}
						}
					}
				}
				catch
				{
				}
			}
			_componentCursor++;
		}
	}

	private static void ProcessRenderers()
	{
		int num = Math.Min(_rendererCursor + 400, RendererWork.Count);
		while (_rendererCursor < num)
		{
			Renderer val = RendererWork[_rendererCursor];
			if (!((Object)(object)val == (Object)null) && LooksLikeBlood(val))
			{
				try
				{
					HideBloodRenderer(val);
				}
				catch
				{
				}
			}
			_rendererCursor++;
		}
	}

	private static void ClearVisualDamage(MonoBehaviour behaviour, Type type)
	{
		bool flag = false;
		try
		{
			MethodInfo method = type.GetMethod("ResetHits", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method != null)
			{
				method.Invoke(behaviour, null);
				flag = true;
			}
		}
		catch
		{
		}
		try
		{
			object obj2 = ReadMember(behaviour, "impactManager");
			if (obj2 != null)
			{
				MethodInfo method2 = obj2.GetType().GetMethod("ClearImpacts", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				if (method2 != null)
				{
					method2.Invoke(obj2, null);
					_impactManagersCleared++;
				}
				else if (!flag)
				{
					_impactManagerFailures++;
				}
			}
			else if (!flag)
			{
				_impactManagerFailures++;
			}
		}
		catch
		{
			_impactManagerFailures++;
		}
		if (flag)
		{
			_damageControllersReset++;
		}
	}

	private static bool HideProjector(MonoBehaviour behaviour)
	{
		Renderer val = FindProjectorRenderer(behaviour);
		if ((Object)(object)val != (Object)null)
		{
			if (!val.enabled)
			{
				_alreadyHidden++;
				return true;
			}
			val.enabled = false;
			try
			{
				HiddenRenderers.Add(((Object)val).GetInstanceID());
			}
			catch
			{
			}
			_bloodRenderersHidden++;
			return true;
		}
		if (!((Behaviour)behaviour).enabled)
		{
			_alreadyHidden++;
			return true;
		}
		((Behaviour)behaviour).enabled = false;
		return true;
	}

	private static void HideBloodRenderer(Renderer renderer)
	{
		int instanceID = ((Object)renderer).GetInstanceID();
		if (!HiddenRenderers.Contains(instanceID))
		{
			HiddenRenderers.Add(instanceID);
			if (!renderer.enabled)
			{
				_alreadyHidden++;
				return;
			}
			renderer.enabled = false;
			_bloodRenderersHidden++;
		}
	}

	private static bool LooksLikeBlood(MonoBehaviour behaviour, Type type)
	{
		if (HasBloodMarker(((Object)behaviour).name) || HasBloodMarker(type.Name))
		{
			return true;
		}
		try
		{
			Transform val = ((Component)behaviour).transform;
			int num = 0;
			while ((Object)(object)val != (Object)null && num < 3)
			{
				if (HasBloodMarker(((Object)val).name))
				{
					return true;
				}
				val = val.parent;
				num++;
			}
		}
		catch
		{
		}
		if (MemberHasBloodMarker(behaviour, "decalMaterial") || MemberHasBloodMarker(behaviour, "atlasData") || MemberHasBloodMarker(behaviour, "atlasSelection") || MemberHasBloodMarker(behaviour, "material"))
		{
			return true;
		}
		Renderer val2 = FindProjectorRenderer(behaviour);
		if ((Object)(object)val2 != (Object)null)
		{
			return MaterialHasBloodMarker(val2);
		}
		return false;
	}

	private static bool LooksLikeBlood(Renderer renderer)
	{
		try
		{
			if (HasBloodMarker(((Object)renderer).name) || HasBloodMarker(((Object)((Component)renderer).gameObject).name))
			{
				return true;
			}
			Transform parent = ((Component)renderer).transform.parent;
			int num = 0;
			while ((Object)(object)parent != (Object)null && num < 2)
			{
				string name = ((Object)parent).name;
				if (HasBloodMarker(name) && (HasDecalMarker(name) || name.IndexOf("blood", StringComparison.OrdinalIgnoreCase) >= 0))
				{
					return true;
				}
				parent = parent.parent;
				num++;
			}
			if (((object)renderer).GetType().Name.IndexOf("SkinnedMeshRenderer", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			return MaterialHasBloodMarker(renderer);
		}
		catch
		{
			return false;
		}
	}

	private static bool MaterialHasBloodMarker(Renderer renderer)
	{
		try
		{
			Material sharedMaterial = renderer.sharedMaterial;
			return (Object)(object)sharedMaterial != (Object)null && HasBloodMarker(((Object)sharedMaterial).name);
		}
		catch
		{
			return false;
		}
	}

	private static bool MemberHasBloodMarker(object instance, string memberName)
	{
		object obj = ReadMember(instance, memberName);
		if (obj == null)
		{
			return false;
		}
		Object val = (Object)((obj is Object) ? obj : null);
		if (val != (Object)null && HasBloodMarker(val.name))
		{
			return true;
		}
		return HasBloodMarker(obj.ToString());
	}

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

	private static Renderer FindProjectorRenderer(MonoBehaviour behaviour)
	{
		try
		{
			object obj = ReadMember(behaviour, "meshRenderer");
			Renderer val = (Renderer)((obj is Renderer) ? obj : null);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
		}
		catch
		{
		}
		try
		{
			Il2CppArrayBase<Renderer> componentsInChildren = ((Component)behaviour).GetComponentsInChildren<Renderer>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if ((Object)(object)componentsInChildren[i] != (Object)null)
				{
					return componentsInChildren[i];
				}
			}
		}
		catch
		{
		}
		return null;
	}

	private static bool HasDecalMarker(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return false;
		}
		if (value.IndexOf("decal", StringComparison.OrdinalIgnoreCase) < 0)
		{
			return value.IndexOf("splat", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		return true;
	}

	private static bool HasBloodMarker(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return false;
		}
		for (int i = 0; i < BloodMarkers.Length; i++)
		{
			if (value.IndexOf(BloodMarkers[i], StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
		}
		return false;
	}

	private static void AddUnique(MonoBehaviour behaviour)
	{
		if ((Object)(object)behaviour == (Object)null)
		{
			return;
		}
		try
		{
			int instanceID = ((Object)behaviour).GetInstanceID();
			if (SeenComponents.Add(instanceID))
			{
				ComponentWork.Add(behaviour);
			}
		}
		catch
		{
		}
	}

	private static void AddUnique(Renderer renderer)
	{
		if ((Object)(object)renderer == (Object)null)
		{
			return;
		}
		try
		{
			int instanceID = ((Object)renderer).GetInstanceID();
			if (SeenRenderers.Add(instanceID))
			{
				RendererWork.Add(renderer);
			}
		}
		catch
		{
		}
	}

	private static void Finish()
	{
		string text = (_includeAllDecals ? "complete decal and impact cleanup" : "named blood cleanup");
		MelonLogger.Msg("[BloodCleaner] Finished " + text + ": " + _projectorsHidden + "/" + _projectorsFound + " decal projector(s) hidden; " + _damageControllersReset + " visual damage controller(s) reset; " + _impactManagersCleared + " impact manager(s) cleared; " + _bloodRenderersHidden + " blood renderer(s) hidden; " + _alreadyHidden + " already hidden; " + _impactManagerFailures + " impact reset call(s) failed.");
		if (_projectorsHidden == 0 && _damageControllersReset == 0 && _impactManagersCleared == 0 && _bloodRenderersHidden == 0 && _alreadyHidden == 0)
		{
			MelonLogger.Warning("[BloodCleaner] No supported decal or blood renderer was found in the loaded scene.");
		}
		_running = false;
		_componentCursor = 0;
		_rendererCursor = 0;
		ComponentWork.Clear();
		SeenComponents.Clear();
		RendererWork.Clear();
		SeenRenderers.Clear();
		HiddenRenderers.Clear();
	}
}
internal static class FusionBridge
{
	private const BindingFlags PublicStatic = BindingFlags.Static | BindingFlags.Public;

	private const BindingFlags PublicInstance = BindingFlags.Instance | BindingFlags.Public;

	private static string _status = "Fusion has not been checked yet.";

	public static string GetStatus()
	{
		return _status;
	}

	public static int AppendNetworkPlayerComponents(List<MonoBehaviour> destination, HashSet<int> seen)
	{
		int num = 0;
		try
		{
			Assembly assembly = null;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly2 in assemblies)
			{
				if (assembly2 != null && assembly2.GetName().Name == "LabFusion")
				{
					assembly = assembly2;
					break;
				}
			}
			if (assembly == null)
			{
				_status = "LabFusion not detected; the normal scene-wide cleanup is available.";
				return 0;
			}
			Type type = assembly.GetType("LabFusion.Entities.NetworkPlayer", throwOnError: false);
			if (type == null)
			{
				_status = "LabFusion detected, but its NetworkPlayer type was not found. The scene-wide cleanup is still active.";
				return 0;
			}
			FieldInfo field = type.GetField("Players", BindingFlags.Static | BindingFlags.Public);
			PropertyInfo property = type.GetProperty("RigRefs", BindingFlags.Instance | BindingFlags.Public);
			PropertyInfo propertyInfo = ((property == null) ? null : property.PropertyType.GetProperty("RigManager", BindingFlags.Instance | BindingFlags.Public));
			if (field == null || property == null || propertyInfo == null)
			{
				_status = "LabFusion detected, but its player API differs. The scene-wide cleanup is still active.";
				return 0;
			}
			if (!(field.GetValue(null) is IEnumerable enumerable))
			{
				_status = "LabFusion detected, but its player list is unavailable. The scene-wide cleanup is still active.";
				return 0;
			}
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				try
				{
					object value = property.GetValue(item, null);
					if (value == null)
					{
						continue;
					}
					object? value2 = propertyInfo.GetValue(value, null);
					Component val = (Component)((value2 is Component) ? value2 : null);
					if ((Object)(object)val == (Object)null || (Object)(object)val.transform == (Object)null)
					{
						continue;
					}
					Transform root = val.transform.root;
					if (!((Object)(object)root == (Object)null))
					{
						num++;
						Il2CppArrayBase<MonoBehaviour> componentsInChildren = ((Component)root).GetComponentsInChildren<MonoBehaviour>(true);
						for (int j = 0; j < componentsInChildren.Length; j++)
						{
							AddUnique(componentsInChildren[j], destination, seen);
						}
					}
				}
				catch
				{
				}
			}
			_status = "LabFusion detected; included " + num + " network player rig(s) in the cleanup pass. Cleanup remains local to this client.";
		}
		catch (Exception ex)
		{
			_status = "LabFusion bridge unavailable (" + ex.GetType().Name + "); the scene-wide cleanup is still active.";
			MelonLogger.Warning("[BloodCleaner] Fusion bridge fallback: " + ex.Message);
		}
		return num;
	}

	private static void AddUnique(MonoBehaviour behaviour, List<MonoBehaviour> destination, HashSet<int> seen)
	{
		if ((Object)(object)behaviour == (Object)null)
		{
			return;
		}
		try
		{
			int instanceID = ((Object)behaviour).GetInstanceID();
			if (seen.Add(instanceID))
			{
				destination.Add(behaviour);
			}
		}
		catch
		{
		}
	}
}
public class Main : MelonMod
{
	private bool _autoCleanPending;

	private float _autoCleanTimer;

	public override void OnInitializeMelon()
	{
		try
		{
			Config.Init();
			BloodCleanerMenu.Build();
			MelonLogger.Msg("[BloodCleaner] Loaded. Open BoneMenu > Blood Cleaner.");
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[BloodCleaner] Initialization failed: " + ex);
		}
	}

	public override void OnUpdate()
	{
		try
		{
			DecalCleaner.Tick();
			if (_autoCleanPending)
			{
				_autoCleanTimer -= Time.deltaTime;
				if (_autoCleanTimer <= 0f)
				{
					_autoCleanPending = false;
					DecalCleaner.Begin(includeAllDecals: true);
				}
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("[BloodCleaner] Update failed: " + ex.Message);
		}
	}

	public override void OnSceneWasLoaded(int buildIndex, string sceneName)
	{
		DecalCleaner.Cancel();
		_autoCleanPending = Config.AutoCleanOnLevelLoad != null && Config.AutoCleanOnLevelLoad.Value;
		_autoCleanTimer = 5f;
		if (_autoCleanPending)
		{
			MelonLogger.Msg("[BloodCleaner] Auto-clean queued for the newly loaded map.");
		}
	}

	public override void OnDeinitializeMelon()
	{
		_autoCleanPending = false;
		DecalCleaner.Cancel();
	}
}