using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ValheimWorldConvertDiag")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ValheimWorldConvertDiag")] [assembly: AssemblyTitle("ValheimWorldConvertDiag")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ValheimWorldConvertDiag { [BepInPlugin("sonicdm.valheimworldconvertdiag", "Valheim World Convert Diagnostic", "1.2.0")] public sealed class ValheimWorldConvertDiagPlugin : BaseUnityPlugin { public const string PluginGuid = "sonicdm.valheimworldconvertdiag"; public const string PluginName = "Valheim World Convert Diagnostic"; public const string PluginVersion = "1.2.0"; internal static ManualLogSource Log; internal static readonly object Sync = new object(); internal static bool InWorldConversion; internal static string CurrentInventory = ""; internal static string CurrentZdo = ""; internal static string InventorySourceZdo = ""; internal static string LastNamedItem = ""; internal static string LastTempItem = ""; internal static string LastComparison = ""; internal static readonly Queue RecentNamedItems = new Queue(); internal static string DiagnosticFile; private Harmony _harmony; private const BindingFlags AllMethods = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private void Awake() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; DiagnosticFile = GetDiagnosticPath(); Write("============================================================"); Write("Valheim World Convert Diagnostic 1.2.0 loading"); Write("Diagnostic file: " + DiagnosticFile); try { _harmony = new Harmony("sonicdm.valheimworldconvertdiag"); PatchRequiredMethods(_harmony); Write("Diagnostic patches installed successfully."); Write("Waiting for ZDOMan.ConvertInventories(). No world data is modified by this plugin."); } catch (Exception ex) { Write("FATAL: Failed to install diagnostic patches:\n" + ex); throw; } } private void OnDestroy() { try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } } private static void PatchRequiredMethods(Harmony harmony) { //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Expected O, but got Unknown //IL_02a6: Expected O, but got Unknown //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Expected O, but got Unknown //IL_02dc: Expected O, but got Unknown //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Expected O, but got Unknown //IL_0353: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Expected O, but got Unknown //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03b0: Expected O, but got Unknown //IL_03f1: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Expected O, but got Unknown Type type = FindType("ZDOMan"); Type type2 = FindType("Inventory"); Type type3 = FindType("ItemDrop+ItemData"); Type type4 = FindType("ZDO"); if (type == null) { throw new TypeLoadException("Could not find Valheim type ZDOMan"); } if (type2 == null) { throw new TypeLoadException("Could not find Valheim type Inventory"); } if (type3 == null) { throw new TypeLoadException("Could not find Valheim type ItemDrop+ItemData"); } if (type4 == null) { throw new TypeLoadException("Could not find Valheim type ZDO"); } MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "ConvertInventories"); if (methodInfo == null) { throw new MissingMethodException("Could not find ZDOMan.ConvertInventories"); } MethodInfo methodInfo2 = type2.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "LoadOld"); if (methodInfo2 == null) { throw new MissingMethodException("Could not find Inventory.LoadOld"); } List list = (from m in type2.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "AddItem" select m).Where(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return parameters.Length != 0 && parameters[0].ParameterType == typeof(string); }).ToList(); if (list.Count == 0) { throw new MissingMethodException("Could not find Inventory.AddItem(string, ...)"); } List list2 = (from m in type2.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "AddTempItem" select m).Where(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return parameters.Length != 0 && parameters[0].ParameterType == typeof(int); }).ToList(); if (list2.Count == 0) { throw new MissingMethodException("Could not find Inventory.AddTempItem(int, ...)"); } List list3 = (from m in type3.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "IsSameType" select m).ToList(); if (list3.Count == 0) { throw new MissingMethodException("Could not find ItemDrop.ItemData.IsSameType"); } List list4 = (from m in type4.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "GetString" || m.Name == "GetByteArray" || m.Name == "GetBytes" select m).Where(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return parameters.Length != 0 && (parameters[0].ParameterType == typeof(string) || parameters[0].ParameterType == typeof(int)); }).ToList(); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "ConvertInventoriesPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "ConvertInventoriesFinalizer", (Type[])null), (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "InventoryLoadOldPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "InventoryLoadOldFinalizer", (Type[])null), (HarmonyMethod)null); foreach (MethodInfo item in list) { harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "AddItemStringPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } foreach (MethodInfo item2 in list2) { harmony.Patch((MethodBase)item2, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "AddTempItemPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } foreach (MethodInfo item3 in list3) { harmony.Patch((MethodBase)item3, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "IsSameTypePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } foreach (MethodInfo item4 in list4) { harmony.Patch((MethodBase)item4, new HarmonyMethod(typeof(ValheimWorldConvertDiagPlugin), "ZdoInventoryGetterPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Write($"Patched: ConvertInventories=1, LoadOld=1, AddItem(string)={list.Count}, AddTempItem={list2.Count}, IsSameType={list3.Count}, ZDO inventory getters={list4.Count}"); } public static void ConvertInventoriesPrefix(MethodBase __originalMethod, object[] __args) { SafeDiagnostic(delegate { lock (Sync) { InWorldConversion = true; CurrentInventory = ""; CurrentZdo = ""; InventorySourceZdo = ""; LastNamedItem = ""; LastTempItem = ""; LastComparison = ""; RecentNamedItems.Clear(); } Write("=== LEGACY WORLD INVENTORY CONVERSION STARTED ==="); Write("ConvertInventories args: " + FormatArguments(__originalMethod, __args)); }); } public static Exception ConvertInventoriesFinalizer(Exception __exception) { SafeDiagnostic(delegate { if (__exception != null) { Write("=== ConvertInventories EXITED WITH EXCEPTION ==="); WriteFailureReport(__exception, null); } else { Write("=== LEGACY WORLD INVENTORY CONVERSION COMPLETED ==="); } lock (Sync) { InWorldConversion = false; } }); return __exception; } public static void InventoryLoadOldPrefix(object __instance, MethodBase __originalMethod, object[] __args) { if (!InWorldConversion) { return; } SafeDiagnostic(delegate { lock (Sync) { CurrentInventory = DescribeInventory(__instance); InventorySourceZdo = CurrentZdo; LastNamedItem = ""; LastTempItem = ""; LastComparison = ""; RecentNamedItems.Clear(); } }); } public static Exception InventoryLoadOldFinalizer(Exception __exception, object __instance) { if (!InWorldConversion || __exception == null) { return __exception; } SafeDiagnostic(delegate { Write("!!! Inventory.LoadOld FAILED !!!"); WriteFailureReport(__exception, __instance); }); return __exception; } public static void AddItemStringPrefix(MethodBase __originalMethod, object __instance, object[] __args) { if (!InWorldConversion) { return; } SafeDiagnostic(delegate { string text = FormatArguments(__originalMethod, __args); lock (Sync) { CurrentInventory = DescribeInventory(__instance); LastNamedItem = text; RecentNamedItems.Enqueue(text); while (RecentNamedItems.Count > 12) { RecentNamedItems.Dequeue(); } } }); } public static void AddTempItemPrefix(MethodBase __originalMethod, object __instance, object[] __args) { if (!InWorldConversion) { return; } SafeDiagnostic(delegate { lock (Sync) { CurrentInventory = DescribeInventory(__instance); LastTempItem = FormatArguments(__originalMethod, __args); } }); } public static void ZdoInventoryGetterPrefix(object __instance, MethodBase __originalMethod, object[] __args) { if (!InWorldConversion || __args == null || __args.Length == 0) { return; } SafeDiagnostic(delegate { object obj = __args[0]; bool flag = false; if (obj is string a) { flag = string.Equals(a, "items", StringComparison.Ordinal); } else if (obj is int num) { flag = num == StableHash("items"); } if (!flag) { return; } lock (Sync) { CurrentZdo = DescribeZdo(__instance); } }); } public static void IsSameTypePrefix(object __instance, MethodBase __originalMethod, object[] __args) { if (!InWorldConversion) { return; } SafeDiagnostic(delegate { object item = ((__args != null && __args.Length != 0) ? __args[0] : null); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("THIS ItemData:"); stringBuilder.AppendLine(DescribeItemData(__instance)); stringBuilder.AppendLine("OTHER ItemData:"); stringBuilder.AppendLine(DescribeItemData(item)); lock (Sync) { LastComparison = stringBuilder.ToString().TrimEnd(); } }); } private static void WriteFailureReport(Exception ex, object inventoryInstance) { string currentInventory; string inventorySourceZdo; string lastNamedItem; string lastTempItem; string lastComparison; string[] array; lock (Sync) { currentInventory = CurrentInventory; inventorySourceZdo = InventorySourceZdo; lastNamedItem = LastNamedItem; lastTempItem = LastTempItem; lastComparison = LastComparison; array = RecentNamedItems.ToArray(); } Write("---------------- CONVERSION FAILURE REPORT ----------------"); Write("Inventory snapshot: " + ((inventoryInstance != null) ? DescribeInventory(inventoryInstance) : currentInventory)); Write("Source ZDO candidate: " + inventorySourceZdo); Write("Last Inventory.AddItem(string, ...) call:\n" + lastNamedItem); Write("Last Inventory.AddTempItem(int prefabHash, ...) call:\n" + lastTempItem); Write("Last ItemData.IsSameType comparison:\n" + lastComparison); if (array.Length != 0) { Write("Recent named items in this inventory, oldest -> newest:"); for (int i = 0; i < array.Length; i++) { Write($" [{i + 1:00}] {array[i]}"); } } Write("Exception:\n" + ex); Write("-----------------------------------------------------------"); } private static string DescribeZdo(object zdo) { if (zdo == null) { return ""; } try { List list = new List(); list.Add("type=" + zdo.GetType().FullName); string[] array = new string[7] { "m_uid", "m_position", "m_sector", "m_prefab", "m_type", "m_owner", "m_dataRevision" }; foreach (string text in array) { object memberValue = GetMemberValue(zdo, text); if (memberValue != null) { list.Add(text + "=" + FormatSimple(memberValue)); } } array = new string[2] { "GetPosition", "GetPrefab" }; foreach (string methodName in array) { try { MethodInfo methodInfo = zdo.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo x) => x.Name == methodName && x.GetParameters().Length == 0); if (methodInfo != null) { object obj = methodInfo.Invoke(zdo, null); if (obj != null) { list.Add(methodName + "=" + FormatSimple(obj)); } } } catch { } } return string.Join(", ", list); } catch (Exception ex) { return ""; } } private static int StableHash(string str) { int num = 5381; int num2 = num; for (int i = 0; i < str.Length && str[i] != 0; i += 2) { num = ((num << 5) + num) ^ str[i]; if (i == str.Length - 1 || str[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ str[i + 1]; } return num + num2 * 1566083941; } private static string DescribeInventory(object inventory) { if (inventory == null) { return ""; } try { object memberValue = GetMemberValue(inventory, "m_name"); object memberValue2 = GetMemberValue(inventory, "m_width"); object memberValue3 = GetMemberValue(inventory, "m_height"); int? collectionCount = GetCollectionCount(GetMemberValue(inventory, "m_inventory")); return "type=" + inventory.GetType().FullName + ", name=" + FormatSimple(memberValue) + ", size=" + FormatSimple(memberValue2) + "x" + FormatSimple(memberValue3) + ", loadedItemCount=" + (collectionCount.HasValue ? collectionCount.Value.ToString() : "?"); } catch (Exception ex) { return ""; } } private static string DescribeItemData(object item) { if (item == null) { return " "; } try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(" runtimeType=" + item.GetType().FullName); AppendMember(stringBuilder, item, "m_dropPrefab", describeUnityName: true); AppendMember(stringBuilder, item, "m_stack"); AppendMember(stringBuilder, item, "m_durability"); AppendMember(stringBuilder, item, "m_quality"); AppendMember(stringBuilder, item, "m_variant"); AppendMember(stringBuilder, item, "m_crafterID"); AppendMember(stringBuilder, item, "m_crafterName"); AppendMember(stringBuilder, item, "m_worldLevel"); AppendMember(stringBuilder, item, "m_equipped"); AppendMember(stringBuilder, item, "m_gridPos"); object memberValue = GetMemberValue(item, "m_shared"); if (memberValue == null) { stringBuilder.AppendLine(" m_shared= <<< IMPORTANT"); } else { stringBuilder.AppendLine(" m_shared.runtimeType=" + memberValue.GetType().FullName); AppendMember(stringBuilder, memberValue, "m_name", describeUnityName: false, " m_shared."); AppendMember(stringBuilder, memberValue, "m_itemType", describeUnityName: false, " m_shared."); AppendMember(stringBuilder, memberValue, "m_maxStackSize", describeUnityName: false, " m_shared."); AppendMember(stringBuilder, memberValue, "m_maxQuality", describeUnityName: false, " m_shared."); AppendMember(stringBuilder, memberValue, "m_weight", describeUnityName: false, " m_shared."); AppendMember(stringBuilder, memberValue, "m_dlc", describeUnityName: false, " m_shared."); } return stringBuilder.ToString().TrimEnd(); } catch (Exception ex) { return " "; } } private static void AppendMember(StringBuilder sb, object obj, string memberName, bool describeUnityName = false, string prefix = " ") { object memberValue = GetMemberValue(obj, memberName); string text = (describeUnityName ? DescribeUnityObject(memberValue) : FormatSimple(memberValue)); sb.AppendLine(prefix + memberName + "=" + text); } private static string DescribeUnityObject(object value) { if (value == null) { return " <<< IMPORTANT"; } try { object memberValue = GetMemberValue(value, "name"); if (memberValue != null) { return value.GetType().FullName + " name=" + FormatSimple(memberValue); } } catch { } return "<" + value.GetType().FullName + ">"; } private static string FormatArguments(MethodBase method, object[] args) { try { ParameterInfo[] parameters = method.GetParameters(); List list = new List(); int num = Math.Max(parameters.Length, (args != null) ? args.Length : 0); for (int i = 0; i < num; i++) { string text = ((i < parameters.Length) ? parameters[i].Name : ("arg" + i)); object value = ((args != null && i < args.Length) ? args[i] : null); list.Add(text + "=" + FormatSimple(value)); } return method.DeclaringType?.FullName + "." + method.Name + "(" + string.Join(", ", list.ToArray()) + ")"; } catch (Exception ex) { return ""; } } private static string FormatSimple(object value) { if (value == null) { return ""; } Type type = value.GetType(); if (value is string text) { return "\"" + text.Replace("\r", "\\r").Replace("\n", "\\n") + "\""; } if (type.IsPrimitive || type.IsEnum || value is decimal) { return Convert.ToString(value, CultureInfo.InvariantCulture); } if (value is IDictionary dictionary) { return $"<{type.FullName} Count={dictionary.Count}>"; } if (value is ICollection collection) { return $"<{type.FullName} Count={collection.Count}>"; } if (type.IsValueType) { try { return value.ToString(); } catch { return "<" + type.FullName + ">"; } } return "<" + type.FullName + ">"; } private static object GetMemberValue(object instance, string name) { if (instance == null) { return null; } Type type = instance.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(instance); } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(instance, null); } type = type.BaseType; } return null; } private static int? GetCollectionCount(object value) { if (value == null) { return null; } if (value is ICollection collection) { return collection.Count; } PropertyInfo property = value.GetType().GetProperty("Count", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object value2 = property.GetValue(value, null); if (value2 is int) { return (int)value2; } } return null; } private static Type FindType(string fullName) { Type type = AccessTools.TypeByName(fullName); if (type != null) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type2 = assemblies[i].GetType(fullName, throwOnError: false); if (type2 != null) { return type2; } } return null; } private static string GetDiagnosticPath() { try { if (Directory.Exists("/config/bepinex")) { return Path.Combine("/config/bepinex", "ValheimWorldConvertDiag.log"); } } catch { } try { return Path.Combine(Paths.ConfigPath, "ValheimWorldConvertDiag.log"); } catch { return Path.Combine(Environment.CurrentDirectory, "ValheimWorldConvertDiag.log"); } } private static void SafeDiagnostic(Action action) { try { action(); } catch (Exception ex) { try { Write("Diagnostic code caught its own exception and ignored it: " + ex); } catch { } } } private static void Write(string message) { string text = "[ValheimWorldConvertDiag] " + message; try { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)message); } } catch { } try { lock (Sync) { File.AppendAllText(DiagnosticFile, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " " + text + Environment.NewLine); } } catch { } } } }