diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index bb3b9c40..a6cc7858 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -11,21 +11,37 @@ namespace LibHac.Fs; public struct PathFlags { - private uint _value; + private PathFormatFlags _formatFlags; - public void AllowWindowsPath() => _value |= 1 << 0; - public void AllowRelativePath() => _value |= 1 << 1; - public void AllowEmptyPath() => _value |= 1 << 2; - public void AllowMountName() => _value |= 1 << 3; - public void AllowBackslash() => _value |= 1 << 4; - public void AllowAllCharacters() => _value |= 1 << 5; + [Flags] + public enum PathFormatFlags + { + AllowWindowsPath = 1 << 0, + AllowRelativePath = 1 << 1, + AllowEmptyPath = 1 << 2, + AllowMountName = 1 << 3, + AllowBackslash = 1 << 4, + AllowInvalidCharacter = 1 << 5 + } - public readonly bool IsWindowsPathAllowed() => (_value & (1 << 0)) != 0; - public readonly bool IsRelativePathAllowed() => (_value & (1 << 1)) != 0; - public readonly bool IsEmptyPathAllowed() => (_value & (1 << 2)) != 0; - public readonly bool IsMountNameAllowed() => (_value & (1 << 3)) != 0; - public readonly bool IsBackslashAllowed() => (_value & (1 << 4)) != 0; - public readonly bool AreAllCharactersAllowed() => (_value & (1 << 5)) != 0; + public PathFlags(PathFormatFlags formatFlags) + { + _formatFlags = formatFlags; + } + + public void AllowWindowsPath() => _formatFlags |= PathFormatFlags.AllowWindowsPath; + public void AllowRelativePath() => _formatFlags |= PathFormatFlags.AllowRelativePath; + public void AllowEmptyPath() => _formatFlags |= PathFormatFlags.AllowEmptyPath; + public void AllowMountName() => _formatFlags |= PathFormatFlags.AllowMountName; + public void AllowBackslash() => _formatFlags |= PathFormatFlags.AllowBackslash; + public void AllowInvalidCharacter() => _formatFlags |= PathFormatFlags.AllowInvalidCharacter; + + public readonly bool IsWindowsPathAllowed() => (_formatFlags & PathFormatFlags.AllowWindowsPath) != 0; + public readonly bool IsRelativePathAllowed() => (_formatFlags & PathFormatFlags.AllowRelativePath) != 0; + public readonly bool IsEmptyPathAllowed() => (_formatFlags & PathFormatFlags.AllowEmptyPath) != 0; + public readonly bool IsMountNameAllowed() => (_formatFlags & PathFormatFlags.AllowMountName) != 0; + public readonly bool IsBackslashAllowed() => (_formatFlags & PathFormatFlags.AllowBackslash) != 0; + public readonly bool IsInvalidCharacterAllowed() => (_formatFlags & PathFormatFlags.AllowInvalidCharacter) != 0; } /// diff --git a/src/LibHac/Fs/Common/PathFormatter.cs b/src/LibHac/Fs/Common/PathFormatter.cs index 4636e467..b2061e8c 100644 --- a/src/LibHac/Fs/Common/PathFormatter.cs +++ b/src/LibHac/Fs/Common/PathFormatter.cs @@ -517,7 +517,7 @@ public static class PathFormatter return Result.Success; } - res = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer, flags.AreAllCharactersAllowed()); + res = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer, flags.IsInvalidCharacterAllowed()); if (res.IsFailure()) return res.Miss(); totalLength += length; @@ -639,7 +639,7 @@ public static class PathFormatter } res = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative, - flags.AreAllCharactersAllowed()); + flags.IsInvalidCharacterAllowed()); if (res.IsFailure()) return res.Miss(); return Result.Success; diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index c9841619..c0ddfbf4 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -293,6 +293,8 @@ public class FileSystemInterfaceAdapter : IFileSystemSf _selfReference.Destroy(); } + public static PathFlags GetDefaultPathFlags() => new PathFlags(); + private static ReadOnlySpan RootDir => "/"u8; private Result SetUpPath(ref Path fsPath, ref readonly PathSf sfPath) diff --git a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs index f6dafdc4..8f5e3b8d 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs @@ -115,19 +115,57 @@ public static class SaveDataProperties public static bool IsObsoleteSystemSaveData(in SaveDataInfo info) { + foreach (ulong id in ObsoleteSystemSaveDataIdList) + { + if (info.StaticSaveDataId == id) + return true; + } + return false; - throw new NotImplementedException(); } public static bool IsWipingNeededAtCleanUp(in SaveDataInfo info) { - return false; - throw new NotImplementedException(); + switch (info.Type) + { + case SaveDataType.System: + break; + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return true; + default: + Abort.UnexpectedDefault(); + break; + } + + foreach (ulong id in SystemSaveDataIdWipingExceptionList) + { + if (info.StaticSaveDataId == id) + return false; + } + + return true; } public static bool IsValidSpaceIdForSaveDataMover(SaveDataType type, SaveDataSpaceId spaceId) { - throw new NotImplementedException(); + switch (type) + { + case SaveDataType.System: + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Temporary: + return false; + case SaveDataType.Cache: + return spaceId == SaveDataSpaceId.User || spaceId == SaveDataSpaceId.SdUser; + default: + Abort.UnexpectedDefault(); + return default; + } } public static bool IsReconstructible(SaveDataType type, SaveDataSpaceId spaceId) @@ -161,4 +199,16 @@ public static class SaveDataProperties return default; } } + + private static ReadOnlySpan ObsoleteSystemSaveDataIdList => [0x8000000000000060, 0x8000000000000075]; + + private static ReadOnlySpan SystemSaveDataIdWipingExceptionList => + [ + 0x8000000000000040, 0x8000000000000041, 0x8000000000000043, 0x8000000000000044, 0x8000000000000045, + 0x8000000000000046, 0x8000000000000047, 0x8000000000000048, 0x8000000000000049, 0x800000000000004A, + 0x8000000000000070, 0x8000000000000071, 0x8000000000000072, 0x8000000000000074, 0x8000000000000076, + 0x8000000000000077, 0x8000000000000090, 0x8000000000000091, 0x8000000000000092, 0x80000000000000B0, + 0x80000000000000C1, 0x80000000000000C2, 0x8000000000000120, 0x8000000000000121, 0x8000000000000180, + 0x8000000000010003, 0x8000000000010004 + ]; } \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index 23dffb7b..50f9695b 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -32,6 +32,12 @@ using Utility = LibHac.FsSystem.Utility; namespace LibHac.FsSrv; +/// +/// Creates locks for incrementing and decrementing the save data open-count semaphore +/// from a to keep track of how many save data files are currently open. +/// +/// Used by objects such as s that open save data files. +///
Based on nnSdk 17.5.0 (FS 17.0.0)
file class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager { private SharedRef _saveService; @@ -115,7 +121,7 @@ file static class Anonymous : SaveDataFormatType.NoJournal; } - public static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) + public static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, ulong processId, SaveDataSpaceId spaceId) { Assert.SdkNotNull(programInfo); @@ -142,6 +148,10 @@ file static class Anonymous } } +/// +/// Determines if a specified program has access to perform various functions on a specified save data. +/// +/// Based on nnSdk 17.5.0 (FS 17.0.0) file static class SaveDataAccessibilityChecker { public delegate Result ExtraDataReader(out SaveDataExtraData extraData); @@ -565,7 +575,7 @@ file static class SaveDataAccessibilityChecker ///
/// 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 nnSdk 14.3.0 (FS 14.1.0)
+///
Based on nnSdk 17.5.0 (FS 17.0.0) internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface { private const int OpenEntrySemaphoreCount = 256; @@ -761,8 +771,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return DeleteSaveDataFileSystemCommon(spaceId, saveDataId).Ret(); } - public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, - in SaveDataAttribute attribute) + public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute) { using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); @@ -1725,8 +1734,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var fileSystem = new SharedRef(); // Open the file system - res = OpenSaveDataFileSystemCore(ref fileSystem.Ref, out ulong saveDataId, spaceId, in attribute, - openReadOnly, cacheExtraData: true); + res = OpenSaveDataFileSystemCore(ref fileSystem.Ref, out ulong saveDataId, spaceId, in attribute, openReadOnly, + cacheExtraData: true); if (res.IsFailure()) return res.Miss(); // Can't use attribute in a closure, so copy the needed field @@ -1764,9 +1773,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var openCountFileSystem = new SharedRef(new OpenCountFileSystem(ref asyncFileSystem.Ref, ref openEntryCountAdapter.Ref, ref mountCountSemaphore.Ref)); - var pathFlags = new PathFlags(); + PathFlags pathFlags = FileSystemInterfaceAdapter.GetDefaultPathFlags(); pathFlags.AllowBackslash(); - pathFlags.AllowAllCharacters(); + pathFlags.AllowInvalidCharacter(); using SharedRef fileSystemAdapter = FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, false); @@ -1862,7 +1871,7 @@ 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).Ret(); } // Check if we have permissions to open this save data @@ -1891,25 +1900,23 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var openCountFileSystem = new SharedRef( new OpenCountFileSystem(ref asyncFileSystem.Ref, ref openEntryCountAdapter.Ref)); - var pathFlags = new PathFlags(); + PathFlags pathFlags = FileSystemInterfaceAdapter.GetDefaultPathFlags(); pathFlags.AllowBackslash(); - pathFlags.AllowAllCharacters(); + pathFlags.AllowInvalidCharacter(); using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, - allowAllOperations: false); + FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, allowAllOperations: false); outFileSystem.SetByMove(ref fileSystemAdapter.Ref); - return Result.Success; } // ReSharper disable once UnusedParameter.Local - // Nintendo used isTemporarySaveData in older FS versions, but never removed the parameter. - private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, + // The unused parameter hasn't been removed because this method is part of an interface. It was used in older FS versions. + private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData) { - UnsafeHelpers.SkipParamInit(out extraData); + UnsafeHelpers.SkipParamInit(out outExtraData); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); using var accessor = new UniqueRef(); @@ -1921,14 +1928,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (res.IsFailure()) return res.Miss(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - return _serviceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type, + return _serviceImpl.ReadSaveDataFileSystemExtraData(out outExtraData, spaceId, saveDataId, key.Type, in saveDataRootPath).Ret(); } - private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, + private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData, Optional spaceId, ulong saveDataId, in SaveDataExtraData extraDataMask) { - UnsafeHelpers.SkipParamInit(out extraData); + UnsafeHelpers.SkipParamInit(out outExtraData); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); @@ -1971,7 +1978,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave res = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue _, saveDataId); if (res.IsFailure()) return res.Miss(); - resolvedSpaceId = spaceId.ValueRo; + resolvedSpaceId = spaceId.Value; res = accessor.Get.GetInterface().GetKey(out key, saveDataId); if (res.IsFailure()) return res.Miss(); @@ -1984,7 +1991,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave in savePath); } - res = SaveDataAccessibilityChecker.CheckReadExtraData(resolvedSpaceId, in key, in extraDataMask, programInfo, _processId, ReadExtraData); + res = SaveDataAccessibilityChecker.CheckReadExtraData(resolvedSpaceId, in key, in extraDataMask, programInfo, + _processId, ReadExtraData); if (res.IsFailure()) return res.Miss(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); @@ -1993,7 +2001,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (res.IsFailure()) return res.Miss(); MaskExtraData(ref tempExtraData, in extraDataMask); - extraData = tempExtraData; + outExtraData = tempExtraData; return Result.Success; } @@ -2003,6 +2011,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraData.IsNull) + return ResultFs.NullptrArgument.Log(); + // Make a mask for reading the entire extra data Unsafe.SkipInit(out SaveDataExtraData extraDataMask); SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); @@ -2017,6 +2028,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraData.IsNull) + return ResultFs.NullptrArgument.Log(); + ref SaveDataExtraData extraDataRef = ref extraData.As(); Result res = GetProgramInfo(out ProgramInfo programInfo); @@ -2045,6 +2059,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraData.IsNull) + return ResultFs.NullptrArgument.Log(); + ref SaveDataExtraData extraDataRef = ref extraData.As(); // Make a mask for reading the entire extra data @@ -2060,9 +2077,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraDataMask.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraDataMask.IsNull) + return ResultFs.NullptrArgument.Log(); + if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraData.IsNull) + return ResultFs.NullptrArgument.Log(); + ref readonly SaveDataExtraData maskRef = ref extraDataMask.As(); ref SaveDataExtraData extraDataRef = ref extraData.As(); @@ -2101,13 +2124,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (res.IsFailure()) return res.Miss(); using var accessor = new UniqueRef(); - res = OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId); if (res.IsFailure()) return res.Miss(); res = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); if (res.IsFailure()) return res.Miss(); + SaveDataType saveDataType = key.Type; + Result ReadExtraData(out SaveDataExtraData data) { using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); @@ -2120,13 +2144,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); res = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, saveDataId, - key.Type, in saveDataRootPath); + saveDataType, in saveDataRootPath); if (res.IsFailure()) return res.Miss(); ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask); return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify, - in saveDataRootPath, key.Type, updateTimeStamp: false).Ret(); + in saveDataRootPath, saveDataType, updateTimeStamp: false).Ret(); } public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData) @@ -2134,6 +2158,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraData.IsNull) + return ResultFs.NullptrArgument.Log(); + ref readonly SaveDataExtraData extraDataRef = ref extraData.As(); SaveDataExtraData extraDataMask = default; @@ -2167,9 +2194,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (extraData.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraData.IsNull) + return ResultFs.NullptrArgument.Log(); + if (extraDataMask.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (extraDataMask.IsNull) + return ResultFs.NullptrArgument.Log(); + ref readonly SaveDataExtraData extraDataRef = ref extraData.As(); ref readonly SaveDataExtraData maskRef = ref extraDataMask.As(); @@ -2213,7 +2246,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); - res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId); if (res.IsFailure()) return res.Miss(); using var filterReader = new UniqueRef(); @@ -2250,7 +2283,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal)) return ResultFs.PermissionDenied.Log(); - res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId); if (res.IsFailure()) return res.Miss(); using var filterReader = new UniqueRef(); @@ -2303,17 +2336,20 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (saveDataInfoBuffer.Size != Unsafe.SizeOf()) return ResultFs.InvalidArgument.Log(); + if (saveDataInfoBuffer.IsNull) + return ResultFs.NullptrArgument.Log(); + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); - res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId); if (res.IsFailure()) { if (!ResultFs.PermissionDenied.Includes(res)) - return res; + return res.Miss(); // Don't have full info reader permissions. Check if we have find permissions. res = SaveDataAccessibilityChecker.CheckFind(spaceId, in filter, programInfo, _processId); @@ -2328,7 +2364,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in tempFilter); - return FindSaveDataWithFilterImpl(out count, out saveDataInfoBuffer.As(), spaceId, in infoFilter).Ret(); + res = FindSaveDataWithFilterImpl(out var outCount, out saveDataInfoBuffer.As(), spaceId, in infoFilter); + if (res.IsFailure()) return res.Miss(); + + count = outCount; + return Result.Success; } private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId) @@ -2519,10 +2559,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (res.IsFailure()) { - spaceId = SaveDataSpaceId.User; - - if (!ResultFs.TargetNotFound.Includes(res)) - return res; + if (ResultFs.TargetNotFound.Includes(res)) + { + spaceId = SaveDataSpaceId.User; + } + else + { + return res.Miss(); + } } return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId).Ret(); @@ -2536,7 +2580,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); - if (spaceId != SaveDataSpaceId.SdUser && spaceId != SaveDataSpaceId.User) + if (spaceId != SaveDataSpaceId.User && spaceId != SaveDataSpaceId.SdUser) return ResultFs.InvalidSaveDataSpaceId.Log(); using var filterReader = new UniqueRef(); @@ -2686,7 +2730,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (res.IsFailure()) return res.Miss(); ulong resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; - return GetCacheStorageSpaceId(out spaceId, resolvedProgramId); + return GetCacheStorageSpaceId(out spaceId, resolvedProgramId).Ret(); } private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId, ulong programId) @@ -2694,7 +2738,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave UnsafeHelpers.SkipParamInit(out spaceId); Result res; - // Cache storage on the SD card will always take priority over case storage in NAND + // Cache storage on the SD card will always take priority over cache storage in NAND if (_serviceImpl.IsSdCardAccessible()) { res = DoesCacheStorageExist(out bool existsOnSdCard, SaveDataSpaceId.SdUser); @@ -2722,8 +2766,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { UnsafeHelpers.SkipParamInit(out exists); - var filter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache, - userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary); + var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(saveSpaceId), new ProgramId(programId), + SaveDataType.Cache, userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary); Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in filter); if (result.IsFailure()) return result; @@ -2954,6 +2998,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (bufferIdCount > 0) { + if (idBuffer.IsNull) + return ResultFs.NullptrArgument.Log(); + ids = idBuffer.AsSpan(); if (ids.Length < bufferIdCount) @@ -3015,8 +3062,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); res = _serviceImpl.OpenSaveDataFileSystem(ref fileSystem.Ref, value.SpaceId, saveDataId, - in saveDataRootPath, - openReadOnly: false, saveDataType, cacheExtraData: true); + in saveDataRootPath, openReadOnly: false, saveDataType, cacheExtraData: true); if (res.IsFailure()) return res.Miss(); // Verify the file system. @@ -3027,7 +3073,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } finally { - // Make sure we don't leak any invalid data. + // Make sure we don't leak any data. workBuffer.Buffer.Clear(); } } @@ -3305,8 +3351,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback) { + ulong saveDataId = IsStaticSaveDataIdValueRange(saveInfo.SaveDataId) + ? saveInfo.SaveDataId + : InvalidSystemSaveDataId; + Result res = SaveDataAttribute.Make(out SaveDataAttribute attribute, saveInfo.ProgramId, saveInfo.Type, - saveInfo.UserId, saveInfo.SaveDataId, saveInfo.Index); + saveInfo.UserId, saveDataId, saveInfo.Index); if (res.IsFailure()) return res.Miss(); using var fileSystem = new SharedRef(); @@ -3333,8 +3383,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { using SharedRef saveService = GetSharedFromThis(); - Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, - ref saveService.Ref); + Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, ref saveService.Ref); if (res.IsFailure()) return res.Miss(); return Result.Success; @@ -3344,8 +3393,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave { using SharedRef saveService = GetSharedFromThis(); - Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, - ref saveService.Ref); + Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, ref saveService.Ref); if (res.IsFailure()) return res.Miss(); return Result.Success; @@ -3407,8 +3455,23 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (isInitialOpen) { - CleanUpSaveData(accessor.Get).IgnoreResult(); - CompleteSaveDataExtension(accessor.Get).IgnoreResult(); + Unsafe.SkipInit(out Array80 stringBuffer); + + Result result = CleanUpSaveData(accessor.Get); + if (result.IsFailure()) + { + var sb = new U8StringBuilder(stringBuffer, true); + sb.Append("[fs] Failed to clean up save data ("u8).AppendFormat(result.Value, 'x').Append(")\n"u8); + Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, sb.Buffer); + } + + result = CompleteSaveDataExtension(accessor.Get); + if (result.IsFailure()) + { + var sb = new U8StringBuilder(stringBuffer, true); + sb.Append("[fs] Failed to complete save data extension ("u8).AppendFormat(result.Value, 'x').Append(")\n"u8); + Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, sb.Buffer); + } } outAccessor.Set(ref accessor.Ref); @@ -3419,14 +3482,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized) { - return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, - leaveUnfinalized); + return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, leaveUnfinalized); } - Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, + Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData) { - return ReadSaveDataFileSystemExtraDataCore(out extraData, spaceId, saveDataId, isTemporarySaveData); + return ReadSaveDataFileSystemExtraDataCore(out outExtraData, spaceId, saveDataId, isTemporarySaveData); } Result ISaveDataTransferCoreInterface.WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, diff --git a/src/LibHac/Sf/Buffers.cs b/src/LibHac/Sf/Buffers.cs index 125e7ece..cac5fa40 100644 --- a/src/LibHac/Sf/Buffers.cs +++ b/src/LibHac/Sf/Buffers.cs @@ -11,6 +11,7 @@ public readonly ref struct InBuffer public int Size => _buffer.Length; public ReadOnlySpan Buffer => _buffer; + public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer)); public InBuffer(ReadOnlySpan buffer) { @@ -46,6 +47,7 @@ public readonly ref struct OutBuffer public int Size => _buffer.Length; public Span Buffer => _buffer; + public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer)); public OutBuffer(Span buffer) { @@ -81,6 +83,7 @@ public readonly ref struct InArray where T : unmanaged public int Size => _array.Length; public ReadOnlySpan Array => _array; + public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_array)); public InArray(ReadOnlySpan array) { @@ -96,6 +99,7 @@ public readonly ref struct OutArray where T : unmanaged public int Size => _array.Length; public Span Array => _array; + public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_array)); public OutArray(Span array) { diff --git a/tests/LibHac.Tests/Fs/PathFormatterTests.cs b/tests/LibHac.Tests/Fs/PathFormatterTests.cs index 7dfce412..1b81b02b 100644 --- a/tests/LibHac.Tests/Fs/PathFormatterTests.cs +++ b/tests/LibHac.Tests/Fs/PathFormatterTests.cs @@ -455,7 +455,7 @@ public class PathFormatterTests flags.AllowWindowsPath(); break; case 'C': - flags.AllowAllCharacters(); + flags.AllowInvalidCharacter(); break; default: throw new NotSupportedException();