From c6127972f85761435e8e641d79732791608c5e8d Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Wed, 25 Jan 2023 23:34:36 -0700 Subject: [PATCH] Cleanup and update app save management for 15.x --- .../Fs/ApplicationSaveDataManagement.cs | 113 ++++++++---------- src/LibHac/Fs/Shim/SaveData.cs | 32 +++++ 2 files changed, 83 insertions(+), 62 deletions(-) diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index 8cc48fac..8a3fe7a4 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -12,9 +12,12 @@ namespace LibHac.Fs; /// /// Contains functions for ensuring that an application's save data exists and is the correct size. /// -/// Based on nnSdk 13.4.0 +/// Based on nnSdk 15.3.0 public static class ApplicationSaveDataManagement { + private const int LeftoverFreeSpaceRequiredForUserAndDeviceSaves = 0x4000; + private const int SaveDataOverheadSize = 0x4000; + private const int UserAndDeviceSaveDataOverheadSize = SaveDataOverheadSize + LeftoverFreeSpaceRequiredForUserAndDeviceSaves; private const int SaveDataBlockSize = 0x4000; private const int SaveDataExtensionSizeAlignment = 0x100000; // 1 MiB @@ -75,7 +78,7 @@ public static class ApplicationSaveDataManagement outRequiredSize += newSaveDataSizeDifference + CalculateSaveDataExtensionContextFileSize(newSaveDataSize, - newSaveDataJournalSize) + 0x8000; + newSaveDataJournalSize) + UserAndDeviceSaveDataOverheadSize; return ResultFs.UsableSpaceNotEnough.Log(); } @@ -97,15 +100,20 @@ public static class ApplicationSaveDataManagement if (res.IsSuccess()) return Result.Success; - if (ResultFs.UsableSpaceNotEnough.Includes(res)) + if (res.IsFailure()) { - res = fs.QuerySaveDataTotalSize(out long saveDataTotalSize, saveDataSize, saveDataJournalSize); - if (res.IsFailure()) return res.Miss(); + if (ResultFs.UsableSpaceNotEnough.Includes(res)) + { + res = fs.QuerySaveDataTotalSize(out long saveDataTotalSize, saveDataSize, saveDataJournalSize); + if (res.IsFailure()) return res.Miss(); + + inOutRequiredSize += RoundUpOccupationSize(saveDataTotalSize) + saveDataBaseSize; + } + else if (ResultFs.PathAlreadyExists.Includes(res)) + { + return Result.Success; + } - inOutRequiredSize += RoundUpOccupationSize(saveDataTotalSize) + saveDataBaseSize; - } - else if (!ResultFs.PathAlreadyExists.Includes(res)) - { return res.Miss(); } @@ -138,10 +146,12 @@ public static class ApplicationSaveDataManagement if (res.IsFailure()) { - if (!ResultFs.UsableSpaceNotEnough.Includes(res)) - return res.Miss(); + if (ResultFs.UsableSpaceNotEnough.Includes(res)) + { + inOutRequiredSize += requiredSize; + } - inOutRequiredSize += requiredSize; + return res.Miss(); } return Result.Success; @@ -217,7 +227,7 @@ public static class ApplicationSaveDataManagement Result CreateBcatStorageFunc() => fs.CreateBcatSaveData(applicationId, bcatDeliveryCacheStorageSize); res = EnsureAndExtendSaveData(fs, ref requiredSize, in filter, CreateBcatStorageFunc, - bcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize, 0x4000); + bcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize, SaveDataOverheadSize); if (res.IsFailure()) return res.Miss(); } @@ -328,7 +338,7 @@ public static class ApplicationSaveDataManagement if (ResultFs.UsableSpaceNotEnough.Includes(res)) { // Don't return this error. If there's not enough space we return Success along with - // The amount of space required to create the cache storage. + // the amount of space required to create the cache storage. } else if (ResultFs.SaveDataExtending.Includes(res)) { @@ -351,7 +361,7 @@ public static class ApplicationSaveDataManagement cacheStorageSize, cacheStorageJournalSize, SaveDataFlags.None); res = CreateSaveData(fs, ref requiredSize, CreateCacheFunc, cacheStorageSize, cacheStorageJournalSize, - 0x4000); + SaveDataOverheadSize); fs.Impl.AbortIfNeeded(res); if (res.IsFailure()) return res.Miss(); @@ -362,29 +372,29 @@ public static class ApplicationSaveDataManagement } private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, ref long outRequiredSize, - out CacheStorageTargetMedia targetMedia, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, + out CacheStorageTargetMedia outTargetMedia, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, long cacheStorageSize, long cacheStorageJournalSize, bool allowExisting) { - targetMedia = CacheStorageTargetMedia.SdCard; + outTargetMedia = CacheStorageTargetMedia.SdCard; long requiredSize = 0; // Check if the cache storage already exists - Result res = GetCacheStorageTargetMediaImpl(fs, out CacheStorageTargetMedia media, applicationId); + Result res = GetCacheStorageTargetMediaImpl(fs, out CacheStorageTargetMedia targetMedia, applicationId); if (res.IsFailure()) return res.Miss(); - if (media == CacheStorageTargetMedia.SdCard) + if (targetMedia == CacheStorageTargetMedia.SdCard) { // If it exists on the SD card, ensure it's large enough. - targetMedia = CacheStorageTargetMedia.SdCard; + outTargetMedia = CacheStorageTargetMedia.SdCard; res = TryCreateCacheStorage(fs, ref requiredSize, SaveDataSpaceId.SdUser, applicationId, saveDataOwnerId, index, cacheStorageSize, cacheStorageJournalSize, allowExisting); if (res.IsFailure()) return res.Miss(); } - else if (media == CacheStorageTargetMedia.Nand) + else if (targetMedia == CacheStorageTargetMedia.Nand) { // If it exists on the BIS, ensure it's large enough. - targetMedia = CacheStorageTargetMedia.Nand; + outTargetMedia = CacheStorageTargetMedia.Nand; res = TryCreateCacheStorage(fs, ref requiredSize, SaveDataSpaceId.User, applicationId, saveDataOwnerId, index, cacheStorageSize, cacheStorageJournalSize, allowExisting); @@ -396,13 +406,13 @@ public static class ApplicationSaveDataManagement bool isSdCardAccessible = fs.IsSdCardAccessible(); if (isSdCardAccessible) { - targetMedia = CacheStorageTargetMedia.SdCard; + outTargetMedia = CacheStorageTargetMedia.SdCard; Result CreateStorageOnSdCard() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdUser, saveDataOwnerId, index, cacheStorageSize, cacheStorageJournalSize, SaveDataFlags.None); res = CreateSaveData(fs, ref requiredSize, CreateStorageOnSdCard, cacheStorageSize, cacheStorageJournalSize, - 0x4000); + SaveDataOverheadSize); if (res.IsFailure()) return res.Miss(); // Don't use the SD card if it doesn't have enough space. @@ -414,17 +424,17 @@ public static class ApplicationSaveDataManagement if (!isSdCardAccessible) { requiredSize = 0; - targetMedia = CacheStorageTargetMedia.Nand; + outTargetMedia = CacheStorageTargetMedia.Nand; Result CreateStorageOnNand() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, saveDataOwnerId, index, cacheStorageSize, cacheStorageSize, SaveDataFlags.None); res = CreateSaveData(fs, ref requiredSize, CreateStorageOnNand, cacheStorageSize, cacheStorageJournalSize, - 0x4000); + SaveDataOverheadSize); if (res.IsFailure()) return res.Miss(); if (requiredSize != 0) - targetMedia = CacheStorageTargetMedia.None; + outTargetMedia = CacheStorageTargetMedia.None; } } @@ -453,7 +463,7 @@ public static class ApplicationSaveDataManagement fs.CreateDeviceSaveData(applicationId, applicationId.Value, saveDataSize, saveDataJournalSize, flags); res = EnsureAndExtendSaveData(fs.Fs, ref requiredSize, in filter, CreateSave, saveDataSize, saveDataJournalSize, - 0x4000); + SaveDataOverheadSize); if (res.IsFailure()) return res.Miss(); outRequiredSize = requiredSize; @@ -470,6 +480,7 @@ public static class ApplicationSaveDataManagement return Result.Success; } + // Removed in 15.x public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long outRequiredSize, out CacheStorageTargetMedia targetMedia, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, long cacheStorageSize, long cacheStorageJournalSize, bool allowExisting) @@ -492,7 +503,7 @@ public static class ApplicationSaveDataManagement Result res = EnsureApplicationCacheStorageImpl(fs, ref outRequiredSize, out _, applicationId, controlProperty.SaveDataOwnerId, index: 0, controlProperty.CacheStorageSize, - controlProperty.CacheStorageJournalSize, true); + controlProperty.CacheStorageJournalSize, allowExisting: true); fs.Impl.AbortIfNeeded(res); if (res.IsFailure()) return res.Miss(); @@ -544,7 +555,7 @@ public static class ApplicationSaveDataManagement } res = EnsureApplicationCacheStorageImpl(fs, ref outRequiredSize, out targetMedia, applicationId, - controlProperty.SaveDataOwnerId, index, cacheStorageSize, cacheStorageJournalSize, false); + controlProperty.SaveDataOwnerId, index, cacheStorageSize, cacheStorageJournalSize, allowExisting: false); fs.Impl.AbortIfNeeded(res); if (res.IsFailure()) return res.Miss(); @@ -554,34 +565,12 @@ public static class ApplicationSaveDataManagement public static Result CleanUpTemporaryStorage(this FileSystemClient fs) { - while (true) - { - Result res = SaveDataFilter.Make(out SaveDataFilter filter, programId: default, SaveDataType.Temporary, - userId: default, saveDataId: default, index: default); + Result res = fs.Impl.CleanUpTemporaryStorageImpl(); - fs.Impl.AbortIfNeeded(res); - if (res.IsFailure()) return res.Miss(); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); - // Try to find any temporary save data. - res = fs.Impl.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.Temporary, in filter); - - if (res.IsFailure()) - { - if (ResultFs.TargetNotFound.Includes(res)) - { - // No more save data found. We're done cleaning. - return Result.Success; - } - - fs.Impl.AbortIfNeeded(res); - return res.Miss(); - } - - // Delete the found save data. - res = fs.Impl.DeleteSaveData(SaveDataSpaceId.Temporary, info.SaveDataId); - fs.Impl.AbortIfNeeded(res); - if (res.IsFailure()) return res.Miss(); - } + return Result.Success; } public static Result EnsureApplicationBcatDeliveryCacheStorage(this FileSystemClient fs, out long outRequiredSize, @@ -636,7 +625,7 @@ public static class ApplicationSaveDataManagement if (res.IsFailure()) return res.Miss(); long baseSize = RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Account).GetSaveDataMetaSize()) + - 0x8000; + UserAndDeviceSaveDataOverheadSize; res = EnsureAndExtendSaveData(fs, ref requiredSize, in filter, CreateAccountSaveFunc, accountSaveDataSize, accountSaveJournalSize, baseSize); @@ -658,7 +647,7 @@ public static class ApplicationSaveDataManagement if (res.IsFailure()) return res.Miss(); long baseSize = RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Device).GetSaveDataMetaSize()) + - 0x8000; + UserAndDeviceSaveDataOverheadSize; long requiredSizeForDeviceSaveData = 0; res = EnsureAndExtendSaveData(fs, ref requiredSizeForDeviceSaveData, in filter, CreateDeviceSaveFunc, @@ -683,7 +672,7 @@ public static class ApplicationSaveDataManagement { requiredSizeForDeviceSaveData += RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Device).GetSaveDataMetaSize()) + - 0x4000; + SaveDataOverheadSize; } else { @@ -724,7 +713,7 @@ public static class ApplicationSaveDataManagement controlProperty.TemporaryStorageSize, saveDataJournalSize: 0); if (res.IsFailure()) return res.Miss(); - requiredSize = RoundUpOccupationSize(saveDataTotalSize) + 0x4000; + requiredSize = RoundUpOccupationSize(saveDataTotalSize) + SaveDataOverheadSize; return Result.Success; } @@ -759,8 +748,8 @@ public static class ApplicationSaveDataManagement } else { - // 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. + // If there was already insufficient space to create the previous saves, don't try to create a + // temporary save. Just calculate the space required, if any, to create it. res = SaveDataFilter.Make(out SaveDataFilter filter, applicationId.Value, SaveDataType.Temporary, userId: default, saveDataId: default, index: default); diff --git a/src/LibHac/Fs/Shim/SaveData.cs b/src/LibHac/Fs/Shim/SaveData.cs index 384c65da..bbbf6cbb 100644 --- a/src/LibHac/Fs/Shim/SaveData.cs +++ b/src/LibHac/Fs/Shim/SaveData.cs @@ -314,6 +314,38 @@ public static class SaveData return res.Miss(); } + public static Result CleanUpTemporaryStorageImpl(this FileSystemClientImpl fs) + { + while (true) + { + Result res = SaveDataFilter.Make(out SaveDataFilter filter, programId: default, SaveDataType.Temporary, + userId: default, saveDataId: default, index: default); + + fs.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + // Try to find any temporary save data. + res = fs.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.Temporary, in filter); + + if (res.IsFailure()) + { + if (ResultFs.TargetNotFound.Includes(res)) + { + // No more save data found. We're done cleaning. + return Result.Success; + } + + fs.AbortIfNeeded(res); + return res.Miss(); + } + + // Delete the found save data. + res = fs.DeleteSaveData(SaveDataSpaceId.Temporary, info.SaveDataId); + fs.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + } + } + public static Result MountTemporaryStorage(this FileSystemClient fs, U8Span mountName) { Result res;