From 44ff25ee9bfa73d04ccffbf0bf5705e7bb757d98 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 13 Feb 2020 17:21:24 -0700 Subject: [PATCH 1/3] Add cache storage and an emulated SD card --- .../Fs/ApplicationSaveDataManagement.cs | 8 +- src/LibHac/Fs/EncryptionSeed.cs | 29 ++++++ src/LibHac/Fs/FsEnums.cs | 7 ++ src/LibHac/Fs/ResultFs.cs | 3 +- src/LibHac/Fs/SaveDataStructs.cs | 10 +- src/LibHac/Fs/Shim/SdCard.cs | 91 +++++++++++++++++++ .../Creators/EmulatedSdFileSystemCreator.cs | 12 ++- .../FsService/DefaultFsServerObjects.cs | 9 +- .../FsService/EmulatedDeviceOperator.cs | 10 +- src/LibHac/FsService/EmulatedGameCard.cs | 1 + src/LibHac/FsService/EmulatedSdCard.cs | 17 ++++ src/LibHac/FsService/FileSystemProxy.cs | 62 +++++++++++-- src/LibHac/FsService/FileSystemProxyCore.cs | 9 +- src/LibHac/FsService/FileSystemServer.cs | 5 + src/LibHac/FsService/IFileSystemProxy.cs | 2 +- src/LibHac/FsService/ResultSdmmc.cs | 10 ++ .../FsService/SaveDataIndexerManager.cs | 10 +- .../FsService/SaveDataInfoFilterReader.cs | 48 ++++++++-- .../FileSystemServerFactory.cs | 33 +++++++ .../ShimTests/SaveData.cs | 67 ++++++++++++++ .../ShimTests/SaveDataManagement.cs | 88 ++++++++++++++++++ .../FileSystemClientTests/ShimTests/SdCard.cs | 80 ++++++++++++++++ tests/LibHac.Tests/GlobalSuppressions.cs | 7 ++ tests/LibHac.Tests/LibHac.Tests.csproj | 4 +- tests/LibHac.Tests/ResultAsserts.cs | 25 +++++ 25 files changed, 606 insertions(+), 41 deletions(-) create mode 100644 src/LibHac/Fs/EncryptionSeed.cs create mode 100644 src/LibHac/Fs/Shim/SdCard.cs create mode 100644 src/LibHac/FsService/EmulatedSdCard.cs create mode 100644 src/LibHac/FsService/ResultSdmmc.cs create mode 100644 tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs create mode 100644 tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs create mode 100644 tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs create mode 100644 tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs create mode 100644 tests/LibHac.Tests/GlobalSuppressions.cs create mode 100644 tests/LibHac.Tests/ResultAsserts.cs diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index 1efd9b96..bce2ad33 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -19,7 +19,7 @@ namespace LibHac.Fs if (uid != Uid.Zero && nacp.UserAccountSaveDataSize > 0) { var filter = new SaveDataFilter(); - filter.SetTitleId(applicationId); + filter.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Account); filter.SetUserId(new UserId(uid.Id.High, uid.Id.Low)); @@ -82,7 +82,7 @@ namespace LibHac.Fs if (nacp.DeviceSaveDataSize > 0) { var filter = new SaveDataFilter(); - filter.SetTitleId(applicationId); + filter.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Device); Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); @@ -155,7 +155,7 @@ namespace LibHac.Fs // 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.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Temporary); Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.Temporary, ref filter); @@ -234,7 +234,7 @@ namespace LibHac.Fs } var filter = new SaveDataFilter(); - filter.SetTitleId(applicationId); + filter.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Bcat); Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); diff --git a/src/LibHac/Fs/EncryptionSeed.cs b/src/LibHac/Fs/EncryptionSeed.cs new file mode 100644 index 00000000..7979b17c --- /dev/null +++ b/src/LibHac/Fs/EncryptionSeed.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using LibHac.Common; + +namespace LibHac.Fs +{ + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct EncryptionSeed : IEquatable + { + private readonly Key128 Key; + + public ReadOnlySpan Value => SpanHelpers.AsByteSpan(ref this); + + public EncryptionSeed(ReadOnlySpan bytes) + { + Key = new Key128(bytes); + } + + public override string ToString() => Key.ToString(); + + public override bool Equals(object obj) => obj is EncryptionSeed key && Equals(key); + public bool Equals(EncryptionSeed other) => Key.Equals(other.Key); + public override int GetHashCode() => Key.GetHashCode(); + public static bool operator ==(EncryptionSeed left, EncryptionSeed right) => left.Equals(right); + public static bool operator !=(EncryptionSeed left, EncryptionSeed right) => !(left == right); + } +} diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index 85539ffa..7a877848 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -173,4 +173,11 @@ namespace LibHac.Fs KeepAfterResettingSystemSaveDataWithoutUserSaveData = 1 << 2, NeedsSecureDelete = 1 << 3 } + + public enum SdmmcPort + { + Mmc = 0, + SdCard = 1, + GcAsic = 2 + } } diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index 016841a8..c6bcd284 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -24,7 +24,8 @@ namespace LibHac.Fs public static Result.Base ExternalKeyNotFound => new Result.Base(ModuleFs, 1004); public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2000, 2499); } - public static Result.Base SdCardNotPresent => new Result.Base(ModuleFs, 2001); + public static Result.Base SdCardNotFound => new Result.Base(ModuleFs, 2001); + public static Result.Base SdCardAsleep => new Result.Base(ModuleFs, 2004); public static Result.Base GameCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2500, 2999); } public static Result.Base InvalidBufferForGameCard => new Result.Base(ModuleFs, 2503); diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs index fd62cfde..a9e07435 100644 --- a/src/LibHac/Fs/SaveDataStructs.cs +++ b/src/LibHac/Fs/SaveDataStructs.cs @@ -56,23 +56,23 @@ namespace LibHac.Fs [StructLayout(LayoutKind.Explicit, Size = 0x48)] public struct SaveDataFilter { - [FieldOffset(0x00)] public bool FilterByTitleId; + [FieldOffset(0x00)] public bool FilterByProgramId; [FieldOffset(0x01)] public bool FilterBySaveDataType; [FieldOffset(0x02)] public bool FilterByUserId; [FieldOffset(0x03)] public bool FilterBySaveDataId; [FieldOffset(0x04)] public bool FilterByIndex; [FieldOffset(0x05)] public SaveDataRank Rank; - [FieldOffset(0x08)] public TitleId TitleId; + [FieldOffset(0x08)] public TitleId ProgramId; [FieldOffset(0x10)] public UserId UserId; [FieldOffset(0x20)] public ulong SaveDataId; [FieldOffset(0x28)] public SaveDataType SaveDataType; [FieldOffset(0x2A)] public short Index; - public void SetTitleId(TitleId value) + public void SetProgramId(TitleId value) { - FilterByTitleId = true; - TitleId = value; + FilterByProgramId = true; + ProgramId = value; } public void SetSaveDataType(SaveDataType value) diff --git a/src/LibHac/Fs/Shim/SdCard.cs b/src/LibHac/Fs/Shim/SdCard.cs new file mode 100644 index 00000000..d0504001 --- /dev/null +++ b/src/LibHac/Fs/Shim/SdCard.cs @@ -0,0 +1,91 @@ +using System; +using LibHac.Common; +using LibHac.FsService; + +namespace LibHac.Fs.Shim +{ + public static class SdCard + { + public static Result MountSdCard(this FileSystemClient fs, U8Span mountName) + { + Result rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = Run(fs, mountName); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, ""); + } + else + { + rc = Run(fs, mountName); + } + + if (rc.IsFailure()) return rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return Result.Success; + + static Result Run(FileSystemClient fs, U8Span mountName) + { + // ReSharper disable once VariableHidesOuterVariable + Result rc = MountHelpers.CheckMountName(mountName); + if (rc.IsFailure()) return rc; + + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + rc = fsProxy.OpenSdCardFileSystem(out IFileSystem fileSystem); + if (rc.IsFailure()) return rc; + + return fs.Register(mountName, fileSystem); + } + } + + public static bool IsSdCardInserted(this FileSystemClient fs) + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator); + if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); + + rc = deviceOperator.IsSdCardInserted(out bool isInserted); + if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); + + return isInserted; + } + + public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, ref EncryptionSeed seed) + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.SetSdCardEncryptionSeed(ref seed); + if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); + + return Result.Success; + } + + public static void SetSdCardAccessibility(this FileSystemClient fs, bool isAccessible) + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.SetSdCardAccessibility(isAccessible); + if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); + } + + public static bool IsSdCardAccessible(this FileSystemClient fs) + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.IsSdCardAccessible(out bool isAccessible); + if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); + + return isAccessible; + } + } +} diff --git a/src/LibHac/FsService/Creators/EmulatedSdFileSystemCreator.cs b/src/LibHac/FsService/Creators/EmulatedSdFileSystemCreator.cs index 7844bbbf..ce13dc4c 100644 --- a/src/LibHac/FsService/Creators/EmulatedSdFileSystemCreator.cs +++ b/src/LibHac/FsService/Creators/EmulatedSdFileSystemCreator.cs @@ -7,18 +7,21 @@ namespace LibHac.FsService.Creators { private const string DefaultPath = "/sdcard"; + private EmulatedSdCard SdCard { get; } private IFileSystem RootFileSystem { get; } private string Path { get; } private IFileSystem SdCardFileSystem { get; set; } - public EmulatedSdFileSystemCreator(IFileSystem rootFileSystem) + public EmulatedSdFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem) { + SdCard = sdCard; RootFileSystem = rootFileSystem; } - public EmulatedSdFileSystemCreator(IFileSystem rootFileSystem, string path) + public EmulatedSdFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path) { + SdCard = sdCard; RootFileSystem = rootFileSystem; Path = path; } @@ -27,6 +30,11 @@ namespace LibHac.FsService.Creators { fileSystem = default; + if (!SdCard.IsSdCardInserted()) + { + return ResultFs.SdCardNotFound.Log(); + } + if (SdCardFileSystem != null) { fileSystem = SdCardFileSystem; diff --git a/src/LibHac/FsService/DefaultFsServerObjects.cs b/src/LibHac/FsService/DefaultFsServerObjects.cs index 57fb3538..86732891 100644 --- a/src/LibHac/FsService/DefaultFsServerObjects.cs +++ b/src/LibHac/FsService/DefaultFsServerObjects.cs @@ -8,11 +8,13 @@ namespace LibHac.FsService public FileSystemCreators FsCreators { get; set; } public IDeviceOperator DeviceOperator { get; set; } public EmulatedGameCard GameCard { get; set; } + public EmulatedSdCard SdCard { get; set; } public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, Keyset keyset) { var creators = new FileSystemCreators(); var gameCard = new EmulatedGameCard(keyset); + var sdCard = new EmulatedSdCard(); var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard); @@ -26,15 +28,16 @@ namespace LibHac.FsService creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard); creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keyset); creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(rootFileSystem); - creators.SdFileSystemCreator = new EmulatedSdFileSystemCreator(rootFileSystem); + creators.SdFileSystemCreator = new EmulatedSdFileSystemCreator(sdCard, rootFileSystem); - var deviceOperator = new EmulatedDeviceOperator(gameCard); + var deviceOperator = new EmulatedDeviceOperator(gameCard, sdCard); return new DefaultFsServerObjects { FsCreators = creators, DeviceOperator = deviceOperator, - GameCard = gameCard + GameCard = gameCard, + SdCard = sdCard }; } } diff --git a/src/LibHac/FsService/EmulatedDeviceOperator.cs b/src/LibHac/FsService/EmulatedDeviceOperator.cs index e1378f96..1abe98aa 100644 --- a/src/LibHac/FsService/EmulatedDeviceOperator.cs +++ b/src/LibHac/FsService/EmulatedDeviceOperator.cs @@ -1,20 +1,22 @@ -using System; -using LibHac.Fs; +using LibHac.Fs; namespace LibHac.FsService { public class EmulatedDeviceOperator : IDeviceOperator { private EmulatedGameCard GameCard { get; set; } + private EmulatedSdCard SdCard { get; set; } - public EmulatedDeviceOperator(EmulatedGameCard gameCard) + public EmulatedDeviceOperator(EmulatedGameCard gameCard, EmulatedSdCard sdCard) { GameCard = gameCard; + SdCard = sdCard; } public Result IsSdCardInserted(out bool isInserted) { - throw new NotImplementedException(); + isInserted = SdCard.IsSdCardInserted(); + return Result.Success; } public Result IsGameCardInserted(out bool isInserted) diff --git a/src/LibHac/FsService/EmulatedGameCard.cs b/src/LibHac/FsService/EmulatedGameCard.cs index b54d20c3..09b9ea14 100644 --- a/src/LibHac/FsService/EmulatedGameCard.cs +++ b/src/LibHac/FsService/EmulatedGameCard.cs @@ -61,6 +61,7 @@ namespace LibHac.FsService xci = CardImage; return Result.Success; } + public Result Read(GameCardHandle handle, long offset, Span destination) { if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnRead.Log(); diff --git a/src/LibHac/FsService/EmulatedSdCard.cs b/src/LibHac/FsService/EmulatedSdCard.cs new file mode 100644 index 00000000..7115993d --- /dev/null +++ b/src/LibHac/FsService/EmulatedSdCard.cs @@ -0,0 +1,17 @@ +namespace LibHac.FsService +{ + public class EmulatedSdCard + { + private bool IsInserted { get; set; } + + public bool IsSdCardInserted() + { + return IsInserted; + } + + public void SetSdCardInsertionStatus(bool isInserted) + { + IsInserted = isInserted; + } + } +} diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs index 3e4e8916..044baf3d 100644 --- a/src/LibHac/FsService/FileSystemProxy.cs +++ b/src/LibHac/FsService/FileSystemProxy.cs @@ -554,7 +554,8 @@ namespace LibHac.FsService if (attributeCopy.Type == SaveDataType.Cache) { // Check whether the save is on the SD card or the BIS - throw new NotImplementedException(); + Result rc = GetSpaceIdForCacheStorage(out actualSpaceId, attributeCopy.TitleId); + if (rc.IsFailure()) return rc; } else { @@ -854,6 +855,48 @@ namespace LibHac.FsService throw new NotImplementedException(); } + private Result GetSpaceIdForCacheStorage(out SaveDataSpaceId spaceId, TitleId programId) + { + spaceId = default; + + if (FsProxyCore.IsSdCardAccessible) + { + var filter = new SaveDataFilterInternal(); + + filter.SetSaveDataSpaceId(SaveDataSpaceId.SdCache); + filter.SetProgramId(programId); + filter.SetSaveDataType(SaveDataType.Cache); + + Result rc = FindSaveDataWithFilterImpl(out long count, out _, SaveDataSpaceId.SdCache, ref filter); + if (rc.IsFailure()) return rc; + + if (count > 0) + { + spaceId = SaveDataSpaceId.SdCache; + return Result.Success; + } + } + + { + var filter = new SaveDataFilterInternal(); + + filter.SetSaveDataSpaceId(SaveDataSpaceId.User); + filter.SetProgramId(programId); + filter.SetSaveDataType(SaveDataType.Cache); + + Result rc = FindSaveDataWithFilterImpl(out long count, out _, SaveDataSpaceId.User, ref filter); + if (rc.IsFailure()) return rc; + + if (count > 0) + { + spaceId = SaveDataSpaceId.User; + return Result.Success; + } + } + + return ResultFs.TargetNotFound.Log(); + } + public Result DeleteCacheStorage(short index) { throw new NotImplementedException(); @@ -973,17 +1016,14 @@ namespace LibHac.FsService return FsProxyCore.UnregisterAllExternalKey(); } - public Result SetSdCardEncryptionSeed(ReadOnlySpan seed) + public Result SetSdCardEncryptionSeed(ref EncryptionSeed seed) { - // todo: use struct instead of byte span - if (seed.Length != 0x10) return ResultFs.InvalidSize.Log(); - // Missing permission check - Result rc = FsProxyCore.SetSdCardEncryptionSeed(seed); + Result rc = FsProxyCore.SetSdCardEncryptionSeed(ref seed); if (rc.IsFailure()) return rc; - // todo: Reset save data indexer + FsServer.SaveDataIndexerManager.ResetSdCardIndexer(); return Result.Success; } @@ -1095,12 +1135,16 @@ namespace LibHac.FsService public Result SetSdCardAccessibility(bool isAccessible) { - throw new NotImplementedException(); + // Missing permission check + + FsProxyCore.IsSdCardAccessible = isAccessible; + return Result.Success; } public Result IsSdCardAccessible(out bool isAccessible) { - throw new NotImplementedException(); + isAccessible = FsProxyCore.IsSdCardAccessible; + return Result.Success; } private static bool IsSystemSaveDataId(ulong id) diff --git a/src/LibHac/FsService/FileSystemProxyCore.cs b/src/LibHac/FsService/FileSystemProxyCore.cs index 3047ab78..1b5798c2 100644 --- a/src/LibHac/FsService/FileSystemProxyCore.cs +++ b/src/LibHac/FsService/FileSystemProxyCore.cs @@ -21,7 +21,8 @@ namespace LibHac.FsService private const string ContentDirectoryName = "Contents"; private GlobalAccessLogMode LogMode { get; set; } - + public bool IsSdCardAccessible { get; set; } + public FileSystemProxyCore(FileSystemCreators fsCreators, ExternalKeySet externalKeys, IDeviceOperator deviceOperator) { FsCreators = fsCreators; @@ -176,10 +177,10 @@ namespace LibHac.FsService return Result.Success; } - public Result SetSdCardEncryptionSeed(ReadOnlySpan seed) + public Result SetSdCardEncryptionSeed(ref EncryptionSeed seed) { - seed.CopyTo(SdEncryptionSeed); - //FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed); + seed.Value.CopyTo(SdEncryptionSeed); + // todo: FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed); return Result.Success; } diff --git a/src/LibHac/FsService/FileSystemServer.cs b/src/LibHac/FsService/FileSystemServer.cs index 285076b9..ae55b265 100644 --- a/src/LibHac/FsService/FileSystemServer.cs +++ b/src/LibHac/FsService/FileSystemServer.cs @@ -1,5 +1,6 @@ using System; using LibHac.Fs; +using LibHac.Fs.Shim; using LibHac.FsService.Creators; namespace LibHac.FsService @@ -35,6 +36,10 @@ namespace LibHac.FsService var fsProxy = new FileSystemProxy(FsProxyCore, this); FsClient = new FileSystemClient(this, fsProxy, Timer); + // NS usually takes care of this + if (FsClient.IsSdCardInserted()) + FsClient.SetSdCardAccessibility(true); + SaveDataIndexerManager = new SaveDataIndexerManager(FsClient, SaveIndexerId); fsProxy.CleanUpTemporaryStorage(); diff --git a/src/LibHac/FsService/IFileSystemProxy.cs b/src/LibHac/FsService/IFileSystemProxy.cs index f507481a..11f79cd5 100644 --- a/src/LibHac/FsService/IFileSystemProxy.cs +++ b/src/LibHac/FsService/IFileSystemProxy.cs @@ -82,7 +82,7 @@ namespace LibHac.FsService Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId); Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId); Result UnregisterExternalKey(ref RightsId rightsId); - Result SetSdCardEncryptionSeed(ReadOnlySpan seed); + Result SetSdCardEncryptionSeed(ref EncryptionSeed seed); Result SetSdCardAccessibility(bool isAccessible); Result IsSdCardAccessible(out bool isAccessible); diff --git a/src/LibHac/FsService/ResultSdmmc.cs b/src/LibHac/FsService/ResultSdmmc.cs new file mode 100644 index 00000000..482132f3 --- /dev/null +++ b/src/LibHac/FsService/ResultSdmmc.cs @@ -0,0 +1,10 @@ +namespace LibHac.FsService +{ + public static class ResultSdmmc + { + public const int ModuleSdmmc = 24; + + public static Result.Base DeviceNotFound => new Result.Base(ModuleSdmmc, 1); + public static Result.Base DeviceAsleep => new Result.Base(ModuleSdmmc, 4); + } +} diff --git a/src/LibHac/FsService/SaveDataIndexerManager.cs b/src/LibHac/FsService/SaveDataIndexerManager.cs index 41c7bd3b..1e6a18c2 100644 --- a/src/LibHac/FsService/SaveDataIndexerManager.cs +++ b/src/LibHac/FsService/SaveDataIndexerManager.cs @@ -40,7 +40,7 @@ namespace LibHac.FsService case SaveDataSpaceId.SdCache: Monitor.Enter(_sdCardIndexer.Locker); - // Missing reinitialize if SD handle is old + // todo: Missing reinitialize if SD handle is old if (!_sdCardIndexer.IsInitialized) { @@ -89,6 +89,14 @@ namespace LibHac.FsService } } + internal void ResetSdCardIndexer() + { + lock (_sdCardIndexer.Locker) + { + _sdCardIndexer.Indexer = null; + } + } + private struct IndexerHolder { public object Locker { get; } diff --git a/src/LibHac/FsService/SaveDataInfoFilterReader.cs b/src/LibHac/FsService/SaveDataInfoFilterReader.cs index 01dd7dec..83ba1ace 100644 --- a/src/LibHac/FsService/SaveDataInfoFilterReader.cs +++ b/src/LibHac/FsService/SaveDataInfoFilterReader.cs @@ -60,8 +60,8 @@ namespace LibHac.FsService [FieldOffset(0x00)] public bool FilterBySaveDataSpaceId; [FieldOffset(0x01)] public SaveDataSpaceId SpaceId; - [FieldOffset(0x08)] public bool FilterByTitleId; - [FieldOffset(0x10)] public TitleId TitleId; + [FieldOffset(0x08)] public bool FilterByProgramId; + [FieldOffset(0x10)] public TitleId ProgramId; [FieldOffset(0x18)] public bool FilterBySaveDataType; [FieldOffset(0x19)] public SaveDataType SaveDataType; @@ -86,10 +86,10 @@ namespace LibHac.FsService Rank = (int)filter.Rank; - if (filter.FilterByTitleId) + if (filter.FilterByProgramId) { - FilterByTitleId = true; - TitleId = filter.TitleId; + FilterByProgramId = true; + ProgramId = filter.ProgramId; } if (filter.FilterBySaveDataType) @@ -117,6 +117,42 @@ namespace LibHac.FsService } } + public void SetSaveDataSpaceId(SaveDataSpaceId spaceId) + { + FilterBySaveDataSpaceId = true; + SpaceId = spaceId; + } + + public void SetProgramId(TitleId value) + { + FilterByProgramId = true; + ProgramId = value; + } + + public void SetSaveDataType(SaveDataType value) + { + FilterBySaveDataType = true; + SaveDataType = value; + } + + public void SetUserId(UserId value) + { + FilterByUserId = true; + UserId = value; + } + + public void SetSaveDataId(ulong value) + { + FilterBySaveDataId = true; + SaveDataId = value; + } + + public void SetIndex(short value) + { + FilterByIndex = true; + Index = value; + } + public bool Matches(ref SaveDataInfo info) { if (FilterBySaveDataSpaceId && info.SpaceId != SpaceId) @@ -124,7 +160,7 @@ namespace LibHac.FsService return false; } - if (FilterByTitleId && info.TitleId != TitleId) + if (FilterByProgramId && info.TitleId != ProgramId) { return false; } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs new file mode 100644 index 00000000..aeb285b1 --- /dev/null +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs @@ -0,0 +1,33 @@ +using LibHac.Fs; +using LibHac.FsService; + +namespace LibHac.Tests.Fs.FileSystemClientTests +{ + public static class FileSystemServerFactory + { + public static FileSystemServer CreateServer(bool sdCardInserted) + { + var rootFs = new InMemoryFileSystem(); + + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, new Keyset()); + + defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted); + + var config = new FileSystemServerConfig(); + config.FsCreators = defaultObjects.FsCreators; + config.DeviceOperator = defaultObjects.DeviceOperator; + config.ExternalKeySet = new ExternalKeySet(); + + var fsServer = new FileSystemServer(config); + + return fsServer; + } + + public static FileSystemClient CreateClient(bool sdCardInserted) + { + FileSystemServer fsServer = CreateServer(sdCardInserted); + + return fsServer.CreateFileSystemClient(); + } + } +} diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs new file mode 100644 index 00000000..61c5fd5a --- /dev/null +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs @@ -0,0 +1,67 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.Ncm; +using Xunit; + +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +{ + public class SaveData + { + [Fact] + public void MountCacheStorage_CanMountCreatedCacheStorage() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId, 0, 0, SaveDataFlags.None); + + Assert.Success(fs.MountCacheStorage("cache".ToU8String(), applicationId)); + } + + [Fact] + public void MountCacheStorage_WrittenDataPersists() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId, 0, 0, SaveDataFlags.None); + fs.MountCacheStorage("cache".ToU8String(), applicationId); + + fs.CreateFile("cache:/file", 0); + fs.Commit("cache"); + fs.Unmount("cache"); + + Assert.Success(fs.MountCacheStorage("cache".ToU8String(), applicationId)); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "cache:/file")); + Assert.Equal(DirectoryEntryType.File, type); + } + [Fact] + public void MountCacheStorage_SdCardIsPreferredOverBis() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId, 0, 0, SaveDataFlags.None); + fs.MountCacheStorage("cache".ToU8String(), applicationId); + fs.CreateFile("cache:/sd", 0); + fs.Commit("cache"); + fs.Unmount("cache"); + + // Turn off the SD card so the User save is mounted + fs.SetSdCardAccessibility(false); + + fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId, 0, 0, SaveDataFlags.None); + fs.MountCacheStorage("cache".ToU8String(), applicationId); + fs.CreateFile("cache:/bis", 0); + fs.Commit("cache"); + fs.Unmount("cache"); + + fs.SetSdCardAccessibility(true); + + Assert.Success(fs.MountCacheStorage("cache".ToU8String(), applicationId)); + Assert.Success(fs.GetEntryType(out _, "cache:/sd")); + Assert.Failure(fs.GetEntryType(out _, "cache:/bis")); + } + } +} diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs new file mode 100644 index 00000000..ec5867f5 --- /dev/null +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs @@ -0,0 +1,88 @@ +using System.Linq; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.Ncm; +using Xunit; + +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +{ + public class SaveDataManagement + { + [Fact] + public void CreateCacheStorage_InUserSaveSpace_StorageIsCreated() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId, 0, 0, SaveDataFlags.None)); + + fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User); + + var info = new SaveDataInfo[2]; + iterator.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].TitleId); + } + + [Fact] + public void CreateCacheStorage_InSdCacheSaveSpace_StorageIsCreated() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId, 0, 0, SaveDataFlags.None)); + + fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.SdCache); + + var info = new SaveDataInfo[2]; + iterator.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].TitleId); + } + + [Fact] + public void CreateCacheStorage_InSdCacheSaveSpaceWhenNoSdCard_ReturnsSdCardNotFound() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + + Assert.Result(ResultFs.SdCardNotFound, fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId, 0, 0, SaveDataFlags.None)); + } + + [Fact] + public void CreateCacheStorage_AlreadyExists_ReturnsPathAlreadyExists() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId, 0, 0, SaveDataFlags.None)); + Assert.Result(ResultFs.PathAlreadyExists, fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId, 0, 0, SaveDataFlags.None)); + } + + [Fact] + public void CreateCacheStorage_WithIndex_CreatesMultiple() + { + var applicationId = new TitleId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId, 0, 0, 0, SaveDataFlags.None)); + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId, 1, 0, 0, SaveDataFlags.None)); + + fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User); + + var info = new SaveDataInfo[3]; + iterator.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(2, entriesRead); + Assert.Equal(applicationId, info[0].TitleId); + Assert.Equal(applicationId, info[1].TitleId); + + var expectedIndexes = new short[] { 0, 1 }; + short[] actualIndexes = info.Take(2).Select(x => x.Index).OrderBy(x => x).ToArray(); + + Assert.Equal(expectedIndexes, actualIndexes); + } + } +} diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs new file mode 100644 index 00000000..86000580 --- /dev/null +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs @@ -0,0 +1,80 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using Xunit; + +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +{ + public class SdCard + { + [Fact] + public void MountSdCard_CardIsInserted_Succeeds() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.MountSdCard("sdcard".ToU8String())); + } + + [Fact] + public void MountSdCard_CardIsNotInserted_Fails() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + + Assert.Result(ResultFs.SdCardNotFound, fs.MountSdCard("sdcard".ToU8String())); + } + + [Fact] + public void MountSdCard_CanWriteToFsAfterMounted() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + fs.MountSdCard("sdcard".ToU8String()); + + Assert.Success(fs.CreateFile("sdcard:/file", 100, CreateFileOptions.None)); + } + + [Fact] + public void IsSdCardInserted_CardIsInserted_ReturnsTrue() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.True(fs.IsSdCardInserted()); + } + + [Fact] + public void IsSdCardInserted_CardIsNotInserted_ReturnsFalse() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + + Assert.False(fs.IsSdCardInserted()); + } + + [Fact] + public void IsSdCardAccessible_CardIsInserted_ReturnsTrue() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.True(fs.IsSdCardAccessible()); + } + + [Fact] + public void IsSdCardAccessible_CardIsNotInserted_ReturnsFalse() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + + Assert.False(fs.IsSdCardAccessible()); + } + + [Fact] + public void SetSdCardAccessibility_SetAccessibilityPersists() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + + fs.SetSdCardAccessibility(true); + Assert.True(fs.IsSdCardAccessible()); + + fs.SetSdCardAccessibility(false); + Assert.False(fs.IsSdCardAccessible()); + } + } +} diff --git a/tests/LibHac.Tests/GlobalSuppressions.cs b/tests/LibHac.Tests/GlobalSuppressions.cs new file mode 100644 index 00000000..e3feb7bc --- /dev/null +++ b/tests/LibHac.Tests/GlobalSuppressions.cs @@ -0,0 +1,7 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2007:Do not use typeof expression to check the type", Justification = "Analyzer doesn't apply to Xunit internals.", Scope = "namespaceanddescendants", Target = "Xunit")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2015:Do not use typeof expression to check the exception type", Justification = "Analyzer doesn't apply to Xunit internals.", Scope = "namespaceanddescendants", Target = "Xunit")] \ No newline at end of file diff --git a/tests/LibHac.Tests/LibHac.Tests.csproj b/tests/LibHac.Tests/LibHac.Tests.csproj index 99b3149c..d5c07371 100644 --- a/tests/LibHac.Tests/LibHac.Tests.csproj +++ b/tests/LibHac.Tests/LibHac.Tests.csproj @@ -8,7 +8,9 @@ - + + + diff --git a/tests/LibHac.Tests/ResultAsserts.cs b/tests/LibHac.Tests/ResultAsserts.cs new file mode 100644 index 00000000..c158bae6 --- /dev/null +++ b/tests/LibHac.Tests/ResultAsserts.cs @@ -0,0 +1,25 @@ +using LibHac; +using Xunit.Sdk; + +// ReSharper disable once CheckNamespace +namespace Xunit +{ + public partial class Assert + { + public static void Success(Result result) + { + Equal(LibHac.Result.Success, result); + } + + public static void Failure(Result result) + { + NotEqual(LibHac.Result.Success, result); + } + + public static void Result(Result.Base expected, Result actual) + { + if (!expected.Includes(actual)) + throw new EqualException(expected.Value, actual); + } + } +} From cfb79f57808e4d95454217d33a7ac569b6739518 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 18 Feb 2020 23:12:23 -0700 Subject: [PATCH 2/3] Add EnsureApplicationCacheStorage --- .../Fs/ApplicationSaveDataManagement.cs | 220 +++++++++++++++++- src/LibHac/Fs/FsEnums.cs | 7 + .../ApplicationSaveDataManagementTests.cs | 206 ++++++++++++++++ 3 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index bce2ad33..f73cf2fd 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -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 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(); diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index 7a877848..64534def 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -180,4 +180,11 @@ namespace LibHac.Fs SdCard = 1, GcAsic = 2 } + + public enum CacheStorageTargetMedia + { + None = 0, + Nand = 1, + SdCard = 2 + } } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs new file mode 100644 index 00000000..5a3fe24f --- /dev/null +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs @@ -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); + } + } +} From 7bef4c6cd93d0f57c8c48f98cb0a8c549fe5ef1e Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 22 Feb 2020 03:05:04 -0700 Subject: [PATCH 3/3] Combine common code in EnsureApplicationSaveData --- .../Fs/ApplicationSaveDataManagement.cs | 253 ++++++------------ 1 file changed, 85 insertions(+), 168 deletions(-) diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index f73cf2fd..e4798644 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -13,127 +13,55 @@ namespace LibHac.Fs ref ApplicationControlProperty nacp, ref Uid uid) { requiredSize = default; - long requiredSizeSum = 0; - // If the application needs a user save + // Create local variable for use in closures + TitleId saveDataOwnerId = nacp.SaveDataOwnerId; + + // Ensure the user account save exists if (uid != Uid.Zero && nacp.UserAccountSaveDataSize > 0) { + // More local variables for use in closures + Uid uidLocal = uid; + long accountSaveDataSize = nacp.UserAccountSaveDataSize; + long accountSaveJournalSize = nacp.UserAccountSaveDataJournalSize; + + Result CreateAccountSaveFunc() + { + UserId userId = ConvertAccountUidToFsUserId(uidLocal); + return fs.CreateSaveData(applicationId, userId, saveDataOwnerId, accountSaveDataSize, + accountSaveJournalSize, SaveDataFlags.None); + } + var filter = new SaveDataFilter(); filter.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Account); filter.SetUserId(new UserId(uid.Id.High, uid.Id.Low)); - Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + // The 0x4c000 includes the save meta and other stuff + Result rc = EnsureAndExtendSaveData(fs, CreateAccountSaveFunc, ref requiredSizeSum, ref filter, 0x4c000, + accountSaveDataSize, accountSaveJournalSize); - // 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); - - if (rc.IsFailure()) - { - if (!ResultFs.InsufficientFreeSpace.Includes(rc)) - { - return rc; - } - - requiredSizeSum = requiredSizeUser; - } - } - else if (!ResultFs.TargetNotFound.Includes(rc)) - { - return rc; - } - else - { - // The save doesn't exist, so try to create it - UserId userId = ConvertAccountUidToFsUserId(uid); - - Result createRc = fs.CreateSaveData(applicationId, userId, nacp.SaveDataOwnerId, - nacp.UserAccountSaveDataSize, nacp.UserAccountSaveDataJournalSize, 0); - - if (createRc.IsFailure()) - { - // If there's insufficient free space, calculate the space required to create the save - if (ResultFs.InsufficientFreeSpace.Includes(createRc)) - { - 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 (ResultFs.PathAlreadyExists.Includes(createRc)) - { - requiredSizeSum = 0; - } - else - { - return createRc; - } - } - } + if (rc.IsFailure()) return rc; } + // Ensure the device save exists if (nacp.DeviceSaveDataSize > 0) { + long deviceSaveDataSize = nacp.DeviceSaveDataSize; + long deviceSaveJournalSize = nacp.DeviceSaveDataJournalSize; + + Result CreateDeviceSaveFunc() => fs.CreateDeviceSaveData(applicationId, saveDataOwnerId, + deviceSaveDataSize, deviceSaveJournalSize, 0); + var filter = new SaveDataFilter(); filter.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Device); - Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + Result rc = EnsureAndExtendSaveData(fs, CreateDeviceSaveFunc, ref requiredSizeSum, ref filter, 0x4000, + deviceSaveDataSize, deviceSaveJournalSize); - if (rc.IsSuccess()) - { - rc = ExtendSaveDataIfNeeded(fs, out long requiredSizeDevice, SaveDataSpaceId.User, - saveDataInfo.SaveDataId, nacp.DeviceSaveDataSize, nacp.DeviceSaveDataJournalSize); - - if (rc.IsFailure()) - { - if (!ResultFs.InsufficientFreeSpace.Includes(rc)) - { - return rc; - } - - requiredSizeSum += requiredSizeDevice; - } - } - else if (!ResultFs.TargetNotFound.Includes(rc)) - { - return rc; - } - else - { - Result createRc = fs.CreateDeviceSaveData(applicationId, nacp.SaveDataOwnerId, - nacp.DeviceSaveDataSize, nacp.DeviceSaveDataJournalSize, 0); - - if (createRc.IsFailure()) - { - if (ResultFs.InsufficientFreeSpace.Includes(createRc)) - { - 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 (ResultFs.PathAlreadyExists.Includes(createRc)) - { - requiredSizeSum += 0; - } - else - { - return createRc; - } - } - } + if (rc.IsFailure()) return rc; } Result bcatRc = EnsureApplicationBcatDeliveryCacheStorageImpl(fs, @@ -244,71 +172,63 @@ namespace LibHac.Fs return Result.Success; } + private static Result EnsureAndExtendSaveData(FileSystemClient fs, Func createFunc, + ref long requiredSize, ref SaveDataFilter filter, long baseSize, long dataSize, long journalSize) + { + Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.User, ref filter); + + if (rc.IsFailure()) + { + if (ResultFs.TargetNotFound.Includes(rc)) + { + rc = CreateSaveData(fs, createFunc, ref requiredSize, baseSize, dataSize, journalSize); + } + + return rc; + } + + rc = ExtendSaveDataIfNeeded(fs, out long requiredSizeExtend, SaveDataSpaceId.User, info.SaveDataId, + dataSize, journalSize); + + if (rc.IsFailure()) + { + if (!ResultFs.InsufficientFreeSpace.Includes(rc)) + return rc; + + requiredSize += requiredSizeExtend; + } + + return Result.Success; + } + private static Result EnsureApplicationBcatDeliveryCacheStorageImpl(FileSystemClient fs, out long requiredSize, TitleId applicationId, ref ApplicationControlProperty nacp) { const long bcatDeliveryCacheJournalSize = 0x200000; - requiredSize = default; - - if (nacp.BcatDeliveryCacheStorageSize <= 0) + long bcatStorageSize = nacp.BcatDeliveryCacheStorageSize; + if (bcatStorageSize <= 0) { requiredSize = 0; return Result.Success; } + requiredSize = default; + long requiredSizeBcat = 0; + var filter = new SaveDataFilter(); filter.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Bcat); - Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + Result CreateBcatStorageFunc() => fs.CreateBcatSaveData(applicationId, bcatStorageSize); - if (rc.IsSuccess()) - { - rc = ExtendSaveDataIfNeeded(fs, out long requiredSizeBcat, SaveDataSpaceId.User, - saveDataInfo.SaveDataId, nacp.BcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize); + Result rc = EnsureAndExtendSaveData(fs, CreateBcatStorageFunc, + ref requiredSizeBcat, ref filter, 0x4000, bcatStorageSize, bcatDeliveryCacheJournalSize); - if (rc.IsFailure()) - { - if (!ResultFs.InsufficientFreeSpace.Includes(rc)) - { - return rc; - } + if (rc.IsFailure()) return rc; - requiredSize = requiredSizeBcat; - } - } - else if (!ResultFs.TargetNotFound.Includes(rc)) - { - return rc; - } - else - { - Result createRc = fs.CreateBcatSaveData(applicationId, nacp.BcatDeliveryCacheStorageSize); - - if (createRc.IsFailure()) - { - if (ResultFs.InsufficientFreeSpace.Includes(createRc)) - { - Result queryRc = fs.QuerySaveDataTotalSize(out long saveTotalSize, - nacp.BcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize); - - if (queryRc.IsFailure()) return queryRc; - - requiredSize = Util.AlignUp(saveTotalSize, 0x4000) + 0x4000; - } - else if (ResultFs.PathAlreadyExists.Includes(createRc)) - { - requiredSize = 0; - } - else - { - return createRc; - } - } - } - - return requiredSize > 0 ? ResultFs.InsufficientFreeSpace.Log() : Result.Success; + requiredSize = requiredSizeBcat; + return requiredSizeBcat > 0 ? ResultFs.InsufficientFreeSpace.Log() : Result.Success; } private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, out long requiredSize, @@ -407,6 +327,12 @@ namespace LibHac.Fs nacp.SaveDataOwnerId, 0, nacp.CacheStorageSize, nacp.CacheStorageJournalSize, true); } + public static Result EnsureApplicationBcatDeliveryCacheStorage(this FileSystemClient fs, out long requiredSize, + TitleId applicationId, ref ApplicationControlProperty nacp) + { + return EnsureApplicationBcatDeliveryCacheStorageImpl(fs, out requiredSize, applicationId, ref nacp); + } + public static Result TryCreateCacheStorage(this FileSystemClient fs, out long requiredSize, SaveDataSpaceId spaceId, TitleId applicationId, TitleId saveDataOwnerId, short index, long dataSize, long journalSize, bool allowExisting) @@ -464,44 +390,35 @@ namespace LibHac.Fs private static Result GetCacheStorageTargetMediaImpl(this FileSystemClient fs, out CacheStorageTargetMedia target, TitleId applicationId) { - target = default; + target = CacheStorageTargetMedia.None; + + var filter = new SaveDataFilter(); + filter.SetProgramId(applicationId); + filter.SetSaveDataType(SaveDataType.Cache); 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.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; if (rc.IsSuccess()) { target = CacheStorageTargetMedia.SdCard; - return Result.Success; } - - if (!ResultFs.TargetNotFound.Includes(rc)) - return rc; } + // Not on the SD card. Check it it's in NAND + if (target == CacheStorageTargetMedia.None) { - var filter = new SaveDataFilter(); - filter.SetProgramId(applicationId); - filter.SetSaveDataType(SaveDataType.Cache); - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.User, ref filter); + if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; if (rc.IsSuccess()) { target = CacheStorageTargetMedia.Nand; - return Result.Success; } - - if (!ResultFs.TargetNotFound.Includes(rc)) - return rc; } - target = CacheStorageTargetMedia.None; return Result.Success; }