using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using ValheimVRM; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("VRMCompanions")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("VRMCompanions")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("8aaf6e91-f870-4b1f-a224-b3b681dbbf1f")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] [BepInPlugin("com.vrm.companions", "VRM Companions", "1.1.1")] public class VRMCompanionsPlugin : BaseUnityPlugin { internal static ManualLogSource Log; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Harmony.CreateAndPatchAll(typeof(CompanionPatch), (string)null); Harmony.CreateAndPatchAll(typeof(HideArmorPatch), (string)null); Log.LogInfo((object)"VRM Companions loaded"); } } [HarmonyPatch(typeof(VisEquipment), "UpdateEquipmentVisuals")] public class HideArmorPatch { private static void Postfix(VisEquipment __instance) { Humanoid component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && ((Object)component).name.Contains("HC_Companion")) { CompanionPatch.ApplyArmorHide(component); } } } [HarmonyPatch(typeof(Humanoid), "Start")] public class CompanionPatch { public class VRMMarker : MonoBehaviour { } private static void Postfix(Humanoid __instance) { if (!((Object)(object)__instance == (Object)null) && ((Object)__instance).name.Contains("HC_Companion")) { LogSafe("✔ Companion detected: " + ((Object)__instance).name); ((MonoBehaviour)__instance).StartCoroutine(ReplaceVRM(__instance)); } } private static IEnumerator ReplaceVRM(Humanoid humanoid) { yield return (object)new WaitForSeconds(0.5f); VisEquipment vis = ((Component)humanoid).GetComponent(); if ((Object)(object)vis == (Object)null) { LogSafe("❌ VisEquipment missing"); yield break; } string companionName = null; float timeout = 3f; float timer = 0f; while (timer < timeout) { Character c2 = (Character)(object)humanoid; if (c2 != null && !string.IsNullOrWhiteSpace(c2.m_name) && c2.m_name != "Companion") { companionName = c2.m_name; LogSafe("✔ Name resolved from Character.m_name: " + companionName); break; } timer += 0.2f; yield return (object)new WaitForSeconds(0.2f); } if (string.IsNullOrWhiteSpace(companionName)) { ZNetView nview = ((Component)humanoid).GetComponent(); if ((Object)(object)nview != (Object)null && nview.IsValid()) { string zdoName = nview.GetZDO().GetString("name", ""); if (!string.IsNullOrWhiteSpace(zdoName) && zdoName != "Companion") { companionName = zdoName; LogSafe("✔ Name from ZDO: " + companionName); } } } if (string.IsNullOrWhiteSpace(companionName) || companionName == "Companion") { LogSafe("❌ Failed to resolve unique companion name, using fallback"); companionName = "Companion"; } Character dbgChar = (Character)(object)humanoid; if (dbgChar != null) { LogSafe("DEBUG final m_name: " + dbgChar.m_name); } ZNetView dbgNview = ((Component)humanoid).GetComponent(); if ((Object)(object)dbgNview != (Object)null && dbgNview.IsValid()) { LogSafe("DEBUG ZDO name: " + dbgNview.GetZDO().GetString("name", "NULL")); } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char c in invalidFileNameChars) { companionName = companionName.Replace(c.ToString(), ""); } string folder = Path.Combine(Paths.GameRootPath, "ValheimVRM"); if (!Directory.Exists(folder)) { LogSafe("❌ VRM folder missing: " + folder); yield break; } string path = null; string[] files = Directory.GetFiles(folder, "*.vrm"); foreach (string file in files) { if (Path.GetFileNameWithoutExtension(file).Equals(companionName, StringComparison.OrdinalIgnoreCase)) { path = file; break; } } if (path == null) { LogSafe("❌ No VRM found for '" + companionName + "', trying fallback"); string fallback = Path.Combine(folder, "Companion.vrm"); if (!File.Exists(fallback)) { LogSafe("❌ Fallback VRM missing (Companion.vrm)"); yield break; } path = fallback; } LogSafe("✔ Using VRM: " + path); Dictionary cfg = LoadSettingsFile(path); byte[] data = File.ReadAllBytes(path); bool done = false; yield return VRM.ImportVisualAsync(data, path, 1f, (Action)delegate(GameObject loaded) { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)loaded == (Object)null) { LogSafe("❌ VRM load failed"); done = true; } else { VisEquipment component = ((Component)humanoid).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.m_bodyModel != (Object)null) { ((Renderer)component.m_bodyModel).enabled = false; LogSafe("✔ Vanilla body hidden"); } float result = 1f; float result2 = 1f; float result3 = 1f; if (cfg.TryGetValue("ModelScale", out var value)) { float.TryParse(value, out result); } if (cfg.TryGetValue("SpringBoneStiffness", out var value2)) { float.TryParse(value2, out result2); } if (cfg.TryGetValue("SpringBoneGravityPower", out var value3)) { float.TryParse(value3, out result3); } loaded.transform.localScale = Vector3.one * result; Animator componentInChildren = ((Component)humanoid).GetComponentInChildren(); VrmSettingsContainer settings = Settings.GetSettings(companionName); loaded.transform.SetParent(((Component)componentInChildren).transform.parent, false); loaded.transform.localPosition = Vector3.zero; loaded.transform.localRotation = Quaternion.identity; loaded.AddComponent(); LogSafe("✔ VRM attached to companion"); VRMAnimationSync val = loaded.GetComponent(); if ((Object)(object)val == (Object)null) { val = loaded.AddComponent(); } val.Setup(componentInChildren, settings, false); componentInChildren.cullingMode = (AnimatorCullingMode)0; componentInChildren.updateMode = (AnimatorUpdateMode)0; ForceSpringBoneUpdate(loaded, result2, result3); ApplyArmorHide(humanoid); done = true; } }); while (!done) { yield return null; } } public static void ApplyArmorHide(Humanoid humanoid) { VisEquipment component = ((Component)humanoid).GetComponent(); if ((Object)(object)component != (Object)null) { component.SetHelmetItem(""); component.SetShoulderItem("", 0); component.SetChestItem(""); component.SetLegItem(""); component.SetUtilityItem(""); component.SetHairItem(""); component.SetBeardItem(""); } Renderer[] componentsInChildren = ((Component)humanoid).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).GetComponentInParent() != (Object)null)) { string text = ((Object)((Component)val).gameObject).name.ToLower(); if (text.Contains("armor") || text.Contains("helmet") || text.Contains("helm") || text.Contains("chest") || text.Contains("shoulder") || text.Contains("cape") || text.Contains("leg")) { val.enabled = false; } } } } private static void ForceSpringBoneUpdate(GameObject vrmRoot, float springStiffness, float springGravity) { MonoBehaviour[] componentsInChildren = vrmRoot.GetComponentsInChildren(true); foreach (MonoBehaviour val in componentsInChildren) { Type type = ((object)val).GetType(); if (type.Name.Contains("VRMSpringBone")) { FieldInfo field = type.GetField("m_stiffnessForce"); FieldInfo field2 = type.GetField("m_gravityPower"); if (field != null) { float num = (float)field.GetValue(val); field.SetValue(val, num * Mathf.Clamp(springStiffness, 0.1f, 5f)); } if (field2 != null) { float num2 = (float)field2.GetValue(val); field2.SetValue(val, num2 * Mathf.Clamp(springGravity, 0.1f, 5f)); } } } } private static Dictionary LoadSettingsFile(string vrmPath) { string text = Path.Combine(Path.GetDirectoryName(vrmPath), "settings_" + Path.GetFileNameWithoutExtension(vrmPath) + ".txt"); Dictionary dictionary = new Dictionary(); if (!File.Exists(text)) { return dictionary; } string[] array = File.ReadAllLines(text); foreach (string text2 in array) { if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("//") && text2.Contains("=")) { string[] array2 = text2.Split(new char[1] { '=' }); if (array2.Length == 2) { dictionary[array2[0].Trim()] = array2[1].Trim(); } } } LogSafe("✔ Loaded VRM settings: " + text); return dictionary; } private static void LogSafe(string msg) { if (VRMCompanionsPlugin.Log != null) { VRMCompanionsPlugin.Log.LogInfo((object)msg); } else { Debug.Log((object)msg); } } }