mirror of
https://github.com/par274/sharpemu.git
synced 2026-09-05 08:10:13 +08:00
[host] wired the sdl session, save-data paths and project references
This commit is contained in:
@@ -38,6 +38,7 @@ public static class SaveDataExports
|
||||
private static readonly object _memoryGate = new();
|
||||
private static readonly HashSet<int> _preparedTransactionResources = [];
|
||||
private static string? _titleId;
|
||||
private static int _legacySaveMigrationChecked;
|
||||
|
||||
public static void ConfigureApplicationInfo(string? titleId)
|
||||
{
|
||||
@@ -1115,7 +1116,7 @@ public static class SaveDataExports
|
||||
}
|
||||
|
||||
// Saves are keyed by title id only (single-user emulation) under
|
||||
// ~/SharpEmu/Saves/<titleId>/; userId is accepted for API fidelity but not
|
||||
// user/savedata/<titleId>/; userId is accepted for API fidelity but not
|
||||
// part of the host path.
|
||||
private static string ResolveTitleSaveRoot(int userId, string titleId) =>
|
||||
SaveDataStorage.TitleRoot(ResolveSaveDataRoot(), titleId);
|
||||
@@ -1133,7 +1134,24 @@ public static class SaveDataExports
|
||||
ctx.TryReadUInt64(address + 0x10, out offset);
|
||||
}
|
||||
|
||||
private static string ResolveSaveDataRoot() => SaveDataStorage.Root();
|
||||
private static string ResolveSaveDataRoot()
|
||||
{
|
||||
var root = SaveDataStorage.Root();
|
||||
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR")) &&
|
||||
Interlocked.Exchange(ref _legacySaveMigrationChecked, 1) == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
SaveDataStorage.MigrateLegacyLayout(root);
|
||||
}
|
||||
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
TraceSaveData($"migration_failed root='{root}' error='{exception.Message}'");
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string ResolveConfiguredTitleId()
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace SharpEmu.Libs.SaveData;
|
||||
|
||||
/// <summary>
|
||||
/// Host-side layout and metadata for PS5 save data. Saves live under
|
||||
/// <c>~/SharpEmu/Saves/<titleId>/<dirName>/</c> (overridable via
|
||||
/// <c>user/savedata/<titleId>/<dirName>/</c> next to the executable (overridable via
|
||||
/// <c>SHARPEMU_SAVEDATA_DIR</c>); the game's files are written directly inside a
|
||||
/// slot through the mounted <c>/savedata0</c> filesystem, and the PS5 UI
|
||||
/// metadata (title/subtitle/detail/userParam) plus icon live under
|
||||
@@ -17,19 +17,74 @@ namespace SharpEmu.Libs.SaveData;
|
||||
/// </summary>
|
||||
public static class SaveDataStorage
|
||||
{
|
||||
/// <summary>Root of all saves: the env override, else <c>~/SharpEmu/Saves</c>.</summary>
|
||||
/// <summary>Root of all saves: the env override, else the portable <c>user/savedata</c> directory.</summary>
|
||||
public static string Root(string? overrideDir = null)
|
||||
{
|
||||
var configured = overrideDir ?? Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR");
|
||||
var root = string.IsNullOrWhiteSpace(configured)
|
||||
? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"SharpEmu",
|
||||
"Saves")
|
||||
? Path.Combine(AppContext.BaseDirectory, "user", "savedata")
|
||||
: configured;
|
||||
return Path.GetFullPath(root);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports saves written by the short-lived profile layout and by the old
|
||||
/// numeric-user layout. Newer destination files are never overwritten.
|
||||
/// </summary>
|
||||
public static void MigrateLegacyLayout(string destinationRoot, string? profileRoot = null)
|
||||
{
|
||||
destinationRoot = Path.GetFullPath(destinationRoot);
|
||||
profileRoot ??= Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"SharpEmu",
|
||||
"Saves");
|
||||
|
||||
if (Directory.Exists(profileRoot) &&
|
||||
!string.Equals(Path.GetFullPath(profileRoot), destinationRoot, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
MergeDirectory(profileRoot, destinationRoot);
|
||||
}
|
||||
|
||||
if (!Directory.Exists(destinationRoot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var userRoot in Directory.EnumerateDirectories(destinationRoot).ToArray())
|
||||
{
|
||||
if (!uint.TryParse(Path.GetFileName(userRoot), out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var titleRoot in Directory.EnumerateDirectories(userRoot))
|
||||
{
|
||||
MergeDirectory(titleRoot, Path.Combine(destinationRoot, Path.GetFileName(titleRoot)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeDirectory(string sourceRoot, string destinationRoot)
|
||||
{
|
||||
Directory.CreateDirectory(destinationRoot);
|
||||
foreach (var sourceFile in Directory.EnumerateFiles(sourceRoot))
|
||||
{
|
||||
var destinationFile = Path.Combine(destinationRoot, Path.GetFileName(sourceFile));
|
||||
if (!File.Exists(destinationFile) ||
|
||||
File.GetLastWriteTimeUtc(sourceFile) > File.GetLastWriteTimeUtc(destinationFile))
|
||||
{
|
||||
File.Copy(sourceFile, destinationFile, overwrite: true);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sourceDirectory in Directory.EnumerateDirectories(sourceRoot))
|
||||
{
|
||||
MergeDirectory(
|
||||
sourceDirectory,
|
||||
Path.Combine(destinationRoot, Path.GetFileName(sourceDirectory)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Per-title directory: <c><root>/<titleId></c>.</summary>
|
||||
public static string TitleRoot(string root, string titleId) =>
|
||||
Path.Combine(root, Sanitize(titleId));
|
||||
|
||||
Reference in New Issue
Block a user