diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index f6eeebe6..4fb9abc7 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -934,6 +934,10 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,5326,,,,UnexpectedInCompressedStorageC, 2,5327,,,,UnexpectedInCompressedStorageD, 2,5328,,,,UnexpectedInPathA, +2,5333,,,,UnexpectedInSaveDataFileSystemCoreImplA, +2,5334,,,,UnexpectedInIntegritySaveDataFileSystemA, +2,5335,,,,UnexpectedInJournalIntegritySaveDataFileSystemD, +2,5336,,,,UnexpectedInAlignmentMatchableFileSystemA, 2,6000,6499,,,PreconditionViolation, 2,6001,6199,,,InvalidArgument, @@ -1085,11 +1089,14 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,6460,,,,GameCardLogoDataSizeInvalid, 2,6461,,,,AllocatorAlignmentViolation, 2,6462,,,,GlobalFileDataCacheAlreadyEnabled, -2,6463,,,,MultiCommitHasOverlappingTargets,The provided file system has already been added to the multi-commit manager. -2,6464,,,,MultiCommitAlreadyInProgress,A multi-commit was performed while another multi-commit operation was already running. +2,6463,,,,MultiCommitFileSystemDuplicated,The provided file system has already been added to the multi-commit manager. +2,6464,,,,SaveDataMultiCommitRepeated,A multi-commit was performed while another multi-commit operation was already running. 2,6465,,,,UserNotExist, 2,6466,,,,DefaultGlobalFileDataCacheEnabled, 2,6467,,,,SaveDataRootPathUnavailable, +2,6470,,,,RomMountDivisionSizeUnitCountLimit, +2,6471,,,,RomMountCountLimit, +2,6472,,,,AocMountDivisionSizeUnitCountLimit, 2,6600,6699,,,NotFound, 2,6602,,,,FileNotFound, @@ -1150,7 +1157,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,7113,,,,InvalidRamDiskSaveDataFileReadOffset, 2,7114,,,,InvalidRamDiskSaveDataCoreDataStorageSize, -2,7121,7139,,,RamDiskDatabaseCorrupted, +2,7121,7129,,,RamDiskDatabaseCorrupted, 2,7122,,,,InvalidRamDiskAllocationTableBlock, 2,7123,,,,InvalidRamDiskKeyValueListElementIndex, 2,7124,,,,InvalidRamDiskAllocationTableChainEntry, diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index cf5693b2..c255d46e 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -28,7 +28,8 @@ public enum ContentStorageId { System = 0, User = 1, - SdCard = 2 + SdCard = 2, + System0 = 3 } public enum GameCardPartition diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index b6b9cdc5..81fc7d1f 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -1699,6 +1699,14 @@ public static class ResultFs public static Result.Base UnexpectedInCompressedStorageD => new Result.Base(ModuleFs, 5327); /// Error code: 2002-5328; Inner value: 0x29a002 public static Result.Base UnexpectedInPathA => new Result.Base(ModuleFs, 5328); + /// Error code: 2002-5333; Inner value: 0x29aa02 + public static Result.Base UnexpectedInSaveDataFileSystemCoreImplA => new Result.Base(ModuleFs, 5333); + /// Error code: 2002-5334; Inner value: 0x29ac02 + public static Result.Base UnexpectedInIntegritySaveDataFileSystemA => new Result.Base(ModuleFs, 5334); + /// Error code: 2002-5335; Inner value: 0x29ae02 + public static Result.Base UnexpectedInJournalIntegritySaveDataFileSystemD => new Result.Base(ModuleFs, 5335); + /// Error code: 2002-5336; Inner value: 0x29b002 + public static Result.Base UnexpectedInAlignmentMatchableFileSystemA => new Result.Base(ModuleFs, 5336); /// Error code: 2002-6000; Range: 6000-6499; Inner value: 0x2ee002 public static Result.Base PreconditionViolation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6000, 6499); } @@ -1993,15 +2001,21 @@ public static class ResultFs /// Error code: 2002-6462; Inner value: 0x327c02 public static Result.Base GlobalFileDataCacheAlreadyEnabled => new Result.Base(ModuleFs, 6462); /// The provided file system has already been added to the multi-commit manager.
Error code: 2002-6463; Inner value: 0x327e02
- public static Result.Base MultiCommitHasOverlappingTargets => new Result.Base(ModuleFs, 6463); + public static Result.Base MultiCommitFileSystemDuplicated => new Result.Base(ModuleFs, 6463); /// A multi-commit was performed while another multi-commit operation was already running.
Error code: 2002-6464; Inner value: 0x328002
- public static Result.Base MultiCommitAlreadyInProgress => new Result.Base(ModuleFs, 6464); + public static Result.Base SaveDataMultiCommitRepeated => new Result.Base(ModuleFs, 6464); /// Error code: 2002-6465; Inner value: 0x328202 public static Result.Base UserNotExist => new Result.Base(ModuleFs, 6465); /// Error code: 2002-6466; Inner value: 0x328402 public static Result.Base DefaultGlobalFileDataCacheEnabled => new Result.Base(ModuleFs, 6466); /// Error code: 2002-6467; Inner value: 0x328602 public static Result.Base SaveDataRootPathUnavailable => new Result.Base(ModuleFs, 6467); + /// Error code: 2002-6470; Inner value: 0x328c02 + public static Result.Base RomMountDivisionSizeUnitCountLimit => new Result.Base(ModuleFs, 6470); + /// Error code: 2002-6471; Inner value: 0x328e02 + public static Result.Base RomMountCountLimit => new Result.Base(ModuleFs, 6471); + /// Error code: 2002-6472; Inner value: 0x329002 + public static Result.Base AocMountDivisionSizeUnitCountLimit => new Result.Base(ModuleFs, 6472); /// Error code: 2002-6600; Range: 6600-6699; Inner value: 0x339002 public static Result.Base NotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6600, 6699); } @@ -2112,8 +2126,8 @@ public static class ResultFs /// Error code: 2002-7114; Inner value: 0x379402 public static Result.Base InvalidRamDiskSaveDataCoreDataStorageSize => new Result.Base(ModuleFs, 7114); - /// Error code: 2002-7121; Range: 7121-7139; Inner value: 0x37a202 - public static Result.Base RamDiskDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7121, 7139); } + /// Error code: 2002-7121; Range: 7121-7129; Inner value: 0x37a202 + public static Result.Base RamDiskDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7121, 7129); } /// Error code: 2002-7122; Inner value: 0x37a402 public static Result.Base InvalidRamDiskAllocationTableBlock => new Result.Base(ModuleFs, 7122); /// Error code: 2002-7123; Inner value: 0x37a602 diff --git a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs index 60c9794f..2837c93f 100644 --- a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs +++ b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs @@ -120,7 +120,7 @@ internal class MultiCommitManager : IMultiCommitManager /// : The operation was successful.
/// : The maximum number of file systems have been added. /// file systems may be added to a single multi-commit.
- /// : The provided file system has already been added.
+ /// : The provided file system has already been added. public Result Add(ref SharedRef fileSystem) { if (_fileSystemCount >= MaxFileSystemCount) @@ -134,7 +134,7 @@ internal class MultiCommitManager : IMultiCommitManager for (int i = 0; i < _fileSystemCount; i++) { if (ReferenceEquals(fsaFileSystem.Get, _fileSystems[i].Get)) - return ResultFs.MultiCommitHasOverlappingTargets.Log(); + return ResultFs.MultiCommitFileSystemDuplicated.Log(); } _fileSystems[_fileSystemCount].SetByMove(ref fsaFileSystem.Ref); diff --git a/src/LibHac/FsSrv/NcaFileSystemService.cs b/src/LibHac/FsSrv/NcaFileSystemService.cs index 3912f7a5..400203bd 100644 --- a/src/LibHac/FsSrv/NcaFileSystemService.cs +++ b/src/LibHac/FsSrv/NcaFileSystemService.cs @@ -506,13 +506,12 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager ContentStorageId contentStorageId) { StorageLayoutType storageFlag = contentStorageId == ContentStorageId.System ? StorageLayoutType.Bis : StorageLayoutType.All; - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentStorage); + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentStorage); if (!accessibility.CanRead || !accessibility.CanWrite) return ResultFs.PermissionDenied.Log(); @@ -522,14 +521,10 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager if (res.IsFailure()) return res.Miss(); // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref, storageFlag)); - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref)); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref, false); + using var typeSetFileSystem = new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref, storageFlag)); + using var alignmentMatchableFileSystem = new SharedRef(new AlignmentMatchableFileSystem(ref fileSystem.Ref)); + using var asyncFileSystem = new SharedRef(new AsynchronousAccessFileSystem(ref alignmentMatchableFileSystem.Ref)); + using SharedRef fileSystemAdapter = FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref, allowAllOperations: false); outFileSystem.SetByMove(ref fileSystemAdapter.Ref); @@ -544,7 +539,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) return ResultFs.PermissionDenied.Log(); - return _serviceImpl.RegisterExternalKey(in rightsId, in accessKey); + return _serviceImpl.RegisterExternalKey(in rightsId, in accessKey).Ret(); } public Result UnregisterExternalKey(in RightsId rightsId) @@ -555,7 +550,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) return ResultFs.PermissionDenied.Log(); - return _serviceImpl.UnregisterExternalKey(in rightsId); + return _serviceImpl.UnregisterExternalKey(in rightsId).Ret(); } public Result UnregisterAllExternalKey() @@ -584,13 +579,13 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager targetProgramId, programInfo.StorageId); if (res.IsFailure()) return res.Miss(); - return _serviceImpl.RegisterUpdatePartition(targetProgramId, in programPath, contentAttributes); + return _serviceImpl.RegisterUpdatePartition(targetProgramId, in programPath, contentAttributes).Ret(); } public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) { - var storageFlag = StorageLayoutType.All; - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + const StorageLayoutType storageFlag = StorageLayoutType.All; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); @@ -612,7 +607,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref)); using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref, false); + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref, allowAllOperations: false); outFileSystem.SetByMove(ref fileSystemAdapter.Ref); @@ -632,7 +627,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed)) return ResultFs.PermissionDenied.Log(); - return _serviceImpl.SetSdCardEncryptionSeed(in encryptionSeed); + return _serviceImpl.SetSdCardEncryptionSeed(in encryptionSeed).Ret(); } public Result OpenHostFileSystem(ref SharedRef outFileSystem, ref readonly FspPath path) @@ -652,7 +647,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager public Result HandleResolubleAccessFailure(out bool wasDeferred, Result nonDeferredResult) { - return _serviceImpl.HandleResolubleAccessFailure(out wasDeferred, nonDeferredResult, _processId); + return _serviceImpl.HandleResolubleAccessFailure(out wasDeferred, nonDeferredResult, _processId).Ret(); } Result IRomFileSystemAccessFailureManager.OpenDataStorageCore(ref SharedRef outStorage, diff --git a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs index 24c214fc..8af41156 100644 --- a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs @@ -260,12 +260,17 @@ public class NcaFileSystemServiceImpl : IDisposable public Result RegisterUpdatePartition(ulong programId, ref readonly Path path, ContentAttributes attributes) { - throw new NotImplementedException(); + return _updatePartitionPath.Set(programId, in path, attributes).Ret(); } public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) { - throw new NotImplementedException(); + using var path = new Path(); + Result res = _updatePartitionPath.Get(ref path.Ref(), out ContentAttributes contentAttributes, out ulong updaterProgramId); + if (res.IsFailure()) return res.Miss(); + + return OpenFileSystem(ref outFileSystem, in path, contentAttributes, FileSystemProxyType.RegisteredUpdate, + updaterProgramId, isDirectory: false).Ret(); } private Result ParseMountName(ref U8Span path, ref SharedRef outFileSystem, out MountInfo outMountInfo) diff --git a/src/LibHac/FsSystem/AlignmentMatchableFileSystem.cs b/src/LibHac/FsSystem/AlignmentMatchableFileSystem.cs new file mode 100644 index 00000000..68399b58 --- /dev/null +++ b/src/LibHac/FsSystem/AlignmentMatchableFileSystem.cs @@ -0,0 +1,128 @@ +using System; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Util; + +namespace LibHac.FsSystem; + +public class AlignmentMatchingFile : ForwardingFile +{ + private readonly OpenMode _openMode; + + public AlignmentMatchingFile(ref UniqueRef baseFile, OpenMode openMode) : base(ref baseFile) + { + _openMode = openMode; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Assert.SdkNotNull(BaseFile); + + Result res = DryWrite(out bool needsAppend, offset, source.Length, in option, _openMode); + if (res.IsFailure()) return res.Miss(); + + if (needsAppend) + { + res = SetSize(offset + source.Length); + if (res.IsFailure()) return res.Miss(); + } + + res = GetSize(out long fileSize); + if (res.IsFailure()) return res.Miss(); + + const int writeOffsetAlignment = 0x4000; // All writes to the base file must start on a multiple of this value + const int writeSizeAlignment = 0x200; // All writes to the base file must end on a multiple of this value, or end at the end of the file + + // The offset and size of the head and tail blocks will be aligned to this value. + // Must be >= writeSizeAlignment and <= writeOffsetAlignment + const int blockAlignment = 0x4000; + + long alignedStartOffset = Alignment.AlignDown(offset, writeOffsetAlignment); + long endOffset = offset + source.Length; + long alignedEndOffset = Math.Min(Alignment.AlignUp(endOffset, writeSizeAlignment), fileSize); + + if (alignedStartOffset == offset && alignedEndOffset == endOffset) + { + return BaseFile.Get.Write(offset, source, in option).Ret(); + } + + if (!_openMode.HasFlag(OpenMode.Read)) + return ResultFs.UnexpectedInAlignmentMatchableFileSystemA.Log(); + + long alignedBufferEndOffset = Math.Min(Alignment.AlignUp(endOffset, blockAlignment), fileSize); + int pooledBufferSize = (int)(alignedBufferEndOffset - alignedStartOffset); + + Assert.SdkLessEqual(pooledBufferSize, PooledBuffer.GetAllocatableSizeMax()); + + using var pooledBuffer = new PooledBuffer(); + pooledBuffer.Allocate(pooledBufferSize, pooledBufferSize); + Assert.SdkNotNull(pooledBuffer.GetBuffer()); + + // Read the head block into the buffer if the start offset isn't aligned + if (offset != alignedStartOffset) + { + long headEndOffset = Math.Min(Alignment.AlignUp(offset, blockAlignment), fileSize); + int headSize = (int)(headEndOffset - alignedStartOffset); + Span headBuffer = pooledBuffer.GetBuffer().Slice(0, headSize); + + res = BaseFile.Get.Read(out long readSizeActual, alignedStartOffset, headBuffer, ReadOption.None); + if (res.IsFailure()) return res.Miss(); + + if (headSize != readSizeActual) + return ResultFs.UnexpectedInAlignmentMatchableFileSystemA.Log(); + } + + // In the case where the write size is small enough that it's entirely contained in the head block, + // we don't need to check if we need to read a tail block + bool headBlockCoversEntireBuffer = offset != alignedStartOffset && + alignedBufferEndOffset == Math.Min(Alignment.AlignUp(offset, blockAlignment), fileSize); + + // Read the tail block into the buffer if the end offset isn't aligned + if (!headBlockCoversEntireBuffer && endOffset != alignedEndOffset) + { + long tailStartOffset = Alignment.AlignDown(endOffset, blockAlignment); + int tailSize = (int)(alignedBufferEndOffset - tailStartOffset); + Span tailBuffer = pooledBuffer.GetBuffer().Slice((int)(tailStartOffset - alignedStartOffset), tailSize); + + res = BaseFile.Get.Read(out long readSizeActual, tailStartOffset, tailBuffer, ReadOption.None); + if (res.IsFailure()) return res.Miss(); + + if (tailSize != readSizeActual) + return ResultFs.UnexpectedInAlignmentMatchableFileSystemA.Log(); + } + + source.CopyTo(pooledBuffer.GetBuffer().Slice((int)(offset - alignedStartOffset))); + + Span writeBuffer = pooledBuffer.GetBuffer().Slice(0, (int)(alignedEndOffset - alignedStartOffset)); + res = BaseFile.Get.Write(alignedStartOffset, writeBuffer, in option); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } +} + +public class AlignmentMatchableFileSystem : ForwardingFileSystem +{ + public AlignmentMatchableFileSystem(ref SharedRef baseFileSystem) : base(ref baseFileSystem) { } + + protected override Result DoOpenFile(ref UniqueRef outFile, ref readonly Path path, OpenMode mode) + { + using var baseFile = new UniqueRef(); + Result res = BaseFileSystem.Get.OpenFile(ref baseFile.Ref, in path, mode); + if (res.IsFailure()) return res.Miss(); + + if (!mode.HasFlag(OpenMode.Read)) + { + using var alignmentMatchingFile = new UniqueRef(new AlignmentMatchingFile(ref baseFile.Ref, mode)); + outFile.Set(ref alignmentMatchingFile.Ref); + } + else + { + outFile.Set(ref baseFile.Ref); + } + + return Result.Success; + } +} \ No newline at end of file diff --git a/tests/LibHac.Tests/FsSystem/AlignmentMatchingFileTests.cs b/tests/LibHac.Tests/FsSystem/AlignmentMatchingFileTests.cs new file mode 100644 index 00000000..15c47888 --- /dev/null +++ b/tests/LibHac.Tests/FsSystem/AlignmentMatchingFileTests.cs @@ -0,0 +1,53 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tests.Fs; +using Xunit; + +namespace LibHac.Tests.FsSystem; + +public class AlignmentMatchingFileTests +{ + [Fact] + public void ReadWrite_AccessCorrectnessTestAgainstMemoryStorage() + { + SetupRandomAccessTest().Run(1000); + } + + private StorageTester SetupRandomAccessTest() + { + byte[] fileBuffer = new byte[0x180000]; + byte[] referenceBuffer = new byte[0x180000]; + + fileBuffer.AsSpan().Fill(0x55); + referenceBuffer.AsSpan().Fill(0x55); + + var referenceStorage = new MemoryStorage(referenceBuffer); + + using var baseFile = new UniqueRef(new StorageFile(new MemoryStorage(fileBuffer), OpenMode.All)); + var alignmentFile = new AlignmentMatchingFile(ref baseFile.Ref, OpenMode.All); + var alignmentStorage = new FileStorage(alignmentFile); + + var referenceEntry = new StorageTester.Entry(referenceStorage, referenceBuffer); + var alignmentEntry = new StorageTester.Entry(alignmentStorage, fileBuffer); + + var testerConfig = new StorageTester.Configuration + { + Entries = [referenceEntry, alignmentEntry], + AccessParams = [ + new(50, 0x100), + new(50, 0x4000), + new(50, 0, 0x40000, 0x100, 0x100), + new(50, 0, 0x4000, 0x100, 0) + ], + TaskProbs = [50, 50, 1], + AccessTypeProbs = [10, 10, 5], + RngSeed = 64972, + FrequentAccessBlockCount = 3 + }; + + return new StorageTester(testerConfig); + } +} \ No newline at end of file