Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Prefab Hash Probe v0.2.0
plugins/PrefabHashProbe/PrefabHashProbe.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] [BepInPlugin("local.diagnostics.prefabhashprobe", "Prefab Hash Probe", "0.2.0")] public sealed class PrefabHashProbe : BaseUnityPlugin { private sealed class Missing { public long Attempts; public readonly List<string> Examples = new List<string>(); public readonly HashSet<object> SampledZdos = new HashSet<object>(); } private static PrefabHashProbe probe; private Harmony harmony; private ConfigEntry<bool> suppress; private ConfigEntry<bool> diagnostics; private string folder; private Type sceneType; private Type zdoType; private Type managerType; private Type netType; private FieldInfo registryField; private MethodInfo getHash; private MethodInfo getPosition; private object observedScene; private float sceneSince; private float nextWrite; private int phase; private int sceneNumber; private readonly Dictionary<int, Missing> missing = new Dictionary<int, Missing>(); private readonly WarningGate warningGate = new WarningGate(10000); private readonly Dictionary<int, string> knownNames = new Dictionary<int, string>(); private bool diagnosticFailureReported; private void Awake() { //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Expected O, but got Unknown probe = this; suppress = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "SuppressRepeatedMissingWarnings", true, "Keep the first identical Missing prefab hash warning; count every failed attempt in CSV. Does not skip object loading or change saves."); diagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnableDiagnostics", false, "Opt-in detailed exports: prefab names, object IDs/positions, plugin paths and environment. Restart required. No uploads."); if (diagnostics.Value) { folder = Path.Combine(Paths.BepInExRootPath, "diagnostics", "PrefabHashProbe", DateTime.UtcNow.ToString("yyyyMMdd-HHmmss") + "-" + Guid.NewGuid().ToString("N").Substring(0, 6)); Directory.CreateDirectory(folder); } sceneType = AccessTools.TypeByName("ZNetScene"); zdoType = AccessTools.TypeByName("ZDO"); managerType = AccessTools.TypeByName("ZDOMan"); netType = AccessTools.TypeByName("ZNet"); if (sceneType == null || zdoType == null) { throw new InvalidOperationException("Game types unavailable"); } registryField = AccessTools.Field(sceneType, "m_namedPrefabs"); getHash = AccessTools.Method(zdoType, "GetPrefab", Type.EmptyTypes, (Type[])null); getPosition = AccessTools.Method(zdoType, "GetPosition", Type.EmptyTypes, (Type[])null); harmony = new Harmony("local.diagnostics.prefabhashprobe"); if (diagnostics.Value) { harmony.Patch((MethodBase)AccessTools.Method(sceneType, "CreateObject", new Type[1] { zdoType }, (Type[])null), new HarmonyMethod(typeof(PrefabHashProbe), "BeforeCreate", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } harmony.Patch((MethodBase)AccessTools.Method(AccessTools.TypeByName("ZLog"), "LogWarning", new Type[1] { typeof(object) }, (Type[])null), new HarmonyMethod(typeof(PrefabHashProbe), "BeforeWarning", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)(diagnostics.Value ? ("Diagnostic output: " + folder) : "Detailed diagnostics disabled; no diagnostic files written.")); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Repeated missing-prefab warning suppression: " + suppress.Value)); } private static void BeforeCreate(object __instance, object __0) { //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)probe == (Object)null || __0 == null) { return; } try { int num = (int)probe.getHash.Invoke(__0, null); if (num == 0) { return; } IDictionary dictionary = (IDictionary)probe.registryField.GetValue(__instance); object? obj = dictionary[num]; Object val = (Object)((obj is Object) ? obj : null); if (val != (Object)null) { return; } if (!probe.missing.TryGetValue(num, out var value)) { if (probe.missing.Count >= 10000) { return; } value = new Missing(); probe.missing.Add(num, value); ((BaseUnityPlugin)probe).Logger.LogInfo((object)("First missing hash " + num + "; capturing ZDO example.")); } value.Attempts++; if (value.Examples.Count < 8 && value.SampledZdos.Add(__0)) { object value2 = AccessTools.Field(probe.zdoType, "m_uid").GetValue(__0); Vector3 val2 = (Vector3)probe.getPosition.Invoke(__0, null); value.Examples.Add(string.Concat(value2, " @ ", val2.x.ToString("R", CultureInfo.InvariantCulture), ",", val2.y.ToString("R", CultureInfo.InvariantCulture), ",", val2.z.ToString("R", CultureInfo.InvariantCulture))); } } catch (Exception e) { probe.ReportFailure(e); } } private static bool BeforeWarning(object __0) { if (!((Object)(object)probe == (Object)null)) { return probe.warningGate.Allow(__0, probe.suppress.Value); } return true; } private static object Instance(Type t) { if (t == null) { return null; } PropertyInfo propertyInfo = AccessTools.Property(t, "instance"); if (propertyInfo != null) { return propertyInfo.GetValue(null, null); } FieldInfo fieldInfo = AccessTools.Field(t, "s_instance") ?? AccessTools.Field(t, "m_instance"); if (!(fieldInfo == null)) { return fieldInfo.GetValue(null); } return null; } private void Update() { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown try { object obj = Instance(sceneType); if (!object.ReferenceEquals(obj, observedScene)) { if (observedScene != null) { WriteMissing(); } missing.Clear(); knownNames.Clear(); warningGate.Reset(); observedScene = obj; sceneSince = Time.realtimeSinceStartup; phase = 0; sceneNumber++; } if (diagnostics.Value && obj != null && (!(obj is Object) || !((Object)obj == (Object)null))) { float num = Time.realtimeSinceStartup - sceneSince; if (phase == 0 && num > 5f) { phase = 1; Snapshot(obj, "initial"); } else if (phase == 1 && num > 25f) { phase = 2; Snapshot(obj, "settled"); } if (Time.realtimeSinceStartup > nextWrite) { nextWrite = Time.realtimeSinceStartup + 5f; WriteMissing(); } } } catch (Exception e) { ReportFailure(e); } } private void Snapshot(object scene, string phaseName) { string text = "scene-" + sceneNumber + "-" + phaseName; IDictionary dictionary = (IDictionary)registryField.GetValue(scene); Dictionary<string, string> sources = GetSources(); using (StreamWriter streamWriter = Writer(text + "-registry.csv")) { streamWriter.WriteLine("hash,name,source,calculated_hash"); foreach (DictionaryEntry item in dictionary) { object? value = item.Value; GameObject val = (GameObject)((value is GameObject) ? value : null); if (!((Object)(object)val == (Object)null)) { sources.TryGetValue(((Object)val).name, out var value2); int num = (int)item.Key; knownNames[num] = ((Object)val).name; streamWriter.WriteLine(num + "," + Csv(((Object)val).name) + "," + Csv(value2 ?? "unattributed") + "," + StableHash(((Object)val).name)); } } } HashSet<string> hashSet = new HashSet<string>(); using (StreamWriter streamWriter2 = Writer(text + "-assets.csv")) { streamWriter2.WriteLine("hash,name,source,calculated_hash"); GameObject[] array = Resources.FindObjectsOfTypeAll<GameObject>(); foreach (GameObject val2 in array) { if (!((Object)(object)val2 == (Object)null) && hashSet.Add(((Object)val2).name)) { sources.TryGetValue(((Object)val2).name, out var value3); int num2 = StableHash(((Object)val2).name); if (!knownNames.ContainsKey(num2)) { knownNames[num2] = ((Object)val2).name; } streamWriter2.WriteLine(num2 + "," + Csv(((Object)val2).name) + "," + Csv(value3 ?? "unattributed") + "," + num2); } } } DumpWorldHashes(text, dictionary); DumpEnvironment(text); WriteMissing(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Captured " + text + ": registry=" + dictionary.Count + ", loaded names=" + hashSet.Count + ", missing hashes=" + missing.Count)); } private Dictionary<string, string> GetSources() { Dictionary<string, string> dictionary = new Dictionary<string, string>(); Type type = AccessTools.TypeByName("Jotunn.Managers.PrefabManager"); if (type == null) { return dictionary; } object value = AccessTools.Property(type, "Instance").GetValue(null, null); if (!(AccessTools.Property(type, "Prefabs").GetValue(value, null) is IDictionary dictionary2)) { return dictionary; } foreach (DictionaryEntry item in dictionary2) { object value2 = item.Value; PropertyInfo propertyInfo = AccessTools.Property(value2.GetType(), "SourceMod"); BepInPlugin val = (BepInPlugin)((propertyInfo == null) ? null : /*isinst with value type is only supported in some contexts*/); if (val != null) { dictionary[Convert.ToString(item.Key)] = val.GUID + " / " + val.Name + " " + val.Version; } } return dictionary; } private void DumpWorldHashes(string prefix, IDictionary registry) { object obj = Instance(managerType); if (obj == null || !(AccessTools.Field(managerType, "m_objectsByID").GetValue(obj) is IDictionary dictionary)) { return; } Dictionary<int, int> dictionary2 = new Dictionary<int, int>(); foreach (DictionaryEntry item in dictionary) { int key = (int)getHash.Invoke(item.Value, null); dictionary2.TryGetValue(key, out var value); dictionary2[key] = value + 1; } using StreamWriter streamWriter = Writer(prefix + "-world-hashes.csv"); streamWriter.WriteLine("hash,zdo_count,registered,name"); foreach (KeyValuePair<int, int> item2 in dictionary2) { object? obj2 = registry[item2.Key]; GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null); streamWriter.WriteLine(item2.Key + "," + item2.Value + "," + ((Object)(object)val != (Object)null) + "," + Csv(((Object)(object)val == (Object)null) ? "" : ((Object)val).name)); } } private void DumpEnvironment(string prefix) { using StreamWriter streamWriter = Writer(prefix + "-environment.txt"); streamWriter.WriteLine("UTC=" + DateTime.UtcNow.ToString("o")); streamWriter.WriteLine(string.Concat("OS=", Environment.OSVersion, "; CLR=", Environment.Version, "; Unity=", Application.unityVersion)); object obj = Instance(netType); if (obj != null) { streamWriter.WriteLine("IsServer=" + AccessTools.Method(netType, "IsServer", Type.EmptyTypes, (Type[])null).Invoke(obj, null)); } Type type = sceneType.Assembly.GetType("Version"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "GetVersionString", new Type[1] { typeof(bool) }, (Type[])null)); if (methodInfo != null) { streamWriter.WriteLine("GameVersion=" + methodInfo.Invoke(null, new object[1] { false })); } foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; streamWriter.WriteLine(string.Concat("Plugin=", pluginInfo.Key, " | ", value.Metadata.Name, " | ", value.Metadata.Version, " | ", value.Location, " | SHA256=", FileHash(value.Location))); } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string name = assembly.GetName().Name; switch (name) { case "assembly_valheim": case "assembly_utils": case "Jotunn": streamWriter.WriteLine(string.Concat("Assembly=", name, " | MVID=", assembly.ManifestModule.ModuleVersionId, " | SHA256=", FileHash(assembly.Location))); break; } } Type type2 = AccessTools.TypeByName("StringExtensionMethods"); MethodInfo methodInfo2 = AccessTools.Method(type2, "GetStableHashCode", new Type[1] { typeof(string) }, (Type[])null); string[] array = new string[5] { "Player", "_ZoneCtrl", "_TerrainCompiler", "wood_wall", "piece_workbench" }; foreach (string text in array) { streamWriter.WriteLine(string.Concat("HashCheck=", text, " | game=", methodInfo2.Invoke(null, new object[1] { text }), " | independent=", StableHash(text))); } foreach (MethodBase allPatchedMethod in Harmony.GetAllPatchedMethods()) { Patches patchInfo = Harmony.GetPatchInfo(allPatchedMethod); streamWriter.WriteLine("Patched=" + allPatchedMethod.DeclaringType.FullName + "." + allPatchedMethod.Name + " | " + string.Join(",", new List<string>(patchInfo.Owners).ToArray())); } } private void WriteMissing() { if (!diagnostics.Value || folder == null || missing.Count == 0) { return; } using StreamWriter streamWriter = Writer("scene-" + sceneNumber + "-missing.csv"); streamWriter.WriteLine("hash,failed_create_attempts,candidate_name,zdo_samples_max8"); foreach (KeyValuePair<int, Missing> item in missing) { knownNames.TryGetValue(item.Key, out var value); streamWriter.WriteLine(item.Key + "," + item.Value.Attempts + "," + Csv(value ?? "") + "," + Csv(string.Join(" | ", item.Value.Examples.ToArray()))); } } private static int StableHash(string text) { int num = 5381; int num2 = num; for (int i = 0; i < text.Length && text[i] != 0; i += 2) { num = ((num << 5) + num) ^ text[i]; if (i == text.Length - 1 || text[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ text[i + 1]; } return num + num2 * 1566083941; } private StreamWriter Writer(string name) { return new StreamWriter(Path.Combine(folder, name), append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); } private static string Csv(string text) { return "\"" + text.Replace("\"", "\"\"") + "\""; } private static string FileHash(string path) { if (string.IsNullOrEmpty(path) || !File.Exists(path)) { return "unavailable"; } using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(path); return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").ToLowerInvariant(); } private void ReportFailure(Exception e) { if (!diagnosticFailureReported) { diagnosticFailureReported = true; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Probe diagnostics error (reported once): " + e)); } } private void OnDestroy() { try { WriteMissing(); } catch (Exception e) { ReportFailure(e); } if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)probe == (Object)(object)this) { probe = null; } } } internal sealed class WarningGate { private readonly HashSet<string> seen = new HashSet<string>(); private readonly int capacity; internal WarningGate(int capacity) { this.capacity = capacity; } internal bool Allow(object message, bool enabled) { string text = message as string; if (!enabled || text == null || !text.StartsWith("Missing prefab hash: ", StringComparison.Ordinal)) { return true; } if (seen.Contains(text)) { return false; } if (seen.Count < capacity) { seen.Add(text); } return true; } internal void Reset() { seen.Clear(); } }