Merge pull request #116 from Thealexbarney/cache-storage

Add support for cache storage
This commit is contained in:
Alex Barney 2020-02-24 00:42:00 -07:00 committed by GitHub
commit bd9ad55715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1092 additions and 179 deletions

View File

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using LibHac.Account;
using LibHac.Fs.Shim;
using LibHac.Ncm;
@ -12,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.SetTitleId(applicationId);
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.SetTitleId(applicationId);
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,
@ -155,7 +84,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);
@ -220,71 +149,277 @@ namespace LibHac.Fs
return Result.Success;
}
private static Result CreateSaveData(FileSystemClient fs, Func<Result> createFunc, ref long requiredSize, long baseSize,
long dataSize, long journalSize)
{
Result rc = createFunc();
if (rc.IsSuccess())
return Result.Success;
if (ResultFs.InsufficientFreeSpace.Includes(rc))
{
Result queryRc = fs.QuerySaveDataTotalSize(out long totalSize, dataSize, journalSize);
if (queryRc.IsFailure()) return queryRc;
requiredSize += Util.AlignUp(totalSize, 0x4000) + baseSize;
}
else if (!ResultFs.PathAlreadyExists.Includes(rc))
{
return rc;
}
return Result.Success;
}
private static Result EnsureAndExtendSaveData(FileSystemClient fs, Func<Result> 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.SetTitleId(applicationId);
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())
Result rc = EnsureAndExtendSaveData(fs, CreateBcatStorageFunc,
ref requiredSizeBcat, ref filter, 0x4000, bcatStorageSize, bcatDeliveryCacheJournalSize);
if (rc.IsFailure()) return rc;
requiredSize = requiredSizeBcat;
return requiredSizeBcat > 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 = ExtendSaveDataIfNeeded(fs, out long requiredSizeBcat, SaveDataSpaceId.User,
saveDataInfo.SaveDataId, nacp.BcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize);
if (rc.IsFailure())
{
if (!ResultFs.InsufficientFreeSpace.Includes(rc))
{
return rc;
}
requiredSize = requiredSizeBcat;
}
rc = TryCreateCacheStorage(fs, out requiredSizeLocal, SaveDataSpaceId.User, applicationId,
saveDataOwnerId, index, dataSize, journalSize, allowExisting);
if (rc.IsFailure()) return rc;
}
else if (!ResultFs.TargetNotFound.Includes(rc))
else if (targetMedia == CacheStorageTargetMedia.SdCard)
{
return rc;
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
{
Result createRc = fs.CreateBcatSaveData(applicationId, nacp.BcatDeliveryCacheStorageSize);
if (createRc.IsFailure())
// Try to create the savedata on the SD card first
if (fs.IsSdCardAccessible())
{
if (ResultFs.InsufficientFreeSpace.Includes(createRc))
{
Result queryRc = fs.QuerySaveDataTotalSize(out long saveTotalSize,
nacp.BcatDeliveryCacheStorageSize, bcatDeliveryCacheJournalSize);
target = CacheStorageTargetMedia.SdCard;
if (queryRc.IsFailure()) return queryRc;
Result CreateFuncSdCard() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache,
saveDataOwnerId, index, dataSize, journalSize, SaveDataFlags.None);
requiredSize = Util.AlignUp(saveTotalSize, 0x4000) + 0x4000;
}
else if (ResultFs.PathAlreadyExists.Includes(createRc))
rc = CreateSaveData(fs, CreateFuncSdCard, ref requiredSizeLocal, 0x4000, dataSize, journalSize);
if (rc.IsFailure()) return rc;
if (requiredSizeLocal == 0)
{
requiredSize = 0;
return Result.Success;
}
else
{
return createRc;
}
}
// 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();
}
}
return requiredSize > 0 ? ResultFs.InsufficientFreeSpace.Log() : Result.Success;
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 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)
{
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 = CacheStorageTargetMedia.None;
var filter = new SaveDataFilter();
filter.SetProgramId(applicationId);
filter.SetSaveDataType(SaveDataType.Cache);
if (fs.IsSdCardAccessible())
{
Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.SdCache, ref filter);
if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc;
if (rc.IsSuccess())
{
target = CacheStorageTargetMedia.SdCard;
}
}
// Not on the SD card. Check it it's in NAND
if (target == CacheStorageTargetMedia.None)
{
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;
}
public static Result CleanUpTemporaryStorage(FileSystemClient fs)

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,18 @@ namespace LibHac.Fs
KeepAfterResettingSystemSaveDataWithoutUserSaveData = 1 << 2,
NeedsSecureDelete = 1 << 3
}
public enum SdmmcPort
{
Mmc = 0,
SdCard = 1,
GcAsic = 2
}
public enum CacheStorageTargetMedia
{
None = 0,
Nand = 1,
SdCard = 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,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);
}
}
}

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