Decompiled source of ValheimWorldInstaller v3.7.2

plugins/ValheimWorldInstaller.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ValheimWorldInstaller")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ValheimWorldInstaller")]
[assembly: AssemblyTitle("ValheimWorldInstaller")]
[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 ValheimWorldInstaller
{
	[HarmonyPatch(typeof(SaveSystem), "GetWorldList")]
	internal static class PrivateWorldList
	{
		internal static bool IsBundledWorld(World w)
		{
			if (w == null || ((object)Unsafe.As<FileSource, FileSource>(ref w.m_fileSource)/*cast due to .constrained prefix*/).ToString() != "Local")
			{
				return false;
			}
			string path = WorldStorage.Destination(WorldInstallerPlugin.WorldName);
			if (!Directory.Exists(path))
			{
				return false;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
			{
				WorldInstallerPlugin.WorldName,
				WorldInstallerPlugin.WorldName.Replace(" ", "")
			};
			string[] files = Directory.GetFiles(path, "*.fwl2", SearchOption.TopDirectoryOnly);
			foreach (string path2 in files)
			{
				hashSet.Add(Path.GetFileNameWithoutExtension(path2));
			}
			string[] files2 = Directory.GetFiles(path, "*.fwl", SearchOption.TopDirectoryOnly);
			foreach (string path3 in files2)
			{
				hashSet.Add(Path.GetFileNameWithoutExtension(path3));
			}
			return hashSet.Contains(w.m_worldName);
		}

		private static void Postfix(ref List<World> __result)
		{
			if (__result == null || string.IsNullOrEmpty(WorldInstallerPlugin.WorldName))
			{
				return;
			}
			string path = WorldStorage.Destination(WorldInstallerPlugin.WorldName);
			if (!Directory.Exists(path))
			{
				WorldInstallerPlugin.ModLogger.LogWarning((object)"3.2: bundled world not installed; hiding world list for safety.");
				__result.Clear();
				return;
			}
			HashSet<string> allowed = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { WorldInstallerPlugin.WorldName };
			string[] files = Directory.GetFiles(path, "*.fwl2", SearchOption.TopDirectoryOnly);
			foreach (string path2 in files)
			{
				allowed.Add(Path.GetFileNameWithoutExtension(path2));
			}
			string[] files2 = Directory.GetFiles(path, "*.fwl", SearchOption.TopDirectoryOnly);
			foreach (string path3 in files2)
			{
				allowed.Add(Path.GetFileNameWithoutExtension(path3));
			}
			allowed.Add(WorldInstallerPlugin.WorldName.Replace(" ", ""));
			int count = __result.Count;
			__result.RemoveAll((World w) => w == null || ((object)Unsafe.As<FileSource, FileSource>(ref w.m_fileSource)/*cast due to .constrained prefix*/).ToString() != "Local" || !allowed.Contains(w.m_worldName));
			WorldInstallerPlugin.ModLogger.LogInfo((object)("3.2 WORLD MENU: " + count + " -> " + __result.Count + " (vanilla worlds_local, no path patch)"));
			if (__result.Count == 0)
			{
				WorldInstallerPlugin.ModLogger.LogWarning((object)"3.2: installed save was not accepted by vanilla World.LoadWorld; check version compatibility. No other saves modified.");
			}
		}
	}
	[BepInPlugin("com.valheim.worldinstaller", "Valheim World Installer", "3.7.0-refresh-test")]
	public sealed class WorldInstallerPlugin : BaseUnityPlugin
	{
		internal static ManualLogSource ModLogger;

		internal static string WorldName;

		private GameObject ui;

		private Toggle resetToggle;

		private Button startButton;

		private float nextCheck;

		private bool storageProbeDone;

		private Harmony harmony;

		internal static bool ResetRequested;

		private static WorldInstallerPlugin instance;

		internal static void ClearResetSelection()
		{
			ResetRequested = false;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.resetToggle != (Object)null)
			{
				instance.resetToggle.SetIsOnWithoutNotify(false);
			}
		}

		private void Awake()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			instance = this;
			ModLogger = ((BaseUnityPlugin)this).Logger;
			WorldName = ((BaseUnityPlugin)this).Config.Bind<string>("World", "WorldFolderName", "The Bizarre", "Bundled world folder name.").Value.Trim();
			if (!SafeName(WorldName))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Invalid world name. No files changed.");
				return;
			}
			try
			{
				WorldStorage.InstallIfMissing(WorldName, ((BaseUnityPlugin)this).Logger);
				harmony = new Harmony("com.valheim.worldinstaller.worldslocal");
				harmony.PatchAll(typeof(WorldInstallerPlugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"3.7 experimental two-click reset: Start is blocked during replacement and world reload.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex);
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			if (Time.unscaledTime < nextCheck)
			{
				return;
			}
			nextCheck = Time.unscaledTime + 0.35f;
			FejdStartup val = Object.FindFirstObjectByType<FejdStartup>();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "m_worldStart");
				Button val2 = (Button)((fieldInfo == null) ? null : /*isinst with value type is only supported in some contexts*/);
				if ((Object)(object)val2 == (Object)null)
				{
					return;
				}
				if (!storageProbeDone)
				{
					storageProbeDone = true;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"3.2 UI initialized");
				}
				if (!((Object)(object)ui != (Object)null) || !((Object)(object)startButton == (Object)(object)val2))
				{
					if ((Object)(object)ui != (Object)null)
					{
						Object.Destroy((Object)(object)ui);
					}
					startButton = val2;
					CreateUI(val2);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("2.6 UI: " + ex));
			}
		}

		private void ProbeStorage(FejdStartup startup)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"STORAGE PROBE BEGIN");
			try
			{
				string text = WorldStorage.Destination(WorldName);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("PRIVATE WORLD DIRECTORY: " + text));
				string[] array = (Directory.Exists(text) ? Directory.GetFiles(text, "*", SearchOption.AllDirectories) : new string[0]);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("PRIVATE FILE COUNT: " + array.Length));
				foreach (IGrouping<string, string> item in from f in array
					group f by Path.GetExtension(f).ToLowerInvariant() into g
					orderby g.Key
					select g)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("PRIVATE EXTENSION: " + ((item.Key.Length == 0) ? "(none)" : item.Key) + " count=" + item.Count()));
				}
				foreach (string item2 in array.Where((string f) => Path.GetFileName(f).StartsWith("main", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".fwl", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".fwl2", StringComparison.OrdinalIgnoreCase)))
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("PRIVATE META CANDIDATE: " + item2.Substring(text.Length).TrimStart('\\', '/')));
				}
				if (Directory.Exists(text))
				{
					foreach (string item3 in Directory.GetFiles(text, "*", SearchOption.AllDirectories).Take(60))
					{
						FileInfo fileInfo = new FileInfo(item3);
						((BaseUnityPlugin)this).Logger.LogInfo((object)("PRIVATE FILE: " + item3.Substring(text.Length).TrimStart('\\', '/') + " (" + fileInfo.Length + " bytes)"));
					}
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Private world directory missing");
				}
				Type[] array2 = new Type[3]
				{
					typeof(SaveSystem),
					typeof(World),
					typeof(FejdStartup)
				};
				foreach (Type type in array2)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("TYPE: " + type.FullName));
					MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (MethodInfo methodInfo in methods)
					{
						string text2 = methodInfo.Name.ToLowerInvariant();
						if (text2.Contains("world") || text2.Contains("save") || text2.Contains("file") || text2.Contains("path") || text2.Contains("directory") || text2.Contains("storage"))
						{
							string text3 = string.Join(", ", from a in methodInfo.GetParameters()
								select a.ParameterType.Name + " " + a.Name);
							((BaseUnityPlugin)this).Logger.LogInfo((object)("METHOD " + type.Name + "." + methodInfo.Name + "(" + text3 + ") -> " + methodInfo.ReturnType.Name));
						}
					}
					FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						string text4 = fieldInfo.Name.ToLowerInvariant();
						if (text4.Contains("world") || text4.Contains("save") || text4.Contains("path") || text4.Contains("directory") || text4.Contains("file") || text4.Contains("storage"))
						{
							((BaseUnityPlugin)this).Logger.LogInfo((object)("FIELD " + type.Name + "." + fieldInfo.Name + " : " + fieldInfo.FieldType.FullName));
						}
					}
				}
				FieldInfo fieldInfo2 = AccessTools.Field(typeof(FejdStartup), "m_worlds");
				ICollection collection = ((fieldInfo2 == null) ? null : (fieldInfo2.GetValue(startup) as ICollection));
				((BaseUnityPlugin)this).Logger.LogInfo((object)("VANILLA UI WORLD COUNT: " + ((collection == null) ? "unknown" : collection.Count.ToString())));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("STORAGE PROBE ERROR: " + ex));
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"STORAGE PROBE END");
		}

		private void CreateUI(Button button)
		{
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Expected O, but got Unknown
			//IL_0376: Unknown result type (might be due to invalid IL or missing references)
			//IL_037b: Unknown result type (might be due to invalid IL or missing references)
			//IL_037f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0384: Unknown result type (might be due to invalid IL or missing references)
			//IL_0389: Unknown result type (might be due to invalid IL or missing references)
			//IL_038e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0397: Unknown result type (might be due to invalid IL or missing references)
			//IL_0399: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03be: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03da: Unknown result type (might be due to invalid IL or missing references)
			//IL_03df: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_040b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0410: Unknown result type (might be due to invalid IL or missing references)
			//IL_0412: Unknown result type (might be due to invalid IL or missing references)
			Transform parent = ((Component)button).transform.parent;
			if ((Object)(object)parent == (Object)null)
			{
				return;
			}
			Toggle val = null;
			Toggle[] componentsInChildren = ((Component)parent).GetComponentsInChildren<Toggle>(true);
			foreach (Toggle val2 in componentsInChildren)
			{
				if (!((Object)(object)val2 == (Object)null) && !((Object)val2).name.StartsWith("ValheimWorldInstaller_"))
				{
					string text = ((Object)val2).name.ToLowerInvariant();
					if (text.Contains("server") && !text.Contains("community"))
					{
						val = val2;
						break;
					}
					if ((Object)(object)val == (Object)null)
					{
						val = val2;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"2.7: no vanilla Toggle under WorldPanel; skipped UI instead of drawing a substitute.");
				return;
			}
			GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent);
			((Object)val3).name = "ValheimWorldInstaller_ResetToggle";
			val3.SetActive(false);
			MonoBehaviour[] componentsInChildren2 = val3.GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val4 in componentsInChildren2)
			{
				if (!((Object)(object)val4 == (Object)null))
				{
					string name = ((object)val4).GetType().Name;
					if (name.IndexOf("Localiz", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						((Behaviour)val4).enabled = false;
					}
				}
			}
			resetToggle = val3.GetComponent<Toggle>();
			if ((Object)(object)resetToggle == (Object)null)
			{
				Object.Destroy((Object)(object)val3);
				((BaseUnityPlugin)this).Logger.LogWarning((object)"2.7: source checkbox has no Toggle component.");
				return;
			}
			resetToggle.onValueChanged = new ToggleEvent();
			resetToggle.SetIsOnWithoutNotify(false);
			ResetRequested = false;
			((UnityEvent<bool>)(object)resetToggle.onValueChanged).AddListener((UnityAction<bool>)delegate(bool value)
			{
				ResetRequested = value;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("3.5 reset request on Start: " + (value ? "requested" : "disabled")));
			});
			int num = 0;
			Component[] componentsInChildren3 = val3.GetComponentsInChildren<Component>(true);
			foreach (Component val5 in componentsInChildren3)
			{
				if ((Object)(object)val5 == (Object)null)
				{
					continue;
				}
				Type type = ((object)val5).GetType();
				if (type.Name.IndexOf("TextMeshPro", StringComparison.OrdinalIgnoreCase) < 0 && !(val5 is Text))
				{
					continue;
				}
				PropertyInfo property = type.GetProperty("text");
				if (!(property == null) && property.CanWrite && !(property.PropertyType != typeof(string)))
				{
					string text2 = property.GetValue(val5, null) as string;
					if (string.IsNullOrEmpty(text2) || text2.Contains("server") || text2.Contains("Server") || text2.Contains("сервер") || text2.StartsWith("$"))
					{
						property.SetValue(val5, "Загрузить новое сохранение", null);
						num++;
					}
				}
			}
			if (num == 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"2.7: native label not found; inspect child text components.");
			}
			RectTransform component = val3.GetComponent<RectTransform>();
			RectTransform component2 = ((Component)val).GetComponent<RectTransform>();
			RectTransform component3 = ((Component)button).GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null && (Object)(object)component2 != (Object)null && (Object)(object)component3 != (Object)null)
			{
				Rect rect = component3.rect;
				Vector3 val6 = ((Transform)component3).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center));
				Vector3 val7 = ((Transform)component).parent.InverseTransformPoint(val6);
				Transform parent2 = ((Transform)component).parent;
				rect = component2.rect;
				Vector3 val8 = parent2.InverseTransformPoint(((Transform)component2).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center)));
				float y = val7.y;
				rect = component3.rect;
				Vector3 val9 = default(Vector3);
				((Vector3)(ref val9))..ctor(0f, y + ((Rect)(ref rect)).height * 0.5f + 13f - val8.y, 0f);
				((Transform)component).localPosition = ((Transform)component2).localPosition + val9;
			}
			val3.SetActive(true);
			Component[] componentsInChildren4 = val3.GetComponentsInChildren<Component>(true);
			foreach (Component val10 in componentsInChildren4)
			{
				if ((Object)(object)val10 == (Object)null)
				{
					continue;
				}
				Type type2 = ((object)val10).GetType();
				if (type2.Name.IndexOf("TextMeshPro", StringComparison.OrdinalIgnoreCase) >= 0 || val10 is Text)
				{
					PropertyInfo property2 = type2.GetProperty("text");
					if (property2 != null && property2.CanWrite && property2.PropertyType == typeof(string))
					{
						property2.SetValue(val10, "Загрузить новое сохранение", null);
					}
				}
			}
			ui = val3;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("3.4: cloned native world checkbox: " + ((Object)val).name + "; reset enabled on Start."));
		}

		internal static bool SafeName(string value)
		{
			return !string.IsNullOrWhiteSpace(value) && value != "." && value != ".." && !value.EndsWith(".") && !value.EndsWith(" ") && value.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 && value.IndexOfAny(new char[9] { '/', '\\', ':', '*', '?', '"', '<', '>', '|' }) < 0;
		}
	}
	[HarmonyPatch(typeof(FejdStartup), "OnWorldStart")]
	internal static class WorldStartReset
	{
		private static bool Prefix(FejdStartup __instance)
		{
			if (!WorldInstallerPlugin.ResetRequested)
			{
				return true;
			}
			ManualLogSource modLogger = WorldInstallerPlugin.ModLogger;
			WorldInstallerPlugin.ClearResetSelection();
			modLogger.LogWarning((object)"3.7 RESET: cancelling this Start invocation; refreshing selected world before any subsequent Start.");
			try
			{
				object? value = AccessTools.Field(typeof(FejdStartup), "m_world").GetValue(__instance);
				World val = (World)((value is World) ? value : null);
				modLogger.LogInfo((object)("3.7.2 selected: name=" + ((val == null) ? "<null>" : val.m_worldName) + ", source=" + ((val == null) ? "<null>" : ((object)Unsafe.As<FileSource, FileSource>(ref val.m_fileSource)/*cast due to .constrained prefix*/).ToString()) + ", save=" + ((val == null) ? "<null>" : val.SaveNumber().ToString())));
				if (!PrivateWorldList.IsBundledWorld(val))
				{
					modLogger.LogError((object)"3.7: selected world is not the bundled local world. No files touched.");
					return false;
				}
				if (!WorldStorage.ReplaceInMenu(WorldInstallerPlugin.WorldName, modLogger))
				{
					return false;
				}
				MethodInfo methodInfo = typeof(SaveSystem).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "InvalidateCache" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsEnum);
				if (methodInfo == null)
				{
					throw new MissingMethodException("SaveSystem.InvalidateCache(SaveDataType)");
				}
				Type parameterType = methodInfo.GetParameters()[0].ParameterType;
				string text = Enum.GetNames(parameterType).FirstOrDefault((string n) => string.Equals(n, "World", StringComparison.OrdinalIgnoreCase) || string.Equals(n, "Worlds", StringComparison.OrdinalIgnoreCase));
				if (text == null)
				{
					throw new InvalidOperationException("No world entry in " + parameterType.FullName + ": " + string.Join(", ", Enum.GetNames(parameterType)));
				}
				methodInfo.Invoke(null, new object[1] { Enum.Parse(parameterType, text) });
				SaveSystem.ForceRefreshCache();
				SaveSystem.ClearWorldListCache(true);
				List<World> worldList = SaveSystem.GetWorldList();
				World val2 = worldList?.FirstOrDefault((Func<World, bool>)((World w) => w != null && PrivateWorldList.IsBundledWorld(w)));
				if (val2 == null)
				{
					modLogger.LogError((object)"3.7: reloaded world is missing. BLOCKING START. Do not enter until save is restored.");
					return false;
				}
				uint num = val2.SaveNumber();
				modLogger.LogInfo((object)("3.7.2 refreshed: name=" + val2.m_worldName + ", source=" + ((object)Unsafe.As<FileSource, FileSource>(ref val2.m_fileSource)/*cast due to .constrained prefix*/).ToString() + ", save=" + val2.SaveNumber()));
				modLogger.LogInfo((object)("3.7: old save number=" + val.SaveNumber() + ", refreshed save number=" + num));
				if (num == 0)
				{
					modLogger.LogError((object)"3.7: unexpected save number 0. BLOCKING START.");
					return false;
				}
				AccessTools.Field(typeof(FejdStartup), "m_world").SetValue(__instance, val2);
				FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "m_worlds");
				fieldInfo.SetValue(__instance, worldList);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(FejdStartup), "UpdateWorldList", new Type[1] { typeof(bool) }, (Type[])null);
				if (methodInfo2 != null)
				{
					methodInfo2.Invoke(__instance, new object[1] { true });
				}
				AccessTools.Field(typeof(FejdStartup), "m_world").SetValue(__instance, val2);
				modLogger.LogWarning((object)"3.7: replacement verified by vanilla list; select the world and press Start again (checkbox OFF). This click was intentionally blocked.");
			}
			catch (Exception ex)
			{
				modLogger.LogError((object)("3.7: refresh failed, Start BLOCKED: " + ex));
			}
			return false;
		}
	}
	internal static class WorldStorage
	{
		internal static string Source(string name)
		{
			return Path.Combine(Paths.ConfigPath, "ValheimWorldInstaller", "Worlds", name);
		}

		internal static string Destination(string name)
		{
			string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
			return Path.Combine(Path.GetFullPath(Path.Combine(folderPath, "..", "LocalLow")), "IronGate", "Valheim", "worlds_local", name);
		}

		internal static void InstallIfMissing(string name, ManualLogSource log)
		{
			string text = Destination(name);
			if (Directory.Exists(text))
			{
				return;
			}
			string text2 = Source(name);
			if (!Directory.Exists(text2))
			{
				log.LogError((object)("Bundle missing: " + text2));
				return;
			}
			string directoryName = Path.GetDirectoryName(text);
			Directory.CreateDirectory(directoryName);
			string text3 = Path.Combine(directoryName, ".vwi-stage-" + Guid.NewGuid().ToString("N"));
			try
			{
				CopyRecursive(text2, text3);
				ValidateCopy(text2, text3);
				if (Directory.Exists(text))
				{
					throw new IOException("Target already exists; refusing overwrite");
				}
				Directory.Move(text3, text);
				log.LogInfo((object)("Installed bundled world into vanilla worlds_local: " + text));
			}
			catch (Exception ex)
			{
				log.LogError((object)("Initial install failed: " + ex));
			}
			finally
			{
				Cleanup(text3, log);
			}
		}

		internal static bool ReplaceInMenu(string name, ManualLogSource log)
		{
			string text = Source(name);
			string text2 = Destination(name);
			string directoryName = Path.GetDirectoryName(text2);
			string text3 = Path.Combine(directoryName, ".vwi-stage-" + Guid.NewGuid().ToString("N"));
			string text4 = Path.Combine(directoryName, ".vwi-rollback-" + Guid.NewGuid().ToString("N"));
			bool flag = false;
			bool flag2 = false;
			try
			{
				if (!Directory.Exists(text))
				{
					throw new DirectoryNotFoundException("Bundle missing: " + text);
				}
				Directory.CreateDirectory(directoryName);
				CopyRecursive(text, text3);
				ValidateCopy(text, text3);
				if (Directory.Exists(text2))
				{
					Directory.Move(text2, text4);
					flag = true;
				}
				Directory.Move(text3, text2);
				flag2 = true;
				ValidateCopy(text, text2);
				log.LogInfo((object)("3.7 LIVE RESET COMPLETE: " + text2 + " (no permanent backup)"));
				return true;
			}
			catch (Exception ex)
			{
				log.LogError((object)("3.7 LIVE RESET FAILED: " + ex));
				if (flag)
				{
					try
					{
						if (flag2 && Directory.Exists(text2))
						{
							Directory.Delete(text2, recursive: true);
						}
						if (!Directory.Exists(text2))
						{
							Directory.Move(text4, text2);
						}
						flag = false;
						log.LogWarning((object)"3.7: old world restored after failure.");
					}
					catch (Exception ex2)
					{
						log.LogError((object)("CRITICAL: rollback remains at " + text4 + "; manual recovery needed: " + ex2));
					}
				}
				return false;
			}
			finally
			{
				Cleanup(text3, log);
				if (flag2 && Directory.Exists(text2) && Directory.Exists(text4))
				{
					Cleanup(text4, log);
				}
			}
		}

		private static void ValidateCopy(string source, string copy)
		{
			string[] files = Directory.GetFiles(copy, "*", SearchOption.AllDirectories);
			if (files.Length == 0 || files.Length != Directory.GetFiles(source, "*", SearchOption.AllDirectories).Length)
			{
				throw new IOException("Copy file count mismatch");
			}
			if (Directory.GetFiles(copy, "*.fwl2", SearchOption.AllDirectories).Length == 0 && Directory.GetFiles(copy, "*.fwl", SearchOption.AllDirectories).Length == 0)
			{
				throw new IOException("World metadata is missing");
			}
			string[] files2 = Directory.GetFiles(source, "*", SearchOption.AllDirectories);
			foreach (string text in files2)
			{
				string text2 = text.Substring(source.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
				string text3 = Path.Combine(copy, text2);
				if (!File.Exists(text3) || new FileInfo(text).Length != new FileInfo(text3).Length)
				{
					throw new IOException("Missing or truncated file: " + text2);
				}
			}
		}

		private static void CopyRecursive(string source, string target)
		{
			Directory.CreateDirectory(target);
			string[] files = Directory.GetFiles(source);
			foreach (string text in files)
			{
				File.Copy(text, Path.Combine(target, Path.GetFileName(text)));
			}
			string[] directories = Directory.GetDirectories(source);
			foreach (string text2 in directories)
			{
				CopyRecursive(text2, Path.Combine(target, Path.GetFileName(text2)));
			}
		}

		private static void Cleanup(string path, ManualLogSource log)
		{
			if (!Directory.Exists(path))
			{
				return;
			}
			try
			{
				Directory.Delete(path, recursive: true);
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("Temporary folder cleanup failed: " + path + " " + ex.Message));
			}
		}
	}
}