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 ValheimWorldInstaller v3.7.2
plugins/ValheimWorldInstaller.dll
Decompiled 2 weeks agousing 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)); } } } }