Merge pull request #89 from Thealexbarney/saveIndex

Add a save indexer with related functions

Adds IFileSystemProxy commands:

- DeleteSaveDataFileSystem
- DeleteSaveDataFileSystemBySaveDataSpaceId
- DeleteSaveDataFileSystemBySaveDataAttribute
- CreateSaveDataFileSystem
- CreateSaveDataFileSystemWithHashSalt
- CreateSaveDataFileSystemBySystemSaveDataId
- OpenSaveDataFileSystem
- OpenReadOnlySaveDataFileSystem
- OpenSaveDataInfoReader
- OpenSaveDataInfoReaderBySaveDataSpaceId
- OpenSaveDataInfoReaderWithFilter
- FindSaveDataWithFilter
This commit is contained in:
Alex Barney 2019-10-24 10:37:56 -05:00 committed by GitHub
commit f3b5cad94b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 2555 additions and 408 deletions

View File

@ -0,0 +1,16 @@
using LibHac.Ncm;
namespace LibHac.Common
{
public static class SystemTitleIds
{
public static TitleId Fs => new TitleId(0x0100000000000000);
public static TitleId Loader => new TitleId(0x0100000000000001);
public static TitleId Ncm => new TitleId(0x0100000000000002);
public static TitleId ProcessManager => new TitleId(0x0100000000000003);
public static TitleId Sm => new TitleId(0x0100000000000004);
public static TitleId Boot => new TitleId(0x0100000000000005);
public static TitleId Bcat => new TitleId(0x010000000000000C);
}
}

View File

@ -153,7 +153,7 @@ namespace LibHac.Fs.Accessors
{
if (MountNameGenerator == null) return ResultFs.PreconditionViolation;
return MountNameGenerator.Generate(nameBuffer);
return MountNameGenerator.GenerateCommonMountName(nameBuffer);
}
internal void NotifyCloseFile(FileAccessor file)
@ -176,7 +176,9 @@ namespace LibHac.Fs.Accessors
{
// Todo: Possibly check for open files and directories
// Nintendo checks for them in DumpUnclosedAccessorList in
// FileSystemAccessor's destructor, but doesn't do anything with it
// FileSystemAccessor's destructor
FileSystem?.Dispose();
}
}
}

View File

@ -0,0 +1,46 @@
namespace LibHac.Fs
{
public abstract class AttributeFileSystemBase : FileSystemBase, IAttributeFileSystem
{
protected abstract Result CreateDirectoryImpl(string path, NxFileAttributes archiveAttribute);
protected abstract Result GetFileAttributesImpl(string path, out NxFileAttributes attributes);
protected abstract Result SetFileAttributesImpl(string path, NxFileAttributes attributes);
protected abstract Result GetFileSizeImpl(out long fileSize, string path);
public Result CreateDirectory(string path, NxFileAttributes archiveAttribute)
{
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
return CreateDirectoryImpl(path, archiveAttribute);
}
public Result GetFileAttributes(string path, out NxFileAttributes attributes)
{
if (IsDisposed)
{
attributes = default;
return ResultFs.PreconditionViolation.Log();
}
return GetFileAttributesImpl(path, out attributes);
}
public Result SetFileAttributes(string path, NxFileAttributes attributes)
{
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
return SetFileAttributesImpl(path, attributes);
}
public Result GetFileSize(out long fileSize, string path)
{
if (IsDisposed)
{
fileSize = default;
return ResultFs.PreconditionViolation.Log();
}
return GetFileSizeImpl(out fileSize, path);
}
}
}

View File

@ -7,7 +7,7 @@ namespace LibHac.Fs
{
// 0 = not disposed; 1 = disposed
private int _disposedState;
private bool IsDisposed => _disposedState != 0;
protected bool IsDisposed => _disposedState != 0;
protected abstract Result CreateDirectoryImpl(string path);
protected abstract Result CreateFileImpl(string path, long size, CreateFileOptions options);

View File

@ -115,6 +115,16 @@ namespace LibHac.Fs
return accessor.IsAccessLogEnabled;
}
internal void EnableFileSystemAccessorAccessLog(U8Span mountName)
{
if (MountTable.Find(mountName.ToString(), out FileSystemAccessor accessor).IsFailure())
{
throw new LibHacException("abort");
}
accessor.IsAccessLogEnabled = true;
}
internal bool IsEnabledHandleAccessLog(FileHandle handle)
{
return handle.File.Parent.IsAccessLogEnabled;

View File

@ -108,7 +108,7 @@ namespace LibHac.Fs
int mountLen = 0;
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength);
for (int i = 0; i < maxMountLen; i++)
for (int i = 0; i <= maxMountLen; i++)
{
if (path[i] == PathTools.MountSeparator)
{
@ -122,7 +122,7 @@ namespace LibHac.Fs
mountName = default;
subPath = default;
return ResultFs.InvalidMountName;
return ResultFs.InvalidMountName.Log();
}
mountName = path.Slice(0, mountLen);

View File

@ -83,6 +83,15 @@ namespace LibHac.Fs
ExtensionInfo = 2
}
public enum SaveDataState : byte
{
Normal = 0,
Creating = 1,
State2 = 2,
MarkedForDeletion = 3,
Extending = 4,
}
public enum ImageDirectoryId
{
Nand = 0,

View File

@ -1,60 +1,9 @@
using System;
using LibHac.Common;
using LibHac.FsService;
namespace LibHac.Fs
{
public static class GameCard
{
public static Result OpenGameCardPartition(this FileSystemClient fs, out IStorage storage,
GameCardHandle handle, GameCardPartitionRaw partitionType)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.OpenGameCardStorage(out storage, handle, partitionType);
}
public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle,
GameCardPartition partitionId)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenGameCardFileSystem(out IFileSystem cardFs, handle, partitionId);
if (rc.IsFailure()) return rc;
var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId);
return fs.Register(mountName, cardFs, mountNameGenerator);
}
public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle)
{
handle = default;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
if (rc.IsFailure()) return rc;
return deviceOperator.GetGameCardHandle(out handle);
}
public static bool IsGameCardInserted(this FileSystemClient fs)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
if (rc.IsFailure()) throw new LibHacException("Abort");
rc = deviceOperator.IsGameCardInserted(out bool isInserted);
if (rc.IsFailure()) throw new LibHacException("Abort");
return isInserted;
}
public static long GetGameCardSizeBytes(GameCardSize size)
{
switch (size)
@ -74,40 +23,6 @@ namespace LibHac.Fs
{
return (long)page << 9;
}
private class GameCardCommonMountNameGenerator : ICommonMountNameGenerator
{
private GameCardHandle Handle { get; }
private GameCardPartition PartitionId { get; }
public GameCardCommonMountNameGenerator(GameCardHandle handle, GameCardPartition partitionId)
{
Handle = handle;
PartitionId = partitionId;
}
public Result Generate(Span<byte> nameBuffer)
{
char letter = GetPartitionMountLetter(PartitionId);
string mountName = $"@Gc{letter}{Handle.Value:x8}";
new U8Span(mountName).Value.CopyTo(nameBuffer);
return Result.Success;
}
private static char GetPartitionMountLetter(GameCardPartition partition)
{
switch (partition)
{
case GameCardPartition.Update: return 'U';
case GameCardPartition.Normal: return 'N';
case GameCardPartition.Secure: return 'S';
default:
throw new ArgumentOutOfRangeException(nameof(partition), partition, null);
}
}
}
}
public enum GameCardSize

View File

@ -4,6 +4,6 @@ namespace LibHac.Fs
{
public interface ICommonMountNameGenerator
{
Result Generate(Span<byte> nameBuffer);
Result GenerateCommonMountName(Span<byte> nameBuffer);
}
}

View File

@ -6,7 +6,7 @@ namespace LibHac.Fs
/// <summary>
/// Provides an interface for accessing a file system. <c>/</c> is used as the path delimiter.
/// </summary>
public interface IFileSystem
public interface IFileSystem : IDisposable
{
/// <summary>
/// Creates or overwrites a file at the specified path.

View File

@ -97,6 +97,8 @@
public static Result InvalidMountName => new Result(ModuleFs, 6065);
public static Result ExtensionSizeTooLarge => new Result(ModuleFs, 6066);
public static Result ExtensionSizeInvalid => new Result(ModuleFs, 6067);
public static Result ReadOldSaveDataInfoReader => new Result(ModuleFs, 6068);
public static Result InvalidSaveDataSpaceId => new Result(ModuleFs, 6082);
public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
public static Result FileExtensionWithoutOpenModeAllowAppend => new Result(ModuleFs, 6201);
@ -137,5 +139,6 @@
public static Result SubStorageNotInitialized => new Result(ModuleFs, 6902);
public static Result MountNameNotFound => new Result(ModuleFs, 6905);
public static Result SaveDataIsExtending => new Result(ModuleFs, 6906);
}
}

View File

@ -12,6 +12,7 @@
public static ResultRange FatFsCorrupted => new ResultRange(ResultFs.ModuleFs, 4681, 4699);
public static ResultRange HostFsCorrupted => new ResultRange(ResultFs.ModuleFs, 4701, 4719);
public static ResultRange FileTableCorrupted => new ResultRange(ResultFs.ModuleFs, 4721, 4739);
public static ResultRange Range4771To4779 => new ResultRange(ResultFs.ModuleFs, 4771, 4779);
public static ResultRange Range4811To4819 => new ResultRange(ResultFs.ModuleFs, 4811, 4819);
public static ResultRange UnexpectedFailure => new ResultRange(ResultFs.ModuleFs, 5000, 5999);

View File

@ -1,108 +0,0 @@
using LibHac.Common;
using LibHac.FsService;
namespace LibHac.Fs
{
public static class SaveData
{
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId)
{
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.Zero);
}
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName,
SaveDataSpaceId spaceId, ulong saveDataId, UserId userId)
{
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
SaveDataAttribute attribute = default;
attribute.UserId = userId;
attribute.SaveDataId = saveDataId;
rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, spaceId, ref attribute);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, fileSystem);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId,
ulong saveDataId, UserId userId, ulong ownerId, long size, long journalSize, uint flags)
{
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute
{
UserId = userId,
SaveDataId = saveDataId
};
var createInfo = new SaveDataCreateInfo
{
Size = size,
JournalSize = journalSize,
BlockSize = 0x4000,
OwnerId = ownerId,
Flags = flags,
SpaceId = spaceId
};
return fsProxy.CreateSaveDataFileSystemBySystemSaveDataId(ref attribute, ref createInfo);
},
() => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}, userid: 0x{userId.Id.High:X16}{userId.Id.Low:X16}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{flags:X8}");
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId,
ulong ownerId, long size, long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, ownerId, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, 0, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, 0, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
ulong ownerId, long size, long journalSize, uint flags)
{
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
}
public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId)
{
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.DeleteSaveDataFileSystem(saveDataId);
},
() => $", savedataid: 0x{saveDataId:X}");
}
public static Result DisableAutoSaveDataCreation(this FileSystemClient fsClient)
{
IFileSystemProxy fsProxy = fsClient.GetFileSystemProxyServiceObject();
return fsProxy.DisableAutoSaveDataCreation();
}
}
}

View File

@ -7,14 +7,58 @@ using LibHac.Ncm;
namespace LibHac.Fs
{
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
public struct SaveDataAttribute
public struct SaveDataAttribute : IEquatable<SaveDataAttribute>, IComparable<SaveDataAttribute>
{
[FieldOffset(0x00)] public ulong TitleId;
[FieldOffset(0x00)] public TitleId TitleId;
[FieldOffset(0x08)] public UserId UserId;
[FieldOffset(0x18)] public ulong SaveDataId;
[FieldOffset(0x20)] public SaveDataType Type;
[FieldOffset(0x21)] public byte Rank;
[FieldOffset(0x22)] public short Index;
public override bool Equals(object obj)
{
return obj is SaveDataAttribute attribute && Equals(attribute);
}
public bool Equals(SaveDataAttribute other)
{
return TitleId == other.TitleId &&
Type == other.Type &&
UserId.Equals(other.UserId) &&
SaveDataId == other.SaveDataId &&
Rank == other.Rank &&
Index == other.Index;
}
public override int GetHashCode()
{
// ReSharper disable NonReadonlyMemberInGetHashCode
int hashCode = 487790375;
hashCode = hashCode * -1521134295 + TitleId.GetHashCode();
hashCode = hashCode * -1521134295 + Type.GetHashCode();
hashCode = hashCode * -1521134295 + UserId.GetHashCode();
hashCode = hashCode * -1521134295 + SaveDataId.GetHashCode();
hashCode = hashCode * -1521134295 + Rank.GetHashCode();
hashCode = hashCode * -1521134295 + Index.GetHashCode();
return hashCode;
// ReSharper restore NonReadonlyMemberInGetHashCode
}
public int CompareTo(SaveDataAttribute other)
{
int titleIdComparison = TitleId.CompareTo(other.TitleId);
if (titleIdComparison != 0) return titleIdComparison;
int typeComparison = Type.CompareTo(other.Type);
if (typeComparison != 0) return typeComparison;
int userIdComparison = UserId.CompareTo(other.UserId);
if (userIdComparison != 0) return userIdComparison;
int saveDataIdComparison = SaveDataId.CompareTo(other.SaveDataId);
if (saveDataIdComparison != 0) return saveDataIdComparison;
int rankComparison = Rank.CompareTo(other.Rank);
if (rankComparison != 0) return rankComparison;
return Index.CompareTo(other.Index);
}
}
[StructLayout(LayoutKind.Explicit, Size = 0x48)]
@ -27,37 +71,13 @@ namespace LibHac.Fs
[FieldOffset(0x04)] public bool FilterByIndex;
[FieldOffset(0x05)] public byte Rank;
[FieldOffset(0x08)] public TitleId TitleID;
[FieldOffset(0x08)] public TitleId TitleId;
[FieldOffset(0x10)] public UserId UserId;
[FieldOffset(0x20)] public ulong SaveDataId;
[FieldOffset(0x28)] public SaveDataType SaveDataType;
[FieldOffset(0x2A)] public short Index;
}
[StructLayout(LayoutKind.Explicit, Size = 0x50)]
public struct SaveDataFilterInternal
{
[FieldOffset(0x00)] public bool FilterBySaveDataSpaceId;
[FieldOffset(0x01)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x08)] public bool FilterByTitleId;
[FieldOffset(0x10)] public TitleId TitleID;
[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;
}
[StructLayout(LayoutKind.Explicit, Size = HashLength)]
public struct HashSalt
{
@ -68,6 +88,13 @@ namespace LibHac.Fs
public Span<byte> Hash => SpanHelpers.CreateSpan(ref _hashStart, HashLength);
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct OptionalHashSalt
{
public bool IsSet;
public HashSalt HashSalt;
}
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
public struct SaveMetaCreateInfo
{
@ -81,9 +108,24 @@ namespace LibHac.Fs
[FieldOffset(0x00)] public long Size;
[FieldOffset(0x08)] public long JournalSize;
[FieldOffset(0x10)] public ulong BlockSize;
[FieldOffset(0x18)] public ulong OwnerId;
[FieldOffset(0x18)] public TitleId OwnerId;
[FieldOffset(0x20)] public uint Flags;
[FieldOffset(0x24)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x25)] public bool Field25;
}
[StructLayout(LayoutKind.Explicit, Size = 0x60)]
public struct SaveDataInfo
{
[FieldOffset(0x00)] public ulong SaveDataId;
[FieldOffset(0x08)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x09)] public SaveDataType Type;
[FieldOffset(0x10)] public UserId UserId;
[FieldOffset(0x20)] public ulong SaveDataIdFromKey;
[FieldOffset(0x28)] public TitleId TitleId;
[FieldOffset(0x30)] public long Size;
[FieldOffset(0x38)] public short Index;
[FieldOffset(0x3A)] public byte Rank;
[FieldOffset(0x3B)] public SaveDataState State;
}
}

View File

@ -2,7 +2,7 @@
using LibHac.Common;
using LibHac.FsService;
namespace LibHac.Fs
namespace LibHac.Fs.Shim
{
public static class ContentStorage
{
@ -54,7 +54,7 @@ namespace LibHac.Fs
StorageId = storageId;
}
public Result Generate(Span<byte> nameBuffer)
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
U8String mountName = GetContentStorageMountName(StorageId);

View File

@ -2,7 +2,7 @@
using LibHac.Common;
using LibHac.FsService;
namespace LibHac.Fs
namespace LibHac.Fs.Shim
{
public static class CustomStorage
{

View File

@ -0,0 +1,92 @@
using System;
using LibHac.Common;
using LibHac.FsService;
namespace LibHac.Fs.Shim
{
public static class GameCard
{
public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle)
{
handle = default;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
if (rc.IsFailure()) return rc;
return deviceOperator.GetGameCardHandle(out handle);
}
public static bool IsGameCardInserted(this FileSystemClient fs)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
if (rc.IsFailure()) throw new LibHacException("Abort");
rc = deviceOperator.IsGameCardInserted(out bool isInserted);
if (rc.IsFailure()) throw new LibHacException("Abort");
return isInserted;
}
public static Result OpenGameCardPartition(this FileSystemClient fs, out IStorage storage,
GameCardHandle handle, GameCardPartitionRaw partitionType)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.OpenGameCardStorage(out storage, handle, partitionType);
}
public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle,
GameCardPartition partitionId)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenGameCardFileSystem(out IFileSystem cardFs, handle, partitionId);
if (rc.IsFailure()) return rc;
var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId);
return fs.Register(mountName, cardFs, mountNameGenerator);
}
private class GameCardCommonMountNameGenerator : ICommonMountNameGenerator
{
private GameCardHandle Handle { get; }
private GameCardPartition PartitionId { get; }
public GameCardCommonMountNameGenerator(GameCardHandle handle, GameCardPartition partitionId)
{
Handle = handle;
PartitionId = partitionId;
}
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
char letter = GetPartitionMountLetter(PartitionId);
string mountName = $"@Gc{letter}{Handle.Value:x8}";
new U8Span(mountName).Value.CopyTo(nameBuffer);
return Result.Success;
}
private static char GetPartitionMountLetter(GameCardPartition partition)
{
switch (partition)
{
case GameCardPartition.Update: return 'U';
case GameCardPartition.Normal: return 'N';
case GameCardPartition.Secure: return 'S';
default:
throw new ArgumentOutOfRangeException(nameof(partition), partition, null);
}
}
}
}
}

View File

@ -3,12 +3,13 @@ using LibHac.FsService;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Spl;
using FsRightsId = LibHac.Fs.RightsId;
namespace LibHac.Fs
namespace LibHac.Fs.Shim
{
public static class ExternalKeys
public static class RightsId
{
public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, TitleId programId,
public static Result GetRightsId(this FileSystemClient fs, out FsRightsId rightsId, TitleId programId,
StorageId storageId)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
@ -16,7 +17,7 @@ namespace LibHac.Fs
return fsProxy.GetRightsId(out rightsId, programId, storageId);
}
public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, U8Span path)
public static Result GetRightsId(this FileSystemClient fs, out FsRightsId rightsId, U8Span path)
{
rightsId = default;
@ -28,7 +29,7 @@ namespace LibHac.Fs
return fsProxy.GetRightsIdByPath(out rightsId, ref fsPath);
}
public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, out byte keyGeneration, U8Span path)
public static Result GetRightsId(this FileSystemClient fs, out FsRightsId rightsId, out byte keyGeneration, U8Span path)
{
rightsId = default;
keyGeneration = default;
@ -41,14 +42,14 @@ namespace LibHac.Fs
return fsProxy.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, ref fsPath);
}
public static Result RegisterExternalKey(this FileSystemClient fs, ref RightsId rightsId, ref AccessKey key)
public static Result RegisterExternalKey(this FileSystemClient fs, ref FsRightsId rightsId, ref AccessKey key)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.RegisterExternalKey(ref rightsId, ref key);
}
public static Result UnregisterExternalKey(this FileSystemClient fs, ref RightsId rightsId)
public static Result UnregisterExternalKey(this FileSystemClient fs, ref FsRightsId rightsId)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();

View File

@ -0,0 +1,91 @@
using System;
using LibHac.Common;
using LibHac.FsService;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
namespace LibHac.Fs.Shim
{
public static class SaveData
{
public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, TitleId titleId, UserId userId)
{
Result rc;
if (fs.IsEnabledAccessLog(LocalAccessLogMode.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, false, 0);
TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{titleId}, userid: 0x{userId}");
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, false, 0);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(LocalAccessLogMode.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return rc;
}
public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName, TitleId titleId, UserId userId)
{
Result rc;
if (fs.IsEnabledAccessLog(LocalAccessLogMode.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, true, 0);
TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{titleId}, userid: 0x{userId}");
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, false, 0);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(LocalAccessLogMode.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return rc;
}
private static Result MountSaveDataImpl(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId,
TitleId titleId, UserId userId, SaveDataType type, bool openReadOnly, short index)
{
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
SaveDataAttribute attribute = default;
attribute.TitleId = titleId;
attribute.UserId = userId;
attribute.Type = type;
attribute.Index = index;
IFileSystem saveFs;
if (openReadOnly)
{
rc = fsProxy.OpenReadOnlySaveDataFileSystem(out saveFs, spaceId, ref attribute);
}
else
{
rc = fsProxy.OpenSaveDataFileSystem(out saveFs, spaceId, ref attribute);
}
if (rc.IsFailure()) return rc;
return fs.Register(mountName, saveFs);
}
}
}

View File

@ -0,0 +1,292 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.FsService;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
namespace LibHac.Fs.Shim
{
public static class SaveDataManagement
{
public static Result CreateSaveData(this FileSystemClient fs, TitleId titleId, UserId userId, TitleId ownerId,
long size, long journalSize, uint flags)
{
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute
{
TitleId = titleId,
UserId = userId,
Type = SaveDataType.SaveData
};
var createInfo = new SaveDataCreateInfo
{
Size = size,
JournalSize = journalSize,
BlockSize = 0x4000,
OwnerId = ownerId,
Flags = flags,
SpaceId = SaveDataSpaceId.User
};
var metaInfo = new SaveMetaCreateInfo
{
Type = SaveMetaType.Thumbnail,
Size = 0x40060
};
return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo);
},
() => $", applicationid: 0x{titleId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{flags:x8}");
}
public static Result CreateSaveData(this FileSystemClient fs, TitleId titleId, UserId userId, TitleId ownerId,
long size, long journalSize, HashSalt hashSalt, uint flags)
{
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute
{
TitleId = titleId,
UserId = userId,
Type = SaveDataType.SaveData
};
var createInfo = new SaveDataCreateInfo
{
Size = size,
JournalSize = journalSize,
BlockSize = 0x4000,
OwnerId = ownerId,
Flags = flags,
SpaceId = SaveDataSpaceId.User
};
var metaInfo = new SaveMetaCreateInfo
{
Type = SaveMetaType.Thumbnail,
Size = 0x40060
};
return fsProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref createInfo, ref metaInfo, ref hashSalt);
},
() => $", applicationid: 0x{titleId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{flags:x8}");
}
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId,
ulong saveDataId, UserId userId, TitleId ownerId, long size, long journalSize, uint flags)
{
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute
{
UserId = userId,
SaveDataId = saveDataId
};
var createInfo = new SaveDataCreateInfo
{
Size = size,
JournalSize = journalSize,
BlockSize = 0x4000,
OwnerId = ownerId,
Flags = flags,
SpaceId = spaceId
};
return fsProxy.CreateSaveDataFileSystemBySystemSaveDataId(ref attribute, ref createInfo);
},
() => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}, userid: 0x{userId.Id.High:X16}{userId.Id.Low:X16}, save_data_owner_id: 0x{ownerId.Value:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{flags:X8}");
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId,
TitleId ownerId, long size, long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, ownerId, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, TitleId.Zero, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, TitleId ownerId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, TitleId.Zero, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
TitleId ownerId, long size, long journalSize, uint flags)
{
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
}
public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId)
{
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.DeleteSaveDataFileSystem(saveDataId);
},
() => $", savedataid: 0x{saveDataId:X}");
}
public static Result DeleteSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId)
{
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId);
},
() => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}");
}
public static Result FindSaveDataWithFilter(this FileSystemClient fs, out SaveDataInfo info, SaveDataSpaceId spaceId,
ref SaveDataFilter filter)
{
info = default;
SaveDataFilter tempFilter = filter;
var tempInfo = new SaveDataInfo();
Result result = fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
tempInfo = new SaveDataInfo();
Result rc = fsProxy.FindSaveDataWithFilter(out long count, SpanHelpers.AsByteSpan(ref tempInfo),
spaceId, ref tempFilter);
if (rc.IsFailure()) return rc;
if (count == 0)
return ResultFs.TargetNotFound.Log();
return Result.Success;
},
() => $", savedataspaceid: {spaceId}");
if (result.IsSuccess())
{
info = tempInfo;
}
return result;
}
public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId)
{
var tempIterator = new SaveDataIterator();
Result result = fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader reader, spaceId);
if (rc.IsFailure()) return rc;
tempIterator = new SaveDataIterator(fs, reader);
return Result.Success;
},
() => $", savedataspaceid: {spaceId}");
iterator = result.IsSuccess() ? tempIterator : default;
return result;
}
public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId, ref SaveDataFilter filter)
{
var tempIterator = new SaveDataIterator();
SaveDataFilter tempFilter = filter;
Result result = fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() =>
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader reader, spaceId, ref tempFilter);
if (rc.IsFailure()) return rc;
tempIterator = new SaveDataIterator(fs, reader);
return Result.Success;
},
() => $", savedataspaceid: {spaceId}");
iterator = result.IsSuccess() ? tempIterator : default;
return result;
}
public static Result DisableAutoSaveDataCreation(this FileSystemClient fsClient)
{
IFileSystemProxy fsProxy = fsClient.GetFileSystemProxyServiceObject();
return fsProxy.DisableAutoSaveDataCreation();
}
}
public struct SaveDataIterator : IDisposable
{
private FileSystemClient FsClient { get; }
private ISaveDataInfoReader Reader { get; }
internal SaveDataIterator(FileSystemClient fsClient, ISaveDataInfoReader reader)
{
FsClient = fsClient;
Reader = reader;
}
public Result ReadSaveDataInfo(out long readCount, Span<SaveDataInfo> buffer)
{
Result rc;
Span<byte> byteBuffer = MemoryMarshal.Cast<SaveDataInfo, byte>(buffer);
if (FsClient.IsEnabledAccessLog(LocalAccessLogMode.System))
{
TimeSpan startTime = FsClient.Time.GetCurrent();
rc = Reader.ReadSaveDataInfo(out readCount, byteBuffer);
TimeSpan endTime = FsClient.Time.GetCurrent();
FsClient.OutputAccessLog(rc, startTime, endTime, $", size: {buffer.Length}");
}
else
{
rc = Reader.ReadSaveDataInfo(out readCount, byteBuffer);
}
return rc;
}
public void Dispose()
{
Reader?.Dispose();
}
}
}

View File

@ -0,0 +1,31 @@
using LibHac.Common;
using LibHac.FsService;
namespace LibHac.Fs.Shim
{
public static class SystemSaveData
{
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId)
{
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.Zero);
}
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName,
SaveDataSpaceId spaceId, ulong saveDataId, UserId userId)
{
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
SaveDataAttribute attribute = default;
attribute.UserId = userId;
attribute.SaveDataId = saveDataId;
rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, spaceId, ref attribute);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, fileSystem);
}
}
}

View File

@ -5,7 +5,7 @@ using LibHac.Common;
namespace LibHac.Fs
{
[DebuggerDisplay("{ToString(),nq}")]
[DebuggerDisplay("0x{ToString(),nq}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct UserId : IEquatable<UserId>, IComparable<UserId>, IComparable
{
@ -25,7 +25,7 @@ namespace LibHac.Fs
public override string ToString()
{
return $"0x{Id.High:x8}{Id.Low:x8}";
return $"{Id.High:X16}{Id.Low:X16}";
}
public bool Equals(UserId other) => Id == other.Id;

View File

@ -38,7 +38,8 @@ namespace LibHac.FsService.Creators
switch (entryType)
{
case DirectoryEntryType.Directory:
if (!allowDirectorySaveData) return ResultFs.InvalidSaveDataEntryType.Log();
// Actual FS does this check
// if (!allowDirectorySaveData) return ResultFs.InvalidSaveDataEntryType.Log();
var subDirFs = new SubdirectoryFileSystem(sourceFileSystem, saveDataPath);
@ -55,8 +56,8 @@ namespace LibHac.FsService.Creators
rc = sourceFileSystem.OpenFile(out IFile saveDataFile, saveDataPath, OpenMode.ReadWrite);
if (rc.IsFailure()) return rc;
var saveDataStorage = new FileStorage(saveDataFile);
fileSystem = new SaveDataFileSystem(Keyset, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, true);
var saveDataStorage = new DisposingFileStorage(saveDataFile);
fileSystem = new SaveDataFileSystem(Keyset, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false);
// Todo: ISaveDataExtraDataAccessor

View File

@ -1,7 +1,10 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.Kvdb;
using LibHac.Ncm;
using LibHac.Spl;
@ -14,6 +17,7 @@ namespace LibHac.FsService
/// <summary>The client instance to be used for internal operations like save indexer access.</summary>
// ReSharper disable once UnusedAutoPropertyAccessor.Local
private FileSystemClient FsClient { get; }
private FileSystemServer FsServer { get; }
public long CurrentProcess { get; private set; }
@ -22,12 +26,11 @@ namespace LibHac.FsService
public FsPath SaveDataRootPath { get; } = default;
public bool AutoCreateSaveData { get; private set; }
private const ulong SaveIndexerId = 0x8000000000000000;
internal FileSystemProxy(FileSystemProxyCore fsProxyCore, FileSystemClient fsClient)
internal FileSystemProxy(FileSystemProxyCore fsProxyCore, FileSystemClient fsClient, FileSystemServer fsServer)
{
FsProxyCore = fsProxyCore;
FsClient = fsClient;
FsServer = fsServer;
CurrentProcess = -1;
SaveDataSize = 0x2000000;
@ -104,17 +107,148 @@ namespace LibHac.FsService
public Result DeleteSaveDataFileSystem(ulong saveDataId)
{
throw new NotImplementedException();
return DeleteSaveDataFileSystemImpl(SaveDataSpaceId.System, saveDataId);
}
private Result DeleteSaveDataFileSystemImpl(SaveDataSpaceId spaceId, ulong saveDataId)
{
SaveDataIndexerReader reader = default;
try
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, spaceId);
if (rc.IsFailure()) return rc;
if (saveDataId == FileSystemServer.SaveIndexerId)
{
// missing: This save can only be deleted by the FS process itself
}
else
{
if (spaceId != SaveDataSpaceId.ProperSystem && spaceId != SaveDataSpaceId.Safe)
{
rc = reader.Indexer.GetBySaveDataId(out SaveDataIndexerValue value, saveDataId);
if (rc.IsFailure()) return rc;
spaceId = value.SpaceId;
}
rc = reader.Indexer.GetKey(out SaveDataAttribute key, saveDataId);
if (rc.IsFailure()) return rc;
if (key.Type == SaveDataType.SystemSaveData || key.Type == SaveDataType.BcatSystemStorage)
{
// Check if permissions allow deleting system save data
}
else
{
// Check if permissions allow deleting save data
}
reader.Indexer.SetState(saveDataId, SaveDataState.Creating);
if (rc.IsFailure()) return rc;
reader.Indexer.Commit();
if (rc.IsFailure()) return rc;
}
rc = DeleteSaveDataFileSystemImpl2(spaceId, saveDataId);
if (rc.IsFailure()) return rc;
if (saveDataId != FileSystemServer.SaveIndexerId)
{
rc = reader.Indexer.Delete(saveDataId);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Commit();
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
finally
{
reader.Dispose();
}
}
private Result DeleteSaveDataFileSystemImpl2(SaveDataSpaceId spaceId, ulong saveDataId)
{
// missing: Check extra data flags for this value. Bit 3
bool doSecureDelete = false;
Result rc = FsProxyCore.DeleteSaveDataMetaFiles(saveDataId, spaceId);
if (rc.IsFailure() && rc != ResultFs.PathNotFound)
return rc;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
rc = FsProxyCore.DeleteSaveDataFileSystem(spaceId, saveDataId, doSecureDelete);
if (rc.IsFailure() && rc != ResultFs.PathNotFound)
return rc;
return Result.Success;
}
public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
return DeleteSaveDataFileSystemBySaveDataSpaceIdImpl(spaceId, saveDataId);
}
private Result DeleteSaveDataFileSystemBySaveDataSpaceIdImpl(SaveDataSpaceId spaceId, ulong saveDataId)
{
if (saveDataId != FileSystemServer.SaveIndexerId)
{
SaveDataIndexerReader reader = default;
try
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, spaceId);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.GetBySaveDataId(out SaveDataIndexerValue value, saveDataId);
if (rc.IsFailure()) return rc;
if (value.SpaceId != GetSpaceIdForIndexer(spaceId))
return ResultFs.TargetNotFound.Log();
}
finally
{
reader.Dispose();
}
}
return DeleteSaveDataFileSystemImpl(spaceId, saveDataId);
}
private Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute)
{
info = default;
SaveDataIndexerReader reader = default;
try
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, spaceId);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Get(out SaveDataIndexerValue value, ref attribute);
if (rc.IsFailure()) return rc;
SaveDataIndexer.GetSaveDataInfo(out info, ref attribute, ref value);
return Result.Success;
}
finally
{
reader.Dispose();
}
}
public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute attribute)
{
throw new NotImplementedException();
Result rs = GetSaveDataInfo(out SaveDataInfo info, spaceId, ref attribute);
if (rs.IsFailure()) return rs;
return DeleteSaveDataFileSystemBySaveDataSpaceIdImpl(spaceId, info.SaveDataId);
}
public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId)
@ -122,21 +256,210 @@ namespace LibHac.FsService
throw new NotImplementedException();
}
private Result CreateSaveDataFileSystemImpl(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo, ref OptionalHashSalt hashSalt, bool something)
{
ulong saveDataId = 0;
bool isDeleteNeeded = false;
Result rc;
SaveDataIndexerReader reader = default;
try
{
if (attribute.SaveDataId == FileSystemServer.SaveIndexerId)
{
saveDataId = FileSystemServer.SaveIndexerId;
rc = FsProxyCore.DoesSaveDataExist(out bool saveExists, createInfo.SpaceId, saveDataId);
if (rc.IsSuccess() && saveExists)
{
return ResultFs.PathAlreadyExists.Log();
}
isDeleteNeeded = true;
}
else
{
rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, createInfo.SpaceId);
if (rc.IsFailure()) return rc;
SaveDataAttribute indexerKey = attribute;
if (attribute.SaveDataId != 0 || attribute.UserId == UserId.Zero)
{
saveDataId = attribute.SaveDataId;
rc = reader.Indexer.AddSystemSaveData(ref indexerKey);
}
else
{
if (attribute.Type != SaveDataType.SystemSaveData &&
attribute.Type != SaveDataType.BcatSystemStorage)
{
if (reader.Indexer.IsFull())
{
return ResultKvdb.TooLargeKeyOrDbFull.Log();
}
}
rc = reader.Indexer.Add(out saveDataId, ref indexerKey);
}
if (rc == ResultFs.SaveDataPathAlreadyExists)
{
return ResultFs.PathAlreadyExists.LogConverted(rc);
}
isDeleteNeeded = true;
rc = reader.Indexer.SetState(saveDataId, SaveDataState.Creating);
if (rc.IsFailure()) return rc;
SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(createInfo.SpaceId);
rc = reader.Indexer.SetSpaceId(saveDataId, indexerSpaceId);
if (rc.IsFailure()) return rc;
// todo: calculate size
long size = 0;
rc = reader.Indexer.SetSize(saveDataId, size);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Commit();
if (rc.IsFailure()) return rc;
}
rc = FsProxyCore.CreateSaveDataFileSystem(saveDataId, ref attribute, ref createInfo, SaveDataRootPath,
hashSalt, false);
if (rc.IsFailure())
{
if (rc != ResultFs.PathAlreadyExists) return rc;
rc = DeleteSaveDataFileSystemImpl2(createInfo.SpaceId, saveDataId);
if (rc.IsFailure()) return rc;
rc = FsProxyCore.CreateSaveDataFileSystem(saveDataId, ref attribute, ref createInfo, SaveDataRootPath,
hashSalt, false);
if (rc.IsFailure()) return rc;
}
if (metaCreateInfo.Type != SaveMetaType.None)
{
rc = FsProxyCore.CreateSaveDataMetaFile(saveDataId, createInfo.SpaceId, metaCreateInfo.Type,
metaCreateInfo.Size);
if (rc.IsFailure()) return rc;
if (metaCreateInfo.Type == SaveMetaType.Thumbnail)
{
rc = FsProxyCore.OpenSaveDataMetaFile(out IFile metaFile, saveDataId, createInfo.SpaceId,
metaCreateInfo.Type);
using (metaFile)
{
if (rc.IsFailure()) return rc;
ReadOnlySpan<byte> metaFileData = stackalloc byte[0x20];
rc = metaFile.Write(0, metaFileData, WriteOption.Flush);
if (rc.IsFailure()) return rc;
}
}
}
if (attribute.SaveDataId == FileSystemServer.SaveIndexerId || something)
{
isDeleteNeeded = false;
return Result.Success;
}
rc = reader.Indexer.SetState(saveDataId, SaveDataState.Normal);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Commit();
if (rc.IsFailure()) return rc;
isDeleteNeeded = false;
return Result.Success;
}
finally
{
// Revert changes if an error happened in the middle of creation
if (isDeleteNeeded)
{
DeleteSaveDataFileSystemImpl2(createInfo.SpaceId, saveDataId);
if (reader.IsInitialized && saveDataId != FileSystemServer.SaveIndexerId)
{
rc = reader.Indexer.GetBySaveDataId(out SaveDataIndexerValue value, saveDataId);
if (rc.IsSuccess() && value.SpaceId == createInfo.SpaceId)
{
reader.Indexer.Delete(saveDataId);
reader.Indexer.Commit();
}
}
}
reader.Dispose();
}
}
public Result CreateSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo)
{
throw new NotImplementedException();
OptionalHashSalt hashSalt = default;
return CreateUserSaveDataFileSystem(ref attribute, ref createInfo, ref metaCreateInfo, ref hashSalt);
}
public Result CreateSaveDataFileSystemWithHashSalt(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo, ref HashSalt hashSalt)
{
throw new NotImplementedException();
var hashSaltCopy = new OptionalHashSalt
{
IsSet = true,
HashSalt = hashSalt
};
return CreateUserSaveDataFileSystem(ref attribute, ref createInfo, ref metaCreateInfo, ref hashSaltCopy);
}
private Result CreateUserSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo, ref OptionalHashSalt hashSalt)
{
return CreateSaveDataFileSystemImpl(ref attribute, ref createInfo, ref metaCreateInfo, ref hashSalt, false);
}
public Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo)
{
throw new NotImplementedException();
if (!IsSystemSaveDataId(attribute.SaveDataId))
return ResultFs.InvalidArgument.Log();
SaveDataCreateInfo newCreateInfo = createInfo;
if (createInfo.OwnerId == TitleId.Zero)
{
// Assign the current program's ID
throw new NotImplementedException();
}
// Missing permission checks
if (attribute.Type == SaveDataType.BcatSystemStorage)
{
newCreateInfo.OwnerId = SystemTitleIds.Bcat;
}
SaveMetaCreateInfo metaCreateInfo = default;
OptionalHashSalt optionalHashSalt = default;
return CreateSaveDataFileSystemImpl(ref attribute, ref newCreateInfo, ref metaCreateInfo,
ref optionalHashSalt, false);
}
public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize)
@ -147,6 +470,9 @@ namespace LibHac.FsService
private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId,
SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData)
{
fileSystem = default;
saveDataId = default;
bool hasFixedId = attribute.SaveDataId != 0 && attribute.UserId == UserId.Zero;
if (hasFixedId)
@ -155,7 +481,23 @@ namespace LibHac.FsService
}
else
{
throw new NotImplementedException();
SaveDataAttribute indexerKey = attribute;
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out SaveDataIndexerReader tmpReader, spaceId);
using SaveDataIndexerReader reader = tmpReader;
if (rc.IsFailure()) return rc;
reader.Indexer.Get(out SaveDataIndexerValue indexerValue, ref indexerKey);
SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(spaceId);
if (indexerValue.SpaceId != indexerSpaceId)
return ResultFs.TargetNotFound.Log();
if (indexerValue.State == SaveDataState.Extending)
return ResultFs.SaveDataIsExtending.Log();
saveDataId = indexerValue.SaveDataId;
}
Result saveFsResult = FsProxyCore.OpenSaveDataFileSystem(out fileSystem, spaceId, saveDataId,
@ -165,9 +507,8 @@ namespace LibHac.FsService
if (saveFsResult != ResultFs.PathNotFound && saveFsResult != ResultFs.TargetNotFound) return saveFsResult;
if (saveDataId != SaveIndexerId)
if (saveDataId != FileSystemServer.SaveIndexerId)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (hasFixedId)
{
// todo: remove save indexer entry
@ -177,15 +518,61 @@ namespace LibHac.FsService
return ResultFs.TargetNotFound;
}
private Result OpenSaveDataFileSystem3(out IFileSystem fileSystem, SaveDataSpaceId spaceId,
ref SaveDataAttribute attribute, bool openReadOnly)
{
// Missing check if the open limit has been hit
Result rc = OpenSaveDataFileSystemImpl(out fileSystem, out _, spaceId, ref attribute, openReadOnly, true);
// Missing permission check based on the save's owner ID,
// speed emulation storage type wrapper, and FileSystemInterfaceAdapter
return rc;
}
private Result OpenSaveDataFileSystem2(out IFileSystem fileSystem, SaveDataSpaceId spaceId,
ref SaveDataAttribute attribute, bool openReadOnly)
{
fileSystem = default;
// Missing permission checks
SaveDataAttribute attributeCopy;
if (attribute.TitleId == TitleId.Zero)
{
throw new NotImplementedException();
}
else
{
attributeCopy = attribute;
}
SaveDataSpaceId actualSpaceId;
if (attributeCopy.Type == SaveDataType.CacheStorage)
{
// Check whether the save is on the SD card or the BIS
throw new NotImplementedException();
}
else
{
actualSpaceId = spaceId;
}
return OpenSaveDataFileSystem3(out fileSystem, actualSpaceId, ref attributeCopy, openReadOnly);
}
public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute)
{
throw new NotImplementedException();
return OpenSaveDataFileSystem2(out fileSystem, spaceId, ref attribute, false);
}
public Result OpenReadOnlySaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId,
ref SaveDataAttribute attribute)
{
throw new NotImplementedException();
return OpenSaveDataFileSystem2(out fileSystem, spaceId, ref attribute, true);
}
public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId,
@ -316,24 +703,129 @@ namespace LibHac.FsService
public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader)
{
throw new NotImplementedException();
infoReader = default;
// Missing permission check
SaveDataIndexerReader indexReader = default;
try
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, SaveDataSpaceId.System);
if (rc.IsFailure()) return rc;
return indexReader.Indexer.OpenSaveDataInfoReader(out infoReader);
}
finally
{
indexReader.Dispose();
}
}
public Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId)
{
throw new NotImplementedException();
infoReader = default;
// Missing permission check
SaveDataIndexerReader indexReader = default;
try
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
if (rc.IsFailure()) return rc;
rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
if (rc.IsFailure()) return rc;
var filter = new SaveDataFilterInternal
{
FilterBySaveDataSpaceId = true,
SpaceId = GetSpaceIdForIndexer(spaceId)
};
infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter);
return Result.Success;
}
finally
{
indexReader.Dispose();
}
}
public Result OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId,
ref SaveDataFilter filter)
{
throw new NotImplementedException();
infoReader = default;
// Missing permission check
SaveDataIndexerReader indexReader = default;
try
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
if (rc.IsFailure()) return rc;
rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
if (rc.IsFailure()) return rc;
var filterInternal = new SaveDataFilterInternal(ref filter, spaceId);
infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filterInternal);
return Result.Success;
}
finally
{
indexReader.Dispose();
}
}
public Result FindSaveDataWithFilter(out long count, Span<byte> saveDataInfoBuffer, SaveDataSpaceId spaceId,
ref SaveDataFilter filter)
{
throw new NotImplementedException();
count = default;
if (saveDataInfoBuffer.Length != Unsafe.SizeOf<SaveDataInfo>())
{
return ResultFs.InvalidArgument.Log();
}
// Missing permission check
var internalFilter = new SaveDataFilterInternal(ref filter, GetSpaceIdForIndexer(spaceId));
ref SaveDataInfo saveDataInfo = ref Unsafe.As<byte, SaveDataInfo>(ref saveDataInfoBuffer[0]);
return FindSaveDataWithFilterImpl(out count, out saveDataInfo, spaceId, ref internalFilter);
}
private Result FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, SaveDataSpaceId spaceId,
ref SaveDataFilterInternal filter)
{
count = default;
info = default;
SaveDataIndexerReader indexReader = default;
try
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
if (rc.IsFailure()) return rc;
rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
if (rc.IsFailure()) return rc;
var infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter);
return infoReader.ReadSaveDataInfo(out count, SpanHelpers.AsByteSpan(ref info));
}
finally
{
indexReader.Dispose();
}
}
public Result OpenSaveDataInternalStorageFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId)
@ -585,5 +1077,12 @@ namespace LibHac.FsService
{
return (long)id < 0;
}
private static SaveDataSpaceId GetSpaceIdForIndexer(SaveDataSpaceId spaceId)
{
return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.Safe
? SaveDataSpaceId.System
: spaceId;
}
}
}

View File

@ -1,9 +1,12 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.FsService.Creators;
using LibHac.Spl;
using RightsId = LibHac.Fs.RightsId;
namespace LibHac.FsService
{
@ -187,6 +190,31 @@ namespace LibHac.FsService
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);
if (rc.IsFailure())
{
if (rc == ResultFs.PathNotFound)
{
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)
{
@ -200,14 +228,8 @@ namespace LibHac.FsService
if (allowDirectorySaveData)
{
try
{
saveDirFs.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId));
}
catch (HorizonResultException ex)
{
return ex.ResultValue;
}
rc = saveDirFs.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId));
if (rc.IsFailure()) return rc;
}
// Missing save FS cache lookup
@ -297,6 +319,96 @@ namespace LibHac.FsService
}
}
public Result OpenSaveDataMetaFile(out IFile file, ulong saveDataId, SaveDataSpaceId spaceId, SaveMetaType 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, 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}");
if (rc.IsFailure() && rc != ResultFs.PathNotFound)
return rc;
return Result.Success;
}
}
public Result CreateSaveDataMetaFile(ulong saveDataId, SaveDataSpaceId spaceId, SaveMetaType 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.ValueOutOfRange.Log();
return metaDirFs.CreateFile(metaFilePath, size, CreateFileOptions.None);
}
public Result CreateSaveDataFileSystem(ulong saveDataId, ref SaveDataAttribute attribute,
ref SaveDataCreateInfo createInfo, U8Span rootPath, OptionalHashSalt hashSalt, bool something)
{
// Use directory save data for now
Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, createInfo.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;
string saveDataPath = GetSaveDataIdPath(saveDataId);
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;

View File

@ -6,22 +6,26 @@ namespace LibHac.FsService
{
public class FileSystemServer
{
internal const ulong SaveIndexerId = 0x8000000000000000;
private FileSystemProxyCore FsProxyCore { get; }
/// <summary>The client instance to be used for internal operations like save indexer access.</summary>
private FileSystemClient FsClient { get; }
public FileSystemClient FsClient { get; }
private ITimeSpanGenerator Timer { get; }
internal SaveDataIndexerManager SaveDataIndexerManager { get; }
/// <summary>
/// Creates a new <see cref="FileSystemServer"/>.
/// </summary>
/// <param name="config">The configuration for the created <see cref="FileSystemServer"/>.</param>
public FileSystemServer(FileSystemServerConfig config)
{
if(config.FsCreators == null)
if (config.FsCreators == null)
throw new ArgumentException("FsCreators must not be null");
if(config.DeviceOperator == null)
if (config.DeviceOperator == null)
throw new ArgumentException("DeviceOperator must not be null");
ExternalKeySet externalKeySet = config.ExternalKeySet ?? new ExternalKeySet();
@ -30,6 +34,8 @@ namespace LibHac.FsService
FsProxyCore = new FileSystemProxyCore(config.FsCreators, externalKeySet, config.DeviceOperator);
FsClient = new FileSystemClient(this, timer);
Timer = timer;
SaveDataIndexerManager = new SaveDataIndexerManager(FsClient, SaveIndexerId);
}
/// <summary>
@ -52,7 +58,7 @@ namespace LibHac.FsService
public IFileSystemProxy CreateFileSystemProxyService()
{
return new FileSystemProxy(FsProxyCore, FsClient);
return new FileSystemProxy(FsProxyCore, FsClient, this);
}
}

View File

@ -0,0 +1,21 @@
using LibHac.Fs;
namespace LibHac.FsService
{
public interface ISaveDataIndexer
{
Result Commit();
Result Add(out ulong saveDataId, ref SaveDataAttribute key);
Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key);
Result AddSystemSaveData(ref SaveDataAttribute key);
bool IsFull();
Result Delete(ulong saveDataId);
Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId);
Result SetSize(ulong saveDataId, long size);
Result SetState(ulong saveDataId, SaveDataState state);
Result GetKey(out SaveDataAttribute key, ulong saveDataId);
Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId);
int GetCount();
Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader);
}
}

View File

@ -2,7 +2,7 @@
namespace LibHac.FsService
{
public interface ISaveDataInfoReader
public interface ISaveDataInfoReader : IDisposable
{
Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer);
}

View File

@ -0,0 +1,669 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.Kvdb;
using LibHac.Ncm;
namespace LibHac.FsService
{
public class SaveDataIndexer : ISaveDataIndexer
{
private const string LastIdFileName = "lastPublishedId";
private const long LastIdFileSize = 8;
private FileSystemClient FsClient { get; }
private string MountName { get; }
private ulong SaveDataId { get; }
private SaveDataSpaceId SpaceId { get; }
private KeyValueDatabase<SaveDataAttribute> KvDatabase { get; set; }
private object Locker { get; } = new object();
private bool IsInitialized { get; set; }
private bool IsKvdbLoaded { get; set; }
private ulong LastPublishedId { get; set; }
private int Version { get; set; }
private List<SaveDataInfoReader> OpenedReaders { get; } = new List<SaveDataInfoReader>();
public SaveDataIndexer(FileSystemClient fsClient, string mountName, SaveDataSpaceId spaceId, ulong saveDataId)
{
FsClient = fsClient;
MountName = mountName;
SaveDataId = saveDataId;
SpaceId = spaceId;
Version = 1;
}
public static void GetSaveDataInfo(out SaveDataInfo info, ref SaveDataAttribute key, ref SaveDataIndexerValue value)
{
info = new SaveDataInfo
{
SaveDataId = value.SaveDataId,
SpaceId = value.SpaceId,
Type = key.Type,
UserId = key.UserId,
SaveDataIdFromKey = key.SaveDataId,
TitleId = key.TitleId,
Size = value.Size,
Index = key.Index,
Rank = key.Rank,
State = value.State
};
}
public Result Commit()
{
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
var mount = new Mounter();
try
{
rc = mount.Initialize(FsClient, MountName, SpaceId, SaveDataId);
if (rc.IsFailure()) return rc;
rc = KvDatabase.WriteDatabaseToFile();
if (rc.IsFailure()) return rc;
string idFilePath = $"{MountName}:/{LastIdFileName}";
rc = FsClient.OpenFile(out FileHandle handle, idFilePath, OpenMode.Write);
if (rc.IsFailure()) return rc;
bool fileAlreadyClosed = false;
try
{
ulong lastId = LastPublishedId;
rc = FsClient.WriteFile(handle, 0, SpanHelpers.AsByteSpan(ref lastId), WriteOption.None);
if (rc.IsFailure()) return rc;
rc = FsClient.FlushFile(handle);
if (rc.IsFailure()) return rc;
FsClient.CloseFile(handle);
fileAlreadyClosed = true;
return FsClient.Commit(MountName);
}
finally
{
if (!fileAlreadyClosed)
{
FsClient.CloseFile(handle);
}
}
}
finally
{
mount.Dispose();
}
}
}
public Result Add(out ulong saveDataId, ref SaveDataAttribute key)
{
saveDataId = default;
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
SaveDataIndexerValue value = default;
rc = KvDatabase.Get(ref key, SpanHelpers.AsByteSpan(ref value));
if (rc.IsSuccess())
{
return ResultFs.SaveDataPathAlreadyExists.Log();
}
LastPublishedId++;
ulong newSaveDataId = LastPublishedId;
value = new SaveDataIndexerValue { SaveDataId = newSaveDataId };
rc = KvDatabase.Set(ref key, SpanHelpers.AsByteSpan(ref value));
if (rc.IsFailure())
{
LastPublishedId--;
return rc;
}
rc = AdjustOpenedInfoReaders(ref key);
if (rc.IsFailure()) return rc;
saveDataId = newSaveDataId;
return Result.Success;
}
}
public Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key)
{
value = default;
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
rc = KvDatabase.Get(ref key, SpanHelpers.AsByteSpan(ref value));
if (rc.IsFailure())
{
return ResultFs.TargetNotFound.LogConverted(rc);
}
return Result.Success;
}
}
public Result AddSystemSaveData(ref SaveDataAttribute key)
{
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
foreach (KeyValuePair<SaveDataAttribute, byte[]> kvp in KvDatabase)
{
ref SaveDataIndexerValue value = ref Unsafe.As<byte, SaveDataIndexerValue>(ref kvp.Value[0]);
if (key.SaveDataId == value.SaveDataId)
{
return ResultFs.SaveDataPathAlreadyExists.Log();
}
}
var newValue = new SaveDataIndexerValue
{
SaveDataId = key.SaveDataId
};
rc = KvDatabase.Set(ref key, SpanHelpers.AsByteSpan(ref newValue));
if (rc.IsFailure()) return rc;
rc = AdjustOpenedInfoReaders(ref key);
if (rc.IsFailure()) return rc;
return rc;
}
}
public bool IsFull()
{
return false;
}
public Result Delete(ulong saveDataId)
{
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
if (!TryGetBySaveDataIdInternal(out SaveDataAttribute key, out _, saveDataId))
{
return ResultFs.TargetNotFound.Log();
}
rc = KvDatabase.Delete(ref key);
if (rc.IsFailure()) return rc;
return AdjustOpenedInfoReaders(ref key);
}
}
public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId)
{
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
if (!TryGetBySaveDataIdInternal(out SaveDataAttribute key, out SaveDataIndexerValue value, saveDataId))
{
return ResultFs.TargetNotFound.Log();
}
value.SpaceId = spaceId;
return KvDatabase.Set(ref key, SpanHelpers.AsByteSpan(ref value));
}
}
public Result SetSize(ulong saveDataId, long size)
{
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
if (!TryGetBySaveDataIdInternal(out SaveDataAttribute key, out SaveDataIndexerValue value, saveDataId))
{
return ResultFs.TargetNotFound.Log();
}
value.Size = size;
return KvDatabase.Set(ref key, SpanHelpers.AsByteSpan(ref value));
}
}
public Result SetState(ulong saveDataId, SaveDataState state)
{
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
if (!TryGetBySaveDataIdInternal(out SaveDataAttribute key, out SaveDataIndexerValue value, saveDataId))
{
return ResultFs.TargetNotFound.Log();
}
value.State = state;
return KvDatabase.Set(ref key, SpanHelpers.AsByteSpan(ref value));
}
}
public Result GetKey(out SaveDataAttribute key, ulong saveDataId)
{
key = default;
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
if (TryGetBySaveDataIdInternal(out key, out _, saveDataId))
{
return Result.Success;
}
return ResultFs.TargetNotFound.Log();
}
}
public Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId)
{
value = default;
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
if (TryGetBySaveDataIdInternal(out _, out value, saveDataId))
{
return Result.Success;
}
return ResultFs.TargetNotFound.Log();
}
}
public int GetCount()
{
lock (Locker)
{
return KvDatabase.Count;
}
}
public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader)
{
infoReader = default;
lock (Locker)
{
Result rc = Initialize();
if (rc.IsFailure()) return rc;
rc = EnsureKvDatabaseLoaded(false);
if (rc.IsFailure()) return rc;
var reader = new SaveDataInfoReader(this);
OpenedReaders.Add(reader);
infoReader = reader;
return Result.Success;
}
}
private bool TryGetBySaveDataIdInternal(out SaveDataAttribute key, out SaveDataIndexerValue value, ulong saveDataId)
{
foreach (KeyValuePair<SaveDataAttribute, byte[]> kvp in KvDatabase)
{
ref SaveDataIndexerValue currentValue = ref Unsafe.As<byte, SaveDataIndexerValue>(ref kvp.Value[0]);
if (currentValue.SaveDataId == saveDataId)
{
key = kvp.Key;
value = currentValue;
return true;
}
}
key = default;
value = default;
return false;
}
private Result Initialize()
{
if (IsInitialized) return Result.Success;
var mount = new Mounter();
try
{
Result rc = mount.Initialize(FsClient, MountName, SpaceId, SaveDataId);
if (rc.IsFailure()) return rc;
string dbDirectory = $"{MountName}:/";
rc = FsClient.GetEntryType(out DirectoryEntryType entryType, dbDirectory);
if (rc.IsFailure()) return rc;
if (entryType == DirectoryEntryType.File)
return ResultFs.PathNotFound.Log();
string dbArchiveFile = $"{dbDirectory}imkvdb.arc";
KvDatabase = new KeyValueDatabase<SaveDataAttribute>(FsClient, dbArchiveFile);
IsInitialized = true;
return Result.Success;
}
finally
{
mount.Dispose();
}
}
private Result EnsureKvDatabaseLoaded(bool forceLoad)
{
Debug.Assert(KvDatabase != null);
if (forceLoad)
{
IsKvdbLoaded = false;
}
else if (IsKvdbLoaded)
{
return Result.Success;
}
var mount = new Mounter();
try
{
Result rc = mount.Initialize(FsClient, MountName, SpaceId, SaveDataId);
if (rc.IsFailure()) return rc;
rc = KvDatabase.ReadDatabaseFromFile();
if (rc.IsFailure()) return rc;
bool createdNewFile = false;
string idFilePath = $"{MountName}:/{LastIdFileName}";
rc = FsClient.OpenFile(out FileHandle handle, idFilePath, OpenMode.Read);
if (rc.IsFailure())
{
if (rc != ResultFs.PathNotFound) return rc;
rc = FsClient.CreateFile(idFilePath, LastIdFileSize);
if (rc.IsFailure()) return rc;
rc = FsClient.OpenFile(out handle, idFilePath, OpenMode.Read);
if (rc.IsFailure()) return rc;
createdNewFile = true;
LastPublishedId = 0;
IsKvdbLoaded = true;
}
try
{
if (!createdNewFile)
{
ulong lastId = default;
rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref lastId));
if (rc.IsFailure()) return rc;
LastPublishedId = lastId;
IsKvdbLoaded = true;
}
return Result.Success;
}
finally
{
FsClient.CloseFile(handle);
if (createdNewFile)
{
FsClient.Commit(MountName);
}
}
}
finally
{
mount.Dispose();
}
}
private Result AdjustOpenedInfoReaders(ref SaveDataAttribute key)
{
// If a new key is added or removed during iteration of the list,
// make sure the current item of the iterator remains the same
// Todo: A more efficient way of doing this
List<SaveDataAttribute> list = KvDatabase.ToList().Select(x => x.key).ToList();
int index = list.BinarySearch(key);
bool keyWasAdded = index >= 0;
if (!keyWasAdded)
{
// If the item was not found, List<T>.BinarySearch returns a negative number that
// is the bitwise complement of the index of the next element that is larger than the item
index = ~index;
}
foreach (SaveDataInfoReader reader in OpenedReaders)
{
if (keyWasAdded)
{
// New key was inserted before the iterator's position
// increment the position to compensate
if(reader.Position >= index)
{
reader.Position++;
}
}
else
{
// The position should be decremented if the iterator's position is
// after the key that came directly after the deleted key
if (reader.Position > index)
{
reader.Position--;
}
}
}
return Result.Success;
}
private void CloseReader(SaveDataInfoReader reader)
{
bool wasRemoved = OpenedReaders.Remove(reader);
Debug.Assert(wasRemoved);
}
private ref struct Mounter
{
private FileSystemClient FsClient { get; set; }
private string MountName { get; set; }
private bool IsMounted { get; set; }
public Result Initialize(FileSystemClient fsClient, string mountName, SaveDataSpaceId spaceId,
ulong saveDataId)
{
FsClient = fsClient;
MountName = mountName;
FsClient.DisableAutoSaveDataCreation();
Result rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId);
if (rc.IsFailure())
{
if (rc == ResultFs.TargetNotFound)
{
rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, TitleId.Zero, 0xC0000, 0xC0000, 0);
if (rc.IsFailure()) return rc;
rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId);
if (rc.IsFailure()) return rc;
}
else
{
if (ResultRangeFs.Range4771To4779.Contains(rc)) return rc;
if (!ResultRangeFs.DataCorrupted.Contains(rc)) return rc;
if (spaceId == SaveDataSpaceId.SdSystem) return rc;
rc = FsClient.DeleteSaveData(spaceId, saveDataId);
if (rc.IsFailure()) return rc;
rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, TitleId.Zero, 0xC0000, 0xC0000, 0);
if (rc.IsFailure()) return rc;
rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId);
if (rc.IsFailure()) return rc;
}
}
IsMounted = true;
return Result.Success;
}
public void Dispose()
{
if (IsMounted)
{
FsClient.Unmount(MountName);
}
}
}
private class SaveDataInfoReader : ISaveDataInfoReader
{
private SaveDataIndexer Indexer { get; }
private int Version { get; }
public int Position { get; set; }
public SaveDataInfoReader(SaveDataIndexer indexer)
{
Indexer = indexer;
Version = indexer.Version;
}
public Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer)
{
readCount = default;
lock (Indexer.Locker)
{
// Indexer has been reloaded since this info reader was created
if (Version != Indexer.Version)
{
return ResultFs.ReadOldSaveDataInfoReader.Log();
}
// No more to iterate
if (Position == Indexer.KvDatabase.Count)
{
readCount = 0;
return Result.Success;
}
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
// Todo: A more efficient way of doing this
List<(SaveDataAttribute key, byte[] value)> list = Indexer.KvDatabase.ToList();
int i;
for (i = 0; i < outInfo.Length && Position < list.Count; i++, Position++)
{
SaveDataAttribute key = list[Position].key;
ref SaveDataIndexerValue value = ref Unsafe.As<byte, SaveDataIndexerValue>(ref list[Position].value[0]);
GetSaveDataInfo(out outInfo[i], ref key, ref value);
}
readCount = i;
return Result.Success;
}
}
public void Dispose()
{
Indexer?.CloseReader(this);
}
}
}
}

View File

@ -0,0 +1,124 @@
using System;
using System.Threading;
using LibHac.Fs;
namespace LibHac.FsService
{
internal class SaveDataIndexerManager
{
private FileSystemClient FsClient { get; }
private ulong SaveDataId { get; }
private IndexerHolder _bisIndexer = new IndexerHolder(new object());
private IndexerHolder _sdCardIndexer = new IndexerHolder(new object());
private IndexerHolder _safeIndexer = new IndexerHolder(new object());
private IndexerHolder _properSystemIndexer = new IndexerHolder(new object());
public SaveDataIndexerManager(FileSystemClient fsClient, ulong saveDataId)
{
FsClient = fsClient;
SaveDataId = saveDataId;
}
public Result GetSaveDataIndexer(out SaveDataIndexerReader reader, SaveDataSpaceId spaceId)
{
switch (spaceId)
{
case SaveDataSpaceId.System:
case SaveDataSpaceId.User:
Monitor.Enter(_bisIndexer.Locker);
if (!_bisIndexer.IsInitialized)
{
_bisIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDb", SaveDataSpaceId.System, SaveDataId);
}
reader = new SaveDataIndexerReader(_bisIndexer.Indexer, _bisIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.SdSystem:
case SaveDataSpaceId.SdCache:
Monitor.Enter(_sdCardIndexer.Locker);
// Missing reinitialize if SD handle is old
if (!_sdCardIndexer.IsInitialized)
{
_sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbSd", SaveDataSpaceId.SdSystem, SaveDataId);
}
reader = new SaveDataIndexerReader(_sdCardIndexer.Indexer, _sdCardIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.TemporaryStorage:
throw new NotImplementedException();
case SaveDataSpaceId.ProperSystem:
Monitor.Enter(_safeIndexer.Locker);
if (!_safeIndexer.IsInitialized)
{
_safeIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbPr", SaveDataSpaceId.ProperSystem, SaveDataId);
}
reader = new SaveDataIndexerReader(_safeIndexer.Indexer, _safeIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.Safe:
Monitor.Enter(_properSystemIndexer.Locker);
if (!_properSystemIndexer.IsInitialized)
{
_properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbSf", SaveDataSpaceId.Safe, SaveDataId);
}
reader = new SaveDataIndexerReader(_properSystemIndexer.Indexer, _properSystemIndexer.Locker);
return Result.Success;
default:
reader = default;
return ResultFs.InvalidArgument.Log();
}
}
private struct IndexerHolder
{
public object Locker { get; }
public ISaveDataIndexer Indexer { get; set; }
public IndexerHolder(object locker)
{
Locker = locker;
Indexer = null;
}
public bool IsInitialized => Indexer != null;
}
}
public ref struct SaveDataIndexerReader
{
private bool _isInitialized;
private object _locker;
public ISaveDataIndexer Indexer;
public bool IsInitialized => _isInitialized;
internal SaveDataIndexerReader(ISaveDataIndexer indexer, object locker)
{
_isInitialized = true;
_locker = locker;
Indexer = indexer;
}
public void Dispose()
{
if (_isInitialized)
{
Monitor.Exit(_locker);
_isInitialized = false;
}
}
}
}

View File

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
using LibHac.Fs;
namespace LibHac.FsService
{
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
public struct SaveDataIndexerValue
{
[FieldOffset(0x00)] public ulong SaveDataId;
[FieldOffset(0x08)] public long Size;
[FieldOffset(0x10)] public ulong Field10;
[FieldOffset(0x18)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x19)] public SaveDataState State;
}
}

View File

@ -0,0 +1,161 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
namespace LibHac.FsService
{
internal class SaveDataInfoFilterReader : ISaveDataInfoReader
{
private ISaveDataInfoReader Reader { get; }
private SaveDataFilterInternal Filter { get; }
public SaveDataInfoFilterReader(ISaveDataInfoReader reader, ref SaveDataFilterInternal filter)
{
Reader = reader;
Filter = filter;
}
public Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer)
{
readCount = default;
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
SaveDataInfo tempInfo = default;
Span<byte> tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo);
int count = 0;
while (count < outInfo.Length)
{
Result rc = Reader.ReadSaveDataInfo(out long baseReadCount, tempInfoBytes);
if (rc.IsFailure()) return rc;
if (baseReadCount == 0) break;
if (Filter.Matches(ref tempInfo))
{
outInfo[count] = tempInfo;
count++;
}
}
readCount = count;
return Result.Success;
}
public void Dispose()
{
Reader?.Dispose();
}
}
[StructLayout(LayoutKind.Explicit, Size = 0x50)]
internal struct SaveDataFilterInternal
{
[FieldOffset(0x00)] public bool FilterBySaveDataSpaceId;
[FieldOffset(0x01)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x08)] public bool FilterByTitleId;
[FieldOffset(0x10)] public TitleId TitleId;
[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)
{
this = default;
FilterBySaveDataSpaceId = true;
SpaceId = spaceId;
Rank = filter.Rank;
if (filter.FilterByTitleId)
{
FilterByTitleId = true;
TitleId = filter.TitleId;
}
if (filter.FilterBySaveDataType)
{
FilterBySaveDataType = true;
SaveDataType = filter.SaveDataType;
}
if (filter.FilterByUserId)
{
FilterByUserId = true;
UserId = filter.UserId;
}
if (filter.FilterBySaveDataId)
{
FilterBySaveDataId = true;
SaveDataId = filter.SaveDataId;
}
if (filter.FilterByIndex)
{
FilterByIndex = true;
Index = filter.Index;
}
}
public bool Matches(ref SaveDataInfo info)
{
if (FilterBySaveDataSpaceId && info.SpaceId != SpaceId)
{
return false;
}
if (FilterByTitleId && info.TitleId != TitleId)
{
return false;
}
if (FilterBySaveDataType && info.Type != SaveDataType)
{
return false;
}
if (FilterByUserId && info.UserId != UserId)
{
return false;
}
if (FilterBySaveDataId && info.SaveDataId != SaveDataId)
{
return false;
}
if (FilterByIndex && info.Index != Index)
{
return false;
}
if ((Rank & 1) == 0 && info.Rank != 0)
{
return false;
}
return true;
}
}
}

View File

@ -1,40 +0,0 @@
using System;
using System.Buffers.Binary;
using LibHac.Kvdb;
namespace LibHac.FsService
{
public class SaveIndexerStruct : IExportable
{
public ulong SaveId { get; private set; }
public ulong Size { get; private set; }
public byte SpaceId { get; private set; }
public byte Field19 { get; private set; }
public int ExportSize => 0x40;
private bool _isFrozen;
public void ToBytes(Span<byte> output)
{
if(output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
BinaryPrimitives.WriteUInt64LittleEndian(output, SaveId);
BinaryPrimitives.WriteUInt64LittleEndian(output.Slice(8), Size);
output[0x18] = SpaceId;
output[0x19] = Field19;
}
public void FromBytes(ReadOnlySpan<byte> input)
{
if(_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
if (input.Length < ExportSize) throw new InvalidOperationException("Input data is too short.");
SaveId = BinaryPrimitives.ReadUInt64LittleEndian(input);
Size = BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(8));
SpaceId = input[0x18];
Field19 = input[0x19];
}
public void Freeze() => _isFrozen = true;
}
}

View File

@ -47,6 +47,8 @@ namespace LibHac.FsSystem
{
ParentFs.NotifyCloseWritableFile();
}
BaseFile?.Dispose();
}
}
}

View File

@ -5,7 +5,7 @@ namespace LibHac.FsSystem
{
public class FileStorage : StorageBase
{
private IFile BaseFile { get; }
protected IFile BaseFile { get; }
public FileStorage(IFile baseFile)
{
@ -37,4 +37,17 @@ namespace LibHac.FsSystem
return BaseFile.SetSize(size);
}
}
public class DisposingFileStorage : FileStorage
{
public DisposingFileStorage(IFile baseFile) : base(baseFile) { }
protected override void Dispose(bool disposing)
{
if (disposing)
{
BaseFile?.Dispose();
}
}
}
}

View File

@ -245,10 +245,10 @@ namespace LibHac.FsSystem
return (rc.IsSuccess() && type == DirectoryEntryType.File);
}
public static void EnsureDirectoryExists(this IFileSystem fs, string path)
public static Result EnsureDirectoryExists(this IFileSystem fs, string path)
{
path = PathTools.Normalize(path);
if (fs.DirectoryExists(path)) return;
if (fs.DirectoryExists(path)) return Result.Success;
// Find the first subdirectory in the chain that doesn't exist
int i;
@ -276,11 +276,12 @@ namespace LibHac.FsSystem
{
string subPath = path.Substring(0, i);
fs.CreateDirectory(subPath);
Result rc = fs.CreateDirectory(subPath);
if (rc.IsFailure()) return rc;
}
}
fs.CreateDirectory(path);
return fs.CreateDirectory(path);
}
public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size)

View File

@ -6,7 +6,7 @@ using LibHac.Fs;
namespace LibHac.FsSystem
{
public class LocalFileSystem : IAttributeFileSystem
public class LocalFileSystem : AttributeFileSystemBase
{
private string BasePath { get; }
@ -30,7 +30,7 @@ namespace LibHac.FsSystem
return PathTools.Combine(BasePath, path);
}
public Result GetFileAttributes(string path, out NxFileAttributes attributes)
protected override Result GetFileAttributesImpl(string path, out NxFileAttributes attributes)
{
attributes = default;
@ -49,7 +49,7 @@ namespace LibHac.FsSystem
return Result.Success;
}
public Result SetFileAttributes(string path, NxFileAttributes attributes)
protected override Result SetFileAttributesImpl(string path, NxFileAttributes attributes)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -76,7 +76,7 @@ namespace LibHac.FsSystem
return Result.Success;
}
public Result GetFileSize(out long fileSize, string path)
protected override Result GetFileSizeImpl(out long fileSize, string path)
{
fileSize = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -87,12 +87,12 @@ namespace LibHac.FsSystem
return GetSizeInternal(out fileSize, info);
}
public Result CreateDirectory(string path)
protected override Result CreateDirectoryImpl(string path)
{
return CreateDirectory(path, NxFileAttributes.None);
}
public Result CreateDirectory(string path, NxFileAttributes archiveAttribute)
protected override Result CreateDirectoryImpl(string path, NxFileAttributes archiveAttribute)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -112,7 +112,7 @@ namespace LibHac.FsSystem
return CreateDirInternal(dir, archiveAttribute);
}
public Result CreateFile(string path, long size, CreateFileOptions options)
protected override Result CreateFileImpl(string path, long size, CreateFileOptions options)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -139,7 +139,7 @@ namespace LibHac.FsSystem
}
}
public Result DeleteDirectory(string path)
protected override Result DeleteDirectoryImpl(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -149,7 +149,7 @@ namespace LibHac.FsSystem
return DeleteDirectoryInternal(dir, false);
}
public Result DeleteDirectoryRecursively(string path)
protected override Result DeleteDirectoryRecursivelyImpl(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -159,7 +159,7 @@ namespace LibHac.FsSystem
return DeleteDirectoryInternal(dir, true);
}
public Result CleanDirectoryRecursively(string path)
protected override Result CleanDirectoryRecursivelyImpl(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -184,7 +184,7 @@ namespace LibHac.FsSystem
return Result.Success;
}
public Result DeleteFile(string path)
protected override Result DeleteFileImpl(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -194,7 +194,7 @@ namespace LibHac.FsSystem
return DeleteFileInternal(file);
}
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
protected override Result OpenDirectoryImpl(out IDirectory directory, string path, OpenDirectoryMode mode)
{
directory = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -220,7 +220,7 @@ namespace LibHac.FsSystem
}
}
public Result OpenFile(out IFile file, string path, OpenMode mode)
protected override Result OpenFileImpl(out IFile file, string path, OpenMode mode)
{
file = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -240,7 +240,7 @@ namespace LibHac.FsSystem
return Result.Success;
}
public Result RenameDirectory(string oldPath, string newPath)
protected override Result RenameDirectoryImpl(string oldPath, string newPath)
{
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
@ -263,7 +263,7 @@ namespace LibHac.FsSystem
return RenameDirInternal(srcDir, dstDir);
}
public Result RenameFile(string oldPath, string newPath)
protected override Result RenameFileImpl(string oldPath, string newPath)
{
string srcLocalPath = ResolveLocalPath(PathTools.Normalize(oldPath));
string dstLocalPath = ResolveLocalPath(PathTools.Normalize(newPath));
@ -280,7 +280,7 @@ namespace LibHac.FsSystem
return RenameFileInternal(srcFile, dstFile);
}
public Result GetEntryType(out DirectoryEntryType entryType, string path)
protected override Result GetEntryTypeImpl(out DirectoryEntryType entryType, string path)
{
entryType = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -307,7 +307,7 @@ namespace LibHac.FsSystem
return ResultFs.PathNotFound.Log();
}
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
protected override Result GetFileTimeStampRawImpl(out FileTimeStampRaw timeStamp, string path)
{
timeStamp = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -324,24 +324,24 @@ namespace LibHac.FsSystem
return Result.Success;
}
public Result GetFreeSpaceSize(out long freeSpace, string path)
protected override Result GetFreeSpaceSizeImpl(out long freeSpace, string path)
{
freeSpace = new DriveInfo(BasePath).AvailableFreeSpace;
return Result.Success;
}
public Result GetTotalSpaceSize(out long totalSpace, string path)
protected override Result GetTotalSpaceSizeImpl(out long totalSpace, string path)
{
totalSpace = new DriveInfo(BasePath).TotalSize;
return Result.Success;
}
public Result Commit()
protected override Result CommitImpl()
{
return Result.Success;
}
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
protected override Result QueryEntryImpl(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
return ResultFs.UnsupportedOperation.Log();
}

View File

@ -302,5 +302,16 @@ namespace LibHac.FsSystem.Save
return journalValidity;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (!LeaveOpen)
{
BaseStorage?.Dispose();
}
}
}
}
}

View File

@ -1,31 +0,0 @@
using System;
namespace LibHac.Kvdb
{
/// <summary>
/// A class for handling any value used by <see cref="KeyValueDatabase{TKey,TValue}"/>
/// </summary>
public class GenericValue : IExportable
{
private bool _isFrozen;
private byte[] _value;
public int ExportSize => _value?.Length ?? 0;
public void ToBytes(Span<byte> output)
{
if (output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
_value.CopyTo(output);
}
public void FromBytes(ReadOnlySpan<byte> input)
{
if (_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
_value = input.ToArray();
}
public void Freeze() => _isFrozen = true;
}
}

View File

@ -5,7 +5,7 @@ namespace LibHac.Kvdb
{
public ref struct ImkvdbReader
{
private ReadOnlySpan<byte> _data;
private readonly ReadOnlySpan<byte> _data;
private int _position;
public ImkvdbReader(ReadOnlySpan<byte> data)
@ -18,13 +18,14 @@ namespace LibHac.Kvdb
{
entryCount = default;
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length) return ResultKvdb.InvalidKeyValue;
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length)
return ResultKvdb.InvalidKeyValue.Log();
ref ImkvdbHeader header = ref Unsafe.As<byte, ImkvdbHeader>(ref Unsafe.AsRef(_data[_position]));
if (header.Magic != ImkvdbHeader.ExpectedMagic)
{
return ResultKvdb.InvalidKeyValue;
return ResultKvdb.InvalidKeyValue.Log();
}
entryCount = header.EntryCount;
@ -38,13 +39,14 @@ namespace LibHac.Kvdb
keySize = default;
valueSize = default;
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length) return ResultKvdb.InvalidKeyValue;
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length)
return ResultKvdb.InvalidKeyValue.Log();
ref ImkvdbEntryHeader header = ref Unsafe.As<byte, ImkvdbEntryHeader>(ref Unsafe.AsRef(_data[_position]));
if (header.Magic != ImkvdbEntryHeader.ExpectedMagic)
{
return ResultKvdb.InvalidKeyValue;
return ResultKvdb.InvalidKeyValue.Log();
}
keySize = header.KeySize;
@ -63,7 +65,8 @@ namespace LibHac.Kvdb
_position += Unsafe.SizeOf<ImkvdbEntryHeader>();
if (_position + keySize + valueSize > _data.Length) return ResultKvdb.InvalidKeyValue;
if (_position + keySize + valueSize > _data.Length)
return ResultKvdb.InvalidKeyValue.Log();
key = _data.Slice(_position, keySize);
value = _data.Slice(_position + keySize, valueSize);

View File

@ -5,7 +5,7 @@ namespace LibHac.Kvdb
{
public ref struct ImkvdbWriter
{
private Span<byte> _data;
private readonly Span<byte> _data;
private int _position;
public ImkvdbWriter(Span<byte> data)
@ -27,9 +27,9 @@ namespace LibHac.Kvdb
_position += Unsafe.SizeOf<ImkvdbHeader>();
}
public void WriteEntry(IExportable key, IExportable value)
public void WriteEntry(ReadOnlySpan<byte> key, ReadOnlySpan<byte> value)
{
WriteEntryHeader(key.ExportSize, value.ExportSize);
WriteEntryHeader(key.Length, value.Length);
Write(key);
Write(value);
}
@ -47,13 +47,13 @@ namespace LibHac.Kvdb
_position += Unsafe.SizeOf<ImkvdbEntryHeader>();
}
private void Write(IExportable value)
private void Write(ReadOnlySpan<byte> value)
{
int valueSize = value.ExportSize;
int valueSize = value.Length;
if (_position + valueSize > _data.Length) throw new InvalidOperationException();
Span<byte> dest = _data.Slice(_position, valueSize);
value.ToBytes(dest);
value.CopyTo(dest);
_position += valueSize;
}

View File

@ -1,40 +1,74 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.Kvdb
{
// Todo: Save and load from file
public class KeyValueDatabase<TKey, TValue>
where TKey : IComparable<TKey>, IEquatable<TKey>, IExportable, new()
where TValue : IExportable, new()
public class KeyValueDatabase<TKey> where TKey : unmanaged, IComparable<TKey>, IEquatable<TKey>
{
private Dictionary<TKey, TValue> KvDict { get; } = new Dictionary<TKey, TValue>();
private Dictionary<TKey, byte[]> KvDict { get; } = new Dictionary<TKey, byte[]>();
private FileSystemClient FsClient { get; }
private string FileName { get; }
public int Count => KvDict.Count;
public Result Get(TKey key, out TValue value)
public KeyValueDatabase() { }
public KeyValueDatabase(FileSystemClient fsClient, string fileName)
{
FsClient = fsClient;
FileName = fileName;
}
public Result Get(ref TKey key, Span<byte> valueBuffer)
{
Result rc = GetValue(ref key, out byte[] value);
if (rc.IsFailure()) return rc;
int size = Math.Min(valueBuffer.Length, value.Length);
value.AsSpan(0, size).CopyTo(valueBuffer);
return Result.Success;
}
public Result GetValue(ref TKey key, out byte[] value)
{
if (!KvDict.TryGetValue(key, out value))
{
return ResultKvdb.KeyNotFound;
return ResultKvdb.KeyNotFound.Log();
}
return Result.Success;
}
public Result Set(TKey key, TValue value)
public Result Set(ref TKey key, ReadOnlySpan<byte> value)
{
key.Freeze();
KvDict[key] = value;
KvDict[key] = value.ToArray();
return Result.Success;
}
public Result Delete(ref TKey key)
{
bool deleted = KvDict.Remove(key);
return deleted ? Result.Success : ResultKvdb.KeyNotFound.Log();
}
public Dictionary<TKey, byte[]>.Enumerator GetEnumerator()
{
return KvDict.GetEnumerator();
}
public Result ReadDatabaseFromBuffer(ReadOnlySpan<byte> data)
{
KvDict.Clear();
var reader = new ImkvdbReader(data);
Result rc = reader.ReadHeader(out int entryCount);
@ -45,13 +79,12 @@ namespace LibHac.Kvdb
rc = reader.ReadEntry(out ReadOnlySpan<byte> keyBytes, out ReadOnlySpan<byte> valueBytes);
if (rc.IsFailure()) return rc;
Debug.Assert(keyBytes.Length == Unsafe.SizeOf<TKey>());
var key = new TKey();
var value = new TValue();
keyBytes.CopyTo(SpanHelpers.AsByteSpan(ref key));
key.FromBytes(keyBytes);
value.FromBytes(valueBytes);
key.Freeze();
byte[] value = valueBytes.ToArray();
KvDict.Add(key, value);
}
@ -65,26 +98,102 @@ namespace LibHac.Kvdb
writer.WriteHeader(KvDict.Count);
foreach (KeyValuePair<TKey, TValue> entry in KvDict.OrderBy(x => x.Key))
foreach (KeyValuePair<TKey, byte[]> entry in KvDict.OrderBy(x => x.Key))
{
writer.WriteEntry(entry.Key, entry.Value);
TKey key = entry.Key;
writer.WriteEntry(SpanHelpers.AsByteSpan(ref key), entry.Value);
}
return Result.Success;
}
public Result ReadDatabaseFromFile()
{
if (FsClient == null || FileName == null)
return ResultFs.PreconditionViolation.Log();
Result rc = ReadFile(out byte[] data);
if (rc.IsFailure())
{
return rc == ResultFs.PathNotFound ? Result.Success : rc;
}
return ReadDatabaseFromBuffer(data);
}
public Result WriteDatabaseToFile()
{
if (FsClient == null || FileName == null)
return ResultFs.PreconditionViolation.Log();
var buffer = new byte[GetExportedSize()];
Result rc = WriteDatabaseToBuffer(buffer);
if (rc.IsFailure()) return rc;
return WriteFile(buffer);
}
public int GetExportedSize()
{
int size = Unsafe.SizeOf<ImkvdbHeader>();
foreach (KeyValuePair<TKey, TValue> entry in KvDict)
foreach (byte[] value in KvDict.Values)
{
size += Unsafe.SizeOf<ImkvdbEntryHeader>();
size += entry.Key.ExportSize;
size += entry.Value.ExportSize;
size += Unsafe.SizeOf<TKey>();
size += value.Length;
}
return size;
}
public List<(TKey key, byte[] value)> ToList()
{
return KvDict.OrderBy(x => x.Key).Select(entry => (entry.Key, entry.Value)).ToList();
}
private Result ReadFile(out byte[] data)
{
Debug.Assert(FsClient != null);
Debug.Assert(!string.IsNullOrWhiteSpace(FileName));
data = default;
Result rc = FsClient.OpenFile(out FileHandle handle, FileName, OpenMode.Read);
if (rc.IsFailure()) return rc;
rc = FsClient.GetFileSize(out long fileSize, handle);
if (rc.IsSuccess())
{
data = new byte[fileSize];
rc = FsClient.ReadFile(handle, 0, data);
}
FsClient.CloseFile(handle);
return rc;
}
private Result WriteFile(ReadOnlySpan<byte> data)
{
Debug.Assert(FsClient != null);
Debug.Assert(!string.IsNullOrWhiteSpace(FileName));
FsClient.DeleteFile(FileName);
Result rc = FsClient.CreateFile(FileName, data.Length);
if (rc.IsFailure()) return rc;
rc = FsClient.OpenFile(out FileHandle handle, FileName, OpenMode.Write);
if (rc.IsFailure()) return rc;
rc = FsClient.WriteFile(handle, 0, data, WriteOption.Flush);
FsClient.CloseFile(handle);
return rc;
}
}
}

View File

@ -4,7 +4,7 @@
{
public const int ModuleKvdb = 20;
public static Result TooLargeKey => new Result(ModuleKvdb, 1);
public static Result TooLargeKeyOrDbFull => new Result(ModuleKvdb, 1);
public static Result KeyNotFound => new Result(ModuleKvdb, 2);
public static Result AllocationFailed => new Result(ModuleKvdb, 4);
public static Result InvalidKeyValue => new Result(ModuleKvdb, 5);

View File

@ -1,10 +1,13 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
namespace LibHac.Ncm
{
[DebuggerDisplay("{" + nameof(Value) + "}")]
public struct TitleId
public struct TitleId : IEquatable<TitleId>, IComparable<TitleId>, IComparable
{
public static TitleId Zero => default;
public readonly ulong Value;
public TitleId(ulong value)
@ -13,5 +16,20 @@ namespace LibHac.Ncm
}
public static explicit operator ulong(TitleId titleId) => titleId.Value;
public override string ToString() => $"{Value:X16}";
public int CompareTo(object obj)
{
if (ReferenceEquals(null, obj)) return 1;
return obj is TitleId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TitleId)}");
}
public int CompareTo(TitleId other) => Value.CompareTo(other.Value);
public bool Equals(TitleId other) => Value == other.Value;
public override bool Equals(object obj) => obj is TitleId other && Equals(other);
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(TitleId left, TitleId right) => left.Equals(right);
public static bool operator !=(TitleId left, TitleId right) => !left.Equals(right);
}
}

View File

@ -45,6 +45,16 @@ namespace LibHac
return this;
}
/// <summary>
/// Same as <see cref="Log"/>, but for when one result is converted to another.
/// </summary>
/// <param name="originalResult">The original <see cref="Result"/> value.</param>
/// <returns>The called <see cref="Result"/> value.</returns>
public Result LogConverted(Result originalResult)
{
return this;
}
public override string ToString()
{
return IsSuccess() ? "Success" : ErrorCode;