Decompiled source of RandomYggdrasil v1.2.0

RandomYggdrasil.dll

Decompiled a week ago
using System;
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.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using MushroomMods;
using MushroomSync;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("RandomYggdrasil")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RandomYggdrasil")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("f50fb28c-a3c2-4aed-8241-53d463a14ab0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace RandomYggdrasil
{
	[BepInPlugin("gonfreecss.RandomYggdrasilMod", "RandomYggdrasil", "1.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class RandomYggdrasilMod : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(ZNetScene))]
		private class RandomizeYggdrasil_Patch
		{
			[HarmonyPatch("Awake")]
			[HarmonyPostfix]
			private static void RandomizeYggdrasil()
			{
				//IL_005f: Unknown result type (might be due to invalid IL or missing references)
				//IL_008f: Unknown result type (might be due to invalid IL or missing references)
				Debug.Log((object)"Starting location randomizer for Yggdrassil");
				string worldIdentifier = GetWorldIdentifier();
				int num = -1;
				if (worldIdentifier != null)
				{
					num = GetOrCreateRotation(worldIdentifier);
				}
				GameObject val = FindYggdrasilBranch();
				if ((Object)(object)val == (Object)null)
				{
					if (worldIdentifier != null && num >= 0)
					{
						Debug.Log((object)$"RandomYggdrasil: Stored rotation {num} degrees for world '{worldIdentifier}' (no scene object on this instance)");
					}
				}
				else if (num >= 0)
				{
					val.transform.rotation = Quaternion.Euler(0f, (float)num, 0f);
				}
				else if (worldIdentifier == null)
				{
					num = new Random().Next(360);
					val.transform.rotation = Quaternion.Euler(0f, (float)num, 0f);
					Debug.Log((object)$"RandomYggdrasil: No world info available, using one-time random rotation of {num} degrees");
				}
				else
				{
					Debug.Log((object)"RandomYggdrasil: Waiting for server config sync before applying rotation");
				}
			}
		}

		internal const string PluginId = "gonfreecss.RandomYggdrasilMod";

		internal const string PluginName = "RandomYggdrasil";

		internal const string PluginVersion = "1.2.0";

		private const string RotationSection = "WorldRotations";

		private const string ConfigFileName = "RandomYggdrasil.cfg";

		private const int RotationNotGenerated = -1;

		private const string BranchObjectName = "YggdrasilBranch";

		private const string VanillaBranchPath = "_GameMain/_Environment/YggdrasilBranch";

		private readonly Harmony harmony = new Harmony("gonfreecss.RandomYggdrasilMod");

		private static ConfigFile modConfig;

		private static ConfigFile alternateConfig;

		private static bool loggedMissingBranch;

		private static readonly Dictionary<string, ConfigEntry<int>> rotationEntries = new Dictionary<string, ConfigEntry<int>>();

		private void Awake()
		{
			InitializeConfigFiles();
			modConfig.Bind<bool>("General", "LockConfiguration", true, "If on, the server owns world rotations and connected clients use the server's values.");
			RotationSync.Initialize(((BaseUnityPlugin)this).Logger);
			int num = PatchIsolation.PatchAllIsolated(harmony, typeof(RandomYggdrasilMod).Assembly, ((BaseUnityPlugin)this).Logger);
			if (num > 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"RandomYggdrasil: {num} patch class(es) skipped - see the errors above.");
			}
			Debug.Log((object)("RandomYggdrasil: Loaded (" + (IsDedicatedServer() ? "dedicated server" : "client") + "), config at '" + modConfig.ConfigFilePath + "'"));
		}

		private static bool IsDedicatedServer()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			if ((int)SystemInfo.graphicsDeviceType == 4)
			{
				return true;
			}
			return Process.GetCurrentProcess().ProcessName.IndexOf("valheim_server", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static string GetClientConfigPath()
		{
			return Path.Combine(Paths.ConfigPath, "RandomYggdrasil.cfg");
		}

		private static string GetServerConfigPath()
		{
			if (Directory.Exists("/config"))
			{
				return Path.Combine("/config", "bepinex", "RandomYggdrasil.cfg");
			}
			return Path.Combine(Path.GetDirectoryName(Paths.BepInExRootPath), "config", "bepinex", "RandomYggdrasil.cfg");
		}

		private static string GetPrimaryConfigPath()
		{
			if (!IsDedicatedServer())
			{
				return GetClientConfigPath();
			}
			return GetServerConfigPath();
		}

		private static string GetAlternateConfigPath()
		{
			if (!IsDedicatedServer())
			{
				return GetServerConfigPath();
			}
			return GetClientConfigPath();
		}

		private static void EnsureConfigDirectoryExists(string configPath)
		{
			string directoryName = Path.GetDirectoryName(configPath);
			if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
		}

		private static void InitializeConfigFiles()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			string primaryConfigPath = GetPrimaryConfigPath();
			string alternateConfigPath = GetAlternateConfigPath();
			string text = (File.Exists(primaryConfigPath) ? primaryConfigPath : ((!File.Exists(alternateConfigPath)) ? primaryConfigPath : alternateConfigPath));
			EnsureConfigDirectoryExists(text);
			modConfig = new ConfigFile(text, true);
			alternateConfig = null;
			if (File.Exists(alternateConfigPath) && !string.Equals(text, alternateConfigPath, StringComparison.OrdinalIgnoreCase))
			{
				alternateConfig = new ConfigFile(alternateConfigPath, true);
			}
		}

		private static bool TryGetRotationFromConfig(ConfigFile config, string worldIdentifier, out int degrees)
		{
			ConfigEntry<int> val = config.Bind<int>("WorldRotations", worldIdentifier, -1, "Y-axis rotation (in degrees, 0-359) of Yggdrasil for this world. Generated once per world on the server; delete this entry to re-roll.");
			if (val.Value >= 0 && val.Value < 360)
			{
				degrees = val.Value;
				return true;
			}
			degrees = -1;
			return false;
		}

		private static bool TryImportRotationFromAlternateConfig(string worldIdentifier, ConfigEntry<int> entry)
		{
			if (alternateConfig == null)
			{
				return false;
			}
			if (!TryGetRotationFromConfig(alternateConfig, worldIdentifier, out var degrees))
			{
				return false;
			}
			entry.Value = degrees;
			modConfig.Save();
			Debug.Log((object)$"RandomYggdrasil: Imported rotation of {degrees} degrees for world '{worldIdentifier}' from alternate config");
			return true;
		}

		private static string GetWorldIdentifier()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.GetWorldName() == null)
			{
				return null;
			}
			string text = $"{instance.GetWorldName()}_{instance.GetWorldUID()}";
			string text2 = "=\n\t\\\"'[]";
			for (int i = 0; i < text2.Length; i++)
			{
				text = text.Replace(text2[i].ToString(), "");
			}
			return text;
		}

		private static ConfigEntry<int> GetRotationEntry(string worldIdentifier)
		{
			if (!rotationEntries.TryGetValue(worldIdentifier, out var value))
			{
				value = modConfig.Bind<int>("WorldRotations", worldIdentifier, -1, "Y-axis rotation (in degrees, 0-359) of Yggdrasil for this world. Generated once per world on the server; delete this entry to re-roll.");
				rotationEntries[worldIdentifier] = value;
			}
			return value;
		}

		private static int GetOrCreateRotation(string worldIdentifier)
		{
			if (!RotationSync.IsServerAuthority() && RotationSync.TryGetSyncedRotation(worldIdentifier, out var degrees))
			{
				Debug.Log((object)$"RandomYggdrasil: Using server rotation of {degrees} degrees for world '{worldIdentifier}'");
				return degrees;
			}
			ConfigEntry<int> rotationEntry = GetRotationEntry(worldIdentifier);
			if (rotationEntry.Value >= 0 && rotationEntry.Value < 360)
			{
				Debug.Log((object)$"RandomYggdrasil: Using rotation of {rotationEntry.Value} degrees for world '{worldIdentifier}'");
				return rotationEntry.Value;
			}
			if (TryImportRotationFromAlternateConfig(worldIdentifier, rotationEntry))
			{
				RotationSync.Broadcast();
				return rotationEntry.Value;
			}
			if (RotationSync.IsServerAuthority())
			{
				rotationEntry.Value = new Random().Next(360);
				modConfig.Save();
				RotationSync.Broadcast();
				Debug.Log((object)$"RandomYggdrasil: Generated new rotation of {rotationEntry.Value} degrees for world '{worldIdentifier}'");
				return rotationEntry.Value;
			}
			if (!RotationSync.HasReceivedSync)
			{
				return -1;
			}
			Debug.LogWarning((object)("RandomYggdrasil: No rotation received from server for world '" + worldIdentifier + "', leaving Yggdrasil at default orientation"));
			return -1;
		}

		internal static void EnsureCurrentWorldRotation()
		{
			if (RotationSync.IsServerAuthority())
			{
				string worldIdentifier = GetWorldIdentifier();
				if (worldIdentifier != null)
				{
					GetOrCreateRotation(worldIdentifier);
				}
			}
		}

		internal static Dictionary<string, int> GetRotationSnapshot()
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (KeyValuePair<string, ConfigEntry<int>> rotationEntry in rotationEntries)
			{
				int value = rotationEntry.Value.Value;
				if (value >= 0 && value < 360)
				{
					dictionary[rotationEntry.Key] = value;
				}
			}
			return dictionary;
		}

		private static GameObject FindYggdrasilBranch()
		{
			GameObject obj = GameObject.Find("YggdrasilBranch");
			if ((Object)(object)obj == (Object)null)
			{
				LogMissingBranchOnce();
			}
			return obj;
		}

		private static void LogMissingBranchOnce()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			if (!loggedMissingBranch)
			{
				loggedMissingBranch = true;
				Scene activeScene = SceneManager.GetActiveScene();
				string text = (IsDedicatedServer() ? "dedicated server" : "client");
				Debug.LogWarning((object)("RandomYggdrasil: no active GameObject named 'YggdrasilBranch' in scene '" + ((Scene)(ref activeScene)).name + "' (" + text + "). Valheim 1.0.7 keeps it at '_GameMain/_Environment/YggdrasilBranch'. Yggdrasil stays at its default orientation until the name below is corrected in the source."));
				Debug.LogWarning((object)("RandomYggdrasil: scene roots: " + string.Join(", ", GetRootObjectNames(activeScene))));
				List<string> list = FindRenameCandidates(activeScene);
				Debug.LogWarning((object)((list.Count == 0) ? "RandomYggdrasil: no object in this scene has 'ygg' in its name, so the branch was removed rather than renamed." : ("RandomYggdrasil: scene objects with 'ygg' in the name - a rename shows up here: " + string.Join(", ", list))));
			}
		}

		private static string[] GetRootObjectNames(Scene scene)
		{
			GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			string[] array = new string[rootGameObjects.Length];
			for (int i = 0; i < rootGameObjects.Length; i++)
			{
				array[i] = ((Object)rootGameObjects[i]).name;
			}
			return array;
		}

		private static List<string> FindRenameCandidates(Scene scene)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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)
			List<string> list = new List<string>();
			GameObject[] array = Resources.FindObjectsOfTypeAll<GameObject>();
			foreach (GameObject val in array)
			{
				if (list.Count >= 20)
				{
					break;
				}
				Scene scene2 = val.scene;
				if (((Scene)(ref scene2)).IsValid() && !(val.scene != scene) && ((Object)val).name.IndexOf("ygg", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(GetHierarchyPath(val) + (val.activeInHierarchy ? "" : " (inactive)"));
				}
			}
			return list;
		}

		private static string GetHierarchyPath(GameObject gameObject)
		{
			string text = ((Object)gameObject).name;
			Transform parent = gameObject.transform.parent;
			while ((Object)(object)parent != (Object)null)
			{
				text = ((Object)parent).name + "/" + text;
				parent = parent.parent;
			}
			return text;
		}

		internal static void TryApplyStoredRotation()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			string worldIdentifier = GetWorldIdentifier();
			if (worldIdentifier == null)
			{
				return;
			}
			int orCreateRotation = GetOrCreateRotation(worldIdentifier);
			if (orCreateRotation >= 0)
			{
				GameObject val = FindYggdrasilBranch();
				if (!((Object)(object)val == (Object)null))
				{
					val.transform.rotation = Quaternion.Euler(0f, (float)orCreateRotation, 0f);
					Debug.Log((object)$"RandomYggdrasil: Applied rotation of {orCreateRotation} degrees after config sync");
				}
			}
		}
	}
	internal static class RotationSync
	{
		private const int DegreesInCircle = 360;

		private static readonly Dictionary<string, int> ClientSyncedRotations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private static SyncChannel _channel;

		internal static bool HasReceivedSync
		{
			get
			{
				if (_channel != null)
				{
					return _channel.ClientSyncActive;
				}
				return false;
			}
		}

		internal static bool IsServerAuthority()
		{
			return SyncChannel.IsServerAuthority();
		}

		internal static bool TryGetSyncedRotation(string worldIdentifier, out int degrees)
		{
			if (HasReceivedSync && ClientSyncedRotations.TryGetValue(worldIdentifier, out degrees))
			{
				return IsValid(degrees);
			}
			degrees = -1;
			return false;
		}

		internal static void Initialize(ManualLogSource log)
		{
			_channel = SyncChannel.Create("RandomYggdrasil.Rotations", "1.2.0", log);
			_channel.WritePayload = WritePayload;
			_channel.ReadPayload = ReadPayload;
			_channel.Cleared = OnCleared;
			_channel.Start();
		}

		internal static void Broadcast()
		{
			if (_channel != null)
			{
				_channel.Broadcast();
			}
		}

		private static bool IsValid(int degrees)
		{
			if (degrees >= 0)
			{
				return degrees < 360;
			}
			return false;
		}

		private static void WritePayload(ZPackage payload)
		{
			RandomYggdrasilMod.EnsureCurrentWorldRotation();
			Dictionary<string, int> rotationSnapshot = RandomYggdrasilMod.GetRotationSnapshot();
			payload.Write(rotationSnapshot.Count);
			foreach (KeyValuePair<string, int> item in rotationSnapshot)
			{
				payload.Write(item.Key);
				payload.Write(item.Value);
			}
		}

		private static void ReadPayload(ZPackage payload)
		{
			int num = SyncChannel.ReadCount(payload);
			ClientSyncedRotations.Clear();
			for (int i = 0; i < num; i++)
			{
				string text = payload.ReadString();
				int num2 = payload.ReadInt();
				if (IsValid(num2) && !string.IsNullOrEmpty(text))
				{
					ClientSyncedRotations[text] = num2;
				}
			}
			Debug.Log((object)("RandomYggdrasil: received " + ClientSyncedRotations.Count + " world rotation(s) from server"));
			RandomYggdrasilMod.TryApplyStoredRotation();
		}

		private static void OnCleared(string reason)
		{
			ClientSyncedRotations.Clear();
			Debug.Log((object)("RandomYggdrasil: server rotations dropped (" + reason + ")"));
		}
	}
}
namespace MushroomMods
{
	internal static class PatchIsolation
	{
		internal static int PatchAllIsolated(Harmony harmony, Assembly assembly, ManualLogSource log)
		{
			int num = 0;
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					harmony.CreateClassProcessor(type).Patch();
				}
				catch (Exception ex)
				{
					num++;
					log.LogError((object)("Harmony patch class " + type.FullName + " was skipped and the rest of " + harmony.Id + " is still active. " + ex));
				}
			}
			return num;
		}
	}
}