diff --git a/src/LibHac/Common/SystemTitleIds.cs b/src/LibHac/Common/SystemTitleIds.cs new file mode 100644 index 00000000..c8dcfef6 --- /dev/null +++ b/src/LibHac/Common/SystemTitleIds.cs @@ -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); + } +} diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs index 914cf094..736df275 100644 --- a/src/LibHac/Fs/SaveDataStructs.cs +++ b/src/LibHac/Fs/SaveDataStructs.cs @@ -112,6 +112,13 @@ namespace LibHac.Fs public Span 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 { @@ -125,7 +132,7 @@ 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; diff --git a/src/LibHac/Fs/Shim/SaveData.cs b/src/LibHac/Fs/Shim/SaveData.cs index 1ed6e25e..953365b5 100644 --- a/src/LibHac/Fs/Shim/SaveData.cs +++ b/src/LibHac/Fs/Shim/SaveData.cs @@ -18,7 +18,32 @@ namespace LibHac.Fs.Shim 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: {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 { diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index 409a28be..26f00d39 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -1,11 +1,85 @@ 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, 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, () => @@ -30,11 +104,11 @@ namespace LibHac.Fs.Shim 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, - 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); } @@ -42,10 +116,10 @@ namespace LibHac.Fs.Shim 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); + 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) { 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, 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, - 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); } diff --git a/src/LibHac/Fs/UserId.cs b/src/LibHac/Fs/UserId.cs index dd1f4761..0bc21c7c 100644 --- a/src/LibHac/Fs/UserId.cs +++ b/src/LibHac/Fs/UserId.cs @@ -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, IComparable, IComparable { @@ -25,7 +25,7 @@ namespace LibHac.Fs 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; diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs index 170d1a6b..c9b34a77 100644 --- a/src/LibHac/FsService/FileSystemProxy.cs +++ b/src/LibHac/FsService/FileSystemProxy.cs @@ -3,6 +3,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; using LibHac.FsSystem.Save; +using LibHac.Kvdb; using LibHac.Ncm; using LibHac.Spl; @@ -123,21 +124,188 @@ namespace LibHac.FsService 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 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, 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); + } + + 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) { - 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) @@ -161,15 +329,13 @@ namespace LibHac.FsService { SaveDataAttribute indexerKey = attribute; - Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out SaveDataIndexerReader reader, spaceId); - using SaveDataIndexerReader c = reader; + Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out SaveDataIndexerReader tmpReader, spaceId); + using SaveDataIndexerReader reader = tmpReader; 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.System - : spaceId; + SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(spaceId); if (indexerValue.SpaceId != indexerSpaceId) return ResultFs.TargetNotFound.Log(); @@ -652,5 +818,12 @@ namespace LibHac.FsService { return (long)id < 0; } + + private static SaveDataSpaceId GetSpaceIdForIndexer(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.Safe + ? SaveDataSpaceId.System + : spaceId; + } } } diff --git a/src/LibHac/FsService/FileSystemProxyCore.cs b/src/LibHac/FsService/FileSystemProxyCore.cs index c1ff51d8..33e612b4 100644 --- a/src/LibHac/FsService/FileSystemProxyCore.cs +++ b/src/LibHac/FsService/FileSystemProxyCore.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Shim; using LibHac.FsSystem; @@ -189,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) { @@ -202,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 @@ -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) { LogMode = mode; diff --git a/src/LibHac/FsService/ISaveDataIndexer.cs b/src/LibHac/FsService/ISaveDataIndexer.cs index cb5ee01a..bea271e3 100644 --- a/src/LibHac/FsService/ISaveDataIndexer.cs +++ b/src/LibHac/FsService/ISaveDataIndexer.cs @@ -4,6 +4,14 @@ 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 SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId); + Result SetSize(ulong saveDataId, long size); + Result SetState(ulong saveDataId, byte state); + Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId); } } \ No newline at end of file diff --git a/src/LibHac/FsService/SaveDataIndexer.cs b/src/LibHac/FsService/SaveDataIndexer.cs index 032554c6..e250b939 100644 --- a/src/LibHac/FsService/SaveDataIndexer.cs +++ b/src/LibHac/FsService/SaveDataIndexer.cs @@ -1,8 +1,11 @@ -using System.Diagnostics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Shim; using LibHac.Kvdb; +using LibHac.Ncm; namespace LibHac.FsService { @@ -19,7 +22,7 @@ namespace LibHac.FsService private object Locker { get; } = new object(); private bool IsInitialized { 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) { @@ -29,6 +32,102 @@ namespace LibHac.FsService 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) { 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 kvp in KvDatabase) + { + ref SaveDataIndexerValue value = ref Unsafe.As(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 kvp in KvDatabase) + { + ref SaveDataIndexerValue currentValue = ref Unsafe.As(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; @@ -132,7 +349,7 @@ namespace LibHac.FsService { if (!createdNewFile) { - long lastId = default; + ulong lastId = default; rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref lastId)); if (rc.IsFailure()) return rc; @@ -179,7 +396,7 @@ namespace LibHac.FsService { 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; rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId); @@ -195,7 +412,7 @@ namespace LibHac.FsService rc = FsClient.DeleteSaveData(spaceId, saveDataId); 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; rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId); diff --git a/src/LibHac/FsService/SaveDataIndexerManager.cs b/src/LibHac/FsService/SaveDataIndexerManager.cs index 74604ae1..b0e5e289 100644 --- a/src/LibHac/FsService/SaveDataIndexerManager.cs +++ b/src/LibHac/FsService/SaveDataIndexerManager.cs @@ -98,18 +98,25 @@ namespace LibHac.FsService public ref struct SaveDataIndexerReader { - private object Locker; + private bool _isInitialized; + private object _locker; public ISaveDataIndexer Indexer; internal SaveDataIndexerReader(ISaveDataIndexer indexer, object locker) { - Locker = locker; + _isInitialized = true; + _locker = locker; Indexer = indexer; } public void Dispose() { - Monitor.Exit(Locker); + if (_isInitialized) + { + Monitor.Exit(_locker); + + _isInitialized = false; + } } } } diff --git a/src/LibHac/FsService/SaveDataIndexerValue.cs b/src/LibHac/FsService/SaveDataIndexerValue.cs index 864eb3d4..7a2d7fc0 100644 --- a/src/LibHac/FsService/SaveDataIndexerValue.cs +++ b/src/LibHac/FsService/SaveDataIndexerValue.cs @@ -7,7 +7,7 @@ namespace LibHac.FsService public struct SaveDataIndexerValue { [FieldOffset(0x00)] public ulong SaveDataId; - [FieldOffset(0x08)] public ulong Size; + [FieldOffset(0x08)] public long Size; [FieldOffset(0x10)] public ulong Field10; [FieldOffset(0x18)] public SaveDataSpaceId SpaceId; [FieldOffset(0x19)] public byte State; diff --git a/src/LibHac/FsSystem/DirectorySaveDataFile.cs b/src/LibHac/FsSystem/DirectorySaveDataFile.cs index de54607d..1fd44bc7 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFile.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFile.cs @@ -47,6 +47,8 @@ namespace LibHac.FsSystem { ParentFs.NotifyCloseWritableFile(); } + + BaseFile?.Dispose(); } } } diff --git a/src/LibHac/FsSystem/FileSystemExtensions.cs b/src/LibHac/FsSystem/FileSystemExtensions.cs index cbe7478d..72ae432a 100644 --- a/src/LibHac/FsSystem/FileSystemExtensions.cs +++ b/src/LibHac/FsSystem/FileSystemExtensions.cs @@ -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) diff --git a/src/LibHac/Kvdb/KeyValueDatabase.cs b/src/LibHac/Kvdb/KeyValueDatabase.cs index 1c2d07a5..2375e5e8 100644 --- a/src/LibHac/Kvdb/KeyValueDatabase.cs +++ b/src/LibHac/Kvdb/KeyValueDatabase.cs @@ -44,13 +44,18 @@ namespace LibHac.Kvdb return Result.Success; } - public Result Set(ref TKey key, byte[] value) + public Result Set(ref TKey key, ReadOnlySpan value) { - KvDict[key] = value; + KvDict[key] = value.ToArray(); return Result.Success; } + public Dictionary.Enumerator GetEnumerator() + { + return KvDict.GetEnumerator(); + } + public Result ReadDatabaseFromBuffer(ReadOnlySpan data) { KvDict.Clear(); diff --git a/src/LibHac/Kvdb/ResultKvdb.cs b/src/LibHac/Kvdb/ResultKvdb.cs index 377e773a..8fec9a36 100644 --- a/src/LibHac/Kvdb/ResultKvdb.cs +++ b/src/LibHac/Kvdb/ResultKvdb.cs @@ -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);