diff --git a/src/LibHac/Diag/Log.cs b/src/LibHac/Diag/Log.cs index e452a426..df172d06 100644 --- a/src/LibHac/Diag/Log.cs +++ b/src/LibHac/Diag/Log.cs @@ -43,4 +43,7 @@ public static class Log diag.LogImpl(in metaData, message); } + + /// "$" + public static ReadOnlySpan EmptyModuleName => new[] { (byte)'$' }; // "$" } diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index 89dd8230..aaa52a1b 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -94,9 +94,17 @@ public ref struct Path [DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] public struct Stored : IDisposable { + private static readonly byte[] EmptyBuffer = { 0 }; + private byte[] _buffer; private int _length; + public Stored() + { + _buffer = EmptyBuffer; + _length = 0; + } + public void Dispose() { byte[] buffer = Shared.Move(ref _buffer); @@ -160,7 +168,8 @@ public ref struct Path byte[] oldBuffer = _buffer; _buffer = buffer; - if (oldBuffer is not null) + // Check if the buffer is longer than 1 so we don't try to return EmptyBuffer to the pool. + if (oldBuffer?.Length > 1) ArrayPool.Shared.Return(oldBuffer); return Result.Success; diff --git a/src/LibHac/Fs/Common/SaveDataTypes.cs b/src/LibHac/Fs/Common/SaveDataTypes.cs index d5db41c8..e7f98453 100644 --- a/src/LibHac/Fs/Common/SaveDataTypes.cs +++ b/src/LibHac/Fs/Common/SaveDataTypes.cs @@ -16,8 +16,7 @@ public enum SaveDataSpaceId : byte Temporary = 3, SdUser = 4, ProperSystem = 100, - SafeMode = 101, - BisAuto = 127 + SafeMode = 101 } public enum SaveDataType : byte @@ -99,6 +98,7 @@ public struct SaveDataExtraData public ulong OwnerId; public long TimeStamp; public SaveDataFlags Flags; + public SaveDataFormatType FormatType; public long DataSize; public long JournalSize; public long CommitId; @@ -371,7 +371,7 @@ internal static class SaveDataTypesValidity { public static bool IsValid(in SaveDataAttribute attribute) { - return IsValid(in attribute.Type)&& IsValid(in attribute.Rank); + return IsValid(in attribute.Type) && IsValid(in attribute.Rank); } public static bool IsValid(in SaveDataCreationInfo creationInfo) diff --git a/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs b/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs index 09433d0d..6492747a 100644 --- a/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs +++ b/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs @@ -4,8 +4,9 @@ namespace LibHac.Fs.Impl; internal readonly struct SaveDataMetaPolicy { + internal const int ThumbnailFileSize = 0x40060; + private readonly SaveDataType _type; - private const int ThumbnailFileSize = 0x40060; public SaveDataMetaPolicy(SaveDataType saveType) { @@ -39,4 +40,4 @@ internal readonly struct SaveDataMetaPolicy GenerateMetaInfo(out SaveDataMetaInfo metaInfo); return metaInfo.Type; } -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/FileSystemProxyImpl.cs b/src/LibHac/FsSrv/FileSystemProxyImpl.cs index 60e783a4..71ae758f 100644 --- a/src/LibHac/FsSrv/FileSystemProxyImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyImpl.cs @@ -352,6 +352,14 @@ public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader return saveFsService.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo); } + public Result CreateSaveDataFileSystemWithCreationInfo2(in SaveDataCreationInfo2 creationInfo) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CreateSaveDataFileSystemWithCreationInfo2(in creationInfo); + } + public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize) { diff --git a/src/LibHac/FsSrv/FileSystemServerInitializer.cs b/src/LibHac/FsSrv/FileSystemServerInitializer.cs index 0f02dcf8..0e131516 100644 --- a/src/LibHac/FsSrv/FileSystemServerInitializer.cs +++ b/src/LibHac/FsSrv/FileSystemServerInitializer.cs @@ -136,7 +136,7 @@ public static class FileSystemServerInitializer saveFsServiceConfig.BufferManager = bufferManager; saveFsServiceConfig.GenerateRandomData = config.RandomGenerator; saveFsServiceConfig.IsPseudoSaveData = () => true; - saveFsServiceConfig.MaxSaveFsCacheCount = 1; + saveFsServiceConfig.SaveDataFileSystemCacheCount = 1; saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager; saveFsServiceConfig.FsServer = server; diff --git a/src/LibHac/FsSrv/ISaveDataIndexerManager.cs b/src/LibHac/FsSrv/ISaveDataIndexerManager.cs index 7a4fb4f4..62bfe637 100644 --- a/src/LibHac/FsSrv/ISaveDataIndexerManager.cs +++ b/src/LibHac/FsSrv/ISaveDataIndexerManager.cs @@ -5,7 +5,7 @@ namespace LibHac.FsSrv; public interface ISaveDataIndexerManager { - Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, out bool neededInit, SaveDataSpaceId spaceId); + Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, out bool isInitialOpen, SaveDataSpaceId spaceId); void ResetIndexer(SaveDataSpaceId spaceId); void InvalidateIndexer(SaveDataSpaceId spaceId); } diff --git a/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs index 2e0a35a0..6b73228e 100644 --- a/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs +++ b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs @@ -2,15 +2,15 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Os; using LibHac.Util; -using IFileSf = LibHac.FsSrv.Sf.IFile; namespace LibHac.FsSrv.Impl; public interface ISaveDataTransferCoreInterface : IDisposable { - Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId); - Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize); + Result GetFreeSpaceSizeForSaveData(out long outFreeSpaceSize, SaveDataSpaceId spaceId); + Result QuerySaveDataTotalSize(out long outTotalSize, long dataSize, long journalSize); Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId); Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized); Result GetSaveDataInfo(out SaveDataInfo saveInfo, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); @@ -18,13 +18,15 @@ public interface ISaveDataTransferCoreInterface : IDisposable Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp); Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); - Result OpenSaveDataFile(ref SharedRef file, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType metaType); - Result OpenSaveDataMetaFileRaw(ref SharedRef file, SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode); + Result OpenSaveDataFile(ref SharedRef oufFile, SaveDataSpaceId spaceId, ulong saveDataId, OpenMode mode); + Result OpenSaveDataMetaFileRaw(ref SharedRef outFile, SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode); Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey); + Result OpenSaveDataFileSystemCore(ref SharedRef outFileSystem, out ulong outSaveDataId, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData); Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize); Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2); Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state); Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank); Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, SaveDataSpaceId spaceId); -} + bool IsProhibited(ref UniqueLock outLock, ApplicationId applicationId); +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs index ce2c856f..af60437d 100644 --- a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs +++ b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs @@ -46,7 +46,7 @@ internal class MultiCommitManager : IMultiCommitManager { private const int MaxFileSystemCount = 10; - public const ulong ProgramId = 0x0100000000000000; + public const ulong ProgramId = 0; public const ulong SaveDataId = 0x8000000000000001; private const long SaveDataSize = 0xC000; private const long SaveJournalSize = 0xC000; diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index bfa316c2..b9bd2f43 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -1,13 +1,17 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Diag; using LibHac.Fs; +using LibHac.Fs.Impl; using LibHac.Fs.Shim; using LibHac.FsSrv.Impl; using LibHac.FsSrv.Sf; using LibHac.FsSystem; using LibHac.Kvdb; using LibHac.Ncm; +using LibHac.Os; using LibHac.Sf; using LibHac.Util; @@ -28,7 +32,7 @@ namespace LibHac.FsSrv; /// /// FS will have one instance of this class for every connected process. /// The FS permissions of the calling process are checked on every function call. -///
Based on FS 10.2.0 (nnSdk 10.6.0)
+///
Based on FS 14.1.0 (nnSdk 14.3.0) internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface { private const int OpenEntrySemaphoreCount = 256; @@ -51,14 +55,6 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave private SharedRef GetSharedMultiCommitInterfaceFromThis() => SharedRef.Create(in _selfReference); - public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) - { - _serviceImpl = serviceImpl; - _processId = processId; - _openEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); - _saveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); - } - public static SharedRef CreateShared(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) { // Create the service @@ -80,19 +76,21 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave _saveService = SharedRef.CreateMove(ref saveService); } - public Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore) - { - return _saveService.Get.TryAcquireSaveDataEntryOpenCountSemaphore(ref outSemaphore); - } - public void Dispose() { _saveService.Destroy(); } + + public Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore) + { + return _saveService.Get.TryAcquireSaveDataEntryOpenCountSemaphore(ref outSemaphore).Ret(); + } } - private Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) + private static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) { + Assert.SdkNotNull(programInfo); + switch (spaceId) { case SaveDataSpaceId.System: @@ -119,30 +117,29 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { public delegate Result ExtraDataGetter(out SaveDataExtraData extraData); - public static Result CheckCreate(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, - ProgramInfo programInfo, ProgramId programId) + public static Result CheckCreate(in SaveDataAttribute attribute, ulong ownerId, ProgramInfo programInfo, + ProgramId programId) { AccessControl accessControl = programInfo.AccessControl; if (SaveDataProperties.IsSystemSaveData(attribute.Type)) { - if (creationInfo.OwnerId == programInfo.ProgramIdValue) + if (ownerId != programInfo.ProgramIdValue) { - bool canAccess = accessControl.CanCall(OperationType.CreateSystemSaveData); + // If the program doesn't own the created save data it needs either the permission to create + // any system save data or it needs explicit access to the owner's save data. + Accessibility accessibility = accessControl.GetAccessibilitySaveDataOwnedBy(ownerId); + + bool canAccess = + accessControl.CanCall(OperationType.CreateSystemSaveData) && + accessControl.CanCall(OperationType.CreateOthersSystemSaveData) || accessibility.CanWrite; if (!canAccess) return ResultFs.PermissionDenied.Log(); } else { - // If the program doesn't own the created save data it needs either the permission to create - // any system save data or it needs explicit access to the owner's save data. - Accessibility accessibility = - accessControl.GetAccessibilitySaveDataOwnedBy(creationInfo.OwnerId); - - bool canAccess = - accessControl.CanCall(OperationType.CreateSystemSaveData) && - accessControl.CanCall(OperationType.CreateOthersSystemSaveData) || accessibility.CanWrite; + bool canAccess = accessControl.CanCall(OperationType.CreateSystemSaveData); if (!canAccess) return ResultFs.PermissionDenied.Log(); @@ -159,9 +156,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } else { - Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - creationInfo.OwnerId); - if (rc.IsFailure()) return rc; + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, ownerId); + if (rc.IsFailure()) return rc.Miss(); // If none of the above conditions apply, the program needs write access to the owner's save data. // The program also needs either permission to create any save data, or it must be creating its own @@ -169,7 +165,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave bool canAccess = accessControl.CanCall(OperationType.CreateSaveData); if (accessibility.CanWrite && - attribute.ProgramId == programId || attribute.ProgramId.Value == creationInfo.OwnerId) + attribute.ProgramId == programId || attribute.ProgramId.Value == ownerId) { canAccess |= accessControl.CanCall(OperationType.CreateOwnSaveData); } @@ -213,10 +209,10 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave AccessControl accessControl = programInfo.AccessControl; Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - // Note: This is correct. Even if a program only has read accessibility to another program's save data, - // Nintendo gives it full read/write accessibility as of FS 12.0.0 + // Note: This is correct. Even if a program only has read accessibility to a save data, + // Nintendo allows opening it with read/write accessibility as of FS 14.0.0 if (accessibility.CanRead || accessibility.CanWrite) return Result.Success; @@ -257,7 +253,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Otherwise the program needs the DeleteOwnSaveData permission and write access to the save Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (accessControl.CanCall(OperationType.DeleteOwnSaveData) && accessibility.CanWrite) { @@ -267,6 +263,65 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return ResultFs.PermissionDenied.Log(); } + public static Result CheckExtend(SaveDataSpaceId spaceId, in SaveDataAttribute attribute, + ProgramInfo programInfo, ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.ProperSystem: + case SaveDataSpaceId.SafeMode: + { + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + extraDataGetter); + if (rc.IsFailure()) return rc.Miss(); + + // The program needs the ExtendSystemSaveData permission and either one of + // read/write access to the save or the ExtendOthersSystemSaveData permission + bool canAccess = accessControl.CanCall(OperationType.ExtendSystemSaveData) && + (accessibility.CanRead && accessibility.CanWrite || + accessControl.CanCall(OperationType.ExtendOthersSystemSaveData)); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + break; + } + case SaveDataSpaceId.User: + case SaveDataSpaceId.SdUser: + { + bool canAccess = accessControl.CanCall(OperationType.ExtendSaveData); + + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + extraDataGetter); + if (rc.IsFailure()) return rc.Miss(); + + if (attribute.ProgramId == programInfo.ProgramId || accessibility.CanRead) + { + canAccess |= accessControl.CanCall(OperationType.ExtendOwnSaveData); + + bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData) + && attribute.Type == SaveDataType.Account + && attribute.UserId == UserId.InvalidId; + + canAccess |= hasDebugAccess; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + break; + } + default: + return ResultFs.InvalidSaveDataSpaceId.Log(); + } + + return Result.Success; + } + public static Result CheckReadExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, ProgramInfo programInfo, ExtraDataGetter extraDataGetter) { @@ -274,9 +329,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave bool canAccess = accessControl.CanCall(OperationType.ReadSaveDataFileSystemExtraData); - Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (rc.IsFailure()) return rc; + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); + if (rc.IsFailure()) return rc.Miss(); SaveDataExtraData emptyMask = default; SaveDataExtraData maskWithoutRestoreFlag = mask; @@ -293,9 +347,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { canAccess |= accessibility.CanRead; } - else if (attribute.ProgramId == programInfo.ProgramId) + else if (attribute.ProgramId == programInfo.ProgramId || accessibility.CanRead) { canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData); + + bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData) + && attribute.Type == SaveDataType.Account + && attribute.UserId == UserId.InvalidId; + + canAccess |= hasDebugAccess; } if (!canAccess) @@ -318,7 +378,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); canAccess |= accessibility.CanWrite; } @@ -327,7 +387,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); canAccess |= accessibility.CanWrite; } @@ -379,7 +439,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (programInfo.ProgramId == filter.Attribute.ProgramId) { - canAccess = programInfo.AccessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); + AccessControl accessControl = programInfo.AccessControl; + canAccess = accessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); + + bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData) + && filter.Attribute.Type == SaveDataType.Account + && filter.Attribute.UserId == UserId.InvalidId; + + canAccess |= hasDebugAccess; } else { @@ -392,6 +459,39 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } + public static Result CheckOpenProhibiter(ProgramId programId, ProgramInfo programInfo) + { + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, programId.Value); + if (rc.IsFailure()) return rc.Miss(); + + bool canAccess = programInfo.AccessControl.CanCall(OperationType.OpenSaveDataTransferProhibiter); + + if (programInfo.ProgramId == programId || accessibility.CanRead) + { + canAccess |= programInfo.AccessControl.CanCall(OperationType.OpenOwnSaveDataTransferProhibiter); + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, + ulong ownerId) + { + if (ownerId == programInfo.ProgramIdValue) + { + // A program always has full access to its own save data + accessibility = new Accessibility(true, true); + } + else + { + accessibility = programInfo.AccessControl.GetAccessibilitySaveDataOwnedBy(ownerId); + } + + return Result.Success; + } private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, ExtraDataGetter extraDataGetter) @@ -411,48 +511,186 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } // Allow access when opening a directory save FS on a dev console - if (extraData.OwnerId == 0 && extraData.DataSize == 0 && extraData.JournalSize == 0 && + if (IsDirectorySaveDataExtraData(in extraData) && programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) { accessibility = new Accessibility(true, true); return Result.Success; } - return GetAccessibilityForSaveData(out accessibility, programInfo, extraData.OwnerId); - } - - private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, - ulong ownerId) - { - if (ownerId == programInfo.ProgramIdValue) - { - // A program always has full access to its own save data - accessibility = new Accessibility(true, true); - } - else - { - accessibility = programInfo.AccessControl.GetAccessibilitySaveDataOwnedBy(ownerId); - } - - return Result.Success; + return GetAccessibilityForSaveData(out accessibility, programInfo, extraData.OwnerId).Ret(); } } - public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) + public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) { - throw new NotImplementedException(); + _serviceImpl = serviceImpl; + _processId = processId; + using var path = new Path(); + _openEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); + _saveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); + } + + public void Dispose() + { + _openEntryCountSemaphore.Dispose(); + _saveDataMountCountSemaphore.Dispose(); + _selfReference.Destroy(); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + var registry = new ProgramRegistryImpl(_serviceImpl.FsServer); + return registry.GetProgramInfo(out programInfo, _processId); + } + + private Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + var registry = new ProgramRegistryImpl(_serviceImpl.FsServer); + return registry.GetProgramInfoByProgramId(out programInfo, programId); + } + + private static SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode + ? SaveDataSpaceId.System + : spaceId; + } + + private static bool IsStaticSaveDataIdValueRange(ulong id) + { + return unchecked((long)id) < 0; + } + + private static bool IsDirectorySaveDataExtraData(in SaveDataExtraData extraData) + { + return extraData.OwnerId == 0 && extraData.DataSize == 0 && extraData.JournalSize == 0; + } + + private static void ModifySaveDataExtraData(ref SaveDataExtraData currentExtraData, in SaveDataExtraData extraData, + in SaveDataExtraData extraDataMask) + { + Span currentExtraDataBytes = SpanHelpers.AsByteSpan(ref currentExtraData); + ReadOnlySpan extraDataBytes = SpanHelpers.AsReadOnlyByteSpan(in extraData); + ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); + + for (int i = 0; i < Unsafe.SizeOf(); i++) + { + currentExtraDataBytes[i] = (byte)(extraDataBytes[i] & extraDataMaskBytes[i] | + currentExtraDataBytes[i] & ~extraDataMaskBytes[i]); + } + } + + private static void MaskExtraData(ref SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + Span extraDataBytes = SpanHelpers.AsByteSpan(ref extraData); + ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); + + for (int i = 0; i < Unsafe.SizeOf(); i++) + { + extraDataBytes[i] &= extraDataMaskBytes[i]; + } + } + + private static StorageLayoutType DecidePossibleStorageFlag(SaveDataType type, SaveDataSpaceId spaceId) + { + if (type == SaveDataType.Cache || type == SaveDataType.Bcat) + return StorageLayoutType.Bis | StorageLayoutType.SdCard | StorageLayoutType.Usb; + + if (type == SaveDataType.System && (spaceId == SaveDataSpaceId.SdSystem || spaceId == SaveDataSpaceId.SdUser)) + return StorageLayoutType.SdCard | StorageLayoutType.Usb; + + return StorageLayoutType.Bis; + } + + private static SaveDataFormatType GetSaveDataFormatType(in SaveDataAttribute attribute) + { + return SaveDataProperties.IsJournalingSupported(attribute.Type) + ? SaveDataFormatType.Normal + : SaveDataFormatType.NoJournal; + } + + public Result GetFreeSpaceSizeForSaveData(out long outFreeSpaceSize, SaveDataSpaceId spaceId) + { + UnsafeHelpers.SkipParamInit(out outFreeSpaceSize); + + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); + using var fileSystem = new SharedRef(); + + Result rc = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + using var pathRoot = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathRoot.Ref(), new[] { (byte)'/' }); + if (rc.IsFailure()) return rc.Miss(); + + fileSystem.Get.GetFreeSpaceSize(out long freeSpaceSize, in pathRoot); + if (rc.IsFailure()) return rc.Miss(); + + outFreeSpaceSize = freeSpaceSize; + return Result.Success; } public Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds) { - throw new NotImplementedException(); + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc.Miss(); + + // This function only operates on the system save data space, so the caller + // must have permissions to delete system save data + AccessControl accessControl = programInfo.AccessControl; + + bool canAccess = accessControl.CanCall(OperationType.DeleteSaveData) && + accessControl.CanCall(OperationType.DeleteSystemSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + bool success = false; + + using var accessor = new UniqueRef(); + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); + if (rc.IsFailure()) return rc.Miss(); + + ReadOnlySpan ids = MemoryMarshal.Cast(saveDataIds.Buffer); + + try + { + // Try to set the state of all the save IDs as being marked for deletion. + for (int i = 0; i < ids.Length; i++) + { + rc = accessor.Get.GetInterface().SetState(ids[i], SaveDataState.MarkedForDeletion); + if (rc.IsFailure()) return rc.Miss(); + } + + rc = accessor.Get.GetInterface().Commit(); + if (rc.IsFailure()) return rc.Miss(); + + success = true; + return Result.Success; + } + finally + { + // Rollback the operation if something goes wrong. + if (!success) + { + rc = accessor.Get.GetInterface().Rollback(); + if (rc.IsFailure()) + { + Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, + "[fs] Error: Failed to rollback save data indexer.\n".ToU8Span()); + } + } + } } public Result DeleteSaveDataFileSystem(ulong saveDataId) { using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis); - return DeleteSaveDataFileSystemCommon(SaveDataSpaceId.System, saveDataId); + return DeleteSaveDataFileSystemCommon(SaveDataSpaceId.System, saveDataId).Ret(); } private Result DeleteSaveDataFileSystemCore(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile) @@ -460,13 +698,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Delete the save data's meta files Result rc = _serviceImpl.DeleteAllSaveDataMetas(saveDataId, spaceId); if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) - return rc; + return rc.Miss(); // Delete the actual save data. using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); rc = _serviceImpl.DeleteSaveDataFileSystem(spaceId, saveDataId, wipeSaveFile, in saveDataRootPath); if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) - return rc; + return rc.Miss(); return Result.Success; } @@ -475,7 +713,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); - return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, saveDataId); + return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, saveDataId).Ret(); } private Result DeleteSaveDataFileSystemBySaveDataSpaceIdCore(SaveDataSpaceId spaceId, ulong saveDataId) @@ -484,16 +722,16 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { using var accessor = new UniqueRef(); Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (value.SpaceId != ConvertToRealSpaceId(spaceId)) return ResultFs.TargetNotFound.Log(); } - return DeleteSaveDataFileSystemCommon(spaceId, saveDataId); + return DeleteSaveDataFileSystemCommon(spaceId, saveDataId).Ret(); } public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, @@ -504,76 +742,76 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rs = GetSaveDataInfo(out SaveDataInfo info, spaceId, in attribute); if (rs.IsFailure()) return rs; - return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, info.SaveDataId); + return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, info.SaveDataId).Ret(); } private Result DeleteSaveDataFileSystemCommon(SaveDataSpaceId spaceId, ulong saveDataId) { Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var accessor = new UniqueRef(); - SaveDataSpaceId actualSpaceId; + SaveDataSpaceId targetSpaceId; - // Only the FS process may delete the save indexer's save data. - if (saveDataId == SaveIndexerId) - { - if (!_serviceImpl.FsServer.IsCurrentProcess(_processId)) - return ResultFs.PermissionDenied.Log(); - - actualSpaceId = spaceId; - } - else + if (saveDataId != SaveIndexerId) { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); // Get the actual space ID of this save. if (spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode) { - actualSpaceId = spaceId; + targetSpaceId = spaceId; } else { rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - actualSpaceId = value.SpaceId; + targetSpaceId = value.SpaceId; } // Check if the caller has permission to delete this save. rc = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); Result GetExtraData(out SaveDataExtraData data) => - _serviceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type, + _serviceImpl.ReadSaveDataFileSystemExtraData(out data, targetSpaceId, saveDataId, key.Type, _saveDataRootPath.DangerousGetPath()); rc = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, GetExtraData); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); // Pre-delete checks successful. Put the save in the Processing state until deletion is finished. rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Processing); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().Commit(); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); + } + else + { + // Only the FS process may delete the save indexer's save data. + if (!_serviceImpl.FsServer.IsCurrentProcess(_processId)) + return ResultFs.PermissionDenied.Log(); + + targetSpaceId = spaceId; } // Do the actual deletion. - rc = DeleteSaveDataFileSystemCore(actualSpaceId, saveDataId, false); - if (rc.IsFailure()) return rc; + rc = DeleteSaveDataFileSystemCore(targetSpaceId, saveDataId, wipeSaveFile: false); + if (rc.IsFailure()) return rc.Miss(); // Remove the save data from the indexer. // The indexer doesn't track itself, so skip if deleting its save data. if (saveDataId != SaveIndexerId) { rc = accessor.Get.GetInterface().Delete(saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().Commit(); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } return Result.Success; @@ -607,7 +845,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result SetSaveDataRootPath(in FspPath path) { Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) return ResultFs.PermissionDenied.Log(); @@ -616,13 +854,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (path.Str[0] == NullTerminator) { - rc = saveDataRootPath.Initialize(new[] { (byte)'/' }); - if (rc.IsFailure()) return rc; + rc = saveDataRootPath.Initialize(new[] { (byte)'.' }); + if (rc.IsFailure()) return rc.Miss(); } else { rc = saveDataRootPath.InitializeWithReplaceUnc(path.Str); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } var pathFlags = new PathFlags(); @@ -631,7 +869,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave pathFlags.AllowEmptyPath(); rc = saveDataRootPath.Normalize(pathFlags); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); _saveDataRootPath.Initialize(in saveDataRootPath); @@ -641,14 +879,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result UnsetSaveDataRootPath() { Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) return ResultFs.PermissionDenied.Log(); using var saveDataRootPath = new Path(); rc = saveDataRootPath.InitializeAsEmpty(); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); _saveDataRootPath.Initialize(in saveDataRootPath); @@ -664,10 +902,20 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return ResultFs.NotImplemented.Log(); } - public Result OpenSaveDataFile(ref SharedRef file, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute, SaveDataMetaType metaType) + public Result OpenSaveDataFile(ref SharedRef outFile, SaveDataSpaceId spaceId, ulong saveDataId, + OpenMode openMode) { - throw new NotImplementedException(); + var storageFlag = StorageLayoutType.NonGameCard; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + using var file = new SharedRef(); + Result rc = _serviceImpl.OpenSaveDataFile(ref file.Ref(), spaceId, saveDataId, openMode); + if (rc.IsFailure()) return rc.Miss(); + + using var typeSetFile = new SharedRef(new StorageLayoutTypeSetFile(ref file.Ref(), storageFlag)); + + outFile.SetByMove(ref typeSetFile.Ref()); + return Result.Success; } public Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId) @@ -675,23 +923,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave throw new NotImplementedException(); } - private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, - in SaveDataMetaInfo metaInfo, in Optional hashSalt) - { - return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, false); - } - - private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, - in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized) + private Result CreateSaveDataFileSystemCore(in SaveDataCreationInfo2 creationInfo, bool leaveUnfinalized) { ulong saveDataId = 0; bool creating = false; bool accessorInitialized = false; Result rc; - StorageLayoutType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + StorageLayoutType storageFlag = DecidePossibleStorageFlag(creationInfo.Attribute.Type, creationInfo.SpaceId); using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); using var accessor = new UniqueRef(); @@ -699,7 +938,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave try { // Add the new save data to the save indexer - if (attribute.StaticSaveDataId == SaveIndexerId) + if (creationInfo.Attribute.StaticSaveDataId == SaveIndexerId) { // The save indexer doesn't index itself saveDataId = SaveIndexerId; @@ -715,17 +954,17 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave else { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), creationInfo.SpaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); accessorInitialized = true; - SaveDataAttribute indexerKey = attribute; + SaveDataAttribute indexerKey = creationInfo.Attribute; // Add the new value to the indexer - if (attribute.StaticSaveDataId != 0 && attribute.UserId == InvalidUserId) + if (creationInfo.Attribute.StaticSaveDataId != 0 && creationInfo.Attribute.UserId == InvalidUserId) { // If a static save data ID is specified that ID is always used - saveDataId = attribute.StaticSaveDataId; + saveDataId = creationInfo.Attribute.StaticSaveDataId; rc = accessor.Get.GetInterface().PutStaticSaveDataIdIndex(in indexerKey); } @@ -734,7 +973,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // The save indexer has an upper limit on the number of entries it can hold. // A few of those entries are reserved for system saves so the system doesn't // end up in a situation where it can't create a required system save. - if (!SaveDataProperties.CanUseIndexerReservedArea(attribute.Type)) + if (!SaveDataProperties.CanUseIndexerReservedArea(creationInfo.Attribute.Type)) { if (accessor.Get.GetInterface().IsRemainedReservedOnly()) { @@ -746,6 +985,49 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave rc = accessor.Get.GetInterface().Publish(out saveDataId, in indexerKey); } + if (rc.IsSuccess()) + { + creating = true; + + // Set the state, space ID and size on the new save indexer entry. + rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Processing); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().SetSpaceId(saveDataId, ConvertToRealSpaceId(creationInfo.SpaceId)); + if (rc.IsFailure()) return rc.Miss(); + + rc = QuerySaveDataTotalSize(out long saveDataSize, creationInfo.Size, creationInfo.JournalSize); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().SetSize(saveDataId, saveDataSize); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().Commit(); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + if (ResultFs.AlreadyExists.Includes(rc)) + { + // The save already exists. Ensure the thumbnail meta file exists if needed. + if (creationInfo.MetaType == SaveDataMetaType.Thumbnail) + { + Result rcMeta = accessor.Get.GetInterface().Get(out SaveDataIndexerValue value, in indexerKey); + if (rcMeta.IsFailure()) return rcMeta.Miss(); + + rcMeta = CreateEmptyThumbnailFile(creationInfo.SpaceId, value.SaveDataId); + + // Allow the thumbnail file to already exist + if (rcMeta.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rcMeta)) + return rcMeta.Miss(); + } + + return ResultFs.PathAlreadyExists.LogConverted(rc); + } + + return rc.Miss(); + } + if (rc.IsFailure()) { if (ResultFs.AlreadyExists.Includes(rc)) @@ -755,66 +1037,47 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return rc; } - - creating = true; - - // Set the state, space ID and size on the new save indexer entry. - rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Processing); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.GetInterface().SetSpaceId(saveDataId, ConvertToRealSpaceId(creationInfo.SpaceId)); - if (rc.IsFailure()) return rc; - - rc = QuerySaveDataTotalSize(out long saveDataSize, creationInfo.Size, creationInfo.JournalSize); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.GetInterface().SetSize(saveDataId, saveDataSize); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.GetInterface().Commit(); - if (rc.IsFailure()) return rc; } - // After the new save was added to the save indexer, create the save data file or directory. + // After the new save is added to the save indexer, create the save data file or directory. using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, - in saveDataRootPath, in hashSalt, false); + rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in creationInfo, in saveDataRootPath, skipFormat: false); if (rc.IsFailure()) { - if (!ResultFs.PathAlreadyExists.Includes(rc)) return rc; + if (!ResultFs.PathAlreadyExists.Includes(rc)) + return rc.Miss(); - // Handle the situation where a save exists on disk but not in the save indexer. + // The save exists on the file system but not in the save indexer. // Delete the save data and try creating it again. rc = DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, - in saveDataRootPath, in hashSalt, false); - if (rc.IsFailure()) return rc; + rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in creationInfo, in saveDataRootPath, + skipFormat: false); + if (rc.IsFailure()) return rc.Miss(); } - if (metaInfo.Type != SaveDataMetaType.None) + if (creationInfo.MetaType != SaveDataMetaType.None) { // Create the requested save data meta file. - rc = _serviceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, metaInfo.Type, - metaInfo.Size); - if (rc.IsFailure()) return rc; + rc = _serviceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, creationInfo.MetaType, + creationInfo.MetaSize); + if (rc.IsFailure()) return rc.Miss(); - if (metaInfo.Type == SaveDataMetaType.Thumbnail) + if (creationInfo.MetaType == SaveDataMetaType.Thumbnail) { using var metaFile = new UniqueRef(); rc = _serviceImpl.OpenSaveDataMeta(ref metaFile.Ref(), saveDataId, creationInfo.SpaceId, - metaInfo.Type); - - if (rc.IsFailure()) return rc; + creationInfo.MetaType); + if (rc.IsFailure()) return rc.Miss(); // The first 0x20 bytes of thumbnail meta files is an SHA-256 hash. // Zero the hash to indicate that it's currently unused. ReadOnlySpan metaFileHash = stackalloc byte[0x20]; rc = metaFile.Get.Write(0, metaFileHash, WriteOption.Flush); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } } @@ -825,14 +1088,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } // The indexer's save data isn't tracked, so we don't need to update its state. - if (attribute.StaticSaveDataId != SaveIndexerId) + if (creationInfo.Attribute.StaticSaveDataId != SaveIndexerId) { // Mark the save data as being successfully created rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Normal); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().Commit(); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } creating = false; @@ -843,7 +1106,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Revert changes if an error happened in the middle of creation if (creating) { - DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false).IgnoreResult(); + rc = DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false); + if (rc.IsFailure()) + { + Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, GetRollbackErrorMessage(rc)); + } if (accessorInitialized && saveDataId != SaveIndexerId) { @@ -851,14 +1118,53 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId) { - accessor.Get.GetInterface().Delete(saveDataId).IgnoreResult(); - accessor.Get.GetInterface().Commit().IgnoreResult(); + accessor.Get.GetInterface().Delete(saveDataId); + if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) + { + Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, GetRollbackErrorMessage(rc)); + } + + accessor.Get.GetInterface().Commit(); + if (rc.IsFailure()) + { + Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, GetRollbackErrorMessage(rc)); + } } } } + + static byte[] GetRollbackErrorMessage(Result result) + { + string message = $"[fs] Error : Failed to rollback save data creation. ({result.Value:x})\n"; + return System.Text.Encoding.UTF8.GetBytes(message); + } } } + private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized) + { + // Changed: The original allocates a SaveDataCreationInfo2 on the heap for some reason + Result rc = SaveDataCreationInfo2.Make(out SaveDataCreationInfo2 newCreationInfo, in attribute, + creationInfo.Size, creationInfo.JournalSize, creationInfo.BlockSize, creationInfo.OwnerId, + creationInfo.Flags, creationInfo.SpaceId, GetSaveDataFormatType(in attribute)); + if (rc.IsFailure()) return rc.Miss(); + + newCreationInfo.IsHashSaltEnabled = hashSalt.HasValue; + if (hashSalt.HasValue) + { + newCreationInfo.HashSalt = hashSalt.ValueRo; + } + + newCreationInfo.MetaType = metaInfo.Type; + newCreationInfo.MetaSize = metaInfo.Size; + + rc = CreateSaveDataFileSystemCore(in newCreationInfo, leaveUnfinalized); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + public Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) { UnsafeHelpers.SkipParamInit(out info); @@ -867,77 +1173,112 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var accessor = new UniqueRef(); Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().Get(out SaveDataIndexerValue value, in attribute); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); SaveDataIndexer.GenerateSaveDataInfo(out info, in attribute, in value); return Result.Success; } - public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) + public Result QuerySaveDataTotalSize(out long outTotalSize, long dataSize, long journalSize) { - UnsafeHelpers.SkipParamInit(out totalSize); + UnsafeHelpers.SkipParamInit(out outTotalSize); if (dataSize < 0 || journalSize < 0) return ResultFs.InvalidSize.Log(); - return _serviceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize); + Result rc = _serviceImpl.QuerySaveDataTotalSize(out long totalSize, SaveDataBlockSize, dataSize, journalSize); + if (rc.IsFailure()) return rc.Miss(); + + outTotalSize = totalSize; + return Result.Success; } public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo) { - var hashSalt = new Optional(); + // Changed: The original allocates a SaveDataCreationInfo2 on the heap for some reason + Result rc = SaveDataCreationInfo2.Make(out SaveDataCreationInfo2 newCreationInfo, in attribute, + creationInfo.Size, creationInfo.JournalSize, creationInfo.BlockSize, creationInfo.OwnerId, + creationInfo.Flags, creationInfo.SpaceId, GetSaveDataFormatType(in attribute)); + if (rc.IsFailure()) return rc.Miss(); - return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, in hashSalt); + newCreationInfo.IsHashSaltEnabled = false; + newCreationInfo.MetaType = metaInfo.Type; + newCreationInfo.MetaSize = metaInfo.Size; + + rc = CreateSaveDataFileSystemWithCreationInfo2(in newCreationInfo); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; } public Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) { - var optionalHashSalt = new Optional(in hashSalt); + // Changed: The original allocates a SaveDataCreationInfo2 on the heap for some reason + Result rc = SaveDataCreationInfo2.Make(out SaveDataCreationInfo2 newCreationInfo, in attribute, + creationInfo.Size, creationInfo.JournalSize, creationInfo.BlockSize, creationInfo.OwnerId, + creationInfo.Flags, creationInfo.SpaceId, GetSaveDataFormatType(in attribute)); + if (rc.IsFailure()) return rc.Miss(); - return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, - in optionalHashSalt); + newCreationInfo.IsHashSaltEnabled = true; + newCreationInfo.HashSalt = hashSalt; + newCreationInfo.MetaType = metaInfo.Type; + newCreationInfo.MetaSize = metaInfo.Size; + + rc = CreateSaveDataFileSystemWithCreationInfo2(in newCreationInfo); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; } - private Result CreateSaveDataFileSystemWithHashSaltImpl(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt) + public Result CreateSaveDataFileSystemWithCreationInfo2(in SaveDataCreationInfo2 creationInfo) { - StorageLayoutType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + StorageLayoutType storageFlag = DecidePossibleStorageFlag(creationInfo.Attribute.Type, creationInfo.SpaceId); using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - SaveDataAttribute tempAttribute = attribute; - SaveDataCreationInfo tempCreationInfo = creationInfo; - - if (hashSalt.HasValue && !programInfo.AccessControl.CanCall(OperationType.CreateSaveDataWithHashSalt)) + if (creationInfo.IsHashSaltEnabled && + !programInfo.AccessControl.CanCall(OperationType.CreateSaveDataWithHashSalt)) { return ResultFs.PermissionDenied.Log(); } - ProgramId programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in creationInfo, programInfo, programId); - if (rc.IsFailure()) return rc; + ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + rc = SaveDataAccessibilityChecker.CheckCreate(in creationInfo.Attribute, creationInfo.OwnerId, programInfo, + resolvedProgramId); + if (rc.IsFailure()) return rc.Miss(); - if (tempAttribute.Type == SaveDataType.Account && tempAttribute.UserId == InvalidUserId) + if (creationInfo.Attribute.Type == SaveDataType.Account && creationInfo.Attribute.UserId == InvalidUserId) { - if (tempAttribute.ProgramId == ProgramId.InvalidId) + // Changed: The original allocates a SaveDataCreationInfo2 on the heap for some reason + SaveDataCreationInfo2 newCreationInfo = creationInfo; + + if (newCreationInfo.Attribute.ProgramId == ProgramId.InvalidId) { - tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + newCreationInfo.Attribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); } - if (tempCreationInfo.OwnerId == 0) + if (newCreationInfo.OwnerId == 0) { - tempCreationInfo.OwnerId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; + newCreationInfo.OwnerId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; } + + rc = CreateSaveDataFileSystemCore(in newCreationInfo, leaveUnfinalized: false); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + rc = CreateSaveDataFileSystemCore(in creationInfo, leaveUnfinalized: false); + if (rc.IsFailure()) return rc.Miss(); } - return CreateSaveDataFileSystemCore(in tempAttribute, in tempCreationInfo, in metaInfo, in hashSalt); + return Result.Success; } public Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, @@ -947,71 +1288,189 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (!IsStaticSaveDataIdValueRange(attribute.StaticSaveDataId)) return ResultFs.InvalidArgument.Log(); - SaveDataCreationInfo tempCreationInfo = creationInfo; + ulong ownerId = creationInfo.OwnerId == 0 ? programInfo.ProgramIdValue : creationInfo.OwnerId; - if (tempCreationInfo.OwnerId == 0) - { - tempCreationInfo.OwnerId = programInfo.ProgramIdValue; - } + rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, ownerId, programInfo, programInfo.ProgramId); + if (rc.IsFailure()) return rc.Miss(); - rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in tempCreationInfo, programInfo, - programInfo.ProgramId); - if (rc.IsFailure()) return rc; + // Changed: The original allocates a SaveDataCreationInfo2 on the heap for some reason + rc = SaveDataCreationInfo2.Make(out SaveDataCreationInfo2 newCreationInfo, in attribute, creationInfo.Size, + creationInfo.JournalSize, creationInfo.BlockSize, ownerId, creationInfo.Flags, creationInfo.SpaceId, + GetSaveDataFormatType(in attribute)); + if (rc.IsFailure()) return rc.Miss(); + + newCreationInfo.IsHashSaltEnabled = false; // Static system saves don't usually have meta files - SaveDataMetaInfo metaInfo = default; - Optional hashSalt = default; + newCreationInfo.MetaType = SaveDataMetaType.None; + newCreationInfo.MetaSize = 0; - return CreateSaveDataFileSystemCore(in attribute, in tempCreationInfo, in metaInfo, in hashSalt); + rc = CreateSaveDataFileSystemCore(in newCreationInfo, leaveUnfinalized: false); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; } - public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, - long journalSize) + public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize) { - throw new NotImplementedException(); + SaveDataAttribute key; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc.Miss(); + + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().GetKey(out key, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + Result ReadExtraData(out SaveDataExtraData data) + { + using Path savePath = _saveDataRootPath.DangerousGetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, key.Type, in savePath); + } + + // Check if we have permissions to extend this save data. + rc = SaveDataAccessibilityChecker.CheckExtend(spaceId, in key, programInfo, ReadExtraData); + if (rc.IsFailure()) return rc.Miss(); + + // Check that the save data is in a state that we can start extending it. + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + switch (value.State) + { + case SaveDataState.Normal: + break; + case SaveDataState.Processing: + case SaveDataState.MarkedForDeletion: + case SaveDataState.Extending: + return ResultFs.TargetLocked.Log(); + case SaveDataState.State2: + return ResultFs.SaveDataCorrupted.Log(); + default: + return ResultFs.InvalidSaveDataState.Log(); + } + + // Mark the save data as being extended. + rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Extending); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().Commit(); + if (rc.IsFailure()) return rc.Miss(); + } + + // Get the indexer key for the save data. + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().GetKey(out key, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + } + + // Start the extension. + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + Result extendResult = _serviceImpl.StartExtendSaveDataFileSystem(out long extendedTotalSize, saveDataId, + spaceId, key.Type, dataSize, journalSize, in saveDataRootPath); + + if (extendResult.IsFailure()) + { + // Try to return the save data to its original state if something went wrong. + using var accessor = new UniqueRef(); + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); + if (rc.IsSuccess()) + { + _serviceImpl.RevertExtendSaveDataFileSystem(saveDataId, spaceId, value.Size, in saveDataRootPath); + } + + rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Normal); + if (rc.IsSuccess()) + { + accessor.Get.GetInterface().Commit().IgnoreResult(); + } + + return extendResult.Log(); + } + + // Update the save data's size in the indexer. + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + accessor.Get.GetInterface().SetSize(saveDataId, extendedTotalSize).IgnoreResult(); + + rc = accessor.Get.GetInterface().Commit(); + if (rc.IsFailure()) return rc.Miss(); + } + + // Finish the extension. + rc = _serviceImpl.FinishExtendSaveDataFileSystem(saveDataId, spaceId); + if (rc.IsFailure()) return rc.Miss(); + + // Set the save data's state back to normal. + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Normal); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().Commit(); + if (rc.IsFailure()) return rc.Miss(); + } + + return Result.Success; } - public Result OpenSaveDataFileSystem(ref SharedRef fileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + public Result OpenSaveDataFileSystem(ref SharedRef fileSystem, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) { - return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, false); + return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, openReadOnly: false).Ret(); } - public Result OpenReadOnlySaveDataFileSystem(ref SharedRef fileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + public Result OpenReadOnlySaveDataFileSystem(ref SharedRef fileSystem, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) { - return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, true); + return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, openReadOnly: true).Ret(); } - private Result OpenSaveDataFileSystemCore(ref SharedRef outFileSystem, - out ulong saveDataId, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly, - bool cacheExtraData) + public Result OpenSaveDataFileSystemCore(ref SharedRef outFileSystem, out ulong outSaveDataId, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) { - UnsafeHelpers.SkipParamInit(out saveDataId); + UnsafeHelpers.SkipParamInit(out outSaveDataId); using var accessor = new UniqueRef(); - ulong tempSaveDataId; + ulong saveDataId; bool isStaticSaveDataId = attribute.StaticSaveDataId != InvalidSystemSaveDataId && attribute.UserId == InvalidUserId; // Get the ID of the save data if (isStaticSaveDataId) { - tempSaveDataId = attribute.StaticSaveDataId; + saveDataId = attribute.StaticSaveDataId; } else { Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().Get(out SaveDataIndexerValue indexerValue, in attribute); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (indexerValue.SpaceId != ConvertToRealSpaceId(spaceId)) return ResultFs.TargetNotFound.Log(); @@ -1019,17 +1478,17 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (indexerValue.State == SaveDataState.Extending) return ResultFs.SaveDataExtending.Log(); - tempSaveDataId = indexerValue.SaveDataId; + saveDataId = indexerValue.SaveDataId; } // Open the save data using its ID using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - Result saveFsResult = _serviceImpl.OpenSaveDataFileSystem(ref outFileSystem, spaceId, tempSaveDataId, + Result saveFsResult = _serviceImpl.OpenSaveDataFileSystem(ref outFileSystem, spaceId, saveDataId, in saveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); if (saveFsResult.IsSuccess()) { - saveDataId = tempSaveDataId; + outSaveDataId = saveDataId; return Result.Success; } @@ -1040,7 +1499,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (ResultFs.PathNotFound.Includes(saveFsResult)) { Result rc = RemoveSaveIndexerEntry(); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); return ResultFs.TargetNotFound.LogConverted(saveFsResult); } @@ -1048,40 +1507,40 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (ResultFs.TargetNotFound.Includes(saveFsResult)) { Result rc = RemoveSaveIndexerEntry(); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } return saveFsResult; Result RemoveSaveIndexerEntry() { - if (tempSaveDataId == SaveIndexerId) + if (saveDataId == SaveIndexerId) return Result.Success; if (isStaticSaveDataId) { // The accessor won't be open yet if the save has a static ID Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); // Check the space ID of the save data rc = accessor.Get.GetInterface().Get(out SaveDataIndexerValue value, in key); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (value.SpaceId != ConvertToRealSpaceId(spaceId)) return ResultFs.TargetNotFound.Log(); } // Remove the indexer entry. Nintendo ignores these results - accessor.Get.GetInterface().Delete(tempSaveDataId).IgnoreResult(); + accessor.Get.GetInterface().Delete(saveDataId).IgnoreResult(); accessor.Get.GetInterface().Commit().IgnoreResult(); return Result.Success; } } - private Result OpenUserSaveDataFileSystemCore(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute, ProgramInfo programInfo, bool openReadOnly) + private Result OpenUserSaveDataFileSystemCore(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, ProgramInfo programInfo, bool openReadOnly) { StorageLayoutType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); @@ -1089,7 +1548,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Try grabbing the mount count semaphore using var mountCountSemaphore = new UniqueRef(); Result rc = TryAcquireSaveDataMountCountSemaphore(ref mountCountSemaphore.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); @@ -1098,8 +1557,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Open the file system rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out ulong saveDataId, spaceId, in attribute, - openReadOnly, true); - if (rc.IsFailure()) return rc; + openReadOnly, cacheExtraData: true); + if (rc.IsFailure()) return rc.Miss(); // Can't use attribute in a closure, so copy the needed field SaveDataType type = attribute.Type; @@ -1107,13 +1566,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result ReadExtraData(out SaveDataExtraData data) { using Path savePath = _saveDataRootPath.DangerousGetPath(); - return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, - in savePath); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, in savePath); } // Check if we have permissions to open this save data rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); // Add all the wrappers for the file system using var typeSetFileSystem = @@ -1134,12 +1592,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var openEntryCountAdapter = new SharedRef(new SaveDataOpenCountAdapter(ref saveService.Ref())); - using var openCountFileSystem = new SharedRef( - new OpenCountFileSystem(ref asyncFileSystem.Ref(), ref openEntryCountAdapter.Ref(), - ref mountCountSemaphore.Ref())); + using var openCountFileSystem = new SharedRef(new OpenCountFileSystem(ref asyncFileSystem.Ref(), + ref openEntryCountAdapter.Ref(), ref mountCountSemaphore.Ref())); var pathFlags = new PathFlags(); pathFlags.AllowBackslash(); + pathFlags.AllowAllCharacters(); using SharedRef fileSystemAdapter = FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref(), pathFlags, false); @@ -1149,45 +1607,45 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - private Result OpenUserSaveDataFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly) + private Result OpenUserSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, bool openReadOnly) { Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = SaveDataAccessibilityChecker.CheckOpenPre(in attribute, programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); SaveDataAttribute tempAttribute; if (attribute.ProgramId.Value == 0) { - ProgramId programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - rc = SaveDataAttribute.Make(out tempAttribute, programId, attribute.Type, attribute.UserId, + rc = SaveDataAttribute.Make(out tempAttribute, resolvedProgramId, attribute.Type, attribute.UserId, attribute.StaticSaveDataId, attribute.Index); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } else { tempAttribute = attribute; } - SaveDataSpaceId actualSpaceId; + SaveDataSpaceId targetSpaceId; if (tempAttribute.Type == SaveDataType.Cache) { // Check whether the save is on the SD card or the BIS - rc = GetCacheStorageSpaceId(out actualSpaceId, tempAttribute.ProgramId.Value); - if (rc.IsFailure()) return rc; + rc = GetCacheStorageSpaceId(out targetSpaceId, tempAttribute.ProgramId.Value); + if (rc.IsFailure()) return rc.Miss(); } else { - actualSpaceId = spaceId; + targetSpaceId = spaceId; } - return OpenUserSaveDataFileSystemCore(ref outFileSystem, actualSpaceId, in tempAttribute, programInfo, - openReadOnly); + return OpenUserSaveDataFileSystemCore(ref outFileSystem, targetSpaceId, in tempAttribute, programInfo, + openReadOnly).Ret(); } public Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, @@ -1197,7 +1655,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return ResultFs.InvalidArgument.Log(); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); StorageLayoutType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); @@ -1215,8 +1673,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Open the file system rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out ulong saveDataId, spaceId, in attribute, - false, true); - if (rc.IsFailure()) return rc; + openReadOnly: false, cacheExtraData: true); + if (rc.IsFailure()) return rc.Miss(); // Can't use attribute in a closure, so copy the needed field SaveDataType type = attribute.Type; @@ -1224,13 +1682,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result ReadExtraData(out SaveDataExtraData data) { using Path savePath = _saveDataRootPath.DangerousGetPath(); - return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, - in savePath); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, in savePath); } // Check if we have permissions to open this save data rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); // Add all the wrappers for the file system using var typeSetFileSystem = @@ -1256,9 +1713,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave var pathFlags = new PathFlags(); pathFlags.AllowBackslash(); + pathFlags.AllowAllCharacters(); using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref(), pathFlags, false); + FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref(), pathFlags, + allowAllOperations: false); outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); @@ -1276,79 +1735,82 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var accessor = new UniqueRef(); Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); return _serviceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type, - in saveDataRootPath); + in saveDataRootPath).Ret(); } - private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, - ulong saveDataId, in SaveDataExtraData extraDataMask) + private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, + Optional spaceId, ulong saveDataId, in SaveDataExtraData extraDataMask) { UnsafeHelpers.SkipParamInit(out extraData); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); SaveDataSpaceId resolvedSpaceId; SaveDataAttribute key; - if (spaceId == SaveDataSpaceId.BisAuto) + if (!spaceId.HasValue) { using var accessor = new UniqueRef(); if (IsStaticSaveDataIdValueRange(saveDataId)) { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } else { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); resolvedSpaceId = value.SpaceId; rc = accessor.Get.GetInterface().GetKey(out key, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } else { using var accessor = new UniqueRef(); - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId.ValueRo); + if (rc.IsFailure()) return rc.Miss(); - rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue _, saveDataId); + if (rc.IsFailure()) return rc.Miss(); - resolvedSpaceId = value.SpaceId; + resolvedSpaceId = spaceId.ValueRo; rc = accessor.Get.GetInterface().GetKey(out key, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } - Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, - resolvedSpaceId, saveDataId, key.Type, _saveDataRootPath.DangerousGetPath()); + Result ReadExtraData(out SaveDataExtraData data) + { + using Path savePath = _saveDataRootPath.DangerousGetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, resolvedSpaceId, saveDataId, key.Type, + in savePath); + } - rc = SaveDataAccessibilityChecker.CheckReadExtraData(in key, in extraDataMask, programInfo, - ReadExtraData); - if (rc.IsFailure()) return rc; + rc = SaveDataAccessibilityChecker.CheckReadExtraData(in key, in extraDataMask, programInfo, ReadExtraData); + if (rc.IsFailure()) return rc.Miss(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId, saveDataId, key.Type, in saveDataRootPath); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); MaskExtraData(ref tempExtraData, in extraDataMask); extraData = tempExtraData; @@ -1366,11 +1828,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); return ReadSaveDataFileSystemExtraDataCore(out SpanHelpers.AsStruct(extraData.Buffer), - SaveDataSpaceId.BisAuto, saveDataId, in extraDataMask); + spaceId: default, saveDataId, in extraDataMask).Ret(); } - public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) { if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); @@ -1378,7 +1840,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); SaveDataAttribute tempAttribute = attribute; @@ -1388,17 +1850,17 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); // Make a mask for reading the entire extra data Unsafe.SkipInit(out SaveDataExtraData extraDataMask); SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in extraDataMask); + return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in extraDataMask).Ret(); } - public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraData, - SaveDataSpaceId spaceId, ulong saveDataId) + public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraData, SaveDataSpaceId spaceId, + ulong saveDataId) { if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); @@ -1409,7 +1871,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Unsafe.SkipInit(out SaveDataExtraData extraDataMask); SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, saveDataId, in extraDataMask); + return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, saveDataId, in extraDataMask).Ret(); } public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraData, @@ -1427,7 +1889,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); SaveDataAttribute tempAttribute = attribute; @@ -1437,9 +1899,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in maskRef); + return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in maskRef).Ret(); } private Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, @@ -1449,7 +1911,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in saveDataRootPath, - saveType, updateTimeStamp); + saveType, updateTimeStamp).Ret(); } private Result WriteSaveDataFileSystemExtraDataWithMaskCore(ulong saveDataId, SaveDataSpaceId spaceId, @@ -1458,32 +1920,35 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var accessor = new UniqueRef(); rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, - spaceId, saveDataId, key.Type, _saveDataRootPath.DangerousGetPath()); + Result ReadExtraData(out SaveDataExtraData data) + { + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, key.Type, + in saveDataRootPath); + } - rc = SaveDataAccessibilityChecker.CheckWriteExtraData(in key, in extraDataMask, programInfo, - ReadExtraData); - if (rc.IsFailure()) return rc; + rc = SaveDataAccessibilityChecker.CheckWriteExtraData(in key, in extraDataMask, programInfo, ReadExtraData); + if (rc.IsFailure()) return rc.Miss(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, - saveDataId, key.Type, in saveDataRootPath); - if (rc.IsFailure()) return rc; + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, saveDataId, + key.Type, in saveDataRootPath); + if (rc.IsFailure()) return rc.Miss(); ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask); return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify, - in saveDataRootPath, key.Type, false); + in saveDataRootPath, key.Type, updateTimeStamp: false).Ret(); } public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData) @@ -1494,17 +1959,17 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave ref readonly SaveDataExtraData extraDataRef = ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); - var extraDataMask = new SaveDataExtraData(); + SaveDataExtraData extraDataMask = default; extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF); - return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in extraDataMask); + return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in extraDataMask).Ret(); } public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, SaveDataSpaceId spaceId, InBuffer extraData, InBuffer extraDataMask) { Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); SaveDataAttribute tempAttribute = attribute; @@ -1514,27 +1979,27 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - return WriteSaveDataFileSystemExtraDataWithMask(info.SaveDataId, spaceId, extraData, extraDataMask); + return WriteSaveDataFileSystemExtraDataWithMask(info.SaveDataId, spaceId, extraData, extraDataMask).Ret(); } public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData, InBuffer extraDataMask) { - if (extraDataMask.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); - ref readonly SaveDataExtraData maskRef = - ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); + if (extraDataMask.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); ref readonly SaveDataExtraData extraDataRef = ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); - return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in maskRef); + ref readonly SaveDataExtraData maskRef = + ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); + + return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in maskRef).Ret(); } public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) @@ -1542,23 +2007,23 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader) || - !programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) - { + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader)) + return ResultFs.PermissionDenied.Log(); + + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) return ResultFs.PermissionDenied.Log(); - } using var reader = new SharedRef(); using (var accessor = new UniqueRef()) { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } outInfoReader.SetByMove(ref reader.Ref()); @@ -1566,31 +2031,31 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - public Result OpenSaveDataInfoReaderBySaveDataSpaceId( - ref SharedRef outInfoReader, SaveDataSpaceId spaceId) + public Result OpenSaveDataInfoReaderBySaveDataSpaceId(ref SharedRef outInfoReader, + SaveDataSpaceId spaceId) { using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var filterReader = new UniqueRef(); using (var accessor = new UniqueRef()) { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var reader = new SharedRef(); rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), programId: default, - saveDataType: default, userId: default, saveDataId: default, index: default, rank: 0); + saveDataType: default, userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary); filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in filter)); } @@ -1606,25 +2071,25 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal)) return ResultFs.PermissionDenied.Log(); rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var filterReader = new UniqueRef(); using (var accessor = new UniqueRef()) { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var reader = new SharedRef(); rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); @@ -1645,15 +2110,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var accessor = new UniqueRef(); Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var filterReader = new UniqueRef(new SaveDataInfoFilterReader(ref reader.Ref(), in infoFilter)); - return filterReader.Get.Read(out count, new OutBuffer(SpanHelpers.AsByteSpan(ref info))); + return filterReader.Get.Read(out count, new OutBuffer(SpanHelpers.AsByteSpan(ref info))).Ret(); } public Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, @@ -1667,7 +2132,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); @@ -1678,18 +2143,36 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Don't have full info reader permissions. Check if we have find permissions. rc = SaveDataAccessibilityChecker.CheckFind(in filter, programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); } - var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); + SaveDataFilter tempFilter = filter; + if (filter.Attribute.ProgramId == ProgramId.InvalidId) + { + tempFilter.Attribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + } - return FindSaveDataWithFilterImpl(out count, - out SpanHelpers.AsStruct(saveDataInfoBuffer.Buffer), spaceId, in infoFilter); + var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in tempFilter); + + return FindSaveDataWithFilterImpl(out count, out SpanHelpers.AsStruct(saveDataInfoBuffer.Buffer), + spaceId, in infoFilter).Ret(); } private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId) { - throw new NotImplementedException(); + Result rc = _serviceImpl.CreateSaveDataMeta(saveDataId, spaceId, SaveDataMetaType.Thumbnail, + SaveDataMetaPolicy.ThumbnailFileSize); + if (rc.IsFailure()) return rc.Miss(); + + using var file = new UniqueRef(); + rc = _serviceImpl.OpenSaveDataMeta(ref file.Ref(), saveDataId, spaceId, SaveDataMetaType.Thumbnail); + if (rc.IsFailure()) return rc.Miss(); + + Hash hash = default; + rc = file.Get.Write(0, hash.Value, WriteOption.Flush); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; } private Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef fileSystem, @@ -1714,15 +2197,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave UnsafeHelpers.SkipParamInit(out commitId); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (!programInfo.AccessControl.CanCall(OperationType.GetSaveDataCommitId)) return ResultFs.PermissionDenied.Log(); Unsafe.SkipInit(out SaveDataExtraData extraData); - rc = ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer.FromStruct(ref extraData), spaceId, - saveDataId); - if (rc.IsFailure()) return rc; + rc = ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer.FromStruct(ref extraData), spaceId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); commitId = Impl.Utility.ConvertZeroCommitId(in extraData); return Result.Success; @@ -1743,7 +2225,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return rc; } - return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId); + return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId).Ret(); } private Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader, @@ -1752,7 +2234,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (spaceId != SaveDataSpaceId.SdUser && spaceId != SaveDataSpaceId.User) return ResultFs.InvalidSaveDataSpaceId.Log(); @@ -1763,16 +2245,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using (var accessor = new UniqueRef()) { rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), resolvedProgramId, - SaveDataType.Cache, userId: default, saveDataId: default, index: default, - (int)SaveDataRank.Primary); + var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), resolvedProgramId, SaveDataType.Cache, + userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary); filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in filter)); } @@ -1782,8 +2263,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - private Result OpenSaveDataMetaFileRaw(ref SharedRef file, SaveDataSpaceId spaceId, - ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) + private Result OpenSaveDataMetaFileRaw(ref SharedRef outFile, SaveDataSpaceId spaceId, ulong saveDataId, + SaveDataMetaType metaType, OpenMode mode) { throw new NotImplementedException(); } @@ -1799,10 +2280,10 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave UnsafeHelpers.SkipParamInit(out spaceId); Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - ulong programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; - return GetCacheStorageSpaceId(out spaceId, programId); + ulong resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; + return GetCacheStorageSpaceId(out spaceId, resolvedProgramId); } private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId, ulong programId) @@ -1813,8 +2294,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Cache storage on the SD card will always take priority over case storage in NAND if (_serviceImpl.IsSdCardAccessible()) { - rc = SaveExists(out bool existsOnSdCard, SaveDataSpaceId.SdUser); - if (rc.IsFailure()) return rc; + rc = DoesCacheStorageExist(out bool existsOnSdCard, SaveDataSpaceId.SdUser); + if (rc.IsFailure()) return rc.Miss(); if (existsOnSdCard) { @@ -1823,8 +2304,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } } - rc = SaveExists(out bool existsOnNand, SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; + rc = DoesCacheStorageExist(out bool existsOnNand, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc.Miss(); if (existsOnNand) { @@ -1834,14 +2315,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return ResultFs.TargetNotFound.Log(); - Result SaveExists(out bool exists, SaveDataSpaceId saveSpaceId) + Result DoesCacheStorageExist(out bool exists, SaveDataSpaceId saveSpaceId) { UnsafeHelpers.SkipParamInit(out exists); - var infoFilter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache, - default, default, default, 0); + var filter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache, + userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary); - Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in infoFilter); + Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in filter); if (result.IsFailure()) return result; exists = count != 0; @@ -1851,13 +2332,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave private Result FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, ushort index) { - UnsafeHelpers.SkipParamInit(out saveInfo, out spaceId); + UnsafeHelpers.SkipParamInit(out saveInfo); Result rc = GetCacheStorageSpaceId(out spaceId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); @@ -1865,7 +2346,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave userId: default, saveDataId: default, index, (int)SaveDataRank.Primary); rc = FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, spaceId, in filter); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (count == 0) return ResultFs.TargetNotFound.Log(); @@ -1879,10 +2360,10 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = Hos.Fs.DeleteSaveData(spaceId, saveInfo.SaveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); return Result.Success; } @@ -1894,12 +2375,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, - saveInfo.SaveDataId, saveInfo.Type, in saveDataRootPath); - if (rc.IsFailure()) return rc; + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, saveInfo.SaveDataId, + saveInfo.Type, in saveDataRootPath); + if (rc.IsFailure()) return rc.Miss(); usableDataSize = extraData.DataSize; journalSize = extraData.JournalSize; @@ -1912,8 +2393,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave throw new NotImplementedException(); } - public Result OpenSaveDataTransferManagerVersion2( - ref SharedRef manager) + public Result OpenSaveDataTransferManagerVersion2(ref SharedRef manager) { throw new NotImplementedException(); } @@ -1924,34 +2404,76 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave throw new NotImplementedException(); } - public Result OpenSaveDataTransferManagerForRepair( - ref SharedRef manager) + public Result OpenSaveDataTransferManagerForRepair(ref SharedRef manager) { throw new NotImplementedException(); } - public Result OpenSaveDataTransferProhibiter( - ref SharedRef prohibiter, Ncm.ApplicationId applicationId) + private Result OpenSaveDataTransferProhibiterCore(ref SharedRef prohibiter, + Ncm.ApplicationId applicationId) { throw new NotImplementedException(); } - public Result OpenSaveDataMover(ref SharedRef saveMover, - SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, - ulong workBufferSize) + public Result OpenSaveDataTransferProhibiter(ref SharedRef prohibiter, + Ncm.ApplicationId applicationId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkNotNull(_serviceImpl.GetSaveDataTransferCryptoConfiguration()); + + rc = SaveDataAccessibilityChecker.CheckOpenProhibiter(applicationId, programInfo); + if (rc.IsFailure()) return rc.Miss(); + + return OpenSaveDataTransferProhibiterCore(ref prohibiter, applicationId).Ret(); + } + + public bool IsProhibited(ref UniqueLock outLock, ApplicationId applicationId) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataMover(ref SharedRef saveMover, SaveDataSpaceId sourceSpaceId, + SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize) { throw new NotImplementedException(); } public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) { - return _serviceImpl.SetSdCardEncryptionSeed(in seed); + return _serviceImpl.SetSdCardEncryptionSeed(in seed).Ret(); } public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, int startIndex, int bufferIdCount) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out readCount); + + if (startIndex < 0) + return ResultFs.InvalidOffset.Log(); + + var ids = Span.Empty; + + if (bufferIdCount > 0) + { + ids = MemoryMarshal.Cast(idBuffer.Buffer); + + if (ids.Length < bufferIdCount) + return ResultFs.InvalidSize.Log(); + } + + Result rc = GetProgramInfo(out ProgramInfo callerProgramInfo); + if (rc.IsFailure()) return rc.Miss(); + + if (!callerProgramInfo.AccessControl.CanCall(OperationType.ListAccessibleSaveDataOwnerId)) + return ResultFs.PermissionDenied.Log(); + + rc = GetProgramInfoByProgramId(out ProgramInfo targetProgramInfo, programId.Value); + if (rc.IsFailure()) return rc.Miss(); + + targetProgramInfo.AccessControl.ListSaveDataOwnedId(out readCount, ids.Slice(0, bufferIdCount), startIndex); + return Result.Success; } private ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId) @@ -1962,7 +2484,55 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, OutBuffer workBuffer) { - throw new NotImplementedException(); + // The caller needs the VerifySaveData permission. + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc.Miss(); + + if (!programInfo.AccessControl.CanCall(OperationType.VerifySaveData)) + return ResultFs.PermissionDenied.Log(); + + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); + + try + { + // Get the information needed to open the file system. + SaveDataIndexerValue value; + SaveDataType saveDataType; + + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().GetValue(out value, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + rc = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + saveDataType = key.Type; + } + + // Open the save data file system. + using var fileSystem = new SharedRef(); + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + rc = _serviceImpl.OpenSaveDataFileSystem(ref fileSystem.Ref(), value.SpaceId, saveDataId, + in saveDataRootPath, + openReadOnly: false, saveDataType, cacheExtraData: true); + if (rc.IsFailure()) return rc.Miss(); + + // Verify the file system. + rc = Utility.VerifyDirectoryRecursively(fileSystem.Get, workBuffer.Buffer); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + finally + { + // Make sure we don't leak any invalid data. + workBuffer.Buffer.Clear(); + } } public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) @@ -1976,9 +2546,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var accessor = new UniqueRef(); Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - return CleanUpSaveData(accessor.Get); + return CleanUpSaveData(accessor.Get).Ret(); } private Result CleanUpSaveData(SaveDataIndexerAccessor accessor) @@ -1993,9 +2563,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var accessor = new UniqueRef(); Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - return CompleteSaveDataExtension(accessor.Get); + return CompleteSaveDataExtension(accessor.Get).Ret(); } private Result CompleteSaveDataExtension(SaveDataIndexerAccessor accessor) @@ -2010,14 +2580,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var fileSystem = new SharedRef(); Result rc = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), SaveDataSpaceId.Temporary); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); using var pathRoot = new Path(); rc = PathFunctions.SetUpFixedPath(ref pathRoot.Ref(), new[] { (byte)'/' }); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = fileSystem.Get.CleanDirectoryRecursively(in pathRoot); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); _serviceImpl.ResetTemporaryStorageIndexer(); return Result.Success; @@ -2031,8 +2601,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result OpenMultiCommitManager(ref SharedRef outCommitManager) { - using SharedRef - commitInterface = GetSharedMultiCommitInterfaceFromThis(); + using SharedRef commitInterface = GetSharedMultiCommitInterfaceFromThis(); outCommitManager.Reset(new MultiCommitManager(_serviceImpl.FsServer, ref commitInterface.Ref())); @@ -2041,56 +2610,54 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result OpenMultiCommitContext(ref SharedRef contextFileSystem) { - var attribute = new SaveDataAttribute - { - Index = 0, - Type = SaveDataType.System, - UserId = InvalidUserId, - StaticSaveDataId = MultiCommitManager.SaveDataId, - ProgramId = new ProgramId(MultiCommitManager.ProgramId) - }; + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(MultiCommitManager.ProgramId), + SaveDataType.System, InvalidUserId, MultiCommitManager.SaveDataId, index: 0); + if (rc.IsFailure()) return rc.Miss(); - return OpenSaveDataFileSystemCore(ref contextFileSystem, out _, SaveDataSpaceId.System, in attribute, false, - true); + using var fileSystem = new SharedRef(); + + rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out _, SaveDataSpaceId.System, in attribute, + openReadOnly: false, cacheExtraData: true); + if (rc.IsFailure()) return rc.Miss(); + + contextFileSystem.SetByMove(ref fileSystem.Ref()); + return Result.Success; } public Result RecoverMultiCommit() { - return MultiCommitManager.Recover(_serviceImpl.FsServer, this, _serviceImpl); + return MultiCommitManager.Recover(_serviceImpl.FsServer, this, _serviceImpl).Ret(); } public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) { - return _serviceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo); + return _serviceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo).Ret(); } public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback) { - var attribute = new SaveDataAttribute - { - Index = saveInfo.Index, - Type = saveInfo.Type, - UserId = InvalidUserId, - StaticSaveDataId = saveInfo.StaticSaveDataId, - ProgramId = saveInfo.ProgramId - }; + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, saveInfo.ProgramId, saveInfo.Type, + saveInfo.UserId, saveInfo.SaveDataId, saveInfo.Index); + if (rc.IsFailure()) return rc.Miss(); using var fileSystem = new SharedRef(); - Result rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out _, saveInfo.SpaceId, in attribute, false, - false); - if (rc.IsFailure()) return rc; + rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out _, saveInfo.SpaceId, in attribute, + openReadOnly: false, cacheExtraData: false); + if (rc.IsFailure()) return rc.Miss(); - if (doRollback) + if (!doRollback) { - rc = fileSystem.Get.Rollback(); + rc = fileSystem.Get.Commit(); + if (rc.IsFailure()) return rc.Miss(); } else { - rc = fileSystem.Get.Commit(); + rc = fileSystem.Get.Rollback(); + if (rc.IsFailure()) return rc.Miss(); } - return rc; + return Result.Success; } private Result TryAcquireSaveDataEntryOpenCountSemaphore(ref UniqueRef outSemaphoreLock) @@ -2099,7 +2666,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rc = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, ref saveService.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); return Result.Success; } @@ -2110,7 +2677,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rc = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, ref saveService.Ref()); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); return Result.Success; } @@ -2123,7 +2690,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result SetSdCardAccessibility(bool isAccessible) { Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); if (!programInfo.AccessControl.CanCall(OperationType.SetSdCardAccessibility)) return ResultFs.PermissionDenied.Log(); @@ -2142,73 +2709,19 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave SaveDataSpaceId spaceId) { using var accessor = new UniqueRef(); - Result rc = _serviceImpl.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out bool neededInit, spaceId); - if (rc.IsFailure()) return rc; + Result rc = _serviceImpl.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out bool isInitialOpen, spaceId); + if (rc.IsFailure()) return rc.Miss(); - if (neededInit) + if (isInitialOpen) { - // todo: nn::fssrv::SaveDataFileSystemService::CleanUpSaveDataCore - // nn::fssrv::SaveDataFileSystemService::CompleteSaveDataExtensionCore + CleanUpSaveData(accessor.Get).IgnoreResult(); + CompleteSaveDataExtension(accessor.Get).IgnoreResult(); } outAccessor.Set(ref accessor.Ref()); return Result.Success; } - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return _serviceImpl.GetProgramInfo(out programInfo, _processId); - } - - private SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId) - { - return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode - ? SaveDataSpaceId.System - : spaceId; - } - - private bool IsStaticSaveDataIdValueRange(ulong id) - { - return unchecked((long)id) < 0; - } - - private void ModifySaveDataExtraData(ref SaveDataExtraData currentExtraData, in SaveDataExtraData extraData, - in SaveDataExtraData extraDataMask) - { - Span currentExtraDataBytes = SpanHelpers.AsByteSpan(ref currentExtraData); - ReadOnlySpan extraDataBytes = SpanHelpers.AsReadOnlyByteSpan(in extraData); - ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); - - for (int i = 0; i < Unsafe.SizeOf(); i++) - { - currentExtraDataBytes[i] = (byte)(extraDataBytes[i] & extraDataMaskBytes[i] | - currentExtraDataBytes[i] & ~extraDataMaskBytes[i]); - } - } - - private void MaskExtraData(ref SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) - { - Span extraDataBytes = SpanHelpers.AsByteSpan(ref extraData); - ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); - - for (int i = 0; i < Unsafe.SizeOf(); i++) - { - extraDataBytes[i] &= extraDataMaskBytes[i]; - } - } - - private StorageLayoutType DecidePossibleStorageFlag(SaveDataType type, SaveDataSpaceId spaceId) - { - if (type == SaveDataType.Cache || type == SaveDataType.Bcat) - return StorageLayoutType.Bis | StorageLayoutType.SdCard | StorageLayoutType.Usb; - - if (type == SaveDataType.System || - spaceId != SaveDataSpaceId.SdSystem && spaceId != SaveDataSpaceId.SdUser) - return StorageLayoutType.Bis; - - return StorageLayoutType.SdCard | StorageLayoutType.Usb; - } - Result ISaveDataTransferCoreInterface.CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized) @@ -2229,28 +2742,21 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return WriteSaveDataFileSystemExtraDataCore(spaceId, saveDataId, in extraData, type, updateTimeStamp); } - Result ISaveDataTransferCoreInterface.OpenSaveDataMetaFileRaw(ref SharedRef file, - SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) - { - return OpenSaveDataMetaFileRaw(ref file, spaceId, saveDataId, metaType, mode); - } - Result ISaveDataTransferCoreInterface.OpenSaveDataInternalStorageFileSystemCore( - ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, - bool useSecondMacKey) + ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey) { return OpenSaveDataInternalStorageFileSystemCore(ref fileSystem, spaceId, saveDataId, useSecondMacKey); } + Result ISaveDataTransferCoreInterface.OpenSaveDataMetaFileRaw(ref SharedRef outFile, SaveDataSpaceId spaceId, + ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) + { + return OpenSaveDataMetaFileRaw(ref outFile, spaceId, saveDataId, metaType, mode); + } + Result ISaveDataTransferCoreInterface.OpenSaveDataIndexerAccessor( ref UniqueRef outAccessor, SaveDataSpaceId spaceId) { return OpenSaveDataIndexerAccessor(ref outAccessor, spaceId); } - - public void Dispose() - { - _openEntryCountSemaphore.Dispose(); - _saveDataMountCountSemaphore.Dispose(); - } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 0876cfaa..d04a86d9 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -15,13 +15,13 @@ using Utility = LibHac.FsSrv.Impl.Utility; namespace LibHac.FsSrv; -public class SaveDataFileSystemServiceImpl +public class SaveDataFileSystemServiceImpl : IDisposable { private Configuration _config; private EncryptionSeed _encryptionSeed; - private SaveDataFileSystemCacheManager _saveDataFsCacheManager; - private SaveDataExtraDataAccessorCacheManager _extraDataCacheManager; + private SaveDataFileSystemCacheManager _saveFileSystemCacheManager; + private SaveDataExtraDataAccessorCacheManager _saveExtraDataCacheManager; // Save data porter manager private bool _isSdCardAccessible; private TimeStampGetter _timeStampGetter; @@ -40,22 +40,10 @@ public class SaveDataFileSystemServiceImpl public Result Get(out long timeStamp) { - return _saveService.GetSaveDataCommitTimeStamp(out timeStamp); + return _saveService.GetSaveDataCommitTimeStamp(out timeStamp).Ret(); } } - public SaveDataFileSystemServiceImpl(in Configuration configuration) - { - _config = configuration; - _saveDataFsCacheManager = new SaveDataFileSystemCacheManager(); - _extraDataCacheManager = new SaveDataExtraDataAccessorCacheManager(); - - _timeStampGetter = new TimeStampGetter(this); - - Result rc = _saveDataFsCacheManager.Initialize(_config.MaxSaveFsCacheCount); - Abort.DoAbortUnless(rc.IsSuccess()); - } - public struct Configuration { public BaseFileSystemServiceImpl BaseFsService; @@ -68,7 +56,7 @@ public class SaveDataFileSystemServiceImpl public IBufferManager BufferManager; public RandomDataGenerator GenerateRandomData; public SaveDataTransferCryptoConfiguration SaveTransferCryptoConfig; - public int MaxSaveFsCacheCount; + public int SaveDataFileSystemCacheCount; public Func IsPseudoSaveData; public ISaveDataIndexerManager SaveIndexerManager; @@ -76,10 +64,36 @@ public class SaveDataFileSystemServiceImpl public FileSystemServer FsServer; } - internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + private static bool IsDeviceUniqueMac(SaveDataSpaceId spaceId) { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); + return spaceId == SaveDataSpaceId.System || + spaceId == SaveDataSpaceId.User || + spaceId == SaveDataSpaceId.Temporary || + spaceId == SaveDataSpaceId.ProperSystem || + spaceId == SaveDataSpaceId.SafeMode; + } + + private static Result WipeData(IFileSystem fileSystem, in Path filePath, RandomDataGenerator random) + { + throw new NotImplementedException(); + } + + public SaveDataFileSystemServiceImpl(in Configuration configuration) + { + _config = configuration; + _saveFileSystemCacheManager = new SaveDataFileSystemCacheManager(); + _saveExtraDataCacheManager = new SaveDataExtraDataAccessorCacheManager(); + + _timeStampGetter = new TimeStampGetter(this); + + Result rc = _saveFileSystemCacheManager.Initialize(_config.SaveDataFileSystemCacheCount); + Abort.DoAbortUnless(rc.IsSuccess()); + } + + public void Dispose() + { + _saveFileSystemCacheManager.Dispose(); + _saveExtraDataCacheManager.Dispose(); } public Result DoesSaveDataEntityExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId) @@ -116,7 +130,12 @@ public class SaveDataFileSystemServiceImpl } } - // 14.3.0 + public Result OpenSaveDataFile(ref SharedRef outFile, SaveDataSpaceId spaceId, ulong saveDataId, + OpenMode openMode) + { + throw new NotImplementedException(); + } + public Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) { @@ -141,10 +160,10 @@ public class SaveDataFileSystemServiceImpl using var saveDataFs = new SharedRef(); - using (_saveDataFsCacheManager.GetScopedLock()) - using (_extraDataCacheManager.GetScopedLock()) + using (_saveFileSystemCacheManager.GetScopedLock()) + using (_saveExtraDataCacheManager.GetScopedLock()) { - if (isEmulatedOnHost || !_saveDataFsCacheManager.GetCache(ref saveDataFs.Ref(), spaceId, saveDataId)) + if (isEmulatedOnHost || !_saveFileSystemCacheManager.GetCache(ref saveDataFs.Ref(), spaceId, saveDataId)) { bool isDeviceUniqueMac = IsDeviceUniqueMac(spaceId); bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(type); @@ -163,13 +182,13 @@ public class SaveDataFileSystemServiceImpl using SharedRef extraDataAccessor = SharedRef.CreateCopy(in saveDataFs); - rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId); + rc = _saveExtraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId); if (rc.IsFailure()) return rc.Miss(); } } using var registerFs = new SharedRef( - new SaveDataFileSystemCacheRegister(ref saveDataFs.Ref(), _saveDataFsCacheManager, spaceId, saveDataId)); + new SaveDataFileSystemCacheRegister(ref saveDataFs.Ref(), _saveFileSystemCacheManager, spaceId, saveDataId)); if (openReadOnly) { @@ -203,11 +222,61 @@ public class SaveDataFileSystemServiceImpl } public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool useSecondMacKey) + SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool useSecondMacKey, + bool isReconstructible) { throw new NotImplementedException(); } + public Result ExtendSaveDataFileSystemCore(out long extendedTotalSize, ulong saveDataId, SaveDataSpaceId spaceId, + SaveDataType type, long dataSize, long journalSize, in Path saveDataRootPath, bool isExtensionStart) + { + throw new NotImplementedException(); + } + + private Result OpenSaveDataImageFile(ref UniqueRef outFile, SaveDataSpaceId spaceId, ulong saveDataId, + in Path saveDataRootPath) + { + throw new NotImplementedException(); + } + + public Result StartExtendSaveDataFileSystem(out long extendedTotalSize, ulong saveDataId, SaveDataSpaceId spaceId, + SaveDataType type, long dataSize, long journalSize, in Path saveDataRootPath) + { + return ExtendSaveDataFileSystemCore(out extendedTotalSize, saveDataId, spaceId, type, dataSize, journalSize, + in saveDataRootPath, isExtensionStart: true); + } + + public Result ResumeExtendSaveDataFileSystem(out long extendedTotalSize, ulong saveDataId, SaveDataSpaceId spaceId, + SaveDataType type, in Path saveDataRootPath) + { + return ExtendSaveDataFileSystemCore(out extendedTotalSize, saveDataId, spaceId, type, dataSize: 0, + journalSize: 0, in saveDataRootPath, isExtensionStart: false); + } + + public Result FinishExtendSaveDataFileSystem(ulong saveDataId, SaveDataSpaceId spaceId) + { + Result rc = DeleteSaveDataMeta(saveDataId, spaceId, SaveDataMetaType.ExtensionContext); + if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) + return rc.Miss(); + + return Result.Success; + } + + public void RevertExtendSaveDataFileSystem(ulong saveDataId, SaveDataSpaceId spaceId, long originalSize, + in Path saveDataRootPath) + { + using var saveDataFile = new UniqueRef(); + Result rc = OpenSaveDataImageFile(ref saveDataFile.Ref(), spaceId, saveDataId, in saveDataRootPath); + + if (rc.IsSuccess()) + { + saveDataFile.Get.SetSize(originalSize).IgnoreResult(); + } + + FinishExtendSaveDataFileSystem(saveDataId, spaceId).IgnoreResult(); + } + public Result QuerySaveDataTotalSize(out long totalSize, int blockSize, long dataSize, long journalSize) { // Todo: Implement @@ -316,6 +385,82 @@ public class SaveDataFileSystemServiceImpl return Result.Success; } + public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataCreationInfo2 creationInfo, + in Path saveDataRootPath, bool skipFormat) + { + Unsafe.SkipInit(out Array18 saveImageNameBuffer); + + long dataSize = creationInfo.Size; + long journalSize = creationInfo.JournalSize; + ulong ownerId = creationInfo.OwnerId; + SaveDataSpaceId spaceId = creationInfo.SpaceId; + SaveDataFlags flags = creationInfo.Flags; + + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), creationInfo.SpaceId, in saveDataRootPath, + allowEmulatedSave: false); + if (rc.IsFailure()) return rc.Miss(); + + using var saveImageName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer.Items, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + bool isPseudoSaveFs = _config.IsPseudoSaveData(); + bool isCreationSuccessful = false; + + try + { + if (isPseudoSaveFs) + { + rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveImageName); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + throw new NotImplementedException(); + } + + SaveDataExtraData extraData = default; + extraData.Attribute = creationInfo.Attribute; + extraData.OwnerId = ownerId; + + rc = GetSaveDataCommitTimeStamp(out extraData.TimeStamp); + if (rc.IsFailure()) + extraData.TimeStamp = 0; + + extraData.CommitId = 0; + _config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId)); + + extraData.Flags = flags; + extraData.DataSize = dataSize; + extraData.JournalSize = journalSize; + extraData.FormatType = creationInfo.FormatType; + + rc = WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in saveDataRootPath, + creationInfo.Attribute.Type, updateTimeStamp: true); + if (rc.IsFailure()) return rc.Miss(); + + isCreationSuccessful = true; + return Result.Success; + } + finally + { + // Delete any created save if something goes wrong. + if (!isCreationSuccessful) + { + if (isPseudoSaveFs) + { + fileSystem.Get.DeleteDirectoryRecursively(in saveImageName).IgnoreResult(); + } + else + { + fileSystem.Get.DeleteFile(in saveImageName).IgnoreResult(); + } + } + } + } + public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in Path saveDataRootPath, in Optional hashSalt, bool skipFormat) @@ -379,11 +524,6 @@ public class SaveDataFileSystemServiceImpl return Result.Success; } - private Result WipeData(IFileSystem fileSystem, in Path filePath, RandomDataGenerator random) - { - throw new NotImplementedException(); - } - public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile, in Path saveDataRootPath) { @@ -391,7 +531,7 @@ public class SaveDataFileSystemServiceImpl using var fileSystem = new SharedRef(); - _saveDataFsCacheManager.Unregister(spaceId, saveDataId); + _saveFileSystemCacheManager.Unregister(spaceId, saveDataId); // Open the directory containing the save data Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId, in saveDataRootPath, false); @@ -433,13 +573,13 @@ public class SaveDataFileSystemServiceImpl // Nintendo returns blank extra data for directory save data. // We've extended directory save data to store extra data so we don't need to do that. - using UniqueLockRef scopedLockFsCache = _saveDataFsCacheManager.GetScopedLock(); - using UniqueLockRef scopedLockExtraDataCache = _extraDataCacheManager.GetScopedLock(); + using UniqueLockRef scopedLockFsCache = _saveFileSystemCacheManager.GetScopedLock(); + using UniqueLockRef scopedLockExtraDataCache = _saveExtraDataCacheManager.GetScopedLock(); using var extraDataAccessor = new SharedRef(); // Try to grab an extra data accessor for the requested save from the cache. - Result rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + Result rc = _saveExtraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); if (rc.IsSuccess()) { @@ -456,7 +596,7 @@ public class SaveDataFileSystemServiceImpl if (rc.IsFailure()) return rc.Miss(); // Try to grab an accessor from the cache again. - rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + rc = _saveExtraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); if (rc.IsFailure()) { @@ -478,13 +618,13 @@ public class SaveDataFileSystemServiceImpl // Nintendo does nothing when writing directory save data extra data. // We've extended directory save data to store extra data so we don't return early. - using UniqueLockRef scopedLockFsCache = _saveDataFsCacheManager.GetScopedLock(); - using UniqueLockRef scopedLockExtraDataCache = _extraDataCacheManager.GetScopedLock(); + using UniqueLockRef scopedLockFsCache = _saveFileSystemCacheManager.GetScopedLock(); + using UniqueLockRef scopedLockExtraDataCache = _saveExtraDataCacheManager.GetScopedLock(); using var extraDataAccessor = new SharedRef(); // Try to grab an extra data accessor for the requested save from the cache. - Result rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + Result rc = _saveExtraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); if (rc.IsFailure()) { @@ -498,7 +638,7 @@ public class SaveDataFileSystemServiceImpl if (rc.IsFailure()) return rc.Miss(); // Try to grab an accessor from the cache again. - rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + rc = _saveExtraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); if (rc.IsFailure()) { @@ -592,12 +732,6 @@ public class SaveDataFileSystemServiceImpl return Result.Success; } - public Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in Path basePath) - { - return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in basePath, true); - } - public Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in Path basePath, bool createIfMissing) { @@ -680,15 +814,10 @@ public class SaveDataFileSystemServiceImpl } } - public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + public Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in Path basePath) { - _encryptionSeed = seed; - - _config.SaveFsCreator.SetSdCardEncryptionSeed(seed.Value); - _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdSystem); - _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdUser); - - return Result.Success; + return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in basePath, true); } public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) @@ -704,13 +833,15 @@ public class SaveDataFileSystemServiceImpl return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath); } - public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId) + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) { - return spaceId == SaveDataSpaceId.System || - spaceId == SaveDataSpaceId.User || - spaceId == SaveDataSpaceId.Temporary || - spaceId == SaveDataSpaceId.ProperSystem || - spaceId == SaveDataSpaceId.SafeMode; + _encryptionSeed = seed; + + _config.SaveFsCreator.SetSdCardEncryptionSeed(seed.Value); + _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdSystem); + _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdUser); + + return Result.Success; } public void SetSdCardAccessibility(bool isAccessible) @@ -753,6 +884,11 @@ public class SaveDataFileSystemServiceImpl return programId; } + public SaveDataTransferCryptoConfiguration GetSaveDataTransferCryptoConfiguration() + { + throw new NotImplementedException(); + } + public Result GetSaveDataIndexCount(out int count) { UnsafeHelpers.SkipParamInit(out count); @@ -766,10 +902,10 @@ public class SaveDataFileSystemServiceImpl return Result.Success; } - public Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, out bool neededInit, - SaveDataSpaceId spaceId) + public Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, + out bool isInitialOpen, SaveDataSpaceId spaceId) { - return _config.SaveIndexerManager.OpenSaveDataIndexerAccessor(ref outAccessor, out neededInit, spaceId); + return _config.SaveIndexerManager.OpenSaveDataIndexerAccessor(ref outAccessor, out isInitialOpen, spaceId); } public void ResetTemporaryStorageIndexer() diff --git a/src/LibHac/FsSrv/SaveDataIndexerManager.cs b/src/LibHac/FsSrv/SaveDataIndexerManager.cs index a2bb10cc..c7730787 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerManager.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerManager.cs @@ -122,14 +122,14 @@ internal class SaveDataIndexerManager : ISaveDataIndexerManager, IDisposable /// The accessor must be disposed after use. /// /// If the method returns successfully, contains the created accessor. - /// If the method returns successfully, contains - /// if the indexer needed to be initialized. + /// If the method returns successfully, contains + /// if the indexer needed to be initialized because this was the first time it was opened. /// The of the indexer to open. /// The of the operation. public Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, - out bool neededInit, SaveDataSpaceId spaceId) + out bool isInitialOpen, SaveDataSpaceId spaceId) { - UnsafeHelpers.SkipParamInit(out neededInit); + UnsafeHelpers.SkipParamInit(out isInitialOpen); if (_isBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User) { @@ -226,7 +226,7 @@ internal class SaveDataIndexerManager : ISaveDataIndexerManager, IDisposable } outAccessor.Reset(new SaveDataIndexerAccessor(indexer, ref indexerLock.Ref())); - neededInit = wasIndexerInitialized; + isInitialOpen = wasIndexerInitialized; return Result.Success; } diff --git a/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs b/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs index c04a7be0..5b574bba 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs @@ -13,7 +13,7 @@ namespace LibHac.FsSrv.Sf; /// /// The interface most programs use to interact with the FS service. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) +/// Based on FS 14.1.0 (nnSdk 14.3.0) public interface IFileSystemProxy : IDisposable { Result SetCurrentProcess(ulong processId); @@ -42,6 +42,7 @@ public interface IFileSystemProxy : IDisposable Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index); Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt); Result OpenHostFileSystemWithOption(ref SharedRef outFileSystem, in FspPath path, MountHostOption option); + Result CreateSaveDataFileSystemWithCreationInfo2(in SaveDataCreationInfo2 creationInfo); Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); Result OpenReadOnlySaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); diff --git a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs index 5c102ac9..0ef83b6e 100644 --- a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs @@ -136,6 +136,7 @@ public class TypeLayoutTests Assert.Equal(0x40, GetOffset(in s, in s.OwnerId)); Assert.Equal(0x48, GetOffset(in s, in s.TimeStamp)); Assert.Equal(0x50, GetOffset(in s, in s.Flags)); + Assert.Equal(0x54, GetOffset(in s, in s.FormatType)); Assert.Equal(0x58, GetOffset(in s, in s.DataSize)); Assert.Equal(0x60, GetOffset(in s, in s.JournalSize)); Assert.Equal(0x68, GetOffset(in s, in s.CommitId));