mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Separate fssrv save code to SaveDataFileSystemService
This commit is contained in:
parent
3837ed7eea
commit
882e6bc937
@ -1 +1 @@
|
||||
3.1.401
|
||||
3.1.402
|
@ -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.
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
22
src/LibHac/Common/SharedObjectHelpers.cs
Normal file
22
src/LibHac/Common/SharedObjectHelpers.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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'
|
||||
};
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -1,5 +1,5 @@
|
||||
using LibHac.Common;
|
||||
using static LibHac.Fs.CommonMountNames;
|
||||
using static LibHac.Fs.CommonPaths;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
|
@ -25,7 +25,7 @@ namespace LibHac.Fs
|
||||
{
|
||||
InternalKeyForSoftwareAes = 1 << 0,
|
||||
InternalKeyForHardwareAes = 1 << 1,
|
||||
ExternalKeyForHardwareAes = 1 << 2,
|
||||
ExternalKeyForHardwareAes = 1 << 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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); }
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
52
src/LibHac/Fs/Shim/UserFileSystem.cs
Normal file
52
src/LibHac/Fs/Shim/UserFileSystem.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
12
src/LibHac/FsSrv/Delegates.cs
Normal file
12
src/LibHac/FsSrv/Delegates.cs
Normal 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);
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
public interface IMultiCommitManager
|
||||
{
|
||||
Result Add(IFileSystem fileSystem);
|
||||
Result Commit();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -830,6 +830,7 @@ namespace LibHac.FsSrv.Impl
|
||||
RegisterProgramIndexMapInfo,
|
||||
ChallengeCardExistence,
|
||||
CreateOwnSaveData,
|
||||
DeleteOwnSaveData,
|
||||
ReadOwnSaveDataFileSystemExtraData,
|
||||
ExtendOwnSaveData,
|
||||
OpenOwnSaveDataTransferProhibiter,
|
||||
|
37
src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs
Normal file
37
src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
10
src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs
Normal file
10
src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
public interface IEntryOpenCountSemaphoreManager : IDisposable
|
||||
{
|
||||
Result TryAcquireEntryOpenCountSemaphore(out IUniqueLock semaphore);
|
||||
}
|
||||
}
|
14
src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs
Normal file
14
src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs
Normal 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);
|
||||
}
|
||||
}
|
30
src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs
Normal file
30
src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
74
src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs
Normal file
74
src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
100
src/LibHac/FsSrv/Impl/SaveDataProperties.cs
Normal file
100
src/LibHac/FsSrv/Impl/SaveDataProperties.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>();
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -69,7 +69,7 @@ namespace LibHac.FsSrv
|
||||
PreserveUnc = (1 << 0),
|
||||
PreserveTailSeparator = (1 << 1),
|
||||
HasMountName = (1 << 2),
|
||||
AcceptEmpty = (1 << 3),
|
||||
AcceptEmpty = (1 << 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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();
|
@ -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"/>
|
||||
|
@ -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);
|
||||
}
|
||||
|
1938
src/LibHac/FsSrv/SaveDataFileSystemService.cs
Normal file
1938
src/LibHac/FsSrv/SaveDataFileSystemService.cs
Normal file
File diff suppressed because it is too large
Load Diff
625
src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs
Normal file
625
src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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++)
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
28
src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs
Normal file
28
src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs
Normal 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);
|
||||
}
|
||||
}
|
35
src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs
Normal file
35
src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
10
src/LibHac/FsSrv/Sf/IMultiCommitManager.cs
Normal file
10
src/LibHac/FsSrv/Sf/IMultiCommitManager.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.FsSrv.Sf
|
||||
{
|
||||
public interface IMultiCommitManager : IDisposable
|
||||
{
|
||||
Result Add(ReferenceCountedDisposable<IFileSystemSf> fileSystem);
|
||||
Result Commit();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
11
src/LibHac/FsSrv/Sf/ISaveDataMover.cs
Normal file
11
src/LibHac/FsSrv/Sf/ISaveDataMover.cs
Normal 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
90
src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs
Normal file
90
src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
83
src/LibHac/FsSystem/ForwardingFileSystem.cs
Normal file
83
src/LibHac/FsSystem/ForwardingFileSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
13
src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs
Normal file
13
src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public interface ISaveDataExtraDataAccessorCacheObserver : IDisposable
|
||||
{
|
||||
void Unregister(SaveDataSpaceId spaceId, ulong saveDataId);
|
||||
}
|
||||
}
|
14
src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs
Normal file
14
src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public interface IUniqueLock : IDisposable
|
||||
{
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -1,9 +0,0 @@
|
||||
namespace LibHac.FsSystem.Save
|
||||
{
|
||||
public interface ISaveDataExtraDataAccessor
|
||||
{
|
||||
Result Write(ExtraData data);
|
||||
Result Commit();
|
||||
Result Read(out ExtraData data);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
165
src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs
Normal file
165
src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
30
src/LibHac/FsSystem/StorageLayoutTypeSetter.cs
Normal file
30
src/LibHac/FsSystem/StorageLayoutTypeSetter.cs
Normal 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
|
||||
}
|
||||
}
|
97
src/LibHac/FsSystem/UniqueLockSemaphore.cs
Normal file
97
src/LibHac/FsSystem/UniqueLockSemaphore.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
44
src/LibHac/Sf/Buffers.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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 ],
|
||||
|
@ -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)
|
||||
{
|
||||
|
71
src/LibHac/Util/UniqueLock.cs
Normal file
71
src/LibHac/Util/UniqueLock.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,13 @@ namespace hactoolnet
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Run(args)) return 0;
|
||||
if (Run(args))
|
||||
{
|
||||
// Console.ReadKey();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (MissingKeyException ex)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user