Add EnsureApplicationCacheStorage

This commit is contained in:
Alex Barney 2020-02-18 23:12:23 -07:00
parent 44ff25ee9b
commit cfb79f5780
3 changed files with 432 additions and 1 deletions

View file

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using LibHac.Account;
using LibHac.Fs.Shim;
using LibHac.Ncm;
@ -220,6 +221,29 @@ namespace LibHac.Fs
return Result.Success;
}
private static Result CreateSaveData(FileSystemClient fs, Func<Result> createFunc, ref long requiredSize, long baseSize,
long dataSize, long journalSize)
{
Result rc = createFunc();
if (rc.IsSuccess())
return Result.Success;
if (ResultFs.InsufficientFreeSpace.Includes(rc))
{
Result queryRc = fs.QuerySaveDataTotalSize(out long totalSize, dataSize, journalSize);
if (queryRc.IsFailure()) return queryRc;
requiredSize += Util.AlignUp(totalSize, 0x4000) + baseSize;
}
else if (!ResultFs.PathAlreadyExists.Includes(rc))
{
return rc;
}
return Result.Success;
}
private static Result EnsureApplicationBcatDeliveryCacheStorageImpl(FileSystemClient fs, out long requiredSize,
TitleId applicationId, ref ApplicationControlProperty nacp)
{
@ -287,6 +311,200 @@ namespace LibHac.Fs
return requiredSize > 0 ? ResultFs.InsufficientFreeSpace.Log() : Result.Success;
}
private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, out long requiredSize,
out CacheStorageTargetMedia target, TitleId applicationId, TitleId saveDataOwnerId, short index,
long dataSize, long journalSize, bool allowExisting)
{
requiredSize = default;
target = CacheStorageTargetMedia.SdCard;
Result rc = fs.GetCacheStorageTargetMediaImpl(out CacheStorageTargetMedia targetMedia, applicationId);
if (rc.IsFailure()) return rc;
long requiredSizeLocal = 0;
if (targetMedia == CacheStorageTargetMedia.Nand)
{
rc = TryCreateCacheStorage(fs, out requiredSizeLocal, SaveDataSpaceId.User, applicationId,
saveDataOwnerId, index, dataSize, journalSize, allowExisting);
if (rc.IsFailure()) return rc;
}
else if (targetMedia == CacheStorageTargetMedia.SdCard)
{
rc = TryCreateCacheStorage(fs, out requiredSizeLocal, SaveDataSpaceId.SdCache, applicationId,
saveDataOwnerId, index, dataSize, journalSize, allowExisting);
if (rc.IsFailure()) return rc;
}
// Savedata doesn't exist. Try to create a new one.
else
{
// Try to create the savedata on the SD card first
if (fs.IsSdCardAccessible())
{
target = CacheStorageTargetMedia.SdCard;
Result CreateFuncSdCard() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache,
saveDataOwnerId, index, dataSize, journalSize, SaveDataFlags.None);
rc = CreateSaveData(fs, CreateFuncSdCard, ref requiredSizeLocal, 0x4000, dataSize, journalSize);
if (rc.IsFailure()) return rc;
if (requiredSizeLocal == 0)
{
requiredSize = 0;
return Result.Success;
}
}
// If the save can't be created on the SD card, try creating it on the User BIS partition
requiredSizeLocal = 0;
target = CacheStorageTargetMedia.Nand;
Result CreateFuncNand() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, saveDataOwnerId,
index, dataSize, journalSize, SaveDataFlags.None);
rc = CreateSaveData(fs, CreateFuncNand, ref requiredSizeLocal, 0x4000, dataSize, journalSize);
if (rc.IsFailure()) return rc;
if (requiredSizeLocal != 0)
{
target = CacheStorageTargetMedia.None;
requiredSize = requiredSizeLocal;
return ResultFs.InsufficientFreeSpace.Log();
}
}
requiredSize = 0;
return Result.Success;
}
public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize,
out CacheStorageTargetMedia target, TitleId applicationId, TitleId saveDataOwnerId, short index,
long dataSize, long journalSize, bool allowExisting)
{
return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId, saveDataOwnerId,
index, dataSize, journalSize, allowExisting);
}
public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize,
TitleId applicationId, ref ApplicationControlProperty nacp)
{
return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out _, applicationId, nacp.SaveDataOwnerId,
0, nacp.CacheStorageSize, nacp.CacheStorageJournalSize, true);
}
public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize,
out CacheStorageTargetMedia target, TitleId applicationId, ref ApplicationControlProperty nacp)
{
if (nacp.CacheStorageSize <= 0)
{
requiredSize = default;
target = default;
return Result.Success;
}
return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId,
nacp.SaveDataOwnerId, 0, nacp.CacheStorageSize, nacp.CacheStorageJournalSize, true);
}
public static Result TryCreateCacheStorage(this FileSystemClient fs, out long requiredSize,
SaveDataSpaceId spaceId, TitleId applicationId, TitleId saveDataOwnerId, short index, long dataSize,
long journalSize, bool allowExisting)
{
requiredSize = default;
long requiredSizeLocal = 0;
var filter = new SaveDataFilter();
filter.SetProgramId(applicationId);
filter.SetIndex(index);
filter.SetSaveDataType(SaveDataType.Cache);
Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, spaceId, ref filter);
if (rc.IsFailure())
{
if (!ResultFs.TargetNotFound.Includes(rc))
return rc;
Result CreateCacheFunc() => fs.CreateCacheStorage(applicationId, spaceId, saveDataOwnerId, index,
dataSize, journalSize, SaveDataFlags.None);
rc = CreateSaveData(fs, CreateCacheFunc, ref requiredSizeLocal, 0x4000, dataSize, journalSize);
if (rc.IsFailure()) return rc;
requiredSize = requiredSizeLocal;
return Result.Success;
}
if (!allowExisting)
{
return ResultFs.SaveDataPathAlreadyExists.Log();
}
rc = ExtendSaveDataIfNeeded(fs, out requiredSizeLocal, spaceId, info.SaveDataId, dataSize, journalSize);
if (rc.IsSuccess() || ResultFs.InsufficientFreeSpace.Includes(rc))
{
requiredSize = requiredSizeLocal;
return Result.Success;
}
if (ResultFs.SaveDataIsExtending.Includes(rc))
{
return ResultFs.SaveDataCorrupted.LogConverted(rc);
}
return rc;
}
public static Result GetCacheStorageTargetMedia(this FileSystemClient fs, out CacheStorageTargetMedia target, TitleId applicationId)
{
return GetCacheStorageTargetMediaImpl(fs, out target, applicationId);
}
private static Result GetCacheStorageTargetMediaImpl(this FileSystemClient fs, out CacheStorageTargetMedia target, TitleId applicationId)
{
target = default;
if (fs.IsSdCardAccessible())
{
var filter = new SaveDataFilter();
filter.SetProgramId(applicationId);
filter.SetSaveDataType(SaveDataType.Cache);
Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.SdCache, ref filter);
if (rc.IsSuccess())
{
target = CacheStorageTargetMedia.SdCard;
return Result.Success;
}
if (!ResultFs.TargetNotFound.Includes(rc))
return rc;
}
{
var filter = new SaveDataFilter();
filter.SetProgramId(applicationId);
filter.SetSaveDataType(SaveDataType.Cache);
Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.User, ref filter);
if (rc.IsSuccess())
{
target = CacheStorageTargetMedia.Nand;
return Result.Success;
}
if (!ResultFs.TargetNotFound.Includes(rc))
return rc;
}
target = CacheStorageTargetMedia.None;
return Result.Success;
}
public static Result CleanUpTemporaryStorage(FileSystemClient fs)
{
var filter = new SaveDataFilter();

View file

@ -180,4 +180,11 @@ namespace LibHac.Fs
SdCard = 1,
GcAsic = 2
}
public enum CacheStorageTargetMedia
{
None = 0,
Nand = 1,
SdCard = 2
}
}

View file

@ -0,0 +1,206 @@
using LibHac.Account;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.Ncm;
using LibHac.Ns;
using Xunit;
using static LibHac.Fs.ApplicationSaveDataManagement;
namespace LibHac.Tests.Fs.FileSystemClientTests
{
public class ApplicationSaveDataManagementTests
{
[Fact]
public static void EnsureApplicationSaveData_CreatesAccountSaveData()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
var applicationId = new TitleId(11);
var userId = new Uid(2, 3);
var nacp = new ApplicationControlProperty
{
UserAccountSaveDataSize = 0x1000,
UserAccountSaveDataJournalSize = 0x1000
};
Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId));
fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User);
var info = new SaveDataInfo[2];
Assert.Success(iterator.ReadSaveDataInfo(out long entriesRead, info));
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].TitleId);
Assert.Equal(ConvertAccountUidToFsUserId(userId), info[0].UserId);
Assert.Equal(SaveDataType.Account, info[0].Type);
}
[Fact]
public static void EnsureApplicationSaveData_CreatesDeviceSaveData()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
var applicationId = new TitleId(11);
var userId = new Uid(2, 3);
var nacp = new ApplicationControlProperty
{
DeviceSaveDataSize = 0x1000,
DeviceSaveDataJournalSize = 0x1000
};
Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId));
fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User);
var info = new SaveDataInfo[2];
Assert.Success(iterator.ReadSaveDataInfo(out long entriesRead, info));
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].TitleId);
Assert.Equal(UserId.Zero, info[0].UserId);
Assert.Equal(SaveDataType.Device, info[0].Type);
}
[Fact]
public static void EnsureApplicationSaveData_CreatesBcatCacheStorage()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
var applicationId = new TitleId(11);
var userId = new Uid(2, 3);
var nacp = new ApplicationControlProperty
{
BcatDeliveryCacheStorageSize = 0x1000
};
Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId));
fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User);
var info = new SaveDataInfo[2];
Assert.Success(iterator.ReadSaveDataInfo(out long entriesRead, info));
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].TitleId);
Assert.Equal(UserId.Zero, info[0].UserId);
Assert.Equal(SaveDataType.Bcat, info[0].Type);
}
[Fact]
public static void EnsureApplicationSaveData_CreatesTemporaryStorage()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
var applicationId = new TitleId(11);
var userId = new Uid(2, 3);
var nacp = new ApplicationControlProperty
{
TemporaryStorageSize = 0x1000
};
Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId));
fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.Temporary);
var info = new SaveDataInfo[2];
Assert.Success(iterator.ReadSaveDataInfo(out long entriesRead, info));
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].TitleId);
Assert.Equal(UserId.Zero, info[0].UserId);
Assert.Equal(SaveDataType.Temporary, info[0].Type);
}
[Fact]
public static void EnsureApplicationCacheStorage_SdCardAvailable_CreatesCacheStorageOnSd()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
var applicationId = new TitleId(11);
var nacp = new ApplicationControlProperty
{
CacheStorageSize = 0x1000,
CacheStorageJournalSize = 0x1000
};
Assert.Success(fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia target, applicationId,
ref nacp));
Assert.Equal(CacheStorageTargetMedia.SdCard, target);
fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.SdCache);
var info = new SaveDataInfo[2];
Assert.Success(iterator.ReadSaveDataInfo(out long entriesRead, info));
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].TitleId);
Assert.Equal(SaveDataType.Cache, info[0].Type);
}
[Fact]
public static void EnsureApplicationCacheStorage_SdCardNotAvailable_CreatesCacheStorageOnBis()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(false);
var applicationId = new TitleId(11);
var nacp = new ApplicationControlProperty
{
CacheStorageSize = 0x1000,
CacheStorageJournalSize = 0x1000
};
Assert.Success(fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia target, applicationId,
ref nacp));
Assert.Equal(CacheStorageTargetMedia.Nand, target);
fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User);
var info = new SaveDataInfo[2];
Assert.Success(iterator.ReadSaveDataInfo(out long entriesRead, info));
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].TitleId);
Assert.Equal(SaveDataType.Cache, info[0].Type);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public static void GetCacheStorageTargetMedia_ReturnsTargetOfNewCacheStorage(bool isSdCardInserted)
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(isSdCardInserted);
var applicationId = new TitleId(11);
var nacp = new ApplicationControlProperty
{
CacheStorageSize = 0x1000,
CacheStorageJournalSize = 0x1000
};
fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia targetFromCreation, applicationId, ref nacp);
Assert.Success(fs.GetCacheStorageTargetMedia(out CacheStorageTargetMedia target, applicationId));
Assert.Equal(targetFromCreation, target);
}
[Fact]
public static void GetCacheStorageTargetMedia_CacheStorageDoesNotExist_ReturnsNone()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
Assert.Success(fs.GetCacheStorageTargetMedia(out CacheStorageTargetMedia target, new TitleId(11)));
Assert.Equal(CacheStorageTargetMedia.None, target);
}
}
}