From 1ff89d6482a6ec907bb983c1d2bc473bea546367 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Sun, 8 Sep 2019 23:33:40 +0200 Subject: [PATCH] Implement basic support of SystemSaveData and Cleanup IFileSystemProxy (#767) * Implement basic support of SystemSaveData and Cleanup IFileSystemProxy - Implement `OpenSystemSaveData` as a `IFileSystem` in `SaveHelper`: On real device, system saves data are stored encrypted, and we can't create an empty system save data for now. That's why if a user put his own dump of system save in `RyuFs\nand\system\save\`, we extract content in associated folder and open it as a `IFileSystem`. If the system save data don't exist, a folder is created. - Cleanup `IFileSystemProxy` by adding a Helper class. - Implement `GetSavePath` in `VirtualFileSystem` and remove `GetGameSavePath` in `SaveHelper`. * remove the forgotten I * Fix align --- Ryujinx.HLE/FileSystem/SaveDataType.cs | 2 +- Ryujinx.HLE/FileSystem/SaveHelper.cs | 63 +++--- Ryujinx.HLE/FileSystem/SaveInfo.cs | 15 +- Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 67 +++++-- .../HOS/Services/FspSrv/FileSystemHelper.cs | 144 ++++++++++++++ .../HOS/Services/FspSrv/IFileSystemProxy.cs | 184 +++++------------- 6 files changed, 280 insertions(+), 195 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Services/FspSrv/FileSystemHelper.cs diff --git a/Ryujinx.HLE/FileSystem/SaveDataType.cs b/Ryujinx.HLE/FileSystem/SaveDataType.cs index edfe8ab1d..2207fc23b 100644 --- a/Ryujinx.HLE/FileSystem/SaveDataType.cs +++ b/Ryujinx.HLE/FileSystem/SaveDataType.cs @@ -9,4 +9,4 @@ TemporaryStorage, CacheStorage } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/SaveHelper.cs b/Ryujinx.HLE/FileSystem/SaveHelper.cs index 51400458b..a10724982 100644 --- a/Ryujinx.HLE/FileSystem/SaveHelper.cs +++ b/Ryujinx.HLE/FileSystem/SaveHelper.cs @@ -1,45 +1,44 @@ -using Ryujinx.HLE.HOS; +using LibHac.Fs; +using Ryujinx.HLE.HOS; using System.IO; -using static Ryujinx.HLE.FileSystem.VirtualFileSystem; - namespace Ryujinx.HLE.FileSystem { static class SaveHelper { - public static string GetSavePath(SaveInfo saveMetaData, ServiceCtx context) + public static IFileSystem OpenSystemSaveData(ServiceCtx context, ulong saveId) { - string baseSavePath = NandPath; - ulong currentTitleId = saveMetaData.TitleId; + SaveInfo saveInfo = new SaveInfo(0, (long)saveId, SaveDataType.SystemSaveData, SaveSpaceId.NandSystem); + string savePath = context.Device.FileSystem.GetSavePath(context, saveInfo, false); - switch (saveMetaData.SaveSpaceId) + if (File.Exists(savePath)) { - case SaveSpaceId.NandUser: - baseSavePath = UserNandPath; - break; - case SaveSpaceId.NandSystem: - baseSavePath = SystemNandPath; - break; - case SaveSpaceId.SdCard: - baseSavePath = Path.Combine(SdCardPath, "Nintendo"); - break; + string tempDirectoryPath = $"{savePath}_temp"; + + Directory.CreateDirectory(tempDirectoryPath); + + IFileSystem outputFolder = new LocalFileSystem(tempDirectoryPath); + + using (LocalStorage systemSaveData = new LocalStorage(savePath, FileAccess.Read, FileMode.Open)) + { + IFileSystem saveFs = new LibHac.Fs.Save.SaveDataFileSystem(context.Device.System.KeySet, systemSaveData, IntegrityCheckLevel.None, false); + + saveFs.CopyFileSystem(outputFolder); + } + + File.Delete(savePath); + + Directory.Move(tempDirectoryPath, savePath); + } + else + { + if (!Directory.Exists(savePath)) + { + Directory.CreateDirectory(savePath); + } } - baseSavePath = Path.Combine(baseSavePath, "save"); - - if (saveMetaData.TitleId == 0 && saveMetaData.SaveDataType == SaveDataType.SaveData) - { - currentTitleId = context.Process.TitleId; - } - - string saveAccount = saveMetaData.UserId.IsNull ? "savecommon" : saveMetaData.UserId.ToString(); - - string savePath = Path.Combine(baseSavePath, - saveMetaData.SaveId.ToString("x16"), - saveAccount, - saveMetaData.SaveDataType == SaveDataType.SaveData ? currentTitleId.ToString("x16") : string.Empty); - - return savePath; + return new LocalFileSystem(savePath); } } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/SaveInfo.cs b/Ryujinx.HLE/FileSystem/SaveInfo.cs index 8685e6ca5..0fc355758 100644 --- a/Ryujinx.HLE/FileSystem/SaveInfo.cs +++ b/Ryujinx.HLE/FileSystem/SaveInfo.cs @@ -4,25 +4,24 @@ namespace Ryujinx.HLE.FileSystem { struct SaveInfo { - public ulong TitleId { get; private set; } - public long SaveId { get; private set; } - public UInt128 UserId { get; private set; } - + public ulong TitleId { get; private set; } + public long SaveId { get; private set; } public SaveDataType SaveDataType { get; private set; } public SaveSpaceId SaveSpaceId { get; private set; } + public UInt128 UserId { get; private set; } public SaveInfo( ulong titleId, long saveId, SaveDataType saveDataType, - UInt128 userId, - SaveSpaceId saveSpaceId) + SaveSpaceId saveSpaceId, + UInt128 userId = new UInt128()) { TitleId = titleId; - UserId = userId; SaveId = saveId; SaveDataType = saveDataType; SaveSpaceId = saveSpaceId; + UserId = userId; } } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index e71fc27f3..5511ebcc9 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -54,20 +54,48 @@ namespace Ryujinx.HLE.FileSystem return fullPath; } - public string GetSdCardPath() => MakeDirAndGetFullPath(SdCardPath); + public string GetSdCardPath() => MakeFullPath(SdCardPath); - public string GetNandPath() => MakeDirAndGetFullPath(NandPath); + public string GetNandPath() => MakeFullPath(NandPath); - public string GetSystemPath() => MakeDirAndGetFullPath(SystemPath); + public string GetSystemPath() => MakeFullPath(SystemPath); - internal string GetGameSavePath(SaveInfo save, ServiceCtx context) + internal string GetSavePath(ServiceCtx context, SaveInfo saveInfo, bool isDirectory = true) { - return MakeDirAndGetFullPath(SaveHelper.GetSavePath(save, context)); + string saveUserPath = ""; + string baseSavePath = NandPath; + ulong currentTitleId = saveInfo.TitleId; + + switch (saveInfo.SaveSpaceId) + { + case SaveSpaceId.NandUser: baseSavePath = UserNandPath; break; + case SaveSpaceId.NandSystem: baseSavePath = SystemNandPath; break; + case SaveSpaceId.SdCard: baseSavePath = Path.Combine(SdCardPath, "Nintendo"); break; + } + + baseSavePath = Path.Combine(baseSavePath, "save"); + + if (saveInfo.TitleId == 0 && saveInfo.SaveDataType == SaveDataType.SaveData) + { + currentTitleId = context.Process.TitleId; + } + + if (saveInfo.SaveSpaceId == SaveSpaceId.NandUser) + { + saveUserPath = saveInfo.UserId.IsNull ? "savecommon" : saveInfo.UserId.ToString(); + } + + string savePath = Path.Combine(baseSavePath, + saveInfo.SaveId.ToString("x16"), + saveUserPath, + saveInfo.SaveDataType == SaveDataType.SaveData ? currentTitleId.ToString("x16") : string.Empty); + + return MakeFullPath(savePath, isDirectory); } public string GetFullPartitionPath(string partitionPath) { - return MakeDirAndGetFullPath(partitionPath); + return MakeFullPath(partitionPath); } public string SwitchPathToSystemPath(string switchPath) @@ -79,7 +107,7 @@ namespace Ryujinx.HLE.FileSystem return null; } - return GetFullPath(MakeDirAndGetFullPath(parts[0]), parts[1]); + return GetFullPath(MakeFullPath(parts[0]), parts[1]); } public string SystemPathToSwitchPath(string systemPath) @@ -104,37 +132,40 @@ namespace Ryujinx.HLE.FileSystem return null; } - private string MakeDirAndGetFullPath(string dir) + private string MakeFullPath(string path, bool isDirectory = true) { // Handles Common Switch Content Paths - switch (dir) + switch (path) { case ContentPath.SdCard: case "@Sdcard": - dir = SdCardPath; + path = SdCardPath; break; case ContentPath.User: - dir = UserNandPath; + path = UserNandPath; break; case ContentPath.System: - dir = SystemNandPath; + path = SystemNandPath; break; case ContentPath.SdCardContent: - dir = Path.Combine(SdCardPath, "Nintendo", "Contents"); + path = Path.Combine(SdCardPath, "Nintendo", "Contents"); break; case ContentPath.UserContent: - dir = Path.Combine(UserNandPath, "Contents"); + path = Path.Combine(UserNandPath, "Contents"); break; case ContentPath.SystemContent: - dir = Path.Combine(SystemNandPath, "Contents"); + path = Path.Combine(SystemNandPath, "Contents"); break; } - string fullPath = Path.Combine(GetBasePath(), dir); + string fullPath = Path.Combine(GetBasePath(), path); - if (!Directory.Exists(fullPath)) + if (isDirectory) { - Directory.CreateDirectory(fullPath); + if (!Directory.Exists(fullPath)) + { + Directory.CreateDirectory(fullPath); + } } return fullPath; diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/FileSystemHelper.cs b/Ryujinx.HLE/HOS/Services/FspSrv/FileSystemHelper.cs new file mode 100644 index 000000000..d2e157f5a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/FspSrv/FileSystemHelper.cs @@ -0,0 +1,144 @@ +using LibHac; +using LibHac.Fs; +using LibHac.Fs.NcaUtils; +using Ryujinx.Common; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Utilities; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.FspSrv +{ + static class FileSystemHelper + { + public static ResultCode LoadSaveDataFileSystem(ServiceCtx context, bool readOnly, out IFileSystem loadedFileSystem) + { + loadedFileSystem = null; + + SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64(); + ulong titleId = context.RequestData.ReadUInt64(); + UInt128 userId = context.RequestData.ReadStruct(); + long saveId = context.RequestData.ReadInt64(); + SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte(); + SaveInfo saveInfo = new SaveInfo(titleId, saveId, saveDataType, saveSpaceId, userId); + string savePath = context.Device.FileSystem.GetSavePath(context, saveInfo); + + try + { + LocalFileSystem fileSystem = new LocalFileSystem(savePath); + LibHac.Fs.IFileSystem saveFileSystem = new DirectorySaveDataFileSystem(fileSystem); + + if (readOnly) + { + saveFileSystem = new ReadOnlyFileSystem(saveFileSystem); + } + + loadedFileSystem = new IFileSystem(saveFileSystem); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenNsp(ServiceCtx context, string pfsPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open); + PartitionFileSystem nsp = new PartitionFileSystem(storage); + + ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); + + openedFileSystem = new IFileSystem(nsp); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenNcaFs(ServiceCtx context, string ncaPath, LibHac.Fs.IStorage ncaStorage, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + Nca nca = new Nca(context.Device.System.KeySet, ncaStorage); + + if (!nca.SectionExists(NcaSectionType.Data)) + { + return ResultCode.PartitionNotFound; + } + + LibHac.Fs.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + + openedFileSystem = new IFileSystem(fileSystem); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenFileSystemFromInternalFile(ServiceCtx context, string fullPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent; + + while (string.IsNullOrWhiteSpace(archivePath.Extension)) + { + archivePath = archivePath.Parent; + } + + if (archivePath.Extension == ".nsp" && File.Exists(archivePath.FullName)) + { + FileStream pfsFile = new FileStream( + archivePath.FullName.TrimEnd(Path.DirectorySeparatorChar), + FileMode.Open, + FileAccess.Read); + + try + { + PartitionFileSystem nsp = new PartitionFileSystem(pfsFile.AsStorage()); + + ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); + + string filename = fullPath.Replace(archivePath.FullName, string.Empty).TrimStart('\\'); + + if (nsp.FileExists(filename)) + { + return OpenNcaFs(context, fullPath, nsp.OpenFile(filename, OpenMode.Read).AsStorage(), out openedFileSystem); + } + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + } + + return ResultCode.PathDoesNotExist; + } + + public static void ImportTitleKeysFromNsp(LibHac.Fs.IFileSystem nsp, Keyset keySet) + { + foreach (DirectoryEntry ticketEntry in nsp.EnumerateEntries("*.tik")) + { + Ticket ticket = new Ticket(nsp.OpenFile(ticketEntry.FullPath, OpenMode.Read).AsStream()); + + if (!keySet.TitleKeys.ContainsKey(ticket.RightsId)) + { + keySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(keySet)); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs index 3073d4c76..43f5d6477 100644 --- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs @@ -1,10 +1,8 @@ using LibHac; using LibHac.Fs; using LibHac.Fs.NcaUtils; -using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.Utilities; using System.IO; using static Ryujinx.HLE.FileSystem.VirtualFileSystem; @@ -38,7 +36,14 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv { if (fullPath.Contains(".")) { - return OpenFileSystemFromInternalFile(context, fullPath); + ResultCode result = FileSystemHelper.OpenFileSystemFromInternalFile(context, fullPath, out IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; } return ResultCode.PathDoesNotExist; @@ -49,11 +54,25 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv if (extension == ".nca") { - return OpenNcaFs(context, fullPath, fileStream.AsStorage()); + ResultCode result = FileSystemHelper.OpenNcaFs(context, fullPath, fileStream.AsStorage(), out IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; } else if (extension == ".nsp") { - return OpenNsp(context, fullPath); + ResultCode result = FileSystemHelper.OpenNsp(context, fullPath, out IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; } return ResultCode.InvalidInput; @@ -109,21 +128,42 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv // OpenSaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object saveDataFs public ResultCode OpenSaveDataFileSystem(ServiceCtx context) { - return LoadSaveDataFileSystem(context, false); + ResultCode result = FileSystemHelper.LoadSaveDataFileSystem(context, false, out IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; } [Command(52)] // OpenSaveDataFileSystemBySystemSaveDataId(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object systemSaveDataFs public ResultCode OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) { - return LoadSaveDataFileSystem(context, false); + ResultCode result = FileSystemHelper.LoadSaveDataFileSystem(context, false, out IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; } [Command(53)] // OpenReadOnlySaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct save_struct) -> object public ResultCode OpenReadOnlySaveDataFileSystem(ServiceCtx context) { - return LoadSaveDataFileSystem(context, true); + ResultCode result = FileSystemHelper.LoadSaveDataFileSystem(context, true, out IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; } [Command(200)] @@ -227,133 +267,5 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv return ResultCode.Success; } - - public ResultCode LoadSaveDataFileSystem(ServiceCtx context, bool readOnly) - { - SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64(); - - ulong titleId = context.RequestData.ReadUInt64(); - - UInt128 userId = context.RequestData.ReadStruct(); - - long saveId = context.RequestData.ReadInt64(); - SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte(); - SaveInfo saveInfo = new SaveInfo(titleId, saveId, saveDataType, userId, saveSpaceId); - string savePath = context.Device.FileSystem.GetGameSavePath(saveInfo, context); - - try - { - LocalFileSystem fileSystem = new LocalFileSystem(savePath); - LibHac.Fs.IFileSystem saveFileSystem = new DirectorySaveDataFileSystem(fileSystem); - - if (readOnly) - { - saveFileSystem = new ReadOnlyFileSystem(saveFileSystem); - } - - MakeObject(context, new IFileSystem(saveFileSystem)); - } - catch (HorizonResultException ex) - { - return (ResultCode)ex.ResultValue.Value; - } - - return ResultCode.Success; - } - - private ResultCode OpenNsp(ServiceCtx context, string pfsPath) - { - try - { - LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open); - PartitionFileSystem nsp = new PartitionFileSystem(storage); - - ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); - - IFileSystem nspFileSystem = new IFileSystem(nsp); - - MakeObject(context, nspFileSystem); - } - catch (HorizonResultException ex) - { - return (ResultCode)ex.ResultValue.Value; - } - - return ResultCode.Success; - } - - private ResultCode OpenNcaFs(ServiceCtx context, string ncaPath, LibHac.Fs.IStorage ncaStorage) - { - try - { - Nca nca = new Nca(context.Device.System.KeySet, ncaStorage); - - if (!nca.SectionExists(NcaSectionType.Data)) - { - return ResultCode.PartitionNotFound; - } - - LibHac.Fs.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); - - MakeObject(context, new IFileSystem(fileSystem)); - } - catch (HorizonResultException ex) - { - return (ResultCode)ex.ResultValue.Value; - } - - return ResultCode.Success; - } - - private ResultCode OpenFileSystemFromInternalFile(ServiceCtx context, string fullPath) - { - DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent; - - while (string.IsNullOrWhiteSpace(archivePath.Extension)) - { - archivePath = archivePath.Parent; - } - - if (archivePath.Extension == ".nsp" && File.Exists(archivePath.FullName)) - { - FileStream pfsFile = new FileStream( - archivePath.FullName.TrimEnd(Path.DirectorySeparatorChar), - FileMode.Open, - FileAccess.Read); - - try - { - PartitionFileSystem nsp = new PartitionFileSystem(pfsFile.AsStorage()); - - ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); - - string filename = fullPath.Replace(archivePath.FullName, string.Empty).TrimStart('\\'); - - if (nsp.FileExists(filename)) - { - return OpenNcaFs(context, fullPath, nsp.OpenFile(filename, OpenMode.Read).AsStorage()); - } - } - catch (HorizonResultException ex) - { - return (ResultCode)ex.ResultValue.Value; - } - } - - return ResultCode.PathDoesNotExist; - } - - private void ImportTitleKeysFromNsp(LibHac.Fs.IFileSystem nsp, Keyset keySet) - { - foreach (DirectoryEntry ticketEntry in nsp.EnumerateEntries("*.tik")) - { - Ticket ticket = new Ticket(nsp.OpenFile(ticketEntry.FullPath, OpenMode.Read).AsStream()); - - if (!keySet.TitleKeys.ContainsKey(ticket.RightsId)) - { - keySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(keySet)); - } - } - } } } \ No newline at end of file