diff --git a/src/LibHac/Fs/Common/PathNormalizer.cs b/src/LibHac/Fs/Common/PathNormalizer.cs index f9bd509e..88d55ea2 100644 --- a/src/LibHac/Fs/Common/PathNormalizer.cs +++ b/src/LibHac/Fs/Common/PathNormalizer.cs @@ -207,7 +207,7 @@ public static class PathNormalizer /// contains if the path is normalized or if it is not. /// Contents are undefined if the function does not return . /// - /// When this function returns and + /// When this function returns and /// is , contains the length of the normalized path. /// Contents are undefined if the function does not return /// or is . @@ -216,15 +216,15 @@ public static class PathNormalizer /// : The operation was successful.
/// : The path contains an invalid character.
/// : The path is not in a valid format.
- public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan path) + public static Result IsNormalized(out bool isNormalized, out int outNormalizedLength, ReadOnlySpan path) { - return IsNormalized(out isNormalized, out length, path, false); + return IsNormalized(out isNormalized, out outNormalizedLength, path, false); } - public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan path, + public static Result IsNormalized(out bool isNormalized, out int outNormalizedLength, ReadOnlySpan path, bool allowAllCharacters) { - UnsafeHelpers.SkipParamInit(out isNormalized, out length); + UnsafeHelpers.SkipParamInit(out isNormalized, out outNormalizedLength); var state = PathState.Initial; int pathLength = 0; @@ -311,7 +311,7 @@ public static class PathNormalizer break; } - length = pathLength; + outNormalizedLength = pathLength; return Result.Success; } diff --git a/src/LibHac/FsSrv/FileSystemServerInitializer.cs b/src/LibHac/FsSrv/FileSystemServerInitializer.cs index b6aa323f..18e247d9 100644 --- a/src/LibHac/FsSrv/FileSystemServerInitializer.cs +++ b/src/LibHac/FsSrv/FileSystemServerInitializer.cs @@ -47,13 +47,13 @@ public static class FileSystemServerInitializer InitializeFileSystemProxyServer(client, server); - var saveService = new SaveDataFileSystemService(fspConfig.SaveDataFileSystemService, processId); + using SharedRef saveService = SaveDataFileSystemService.CreateShared(fspConfig.SaveDataFileSystemService, processId); - saveService.CleanUpTemporaryStorage().IgnoreResult(); - saveService.CleanUpSaveData().IgnoreResult(); - saveService.CompleteSaveDataExtension().IgnoreResult(); - saveService.FixSaveData().IgnoreResult(); - saveService.RecoverMultiCommit().IgnoreResult(); + saveService.Get.CleanUpTemporaryStorage().IgnoreResult(); + saveService.Get.CleanUpSaveData().IgnoreResult(); + saveService.Get.CompleteSaveDataExtension().IgnoreResult(); + saveService.Get.FixSaveData().IgnoreResult(); + saveService.Get.RecoverMultiCommit().IgnoreResult(); config.StorageDeviceManagerFactory.SetReady(StorageDevicePortId.SdCard, null); config.StorageDeviceManagerFactory.SetReady(StorageDevicePortId.GameCard, null); diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index 43a157e1..c9841619 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -204,7 +204,7 @@ public class DirectoryInterfaceAdapter : IDirectorySf const int maxTryCount = 2; UnsafeHelpers.SkipParamInit(out entriesRead); - Span entries = MemoryMarshal.Cast(entryBuffer.Buffer); + Span entries = entryBuffer.AsSpan(); Result res = Result.Success; long numRead = 0; diff --git a/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs b/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs index ef65689c..3451a47e 100644 --- a/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs +++ b/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs @@ -10,5 +10,5 @@ public interface ISaveDataMultiCommitCoreInterface : IDisposable Result RecoverMultiCommit(); Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo); Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback); - Result OpenMultiCommitContext(ref SharedRef contextFileSystem); + Result OpenMultiCommitContext(ref SharedRef outContextFileSystem); } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs index d1d98aed..7d954c0e 100644 --- a/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs +++ b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs @@ -20,7 +20,7 @@ public interface ISaveDataTransferCoreInterface : IDisposable Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); 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 OpenSaveDataInternalStorageFileSystemCore(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporaryTransferSave); 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); diff --git a/src/LibHac/FsSrv/ProgramRegistryService.cs b/src/LibHac/FsSrv/ProgramRegistryService.cs index 4ddec122..1b601dd1 100644 --- a/src/LibHac/FsSrv/ProgramRegistryService.cs +++ b/src/LibHac/FsSrv/ProgramRegistryService.cs @@ -59,8 +59,7 @@ internal readonly struct ProgramIndexRegistryService return Result.Success; // Verify that the provided buffer is large enough to hold "programCount" entries - ReadOnlySpan - mapInfo = MemoryMarshal.Cast(programIndexMapInfo.Buffer); + ReadOnlySpan mapInfo = programIndexMapInfo.AsSpan(); if (mapInfo.Length < programCount) return ResultFs.InvalidSize.Log(); diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index 2c5e92e1..7b086dff 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -16,6 +16,7 @@ using LibHac.Sf; using LibHac.Util; using static LibHac.Fs.SaveData; using static LibHac.Fs.StringTraits; +using static LibHac.FsSrv.Anonymous; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IFile = LibHac.Fs.Fsa.IFile; @@ -25,6 +26,526 @@ using Utility = LibHac.FsSystem.Utility; namespace LibHac.FsSrv; +file class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager +{ + private SharedRef _saveService; + + public SaveDataOpenCountAdapter(ref SharedRef saveService) + { + _saveService = SharedRef.CreateMove(ref saveService); + } + + public void Dispose() + { + _saveService.Destroy(); + } + + public Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore) + { + return _saveService.Get.TryAcquireSaveDataEntryOpenCountSemaphore(ref outSemaphore).Ret(); + } +} + +file static class Anonymous +{ + public static SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode + ? SaveDataSpaceId.System + : spaceId; + } + + public static bool IsStaticSaveDataIdValueRange(ulong id) + { + return unchecked((long)id) < 0; + } + + public static bool IsDirectorySaveDataExtraData(in SaveDataExtraData extraData) + { + return extraData.OwnerId == 0 && extraData.DataSize == 0 && extraData.JournalSize == 0; + } + + public 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]); + } + } + + public 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]; + } + } + + public 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; + } + + public static SaveDataFormatType GetSaveDataFormatType(in SaveDataAttribute attribute) + { + return SaveDataProperties.IsJournalingSupported(attribute.Type) + ? SaveDataFormatType.Normal + : SaveDataFormatType.NoJournal; + } + + public static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) + { + Assert.SdkNotNull(programInfo); + + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.ProperSystem: + case SaveDataSpaceId.SafeMode: + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) + return ResultFs.PermissionDenied.Log(); + break; + case SaveDataSpaceId.User: + case SaveDataSpaceId.Temporary: + case SaveDataSpaceId.SdUser: + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader)) + return ResultFs.PermissionDenied.Log(); + break; + default: + return ResultFs.InvalidSaveDataSpaceId.Log(); + } + + return Result.Success; + } +} + +file static class SaveDataAccessibilityChecker +{ + public delegate Result ExtraDataReader(out SaveDataExtraData extraData); + + public static Result CheckCreate(in SaveDataAttribute attribute, ulong ownerId, ProgramInfo programInfo, + ProgramId programId) + { + AccessControl accessControl = programInfo.AccessControl; + + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + if (ownerId != programInfo.ProgramIdValue) + { + // 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 + { + bool canAccess = accessControl.CanCall(OperationType.CreateSystemSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + } + else if (attribute.Type == SaveDataType.Account && attribute.UserId == InvalidUserId) + { + // Trying to create a program's debug save. + bool canAccess = + accessControl.CanCall(OperationType.CreateSaveData) || + accessControl.CanCall(OperationType.DebugSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + else + { + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, ownerId); + if (res.IsFailure()) return res.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 + // save data and have the permission to do so. + bool canAccess = accessControl.CanCall(OperationType.CreateSaveData); + + if (accessibility.CanWrite && + attribute.ProgramId == programId || attribute.ProgramId.Value == ownerId) + { + canAccess |= accessControl.CanCall(OperationType.CreateOwnSaveData); + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + return Result.Success; + } + + public static Result CheckOpenPre(in SaveDataAttribute attribute, ProgramInfo programInfo) + { + AccessControl accessControl = programInfo.AccessControl; + + if (attribute.Type == SaveDataType.Device) + { + Accessibility accessibility = + accessControl.GetAccessibilityFor(AccessibilityType.MountDeviceSaveData); + + bool canAccess = accessibility.CanRead && accessibility.CanWrite; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + else if (attribute.Type == SaveDataType.Account) + { + // We need debug save data permissions to open a debug save. + bool canAccess = attribute.UserId != InvalidUserId || + accessControl.CanCall(OperationType.DebugSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + return Result.Success; + } + + public static Result CheckOpen(in SaveDataAttribute attribute, ProgramInfo programInfo, + ExtraDataReader readExtraData) + { + AccessControl accessControl = programInfo.AccessControl; + + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, readExtraData); + if (res.IsFailure()) return res.Miss(); + + // 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; + + // The program doesn't have permissions for this specific save data. Check if it has overall + // permissions for other programs' save data. + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSystemSaveData); + } + else + { + accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSaveData); + } + + if (accessibility.CanRead && accessibility.CanWrite) + return Result.Success; + + return ResultFs.PermissionDenied.Log(); + } + + public static Result CheckDelete(in SaveDataAttribute attribute, ProgramInfo programInfo, + ExtraDataReader readExtraData) + { + AccessControl accessControl = programInfo.AccessControl; + + // DeleteSystemSaveData permission is needed to delete system save data + if (SaveDataProperties.IsSystemSaveData(attribute.Type) && + !accessControl.CanCall(OperationType.DeleteSystemSaveData)) + { + return ResultFs.PermissionDenied.Log(); + } + + // The DeleteSaveData permission allows deleting any non-system save data + if (accessControl.CanCall(OperationType.DeleteSaveData)) + { + return Result.Success; + } + + // Otherwise the program needs the DeleteOwnSaveData permission and write access to the save + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, readExtraData); + if (res.IsFailure()) return res.Miss(); + + if (accessControl.CanCall(OperationType.DeleteOwnSaveData) && accessibility.CanWrite) + { + return Result.Success; + } + + return ResultFs.PermissionDenied.Log(); + } + + public static Result CheckExtend(SaveDataSpaceId spaceId, in SaveDataAttribute attribute, + ProgramInfo programInfo, ExtraDataReader readExtraData) + { + AccessControl accessControl = programInfo.AccessControl; + + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.ProperSystem: + case SaveDataSpaceId.SafeMode: + { + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + readExtraData); + if (res.IsFailure()) return res.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 res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + readExtraData); + if (res.IsFailure()) return res.Miss(); + + if (attribute.ProgramId == programInfo.ProgramId || accessibility.CanRead) + { + canAccess |= accessControl.CanCall(OperationType.ExtendOwnSaveData); + + bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData) + && attribute.Type == SaveDataType.Account + && attribute.UserId == UserId.InvalidId; + + canAccess |= canAccessDebugSave; + + 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, ExtraDataReader readExtraData) + { + AccessControl accessControl = programInfo.AccessControl; + + bool canAccess = accessControl.CanCall(OperationType.ReadSaveDataFileSystemExtraData); + + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, readExtraData); + if (res.IsFailure()) return res.Miss(); + + SaveDataExtraData emptyMask = default; + SaveDataExtraData maskWithoutRestoreFlag = mask; + maskWithoutRestoreFlag.Flags &= ~SaveDataFlags.Restore; + + // Only read access to the save is needed to read the restore flag + if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) + .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutRestoreFlag))) + { + canAccess |= accessibility.CanRead; + } + + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + canAccess |= accessibility.CanRead; + } + else if (attribute.ProgramId == programInfo.ProgramId || accessibility.CanRead) + { + canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData); + + bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData) + && attribute.Type == SaveDataType.Account + && attribute.UserId == UserId.InvalidId; + + canAccess |= canAccessDebugSave; + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + public static Result CheckWriteExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, + ProgramInfo programInfo, ExtraDataReader readExtraData) + { + AccessControl accessControl = programInfo.AccessControl; + + if (mask.Flags != SaveDataFlags.None) + { + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || + accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataFlags); + + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + readExtraData); + if (res.IsFailure()) return res.Miss(); + + canAccess |= accessibility.CanWrite; + } + + if ((mask.Flags & ~SaveDataFlags.Restore) == 0) + { + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + readExtraData); + if (res.IsFailure()) return res.Miss(); + + canAccess |= accessibility.CanWrite; + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + if (mask.TimeStamp != 0) + { + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || + accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataTimeStamp); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + if (mask.CommitId != 0) + { + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || + accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataCommitId); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + SaveDataExtraData emptyMask = default; + SaveDataExtraData maskWithoutFlags = mask; + maskWithoutFlags.Flags = SaveDataFlags.None; + maskWithoutFlags.TimeStamp = 0; + maskWithoutFlags.CommitId = 0; + + // Full write access is needed for writing anything other than flags, timestamp or commit ID + if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) + .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutFlags))) + { + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + return Result.Success; + } + + public static Result CheckFind(in SaveDataFilter filter, ProgramInfo programInfo) + { + bool canAccess; + + if (programInfo.ProgramId == filter.Attribute.ProgramId) + { + AccessControl accessControl = programInfo.AccessControl; + canAccess = accessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); + + bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData) + && filter.Attribute.Type == SaveDataType.Account + && filter.Attribute.UserId == UserId.InvalidId; + + canAccess |= canAccessDebugSave; + } + else + { + canAccess = true; + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + public static Result CheckOpenProhibiter(ProgramId programId, ProgramInfo programInfo) + { + Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, programId.Value); + if (res.IsFailure()) return res.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, + ExtraDataReader readExtraData) + { + UnsafeHelpers.SkipParamInit(out accessibility); + + Result res = readExtraData(out SaveDataExtraData extraData); + if (res.IsFailure()) + { + if (ResultFs.TargetNotFound.Includes(res)) + { + accessibility = new Accessibility(false, false); + return Result.Success; + } + + return res; + } + + // Allow access when opening a directory save FS on a dev console + if (IsDirectorySaveDataExtraData(in extraData) && + programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) + { + accessibility = new Accessibility(true, true); + return Result.Success; + } + + return GetAccessibilityForSaveData(out accessibility, programInfo, extraData.OwnerId).Ret(); + } +} + /// /// Handles save-data-related calls for . /// @@ -38,6 +559,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave private const int SaveDataBlockSize = 0x4000; + private const ulong UnspecifiedSaveDataId = ulong.MaxValue; + private WeakRef _selfReference; private SaveDataFileSystemServiceImpl _serviceImpl; private ulong _processId; @@ -65,549 +588,36 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return SharedRef.CreateMove(ref sharedService.Ref); } - private class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager - { - private SharedRef _saveService; - - public SaveDataOpenCountAdapter(ref SharedRef saveService) - { - _saveService = SharedRef.CreateMove(ref saveService); - } - - public void Dispose() - { - _saveService.Destroy(); - } - - public Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore) - { - return _saveService.Get.TryAcquireSaveDataEntryOpenCountSemaphore(ref outSemaphore).Ret(); - } - } - - private static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) - { - Assert.SdkNotNull(programInfo); - - switch (spaceId) - { - case SaveDataSpaceId.System: - case SaveDataSpaceId.SdSystem: - case SaveDataSpaceId.ProperSystem: - case SaveDataSpaceId.SafeMode: - if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) - return ResultFs.PermissionDenied.Log(); - break; - case SaveDataSpaceId.User: - case SaveDataSpaceId.Temporary: - case SaveDataSpaceId.SdUser: - if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader)) - return ResultFs.PermissionDenied.Log(); - break; - default: - return ResultFs.InvalidSaveDataSpaceId.Log(); - } - - return Result.Success; - } - - private static class SaveDataAccessibilityChecker - { - public delegate Result ExtraDataGetter(out SaveDataExtraData extraData); - - public static Result CheckCreate(in SaveDataAttribute attribute, ulong ownerId, ProgramInfo programInfo, - ProgramId programId) - { - AccessControl accessControl = programInfo.AccessControl; - - if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - if (ownerId != programInfo.ProgramIdValue) - { - // 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 - { - bool canAccess = accessControl.CanCall(OperationType.CreateSystemSaveData); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - } - else if (attribute.Type == SaveDataType.Account && attribute.UserId == InvalidUserId) - { - // Trying to create a program's debug save. - bool canAccess = - accessControl.CanCall(OperationType.CreateSaveData) || - accessControl.CanCall(OperationType.DebugSaveData); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - else - { - Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, ownerId); - if (res.IsFailure()) return res.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 - // save data and have the permission to do so. - bool canAccess = accessControl.CanCall(OperationType.CreateSaveData); - - if (accessibility.CanWrite && - attribute.ProgramId == programId || attribute.ProgramId.Value == ownerId) - { - canAccess |= accessControl.CanCall(OperationType.CreateOwnSaveData); - } - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - return Result.Success; - } - - public static Result CheckOpenPre(in SaveDataAttribute attribute, ProgramInfo programInfo) - { - AccessControl accessControl = programInfo.AccessControl; - - if (attribute.Type == SaveDataType.Device) - { - Accessibility accessibility = - accessControl.GetAccessibilityFor(AccessibilityType.MountDeviceSaveData); - - bool canAccess = accessibility.CanRead && accessibility.CanWrite; - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - else if (attribute.Type == SaveDataType.Account) - { - // We need debug save data permissions to open a debug save. - bool canAccess = attribute.UserId != InvalidUserId || - accessControl.CanCall(OperationType.DebugSaveData); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - return Result.Success; - } - - public static Result CheckOpen(in SaveDataAttribute attribute, ProgramInfo programInfo, - ExtraDataGetter extraDataGetter) - { - AccessControl accessControl = programInfo.AccessControl; - - Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (res.IsFailure()) return res.Miss(); - - // 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; - - // The program doesn't have permissions for this specific save data. Check if it has overall - // permissions for other programs' save data. - if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSystemSaveData); - } - else - { - accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSaveData); - } - - if (accessibility.CanRead && accessibility.CanWrite) - return Result.Success; - - return ResultFs.PermissionDenied.Log(); - } - - public static Result CheckDelete(in SaveDataAttribute attribute, ProgramInfo programInfo, - ExtraDataGetter extraDataGetter) - { - AccessControl accessControl = programInfo.AccessControl; - - // DeleteSystemSaveData permission is needed to delete system save data - if (SaveDataProperties.IsSystemSaveData(attribute.Type) && - !accessControl.CanCall(OperationType.DeleteSystemSaveData)) - { - return ResultFs.PermissionDenied.Log(); - } - - // The DeleteSaveData permission allows deleting any non-system save data - if (accessControl.CanCall(OperationType.DeleteSaveData)) - { - return Result.Success; - } - - // Otherwise the program needs the DeleteOwnSaveData permission and write access to the save - Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (res.IsFailure()) return res.Miss(); - - if (accessControl.CanCall(OperationType.DeleteOwnSaveData) && accessibility.CanWrite) - { - return Result.Success; - } - - 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 res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (res.IsFailure()) return res.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 res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (res.IsFailure()) return res.Miss(); - - if (attribute.ProgramId == programInfo.ProgramId || accessibility.CanRead) - { - canAccess |= accessControl.CanCall(OperationType.ExtendOwnSaveData); - - bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData) - && attribute.Type == SaveDataType.Account - && attribute.UserId == UserId.InvalidId; - - canAccess |= canAccessDebugSave; - - 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) - { - AccessControl accessControl = programInfo.AccessControl; - - bool canAccess = accessControl.CanCall(OperationType.ReadSaveDataFileSystemExtraData); - - Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (res.IsFailure()) return res.Miss(); - - SaveDataExtraData emptyMask = default; - SaveDataExtraData maskWithoutRestoreFlag = mask; - maskWithoutRestoreFlag.Flags &= ~SaveDataFlags.Restore; - - // Only read access to the save is needed to read the restore flag - if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) - .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutRestoreFlag))) - { - canAccess |= accessibility.CanRead; - } - - if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - canAccess |= accessibility.CanRead; - } - else if (attribute.ProgramId == programInfo.ProgramId || accessibility.CanRead) - { - canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData); - - bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData) - && attribute.Type == SaveDataType.Account - && attribute.UserId == UserId.InvalidId; - - canAccess |= canAccessDebugSave; - } - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - - return Result.Success; - } - - public static Result CheckWriteExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, - ProgramInfo programInfo, ExtraDataGetter extraDataGetter) - { - AccessControl accessControl = programInfo.AccessControl; - - if (mask.Flags != SaveDataFlags.None) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || - accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataFlags); - - if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (res.IsFailure()) return res.Miss(); - - canAccess |= accessibility.CanWrite; - } - - if ((mask.Flags & ~SaveDataFlags.Restore) == 0) - { - Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (res.IsFailure()) return res.Miss(); - - canAccess |= accessibility.CanWrite; - } - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - if (mask.TimeStamp != 0) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || - accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataTimeStamp); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - if (mask.CommitId != 0) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || - accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataCommitId); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - SaveDataExtraData emptyMask = default; - SaveDataExtraData maskWithoutFlags = mask; - maskWithoutFlags.Flags = SaveDataFlags.None; - maskWithoutFlags.TimeStamp = 0; - maskWithoutFlags.CommitId = 0; - - // Full write access is needed for writing anything other than flags, timestamp or commit ID - if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) - .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutFlags))) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - return Result.Success; - } - - public static Result CheckFind(in SaveDataFilter filter, ProgramInfo programInfo) - { - bool canAccess; - - if (programInfo.ProgramId == filter.Attribute.ProgramId) - { - AccessControl accessControl = programInfo.AccessControl; - canAccess = accessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); - - bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData) - && filter.Attribute.Type == SaveDataType.Account - && filter.Attribute.UserId == UserId.InvalidId; - - canAccess |= canAccessDebugSave; - } - else - { - canAccess = true; - } - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - - return Result.Success; - } - - public static Result CheckOpenProhibiter(ProgramId programId, ProgramInfo programInfo) - { - Result res = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, programId.Value); - if (res.IsFailure()) return res.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) - { - UnsafeHelpers.SkipParamInit(out accessibility); - - Result res = extraDataGetter(out SaveDataExtraData extraData); - if (res.IsFailure()) - { - if (ResultFs.TargetNotFound.Includes(res)) - { - accessibility = new Accessibility(false, false); - return Result.Success; - } - - return res; - } - - // Allow access when opening a directory save FS on a dev console - if (IsDirectorySaveDataExtraData(in extraData) && - programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) - { - accessibility = new Accessibility(true, true); - return Result.Success; - } - - return GetAccessibilityForSaveData(out accessibility, programInfo, extraData.OwnerId).Ret(); - } - } - - public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) + private SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) { _serviceImpl = serviceImpl; _processId = processId; using var path = new Path(); _openEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); _saveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); + path.InitializeAsEmpty().IgnoreResult(); + + _saveDataRootPath.Initialize(in path).IgnoreResult(); } public void Dispose() { - _openEntryCountSemaphore.Dispose(); _saveDataMountCountSemaphore.Dispose(); + _openEntryCountSemaphore.Dispose(); + _saveDataRootPath.Dispose(); _selfReference.Destroy(); } private Result GetProgramInfo(out ProgramInfo programInfo) { var registry = new ProgramRegistryImpl(_serviceImpl.FsServer); - return registry.GetProgramInfo(out programInfo, _processId); + return registry.GetProgramInfo(out programInfo, _processId).Ret(); } 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; + return registry.GetProgramInfoByProgramId(out programInfo, programId).Ret(); } public Result GetFreeSpaceSizeForSaveData(out long outFreeSpaceSize, SaveDataSpaceId spaceId) @@ -617,7 +627,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); using var fileSystem = new SharedRef(); - Result res = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId); + Result res = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, UnspecifiedSaveDataId); if (res.IsFailure()) return res.Miss(); using var pathRoot = new Path(); @@ -654,14 +664,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave res = OpenSaveDataIndexerAccessor(ref accessor.Ref, SaveDataSpaceId.System); if (res.IsFailure()) return res.Miss(); - ReadOnlySpan ids = MemoryMarshal.Cast(saveDataIds.Buffer); + ReadOnlySpan saveDataIdArray = saveDataIds.AsSpan(); try { // Try to set the state of all the save IDs as being marked for deletion. - for (int i = 0; i < ids.Length; i++) + for (int i = 0; i < saveDataIdArray.Length; i++) { - res = accessor.Get.GetInterface().SetState(ids[i], SaveDataState.MarkedForDeletion); + res = accessor.Get.GetInterface().SetState(saveDataIdArray[i], SaveDataState.MarkedForDeletion); if (res.IsFailure()) return res.Miss(); } @@ -686,13 +696,6 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } } - public Result DeleteSaveDataFileSystem(ulong saveDataId) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis); - - return DeleteSaveDataFileSystemCommon(SaveDataSpaceId.System, saveDataId).Ret(); - } - private Result DeleteSaveDataFileSystemCore(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile) { // Delete the save data's meta files @@ -709,6 +712,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } + public Result DeleteSaveDataFileSystem(ulong saveDataId) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis); + + return DeleteSaveDataFileSystemCommon(SaveDataSpaceId.System, saveDataId).Ret(); + } + public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) { using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); @@ -777,13 +787,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (res.IsFailure()) return res.Miss(); - Result GetExtraData(out SaveDataExtraData data) + Result ReadExtraData(out SaveDataExtraData data) { - var path = _saveDataRootPath.DangerousGetPath(); + using Path path = _saveDataRootPath.DangerousGetPath(); return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, targetSpaceId, saveDataId, key.Type, in path); } - res = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, GetExtraData); + res = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, ReadExtraData); if (res.IsFailure()) return res.Miss(); // Pre-delete checks successful. Put the save in the Processing state until deletion is finished. @@ -822,7 +832,36 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2) { - throw new NotImplementedException(); + using var accessor = new UniqueRef(); + Result res = OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().GetKey(out SaveDataAttribute lhsKey, saveDataId1); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue lhsValue, saveDataId1); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().GetKey(out SaveDataAttribute rhsKey, saveDataId2); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue rhsValue, saveDataId2); + if (res.IsFailure()) return res.Miss(); + + var rhsState = rhsValue.State; + rhsValue.State = lhsValue.State; + lhsValue.State = rhsState; + + res = accessor.Get.GetInterface().SetValue(in rhsKey, in lhsValue); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().SetValue(in lhsKey, in rhsValue); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().Commit(); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; } public Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state) @@ -842,7 +881,35 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId) { - throw new NotImplementedException(); + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); + + // Don't allow deleting the save data indexer + if (saveDataId == SaveIndexerId) + return ResultFs.InvalidArgument.Log(); + + // Delete the actual save data + Result res = DeleteSaveDataFileSystemCore(spaceId, saveDataId, wipeSaveFile: false); + if (res.IsFailure()) return res.Miss(); + + // Make sure the save data isn't in certain save data spaces + using var accessor = new UniqueRef(); + res = OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); + if (res.IsFailure()) return res.Miss(); + + if (value.SpaceId != ConvertToRealSpaceId(spaceId)) + return ResultFs.TargetNotFound.Log(); + + // Delete the save data from the indexer + res = accessor.Get.GetInterface().Delete(saveDataId); + if (res.IsFailure()) return res.Miss(); + + res = accessor.Get.GetInterface().Commit(); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; } public Result SetSaveDataRootPath(ref readonly FspPath path) @@ -926,6 +993,30 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave throw new NotImplementedException(); } + 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 res = SaveDataCreationInfo2.Make(out SaveDataCreationInfo2 newCreationInfo, in attribute, + creationInfo.Size, creationInfo.JournalSize, creationInfo.BlockSize, creationInfo.OwnerId, + creationInfo.Flags, creationInfo.SpaceId, GetSaveDataFormatType(in attribute)); + if (res.IsFailure()) return res.Miss(); + + newCreationInfo.IsHashSaltEnabled = hashSalt.HasValue; + if (hashSalt.HasValue) + { + newCreationInfo.HashSalt = hashSalt.ValueRo; + } + + newCreationInfo.MetaType = metaInfo.Type; + newCreationInfo.MetaSize = metaInfo.Size; + + res = CreateSaveDataFileSystemCore(in newCreationInfo, leaveUnfinalized); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + private Result CreateSaveDataFileSystemCore(in SaveDataCreationInfo2 creationInfo, bool leaveUnfinalized) { ulong saveDataId = 0; @@ -1144,30 +1235,6 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } } - 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 res = SaveDataCreationInfo2.Make(out SaveDataCreationInfo2 newCreationInfo, in attribute, - creationInfo.Size, creationInfo.JournalSize, creationInfo.BlockSize, creationInfo.OwnerId, - creationInfo.Flags, creationInfo.SpaceId, GetSaveDataFormatType(in attribute)); - if (res.IsFailure()) return res.Miss(); - - newCreationInfo.IsHashSaltEnabled = hashSalt.HasValue; - if (hashSalt.HasValue) - { - newCreationInfo.HashSalt = hashSalt.ValueRo; - } - - newCreationInfo.MetaType = metaInfo.Type; - newCreationInfo.MetaSize = metaInfo.Size; - - res = CreateSaveDataFileSystemCore(in newCreationInfo, leaveUnfinalized); - if (res.IsFailure()) return res.Miss(); - - return Result.Success; - } - public Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, SaveDataAttribute attribute) { UnsafeHelpers.SkipParamInit(out info); @@ -1439,18 +1506,6 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - public Result OpenSaveDataFileSystem(ref SharedRef fileSystem, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute) - { - return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, openReadOnly: false).Ret(); - } - - public Result OpenReadOnlySaveDataFileSystem(ref SharedRef fileSystem, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute) - { - return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, openReadOnly: true).Ret(); - } - public Result OpenSaveDataFileSystemCore(ref SharedRef outFileSystem, out ulong outSaveDataId, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) { @@ -1651,6 +1706,18 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave openReadOnly).Ret(); } + public Result OpenSaveDataFileSystem(ref SharedRef fileSystem, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) + { + return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, openReadOnly: false).Ret(); + } + + public Result OpenReadOnlySaveDataFileSystem(ref SharedRef fileSystem, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) + { + return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, openReadOnly: true).Ret(); + } + public Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) { @@ -1830,8 +1897,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Unsafe.SkipInit(out SaveDataExtraData extraDataMask); SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - return ReadSaveDataFileSystemExtraDataCore(out SpanHelpers.AsStruct(extraData.Buffer), - spaceId: default, saveDataId, in extraDataMask).Ret(); + return ReadSaveDataFileSystemExtraDataCore(out extraData.As(), spaceId: default, saveDataId, + in extraDataMask).Ret(); } public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData, SaveDataSpaceId spaceId, @@ -1840,7 +1907,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); - ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); + ref SaveDataExtraData extraDataRef = ref extraData.As(); Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); @@ -1868,7 +1935,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); - ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); + ref SaveDataExtraData extraDataRef = ref extraData.As(); // Make a mask for reading the entire extra data Unsafe.SkipInit(out SaveDataExtraData extraDataMask); @@ -1886,10 +1953,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); - ref readonly SaveDataExtraData maskRef = - ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); - - ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); + ref readonly SaveDataExtraData maskRef = ref extraDataMask.As(); + ref SaveDataExtraData extraDataRef = ref extraData.As(); Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); @@ -1959,8 +2024,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); - ref readonly SaveDataExtraData extraDataRef = - ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); + ref readonly SaveDataExtraData extraDataRef = ref extraData.As(); SaveDataExtraData extraDataMask = default; extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF); @@ -1996,11 +2060,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraDataMask.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); - ref readonly SaveDataExtraData extraDataRef = - ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); - - ref readonly SaveDataExtraData maskRef = - ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); + ref readonly SaveDataExtraData extraDataRef = ref extraData.As(); + ref readonly SaveDataExtraData maskRef = ref extraDataMask.As(); return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in maskRef).Ret(); } @@ -2157,8 +2218,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in tempFilter); - return FindSaveDataWithFilterImpl(out count, out SpanHelpers.AsStruct(saveDataInfoBuffer.Buffer), - spaceId, in infoFilter).Ret(); + return FindSaveDataWithFilterImpl(out count, out saveDataInfoBuffer.As(), spaceId, in infoFilter).Ret(); } private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId) @@ -2178,13 +2238,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - private Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef fileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey) + private Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporaryTransferSave) { throw new NotImplementedException(); } - public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef fileSystem, + public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId) { throw new NotImplementedException(); @@ -2391,34 +2451,34 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - public Result OpenSaveDataTransferManager(ref SharedRef manager) + public Result OpenSaveDataTransferManager(ref SharedRef outManager) { throw new NotImplementedException(); } - public Result OpenSaveDataTransferManagerVersion2(ref SharedRef manager) + public Result OpenSaveDataTransferManagerVersion2(ref SharedRef outManager) { throw new NotImplementedException(); } public Result OpenSaveDataTransferManagerForSaveDataRepair( - ref SharedRef manager) + ref SharedRef outManager) { throw new NotImplementedException(); } - public Result OpenSaveDataTransferManagerForRepair(ref SharedRef manager) + public Result OpenSaveDataTransferManagerForRepair(ref SharedRef outManager) { throw new NotImplementedException(); } - private Result OpenSaveDataTransferProhibiterCore(ref SharedRef prohibiter, + private Result OpenSaveDataTransferProhibiterCore(ref SharedRef outProhibiter, Ncm.ApplicationId applicationId) { throw new NotImplementedException(); } - public Result OpenSaveDataTransferProhibiter(ref SharedRef prohibiter, + public Result OpenSaveDataTransferProhibiter(ref SharedRef outProhibiter, Ncm.ApplicationId applicationId) { Result res = GetProgramInfo(out ProgramInfo programInfo); @@ -2429,7 +2489,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave res = SaveDataAccessibilityChecker.CheckOpenProhibiter(applicationId, programInfo); if (res.IsFailure()) return res.Miss(); - return OpenSaveDataTransferProhibiterCore(ref prohibiter, applicationId).Ret(); + return OpenSaveDataTransferProhibiterCore(ref outProhibiter, applicationId).Ret(); } public bool IsProhibited(ref UniqueLock outLock, ApplicationId applicationId) @@ -2437,7 +2497,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave throw new NotImplementedException(); } - public Result OpenSaveDataMover(ref SharedRef saveMover, SaveDataSpaceId sourceSpaceId, + public Result OpenSaveDataMover(ref SharedRef outSaveMover, SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize) { throw new NotImplementedException(); @@ -2460,7 +2520,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (bufferIdCount > 0) { - ids = MemoryMarshal.Cast(idBuffer.Buffer); + ids = idBuffer.AsSpan(); if (ids.Length < bufferIdCount) return ResultFs.InvalidSize.Log(); @@ -2582,7 +2642,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis); using var fileSystem = new SharedRef(); - Result res = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, SaveDataSpaceId.Temporary); + Result res = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, SaveDataSpaceId.Temporary, UnspecifiedSaveDataId); if (res.IsFailure()) return res.Miss(); using var pathRoot = new Path(); @@ -2611,7 +2671,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - public Result OpenMultiCommitContext(ref SharedRef contextFileSystem) + public Result OpenMultiCommitContext(ref SharedRef outContextFileSystem) { Result res = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(MultiCommitManager.ProgramId), SaveDataType.System, InvalidUserId, MultiCommitManager.SaveDataId, index: 0); @@ -2623,7 +2683,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave openReadOnly: false, cacheExtraData: true); if (res.IsFailure()) return res.Miss(); - contextFileSystem.SetByMove(ref fileSystem.Ref); + outContextFileSystem.SetByMove(ref fileSystem.Ref); return Result.Success; } @@ -2663,7 +2723,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return Result.Success; } - private Result TryAcquireSaveDataEntryOpenCountSemaphore(ref UniqueRef outSemaphoreLock) + public Result TryAcquireSaveDataEntryOpenCountSemaphore(ref UniqueRef outSemaphoreLock) { using SharedRef saveService = GetSharedFromThis(); @@ -2746,9 +2806,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } Result ISaveDataTransferCoreInterface.OpenSaveDataInternalStorageFileSystemCore( - ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey) + ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporaryTransferSave) { - return OpenSaveDataInternalStorageFileSystemCore(ref fileSystem, spaceId, saveDataId, useSecondMacKey); + return OpenSaveDataInternalStorageFileSystemCore(ref outFileSystem, spaceId, saveDataId, isTemporaryTransferSave); } Result ISaveDataTransferCoreInterface.OpenSaveDataMetaFileRaw(ref SharedRef outFile, SaveDataSpaceId spaceId, diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 63dca759..2590b0da 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -121,7 +121,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable using var fileSystem = new SharedRef(); - Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId); + Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId); if (res.IsFailure()) return res.Miss(); // Get the path of the save data @@ -160,7 +160,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable { using var fileSystem = new SharedRef(); - Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, in saveDataRootPath, true); + Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, true); if (res.IsFailure()) return res.Miss(); bool isEmulatedOnHost = IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); @@ -413,8 +413,8 @@ public class SaveDataFileSystemServiceImpl : IDisposable using var fileSystem = new SharedRef(); - Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, creationInfo.SpaceId, in saveDataRootPath, - allowEmulatedSave: false); + Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, creationInfo.SpaceId, saveDataId, + in saveDataRootPath, allowEmulatedSave: false); if (res.IsFailure()) return res.Miss(); using scoped var saveImageName = new Path(); @@ -486,7 +486,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable _saveFileSystemCacheManager.Unregister(spaceId, saveDataId); // Open the directory containing the save data - Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, in saveDataRootPath, false); + Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, false); if (res.IsFailure()) return res.Miss(); using scoped var saveImageName = new Path(); @@ -642,15 +642,15 @@ public class SaveDataFileSystemServiceImpl : IDisposable } public Result OpenSaveDataDirectoryFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId) + SaveDataSpaceId spaceId, ulong saveDataId) { using scoped var rootPath = new Path(); - return OpenSaveDataDirectoryFileSystem(ref outFileSystem, spaceId, in rootPath, allowEmulatedSave: true); + return OpenSaveDataDirectoryFileSystem(ref outFileSystem, spaceId, saveDataId, in rootPath, allowEmulatedSave: true); } public Result OpenSaveDataDirectoryFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, ref readonly Path saveDataRootPath, bool allowEmulatedSave) + SaveDataSpaceId spaceId, ulong saveDataId, ref readonly Path saveDataRootPath, bool allowEmulatedSave) { Result res; diff --git a/src/LibHac/FsSrv/SaveDataIndexer.cs b/src/LibHac/FsSrv/SaveDataIndexer.cs index b0563f92..68dd8aa1 100644 --- a/src/LibHac/FsSrv/SaveDataIndexer.cs +++ b/src/LibHac/FsSrv/SaveDataIndexer.cs @@ -156,7 +156,7 @@ public class SaveDataIndexer : ISaveDataIndexer if (_handle != _indexer.GetHandle()) return ResultFs.InvalidHandle.Log(); - Span outInfos = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); + Span outInfos = saveDataInfoBuffer.AsSpan(); int count; for (count = 0; !_iterator.IsEnd() && count < outInfos.Length; count++) diff --git a/src/LibHac/FsSrv/SaveDataIndexerLite.cs b/src/LibHac/FsSrv/SaveDataIndexerLite.cs index f90085f4..81e646cf 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerLite.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerLite.cs @@ -53,7 +53,7 @@ internal class SaveDataIndexerLiteInfoReader : SaveDataInfoReaderImpl if (saveDataInfoBuffer.Size < Unsafe.SizeOf()) return ResultFs.InvalidSize.Log(); - Unsafe.As(ref MemoryMarshal.GetReference(saveDataInfoBuffer.Buffer)) = _info; + saveDataInfoBuffer.As() = _info; readCount = 1; _finishedIterating = true; diff --git a/src/LibHac/FsSrv/SaveDataInfoFilter.cs b/src/LibHac/FsSrv/SaveDataInfoFilter.cs index 71f98f1d..1e6f2c8e 100644 --- a/src/LibHac/FsSrv/SaveDataInfoFilter.cs +++ b/src/LibHac/FsSrv/SaveDataInfoFilter.cs @@ -132,7 +132,7 @@ internal class SaveDataInfoFilterReader : SaveDataInfoReaderImpl { UnsafeHelpers.SkipParamInit(out readCount); - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); + Span outInfo = saveDataInfoBuffer.AsSpan(); int count = 0; while (count < outInfo.Length) diff --git a/src/LibHac/Sf/Buffers.cs b/src/LibHac/Sf/Buffers.cs index 9a9d6a3b..125e7ece 100644 --- a/src/LibHac/Sf/Buffers.cs +++ b/src/LibHac/Sf/Buffers.cs @@ -22,6 +22,11 @@ public readonly ref struct InBuffer return ref SpanHelpers.AsReadOnlyStruct(_buffer); } + public ReadOnlySpan AsSpan() where T : unmanaged + { + return MemoryMarshal.Cast(_buffer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static InBuffer FromSpan(ReadOnlySpan buffer) where T : unmanaged { @@ -52,6 +57,11 @@ public readonly ref struct OutBuffer return ref SpanHelpers.AsStruct(_buffer); } + public Span AsSpan() where T : unmanaged + { + return MemoryMarshal.Cast(_buffer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static OutBuffer FromSpan(Span buffer) where T : unmanaged {