Cleanup and update app save management for 15.x

This commit is contained in:
Alex Barney 2023-01-25 23:34:36 -07:00
parent 903942b2f4
commit c6127972f8
2 changed files with 83 additions and 62 deletions

View file

@ -12,9 +12,12 @@ namespace LibHac.Fs;
/// <summary> /// <summary>
/// Contains functions for ensuring that an application's save data exists and is the correct size. /// Contains functions for ensuring that an application's save data exists and is the correct size.
/// </summary> /// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks> /// <remarks>Based on nnSdk 15.3.0</remarks>
public static class ApplicationSaveDataManagement 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 SaveDataBlockSize = 0x4000;
private const int SaveDataExtensionSizeAlignment = 0x100000; // 1 MiB private const int SaveDataExtensionSizeAlignment = 0x100000; // 1 MiB
@ -75,7 +78,7 @@ public static class ApplicationSaveDataManagement
outRequiredSize += newSaveDataSizeDifference + outRequiredSize += newSaveDataSizeDifference +
CalculateSaveDataExtensionContextFileSize(newSaveDataSize, CalculateSaveDataExtensionContextFileSize(newSaveDataSize,
newSaveDataJournalSize) + 0x8000; newSaveDataJournalSize) + UserAndDeviceSaveDataOverheadSize;
return ResultFs.UsableSpaceNotEnough.Log(); return ResultFs.UsableSpaceNotEnough.Log();
} }
@ -97,6 +100,8 @@ public static class ApplicationSaveDataManagement
if (res.IsSuccess()) if (res.IsSuccess())
return Result.Success; return Result.Success;
if (res.IsFailure())
{
if (ResultFs.UsableSpaceNotEnough.Includes(res)) if (ResultFs.UsableSpaceNotEnough.Includes(res))
{ {
res = fs.QuerySaveDataTotalSize(out long saveDataTotalSize, saveDataSize, saveDataJournalSize); res = fs.QuerySaveDataTotalSize(out long saveDataTotalSize, saveDataSize, saveDataJournalSize);
@ -104,8 +109,11 @@ public static class ApplicationSaveDataManagement
inOutRequiredSize += RoundUpOccupationSize(saveDataTotalSize) + saveDataBaseSize; inOutRequiredSize += RoundUpOccupationSize(saveDataTotalSize) + saveDataBaseSize;
} }
else if (!ResultFs.PathAlreadyExists.Includes(res)) else if (ResultFs.PathAlreadyExists.Includes(res))
{ {
return Result.Success;
}
return res.Miss(); return res.Miss();
} }
@ -138,12 +146,14 @@ public static class ApplicationSaveDataManagement
if (res.IsFailure()) if (res.IsFailure())
{ {
if (!ResultFs.UsableSpaceNotEnough.Includes(res)) if (ResultFs.UsableSpaceNotEnough.Includes(res))
return res.Miss(); {
inOutRequiredSize += requiredSize; inOutRequiredSize += requiredSize;
} }
return res.Miss();
}
return Result.Success; return Result.Success;
} }
@ -217,7 +227,7 @@ public static class ApplicationSaveDataManagement
Result CreateBcatStorageFunc() => fs.CreateBcatSaveData(applicationId, bcatDeliveryCacheStorageSize); Result CreateBcatStorageFunc() => fs.CreateBcatSaveData(applicationId, bcatDeliveryCacheStorageSize);
res = EnsureAndExtendSaveData(fs, ref requiredSize, in filter, CreateBcatStorageFunc, res = EnsureAndExtendSaveData(fs, ref requiredSize, in filter, CreateBcatStorageFunc,
bcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize, 0x4000); bcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize, SaveDataOverheadSize);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
} }
@ -328,7 +338,7 @@ public static class ApplicationSaveDataManagement
if (ResultFs.UsableSpaceNotEnough.Includes(res)) if (ResultFs.UsableSpaceNotEnough.Includes(res))
{ {
// Don't return this error. If there's not enough space we return Success along with // 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)) else if (ResultFs.SaveDataExtending.Includes(res))
{ {
@ -351,7 +361,7 @@ public static class ApplicationSaveDataManagement
cacheStorageSize, cacheStorageJournalSize, SaveDataFlags.None); cacheStorageSize, cacheStorageJournalSize, SaveDataFlags.None);
res = CreateSaveData(fs, ref requiredSize, CreateCacheFunc, cacheStorageSize, cacheStorageJournalSize, res = CreateSaveData(fs, ref requiredSize, CreateCacheFunc, cacheStorageSize, cacheStorageJournalSize,
0x4000); SaveDataOverheadSize);
fs.Impl.AbortIfNeeded(res); fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
@ -362,29 +372,29 @@ public static class ApplicationSaveDataManagement
} }
private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, ref long outRequiredSize, 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) long cacheStorageSize, long cacheStorageJournalSize, bool allowExisting)
{ {
targetMedia = CacheStorageTargetMedia.SdCard; outTargetMedia = CacheStorageTargetMedia.SdCard;
long requiredSize = 0; long requiredSize = 0;
// Check if the cache storage already exists // 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 (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. // 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, res = TryCreateCacheStorage(fs, ref requiredSize, SaveDataSpaceId.SdUser, applicationId, saveDataOwnerId,
index, cacheStorageSize, cacheStorageJournalSize, allowExisting); index, cacheStorageSize, cacheStorageJournalSize, allowExisting);
if (res.IsFailure()) return res.Miss(); 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. // 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, res = TryCreateCacheStorage(fs, ref requiredSize, SaveDataSpaceId.User, applicationId, saveDataOwnerId,
index, cacheStorageSize, cacheStorageJournalSize, allowExisting); index, cacheStorageSize, cacheStorageJournalSize, allowExisting);
@ -396,13 +406,13 @@ public static class ApplicationSaveDataManagement
bool isSdCardAccessible = fs.IsSdCardAccessible(); bool isSdCardAccessible = fs.IsSdCardAccessible();
if (isSdCardAccessible) if (isSdCardAccessible)
{ {
targetMedia = CacheStorageTargetMedia.SdCard; outTargetMedia = CacheStorageTargetMedia.SdCard;
Result CreateStorageOnSdCard() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdUser, Result CreateStorageOnSdCard() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdUser,
saveDataOwnerId, index, cacheStorageSize, cacheStorageJournalSize, SaveDataFlags.None); saveDataOwnerId, index, cacheStorageSize, cacheStorageJournalSize, SaveDataFlags.None);
res = CreateSaveData(fs, ref requiredSize, CreateStorageOnSdCard, cacheStorageSize, cacheStorageJournalSize, res = CreateSaveData(fs, ref requiredSize, CreateStorageOnSdCard, cacheStorageSize, cacheStorageJournalSize,
0x4000); SaveDataOverheadSize);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
// Don't use the SD card if it doesn't have enough space. // Don't use the SD card if it doesn't have enough space.
@ -414,17 +424,17 @@ public static class ApplicationSaveDataManagement
if (!isSdCardAccessible) if (!isSdCardAccessible)
{ {
requiredSize = 0; requiredSize = 0;
targetMedia = CacheStorageTargetMedia.Nand; outTargetMedia = CacheStorageTargetMedia.Nand;
Result CreateStorageOnNand() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, saveDataOwnerId, Result CreateStorageOnNand() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, saveDataOwnerId,
index, cacheStorageSize, cacheStorageSize, SaveDataFlags.None); index, cacheStorageSize, cacheStorageSize, SaveDataFlags.None);
res = CreateSaveData(fs, ref requiredSize, CreateStorageOnNand, cacheStorageSize, cacheStorageJournalSize, res = CreateSaveData(fs, ref requiredSize, CreateStorageOnNand, cacheStorageSize, cacheStorageJournalSize,
0x4000); SaveDataOverheadSize);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
if (requiredSize != 0) 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); fs.CreateDeviceSaveData(applicationId, applicationId.Value, saveDataSize, saveDataJournalSize, flags);
res = EnsureAndExtendSaveData(fs.Fs, ref requiredSize, in filter, CreateSave, saveDataSize, saveDataJournalSize, res = EnsureAndExtendSaveData(fs.Fs, ref requiredSize, in filter, CreateSave, saveDataSize, saveDataJournalSize,
0x4000); SaveDataOverheadSize);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
outRequiredSize = requiredSize; outRequiredSize = requiredSize;
@ -470,6 +480,7 @@ public static class ApplicationSaveDataManagement
return Result.Success; return Result.Success;
} }
// Removed in 15.x
public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long outRequiredSize, public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long outRequiredSize,
out CacheStorageTargetMedia targetMedia, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, out CacheStorageTargetMedia targetMedia, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index,
long cacheStorageSize, long cacheStorageJournalSize, bool allowExisting) long cacheStorageSize, long cacheStorageJournalSize, bool allowExisting)
@ -492,7 +503,7 @@ public static class ApplicationSaveDataManagement
Result res = EnsureApplicationCacheStorageImpl(fs, ref outRequiredSize, out _, applicationId, Result res = EnsureApplicationCacheStorageImpl(fs, ref outRequiredSize, out _, applicationId,
controlProperty.SaveDataOwnerId, index: 0, controlProperty.CacheStorageSize, controlProperty.SaveDataOwnerId, index: 0, controlProperty.CacheStorageSize,
controlProperty.CacheStorageJournalSize, true); controlProperty.CacheStorageJournalSize, allowExisting: true);
fs.Impl.AbortIfNeeded(res); fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
@ -544,7 +555,7 @@ public static class ApplicationSaveDataManagement
} }
res = EnsureApplicationCacheStorageImpl(fs, ref outRequiredSize, out targetMedia, applicationId, 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); fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
@ -554,36 +565,14 @@ public static class ApplicationSaveDataManagement
public static Result CleanUpTemporaryStorage(this FileSystemClient fs) public static Result CleanUpTemporaryStorage(this FileSystemClient fs)
{ {
while (true) Result res = fs.Impl.CleanUpTemporaryStorageImpl();
{
Result res = SaveDataFilter.Make(out SaveDataFilter filter, programId: default, SaveDataType.Temporary,
userId: default, saveDataId: default, index: default);
fs.Impl.AbortIfNeeded(res); fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss(); 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; 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();
}
}
public static Result EnsureApplicationBcatDeliveryCacheStorage(this FileSystemClient fs, out long outRequiredSize, public static Result EnsureApplicationBcatDeliveryCacheStorage(this FileSystemClient fs, out long outRequiredSize,
Ncm.ApplicationId applicationId, in ApplicationControlProperty controlProperty) Ncm.ApplicationId applicationId, in ApplicationControlProperty controlProperty)
{ {
@ -636,7 +625,7 @@ public static class ApplicationSaveDataManagement
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
long baseSize = RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Account).GetSaveDataMetaSize()) + long baseSize = RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Account).GetSaveDataMetaSize()) +
0x8000; UserAndDeviceSaveDataOverheadSize;
res = EnsureAndExtendSaveData(fs, ref requiredSize, in filter, CreateAccountSaveFunc, accountSaveDataSize, res = EnsureAndExtendSaveData(fs, ref requiredSize, in filter, CreateAccountSaveFunc, accountSaveDataSize,
accountSaveJournalSize, baseSize); accountSaveJournalSize, baseSize);
@ -658,7 +647,7 @@ public static class ApplicationSaveDataManagement
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
long baseSize = RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Device).GetSaveDataMetaSize()) + long baseSize = RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Device).GetSaveDataMetaSize()) +
0x8000; UserAndDeviceSaveDataOverheadSize;
long requiredSizeForDeviceSaveData = 0; long requiredSizeForDeviceSaveData = 0;
res = EnsureAndExtendSaveData(fs, ref requiredSizeForDeviceSaveData, in filter, CreateDeviceSaveFunc, res = EnsureAndExtendSaveData(fs, ref requiredSizeForDeviceSaveData, in filter, CreateDeviceSaveFunc,
@ -683,7 +672,7 @@ public static class ApplicationSaveDataManagement
{ {
requiredSizeForDeviceSaveData += requiredSizeForDeviceSaveData +=
RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Device).GetSaveDataMetaSize()) + RoundUpOccupationSize(new SaveDataMetaPolicy(SaveDataType.Device).GetSaveDataMetaSize()) +
0x4000; SaveDataOverheadSize;
} }
else else
{ {
@ -724,7 +713,7 @@ public static class ApplicationSaveDataManagement
controlProperty.TemporaryStorageSize, saveDataJournalSize: 0); controlProperty.TemporaryStorageSize, saveDataJournalSize: 0);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
requiredSize = RoundUpOccupationSize(saveDataTotalSize) + 0x4000; requiredSize = RoundUpOccupationSize(saveDataTotalSize) + SaveDataOverheadSize;
return Result.Success; return Result.Success;
} }
@ -759,8 +748,8 @@ public static class ApplicationSaveDataManagement
} }
else else
{ {
// If there was already insufficient space to create the previous saves, check if the temp // If there was already insufficient space to create the previous saves, don't try to create a
// save already exists instead of trying to create a new one. // temporary save. Just calculate the space required, if any, to create it.
res = SaveDataFilter.Make(out SaveDataFilter filter, applicationId.Value, SaveDataType.Temporary, res = SaveDataFilter.Make(out SaveDataFilter filter, applicationId.Value, SaveDataType.Temporary,
userId: default, saveDataId: default, index: default); userId: default, saveDataId: default, index: default);

View file

@ -314,6 +314,38 @@ public static class SaveData
return res.Miss(); 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) public static Result MountTemporaryStorage(this FileSystemClient fs, U8Span mountName)
{ {
Result res; Result res;