From 6abe565de11194f25a7e9dd67007af967444ec97 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 5 Jan 2020 17:11:42 -0700 Subject: [PATCH] Add more savedata shim functions --- .../Fs/ApplicationSaveDataManagement.cs | 78 ++++++++-- src/LibHac/Fs/Shim/SaveData.cs | 141 +++++++++++++++++- src/LibHac/Fs/Shim/SaveDataManagement.cs | 75 +++++++++- src/LibHac/FsService/FileSystemProxy.cs | 6 +- 4 files changed, 274 insertions(+), 26 deletions(-) diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index 4da79096..0aea1754 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -15,6 +15,7 @@ namespace LibHac.Fs long requiredSizeSum = 0; + // If the application needs a user save if (uid != Uid.Zero && nacp.UserAccountSaveDataSize > 0) { var filter = new SaveDataFilter(); @@ -24,8 +25,10 @@ namespace LibHac.Fs Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + // If the save already exists if (rc.IsSuccess()) { + // Make sure the save is large enough rc = ExtendSaveDataIfNeeded(fs, out long requiredSizeUser, SaveDataSpaceId.User, saveDataInfo.SaveDataId, nacp.UserAccountSaveDataSize, nacp.UserAccountSaveDataJournalSize); @@ -45,6 +48,7 @@ namespace LibHac.Fs } else { + // The save doesn't exist, so try to create it UserId userId = ConvertAccountUidToFsUserId(uid); Result createRc = fs.CreateSaveData(applicationId, userId, nacp.SaveDataOwnerId, @@ -52,10 +56,16 @@ namespace LibHac.Fs if (createRc.IsFailure()) { + // If there's insufficient free space, calculate the space required to create the save if (ResultRangeFs.InsufficientFreeSpace.Contains(createRc)) { - // todo: Call QuerySaveDataTotalSize and assign the value to requiredSizeSum - requiredSizeSum = 0; + Result queryRc = fs.QuerySaveDataTotalSize(out long userAccountTotalSize, + nacp.UserAccountSaveDataSize, nacp.UserAccountSaveDataJournalSize); + + if (queryRc.IsFailure()) return queryRc; + + // The 0x4c000 includes the save meta and other stuff + requiredSizeSum = Util.AlignUp(userAccountTotalSize, 0x4000) + 0x4c000; } else if (createRc == ResultFs.PathAlreadyExists) { @@ -105,8 +115,13 @@ namespace LibHac.Fs { if (ResultRangeFs.InsufficientFreeSpace.Contains(createRc)) { - // todo: Call QuerySaveDataTotalSize and add the value to requiredSizeSum - requiredSizeSum += 0; + Result queryRc = fs.QuerySaveDataTotalSize(out long deviceSaveTotalSize, + nacp.DeviceSaveDataSize, nacp.DeviceSaveDataJournalSize); + + if (queryRc.IsFailure()) return queryRc; + + // Not sure what the additional 0x4000 is + requiredSizeSum += Util.AlignUp(deviceSaveTotalSize, 0x4000) + 0x4000; } else if (createRc == ResultFs.PathAlreadyExists) { @@ -139,21 +154,27 @@ namespace LibHac.Fs { if (requiredSizeSum > 0) { + // If there was already insufficient space to create the previous saves, check if the temp + // save already exists instead of trying to create a new one. var filter = new SaveDataFilter(); filter.SetTitleId(applicationId); filter.SetSaveDataType(SaveDataType.Temporary); - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.User, ref filter); + Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.Temporary, ref filter); if (rc.IsFailure()) { - if(rc != ResultFs.TargetNotFound) + if (rc != ResultFs.TargetNotFound) { return rc; } - // todo: Call QuerySaveDataTotalSize and add the value to requiredSizeSum - requiredSizeSum += 0; + Result queryRc = fs.QuerySaveDataTotalSize(out long tempSaveTotalSize, + nacp.TemporaryStorageSize, 0); + + if (queryRc.IsFailure()) return queryRc; + + requiredSizeSum += Util.AlignUp(tempSaveTotalSize, 0x4000) + 0x4000; } } else @@ -165,8 +186,12 @@ namespace LibHac.Fs { if (ResultRangeFs.InsufficientFreeSpace.Contains(createRc)) { - // todo: Call QuerySaveDataTotalSize and assign the value to requiredSizeSum - requiredSizeSum += 0; + Result queryRc = fs.QuerySaveDataTotalSize(out long tempSaveTotalSize, + nacp.TemporaryStorageSize, 0); + + if (queryRc.IsFailure()) return queryRc; + + requiredSizeSum += Util.AlignUp(tempSaveTotalSize, 0x4000) + 0x4000; } else if (createRc == ResultFs.PathAlreadyExists) { @@ -219,7 +244,7 @@ namespace LibHac.Fs if (rc.IsSuccess()) { rc = ExtendSaveDataIfNeeded(fs, out long requiredSizeBcat, SaveDataSpaceId.User, - saveDataInfo.SaveDataId, nacp.DeviceSaveDataSize, bcatDeliveryCacheJournalSize); + saveDataInfo.SaveDataId, nacp.BcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize); if (rc.IsFailure()) { @@ -243,8 +268,12 @@ namespace LibHac.Fs { if (ResultRangeFs.InsufficientFreeSpace.Contains(createRc)) { - // todo: Call QuerySaveDataTotalSize and assign the value to requiredSize - requiredSize = 0; + Result queryRc = fs.QuerySaveDataTotalSize(out long saveTotalSize, + nacp.BcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize); + + if (queryRc.IsFailure()) return queryRc; + + requiredSize = Util.AlignUp(saveTotalSize, 0x4000) + 0x4000; } else if (createRc == ResultFs.PathAlreadyExists) { @@ -260,6 +289,29 @@ namespace LibHac.Fs return requiredSize > 0 ? ResultFs.InsufficientFreeSpace.Log() : Result.Success; } + public static Result CleanUpTemporaryStorage(FileSystemClient fs) + { + var filter = new SaveDataFilter(); + filter.SetSaveDataType(SaveDataType.Temporary); + + Result rc; + + while (true) + { + rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveInfo, SaveDataSpaceId.Temporary, ref filter); + + if (rc.IsFailure()) break; + + rc = fs.DeleteSaveData(SaveDataSpaceId.Temporary, saveInfo.SaveDataId); + if (rc.IsFailure()) return rc; + } + + if (rc == ResultFs.TargetNotFound) + return Result.Success; + + return rc; + } + public static UserId ConvertAccountUidToFsUserId(Uid uid) { return new UserId(uid.Id.High, uid.Id.Low); diff --git a/src/LibHac/Fs/Shim/SaveData.cs b/src/LibHac/Fs/Shim/SaveData.cs index f4920bc7..6f4ff330 100644 --- a/src/LibHac/Fs/Shim/SaveData.cs +++ b/src/LibHac/Fs/Shim/SaveData.cs @@ -7,21 +7,21 @@ namespace LibHac.Fs.Shim { public static class SaveData { - public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, TitleId titleId, UserId userId) + public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, TitleId applicationId, UserId userId) { Result rc; if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) { TimeSpan startTime = fs.Time.GetCurrent(); - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.Account, false, 0); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, false, 0); TimeSpan endTime = fs.Time.GetCurrent(); - fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{titleId}, userid: 0x{userId}"); + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, userid: 0x{userId}"); } else { - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.Account, false, 0); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, false, 0); } if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application)) @@ -32,21 +32,21 @@ namespace LibHac.Fs.Shim return rc; } - public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName, TitleId titleId, UserId userId) + public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName, TitleId applicationId, UserId userId) { Result rc; if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) { TimeSpan startTime = fs.Time.GetCurrent(); - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.Account, true, 0); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, true, 0); TimeSpan endTime = fs.Time.GetCurrent(); - fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{titleId}, userid: 0x{userId}"); + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, userid: 0x{userId}"); } else { - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.Account, false, 0); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, false, 0); } if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application)) @@ -57,6 +57,131 @@ namespace LibHac.Fs.Shim return rc; } + public static Result MountTemporaryStorage(this FileSystemClient fs, U8Span mountName) + { + Result rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.Temporary, default, default, SaveDataType.Temporary, false, 0); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\""); + } + else + { + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.Temporary, default, default, SaveDataType.Temporary, false, 0); + } + + if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName) + { + Result rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, 0); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\""); + } + else + { + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, 0); + } + + if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, int index) + { + Result rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (short)index); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", index: {index}"); + } + else + { + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (short)index); + } + + if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, TitleId applicationId) + { + Result rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.System)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, 0); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}"); + } + else + { + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, 0); + } + + if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, TitleId applicationId, int index) + { + Result rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.System)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (short)index); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, index: {index}"); + } + else + { + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (short)index); + } + + if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return rc; + } + private static Result MountSaveDataImpl(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, TitleId titleId, UserId userId, SaveDataType type, bool openReadOnly, short index) { diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index 2e1a7f15..85d0f7b8 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -41,7 +41,7 @@ namespace LibHac.Fs.Shim return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo); }, - () => $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:x8}"); + () => $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); } public static Result CreateSaveData(this FileSystemClient fs, TitleId applicationId, UserId userId, TitleId ownerId, @@ -77,7 +77,7 @@ namespace LibHac.Fs.Shim return fsProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref createInfo, ref metaInfo, ref hashSalt); }, - () => $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:x8}"); + () => $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); } public static Result CreateBcatSaveData(this FileSystemClient fs, TitleId applicationId, long size) @@ -138,7 +138,7 @@ namespace LibHac.Fs.Shim return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo); }, - () => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:x8}"); + () => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); } public static Result CreateTemporaryStorage(this FileSystemClient fs, TitleId applicationId, TitleId ownerId, long size, SaveDataFlags flags) @@ -167,7 +167,51 @@ namespace LibHac.Fs.Shim return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo); }, - () => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_flags: 0x{(int)flags:x8}"); + () => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_flags: 0x{(int)flags:X8}"); + } + + public static Result CreateCacheStorage(this FileSystemClient fs, TitleId applicationId, + SaveDataSpaceId spaceId, TitleId ownerId, short index, long size, long journalSize, SaveDataFlags flags) + { + return fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + var attribute = new SaveDataAttribute + { + TitleId = applicationId, + Type = SaveDataType.Cache, + Index = index + }; + + var creationInfo = new SaveDataCreationInfo + { + Size = size, + JournalSize = journalSize, + BlockSize = 0x4000, + OwnerId = ownerId, + Flags = flags, + SpaceId = spaceId + }; + + var metaInfo = new SaveMetaCreateInfo(); + + return fsProxy.CreateSaveDataFileSystem(ref attribute, ref creationInfo, ref metaInfo); + }, + () => $", applicationid: 0x{applicationId.Value:X}, savedataspaceid: {spaceId}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); + } + + public static Result CreateCacheStorage(this FileSystemClient fs, TitleId applicationId, + SaveDataSpaceId spaceId, TitleId ownerId, long size, long journalSize, SaveDataFlags flags) + { + return CreateCacheStorage(fs, applicationId, spaceId, ownerId, 0, size, journalSize, flags); + } + + public static Result CreateCacheStorage(this FileSystemClient fs, TitleId applicationId, TitleId ownerId, + long size, long journalSize, SaveDataFlags flags) + { + return CreateCacheStorage(fs, applicationId, SaveDataSpaceId.User, ownerId, 0, size, journalSize, flags); } public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, @@ -285,6 +329,29 @@ namespace LibHac.Fs.Shim return result; } + public static Result QuerySaveDataTotalSize(this FileSystemClient fs, out long totalSize, long size, long journalSize) + { + totalSize = default; + + long totalSizeTemp = 0; + + Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + return fsProxy.QuerySaveDataTotalSize(out totalSizeTemp, size, journalSize); + }, + () => $", save_data_size: {size}, save_data_journal_size: {journalSize}"); + + if (result.IsSuccess()) + { + totalSize = totalSizeTemp; + } + + return result; + } + public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId) { var tempIterator = new SaveDataIterator(); diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs index 23ee446c..eaf4b4b6 100644 --- a/src/LibHac/FsService/FileSystemProxy.cs +++ b/src/LibHac/FsService/FileSystemProxy.cs @@ -929,7 +929,11 @@ namespace LibHac.FsService public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) { - throw new NotImplementedException(); + // todo: Implement properly + + totalSize = 0; + + return Result.Success; } public Result SetCurrentPosixTimeWithTimeDifference(long time, int difference)