Add main parts of CreateSaveDataFileSystem

This commit is contained in:
Alex Barney 2019-10-18 18:57:45 -05:00
parent fef6d19900
commit 92049bf9b7
15 changed files with 640 additions and 44 deletions

View file

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

View file

@ -112,6 +112,13 @@ namespace LibHac.Fs
public Span<byte> Hash => SpanHelpers.CreateSpan(ref _hashStart, HashLength); 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)] [StructLayout(LayoutKind.Explicit, Size = 0x10)]
public struct SaveMetaCreateInfo public struct SaveMetaCreateInfo
{ {
@ -125,7 +132,7 @@ namespace LibHac.Fs
[FieldOffset(0x00)] public long Size; [FieldOffset(0x00)] public long Size;
[FieldOffset(0x08)] public long JournalSize; [FieldOffset(0x08)] public long JournalSize;
[FieldOffset(0x10)] public ulong BlockSize; [FieldOffset(0x10)] public ulong BlockSize;
[FieldOffset(0x18)] public ulong OwnerId; [FieldOffset(0x18)] public TitleId OwnerId;
[FieldOffset(0x20)] public uint Flags; [FieldOffset(0x20)] public uint Flags;
[FieldOffset(0x24)] public SaveDataSpaceId SpaceId; [FieldOffset(0x24)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x25)] public bool Field25; [FieldOffset(0x25)] public bool Field25;

View file

@ -18,7 +18,32 @@ namespace LibHac.Fs.Shim
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, false, 0); rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, false, 0);
TimeSpan endTime = fs.Time.GetCurrent(); TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{titleId}, userid: {userId}"); 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 else
{ {

View file

@ -1,11 +1,85 @@
using LibHac.FsService; using LibHac.FsService;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
namespace LibHac.Fs.Shim namespace LibHac.Fs.Shim
{ {
public static class SaveDataManagement 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, public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId,
ulong saveDataId, UserId userId, ulong ownerId, long size, long journalSize, uint flags) ulong saveDataId, UserId userId, TitleId ownerId, long size, long journalSize, uint flags)
{ {
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System, return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
() => () =>
@ -30,11 +104,11 @@ namespace LibHac.Fs.Shim
return fsProxy.CreateSaveDataFileSystemBySystemSaveDataId(ref attribute, ref createInfo); 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}"); () => $", 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, public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId,
ulong ownerId, long size, long journalSize, uint flags) TitleId ownerId, long size, long journalSize, uint flags)
{ {
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, ownerId, size, journalSize, flags); return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, ownerId, size, journalSize, flags);
} }
@ -42,10 +116,10 @@ namespace LibHac.Fs.Shim
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size, public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size,
long journalSize, uint flags) long journalSize, uint flags)
{ {
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, 0, size, journalSize, flags); return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, TitleId.Zero, size, journalSize, flags);
} }
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size, public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, TitleId ownerId, long size,
long journalSize, uint flags) long journalSize, uint flags)
{ {
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags); return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
@ -54,11 +128,11 @@ namespace LibHac.Fs.Shim
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size, public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size,
long journalSize, uint flags) long journalSize, uint flags)
{ {
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, 0, size, journalSize, 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, public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
ulong ownerId, long size, long journalSize, uint flags) TitleId ownerId, long size, long journalSize, uint flags)
{ {
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags); return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
} }

View file

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

View file

@ -3,6 +3,7 @@ using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.FsSystem.Save; using LibHac.FsSystem.Save;
using LibHac.Kvdb;
using LibHac.Ncm; using LibHac.Ncm;
using LibHac.Spl; using LibHac.Spl;
@ -123,21 +124,188 @@ namespace LibHac.FsService
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Result CreateSaveDataFileSystemImpl(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo, ref OptionalHashSalt hashSalt, bool something)
{
ulong saveDataId;
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();
}
// todo
}
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);
}
rc = reader.Indexer.SetState(saveDataId, 1);
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 = FsProxyCore.CreateSaveDataFileSystem(saveDataId, ref attribute, ref createInfo, SaveDataRootPath,
hashSalt, false);
if (rc.IsFailure())
{
// todo: remove and recreate
throw new NotImplementedException();
}
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)
{
return Result.Success;
}
rc = reader.Indexer.SetState(saveDataId, 0);
if (rc.IsFailure())
{
// Delete if flags allow
throw new NotImplementedException();
}
rc = reader.Indexer.Commit();
if (rc.IsFailure())
{
// Delete if flags allow
throw new NotImplementedException();
}
return Result.Success;
}
finally
{
reader.Dispose();
}
}
public Result CreateSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo, public Result CreateSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo) 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, public Result CreateSaveDataFileSystemWithHashSalt(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo, ref HashSalt hashSalt) 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);
}
public 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) 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) public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize)
@ -161,15 +329,13 @@ namespace LibHac.FsService
{ {
SaveDataAttribute indexerKey = attribute; SaveDataAttribute indexerKey = attribute;
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out SaveDataIndexerReader reader, spaceId); Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out SaveDataIndexerReader tmpReader, spaceId);
using SaveDataIndexerReader c = reader; using SaveDataIndexerReader reader = tmpReader;
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
c.Indexer.Get(out SaveDataIndexerValue indexerValue, ref indexerKey); reader.Indexer.Get(out SaveDataIndexerValue indexerValue, ref indexerKey);
SaveDataSpaceId indexerSpaceId = spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.Safe SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(spaceId);
? SaveDataSpaceId.System
: spaceId;
if (indexerValue.SpaceId != indexerSpaceId) if (indexerValue.SpaceId != indexerSpaceId)
return ResultFs.TargetNotFound.Log(); return ResultFs.TargetNotFound.Log();
@ -652,5 +818,12 @@ namespace LibHac.FsService
{ {
return (long)id < 0; return (long)id < 0;
} }
private static SaveDataSpaceId GetSpaceIdForIndexer(SaveDataSpaceId spaceId)
{
return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.Safe
? SaveDataSpaceId.System
: spaceId;
}
} }
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using LibHac.FsSystem; using LibHac.FsSystem;
@ -189,6 +190,31 @@ namespace LibHac.FsService
return spaceId == SaveDataSpaceId.User && !string.IsNullOrWhiteSpace(saveDataRootPath); return spaceId == SaveDataSpaceId.User && !string.IsNullOrWhiteSpace(saveDataRootPath);
} }
public Result DoesSaveDataExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId)
{
exists = false;
Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, spaceId, string.Empty, true);
if (rc.IsFailure()) return rc;
string saveDataPath = $"/{saveDataId:x16}";
rc = fileSystem.GetEntryType(out _, saveDataPath);
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, public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId,
string saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) string saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData)
{ {
@ -202,14 +228,8 @@ namespace LibHac.FsService
if (allowDirectorySaveData) if (allowDirectorySaveData)
{ {
try rc = saveDirFs.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId));
{ if (rc.IsFailure()) return rc;
saveDirFs.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId));
}
catch (HorizonResultException ex)
{
return ex.ResultValue;
}
} }
// Missing save FS cache lookup // Missing save FS cache lookup
@ -299,6 +319,47 @@ 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 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 SetGlobalAccessLogMode(GlobalAccessLogMode mode) public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode)
{ {
LogMode = mode; LogMode = mode;

View file

@ -4,6 +4,14 @@ namespace LibHac.FsService
{ {
public interface ISaveDataIndexer public interface ISaveDataIndexer
{ {
Result Commit();
Result Add(out ulong saveDataId, ref SaveDataAttribute key);
Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key); Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key);
Result AddSystemSaveData(ref SaveDataAttribute key);
bool IsFull();
Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId);
Result SetSize(ulong saveDataId, long size);
Result SetState(ulong saveDataId, byte state);
Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId);
} }
} }

View file

@ -1,8 +1,11 @@
using System.Diagnostics; using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using LibHac.Kvdb; using LibHac.Kvdb;
using LibHac.Ncm;
namespace LibHac.FsService namespace LibHac.FsService
{ {
@ -19,7 +22,7 @@ namespace LibHac.FsService
private object Locker { get; } = new object(); private object Locker { get; } = new object();
private bool IsInitialized { get; set; } private bool IsInitialized { get; set; }
private bool IsKvdbLoaded { get; set; } private bool IsKvdbLoaded { get; set; }
private long LastPublishedId { get; set; } private ulong LastPublishedId { get; set; }
public SaveDataIndexer(FileSystemClient fsClient, string mountName, SaveDataSpaceId spaceId, ulong saveDataId) public SaveDataIndexer(FileSystemClient fsClient, string mountName, SaveDataSpaceId spaceId, ulong saveDataId)
{ {
@ -29,6 +32,102 @@ namespace LibHac.FsService
SpaceId = spaceId; SpaceId = spaceId;
} }
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;
}
saveDataId = newSaveDataId;
return Result.Success;
}
}
public Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key) public Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key)
{ {
value = default; value = default;
@ -52,6 +151,124 @@ namespace LibHac.FsService
} }
} }
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())
{
// todo: Missing some function call here
}
return rc;
}
}
public bool IsFull()
{
return false;
}
public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId)
{
lock (Locker)
{
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)
{
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, byte state)
{
lock (Locker)
{
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 GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId)
{
lock (Locker)
{
if (TryGetBySaveDataIdInternal(out _, out value, saveDataId))
{
return Result.Success;
}
return ResultFs.TargetNotFound.Log();
}
}
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() private Result Initialize()
{ {
if (IsInitialized) return Result.Success; if (IsInitialized) return Result.Success;
@ -132,7 +349,7 @@ namespace LibHac.FsService
{ {
if (!createdNewFile) if (!createdNewFile)
{ {
long lastId = default; ulong lastId = default;
rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref lastId)); rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref lastId));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
@ -179,7 +396,7 @@ namespace LibHac.FsService
{ {
if (rc == ResultFs.TargetNotFound) if (rc == ResultFs.TargetNotFound)
{ {
rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, TitleId.Zero, 0xC0000, 0xC0000, 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId); rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId);
@ -195,7 +412,7 @@ namespace LibHac.FsService
rc = FsClient.DeleteSaveData(spaceId, saveDataId); rc = FsClient.DeleteSaveData(spaceId, saveDataId);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, TitleId.Zero, 0xC0000, 0xC0000, 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId); rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId);

View file

@ -98,18 +98,25 @@ namespace LibHac.FsService
public ref struct SaveDataIndexerReader public ref struct SaveDataIndexerReader
{ {
private object Locker; private bool _isInitialized;
private object _locker;
public ISaveDataIndexer Indexer; public ISaveDataIndexer Indexer;
internal SaveDataIndexerReader(ISaveDataIndexer indexer, object locker) internal SaveDataIndexerReader(ISaveDataIndexer indexer, object locker)
{ {
Locker = locker; _isInitialized = true;
_locker = locker;
Indexer = indexer; Indexer = indexer;
} }
public void Dispose() public void Dispose()
{ {
Monitor.Exit(Locker); if (_isInitialized)
{
Monitor.Exit(_locker);
_isInitialized = false;
}
} }
} }
} }

View file

@ -7,7 +7,7 @@ namespace LibHac.FsService
public struct SaveDataIndexerValue public struct SaveDataIndexerValue
{ {
[FieldOffset(0x00)] public ulong SaveDataId; [FieldOffset(0x00)] public ulong SaveDataId;
[FieldOffset(0x08)] public ulong Size; [FieldOffset(0x08)] public long Size;
[FieldOffset(0x10)] public ulong Field10; [FieldOffset(0x10)] public ulong Field10;
[FieldOffset(0x18)] public SaveDataSpaceId SpaceId; [FieldOffset(0x18)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x19)] public byte State; [FieldOffset(0x19)] public byte State;

View file

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

View file

@ -245,10 +245,10 @@ namespace LibHac.FsSystem
return (rc.IsSuccess() && type == DirectoryEntryType.File); 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); 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 // Find the first subdirectory in the chain that doesn't exist
int i; int i;
@ -276,11 +276,12 @@ namespace LibHac.FsSystem
{ {
string subPath = path.Substring(0, i); 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) public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size)

View file

@ -44,13 +44,18 @@ namespace LibHac.Kvdb
return Result.Success; return Result.Success;
} }
public Result Set(ref TKey key, byte[] value) public Result Set(ref TKey key, ReadOnlySpan<byte> value)
{ {
KvDict[key] = value; KvDict[key] = value.ToArray();
return Result.Success; return Result.Success;
} }
public Dictionary<TKey, byte[]>.Enumerator GetEnumerator()
{
return KvDict.GetEnumerator();
}
public Result ReadDatabaseFromBuffer(ReadOnlySpan<byte> data) public Result ReadDatabaseFromBuffer(ReadOnlySpan<byte> data)
{ {
KvDict.Clear(); KvDict.Clear();

View file

@ -4,7 +4,7 @@
{ {
public const int ModuleKvdb = 20; 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 KeyNotFound => new Result(ModuleKvdb, 2);
public static Result AllocationFailed => new Result(ModuleKvdb, 4); public static Result AllocationFailed => new Result(ModuleKvdb, 4);
public static Result InvalidKeyValue => new Result(ModuleKvdb, 5); public static Result InvalidKeyValue => new Result(ModuleKvdb, 5);