[host] wired the sdl session, save-data paths and project references

This commit is contained in:
ParantezTech
2026-07-28 01:05:55 +03:00
parent 12432f8fa2
commit c32ba52ca6
14 changed files with 542 additions and 153 deletions
@@ -129,6 +129,24 @@ public static partial class KernelMemoryCompatExports
private static readonly HashSet<string> _negativeStatCache = new(HostFsPathComparer);
private static readonly ConcurrentDictionary<string, ulong> _aprFileSizeCache = new(HostFsPathComparer);
private static long _nextFileDescriptor = 2;
private static string _applicationTitleId = "UNKNOWN";
public static void ConfigureApplicationInfo(string? titleId)
{
var value = string.IsNullOrWhiteSpace(titleId) ? "UNKNOWN" : titleId.Trim();
Span<char> sanitized = value.Length <= 128
? stackalloc char[value.Length]
: new char[value.Length];
for (var index = 0; index < value.Length; index++)
{
var character = value[index];
sanitized[index] = char.IsAsciiLetterOrDigit(character) || character is '-' or '_'
? char.ToUpperInvariant(character)
: '_';
}
Volatile.Write(ref _applicationTitleId, new string(sanitized));
}
internal static int AllocateGuestFileDescriptor()
{
@@ -5350,7 +5368,7 @@ public static partial class KernelMemoryCompatExports
}
else
{
root = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs", "devlog", "app"));
root = Path.Combine(ResolveGameLogRoot(), "devlog", "app");
}
Directory.CreateDirectory(root);
@@ -5419,14 +5437,20 @@ public static partial class KernelMemoryCompatExports
}
else
{
root = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs", "hostapp"));
Environment.SetEnvironmentVariable(hostappVariableName, root);
root = Path.Combine(ResolveGameLogRoot(), "hostapp");
}
Directory.CreateDirectory(root);
return root;
}
private static string ResolveGameLogRoot() =>
Path.GetFullPath(Path.Combine(
AppContext.BaseDirectory,
"user",
"game_logs",
Volatile.Read(ref _applicationTitleId)));
private static string GetPerAppWritableRoot()
{
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
+20 -2
View File
@@ -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()
{
+61 -6
View File
@@ -8,7 +8,7 @@ namespace SharpEmu.Libs.SaveData;
/// <summary>
/// Host-side layout and metadata for PS5 save data. Saves live under
/// <c>~/SharpEmu/Saves/&lt;titleId&gt;/&lt;dirName&gt;/</c> (overridable via
/// <c>user/savedata/&lt;titleId&gt;/&lt;dirName&gt;/</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>&lt;root&gt;/&lt;titleId&gt;</c>.</summary>
public static string TitleRoot(string root, string titleId) =>
Path.Combine(root, Sanitize(titleId));
+3 -2
View File
@@ -5,7 +5,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\SharpEmu.LibAtrac9\SharpEmu.LibAtrac9.csproj" />
<ProjectReference Include="..\SharpEmu.HLE\SharpEmu.HLE.csproj" />
<ProjectReference Include="..\SharpEmu.Logging\SharpEmu.Logging.csproj" />
<ProjectReference Include="..\SharpEmu.ShaderCompiler\SharpEmu.ShaderCompiler.csproj" />
<ProjectReference Include="..\SharpEmu.ShaderCompiler.Metal\SharpEmu.ShaderCompiler.Metal.csproj" />
<ProjectReference Include="..\SharpEmu.ShaderCompiler.Vulkan\SharpEmu.ShaderCompiler.Vulkan.csproj" />
@@ -27,11 +29,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ItemGroup>
<PackageReference Include="FFmpeg.AutoGen" />
<PackageReference Include="NLayer" />
<PackageReference Include="Silk.NET.Input" />
<PackageReference Include="ppy.SDL3-CS" />
<PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="Silk.NET.Windowing" />
</ItemGroup>
<PropertyGroup>