Decompiled source of ModsmithCycle v1.1.0

ModsmithCycle.dll

Decompiled a day ago
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.Security.Cryptography;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[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("ModsmithCycle")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+51dd64e63e3ff3f81ddf60d59666c64cadea44df")]
[assembly: AssemblyProduct("ModsmithCycle")]
[assembly: AssemblyTitle("ModsmithCycle")]
[assembly: AssemblyVersion("1.1.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 ModsmithCycle
{
	internal static class BackupIdentityDirectoryService
	{
		private const int LabelLimit = 28;

		private const int HashSuffixLength = 12;

		private const string MarkerFilename = ".ModsmithCycle.identity";

		private const string RotationLockFilename = ".ModsmithCycle.rotation.lock";

		internal static bool TryResolve(BackupSnapshotKind kind, string stableIdentity, string saveFilename, out string identityDirectory, out string failureReason)
		{
			identityDirectory = null;
			failureReason = null;
			if (string.IsNullOrWhiteSpace(stableIdentity) || string.IsNullOrWhiteSpace(saveFilename) || !SaveBackupCoordinator.TryGetBackupRoot(out var backupRoot, out failureReason))
			{
				if (failureReason == null)
				{
					failureReason = "Backup identity is incomplete.";
				}
				return false;
			}
			try
			{
				string text = ComputeIdentityHash(stableIdentity);
				string fullPath = Path.GetFullPath(Path.Combine(backupRoot, (kind == BackupSnapshotKind.Character) ? "Characters" : "Worlds"));
				string text2 = Path.Combine(fullPath, text);
				string text3 = Path.Combine(fullPath, SafeLabel(saveFilename) + "-" + text.Substring(0, 12));
				string text4 = MarkerContents(text, stableIdentity);
				if (!HasNoReparseExistingAncestors(fullPath))
				{
					failureReason = "Backup destination contains a filesystem link.";
					return false;
				}
				Directory.CreateDirectory(ToIoPath(fullPath));
				if (!HasNoReparseAncestors(fullPath))
				{
					failureReason = "Backup destination contains a filesystem link.";
					return false;
				}
				if (File.Exists(ToIoPath(text2)) || File.Exists(ToIoPath(text3)))
				{
					failureReason = "A backup identity path is occupied by a file.";
					return false;
				}
				bool flag = Directory.Exists(ToIoPath(text2));
				bool flag2 = Directory.Exists(ToIoPath(text3));
				if (flag && flag2)
				{
					failureReason = "Both the former identity folder and the readable folder exist. Neither was changed.";
					return false;
				}
				if (flag2)
				{
					if (!HasNoReparseAncestors(text3))
					{
						failureReason = "Readable backup folder is a filesystem link.";
						return false;
					}
					if (!TryValidateOrInitializeNewFolder(text3, text4, out failureReason))
					{
						return false;
					}
					identityDirectory = text3;
					return true;
				}
				if (flag)
				{
					if (!HasNoReparseAncestors(text2))
					{
						failureReason = "Former backup folder is a filesystem link.";
						return false;
					}
					if (ExistsEither(Path.Combine(text2, "Tx")) || ExistsEither(Path.Combine(text2, ".ModsmithCycleTransaction")))
					{
						identityDirectory = text2;
						Squirrel.Info(SquirrelChannel.Backup, "[ExitBackup] Readable backup folder migration deferred: existing transaction must be handled first.");
						return true;
					}
					string path = Path.Combine(text2, ".ModsmithCycle.rotation.lock");
					if (File.Exists(ToIoPath(path)) && (File.GetAttributes(ToIoPath(path)) & FileAttributes.ReparsePoint) != FileAttributes.None)
					{
						failureReason = "Former backup lock is a filesystem link.";
						return false;
					}
					using (new FileStream(ToIoPath(path), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
					{
						if (ExistsEither(Path.Combine(text2, "Tx")) || ExistsEither(Path.Combine(text2, ".ModsmithCycleTransaction")))
						{
							identityDirectory = text2;
							return true;
						}
						if (!TryValidateOrCreateMarker(text2, text4, out failureReason))
						{
							return false;
						}
					}
					if (ExistsEither(text3))
					{
						failureReason = "Readable backup folder appeared during migration. Former backups were left untouched.";
						return false;
					}
					Directory.Move(ToIoPath(text2), ToIoPath(text3));
					identityDirectory = text3;
					Squirrel.Info(SquirrelChannel.Backup, "[ExitBackup] Backup folder migrated to readable name: " + Path.GetFileName(text3) + ".");
					return true;
				}
				Directory.CreateDirectory(ToIoPath(text3));
				if (!HasNoReparseAncestors(text3) || !TryValidateOrInitializeNewFolder(text3, text4, out failureReason))
				{
					if (failureReason == null)
					{
						failureReason = "New backup folder is a filesystem link.";
					}
					return false;
				}
				identityDirectory = text3;
				return true;
			}
			catch (IOException ex)
			{
				failureReason = "Backup folder is busy or unavailable: " + ex.Message;
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Backup folder access was denied.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup folder path is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup folder path is unsupported.";
			}
			return false;
		}

		private static bool TryValidateOrInitializeNewFolder(string directory, string expectedMarker, out string reason)
		{
			reason = null;
			if (File.Exists(ToIoPath(Path.Combine(directory, ".ModsmithCycle.identity"))))
			{
				return TryValidateOrCreateMarker(directory, expectedMarker, out reason);
			}
			using (IEnumerator<string> enumerator = Directory.EnumerateFileSystemEntries(ToIoPath(directory)).GetEnumerator())
			{
				if (enumerator.MoveNext())
				{
					_ = enumerator.Current;
					reason = "Readable backup folder has no identity marker and contains unknown data. Nothing was changed.";
					return false;
				}
			}
			return TryValidateOrCreateMarker(directory, expectedMarker, out reason);
		}

		private static bool TryValidateOrCreateMarker(string directory, string expected, out string reason)
		{
			reason = null;
			string path = Path.Combine(directory, ".ModsmithCycle.identity");
			if (File.Exists(ToIoPath(path)))
			{
				if ((File.GetAttributes(ToIoPath(path)) & FileAttributes.ReparsePoint) != FileAttributes.None || new FileInfo(ToIoPath(path)).Length > 4096 || !string.Equals(File.ReadAllText(ToIoPath(path), Encoding.UTF8), expected, StringComparison.Ordinal))
				{
					reason = "Backup identity marker does not match this save. The folder was left untouched.";
					return false;
				}
				return true;
			}
			string path2 = Path.Combine(Path.GetDirectoryName(directory), ".ModsmithCycle-identity-" + Guid.NewGuid().ToString("N") + ".tmp");
			using (FileStream fileStream = new FileStream(ToIoPath(path2), FileMode.CreateNew, FileAccess.Write, FileShare.None))
			{
				byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(expected);
				fileStream.Write(bytes, 0, bytes.Length);
				fileStream.Flush(flushToDisk: true);
			}
			File.Move(ToIoPath(path2), ToIoPath(path));
			return true;
		}

		private static bool ExistsEither(string path)
		{
			if (!Directory.Exists(ToIoPath(path)))
			{
				return File.Exists(ToIoPath(path));
			}
			return true;
		}

		private static string ComputeIdentityHash(string identity)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(identity))).Replace("-", string.Empty);
		}

		private static string MarkerContents(string hash, string identity)
		{
			return "ModsmithCycleIdentityV1\n" + hash + "\n" + Convert.ToBase64String(Encoding.UTF8.GetBytes(identity)) + "\n";
		}

		private static string SafeLabel(string name)
		{
			StringBuilder stringBuilder = new StringBuilder(28);
			foreach (char c in name)
			{
				if (stringBuilder.Length >= 28)
				{
					break;
				}
				if (!char.IsLetterOrDigit(c))
				{
					switch (c)
					{
					case '-':
					case '.':
					case '_':
						break;
					case ' ':
						stringBuilder.Append('-');
						continue;
					default:
						stringBuilder.Append('_');
						continue;
					}
				}
				stringBuilder.Append(c);
			}
			string text = stringBuilder.ToString().Trim(' ', '-', '_', '.');
			if (text.Length != 0)
			{
				return text;
			}
			return "Save";
		}

		private static string ToIoPath(string path)
		{
			string fullPath = Path.GetFullPath(path);
			if (Path.DirectorySeparatorChar != '\\' || fullPath.Length < 240)
			{
				return fullPath;
			}
			if (!fullPath.StartsWith("\\\\", StringComparison.Ordinal))
			{
				return "\\\\?\\" + fullPath;
			}
			return "\\\\?\\UNC\\" + fullPath.Substring(2);
		}

		private static bool HasNoReparseAncestors(string directory)
		{
			string text = Path.GetFullPath(directory);
			while (!string.IsNullOrEmpty(text))
			{
				if (!Directory.Exists(ToIoPath(text)) || (File.GetAttributes(ToIoPath(text)) & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					return false;
				}
				string directoryName = Path.GetDirectoryName(text);
				if (string.IsNullOrEmpty(directoryName) || string.Equals(directoryName, text, StringComparison.OrdinalIgnoreCase))
				{
					break;
				}
				text = directoryName;
			}
			return true;
		}

		private static bool HasNoReparseExistingAncestors(string directory)
		{
			string text = Path.GetFullPath(directory);
			while (!string.IsNullOrEmpty(text))
			{
				if (ExistsEither(text) && (File.GetAttributes(ToIoPath(text)) & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					return false;
				}
				string directoryName = Path.GetDirectoryName(text);
				if (string.IsNullOrEmpty(directoryName) || string.Equals(directoryName, text, StringComparison.OrdinalIgnoreCase))
				{
					break;
				}
				text = directoryName;
			}
			return true;
		}
	}
	internal static class BackupRotationCleanupService
	{
		private const string StagingFolder = ".ModsmithCycleStaging";

		private const string CompletedPrefix = ".ModsmithCycleCommitted-";

		internal static bool TryCleanupCommittedRotation(BackupRotationLease lease, StagedBackupSnapshot snapshot, Guid operationId, string stableIdentity, string completionArchive, out string failureReason)
		{
			failureReason = null;
			if (lease == null || snapshot == null || operationId == Guid.Empty || string.IsNullOrWhiteSpace(stableIdentity) || string.IsNullOrWhiteSpace(completionArchive))
			{
				failureReason = "Completed cleanup information is incomplete.";
				return false;
			}
			if (!SaveBackupCoordinator.TryGetBackupRoot(out var backupRoot, out failureReason))
			{
				return false;
			}
			string step = "resolving completed backup paths";
			string path = lease.IdentityDirectory;
			try
			{
				string fullPath = Path.GetFullPath(lease.IdentityDirectory);
				string fullPath2 = Path.GetFullPath(completionArchive);
				string fullPath3 = Path.GetFullPath(Path.Combine(fullPath, ".ModsmithCycleCommitted-" + operationId.ToString("N")));
				step = "verifying committed archive";
				path = fullPath2;
				if (!SamePath(fullPath2, fullPath3) || !Directory.Exists(ToIoPath(fullPath2)) || !HasNoReparseAncestors(fullPath) || !IsVerifiedCompletedArchive(fullPath2, operationId, stableIdentity))
				{
					failureReason = "The newly committed archive could not be verified.";
					return false;
				}
				string fullPath4 = Path.GetFullPath(Path.Combine(backupRoot, ".ModsmithCycleStaging"));
				string fullPath5 = Path.GetFullPath(snapshot.StagingDirectory);
				string text = Path.Combine(fullPath5, "Files");
				string fileName = Path.GetFileName(fullPath5);
				step = "validating completed staging directory";
				path = fullPath5;
				if (!SamePath(Path.GetDirectoryName(fullPath5), fullPath4) || !SamePath(snapshot.ContentDirectory, text) || !HasNoReparseAncestors(fullPath4) || fileName.Length != 65 || !fileName.StartsWith(operationId.ToString("N") + "-", StringComparison.OrdinalIgnoreCase) || !Guid.TryParseExact(fileName.Substring(33), "N", out var _) || !Directory.Exists(ToIoPath(fullPath5)) || !Directory.Exists(ToIoPath(text)))
				{
					failureReason = "Completed staging path did not match its operation.";
					return false;
				}
				if (!HasNoReparsePoints(fullPath5))
				{
					failureReason = "Completed staging contains an unsafe filesystem link.";
					return false;
				}
				List<string> list = new List<string>();
				step = "enumerating older committed archives";
				path = fullPath;
				string[] directories = Directory.GetDirectories(ToIoPath(fullPath), ".ModsmithCycleCommitted-*", SearchOption.TopDirectoryOnly);
				for (int i = 0; i < directories.Length; i++)
				{
					string text2 = FromIoPath(directories[i]);
					if (SamePath(text2, fullPath2))
					{
						continue;
					}
					string fileName2 = Path.GetFileName(text2);
					if (fileName2.StartsWith(".ModsmithCycleCommitted-", StringComparison.Ordinal) && Guid.TryParseExact(fileName2.Substring(".ModsmithCycleCommitted-".Length), "N", out var result2))
					{
						step = "verifying older committed archive";
						path = text2;
						if (IsVerifiedCompletedArchive(text2, result2, stableIdentity))
						{
							list.Add(text2);
						}
					}
				}
				step = "deleting completed staging directory";
				path = fullPath5;
				Directory.Delete(ToIoPath(fullPath5, recursive: true), recursive: true);
				foreach (string item in list)
				{
					step = "deleting older committed archive";
					path = item;
					Directory.Delete(ToIoPath(item, recursive: true), recursive: true);
				}
				return true;
			}
			catch (IOException exception)
			{
				failureReason = DescribeCleanupException(step, path, exception);
			}
			catch (UnauthorizedAccessException exception2)
			{
				failureReason = DescribeCleanupException(step, path, exception2);
			}
			catch (ArgumentException exception3)
			{
				failureReason = DescribeCleanupException(step, path, exception3);
			}
			catch (NotSupportedException exception4)
			{
				failureReason = DescribeCleanupException(step, path, exception4);
			}
			return false;
		}

		private static string DescribeCleanupException(string step, string path, Exception exception)
		{
			return "Temporary backup cleanup failed while " + step + "; target=" + path + "; exception=" + exception.GetType().Name + "; HRESULT=0x" + exception.HResult.ToString("X8") + "; detail=" + exception.Message;
		}

		private static bool IsVerifiedCompletedArchive(string archive, Guid operationId, string stableIdentity)
		{
			if (!Directory.Exists(ToIoPath(archive)) || !HasNoReparsePoints(archive))
			{
				return false;
			}
			string text = Path.Combine(archive, "prepared.journal");
			string text2 = Path.Combine(archive, "committed.marker");
			if (!File.Exists(ToIoPath(text)) || !File.Exists(ToIoPath(text2)) || File.Exists(ToIoPath(Path.Combine(archive, "rollback.completed"))) || new FileInfo(ToIoPath(text)).Length > 4096 || new FileInfo(ToIoPath(text2)).Length > 256)
			{
				return false;
			}
			string expected = "OperationId=" + operationId.ToString("N");
			string expected2 = "StableIdentity=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(stableIdentity));
			if (!ContainsExactLine(text, "JournalVersion=1") || !ContainsExactLine(text, expected) || !ContainsExactLine(text, expected2) || !ContainsExactLine(text2, expected))
			{
				return false;
			}
			string[] fileSystemEntries = Directory.GetFileSystemEntries(ToIoPath(archive));
			for (int i = 0; i < fileSystemEntries.Length; i++)
			{
				string path = FromIoPath(fileSystemEntries[i]);
				string fileName = Path.GetFileName(path);
				if (File.Exists(ToIoPath(path)))
				{
					if (fileName != "prepared.journal" && fileName != "committed.marker")
					{
						return false;
					}
					continue;
				}
				bool flag = false;
				for (int j = 1; j <= 3; j++)
				{
					if (fileName == "Old-" + j || fileName == "New-" + j)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					return false;
				}
			}
			return true;
		}

		private static bool ContainsExactLine(string file, string expected)
		{
			string[] array = File.ReadAllLines(ToIoPath(file));
			for (int i = 0; i < array.Length; i++)
			{
				if (string.Equals(array[i], expected, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static bool SamePath(string first, string second)
		{
			if (string.IsNullOrWhiteSpace(first) || string.IsNullOrWhiteSpace(second))
			{
				return false;
			}
			return string.Equals(Path.GetFullPath(first).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(second).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase);
		}

		private static string ToIoPath(string path, bool recursive = false)
		{
			string fullPath = Path.GetFullPath(FromIoPath(path));
			if (Path.DirectorySeparatorChar != '\\' || (!recursive && fullPath.Length < 240))
			{
				return fullPath;
			}
			if (fullPath.StartsWith("\\\\", StringComparison.Ordinal))
			{
				return "\\\\?\\UNC\\" + fullPath.Substring(2);
			}
			return "\\\\?\\" + fullPath;
		}

		private static string FromIoPath(string path)
		{
			if (Path.DirectorySeparatorChar != '\\')
			{
				return path;
			}
			if (path.StartsWith("\\\\?\\UNC\\", StringComparison.OrdinalIgnoreCase))
			{
				return "\\\\" + path.Substring(8);
			}
			if (path.StartsWith("\\\\?\\", StringComparison.Ordinal))
			{
				return path.Substring(4);
			}
			return path;
		}

		private static bool HasNoReparsePoints(string root)
		{
			Stack<string> stack = new Stack<string>();
			stack.Push(root);
			while (stack.Count > 0)
			{
				string path = stack.Pop();
				FileAttributes attributes = File.GetAttributes(ToIoPath(path));
				if ((attributes & FileAttributes.ReparsePoint) != FileAttributes.None || (attributes & FileAttributes.Directory) == 0)
				{
					return false;
				}
				string[] fileSystemEntries = Directory.GetFileSystemEntries(ToIoPath(path));
				for (int i = 0; i < fileSystemEntries.Length; i++)
				{
					string text = FromIoPath(fileSystemEntries[i]);
					FileAttributes attributes2 = File.GetAttributes(ToIoPath(text));
					if ((attributes2 & FileAttributes.ReparsePoint) != FileAttributes.None)
					{
						return false;
					}
					if ((attributes2 & FileAttributes.Directory) != FileAttributes.None)
					{
						stack.Push(text);
					}
				}
			}
			return true;
		}

		private static bool HasNoReparseAncestors(string directory)
		{
			string text = Path.GetFullPath(directory);
			while (!string.IsNullOrEmpty(text))
			{
				if (!Directory.Exists(ToIoPath(text)) || (File.GetAttributes(ToIoPath(text)) & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					return false;
				}
				string directoryName = Path.GetDirectoryName(text);
				if (string.IsNullOrEmpty(directoryName) || SamePath(text, directoryName))
				{
					break;
				}
				text = directoryName;
			}
			return true;
		}
	}
	internal sealed class BackupRotationLease : IDisposable
	{
		private readonly FileStream _lockStream;

		internal string IdentityDirectory { get; }

		internal BackupRotationLease(string identityDirectory, FileStream lockStream)
		{
			IdentityDirectory = identityDirectory;
			_lockStream = lockStream;
		}

		public void Dispose()
		{
			_lockStream?.Dispose();
		}
	}
	internal sealed class BackupRotationJournalState
	{
		internal Guid OperationId { get; }

		internal string StableIdentity { get; }

		internal bool HadBackup1 { get; }

		internal bool HadBackup2 { get; }

		internal bool HadBackup3 { get; }

		internal bool IsCommitted { get; }

		internal BackupRotationJournalState(Guid operationId, string stableIdentity, bool hadBackup1, bool hadBackup2, bool hadBackup3, bool isCommitted)
		{
			OperationId = operationId;
			StableIdentity = stableIdentity;
			HadBackup1 = hadBackup1;
			HadBackup2 = hadBackup2;
			HadBackup3 = hadBackup3;
			IsCommitted = isCommitted;
		}
	}
	internal static class BackupRotationJournal
	{
		internal const string TransactionFolderName = "Tx";

		private const string LegacyTransactionFolderName = ".ModsmithCycleTransaction";

		internal const string OldBackupPrefix = "Old-";

		internal const string NewBackupPrefix = "New-";

		private const string LockFilename = ".ModsmithCycle.rotation.lock";

		private const string JournalFilename = "prepared.journal";

		private const string CommitFilename = "committed.marker";

		internal static bool TryAcquireIdentityLock(string identityDirectory, out BackupRotationLease lease, out string failureReason)
		{
			lease = null;
			failureReason = null;
			if (string.IsNullOrWhiteSpace(identityDirectory))
			{
				failureReason = "Backup identity directory is missing.";
				return false;
			}
			if (!SaveBackupCoordinator.TryGetBackupRoot(out var backupRoot, out failureReason))
			{
				return false;
			}
			try
			{
				if (!Path.IsPathFullyQualified(identityDirectory))
				{
					failureReason = "Backup identity directory must be absolute.";
					return false;
				}
				string fullPath = Path.GetFullPath(backupRoot);
				string fullPath2 = Path.GetFullPath(identityDirectory);
				string[] array = Path.GetRelativePath(fullPath, fullPath2).Split(new char[2]
				{
					Path.DirectorySeparatorChar,
					Path.AltDirectorySeparatorChar
				}, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length != 2 || (array[0] != "Characters" && array[0] != "Worlds") || array[1] == "." || array[1] == "..")
				{
					failureReason = "Backup identity directory is outside the expected backup structure.";
					return false;
				}
				CheckDirectoryAncestry(fullPath2);
				Directory.CreateDirectory(fullPath2);
				CheckDirectoryAncestry(fullPath2);
				string path = Path.Combine(fullPath2, ".ModsmithCycle.rotation.lock");
				if (File.Exists(path) && (File.GetAttributes(path) & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					failureReason = "Backup lock path is unsafe.";
					return false;
				}
				FileStream fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
				string path2 = Path.Combine(fullPath2, ".ModsmithCycleTransaction");
				if (Directory.Exists(path2) || File.Exists(path2))
				{
					fileStream.Dispose();
					failureReason = "An interrupted transaction from an older ModsmithCycle build still exists. The legacy transaction must be recovered before a new rotation; its data was left untouched.";
					return false;
				}
				lease = new BackupRotationLease(fullPath2, fileStream);
				return true;
			}
			catch (IOException)
			{
				failureReason = "Backup directory is busy or unavailable.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Backup directory access was denied.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup identity path is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup identity path is unsupported.";
			}
			return false;
		}

		internal static bool TryPrepareTransaction(BackupRotationLease lease, Guid operationId, string stableIdentity, out BackupRotationJournalState state, out string failureReason)
		{
			state = null;
			failureReason = null;
			if (lease == null || operationId == Guid.Empty || string.IsNullOrWhiteSpace(stableIdentity))
			{
				failureReason = "Backup transaction information is incomplete.";
				return false;
			}
			try
			{
				string identityDirectory = lease.IdentityDirectory;
				string text = Path.Combine(identityDirectory, "Tx");
				if (Directory.Exists(text) || File.Exists(text))
				{
					failureReason = "An earlier backup transaction requires inspection or recovery.";
					return false;
				}
				string path = Path.Combine(identityDirectory, "Backup-1");
				string path2 = Path.Combine(identityDirectory, "Backup-2");
				string path3 = Path.Combine(identityDirectory, "Backup-3");
				if (File.Exists(path) || File.Exists(path2) || File.Exists(path3))
				{
					failureReason = "A backup slot contains an unexpected file.";
					return false;
				}
				bool flag = Directory.Exists(path);
				bool flag2 = Directory.Exists(path2);
				bool flag3 = Directory.Exists(path3);
				if ((flag2 && !flag) || (flag3 && !flag2))
				{
					failureReason = "Existing backup slots are inconsistent.";
					return false;
				}
				Directory.CreateDirectory(text);
				string text2 = Path.Combine(text, "prepared.journal.tmp");
				string destFileName = Path.Combine(text, "prepared.journal");
				using (FileStream fileStream = new FileStream(text2, FileMode.CreateNew, FileAccess.Write, FileShare.None))
				{
					using (StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), 4096, leaveOpen: true))
					{
						streamWriter.WriteLine("JournalVersion=1");
						streamWriter.WriteLine("OperationId=" + operationId.ToString("N"));
						streamWriter.WriteLine("StableIdentity=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(stableIdentity)));
						streamWriter.WriteLine("HadBackup1=" + (flag ? "1" : "0"));
						streamWriter.WriteLine("HadBackup2=" + (flag2 ? "1" : "0"));
						streamWriter.WriteLine("HadBackup3=" + (flag3 ? "1" : "0"));
						streamWriter.Flush();
					}
					fileStream.Flush(flushToDisk: true);
				}
				File.Move(text2, destFileName);
				state = new BackupRotationJournalState(operationId, stableIdentity, flag, flag2, flag3, isCommitted: false);
				return true;
			}
			catch (IOException)
			{
				failureReason = "Could not prepare backup transaction journal.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Access to the transaction directory was denied.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup transaction path is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup transaction storage is unsupported.";
			}
			return false;
		}

		internal static bool TryReadTransaction(BackupRotationLease lease, out BackupRotationJournalState state, out string failureReason)
		{
			state = null;
			failureReason = null;
			if (lease == null)
			{
				failureReason = "Backup identity lock is unavailable.";
				return false;
			}
			try
			{
				string text = Path.Combine(lease.IdentityDirectory, "Tx");
				if (!Directory.Exists(text))
				{
					return true;
				}
				CheckDirectoryAncestry(text);
				string path = Path.Combine(text, "prepared.journal");
				if (!File.Exists(path))
				{
					failureReason = "Backup transaction journal is missing.";
					return false;
				}
				string[] array = File.ReadAllLines(path);
				if (array.Length != 6 || array[0] != "JournalVersion=1")
				{
					failureReason = "Backup transaction journal is malformed.";
					return false;
				}
				if (!Guid.TryParseExact(ReadValue(array[1], "OperationId="), "N", out var result) || result == Guid.Empty)
				{
					failureReason = "Backup transaction operation ID is invalid.";
					return false;
				}
				string s = ReadValue(array[2], "StableIdentity=");
				string text2 = Encoding.UTF8.GetString(Convert.FromBase64String(s));
				if (string.IsNullOrWhiteSpace(text2))
				{
					failureReason = "Backup transaction identity is invalid.";
					return false;
				}
				bool flag = ReadValue(array[3], "HadBackup1=") == "1";
				bool flag2 = ReadValue(array[4], "HadBackup2=") == "1";
				bool flag3 = ReadValue(array[5], "HadBackup3=") == "1";
				if (!IsValidFlag(array[3], "HadBackup1=") || !IsValidFlag(array[4], "HadBackup2=") || !IsValidFlag(array[5], "HadBackup3="))
				{
					failureReason = "Backup transaction slot flags are invalid.";
					return false;
				}
				if ((flag2 && !flag) || (flag3 && !flag2))
				{
					failureReason = "Backup transaction slot history is invalid.";
					return false;
				}
				string path2 = Path.Combine(text, "committed.marker");
				bool flag4 = File.Exists(path2);
				if (flag4)
				{
					string[] array2 = File.ReadAllLines(path2);
					if (array2.Length != 1 || array2[0] != "OperationId=" + result.ToString("N"))
					{
						failureReason = "Backup commit marker is inconsistent.";
						return false;
					}
				}
				state = new BackupRotationJournalState(result, text2, flag, flag2, flag3, flag4);
				return true;
			}
			catch (IOException)
			{
				failureReason = "Could not read backup transaction journal.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Access to backup transaction was denied.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup transaction contains invalid data.";
			}
			catch (FormatException)
			{
				failureReason = "Backup transaction identity encoding is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup transaction storage is unsupported.";
			}
			return false;
		}

		internal static bool TryMarkCommitted(BackupRotationLease lease, Guid expectedOperationId, string expectedStableIdentity, out string failureReason)
		{
			failureReason = null;
			if (lease == null || expectedOperationId == Guid.Empty || string.IsNullOrWhiteSpace(expectedStableIdentity))
			{
				failureReason = "Backup commit information is incomplete.";
				return false;
			}
			if (!TryReadTransaction(lease, out var state, out failureReason))
			{
				return false;
			}
			if (state == null)
			{
				failureReason = "No prepared backup transaction exists.";
				return false;
			}
			if (state.IsCommitted)
			{
				failureReason = "Backup transaction is already committed.";
				return false;
			}
			if (state.OperationId != expectedOperationId || !string.Equals(state.StableIdentity, expectedStableIdentity, StringComparison.Ordinal))
			{
				failureReason = "Backup transaction identity does not match.";
				return false;
			}
			try
			{
				string identityDirectory = lease.IdentityDirectory;
				if (!TryResolveKindFromLease(lease, out var kind))
				{
					failureReason = "Cannot determine the backup save kind.";
					return false;
				}
				string text = Path.Combine(identityDirectory, "Tx");
				CheckDirectoryAncestry(text);
				bool[] array = new bool[3] { state.HadBackup1, state.HadBackup2, state.HadBackup3 };
				bool[] array2 = new bool[3] { true, state.HadBackup1, state.HadBackup2 };
				for (int i = 1; i <= 3; i++)
				{
					string text2 = Path.Combine(text, "Old-" + i);
					string text3 = Path.Combine(text, "New-" + i);
					string text4 = Path.Combine(identityDirectory, "Backup-" + i);
					if (Directory.Exists(text2) != array[i - 1])
					{
						failureReason = "Previous backup preservation is incomplete.";
						return false;
					}
					if (array[i - 1] && !BackupRotationService.TryVerifyBackupFolder(text2, kind, expectedStableIdentity, null, out failureReason))
					{
						failureReason = "Preserved Old-" + i + " failed commit verification: " + failureReason;
						return false;
					}
					bool flag = Directory.Exists(text3);
					if (flag != array2[i - 1])
					{
						failureReason = "Prepared replacement backup is unavailable.";
						return false;
					}
					if (flag && (!File.Exists(Path.Combine(text3, "backup-manifest.txt")) || !Directory.Exists(Path.Combine(text3, "Files"))))
					{
						failureReason = "Prepared replacement backup is structurally incomplete.";
						return false;
					}
					if (flag)
					{
						Guid? expectedOperationId2 = ((i == 1) ? new Guid?(expectedOperationId) : ((Guid?)null));
						if (!BackupRotationService.TryVerifyBackupFolder(text3, kind, expectedStableIdentity, expectedOperationId2, out failureReason))
						{
							failureReason = "Prepared New-" + i + " failed commit verification: " + failureReason;
							return false;
						}
					}
					bool flag2 = Directory.Exists(text4);
					if (flag2 != array2[i - 1])
					{
						failureReason = "Installed backup slots are incomplete.";
						return false;
					}
					if (flag2)
					{
						string path = Path.Combine(text4, "backup-manifest.txt");
						string path2 = Path.Combine(text4, "Files");
						if (!File.Exists(path) || !Directory.Exists(path2))
						{
							failureReason = "Installed backup structure is incomplete.";
							return false;
						}
						CheckDirectoryAncestry(text4);
						CheckDirectoryAncestry(path2);
					}
					if (flag2)
					{
						Guid? expectedOperationId3 = ((i == 1) ? new Guid?(expectedOperationId) : ((Guid?)null));
						if (!BackupRotationService.TryVerifyBackupFolder(text4, kind, expectedStableIdentity, expectedOperationId3, out failureReason))
						{
							failureReason = "Installed Backup-" + i + " failed commit verification: " + failureReason;
							return false;
						}
					}
				}
				string text5 = Path.Combine(text, "committed.marker.tmp");
				string text6 = Path.Combine(text, "committed.marker");
				if (File.Exists(text5) || File.Exists(text6))
				{
					failureReason = "Backup commit marker already exists or requires recovery.";
					return false;
				}
				using (FileStream fileStream = new FileStream(text5, FileMode.CreateNew, FileAccess.Write, FileShare.None))
				{
					using (StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), 4096, leaveOpen: true))
					{
						streamWriter.WriteLine("OperationId=" + expectedOperationId.ToString("N"));
						streamWriter.Flush();
					}
					fileStream.Flush(flushToDisk: true);
				}
				File.Move(text5, text6);
				return true;
			}
			catch (IOException)
			{
				failureReason = "Backup commit marker could not be written.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Backup commit marker access was denied.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup commit path is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup commit storage is unsupported.";
			}
			return false;
		}

		internal static bool TryRollbackPreparedTransaction(BackupRotationLease lease, string expectedStableIdentity, out string recoveryArchive, out string failureReason)
		{
			recoveryArchive = null;
			failureReason = null;
			if (lease == null || string.IsNullOrWhiteSpace(expectedStableIdentity))
			{
				failureReason = "Backup recovery identity is unavailable.";
				return false;
			}
			if (!TryReadTransaction(lease, out var state, out failureReason))
			{
				return false;
			}
			if (state == null)
			{
				return true;
			}
			if (state.IsCommitted)
			{
				failureReason = "Committed transactions require forward recovery, not rollback.";
				return false;
			}
			if (!string.Equals(state.StableIdentity, expectedStableIdentity, StringComparison.Ordinal))
			{
				failureReason = "Recovery identity does not match the recorded transaction.";
				return false;
			}
			try
			{
				string identityDirectory = lease.IdentityDirectory;
				if (!TryResolveKindFromLease(lease, out var kind))
				{
					failureReason = "Rollback cannot determine the backup save kind.";
					return false;
				}
				string text = Path.Combine(identityDirectory, "Tx");
				string text2 = Path.Combine(text, "rollback.completed");
				string text3 = text2 + ".tmp";
				string text4 = Path.Combine(identityDirectory, ".ModsmithCycleRecovered-" + state.OperationId.ToString("N"));
				if (Directory.Exists(text4) || File.Exists(text4))
				{
					failureReason = "Recovery archive destination already exists.";
					return false;
				}
				bool[] array = new bool[3] { state.HadBackup1, state.HadBackup2, state.HadBackup3 };
				string[] array2 = new string[3];
				string[] array3 = new string[3];
				string[] array4 = new string[3];
				bool flag = File.Exists(text2);
				if (flag)
				{
					string[] array5 = File.ReadAllLines(text2);
					if (array5.Length != 1 || array5[0] != "OperationId=" + state.OperationId.ToString("N"))
					{
						failureReason = "Rollback completion marker is invalid.";
						return false;
					}
				}
				for (int i = 1; i <= 3; i++)
				{
					array2[i - 1] = Path.Combine(text, "Old-" + i);
					array3[i - 1] = Path.Combine(identityDirectory, "Backup-" + i);
					array4[i - 1] = Path.Combine(text, "Aborted-" + i);
					string text5 = array2[i - 1];
					string text6 = array3[i - 1];
					string path = array4[i - 1];
					CheckDirectoryAncestry(text5);
					CheckDirectoryAncestry(text6);
					CheckDirectoryAncestry(path);
					if (File.Exists(text5) || File.Exists(text6) || File.Exists(path))
					{
						failureReason = "Recovery encountered an unexpected file.";
						return false;
					}
					bool flag2 = Directory.Exists(text5);
					bool flag3 = Directory.Exists(text6);
					bool flag4 = Directory.Exists(path);
					bool flag5 = array[i - 1];
					if (flag2 && !BackupRotationService.TryVerifyBackupFolder(text5, kind, expectedStableIdentity, null, out failureReason))
					{
						failureReason = "Rollback Old-" + i + " failed verification: " + failureReason;
						return false;
					}
					if (flag5 && !flag2 && flag3 && !BackupRotationService.TryVerifyBackupFolder(text6, kind, expectedStableIdentity, null, out failureReason))
					{
						failureReason = "Rollback visible Backup-" + i + " failed verification: " + failureReason;
						return false;
					}
					if (flag)
					{
						if (flag2 || flag3 != flag5)
						{
							failureReason = "Previously completed rollback has an inconsistent folder state.";
							return false;
						}
						continue;
					}
					if (flag5 && !flag2 && !flag3)
					{
						failureReason = "An original backup cannot be located.";
						return false;
					}
					if (!flag5 && flag2)
					{
						failureReason = "Recovery found an unexpected original backup.";
						return false;
					}
					if (flag4 && flag3 && (flag2 || !flag5))
					{
						failureReason = "Recovery archive slot is occupied.";
						return false;
					}
				}
				if (!flag)
				{
					for (int j = 0; j < 3; j++)
					{
						string text7 = array2[j];
						string text8 = array3[j];
						string destDirName = array4[j];
						bool flag6 = array[j];
						bool num = Directory.Exists(text7);
						bool flag7 = Directory.Exists(text8);
						if (num)
						{
							if (flag7)
							{
								Directory.Move(text8, destDirName);
							}
							Directory.Move(text7, text8);
						}
						else if (!flag6 && flag7)
						{
							Directory.Move(text8, destDirName);
						}
					}
					for (int k = 0; k < 3; k++)
					{
						if (Directory.Exists(array2[k]) || Directory.Exists(array3[k]) != array[k])
						{
							failureReason = "Previous backup restoration could not be confirmed.";
							return false;
						}
						if (array[k] && !BackupRotationService.TryVerifyBackupFolder(array3[k], kind, expectedStableIdentity, null, out failureReason))
						{
							failureReason = "Restored Backup-" + (k + 1) + " failed verification: " + failureReason;
							return false;
						}
					}
					if (File.Exists(text3))
					{
						failureReason = "Incomplete rollback marker requires inspection.";
						return false;
					}
					using (FileStream fileStream = new FileStream(text3, FileMode.CreateNew, FileAccess.Write, FileShare.None))
					{
						using (StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), 4096, leaveOpen: true))
						{
							streamWriter.WriteLine("OperationId=" + state.OperationId.ToString("N"));
							streamWriter.Flush();
						}
						fileStream.Flush(flushToDisk: true);
					}
					File.Move(text3, text2);
				}
				Directory.Move(text, text4);
				recoveryArchive = text4;
				return true;
			}
			catch (IOException)
			{
				failureReason = "Backup rollback encountered a file error.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Backup rollback was denied filesystem access.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup rollback encountered an invalid path.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup rollback storage is unsupported.";
			}
			return false;
		}

		internal static bool TryFinalizeCommittedTransaction(BackupRotationLease lease, string expectedStableIdentity, out string completionArchive, out string failureReason)
		{
			completionArchive = null;
			failureReason = null;
			if (lease == null || string.IsNullOrWhiteSpace(expectedStableIdentity))
			{
				failureReason = "Committed recovery identity is unavailable.";
				return false;
			}
			if (!TryReadTransaction(lease, out var state, out failureReason))
			{
				return false;
			}
			if (state == null)
			{
				return true;
			}
			if (!state.IsCommitted)
			{
				failureReason = "Transaction is not committed. Rollback recovery is required.";
				return false;
			}
			if (!string.Equals(state.StableIdentity, expectedStableIdentity, StringComparison.Ordinal))
			{
				failureReason = "Committed recovery identity does not match.";
				return false;
			}
			try
			{
				string identityDirectory = lease.IdentityDirectory;
				if (!TryResolveKindFromLease(lease, out var kind))
				{
					failureReason = "Committed recovery cannot determine the backup save kind.";
					return false;
				}
				string text = Path.Combine(identityDirectory, "Tx");
				string text2 = Path.Combine(identityDirectory, ".ModsmithCycleCommitted-" + state.OperationId.ToString("N"));
				if (Directory.Exists(text2) || File.Exists(text2))
				{
					failureReason = "Committed recovery archive already exists.";
					return false;
				}
				if (File.Exists(Path.Combine(text, "rollback.completed")))
				{
					failureReason = "Transaction contains conflicting recovery markers.";
					return false;
				}
				bool[] array = new bool[3] { state.HadBackup1, state.HadBackup2, state.HadBackup3 };
				bool[] array2 = new bool[3] { true, state.HadBackup1, state.HadBackup2 };
				string[] array3 = new string[3];
				string[] array4 = new string[3];
				for (int i = 1; i <= 3; i++)
				{
					string text3 = Path.Combine(text, "Old-" + i);
					string text4 = Path.Combine(text, "New-" + i);
					string text5 = (array3[i - 1] = Path.Combine(identityDirectory, "Backup-" + i));
					array4[i - 1] = text4;
					CheckDirectoryAncestry(text3);
					CheckDirectoryAncestry(text4);
					CheckDirectoryAncestry(text5);
					if (File.Exists(text3) || File.Exists(text4) || File.Exists(text5))
					{
						failureReason = "Committed recovery encountered an unexpected file.";
						return false;
					}
					if (Directory.Exists(text3) != array[i - 1])
					{
						failureReason = "Previous backup preservation is inconsistent.";
						return false;
					}
					if (array[i - 1] && !BackupRotationService.TryVerifyBackupFolder(text3, kind, expectedStableIdentity, null, out failureReason))
					{
						failureReason = "Committed Old-" + i + " failed verification: " + failureReason;
						return false;
					}
					bool flag = Directory.Exists(text5);
					bool flag2 = Directory.Exists(text4);
					if (!array2[i - 1])
					{
						if (flag || flag2)
						{
							failureReason = "Unexpected committed backup slot.";
							return false;
						}
						continue;
					}
					if (!flag && !flag2)
					{
						failureReason = "Committed backup and its recovery copy are both missing.";
						return false;
					}
					if (flag)
					{
						if (!File.Exists(Path.Combine(text5, "backup-manifest.txt")) || !Directory.Exists(Path.Combine(text5, "Files")))
						{
							failureReason = "Installed committed backup is structurally incomplete.";
							return false;
						}
						Guid? expectedOperationId = ((i == 1) ? new Guid?(state.OperationId) : ((Guid?)null));
						if (flag && !BackupRotationService.TryVerifyBackupFolder(text5, kind, expectedStableIdentity, expectedOperationId, out failureReason))
						{
							failureReason = "Committed visible Backup-" + i + " failed verification: " + failureReason;
							return false;
						}
						if (flag2 && !BackupRotationService.TryVerifyBackupFolder(text4, kind, expectedStableIdentity, expectedOperationId, out failureReason))
						{
							failureReason = "Committed prepared New-" + i + " failed verification: " + failureReason;
							return false;
						}
					}
					if (flag2)
					{
						if (!File.Exists(Path.Combine(text4, "backup-manifest.txt")) || !Directory.Exists(Path.Combine(text4, "Files")))
						{
							failureReason = "Retained replacement backup is structurally incomplete.";
							return false;
						}
						Guid? expectedOperationId2 = ((i == 1) ? new Guid?(state.OperationId) : ((Guid?)null));
						if (flag && !BackupRotationService.TryVerifyBackupFolder(text5, kind, expectedStableIdentity, expectedOperationId2, out failureReason))
						{
							failureReason = "Committed visible Backup-" + i + " failed verification: " + failureReason;
							return false;
						}
						if (flag2 && !BackupRotationService.TryVerifyBackupFolder(text4, kind, expectedStableIdentity, expectedOperationId2, out failureReason))
						{
							failureReason = "Committed prepared New-" + i + " failed verification: " + failureReason;
							return false;
						}
					}
				}
				for (int j = 0; j < 3; j++)
				{
					if (array2[j] && !Directory.Exists(array3[j]))
					{
						Directory.Move(array4[j], array3[j]);
					}
				}
				for (int k = 0; k < 3; k++)
				{
					if (Directory.Exists(array3[k]) != array2[k])
					{
						failureReason = "Committed backup layout could not be restored.";
						return false;
					}
				}
				for (int l = 0; l < 3; l++)
				{
					if (array2[l])
					{
						Guid? expectedOperationId3 = ((l == 0) ? new Guid?(state.OperationId) : ((Guid?)null));
						if (!BackupRotationService.TryVerifyBackupFolder(array3[l], kind, expectedStableIdentity, expectedOperationId3, out failureReason))
						{
							failureReason = "Recovered Backup-" + (l + 1) + " failed final verification: " + failureReason;
							return false;
						}
					}
				}
				Directory.Move(text, text2);
				completionArchive = text2;
				return true;
			}
			catch (IOException)
			{
				failureReason = "Committed recovery encountered a file error.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Committed recovery was denied access.";
			}
			catch (ArgumentException)
			{
				failureReason = "Committed recovery encountered an invalid path.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Committed recovery storage is unsupported.";
			}
			return false;
		}

		private static bool TryResolveKindFromLease(BackupRotationLease lease, out BackupSnapshotKind kind)
		{
			kind = BackupSnapshotKind.Character;
			if (lease == null || string.IsNullOrWhiteSpace(lease.IdentityDirectory))
			{
				return false;
			}
			string fileName = Path.GetFileName(Path.GetDirectoryName(lease.IdentityDirectory));
			if (string.Equals(fileName, "Characters", StringComparison.OrdinalIgnoreCase))
			{
				kind = BackupSnapshotKind.Character;
				return true;
			}
			if (string.Equals(fileName, "Worlds", StringComparison.OrdinalIgnoreCase))
			{
				kind = BackupSnapshotKind.World;
				return true;
			}
			return false;
		}

		private static string ReadValue(string line, string prefix)
		{
			if (line == null || !line.StartsWith(prefix, StringComparison.Ordinal))
			{
				throw new FormatException("Invalid backup journal entry.");
			}
			return line.Substring(prefix.Length);
		}

		private static bool IsValidFlag(string line, string prefix)
		{
			string text = ReadValue(line, prefix);
			if (!(text == "0"))
			{
				return text == "1";
			}
			return true;
		}

		private static void CheckDirectoryAncestry(string path)
		{
			for (DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetFullPath(path)); directoryInfo != null; directoryInfo = directoryInfo.Parent)
			{
				if (directoryInfo.Exists && (directoryInfo.Attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					throw new IOException("Backup path contains a reparse point.");
				}
			}
		}
	}
	internal enum BackupSnapshotKind
	{
		Character,
		World
	}
	internal sealed class StagedBackupSnapshot
	{
		internal string StagingDirectory { get; }

		internal string ContentDirectory { get; }

		internal int FileCount { get; }

		internal long TotalBytes { get; }

		internal StagedBackupSnapshot(string stagingDirectory, string contentDirectory, int fileCount, long totalBytes)
		{
			StagingDirectory = stagingDirectory;
			ContentDirectory = contentDirectory;
			FileCount = fileCount;
			TotalBytes = totalBytes;
		}
	}
	internal static class BackupRotationService
	{
		private sealed class BackupFileRecord
		{
			internal string RelativePath;

			internal long Length;

			internal byte[] Sha256;
		}

		private const int CopyBufferSize = 131072;

		internal static bool TryStageLocalSnapshot(string sourcePath, BackupSnapshotKind kind, string stableIdentity, Guid operationId, out StagedBackupSnapshot snapshot, out string failureReason)
		{
			snapshot = null;
			failureReason = null;
			if (operationId == Guid.Empty)
			{
				failureReason = "Backup operation identifier is missing.";
				return false;
			}
			if (string.IsNullOrWhiteSpace(stableIdentity))
			{
				failureReason = "Backup save identity is missing.";
				return false;
			}
			if (string.IsNullOrWhiteSpace(sourcePath))
			{
				failureReason = "Backup source path is missing.";
				return false;
			}
			if (!SaveBackupCoordinator.TryGetBackupRoot(out var backupRoot, out failureReason))
			{
				return false;
			}
			try
			{
				if (!Path.IsPathFullyQualified(sourcePath))
				{
					failureReason = "Backup source must be an absolute path.";
					return false;
				}
				string fullPath = Path.GetFullPath(sourcePath);
				string fullPath2 = Path.GetFullPath(backupRoot);
				bool flag = kind == BackupSnapshotKind.World;
				if (PathsOverlap(flag ? fullPath : Path.GetDirectoryName(fullPath), fullPath2))
				{
					failureReason = "Backup destination overlaps native saves.";
					return false;
				}
				CheckPathForReparsePoints(fullPath);
				CheckPathForReparsePoints(fullPath2);
				if (flag)
				{
					if (!Directory.Exists(fullPath))
					{
						failureReason = "World source directory is missing.";
						return false;
					}
				}
				else if (!File.Exists(fullPath))
				{
					failureReason = "Character source file is missing.";
					return false;
				}
				string text = Path.Combine(fullPath2, ".ModsmithCycleStaging");
				Directory.CreateDirectory(text);
				string text2 = Path.Combine(text, operationId.ToString("N") + "-" + Guid.NewGuid().ToString("N"));
				Directory.CreateDirectory(text2);
				string text3 = Path.Combine(text2, "Files");
				Directory.CreateDirectory(text3);
				List<BackupFileRecord> list = new List<BackupFileRecord>();
				if (flag)
				{
					CopyDirectoryContents(fullPath, text3, string.Empty, list);
				}
				else
				{
					string fileName = Path.GetFileName(fullPath);
					CopyVerifiedFile(fullPath, Path.Combine(text3, fileName), fileName, list);
				}
				if (list.Count == 0)
				{
					failureReason = "No backup files were staged.";
					return false;
				}
				WriteManifest(text2, kind, stableIdentity, operationId, list);
				long num = 0L;
				foreach (BackupFileRecord item in list)
				{
					num += item.Length;
				}
				snapshot = new StagedBackupSnapshot(text2, text3, list.Count, num);
				return true;
			}
			catch (IOException)
			{
				failureReason = "Backup staging encountered a file error.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Backup staging encountered an access error.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup source or destination path is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup storage location is unsupported.";
			}
			catch (CryptographicException)
			{
				failureReason = "Backup file verification failed.";
			}
			return false;
		}

		private static void CopyDirectoryContents(string sourceDirectory, string destinationDirectory, string relativePrefix, List<BackupFileRecord> records)
		{
			foreach (string item in Directory.EnumerateFileSystemEntries(sourceDirectory))
			{
				FileAttributes attributes = File.GetAttributes(item);
				if ((attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					throw new IOException("Backup source contains a reparse point.");
				}
				string fileName = Path.GetFileName(item);
				string text = ((relativePrefix.Length == 0) ? fileName : (relativePrefix + "/" + fileName));
				string text2 = Path.Combine(destinationDirectory, fileName);
				if ((attributes & FileAttributes.Directory) != FileAttributes.None)
				{
					Directory.CreateDirectory(text2);
					CopyDirectoryContents(item, text2, text, records);
				}
				else
				{
					CopyVerifiedFile(item, text2, text, records);
				}
			}
			TryPreserveDirectoryWriteTime(sourceDirectory, destinationDirectory);
		}

		private static void CopyVerifiedFile(string sourcePath, string destinationPath, string relativePath, List<BackupFileRecord> records)
		{
			FileInfo fileInfo = new FileInfo(sourcePath);
			long length = fileInfo.Length;
			DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
			long num = 0L;
			byte[] array = new byte[131072];
			using (FileStream fileStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				using FileStream fileStream2 = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
				int num2;
				while ((num2 = fileStream.Read(array, 0, array.Length)) > 0)
				{
					fileStream2.Write(array, 0, num2);
					num += num2;
				}
				fileStream2.Flush(flushToDisk: true);
			}
			FileInfo fileInfo2 = new FileInfo(sourcePath);
			if (num != length || fileInfo2.Length != length || fileInfo2.LastWriteTimeUtc != lastWriteTimeUtc)
			{
				throw new IOException("Backup source changed during copying.");
			}
			byte[] array2 = ComputeFileHash(sourcePath);
			byte[] array3 = ComputeFileHash(destinationPath);
			if (!CryptographicOperations.FixedTimeEquals(array2, array3))
			{
				throw new IOException("Staged backup differs from its source.");
			}
			TryPreserveFileWriteTime(destinationPath, lastWriteTimeUtc);
			records.Add(new BackupFileRecord
			{
				RelativePath = relativePath,
				Length = num,
				Sha256 = array3
			});
		}

		private static void TryPreserveFileWriteTime(string destinationPath, DateTime originalWriteTime)
		{
			try
			{
				File.SetLastWriteTimeUtc(destinationPath, originalWriteTime);
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
			catch (ArgumentException)
			{
			}
			catch (NotSupportedException)
			{
			}
		}

		private static void TryPreserveDirectoryWriteTime(string sourceDirectory, string destinationDirectory)
		{
			try
			{
				Directory.SetLastWriteTimeUtc(destinationDirectory, Directory.GetLastWriteTimeUtc(sourceDirectory));
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
			catch (ArgumentException)
			{
			}
			catch (NotSupportedException)
			{
			}
		}

		private static byte[] ComputeFileHash(string path)
		{
			using FileStream inputStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
			using SHA256 sHA = SHA256.Create();
			return sHA.ComputeHash(inputStream);
		}

		private static void WriteManifest(string stagingDirectory, BackupSnapshotKind kind, string stableIdentity, Guid operationId, List<BackupFileRecord> records)
		{
			using FileStream fileStream = new FileStream(Path.Combine(stagingDirectory, "backup-manifest.txt"), FileMode.CreateNew, FileAccess.Write, FileShare.None);
			using (StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), 4096, leaveOpen: true))
			{
				streamWriter.WriteLine("ManifestVersion=1");
				streamWriter.WriteLine("OperationId=" + operationId.ToString("N"));
				streamWriter.WriteLine("Kind=" + kind);
				streamWriter.WriteLine("StableIdentity=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(stableIdentity)));
				streamWriter.WriteLine("FileCount=" + records.Count.ToString(CultureInfo.InvariantCulture));
				foreach (BackupFileRecord record in records)
				{
					string text = Convert.ToBase64String(Encoding.UTF8.GetBytes(record.RelativePath));
					string text2 = BitConverter.ToString(record.Sha256).Replace("-", string.Empty);
					streamWriter.WriteLine(text + "|" + record.Length.ToString(CultureInfo.InvariantCulture) + "|" + text2);
				}
				streamWriter.Flush();
			}
			fileStream.Flush(flushToDisk: true);
		}

		private static bool PathsOverlap(string firstPath, string secondPath)
		{
			if (!IsWithin(firstPath, secondPath))
			{
				return IsWithin(secondPath, firstPath);
			}
			return true;
		}

		private static bool IsWithin(string candidate, string directory)
		{
			string text = Path.GetFullPath(candidate).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			string text2 = Path.GetFullPath(directory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			if (string.Equals(text, text2, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return text.StartsWith(text2 + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase);
		}

		internal static bool TryPrepareReplacementCopies(StagedBackupSnapshot snapshot, BackupSnapshotKind kind, string stableIdentity, Guid operationId, BackupRotationLease lease, out string failureReason)
		{
			failureReason = null;
			if (snapshot == null || lease == null || operationId == Guid.Empty || string.IsNullOrWhiteSpace(stableIdentity))
			{
				failureReason = "Replacement preparation information is incomplete.";
				return false;
			}
			if (!TryVerifyStagedSnapshot(snapshot, kind, stableIdentity, operationId, out failureReason))
			{
				return false;
			}
			if (!BackupRotationJournal.TryReadTransaction(lease, out var state, out failureReason))
			{
				return false;
			}
			if (state == null || state.IsCommitted || state.OperationId != operationId || !string.Equals(state.StableIdentity, stableIdentity, StringComparison.Ordinal))
			{
				failureReason = "Prepared transaction does not match the requested backup operation.";
				return false;
			}
			try
			{
				string identityDirectory = lease.IdentityDirectory;
				string path = Path.Combine(identityDirectory, "Tx");
				string[] array = new string[3]
				{
					snapshot.StagingDirectory,
					Path.Combine(identityDirectory, "Backup-1"),
					Path.Combine(identityDirectory, "Backup-2")
				};
				bool[] array2 = new bool[3] { true, state.HadBackup1, state.HadBackup2 };
				for (int i = 0; i < 3; i++)
				{
					string path2 = Path.Combine(path, "New-" + (i + 1));
					if (Directory.Exists(path2) || File.Exists(path2))
					{
						failureReason = "A prepared replacement folder already exists.";
						return false;
					}
					if (array2[i])
					{
						string text = array[i];
						if (!Directory.Exists(text))
						{
							failureReason = "Required replacement source directory is missing.";
							return false;
						}
						CheckPathForReparsePoints(text);
						if (!File.Exists(Path.Combine(text, "backup-manifest.txt")) || !Directory.Exists(Path.Combine(text, "Files")))
						{
							failureReason = "Replacement source is missing its backup structure.";
							return false;
						}
						if (i > 0 && !TryVerifyBackupFolder(text, kind, stableIdentity, null, out failureReason))
						{
							failureReason = "Existing Backup-" + i + " failed verification: " + failureReason;
							return false;
						}
					}
				}
				for (int j = 0; j < 3; j++)
				{
					if (array2[j])
					{
						string sourceDirectory = array[j];
						string text2 = Path.Combine(path, "New-" + (j + 1));
						Directory.CreateDirectory(text2);
						List<BackupFileRecord> list = new List<BackupFileRecord>();
						CopyDirectoryContents(sourceDirectory, text2, string.Empty, list);
						if (list.Count == 0)
						{
							failureReason = "Prepared replacement contains no files.";
							return false;
						}
						if (!File.Exists(Path.Combine(text2, "backup-manifest.txt")) || !Directory.Exists(Path.Combine(text2, "Files")))
						{
							failureReason = "Prepared replacement structure is incomplete.";
							return false;
						}
						Guid? expectedOperationId = ((j == 0) ? new Guid?(operationId) : ((Guid?)null));
						if (!TryVerifyBackupFolder(text2, kind, stableIdentity, expectedOperationId, out failureReason))
						{
							failureReason = "Prepared New-" + (j + 1) + " failed verification: " + failureReason;
							return false;
						}
					}
				}
				return true;
			}
			catch (PathTooLongException ex)
			{
				failureReason = "Replacement preparation exceeded the supported Windows path length: " + ex.Message;
			}
			catch (IOException ex2)
			{
				failureReason = "Replacement preparation I/O failure (" + ex2.GetType().Name + ", HResult=0x" + ex2.HResult.ToString("X8") + "): " + ex2.Message;
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Replacement preparation was denied access.";
			}
			catch (ArgumentException)
			{
				failureReason = "Replacement preparation path is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Replacement storage location is unsupported.";
			}
			catch (CryptographicException)
			{
				failureReason = "Replacement file verification failed.";
			}
			return false;
		}

		internal static bool TryVerifyBackupFolder(string folderPath, BackupSnapshotKind expectedKind, string expectedStableIdentity, Guid? expectedOperationId, out string failureReason)
		{
			failureReason = null;
			if (string.IsNullOrWhiteSpace(folderPath) || string.IsNullOrWhiteSpace(expectedStableIdentity))
			{
				failureReason = "Backup folder verification information is incomplete.";
				return false;
			}
			try
			{
				if (!SaveBackupCoordinator.TryGetBackupRoot(out var backupRoot, out failureReason))
				{
					return false;
				}
				string fullPath = Path.GetFullPath(backupRoot);
				string fullPath2 = Path.GetFullPath(folderPath);
				if (!IsWithin(fullPath2, fullPath) || string.Equals(fullPath2, fullPath, StringComparison.OrdinalIgnoreCase))
				{
					failureReason = "Backup folder is outside the configured backup area.";
					return false;
				}
				if (!Directory.Exists(fullPath2))
				{
					failureReason = "Backup folder does not exist.";
					return false;
				}
				CheckPathForReparsePoints(fullPath2);
				string path = Path.Combine(fullPath2, "backup-manifest.txt");
				string text = Path.Combine(fullPath2, "Files");
				if (!File.Exists(path) || !Directory.Exists(text))
				{
					failureReason = "Backup folder is missing its manifest or Files directory.";
					return false;
				}
				CheckPathForReparsePoints(path);
				CheckPathForReparsePoints(text);
				foreach (string item in Directory.EnumerateFileSystemEntries(fullPath2))
				{
					string fileName = Path.GetFileName(item);
					if (!string.Equals(fileName, "backup-manifest.txt", StringComparison.OrdinalIgnoreCase) && !string.Equals(fileName, "Files", StringComparison.OrdinalIgnoreCase))
					{
						failureReason = "Backup folder contains an unexpected root entry.";
						return false;
					}
				}
				string[] array = File.ReadAllLines(path);
				if (array.Length < 6 || array[0] != "ManifestVersion=1")
				{
					failureReason = "Backup manifest is incomplete or unsupported.";
					return false;
				}
				if (!array[1].StartsWith("OperationId=", StringComparison.Ordinal) || !Guid.TryParseExact(array[1].Substring("OperationId=".Length), "N", out var result))
				{
					failureReason = "Backup manifest operation ID is invalid.";
					return false;
				}
				if (expectedOperationId.HasValue && result != expectedOperationId.Value)
				{
					failureReason = "Backup manifest operation ID does not match.";
					return false;
				}
				if (array[2] != "Kind=" + expectedKind)
				{
					failureReason = "Backup manifest save kind does not match.";
					return false;
				}
				string text2 = Convert.ToBase64String(Encoding.UTF8.GetBytes(expectedStableIdentity));
				if (array[3] != "StableIdentity=" + text2)
				{
					failureReason = "Backup manifest identity does not match.";
					return false;
				}
				if (!array[4].StartsWith("FileCount=", StringComparison.Ordinal) || !int.TryParse(array[4].Substring("FileCount=".Length), NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || result2 <= 0 || result2 > 1000000 || array.Length != result2 + 5)
				{
					failureReason = "Backup manifest file count is invalid.";
					return false;
				}
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				for (int i = 5; i < array.Length; i++)
				{
					string[] array2 = array[i].Split('|');
					if (array2.Length != 3)
					{
						failureReason = "Backup manifest contains a malformed file record.";
						return false;
					}
					string text3 = Encoding.UTF8.GetString(Convert.FromBase64String(array2[0]));
					if (!IsSafeRelativeBackupPath(text3) || !hashSet.Add(text3))
					{
						failureReason = "Backup manifest contains an unsafe or duplicate path.";
						return false;
					}
					if (!long.TryParse(array2[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result3) || result3 < 0)
					{
						failureReason = "Backup manifest contains an invalid file size.";
						return false;
					}
					string fullPath3 = Path.GetFullPath(Path.Combine(text, text3.Replace('/', Path.DirectorySeparatorChar)));
					if (!IsWithin(fullPath3, text))
					{
						failureReason = "Backup file escapes its content directory.";
						return false;
					}
					CheckPathForReparsePoints(fullPath3);
					FileInfo fileInfo = new FileInfo(fullPath3);
					if (!fileInfo.Exists || fileInfo.Length != result3)
					{
						failureReason = "Backup file is missing or has an incorrect size.";
						return false;
					}
					if (!string.Equals(BitConverter.ToString(ComputeFileHash(fullPath3)).Replace("-", string.Empty), array2[2], StringComparison.OrdinalIgnoreCase))
					{
						failureReason = "Backup file SHA-256 fingerprint does not match.";
						return false;
					}
				}
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				CollectStagedFileNames(text, string.Empty, hashSet2);
				if (!hashSet.SetEquals(hashSet2))
				{
					failureReason = "Backup contents do not match the manifest inventory.";
					return false;
				}
				return true;
			}
			catch (IOException)
			{
				failureReason = "Backup verification encountered a filesystem error.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Backup verification was denied access.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup verification encountered an invalid path.";
			}
			catch (FormatException)
			{
				failureReason = "Backup manifest encoding is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup verification encountered an unsupported path.";
			}
			catch (CryptographicException)
			{
				failureReason = "Backup fingerprint verification failed.";
			}
			return false;
		}

		internal static bool TryPreflightPreparedInstallation(BackupRotationLease lease, BackupSnapshotKind kind, string stableIdentity, Guid operationId, out string failureReason)
		{
			failureReason = null;
			if (lease == null || operationId == Guid.Empty || string.IsNullOrWhiteSpace(stableIdentity))
			{
				failureReason = "Installation preflight information is incomplete.";
				return false;
			}
			if (!BackupRotationJournal.TryReadTransaction(lease, out var state, out failureReason))
			{
				return false;
			}
			if (state == null || state.IsCommitted || state.OperationId != operationId || !string.Equals(state.StableIdentity, stableIdentity, StringComparison.Ordinal))
			{
				failureReason = "Installation journal does not match this operation.";
				return false;
			}
			try
			{
				string identityDirectory = lease.IdentityDirectory;
				string b = ((kind == BackupSnapshotKind.Character) ? "Characters" : "Worlds");
				if (!string.Equals(Path.GetFileName(Path.GetDirectoryName(identityDirectory)), b, StringComparison.OrdinalIgnoreCase))
				{
					failureReason = "Backup directory does not match the save kind.";
					return false;
				}
				string path = Path.Combine(identityDirectory, "Tx");
				if (File.Exists(Path.Combine(path, "committed.marker.tmp")) || File.Exists(Path.Combine(path, "rollback.completed")) || File.Exists(Path.Combine(path, "rollback.completed.tmp")))
				{
					failureReason = "Transaction contains recovery or incomplete markers.";
					return false;
				}
				bool[] array = new bool[3] { state.HadBackup1, state.HadBackup2, state.HadBackup3 };
				bool[] array2 = new bool[3] { true, state.HadBackup1, state.HadBackup2 };
				for (int i = 1; i <= 3; i++)
				{
					string text = Path.Combine(identityDirectory, "Backup-" + i);
					string path2 = Path.Combine(path, "Old-" + i);
					string text2 = Path.Combine(path, "New-" + i);
					CheckPathForReparsePoints(text);
					CheckPathForReparsePoints(path2);
					CheckPathForReparsePoints(text2);
					if (File.Exists(text) || File.Exists(path2) || File.Exists(text2))
					{
						failureReason = "Installation encountered a file where a backup directory was expected.";
						return false;
					}
					if (Directory.Exists(path2))
					{
						failureReason = "An original backup was already moved. Recovery is required.";
						return false;
					}
					if (Directory.Exists(text) != array[i - 1])
					{
						failureReason = "Visible backup slots changed after the transaction was prepared.";
						return false;
					}
					if (Directory.Exists(text2) != array2[i - 1])
					{
						failureReason = "Prepared replacement slots are incomplete.";
						return false;
					}
					if (array[i - 1] && !TryVerifyBackupFolder(text, kind, stableIdentity, null, out failureReason))
					{
						failureReason = "Visible Backup-" + i + " failed verification: " + failureReason;
						return false;
					}
					if (array2[i - 1])
					{
						Guid? expectedOperationId = ((i == 1) ? new Guid?(operationId) : ((Guid?)null));
						if (!TryVerifyBackupFolder(text2, kind, stableIdentity, expectedOperationId, out failureReason))
						{
							failureReason = "Prepared New-" + i + " failed verification: " + failureReason;
							return false;
						}
					}
				}
				for (int j = 1; j <= 2; j++)
				{
					if (array[j - 1])
					{
						string path3 = Path.Combine(identityDirectory, "Backup-" + j, "backup-manifest.txt");
						string path4 = Path.Combine(path, "New-" + (j + 1), "backup-manifest.txt");
						byte[] array3 = ComputeFileHash(path3);
						if (!CryptographicOperations.FixedTimeEquals(right: ComputeFileHash(path4), left: array3))
						{
							failureReason = "Existing backup history changed after replacement preparation.";
							return false;
						}
					}
				}
				return true;
			}
			catch (IOException)
			{
				failureReason = "Installation preflight encountered a filesystem error.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Installation preflight was denied access.";
			}
			catch (ArgumentException)
			{
				failureReason = "Installation preflight encountered an invalid path.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Installation preflight encountered an unsupported path.";
			}
			catch (CryptographicException)
			{
				failureReason = "Installation preflight fingerprint verification failed.";
			}
			return false;
		}

		internal static bool TryInstallPreparedReplacementCopies(BackupRotationLease lease, BackupSnapshotKind kind, string stableIdentity, Guid operationId, out string failureReason)
		{
			failureReason = null;
			if (!TryPreflightPreparedInstallation(lease, kind, stableIdentity, operationId, out failureReason))
			{
				return false;
			}
			if (!BackupRotationJournal.TryReadTransaction(lease, out var state, out failureReason))
			{
				return false;
			}
			if (state == null || state.IsCommitted || state.OperationId != operationId || !string.Equals(state.StableIdentity, stableIdentity, StringComparison.Ordinal))
			{
				failureReason = "Installation journal changed after preflight.";
				return false;
			}
			try
			{
				string identityDirectory = lease.IdentityDirectory;
				string path = Path.Combine(identityDirectory, "Tx");
				bool[] array = new bool[3] { state.HadBackup1, state.HadBackup2, state.HadBackup3 };
				bool[] array2 = new bool[3] { true, state.HadBackup1, state.HadBackup2 };
				long num = 0L;
				for (int i = 1; i <= 3; i++)
				{
					if (!array2[i - 1])
					{
						continue;
					}
					foreach (string item in Directory.EnumerateFiles(Path.Combine(path, "New-" + i), "*", SearchOption.AllDirectories))
					{
						num = checked(num + new FileInfo(item).Length);
					}
				}
				string pathRoot = Path.GetPathRoot(identityDirectory);
				if (string.IsNullOrWhiteSpace(pathRoot))
				{
					failureReason = "Installation storage volume cannot be identified.";
					return false;
				}
				DriveInfo driveInfo = new DriveInfo(pathRoot);
				if (!driveInfo.IsReady)
				{
					failureReason = "Installation storage volume is unavailable.";
					return false;
				}
				long num2 = Math.Max(67108864L, num / 20);
				long num3 = checked(num + num2);
				if (driveInfo.AvailableFreeSpace < num3)
				{
					failureReason = "Insufficient free space to install the prepared backup copies safely.";
					return false;
				}
				for (int j = 1; j <= 3; j++)
				{
					if (array[j - 1])
					{
						string sourceDirName = Path.Combine(identityDirectory, "Backup-" + j);
						string text = Path.Combine(path, "Old-" + j);
						Directory.Move(sourceDirName, text);
						if (!TryVerifyBackupFolder(text, kind, stableIdentity, null, out failureReason))
						{
							failureReason = "Preserved Old-" + j + " failed verification: " + failureReason;
							return false;
						}
					}
				}
				for (int k = 1; k <= 3; k++)
				{
					if (array2[k - 1])
					{
						string sourceDirectory = Path.Combine(path, "New-" + k);
						string text2 = Path.Combine(identityDirectory, "Backup-" + k);
						Directory.CreateDirectory(text2);
						List<BackupFileRecord> list = new List<BackupFileRecord>();
						CopyDirectoryContents(sourceDirectory, text2, string.Empty, list);
						if (list.Count == 0)
						{
							failureReason = "Installed Backup-" + k + " contains no copied files.";
							return false;
						}
						Guid? expectedOperationId = ((k == 1) ? new Guid?(operationId) : ((Guid?)null));
						if (!TryVerifyBackupFolder(text2, kind, stableIdentity, expectedOperationId, out failureReason))
						{
							failureReason = "Installed Backup-" + k + " failed verification: " + failureReason;
							return false;
						}
					}
				}
				for (int l = 1; l <= 3; l++)
				{
					if (Directory.Exists(Path.Combine(identityDirectory, "Backup-" + l)) != array2[l - 1])
					{
						failureReason = "Installed backup layout does not match the expected rotation.";
						return false;
					}
				}
				return true;
			}
			catch (IOException)
			{
				failureReason = "Backup installation encountered a filesystem error.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Backup installation was denied access.";
			}
			catch (ArgumentException)
			{
				failureReason = "Backup installation encountered an invalid path.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Backup installation storage is unsupported.";
			}
			catch (OverflowException)
			{
				failureReason = "Backup installation size calculation overflowed.";
			}
			catch (CryptographicException)
			{
				failureReason = "Backup installation fingerprint verification failed.";
			}
			return false;
		}

		internal static bool TryVerifyStagedSnapshot(StagedBackupSnapshot snapshot, BackupSnapshotKind expectedKind, string expectedStableIdentity, Guid expectedOperationId, out string failureReason)
		{
			failureReason = null;
			if (snapshot == null)
			{
				failureReason = "No staged backup was supplied.";
				return false;
			}
			if (expectedOperationId == Guid.Empty || string.IsNullOrWhiteSpace(expectedStableIdentity))
			{
				failureReason = "Expected backup identity is incomplete.";
				return false;
			}
			try
			{
				if (!SaveBackupCoordinator.TryGetBackupRoot(out var backupRoot, out failureReason))
				{
					return false;
				}
				string fullPath = Path.GetFullPath(Path.Combine(backupRoot, ".ModsmithCycleStaging"));
				string fullPath2 = Path.GetFullPath(snapshot.StagingDirectory);
				string fullPath3 = Path.GetFullPath(snapshot.ContentDirectory);
				if (!IsWithin(fullPath2, fullPath) || string.Equals(fullPath2, fullPath, StringComparison.OrdinalIgnoreCase) || !IsWithin(fullPath3, fullPath2))
				{
					failureReason = "Staging location is outside the expected backup area.";
					return false;
				}
				CheckPathForReparsePoints(fullPath2);
				CheckPathForReparsePoints(fullPath3);
				string[] array = File.ReadAllLines(Path.Combine(fullPath2, "backup-manifest.txt"));
				if (array.Length < 6)
				{
					failureReason = "Staging manifest is incomplete.";
					return false;
				}
				if (array[0] != "ManifestVersion=1")
				{
					failureReason = "Staging manifest version is unsupported.";
					return false;
				}
				string text = "OperationId=" + expectedOperationId.ToString("N");
				if (array[1] != text)
				{
					failureReason = "Staging operation ID does not match.";
					return false;
				}
				if (array[2] != "Kind=" + expectedKind)
				{
					failureReason = "Staging save kind does not match.";
					return false;
				}
				string text2 = "StableIdentity=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(expectedStableIdentity));
				if (array[3] != text2)
				{
					failureReason = "Staging save identity does not match.";
					return false;
				}
				if (!array[4].StartsWith("FileCount=", StringComparison.Ordinal))
				{
					failureReason = "Staging file count is missing.";
					return false;
				}
				if (!int.TryParse(array[4].Substring("FileCount=".Length), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
				{
					failureReason = "Staging file count is invalid.";
					return false;
				}
				if (result <= 0 || result > 1000000 || result != snapshot.FileCount || array.Length != result + 5)
				{
					failureReason = "Staging manifest file count does not match.";
					return false;
				}
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				long num = 0L;
				for (int i = 5; i < array.Length; i++)
				{
					string[] array2 = array[i].Split('|');
					if (array2.Length != 3)
					{
						failureReason = "Staging manifest contains a malformed file record.";
						return false;
					}
					string text3 = Encoding.UTF8.GetString(Convert.FromBase64String(array2[0]));
					if (!IsSafeRelativeBackupPath(text3))
					{
						failureReason = "Staging manifest contains an unsafe relative path.";
						return false;
					}
					if (!hashSet.Add(text3))
					{
						failureReason = "Staging manifest contains duplicate files.";
						return false;
					}
					if (!long.TryParse(array2[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || result2 < 0)
					{
						failureReason = "Staging file size is invalid.";
						return false;
					}
					string fullPath4 = Path.GetFullPath(Path.Combine(fullPath3, text3.Replace('/', Path.DirectorySeparatorChar)));
					if (!IsWithin(fullPath4, fullPath3))
					{
						failureReason = "Staging file escapes its directory.";
						return false;
					}
					CheckPathForReparsePoints(fullPath4);
					FileInfo fileInfo = new FileInfo(fullPath4);
					if (!fileInfo.Exists || fileInfo.Length != result2)
					{
						failureReason = "Staging file is missing or incomplete.";
						return false;
					}
					if (!string.Equals(BitConverter.ToString(ComputeFileHash(fullPath4)).Replace("-", string.Empty), array2[2], StringComparison.OrdinalIgnoreCase))
					{
						failureReason = "Staging file fingerprint does not match.";
						return false;
					}
					num = checked(num + fileInfo.Length);
				}
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				CollectStagedFileNames(fullPath3, string.Empty, hashSet2);
				if (!hashSet.SetEquals(hashSet2))
				{
					failureReason = "Staging directory contains unlisted or missing files.";
					return false;
				}
				if (num != snapshot.TotalBytes)
				{
					failureReason = "Staging total byte count does not match.";
					return false;
				}
				return true;
			}
			catch (InvalidDataException)
			{
				failureReason = "Staging data failed validation.";
			}
			catch (IOException)
			{
				failureReason = "Staging verification encountered a file error.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Staging verification was denied access.";
			}
			catch (ArgumentException)
			{
				failureReason = "Staging manifest contains invalid data.";
			}
			catch (FormatException)
			{
				failureReason = "Staging manifest encoding is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Staging verification encountered an unsupported path.";
			}
			catch (OverflowException)
			{
				failureReason = "Staging manifest contains an invalid size.";
			}
			catch (CryptographicException)
			{
				failureReason = "Staging fingerprint calculation failed.";
			}
			return false;
		}

		private static bool IsSafeRelativeBackupPath(string relativePath)
		{
			if (string.IsNullOrWhiteSpace(relativePath) || relativePath.Contains("\\") || Path.IsPathRooted(relativePath))
			{
				return false;
			}
			string[] array = relativePath.Split('/');
			foreach (string text in array)
			{
				if (text.Length == 0 || text == "." || text == ".." || text.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
				{
					return false;
				}
			}
			return true;
		}

		private static void CollectStagedFileNames(string directory, string relativePrefix, HashSet<string> results)
		{
			foreach (string item in Directory.EnumerateFileSystemEntries(directory))
			{
				FileAttributes attributes = File.GetAttributes(item);
				if ((attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					throw new IOException("Staging directory contains a reparse point.");
				}
				string fileName = Path.GetFileName(item);
				string text = ((relativePrefix.Length == 0) ? fileName : (relativePrefix + "/" + fileName));
				if ((attributes & FileAttributes.Directory) != FileAttributes.None)
				{
					CollectStagedFileNames(item, text, results);
				}
				else if (!results.Add(text))
				{
					throw new InvalidDataException("Duplicate staged file.");
				}
			}
		}

		private static void CheckPathForReparsePoints(string path)
		{
			string fullPath = Path.GetFullPath(path);
			if (File.Exists(fullPath) && (File.GetAttributes(fullPath) & FileAttributes.ReparsePoint) != FileAttributes.None)
			{
				throw new IOException("Backup path contains a reparse point.");
			}
			for (DirectoryInfo directoryInfo = new DirectoryInfo(Directory.Exists(fullPath) ? fullPath : Path.GetDirectoryName(fullPath)); directoryInfo != null; directoryInfo = directoryInfo.Parent)
			{
				if (directoryInfo.Exists && (directoryInfo.Attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					throw new IOException("Backup path contains a reparse point.");
				}
			}
		}
	}
	internal sealed class CharacterBackupIdentity
	{
		internal long PlayerId { get; }

		internal string DisplayName { get; }

		internal string SaveFilename { get; }

		internal CharacterBackupIdentity(long playerId, string displayName, string saveFilename)
		{
			PlayerId = playerId;
			DisplayName = displayName;
			SaveFilename = saveFilename;
		}
	}
	internal static class CharacterSaveBackupService
	{
		private const int ExpectedHashLength = 64;

		private const int CopyBufferSize = 65536;

		internal static bool TryCaptureIdentity(PlayerProfile profile, out CharacterBackupIdentity identity, out string failureReason)
		{
			identity = null;
			failureReason = null;
			if (profile == null)
			{
				failureReason = "No active character profile was supplied.";
				return false;
			}
			try
			{
				long playerID = profile.GetPlayerID();
				string name = profile.GetName();
				string filename = profile.GetFilename();
				if (playerID == 0L)
				{
					failureReason = "Character has no valid player ID.";
					return false;
				}
				if (string.IsNullOrWhiteSpace(name))
				{
					failureReason = "Character display name is missing.";
					return false;
				}
				if (string.IsNullOrWhiteSpace(filename))
				{
					failureReason = "Character save filename is missing.";
					return false;
				}
				if (filename == "." || filename == ".." || filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
				{
					failureReason = "Character save filename is invalid.";
					return false;
				}
				identity = new CharacterBackupIdentity(playerID, name, filename);
				return true;
			}
			catch (Exception ex)
			{
				failureReason = "Could not capture character identity: " + ex.GetType().Name;
				return false;
			}
		}

		internal static bool TryVerifyLocalEnvelope(string sourcePath, byte[] expectedPayloadHash, out string failureReason)
		{
			failureReason = null;
			if (string.IsNullOrWhiteSpace(sourcePath))
			{
				failureReason = "Character source path is missing.";
				return false;
			}
			if (expectedPayloadHash == null || expectedPayloadHash.Length != 64)
			{
				failureReason = "Expected character save hash is unavailable.";
				return false;
			}
			try
			{
				if (!Path.IsPathFullyQualified(sourcePath))
				{
					failureReason = "Character source is not a local absolute path.";
					return false;
				}
				using (FileStream fileStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
				{
					using BinaryReader binaryReader = new BinaryReader(fileStream);
					long length = fileStream.Length;
					if (length < 72)
					{
						failureReason = "Character file is incomplete.";
						return false;
					}
					int num = binaryReader.ReadInt32();
					if (num <= 0)
					{
						failureReason = "Character payload length is invalid.";
						return false;
					}
					long num2 = 4L + (long)num + 4 + 64;
					if (length != num2)
					{
						failureReason = "Character file length does not match its declared contents.";
						return false;
					}
					byte[] hashAndReset;
					using (IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA512))
					{
						byte[] array = new byte[65536];
						int num3 = num;
						while (num3 > 0)
						{
							int count = Math.Min(num3, array.Length);
							int num4 = binaryReader.Read(array, 0, count);
							if (num4 <= 0)
							{
								failureReason = "Character payload ended early.";
								return false;
							}
							incrementalHash.AppendData(array, 0, num4);
							num3 -= num4;
						}
						hashAndReset = incrementalHash.GetHashAndReset();
					}
					if (binaryReader.ReadInt32() != 64)
					{
						failureReason = "Character hash length is invalid.";
						return false;
					}
					byte[] array2 = binaryReader.ReadBytes(64);
					if (array2.Length != 64)
					{
						failureReason = "Character hash is incomplete.";
						return false;
					}
					if (fileStream.Position != length)
					{
						failureReason = "Unexpected trailing character data.";
						return false;
					}
					if (!CryptographicOperations.FixedTimeEquals(hashAndReset, array2))
					{
						failureReason = "Character file failed its integrity check.";
						return false;
					}
					if (!CryptographicOperations.FixedTimeEquals(hashAndReset, expectedPayloadHash))
					{
						failureReason = "Character file does not match the expected save operation.";
						return false;
					}
				}
				return true;
			}
			catch (IOException)
			{
				failureReason = "Character save could not be read consistently.";
			}
			catch (UnauthorizedAccessException)
			{
				failureReason = "Access to the character save was denied.";
			}
			catch (ArgumentException)
			{
				failureReason = "Character source path is invalid.";
			}
			catch (NotSupportedException)
			{
				failureReason = "Character source path is unsupported.";
			}
			catch (CryptographicException)
			{
				failureReason = "Character hash verification failed.";
			}
			return false;
		}
	}
	internal static class CloudExitSnapshotService
	{
		private const int MaxFileBytes = 268435456;

		private const long MaxTotalBytes = 3221225472L;

		private const int MaxFiles = 100000;

		private const string PrivateFolder = ".ModsmithCycleCloudCapture";

		private static string Describe(Exception ex)
		{
			string text = (ex.Message ?? string.Empty).Replace('\r', ' ').Replace('\n', ' ');
			if (text.Length > 240)
			{
				text = text.Substring(0, 240);
			}
			return ex.GetType().Name + ": " + text;
		}

		private static bool TryReadExact(string key, out byte[] data, out string reason)
		{
			data = null;
			reason = null;
			try
			{
				int fileSize = SteamRemoteStorage.GetFileSize(key);
				if (fileSize <= 0 || fileSize > 268435456)
				{
					reason = "Cloud key has invalid/unsupported size: " + key;
					return false;
				}
				byte[] array = new byte[fileSize];
				int actualBytesRead = SteamRemoteStorage.FileRead(key, array, fileSize);
				if (!CloudSaveService.TryVerifyCloudRead(key, (ulong)fileSize, array, actualBytesRead, out var _, out reason))
				{
					return false;
				}
				data = array;
				return true;
			}
			catch (Exception ex)
			{
				reason = "Cloud read could not complete (" + Describe(ex) + ")";
				return false;
			}
		}

		private static string CreatePrivateCapture(Guid id)
		{
			if (!SaveBackupCoordinator.TryGetDataRoot(out var dataRoot, out var failureReason))
			{
				throw new IOException("ModsmithCycle data directory is unavailable: " + failureReason);
			}
			string text = Path.Combine(dataRoot, ".ModsmithCycleCloudCapture");
			Directory.CreateDirectory(text);
			string text2 = Path.Combine(text, id.ToString("N") + "-" + Guid.NewGuid().ToString("N"));
			Directory.CreateDirectory(text2);
			return text2;
		}

		private static void WriteExact(string path, byte[] data)
		{
			using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None);
			fileStream.Write(data, 0, data.Length);
			fileStream.Flush(flushToDisk: true);
		}

		private static bool SameBytes(byte[] first, byte[] second)
		{
			if (first == null || second == null || first.Length != second.Length)
			{
				return false;
			}
			using SHA256 sHA = SHA256.Create();
			byte[] first2 = sHA.ComputeHash(first);
			byte[] second2 = sHA.ComputeHash(second);
			return CryptographicOperationsUnavailableSafeEquals(first2, second2);
		}

		private static bool CryptographicOperationsUnavailableSafeEquals(byte[] first, byte[] second)
		{
			if (first.Length != second.Length)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < first.Length; i++)
			{
				num |= first[i] ^ second[i];
			}
			return num == 0;
		}

		internal static bool TryCaptureCharacter(LiveSaveBackupService.CharacterCandidate character, Guid operationId, out string savedPath, out string captureDirectory, out string reason)
		{
			savedPath = null;
			captureDirectory = null;
			reason = null;
			string characterPath = SaveSystem.GetCharacterPath((FileSource)4, character.Identity.SaveFilename);
			if (!string.Equals(character.Path, characterPath, StringComparison.Ordinal))
			{
				reason = "Character Cloud publication did not match its native key.";
				return false;
			}
			if (!TryReadExact(characterPath, out var data, out reason) || !TryReadExact(characterPath, out var data2, out reason))
			{
				return false;
			}
			if (!SameBytes(data, data2))
			{
				reason = "Character Cloud content changed between complete reads.";
				return false;
			}
			try
			{
				captureDirectory = CreatePrivateCapture(operationId);
				savedPath = Path.Combine(captureDirectory, character.Identity.SaveFilename + ".fch");
				WriteExact(savedPath, data);
				return true;
			}
			catch (Exception ex)
			{
				reason = "Character Cloud staging failed (" + Describe(ex) + ")";
				return false;
			}
		}

		private static bool TryEnumerate(out string[] all, out string reason)
		{
			all = null;
			reason = null;
			try
			{
				int fileCount = SteamRemoteStorage.GetFileCount();
				if (fileCount <= 0 || fileCount > 100000)
				{
					reason = "Cloud enumeration reported invalid file count.";
					return false;
				}
				string[] array = new string[fileCount];
				int num = default(int);
				for (int i = 0; i < fileCount; i++)
				{
					array[i] = SteamRemoteStorage.GetFileNameAndSize(i, ref num);
					if (string.IsNullOrWhiteSpace(array[i]) || num < 0)
					{
						reason = "Cloud enumeration returned an invalid entry.";
						return false;
					}
				}
				all = array;
				return true;
			}
			catch (Exception ex)
			{
				reason = "Cloud enumeration failed (" + Describe(ex) + ")";
				return false;
			}
		}

		internal static bool TryCaptureWorld(WorldBackupIdentity world, uint generation, Guid operationId, out string savedDirectory, out string captureDirectory, out string reason)
		{
			savedDirectory = null;
			captureDirectory = null;
			reason = null;
			string text = World.GetSaveDirectory((FileSource)4, world.SaveName).Replace('\\', '/').TrimStart('/');
			string text2 = text.TrimEnd('/') + "/";
			if (!TryEnumerate(out var all, out reason))
			{
				return false;
			}
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array = all;
			foreach (string text3 in array)
			{
				string text4 = text3.Replace('\\', '/');
				if (text4.StartsWith(text2, StringComparison.OrdinalIgnoreCase))
				{
					string text5 = text4.Substring(text2.Length);
					if (text5.Length == 0 || text5.IndexOf('/') >= 0 || text5 == "." || text5 == ".." || text5.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || !hashSet.Add(text4))
					{
						reason = "Cloud world contains an invalid/duplicate key.";
						return false;
					}
					list.Add(text3);
				}
			}
			if (list.Count < 4)
			{
				string text6 = "worlds/" + world.SaveName + "/";
				int num = 0;
				int num2 = 0;
				string text7 = null;
				array = all;
				for (int i = 0; i < array.Length; i++)
				{
					string text8 = array[i].Replace('\\', '/');
					if (!text8.StartsWith(text6, StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					num++;
					if (text8.Substring(text6.Length).StartsWith("_main.", StringComparison.OrdinalIgnoreCase))
					{
						num2++;
						if (text7 == null)
						{
							text7 = text8;
						}
					}
				}
				Squirrel.Warning(SquirrelChannel.Backup, "[ExitBackup] Cloud world listing diagnostic: expectedPrefix=" + text2 + "; matched=" + list.Count + "; totalKeys=" + all.Length + "; directWorldMatches=" + num + "; directMainFiles=" + num2 + "; exampleMainKey=" + (text7 ?? "<none>") + ".");
				reason = "Cloud world does not have its four generation files.";
				return false;
			}
			try
			{
				captureDirectory = CreatePrivateCapture(operationId);
				Dictionary<string, byte[]> dictionary = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
				long num3 = 0L;
				foreach (string item in list)
				{
					if (!TryReadExact(item, out var data, out reason))
					{
						return false;
					}
					num3 = checked(num3 + data.LongLength);
					if (num3 > 3221225472u)
					{
						reason = "Cloud world exceeds the configured safety size limit.";
						return false;
					}
					string path = item.Replace('\\', '/').Substring(text2.Length);
					WriteExact(Path.Combine(captureDirectory, path), data);
					using SHA256 sHA = SHA256.Create();
					dictionary[item] = sHA.ComputeHash(data);
				}
				if (!TryEnumerate(out var all2, out reason))
				{
					return false;
				}
				if (!CloudSaveService.TryConfirmWorldFileListing(text, generation, all, all2, out var _, out reason))
				{
					return false;
				}
				foreach (string item2 in list)
				{
					if (!TryReadExact(item2, out var data2, out reason))
					{
						return false;
					}
					using SHA256 sHA2 = SHA256.Create();
					byte[] second = sHA2.ComputeHash(data2);
					if (!CryptographicOperationsUnavailableSafeEquals(dictionary[item2], second))
					{
						reason = "Cloud world content changed during capture.";
						return false;
					}
				}
				if (!TryEnumerate(out var all3, out reason))
				{
					return false;
				}
				if (!CloudSaveService.TryConfirmWorldFileListing(text, generation, all, all3, out var _, out reason))
				{
					return false;
				}
				savedDirectory = captureDirectory;
				return true;
			}
			catch (Exception ex)
			{
				reason = "Cloud world capture failed (" + Describe(ex) + ")";
				return false;
			}
		}

		internal static void TryCleanupPrivateCapture(string path)
		{
			if (string.IsNullOrWhiteSpace(path))
			{
				return;
			}
			try
			{
				if (!SaveBackupCoordinator.TryGetDataRoot(out var dataRoot, out var _))
				{
					return;
				}
				string fullPath = Path.GetFullPath(Path.Combine(dataRoot, ".ModsmithCycleCloudCapture"));
				string fullPath2 = Path.GetFullPath(path);
				string fileName = Path.GetFileName(fullPath2);
				if (!string.Equals(Path.GetDirectoryName(fullPath2), fullPath, StringComparison.OrdinalIgnoreCase) || fileName.Length != 65 || fileName[32] != '-' || !Guid.TryParseExact(fileName.Substring(0, 32), "N", out var _) || !Guid.TryParseExact(fileName.Substring(33), "N", out var _) || !Directory.Exists(fullPath2) || (File.GetAttributes(fullPath2) & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					return;
				}
				foreach (string item in Directory.EnumerateFileSystemEntries(fullPath2))
				{
					if ((File.GetAttributes(item) & FileAttributes.ReparsePoint) != FileAttributes.None)
					{
						return;
					}
				}
				Directory.Delete(fullPath2, recursive: true);
			}
			catch (Exception ex)
			{
				Squirrel.Warning(SquirrelChannel.Backup, "[ExitBackup] Private Cloud capture cleanup failed: " + Describe(ex));
			}
		}
	}
	internal sealed class CloudReadReceipt
	{
		private readonly byte[] _sha256;

		internal string LogicalKey { get; }

		internal long ByteCount { get; }

		internal byte[] GetSha256()
		{
			return (byte[])_sha256.Clone();
		}

		internal CloudReadReceipt(string logicalKey, long byteCount, byte[] sha256)
		{
			LogicalKey = logicalKey;
			ByteCount = byteCount;
			_sha256 = (byte[])sha256.Clone();
		}
	}
	internal static class CloudSaveService
	{
		private const int Sha256Length = 32;

		internal static bool TryVerifyCloudRead(string logicalKey, ulong providerReportedSize, byte[] data, int actualBytesRead, out CloudReadReceipt receipt, out string failureReason)
		{
			receipt = null;
			failureReason = null;
			if (string.IsNullOrWhiteSpace(logicalKey))
			{
				failureReason = "Cloud file identity is missing.";
				return false;
			}
			if (providerReportedSize == 0L || providerReportedSize > int.MaxValue)
			{
				failureReason = "Cloud file size is invalid or unsupported.";
				return false;
			}
			if (data == null)
			{
				failureReason = "Cloud provider returned no data.";
				return false;
			}
			if (actualBytesRead <= 0)
			{
				failureReason = "Cloud provider did not report a successful full read.";
				return false;
			}
			if (data.LongLength != (long)providerReportedSize)
			{
				failureReason = "Cloud read buffer size does not match the provider's reported file size.";
				return false;
			}
			if ((ulong)actualBytesRead != providerReportedSize)
			{
				failureReason = "Cloud read was incomplete.";
				return false;
			}
			try
			{
				byte[] array;
				using (SHA256 sHA = SHA256.Create())
				{
					array = sHA.ComputeHash(data, 0, actualBytesRead);
				}
				if (array.Length != 32)
				{
					failureReason = "Cloud file fingerprint is invalid.";
					return false;
				}
				receipt = new CloudReadReceipt(logicalKey, actualBytesRead, array);
				return true;
			}
			catch (CryptographicException)
			{
				failureReason = "Cloud file fingerprint calculation failed.";
				return false;
			}
		}

		internal static bool TryConfirmWorldFileListing(string cloudWorldDirectory, uint expectedGeneration, string[] beforeRead, string[] afterRead, out string[] confirmedFiles, out string failureReason)
		{
			confirmedFiles = null;
			failureReason = null;
			if (string.IsNullOrWhiteSpace(cloudWorldDirectory))
			{
				failureReason = "Cloud world directory is missing.";
				return false;
			}
			if (expectedGeneration == 0)
			{
				failureReason = "Expected Cloud world generation is missing.";
				return false;
			}
			if (beforeRead == null || afterRead == null)
			{
				failureReason = "Cloud file enumeration is incomplete.";
				return false;
			}
			string text = cloudWorldDirectory.Replace('\\', '/').TrimEnd('/') + "/";
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (!CollectWorldFiles(beforeRead, 

ModsmithCycle.Restarter.exe

Decompiled a day ago
using System;
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 System.Threading;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("ModsmithCycle.Restarter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: AssemblyInformationalVersion("0.1.3+51dd64e63e3ff3f81ddf60d59666c64cadea44df")]
[assembly: AssemblyProduct("ModsmithCycle.Restarter")]
[assembly: AssemblyTitle("ModsmithCycle.Restarter")]
[assembly: AssemblyVersion("0.1.3.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 ModsmithCycle.Restarter
{
	internal static class Program
	{
		private sealed class LaunchRequest
		{
			public int ParentProcessId;

			public string SteamExecutablePath;

			public string[] ProfileArguments;

			public bool HelperDiagnostics;

			public string HelperLogDirectory;

			public static LaunchRequest Load(string path)
			{
				string[] array = File.ReadAllLines(path);
				if (array.Length == 0 || !string.Equals(array[0], "MODSMITH_CYCLE_LAUNCH_V2", StringComparison.Ordinal))
				{
					throw new InvalidDataException("Unsupported restart request.");
				}
				LaunchRequest launchRequest = new LaunchRequest();
				List<string> list = new List<string>();
				bool flag = false;
				bool flag2 = false;
				for (int i = 1; i < array.Length; i++)
				{
					string text = array[i];
					int num = text.IndexOf('=');
					if (num <= 0)
					{
						continue;
					}
					string a = text.Substring(0, num);
					string text2 = text.Substring(num + 1);
					if (string.Equals(a, "ParentProcessId", StringComparison.Ordinal))
					{
						if (!int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out launchRequest.ParentProcessId))
						{
							throw new InvalidDataException("Invalid parent process id.");
						}
						flag = true;
					}
					else if (string.Equals(a, "SteamExecutablePath", StringComparison.Ordinal))
					{
						launchRequest.SteamExecutablePath = Decode(text2);
						flag2 = true;
					}
					else if (string.Equals(a, "HelperDiagnostics", StringComparison.Ordinal))
					{
						launchRequest.HelperDiagnostics = string.Equals(text2, "1", StringComparison.Ordinal);
					}
					else if (string.Equals(a, "HelperLogDirectory", StringComparison.Ordinal))
					{
						launchRequest.HelperLogDirectory = Decode(text2);
					}
					else if (string.Equals(a, "Argument", StringComparison.Ordinal))
					{
						list.Add(Decode(text2));
					}
				}
				if (!flag)
				{
					throw new InvalidDataException("Missing parent process id.");
				}
				if (!flag2 || string.IsNullOrEmpty(launchRequest.SteamExecutablePath))
				{
					throw new InvalidDataException("Missing Steam executable path.");
				}
				launchRequest.ProfileArguments = list.ToArray();
				return launchRequest;
			}

			private static string Decode(string value)
			{
				if (string.IsNullOrEmpty(value))
				{
					return string.Empty;
				}
				return Encoding.UTF8.GetString(Convert.FromBase64String(value));
			}
		}

		private const int SteamAppId = 892970;

		private const int ParentExitTimeoutMilliseconds = 120000;

		[STAThread]
		private static int Main(string[] args)
		{
			string text = ((args != null && args.Length != 0) ? args[0] : string.Empty);
			try
			{
				if (string.IsNullOrEmpty(text) || !File.Exists(text))
				{
					throw new FileNotFoundException("Restart request file was not found.", text);
				}
				LaunchRequest launchRequest = LaunchRequest.Load(text);
				RestarterSquirrel.Initialize(text, launchRequest.HelperDiagnostics, launchRequest.HelperLogDirectory);
				RestarterSquirrel.Info("Restart request loaded successfully.");
				if (!File.Exists(launchRequest.SteamExecutablePath))
				{
					throw new FileNotFoundException("Steam.exe was not found.", launchRequest.SteamExecutablePath);
				}
				WaitForParent(launchRequest.ParentProcessId);
				RestarterSquirrel.Info("Previous Valheim process has exited.");
				Process.Start(new ProcessStartInfo
				{
					FileName = launchRequest.SteamExecutablePath,
					Arguments = BuildSteamCommandLine(launchRequest.ProfileArguments),
					WorkingDirectory = Path.GetDirectoryName(launchRequest.SteamExecutablePath),
					UseShellExecute = false
				});
				RestarterSquirrel.Info("Steam launch request submitted.");
				DeleteIfPresent(text);
				DeleteIfPresent(text + ".error");
				return 0;
			}
			catch (Exception ex)
			{
				RestarterSquirrel.Error("Restart failed: " + ex.GetType().Name);
				TryWriteError(text, ex);
				return 1;
			}
		}

		private static void WaitForParent(int processId)
		{
			try
			{
				using Process process = Process.GetProcessById(processId);
				if (!process.WaitForExit(120000))
				{
					throw new TimeoutException("Valheim did not exit before the restart timeout.");
				}
			}
			catch (ArgumentException)
			{
			}
			Thread.Sleep(1500);
		}

		private static string BuildSteamCommandLine(string[] profileArguments)
		{
			IEnumerable<string> source = profileArguments ?? new string[0];
			string text = string.Join(" ", source.Select(QuoteArgument));
			if (string.IsNullOrEmpty(text))
			{
				return "-applaunch " + 892970.ToString(CultureInfo.InvariantCulture);
			}
			return "-applaunch " + 892970.ToString(CultureInfo.InvariantCulture) + " " + text;
		}

		private static string QuoteArgument(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "\"\"";
			}
			if (value.All((char c2) => !char.IsWhiteSpace(c2) && c2 != '"'))
			{
				return value;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append('"');
			int num = 0;
			foreach (char c in value)
			{
				switch (c)
				{
				case '\\':
					num++;
					break;
				case '"':
					stringBuilder.Append('\\', num * 2 + 1);
					stringBuilder.Append('"');
					num = 0;
					break;
				default:
					stringBuilder.Append('\\', num);
					num = 0;
					stringBuilder.Append(c);
					break;
				}
			}
			stringBuilder.Append('\\', num * 2);
			stringBuilder.Append('"');
			return stringBuilder.ToString();
		}

		private static void TryWriteError(string requestPath, Exception exception)
		{
			if (string.IsNullOrEmpty(requestPath))
			{
				return;
			}
			try
			{
				File.WriteAllText(requestPath + ".error", exception.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			}
			catch
			{
			}
		}

		private static void DeleteIfPresent(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}
	}
	internal static class RestarterSquirrel
	{
		private static readonly object SyncRoot = new object();

		private static bool _enabled;

		private static string _logPath;

		internal static void Initialize(string requestPath, bool enabled, string dataDirectory)
		{
			_enabled = false;
			_logPath = null;
			if (!enabled || string.IsNullOrEmpty(requestPath))
			{
				return;
			}
			try
			{
				string directoryName = Path.GetDirectoryName(Path.GetFullPath(requestPath));
				if (string.IsNullOrEmpty(directoryName) || !string.Equals(Path.GetFileName(directoryName), "config", StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
				DirectoryInfo parent = Directory.GetParent(directoryName);
				if (parent == null)
				{
					return;
				}
				string directory = Path.Combine(parent.FullName, "ModsmithCycle");
				bool flag = !TryUseDirectory(dataDirectory);
				if (!flag || TryUseDirectory(directory))
				{
					_enabled = true;
					if (flag && !string.IsNullOrWhiteSpace(dataDirectory))
					{
						Warning("Configured helper log directory was unavailable; using BepInEx/ModsmithCycle.");
					}
					Info("Helper diagnostic session initialized.");
				}
			}
			catch
			{
				_enabled = false;
				_logPath = null;
			}
		}

		private static bool TryUseDirectory(string directory)
		{
			if (string.IsNullOrWhiteSpace(directory))
			{
				return false;
			}
			try
			{
				if (!Path.IsPathRooted(directory))
				{
					return false;
				}
				string pathRoot = Path.GetPathRoot(directory);
				if (!string.IsNullOrEmpty(pathRoot))
				{
					char directorySeparatorChar = Path.DirectorySeparatorChar;
					if (pathRoot.EndsWith(directorySeparatorChar.ToString(), StringComparison.Ordinal))
					{
						Directory.CreateDirectory(directory);
						string text = Path.Combine(directory, "ModsmithCycleHelper.log");
						File.AppendAllText(text, string.Empty, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
						_logPath = text;
						return true;
					}
				}
				return false;
			}
			catch
			{
				_logPath = null;
				return false;
			}
		}

		internal static void Info(string message)
		{
			Write("INFO", message);
		}

		internal static void Warning(string message)
		{
			Write("WARNING", message);
		}

		internal static void Error(string message)
		{
			Write("ERROR", message);
		}

		private static void Write(string level, string message)
		{
			if (!_enabled || string.IsNullOrEmpty(_logPath))
			{
				return;
			}
			try
			{
				string contents = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture) + " [RestarterSquirrel/" + level + "] " + message + Environment.NewLine;
				lock (SyncRoot)
				{
					File.AppendAllText(_logPath, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				}
			}
			catch
			{
			}
		}
	}
}