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