From dc6acacf450ecfcb898a72d9421d5a358cc4ebf5 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 27 Apr 2024 14:07:24 -0700 Subject: [PATCH] Update NcaFileSystemServiceImpl --- src/LibHac/Fs/Common/Path.cs | 8 - src/LibHac/Fs/FsEnums.cs | 4 +- src/LibHac/Fs/Impl/CommonMountNames.cs | 6 + src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs | 2 +- .../FsSrv/FileSystemServerInitializer.cs | 18 +- .../FsCreator/EncryptedFileSystemCreator.cs | 4 +- .../FsCreator/IEncryptedFileSystemCreator.cs | 2 +- .../FsCreator/ILocalFileSystemCreator.cs | 5 + .../FsCreator/IPartitionFileSystemCreator.cs | 2 +- .../FsSrv/FsCreator/IRomFileSystemCreator.cs | 2 +- .../FsSrv/FsCreator/IStorageOnNcaCreator.cs | 4 +- .../FsCreator/PartitionFileSystemCreator.cs | 2 +- .../FsSrv/FsCreator/RomFileSystemCreator.cs | 4 +- .../FsSrv/FsCreator/StorageOnNcaCreator.cs | 8 +- src/LibHac/FsSrv/Impl/ExternalKeyManager.cs | 4 +- src/LibHac/FsSrv/Impl/LocationResolverSet.cs | 113 +- .../FsSrv/Impl/NpdmVerificationFileSystem.cs | 172 +++ .../FsSrv/Impl/ProgramIndexMapInfoManager.cs | 46 +- src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs | 1172 +++++++++++++++-- src/LibHac/FsSrv/ProgramRegistryImpl.cs | 13 +- src/LibHac/FsSrv/ProgramRegistryService.cs | 48 +- .../FsSrv/SaveDataFileSystemServiceImpl.cs | 2 +- .../FsSystem/ConcatenationFileSystem.cs | 2 +- src/LibHac/FsSystem/NcaFileSystemDriver.cs | 4 +- src/LibHac/Lr/AddOnContentLocationResolver.cs | 9 + .../Lr/IAddOnContentLocationResolver.cs | 5 +- src/LibHac/Tools/FsSystem/AesXtsFileSystem.cs | 4 +- .../Tools/FsSystem/RomFs/RomFsFileSystem.cs | 4 +- .../LibHac.Tests.csproj.DotSettings | 4 +- 29 files changed, 1474 insertions(+), 199 deletions(-) create mode 100644 src/LibHac/FsSrv/Impl/NpdmVerificationFileSystem.cs diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index fcff40fa..054d0869 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -84,14 +84,6 @@ public static class PathExtensions return p == null; } } - - public static unsafe bool IsNullRef(ref readonly int path) - { - fixed (int* p = &path) - { - return p == null; - } - } #pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type #pragma warning restore LH0001 // DoNotCopyValue } diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index c255d46e..c98d5a2b 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -21,7 +21,9 @@ public enum BisPartitionId System = 31, SystemProperEncryption = 32, SystemProperPartition = 33, - SignedSystemPartitionOnSafeMode = 34 + SignedSystemPartitionOnSafeMode = 34, + DeviceTreeBlob = 35, + System0 = 36 } public enum ContentStorageId diff --git a/src/LibHac/Fs/Impl/CommonMountNames.cs b/src/LibHac/Fs/Impl/CommonMountNames.cs index 5fce8a99..392f9118 100644 --- a/src/LibHac/Fs/Impl/CommonMountNames.cs +++ b/src/LibHac/Fs/Impl/CommonMountNames.cs @@ -10,6 +10,9 @@ public static class CommonMountNames /// "@Host" public static ReadOnlySpan HostRootFileSystemMountName => "@Host"u8; + /// "@Local" + public static ReadOnlySpan LocalRootFileSystemMountName => "@Local"u8; + /// "@Sdcard" public static ReadOnlySpan SdCardFileSystemMountName => "@Sdcard"u8; @@ -38,6 +41,9 @@ public static class CommonMountNames /// "@System" public static ReadOnlySpan BisSystemPartitionMountName => "@System"u8; + /// "@System0" + public static ReadOnlySpan BisSystemPartition0MountName => "@System0"u8; + //Content storage names. /// "@SystemContent" public static ReadOnlySpan ContentStorageSystemMountName => "@SystemContent"u8; diff --git a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs index 7bcf0ed1..22dc09b4 100644 --- a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs @@ -65,7 +65,7 @@ public class FileSystemProxyCoreImpl if (res.IsFailure()) return res.Miss(); tempFs.SetByMove(ref fileSystem.Ref); - res = _fsCreators.EncryptedFileSystemCreator.Create(ref fileSystem.Ref, ref tempFs.Ref, + res = _fsCreators.EncryptedFileSystemCreator.Create(ref fileSystem.Ref, in tempFs, IEncryptedFileSystemCreator.KeyId.CustomStorage, in _sdEncryptionSeed); if (res.IsFailure()) return res.Miss(); } diff --git a/src/LibHac/FsSrv/FileSystemServerInitializer.cs b/src/LibHac/FsSrv/FileSystemServerInitializer.cs index 6de58e05..4834828f 100644 --- a/src/LibHac/FsSrv/FileSystemServerInitializer.cs +++ b/src/LibHac/FsSrv/FileSystemServerInitializer.cs @@ -13,8 +13,10 @@ namespace LibHac.FsSrv; public static class FileSystemServerInitializer { - private const ulong SpeedEmulationProgramIdMinimum = 0x100000000000000; - private const ulong SpeedEmulationProgramIdMaximum = 0x100000000001FFF; + private const ulong SpeedEmulationProgramIdWithoutPlatformIdMinimum = 0; + private const ulong SpeedEmulationProgramIdWithoutPlatformIdMaximum = 0x1FFF; + + private const uint ContentDivisionSize = ConcatenationFileSystem.DefaultInternalFileSize; private const int BufferManagerHeapSize = 1024 * 1024 * 14; private const int BufferManagerCacheSize = 1024; @@ -108,8 +110,8 @@ public static class FileSystemServerInitializer new AccessFailureManagementServiceImpl(in accessFailureManagementServiceConfig); var speedEmulationRange = - new InternalProgramIdRangeForSpeedEmulation(SpeedEmulationProgramIdMinimum, - SpeedEmulationProgramIdMaximum); + new InternalProgramIdRangeForSpeedEmulation(SpeedEmulationProgramIdWithoutPlatformIdMinimum, + SpeedEmulationProgramIdWithoutPlatformIdMaximum); var ncaFsServiceConfig = new NcaFileSystemServiceImpl.Configuration(); ncaFsServiceConfig.BaseFsService = baseFsService; @@ -123,6 +125,8 @@ public static class FileSystemServerInitializer ncaFsServiceConfig.ProgramRegistryService = programRegistryService; ncaFsServiceConfig.AccessFailureManagementService = accessFailureManagementService; ncaFsServiceConfig.SpeedEmulationRange = speedEmulationRange; + ncaFsServiceConfig.AddOnContentDivisionSize = ContentDivisionSize; + ncaFsServiceConfig.RomDivisionSize = ContentDivisionSize; ncaFsServiceConfig.FsServer = server; var ncaFsService = new NcaFileSystemServiceImpl(in ncaFsServiceConfig); @@ -141,6 +145,12 @@ public static class FileSystemServerInitializer saveFsServiceConfig.SaveDataFileSystemCacheCount = 1; saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager; saveFsServiceConfig.DebugConfigService = debugConfigurationService; + saveFsServiceConfig.JournalIntegritySaveDataVersion = 0x50000; + saveFsServiceConfig.JournalIntegritySupportedVersionMin = 0x40000; + saveFsServiceConfig.JournalIntegritySupportedVersionMax = 0x50000; + saveFsServiceConfig.IntegritySaveDataVersion = 0x10000; + saveFsServiceConfig.IntegritySupportedVersionMin = 0x10000; + saveFsServiceConfig.IntegritySupportedVersionMax = 0x10000; saveFsServiceConfig.FsServer = server; var saveFsService = new SaveDataFileSystemServiceImpl(in saveFsServiceConfig); diff --git a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs index 15d26e3e..94650694 100644 --- a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs @@ -16,7 +16,7 @@ public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator KeySet = keySet; } - public Result Create(ref SharedRef outEncryptedFileSystem, ref SharedRef baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed) + public Result Create(ref SharedRef outEncryptedFileSystem, ref readonly SharedRef baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed) { if (idIndex < KeyId.Save || idIndex > KeyId.CustomStorage) { @@ -26,7 +26,7 @@ public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator // todo: "proper" key generation instead of a lazy hack KeySet.SetSdSeed(encryptionSeed.Value); - using var encryptedFileSystem = new SharedRef(new AesXtsFileSystem(ref baseFileSystem, + using var encryptedFileSystem = new SharedRef(new AesXtsFileSystem(in baseFileSystem, KeySet.SdCardEncryptionKeys[(int)idIndex].DataRo.ToArray(), 0x4000)); outEncryptedFileSystem.SetByMove(ref encryptedFileSystem.Ref); diff --git a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs index 10e65124..dcb182eb 100644 --- a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs @@ -13,6 +13,6 @@ public interface IEncryptedFileSystemCreator CustomStorage = 2 } - Result Create(ref SharedRef outEncryptedFileSystem, ref SharedRef baseFileSystem, + Result Create(ref SharedRef outEncryptedFileSystem, ref readonly SharedRef baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed); } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs index ef019306..d7b10258 100644 --- a/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs @@ -7,4 +7,9 @@ namespace LibHac.FsSrv.FsCreator; public interface ILocalFileSystemCreator { Result Create(ref SharedRef outFileSystem, ref readonly Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult); + + public Result Create(ref SharedRef outFileSystem, ref readonly Path rootPath, bool openCaseSensitive) + { + return Create(ref outFileSystem, in rootPath, openCaseSensitive, false, Result.Success).Ret(); + } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs index ce5ee001..4060a3c1 100644 --- a/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs @@ -6,5 +6,5 @@ namespace LibHac.FsSrv.FsCreator; public interface IPartitionFileSystemCreator { - Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage); + Result Create(ref SharedRef outFileSystem, ref readonly SharedRef baseStorage); } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs index edd1ead6..ec70c8bd 100644 --- a/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs @@ -6,5 +6,5 @@ namespace LibHac.FsSrv.FsCreator; public interface IRomFileSystemCreator { - Result Create(ref SharedRef outFileSystem, ref SharedRef romFsStorage); + Result Create(ref SharedRef outFileSystem, ref readonly SharedRef romFsStorage); } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs b/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs index f122ce98..d8589fd6 100644 --- a/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs @@ -7,11 +7,11 @@ namespace LibHac.FsSrv.FsCreator; public interface IStorageOnNcaCreator { Result Create(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader, + ref SharedRef outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader, ref readonly SharedRef ncaReader, int fsIndex); Result CreateWithPatch(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader, + ref SharedRef outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader, ref readonly SharedRef originalNcaReader, ref readonly SharedRef currentNcaReader, int fsIndex); diff --git a/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs index 07354dcf..b6550c5e 100644 --- a/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs @@ -7,7 +7,7 @@ namespace LibHac.FsSrv.FsCreator; public class PartitionFileSystemCreator : IPartitionFileSystemCreator { - public Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage) + public Result Create(ref SharedRef outFileSystem, ref readonly SharedRef baseStorage) { using var partitionFs = new SharedRef(new PartitionFileSystem()); diff --git a/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs index 0208813a..5bc2d710 100644 --- a/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs @@ -8,9 +8,9 @@ namespace LibHac.FsSrv.FsCreator; public class RomFileSystemCreator : IRomFileSystemCreator { // todo: Implement properly - public Result Create(ref SharedRef outFileSystem, ref SharedRef romFsStorage) + public Result Create(ref SharedRef outFileSystem, ref readonly SharedRef romFsStorage) { - outFileSystem.Reset(new RomFsFileSystem(ref romFsStorage)); + outFileSystem.Reset(new RomFsFileSystem(in romFsStorage)); return Result.Success; } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs b/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs index aa21e61c..7c833309 100644 --- a/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs @@ -28,7 +28,7 @@ public class StorageOnNcaCreator : IStorageOnNcaCreator } public Result Create(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader, + ref SharedRef outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader, ref readonly SharedRef ncaReader, int fsIndex) { var ncaFsDriver = new NcaFileSystemDriver(in ncaReader, _memoryResource, _bufferManager, _hashGeneratorFactorySelector); @@ -36,7 +36,7 @@ public class StorageOnNcaCreator : IStorageOnNcaCreator using var storage = new SharedRef(); using var storageAccessSplitter = new SharedRef(); Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref, - ref storageAccessSplitter.Ref, out outHeaderReader, fsIndex)); + ref storageAccessSplitter.Ref, ref outHeaderReader, fsIndex)); if (res.IsFailure()) return res.Miss(); using var resultConvertStorage = new SharedRef(new RomResultConvertStorage(in storage)); @@ -48,7 +48,7 @@ public class StorageOnNcaCreator : IStorageOnNcaCreator } public Result CreateWithPatch(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader, + ref SharedRef outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader, ref readonly SharedRef originalNcaReader, ref readonly SharedRef currentNcaReader, int fsIndex) { @@ -58,7 +58,7 @@ public class StorageOnNcaCreator : IStorageOnNcaCreator using var storage = new SharedRef(); using var storageAccessSplitter = new SharedRef(); Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref, - ref storageAccessSplitter.Ref, out outHeaderReader, fsIndex)); + ref storageAccessSplitter.Ref, ref outHeaderReader, fsIndex)); if (res.IsFailure()) return res.Miss(); using var resultConvertStorage = new SharedRef(new RomResultConvertStorage(in storage)); diff --git a/src/LibHac/FsSrv/Impl/ExternalKeyManager.cs b/src/LibHac/FsSrv/Impl/ExternalKeyManager.cs index 53317c36..a123daca 100644 --- a/src/LibHac/FsSrv/Impl/ExternalKeyManager.cs +++ b/src/LibHac/FsSrv/Impl/ExternalKeyManager.cs @@ -34,12 +34,12 @@ public class ExternalKeyManager throw new NotImplementedException(); } - public Result Find(out AccessKey outAccessKey, in RightsId rightsId) + public Result Find(out AccessKey outAccessKey, RightsId rightsId) { throw new NotImplementedException(); } - private Result FindCore(out AccessKey outAccessKey, in RightsId rightsId) + private Result FindCore(out AccessKey outAccessKey, RightsId rightsId) { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs index 200950e6..972d82a5 100644 --- a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs +++ b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs @@ -12,14 +12,14 @@ namespace LibHac.FsSrv.Impl; public static class LocationResolverSetGlobalMethods { - public static void InitializeLocationResolverSet(this FileSystemServer fsSrv) + public static void InitializeLocationResolverSet(this FileSystemServer fsServer) { - ref LocationResolverSetGlobals globals = ref fsSrv.Globals.LocationResolverSet; + ref LocationResolverSetGlobals globals = ref fsServer.Globals.LocationResolverSet; using ScopedLock scopedLock = ScopedLock.Lock(ref globals.Mutex); if (!globals.IsLrInitialized) { - fsSrv.Hos.Lr.Initialize(); + fsServer.Hos.Lr.Initialize(); globals.IsLrInitialized = true; } } @@ -39,8 +39,8 @@ internal struct LocationResolverSetGlobals /// /// Manages resolving the location of NCAs via the lr service. /// -/// Based on nnSdk 13.4.0 (FS 13.1.0) -internal class LocationResolverSet : IDisposable +/// Based on nnSdk 17.5.0 (FS 17.0.0) +public class LocationResolverSet : IDisposable { private Array5> _resolvers; private Optional _aocResolver; @@ -76,16 +76,16 @@ internal class LocationResolverSet : IDisposable private static Result SetUpFsPath(ref Fs.Path outPath, ref readonly Lr.Path lrPath) { - var pathFlags = new PathFlags(); - pathFlags.AllowMountName(); + var flags = new PathFlags(); + flags.AllowMountName(); if (Utility.IsHostFsMountName(lrPath.Value)) - pathFlags.AllowWindowsPath(); + flags.AllowWindowsPath(); Result res = outPath.InitializeWithReplaceUnc(lrPath.Value); if (res.IsFailure()) return res.Miss(); - res = outPath.Normalize(pathFlags); + res = outPath.Normalize(flags); if (res.IsFailure()) return res.Miss(); return Result.Success; @@ -124,7 +124,7 @@ internal class LocationResolverSet : IDisposable { _fsServer.InitializeLocationResolverSet(); - return Hos.Lr.OpenRegisteredLocationResolver(out resolver); + return Hos.Lr.OpenRegisteredLocationResolver(out resolver).Ret(); } private Result GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver) @@ -147,75 +147,109 @@ internal class LocationResolverSet : IDisposable return Result.Success; } - public Result ResolveApplicationControlPath(ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId) + public Result ResolveApplicationControlPath(ref Fs.Path outPath, out ContentAttributes outContentAttributes, + Ncm.ApplicationId applicationId, StorageId storageId) { + UnsafeHelpers.SkipParamInit(out outContentAttributes); + Result res = GetLocationResolver(out LocationResolver resolver, storageId); if (res.IsFailure()) return res.Miss(); res = resolver.ResolveApplicationControlPath(out Lr.Path path, applicationId); if (res.IsFailure()) return res.Miss(); - return SetUpFsPath(ref outPath, in path); + outContentAttributes = ContentAttributes.None; + return SetUpFsPath(ref outPath, in path).Ret(); } - public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId) + public Result ResolveApplicationHtmlDocumentPath(out bool outIsDirectory, ref Fs.Path outPath, + out ContentAttributes outContentAttributes, ulong applicationId, StorageId storageId) { - UnsafeHelpers.SkipParamInit(out isDirectory); + UnsafeHelpers.SkipParamInit(out outIsDirectory, out outContentAttributes); Result res = GetLocationResolver(out LocationResolver resolver, storageId); if (res.IsFailure()) return res.Miss(); - res = resolver.ResolveApplicationHtmlDocumentPath(out Lr.Path path, applicationId); + res = resolver.ResolveApplicationHtmlDocumentPath(out Lr.Path path, new ProgramId(applicationId)); if (res.IsFailure()) return res.Miss(); - isDirectory = PathUtility.IsDirectoryPath(path.Value); + outContentAttributes = ContentAttributes.None; + outIsDirectory = PathUtility.IsDirectoryPath(path.Value); - return SetUpFsPath(ref outPath, in path); + return SetUpFsPath(ref outPath, in path).Ret(); } - public Result ResolveProgramPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId) + public Result ResolveProgramPath(out bool outIsDirectory, ref Fs.Path outPath, + out ContentAttributes outContentAttributes, ulong programId, StorageId storageId) { - UnsafeHelpers.SkipParamInit(out isDirectory); + UnsafeHelpers.SkipParamInit(out outIsDirectory, out outContentAttributes); Result res = GetLocationResolver(out LocationResolver resolver, storageId); if (res.IsFailure()) return res.Miss(); - res = resolver.ResolveProgramPath(out Lr.Path path, programId); + res = resolver.ResolveProgramPath(out Lr.Path path, new ProgramId(programId)); if (res.IsFailure()) return res.Miss(); - isDirectory = PathUtility.IsDirectoryPath(path.Value); + outContentAttributes = ContentAttributes.None; + outIsDirectory = PathUtility.IsDirectoryPath(path.Value); - return SetUpFsPath(ref outPath, in path); + return SetUpFsPath(ref outPath, in path).Ret(); } - public Result ResolveRomPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId) + public Result ResolveRomPath(out bool outIsDirectory, ref Fs.Path outPath, + out ContentAttributes outContentAttributes, ulong programId, StorageId storageId) { - UnsafeHelpers.SkipParamInit(out isDirectory); + UnsafeHelpers.SkipParamInit(out outIsDirectory, out outContentAttributes); Result res = GetLocationResolver(out LocationResolver resolver, storageId); if (res.IsFailure()) return res.Miss(); - res = resolver.ResolveProgramPathForDebug(out Lr.Path path, programId); + res = resolver.ResolveProgramPathForDebug(out Lr.Path path, new ProgramId(programId)); if (res.IsFailure()) return res.Miss(); - isDirectory = PathUtility.IsDirectoryPath(path.Value); + outContentAttributes = ContentAttributes.None; + outIsDirectory = PathUtility.IsDirectoryPath(path.Value); - return SetUpFsPath(ref outPath, in path); + return SetUpFsPath(ref outPath, in path).Ret(); } - public Result ResolveAddOnContentPath(ref Fs.Path outPath, DataId dataId) + public Result ResolveAddOnContentPath(ref Fs.Path outPath, out ContentAttributes outContentAttributes, + ref Fs.Path outPatchPath, out ContentAttributes outPatchContentAttributes, DataId dataId) { + UnsafeHelpers.SkipParamInit(out outContentAttributes, out outPatchContentAttributes); + Result res = GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver); if (res.IsFailure()) return res.Miss(); - res = resolver.ResolveAddOnContentPath(out Lr.Path path, dataId); + res = resolver.GetRegisteredAddOnContentPaths(out Lr.Path path, out Lr.Path patchPath, dataId); if (res.IsFailure()) return res.Miss(); - return SetUpFsPath(ref outPath, in path); + outContentAttributes = ContentAttributes.None; + + if (patchPath.Value[0] != 0) + { + // Note: FS appears to assign the paths to the wrong outputs here + res = SetUpFsPath(ref outPath, in patchPath); + if (res.IsFailure()) return res.Miss(); + + res = SetUpFsPath(ref outPatchPath, in path); + if (res.IsFailure()) return res.Miss(); + + outPatchContentAttributes = ContentAttributes.None; + } + else + { + res = SetUpFsPath(ref outPath, in path); + if (res.IsFailure()) return res.Miss(); + } + + return Result.Success; } - public Result ResolveDataPath(ref Fs.Path outPath, DataId dataId, StorageId storageId) + public Result ResolveDataPath(ref Fs.Path outPath, out ContentAttributes outContentAttributes, DataId dataId, StorageId storageId) { + UnsafeHelpers.SkipParamInit(out outContentAttributes); + if (storageId == StorageId.None) return ResultFs.InvalidAlignment.Log(); @@ -225,11 +259,14 @@ internal class LocationResolverSet : IDisposable res = resolver.ResolveDataPath(out Lr.Path path, dataId); if (res.IsFailure()) return res.Miss(); - return SetUpFsPath(ref outPath, in path); + outContentAttributes = ContentAttributes.None; + return SetUpFsPath(ref outPath, in path).Ret(); } - public Result ResolveRegisteredProgramPath(ref Fs.Path outPath, ulong id) + public Result ResolveRegisteredProgramPath(ref Fs.Path outPath, out ContentAttributes outContentAttributes, ulong id) { + UnsafeHelpers.SkipParamInit(out outContentAttributes); + RegisteredLocationResolver resolver = null; try { @@ -239,7 +276,8 @@ internal class LocationResolverSet : IDisposable res = resolver.ResolveProgramPath(out Lr.Path path, new ProgramId(id)); if (res.IsFailure()) return res.Miss(); - return SetUpFsPath(ref outPath, in path); + outContentAttributes = ContentAttributes.None; + return SetUpFsPath(ref outPath, in path).Ret(); } finally { @@ -247,8 +285,10 @@ internal class LocationResolverSet : IDisposable } } - public Result ResolveRegisteredHtmlDocumentPath(ref Fs.Path outPath, ulong id) + public Result ResolveRegisteredHtmlDocumentPath(ref Fs.Path outPath, out ContentAttributes outContentAttributes, ulong id) { + UnsafeHelpers.SkipParamInit(out outContentAttributes); + RegisteredLocationResolver resolver = null; try { @@ -258,7 +298,8 @@ internal class LocationResolverSet : IDisposable res = resolver.ResolveHtmlDocumentPath(out Lr.Path path, new ProgramId(id)); if (res.IsFailure()) return res.Miss(); - return SetUpFsPath(ref outPath, in path); + outContentAttributes = ContentAttributes.None; + return SetUpFsPath(ref outPath, in path).Ret(); } finally { diff --git a/src/LibHac/FsSrv/Impl/NpdmVerificationFileSystem.cs b/src/LibHac/FsSrv/Impl/NpdmVerificationFileSystem.cs new file mode 100644 index 00000000..edfc6914 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/NpdmVerificationFileSystem.cs @@ -0,0 +1,172 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; + +namespace LibHac.FsSrv.Impl; + +[InlineArray(0x20)] +public struct NpdmHash +{ + private byte _data; +} + +public class NpdmVerificationFile : IFile +{ + private NpdmHash _hash; + private UniqueRef _baseFile; + + public NpdmVerificationFile(ref UniqueRef baseFile, NpdmHash hash) + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + _baseFile.Destroy(); + base.Dispose(); + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + throw new NotImplementedException(); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + throw new NotImplementedException(); + } + + protected override Result DoFlush() + { + throw new NotImplementedException(); + } + + protected override Result DoSetSize(long size) + { + throw new NotImplementedException(); + } + + protected override Result DoGetSize(out long size) + { + throw new NotImplementedException(); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + throw new NotImplementedException(); + } +} + +public class NpdmVerificationFileSystem : IFileSystem +{ + private NpdmHash _hash; + private ReadOnlyFileSystem _baseFileSystem; + + public NpdmVerificationFileSystem(in SharedRef baseFileSystem, NpdmHash hash) + { + + } + + public override void Dispose() + { + _baseFileSystem.Dispose(); + base.Dispose(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, ref readonly Path path, OpenMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoCreateFile(ref readonly Path path, long size, CreateFileOptions option) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteFile(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCreateDirectory(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectory(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectoryRecursively(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCleanDirectoryRecursively(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameFile(ref readonly Path currentPath, ref readonly Path newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameDirectory(ref readonly Path currentPath, ref readonly Path newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, ref readonly Path path, OpenDirectoryMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoCommit() + { + throw new NotImplementedException(); + } + + protected override Result DoCommitProvisionally(long counter) + { + throw new NotImplementedException(); + } + + protected override Result DoRollback() + { + throw new NotImplementedException(); + } + + protected override Result DoFlush() + { + throw new NotImplementedException(); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, ref readonly Path path) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/ProgramIndexMapInfoManager.cs b/src/LibHac/FsSrv/Impl/ProgramIndexMapInfoManager.cs index 13c540df..d042c286 100644 --- a/src/LibHac/FsSrv/Impl/ProgramIndexMapInfoManager.cs +++ b/src/LibHac/FsSrv/Impl/ProgramIndexMapInfoManager.cs @@ -10,9 +10,10 @@ namespace LibHac.FsSrv.Impl; /// /// Keeps track of the program IDs and program indexes of each program in a multi-program application. /// -/// Based on nnSdk 13.4.0 (FS 13.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class ProgramIndexMapInfoManager : IDisposable { + // Changed: The original uses an intrusive list for the entries private LinkedList _mapEntries; private SdkMutexType _mutex; @@ -38,21 +39,29 @@ public class ProgramIndexMapInfoManager : IDisposable ClearImpl(); - for (int i = 0; i < programIndexMapInfo.Length; i++) + bool isSuccess = false; + try { - var entry = new ProgramIndexMapInfo + for (int i = 0; i < programIndexMapInfo.Length; i++) { - ProgramId = programIndexMapInfo[i].ProgramId, - MainProgramId = programIndexMapInfo[i].MainProgramId, - ProgramIndex = programIndexMapInfo[i].ProgramIndex - }; + var entry = new ProgramIndexMapInfo + { + ProgramId = programIndexMapInfo[i].ProgramId, + MainProgramId = programIndexMapInfo[i].MainProgramId, + ProgramIndex = programIndexMapInfo[i].ProgramIndex + }; - _mapEntries.AddLast(entry); + _mapEntries.AddLast(entry); + } + + isSuccess = true; + return Result.Success; + } + finally + { + if (!isSuccess) + ClearImpl(); } - - // We skip running ClearImpl() if the allocation failed because we don't need to worry about that in C# - - return Result.Success; } /// @@ -116,6 +125,19 @@ public class ProgramIndexMapInfoManager : IDisposable return _mapEntries.Count; } + public ProgramId GetApplicationProgramId(ProgramId programId) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Optional programIndexMapInfo = + GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId); + + if (!programIndexMapInfo.HasValue) + return ProgramId.InvalidId; + + return new ProgramId(programIndexMapInfo.Value.MainProgramId.Value + programIndexMapInfo.Value.ProgramIndex); + } + private delegate bool EntrySelector(in ProgramIndexMapInfo candidate); private Optional GetImpl(EntrySelector selector) diff --git a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs index 66d865ab..6645429b 100644 --- a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs @@ -1,17 +1,23 @@ using System; +using System.Buffers.Text; +using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Crypto; using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv.FsCreator; using LibHac.FsSrv.Impl; using LibHac.FsSystem; +using LibHac.Lr; using LibHac.Ncm; using LibHac.Os; using LibHac.Spl; -using NcaFsHeader = LibHac.FsSystem.NcaFsHeader; +using LibHac.Util; +using static LibHac.Fs.Impl.CommonMountNames; +using static LibHac.FsSrv.Anonymous; +using Path = LibHac.Fs.Path; using RightsId = LibHac.Fs.RightsId; -using Utility = LibHac.FsSrv.Impl.Utility; namespace LibHac.FsSrv; @@ -19,12 +25,47 @@ file static class Anonymous { public static Result GetDeviceHandleByMountName(out GameCardHandle outHandle, U8Span name) { - throw new NotImplementedException(); + const int handleStringLength = 8; + + UnsafeHelpers.SkipParamInit(out outHandle); + + if (StringUtils.GetLength(name, handleStringLength) < handleStringLength) + return ResultFs.InvalidPath.Log(); + + Span handleString = stackalloc byte[handleStringLength + 1]; + handleString.Clear(); + StringUtils.Copy(handleString, name, handleStringLength); + + bool handleParsed = Utf8Parser.TryParse(handleString, out GameCardHandle handle, out int bytesConsumed); + if (!handleParsed || bytesConsumed != handleStringLength) + return ResultFs.InvalidPath.Log(); + + outHandle = handle; + return Result.Success; } public static Result GetGameCardPartitionByMountName(out GameCardPartition outPartition, U8Span name) { - throw new NotImplementedException(); + if (StringUtils.Compare(name, GameCardFileSystemMountNameSuffixUpdate, 1) == 0) + { + outPartition = GameCardPartition.Update; + return Result.Success; + } + + if (StringUtils.Compare(name, GameCardFileSystemMountNameSuffixNormal, 1) == 0) + { + outPartition = GameCardPartition.Normal; + return Result.Success; + } + + if (StringUtils.Compare(name, GameCardFileSystemMountNameSuffixSecure, 1) == 0) + { + outPartition = GameCardPartition.Secure; + return Result.Success; + } + + outPartition = default; + return ResultFs.InvalidPath.Log(); } public static Result GetPartitionIndex(out int outIndex, FileSystemProxyType type) @@ -53,15 +94,79 @@ file static class Anonymous public static void GenerateNcaDigest(out Hash outDigest, NcaReader reader1, NcaReader reader2) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outDigest); + + Assert.SdkAssert(reader1 is not null || reader2 is not null); + + var generator = new Sha256Generator(); + generator.Initialize(); + + if (reader1 is not null) + { + RuntimeNcaHeader header = reader1.GetHeader(); + generator.Update(SpanHelpers.AsReadOnlyByteSpan(in header)); + } + + if (reader2 is not null) + { + RuntimeNcaHeader header = reader2.GetHeader(); + generator.Update(SpanHelpers.AsReadOnlyByteSpan(in header)); + } + + generator.GetHash(SpanHelpers.AsByteSpan(ref outDigest)); } - public static Result LoadNspdVerificationData(out CodeVerificationData outCodeVerificationData, IFileSystem fileSystem) + public static Result LoadNspdVerificationData(ref CodeVerificationData outCodeVerificationData, IFileSystem fileSystem) { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(ref outCodeVerificationData); + Assert.SdkRequiresNotNull(fileSystem); + + const int verificationDataSignatureSize = 0x100; + const int verificationDataHashSize = 0x20; + ReadOnlySpan verificationDataPath = "/verificationData"u8; + + using var verificationDataFile = new UniqueRef(); + + using var pathVerificationData = new Path(); + Result res = PathFunctions.SetUpFixedPath(ref pathVerificationData.Ref(), verificationDataPath); + if (res.IsFailure()) return res.Miss(); + + res = fileSystem.OpenFile(ref verificationDataFile.Ref, in pathVerificationData, OpenMode.Read); + if (!res.IsSuccess()) + { + if (ResultFs.PathNotFound.Includes(res)) + { + return ResultFs.MissingNspdVerificationData.LogConverted(res); + } + + return res.Miss(); + } + + res = ReadData(in verificationDataFile, ref outCodeVerificationData); + if (res.IsFailure()) + { + return ResultFs.InvalidNspdVerificationData.LogConverted(res); + } + + return Result.Success; + + static Result ReadData(in UniqueRef file, ref CodeVerificationData outData) + { + Result res = file.Get.GetSize(out long verificationDataSize); + if (res.IsFailure()) return res.Miss(); + + if (verificationDataSize != verificationDataSignatureSize + verificationDataHashSize) + return ResultFs.InvalidNspdVerificationData.Log(); + + return file.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref outData), ReadOption.None).Ret(); + } } } +/// +/// Handles locating and opening NCA content files. +/// +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class NcaFileSystemServiceImpl : IDisposable { private Configuration _config; @@ -87,7 +192,7 @@ public class NcaFileSystemServiceImpl : IDisposable public ISubDirectoryFileSystemCreator SubDirectoryFsCreator; public IEncryptedFileSystemCreator EncryptedFsCreator; public INspRootFileSystemCreator NspRootFileSystemCreator; - private LocationResolverSet LocationResolverSet; + public LocationResolverSet LocationResolverSet; public ProgramRegistryServiceImpl ProgramRegistryService; public AccessFailureManagementServiceImpl AccessFailureManagementService; public InternalProgramIdRangeForSpeedEmulation SpeedEmulationRange; @@ -146,101 +251,446 @@ public class NcaFileSystemServiceImpl : IDisposable _updatePartitionPath.Dispose(); } - public long GetAddOnContentDivisionSize() - { - throw new NotImplementedException(); - } - - public long GetRomDivisionSize() - { - throw new NotImplementedException(); - } + public long GetAddOnContentDivisionSize() => _config.AddOnContentDivisionSize; + public long GetRomDivisionSize() => _config.RomDivisionSize; public Result OpenFileSystem(ref SharedRef outFileSystem, ref readonly Path path, ContentAttributes attributes, FileSystemProxyType type, bool canMountSystemDataPrivate, ulong id, bool isDirectory) { - throw new NotImplementedException(); + return OpenFileSystem(ref outFileSystem, ref Unsafe.NullRef(), in path, attributes, type, + canMountSystemDataPrivate, id, isDirectory).Ret(); } public Result OpenFileSystem(ref SharedRef outFileSystem, ref readonly Path path, ContentAttributes attributes, FileSystemProxyType type, ulong id, bool isDirectory) { - throw new NotImplementedException(); + return OpenFileSystem(ref outFileSystem, ref Unsafe.NullRef(), in path, attributes, type, + canMountSystemDataPrivate: false, id, isDirectory).Ret(); } - public Result OpenFileSystem(ref SharedRef outFileSystem, out CodeVerificationData outVerificationData, + public Result OpenFileSystem(ref SharedRef outFileSystem, ref CodeVerificationData outVerificationData, ref readonly Path path, ContentAttributes attributes, FileSystemProxyType type, bool canMountSystemDataPrivate, ulong id, bool isDirectory) { - throw new NotImplementedException(); + // Get a reference to the path that will be advanced as each part of the path is parsed + var currentPath = new U8Span(path.GetString()); + + var mountInfo = new MountInfo(); + + if (!Unsafe.IsNullRef(ref outVerificationData)) + outVerificationData.HasData = false; + + // Open the root filesystem based on the path's mount name + using var baseFileSystem = new SharedRef(); + Result res = ParseMountName(ref currentPath, ref baseFileSystem.Ref, ref mountInfo); + if (res.IsFailure()) return res.Miss(); + + if (mountInfo.IsGameCard() && type == FileSystemProxyType.Logo) + { + res = _config.BaseFsService.OpenGameCardFileSystem(ref outFileSystem, mountInfo.GcHandle, GameCardPartition.Logo); + + if (res.IsSuccess()) return Result.Success; + + if (!ResultFs.PartitionNotFound.Includes(res)) + return res.Miss(); + } + + if (isDirectory) + { + if (!mountInfo.IsHostOrLocalFs()) + return ResultFs.PermissionDenied.Log(); + + using var directoryPath = new Path(); + res = directoryPath.InitializeWithNormalization(currentPath.Value); + if (res.IsFailure()) return res.Miss(); + + if (type == FileSystemProxyType.Manual) + { + using var hostFileSystem = new SharedRef(); + + res = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostOrLocalFs(ref hostFileSystem.Ref, in directoryPath, mountInfo.FsType); + if (res.IsFailure()) return res.Miss(); + + using var readOnlyFileSystem = new SharedRef(new ReadOnlyFileSystem(ref hostFileSystem.Ref)); + outFileSystem.SetByMove(ref readOnlyFileSystem.Ref); + + return Result.Success; + } + + using var ncdFileSystem = new SharedRef(); + res = _config.SubDirectoryFsCreator.Create(ref ncdFileSystem.Ref, in baseFileSystem, in directoryPath); + if (res.IsFailure()) return res.Miss(); + + using var fsPartitionOnNcdFileSystem = new SharedRef(); + res = ParseContentTypeForDirectory(ref fsPartitionOnNcdFileSystem.Ref, in ncdFileSystem, type); + if (res.IsFailure()) return res.Miss(); + + if (type == FileSystemProxyType.Code) + { + if (Unsafe.IsNullRef(ref outVerificationData)) + return ResultFs.NullptrArgument.Log(); + + res = LoadNspdVerificationData(ref outVerificationData, ncdFileSystem.Get); + if (!res.IsSuccess()) + { + if (ResultFs.MissingNspdVerificationData.Includes(res)) + { + outVerificationData.HasData = false; + outFileSystem.SetByMove(ref fsPartitionOnNcdFileSystem.Ref); + return Result.Success; + } + + return res.Miss(); + } + + NpdmHash hash = default; + outVerificationData.Hash[..].CopyTo(hash); + + using var codeFileSystem = + new SharedRef(new NpdmVerificationFileSystem(in fsPartitionOnNcdFileSystem, hash)); + + outFileSystem.SetByMove(ref codeFileSystem.Ref); + + return Result.Success; + } + } + + res = CheckNcaOrNsp(ref currentPath); + if (res.IsFailure()) return res.Miss(); + + bool foundNspPath; + + using (SharedRef baseFileSystemCopy = SharedRef.CreateCopy(in baseFileSystem)) + { + res = ParseNsp(out foundNspPath, ref currentPath, ref baseFileSystem.Ref, in baseFileSystemCopy); + if (res.IsFailure()) return res.Miss(); + } + + // Must be the end of the path to open Application Package FS type + if (foundNspPath && currentPath.Value.At(0) == 0) + { + if (type != FileSystemProxyType.Package) + return ResultFs.InvalidArgument.Log(); + + outFileSystem.SetByMove(ref baseFileSystem.Ref); + return Result.Success; + } + + if (!mountInfo.CanMountNca) + return ResultFs.UnexpectedInNcaFileSystemServiceImplA.Log(); + + using var ncaReader = new SharedRef(); + ulong openProgramId = mountInfo.IsHostOrLocalFs() ? ulong.MaxValue : id; + res = ParseNca(ref ncaReader.Ref, ref baseFileSystem.Ref, currentPath, attributes, openProgramId); + if (res.IsFailure()) return res.Miss(); + + using var storage = new SharedRef(); + using var storageAccessSplitter = new SharedRef(); + res = OpenStorageByContentType(ref storage.Ref, ref storageAccessSplitter.Ref, in ncaReader, + out NcaFsHeader.FsType fsType, type, mountInfo.IsGameCard(), canMountSystemDataPrivate); + if (res.IsFailure()) return res.Miss(); + + switch (fsType) + { + case NcaFsHeader.FsType.RomFs: + Assert.SdkAssert(type != FileSystemProxyType.Code); + + return _config.RomFsCreator.Create(ref outFileSystem.Ref, in storage).Ret(); + + case NcaFsHeader.FsType.PartitionFs: + if (type == FileSystemProxyType.Code) + { + if (Unsafe.IsNullRef(ref outVerificationData)) + return ResultFs.NullptrArgument.Log(); + + ncaReader.Get.GetHeaderSign2(outVerificationData.Signature); + ncaReader.Get.GetHeaderSign2TargetHash(outVerificationData.Hash); + outVerificationData.HasData = true; + } + + return _config.PartitionFsCreator.Create(ref outFileSystem.Ref, in storage).Ret(); + default: + return ResultFs.InvalidNcaFileSystemType.Log(); + } } public Result OpenDataFileSystem(ref SharedRef outFileSystem, ref readonly Path path, - ContentAttributes attributes, FileSystemProxyType fsType, ulong programId, bool isDirectory) + ContentAttributes attributes, FileSystemProxyType type, ulong programId, bool isDirectory) { - throw new NotImplementedException(); - } + U8Span currentPath = path.GetString(); + var mountInfo = new MountInfo(); - public Result OpenDataFileSystem(ref SharedRef outFileSystem) - { - throw new NotImplementedException(); + if (!(type == FileSystemProxyType.Rom || type == FileSystemProxyType.Data && isDirectory)) + { + return ResultFs.PreconditionViolation.Log(); + } + + using var fileSystem = new SharedRef(); + Result res = ParseMountName(ref currentPath, ref fileSystem.Ref, ref mountInfo); + if (res.IsFailure()) return res.Miss(); + + if (isDirectory) + { + if (!mountInfo.IsHostOrLocalFs()) + return ResultFs.PreconditionViolation.Log(); + + using var hostFileSystem = new SharedRef(); + + using var directoryPath = new Path(); + res = directoryPath.InitializeWithNormalization(currentPath); + if (res.IsFailure()) return res.Miss(); + + res = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostOrLocalFs(ref hostFileSystem.Ref, in directoryPath, mountInfo.FsType); + if (res.IsFailure()) return res.Miss(); + + using var readOnlyFileSystem = new SharedRef(new ReadOnlyFileSystem(ref hostFileSystem.Ref)); + + outFileSystem.SetByMove(ref readOnlyFileSystem.Ref); + return Result.Success; + } + + res = CheckNcaOrNsp(ref currentPath); + if (res.IsFailure()) return res.Miss(); + + bool foundNspPath; + + using (SharedRef fileSystemCopy = SharedRef.CreateCopy(in fileSystem)) + { + res = ParseNsp(out foundNspPath, ref currentPath, ref fileSystem.Ref, in fileSystemCopy); + if (res.IsFailure()) return res.Miss(); + } + + // If we found an .nsp file in the file path, the portion of the path after the .nsp file will be used to open + // a file inside the .nsp file. + // We're trying to open an .nca file, so there must be something in the path after the .nsp file. + if (foundNspPath && (currentPath.Length == 0 || currentPath[0] == 0)) + return ResultFs.TargetNotFound.Log(); + + if (!mountInfo.CanMountNca) + return ResultFs.UnexpectedInNcaFileSystemServiceImplA.Log(); + + using var ncaReader = new SharedRef(); + res = ParseNca(ref ncaReader.Ref, in fileSystem, currentPath, attributes, programId); + if (res.IsFailure()) return res.Miss(); + + using var storage = new SharedRef(); + using var storageAccessSplitter = new SharedRef(); + res = OpenStorageByContentType(ref storage.Ref, ref storageAccessSplitter.Ref, in ncaReader, + out NcaFsHeader.FsType fsType, type, mountInfo.IsGameCard(), canMountSystemDataPrivate: false); + if (res.IsFailure()) return res.Miss(); + + if (fsType != NcaFsHeader.FsType.RomFs) + return ResultFs.PreconditionViolation.Log(); + + return _config.RomFsCreator.Create(ref outFileSystem, in storage).Ret(); } public Result OpenDataStorage(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out Hash outNcaHeaderDigest, - ref readonly Path path, ContentAttributes attributes, FileSystemProxyType fsType, ulong id) + ref SharedRef outStorageAccessSplitter, ref Hash outNcaDigest, + ref readonly Path path, ContentAttributes attributes, FileSystemProxyType type, ulong id) { - throw new NotImplementedException(); + return OpenDataStorage(ref outStorage, ref outStorageAccessSplitter, ref outNcaDigest, in path, attributes, + type, id, canMountSystemDataPrivate: false).Ret(); } public Result OpenDataStorage(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out Hash outNcaHeaderDigest, - ref readonly Path path, ContentAttributes attributes, FileSystemProxyType fsType, ulong id, + ref SharedRef outStorageAccessSplitter, ref Hash outNcaDigest, + ref readonly Path path, ContentAttributes attributes, FileSystemProxyType type, ulong id, bool canMountSystemDataPrivate) { - throw new NotImplementedException(); + using var ncaReader = new SharedRef(); + Result res = ParseNca(ref ncaReader.Ref, out bool isGameCard, path.GetString(), attributes, id); + if (res.IsFailure()) return res.Miss(); + + if (!Unsafe.IsNullRef(in outNcaDigest)) + { + GenerateNcaDigest(out outNcaDigest, ncaReader.Get, null); + } + + res = OpenStorageByContentType(ref outStorage, ref outStorageAccessSplitter, in ncaReader, + out NcaFsHeader.FsType fsType, type, isGameCard, canMountSystemDataPrivate); + if (res.IsFailure()) return res.Miss(); + + if (fsType != NcaFsHeader.FsType.RomFs) + return ResultFs.PreconditionViolation.Log(); + + return Result.Success; } public Result OpenStorageWithPatch(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out Hash ncaHeaderDigest, + ref SharedRef outStorageAccessSplitter, ref Hash outNcaDigest, ref readonly Path originalNcaPath, ContentAttributes originalAttributes, ref readonly Path currentNcaPath, - ContentAttributes currentAttributes, FileSystemProxyType fsType, ulong originalId, ulong currentId) + ContentAttributes currentAttributes, FileSystemProxyType type, ulong originalId, ulong currentId) { - throw new NotImplementedException(); + return OpenStorageWithPatch(ref outStorage, ref outStorageAccessSplitter, ref outNcaDigest, + in originalNcaPath, originalAttributes, in currentNcaPath, currentAttributes, type, originalId, currentId, + false).Ret(); } public Result OpenStorageWithPatch(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out Hash ncaHeaderDigest, + ref SharedRef outStorageAccessSplitter, ref Hash outNcaDigest, ref readonly Path originalNcaPath, ContentAttributes originalAttributes, ref readonly Path currentNcaPath, - ContentAttributes currentAttributes, FileSystemProxyType fsType, ulong originalId, ulong currentId, + ContentAttributes currentAttributes, FileSystemProxyType type, ulong originalId, ulong currentId, bool canMountSystemDataPrivate) { - throw new NotImplementedException(); + Result res; + bool isOriginalGameCard = false; + using var originalNcaReader = new SharedRef(); + + if (!PathExtensions.IsNullRef(in originalNcaPath)) + { + res = ParseNca(ref originalNcaReader.Ref, out isOriginalGameCard, originalNcaPath.GetString(), + originalAttributes, originalId); + if (res.IsFailure()) return res.Miss(); + + if (originalNcaReader.Get.GetDistributionType() == NcaHeader.DistributionType.GameCard && !isOriginalGameCard) + return ResultFs.PermissionDenied.Log(); + } + + if (isOriginalGameCard) + originalNcaReader.Get.PrioritizeSwAes(); + + using var currentNcaReader = new SharedRef(); + res = ParseNca(ref currentNcaReader.Ref, out bool isCurrentGameCard, currentNcaPath.GetString(), + currentAttributes, currentId); + if (res.IsFailure()) return res.Miss(); + + if (currentNcaReader.Get.GetDistributionType() == NcaHeader.DistributionType.GameCard && !isCurrentGameCard) + return ResultFs.PermissionDenied.Log(); + + if (isCurrentGameCard) + currentNcaReader.Get.PrioritizeSwAes(); + + if (!Unsafe.IsNullRef(in outNcaDigest)) + { + GenerateNcaDigest(out outNcaDigest, originalNcaReader.Get, currentNcaReader.Get); + } + + res = OpenStorageWithPatchByContentType(ref outStorage, ref outStorageAccessSplitter, in originalNcaReader, + in currentNcaReader, out NcaFsHeader.FsType fsType, type, canMountSystemDataPrivate); + if (res.IsFailure()) return res.Miss(); + + if (fsType != NcaFsHeader.FsType.RomFs) + return ResultFs.PreconditionViolation.Log(); + + return Result.Success; } public Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, ref readonly Path originalNcaPath, ContentAttributes originalAttributes, ref readonly Path currentNcaPath, ContentAttributes currentAttributes, - FileSystemProxyType fsType, ulong originalId, ulong currentId) + FileSystemProxyType type, ulong originalId, ulong currentId) { - throw new NotImplementedException(); + using var storage = new SharedRef(); + using var storageAccessSplitter = new SharedRef(); + using var fileSystem = new SharedRef(); + + Result res = OpenStorageWithPatch(ref storage.Ref, ref storageAccessSplitter.Ref, ref Unsafe.NullRef(), + in originalNcaPath, originalAttributes, in currentNcaPath, currentAttributes, type, originalId, currentId, + canMountSystemDataPrivate: false); + if (res.IsFailure()) return res.Miss(); + + res = _config.RomFsCreator.Create(ref fileSystem.Ref, in storage); + if (res.IsFailure()) return res.Miss(); + + outFileSystem.SetByMove(ref fileSystem.Ref); + return Result.Success; } public Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, ContentStorageId contentStorageId) { - throw new NotImplementedException(); + using var fileSystem = new SharedRef(); + Result res; + + // Open the appropriate base file system for the content storage ID + switch (contentStorageId) + { + case ContentStorageId.System: + res = _config.BaseFsService.OpenBisFileSystem(ref fileSystem.Ref, BisPartitionId.System); + if (res.IsFailure()) return res.Miss(); + break; + case ContentStorageId.User: + res = _config.BaseFsService.OpenBisFileSystem(ref fileSystem.Ref, BisPartitionId.User); + if (res.IsFailure()) return res.Miss(); + break; + case ContentStorageId.SdCard: + res = _config.BaseFsService.OpenSdCardProxyFileSystem(ref fileSystem.Ref); + if (res.IsFailure()) return res.Miss(); + break; + case ContentStorageId.System0: + res = _config.BaseFsService.OpenBisFileSystem(ref fileSystem.Ref, BisPartitionId.System0); + if (res.IsFailure()) return res.Miss(); + break; + default: + return ResultFs.InvalidArgument.Log(); + } + + Span contentStoragePathBuffer = stackalloc byte[64]; + + // Build the appropriate path for the content storage ID + if (contentStorageId == ContentStorageId.SdCard) + { + var sb = new U8StringBuilder(contentStoragePathBuffer); + sb.Append(StringTraits.DirectorySeparator).Append(CommonDirNames.SdCardNintendoRootDirectoryName); + sb.Append(StringTraits.DirectorySeparator).Append(CommonDirNames.ContentStorageDirectoryName); + } + else + { + var sb = new U8StringBuilder(contentStoragePathBuffer); + sb.Append(StringTraits.DirectorySeparator).Append(CommonDirNames.ContentStorageDirectoryName); + } + + using scoped var contentStoragePath = new Path(); + res = PathFunctions.SetUpFixedPath(ref contentStoragePath.Ref(), contentStoragePathBuffer); + if (res.IsFailure()) return res.Miss(); + + // Make sure the content storage path exists + res = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in contentStoragePath); + if (res.IsFailure()) return res.Miss(); + + using var subDirFs = new SharedRef(); + res = _config.SubDirectoryFsCreator.Create(ref subDirFs.Ref, in fileSystem, in contentStoragePath); + if (res.IsFailure()) return res.Miss(); + + // Only content on the SD card is encrypted + if (contentStorageId == ContentStorageId.SdCard) + { + using SharedRef tempFileSystem = SharedRef.CreateMove(ref subDirFs.Ref); + res = _config.EncryptedFsCreator.Create(ref subDirFs.Ref, in tempFileSystem, + IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed); + if (res.IsFailure()) return res.Miss(); + } + + outFileSystem.SetByMove(ref subDirFs.Ref); + return Result.Success; } - public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, ref readonly Path path, + public Result GetRightsId(out RightsId outRightsId, out byte outKeyGeneration, ref readonly Path path, ContentAttributes attributes, ProgramId programId) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outRightsId, out outKeyGeneration); + + using var ncaReader = new SharedRef(); + Result res = ParseNca(ref ncaReader.Ref, out _, path.GetString(), attributes, programId.Value); + if (res.IsFailure()) return res.Miss(); + + ncaReader.Get.GetRightsId(SpanHelpers.AsByteSpan(ref outRightsId)); + outKeyGeneration = ncaReader.Get.GetKeyGeneration(); + + return Result.Success; } public Result GetProgramId(out ProgramId outProgramId, ref readonly Path path, ContentAttributes attributes) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outProgramId); + + using var ncaReader = new SharedRef(); + Result res = ParseNca(ref ncaReader.Ref, out _, path.GetString(), attributes, ulong.MaxValue); + if (res.IsFailure()) return res.Miss(); + + outProgramId = new ProgramId(ncaReader.Get.GetProgramId()); + return Result.Success; } public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey) @@ -273,64 +723,438 @@ public class NcaFileSystemServiceImpl : IDisposable updaterProgramId, isDirectory: false).Ret(); } - private Result ParseMountName(ref U8Span path, ref SharedRef outFileSystem, out MountInfo outMountInfo) + private Result ParseMountName(ref U8Span path, ref SharedRef outFileSystem, ref MountInfo outMountInfo) { - throw new NotImplementedException(); + outMountInfo.FsType = MountInfo.FileSystemType.None; + outMountInfo.CanMountNca = false; + + if (StringUtils.Compare(path, GameCardFileSystemMountName, + GameCardFileSystemMountName.Length) == 0) + { + path = path.Slice(GameCardFileSystemMountName.Length); + Result res = GetGameCardPartitionByMountName(out GameCardPartition partition, path); + if (res.IsFailure()) return res.Miss(); + + path = path.Slice(1); + res = GetDeviceHandleByMountName(out GameCardHandle handle, path); + if (res.IsFailure()) return res.Miss(); + + path = path.Slice(8); + res = _config.BaseFsService.OpenGameCardFileSystem(ref outFileSystem, handle, partition); + if (res.IsFailure()) return res.Miss(); + + outMountInfo.GcHandle = handle; + outMountInfo.FsType = MountInfo.FileSystemType.GameCard; + outMountInfo.CanMountNca = true; + } + + else if (StringUtils.Compare(path, ContentStorageSystemMountName, + ContentStorageSystemMountName.Length) == 0) + { + path = path.Slice(ContentStorageSystemMountName.Length); + + Result res = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.System); + if (res.IsFailure()) return res.Miss(); + + outMountInfo.CanMountNca = true; + } + + else if (StringUtils.Compare(path, ContentStorageUserMountName, + ContentStorageUserMountName.Length) == 0) + { + path = path.Slice(ContentStorageUserMountName.Length); + + Result res = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.User); + if (res.IsFailure()) return res.Miss(); + + outMountInfo.CanMountNca = true; + } + + else if (StringUtils.Compare(path, ContentStorageSdCardMountName, + ContentStorageSdCardMountName.Length) == 0) + { + path = path.Slice(ContentStorageSdCardMountName.Length); + + Result res = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.SdCard); + if (res.IsFailure()) return res.Miss(); + + outMountInfo.CanMountNca = true; + } + + else if (StringUtils.Compare(path, BisCalibrationFilePartitionMountName, + BisCalibrationFilePartitionMountName.Length) == 0) + { + path = path.Slice(BisCalibrationFilePartitionMountName.Length); + + Result res = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.CalibrationFile); + if (res.IsFailure()) return res.Miss(); + } + + else if (StringUtils.Compare(path, BisSafeModePartitionMountName, + BisSafeModePartitionMountName.Length) == 0) + { + path = path.Slice(BisSafeModePartitionMountName.Length); + + Result res = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.SafeMode); + if (res.IsFailure()) return res.Miss(); + } + + else if (StringUtils.Compare(path, BisUserPartitionMountName, + BisUserPartitionMountName.Length) == 0) + { + path = path.Slice(BisUserPartitionMountName.Length); + + Result res = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.User); + if (res.IsFailure()) return res.Miss(); + } + + else if (StringUtils.Compare(path, BisSystemPartition0MountName, + BisSystemPartition0MountName.Length) == 0) + { + path = path.Slice(BisSystemPartition0MountName.Length); + + Result res = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.System0); + if (res.IsFailure()) return res.Miss(); + } + + else if (StringUtils.Compare(path, BisSystemPartitionMountName, + BisSystemPartitionMountName.Length) == 0) + { + path = path.Slice(BisSystemPartitionMountName.Length); + + Result res = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.System); + if (res.IsFailure()) return res.Miss(); + } + + else if (StringUtils.Compare(path, SdCardFileSystemMountName, + SdCardFileSystemMountName.Length) == 0) + { + path = path.Slice(SdCardFileSystemMountName.Length); + + Result res = _config.BaseFsService.OpenSdCardProxyFileSystem(ref outFileSystem); + if (res.IsFailure()) return res.Miss(); + } + + else if (StringUtils.Compare(path, HostRootFileSystemMountName, + HostRootFileSystemMountName.Length) == 0) + { + path = path.Slice(HostRootFileSystemMountName.Length); + + using var rootPathEmpty = new Path(); + Result res = rootPathEmpty.InitializeAsEmpty(); + if (res.IsFailure()) return res.Miss(); + + res = OpenHostFileSystem(ref outFileSystem, in rootPathEmpty, openCaseSensitive: false); + if (res.IsFailure()) return res.Miss(); + + outMountInfo.FsType = MountInfo.FileSystemType.HostFs; + outMountInfo.CanMountNca = true; + } + + else if (StringUtils.Compare(path, LocalRootFileSystemMountName, + LocalRootFileSystemMountName.Length) == 0) + { + path = path.Slice(LocalRootFileSystemMountName.Length); + + using var rootPathEmpty = new Path(); + Result res = rootPathEmpty.InitializeAsEmpty(); + if (res.IsFailure()) return res.Miss(); + + res = _config.LocalFsCreator.Create(ref outFileSystem, in rootPathEmpty, openCaseSensitive: false); + if (res.IsFailure()) return res.Miss(); + + outMountInfo.FsType = MountInfo.FileSystemType.LocalFs; + outMountInfo.CanMountNca = true; + } + + else if (StringUtils.Compare(path, RegisteredUpdatePartitionMountName, + RegisteredUpdatePartitionMountName.Length) == 0) + { + path = path.Slice(RegisteredUpdatePartitionMountName.Length); + + Result res = OpenRegisteredUpdatePartition(ref outFileSystem); + if (res.IsFailure()) return res.Miss(); + + outMountInfo.CanMountNca = true; + } + else + { + return ResultFs.PathNotFound.Log(); + } + + if (StringUtils.GetLength(path, PathTool.EntryNameLengthMax) == 0) + return ResultFs.PathNotFound.Log(); + + if (path[0] == (byte)':') + { + path = path.Slice(1); + } + + return Result.Success; } private Result CheckNcaOrNsp(ref U8Span path) { - throw new NotImplementedException(); + ReadOnlySpan ncaExtension = ".nca"u8; + ReadOnlySpan nspExtension = ".nsp"u8; + + int pathLen = StringUtils.GetLength(path); + + // Now make sure the path has a content file extension + if (pathLen <= 4) + return ResultFs.PathNotFound.Log(); + + ReadOnlySpan fileExtension = path.Value.Slice(pathLen - 4); + + if (StringUtils.CompareCaseInsensitive(fileExtension, ncaExtension) == 0) + return Result.Success; + + if (StringUtils.CompareCaseInsensitive(fileExtension, nspExtension) == 0) + return Result.Success; + + return ResultFs.PathNotFound.Log(); } private Result ParseDir(ref readonly Path path, ref SharedRef outContentFileSystem, - ref SharedRef baseFileSystem, FileSystemProxyType fsType, bool preserveUnc) + ref SharedRef baseFileSystem, FileSystemProxyType type, bool preserveUnc) { using var fileSystem = new SharedRef(); Result res = _config.SubDirectoryFsCreator.Create(ref fileSystem.Ref, baseFileSystem, path); if (res.IsFailure()) return res.Miss(); - return ParseContentTypeForDirectory(ref outContentFileSystem, ref fileSystem.Ref, fsType); + return ParseContentTypeForDirectory(ref outContentFileSystem, ref fileSystem.Ref, type); } private Result ParseDirWithPathCaseNormalizationOnCaseSensitiveHostOrLocalFs( ref SharedRef outFileSystem, ref readonly Path path, MountInfo.FileSystemType fsType) { - throw new NotImplementedException(); + using var pathRoot = new Path(); + using var pathData = new Path(); + + Result res = PathFunctions.SetUpFixedPath(ref pathData.Ref(), "/data"u8); + if (res.IsFailure()) return res.Miss(); + + res = pathRoot.Combine(in path, in pathData); + if (res.IsFailure()) return res.Miss(); + + switch (fsType) + { + case MountInfo.FileSystemType.HostFs: + res = OpenHostFileSystem(ref outFileSystem, in pathRoot, openCaseSensitive: true); + if (res.IsFailure()) return res.Miss(); + break; + + case MountInfo.FileSystemType.LocalFs: + res = _config.LocalFsCreator.Create(ref outFileSystem, in pathRoot, openCaseSensitive: true); + if (res.IsFailure()) return res.Miss(); + break; + + default: + Abort.UnexpectedDefault(); + break; + } + + return Result.Success; } private Result ParseNsp(out bool outFoundNspPath, ref U8Span path, ref SharedRef outFileSystem, ref readonly SharedRef baseFileSystem) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outFoundNspPath); + + ReadOnlySpan nspExtension = ".nsp"u8; + const int nspExtensionSize = 4; + + // Search for the end of the nsp part of the path + int nspPathLen = 0; + + while (true) + { + U8Span currentSpan = path.Slice(nspPathLen); + + if (StringUtils.CompareCaseInsensitive(currentSpan, nspExtension, nspExtensionSize) == 0) + { + // The nsp filename must be the end of the entire path or the end of a path segment + if (currentSpan.Length <= 4 || currentSpan[4] == 0 || currentSpan[4] == (byte)'/') + break; + + nspPathLen += nspExtensionSize; + } + else if (currentSpan.Length == 0 || currentSpan[0] == 0) + { + outFoundNspPath = false; + return Result.Success; + } + else + { + nspPathLen++; + } + } + + nspPathLen += nspExtensionSize; + + using var nspPath = new Path(); + Result res = nspPath.InitializeWithNormalization(path, nspPathLen); + if (res.IsFailure()) return res.Miss(); + + using var fileStorage = new SharedRef(new FileStorageBasedFileSystem()); + res = fileStorage.Get.Initialize(in baseFileSystem, in nspPath, OpenMode.Read); + if (res.IsFailure()) return res.Miss(); + + using SharedRef tempStorage = SharedRef.CreateMove(ref fileStorage.Ref); + res = _config.NspRootFileSystemCreator.Create(ref outFileSystem, in tempStorage); + if (res.IsFailure()) return res.Miss(); + + path = path.Slice(nspPathLen); + outFoundNspPath = true; + + return Result.Success; } private Result ParseNca(ref SharedRef outNcaReader, ref readonly SharedRef baseFileSystem, U8Span path, ContentAttributes attributes, ulong programId) { - throw new NotImplementedException(); + using var fileStorage = new SharedRef(new FileStorageBasedFileSystem()); + + using var pathNca = new Path(); + Result res = pathNca.InitializeWithNormalization(path); + if (res.IsFailure()) return res.Miss(); + + res = fileStorage.Get.Initialize(in baseFileSystem, in pathNca, OpenMode.Read); + if (res.IsFailure()) return res.Miss(); + + using var ncaReader = new SharedRef(); + using SharedRef tempStorage = SharedRef.CreateMove(ref fileStorage.Ref); + res = _config.StorageOnNcaCreator.CreateNcaReader(ref ncaReader.Ref, in tempStorage, attributes); + if (res.IsFailure()) return res.Miss(); + + if (programId != ulong.MaxValue) + { + ulong ncaProgramId = ncaReader.Get.GetProgramId(); + + if (ncaProgramId != ulong.MaxValue && programId != ncaProgramId) + { + return ResultFs.InvalidNcaId.Log(); + } + } + + outNcaReader.SetByMove(ref ncaReader.Ref); + + return Result.Success; } private Result ParseNca(ref SharedRef outNcaReader, out bool outIsGameCard, U8Span path, ContentAttributes attributes, ulong programId) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outIsGameCard); + + U8Span currentPath = path; + + var mountInfo = new MountInfo(); + using var fileSystem = new SharedRef(); + Result res = ParseMountName(ref currentPath, ref fileSystem.Ref, ref mountInfo); + if (res.IsFailure()) return res.Miss(); + + outIsGameCard = mountInfo.IsGameCard(); + + res = CheckNcaOrNsp(ref currentPath); + if (res.IsFailure()) return res.Miss(); + + bool foundNspPath; + + using (SharedRef fileSystemCopy = SharedRef.CreateCopy(in fileSystem)) + { + res = ParseNsp(out foundNspPath, ref currentPath, ref fileSystem.Ref, in fileSystemCopy); + if (res.IsFailure()) return res.Miss(); + } + + // If we found an .nsp file in the file path, the portion of the path after the .nsp file will be used to open + // a file inside the .nsp file. + // We're trying to open an .nca file, so there must be something in the path after the .nsp file. + if (foundNspPath && (currentPath.Length == 0 || currentPath[0] == 0)) + return ResultFs.TargetNotFound.Log(); + + if (!mountInfo.CanMountNca) + return ResultFs.UnexpectedInNcaFileSystemServiceImplA.Log(); + + res = ParseNca(ref outNcaReader, in fileSystem, currentPath, attributes, programId); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; } private Result ParseContentTypeForDirectory(ref SharedRef outFileSystem, - ref readonly SharedRef baseFileSystem, FileSystemProxyType fsType) + ref readonly SharedRef baseFileSystem, FileSystemProxyType type) { - throw new NotImplementedException(); + Span directoryPathBuffer = stackalloc byte[0x10]; + + // Get the name of the subdirectory for the filesystem type + switch (type) + { + case FileSystemProxyType.Code: + StringUtils.Strlcpy(directoryPathBuffer, "/code"u8, 16); + break; + + case FileSystemProxyType.Logo: + StringUtils.Strlcpy(directoryPathBuffer, "/logo"u8, 16); + break; + + case FileSystemProxyType.Rom: + case FileSystemProxyType.Control: + case FileSystemProxyType.Manual: + case FileSystemProxyType.Meta: + case FileSystemProxyType.RegisteredUpdate: + StringUtils.Strlcpy(directoryPathBuffer, "/data"u8, 16); + break; + + case FileSystemProxyType.Package: + outFileSystem.SetByCopy(in baseFileSystem); + return Result.Success; + + default: + return ResultFs.InvalidArgument.Log(); + } + + using scoped var directoryPath = new Path(); + Result res = PathFunctions.SetUpFixedPath(ref directoryPath.Ref(), directoryPathBuffer); + if (res.IsFailure()) return res.Miss(); + + if (directoryPath.IsEmpty()) + return ResultFs.InvalidArgument.Log(); + + // Open the subdirectory filesystem + using var fileSystem = new SharedRef(); + res = _config.SubDirectoryFsCreator.Create(ref fileSystem.Ref, in baseFileSystem, in directoryPath); + if (res.IsFailure()) return res.Miss(); + + outFileSystem.SetByMove(ref fileSystem.Ref); + return Result.Success; } public Result SetExternalKeyForRightsId(NcaReader ncaReader) { - throw new NotImplementedException(); + Span zeroRightsId = stackalloc byte[Unsafe.SizeOf()]; + Span rightsId = stackalloc byte[Unsafe.SizeOf()]; + + zeroRightsId.Clear(); + ncaReader.GetRightsId(rightsId); + + bool hasRightsId = !Crypto.CryptoUtil.IsSameBytes(rightsId, zeroRightsId, Unsafe.SizeOf()); + + if (hasRightsId) + { + Result res = _externalKeyManager.Find(out AccessKey keySource, SpanHelpers.AsStruct(rightsId)); + if (res.IsFailure()) return res.Miss(); + + ncaReader.SetExternalDecryptionKey(in keySource); + } + + return Result.Success; } public bool IsAvailableKeySource(ReadOnlySpan keySource) { - throw new NotImplementedException(); + return _externalKeyManager.IsAvailableKeySource(keySource); } public Result OpenStorageByContentType(ref SharedRef outNcaStorage, @@ -338,7 +1162,81 @@ public class NcaFileSystemServiceImpl : IDisposable ref readonly SharedRef ncaReader, out NcaFsHeader.FsType outFsType, FileSystemProxyType fsProxyType, bool isGameCard, bool canMountSystemDataPrivate) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outFsType); + + NcaHeader.ContentType contentType = ncaReader.Get.GetContentType(); + + switch (fsProxyType) + { + case FileSystemProxyType.Code: + if (contentType != NcaHeader.ContentType.Program) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Rom: + if (contentType != NcaHeader.ContentType.Program) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Logo: + if (contentType != NcaHeader.ContentType.Program) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Control: + if (contentType != NcaHeader.ContentType.Control) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Manual: + if (contentType != NcaHeader.ContentType.Manual) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Meta: + if (contentType != NcaHeader.ContentType.Meta) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Data: + if (contentType != NcaHeader.ContentType.Data && contentType != NcaHeader.ContentType.PublicData) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.RegisteredUpdate: + if (contentType != NcaHeader.ContentType.Program) + return ResultFs.PreconditionViolation.Log(); + break; + + default: + return ResultFs.InvalidArgument.Log(); + } + + if (contentType == NcaHeader.ContentType.Data && !canMountSystemDataPrivate) + return ResultFs.PermissionDenied.Log(); + + if (ncaReader.Get.GetDistributionType() == NcaHeader.DistributionType.GameCard && !isGameCard) + return ResultFs.PermissionDenied.Log(); + + if (isGameCard) + { + ncaReader.Get.PrioritizeSwAes(); + } + + Result res = SetExternalKeyForRightsId(ncaReader.Get); + if (res.IsFailure()) return res.Miss(); + + res = GetPartitionIndex(out int partitionIndex, fsProxyType); + if (res.IsFailure()) return res.Miss(); + + var ncaFsHeaderReader = new NcaFsHeaderReader(); + + res = _config.StorageOnNcaCreator.Create(ref outNcaStorage, ref outStorageAccessSplitter, ref ncaFsHeaderReader, + in ncaReader, partitionIndex); + if (res.IsFailure()) return res.Miss(); + + outFsType = ncaFsHeaderReader.GetFsType(); + return Result.Success; } public Result OpenStorageWithPatchByContentType(ref SharedRef outNcaStorage, @@ -346,7 +1244,57 @@ public class NcaFileSystemServiceImpl : IDisposable ref readonly SharedRef originalNcaReader, ref readonly SharedRef currentNcaReader, out NcaFsHeader.FsType outFsType, FileSystemProxyType fsProxyType, bool canMountSystemDataPrivate) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outFsType); + + NcaHeader.ContentType contentType = currentNcaReader.Get.GetContentType(); + + switch (fsProxyType) + { + case FileSystemProxyType.Rom: + if (contentType != NcaHeader.ContentType.Program) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Manual: + if (contentType != NcaHeader.ContentType.Manual) + return ResultFs.PreconditionViolation.Log(); + break; + + case FileSystemProxyType.Data: + if (contentType != NcaHeader.ContentType.Data && contentType != NcaHeader.ContentType.PublicData) + return ResultFs.PreconditionViolation.Log(); + break; + + default: + return ResultFs.InvalidArgument.Log(); + } + + if (contentType == NcaHeader.ContentType.Data && !canMountSystemDataPrivate) + return ResultFs.PermissionDenied.Log(); + + Result res = SetExternalKeyForRightsId(currentNcaReader.Get); + if (res.IsFailure()) return res.Miss(); + + if (originalNcaReader.HasValue) + { + if (originalNcaReader.Get.GetContentType() != contentType) + return ResultFs.PreconditionViolation.Log(); + + res = SetExternalKeyForRightsId(originalNcaReader.Get); + if (res.IsFailure()) return res.Miss(); + } + + res = GetPartitionIndex(out int partitionIndex, fsProxyType); + if (res.IsFailure()) return res.Miss(); + + var ncaFsHeaderReader = new NcaFsHeaderReader(); + + res = _config.StorageOnNcaCreator.CreateWithPatch(ref outNcaStorage, ref outStorageAccessSplitter, + ref ncaFsHeaderReader, in originalNcaReader, in currentNcaReader, partitionIndex); + if (res.IsFailure()) return res.Miss(); + + outFsType = ncaFsHeaderReader.GetFsType(); + return Result.Success; } public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed) @@ -369,60 +1317,122 @@ public class NcaFileSystemServiceImpl : IDisposable return Result.Success; } - public Result ResolveProgramPath(out bool isDirectory, ref Path outPath, out ContentAttributes outContentAttributes, + public Result ResolveProgramPath(out bool outIsDirectory, ref Path outPath, out ContentAttributes outContentAttributes, ProgramId programId, StorageId storageId) { - throw new NotImplementedException(); + Result res = _config.LocationResolverSet.ResolveProgramPath(out outIsDirectory, ref outPath, + out outContentAttributes, programId.Value, storageId); + if (res.IsSuccess()) + return Result.Success; + + outIsDirectory = false; + + res = _config.LocationResolverSet.ResolveDataPath(ref outPath, out outContentAttributes, + new DataId(programId.Value), storageId); + if (res.IsSuccess()) + return Result.Success; + + return ResultFs.TargetNotFound.Log(); } public Result ResolveApplicationControlPath(ref Path outPath, out ContentAttributes outContentAttributes, - ApplicationId applicationId, StorageId storageId) + Ncm.ApplicationId applicationId, StorageId storageId) { - throw new NotImplementedException(); + return _config.LocationResolverSet + .ResolveApplicationControlPath(ref outPath, out outContentAttributes, applicationId, storageId).Ret(); } - public Result ResolveRomPath(out bool isDirectory, ref Path outPath, out ContentAttributes outContentAttributes, + public Result ResolveRomPath(out bool outIsDirectory, ref Path outPath, out ContentAttributes outContentAttributes, out ulong outOriginalProgramId, ulong programId, StorageId storageId) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outOriginalProgramId); + + Result res = _config.LocationResolverSet.ResolveRomPath(out outIsDirectory, ref outPath, + out outContentAttributes, programId, storageId); + + if (!res.IsSuccess()) + { + if (ResultLr.DebugProgramNotFound.Includes(res)) + { + ProgramId targetProgramId = _config.ProgramRegistryService.GetApplicationProgramProgramIdByPatchProgramProgramId(new ProgramId(programId)); + if (targetProgramId == ProgramId.InvalidId) + return res.Rethrow(); + + res = _config.LocationResolverSet.ResolveRomPath(out outIsDirectory, ref outPath, + out outContentAttributes, targetProgramId.Value, storageId); + if (res.IsFailure()) return res.Miss(); + + outOriginalProgramId = targetProgramId.Value; + } + + return res.Miss(); + } + + outOriginalProgramId = programId; + return Result.Success; } - public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Path outPath, + public Result ResolveApplicationHtmlDocumentPath(out bool outIsDirectory, ref Path outPath, out ContentAttributes outContentAttributes, out ulong outOriginalProgramId, ulong programId, StorageId storageId) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outOriginalProgramId); + + Result res = _config.LocationResolverSet.ResolveApplicationHtmlDocumentPath(out outIsDirectory, ref outPath, + out outContentAttributes, programId, storageId); + + if (!res.IsSuccess()) + { + if (ResultLr.HtmlDocumentNotFound.Includes(res)) + { + ProgramId targetProgramId = _config.ProgramRegistryService.GetApplicationHtmlDocumentProgramIdByPatchProgramProgramId(new ProgramId(programId)); + if (targetProgramId == ProgramId.InvalidId) + return res.Rethrow(); + + res = _config.LocationResolverSet.ResolveApplicationHtmlDocumentPath(out outIsDirectory, ref outPath, + out outContentAttributes, targetProgramId.Value, storageId); + if (res.IsFailure()) return res.Miss(); + + outOriginalProgramId = targetProgramId.Value; + } + + return res.Miss(); + } + + outOriginalProgramId = programId; + return Result.Success; } public Result ResolveDataPath(ref Path outPath, out ContentAttributes outContentAttributes, DataId dataId, StorageId storageId) { - throw new NotImplementedException(); + return _config.LocationResolverSet.ResolveDataPath(ref outPath, out outContentAttributes, dataId, storageId).Ret(); } public Result ResolveAddOnContentPath(ref Path outPath, out ContentAttributes outContentAttributes, ref Path outPatchPath, out ContentAttributes outPatchContentAttributes, DataId dataId) { - throw new NotImplementedException(); + return _config.LocationResolverSet.ResolveAddOnContentPath(ref outPath, out outContentAttributes, + ref outPatchPath, out outPatchContentAttributes, dataId).Ret(); } public Result ResolveRegisteredProgramPath(ref Path outPath, out ContentAttributes outContentAttributes, ulong programId) { - throw new NotImplementedException(); + return _config.LocationResolverSet.ResolveRegisteredProgramPath(ref outPath, out outContentAttributes, programId).Ret(); } public Result ResolveRegisteredHtmlDocumentPath(ref Path outPath, out ContentAttributes outContentAttributes, ulong programId) { - throw new NotImplementedException(); + return _config.LocationResolverSet.ResolveRegisteredHtmlDocumentPath(ref outPath, out outContentAttributes, programId).Ret(); } internal StorageLayoutType GetStorageFlag(ulong programId) { Assert.SdkRequiresNotEqual(_config.SpeedEmulationRange.ProgramIdWithoutPlatformIdMax, 0ul); - ulong programIdWithoutPlatformId = Utility.ClearPlatformIdInProgramId(programId); + ulong programIdWithoutPlatformId = Impl.Utility.ClearPlatformIdInProgramId(programId); if (programIdWithoutPlatformId >= _config.SpeedEmulationRange.ProgramIdWithoutPlatformIdMin && programIdWithoutPlatformId <= _config.SpeedEmulationRange.ProgramIdWithoutPlatformIdMax) @@ -489,12 +1499,12 @@ public class NcaFileSystemServiceImpl : IDisposable public Result CreateNotifier(ref UniqueRef outNotifier) { - throw new NotImplementedException(); + return _systemDataUpdateEventManager.CreateNotifier(ref outNotifier).Ret(); } public Result NotifySystemDataUpdateEvent() { - throw new NotImplementedException(); + return _systemDataUpdateEventManager.NotifySystemDataUpdateEvent().Ret(); } public Result OpenHostFileSystem(ref SharedRef outFileSystem, ref readonly Path rootPath, bool openCaseSensitive) @@ -502,18 +1512,6 @@ public class NcaFileSystemServiceImpl : IDisposable return _config.TargetManagerFsCreator.Create(ref outFileSystem, in rootPath, openCaseSensitive, false, Result.Success).Ret(); } - - internal Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); - } - - internal Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfoByProgramId(out programInfo, programId); - } } public readonly struct InternalProgramIdRangeForSpeedEmulation diff --git a/src/LibHac/FsSrv/ProgramRegistryImpl.cs b/src/LibHac/FsSrv/ProgramRegistryImpl.cs index 92eddbf7..cf7c810c 100644 --- a/src/LibHac/FsSrv/ProgramRegistryImpl.cs +++ b/src/LibHac/FsSrv/ProgramRegistryImpl.cs @@ -19,7 +19,7 @@ internal struct ProgramRegistryImplGlobals /// is stored in a and includes the process' process ID, program ID, /// storage location and file system permissions. This allows FS to resolve the program ID and /// verify the permissions of any process calling it. -/// Based on nnSdk 13.4.0 (FS 13.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class ProgramRegistryImpl : IProgramRegistry { private ulong _processId; @@ -55,8 +55,9 @@ public class ProgramRegistryImpl : IProgramRegistry if (accessControlDescriptorSize > accessControlDescriptor.Size) return ResultFs.InvalidSize.Log(); - return Globals.ServiceImpl.RegisterProgramInfo(processId, programId, storageId, accessControlData.Buffer, - accessControlDescriptor.Buffer); + return Globals.ServiceImpl.RegisterProgramInfo(processId, programId, storageId, + accessControlData.Buffer.Slice(0, (int)accessControlDataSize), + accessControlDescriptor.Buffer.Slice(0, (int)accessControlDescriptorSize)).Ret(); } /// : The operation was successful.
@@ -70,7 +71,7 @@ public class ProgramRegistryImpl : IProgramRegistry if (!_fsServer.IsInitialProgram(_processId)) return ResultFs.PermissionDenied.Log(); - return Globals.ServiceImpl.UnregisterProgramInfo(processId); + return Globals.ServiceImpl.UnregisterProgramInfo(processId).Ret(); } /// @@ -78,7 +79,7 @@ public class ProgramRegistryImpl : IProgramRegistry { Assert.SdkRequiresNotNull(Globals.ServiceImpl); - return Globals.ServiceImpl.GetProgramInfo(out programInfo, processId); + return Globals.ServiceImpl.GetProgramInfo(out programInfo, processId).Ret(); } /// @@ -86,7 +87,7 @@ public class ProgramRegistryImpl : IProgramRegistry { Assert.SdkRequiresNotNull(Globals.ServiceImpl); - return Globals.ServiceImpl.GetProgramInfoByProgramId(out programInfo, programId); + return Globals.ServiceImpl.GetProgramInfoByProgramId(out programInfo, programId).Ret(); } /// diff --git a/src/LibHac/FsSrv/ProgramRegistryService.cs b/src/LibHac/FsSrv/ProgramRegistryService.cs index 1b601dd1..bc78b82b 100644 --- a/src/LibHac/FsSrv/ProgramRegistryService.cs +++ b/src/LibHac/FsSrv/ProgramRegistryService.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.FsSrv.Impl; @@ -14,7 +13,7 @@ namespace LibHac.FsSrv; /// /// Appropriate methods calls on IFileSystemProxy are forwarded to this class /// which then checks the calling process' permissions and performs the requested operation. -/// Based on nnSdk 13.4.0 (FS 13.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) internal readonly struct ProgramIndexRegistryService { private readonly ProgramRegistryServiceImpl _serviceImpl; @@ -53,11 +52,14 @@ internal readonly struct ProgramIndexRegistryService return ResultFs.PermissionDenied.Log(); } - // Return early if the program count is 0 so we leave any previously + // Return early if the program count is 0, so we leave any previously // registered entries as they were if (programCount == 0) return Result.Success; + if (programIndexMapInfo.IsNull) + return ResultFs.NullptrArgument.Log(); + // Verify that the provided buffer is large enough to hold "programCount" entries ReadOnlySpan mapInfo = programIndexMapInfo.AsSpan(); @@ -65,7 +67,7 @@ internal readonly struct ProgramIndexRegistryService return ResultFs.InvalidSize.Log(); // Register the map info - return _serviceImpl.ResetProgramIndexMapInfo(mapInfo.Slice(0, programCount)); + return _serviceImpl.ResetProgramIndexMapInfo(mapInfo.Slice(0, programCount)).Ret(); } /// @@ -88,10 +90,10 @@ internal readonly struct ProgramIndexRegistryService if (res.IsFailure()) return res.Miss(); // Try to get map info for this process - Optional mapInfo = _serviceImpl.GetProgramIndexMapInfo(programInfo.ProgramId); + Optional programMapInfo = _serviceImpl.GetProgramIndexMapInfo(programInfo.ProgramId); // Set the output program index if map info was found - programIndex = mapInfo.HasValue ? mapInfo.ValueRo.ProgramIndex : 0; + programIndex = programMapInfo.HasValue ? programMapInfo.ValueRo.ProgramIndex : 0; // Set the number of programs in the current application programCount = _serviceImpl.GetProgramIndexMapInfoCount(); @@ -110,7 +112,7 @@ internal readonly struct ProgramIndexRegistryService /// /// Manages the main program registry and the multi-program registry. /// -/// Based on nnSdk 13.4.0 (FS 13.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class ProgramRegistryServiceImpl : IDisposable { // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable @@ -142,37 +144,31 @@ public class ProgramRegistryServiceImpl : IDisposable ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) { return _registryManager.RegisterProgram(processId, programId, storageId, accessControlData, - accessControlDescriptor); + accessControlDescriptor).Ret(); } /// public Result UnregisterProgramInfo(ulong processId) { - return _registryManager.UnregisterProgram(processId); + return _registryManager.UnregisterProgram(processId).Ret(); } /// public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) { - return _registryManager.GetProgramInfo(out programInfo, processId); + return _registryManager.GetProgramInfo(out programInfo, processId).Ret(); } /// public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) { - return _registryManager.GetProgramInfoByProgramId(out programInfo, programId); + return _registryManager.GetProgramInfoByProgramId(out programInfo, programId).Ret(); } /// public Result ResetProgramIndexMapInfo(ReadOnlySpan programIndexMapInfo) { - return _programIndexManager.Reset(programIndexMapInfo); - } - - /// - public ProgramId GetProgramIdByIndex(ProgramId programId, byte programIndex) - { - return _programIndexManager.GetProgramId(programId, programIndex); + return _programIndexManager.Reset(programIndexMapInfo).Ret(); } /// @@ -181,6 +177,12 @@ public class ProgramRegistryServiceImpl : IDisposable return _programIndexManager.Get(programId); } + /// + public ProgramId GetProgramIdByIndex(ProgramId programId, byte programIndex) + { + return _programIndexManager.GetProgramId(programId, programIndex); + } + /// /// Gets the number of programs in the currently registered application. /// @@ -189,4 +191,14 @@ public class ProgramRegistryServiceImpl : IDisposable { return _programIndexManager.GetProgramCount(); } + + public ProgramId GetApplicationProgramProgramIdByPatchProgramProgramId(ProgramId programId) + { + return _programIndexManager.GetApplicationProgramId(programId); + } + + public ProgramId GetApplicationHtmlDocumentProgramIdByPatchProgramProgramId(ProgramId programId) + { + return GetApplicationProgramProgramIdByPatchProgramProgramId(programId); + } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 6a5c7fcc..90ec04e2 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -1191,7 +1191,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable res = Utility.WrapSubDirectory(ref baseFileSystem.Ref, ref tempFileSystem.Ref, in pathSdRoot, createIfMissing); if (res.IsFailure()) return res.Miss(); - res = _config.EncryptedFsCreator.Create(ref outFileSystem, ref baseFileSystem.Ref, + res = _config.EncryptedFsCreator.Create(ref outFileSystem, in baseFileSystem, IEncryptedFileSystemCreator.KeyId.Save, in _encryptionSeed); if (res.IsFailure()) return res.Miss(); diff --git a/src/LibHac/FsSystem/ConcatenationFileSystem.cs b/src/LibHac/FsSystem/ConcatenationFileSystem.cs index 6502271a..b4b6cfa1 100644 --- a/src/LibHac/FsSystem/ConcatenationFileSystem.cs +++ b/src/LibHac/FsSystem/ConcatenationFileSystem.cs @@ -490,7 +490,7 @@ public class ConcatenationFileSystem : IFileSystem } } - public static readonly long DefaultInternalFileSize = 0xFFFF0000; // Hard-coded value used by FS + public const uint DefaultInternalFileSize = 0xFFFF0000; // Hard-coded value used by FS private UniqueRef _baseFileSystem; private long _internalFileSize; diff --git a/src/LibHac/FsSystem/NcaFileSystemDriver.cs b/src/LibHac/FsSystem/NcaFileSystemDriver.cs index 877ad27d..5e4eac52 100644 --- a/src/LibHac/FsSystem/NcaFileSystemDriver.cs +++ b/src/LibHac/FsSystem/NcaFileSystemDriver.cs @@ -204,13 +204,13 @@ public class NcaFileSystemDriver : IDisposable } public Result OpenStorage(ref SharedRef outStorage, - ref SharedRef outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader, + ref SharedRef outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader, int fsIndex) { throw new NotImplementedException(); } - private Result OpenStorageImpl(ref SharedRef outStorage, out NcaFsHeaderReader outHeaderReader, + private Result OpenStorageImpl(ref SharedRef outStorage, ref NcaFsHeaderReader outHeaderReader, int fsIndex, ref StorageContext storageContext) { throw new NotImplementedException(); diff --git a/src/LibHac/Lr/AddOnContentLocationResolver.cs b/src/LibHac/Lr/AddOnContentLocationResolver.cs index 6758e624..043cb75b 100644 --- a/src/LibHac/Lr/AddOnContentLocationResolver.cs +++ b/src/LibHac/Lr/AddOnContentLocationResolver.cs @@ -33,4 +33,13 @@ public class AddOnContentLocationResolver : IDisposable public Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id) => _interface.Get.UnregisterApplicationAddOnContent(id); + + public Result GetRegisteredAddOnContentPaths(out Path outPath, out Path outPatchPath, DataId id) => + _interface.Get.GetRegisteredAddOnContentPaths(out outPath, out outPatchPath, id); + + public Result RegisterAddOnContentPath(DataId id, ApplicationId applicationId, in Path path) => + _interface.Get.RegisterAddOnContentPath(id, applicationId, in path); + + public Result RegisterAddOnContentPaths(DataId id, ApplicationId applicationId, in Path path, in Path patchPath) => + _interface.Get.RegisterAddOnContentPaths(id, applicationId, in path, in patchPath); } \ No newline at end of file diff --git a/src/LibHac/Lr/IAddOnContentLocationResolver.cs b/src/LibHac/Lr/IAddOnContentLocationResolver.cs index e4d46606..24619132 100644 --- a/src/LibHac/Lr/IAddOnContentLocationResolver.cs +++ b/src/LibHac/Lr/IAddOnContentLocationResolver.cs @@ -6,9 +6,12 @@ namespace LibHac.Lr; public interface IAddOnContentLocationResolver : IDisposable { - Result ResolveAddOnContentPath(out Path path, DataId id); + Result ResolveAddOnContentPath(out Path outPath, DataId id); Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId); Result UnregisterAllAddOnContentPath(); Result RefreshApplicationAddOnContent(InArray ids); Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id); + Result GetRegisteredAddOnContentPaths(out Path outPath, out Path outPatchPath, DataId id); + Result RegisterAddOnContentPath(DataId id, ApplicationId applicationId, in Path path); + Result RegisterAddOnContentPaths(DataId id, ApplicationId applicationId, in Path path, in Path patchPath); } \ No newline at end of file diff --git a/src/LibHac/Tools/FsSystem/AesXtsFileSystem.cs b/src/LibHac/Tools/FsSystem/AesXtsFileSystem.cs index 5a4ee0ea..97eec75b 100644 --- a/src/LibHac/Tools/FsSystem/AesXtsFileSystem.cs +++ b/src/LibHac/Tools/FsSystem/AesXtsFileSystem.cs @@ -17,9 +17,9 @@ public class AesXtsFileSystem : IFileSystem private byte[] _kekSource; private byte[] _validationKey; - public AesXtsFileSystem(ref SharedRef fs, byte[] keys, int blockSize) + public AesXtsFileSystem(ref readonly SharedRef fs, byte[] keys, int blockSize) { - _sharedBaseFileSystem = SharedRef.CreateMove(ref fs); + _sharedBaseFileSystem = SharedRef.CreateCopy(in fs); _baseFileSystem = _sharedBaseFileSystem.Get; _kekSource = keys.AsSpan(0, 0x10).ToArray(); _validationKey = keys.AsSpan(0x10, 0x10).ToArray(); diff --git a/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs b/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs index f6d8c279..9bb7c429 100644 --- a/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs +++ b/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs @@ -26,9 +26,9 @@ public class RomFsFileSystem : IFileSystem FileTable = new HierarchicalRomFileTable(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable); } - public RomFsFileSystem(ref SharedRef storage) : this(storage.Get) + public RomFsFileSystem(ref readonly SharedRef storage) : this(storage.Get) { - _baseStorageShared = SharedRef.CreateMove(ref storage); + _baseStorageShared = SharedRef.CreateCopy(in storage); } public override void Dispose() diff --git a/tests/LibHac.Tests/LibHac.Tests.csproj.DotSettings b/tests/LibHac.Tests/LibHac.Tests.csproj.DotSettings index 19c37353..46c92a72 100644 --- a/tests/LibHac.Tests/LibHac.Tests.csproj.DotSettings +++ b/tests/LibHac.Tests/LibHac.Tests.csproj.DotSettings @@ -1,2 +1,4 @@  - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy> \ No newline at end of file + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy> + True \ No newline at end of file