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 Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("Cloudward.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d3fbef8726f47d3953b79ef11c8e75d2f5805c9a")] [assembly: AssemblyProduct("Cloudward.Core")] [assembly: AssemblyTitle("Cloudward.Core")] [assembly: AssemblyMetadata("BuildStamp", "d3fbef8 2026-07-24")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 Cloudward.Core { public enum ForkResolution { NewestWins, ThisDevice, OtherDevice, KeepBoth, AskMe } public static class ForkResolve { public static bool IsAuto(ForkResolution policy) { return policy != ForkResolution.AskMe; } public static bool? LocalWins(ForkResolution policy, bool localIsNewer) { return policy switch { ForkResolution.NewestWins => localIsNewer, ForkResolution.ThisDevice => true, ForkResolution.OtherDevice => false, ForkResolution.KeepBoth => true, ForkResolution.AskMe => null, _ => localIsNewer, }; } } public sealed class LocalCharState { public int BasedOnGen { get; } public string LastPushedHead { get; } public LocalCharState(int basedOnGen, string lastPushedHead) { BasedOnGen = basedOnGen; LastPushedHead = lastPushedHead ?? string.Empty; } } public sealed class ForkRecord { public string LocalHead { get; } public string ShareHead { get; } public ForkRecord(string localHead, string shareHead) { LocalHead = localHead ?? string.Empty; ShareHead = shareHead ?? string.Empty; } } public sealed class LocalSyncState { private readonly Dictionary _state = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _forks = new Dictionary(StringComparer.Ordinal); private readonly HashSet _bootstrapped = new HashSet(StringComparer.Ordinal); public IEnumerable PendingForks => _forks.Keys; public bool HasForks => _forks.Count > 0; public LocalCharState? Get(string uid) { if (!_state.TryGetValue(uid, out LocalCharState value)) { return null; } return value; } public void Set(string uid, LocalCharState s) { _state[uid] = s; } public ForkRecord? GetFork(string uid) { if (!_forks.TryGetValue(uid, out ForkRecord value)) { return null; } return value; } public void SetFork(string uid, ForkRecord f) { _forks[uid] = f; } public void ClearFork(string uid) { _forks.Remove(uid); } public bool IsBootstrapped(string mountPath) { return _bootstrapped.Contains(mountPath ?? string.Empty); } public void MarkBootstrapped(string mountPath) { _bootstrapped.Add(mountPath ?? string.Empty); } public string Serialize() { List list = new List(); foreach (KeyValuePair item in _state.OrderBy, string>((KeyValuePair k) => k.Key, StringComparer.Ordinal)) { list.Add("S|" + Escape(item.Key) + "|" + item.Value.BasedOnGen.ToString(CultureInfo.InvariantCulture) + "|" + Escape(item.Value.LastPushedHead)); } foreach (KeyValuePair item2 in _forks.OrderBy, string>((KeyValuePair k) => k.Key, StringComparer.Ordinal)) { list.Add("F|" + Escape(item2.Key) + "|" + Escape(item2.Value.LocalHead) + "|" + Escape(item2.Value.ShareHead)); } foreach (string item3 in _bootstrapped.OrderBy((string k) => k, StringComparer.Ordinal)) { list.Add("B|" + Escape(item3)); } return string.Join("\n", list); } public static LocalSyncState Parse(string text) { LocalSyncState localSyncState = new LocalSyncState(); if (string.IsNullOrEmpty(text)) { return localSyncState; } string[] array = text.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0) { continue; } string[] array2 = text2.Split(new char[1] { '|' }); if (array2.Length < 2) { continue; } if (array2[0] == "B") { localSyncState._bootstrapped.Add(Unescape(array2[1])); } else if (array2.Length >= 4) { if (array2[0] == "S" && int.TryParse(array2[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { localSyncState._state[Unescape(array2[1])] = new LocalCharState(result, Unescape(array2[3])); } else if (array2[0] == "F") { localSyncState._forks[Unescape(array2[1])] = new ForkRecord(Unescape(array2[2]), Unescape(array2[3])); } } } return localSyncState; } private static string Escape(string v) { return (v ?? string.Empty).Replace("|", "%7C"); } private static string Unescape(string v) { return v.Replace("%7C", "|"); } } public enum LockHeldPolicy { FallbackLocal, ReadOnly, ForceTake } public enum LeaseDecision { Take, FallbackLocal, ReadOnly } public static class LockLease { public static LeaseDecision Decide(DateTime nowUtc, LockStamp existing, string ownDevice, int ownPid, int staleSeconds, LockHeldPolicy policy) { if (existing == null) { return LeaseDecision.Take; } if (string.Equals(existing.Device, ownDevice, StringComparison.OrdinalIgnoreCase)) { return LeaseDecision.Take; } if ((nowUtc - existing.HeartbeatUtc).TotalSeconds > (double)staleSeconds) { return LeaseDecision.Take; } return policy switch { LockHeldPolicy.ForceTake => LeaseDecision.Take, LockHeldPolicy.ReadOnly => LeaseDecision.ReadOnly, _ => LeaseDecision.FallbackLocal, }; } } public sealed class LockStamp { public string Device { get; } public int Pid { get; } public DateTime HeartbeatUtc { get; } public LockStamp(string device, int pid, DateTime heartbeatUtc) { Device = Sanitize(device); Pid = pid; HeartbeatUtc = heartbeatUtc.ToUniversalTime(); } public string Serialize() { return Device + "\n" + Pid.ToString(CultureInfo.InvariantCulture) + "\n" + HeartbeatUtc.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture); } public static bool TryParse(string text, out LockStamp? stamp) { stamp = null; if (string.IsNullOrWhiteSpace(text)) { return false; } string[] array = text.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }); if (array.Length < 3) { return false; } string device = array[0].Trim(); if (!int.TryParse(array[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return false; } if (!DateTime.TryParse(array[2].Trim(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result2)) { return false; } stamp = new LockStamp(device, result, result2); return true; } private static string Sanitize(string s) { if (!string.IsNullOrEmpty(s)) { return s.Replace("\r", " ").Replace("\n", " ").Trim(); } return string.Empty; } } public enum MountDecision { Live, Offline, CreateMarkerThenLive } public static class MountGuard { public static bool IsLive(bool markerExists, bool driveReady) { return markerExists && driveReady; } public static MountDecision Decide(bool markerExists, bool alreadyBootstrapped, bool shareHasData, bool driveReady, bool requireExistingMarker) { if (!driveReady) { return MountDecision.Offline; } if (markerExists) { return MountDecision.Live; } if (requireExistingMarker) { return MountDecision.Offline; } if (shareHasData) { return MountDecision.CreateMarkerThenLive; } if (!alreadyBootstrapped) { return MountDecision.CreateMarkerThenLive; } return MountDecision.Offline; } } public static class PathRebase { public const string SaveBase = "SaveGames"; public static string Target(string originalSavePath, string mountRoot) { string text = LastElement(originalSavePath); return TrimTrailingSeparators(mountRoot) + "/SaveGames/" + text; } public static string LastElement(string path) { if (string.IsNullOrEmpty(path)) { return string.Empty; } string text = TrimTrailingSeparators(path); int num = text.LastIndexOfAny(new char[2] { '/', '\\' }); if (num >= 0) { return text.Substring(num + 1); } return text; } private static string TrimTrailingSeparators(string path) { if (!string.IsNullOrEmpty(path)) { return path.TrimEnd('/', '\\'); } return path; } } public enum SyncAction { UpToDate, Pull, Push, Fork } public sealed class ReconcilePlan { public string Uid { get; } public SyncAction Action { get; } public IReadOnlyList CopyTimestamps { get; } public int NewGen { get; } public string NewHead { get; } public string LocalHead { get; } public string ShareHead { get; } public ReconcilePlan(string uid, SyncAction action, IReadOnlyList copy, int newGen, string newHead, string localHead, string shareHead) { Uid = uid; Action = action; CopyTimestamps = copy ?? Array.Empty(); NewGen = newGen; NewHead = newHead ?? string.Empty; LocalHead = localHead ?? string.Empty; ShareHead = shareHead ?? string.Empty; } } public static class Reconciler { public static ReconcilePlan Plan(string uid, CharTree? local, CharTree? share, LedgerEntry? ledger, LocalCharState? state) { string text = local?.Head; string text2 = share?.Head; int num = ledger?.Gen ?? 0; int num2 = state?.BasedOnGen ?? 0; bool flag = !string.IsNullOrEmpty(text); bool flag2 = !string.IsNullOrEmpty(text2); if (!flag && !flag2) { return Make(uid, SyncAction.UpToDate, Array.Empty(), num, "", text, text2); } if (flag && !flag2) { return Make(uid, SyncAction.Push, local.Snapshots, num + 1, text, text, text2); } if (!flag && flag2) { return Make(uid, SyncAction.Pull, share.Snapshots, num, text2, text, text2); } if (text == text2) { return Make(uid, SyncAction.UpToDate, Array.Empty(), Math.Max(num, num2), text, text, text2); } if (state == null) { if (share.Snapshots.Contains(text)) { return Make(uid, SyncAction.Pull, Missing(share, local), num, text2, text, text2); } if (local.Snapshots.Contains(text2)) { return Make(uid, SyncAction.Push, Missing(local, share), num + 1, text, text, text2); } return Make(uid, SyncAction.Fork, Array.Empty(), num, "", text, text2); } bool flag3 = text != state.LastPushedHead; bool flag4 = num > num2; if (flag3 && !flag4) { return Make(uid, SyncAction.Push, Missing(local, share), num2 + 1, text, text, text2); } if (!flag3 && flag4) { return Make(uid, SyncAction.Pull, Missing(share, local), num, text2, text, text2); } return Make(uid, SyncAction.Fork, Array.Empty(), num, "", text, text2); } public static bool LocalIsNewer(string localHead, string shareHead) { return string.CompareOrdinal(localHead ?? string.Empty, shareHead ?? string.Empty) > 0; } private static IReadOnlyList Missing(CharTree from, CharTree to) { HashSet have = new HashSet(to.Snapshots, StringComparer.Ordinal); return from.Snapshots.Where((string s) => !have.Contains(s)).ToList(); } private static ReconcilePlan Make(string uid, SyncAction a, IReadOnlyList copy, int newGen, string newHead, string? localHead, string? shareHead) { return new ReconcilePlan(uid, a, copy, newGen, newHead, localHead ?? "", shareHead ?? ""); } } public sealed class CharTree { public string Uid { get; } public IReadOnlyList Snapshots { get; } public string? Head { get { if (Snapshots.Count <= 0) { return null; } return Snapshots[Snapshots.Count - 1]; } } public bool IsEmpty => Snapshots.Count == 0; public CharTree(string uid, IEnumerable snapshots) { Uid = uid; Snapshots = snapshots.OrderBy((string s) => s, StringComparer.Ordinal).ToList(); } } public static class TreeScanner { public const string SavePrefix = "Save_"; public const string ManifestName = "Manifest.txt"; public const string DoneLine = "Done"; public static Dictionary Scan(string saveRoot) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (string.IsNullOrEmpty(saveRoot) || !Directory.Exists(saveRoot)) { return dictionary; } string[] directories = Directory.GetDirectories(saveRoot); foreach (string path in directories) { string text = LastSegment(path); if (!text.StartsWith("Save_", StringComparison.Ordinal)) { continue; } string text2 = text.Substring("Save_".Length); List list = new List(); string[] directories2 = Directory.GetDirectories(path); foreach (string text3 in directories2) { if (IsComplete(text3)) { list.Add(LastSegment(text3)); } } dictionary[text2] = new CharTree(text2, list); } return dictionary; } public static bool IsComplete(string snapshotDir) { try { string path = Path.Combine(snapshotDir, "Manifest.txt"); if (!File.Exists(path)) { return false; } string text = File.ReadAllLines(path).LastOrDefault((string l) => l.Trim().Length > 0); return text != null && text.Trim() == "Done"; } catch { return false; } } private static string LastSegment(string path) { return Path.GetFileName(path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); } } public static class SnapshotCopier { public const int DefaultKeep = 15; public static int CopySnapshots(string srcCharDir, string dstCharDir, IEnumerable timestamps) { Directory.CreateDirectory(dstCharDir); int num = 0; foreach (string timestamp in timestamps) { string text = Path.Combine(srcCharDir, timestamp); string text2 = Path.Combine(dstCharDir, timestamp); if (!Directory.Exists(text) || Directory.Exists(text2)) { continue; } string text3 = text2 + ".tmp"; try { if (Directory.Exists(text3)) { Directory.Delete(text3, recursive: true); } CopyDir(text, text3); Directory.Move(text3, text2); num++; } catch { try { if (Directory.Exists(text3)) { Directory.Delete(text3, recursive: true); } } catch { } throw; } } return num; } public static int RemoveSnapshots(string charDir, IEnumerable timestamps) { int num = 0; foreach (string timestamp in timestamps) { try { string path = Path.Combine(charDir, timestamp); if (Directory.Exists(path)) { Directory.Delete(path, recursive: true); num++; } } catch { } } return num; } public static int PruneTo(string charDir, int keep = 15) { if (!Directory.Exists(charDir)) { return 0; } List list = (from d in Directory.GetDirectories(charDir) select Path.GetFileName(d.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)) into n where !n.EndsWith(".tmp", StringComparison.Ordinal) select n).OrderBy((string n) => n, StringComparer.Ordinal).ToList(); int num = 0; for (int num2 = 0; num2 < list.Count - keep; num2++) { try { Directory.Delete(Path.Combine(charDir, list[num2]), recursive: true); num++; } catch { } } return num; } private static void CopyDir(string src, string dst) { Directory.CreateDirectory(dst); string[] files = Directory.GetFiles(src); foreach (string text in files) { File.Copy(text, Path.Combine(dst, Path.GetFileName(text)), overwrite: true); } files = Directory.GetDirectories(src); foreach (string text2 in files) { CopyDir(text2, Path.Combine(dst, Path.GetFileName(text2.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)))); } } } public sealed class LedgerEntry { public int Gen { get; } public string Head { get; } public string Device { get; } public LedgerEntry(int gen, string head, string device) { Gen = gen; Head = head ?? string.Empty; Device = device ?? string.Empty; } } public sealed class SyncLedger { private readonly Dictionary _entries = new Dictionary(StringComparer.Ordinal); public IEnumerable Uids => _entries.Keys; public LedgerEntry? Get(string uid) { if (!_entries.TryGetValue(uid, out LedgerEntry value)) { return null; } return value; } public void Set(string uid, LedgerEntry entry) { _entries[uid] = entry; } public string Serialize() { return string.Join("\n", from kv in _entries.OrderBy, string>((KeyValuePair kv) => kv.Key, StringComparer.Ordinal) select Escape(kv.Key) + "|" + kv.Value.Gen.ToString(CultureInfo.InvariantCulture) + "|" + Escape(kv.Value.Head) + "|" + Escape(kv.Value.Device)); } public static SyncLedger Parse(string text) { SyncLedger syncLedger = new SyncLedger(); if (string.IsNullOrEmpty(text)) { return syncLedger; } string[] array = text.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0) { string[] array2 = text2.Split(new char[1] { '|' }); if (array2.Length >= 4 && int.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { syncLedger._entries[Unescape(array2[0])] = new LedgerEntry(result, Unescape(array2[2]), Unescape(array2[3])); } } } return syncLedger; } private static string Escape(string s) { return (s ?? string.Empty).Replace("|", "%7C"); } private static string Unescape(string s) { return s.Replace("%7C", "|"); } } }