Separate fssrv save code to SaveDataFileSystemService

This commit is contained in:
Alex Barney 2020-11-01 02:41:51 -07:00
parent 3837ed7eea
commit 882e6bc937
103 changed files with 5406 additions and 1791 deletions

View File

@ -1 +1 @@
3.1.401
3.1.402

View File

@ -267,6 +267,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6203,,InvalidOpenModeForWrite,
2,6300,6399,UnsupportedOperation,
2,6301,,UnsupportedCommitTarget,
2,6302,,UnsupportedOperationInSubStorageSetSize,Attempted to resize a non-resizable SubStorage.
2,6303,,UnsupportedOperationInResizableSubStorageSetSize,Attempted to resize a SubStorage that wasn't located at the end of the base storage.
2,6304,,UnsupportedOperationInMemoryStorageSetSize,
@ -297,10 +298,15 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6452,,ExternalKeyAlreadyRegistered,
2,6454,,WriteStateUnflushed,
2,6456,,DirectoryNotClosed,
2,6457,,WriteModeFileNotClosed,
2,6458,,AllocatorAlreadyRegistered,
2,6459,,DefaultAllocatorUsed,
2,6461,,AllocatorAlignmentViolation,
2,6463,,MultiCommitFileSystemAlreadyAdded,The provided file system has already been added to the multi-commit manager.
2,6465,,UserNotExist,
2,6466,,DefaultGlobalFileDataCacheEnabled,
2,6467,,SaveDataRootPathUnavailable,
2,6600,6699,EntryNotFound,
2,6605,,TargetProgramNotFound,Specified program is not found in the program registry.

Can't render this file because it has a wrong number of fields in line 220.

View File

@ -502,7 +502,7 @@ namespace LibHac.Boot
Package1Section.Bootloader => 0,
Package1Section.SecureMonitor => 1,
Package1Section.WarmBoot => 2,
_ => -1,
_ => -1
};
}
@ -513,7 +513,7 @@ namespace LibHac.Boot
Package1Section.Bootloader => 1,
Package1Section.SecureMonitor => 2,
Package1Section.WarmBoot => 0,
_ => -1,
_ => -1
};
}
@ -522,7 +522,7 @@ namespace LibHac.Boot
Package1Section.Bootloader => 1,
Package1Section.SecureMonitor => 0,
Package1Section.WarmBoot => 2,
_ => -1,
_ => -1
};
}

View File

@ -31,7 +31,7 @@ namespace LibHac.Common.Keys
CommonSeedDiff = Common | Seed | DifferentDev,
CommonDrvd = Common | Derived,
DeviceRoot = Device | Root,
DeviceDrvd = Device | Derived,
DeviceDrvd = Device | Derived
}
public readonly string Name;

View File

@ -0,0 +1,22 @@
using System.Runtime.CompilerServices;
namespace LibHac.Common
{
public static class Shared
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Move<T>(ref T value)
{
T tmp = value;
value = default;
return tmp;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Move<T>(out T dest, ref T value)
{
dest = value;
value = default;
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Sf;
namespace LibHac.Fs.Accessors
{
@ -20,10 +21,13 @@ namespace LibHac.Fs.Accessors
private readonly object _locker = new object();
internal bool IsAccessLogEnabled { get; set; }
public IMultiCommitTarget MultiCommitTarget { get; }
public FileSystemAccessor(string name, IFileSystem baseFileSystem, FileSystemClient fsClient, ICommonMountNameGenerator nameGenerator)
public FileSystemAccessor(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem baseFileSystem,
FileSystemClient fsClient, ICommonMountNameGenerator nameGenerator)
{
Name = name;
Name = name.ToString();
MultiCommitTarget = multiCommitTarget;
FileSystem = baseFileSystem;
FsClient = fsClient;
MountNameGenerator = nameGenerator;
@ -147,6 +151,11 @@ namespace LibHac.Fs.Accessors
return MountNameGenerator.GenerateCommonMountName(nameBuffer);
}
public ReferenceCountedDisposable<IFileSystemSf> GetMultiCommitTarget()
{
return MultiCommitTarget?.GetMultiCommitTarget();
}
internal void NotifyCloseFile(FileAccessor file)
{
lock (_locker)

View File

@ -232,7 +232,7 @@ namespace LibHac.Fs
}
private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, out long requiredSize,
out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, short index,
out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index,
long dataSize, long journalSize, bool allowExisting)
{
requiredSize = default;
@ -299,7 +299,7 @@ namespace LibHac.Fs
}
public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize,
out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, short index,
out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index,
long dataSize, long journalSize, bool allowExisting)
{
return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId, saveDataOwnerId,
@ -334,7 +334,7 @@ namespace LibHac.Fs
}
public static Result TryCreateCacheStorage(this FileSystemClient fs, out long requiredSize,
SaveDataSpaceId spaceId, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, short index,
SaveDataSpaceId spaceId, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index,
long dataSize, long journalSize, bool allowExisting)
{
requiredSize = default;

View File

@ -1,8 +1,9 @@
using LibHac.Common;
using System;
using LibHac.Common;
namespace LibHac.Fs
{
internal static class CommonMountNames
internal static class CommonPaths
{
public const char ReservedMountNamePrefixCharacter = '@';
@ -21,5 +22,11 @@ namespace LibHac.Fs
public const char GameCardFileSystemMountNameUpdateSuffix = 'U';
public const char GameCardFileSystemMountNameNormalSuffix = 'N';
public const char GameCardFileSystemMountNameSecureSuffix = 'S';
public static ReadOnlySpan<byte> SdCardNintendoRootDirectoryName => // Nintendo
new[]
{
(byte) 'N', (byte) 'i', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 'd', (byte) 'o'
};
}
}

View File

@ -134,7 +134,13 @@ namespace LibHac.Fs
public Result Register(U8Span mountName, IFileSystem fileSystem, ICommonMountNameGenerator nameGenerator)
{
var accessor = new FileSystemAccessor(mountName.ToString(), fileSystem, this, nameGenerator);
return Register(mountName, null, fileSystem, nameGenerator);
}
public Result Register(U8Span mountName, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem,
ICommonMountNameGenerator nameGenerator)
{
var accessor = new FileSystemAccessor(mountName, multiCommitTarget, fileSystem, this, nameGenerator);
Result rc = MountTable.Mount(accessor);
if (rc.IsFailure()) return rc;
@ -173,8 +179,8 @@ namespace LibHac.Fs
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
int hostMountNameLen = StringUtils.GetLength(CommonMountNames.HostRootFileSystemMountName);
if (StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, hostMountNameLen) == 0)
int hostMountNameLen = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName);
if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountNameLen) == 0)
{
return ResultFs.NotMounted.Log();
}
@ -195,7 +201,7 @@ namespace LibHac.Fs
if (PathUtility.IsWindowsDrive(path) || PathUtility.IsUnc(path))
{
StringUtils.Copy(mountName.Name, CommonMountNames.HostRootFileSystemMountName);
StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName);
mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator;
subPath = path;

View File

@ -96,10 +96,11 @@ namespace LibHac.Fs
public enum SaveDataState : byte
{
Normal = 0,
Creating = 1,
Processing = 1,
State2 = 2,
MarkedForDeletion = 3,
Extending = 4
Extending = 4,
ImportSuspended = 5
}
public enum ImageDirectoryId
@ -165,7 +166,8 @@ namespace LibHac.Fs
KeepAfterResettingSystemSaveData = 1 << 0,
KeepAfterRefurbishment = 1 << 1,
KeepAfterResettingSystemSaveDataWithoutUserSaveData = 1 << 2,
NeedsSecureDelete = 1 << 3
NeedsSecureDelete = 1 << 3,
Restore = 1 << 4
}
public enum SdmmcPort

View File

@ -37,6 +37,31 @@ namespace LibHac.Fs.Fsa
return DoCreateFile(path, size, option);
}
/// <summary>
/// Creates or overwrites a file at the specified path.
/// </summary>
/// <param name="path">The full path of the file to create.</param>
/// <param name="size">The initial size of the created file.
/// Should usually be <see cref="CreateFileOptions.None"/></param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
/// Insufficient free space to create the file: <see cref="ResultFs.InsufficientFreeSpace"/>
/// </remarks>
public Result CreateFile(U8Span path, long size)
{
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
if (size < 0)
return ResultFs.OutOfRange.Log();
return DoCreateFile(path, size, CreateFileOptions.None);
}
/// <summary>
/// Deletes the specified file.
/// </summary>

View File

@ -129,11 +129,19 @@ namespace LibHac.Fs.Impl
Result rc = GetPathForServiceObject(out Path sfPath, path);
if (rc.IsFailure()) return rc;
rc = BaseFs.Target.OpenFile(out ReferenceCountedDisposable<IFileSf> sfFile, in sfPath, (uint)mode);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSf> sfFile = null;
try
{
rc = BaseFs.Target.OpenFile(out sfFile, in sfPath, (uint)mode);
if (rc.IsFailure()) return rc;
file = new FileServiceObjectAdapter(sfFile);
return Result.Success;
file = new FileServiceObjectAdapter(sfFile);
return Result.Success;
}
finally
{
sfFile?.Dispose();
}
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
@ -143,11 +151,19 @@ namespace LibHac.Fs.Impl
Result rc = GetPathForServiceObject(out Path sfPath, path);
if (rc.IsFailure()) return rc;
rc = BaseFs.Target.OpenDirectory(out ReferenceCountedDisposable<IDirectorySf> sfDir, in sfPath, (uint)mode);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IDirectorySf> sfDir = null;
try
{
rc = BaseFs.Target.OpenDirectory(out sfDir, in sfPath, (uint)mode);
if (rc.IsFailure()) return rc;
directory = new DirectoryServiceObjectAdapter(sfDir);
return Result.Success;
directory = new DirectoryServiceObjectAdapter(sfDir);
return Result.Success;
}
finally
{
sfDir?.Dispose();
}
}
protected override Result DoCommit()
@ -175,7 +191,7 @@ namespace LibHac.Fs.Impl
public ReferenceCountedDisposable<IFileSystemSf> GetMultiCommitTarget()
{
return BaseFs;
return BaseFs.AddReference();
}
protected override void Dispose(bool disposing)

View File

@ -1,5 +1,5 @@
using LibHac.Common;
using static LibHac.Fs.CommonMountNames;
using static LibHac.Fs.CommonPaths;
namespace LibHac.Fs
{

View File

@ -25,7 +25,7 @@ namespace LibHac.Fs
{
InternalKeyForSoftwareAes = 1 << 0,
InternalKeyForHardwareAes = 1 << 1,
ExternalKeyForHardwareAes = 1 << 2,
ExternalKeyForHardwareAes = 1 << 2
}
}
}

View File

@ -423,6 +423,8 @@ namespace LibHac.Fs
/// <summary>Error code: 2002-6300; Range: 6300-6399; Inner value: 0x313802</summary>
public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6300, 6399); }
/// <summary>Error code: 2002-6301; Inner value: 0x313a02</summary>
public static Result.Base UnsupportedCommitTarget => new Result.Base(ModuleFs, 6301);
/// <summary>Attempted to resize a non-resizable SubStorage.<br/>Error code: 2002-6302; Inner value: 0x313c02</summary>
public static Result.Base UnsupportedOperationInSubStorageSetSize => new Result.Base(ModuleFs, 6302);
/// <summary>Attempted to resize a SubStorage that wasn't located at the end of the base storage.<br/>Error code: 2002-6303; Inner value: 0x313e02</summary>
@ -481,14 +483,24 @@ namespace LibHac.Fs
public static Result.Base ExternalKeyAlreadyRegistered => new Result.Base(ModuleFs, 6452);
/// <summary>Error code: 2002-6454; Inner value: 0x326c02</summary>
public static Result.Base WriteStateUnflushed => new Result.Base(ModuleFs, 6454);
/// <summary>Error code: 2002-6456; Inner value: 0x327002</summary>
public static Result.Base DirectoryNotClosed => new Result.Base(ModuleFs, 6456);
/// <summary>Error code: 2002-6457; Inner value: 0x327202</summary>
public static Result.Base WriteModeFileNotClosed => new Result.Base(ModuleFs, 6457);
/// <summary>Error code: 2002-6458; Inner value: 0x327402</summary>
public static Result.Base AllocatorAlreadyRegistered => new Result.Base(ModuleFs, 6458);
/// <summary>Error code: 2002-6459; Inner value: 0x327602</summary>
public static Result.Base DefaultAllocatorUsed => new Result.Base(ModuleFs, 6459);
/// <summary>Error code: 2002-6461; Inner value: 0x327a02</summary>
public static Result.Base AllocatorAlignmentViolation => new Result.Base(ModuleFs, 6461);
/// <summary>The provided file system has already been added to the multi-commit manager.<br/>Error code: 2002-6463; Inner value: 0x327e02</summary>
public static Result.Base MultiCommitFileSystemAlreadyAdded => new Result.Base(ModuleFs, 6463);
/// <summary>Error code: 2002-6465; Inner value: 0x328202</summary>
public static Result.Base UserNotExist => new Result.Base(ModuleFs, 6465);
/// <summary>Error code: 2002-6466; Inner value: 0x328402</summary>
public static Result.Base DefaultGlobalFileDataCacheEnabled => new Result.Base(ModuleFs, 6466);
/// <summary>Error code: 2002-6467; Inner value: 0x328602</summary>
public static Result.Base SaveDataRootPathUnavailable => new Result.Base(ModuleFs, 6467);
/// <summary>Error code: 2002-6600; Range: 6600-6699; Inner value: 0x339002</summary>
public static Result.Base EntryNotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6600, 6699); }

View File

@ -1,7 +1,9 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Ncm;
using LibHac.Util;
namespace LibHac.Fs
{
@ -13,17 +15,17 @@ namespace LibHac.Fs
[FieldOffset(0x18)] public ulong StaticSaveDataId;
[FieldOffset(0x20)] public SaveDataType Type;
[FieldOffset(0x21)] public SaveDataRank Rank;
[FieldOffset(0x22)] public short Index;
[FieldOffset(0x22)] public ushort Index;
public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId) : this(
programId, type, userId, saveDataId, 0, SaveDataRank.Primary)
{ }
public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId,
short index) : this(programId, type, userId, saveDataId, index, SaveDataRank.Primary)
ushort index) : this(programId, type, userId, saveDataId, index, SaveDataRank.Primary)
{ }
public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, short index,
public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, ushort index,
SaveDataRank rank)
{
ProgramId = programId;
@ -34,6 +36,38 @@ namespace LibHac.Fs
Rank = rank;
}
public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type,
UserId userId, ulong staticSaveDataId)
{
return Make(out attribute, programId, type, userId, staticSaveDataId, 0, SaveDataRank.Primary);
}
public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type,
UserId userId, ulong staticSaveDataId, ushort index)
{
return Make(out attribute, programId, type, userId, staticSaveDataId, index, SaveDataRank.Primary);
}
public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type,
UserId userId, ulong staticSaveDataId, ushort index, SaveDataRank rank)
{
Unsafe.SkipInit(out attribute);
SaveDataAttribute tempAttribute = default;
tempAttribute.ProgramId = programId;
tempAttribute.Type = type;
tempAttribute.UserId = userId;
tempAttribute.StaticSaveDataId = staticSaveDataId;
tempAttribute.Index = index;
tempAttribute.Rank = rank;
if (!SaveDataTypesValidity.IsValid(in tempAttribute))
return ResultFs.InvalidArgument.Log();
attribute = tempAttribute;
return Result.Success;
}
public override readonly bool Equals(object obj)
{
return obj is SaveDataAttribute attribute && Equals(attribute);
@ -85,40 +119,96 @@ namespace LibHac.Fs
[FieldOffset(0x04)] public bool FilterByIndex;
[FieldOffset(0x05)] public SaveDataRank Rank;
[FieldOffset(0x08)] public ProgramId ProgramId;
[FieldOffset(0x10)] public UserId UserId;
[FieldOffset(0x20)] public ulong SaveDataId;
[FieldOffset(0x28)] public SaveDataType SaveDataType;
[FieldOffset(0x2A)] public short Index;
[FieldOffset(0x08)] public SaveDataAttribute Attribute;
public void SetProgramId(ProgramId value)
{
FilterByProgramId = true;
ProgramId = value;
Attribute.ProgramId = value;
}
public void SetSaveDataType(SaveDataType value)
{
FilterBySaveDataType = true;
SaveDataType = value;
Attribute.Type = value;
}
public void SetUserId(UserId value)
{
FilterByUserId = true;
UserId = value;
Attribute.UserId = value;
}
public void SetSaveDataId(ulong value)
{
FilterBySaveDataId = true;
SaveDataId = value;
Attribute.StaticSaveDataId = value;
}
public void SetIndex(short value)
public void SetIndex(ushort value)
{
FilterByIndex = true;
Index = value;
Attribute.Index = value;
}
public static Result Make(out SaveDataFilter filter, Optional<ulong> programId, Optional<SaveDataType> saveType,
Optional<UserId> userId, Optional<ulong> saveDataId, Optional<ushort> index)
{
return Make(out filter, programId, saveType, userId, saveDataId, index, SaveDataRank.Primary);
}
public static Result Make(out SaveDataFilter filter, Optional<ulong> programId, Optional<SaveDataType> saveType,
Optional<UserId> userId, Optional<ulong> saveDataId, Optional<ushort> index, SaveDataRank rank)
{
Unsafe.SkipInit(out filter);
SaveDataFilter tempFilter = Make(programId, saveType, userId, saveDataId, index, rank);
if (!SaveDataTypesValidity.IsValid(in tempFilter))
return ResultFs.InvalidArgument.Log();
filter = tempFilter;
return Result.Success;
}
public static SaveDataFilter Make(Optional<ulong> programId, Optional<SaveDataType> saveType,
Optional<UserId> userId, Optional<ulong> saveDataId, Optional<ushort> index, SaveDataRank rank)
{
var filter = new SaveDataFilter();
if (programId.HasValue)
{
filter.FilterByProgramId = true;
filter.Attribute.ProgramId = new ProgramId(programId.Value);
}
if (saveType.HasValue)
{
filter.FilterBySaveDataType = true;
filter.Attribute.Type = saveType.Value;
}
if (userId.HasValue)
{
filter.FilterByUserId = true;
filter.Attribute.UserId = userId.Value;
}
if (saveDataId.HasValue)
{
filter.FilterBySaveDataId = true;
filter.Attribute.StaticSaveDataId = saveDataId.Value;
}
if (index.HasValue)
{
filter.FilterByIndex = true;
filter.Attribute.Index = index.Value;
}
filter.Rank = rank;
return filter;
}
}
@ -130,6 +220,7 @@ namespace LibHac.Fs
[FieldOffset(0x00)] private byte _hashStart;
public Span<byte> Hash => SpanHelpers.CreateSpan(ref _hashStart, HashLength);
public ReadOnlySpan<byte> HashRo => SpanHelpers.CreateReadOnlySpan(in _hashStart, HashLength);
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
@ -140,7 +231,7 @@ namespace LibHac.Fs
}
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
public struct SaveMetaCreateInfo
public struct SaveDataMetaInfo
{
[FieldOffset(0)] public int Size;
[FieldOffset(4)] public SaveDataMetaType Type;
@ -151,7 +242,7 @@ namespace LibHac.Fs
{
[FieldOffset(0x00)] public long Size;
[FieldOffset(0x08)] public long JournalSize;
[FieldOffset(0x10)] public ulong BlockSize;
[FieldOffset(0x10)] public long BlockSize;
[FieldOffset(0x18)] public ulong OwnerId;
[FieldOffset(0x20)] public SaveDataFlags Flags;
[FieldOffset(0x24)] public SaveDataSpaceId SpaceId;
@ -168,8 +259,66 @@ namespace LibHac.Fs
[FieldOffset(0x20)] public ulong StaticSaveDataId;
[FieldOffset(0x28)] public ProgramId ProgramId;
[FieldOffset(0x30)] public long Size;
[FieldOffset(0x38)] public short Index;
[FieldOffset(0x38)] public ushort Index;
[FieldOffset(0x3A)] public SaveDataRank Rank;
[FieldOffset(0x3B)] public SaveDataState State;
}
[StructLayout(LayoutKind.Explicit, Size = 0x200)]
public struct SaveDataExtraData
{
[FieldOffset(0x00)] public SaveDataAttribute Attribute;
[FieldOffset(0x40)] public ulong OwnerId;
[FieldOffset(0x48)] public ulong TimeStamp;
[FieldOffset(0x50)] public SaveDataFlags Flags;
[FieldOffset(0x58)] public long DataSize;
[FieldOffset(0x60)] public long JournalSize;
[FieldOffset(0x68)] public long CommitId;
}
internal static class SaveDataTypesValidity
{
public static bool IsValid(in SaveDataAttribute attribute)
{
return IsValid(in attribute.Type) && IsValid(in attribute.Rank);
}
public static bool IsValid(in SaveDataCreationInfo creationInfo)
{
return creationInfo.Size >= 0 && creationInfo.JournalSize >= 0 && creationInfo.BlockSize >= 0 &&
IsValid(in creationInfo.SpaceId);
}
public static bool IsValid(in SaveDataMetaInfo metaInfo)
{
return IsValid(in metaInfo.Type);
}
public static bool IsValid(in SaveDataFilter filter)
{
return IsValid(in filter.Attribute);
}
public static bool IsValid(in SaveDataType type)
{
// SaveDataType.SystemBcat is excluded in this check
return (uint)type <= (uint)SaveDataType.Cache;
}
public static bool IsValid(in SaveDataRank rank)
{
return (uint)rank <= (uint)SaveDataRank.Secondary;
}
public static bool IsValid(in SaveDataSpaceId spaceId)
{
return (uint)spaceId <= (uint)SaveDataSpaceId.SdCache || spaceId == SaveDataSpaceId.ProperSystem ||
spaceId == SaveDataSpaceId.SafeMode;
}
public static bool IsValid(in SaveDataMetaType metaType)
{
return (uint)metaType <= (uint)SaveDataMetaType.ExtensionContext;
}
}
}

View File

@ -1,7 +1,8 @@
using System;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv;
using LibHac.FsSrv.Sf;
namespace LibHac.Fs.Shim
{
@ -43,12 +44,23 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.Zero, 0);
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.InvalidId, 0);
rc = fsProxy.OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId.User, ref attribute);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSystemSf> saveFs = null;
return fs.Register(mountName, fileSystem);
try
{
rc = fsProxy.OpenSaveDataFileSystem(out saveFs, SaveDataSpaceId.User, in attribute);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
saveFs?.Dispose();
}
}
}
}

View File

@ -6,7 +6,7 @@ using LibHac.FsSrv;
using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using LibHac.Util;
using static LibHac.Fs.CommonMountNames;
using static LibHac.Fs.CommonPaths;
namespace LibHac.Fs.Shim
{

View File

@ -83,7 +83,7 @@ namespace LibHac.Fs.Shim
ContentType.Control => FileSystemProxyType.Control,
ContentType.Manual => FileSystemProxyType.Manual,
ContentType.Data => FileSystemProxyType.Data,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
}

View File

@ -39,11 +39,11 @@ namespace LibHac.Fs.Shim
switch (storageId)
{
case ContentStorageId.System:
return CommonMountNames.ContentStorageSystemMountName;
return CommonPaths.ContentStorageSystemMountName;
case ContentStorageId.User:
return CommonMountNames.ContentStorageUserMountName;
return CommonPaths.ContentStorageUserMountName;
case ContentStorageId.SdCard:
return CommonMountNames.ContentStorageSdCardMountName;
return CommonPaths.ContentStorageSdCardMountName;
default:
throw new ArgumentOutOfRangeException(nameof(storageId), storageId, null);
}

View File

@ -76,7 +76,7 @@ namespace LibHac.Fs.Shim
{
char letter = GetGameCardMountNameSuffix(PartitionId);
string mountName = $"{CommonMountNames.GameCardFileSystemMountName}{letter}{Handle.Value:x8}";
string mountName = $"{CommonPaths.GameCardFileSystemMountName}{letter}{Handle.Value:x8}";
new U8Span(mountName).Value.CopyTo(nameBuffer);
return Result.Success;

View File

@ -6,7 +6,7 @@ using LibHac.Fs.Fsa;
using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.Util;
using static LibHac.Fs.CommonMountNames;
using static LibHac.Fs.CommonPaths;
namespace LibHac.Fs.Shim
{

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using LibHac.FsSrv;
using LibHac.Sf;
namespace LibHac.Fs.Shim
{
@ -21,7 +22,7 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
ReadOnlySpan<byte> mapInfoBuffer = MemoryMarshal.Cast<ProgramIndexMapInfo, byte>(mapInfo);
var mapInfoBuffer = new InBuffer(MemoryMarshal.Cast<ProgramIndexMapInfo, byte>(mapInfo));
return fsProxy.RegisterProgramIndexMapInfo(mapInfoBuffer, mapInfo.Length);
}

View File

@ -1,7 +1,8 @@
using System;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
namespace LibHac.Fs.Shim
@ -115,14 +116,14 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (short)index);
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (ushort)index);
TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", index: {index}");
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (short)index);
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (ushort)index);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application))
@ -165,14 +166,14 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (short)index);
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (ushort)index);
TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, index: {index}");
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (short)index);
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (ushort)index);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System))
@ -184,7 +185,7 @@ namespace LibHac.Fs.Shim
}
private static Result MountSaveDataImpl(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId,
ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, short index)
ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, ushort index)
{
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
@ -193,20 +194,29 @@ namespace LibHac.Fs.Shim
var attribute = new SaveDataAttribute(programId, type, userId, 0, index);
IFileSystem saveFs;
ReferenceCountedDisposable<IFileSystemSf> saveFs = null;
if (openReadOnly)
try
{
rc = fsProxy.OpenReadOnlySaveDataFileSystem(out saveFs, spaceId, ref attribute);
if (openReadOnly)
{
rc = fsProxy.OpenReadOnlySaveDataFileSystem(out saveFs, spaceId, in attribute);
}
else
{
rc = fsProxy.OpenSaveDataFileSystem(out saveFs, spaceId, in attribute);
}
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs);
return fs.Register(mountName, fileSystemAdapter, fileSystemAdapter, null);
}
else
finally
{
rc = fsProxy.OpenSaveDataFileSystem(out saveFs, spaceId, ref attribute);
saveFs?.Dispose();
}
if (rc.IsFailure()) return rc;
return fs.Register(mountName, saveFs);
}
}
}

View File

@ -1,9 +1,10 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.FsSrv;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Sf;
namespace LibHac.Fs.Shim
{
@ -29,13 +30,13 @@ namespace LibHac.Fs.Shim
SpaceId = SaveDataSpaceId.User
};
var metaInfo = new SaveMetaCreateInfo
var metaInfo = new SaveDataMetaInfo
{
Type = SaveDataMetaType.Thumbnail,
Size = 0x40060
};
return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo);
return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo);
},
() =>
$", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}");
@ -61,14 +62,14 @@ namespace LibHac.Fs.Shim
SpaceId = SaveDataSpaceId.User
};
var metaInfo = new SaveMetaCreateInfo
var metaInfo = new SaveDataMetaInfo
{
Type = SaveDataMetaType.Thumbnail,
Size = 0x40060
};
return fsProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref createInfo, ref metaInfo,
ref hashSalt);
return fsProxy.CreateSaveDataFileSystemWithHashSalt(in attribute, in createInfo, in metaInfo,
in hashSalt);
},
() =>
$", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}");
@ -81,7 +82,7 @@ namespace LibHac.Fs.Shim
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.Zero, 0);
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.InvalidId, 0);
var createInfo = new SaveDataCreationInfo
{
@ -93,9 +94,9 @@ namespace LibHac.Fs.Shim
SpaceId = SaveDataSpaceId.User
};
var metaInfo = new SaveMetaCreateInfo();
var metaInfo = new SaveDataMetaInfo();
return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo);
return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo);
},
() => $", applicationid: 0x{applicationId.Value:X}, save_data_size: {size}");
}
@ -108,7 +109,7 @@ namespace LibHac.Fs.Shim
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Device, UserId.Zero, 0);
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Device, UserId.InvalidId, 0);
var createInfo = new SaveDataCreationInfo
{
@ -120,9 +121,9 @@ namespace LibHac.Fs.Shim
SpaceId = SaveDataSpaceId.User
};
var metaInfo = new SaveMetaCreateInfo();
var metaInfo = new SaveDataMetaInfo();
return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo);
return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo);
},
() => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}");
}
@ -134,7 +135,7 @@ namespace LibHac.Fs.Shim
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Temporary, UserId.Zero, 0);
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Temporary, UserId.InvalidId, 0);
var createInfo = new SaveDataCreationInfo
{
@ -145,22 +146,22 @@ namespace LibHac.Fs.Shim
SpaceId = SaveDataSpaceId.Temporary
};
var metaInfo = new SaveMetaCreateInfo();
var metaInfo = new SaveDataMetaInfo();
return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo);
return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo);
},
() => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_flags: 0x{(int)flags:X8}");
}
public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId,
SaveDataSpaceId spaceId, ulong ownerId, short index, long size, long journalSize, SaveDataFlags flags)
SaveDataSpaceId spaceId, ulong ownerId, ushort index, long size, long journalSize, SaveDataFlags flags)
{
return fs.RunOperationWithAccessLog(AccessLogTarget.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Cache, UserId.Zero, 0, index);
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Cache, UserId.InvalidId, 0, index);
var creationInfo = new SaveDataCreationInfo
{
@ -172,9 +173,9 @@ namespace LibHac.Fs.Shim
SpaceId = spaceId
};
var metaInfo = new SaveMetaCreateInfo();
var metaInfo = new SaveDataMetaInfo();
return fsProxy.CreateSaveDataFileSystem(ref attribute, ref creationInfo, ref metaInfo);
return fsProxy.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo);
},
() => $", applicationid: 0x{applicationId.Value:X}, savedataspaceid: {spaceId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}");
}
@ -231,19 +232,19 @@ namespace LibHac.Fs.Shim
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size,
long journalSize, SaveDataFlags flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.InvalidId, ownerId, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size,
long journalSize, SaveDataFlags flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, 0, size, journalSize, flags);
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.InvalidId, 0, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
ulong ownerId, long size, long journalSize, SaveDataFlags flags)
{
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.InvalidId, ownerId, size, journalSize, flags);
}
public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId)
@ -283,8 +284,8 @@ namespace LibHac.Fs.Shim
tempInfo = new SaveDataInfo();
Result rc = fsProxy.FindSaveDataWithFilter(out long count, SpanHelpers.AsByteSpan(ref tempInfo),
spaceId, ref tempFilter);
Result rc = fsProxy.FindSaveDataWithFilter(out long count, OutBuffer.FromStruct(ref tempInfo),
spaceId, in tempFilter);
if (rc.IsFailure()) return rc;
if (count == 0)
@ -329,49 +330,64 @@ namespace LibHac.Fs.Shim
{
var tempIterator = new SaveDataIterator();
Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
try
{
Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(
out ReferenceCountedDisposable<ISaveDataInfoReader> reader, spaceId);
if (rc.IsFailure()) return rc;
Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(
out ReferenceCountedDisposable<ISaveDataInfoReader> reader, spaceId);
if (rc.IsFailure()) return rc;
tempIterator = new SaveDataIterator(fs, reader);
tempIterator = new SaveDataIterator(fs, reader);
return Result.Success;
},
() => $", savedataspaceid: {spaceId}");
return Result.Success;
},
() => $", savedataspaceid: {spaceId}");
iterator = result.IsSuccess() ? tempIterator : default;
iterator = result.IsSuccess() ? tempIterator : default;
tempIterator = default;
return result;
return result;
}
finally
{
tempIterator.Dispose();
}
}
public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId, ref SaveDataFilter filter)
{
ReferenceCountedDisposable<ISaveDataInfoReader> reader = null;
var tempIterator = new SaveDataIterator();
SaveDataFilter tempFilter = filter;
Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
try
{
Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenSaveDataInfoReaderWithFilter(
out ReferenceCountedDisposable<ISaveDataInfoReader> reader, spaceId, ref tempFilter);
if (rc.IsFailure()) return rc;
Result rc = fsProxy.OpenSaveDataInfoReaderWithFilter(out reader, spaceId, in tempFilter);
if (rc.IsFailure()) return rc;
tempIterator = new SaveDataIterator(fs, reader);
tempIterator = new SaveDataIterator(fs, reader);
return Result.Success;
},
() => $", savedataspaceid: {spaceId}");
return Result.Success;
},
() => $", savedataspaceid: {spaceId}");
iterator = result.IsSuccess() ? tempIterator : default;
iterator = result.IsSuccess() ? tempIterator : default;
return result;
return result;
}
finally
{
reader?.Dispose();
}
}
public static void DisableAutoSaveDataCreation(this FileSystemClient fsClient)
@ -395,14 +411,14 @@ namespace LibHac.Fs.Shim
internal SaveDataIterator(FileSystemClient fsClient, ReferenceCountedDisposable<ISaveDataInfoReader> reader)
{
FsClient = fsClient;
Reader = reader;
Reader = reader.AddReference();
}
public Result ReadSaveDataInfo(out long readCount, Span<SaveDataInfo> buffer)
{
Result rc;
Span<byte> byteBuffer = MemoryMarshal.Cast<SaveDataInfo, byte>(buffer);
var byteBuffer = new OutBuffer(MemoryMarshal.Cast<SaveDataInfo, byte>(buffer));
if (FsClient.IsEnabledAccessLog(AccessLogTarget.System))
{

View File

@ -1,15 +1,17 @@
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
namespace LibHac.Fs.Shim
{
public static class SystemSaveData
{
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId)
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId,
ulong saveDataId)
{
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.Zero);
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.InvalidId);
}
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName,
@ -22,10 +24,20 @@ namespace LibHac.Fs.Shim
var attribute = new SaveDataAttribute(ProgramId.InvalidId, SaveDataType.System, userId, saveDataId);
rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, spaceId, ref attribute);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSystemSf> saveFs = null;
return fs.Register(mountName, fileSystem);
try
{
rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out saveFs, spaceId, in attribute);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
saveFs?.Dispose();
}
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using LibHac.Common;
using LibHac.Fs.Accessors;
using LibHac.FsSrv.Sf;
namespace LibHac.Fs.Shim
{
public static class UserFileSystem
{
public static Result Commit(this FileSystemClient fs, ReadOnlySpan<U8String> mountNames)
{
// Todo: Add access log
if (mountNames.Length > 10)
return ResultFs.InvalidCommitNameCount.Log();
if (mountNames.Length == 0)
return Result.Success;
ReferenceCountedDisposable<IMultiCommitManager> commitManager = null;
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
Result rc = fs.GetFileSystemProxyServiceObject().OpenMultiCommitManager(out commitManager);
if (rc.IsFailure()) return rc;
for (int i = 0; i < mountNames.Length; i++)
{
rc = fs.MountTable.Find(mountNames[i].ToString(), out FileSystemAccessor accessor);
if (rc.IsFailure()) return rc;
fileSystem = accessor.GetMultiCommitTarget();
if (fileSystem is null)
return ResultFs.UnsupportedCommitTarget.Log();
rc = commitManager.Target.Add(fileSystem);
if (rc.IsFailure()) return rc;
}
rc = commitManager.Target.Commit();
if (rc.IsFailure()) return rc;
return Result.Success;
}
finally
{
commitManager?.Dispose();
fileSystem?.Dispose();
}
}
}
}

View File

@ -9,7 +9,7 @@ namespace LibHac.Fs
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct UserId : IEquatable<UserId>, IComparable<UserId>, IComparable
{
public static UserId Zero => default;
public static UserId InvalidId => default;
public readonly Id128 Id;

View File

@ -60,7 +60,7 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Create an SF adapter for the file system
fileSystem = FileSystemInterfaceAdapter.CreateSharedSfFileSystem(ref fs);
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
return Result.Success;
}
@ -117,7 +117,7 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Create an SF adapter for the file system
fileSystem = FileSystemInterfaceAdapter.CreateSharedSfFileSystem(ref fs);
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
return Result.Success;
}
@ -147,7 +147,7 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Create an SF adapter for the file system
fileSystem = FileSystemInterfaceAdapter.CreateSharedSfFileSystem(ref fs);
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
return Result.Success;
}
@ -220,7 +220,7 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Create an SF adapter for the file system
fileSystem = FileSystemInterfaceAdapter.CreateSharedSfFileSystem(ref fs);
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
return Result.Success;
}

View File

@ -40,7 +40,14 @@ namespace LibHac.FsSrv
throw new NotImplementedException();
}
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId)
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath,
BisPartitionId partitionId)
{
return OpenBisFileSystem(out fileSystem, rootPath, partitionId, false);
}
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath,
BisPartitionId partitionId, bool caseSensitive)
{
fileSystem = default;
@ -85,12 +92,12 @@ namespace LibHac.FsSrv
return OpenSdCardProxyFileSystem(out fileSystem, false);
}
public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool isCaseSensitive)
public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool openCaseSensitive)
{
fileSystem = default;
// Todo: Shared
Result rc = _config.SdCardFileSystemCreator.Create(out IFileSystem fs, isCaseSensitive);
Result rc = _config.SdCardFileSystemCreator.Create(out IFileSystem fs, openCaseSensitive);
if (rc.IsFailure()) return rc;
fileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);

View File

@ -87,7 +87,9 @@ namespace LibHac.FsSrv.Creators
return Util.CreateSubFileSystemImpl(out fileSystem, subFileSystem, rootPath);
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId)
// Todo: Make case sensitive
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath,
BisPartitionId partitionId, bool caseSensitive)
{
fileSystem = default;
@ -107,21 +109,28 @@ namespace LibHac.FsSrv.Creators
var partitionPath = GetPartitionPath(partitionId).ToU8String();
// Todo: Store shared file systems
using var sharedRootFs = new ReferenceCountedDisposable<IFileSystem>(Config.RootFileSystem);
Result rc = Utility.WrapSubDirectory(out ReferenceCountedDisposable<IFileSystem> partitionFileSystem,
sharedRootFs, partitionPath, true);
if (rc.IsFailure()) return rc;
if (rootPath.IsEmpty())
ReferenceCountedDisposable<IFileSystem> partitionFileSystem = null;
try
{
fileSystem = partitionFileSystem.AddReference();
return Result.Success;
}
// Todo: Store shared file systems
using var sharedRootFs = new ReferenceCountedDisposable<IFileSystem>(Config.RootFileSystem);
return Utility.CreateSubDirectoryFileSystem(out fileSystem, partitionFileSystem, rootPath);
Result rc = Utility.WrapSubDirectory(out partitionFileSystem, sharedRootFs, partitionPath, true);
if (rc.IsFailure()) return rc;
if (rootPath.IsEmpty())
{
Shared.Move(out fileSystem, ref partitionFileSystem);
return Result.Success;
}
return Utility.CreateSubDirectoryFileSystem(out fileSystem, partitionFileSystem, rootPath);
}
finally
{
partitionFileSystem?.Dispose();
}
}
public Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId)

View File

@ -48,7 +48,7 @@ namespace LibHac.FsSrv.Creators
KeySet.SetSdSeed(encryptionSeed.Value);
// Todo: pass ReferenceCountedDisposable to AesXtsFileSystem
var fs = new AesXtsFileSystem(baseFileSystem.AddReference().Target, KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000);
var fs = new AesXtsFileSystem(baseFileSystem, KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000);
encryptedFileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
return Result.Success;

View File

@ -8,7 +8,7 @@ namespace LibHac.FsSrv.Creators
{
// Todo: Remove raw IFileSystem overload
Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId, bool caseSensitive);
Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId);
Result SetBisRootForHost(BisPartitionId partitionId, string rootPath);
}

View File

@ -2,7 +2,7 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem.Save;
using LibHac.FsSystem;
namespace LibHac.FsSrv.Creators
{
@ -10,9 +10,10 @@ namespace LibHac.FsSrv.Creators
{
Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode);
Result Create(out IFileSystem fileSystem, out ISaveDataExtraDataAccessor extraDataAccessor,
IFileSystem sourceFileSystem, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac,
SaveDataType type, ITimeStampGenerator timeStampGenerator);
Result Create(out IFileSystem fileSystem,
out ReferenceCountedDisposable<ISaveDataExtraDataAccessor> extraDataAccessor, IFileSystem sourceFileSystem,
ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, SaveDataType type,
ITimeStampGenerator timeStampGenerator);
void SetSdCardEncryptionSeed(ReadOnlySpan<byte> seed);
}

View File

@ -8,6 +8,6 @@ namespace LibHac.FsSrv.Creators
// Todo: Remove raw IFilesystem function
Result Create(out IFileSystem fileSystem, bool openCaseSensitive);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool openCaseSensitive);
Result GetCaseSensitivePath(out bool isSuccess, Span<byte> path);
Result NormalizeCaseOfPath(out bool isSupported, Span<byte> path);
}
}

View File

@ -22,9 +22,10 @@ namespace LibHac.FsSrv.Creators
throw new NotImplementedException();
}
public Result Create(out IFileSystem fileSystem, out ISaveDataExtraDataAccessor extraDataAccessor,
IFileSystem sourceFileSystem, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac,
SaveDataType type, ITimeStampGenerator timeStampGenerator)
public Result Create(out IFileSystem fileSystem,
out ReferenceCountedDisposable<ISaveDataExtraDataAccessor> extraDataAccessor, IFileSystem sourceFileSystem,
ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, SaveDataType type,
ITimeStampGenerator timeStampGenerator)
{
fileSystem = default;
extraDataAccessor = default;
@ -42,13 +43,15 @@ namespace LibHac.FsSrv.Creators
case DirectoryEntryType.Directory:
if (!allowDirectorySaveData) return ResultFs.InvalidSaveDataEntryType.Log();
rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subDirFs, sourceFileSystem, saveDataPath);
rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subDirFs, sourceFileSystem,
saveDataPath);
if (rc.IsFailure()) return rc;
bool isPersistentSaveData = type != SaveDataType.Temporary;
bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device;
rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs, isPersistentSaveData, isUserSaveData);
rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs,
isPersistentSaveData, isUserSaveData);
if (rc.IsFailure()) return rc;
fileSystem = saveFs;
@ -62,7 +65,8 @@ namespace LibHac.FsSrv.Creators
if (rc.IsFailure()) return rc;
var saveDataStorage = new DisposingFileStorage(saveDataFile);
fileSystem = new SaveDataFileSystem(KeySet, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false);
fileSystem = new SaveDataFileSystem(KeySet, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid,
false);
// Todo: ISaveDataExtraDataAccessor

View File

@ -15,7 +15,7 @@ namespace LibHac.FsSrv.Creators
throw new NotImplementedException();
}
public Result GetCaseSensitivePath(out bool isSuccess, Span<byte> path)
public Result NormalizeCaseOfPath(out bool isSupported, Span<byte> path)
{
throw new NotImplementedException();
}

View File

@ -0,0 +1,12 @@
using System;
namespace LibHac.FsSrv
{
public delegate Result RandomDataGenerator(Span<byte> buffer);
public delegate Result SaveTransferAesKeyGenerator(Span<byte> key,
SaveDataTransferCryptoConfiguration.KeyIndex index, ReadOnlySpan<byte> keySource, int keyGeneration);
public delegate Result SaveTransferCmacGenerator(Span<byte> mac, ReadOnlySpan<byte> data,
SaveDataTransferCryptoConfiguration.KeyIndex index, int keyGeneration);
}

View File

@ -7,6 +7,7 @@ namespace LibHac.FsSrv
public FileSystemCreators FsCreatorInterfaces { get; set; }
public BaseFileSystemServiceImpl BaseFileSystemService { get; set; }
public NcaFileSystemServiceImpl NcaFileSystemService { get; set; }
public SaveDataFileSystemServiceImpl SaveDataFileSystemService { get; set; }
public ProgramRegistryServiceImpl ProgramRegistryService { get; set; }
}
}

View File

@ -3,7 +3,6 @@ using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.FsSrv.Creators;
namespace LibHac.FsSrv
@ -21,7 +20,6 @@ namespace LibHac.FsSrv
private const string NintendoDirectoryName = "Nintendo";
private GlobalAccessLogMode LogMode { get; set; }
public bool IsSdCardAccessible { get; set; }
internal ISaveDataIndexerManager SaveDataIndexerManager { get; private set; }
@ -32,16 +30,6 @@ namespace LibHac.FsSrv
DeviceOperator = deviceOperator;
}
public Result OpenBisFileSystem(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId)
{
return FsCreators.BuiltInStorageFileSystemCreator.Create(out fileSystem, rootPath, partitionId);
}
public Result OpenSdCardFileSystem(out IFileSystem fileSystem)
{
return FsCreators.SdCardFileSystemCreator.Create(out fileSystem, false);
}
public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId)
{
switch (partitionId)
@ -146,242 +134,12 @@ namespace LibHac.FsSrv
seed.Value.CopyTo(SdEncryptionSeed);
// todo: FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed);
SaveDataIndexerManager.InvalidateSdCardIndexer(SaveDataSpaceId.SdSystem);
SaveDataIndexerManager.InvalidateSdCardIndexer(SaveDataSpaceId.SdCache);
SaveDataIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdSystem);
SaveDataIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdCache);
return Result.Success;
}
public bool AllowDirectorySaveData(SaveDataSpaceId spaceId, string saveDataRootPath)
{
return spaceId == SaveDataSpaceId.User && !string.IsNullOrWhiteSpace(saveDataRootPath);
}
public Result DoesSaveDataExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId)
{
exists = false;
Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, spaceId, string.Empty, true);
if (rc.IsFailure()) return rc;
string saveDataPath = $"/{saveDataId:x16}";
rc = fileSystem.GetEntryType(out _, saveDataPath.ToU8Span());
if (rc.IsFailure())
{
if (ResultFs.PathNotFound.Includes(rc))
{
return Result.Success;
}
return rc;
}
exists = true;
return Result.Success;
}
public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId,
string saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData)
{
fileSystem = default;
Result rc = OpenSaveDataDirectory(out IFileSystem saveDirFs, spaceId, saveDataRootPath, true);
if (rc.IsFailure()) return rc;
// ReSharper disable once RedundantAssignment
bool allowDirectorySaveData = AllowDirectorySaveData(spaceId, saveDataRootPath);
bool useDeviceUniqueMac = Util.UseDeviceUniqueSaveMac(spaceId);
// Always allow directory savedata because we don't support transaction with file savedata yet
allowDirectorySaveData = true;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (allowDirectorySaveData)
{
rc = saveDirFs.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId));
if (rc.IsFailure()) return rc;
}
// Missing save FS cache lookup
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
rc = FsCreators.SaveDataFileSystemCreator.Create(out IFileSystem saveFs, out _, saveDirFs, saveDataId,
allowDirectorySaveData, useDeviceUniqueMac, type, null);
if (rc.IsFailure()) return rc;
if (cacheExtraData)
{
// todo: Missing extra data caching
}
fileSystem = openReadOnly ? new ReadOnlyFileSystem(saveFs) : saveFs;
return Result.Success;
}
public Result OpenSaveDataDirectory(out IFileSystem fileSystem, SaveDataSpaceId spaceId, string saveDataRootPath, bool openOnHostFs)
{
if (openOnHostFs && AllowDirectorySaveData(spaceId, saveDataRootPath))
{
Result rc = FsCreators.TargetManagerFileSystemCreator.Create(out IFileSystem hostFs, false);
if (rc.IsFailure())
{
fileSystem = default;
return rc;
}
return Util.CreateSubFileSystem(out fileSystem, hostFs, saveDataRootPath, true);
}
string dirName = spaceId == SaveDataSpaceId.Temporary ? "/temp" : "/save";
return OpenSaveDataDirectoryImpl(out fileSystem, spaceId, dirName, true);
}
public Result OpenSaveDataDirectoryImpl(out IFileSystem fileSystem, SaveDataSpaceId spaceId, string saveDirName, bool createIfMissing)
{
fileSystem = default;
Result rc;
switch (spaceId)
{
case SaveDataSpaceId.System:
rc = OpenBisFileSystem(out IFileSystem sysFs, string.Empty, BisPartitionId.System);
if (rc.IsFailure()) return rc;
return Util.CreateSubFileSystem(out fileSystem, sysFs, saveDirName, createIfMissing);
case SaveDataSpaceId.User:
case SaveDataSpaceId.Temporary:
rc = OpenBisFileSystem(out IFileSystem userFs, string.Empty, BisPartitionId.User);
if (rc.IsFailure()) return rc;
return Util.CreateSubFileSystem(out fileSystem, userFs, saveDirName, createIfMissing);
case SaveDataSpaceId.SdSystem:
case SaveDataSpaceId.SdCache:
rc = OpenSdCardFileSystem(out IFileSystem sdFs);
if (rc.IsFailure()) return rc;
string sdSaveDirPath = $"/{NintendoDirectoryName}{saveDirName}";
rc = Util.CreateSubFileSystem(out IFileSystem sdSubFs, sdFs, sdSaveDirPath, createIfMissing);
if (rc.IsFailure()) return rc;
return FsCreators.EncryptedFileSystemCreator.Create(out fileSystem, sdSubFs,
EncryptedFsKeyId.Save, SdEncryptionSeed);
case SaveDataSpaceId.ProperSystem:
rc = OpenBisFileSystem(out IFileSystem sysProperFs, string.Empty, BisPartitionId.SystemProperPartition);
if (rc.IsFailure()) return rc;
return Util.CreateSubFileSystem(out fileSystem, sysProperFs, saveDirName, createIfMissing);
case SaveDataSpaceId.SafeMode:
rc = OpenBisFileSystem(out IFileSystem safeFs, string.Empty, BisPartitionId.SafeMode);
if (rc.IsFailure()) return rc;
return Util.CreateSubFileSystem(out fileSystem, safeFs, saveDirName, createIfMissing);
default:
return ResultFs.InvalidArgument.Log();
}
}
public Result OpenSaveDataMetaFile(out IFile file, ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType type)
{
file = default;
string metaDirPath = $"/saveMeta/{saveDataId:x16}";
Result rc = OpenSaveDataDirectoryImpl(out IFileSystem tmpMetaDirFs, spaceId, metaDirPath, true);
using IFileSystem metaDirFs = tmpMetaDirFs;
if (rc.IsFailure()) return rc;
string metaFilePath = $"/{(int)type:x8}.meta";
return metaDirFs.OpenFile(out file, metaFilePath.ToU8Span(), OpenMode.ReadWrite);
}
public Result DeleteSaveDataMetaFiles(ulong saveDataId, SaveDataSpaceId spaceId)
{
Result rc = OpenSaveDataDirectoryImpl(out IFileSystem metaDirFs, spaceId, "/saveMeta", false);
using (metaDirFs)
{
if (rc.IsFailure()) return rc;
rc = metaDirFs.DeleteDirectoryRecursively($"/{saveDataId:x16}".ToU8Span());
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc))
return rc;
return Result.Success;
}
}
public Result CreateSaveDataMetaFile(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType type, long size)
{
string metaDirPath = $"/saveMeta/{saveDataId:x16}";
Result rc = OpenSaveDataDirectoryImpl(out IFileSystem tmpMetaDirFs, spaceId, metaDirPath, true);
using IFileSystem metaDirFs = tmpMetaDirFs;
if (rc.IsFailure()) return rc;
string metaFilePath = $"/{(int)type:x8}.meta";
if (size < 0) return ResultFs.OutOfRange.Log();
return metaDirFs.CreateFile(metaFilePath.ToU8Span(), size, CreateFileOptions.None);
}
public Result CreateSaveDataFileSystem(ulong saveDataId, ref SaveDataAttribute attribute,
ref SaveDataCreationInfo creationInfo, U8Span rootPath, OptionalHashSalt hashSalt, bool something)
{
// Use directory save data for now
Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, creationInfo.SpaceId, string.Empty, false);
if (rc.IsFailure()) return rc;
return fileSystem.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId));
}
public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool doSecureDelete)
{
Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, spaceId, string.Empty, false);
using (fileSystem)
{
if (rc.IsFailure()) return rc;
var saveDataPath = GetSaveDataIdPath(saveDataId).ToU8Span();
rc = fileSystem.GetEntryType(out DirectoryEntryType entryType, saveDataPath);
if (rc.IsFailure()) return rc;
if (entryType == DirectoryEntryType.Directory)
{
rc = fileSystem.DeleteDirectoryRecursively(saveDataPath);
}
else
{
if (doSecureDelete)
{
// Overwrite file with garbage before deleting
throw new NotImplementedException();
}
rc = fileSystem.DeleteFile(saveDataPath);
}
return rc;
}
}
public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode)
{
LogMode = mode;
@ -398,15 +156,5 @@ namespace LibHac.FsSrv
{
SaveDataIndexerManager = manager;
}
internal Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId)
{
return SaveDataIndexerManager.OpenAccessor(out accessor, out neededInit, spaceId);
}
private string GetSaveDataIdPath(ulong saveDataId)
{
return $"/{saveDataId:x16}";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -50,8 +50,16 @@ namespace LibHac.FsSrv
new ArrayPoolMemoryResource(), new SdHandleManager(), false));
FileSystemProxyImpl fsProxy = GetFileSystemProxyServiceObject();
fsProxy.SetCurrentProcess(Hos.Os.GetCurrentProcessId().Value).IgnoreResult();
fsProxy.CleanUpTemporaryStorage().IgnoreResult();
ulong processId = Hos.Os.GetCurrentProcessId().Value;
fsProxy.SetCurrentProcess(processId).IgnoreResult();
var saveService = new SaveDataFileSystemService(fspConfig.SaveDataFileSystemService, processId);
saveService.CleanUpTemporaryStorage().IgnoreResult();
saveService.CleanUpSaveData().IgnoreResult();
saveService.CompleteSaveDataExtension().IgnoreResult();
saveService.FixSaveData().IgnoreResult();
saveService.RecoverMultiCommit().IgnoreResult();
Hos.Sm.RegisterService(new FileSystemProxyService(this), "fsp-srv").IgnoreResult();
Hos.Sm.RegisterService(new FileSystemProxyForLoaderService(this), "fsp-ldr").IgnoreResult();
@ -82,6 +90,9 @@ namespace LibHac.FsSrv
private FileSystemProxyConfiguration InitializeFileSystemProxyConfiguration(FileSystemServerConfig config)
{
var saveDataIndexerManager = new SaveDataIndexerManager(Hos.Fs, SaveIndexerId,
new ArrayPoolMemoryResource(), new SdHandleManager(), false);
var programRegistryService = new ProgramRegistryServiceImpl(this);
var programRegistry = new ProgramRegistryImpl(programRegistryService);
@ -111,11 +122,26 @@ namespace LibHac.FsSrv
var ncaFsService = new NcaFileSystemServiceImpl(in ncaFsServiceConfig, config.ExternalKeySet);
var saveFsServiceConfig = new SaveDataFileSystemServiceImpl.Configuration();
saveFsServiceConfig.BaseFsService = baseFsService;
saveFsServiceConfig.HostFsCreator = config.FsCreators.HostFileSystemCreator;
saveFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator;
saveFsServiceConfig.SaveFsCreator = config.FsCreators.SaveDataFileSystemCreator;
saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator;
saveFsServiceConfig.ProgramRegistryService = programRegistryService;
saveFsServiceConfig.ShouldCreateDirectorySaveData = () => true;
saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager;
saveFsServiceConfig.HorizonClient = Hos;
saveFsServiceConfig.ProgramRegistry = programRegistry;
var saveFsService = new SaveDataFileSystemServiceImpl(in saveFsServiceConfig);
var fspConfig = new FileSystemProxyConfiguration
{
FsCreatorInterfaces = config.FsCreators,
BaseFileSystemService = baseFsService,
NcaFileSystemService = ncaFsService,
SaveDataFileSystemService = saveFsService,
ProgramRegistryService = programRegistryService
};
@ -124,12 +150,12 @@ namespace LibHac.FsSrv
private FileSystemProxyImpl GetFileSystemProxyServiceObject()
{
return new FileSystemProxyImpl(Hos, FsProxyCore);
return new FileSystemProxyImpl(FsProxyCore);
}
private FileSystemProxyImpl GetFileSystemProxyForLoaderServiceObject()
{
return new FileSystemProxyImpl(Hos, FsProxyCore);
return new FileSystemProxyImpl(FsProxyCore);
}
private ProgramRegistryImpl GetProgramRegistryServiceObject()

View File

@ -25,38 +25,40 @@ namespace LibHac.FsSrv
Result OpenSdCardFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem);
Result FormatSdCardFileSystem();
Result DeleteSaveDataFileSystem(ulong saveDataId);
Result CreateSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, ref SaveMetaCreateInfo metaCreateInfo);
Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo);
Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo);
Result RegisterSaveDataFileSystemAtomicDeletion(ReadOnlySpan<ulong> saveDataIds);
Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds);
Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId);
Result FormatSdCardDryRun();
Result IsExFatSupported(out bool isSupported);
Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute attribute);
Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute);
Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId);
Result OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, GameCardHandle handle, GameCardPartition partitionId);
Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize);
Result DeleteCacheStorage(short index);
Result GetCacheStorageSize(out long dataSize, out long journalSize, short index);
Result CreateSaveDataFileSystemWithHashSalt(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, ref SaveMetaCreateInfo metaCreateInfo, ref HashSalt hashSalt);
Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute);
Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute);
Result OpenReadOnlySaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute);
Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(Span<byte> extraDataBuffer, SaveDataSpaceId spaceId, ulong saveDataId);
Result ReadSaveDataFileSystemExtraData(Span<byte> extraDataBuffer, ulong saveDataId);
Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer);
Result DeleteCacheStorage(ushort index);
Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index);
Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt);
Result OpenSaveDataFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute);
Result OpenSaveDataFileSystemBySystemSaveDataId(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute);
Result OpenReadOnlySaveDataFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute);
Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, ulong saveDataId);
Result ReadSaveDataFileSystemExtraData(OutBuffer extraDataBuffer, ulong saveDataId);
Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer);
Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader);
Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader, SaveDataSpaceId spaceId);
Result OpenSaveDataInfoReaderOnlyCacheStorage(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader);
Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, SaveDataSpaceId spaceId, ulong saveDataId);
Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId);
Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer, ReadOnlySpan<byte> maskBuffer);
Result FindSaveDataWithFilter(out long count, Span<byte> saveDataInfoBuffer, SaveDataSpaceId spaceId, ref SaveDataFilter filter);
Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader, SaveDataSpaceId spaceId, ref SaveDataFilter filter);
Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(Span<byte> extraDataBuffer, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute);
Result WriteSaveDataFileSystemExtraDataBySaveDataAttribute(ref SaveDataAttribute attribute, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer, ReadOnlySpan<byte> maskBuffer);
Result OpenSaveDataMetaFile(out IFile file, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, SaveDataMetaType type);
Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer);
Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, in SaveDataFilter filter);
Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader, SaveDataSpaceId spaceId, in SaveDataFilter filter);
Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute);
Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer);
Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer maskBuffer);
Result OpenSaveDataMetaFile(out ReferenceCountedDisposable<IFileSf> file, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType type);
Result ListAccessibleSaveDataOwnerId(out int readCount, Span<Ncm.ApplicationId> idBuffer, ProgramId programId, int startIndex, int bufferIdCount);
Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, int startIndex, int bufferIdCount);
Result OpenSaveDataMover(out ReferenceCountedDisposable<ISaveDataMover> saveMover, SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize);
Result OpenImageDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, ImageDirectoryId directoryId);
Result OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, ContentStorageId storageId);
Result OpenCloudBackupWorkStorageFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, CloudBackupWorkStorageId storageId);
@ -73,7 +75,7 @@ namespace LibHac.FsSrv
Result NotifySystemDataUpdateEvent();
Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize);
Result VerifySaveDataFileSystem(ulong saveDataId, Span<byte> readBuffer);
Result VerifySaveDataFileSystem(ulong saveDataId, OutBuffer readBuffer);
Result CorruptSaveDataFileSystem(ulong saveDataId);
Result CreatePaddingFile(long size);
Result DeleteAllPaddingFiles();
@ -84,7 +86,7 @@ namespace LibHac.FsSrv
Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path);
Result SetCurrentPosixTimeWithTimeDifference(long time, int difference);
Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId);
Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, Span<byte> readBuffer);
Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, OutBuffer readBuffer);
Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId);
Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId);
Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId);
@ -93,10 +95,10 @@ namespace LibHac.FsSrv
Result SetSdCardAccessibility(bool isAccessible);
Result IsSdCardAccessible(out bool isAccessible);
Result RegisterProgramIndexMapInfo(ReadOnlySpan<byte> programIndexMapInfoBuffer, int programCount);
Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfoBuffer, int programCount);
Result SetBisRootForHost(BisPartitionId partitionId, ref FsPath path);
Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize);
Result SetSaveDataRootPath(ref FsPath path);
Result SetSaveDataRootPath(in FspPath path);
Result DisableAutoSaveDataCreation();
Result SetGlobalAccessLogMode(GlobalAccessLogMode mode);
Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode);
@ -105,9 +107,10 @@ namespace LibHac.FsSrv
Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable<IFileSystemSf> fileSystem);
Result GetProgramIndexForAccessLog(out int programIndex, out int programCount);
Result UnsetSaveDataRootPath();
Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan<byte> key);
Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset);
Result OpenMultiCommitManager(out IMultiCommitManager commitManager);
Result OpenMultiCommitManager(out ReferenceCountedDisposable<IMultiCommitManager> commitManager);
Result OpenBisWiper(out ReferenceCountedDisposable<IWiper> bisWiper, NativeHandle transferMemoryHandle, ulong transferMemorySize);
}
}

View File

@ -1,10 +0,0 @@
using LibHac.Fs.Fsa;
namespace LibHac.FsSrv
{
public interface IMultiCommitManager
{
Result Add(IFileSystem fileSystem);
Result Commit();
}
}

View File

@ -139,10 +139,10 @@ namespace LibHac.FsSrv
int GetIndexCount();
/// <summary>
/// Returns an <see cref="ISaveDataInfoReader"/> that iterates through the <see cref="SaveDataIndexer"/>.
/// Returns an <see cref="SaveDataInfoReaderImpl"/> that iterates through the <see cref="SaveDataIndexer"/>.
/// </summary>
/// <param name="infoReader">If the method returns successfully, contains the created <see cref="ISaveDataInfoReader"/>.</param>
/// <param name="infoReader">If the method returns successfully, contains the created <see cref="SaveDataInfoReaderImpl"/>.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader);
Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<SaveDataInfoReaderImpl> infoReader);
}
}

View File

@ -4,8 +4,8 @@ namespace LibHac.FsSrv
{
public interface ISaveDataIndexerManager
{
Result OpenAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId);
void ResetTemporaryStorageIndexer(SaveDataSpaceId spaceId);
void InvalidateSdCardIndexer(SaveDataSpaceId spaceId);
Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId);
void ResetIndexer(SaveDataSpaceId spaceId);
void InvalidateIndexer(SaveDataSpaceId spaceId);
}
}

View File

@ -830,6 +830,7 @@ namespace LibHac.FsSrv.Impl
RegisterProgramIndexMapInfo,
ChallengeCardExistence,
CreateOwnSaveData,
DeleteOwnSaveData,
ReadOwnSaveDataFileSystemExtraData,
ExtendOwnSaveData,
OpenOwnSaveDataTransferProhibiter,

View File

@ -0,0 +1,37 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
namespace LibHac.FsSrv.Impl
{
public class AsynchronousAccessFileSystem : ForwardingFileSystem
{
public AsynchronousAccessFileSystem(ReferenceCountedDisposable<IFileSystem> baseFileSystem) : base(
baseFileSystem)
{ }
protected AsynchronousAccessFileSystem(ref ReferenceCountedDisposable<IFileSystem> baseFileSystem) : base(
ref baseFileSystem)
{ }
public static ReferenceCountedDisposable<IFileSystem> CreateShared(
ReferenceCountedDisposable<IFileSystem> baseFileSystem)
{
return new ReferenceCountedDisposable<IFileSystem>(new AsynchronousAccessFileSystem(baseFileSystem));
}
public static ReferenceCountedDisposable<IFileSystem> CreateShared(
ref ReferenceCountedDisposable<IFileSystem> fileSystem)
{
return new ReferenceCountedDisposable<IFileSystem>(new AsynchronousAccessFileSystem(ref fileSystem));
}
// ReSharper disable once RedundantOverriddenMember
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
{
// Todo: Implement
return base.DoOpenFile(out file, path, mode);
}
}
}

View File

@ -63,7 +63,7 @@ namespace LibHac.FsSrv.Impl
/// </summary>
/// <param name="baseFileSystem">The base file system. Will be null upon returning.</param>
/// <param name="isHostFsRoot">Does the base file system come from the root directory of a host file system?</param>
public static ReferenceCountedDisposable<IFileSystemSf> CreateSharedSfFileSystem(
public static ReferenceCountedDisposable<IFileSystemSf> CreateShared(
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, bool isHostFsRoot = false)
{
var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, isHostFsRoot);

View File

@ -0,0 +1,10 @@
using System;
using LibHac.FsSystem;
namespace LibHac.FsSrv.Impl
{
public interface IEntryOpenCountSemaphoreManager : IDisposable
{
Result TryAcquireEntryOpenCountSemaphore(out IUniqueLock semaphore);
}
}

View File

@ -0,0 +1,14 @@
using System;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSrv.Impl
{
public interface ISaveDataMultiCommitCoreInterface : IDisposable
{
Result RecoverMultiCommit();
Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo);
Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback);
Result OpenMultiCommitContext(out ReferenceCountedDisposable<IFileSystem> contextFileSystem);
}
}

View File

@ -0,0 +1,30 @@
using System;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Sf;
using LibHac.Util;
namespace LibHac.FsSrv.Impl
{
public interface ISaveDataTransferCoreInterface : IDisposable
{
Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId);
Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize);
Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId);
Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional<HashSalt> hashSalt, bool leaveUnfinalized);
Result GetSaveDataInfo(out SaveDataInfo saveInfo, SaveDataSpaceId spaceId, in SaveDataAttribute attribute);
Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData);
Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp);
Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId);
Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId);
Result OpenSaveDataFile(out ReferenceCountedDisposable<IFileSf> file, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType metaType);
Result OpenSaveDataMetaFileRaw(out ReferenceCountedDisposable<IFile> file, SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode);
Result OpenSaveDataInternalStorageFileSystemCore(out ReferenceCountedDisposable<IFileSystem> fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey);
Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize);
Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId);
Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2);
Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state);
Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank);
Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, SaveDataSpaceId spaceId);
}
}

View File

@ -1,93 +1,154 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSrv.Sf;
using LibHac.Sf;
namespace LibHac.FsSrv.Impl
{
internal class MultiCommitManager : IMultiCommitManager
{
private const int MaxFileSystemCount = 10;
private const int CurrentContextVersion = 0x10000;
public const ulong ProgramId = 0x100000000000000;
public const ulong ProgramId = 0x0100000000000000;
public const ulong SaveDataId = 0x8000000000000001;
private const long SaveDataSize = 0xC000;
private const long SaveJournalSize = 0xC000;
private const long ContextFileSize = 0x200;
private const int CurrentCommitContextVersion = 0x10000;
private const long CommitContextFileSize = 0x200;
// /commitinfo
private static U8Span ContextFileName =>
private static U8Span CommitContextFileName =>
new U8Span(new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', (byte)'t', (byte)'i', (byte)'n', (byte)'f', (byte)'o' });
// Todo: Don't use global lock object
private static readonly object Locker = new object();
private FileSystemProxyImpl FsProxy { get; }
private List<IFileSystem> FileSystems { get; } = new List<IFileSystem>(MaxFileSystemCount);
private long CommitCount { get; set; }
private ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> MultiCommitInterface { get; }
public MultiCommitManager(FileSystemProxyImpl fsProxy)
private List<ReferenceCountedDisposable<IFileSystem>> FileSystems { get; } =
new List<ReferenceCountedDisposable<IFileSystem>>(MaxFileSystemCount);
private long Counter { get; set; }
private HorizonClient Hos { get; }
public MultiCommitManager(
ref ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> multiCommitInterface,
HorizonClient client)
{
FsProxy = fsProxy;
Hos = client;
MultiCommitInterface = Shared.Move(ref multiCommitInterface);
}
public Result Add(IFileSystem fileSystem)
public static ReferenceCountedDisposable<IMultiCommitManager> CreateShared(
ref ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> multiCommitInterface,
HorizonClient client)
{
var manager = new MultiCommitManager(ref multiCommitInterface, client);
return new ReferenceCountedDisposable<IMultiCommitManager>(manager);
}
public void Dispose()
{
foreach (ReferenceCountedDisposable<IFileSystem> fs in FileSystems)
{
fs.Dispose();
}
}
/// <summary>
/// Ensures the save data used to store the commit context exists.
/// </summary>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private Result EnsureSaveDataForContext()
{
Result rc = MultiCommitInterface.Target.OpenMultiCommitContext(
out ReferenceCountedDisposable<IFileSystem> contextFs);
if (rc.IsFailure())
{
if (!ResultFs.TargetNotFound.Includes(rc))
return rc;
rc = Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize, SaveDataFlags.None);
if (rc.IsFailure()) return rc;
}
contextFs?.Dispose();
return Result.Success;
}
/// <summary>
/// Adds a file system to the list of file systems to be committed.
/// </summary>
/// <param name="fileSystem">The file system to be committed.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.MultiCommitFileSystemLimit"/>: The maximum number of file systems have been added.
/// <see cref="MaxFileSystemCount"/> file systems may be added to a single multi-commit.<br/>
/// <see cref="ResultFs.MultiCommitFileSystemAlreadyAdded"/>: The provided file system has already been added.</returns>
public Result Add(ReferenceCountedDisposable<IFileSystemSf> fileSystem)
{
if (FileSystems.Count >= MaxFileSystemCount)
return ResultFs.MultiCommitFileSystemLimit.Log();
// Check that the file system hasn't already been added
for (int i = 0; i < FileSystems.Count; i++)
ReferenceCountedDisposable<IFileSystem> fsaFileSystem = null;
try
{
if (ReferenceEquals(FileSystems[i], fileSystem))
return ResultFs.MultiCommitFileSystemAlreadyAdded.Log();
Result rc = fileSystem.Target.GetImpl(out fsaFileSystem);
if (rc.IsFailure()) return rc;
// Check that the file system hasn't already been added
foreach (ReferenceCountedDisposable<IFileSystem> fs in FileSystems)
{
if (ReferenceEquals(fs.Target, fsaFileSystem.Target))
return ResultFs.MultiCommitFileSystemAlreadyAdded.Log();
}
FileSystems.Add(fsaFileSystem);
fsaFileSystem = null;
return Result.Success;
}
FileSystems.Add(fileSystem);
return Result.Success;
}
public Result Commit()
{
lock (Locker)
finally
{
Result rc = CreateSave();
if (rc.IsFailure()) return rc;
rc = FsProxy.OpenMultiCommitContextSaveData(out IFileSystem contextFs);
if (rc.IsFailure()) return rc;
return CommitImpl(contextFs);
fsaFileSystem?.Dispose();
}
}
private Result CommitImpl(IFileSystem contextFileSystem)
/// <summary>
/// Commits all added file systems using <paramref name="contextFileSystem"/> to
/// store the <see cref="Context"/>.
/// </summary>
/// <param name="contextFileSystem">The file system where the commit context will be stored.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private Result Commit(IFileSystem contextFileSystem)
{
var context = new CommitContextManager(contextFileSystem);
ContextUpdater context = default;
try
{
CommitCount = 1;
Counter = 1;
Result rc = context.Initialize(CommitCount, FileSystems.Count);
context = new ContextUpdater(contextFileSystem);
Result rc = context.Create(Counter, FileSystems.Count);
if (rc.IsFailure()) return rc;
rc = CommitProvisionally();
rc = CommitProvisionallyFileSystem(Counter);
if (rc.IsFailure()) return rc;
rc = context.SetCommittedProvisionally();
rc = context.CommitProvisionallyDone();
if (rc.IsFailure()) return rc;
foreach (IFileSystem fs in FileSystems)
{
rc = fs.Commit();
if (rc.IsFailure()) return rc;
}
rc = CommitFileSystem();
if (rc.IsFailure()) return rc;
rc = context.Close();
rc = context.CommitDone();
if (rc.IsFailure()) return rc;
}
finally
@ -98,34 +159,45 @@ namespace LibHac.FsSrv.Impl
return Result.Success;
}
private Result CreateSave()
/// <summary>
/// Commits all added file systems.
/// </summary>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result Commit()
{
Result rc = FsProxy.OpenMultiCommitContextSaveData(out IFileSystem contextFs);
if (rc.IsFailure())
lock (Locker)
{
if (!ResultFs.TargetNotFound.Includes(rc))
{
return rc;
}
rc = FsProxy.Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize,
SaveDataFlags.None);
Result rc = EnsureSaveDataForContext();
if (rc.IsFailure()) return rc;
}
contextFs?.Dispose();
return Result.Success;
ReferenceCountedDisposable<IFileSystem> contextFs = null;
try
{
rc = MultiCommitInterface.Target.OpenMultiCommitContext(out contextFs);
if (rc.IsFailure()) return rc;
return Commit(contextFs.Target);
}
finally
{
contextFs?.Dispose();
}
}
}
private Result CommitProvisionally()
/// <summary>
/// Tries to provisionally commit all the added file systems.
/// </summary>
/// <param name="counter">The provisional commit counter.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private Result CommitProvisionallyFileSystem(long counter)
{
Result rc = Result.Success;
int i;
for (i = 0; i < FileSystems.Count; i++)
{
rc = FileSystems[i].CommitProvisionally(CommitCount);
rc = FileSystems[i].Target.CommitProvisionally(counter);
if (rc.IsFailure())
break;
@ -136,20 +208,294 @@ namespace LibHac.FsSrv.Impl
// Rollback all provisional commits including the failed commit
for (int j = 0; j <= i; j++)
{
FileSystems[j].Rollback().IgnoreResult();
FileSystems[j].Target.Rollback().IgnoreResult();
}
}
return rc;
}
/// <summary>
/// Tries to fully commit all the added file systems.
/// </summary>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private Result CommitFileSystem()
{
// All file systems will try to be recovered committed, even if one fails.
// If any commits fail, the result from the first failed recovery will be returned.
Result result = Result.Success;
foreach (ReferenceCountedDisposable<IFileSystem> fs in FileSystems)
{
Result rc = fs.Target.Commit();
if (result.IsSuccess() && rc.IsFailure())
{
result = rc;
}
}
return Result.Success;
}
/// <summary>
/// Recovers a multi-commit that was interrupted after all file systems had been provisionally committed.
/// The recovery will finish committing any file systems that are still provisionally committed.
/// </summary>
/// <param name="multiCommitInterface">The core interface used for multi-commits.</param>
/// <param name="contextFs">The file system containing the multi-commit context file.</param>
/// <param name="saveService">The save data service.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.InvalidMultiCommitContextVersion"/>: The version of the commit context
/// file isn't supported.<br/>
/// <see cref="ResultFs.InvalidMultiCommitContextState"/>: The multi-commit hadn't finished
/// provisionally committing all the file systems.</returns>
private static Result RecoverCommit(ISaveDataMultiCommitCoreInterface multiCommitInterface,
IFileSystem contextFs, SaveDataFileSystemServiceImpl saveService)
{
IFile contextFile = null;
try
{
// Read the multi-commit context
Result rc = contextFs.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite);
if (rc.IsFailure()) return rc;
Unsafe.SkipInit(out Context context);
rc = contextFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref context), ReadOption.None);
if (rc.IsFailure()) return rc;
// Note: Nintendo doesn't check if the proper amount of bytes were read, but it
// doesn't really matter since the context is validated.
if (context.Version > CurrentCommitContextVersion)
return ResultFs.InvalidMultiCommitContextVersion.Log();
// All the file systems in the multi-commit must have been at least provisionally committed
// before we can try to recover the commit.
if (context.State != CommitState.ProvisionallyCommitted)
return ResultFs.InvalidMultiCommitContextState.Log();
// Keep track of the first error that occurs during the recovery
Result recoveryResult = Result.Success;
int saveCount = 0;
Span<SaveDataInfo> savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount];
SaveDataIndexerAccessor accessor = null;
ReferenceCountedDisposable<SaveDataInfoReaderImpl> infoReader = null;
try
{
rc = saveService.OpenSaveDataIndexerAccessor(out accessor, out _, SaveDataSpaceId.User);
if (rc.IsFailure()) return rc;
rc = accessor.Indexer.OpenSaveDataInfoReader(out infoReader);
if (rc.IsFailure()) return rc;
// Iterate through all the saves to find any provisionally committed save data
while (true)
{
Unsafe.SkipInit(out SaveDataInfo info);
rc = infoReader.Target.Read(out long readCount, OutBuffer.FromStruct(ref info));
if (rc.IsFailure()) return rc;
// Break once we're done iterating all save data
if (readCount == 0)
break;
rc = multiCommitInterface.IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted,
in info);
// Note: Some saves could be missed if there are more than MaxFileSystemCount
// provisionally committed saves. Not sure why Nintendo doesn't catch this.
if (rc.IsSuccess() && isProvisionallyCommitted && saveCount < MaxFileSystemCount)
{
savesToRecover[saveCount] = info;
saveCount++;
}
}
}
finally
{
accessor?.Dispose();
infoReader?.Dispose();
}
// Recover the saves by finishing their commits.
// All file systems will try to be recovered, even if one fails.
// If any commits fail, the result from the first failed recovery will be returned.
for (int i = 0; i < saveCount; i++)
{
rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], false);
if (recoveryResult.IsSuccess() && rc.IsFailure())
{
recoveryResult = rc;
}
}
return recoveryResult;
}
finally
{
contextFile?.Dispose();
}
}
/// <summary>
/// Tries to recover a multi-commit using the context in the provided file system.
/// </summary>
/// <param name="multiCommitInterface">The core interface used for multi-commits.</param>
/// <param name="contextFs">The file system containing the multi-commit context file.</param>
/// <param name="saveService">The save data service.</param>
/// <returns></returns>
private static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface, IFileSystem contextFs,
SaveDataFileSystemServiceImpl saveService)
{
if (multiCommitInterface is null)
return ResultFs.InvalidArgument.Log();
if (contextFs is null)
return ResultFs.InvalidArgument.Log();
// Keep track of the first error that occurs during the recovery
Result recoveryResult = Result.Success;
Result rc = RecoverCommit(multiCommitInterface, contextFs, saveService);
if (rc.IsFailure())
{
// Note: Yes, the next ~50 lines are exactly the same as the code in RecoverCommit except
// for a single bool value. No, Nintendo doesn't split it out into its own function.
int saveCount = 0;
Span<SaveDataInfo> savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount];
SaveDataIndexerAccessor accessor = null;
ReferenceCountedDisposable<SaveDataInfoReaderImpl> infoReader = null;
try
{
rc = saveService.OpenSaveDataIndexerAccessor(out accessor, out _, SaveDataSpaceId.User);
if (rc.IsFailure()) return rc;
rc = accessor.Indexer.OpenSaveDataInfoReader(out infoReader);
if (rc.IsFailure()) return rc;
// Iterate through all the saves to find any provisionally committed save data
while (true)
{
Unsafe.SkipInit(out SaveDataInfo info);
rc = infoReader.Target.Read(out long readCount, OutBuffer.FromStruct(ref info));
if (rc.IsFailure()) return rc;
// Break once we're done iterating all save data
if (readCount == 0)
break;
rc = multiCommitInterface.IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted,
in info);
// Note: Some saves could be missed if there are more than MaxFileSystemCount
// provisionally committed saves. Not sure why Nintendo doesn't catch this.
if (rc.IsSuccess() && isProvisionallyCommitted && saveCount < MaxFileSystemCount)
{
savesToRecover[saveCount] = info;
saveCount++;
}
}
}
finally
{
accessor?.Dispose();
infoReader?.Dispose();
}
// Recover the saves by rolling them back to the previous commit.
// All file systems will try to be recovered, even if one fails.
// If any commits fail, the result from the first failed recovery will be returned.
for (int i = 0; i < saveCount; i++)
{
rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], true);
if (recoveryResult.IsSuccess() && rc.IsFailure())
{
recoveryResult = rc;
}
}
}
// Delete the commit context file
rc = contextFs.DeleteFile(CommitContextFileName);
if (rc.IsFailure()) return rc;
rc = contextFs.Commit();
if (rc.IsFailure()) return rc;
return recoveryResult;
}
/// <summary>
/// Recovers an interrupted multi-commit. The commit will either be completed or rolled back depending on
/// where in the commit process it was interrupted. Does nothing if there is no commit to recover.
/// </summary>
/// <param name="multiCommitInterface">The core interface used for multi-commits.</param>
/// <param name="saveService">The save data service.</param>
/// <returns>The <see cref="Result"/> of the operation.<br/>
/// <see cref="Result.Success"/>: The recovery was successful or there was no multi-commit to recover.</returns>
public static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface,
SaveDataFileSystemServiceImpl saveService)
{
lock (Locker)
{
bool needsRecover = true;
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
try
{
// Check if a multi-commit was interrupted by checking if there's a commit context file.
Result rc = multiCommitInterface.OpenMultiCommitContext(out fileSystem);
if (rc.IsFailure())
{
if (!ResultFs.PathNotFound.Includes(rc) && !ResultFs.TargetNotFound.Includes(rc))
return rc;
// Unable to open the multi-commit context file system, so there's nothing to recover
needsRecover = false;
}
if (needsRecover)
{
rc = fileSystem.Target.OpenFile(out IFile file, CommitContextFileName, OpenMode.Read);
file?.Dispose();
if (rc.IsFailure())
{
// Unable to open the context file. No multi-commit to recover.
if (ResultFs.PathNotFound.Includes(rc))
needsRecover = false;
}
}
if (!needsRecover)
return Result.Success;
// There was a context file. Recover the unfinished commit.
return Recover(multiCommitInterface, fileSystem.Target, saveService);
}
finally
{
fileSystem?.Dispose();
}
}
}
[StructLayout(LayoutKind.Explicit, Size = 0x18)]
private struct CommitContext
private struct Context
{
[FieldOffset(0x00)] public int Version;
[FieldOffset(0x04)] public CommitState State;
[FieldOffset(0x08)] public int FileSystemCount;
[FieldOffset(0x10)] public long CommitCount; // I think?
[FieldOffset(0x10)] public long Counter;
}
private enum CommitState
@ -160,35 +506,41 @@ namespace LibHac.FsSrv.Impl
ProvisionallyCommitted = 2
}
private struct CommitContextManager
private struct ContextUpdater
{
private IFileSystem _fileSystem;
private CommitContext _context;
private Context _context;
public CommitContextManager(IFileSystem contextFileSystem)
public ContextUpdater(IFileSystem contextFileSystem)
{
_fileSystem = contextFileSystem;
_context = default;
}
public Result Initialize(long commitCount, int fileSystemCount)
/// <summary>
/// Creates and writes the initial commit context to a file.
/// </summary>
/// <param name="commitCount">The counter.</param>
/// <param name="fileSystemCount">The number of file systems being committed.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result Create(long commitCount, int fileSystemCount)
{
IFile contextFile = null;
try
{
// Open context file and create if it doesn't exist
Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.Read);
Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read);
if (rc.IsFailure())
{
if (!ResultFs.PathNotFound.Includes(rc))
return rc;
rc = _fileSystem.CreateFile(ContextFileName, ContextFileSize, CreateFileOptions.None);
rc = _fileSystem.CreateFile(CommitContextFileName, CommitContextFileSize, CreateFileOptions.None);
if (rc.IsFailure()) return rc;
rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.Read);
rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read);
if (rc.IsFailure()) return rc;
}
}
@ -199,13 +551,13 @@ namespace LibHac.FsSrv.Impl
try
{
Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.ReadWrite);
Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite);
if (rc.IsFailure()) return rc;
_context.Version = CurrentContextVersion;
_context.Version = CurrentCommitContextVersion;
_context.State = CommitState.NotCommitted;
_context.FileSystemCount = fileSystemCount;
_context.CommitCount = commitCount;
_context.Counter = commitCount;
// Write the initial context to the file
rc = contextFile.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None);
@ -222,13 +574,18 @@ namespace LibHac.FsSrv.Impl
return _fileSystem.Commit();
}
public Result SetCommittedProvisionally()
/// <summary>
/// Updates the commit context and writes it to a file, signifying that all
/// the file systems have been provisionally committed.
/// </summary>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result CommitProvisionallyDone()
{
IFile contextFile = null;
try
{
Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.ReadWrite);
Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite);
if (rc.IsFailure()) return rc;
_context.State = CommitState.ProvisionallyCommitted;
@ -247,9 +604,13 @@ namespace LibHac.FsSrv.Impl
return _fileSystem.Commit();
}
public Result Close()
/// <summary>
/// To be called once the multi-commit has been successfully completed. Deletes the commit context file.
/// </summary>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result CommitDone()
{
Result rc = _fileSystem.DeleteFile(ContextFileName);
Result rc = _fileSystem.DeleteFile(CommitContextFileName);
if (rc.IsFailure()) return rc;
rc = _fileSystem.Commit();
@ -263,7 +624,7 @@ namespace LibHac.FsSrv.Impl
{
if (_fileSystem is null) return;
_fileSystem.DeleteFile(ContextFileName).IgnoreResult();
_fileSystem.DeleteFile(CommitContextFileName).IgnoreResult();
_fileSystem.Commit().IgnoreResult();
_fileSystem = null;

View File

@ -0,0 +1,74 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
namespace LibHac.FsSrv.Impl
{
internal class OpenCountFileSystem : ForwardingFileSystem
{
private ReferenceCountedDisposable<IEntryOpenCountSemaphoreManager> _entryCountSemaphore;
private IUniqueLock _mountCountSemaphore;
protected OpenCountFileSystem(ref ReferenceCountedDisposable<IFileSystem> baseFileSystem,
ref ReferenceCountedDisposable<IEntryOpenCountSemaphoreManager> entryCountSemaphore) : base(
ref baseFileSystem)
{
Shared.Move(out _entryCountSemaphore, ref entryCountSemaphore);
}
protected OpenCountFileSystem(ref ReferenceCountedDisposable<IFileSystem> baseFileSystem,
ref ReferenceCountedDisposable<IEntryOpenCountSemaphoreManager> entryCountSemaphore,
ref IUniqueLock mountCountSemaphore) : base(ref baseFileSystem)
{
Shared.Move(out _entryCountSemaphore, ref entryCountSemaphore);
Shared.Move(out _mountCountSemaphore, ref mountCountSemaphore);
}
public static ReferenceCountedDisposable<IFileSystem> CreateShared(
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem,
ref ReferenceCountedDisposable<IEntryOpenCountSemaphoreManager> entryCountSemaphore,
ref IUniqueLock mountCountSemaphore)
{
var filesystem =
new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore, ref mountCountSemaphore);
return new ReferenceCountedDisposable<IFileSystem>(filesystem);
}
public static ReferenceCountedDisposable<IFileSystem> CreateShared(
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem,
ref ReferenceCountedDisposable<IEntryOpenCountSemaphoreManager> entryCountSemaphore)
{
var filesystem =
new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore);
return new ReferenceCountedDisposable<IFileSystem>(filesystem);
}
// ReSharper disable once RedundantOverriddenMember
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
{
// Todo: Implement
return base.DoOpenFile(out file, path, mode);
}
// ReSharper disable once RedundantOverriddenMember
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
// Todo: Implement
return base.DoOpenDirectory(out directory, path, mode);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_entryCountSemaphore?.Dispose();
_mountCountSemaphore?.Dispose();
}
base.Dispose(disposing);
}
}
}

View File

@ -158,6 +158,8 @@ namespace LibHac.FsSrv.Impl
public StorageId StorageId { get; }
public AccessControl AccessControl { get; }
public ulong ProgramIdValue => ProgramId.Value;
public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId,
ReadOnlySpan<byte> accessControlData, ReadOnlySpan<byte> accessControlDescriptor)
{

View File

@ -0,0 +1,100 @@
using LibHac.Diag;
using LibHac.Fs;
namespace LibHac.FsSrv.Impl
{
public static class SaveDataProperties
{
public static bool IsJournalingSupported(SaveDataType type)
{
switch (type)
{
case SaveDataType.System:
case SaveDataType.Account:
case SaveDataType.Bcat:
case SaveDataType.Device:
case SaveDataType.Cache:
return true;
case SaveDataType.Temporary:
return false;
default:
Abort.UnexpectedDefault();
return default;
}
}
public static bool IsMultiCommitSupported(SaveDataType type)
{
switch (type)
{
case SaveDataType.System:
case SaveDataType.Account:
case SaveDataType.Device:
return true;
case SaveDataType.Bcat:
case SaveDataType.Temporary:
case SaveDataType.Cache:
return false;
default:
Abort.UnexpectedDefault();
return default;
}
}
public static bool IsSharedOpenNeeded(SaveDataType type)
{
switch (type)
{
case SaveDataType.System:
case SaveDataType.Bcat:
case SaveDataType.Temporary:
case SaveDataType.Cache:
return false;
case SaveDataType.Account:
case SaveDataType.Device:
return true;
default:
Abort.UnexpectedDefault();
return default;
}
}
public static bool CanUseIndexerReservedArea(SaveDataType type)
{
switch (type)
{
case SaveDataType.System:
case SaveDataType.SystemBcat:
return true;
case SaveDataType.Account:
case SaveDataType.Bcat:
case SaveDataType.Device:
case SaveDataType.Temporary:
case SaveDataType.Cache:
return false;
default:
Abort.UnexpectedDefault();
return default;
}
}
public static bool IsSystemSaveData(SaveDataType type)
{
switch (type)
{
case SaveDataType.System:
case SaveDataType.SystemBcat:
return true;
case SaveDataType.Account:
case SaveDataType.Bcat:
case SaveDataType.Device:
case SaveDataType.Temporary:
case SaveDataType.Cache:
return false;
default:
Abort.UnexpectedDefault();
return default;
}
}
}
}

View File

@ -12,7 +12,7 @@ namespace LibHac.FsSrv.Impl
{
public static Result EnsureDirectory(IFileSystem fileSystem, U8Span path)
{
FsPath.FromSpan(out FsPath pathBuffer, ReadOnlySpan<byte>.Empty).IgnoreResult();
FsPath.FromSpan(out FsPath pathBuffer, path.Value).IgnoreResult();
int pathLength = StringUtils.GetLength(path);
@ -36,7 +36,7 @@ namespace LibHac.FsSrv.Impl
private static Result EnsureDirectoryImpl(IFileSystem fileSystem, Span<byte> path)
{
// Double check the trailing separators have been trimmed
Assert.AssertTrue(path.Length == 0 || path[path.Length - 1] != StringTraits.DirectorySeparator);
Assert.AssertTrue(path.Length <= 1 || path[path.Length - 1] != StringTraits.DirectorySeparator);
// Use the root path if the input path is empty
var pathToCheck = new U8Span(path.IsEmpty ? FileSystemRootPath : path);
@ -99,8 +99,8 @@ namespace LibHac.FsSrv.Impl
return ResultFs.TooLongPath.Log();
// Iterate until we run out of path or find the next separator
int length = path.Length;
while (length > 0 && path[length - 1] != StringTraits.DirectorySeparator)
int length = path.Length - 1;
while (length > 0 && path[length] != StringTraits.DirectorySeparator)
{
length--;
}
@ -112,25 +112,22 @@ namespace LibHac.FsSrv.Impl
}
// We found the length of the parent directory. Ensure it exists
path[length - 1] = StringTraits.NullTerminator;
path[length] = StringTraits.NullTerminator;
Result rc = EnsureDirectoryImpl(fileSystem, path.Slice(0, length));
if (rc.IsFailure()) return rc;
// Restore the separator
path[length - 1] = StringTraits.DirectorySeparator;
path[length] = StringTraits.DirectorySeparator;
return Result.Success;
}
public static Result CreateSubDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool preserveUnc = false)
ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span subPath, bool preserveUnc = false)
{
subDirFileSystem = default;
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
// Check if the directory exists
Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, path, OpenDirectoryMode.Directory);
Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, subPath, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return rc;
dir.Dispose();
@ -138,7 +135,7 @@ namespace LibHac.FsSrv.Impl
var fs = new SubdirectoryFileSystem(baseFileSystem, preserveUnc);
using (var subDirFs = new ReferenceCountedDisposable<SubdirectoryFileSystem>(fs))
{
rc = subDirFs.Target.Initialize(path);
rc = subDirFs.Target.Initialize(subPath);
if (rc.IsFailure()) return rc;
subDirFileSystem = subDirFs.AddReference<IFileSystem>();

View File

@ -30,8 +30,8 @@ namespace LibHac.FsSrv
RomMountCountSemaphore = new SemaphoreAdaptor(RomSemaphoreCount, RomSemaphoreCount);
}
public static ReferenceCountedDisposable<NcaFileSystemService> Create(NcaFileSystemServiceImpl serviceImpl,
ulong processId)
public static ReferenceCountedDisposable<NcaFileSystemService> CreateShared(
NcaFileSystemServiceImpl serviceImpl, ulong processId)
{
// Create the service
var ncaService = new NcaFileSystemService(serviceImpl, processId);
@ -140,7 +140,7 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Create an SF adapter for the file system
fileSystem = FileSystemInterfaceAdapter.CreateSharedSfFileSystem(ref fs);
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
return Result.Success;
}
@ -212,7 +212,7 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Create an SF adapter for the file system
fileSystem = FileSystemInterfaceAdapter.CreateSharedSfFileSystem(ref fs);
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
return Result.Success;
}
@ -341,10 +341,10 @@ namespace LibHac.FsSrv
private bool IsHostFs(U8Span path)
{
int hostMountLength = StringUtils.GetLength(CommonMountNames.HostRootFileSystemMountName,
int hostMountLength = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName,
PathTools.MountNameLengthMax);
return StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, hostMountLength) == 0;
return StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountLength) == 0;
}
public void Dispose()

View File

@ -173,10 +173,10 @@ namespace LibHac.FsSrv
info = new MountNameInfo();
shouldContinue = true;
if (StringUtils.Compare(path, CommonMountNames.GameCardFileSystemMountName,
CommonMountNames.GameCardFileSystemMountName.Length) == 0)
if (StringUtils.Compare(path, CommonPaths.GameCardFileSystemMountName,
CommonPaths.GameCardFileSystemMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.GameCardFileSystemMountName.Length);
path = path.Slice(CommonPaths.GameCardFileSystemMountName.Length);
if (StringUtils.GetLength(path.Value, 9) < 9)
return ResultFs.InvalidPath.Log();
@ -184,13 +184,13 @@ namespace LibHac.FsSrv
GameCardPartition partition;
switch ((char)path[0])
{
case CommonMountNames.GameCardFileSystemMountNameUpdateSuffix:
case CommonPaths.GameCardFileSystemMountNameUpdateSuffix:
partition = GameCardPartition.Update;
break;
case CommonMountNames.GameCardFileSystemMountNameNormalSuffix:
case CommonPaths.GameCardFileSystemMountNameNormalSuffix:
partition = GameCardPartition.Normal;
break;
case CommonMountNames.GameCardFileSystemMountNameSecureSuffix:
case CommonPaths.GameCardFileSystemMountNameSecureSuffix:
partition = GameCardPartition.Secure;
break;
default:
@ -214,10 +214,10 @@ namespace LibHac.FsSrv
info.CanMountNca = true;
}
else if (StringUtils.Compare(path, CommonMountNames.ContentStorageSystemMountName,
CommonMountNames.ContentStorageSystemMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.ContentStorageSystemMountName,
CommonPaths.ContentStorageSystemMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.ContentStorageSystemMountName.Length);
path = path.Slice(CommonPaths.ContentStorageSystemMountName.Length);
Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.System);
if (rc.IsFailure()) return rc;
@ -225,10 +225,10 @@ namespace LibHac.FsSrv
info.CanMountNca = true;
}
else if (StringUtils.Compare(path, CommonMountNames.ContentStorageUserMountName,
CommonMountNames.ContentStorageUserMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.ContentStorageUserMountName,
CommonPaths.ContentStorageUserMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.ContentStorageUserMountName.Length);
path = path.Slice(CommonPaths.ContentStorageUserMountName.Length);
Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.User);
if (rc.IsFailure()) return rc;
@ -236,10 +236,10 @@ namespace LibHac.FsSrv
info.CanMountNca = true;
}
else if (StringUtils.Compare(path, CommonMountNames.ContentStorageSdCardMountName,
CommonMountNames.ContentStorageSdCardMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.ContentStorageSdCardMountName,
CommonPaths.ContentStorageSdCardMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.ContentStorageSdCardMountName.Length);
path = path.Slice(CommonPaths.ContentStorageSdCardMountName.Length);
Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.SdCard);
if (rc.IsFailure()) return rc;
@ -247,58 +247,58 @@ namespace LibHac.FsSrv
info.CanMountNca = true;
}
else if (StringUtils.Compare(path, CommonMountNames.BisCalibrationFilePartitionMountName,
CommonMountNames.BisCalibrationFilePartitionMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.BisCalibrationFilePartitionMountName,
CommonPaths.BisCalibrationFilePartitionMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.BisCalibrationFilePartitionMountName.Length);
path = path.Slice(CommonPaths.BisCalibrationFilePartitionMountName.Length);
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty,
BisPartitionId.CalibrationFile);
if (rc.IsFailure()) return rc;
}
else if (StringUtils.Compare(path, CommonMountNames.BisSafeModePartitionMountName,
CommonMountNames.BisSafeModePartitionMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.BisSafeModePartitionMountName,
CommonPaths.BisSafeModePartitionMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.BisSafeModePartitionMountName.Length);
path = path.Slice(CommonPaths.BisSafeModePartitionMountName.Length);
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty,
BisPartitionId.SafeMode);
if (rc.IsFailure()) return rc;
}
else if (StringUtils.Compare(path, CommonMountNames.BisUserPartitionMountName,
CommonMountNames.BisUserPartitionMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.BisUserPartitionMountName,
CommonPaths.BisUserPartitionMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.BisUserPartitionMountName.Length);
path = path.Slice(CommonPaths.BisUserPartitionMountName.Length);
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, BisPartitionId.User);
if (rc.IsFailure()) return rc;
}
else if (StringUtils.Compare(path, CommonMountNames.BisSystemPartitionMountName,
CommonMountNames.BisSystemPartitionMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.BisSystemPartitionMountName,
CommonPaths.BisSystemPartitionMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.BisSystemPartitionMountName.Length);
path = path.Slice(CommonPaths.BisSystemPartitionMountName.Length);
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty,
BisPartitionId.System);
if (rc.IsFailure()) return rc;
}
else if (StringUtils.Compare(path, CommonMountNames.SdCardFileSystemMountName,
CommonMountNames.SdCardFileSystemMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.SdCardFileSystemMountName,
CommonPaths.SdCardFileSystemMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.SdCardFileSystemMountName.Length);
path = path.Slice(CommonPaths.SdCardFileSystemMountName.Length);
Result rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out fileSystem);
if (rc.IsFailure()) return rc;
}
else if (StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName,
CommonMountNames.HostRootFileSystemMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName,
CommonPaths.HostRootFileSystemMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.HostRootFileSystemMountName.Length);
path = path.Slice(CommonPaths.HostRootFileSystemMountName.Length);
info.IsHostFs = true;
info.CanMountNca = true;
@ -307,10 +307,10 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
}
else if (StringUtils.Compare(path, CommonMountNames.RegisteredUpdatePartitionMountName,
CommonMountNames.RegisteredUpdatePartitionMountName.Length) == 0)
else if (StringUtils.Compare(path, CommonPaths.RegisteredUpdatePartitionMountName,
CommonPaths.RegisteredUpdatePartitionMountName.Length) == 0)
{
path = path.Slice(CommonMountNames.RegisteredUpdatePartitionMountName.Length);
path = path.Slice(CommonPaths.RegisteredUpdatePartitionMountName.Length);
info.CanMountNca = true;
@ -396,7 +396,7 @@ namespace LibHac.FsSrv
if (sb.Overflowed)
return ResultFs.TooLongPath.Log();
Result rc = _config.TargetManagerFsCreator.GetCaseSensitivePath(out bool success, fullPath.Str);
Result rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool success, fullPath.Str);
if (rc.IsFailure()) return rc;
// Reopen the host filesystem as case sensitive

View File

@ -69,7 +69,7 @@ namespace LibHac.FsSrv
PreserveUnc = (1 << 0),
PreserveTailSeparator = (1 << 1),
HasMountName = (1 << 2),
AcceptEmpty = (1 << 3),
AcceptEmpty = (1 << 3)
}
}
}

View File

@ -1,169 +0,0 @@
using System;
namespace LibHac.FsSrv
{
/// <summary>
/// Permissions that control which filesystems or storages can be mounted or opened.
/// </summary>
public enum AccessPermissions
{
MountLogo = 0x0,
MountContentMeta = 0x1,
MountContentControl = 0x2,
MountContentManual = 0x3,
MountContentData = 0x4,
MountApplicationPackage = 0x5,
MountSaveDataStorage = 0x6,
MountContentStorage = 0x7,
MountImageAndVideoStorage = 0x8,
MountCloudBackupWorkStorage = 0x9,
MountCustomStorage = 0xA,
MountBisCalibrationFile = 0xB,
MountBisSafeMode = 0xC,
MountBisUser = 0xD,
MountBisSystem = 0xE,
MountBisSystemProperEncryption = 0xF,
MountBisSystemProperPartition = 0x10,
MountSdCard = 0x11,
MountGameCard = 0x12,
MountDeviceSaveData = 0x13,
MountSystemSaveData = 0x14,
MountOthersSaveData = 0x15,
MountOthersSystemSaveData = 0x16,
OpenBisPartitionBootPartition1Root = 0x17,
OpenBisPartitionBootPartition2Root = 0x18,
OpenBisPartitionUserDataRoot = 0x19,
OpenBisPartitionBootConfigAndPackage2Part1 = 0x1A,
OpenBisPartitionBootConfigAndPackage2Part2 = 0x1B,
OpenBisPartitionBootConfigAndPackage2Part3 = 0x1C,
OpenBisPartitionBootConfigAndPackage2Part4 = 0x1D,
OpenBisPartitionBootConfigAndPackage2Part5 = 0x1E,
OpenBisPartitionBootConfigAndPackage2Part6 = 0x1F,
OpenBisPartitionCalibrationBinary = 0x20,
OpenBisPartitionCalibrationFile = 0x21,
OpenBisPartitionSafeMode = 0x22,
OpenBisPartitionUser = 0x23,
OpenBisPartitionSystem = 0x24,
OpenBisPartitionSystemProperEncryption = 0x25,
OpenBisPartitionSystemProperPartition = 0x26,
OpenSdCardStorage = 0x27,
OpenGameCardStorage = 0x28,
MountSystemDataPrivate = 0x29,
MountHost = 0x2A,
MountRegisteredUpdatePartition = 0x2B,
MountSaveDataInternalStorage = 0x2C,
NotMountCustomStorage = 0x2D
}
/// <summary>
/// Permissions that control which actions can be performed.
/// </summary>
public enum ActionPermissions
{
// Todo
}
public static class PermissionUtils
{
public static ulong GetPermissionMask(AccessPermissions id)
{
switch (id)
{
case AccessPermissions.MountLogo:
return 0x8000000000000801;
case AccessPermissions.MountContentMeta:
return 0x8000000000000801;
case AccessPermissions.MountContentControl:
return 0x8000000000000801;
case AccessPermissions.MountContentManual:
return 0x8000000000000801;
case AccessPermissions.MountContentData:
return 0x8000000000000801;
case AccessPermissions.MountApplicationPackage:
return 0x8000000000000801;
case AccessPermissions.MountSaveDataStorage:
return 0x8000000000000000;
case AccessPermissions.MountContentStorage:
return 0x8000000000000800;
case AccessPermissions.MountImageAndVideoStorage:
return 0x8000000000001000;
case AccessPermissions.MountCloudBackupWorkStorage:
return 0x8000000200000000;
case AccessPermissions.MountCustomStorage:
return 0x8000000000000000;
case AccessPermissions.MountBisCalibrationFile:
return 0x8000000000000084;
case AccessPermissions.MountBisSafeMode:
return 0x8000000000000080;
case AccessPermissions.MountBisUser:
return 0x8000000000008080;
case AccessPermissions.MountBisSystem:
return 0x8000000000008080;
case AccessPermissions.MountBisSystemProperEncryption:
return 0x8000000000000080;
case AccessPermissions.MountBisSystemProperPartition:
return 0x8000000000000080;
case AccessPermissions.MountSdCard:
return 0xC000000000200000;
case AccessPermissions.MountGameCard:
return 0x8000000000000010;
case AccessPermissions.MountDeviceSaveData:
return 0x8000000000040020;
case AccessPermissions.MountSystemSaveData:
return 0x8000000000000028;
case AccessPermissions.MountOthersSaveData:
return 0x8000000000000020;
case AccessPermissions.MountOthersSystemSaveData:
return 0x8000000000000020;
case AccessPermissions.OpenBisPartitionBootPartition1Root:
return 0x8000000000010082;
case AccessPermissions.OpenBisPartitionBootPartition2Root:
return 0x8000000000010080;
case AccessPermissions.OpenBisPartitionUserDataRoot:
return 0x8000000000000080;
case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part1:
return 0x8000000000010080;
case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part2:
return 0x8000000000010080;
case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part3:
return 0x8000000000010080;
case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part4:
return 0x8000000000010080;
case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part5:
return 0x8000000000010080;
case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part6:
return 0x8000000000010080;
case AccessPermissions.OpenBisPartitionCalibrationBinary:
return 0x8000000000000084;
case AccessPermissions.OpenBisPartitionCalibrationFile:
return 0x8000000000000084;
case AccessPermissions.OpenBisPartitionSafeMode:
return 0x8000000000000080;
case AccessPermissions.OpenBisPartitionUser:
return 0x8000000000000080;
case AccessPermissions.OpenBisPartitionSystem:
return 0x8000000000000080;
case AccessPermissions.OpenBisPartitionSystemProperEncryption:
return 0x8000000000000080;
case AccessPermissions.OpenBisPartitionSystemProperPartition:
return 0x8000000000000080;
case AccessPermissions.OpenSdCardStorage:
return 0xC000000000200000;
case AccessPermissions.OpenGameCardStorage:
return 0x8000000000000100;
case AccessPermissions.MountSystemDataPrivate:
return 0x8000000000100008;
case AccessPermissions.MountHost:
return 0xC000000000400000;
case AccessPermissions.MountRegisteredUpdatePartition:
return 0x8000000000010000;
case AccessPermissions.MountSaveDataInternalStorage:
return 0x8000000000000000;
case AccessPermissions.NotMountCustomStorage:
return 0x0;
default:
throw new ArgumentOutOfRangeException(nameof(id), id, null);
}
}
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using LibHac.Fs;
using LibHac.Ncm;
using LibHac.Util;
namespace LibHac.FsSrv
{
@ -50,7 +51,7 @@ namespace LibHac.FsSrv
/// <param name="programId">The program ID of the map info to get.</param>
/// <returns>If the program ID was found, the <see cref="ProgramIndexMapInfo"/> associated
/// with that ID; otherwise, <see langword="null"/>.</returns>
public ProgramIndexMapInfo? Get(ProgramId programId)
public Optional<ProgramIndexMapInfo> Get(ProgramId programId)
{
lock (MapEntries)
{
@ -70,12 +71,13 @@ namespace LibHac.FsSrv
{
lock (MapEntries)
{
ProgramIndexMapInfo? mainProgramMapInfo = GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId);
Optional<ProgramIndexMapInfo> mainProgramMapInfo =
GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId);
if(!mainProgramMapInfo.HasValue)
return ProgramId.InvalidId;
ProgramIndexMapInfo? requestedMapInfo = GetImpl((in ProgramIndexMapInfo x) =>
Optional<ProgramIndexMapInfo> requestedMapInfo = GetImpl((in ProgramIndexMapInfo x) =>
x.MainProgramId == mainProgramMapInfo.Value.MainProgramId && x.ProgramIndex == programIndex);
if (!requestedMapInfo.HasValue)
@ -99,17 +101,17 @@ namespace LibHac.FsSrv
private delegate bool EntrySelector(in ProgramIndexMapInfo candidate);
private ProgramIndexMapInfo? GetImpl(EntrySelector selector)
private Optional<ProgramIndexMapInfo> GetImpl(EntrySelector selector)
{
foreach (ProgramIndexMapInfo entry in MapEntries)
{
if (selector(in entry))
{
return entry;
return new Optional<ProgramIndexMapInfo>(entry);
}
}
return null;
return new Optional<ProgramIndexMapInfo>();
}
private void ClearImpl()

View File

@ -3,21 +3,23 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Fs;
using LibHac.FsSrv.Impl;
using LibHac.Sf;
using LibHac.Util;
namespace LibHac.FsSrv
{
/// <summary>
/// Used to perform operations on the Program Index Map.
/// Used to perform operations on the program index registry.
/// </summary>
/// <remarks>Appropriate methods calls on IFileSystemProxy are forwarded to this class
/// which then checks the calling process' permissions and performs the requested operation.
/// <br/>Based on FS 10.0.0 (nnSdk 10.4.0)</remarks>
internal readonly struct ProgramRegistryService
internal readonly struct ProgramIndexRegistryService
{
private ProgramRegistryServiceImpl ServiceImpl { get; }
private ulong ProcessId { get; }
public ProgramRegistryService(ProgramRegistryServiceImpl serviceImpl, ulong processId)
public ProgramIndexRegistryService(ProgramRegistryServiceImpl serviceImpl, ulong processId)
{
ServiceImpl = serviceImpl;
ProcessId = processId;
@ -33,7 +35,7 @@ namespace LibHac.FsSrv
/// <see cref="ResultFs.PermissionDenied"/>: Insufficient permissions.<br/>
/// <see cref="ResultFs.InvalidSize"/>: The buffer was too small to hold the specified
/// number of <see cref="ProgramIndexMapInfo"/> entries.</returns>
public Result RegisterProgramIndexMapInfo(ReadOnlySpan<byte> programIndexMapInfo, int programCount)
public Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfo, int programCount)
{
// Verify the caller's permissions
Result rc = GetProgramInfo(out ProgramInfo programInfo, ProcessId);
@ -49,7 +51,7 @@ namespace LibHac.FsSrv
// Verify that the provided buffer is large enough to hold "programCount" entries
ReadOnlySpan<ProgramIndexMapInfo>
mapInfo = MemoryMarshal.Cast<byte, ProgramIndexMapInfo>(programIndexMapInfo);
mapInfo = MemoryMarshal.Cast<byte, ProgramIndexMapInfo>(programIndexMapInfo.Buffer);
if (mapInfo.Length < programCount)
return ResultFs.InvalidSize.Log();
@ -69,7 +71,7 @@ namespace LibHac.FsSrv
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.TargetProgramNotFound"/>: The calling program was not found
/// in the program registry. Something's wrong with Loader if this happens.</returns>
public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount)
public Result GetProgramIndex(out int programIndex, out int programCount)
{
Unsafe.SkipInit(out programIndex);
Unsafe.SkipInit(out programCount);
@ -79,10 +81,10 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Try to get map info for this process
ProgramIndexMapInfo? mapInfo = ServiceImpl.GetProgramIndexMapInfo(programInfo.ProgramId);
Optional<ProgramIndexMapInfo> mapInfo = ServiceImpl.GetProgramIndexMapInfo(programInfo.ProgramId);
// Set the output program index if map info was found
programIndex = mapInfo?.ProgramIndex ?? 0;
programIndex = mapInfo.HasValue ? mapInfo.ValueRo.ProgramIndex : 0;
// Set the number of programs in the current application
programCount = ServiceImpl.GetProgramIndexMapInfoCount();

View File

@ -43,7 +43,7 @@ namespace LibHac.FsSrv
if (!ProgramInfo.IsInitialProgram(_processId))
return ResultFs.PermissionDenied.Log();
return _registryService.RegisterProgram(processId, programId, storageId, accessControlData,
return _registryService.RegisterProgramInfo(processId, programId, storageId, accessControlData,
accessControlDescriptor);
}
@ -56,7 +56,7 @@ namespace LibHac.FsSrv
if (!ProgramInfo.IsInitialProgram(_processId))
return ResultFs.PermissionDenied.Log();
return _registryService.UnregisterProgram(processId);
return _registryService.UnregisterProgramInfo(processId);
}
/// <inheritdoc cref="ProgramRegistryManager.GetProgramInfo"/>

View File

@ -2,6 +2,7 @@
using LibHac.Fs;
using LibHac.FsSrv.Impl;
using LibHac.Ncm;
using LibHac.Util;
namespace LibHac.FsSrv
{
@ -21,7 +22,7 @@ namespace LibHac.FsSrv
}
/// <inheritdoc cref="ProgramRegistryManager.RegisterProgram"/>
public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId,
public Result RegisterProgramInfo(ulong processId, ProgramId programId, StorageId storageId,
ReadOnlySpan<byte> accessControlData, ReadOnlySpan<byte> accessControlDescriptor)
{
return RegistryManager.RegisterProgram(processId, programId, storageId, accessControlData,
@ -29,7 +30,7 @@ namespace LibHac.FsSrv
}
/// <inheritdoc cref="ProgramRegistryManager.UnregisterProgram" />
public Result UnregisterProgram(ulong processId)
public Result UnregisterProgramInfo(ulong processId)
{
return RegistryManager.UnregisterProgram(processId);
}
@ -47,13 +48,13 @@ namespace LibHac.FsSrv
}
/// <inheritdoc cref="ProgramIndexMapInfoManager.GetProgramId"/>
public ProgramId GetProgramId(ProgramId programId, byte programIndex)
public ProgramId GetProgramIdByIndex(ProgramId programId, byte programIndex)
{
return ProgramIndexManager.GetProgramId(programId, programIndex);
}
/// <inheritdoc cref="ProgramIndexMapInfoManager.Get"/>
public ProgramIndexMapInfo? GetProgramIndexMapInfo(ProgramId programId)
public Optional<ProgramIndexMapInfo> GetProgramIndexMapInfo(ProgramId programId)
{
return ProgramIndexManager.Get(programId);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,625 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Creators;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
using LibHac.Util;
using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv
{
public class SaveDataFileSystemServiceImpl
{
private Configuration _config;
private EncryptionSeed _encryptionSeed;
private ISaveDataFileSystemCacheManager _saveCacheManager;
// Save data extra data cache
// Save data porter manager
private bool _isSdCardAccessible;
// Timestamp getter
internal HorizonClient Hos => _config.HorizonClient;
public SaveDataFileSystemServiceImpl(in Configuration configuration)
{
_config = configuration;
}
public struct Configuration
{
public BaseFileSystemServiceImpl BaseFsService;
// Time service
public IHostFileSystemCreator HostFsCreator;
public ITargetManagerFileSystemCreator TargetManagerFsCreator;
public ISaveDataFileSystemCreator SaveFsCreator;
public IEncryptedFileSystemCreator EncryptedFsCreator;
public ProgramRegistryServiceImpl ProgramRegistryService;
// Buffer manager
public RandomDataGenerator GenerateRandomData;
public SaveDataTransferCryptoConfiguration SaveTransferCryptoConfig;
// Max save FS cache size
public Func<bool> ShouldCreateDirectorySaveData;
public ISaveDataIndexerManager SaveIndexerManager;
// LibHac additions
public HorizonClient HorizonClient;
public ProgramRegistryImpl ProgramRegistry;
}
internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{
return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId);
}
public Result DoesSaveDataEntityExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId)
{
Unsafe.SkipInit(out exists);
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
try
{
Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, U8Span.Empty, true);
if (rc.IsFailure()) return rc;
// Get the path of the save data
Span<byte> saveDataPath = stackalloc byte[0x12];
var sb = new U8StringBuilder(saveDataPath);
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
rc = fileSystem.Target.GetEntryType(out _, new U8Span(saveDataPath));
if (rc.IsSuccess())
{
exists = true;
return Result.Success;
}
else if (ResultFs.PathNotFound.Includes(rc))
{
exists = false;
return Result.Success;
}
else
{
return rc;
}
}
finally
{
fileSystem?.Dispose();
}
}
public Result OpenSaveDataFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool openReadOnly, SaveDataType type,
bool cacheExtraData)
{
fileSystem = default;
ReferenceCountedDisposable<IFileSystem> saveDirectoryFs = null;
ReferenceCountedDisposable<SaveDataFileSystem> cachedSaveDataFs = null;
ReferenceCountedDisposable<IFileSystem> saveDataFs = null;
try
{
Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, spaceId, saveDataRootPath, true);
if (rc.IsFailure()) return rc;
bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, saveDataRootPath);
if (allowDirectorySaveData)
{
Span<byte> saveDirectoryPath = stackalloc byte[0x12];
var sb = new U8StringBuilder(saveDirectoryPath);
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
rc = Utility.EnsureDirectory(saveDirectoryFs.Target, new U8Span(saveDirectoryPath));
if (rc.IsFailure()) return rc;
}
if (!allowDirectorySaveData)
{
// Todo: Missing save FS cache lookup
}
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (saveDataFs is null)
{
ReferenceCountedDisposable<ISaveDataExtraDataAccessor> extraDataAccessor = null;
try
{
// Todo: Update ISaveDataFileSystemCreator
bool useDeviceUniqueMac = IsDeviceUniqueMac(spaceId);
rc = _config.SaveFsCreator.Create(out IFileSystem saveFs,
out extraDataAccessor, saveDirectoryFs.Target, saveDataId,
allowDirectorySaveData, useDeviceUniqueMac, type, null);
if (rc.IsFailure()) return rc;
saveDataFs = new ReferenceCountedDisposable<IFileSystem>(saveFs);
if (cacheExtraData)
{
// Todo: Missing extra data caching
}
}
finally
{
extraDataAccessor?.Dispose();
}
}
if (openReadOnly)
{
saveDataFs = ReadOnlyFileSystem.CreateShared(ref saveDataFs);
}
Shared.Move(out fileSystem, ref saveDataFs);
return Result.Success;
}
finally
{
saveDirectoryFs?.Dispose();
// ReSharper disable once ExpressionIsAlwaysNull
cachedSaveDataFs?.Dispose();
saveDataFs?.Dispose();
}
}
public Result OpenSaveDataMetaDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
SaveDataSpaceId spaceId, ulong saveDataId)
{
var metaDirPath = // /saveMeta/
new U8Span(new[]
{
(byte) '/', (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'M', (byte) 'e', (byte) 't',
(byte) 'a', (byte) '/'
});
Span<byte> directoryName = stackalloc byte[0x1B];
var sb = new U8StringBuilder(directoryName);
sb.Append(metaDirPath).AppendFormat(saveDataId, 'x', 16);
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(directoryName));
}
public Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool useSecondMacKey)
{
throw new NotImplementedException();
}
public Result QuerySaveDataTotalSize(out long totalSize, int blockSize, long dataSize, long journalSize)
{
// Todo: Implement
totalSize = 0;
return Result.Success;
}
public Result CreateSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType,
long metaSize)
{
ReferenceCountedDisposable<IFileSystem> metaDirFs = null;
try
{
Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId);
if (rc.IsFailure()) return rc;
Span<byte> saveMetaPathBuffer = stackalloc byte[0xF];
var sb = new U8StringBuilder(saveMetaPathBuffer);
sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8);
return metaDirFs.Target.CreateFile(new U8Span(saveMetaPathBuffer), metaSize);
}
finally
{
metaDirFs?.Dispose();
}
}
public Result DeleteSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType)
{
ReferenceCountedDisposable<IFileSystem> metaDirFs = null;
try
{
Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId);
if (rc.IsFailure()) return rc;
Span<byte> saveMetaPathBuffer = stackalloc byte[0xF];
var sb = new U8StringBuilder(saveMetaPathBuffer);
sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8);
return metaDirFs.Target.DeleteFile(new U8Span(saveMetaPathBuffer));
}
finally
{
metaDirFs?.Dispose();
}
}
public Result DeleteAllSaveDataMetas(ulong saveDataId, SaveDataSpaceId spaceId)
{
var metaDirPath = // /saveMeta
new U8Span(new[]
{
(byte) '/', (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'M', (byte) 'e', (byte) 't',
(byte) 'a'
});
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
try
{
Result rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, metaDirPath, false);
if (rc.IsFailure()) return rc;
// Get the path of the save data meta
Span<byte> saveDataPathBuffer = stackalloc byte[0x12];
var sb = new U8StringBuilder(saveDataPathBuffer);
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
// Delete the save data's meta directory, ignoring the error if the directory is already gone
rc = fileSystem.Target.DeleteDirectoryRecursively(new U8Span(saveDataPathBuffer));
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc))
return rc;
return Result.Success;
}
finally
{
fileSystem?.Dispose();
}
}
public Result OpenSaveDataMeta(out IFile metaFile, ulong saveDataId, SaveDataSpaceId spaceId,
SaveDataMetaType metaType)
{
metaFile = default;
ReferenceCountedDisposable<IFileSystem> metaDirFs = null;
try
{
Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId);
if (rc.IsFailure()) return rc;
Span<byte> saveMetaPathBuffer = stackalloc byte[0xF];
var sb = new U8StringBuilder(saveMetaPathBuffer);
sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8);
return metaDirFs.Target.OpenFile(out metaFile, new U8Span(saveMetaPathBuffer), OpenMode.ReadWrite);
}
finally
{
metaDirFs?.Dispose();
}
}
public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataAttribute attribute,
in SaveDataCreationInfo creationInfo, U8Span saveDataRootPath, in Optional<HashSalt> hashSalt,
bool skipFormat)
{
// Use directory save data for now
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
try
{
Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, creationInfo.SpaceId, saveDataRootPath,
false);
if (rc.IsFailure()) return rc;
Span<byte> saveDataPathBuffer = stackalloc byte[0x12];
var sb = new U8StringBuilder(saveDataPathBuffer);
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
if (_config.ShouldCreateDirectorySaveData())
{
return Utility.EnsureDirectory(fileSystem.Target, new U8Span(saveDataPathBuffer));
}
else
{
throw new NotImplementedException();
}
}
finally
{
fileSystem?.Dispose();
}
}
private Result WipeData(IFileSystem fileSystem, U8Span filePath, RandomDataGenerator random)
{
throw new NotImplementedException();
}
public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile,
U8Span saveDataRootPath)
{
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
try
{
// Open the directory containing the save data
Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, saveDataRootPath, false);
if (rc.IsFailure()) return rc;
// Get the path of the save data
Span<byte> saveDataPathBuffer = stackalloc byte[0x12];
var saveDataPath = new U8Span(saveDataPathBuffer);
var sb = new U8StringBuilder(saveDataPathBuffer);
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
// Check if the save data is a file or a directory
rc = fileSystem.Target.GetEntryType(out DirectoryEntryType entryType, saveDataPath);
if (rc.IsFailure()) return rc;
// Delete the save data, wiping the file if needed
if (entryType == DirectoryEntryType.Directory)
{
rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath);
if (rc.IsFailure()) return rc;
}
else
{
if (wipeSaveFile)
{
WipeData(fileSystem.Target, saveDataPath, _config.GenerateRandomData).IgnoreResult();
}
rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath);
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
finally
{
fileSystem?.Dispose();
}
}
public Result ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId spaceId,
ulong saveDataId, SaveDataType type, U8Span saveDataRootPath)
{
// Todo: Find a way to store extra data for directory save data
extraData = default;
return Result.Success;
}
public Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId spaceId, ulong saveDataId,
in SaveDataExtraData extraData, U8Span saveDataRootPath, SaveDataType type, bool updateTimeStamp)
{
throw new NotImplementedException();
}
public Result CorruptSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long offset,
U8Span saveDataRootPath)
{
throw new NotImplementedException();
}
private bool IsSaveEmulated(U8Span saveDataRootPath)
{
return !saveDataRootPath.IsEmpty();
}
public Result OpenSaveDataDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
SaveDataSpaceId spaceId, U8Span saveDataRootPath, bool allowEmulatedSave)
{
if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, saveDataRootPath))
{
fileSystem = default;
ReferenceCountedDisposable<IFileSystem> tmFs = null;
try
{
// Ensure the target save data directory exists
Result rc = _config.TargetManagerFsCreator.Create(out tmFs, false);
if (rc.IsFailure()) return rc;
rc = Utility.EnsureDirectory(tmFs.Target, saveDataRootPath);
if (rc.IsFailure())
return ResultFs.SaveDataRootPathUnavailable.LogConverted(rc);
tmFs.Dispose();
// Nintendo does a straight copy here without checking the input path length,
// stopping before the null terminator.
// FsPath used instead of stackalloc for convenience
FsPath.FromSpan(out FsPath path, saveDataRootPath).IgnoreResult();
rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, path.Str);
if (rc.IsFailure()) return rc;
rc = _config.TargetManagerFsCreator.Create(out tmFs, isTargetFsCaseSensitive);
if (rc.IsFailure()) return rc;
return Utility.CreateSubDirectoryFileSystem(out fileSystem, tmFs, new U8Span(path.Str), true);
}
finally
{
tmFs?.Dispose();
}
}
ReadOnlySpan<byte> saveDirName;
if (spaceId == SaveDataSpaceId.Temporary)
{
saveDirName = new[] { (byte)'/', (byte)'t', (byte)'e', (byte)'m', (byte)'p' }; // /temp
}
else
{
saveDirName = new[] { (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e' }; // /save
}
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(saveDirName), true);
}
public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable<IFileSystem> fileSystem,
SaveDataSpaceId spaceId, U8Span basePath)
{
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, basePath, true);
}
public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable<IFileSystem> fileSystem,
SaveDataSpaceId spaceId, U8Span basePath, bool createIfMissing)
{
fileSystem = default;
ReferenceCountedDisposable<IFileSystem> tempFs = null;
ReferenceCountedDisposable<IFileSystem> tempSubFs = null;
try
{
Result rc;
switch (spaceId)
{
case SaveDataSpaceId.System:
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.System, true);
if (rc.IsFailure()) return rc;
return Utility.WrapSubDirectory(out fileSystem, tempFs, basePath, createIfMissing);
case SaveDataSpaceId.User:
case SaveDataSpaceId.Temporary:
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.User, true);
if (rc.IsFailure()) return rc;
return Utility.WrapSubDirectory(out fileSystem, tempFs, basePath, createIfMissing);
case SaveDataSpaceId.SdSystem:
case SaveDataSpaceId.SdCache:
rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out tempFs, true);
if (rc.IsFailure()) return rc;
Unsafe.SkipInit(out FsPath path);
var sb = new U8StringBuilder(path.Str);
sb.Append((byte)'/')
.Append(basePath.Value)
.Append((byte)'/')
.Append(CommonPaths.SdCardNintendoRootDirectoryName);
rc = Utility.WrapSubDirectory(out tempSubFs, tempFs, path, createIfMissing);
if (rc.IsFailure()) return rc;
return _config.EncryptedFsCreator.Create(out fileSystem, tempSubFs, EncryptedFsKeyId.Save,
in _encryptionSeed);
case SaveDataSpaceId.ProperSystem:
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.SystemProperPartition, true);
if (rc.IsFailure()) return rc;
return Utility.WrapSubDirectory(out fileSystem, tempFs, basePath, createIfMissing);
case SaveDataSpaceId.SafeMode:
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.SafeMode, true);
if (rc.IsFailure()) return rc;
return Utility.WrapSubDirectory(out fileSystem, tempFs, basePath, createIfMissing);
default:
return ResultFs.InvalidArgument.Log();
}
}
finally
{
tempFs?.Dispose();
tempSubFs?.Dispose();
}
}
public Result SetSdCardEncryptionSeed(in EncryptionSeed seed)
{
_encryptionSeed = seed;
_config.SaveFsCreator.SetSdCardEncryptionSeed(seed.Value);
_config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdSystem);
_config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdCache);
return Result.Success;
}
public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo)
{
throw new NotImplementedException();
}
public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, U8Span saveDataRootPath)
{
return spaceId == SaveDataSpaceId.User && IsSaveEmulated(saveDataRootPath);
}
// Todo: remove once file save data is supported
// Used to always allow directory save data in OpenSaveDataFileSystem
public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, U8Span saveDataRootPath)
{
// Todo: remove "|| true" once file save data is supported
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
return spaceId == SaveDataSpaceId.User && IsSaveEmulated(saveDataRootPath) || true;
}
public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId)
{
return spaceId == SaveDataSpaceId.System ||
spaceId == SaveDataSpaceId.User ||
spaceId == SaveDataSpaceId.Temporary ||
spaceId == SaveDataSpaceId.ProperSystem ||
spaceId == SaveDataSpaceId.SafeMode;
}
public void SetSdCardAccessibility(bool isAccessible)
{
_isSdCardAccessible = isAccessible;
}
public bool IsSdCardAccessible()
{
return _isSdCardAccessible;
}
/// <summary>
/// Gets the program ID of the save data associated with the specified programID.
/// </summary>
/// <remarks>In a standard application the program ID will be the same as the input program ID.
/// In multi-program applications all sub-programs use the program ID of the main program
/// for their save data. The main program always has a program index of 0.</remarks>
/// <param name="programId">The program ID to get the save data program ID for.</param>
/// <returns>The program ID of the save data.</returns>
public ProgramId ResolveDefaultSaveDataReferenceProgramId(in ProgramId programId)
{
// First check if there's an entry in the program index map with the program ID and program index 0
ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, 0);
if (mainProgramId != ProgramId.InvalidId)
{
return mainProgramId;
}
// Check if there's an entry with the program ID, ignoring program index
Optional<ProgramIndexMapInfo> mapInfo = _config.ProgramRegistryService.GetProgramIndexMapInfo(programId);
if (mapInfo.HasValue)
{
return mapInfo.Value.MainProgramId;
}
// The program ID isn't in the program index map. Probably running a single-program application
return programId;
}
public void ResetTemporaryStorageIndexer()
{
_config.SaveIndexerManager.ResetIndexer(SaveDataSpaceId.Temporary);
}
public Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit,
SaveDataSpaceId spaceId)
{
return _config.SaveIndexerManager.OpenSaveDataIndexerAccessor(out accessor, out neededInit, spaceId);
}
}
}

View File

@ -8,7 +8,9 @@ using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSrv.Sf;
using LibHac.Kvdb;
using LibHac.Sf;
namespace LibHac.FsSrv
{
@ -504,7 +506,7 @@ namespace LibHac.FsSrv
}
}
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<SaveDataInfoReaderImpl> infoReader)
{
infoReader = default;
@ -522,7 +524,7 @@ namespace LibHac.FsSrv
rc = RegisterReader(reader);
if (rc.IsFailure()) return rc;
infoReader = reader.AddReference<ISaveDataInfoReader>();
infoReader = reader.AddReference<SaveDataInfoReaderImpl>();
return Result.Success;
}
}
@ -802,7 +804,7 @@ namespace LibHac.FsSrv
}
}
private class Reader : ISaveDataInfoReader
private class Reader : SaveDataInfoReaderImpl, ISaveDataInfoReader
{
private readonly SaveDataIndexer _indexer;
private FlatMapKeyValueStore<SaveDataAttribute>.Iterator _iterator;
@ -816,7 +818,7 @@ namespace LibHac.FsSrv
_iterator = indexer.GetBeginIterator();
}
public Result Read(out long readCount, Span<byte> saveDataInfoBuffer)
public Result Read(out long readCount, OutBuffer saveDataInfoBuffer)
{
Unsafe.SkipInit(out readCount);
@ -828,7 +830,7 @@ namespace LibHac.FsSrv
return ResultFs.InvalidSaveDataInfoReader.Log();
}
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer.Buffer);
int i;
for (i = 0; !_iterator.IsEnd() && i < outInfo.Length; i++)

View File

@ -1,6 +1,8 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Fs;
using LibHac.FsSrv.Sf;
using LibHac.Sf;
namespace LibHac.FsSrv
{
@ -207,7 +209,7 @@ namespace LibHac.FsSrv
return 1;
}
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<SaveDataInfoReaderImpl> infoReader)
{
SaveDataIndexerLiteInfoReader reader;
@ -220,12 +222,12 @@ namespace LibHac.FsSrv
reader = new SaveDataIndexerLiteInfoReader();
}
infoReader = new ReferenceCountedDisposable<ISaveDataInfoReader>(reader);
infoReader = new ReferenceCountedDisposable<SaveDataInfoReaderImpl>(reader);
return Result.Success;
}
private class SaveDataIndexerLiteInfoReader : ISaveDataInfoReader
private class SaveDataIndexerLiteInfoReader : SaveDataInfoReaderImpl, ISaveDataInfoReader
{
private bool _finishedIterating;
private SaveDataInfo _info;
@ -240,9 +242,9 @@ namespace LibHac.FsSrv
SaveDataIndexer.GenerateSaveDataInfo(out _info, in key, in value);
}
public Result Read(out long readCount, Span<byte> saveDataInfoBuffer)
public Result Read(out long readCount, OutBuffer saveDataInfoBuffer)
{
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer.Buffer);
// Note: Nintendo doesn't check if the buffer is large enough here
if (_finishedIterating || outInfo.IsEmpty)

View File

@ -1,9 +1,9 @@
using System;
using System.Threading;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.FsSrv.Storage;
using LibHac.Util;
namespace LibHac.FsSrv
{
@ -54,97 +54,112 @@ namespace LibHac.FsSrv
/// if the indexer needed to be initialized.</param>
/// <param name="spaceId">The <see cref="SaveDataSpaceId"/> of the indexer to open.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result OpenAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId)
public Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit,
SaveDataSpaceId spaceId)
{
neededInit = false;
UniqueLock indexerLock = default;
if (IsBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User)
{
spaceId = SaveDataSpaceId.ProperSystem;
}
switch (spaceId)
try
{
case SaveDataSpaceId.System:
case SaveDataSpaceId.User:
Monitor.Enter(_bisIndexer.Locker);
ISaveDataIndexer indexer;
switch (spaceId)
{
case SaveDataSpaceId.System:
case SaveDataSpaceId.User:
indexerLock = new UniqueLock(_bisIndexer.Locker);
if (!_bisIndexer.IsInitialized)
{
_bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName),
SaveDataSpaceId.System, SaveDataId, MemoryResource);
if (!_bisIndexer.IsInitialized)
{
_bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName),
SaveDataSpaceId.System, SaveDataId, MemoryResource);
neededInit = true;
}
neededInit = true;
}
accessor = new SaveDataIndexerAccessor(_bisIndexer.Indexer, _bisIndexer.Locker);
return Result.Success;
indexer = _bisIndexer.Indexer;
break;
case SaveDataSpaceId.SdSystem:
case SaveDataSpaceId.SdCache:
// ReSharper doesn't realize that UniqueLock locks the indexer's lock object
// ReSharper disable InconsistentlySynchronizedField
indexerLock = new UniqueLock(_sdCardIndexer.Locker);
case SaveDataSpaceId.SdSystem:
case SaveDataSpaceId.SdCache:
Monitor.Enter(_sdCardIndexer.Locker);
// We need to reinitialize the indexer if the SD card has changed
if (!_sdCardHandleManager.IsValid(in _sdCardHandle) && _sdCardIndexer.IsInitialized)
{
_sdCardIndexer.Indexer.Dispose();
_sdCardIndexer.Indexer = null;
}
// We need to reinitialize the indexer if the SD card has changed
if (!_sdCardHandleManager.IsValid(in _sdCardHandle) && _sdCardIndexer.IsInitialized)
{
_sdCardIndexer.Indexer.Dispose();
_sdCardIndexer.Indexer = null;
}
if (!_sdCardIndexer.IsInitialized)
{
_sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName),
SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource);
if (!_sdCardIndexer.IsInitialized)
{
_sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName),
SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource);
_sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult();
_sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult();
neededInit = true;
}
neededInit = true;
}
indexer = _sdCardIndexer.Indexer;
// ReSharper restore InconsistentlySynchronizedField
accessor = new SaveDataIndexerAccessor(_sdCardIndexer.Indexer, _sdCardIndexer.Locker);
return Result.Success;
break;
case SaveDataSpaceId.Temporary:
indexerLock = new UniqueLock(_tempIndexer.Locker);
case SaveDataSpaceId.Temporary:
Monitor.Enter(_tempIndexer.Locker);
indexer = _tempIndexer.Indexer;
break;
case SaveDataSpaceId.ProperSystem:
indexerLock = new UniqueLock(_properSystemIndexer.Locker);
accessor = new SaveDataIndexerAccessor(_tempIndexer.Indexer, _tempIndexer.Locker);
return Result.Success;
if (!_properSystemIndexer.IsInitialized)
{
_properSystemIndexer.Indexer = new SaveDataIndexer(FsClient,
new U8Span(ProperSystemIndexerMountName),
SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource);
case SaveDataSpaceId.ProperSystem:
Monitor.Enter(_properSystemIndexer.Locker);
neededInit = true;
}
if (!_properSystemIndexer.IsInitialized)
{
_properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(ProperSystemIndexerMountName),
SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource);
indexer = _properSystemIndexer.Indexer;
break;
case SaveDataSpaceId.SafeMode:
indexerLock = new UniqueLock(_safeIndexer.Locker);
neededInit = true;
}
if (!_safeIndexer.IsInitialized)
{
_safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName),
SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource);
accessor = new SaveDataIndexerAccessor(_properSystemIndexer.Indexer, _properSystemIndexer.Locker);
return Result.Success;
neededInit = true;
}
case SaveDataSpaceId.SafeMode:
Monitor.Enter(_safeIndexer.Locker);
indexer = _safeIndexer.Indexer;
break;
if (!_safeIndexer.IsInitialized)
{
_safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName),
SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource);
default:
accessor = default;
return ResultFs.InvalidArgument.Log();
}
neededInit = true;
}
accessor = new SaveDataIndexerAccessor(_safeIndexer.Indexer, _safeIndexer.Locker);
return Result.Success;
default:
accessor = default;
return ResultFs.InvalidArgument.Log();
accessor = new SaveDataIndexerAccessor(indexer, ref indexerLock);
return Result.Success;
}
finally
{
indexerLock.Dispose();
}
}
public void ResetTemporaryStorageIndexer(SaveDataSpaceId spaceId)
public void ResetIndexer(SaveDataSpaceId spaceId)
{
if (spaceId != SaveDataSpaceId.Temporary)
{
@ -156,7 +171,7 @@ namespace LibHac.FsSrv
Assert.AssertTrue(rc.IsSuccess());
}
public void InvalidateSdCardIndexer(SaveDataSpaceId spaceId)
public void InvalidateIndexer(SaveDataSpaceId spaceId)
{
// Note: Nintendo doesn't lock when doing this operation
lock (_sdCardIndexer.Locker)
@ -225,24 +240,17 @@ namespace LibHac.FsSrv
public class SaveDataIndexerAccessor : IDisposable
{
public ISaveDataIndexer Indexer { get; }
private object Locker { get; }
private bool HasLock { get; set; }
private UniqueLock _locker;
public SaveDataIndexerAccessor(ISaveDataIndexer indexer, object locker)
public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock locker)
{
Indexer = indexer;
Locker = locker;
HasLock = true;
_locker = new UniqueLock(ref locker);
}
public void Dispose()
{
if (HasLock)
{
Monitor.Exit(Locker);
HasLock = false;
}
_locker.Dispose();
}
}
}

View File

@ -2,41 +2,45 @@
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Sf;
using LibHac.Util;
namespace LibHac.FsSrv
{
internal class SaveDataInfoFilterReader : ISaveDataInfoReader
internal class SaveDataInfoFilterReader : SaveDataInfoReaderImpl, ISaveDataInfoReader
{
private ReferenceCountedDisposable<ISaveDataInfoReader> Reader { get; }
private SaveDataFilterInternal Filter { get; }
private ReferenceCountedDisposable<SaveDataInfoReaderImpl> Reader { get; }
private SaveDataInfoFilter InfoFilter { get; }
public SaveDataInfoFilterReader(ReferenceCountedDisposable<ISaveDataInfoReader> reader, ref SaveDataFilterInternal filter)
public SaveDataInfoFilterReader(ReferenceCountedDisposable<SaveDataInfoReaderImpl> reader,
in SaveDataInfoFilter infoFilter)
{
Reader = reader;
Filter = filter;
Reader = reader.AddReference();
InfoFilter = infoFilter;
}
public Result Read(out long readCount, Span<byte> saveDataInfoBuffer)
public Result Read(out long readCount, OutBuffer saveDataInfoBuffer)
{
readCount = default;
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer.Buffer);
SaveDataInfo tempInfo = default;
Span<byte> tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo);
ISaveDataInfoReader reader = Reader.Target;
SaveDataInfoReaderImpl reader = Reader.Target;
int count = 0;
while (count < outInfo.Length)
{
Result rc = reader.Read(out long baseReadCount, tempInfoBytes);
Result rc = reader.Read(out long baseReadCount, new OutBuffer(tempInfoBytes));
if (rc.IsFailure()) return rc;
if (baseReadCount == 0) break;
if (Filter.Matches(ref tempInfo))
if (InfoFilter.Includes(in tempInfo))
{
outInfo[count] = tempInfo;
@ -55,133 +59,97 @@ namespace LibHac.FsSrv
}
}
[StructLayout(LayoutKind.Explicit, Size = 0x50)]
internal struct SaveDataFilterInternal
[StructLayout(LayoutKind.Sequential, Size = 0x60)]
internal struct SaveDataInfoFilter
{
[FieldOffset(0x00)] public bool FilterBySaveDataSpaceId;
[FieldOffset(0x01)] public SaveDataSpaceId SpaceId;
public Optional<SaveDataSpaceId> SpaceId;
public Optional<ProgramId> ProgramId;
public Optional<SaveDataType> SaveDataType;
public Optional<UserId> UserId;
public Optional<ulong> SaveDataId;
public Optional<ushort> Index;
public int Rank;
[FieldOffset(0x08)] public bool FilterByProgramId;
[FieldOffset(0x10)] public ProgramId ProgramId;
[FieldOffset(0x18)] public bool FilterBySaveDataType;
[FieldOffset(0x19)] public SaveDataType SaveDataType;
[FieldOffset(0x20)] public bool FilterByUserId;
[FieldOffset(0x28)] public UserId UserId;
[FieldOffset(0x38)] public bool FilterBySaveDataId;
[FieldOffset(0x40)] public ulong SaveDataId;
[FieldOffset(0x48)] public bool FilterByIndex;
[FieldOffset(0x4A)] public short Index;
[FieldOffset(0x4C)] public int Rank;
public SaveDataFilterInternal(ref SaveDataFilter filter, SaveDataSpaceId spaceId)
public SaveDataInfoFilter(in SaveDataInfoFilter filter)
{
this = default;
this = filter;
}
FilterBySaveDataSpaceId = true;
SpaceId = spaceId;
public SaveDataInfoFilter(SaveDataSpaceId spaceId, in SaveDataFilter filter)
{
// Start out with no optional values
this = new SaveDataInfoFilter();
SpaceId = new Optional<SaveDataSpaceId>(spaceId);
Rank = (int)filter.Rank;
if (filter.FilterByProgramId)
{
FilterByProgramId = true;
ProgramId = filter.ProgramId;
ProgramId = new Optional<ProgramId>(in filter.Attribute.ProgramId);
}
if (filter.FilterBySaveDataType)
{
FilterBySaveDataType = true;
SaveDataType = filter.SaveDataType;
SaveDataType = new Optional<SaveDataType>(in filter.Attribute.Type);
}
if (filter.FilterByUserId)
{
FilterByUserId = true;
UserId = filter.UserId;
UserId = new Optional<UserId>(in filter.Attribute.UserId);
}
if (filter.FilterBySaveDataId)
{
FilterBySaveDataId = true;
SaveDataId = filter.SaveDataId;
SaveDataId = new Optional<ulong>(in filter.Attribute.StaticSaveDataId);
}
if (filter.FilterByIndex)
{
FilterByIndex = true;
Index = filter.Index;
Index = new Optional<ushort>(in filter.Attribute.Index);
}
}
public void SetSaveDataSpaceId(SaveDataSpaceId spaceId)
public SaveDataInfoFilter(Optional<SaveDataSpaceId> spaceId, Optional<ProgramId> programId,
Optional<SaveDataType> saveDataType, Optional<UserId> userId, Optional<ulong> saveDataId,
Optional<ushort> index, int rank)
{
FilterBySaveDataSpaceId = true;
SpaceId = spaceId;
ProgramId = programId;
SaveDataType = saveDataType;
UserId = userId;
SaveDataId = saveDataId;
Index = index;
Rank = rank;
}
public void SetProgramId(ProgramId value)
public bool Includes(in SaveDataInfo saveInfo)
{
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)
if (SpaceId.HasValue && saveInfo.SpaceId != SpaceId.Value)
{
return false;
}
if (FilterByProgramId && info.ProgramId != ProgramId)
if (ProgramId.HasValue && saveInfo.ProgramId != ProgramId.Value)
{
return false;
}
if (FilterBySaveDataType && info.Type != SaveDataType)
if (SaveDataType.HasValue && saveInfo.Type != SaveDataType.Value)
{
return false;
}
if (FilterByUserId && info.UserId != UserId)
if (UserId.HasValue && saveInfo.UserId != UserId.Value)
{
return false;
}
if (FilterBySaveDataId && info.SaveDataId != SaveDataId)
if (SaveDataId.HasValue && saveInfo.SaveDataId != SaveDataId.Value)
{
return false;
}
if (FilterByIndex && info.Index != Index)
if (Index.HasValue && saveInfo.Index != Index.Value)
{
return false;
}
@ -189,7 +157,7 @@ namespace LibHac.FsSrv
var filterRank = (SaveDataRank)(Rank & 1);
// When filtering by secondary rank, match on both primary and secondary ranks
if (filterRank == SaveDataRank.Primary && info.Rank == SaveDataRank.Secondary)
if (filterRank == SaveDataRank.Primary && saveInfo.Rank == SaveDataRank.Secondary)
{
return false;
}

View File

@ -0,0 +1,28 @@
using System;
using LibHac.Fs;
using LibHac.Sf;
namespace LibHac.FsSrv
{
/// <summary>
/// Iterates through the <see cref="SaveDataInfo"/> of the save data
/// in a single save data space.
/// </summary>
// ReSharper disable once InconsistentNaming
// Kinda weird to name an interface / pure abstract class SaveDataInfoReaderImpl. Ask Nintendo, not me.
//
public interface SaveDataInfoReaderImpl : IDisposable
{
/// <summary>
/// Returns the next <see cref="SaveDataInfo"/> entries. This method will continue writing
/// entries to <paramref name="saveDataInfoBuffer"/> until there is either no more space
/// in the buffer, or until there are no more entries to iterate.
/// </summary>
/// <param name="readCount">If the method returns successfully, contains the number
/// of <see cref="SaveDataInfo"/> written to <paramref name="saveDataInfoBuffer"/>.
/// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small.</param>
/// <param name="saveDataInfoBuffer">The buffer in which to write the <see cref="SaveDataInfo"/>.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
Result Read(out long readCount, OutBuffer saveDataInfoBuffer);
}
}

View File

@ -0,0 +1,35 @@
using System;
using LibHac.Crypto;
namespace LibHac.FsSrv
{
public class SaveDataTransferCryptoConfiguration
{
private Data100 _tokenSigningKeyModulus;
private Data100 _keySeedPackageSigningKeyModulus;
private Data100 _kekEncryptionKeyModulus;
private Data100 _keyPackageSigningModulus;
public Span<byte> TokenSigningKeyModulus => _tokenSigningKeyModulus.Data;
public Span<byte> KeySeedPackageSigningKeyModulus => _keySeedPackageSigningKeyModulus.Data;
public Span<byte> KekEncryptionKeyModulus => _kekEncryptionKeyModulus.Data;
public Span<byte> KeyPackageSigningModulus => _keyPackageSigningModulus.Data;
public SaveTransferAesKeyGenerator GenerateAesKey { get; set; }
public RandomDataGenerator GenerateRandomData { get; set; }
public SaveTransferCmacGenerator GenerateCmac { get; set; }
public enum KeyIndex
{
SaveDataTransferToken,
SaveDataTransfer,
SaveDataTransferKeySeedPackage,
CloudBackUpInitialData,
CloudBackUpImportContext,
CloudBackUpInitialDataMac,
SaveDataRepairKeyPackage,
SaveDataRepairInitialDataMacBeforeRepair,
SaveDataRepairInitialDataMacAfterRepair
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace LibHac.FsSrv.Sf
{
public interface IMultiCommitManager : IDisposable
{
Result Add(ReferenceCountedDisposable<IFileSystemSf> fileSystem);
Result Commit();
}
}

View File

@ -1,7 +1,8 @@
using System;
using LibHac.Fs;
using LibHac.Sf;
namespace LibHac.FsSrv
namespace LibHac.FsSrv.Sf
{
/// <summary>
/// Iterates through the <see cref="SaveDataInfo"/> of the save data
@ -19,6 +20,6 @@ namespace LibHac.FsSrv
/// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small.</param>
/// <param name="saveDataInfoBuffer">The buffer in which to write the <see cref="SaveDataInfo"/>.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
Result Read(out long readCount, Span<byte> saveDataInfoBuffer);
Result Read(out long readCount, OutBuffer saveDataInfoBuffer);
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace LibHac.FsSrv.Sf
{
public interface ISaveDataMover : IDisposable
{
Result Register(ulong saveDataId);
Result Process(out long remainingSize, long sizeToProcess);
Result Cancel();
}
}

View File

@ -47,7 +47,7 @@ namespace LibHac.FsSrv
public static Result VerifyHostPath(U8Span path)
{
if(path.IsEmpty())
if (path.IsEmpty())
return Result.Success;
if (path[0] != StringTraits.DirectorySeparator)
@ -55,7 +55,7 @@ namespace LibHac.FsSrv
U8Span path2 = path.Slice(1);
if(path2.IsEmpty())
if (path2.IsEmpty())
return Result.Success;
int skipLength = PathUtility.GetWindowsPathSkipLength(path2);

View File

@ -11,14 +11,16 @@ namespace LibHac.FsSystem
public int BlockSize { get; }
private IFileSystem BaseFileSystem { get; }
private ReferenceCountedDisposable<IFileSystem> SharedBaseFileSystem { get; }
private byte[] KekSource { get; }
private byte[] ValidationKey { get; }
public AesXtsFileSystem(IFileSystem fs, byte[] kekSource, byte[] validationKey, int blockSize)
public AesXtsFileSystem(ReferenceCountedDisposable<IFileSystem> fs, byte[] keys, int blockSize)
{
BaseFileSystem = fs;
KekSource = kekSource;
ValidationKey = validationKey;
SharedBaseFileSystem = fs.AddReference();
BaseFileSystem = SharedBaseFileSystem.Target;
KekSource = keys.AsSpan(0, 0x10).ToArray();
ValidationKey = keys.AsSpan(0x10, 0x10).ToArray();
BlockSize = blockSize;
}
@ -30,6 +32,16 @@ namespace LibHac.FsSystem
BlockSize = blockSize;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
SharedBaseFileSystem?.Dispose();
}
base.Dispose(disposing);
}
protected override Result DoCreateDirectory(U8Span path)
{
return BaseFileSystem.CreateDirectory(path);

View File

@ -0,0 +1,90 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSystem
{
public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAccessor
{
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option)
{
throw new NotImplementedException();
}
protected override Result DoDeleteFile(U8Span path)
{
throw new NotImplementedException();
}
protected override Result DoCreateDirectory(U8Span path)
{
throw new NotImplementedException();
}
protected override Result DoDeleteDirectory(U8Span path)
{
throw new NotImplementedException();
}
protected override Result DoDeleteDirectoryRecursively(U8Span path)
{
throw new NotImplementedException();
}
protected override Result DoCleanDirectoryRecursively(U8Span path)
{
throw new NotImplementedException();
}
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
{
throw new NotImplementedException();
}
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
{
throw new NotImplementedException();
}
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
{
throw new NotImplementedException();
}
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
{
throw new NotImplementedException();
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
throw new NotImplementedException();
}
protected override Result DoCommit()
{
throw new NotImplementedException();
}
public Result WriteExtraData(in SaveDataExtraData extraData)
{
throw new NotImplementedException();
}
public Result CommitExtraData(bool updateTimeStamp)
{
throw new NotImplementedException();
}
public Result ReadExtraData(out SaveDataExtraData extraData)
{
throw new NotImplementedException();
}
public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,83 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSystem
{
public class ForwardingFileSystem : IFileSystem
{
private ReferenceCountedDisposable<IFileSystem> BaseFileSystem { get; set; }
public ForwardingFileSystem(ReferenceCountedDisposable<IFileSystem> baseFileSystem)
{
BaseFileSystem = baseFileSystem.AddReference();
}
protected ForwardingFileSystem(ref ReferenceCountedDisposable<IFileSystem> baseFileSystem)
{
BaseFileSystem = Shared.Move(ref baseFileSystem);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
BaseFileSystem?.Dispose();
}
base.Dispose(disposing);
}
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option) =>
BaseFileSystem.Target.CreateFile(path, size, option);
protected override Result DoDeleteFile(U8Span path) => BaseFileSystem.Target.DeleteFile(path);
protected override Result DoCreateDirectory(U8Span path) => BaseFileSystem.Target.CreateDirectory(path);
protected override Result DoDeleteDirectory(U8Span path) => BaseFileSystem.Target.DeleteDirectory(path);
protected override Result DoDeleteDirectoryRecursively(U8Span path) =>
BaseFileSystem.Target.DeleteDirectoryRecursively(path);
protected override Result DoCleanDirectoryRecursively(U8Span path) =>
BaseFileSystem.Target.CleanDirectoryRecursively(path);
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) =>
BaseFileSystem.Target.RenameFile(oldPath, newPath);
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) =>
BaseFileSystem.Target.RenameDirectory(oldPath, newPath);
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) =>
BaseFileSystem.Target.GetEntryType(out entryType, path);
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) =>
BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path);
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) =>
BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path);
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) =>
BaseFileSystem.Target.OpenFile(out file, path, mode);
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) =>
BaseFileSystem.Target.OpenDirectory(out directory, path, mode);
protected override Result DoCommit() => BaseFileSystem.Target.Commit();
protected override Result DoCommitProvisionally(long counter) =>
BaseFileSystem.Target.CommitProvisionally(counter);
protected override Result DoRollback() => BaseFileSystem.Target.Rollback();
protected override Result DoFlush() => BaseFileSystem.Target.Flush();
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) =>
BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, path);
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
U8Span path) => BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path);
}
}

View File

@ -0,0 +1,13 @@
using System;
using LibHac.Fs;
namespace LibHac.FsSystem
{
public interface ISaveDataExtraDataAccessor : IDisposable
{
Result WriteExtraData(in SaveDataExtraData extraData);
Result CommitExtraData(bool updateTimeStamp);
Result ReadExtraData(out SaveDataExtraData extraData);
void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId);
}
}

View File

@ -0,0 +1,10 @@
using System;
using LibHac.Fs;
namespace LibHac.FsSystem
{
public interface ISaveDataExtraDataAccessorCacheObserver : IDisposable
{
void Unregister(SaveDataSpaceId spaceId, ulong saveDataId);
}
}

View File

@ -0,0 +1,14 @@
using System;
using LibHac.Fs;
using LibHac.FsSystem.Save;
namespace LibHac.FsSystem
{
public interface ISaveDataFileSystemCacheManager : IDisposable
{
bool GetCache(out ReferenceCountedDisposable<SaveDataFileSystem> fileSystem, SaveDataSpaceId spaceId, ulong saveDataId);
void Register(ReferenceCountedDisposable<ApplicationTemporaryFileSystem> fileSystem);
void Register(ReferenceCountedDisposable<SaveDataFileSystem> fileSystem);
void Unregister(SaveDataSpaceId spaceId, ulong saveDataId);
}
}

View File

@ -1,8 +0,0 @@
using System;
namespace LibHac.FsSystem
{
public interface IUniqueLock : IDisposable
{
}
}

View File

@ -21,6 +21,13 @@ namespace LibHac.FsSystem
BaseFs = BaseFsShared.Target;
}
public static ReferenceCountedDisposable<IFileSystem> CreateShared(
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem)
{
var fs = new ReadOnlyFileSystem(Shared.Move(ref baseFileSystem));
return new ReferenceCountedDisposable<IFileSystem>(fs);
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
return BaseFs.OpenDirectory(out directory, path, mode);

View File

@ -1,9 +0,0 @@
namespace LibHac.FsSystem.Save
{
public interface ISaveDataExtraDataAccessor
{
Result Write(ExtraData data);
Result Commit();
Result Read(out ExtraData data);
}
}

View File

@ -72,7 +72,7 @@ namespace LibHac.FsSystem.Save
MapStorage = MetaRemapStorage.Slice(layout.JournalMapTableOffset, layout.JournalMapTableSize),
PhysicalBlockBitmap = MetaRemapStorage.Slice(layout.JournalPhysicalBitmapOffset, layout.JournalPhysicalBitmapSize),
VirtualBlockBitmap = MetaRemapStorage.Slice(layout.JournalVirtualBitmapOffset, layout.JournalVirtualBitmapSize),
FreeBlockBitmap = MetaRemapStorage.Slice(layout.JournalFreeBitmapOffset, layout.JournalFreeBitmapSize),
FreeBlockBitmap = MetaRemapStorage.Slice(layout.JournalFreeBitmapOffset, layout.JournalFreeBitmapSize)
};
IStorage journalData = DataRemapStorage.Slice(layout.JournalDataOffset,

View File

@ -0,0 +1,165 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSystem
{
internal class StorageLayoutTypeSetFileSystem : IFileSystem
{
private ReferenceCountedDisposable<IFileSystem> BaseFileSystem { get; }
private StorageType StorageFlag { get; }
public StorageLayoutTypeSetFileSystem(ReferenceCountedDisposable<IFileSystem> baseFileSystem,
StorageType storageFlag)
{
BaseFileSystem = baseFileSystem.AddReference();
StorageFlag = storageFlag;
}
protected StorageLayoutTypeSetFileSystem(ref ReferenceCountedDisposable<IFileSystem> baseFileSystem,
StorageType storageFlag)
{
BaseFileSystem = Shared.Move(ref baseFileSystem);
StorageFlag = storageFlag;
}
public static ReferenceCountedDisposable<IFileSystem> CreateShared(
ReferenceCountedDisposable<IFileSystem> baseFileSystem, StorageType storageFlag)
{
return new ReferenceCountedDisposable<IFileSystem>(
new StorageLayoutTypeSetFileSystem(baseFileSystem, storageFlag));
}
public static ReferenceCountedDisposable<IFileSystem> CreateShared(
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, StorageType storageFlag)
{
return new ReferenceCountedDisposable<IFileSystem>(
new StorageLayoutTypeSetFileSystem(ref baseFileSystem, storageFlag));
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
BaseFileSystem?.Dispose();
}
base.Dispose(disposing);
}
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.CreateFile(path, size, option);
}
protected override Result DoDeleteFile(U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.DeleteFile(path);
}
protected override Result DoCreateDirectory(U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.CreateDirectory(path);
}
protected override Result DoDeleteDirectory(U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.DeleteDirectory(path);
}
protected override Result DoDeleteDirectoryRecursively(U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.DeleteDirectoryRecursively(path);
}
protected override Result DoCleanDirectoryRecursively(U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.CleanDirectoryRecursively(path);
}
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.RenameFile(oldPath, newPath);
}
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.RenameDirectory(oldPath, newPath);
}
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.GetEntryType(out entryType, path);
}
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path);
}
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path);
}
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.OpenFile(out file, path, mode);
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.OpenDirectory(out directory, path, mode);
}
protected override Result DoCommit()
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.Commit();
}
protected override Result DoCommitProvisionally(long counter)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.CommitProvisionally(counter);
}
protected override Result DoRollback()
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.Rollback();
}
protected override Result DoFlush()
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.Flush();
}
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, path);
}
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, U8Span path)
{
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path);
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace LibHac.FsSystem
{
internal struct ScopedStorageLayoutTypeSetter : IDisposable
{
// ReSharper disable once UnusedParameter.Local
public ScopedStorageLayoutTypeSetter(StorageType storageFlag)
{
// Todo: Implement
}
public void Dispose()
{
}
}
[Flags]
internal enum StorageType
{
Bis = 1 << 0,
SdCard = 1 << 1,
GameCard = 1 << 2,
Usb = 1 << 3,
NonGameCard = Bis | SdCard | Usb,
All = Bis | SdCard | GameCard | Usb
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.Threading;
using LibHac.Common;
using LibHac.Diag;
namespace LibHac.FsSystem
{
public interface IUniqueLock : IDisposable
{
}
/// <summary>
/// Represents a lock that may be passed between functions or objects.
/// </summary>
/// <remarks>This struct must never be copied. It must always be passed by
/// reference or moved via the move constructor.</remarks>
public struct UniqueLockSemaphore : IDisposable
{
private SemaphoreAdaptor _semaphore;
private bool _isLocked;
public UniqueLockSemaphore(SemaphoreAdaptor semaphore)
{
_semaphore = semaphore;
_isLocked = false;
}
public UniqueLockSemaphore(ref UniqueLockSemaphore other)
{
_semaphore = other._semaphore;
_isLocked = other._isLocked;
other._isLocked = false;
other._semaphore = null;
}
public bool IsLocked => _isLocked;
public bool TryLock()
{
if (_isLocked)
{
throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked.");
}
_isLocked = _semaphore.TryLock();
return _isLocked;
}
public void Unlock()
{
if (!_isLocked)
{
throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked.");
}
_semaphore.Unlock();
_isLocked = false;
}
public void Dispose()
{
if (_isLocked)
{
_semaphore.Unlock();
_isLocked = false;
_semaphore = null;
}
}
}
public class UniqueLockWithPin<T> : IUniqueLock where T : class, IDisposable
{
private UniqueLockSemaphore _semaphore;
private ReferenceCountedDisposable<T> _pinnedObject;
public UniqueLockWithPin(ref UniqueLockSemaphore semaphore, ref ReferenceCountedDisposable<T> pinnedObject)
{
Shared.Move(out _semaphore, ref semaphore);
Shared.Move(out _pinnedObject, ref pinnedObject);
Assert.AssertTrue(_semaphore.IsLocked);
}
public void Dispose()
{
if (_pinnedObject != null)
{
_semaphore.Dispose();
_pinnedObject.Dispose();
_pinnedObject = null;
}
}
}
}

View File

@ -226,6 +226,48 @@ namespace LibHac.FsSystem
return Result.Success;
}
public static Result TryAcquireCountSemaphore(out UniqueLockSemaphore uniqueLock, SemaphoreAdaptor semaphore)
{
UniqueLockSemaphore tempUniqueLock = default;
try
{
tempUniqueLock = new UniqueLockSemaphore(semaphore);
if (!tempUniqueLock.TryLock())
{
uniqueLock = default;
return ResultFs.OpenCountLimit.Log();
}
uniqueLock = new UniqueLockSemaphore(ref tempUniqueLock);
return Result.Success;
}
finally
{
tempUniqueLock.Dispose();
}
}
public static Result MakeUniqueLockWithPin<T>(out IUniqueLock uniqueLock, SemaphoreAdaptor semaphore,
ref ReferenceCountedDisposable<T> objectToPin) where T : class, IDisposable
{
uniqueLock = default;
UniqueLockSemaphore tempUniqueLock = default;
try
{
Result rc = TryAcquireCountSemaphore(out tempUniqueLock, semaphore);
if (rc.IsFailure()) return rc;
uniqueLock = new UniqueLockWithPin<T>(ref tempUniqueLock, ref objectToPin);
return Result.Success;
}
finally
{
tempUniqueLock.Dispose();
}
}
public static Result RetryFinitelyForTargetLocked(Func<Result> function)
{
const int maxRetryCount = 10;

View File

@ -1,6 +1,8 @@
namespace LibHac.Ncm
using System;
namespace LibHac.Ncm
{
public readonly struct ApplicationId
public readonly struct ApplicationId : IEquatable<ApplicationId>
{
public readonly ulong Value;
@ -20,6 +22,13 @@
{
return Start <= programId && programId <= End;
}
public bool Equals(ApplicationId other) => Value == other.Value;
public override bool Equals(object obj) => obj is ApplicationId id && Equals(id);
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(ApplicationId left, ApplicationId right) => left.Equals(right);
public static bool operator !=(ApplicationId left, ApplicationId right) => !left.Equals(right);
}
public readonly struct PatchId

View File

@ -441,6 +441,24 @@ namespace LibHac
return TryAddReferenceImpl(target, referenceCount);
}
/// <summary>
/// Increments the reference count for the disposable object, and returns a new disposable reference to
/// it.
/// </summary>
/// <remarks>
/// <para>Unlike <see cref="ReferenceCountedDisposable{T}.TryAddReference"/>, this method is capable of
/// adding a reference to the underlying instance all the way up to the point where it is finally
/// disposed.</para>
///
/// <para>The returned object is an independent reference to the same underlying object. Disposing of
/// the returned value multiple times will only cause the reference count to be decreased once.</para>
/// </remarks>
/// <returns>A new <see cref="ReferenceCountedDisposable{T}"/> pointing to the same underlying object,
/// if it has not yet been disposed; otherwise, <see cref="ObjectDisposedException"/> is thrown if the
/// underlying object has already been disposed.</returns>
public ReferenceCountedDisposable<T> AddReference() =>
TryAddReference() ?? throw new ObjectDisposedException(nameof(WeakReference));
}
}
}

44
src/LibHac/Sf/Buffers.cs Normal file
View File

@ -0,0 +1,44 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
namespace LibHac.Sf
{
public readonly ref struct InBuffer
{
private readonly ReadOnlySpan<byte> _buffer;
public int Size => _buffer.Length;
public ReadOnlySpan<byte> Buffer => _buffer;
public InBuffer(ReadOnlySpan<byte> buffer)
{
_buffer = buffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static InBuffer FromStruct<T>(in T value) where T : unmanaged
{
return new InBuffer(SpanHelpers.AsReadOnlyByteSpan(in value));
}
}
public readonly ref struct OutBuffer
{
private readonly Span<byte> _buffer;
public int Size => _buffer.Length;
public Span<byte> Buffer => _buffer;
public OutBuffer(Span<byte> buffer)
{
_buffer = buffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static OutBuffer FromStruct<T>(ref T value) where T : unmanaged
{
return new OutBuffer(SpanHelpers.AsByteSpan(ref value));
}
}
}

View File

@ -20,7 +20,7 @@ namespace LibHac.Util.Impl
// while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ])
// don't have the 0x20 bit set, so ORing them maps to
// [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want.
Lower = 0x2020U,
Lower = 0x2020U
}
// We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ],

View File

@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
namespace LibHac.Util
@ -8,7 +9,7 @@ namespace LibHac.Util
private bool _hasValue;
private T _value;
public bool HasValue => _hasValue;
public readonly bool HasValue => _hasValue;
public ref T Value
{
get
@ -18,6 +19,22 @@ namespace LibHac.Util
return ref MemoryMarshal.CreateSpan(ref _value, 1)[0];
}
}
public readonly ref readonly T ValueRo
{
get
{
Assert.AssertTrue(_hasValue);
return ref SpanHelpers.CreateReadOnlySpan(in _value, 1)[0];
}
}
public Optional(in T value)
{
_value = value;
_hasValue = true;
}
public static implicit operator Optional<T>(in T value) => new Optional<T>(in value);
public void Set(in T value)
{

View File

@ -0,0 +1,71 @@
using System;
using System.Threading;
namespace LibHac.Util
{
/// <summary>
/// Represents a lock that may be passed between functions or objects.
/// </summary>
/// <remarks>This struct must never be copied. It must always be passed by
/// reference or moved via the move constructor.</remarks>
public struct UniqueLock : IDisposable
{
private object _lockObject;
private bool _isLocked;
/// <summary>
/// Creates a new <see cref="UniqueLock"/> from the provided object and acquires the lock.
/// </summary>
/// <param name="lockObject">The object to lock.</param>
public UniqueLock(object lockObject)
{
_lockObject = lockObject;
_isLocked = false;
Lock();
}
public UniqueLock(ref UniqueLock other)
{
_lockObject = other._lockObject;
_isLocked = other._isLocked;
other._isLocked = false;
other._lockObject = null;
}
public bool IsLocked => _isLocked;
public void Lock()
{
if (_isLocked)
{
throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked.");
}
Monitor.Enter(_lockObject, ref _isLocked);
}
public void Unlock()
{
if (!_isLocked)
{
throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked.");
}
Monitor.Exit(_lockObject);
_isLocked = false;
}
public void Dispose()
{
if (_isLocked)
{
Monitor.Exit(_lockObject);
_isLocked = false;
_lockObject = null;
}
}
}
}

View File

@ -14,7 +14,13 @@ namespace hactoolnet
{
try
{
if (Run(args)) return 0;
if (Run(args))
{
// Console.ReadKey();
return 0;
}
}
catch (MissingKeyException ex)
{

View File

@ -60,7 +60,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].ProgramId);
Assert.Equal(UserId.Zero, info[0].UserId);
Assert.Equal(UserId.InvalidId, info[0].UserId);
Assert.Equal(SaveDataType.Device, info[0].Type);
}
@ -86,7 +86,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].ProgramId);
Assert.Equal(UserId.Zero, info[0].UserId);
Assert.Equal(UserId.InvalidId, info[0].UserId);
Assert.Equal(SaveDataType.Bcat, info[0].Type);
}
@ -112,7 +112,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests
Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].ProgramId);
Assert.Equal(UserId.Zero, info[0].UserId);
Assert.Equal(UserId.InvalidId, info[0].UserId);
Assert.Equal(SaveDataType.Temporary, info[0].Type);
}

View File

@ -80,8 +80,8 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
Assert.Equal(applicationId, info[0].ProgramId);
Assert.Equal(applicationId, info[1].ProgramId);
var expectedIndexes = new short[] { 0, 1 };
short[] actualIndexes = info.Take(2).Select(x => x.Index).OrderBy(x => x).ToArray();
var expectedIndexes = new ushort[] { 0, 1 };
ushort[] actualIndexes = info.Take(2).Select(x => x.Index).OrderBy(x => x).ToArray();
Assert.Equal(expectedIndexes, actualIndexes);
}
@ -294,7 +294,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
for (int i = 1; i <= count; i++)
{
var applicationId = new Ncm.ApplicationId((uint)i);
Result rc = fs.CreateSaveData(applicationId, UserId.Zero, 0, 0x4000, 0x4000, SaveDataFlags.None);
Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None);
if (rc.IsFailure()) return rc;
}
}
@ -305,7 +305,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
for (int i = 1; i <= count; i++)
{
var applicationId = new Ncm.ApplicationId((uint)rng.Next());
Result rc = fs.CreateSaveData(applicationId, UserId.Zero, 0, 0x4000, 0x4000, SaveDataFlags.None);
Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None);
if (rc.IsFailure()) return rc;
}
}

Some files were not shown because too many files have changed in this diff Show More