228 lines
6.0 KiB
C#
228 lines
6.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
public static class LocalRecoveryMirror
|
|
{
|
|
private const string RecoveryDirectoryName = ".save_recovery";
|
|
|
|
public static void SaveJson<T>(string slotKey, T data)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(slotKey) || (!typeof(T).IsValueType && (object)data == null))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
string json = JsonUtility.ToJson(data, false);
|
|
SaveRaw(slotKey, json);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[LocalRecoveryMirror] SaveJson failed ({slotKey}): {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public static bool TryLoadJson<T>(string slotKey, out T data)
|
|
{
|
|
data = default(T);
|
|
if (string.IsNullOrWhiteSpace(slotKey))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string json;
|
|
if (!TryLoadRaw(slotKey, out json) || string.IsNullOrWhiteSpace(json))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
data = JsonUtility.FromJson<T>(json);
|
|
if (typeof(T).IsValueType)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return (object)data != null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[LocalRecoveryMirror] TryLoadJson failed ({slotKey}): {ex.Message}");
|
|
data = default(T);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static void SaveRaw(string slotKey, string json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(slotKey) || json == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
string path = GetCanonicalPath(slotKey);
|
|
string directory = Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrWhiteSpace(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
string backupPath = path + ".bak";
|
|
string tempPath = path + ".tmp";
|
|
File.WriteAllText(tempPath, json, Encoding.UTF8);
|
|
if (File.Exists(path))
|
|
{
|
|
File.Copy(path, backupPath, true);
|
|
}
|
|
|
|
File.Copy(tempPath, path, true);
|
|
File.Delete(tempPath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[LocalRecoveryMirror] SaveRaw failed ({slotKey}): {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public static bool TryLoadRaw(string slotKey, out string json)
|
|
{
|
|
json = null;
|
|
if (string.IsNullOrWhiteSpace(slotKey))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
IReadOnlyList<string> candidates = GetVariantPaths(slotKey);
|
|
for (int i = 0; i < candidates.Count; i++)
|
|
{
|
|
string path = candidates[i];
|
|
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
string loaded = File.ReadAllText(path, Encoding.UTF8);
|
|
if (string.IsNullOrWhiteSpace(loaded))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
json = loaded;
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[LocalRecoveryMirror] TryLoadRaw failed ({path}): {ex.Message}");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool HasSlotData(string slotKey)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(slotKey))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
IReadOnlyList<string> candidates = GetVariantPaths(slotKey);
|
|
for (int i = 0; i < candidates.Count; i++)
|
|
{
|
|
string path = candidates[i];
|
|
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static void DeleteSlot(string slotKey)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(slotKey))
|
|
{
|
|
return;
|
|
}
|
|
|
|
IReadOnlyList<string> candidates = GetVariantPaths(slotKey);
|
|
for (int i = 0; i < candidates.Count; i++)
|
|
{
|
|
string path = candidates[i];
|
|
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[LocalRecoveryMirror] DeleteSlot failed ({path}): {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string GetCanonicalPath(string slotKey)
|
|
{
|
|
string fileName = BuildFileName(slotKey);
|
|
return Path.Combine(SaveIdentityUtility.GetCanonicalPersistentRoot(), RecoveryDirectoryName, fileName);
|
|
}
|
|
|
|
private static IReadOnlyList<string> GetVariantPaths(string slotKey)
|
|
{
|
|
var result = new List<string>();
|
|
string fileName = BuildFileName(slotKey);
|
|
string backupFileName = fileName + ".bak";
|
|
IReadOnlyList<string> roots = SaveIdentityUtility.GetPersistentRootVariants();
|
|
for (int i = 0; i < roots.Count; i++)
|
|
{
|
|
string root = roots[i];
|
|
if (string.IsNullOrWhiteSpace(root))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AddDistinct(result, Path.Combine(root, RecoveryDirectoryName, fileName));
|
|
AddDistinct(result, Path.Combine(root, RecoveryDirectoryName, backupFileName));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static string BuildFileName(string slotKey)
|
|
{
|
|
return slotKey.Trim().Replace('/', '_').Replace('\\', '_').Replace(':', '_') + ".json";
|
|
}
|
|
|
|
private static void AddDistinct(List<string> target, string value)
|
|
{
|
|
if (target == null || string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < target.Count; i++)
|
|
{
|
|
if (string.Equals(target[i], value, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
target.Add(value);
|
|
}
|
|
}
|