mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
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:
commit
f3b5cad94b
16
src/LibHac/Common/SystemTitleIds.cs
Normal file
16
src/LibHac/Common/SystemTitleIds.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
46
src/LibHac/Fs/AttributeFileSystemBase.cs
Normal file
46
src/LibHac/Fs/AttributeFileSystemBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -4,6 +4,6 @@ namespace LibHac.Fs
|
||||
{
|
||||
public interface ICommonMountNameGenerator
|
||||
{
|
||||
Result Generate(Span<byte> nameBuffer);
|
||||
Result GenerateCommonMountName(Span<byte> nameBuffer);
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -2,7 +2,7 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.FsService;
|
||||
|
||||
namespace LibHac.Fs
|
||||
namespace LibHac.Fs.Shim
|
||||
{
|
||||
public static class CustomStorage
|
||||
{
|
92
src/LibHac/Fs/Shim/GameCard.cs
Normal file
92
src/LibHac/Fs/Shim/GameCard.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
91
src/LibHac/Fs/Shim/SaveData.cs
Normal file
91
src/LibHac/Fs/Shim/SaveData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
292
src/LibHac/Fs/Shim/SaveDataManagement.cs
Normal file
292
src/LibHac/Fs/Shim/SaveDataManagement.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
31
src/LibHac/Fs/Shim/SystemSaveData.cs
Normal file
31
src/LibHac/Fs/Shim/SystemSaveData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
21
src/LibHac/FsService/ISaveDataIndexer.cs
Normal file
21
src/LibHac/FsService/ISaveDataIndexer.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace LibHac.FsService
|
||||
{
|
||||
public interface ISaveDataInfoReader
|
||||
public interface ISaveDataInfoReader : IDisposable
|
||||
{
|
||||
Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer);
|
||||
}
|
||||
|
669
src/LibHac/FsService/SaveDataIndexer.cs
Normal file
669
src/LibHac/FsService/SaveDataIndexer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
124
src/LibHac/FsService/SaveDataIndexerManager.cs
Normal file
124
src/LibHac/FsService/SaveDataIndexerManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/LibHac/FsService/SaveDataIndexerValue.cs
Normal file
15
src/LibHac/FsService/SaveDataIndexerValue.cs
Normal 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;
|
||||
}
|
||||
}
|
161
src/LibHac/FsService/SaveDataInfoFilterReader.cs
Normal file
161
src/LibHac/FsService/SaveDataInfoFilterReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -47,6 +47,8 @@ namespace LibHac.FsSystem
|
||||
{
|
||||
ParentFs.NotifyCloseWritableFile();
|
||||
}
|
||||
|
||||
BaseFile?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -302,5 +302,16 @@ namespace LibHac.FsSystem.Save
|
||||
|
||||
return journalValidity;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user