linked titleId to Save File

This commit is contained in:
BeZide93
2025-09-25 17:10:03 -05:00
committed by KeatonTheBot
parent eee4a6272c
commit c125d08636
+149 -17
View File
@@ -35,6 +35,7 @@ using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using Path = System.IO.Path;
namespace LibKenjinx
@@ -209,13 +210,13 @@ namespace LibKenjinx
GetControlFsAndTitleId(pfs, out IFileSystem? controlFs, out string? id);
gameInfo.TitleId = id;
if (controlFs == null)
{
Logger.Error?.Print(LogClass.Application, $"No control FS was returned. Unable to process game any further: {gameInfo.TitleName}");
return null;
}
// Check if there is an update available.
if (IsUpdateApplied(gameInfo.TitleId, out IFileSystem? updatedControlFs))
{
@@ -516,17 +517,17 @@ namespace LibKenjinx
{
FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
IFileSystem pfs = null;
if(Path.GetExtension(updatePath).ToLower() == ".xci")
if (Path.GetExtension(updatePath).ToLower() == ".xci")
{
pfs = new Xci(fileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
pfs = new Xci(fileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
}
else
{
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
}
return GetGameUpdateDataFromPartition(fileSystem, pfs, titleIdBase.ToString("x16"), programIndex);
@@ -625,7 +626,7 @@ namespace LibKenjinx
{
return new Nca(SwitchDevice?.VirtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
catch (Exception)
{
}
@@ -879,21 +880,152 @@ namespace LibKenjinx
control.SaveDataOwnerId = applicationId.Value;
}
LibHac.Result resultCode = LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
if (resultCode.IsFailure())
// --- Prepare paths for the physical save directory (Android sandbox)
string savesRoot = Path.Combine(
AppDataManager.BaseDirPath,
Ryujinx.HLE.FileSystem.VirtualFileSystem.UserNandPath,
"save"
);
// Remember the previous list of existing save dirs (to recognize new creation)
string[] before = Array.Empty<string>();
try
{
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
if (Directory.Exists(savesRoot))
before = Directory.GetDirectories(savesRoot);
}
catch { /* ignore */ }
// Call existing Horizon APIs to create/secure the saves
var rc = LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
if (rc.IsFailure())
{
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}");
}
Uid userId = AccountManager.LastOpenedUser.UserId.ToLibHacUid();
resultCode = LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId);
if (resultCode.IsFailure())
rc = LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId);
if (rc.IsFailure())
{
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}");
}
// Get the after-list of save dirs and calculate the difference
string? createdSaveDirName = null;
try
{
Directory.CreateDirectory(savesRoot);
var after = Directory.GetDirectories(savesRoot);
var beforeSet = new HashSet<string>(before, StringComparer.OrdinalIgnoreCase);
foreach (var d in after)
{
if (!beforeSet.Contains(d))
{
// This is most likely the newly created save folder
createdSaveDirName = Path.GetFileName(d);
break;
}
}
}
catch
{
// If the listing fails, we simply continue without detection.
}
// TitleId string normalized
string titleIdHex = titleId.ToString("x16");
// Marker file in the save folder + central mapping under .../save/_titleid_map.json
try
{
// 1) If we have recognized the newly created folder: Put a marker in
if (!string.IsNullOrEmpty(createdSaveDirName))
{
string markerFile = Path.Combine(savesRoot, createdSaveDirName, "TITLEID.txt");
File.WriteAllText(markerFile, titleIdHex);
}
// 2) Load/update mapping file
WriteOrUpdateTitleMapJson(savesRoot, titleIdHex, createdSaveDirName);
}
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.Application, $"Save TitleId mapping write failed: {ex.Message}");
}
}
// ---------- Helper for TitleId→SaveId mapping ----------
private sealed class TitleMap
{
public Dictionary<string, string> Map { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
private static void WriteOrUpdateTitleMapJson(string savesRoot, string titleIdHex, string? createdSaveDirName)
{
string mapFile = Path.Combine(savesRoot, "_titleid_map.json");
TitleMap map;
try
{
if (File.Exists(mapFile))
{
var json = File.ReadAllText(mapFile);
map = JsonSerializer.Deserialize<TitleMap>(json) ?? new TitleMap();
}
else
{
map = new TitleMap();
}
}
catch
{
map = new TitleMap();
}
// If we know the newly created folder, use this information.
// Otherwise, do not overwrite anything (existing mapping remains intact).
if (!string.IsNullOrEmpty(createdSaveDirName))
{
map.Map[titleIdHex] = createdSaveDirName!;
}
else if (!map.Map.ContainsKey(titleIdHex))
{
// Heuristic: Scan folder with TITLEID.txt and assign if necessary
try
{
foreach (var dir in Directory.GetDirectories(savesRoot))
{
var marker = Path.Combine(dir, "TITLEID.txt");
if (File.Exists(marker))
{
var txt = File.ReadAllText(marker).Trim();
if (string.Equals(txt, titleIdHex, StringComparison.OrdinalIgnoreCase))
{
map.Map[titleIdHex] = Path.GetFileName(dir);
break;
}
}
}
}
catch { /* ignore */ }
}
try
{
var json = JsonSerializer.Serialize(map, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(mapFile, json);
}
catch
{
// ignore mapping is “best effort”.
}
}
// -------------------------------------------------------
internal void ReloadFileSystem()
{
VirtualFileSystem.ReloadKeySet();