From 44ff25ee9bfa73d04ccffbf0bf5705e7bb757d98 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 13 Feb 2020 17:21:24 -0700 Subject: [PATCH] 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); + } + } +}