diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index ba9485b6..b79a2a79 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -83,6 +83,15 @@ namespace LibHac.Fs ExtensionInfo = 2 } + public enum SaveDataState : byte + { + Normal = 0, + Creating = 1, + State2 = 2, + MarkedForDeletion = 3, + State4 = 4, + } + public enum ImageDirectoryId { Nand = 0, diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs index 736df275..adea7949 100644 --- a/src/LibHac/Fs/SaveDataStructs.cs +++ b/src/LibHac/Fs/SaveDataStructs.cs @@ -137,4 +137,19 @@ namespace LibHac.Fs [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; + } } diff --git a/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs b/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs index ca5ccd43..ac0e82f9 100644 --- a/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs @@ -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); diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs index c9b34a77..fd038de3 100644 --- a/src/LibHac/FsService/FileSystemProxy.cs +++ b/src/LibHac/FsService/FileSystemProxy.cs @@ -106,17 +106,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; + } + + // missing: Check extra data flags for this value. Bit 3 + bool doSecureDelete = false; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + rc = DeleteSaveDataFileSystemImpl2(spaceId, saveDataId, doSecureDelete); + 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, bool doSecureDelete) + { + Result rc = FsProxyCore.DeleteSaveDataMetaFiles(saveDataId, spaceId); + if (rc.IsFailure() && rc != ResultFs.PathNotFound) + return rc; + + 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); + } + + public 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); + } + + public 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) @@ -178,7 +309,7 @@ namespace LibHac.FsService return ResultFs.PathAlreadyExists.LogConverted(rc); } - rc = reader.Indexer.SetState(saveDataId, 1); + rc = reader.Indexer.SetState(saveDataId, SaveDataState.Creating); if (rc.IsFailure()) return rc; SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(createInfo.SpaceId); @@ -208,12 +339,12 @@ namespace LibHac.FsService metaCreateInfo.Size); if (rc.IsFailure()) return rc; - if(metaCreateInfo.Type == SaveMetaType.Thumbnail) + if (metaCreateInfo.Type == SaveMetaType.Thumbnail) { rc = FsProxyCore.OpenSaveDataMetaFile(out IFile metaFile, saveDataId, createInfo.SpaceId, metaCreateInfo.Type); - using(metaFile) + using (metaFile) { if (rc.IsFailure()) return rc; @@ -282,7 +413,6 @@ namespace LibHac.FsService public Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute attribute, ref SaveDataCreateInfo createInfo) { - if (!IsSystemSaveDataId(attribute.SaveDataId)) return ResultFs.InvalidArgument.Log(); @@ -340,7 +470,7 @@ namespace LibHac.FsService if (indexerValue.SpaceId != indexerSpaceId) return ResultFs.TargetNotFound.Log(); - if (indexerValue.State == 4) + if (indexerValue.State == SaveDataState.State4) return ResultFs.Result6906.Log(); saveDataId = indexerValue.SaveDataId; diff --git a/src/LibHac/FsService/FileSystemProxyCore.cs b/src/LibHac/FsService/FileSystemProxyCore.cs index 33e612b4..4aa8e83e 100644 --- a/src/LibHac/FsService/FileSystemProxyCore.cs +++ b/src/LibHac/FsService/FileSystemProxyCore.cs @@ -334,6 +334,23 @@ namespace LibHac.FsService 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}"; @@ -360,6 +377,38 @@ namespace LibHac.FsService 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; diff --git a/src/LibHac/FsService/ISaveDataIndexer.cs b/src/LibHac/FsService/ISaveDataIndexer.cs index bea271e3..1ccfc9d2 100644 --- a/src/LibHac/FsService/ISaveDataIndexer.cs +++ b/src/LibHac/FsService/ISaveDataIndexer.cs @@ -9,9 +9,11 @@ namespace LibHac.FsService 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, byte state); + Result SetState(ulong saveDataId, SaveDataState state); + Result GetKey(out SaveDataAttribute key, ulong saveDataId); 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 e250b939..7e1ac8dc 100644 --- a/src/LibHac/FsService/SaveDataIndexer.cs +++ b/src/LibHac/FsService/SaveDataIndexer.cs @@ -32,6 +32,23 @@ namespace LibHac.FsService SpaceId = spaceId; } + 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) @@ -192,10 +209,35 @@ namespace LibHac.FsService 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(); + } + + return KvDatabase.Delete(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(); @@ -211,6 +253,12 @@ namespace LibHac.FsService { 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(); @@ -222,10 +270,16 @@ namespace LibHac.FsService } } - public Result SetState(ulong saveDataId, byte state) + 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(); @@ -237,10 +291,39 @@ namespace LibHac.FsService } } - public Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId) + 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; diff --git a/src/LibHac/FsService/SaveDataIndexerValue.cs b/src/LibHac/FsService/SaveDataIndexerValue.cs index 7a2d7fc0..37652cc6 100644 --- a/src/LibHac/FsService/SaveDataIndexerValue.cs +++ b/src/LibHac/FsService/SaveDataIndexerValue.cs @@ -10,6 +10,6 @@ namespace LibHac.FsService [FieldOffset(0x08)] public long Size; [FieldOffset(0x10)] public ulong Field10; [FieldOffset(0x18)] public SaveDataSpaceId SpaceId; - [FieldOffset(0x19)] public byte State; + [FieldOffset(0x19)] public SaveDataState State; } } diff --git a/src/LibHac/Kvdb/KeyValueDatabase.cs b/src/LibHac/Kvdb/KeyValueDatabase.cs index 2375e5e8..8dfa20af 100644 --- a/src/LibHac/Kvdb/KeyValueDatabase.cs +++ b/src/LibHac/Kvdb/KeyValueDatabase.cs @@ -10,7 +10,7 @@ namespace LibHac.Kvdb { public class KeyValueDatabase where TKey : unmanaged, IComparable, IEquatable { - public Dictionary KvDict { get; } = new Dictionary(); + private Dictionary KvDict { get; } = new Dictionary(); private FileSystemClient FsClient { get; } private string FileName { get; } @@ -51,6 +51,13 @@ namespace LibHac.Kvdb return Result.Success; } + public Result Delete(ref TKey key) + { + bool deleted = KvDict.Remove(key); + + return deleted ? Result.Success : ResultKvdb.KeyNotFound.Log(); + } + public Dictionary.Enumerator GetEnumerator() { return KvDict.GetEnumerator();