Decompiled source of ValheimEffectListCompat v0.1.9

BepInEx\patchers\Ntxfloy-ValheimEffectListCompat\ValheimEffectListCompat.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ValheimEffectListCompat;

public static class Patcher
{
	private static readonly ManualLogSource Log = Logger.CreateLogSource("Ntxfloy EffectList Compat");

	private static bool resolverInstalled;

	public static IEnumerable<string> TargetDLLs => new string[2] { "assembly_valheim.dll", "OdinStorage.dll" };

	public static void Patch(AssemblyDefinition assembly)
	{
		if (assembly != null)
		{
			if (((AssemblyNameReference)assembly.Name).Name == "assembly_valheim")
			{
				PatchValheim(assembly);
			}
			else if (((AssemblyNameReference)assembly.Name).Name == "OdinStorage")
			{
				PatchOdinStorage(assembly);
			}
		}
	}

	private static void PatchValheim(AssemblyDefinition assembly)
	{
		try
		{
			TypeDefinition type = assembly.MainModule.GetType("EffectList");
			if (type == null)
			{
				Log.LogWarning((object)"EffectList type not found; no compatibility change applied.");
			}
			else
			{
				MethodDefinition val = ((IEnumerable<MethodDefinition>)type.Methods).SingleOrDefault((Func<MethodDefinition, bool>)IsNativeCreate);
				if (val == null)
				{
					Log.LogWarning((object)"Expected native EffectList.Create(..., ZDOID) overload not found; game API may have changed.");
				}
				else if (!((IEnumerable<MethodDefinition>)type.Methods).Any(IsLegacyCreate))
				{
					AddEffectListLegacyForwarder(type, val);
					Log.LogInfo((object)"Added EffectList.Create(Vector3, Quaternion, Transform, Single, Int32) forwarder.");
				}
				else
				{
					Log.LogInfo((object)"Legacy EffectList.Create overload already exists; leaving unchanged.");
				}
				InstallHarmonyResolver();
			}
			TypeDefinition type2 = assembly.MainModule.GetType("Character");
			if (type2 == null)
			{
				Log.LogWarning((object)"Character type not found in assembly_valheim; skipping Character.Message patch.");
				return;
			}
			MethodDefinition val2 = ((IEnumerable<MethodDefinition>)type2.Methods).FirstOrDefault((Func<MethodDefinition, bool>)IsNativeCharacterMessage);
			if (val2 == null)
			{
				Log.LogWarning((object)"Expected 5-arg Character.Message not found; game API may have changed.");
			}
			else if (!((IEnumerable<MethodDefinition>)type2.Methods).Any(IsLegacyCharacterMessage))
			{
				AddCharacterMessageForwarder(type2, val2, assembly.MainModule);
				Log.LogInfo((object)"Added Character.Message(MessageType, string, int, Sprite) forwarder -> 5-arg version.");
			}
			else
			{
				Log.LogInfo((object)"Legacy Character.Message(4 args) already exists; leaving unchanged.");
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)("assembly_valheim compatibility patch failed: " + ex));
		}
	}

	private static void PatchOdinStorage(AssemblyDefinition assembly)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			int num = 0;
			foreach (TypeDefinition type in assembly.MainModule.GetTypes())
			{
				Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator();
				try
				{
					while (enumerator2.MoveNext())
					{
						MethodDefinition current = enumerator2.Current;
						if (!current.HasBody)
						{
							continue;
						}
						Collection<Instruction> instructions = current.Body.Instructions;
						ILProcessor iLProcessor = current.Body.GetILProcessor();
						for (int i = 0; i < instructions.Count; i++)
						{
							Instruction val = instructions[i];
							if (val.OpCode != OpCodes.Callvirt && val.OpCode != OpCodes.Call)
							{
								continue;
							}
							object operand = val.Operand;
							MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null);
							if (val2 != null && IsOdinStorage4ArgCharacterMessage(val2))
							{
								MethodReference val3 = Build5ArgCharacterMessageRef(assembly.MainModule, val2);
								if (val3 != null)
								{
									Instruction val4 = iLProcessor.Create(OpCodes.Ldc_I4_0);
									iLProcessor.InsertBefore(val, val4);
									val.Operand = val3;
									num++;
									i++;
								}
							}
						}
					}
				}
				finally
				{
					((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
				}
			}
			if (num > 0)
			{
				Log.LogInfo((object)$"OdinStorage.dll: rewrote {num} Character.Message(4-arg) callsites to 5-arg.");
			}
			else
			{
				Log.LogWarning((object)"OdinStorage.dll: no 4-arg Character.Message callsites found (already patched or API changed).");
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)("OdinStorage.dll IL patch failed: " + ex));
		}
	}

	private static bool IsOdinStorage4ArgCharacterMessage(MethodReference method)
	{
		if (((MemberReference)method).Name != "Message")
		{
			return false;
		}
		if (method.Parameters.Count != 4)
		{
			return false;
		}
		TypeReference declaringType = ((MemberReference)method).DeclaringType;
		string text = ((declaringType != null) ? ((MemberReference)declaringType).FullName : null) ?? "";
		if (text != "Character" && !text.EndsWith(".Character"))
		{
			return false;
		}
		string fullName = ((MemberReference)((ParameterReference)method.Parameters[0]).ParameterType).FullName;
		string fullName2 = ((MemberReference)((ParameterReference)method.Parameters[1]).ParameterType).FullName;
		string fullName3 = ((MemberReference)((ParameterReference)method.Parameters[2]).ParameterType).FullName;
		string fullName4 = ((MemberReference)((ParameterReference)method.Parameters[3]).ParameterType).FullName;
		if ((fullName.Contains("MessageType") || fullName.Contains("MessageHud")) && fullName2 == "System.String" && fullName3 == "System.Int32")
		{
			return fullName4.Contains("Sprite");
		}
		return false;
	}

	private static MethodReference Build5ArgCharacterMessageRef(ModuleDefinition module, MethodReference original4)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Expected O, but got Unknown
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Expected O, but got Unknown
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Expected O, but got Unknown
		try
		{
			TypeReference val = module.TypeSystem.Void;
			TypeReference boolean = module.TypeSystem.Boolean;
			MethodReference val2 = new MethodReference("Message", val, ((MemberReference)original4).DeclaringType)
			{
				HasThis = original4.HasThis,
				ExplicitThis = original4.ExplicitThis,
				CallingConvention = original4.CallingConvention
			};
			Enumerator<ParameterDefinition> enumerator = original4.Parameters.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					ParameterDefinition current = enumerator.Current;
					val2.Parameters.Add(new ParameterDefinition(((ParameterReference)current).Name, current.Attributes, ((ParameterReference)current).ParameterType));
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			val2.Parameters.Add(new ParameterDefinition("log", (ParameterAttributes)0, boolean));
			return module.ImportReference(val2);
		}
		catch
		{
			return null;
		}
	}

	private static bool IsNativeCreate(MethodDefinition method)
	{
		if (((MemberReference)method).Name != "Create" || ((MethodReference)method).Parameters.Count != 6)
		{
			return false;
		}
		string[] array = new string[6] { "UnityEngine.Vector3", "UnityEngine.Quaternion", "UnityEngine.Transform", "System.Single", "System.Int32", "ZDOID" };
		for (int i = 0; i < array.Length; i++)
		{
			if (((MemberReference)((ParameterReference)((MethodReference)method).Parameters[i]).ParameterType).FullName != array[i])
			{
				return false;
			}
		}
		return true;
	}

	private static bool IsLegacyCreate(MethodDefinition method)
	{
		if (((MemberReference)method).Name != "Create" || ((MethodReference)method).Parameters.Count != 5)
		{
			return false;
		}
		string[] array = new string[5] { "UnityEngine.Vector3", "UnityEngine.Quaternion", "UnityEngine.Transform", "System.Single", "System.Int32" };
		for (int i = 0; i < array.Length; i++)
		{
			if (((MemberReference)((ParameterReference)((MethodReference)method).Parameters[i]).ParameterType).FullName != array[i])
			{
				return false;
			}
		}
		return true;
	}

	private static void AddEffectListLegacyForwarder(TypeDefinition type, MethodDefinition native)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Expected O, but got Unknown
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Expected O, but got Unknown
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e8: Expected O, but got Unknown
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_0142: Unknown result type (might be due to invalid IL or missing references)
		MethodAttributes val = (MethodAttributes)134;
		if (native.IsStatic)
		{
			val = (MethodAttributes)(val | 0x10);
		}
		MethodDefinition val2 = new MethodDefinition("Create", val, ((MethodReference)native).ReturnType);
		for (int i = 0; i < 5; i++)
		{
			ParameterDefinition val3 = ((MethodReference)native).Parameters[i];
			((MethodReference)val2).Parameters.Add(new ParameterDefinition(((ParameterReference)val3).Name, val3.Attributes, ((ParameterReference)val3).ParameterType));
		}
		MethodBody body = val2.Body;
		body.InitLocals = true;
		body.MaxStackSize = 8;
		ILProcessor iLProcessor = body.GetILProcessor();
		if (!native.IsStatic)
		{
			iLProcessor.Append(iLProcessor.Create(OpCodes.Ldarg_0));
		}
		for (int j = 0; j < 5; j++)
		{
			iLProcessor.Append(iLProcessor.Create(OpCodes.Ldarg, ((MethodReference)val2).Parameters[j]));
		}
		TypeReference parameterType = ((ParameterReference)((MethodReference)native).Parameters[5]).ParameterType;
		VariableDefinition val4 = new VariableDefinition(parameterType);
		body.Variables.Add(val4);
		iLProcessor.Append(iLProcessor.Create(OpCodes.Ldloca, val4));
		iLProcessor.Append(iLProcessor.Create(OpCodes.Initobj, parameterType));
		iLProcessor.Append(iLProcessor.Create(OpCodes.Ldloc, val4));
		iLProcessor.Append(iLProcessor.Create(OpCodes.Call, (MethodReference)(object)native));
		iLProcessor.Append(iLProcessor.Create(OpCodes.Ret));
		type.Methods.Add(val2);
	}

	private static bool IsNativeCharacterMessage(MethodDefinition method)
	{
		if (((MemberReference)method).Name != "Message" || ((MethodReference)method).Parameters.Count != 5)
		{
			return false;
		}
		string[] array = new string[5] { "MessageHud/MessageType", "System.String", "System.Int32", "UnityEngine.Sprite", "System.Boolean" };
		for (int i = 0; i < array.Length; i++)
		{
			if (((MemberReference)((ParameterReference)((MethodReference)method).Parameters[i]).ParameterType).FullName != array[i])
			{
				return false;
			}
		}
		return true;
	}

	private static bool IsLegacyCharacterMessage(MethodDefinition method)
	{
		if (((MemberReference)method).Name != "Message" || ((MethodReference)method).Parameters.Count != 4)
		{
			return false;
		}
		string[] array = new string[4] { "MessageHud/MessageType", "System.String", "System.Int32", "UnityEngine.Sprite" };
		for (int i = 0; i < array.Length; i++)
		{
			if (((MemberReference)((ParameterReference)((MethodReference)method).Parameters[i]).ParameterType).FullName != array[i])
			{
				return false;
			}
		}
		return true;
	}

	private static void AddCharacterMessageForwarder(TypeDefinition type, MethodDefinition native, ModuleDefinition module)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Expected O, but got Unknown
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		MethodAttributes val = (MethodAttributes)454;
		MethodDefinition val2 = new MethodDefinition("Message", val, module.TypeSystem.Void);
		for (int i = 0; i < 4; i++)
		{
			ParameterDefinition val3 = ((MethodReference)native).Parameters[i];
			((MethodReference)val2).Parameters.Add(new ParameterDefinition(((ParameterReference)val3).Name, val3.Attributes, ((ParameterReference)val3).ParameterType));
		}
		MethodBody body = val2.Body;
		body.InitLocals = false;
		body.MaxStackSize = 7;
		ILProcessor iLProcessor = body.GetILProcessor();
		iLProcessor.Append(iLProcessor.Create(OpCodes.Ldarg_0));
		for (int j = 0; j < 4; j++)
		{
			iLProcessor.Append(iLProcessor.Create(OpCodes.Ldarg, ((MethodReference)val2).Parameters[j]));
		}
		iLProcessor.Append(iLProcessor.Create(OpCodes.Ldc_I4_0));
		iLProcessor.Append(iLProcessor.Create(OpCodes.Callvirt, (MethodReference)(object)native));
		iLProcessor.Append(iLProcessor.Create(OpCodes.Ret));
		type.Methods.Add(val2);
	}

	private static void InstallHarmonyResolver()
	{
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Expected O, but got Unknown
		if (!resolverInstalled)
		{
			MethodInfo method = typeof(AccessTools).GetMethod("DeclaredMethod", BindingFlags.Static | BindingFlags.Public, null, new Type[4]
			{
				typeof(Type),
				typeof(string),
				typeof(Type[]),
				typeof(Type[])
			}, null);
			MethodInfo method2 = typeof(Patcher).GetMethod("ResolveEffectListPatchTarget", BindingFlags.Static | BindingFlags.NonPublic);
			if (method == null || method2 == null)
			{
				Log.LogWarning((object)"Harmony resolver hook could not be installed; by-name patches may remain ambiguous.");
				return;
			}
			new Harmony("Ntxfloy.ValheimEffectListCompat").Patch((MethodBase)method, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			resolverInstalled = true;
			Log.LogInfo((object)"Harmony by-name lookup selects the original EffectList.Create overload by metadata order.");
		}
	}

	private static bool ResolveEffectListPatchTarget(Type type, string name, Type[] parameters, Type[] generics, ref MethodInfo __result)
	{
		if (type == null || type.Name != "EffectList" || type.Assembly.GetName().Name != "assembly_valheim" || name != "Create" || parameters != null)
		{
			return true;
		}
		MethodInfo[] array = (from m in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
			where m.Name == "Create"
			orderby m.MetadataToken
			select m).ToArray();
		if (array.Length != 2 || array[0].MetadataToken >= array[1].MetadataToken)
		{
			return true;
		}
		__result = array[0];
		return false;
	}
}

BepInEx\plugins\Ntxfloy-ValheimSceneCompat\ValheimSceneCompat.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ValheimSceneCompat;

[BepInPlugin("ntxfloy.valheimscenecompat", "Valheim Scene Compatibility", "0.1.7")]
public class Plugin : BaseUnityPlugin
{
	private class ErrorThrottle
	{
		private class ErrorEntry
		{
			public float LastLogTime;

			public int SuppressedCount;
		}

		private const int MaxTrackedSignatures = 200;

		private readonly string _tag;

		private readonly Dictionary<string, ErrorEntry> _entries = new Dictionary<string, ErrorEntry>(32);

		private ErrorEntry _overflowEntry;

		public ErrorThrottle(string tag)
		{
			_tag = tag;
		}

		public void Log(string context, Exception ex)
		{
			string signature = GetSignature(context, ex);
			float unscaledTime = Time.unscaledTime;
			if (!_entries.TryGetValue(signature, out var value))
			{
				if (_entries.Count < 200)
				{
					value = new ErrorEntry
					{
						LastLogTime = unscaledTime,
						SuppressedCount = 0
					};
					_entries[signature] = value;
				}
				else
				{
					if (_overflowEntry == null)
					{
						_overflowEntry = new ErrorEntry
						{
							LastLogTime = unscaledTime,
							SuppressedCount = 0
						};
					}
					value = _overflowEntry;
				}
				string text = ((ex != null) ? (ex.GetType().Name + ": " + ex.Message + "\n" + ex.StackTrace) : context);
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[" + _tag + "] [First occurrence] " + context + ": " + text));
				}
				return;
			}
			value.SuppressedCount++;
			if (unscaledTime - value.LastLogTime >= 10f)
			{
				value.LastLogTime = unscaledTime;
				string text2 = ((ex != null) ? (ex.GetType().Name + ": " + ex.Message + "\n" + ex.StackTrace) : context);
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)$"[{_tag}] [{value.SuppressedCount} events throttled] {context}: {text2}");
				}
				value.SuppressedCount = 0;
			}
		}

		private static string GetSignature(string context, Exception ex)
		{
			if (ex == null)
			{
				return context ?? "unknown";
			}
			string stackTrace = ex.StackTrace;
			string text = "no_stack";
			if (!string.IsNullOrEmpty(stackTrace))
			{
				int num = stackTrace.IndexOfAny(new char[2] { '\r', '\n' });
				text = ((num > 0) ? stackTrace.Substring(0, num).Trim() : stackTrace.Trim());
			}
			return ex.GetType().Name + " @ " + text + " (" + context + ")";
		}
	}

	internal static ManualLogSource Log;

	private static readonly List<ZDO> s_deadKeys = new List<ZDO>(64);

	private static readonly ErrorThrottle s_removeObjectsThrottle = new ErrorThrottle("SceneCompat:RemoveObjects");

	private static readonly ErrorThrottle s_createDestroyThrottle = new ErrorThrottle("SceneCompat:CreateDestroyObjects");

	private static readonly ErrorThrottle s_doubleZNetViewThrottle = new ErrorThrottle("DoubleZNetview Guard");

	public static ConfigEntry<bool> EnableSafeRemoveObjects;

	public static ConfigEntry<bool> EnableCreateDestroyFinalizer;

	public static ConfigEntry<bool> PreventDoubleZNetViewGhostZDO;

	private void Awake()
	{
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Expected O, but got Unknown
		//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fa: Expected O, but got Unknown
		//IL_025c: Unknown result type (might be due to invalid IL or missing references)
		//IL_026a: Expected O, but got Unknown
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		//IL_016e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Expected O, but got Unknown
		//IL_0179: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		EnableSafeRemoveObjects = ((BaseUnityPlugin)this).Config.Bind<bool>("ZNetScene", "EnableSafeRemoveObjects", true, "Enables dictionary sanitization and guaranteed removal in ZNetScene.RemoveObjects, preventing Unity 6 NRE cascades.");
		EnableCreateDestroyFinalizer = ((BaseUnityPlugin)this).Config.Bind<bool>("ZNetScene", "EnableCreateDestroyFinalizer", true, "Installs a safety Finalizer on ZNetScene.CreateDestroyObjects to catch and suppress unhandled exceptions, preventing the main loop from aborting.");
		PreventDoubleZNetViewGhostZDO = ((BaseUnityPlugin)this).Config.Bind<bool>("ZNetScene", "PreventDoubleZNetViewGhostZDO", false, "Experimental: Destroys duplicate ZNetViews on instantiated prefabs instead of creating unmanaged ghost ZDOs in ZDOMan. Default is false.");
		Harmony val = new Harmony("ntxfloy.valheimscenecompat");
		if (EnableSafeRemoveObjects.Value)
		{
			Type typeFromHandle = typeof(ZNetScene);
			MethodInfo method = typeFromHandle.GetMethod("RemoveObjects", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
			{
				typeof(List<ZDO>),
				typeof(List<ZDO>)
			}, null);
			FieldInfo field = typeFromHandle.GetField("m_instances", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			FieldInfo field2 = typeFromHandle.GetField("m_tempRemoved", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (method != null && field != null && field2 != null && field.FieldType == typeof(Dictionary<ZDO, ZNetView>) && field2.FieldType == typeof(List<ZNetView>))
			{
				MethodInfo method2 = typeof(Plugin).GetMethod("RemoveObjectsPrefix", BindingFlags.Static | BindingFlags.Public);
				MethodInfo method3 = typeof(Plugin).GetMethod("RemoveObjectsFinalizer", BindingFlags.Static | BindingFlags.Public);
				val.Patch((MethodBase)method, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null);
				Log.LogInfo((object)"Hooked ZNetScene.RemoveObjects with safe dictionary sanitization, guaranteed removal, and finalizer.");
			}
			else
			{
				Log.LogWarning((object)"ZNetScene.RemoveObjects signature or fields changed; safe RemoveObjects patch skipped safely.");
			}
		}
		if (EnableCreateDestroyFinalizer.Value)
		{
			MethodInfo method4 = typeof(ZNetScene).GetMethod("CreateDestroyObjects", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method4 != null)
			{
				MethodInfo method5 = typeof(Plugin).GetMethod("CreateDestroyObjectsFinalizer", BindingFlags.Static | BindingFlags.Public);
				val.Patch((MethodBase)method4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(method5), (HarmonyMethod)null);
				Log.LogInfo((object)"Installed safety Finalizer on ZNetScene.CreateDestroyObjects.");
			}
		}
		if (PreventDoubleZNetViewGhostZDO.Value)
		{
			MethodInfo method6 = typeof(ZNetView).GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method6 != null)
			{
				MethodInfo method7 = typeof(Plugin).GetMethod("ZNetViewAwakePrefix", BindingFlags.Static | BindingFlags.Public);
				val.Patch((MethodBase)method6, new HarmonyMethod(method7), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Installed DoubleZNetview duplicate suppression on ZNetView.Awake.");
			}
		}
	}

	public static bool RemoveObjectsPrefix(ZNetScene __instance, Dictionary<ZDO, ZNetView> ___m_instances, List<ZNetView> ___m_tempRemoved, List<ZDO> currentNearObjects, List<ZDO> currentDistantObjects)
	{
		if (___m_instances == null || ___m_tempRemoved == null)
		{
			return true;
		}
		byte b = (byte)(Time.frameCount & 0xFF);
		if (currentNearObjects != null)
		{
			for (int i = 0; i < currentNearObjects.Count; i++)
			{
				ZDO val = currentNearObjects[i];
				if (val != null)
				{
					val.TempRemoveEarmark = b;
				}
			}
		}
		if (currentDistantObjects != null)
		{
			for (int j = 0; j < currentDistantObjects.Count; j++)
			{
				ZDO val2 = currentDistantObjects[j];
				if (val2 != null)
				{
					val2.TempRemoveEarmark = b;
				}
			}
		}
		___m_tempRemoved.Clear();
		s_deadKeys.Clear();
		foreach (KeyValuePair<ZDO, ZNetView> ___m_instance in ___m_instances)
		{
			ZDO key = ___m_instance.Key;
			ZNetView value = ___m_instance.Value;
			if (key == null || value == null || (Object)(object)value == (Object)null)
			{
				s_deadKeys.Add(key);
				continue;
			}
			ZDO val3 = null;
			try
			{
				val3 = value.GetZDO();
			}
			catch (Exception ex)
			{
				s_removeObjectsThrottle.Log("Error retrieving ZDO from ZNetView during sanitization", ex);
			}
			if (val3 == null || val3 != key)
			{
				s_deadKeys.Add(key);
			}
			else if (val3.TempRemoveEarmark != b)
			{
				___m_tempRemoved.Add(value);
			}
		}
		for (int k = 0; k < s_deadKeys.Count; k++)
		{
			ZDO val4 = s_deadKeys[k];
			if (val4 != null)
			{
				___m_instances.Remove(val4);
			}
		}
		s_deadKeys.Clear();
		for (int l = 0; l < ___m_tempRemoved.Count; l++)
		{
			ZNetView val5 = ___m_tempRemoved[l];
			if (val5 == null)
			{
				continue;
			}
			ZDO val6 = null;
			try
			{
				val6 = val5.GetZDO();
				val5.ResetZDO();
				if (val6 != null)
				{
					try
					{
						if (!val6.Persistent && val6.IsOwner())
						{
							ZDOMan instance = ZDOMan.instance;
							if (instance != null)
							{
								instance.DestroyZDO(val6);
							}
						}
					}
					catch (Exception ex2)
					{
						s_removeObjectsThrottle.Log("Error checking persistence or destroying non-persistent ZDO in RemoveObjects", ex2);
					}
				}
				if ((Object)(object)val5 != (Object)null)
				{
					GameObject val7 = null;
					try
					{
						val7 = ((Component)val5).gameObject;
					}
					catch (Exception ex3)
					{
						s_removeObjectsThrottle.Log("Component.gameObject threw on destroyed entity in RemoveObjects", ex3);
					}
					if ((Object)(object)val7 != (Object)null)
					{
						Object.Destroy((Object)(object)val7);
					}
				}
			}
			catch (Exception ex4)
			{
				s_removeObjectsThrottle.Log("Error resetting or destroying ZNetView in RemoveObjects", ex4);
			}
			finally
			{
				if (val6 != null)
				{
					___m_instances.Remove(val6);
				}
			}
		}
		return false;
	}

	public static Exception RemoveObjectsFinalizer(Exception __exception)
	{
		if (__exception != null)
		{
			s_removeObjectsThrottle.Log("Unhandled exception in RemoveObjects", __exception);
			return null;
		}
		return null;
	}

	public static Exception CreateDestroyObjectsFinalizer(Exception __exception)
	{
		if (__exception != null)
		{
			s_createDestroyThrottle.Log("External exception in CreateDestroyObjects", __exception);
			return null;
		}
		return null;
	}

	public static bool ZNetViewAwakePrefix(ZNetView __instance)
	{
		if (!PreventDoubleZNetViewGhostZDO.Value)
		{
			return true;
		}
		if (ZNetView.m_useInitZDO && ZNetView.m_initZDO == null)
		{
			try
			{
				string text = (((Object)(object)((__instance != null) ? ((Component)__instance).gameObject : null) != (Object)null) ? ((Object)((Component)__instance).gameObject).name : "unknown");
				s_doubleZNetViewThrottle.Log("Prevented ghost ZDO creation on duplicate ZNetView on " + text + ". Destroying duplicate component.", null);
				if ((Object)(object)__instance != (Object)null)
				{
					((Behaviour)__instance).enabled = false;
					Object.Destroy((Object)(object)__instance);
				}
			}
			catch (Exception ex)
			{
				s_doubleZNetViewThrottle.Log("Error in ZNetViewAwakePrefix duplicate suppression", ex);
			}
			return false;
		}
		return true;
	}
}