Add cache storage and an emulated SD card

This commit is contained in:
Alex Barney 2020-02-13 17:21:24 -07:00
parent 4ff12ec21b
commit 44ff25ee9b
25 changed files with 606 additions and 41 deletions

View File

@ -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);

View File

@ -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<EncryptionSeed>
{
private readonly Key128 Key;
public ReadOnlySpan<byte> Value => SpanHelpers.AsByteSpan(ref this);
public EncryptionSeed(ReadOnlySpan<byte> 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);
}
}

View File

@ -173,4 +173,11 @@ namespace LibHac.Fs
KeepAfterResettingSystemSaveDataWithoutUserSaveData = 1 << 2,
NeedsSecureDelete = 1 << 3
}
public enum SdmmcPort
{
Mmc = 0,
SdCard = 1,
GcAsic = 2
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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
};
}
}

View File

@ -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)

View File

@ -61,6 +61,7 @@ namespace LibHac.FsService
xci = CardImage;
return Result.Success;
}
public Result Read(GameCardHandle handle, long offset, Span<byte> destination)
{
if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnRead.Log();

View File

@ -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;
}
}
}

View File

@ -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<byte> 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)

View File

@ -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<byte> 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;
}

View File

@ -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();

View File

@ -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<byte> seed);
Result SetSdCardEncryptionSeed(ref EncryptionSeed seed);
Result SetSdCardAccessibility(bool isAccessible);
Result IsSdCardAccessible(out bool isAccessible);

View File

@ -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);
}
}

View File

@ -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; }

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -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"));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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")]

View File

@ -8,7 +8,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.core" Version="2.4.1" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.assert.source" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -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);
}
}
}