Decompiled source of NullReferenceFix v1.0.20

NullReferenceFix.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NullReferenceFix.Patches;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("NullReferenceFix")]
[assembly: AssemblyDescription("Fixes a vanilla Valheim NullReferenceException loop in ZNetScene.RemoveObjects.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("KorCaptain")]
[assembly: AssemblyProduct("NullReferenceFix")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("B3C1F0F1-6E8C-4B7A-9B2E-1E7F5C9A2D41")]
[assembly: AssemblyFileVersion("1.0.20")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.20.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace NullReferenceFix
{
	[BepInPlugin("KorCaptain.NullReferenceFix", "NullReferenceFix", "1.0.20")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class NullReferenceFixPlugin : BaseUnityPlugin
	{
		internal const string ModVersion = "1.0.20";

		internal const string ModName = "NullReferenceFix";

		internal const string Author = "KorCaptain";

		private const string ModGUID = "KorCaptain.NullReferenceFix";

		private readonly Harmony _harmony = new Harmony("KorCaptain.NullReferenceFix");

		public static ManualLogSource NullReferenceFixLogger;

		public void Awake()
		{
			NullReferenceFixLogger = ((BaseUnityPlugin)this).Logger;
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			TMPLocalizationFallbackFontRegistrar.Initialize();
			NullReferenceFixLogger.LogInfo((object)"NullReferenceFix v1.0.20 loaded.");
		}
	}
}
namespace NullReferenceFix.Patches
{
	[HarmonyPatch(typeof(ZNetScene), "RemoveObjects")]
	public static class ZNetScene_RemoveObjects_NullGuard_Patch
	{
		private static readonly FieldInfo? InstancesField = typeof(ZNetScene).GetField("m_instances", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static Exception? Finalizer(ZNetScene __instance, Exception __exception)
		{
			if (!(__exception is NullReferenceException))
			{
				return __exception;
			}
			try
			{
				if (!(InstancesField?.GetValue(__instance) is Dictionary<ZDO, ZNetView> dictionary))
				{
					return null;
				}
				List<ZDO> list = new List<ZDO>();
				foreach (KeyValuePair<ZDO, ZNetView> item in dictionary)
				{
					if ((Object)(object)item.Value == (Object)null)
					{
						list.Add(item.Key);
					}
				}
				foreach (ZDO item2 in list)
				{
					dictionary.Remove(item2);
				}
				if (list.Count > 0)
				{
					NullReferenceFixPlugin.NullReferenceFixLogger.LogWarning((object)string.Format("[NullFix] Cleared {0} stale ZNetView entr{1} that caused a ZNetScene.RemoveObjects NullReferenceException.", list.Count, (list.Count == 1) ? "y" : "ies"));
				}
			}
			catch (Exception arg)
			{
				NullReferenceFixPlugin.NullReferenceFixLogger.LogError((object)$"Failed to clean up stale ZNetView entries: {arg}");
			}
			return null;
		}
	}
	[HarmonyPatch]
	public static class AzuCraftyBoxes_HaveRequirements_NullGuard_Patch
	{
		private const string AzuCraftyBoxesPluginGuid = "Azumatt.AzuCraftyBoxes";

		private static readonly FieldInfo? ContainersField = ResolveContainersField();

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		private static MethodBase? TargetMethod()
		{
			Type type = ResolveAzuCraftyBoxesType("AzuCraftyBoxes.Patches.PlayerHaveRequirementsPatchRBoolInt");
			return (type == null) ? null : AccessTools.DeclaredMethod(type, "Postfix", (Type[])null, (Type[])null);
		}

		private static FieldInfo? ResolveContainersField()
		{
			Type type = ResolveAzuCraftyBoxesType("AzuCraftyBoxes.Util.Functions.Boxes");
			return (type == null) ? null : AccessTools.Field(type, "Containers");
		}

		private static Type? ResolveAzuCraftyBoxesType(string typeName)
		{
			if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuCraftyBoxes", out var value))
			{
				return null;
			}
			return ((object)value.Instance)?.GetType().Assembly.GetType(typeName);
		}

		private static Exception? Finalizer(Exception __exception)
		{
			if (!(__exception is NullReferenceException))
			{
				return __exception;
			}
			int num = RemoveDestroyedContainers();
			NullReferenceFixPlugin.NullReferenceFixLogger.LogWarning((object)((num > 0) ? string.Format("[NullFix] Cleared {0} stale (destroyed) container entr{1} that caused an AzuCraftyBoxes NullReferenceException during a recipe check.", num, (num == 1) ? "y" : "ies") : "[NullFix] Swallowed a NullReferenceException from AzuCraftyBoxes' recipe container check."));
			return null;
		}

		private static int RemoveDestroyedContainers()
		{
			try
			{
				if (!(ContainersField?.GetValue(null) is HashSet<Container> hashSet))
				{
					return 0;
				}
				List<Container> list = new List<Container>();
				foreach (Container item in hashSet)
				{
					if ((Object)(object)item == (Object)null)
					{
						list.Add(item);
					}
				}
				foreach (Container item2 in list)
				{
					hashSet.Remove(item2);
				}
				return list.Count;
			}
			catch (Exception arg)
			{
				NullReferenceFixPlugin.NullReferenceFixLogger.LogError((object)$"Failed to clean up stale AzuCraftyBoxes container entries: {arg}");
				return 0;
			}
		}
	}
	[HarmonyPatch(typeof(EnemyHud), "ShowHud")]
	public static class EnemyHud_ShowHud_NullGuard_Patch
	{
		private static readonly FieldInfo? HudsField = typeof(EnemyHud).GetField("m_huds", BindingFlags.Instance | BindingFlags.NonPublic);

		private static FieldInfo? _guiField;

		private static Exception? Finalizer(EnemyHud __instance, Character c, Exception __exception)
		{
			if (!(__exception is NullReferenceException))
			{
				return __exception;
			}
			try
			{
				if (RemoveStaleHud(__instance, c))
				{
					NullReferenceFixPlugin.NullReferenceFixLogger.LogWarning((object)"[NullFix] Cleared a stale EnemyHud entry that caused a NullReferenceException in a mod's EnemyHud.ShowHud patch (seen with EpicMMOSystem's PvP name-color patch when another mod force-refreshes EnemyHud.ShowHud).");
				}
				else
				{
					NullReferenceFixPlugin.NullReferenceFixLogger.LogWarning((object)"[NullFix] Swallowed a NullReferenceException thrown from a mod's EnemyHud.ShowHud patch.");
				}
			}
			catch (Exception arg)
			{
				NullReferenceFixPlugin.NullReferenceFixLogger.LogError((object)$"Failed to clean up stale EnemyHud entry: {arg}");
			}
			return null;
		}

		private static bool RemoveStaleHud(EnemyHud instance, Character c)
		{
			if (!(HudsField?.GetValue(instance) is IDictionary dictionary) || !dictionary.Contains(c))
			{
				return false;
			}
			object obj = dictionary[c];
			if (obj != null)
			{
				if ((object)_guiField == null)
				{
					_guiField = obj.GetType().GetField("m_gui", BindingFlags.Instance | BindingFlags.Public);
				}
				object? obj2 = _guiField?.GetValue(obj);
				GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null);
				if (val != null && (Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
			dictionary.Remove(c);
			return true;
		}
	}
	[HarmonyPatch]
	public static class TMP_Text_LoadFontAsset_MissingFont_Patch
	{
		private static TMP_FontAsset? _fallbackFont;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.Method(typeof(TextMeshProUGUI), "LoadFontAsset", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(TextMeshPro), "LoadFontAsset", (Type[])null, (Type[])null);
		}

		private static void Prefix(TMP_Text __instance)
		{
			if (!((Object)(object)__instance.font != (Object)null))
			{
				TMP_FontAsset val = _fallbackFont ?? (_fallbackFont = ResolveFallbackFont());
				if (!((Object)(object)val == (Object)null))
				{
					__instance.font = val;
				}
			}
		}

		private static TMP_FontAsset? ResolveFallbackFont()
		{
			if ((Object)(object)TMP_Settings.defaultFontAsset != (Object)null)
			{
				return TMP_Settings.defaultFontAsset;
			}
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			return (array.Length != 0) ? array[0] : null;
		}
	}
	public static class TMPLocalizationFallbackFontRegistrar
	{
		private static readonly string[] TargetFontNames = new string[12]
		{
			"NotoSansArabic-Regular SDF", "NotoSansArmenian-Regular SDF", "NotoSansBengali-Regular SDF", "NotoSansDevanagari-Regular SDF", "NotoSansGeorgian-Regular SDF", "NotoSansHebrew-Regular SDF", "NotoSansJP-Regular SDF", "NotoSansKR-Regular SDF", "NotoSansMalayalam-Regular SDF", "NotoSansSC-Regular SDF",
			"NotoSansThai-Regular SDF", "NotoEmoji-Regular SDF"
		};

		private const string WarningSubstring = "font asset or any potential fallbacks";

		private static readonly HashSet<string> _pending = new HashSet<string>(TargetFontNames);

		private static bool _subscribed;

		public static void Initialize()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			if (!_subscribed)
			{
				RegisterAndReport(FindLocalizationFontAssets());
				if (_pending.Count != 0)
				{
					_subscribed = true;
					Application.logMessageReceived += new LogCallback(OnLogMessageReceived);
				}
			}
		}

		private static void OnLogMessageReceived(string message, string stackTrace, LogType type)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			if (_pending.Count == 0)
			{
				Unsubscribe();
			}
			else if ((int)type == 2 && message.Contains("font asset or any potential fallbacks"))
			{
				RegisterAndReport(FindLocalizationFontAssets());
				if (_pending.Count == 0)
				{
					Unsubscribe();
				}
			}
		}

		private static void Unsubscribe()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			Application.logMessageReceived -= new LogCallback(OnLogMessageReceived);
			_subscribed = false;
		}

		private static void RegisterAndReport(List<TMP_FontAsset> found)
		{
			if (found.Count == 0)
			{
				return;
			}
			List<string> list = new List<string>();
			foreach (TMP_FontAsset item in found)
			{
				_pending.Remove(((Object)item).name);
				if (!TMP_Settings.fallbackFontAssets.Contains(item))
				{
					TMP_Settings.fallbackFontAssets.Add(item);
					list.Add(((Object)item).name);
				}
			}
			if (list.Count > 0)
			{
				StringBuilder stringBuilder = new StringBuilder("[NullFix] Added ");
				stringBuilder.Append(list.Count);
				stringBuilder.Append(" localization fallback font(s) to TMP's global fallback list: ");
				stringBuilder.Append(string.Join(", ", list));
				stringBuilder.Append(" - so text in those scripts (e.g. player nicknames, guild names, info panels) renders instead of showing blank spaces or missing-glyph boxes.");
				NullReferenceFixPlugin.NullReferenceFixLogger.LogWarning((object)stringBuilder.ToString());
			}
		}

		private static List<TMP_FontAsset> FindLocalizationFontAssets()
		{
			List<TMP_FontAsset> list = new List<TMP_FontAsset>();
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			TMP_FontAsset[] array2 = array;
			foreach (TMP_FontAsset val in array2)
			{
				if (_pending.Contains(((Object)val).name))
				{
					list.Add(val);
				}
			}
			foreach (string name in _pending)
			{
				if (!list.Exists((TMP_FontAsset f) => ((Object)f).name == name))
				{
					TMP_FontAsset val2 = Resources.Load<TMP_FontAsset>("Fonts & Materials/" + name);
					if ((Object)(object)val2 != (Object)null)
					{
						list.Add(val2);
					}
				}
			}
			return list;
		}
	}
}