Remove the old NcaReader

This commit is contained in:
Alex Barney 2024-03-31 20:03:53 -07:00
parent e73e2861bc
commit 7f7c8b6578
10 changed files with 252 additions and 1086 deletions

View file

@ -1,5 +1,7 @@
using LibHac.Common;
using System;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.FsSrv.FsCreator;
using LibHac.FsSrv.Storage;
using LibHac.FsSystem;
@ -32,14 +34,18 @@ public class DefaultFsServerObjects
using SharedRef<IFileSystem> sharedRootFileSystemCopy =
SharedRef<IFileSystem>.CreateCopy(in sharedRootFileSystem);
var memoryResource = new ArrayPoolMemoryResource();
IBufferManager bufferManager = null;
IHash256GeneratorFactorySelector ncaHashGeneratorFactorySelector = null;
creators.RomFileSystemCreator = new RomFileSystemCreator();
creators.PartitionFileSystemCreator = new PartitionFileSystemCreator();
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet);
creators.StorageOnNcaCreator = new StorageOnNcaCreator(memoryResource, bufferManager, InitializeNcaReader, new NcaCompressionConfiguration(), ncaHashGeneratorFactorySelector);
creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator();
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, null, randomGenerator);
creators.GameCardStorageCreator = gcStorageCreator;
creators.GameCardFileSystemCreator = new GameCardFileSystemCreator(new ArrayPoolMemoryResource(), gcStorageCreator, fsServer);
creators.GameCardFileSystemCreator = new GameCardFileSystemCreator(memoryResource, gcStorageCreator, fsServer);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);
creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(ref sharedRootFileSystem.Ref);
creators.SdCardFileSystemCreator = new EmulatedSdCardFileSystemCreator(sdmmcNew, ref sharedRootFileSystemCopy.Ref);
@ -55,4 +61,11 @@ public class DefaultFsServerObjects
StorageDeviceManagerFactory = storageDeviceManagerFactory
};
}
public static Result InitializeNcaReader(ref SharedRef<NcaReader> outReader, in SharedRef<IStorage> baseStorage,
in NcaCompressionConfiguration compressionConfig, IHash256GeneratorFactorySelector hashGeneratorFactorySelector,
ContentAttributes contentAttributes)
{
throw new NotImplementedException();
}
}

View file

@ -5,22 +5,16 @@ using LibHac.FsSystem;
namespace LibHac.FsSrv.FsCreator;
public interface IStorageOnNcaCreator
{
Result Create(ref SharedRef<IStorage> outStorage, out LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader fsHeader, LibHac.Tools.FsSystem.NcaUtils.Nca nca, int fsIndex, bool isCodeFs);
Result OpenNca(out LibHac.Tools.FsSystem.NcaUtils.Nca nca, IStorage ncaStorage);
}
public interface IStorageOnNcaCreator17
{
Result Create(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader17 outHeaderReader,
ref readonly SharedRef<NcaReader17> ncaReader, int fsIndex);
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> ncaReader, int fsIndex);
Result CreateWithPatch(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader17 outHeaderReader,
ref readonly SharedRef<NcaReader17> originalNcaReader, ref readonly SharedRef<NcaReader17> currentNcaReader,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> originalNcaReader, ref readonly SharedRef<NcaReader> currentNcaReader,
int fsIndex);
Result CreateNcaReader(ref SharedRef<NcaReader17> outReader, ref readonly SharedRef<IStorage> baseStorage,
Result CreateNcaReader(ref SharedRef<NcaReader> outReader, ref readonly SharedRef<IStorage> baseStorage,
ContentAttributes contentAttributes);
}

View file

@ -1,77 +1,84 @@
using System;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader;
namespace LibHac.FsSrv.FsCreator;
/// <summary>
/// Opens the partitions in NCAs as <see cref="IStorage"/>s.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class StorageOnNcaCreator : IStorageOnNcaCreator
{
// ReSharper disable once UnusedMember.Local
private bool IsEnabledProgramVerification { get; set; }
private KeySet KeySet { get; }
private MemoryResource _memoryResource;
private NcaCompressionConfiguration _compressionConfig;
private IBufferManager _bufferManager;
private NcaReaderInitializer _ncaReaderInitializer;
private IHash256GeneratorFactorySelector _hashGeneratorFactorySelector;
public StorageOnNcaCreator(KeySet keySet)
public StorageOnNcaCreator(MemoryResource memoryResource, IBufferManager bufferManager,
NcaReaderInitializer ncaReaderInitializer, in NcaCompressionConfiguration compressionConfig,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
{
KeySet = keySet;
_memoryResource = memoryResource;
_compressionConfig = compressionConfig;
_bufferManager = bufferManager;
_ncaReaderInitializer = ncaReaderInitializer;
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
}
// todo: Implement NcaReader and other Nca classes
public Result Create(ref SharedRef<IStorage> outStorage, out NcaFsHeader fsHeader, Nca nca,
int fsIndex, bool isCodeFs)
public Result Create(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> ncaReader, int fsIndex)
{
UnsafeHelpers.SkipParamInit(out fsHeader);
var ncaFsDriver = new NcaFileSystemDriver(in ncaReader, _memoryResource, _bufferManager, _hashGeneratorFactorySelector);
Result res = OpenStorage(out IStorage storageTemp, nca, fsIndex);
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref,
ref storageAccessSplitter.Ref, out outHeaderReader, fsIndex));
if (res.IsFailure()) return res.Miss();
if (isCodeFs)
{
using var codeFs = new PartitionFileSystem();
res = codeFs.Initialize(storageTemp);
if (res.IsFailure()) return res.Miss();
using var resultConvertStorage = new SharedRef<RomResultConvertStorage>(new RomResultConvertStorage(in storage));
res = VerifyAcidSignature(codeFs, nca);
if (res.IsFailure()) return res.Miss();
}
outStorage.Reset(storageTemp);
fsHeader = nca.GetFsHeader(fsIndex);
outStorage.SetByMove(ref resultConvertStorage.Ref);
outStorageAccessSplitter.SetByMove(ref storageAccessSplitter.Ref);
return Result.Success;
}
public Result CreateWithPatch(ref SharedRef<IStorage> outStorage, out NcaFsHeader fsHeader,
Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs)
public Result CreateWithPatch(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> originalNcaReader, ref readonly SharedRef<NcaReader> currentNcaReader,
int fsIndex)
{
throw new NotImplementedException();
}
var ncaFsDriver = new NcaFileSystemDriver(in originalNcaReader, in currentNcaReader, _memoryResource,
_bufferManager, _hashGeneratorFactorySelector);
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref,
ref storageAccessSplitter.Ref, out outHeaderReader, fsIndex));
if (res.IsFailure()) return res.Miss();
using var resultConvertStorage = new SharedRef<RomResultConvertStorage>(new RomResultConvertStorage(in storage));
outStorage.SetByMove(ref resultConvertStorage.Ref);
outStorageAccessSplitter.SetByMove(ref storageAccessSplitter.Ref);
public Result OpenNca(out Nca nca, IStorage ncaStorage)
{
nca = new Nca(KeySet, ncaStorage);
return Result.Success;
}
public Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca)
public Result CreateNcaReader(ref SharedRef<NcaReader> outReader, ref readonly SharedRef<IStorage> baseStorage,
ContentAttributes contentAttributes)
{
// todo
return Result.Success;
}
using var ncaReader = new SharedRef<NcaReader>();
private Result OpenStorage(out IStorage storage, Nca nca, int fsIndex)
{
UnsafeHelpers.SkipParamInit(out storage);
Result res = RomResultConverter.ConvertRomResult(_ncaReaderInitializer(ref ncaReader.Ref, in baseStorage,
in _compressionConfig, _hashGeneratorFactorySelector, contentAttributes));
if (res.IsFailure()) return res.Miss();
if (!nca.SectionExists(fsIndex))
return ResultFs.PartitionNotFound.Log();
storage = nca.OpenStorage(fsIndex, IntegrityCheckLevel.ErrorOnInvalid);
outReader.SetByMove(ref ncaReader.Ref);
return Result.Success;
}
}

View file

@ -1,84 +0,0 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
namespace LibHac.FsSrv.FsCreator;
/// <summary>
/// Opens the partitions in NCAs as <see cref="IStorage"/>s.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class StorageOnNcaCreator17 : IStorageOnNcaCreator17
{
private MemoryResource _memoryResource;
private NcaCompressionConfiguration _compressionConfig;
private IBufferManager _bufferManager;
private NcaReaderInitializer _ncaReaderInitializer;
private IHash256GeneratorFactorySelector _hashGeneratorFactorySelector;
public StorageOnNcaCreator17(MemoryResource memoryResource, IBufferManager bufferManager,
NcaReaderInitializer ncaReaderInitializer, in NcaCompressionConfiguration compressionConfig,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
{
_memoryResource = memoryResource;
_compressionConfig = compressionConfig;
_bufferManager = bufferManager;
_ncaReaderInitializer = ncaReaderInitializer;
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
}
public Result Create(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader17 outHeaderReader,
ref readonly SharedRef<NcaReader17> ncaReader, int fsIndex)
{
var ncaFsDriver = new NcaFileSystemDriver(in ncaReader, _memoryResource, _bufferManager, _hashGeneratorFactorySelector);
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref,
ref storageAccessSplitter.Ref, out outHeaderReader, fsIndex));
if (res.IsFailure()) return res.Miss();
using var resultConvertStorage = new SharedRef<RomResultConvertStorage>(new RomResultConvertStorage(in storage));
outStorage.SetByMove(ref resultConvertStorage.Ref);
outStorageAccessSplitter.SetByMove(ref storageAccessSplitter.Ref);
return Result.Success;
}
public Result CreateWithPatch(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader17 outHeaderReader,
ref readonly SharedRef<NcaReader17> originalNcaReader, ref readonly SharedRef<NcaReader17> currentNcaReader,
int fsIndex)
{
var ncaFsDriver = new NcaFileSystemDriver(in originalNcaReader, in currentNcaReader, _memoryResource,
_bufferManager, _hashGeneratorFactorySelector);
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref,
ref storageAccessSplitter.Ref, out outHeaderReader, fsIndex));
if (res.IsFailure()) return res.Miss();
using var resultConvertStorage = new SharedRef<RomResultConvertStorage>(new RomResultConvertStorage(in storage));
outStorage.SetByMove(ref resultConvertStorage.Ref);
outStorageAccessSplitter.SetByMove(ref storageAccessSplitter.Ref);
return Result.Success;
}
public Result CreateNcaReader(ref SharedRef<NcaReader17> outReader, ref readonly SharedRef<IStorage> baseStorage,
ContentAttributes contentAttributes)
{
using var ncaReader = new SharedRef<NcaReader17>();
Result res = RomResultConverter.ConvertRomResult(_ncaReaderInitializer(ref ncaReader.Ref, in baseStorage,
in _compressionConfig, _hashGeneratorFactorySelector, contentAttributes));
if (res.IsFailure()) return res.Miss();
outReader.SetByMove(ref ncaReader.Ref);
return Result.Success;
}
}

View file

@ -13,8 +13,6 @@ using LibHac.Spl;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Util;
using static LibHac.Fs.Impl.CommonMountNames;
using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader;
using NcaHeader = LibHac.FsSystem.NcaHeader;
using RightsId = LibHac.Fs.RightsId;
using Utility = LibHac.FsSystem.Utility;
@ -272,9 +270,10 @@ public class NcaFileSystemServiceImpl
{
using SharedRef<IFileSystem> tempFileSystem = SharedRef<IFileSystem>.CreateMove(ref subDirFs.Ref);
res = _config.EncryptedFsCreator.Create(ref subDirFs.Ref, ref tempFileSystem.Ref,
IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed);
IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed);
if (res.IsFailure()) return res.Miss();
}
outFileSystem.SetByMove(ref subDirFs.Ref);
return Result.Success;
@ -355,7 +354,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, ContentStorageSystemMountName,
ContentStorageSystemMountName.Length) == 0)
ContentStorageSystemMountName.Length) == 0)
{
path = path.Slice(ContentStorageSystemMountName.Length);
@ -366,7 +365,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, ContentStorageUserMountName,
ContentStorageUserMountName.Length) == 0)
ContentStorageUserMountName.Length) == 0)
{
path = path.Slice(ContentStorageUserMountName.Length);
@ -377,7 +376,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, ContentStorageSdCardMountName,
ContentStorageSdCardMountName.Length) == 0)
ContentStorageSdCardMountName.Length) == 0)
{
path = path.Slice(ContentStorageSdCardMountName.Length);
@ -388,7 +387,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, BisCalibrationFilePartitionMountName,
BisCalibrationFilePartitionMountName.Length) == 0)
BisCalibrationFilePartitionMountName.Length) == 0)
{
path = path.Slice(BisCalibrationFilePartitionMountName.Length);
@ -397,7 +396,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, BisSafeModePartitionMountName,
BisSafeModePartitionMountName.Length) == 0)
BisSafeModePartitionMountName.Length) == 0)
{
path = path.Slice(BisSafeModePartitionMountName.Length);
@ -406,7 +405,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, BisUserPartitionMountName,
BisUserPartitionMountName.Length) == 0)
BisUserPartitionMountName.Length) == 0)
{
path = path.Slice(BisUserPartitionMountName.Length);
@ -415,7 +414,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, BisSystemPartitionMountName,
BisSystemPartitionMountName.Length) == 0)
BisSystemPartitionMountName.Length) == 0)
{
path = path.Slice(BisSystemPartitionMountName.Length);
@ -424,7 +423,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, SdCardFileSystemMountName,
SdCardFileSystemMountName.Length) == 0)
SdCardFileSystemMountName.Length) == 0)
{
path = path.Slice(SdCardFileSystemMountName.Length);
@ -433,7 +432,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, HostRootFileSystemMountName,
HostRootFileSystemMountName.Length) == 0)
HostRootFileSystemMountName.Length) == 0)
{
path = path.Slice(HostRootFileSystemMountName.Length);
@ -449,7 +448,7 @@ public class NcaFileSystemServiceImpl
}
else if (StringUtils.Compare(path, RegisteredUpdatePartitionMountName,
RegisteredUpdatePartitionMountName.Length) == 0)
RegisteredUpdatePartitionMountName.Length) == 0)
{
path = path.Slice(RegisteredUpdatePartitionMountName.Length);
@ -599,34 +598,7 @@ public class NcaFileSystemServiceImpl
private Result ParseNca(ref U8Span path, out Nca nca, ref SharedRef<IFileSystem> baseFileSystem, ulong ncaId)
{
UnsafeHelpers.SkipParamInit(out nca);
// Todo: Create ref-counted storage
var ncaFileStorage = new FileStorageBasedFileSystem();
using var pathNca = new Path();
Result res = pathNca.InitializeWithNormalization(path);
if (res.IsFailure()) return res.Miss();
res = ncaFileStorage.Initialize(ref baseFileSystem, in pathNca, OpenMode.Read);
if (res.IsFailure()) return res.Miss();
res = _config.StorageOnNcaCreator.OpenNca(out Nca ncaTemp, ncaFileStorage);
if (res.IsFailure()) return res.Miss();
if (ncaId == ulong.MaxValue)
{
ulong ncaProgramId = ncaTemp.Header.TitleId;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (ncaProgramId != ulong.MaxValue && ncaId != ncaProgramId)
{
return ResultFs.InvalidNcaId.Log();
}
}
nca = ncaTemp;
return Result.Success;
throw new NotImplementedException();
}
private Result ParseContentTypeForDirectory(ref SharedRef<IFileSystem> outFileSystem,
@ -696,63 +668,7 @@ public class NcaFileSystemServiceImpl
private Result OpenStorageByContentType(ref SharedRef<IStorage> outNcaStorage, Nca nca,
out NcaFormatType fsType, FileSystemProxyType fsProxyType, bool isGameCard, bool canMountSystemDataPrivate)
{
UnsafeHelpers.SkipParamInit(out fsType);
NcaHeader.ContentType contentType = (NcaHeader.ContentType)nca.Header.ContentType;
switch (fsProxyType)
{
case FileSystemProxyType.Code:
case FileSystemProxyType.Rom:
case FileSystemProxyType.Logo:
case FileSystemProxyType.RegisteredUpdate:
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();
if (contentType == NcaHeader.ContentType.Data && !canMountSystemDataPrivate)
return ResultFs.PermissionDenied.Log();
break;
default:
return ResultFs.InvalidArgument.Log();
}
if (nca.Header.DistributionType == DistributionType.GameCard && !isGameCard)
return ResultFs.PermissionDenied.Log();
Result res = SetExternalKeyForRightsId(nca);
if (res.IsFailure()) return res.Miss();
res = GetPartitionIndex(out int sectionIndex, fsProxyType);
if (res.IsFailure()) return res.Miss();
res = _config.StorageOnNcaCreator.Create(ref outNcaStorage, out NcaFsHeader fsHeader, nca,
sectionIndex, fsProxyType == FileSystemProxyType.Code);
if (res.IsFailure()) return res.Miss();
fsType = fsHeader.FormatType;
return Result.Success;
throw new NotImplementedException();
}
public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed)

View file

@ -9,6 +9,8 @@ using LibHac.Util;
namespace LibHac.FsSystem;
public delegate Result CryptAesXtsFunction(Span<byte> dest, ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
/// <summary>
/// Reads and writes to an <see cref="IStorage"/> that's encrypted with AES-XTS-128.
/// All encryption or decryption will be done externally via a provided function.

View file

@ -1,11 +1,8 @@
// ReSharper disable UnusedMember.Local
using System;
using System;
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.FsSrv;
namespace LibHac.FsSystem;
@ -30,20 +27,6 @@ public struct NcaCryptoConfiguration
public const int KeyGenerationMax = 32;
public const int KeyAreaEncryptionKeyCount = KeyAreaEncryptionKeyIndexCount * KeyGenerationMax;
public Array2<Array256<byte>> Header1SignKeyModuli;
public Array3<byte> Header1SignKeyPublicExponent;
public Array3<Array16<byte>> KeyAreaEncryptionKeySources;
public Array16<byte> HeaderEncryptionKeySource;
public Array2<Array16<byte>> HeaderEncryptedEncryptionKeys;
public GenerateKeyFunction GenerateKey;
public CryptAesXtsFunction EncryptAesXtsForExternalKey;
public CryptAesXtsFunction DecryptAesXtsForExternalKey;
public DecryptAesCtrFunction DecryptAesCtr;
public DecryptAesCtrFunction DecryptAesCtrForExternalKey;
public VerifySign1Function VerifySign1;
public bool IsDev;
public bool IsAvailableSwKey;
}
public struct NcaCompressionConfiguration
@ -88,12 +71,12 @@ public enum KeyType
file static class Anonymous
{
public static long GetFsOffset(NcaReader17 reader, int index)
public static long GetFsOffset(NcaReader reader, int index)
{
return (long)reader.GetFsOffset(index);
}
public static long GetFsEndOffset(NcaReader17 reader, int index)
public static long GetFsEndOffset(NcaReader reader, int index)
{
return (long)reader.GetFsEndOffset(index);
}
@ -102,12 +85,12 @@ file static class Anonymous
file class SharedNcaBodyStorage : IStorage
{
private SharedRef<IStorage> _storage;
private SharedRef<NcaReader17> _ncaReader;
private SharedRef<NcaReader> _ncaReader;
public SharedNcaBodyStorage(in SharedRef<IStorage> baseStorage, in SharedRef<NcaReader17> ncaReader)
public SharedNcaBodyStorage(in SharedRef<IStorage> baseStorage, in SharedRef<NcaReader> ncaReader)
{
_storage = SharedRef<IStorage>.CreateCopy(in baseStorage);
_ncaReader = SharedRef<NcaReader17>.CreateCopy(in ncaReader);
_ncaReader = SharedRef<NcaReader>.CreateCopy(in ncaReader);
}
public override void Dispose()
@ -202,14 +185,14 @@ public class NcaFileSystemDriver : IDisposable
None = 1
}
public NcaFileSystemDriver(ref readonly SharedRef<NcaReader17> ncaReader, MemoryResource allocator,
public NcaFileSystemDriver(ref readonly SharedRef<NcaReader> ncaReader, MemoryResource allocator,
IBufferManager bufferManager, IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
{
throw new NotImplementedException();
}
public NcaFileSystemDriver(ref readonly SharedRef<NcaReader17> originalNcaReader,
ref readonly SharedRef<NcaReader17> currentNcaReader, MemoryResource allocator, IBufferManager bufferManager,
public NcaFileSystemDriver(ref readonly SharedRef<NcaReader> originalNcaReader,
ref readonly SharedRef<NcaReader> currentNcaReader, MemoryResource allocator, IBufferManager bufferManager,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
{
throw new NotImplementedException();
@ -221,20 +204,20 @@ public class NcaFileSystemDriver : IDisposable
}
public Result OpenStorage(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader17 outHeaderReader,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, out NcaFsHeaderReader outHeaderReader,
int fsIndex)
{
throw new NotImplementedException();
}
private Result OpenStorageImpl(ref SharedRef<IStorage> outStorage, out NcaFsHeaderReader17 outHeaderReader,
private Result OpenStorageImpl(ref SharedRef<IStorage> outStorage, out NcaFsHeaderReader outHeaderReader,
int fsIndex, ref StorageContext storageContext)
{
throw new NotImplementedException();
}
private Result OpenIndirectableStorageAsOriginal(ref SharedRef<IStorage> outStorage,
in NcaFsHeaderReader17 headerReader, ref StorageContext storageContext)
in NcaFsHeaderReader headerReader, ref StorageContext storageContext)
{
throw new NotImplementedException();
}
@ -375,7 +358,7 @@ public class NcaFileSystemDriver : IDisposable
throw new NotImplementedException();
}
private Result CreateRegionSwitchStorage(ref SharedRef<IStorage> outStorage, in NcaFsHeaderReader17 headerReader,
private Result CreateRegionSwitchStorage(ref SharedRef<IStorage> outStorage, in NcaFsHeaderReader headerReader,
ref readonly SharedRef<IStorage> insideRegionStorage, ref readonly SharedRef<IStorage> outsideRegionStorage)
{
throw new NotImplementedException();

View file

@ -1,201 +1,53 @@
using System;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Spl;
namespace LibHac.FsSystem;
public delegate void GenerateKeyFunction(Span<byte> destKey, ReadOnlySpan<byte> sourceKey, int keyType);
public delegate Result DecryptAesCtrFunction(Span<byte> dest, int keyIndex, int keyGeneration, ReadOnlySpan<byte> encryptedKey, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
public delegate Result CryptAesXtsFunction(Span<byte> dest, ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
public delegate bool VerifySign1Function(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> data, bool isProd, byte generation);
public delegate Result NcaReaderInitializer(ref SharedRef<NcaReader> outReader, in SharedRef<IStorage> baseStorage,
in NcaCompressionConfiguration compressionConfig, IHash256GeneratorFactorySelector hashGeneratorFactorySelector,
ContentAttributes contentAttributes);
/// <summary>
/// Handles reading information from an NCA file's header.
/// Handles reading information from an NCA's header.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class NcaReader : IDisposable
{
private const uint SdkAddonVersionMin = 0xB0000;
private NcaHeader _header;
private Array5<Array16<byte>> _decryptionKeys;
private RuntimeNcaHeader _header;
private SharedRef<IStorage> _bodyStorage;
private UniqueRef<IStorage> _headerStorage;
private Array16<byte> _externalDataDecryptionKey;
private DecryptAesCtrFunction _decryptAesCtr;
private DecryptAesCtrFunction _decryptAesCtrForExternalKey;
private bool _isSoftwareAesPrioritized;
private bool _isAvailableSwKey;
private NcaHeader.EncryptionType _headerEncryptionType;
private SharedRef<IStorage> _headerStorage;
private SharedRef<IAesCtrDecryptor> _aesCtrDecryptor;
private GetDecompressorFunction _getDecompressorFunc;
private IHash256GeneratorFactorySelector _hashGeneratorFactorySelector;
public void Dispose()
{
_headerStorage.Destroy();
_bodyStorage.Destroy();
}
public Result Initialize(ref SharedRef<IStorage> baseStorage, in NcaCryptoConfiguration cryptoConfig,
public NcaReader(in RuntimeNcaHeader runtimeNcaHeader, ref readonly SharedRef<IStorage> notVerifiedHeaderStorage,
ref readonly SharedRef<IStorage> bodyStorage, ref readonly SharedRef<IAesCtrDecryptor> aesCtrDecryptor,
in NcaCompressionConfiguration compressionConfig, IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
{
Assert.SdkRequiresNotNull(in baseStorage);
Assert.SdkRequiresNotNull(in notVerifiedHeaderStorage);
Assert.SdkRequiresNotNull(in bodyStorage);
Assert.SdkRequiresNotNull(hashGeneratorFactorySelector);
Assert.SdkRequiresNull(in _bodyStorage);
if (cryptoConfig.VerifySign1 is null)
return ResultFs.InvalidArgument.Log();
_header = runtimeNcaHeader;
using var headerStorage = new UniqueRef<IStorage>();
_headerStorage = SharedRef<IStorage>.CreateCopy(in notVerifiedHeaderStorage);
_bodyStorage = SharedRef<IStorage>.CreateCopy(in bodyStorage);
_aesCtrDecryptor = SharedRef<IAesCtrDecryptor>.CreateCopy(in aesCtrDecryptor);
if (cryptoConfig.IsAvailableSwKey)
{
if (cryptoConfig.GenerateKey is null)
return ResultFs.InvalidArgument.Log();
ReadOnlySpan<int> headerKeyTypes = stackalloc int[NcaCryptoConfiguration.HeaderEncryptionKeyCount]
{ (int)KeyType.NcaHeaderKey1, (int)KeyType.NcaHeaderKey2 };
// Generate the keys for decrypting the NCA header.
Unsafe.SkipInit(out Array2<Array16<byte>> commonDecryptionKeys);
for (int i = 0; i < NcaCryptoConfiguration.HeaderEncryptionKeyCount; i++)
{
cryptoConfig.GenerateKey(commonDecryptionKeys[i], cryptoConfig.HeaderEncryptedEncryptionKeys[i],
headerKeyTypes[i]);
}
// Create an XTS storage to read the encrypted header.
Array16<byte> headerIv = default;
headerStorage.Reset(new AesXtsStorage(baseStorage.Get, commonDecryptionKeys[0], commonDecryptionKeys[1],
headerIv, NcaHeader.XtsBlockSize));
}
else
{
// Software key isn't available, so we need to be able to decrypt externally.
if (cryptoConfig.DecryptAesXtsForExternalKey is null)
return ResultFs.InvalidArgument.Log();
// Create the header storage.
Array16<byte> headerIv = default;
headerStorage.Reset(new AesXtsStorageExternal(baseStorage.Get, ReadOnlySpan<byte>.Empty,
ReadOnlySpan<byte>.Empty, headerIv, (uint)NcaHeader.XtsBlockSize, cryptoConfig.EncryptAesXtsForExternalKey,
cryptoConfig.DecryptAesXtsForExternalKey));
}
if (!headerStorage.HasValue)
return ResultFs.AllocationMemoryFailedInNcaReaderA.Log();
// Read the decrypted header.
Result res = headerStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header));
if (res.IsFailure()) return res.Miss();
// Check if the NCA magic value is correct.
Result signatureResult = CheckSignature(in _header);
if (signatureResult.IsFailure())
{
// If the magic value is not correct the header might not be encrypted.
if (cryptoConfig.IsDev)
{
// Read the header without decrypting it and check the magic value again.
res = baseStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header));
if (res.IsFailure()) return res.Miss();
res = CheckSignature(in _header);
if (res.IsFailure())
return signatureResult.Miss();
// We have a plaintext header. Get an IStorage of just the header.
res = baseStorage.Get.GetSize(out long baseStorageSize);
if (res.IsFailure()) return res.Miss();
headerStorage.Reset(new SubStorage(in baseStorage, 0, baseStorageSize));
if (!headerStorage.HasValue)
return ResultFs.AllocationMemoryFailedInNcaReaderA.Log();
_headerEncryptionType = NcaHeader.EncryptionType.None;
}
else
{
return signatureResult.Miss();
}
}
// Validate the fixed key signature.
if (_header.Header1SignatureKeyGeneration > NcaCryptoConfiguration.Header1SignatureKeyGenerationMax)
return ResultFs.InvalidNcaHeader1SignatureKeyGeneration.Log();
int signMessageOffset = NcaHeader.HeaderSignSize * NcaHeader.HeaderSignCount;
int signMessageSize = NcaHeader.Size - signMessageOffset;
ReadOnlySpan<byte> signature = _header.Signature1;
ReadOnlySpan<byte> message = SpanHelpers.AsReadOnlyByteSpan(in _header).Slice(signMessageOffset, signMessageSize);
if (!cryptoConfig.VerifySign1(signature, message, !cryptoConfig.IsDev, _header.Header1SignatureKeyGeneration))
return ResultFs.NcaHeaderSignature1VerificationFailed.Log();
// Validate the sdk version.
if (_header.SdkAddonVersion < SdkAddonVersionMin)
return ResultFs.UnsupportedSdkVersion.Log();
// Validate the key index.
if (_header.KeyAreaEncryptionKeyIndex >= NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount &&
_header.KeyAreaEncryptionKeyIndex != NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexZeroKey)
{
return ResultFs.InvalidNcaKeyIndex.Log();
}
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
// Get keys from the key area if the NCA doesn't have a rights ID.
Array16<byte> zeroRightsId = default;
if (CryptoUtil.IsSameBytes(zeroRightsId, _header.RightsId, NcaHeader.RightsIdSize))
{
// If we don't have a rights ID we need to generate decryption keys if software keys are available.
if (cryptoConfig.IsAvailableSwKey)
{
int keyTypeValue = NcaKeyFunctions.GetKeyTypeValue(_header.KeyAreaEncryptionKeyIndex, _header.GetProperKeyGeneration());
ReadOnlySpan<byte> encryptedKeyCtr = _header.EncryptedKeys[..].Slice((int)NcaHeader.DecryptionKey.AesCtr * Aes.KeySize128, Aes.KeySize128);
cryptoConfig.GenerateKey(_decryptionKeys[(int)NcaHeader.DecryptionKey.AesCtr], encryptedKeyCtr, keyTypeValue);
}
// Copy the plaintext hardware key.
ReadOnlySpan<byte> keyCtrHw = _header.EncryptedKeys[..].Slice((int)NcaHeader.DecryptionKey.AesCtrHw * Aes.KeySize128, Aes.KeySize128);
keyCtrHw.CopyTo(_decryptionKeys[(int)NcaHeader.DecryptionKey.AesCtrHw]);
}
// Clear the external decryption key.
_externalDataDecryptionKey[..].Clear();
// Copy the configuration to the NcaReader.
_isAvailableSwKey = cryptoConfig.IsAvailableSwKey;
_decryptAesCtr = cryptoConfig.DecryptAesCtr;
_decryptAesCtrForExternalKey = cryptoConfig.DecryptAesCtrForExternalKey;
_getDecompressorFunc = compressionConfig.GetDecompressorFunc;
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
}
_bodyStorage.SetByMove(ref baseStorage);
_headerStorage.Set(ref headerStorage.Ref);
return Result.Success;
static Result CheckSignature(in NcaHeader header)
{
if (header.Magic == NcaHeader.Magic0 ||
header.Magic == NcaHeader.Magic1 ||
header.Magic == NcaHeader.Magic2)
{
return ResultFs.UnsupportedSdkVersion.Log();
}
if (header.Magic != NcaHeader.CurrentMagic)
return ResultFs.InvalidNcaSignature.Log();
return Result.Success;
}
public void Dispose()
{
_bodyStorage.Destroy();
_headerStorage.Destroy();
_aesCtrDecryptor.Destroy();
}
public Result ReadHeader(out NcaFsHeader outHeader, int index)
@ -204,29 +56,23 @@ public class NcaReader : IDisposable
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
long offset = Unsafe.SizeOf<NcaHeader>() + Unsafe.SizeOf<NcaFsHeader>() * (long)index;
return _headerStorage.Get.Read(offset, SpanHelpers.AsByteSpan(ref outHeader));
long offset = _header.FsHeadersOffset + Unsafe.SizeOf<NcaFsHeader>() * (long)index;
return _headerStorage.Get.Read(offset, SpanHelpers.AsByteSpan(ref outHeader)).Ret();
}
public void GetHeaderSign2(Span<byte> outBuffer)
public Result GetHeaderSign2(Span<byte> outBuffer)
{
Assert.SdkRequiresEqual(NcaHeader.HeaderSignSize, outBuffer.Length);
Assert.SdkRequiresGreaterEqual((uint)outBuffer.Length, _header.Header2SignInfo.Size);
_header.Signature2[..].CopyTo(outBuffer);
return _headerStorage.Get
.Read(_header.Header2SignInfo.Size, outBuffer.Slice(0, (int)_header.Header2SignInfo.Size)).Ret();
}
public void GetHeaderSign2TargetHash(Span<byte> outBuffer)
{
Assert.SdkRequiresNotNull(_hashGeneratorFactorySelector);
Assert.SdkRequiresEqual(IHash256Generator.HashSize, outBuffer.Length);
Assert.SdkRequiresEqual(outBuffer.Length, Unsafe.SizeOf<Hash>());
int signTargetOffset = NcaHeader.HeaderSignSize * NcaHeader.HeaderSignCount;
int signTargetSize = NcaHeader.Size - signTargetOffset;
ReadOnlySpan<byte> signTarget =
SpanHelpers.AsReadOnlyByteSpan(in _header).Slice(signTargetOffset, signTargetSize);
IHash256GeneratorFactory factory = _hashGeneratorFactorySelector.GetFactory(HashAlgorithmType.Sha2);
factory.GenerateHash(outBuffer, signTarget);
_header.Header2SignInfo.Hash.Value[..].CopyTo(outBuffer);
}
public SharedRef<IStorage> GetSharedBodyStorage()
@ -236,60 +82,26 @@ public class NcaReader : IDisposable
return SharedRef<IStorage>.CreateCopy(in _bodyStorage);
}
public uint GetSignature()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.Magic;
}
public NcaHeader.DistributionType GetDistributionType()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.DistributionTypeValue;
return _header.DistributionType;
}
public NcaHeader.ContentType GetContentType()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.ContentTypeValue;
return _header.ContentType;
}
public byte GetKeyGeneration()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.GetProperKeyGeneration();
}
public byte GetKeyIndex()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.KeyAreaEncryptionKeyIndex;
}
public ulong GetContentSize()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.ContentSize;
return _header.KeyGeneration;
}
public ulong GetProgramId()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.ProgramId;
}
public uint GetContentIndex()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.ContentIndex;
}
public uint GetSdkAddonVersion()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return _header.SdkAddonVersion;
}
public void GetRightsId(Span<byte> outBuffer)
{
Assert.SdkRequiresGreaterEqual(outBuffer.Length, NcaHeader.RightsIdSize);
@ -300,57 +112,30 @@ public class NcaReader : IDisposable
public bool HasFsInfo(int index)
{
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return _header.FsInfos[index].StartSector != 0 || _header.FsInfos[index].EndSector != 0;
}
public int GetFsCount()
{
Assert.SdkRequiresNotNull(_bodyStorage);
for (int i = 0; i < NcaHeader.FsCountMax; i++)
{
if (!HasFsInfo(i))
{
return i;
}
}
return NcaHeader.FsCountMax;
}
public NcaHeader.EncryptionType GetEncryptionType()
{
return _headerEncryptionType;
}
public ref readonly Hash GetFsHeaderHash(int index)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return ref _header.FsHeaderHashes[index];
}
public void GetFsHeaderHash(out Hash outHash, int index)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
outHash = _header.FsHeaderHashes[index];
outHash = _header.FsInfos[index].Hash;
}
public void GetFsInfo(out NcaHeader.FsInfo outFsInfo, int index)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
outFsInfo = _header.FsInfos[index];
outFsInfo = new NcaHeader.FsInfo
{
StartSector = _header.FsInfos[index].StartSector,
EndSector = _header.FsInfos[index].EndSector,
HashSectors = _header.FsInfos[index].HashSectors,
Reserved = 0
};
}
public ulong GetFsOffset(int index)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return NcaHeader.SectorToByte(_header.FsInfos[index].StartSector);
@ -358,7 +143,6 @@ public class NcaReader : IDisposable
public ulong GetFsEndOffset(int index)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return NcaHeader.SectorToByte(_header.FsInfos[index].EndSector);
@ -366,102 +150,35 @@ public class NcaReader : IDisposable
public ulong GetFsSize(int index)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return NcaHeader.SectorToByte(_header.FsInfos[index].EndSector - _header.FsInfos[index].StartSector);
}
public void GetEncryptedKey(Span<byte> outBuffer)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresGreaterEqual(outBuffer.Length, NcaHeader.EncryptedKeyAreaSize);
_header.EncryptedKeys[..].CopyTo(outBuffer);
}
public ReadOnlySpan<byte> GetDecryptionKey(int index)
{
Assert.SdkRequiresNotNull(_bodyStorage);
Assert.SdkRequiresInRange(index, 0, (int)NcaHeader.DecryptionKey.Count);
return _decryptionKeys[index];
}
public bool HasValidInternalKey()
{
Array16<byte> zeroKey = default;
for (int i = 0; i < (int)NcaHeader.DecryptionKey.Count; i++)
{
if (!CryptoUtil.IsSameBytes(zeroKey,
_header.EncryptedKeys[..].Slice(i * Aes.KeySize128, Aes.KeySize128), Aes.KeySize128))
{
return true;
}
}
return false;
}
public bool HasInternalDecryptionKeyForAesHw()
{
Array16<byte> zeroKey = default;
return !CryptoUtil.IsSameBytes(zeroKey, GetDecryptionKey((int)NcaHeader.DecryptionKey.AesCtrHw),
zeroKey.Length);
}
public bool IsSwAesPrioritized()
{
return _isSoftwareAesPrioritized;
}
public void PrioritizeSwAes()
{
_isSoftwareAesPrioritized = true;
if (_aesCtrDecryptor.HasValue)
{
_aesCtrDecryptor.Get.PrioritizeSw();
}
}
public bool IsAvailableSwKey()
public void SetExternalDecryptionKey(in AccessKey keySource)
{
return _isAvailableSwKey;
if (_aesCtrDecryptor.HasValue)
{
_aesCtrDecryptor.Get.SetExternalKeySource(in keySource);
}
}
public void SetExternalDecryptionKey(ReadOnlySpan<byte> key)
public RuntimeNcaHeader GetHeader()
{
Assert.SdkRequiresEqual(_externalDataDecryptionKey[..].Length, key.Length);
key.CopyTo(_externalDataDecryptionKey);
return _header;
}
public ReadOnlySpan<byte> GetExternalDecryptionKey()
public SharedRef<IAesCtrDecryptor> GetDecryptor()
{
return _externalDataDecryptionKey;
}
public bool HasExternalDecryptionKey()
{
Array16<byte> zeroKey = default;
return !CryptoUtil.IsSameBytes(zeroKey, GetExternalDecryptionKey(), zeroKey.Length);
}
public void GetRawData(Span<byte> outBuffer)
{
Assert.SdkRequires(_bodyStorage.HasValue);
Assert.SdkRequiresLessEqual(Unsafe.SizeOf<NcaHeader>(), outBuffer.Length);
SpanHelpers.AsReadOnlyByteSpan(in _header).CopyTo(outBuffer);
}
public DecryptAesCtrFunction GetExternalDecryptAesCtrFunction()
{
Assert.SdkRequiresNotNull(_decryptAesCtr);
return _decryptAesCtr;
}
public DecryptAesCtrFunction GetExternalDecryptAesCtrFunctionForExternalKey()
{
Assert.SdkRequiresNotNull(_decryptAesCtrForExternalKey);
return _decryptAesCtrForExternalKey;
return SharedRef<IAesCtrDecryptor>.CreateCopy(in _aesCtrDecryptor);
}
public GetDecompressorFunction GetDecompressor()
@ -475,12 +192,44 @@ public class NcaReader : IDisposable
Assert.SdkRequiresNotNull(_hashGeneratorFactorySelector);
return _hashGeneratorFactorySelector;
}
public Result Verify()
{
Assert.SdkRequiresNotNull(_bodyStorage);
for (int fsIndex = 0; fsIndex < NcaHeader.FsCountMax; fsIndex++)
{
var reader = new NcaFsHeaderReader();
if (HasFsInfo(fsIndex))
{
Result res = reader.Initialize(this, fsIndex);
if (res.IsFailure()) return res.Miss();
res = reader.Verify(_header.ContentType);
if (res.IsFailure()) return res.Miss();
}
else
{
Result res = ReadHeader(out NcaFsHeader header, fsIndex);
if (res.IsFailure()) return res.Miss();
NcaFsHeader zero = default;
if (!CryptoUtil.IsSameBytes(SpanHelpers.AsReadOnlyByteSpan(in header),
SpanHelpers.AsReadOnlyByteSpan(in zero), Unsafe.SizeOf<NcaFsHeader>()))
{
return ResultFs.InvalidNcaFsHeader.Log();
}
}
}
return Result.Success;
}
}
/// <summary>
/// Handles reading information from the <see cref="NcaFsHeader"/> of a file system inside an NCA file.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class NcaFsHeaderReader
{
private NcaFsHeader _header;
@ -507,7 +256,9 @@ public class NcaFsHeaderReader
IHash256GeneratorFactory generator = reader.GetHashGeneratorFactorySelector().GetFactory(HashAlgorithmType.Sha2);
generator.GenerateHash(hash.Value, SpanHelpers.AsReadOnlyByteSpan(in _header));
if (!CryptoUtil.IsSameBytes(reader.GetFsHeaderHash(index).Value, hash.Value, Unsafe.SizeOf<Hash>()))
reader.GetFsHeaderHash(out Hash fsHeaderHash, index);
if (!CryptoUtil.IsSameBytes(fsHeaderHash.Value, hash.Value, Unsafe.SizeOf<Hash>()))
{
return ResultFs.NcaFsHeaderHashVerificationFailed.Log();
}
@ -647,4 +398,61 @@ public class NcaFsHeaderReader
SpanHelpers.AsReadOnlyByteSpan(in _header).CopyTo(outBuffer);
}
public Result Verify(NcaHeader.ContentType contentType)
{
Assert.SdkRequires(IsInitialized());
Assert.SdkRequiresWithinMinMax((int)contentType, (int)NcaHeader.ContentType.Program, (int)NcaHeader.ContentType.PublicData);
Result res = _header.Verify();
if (res.IsFailure()) return res.Miss();
const uint programSecureValue = 1;
const uint dataSecureValue = 2;
const uint htmlDocumentSecureValue = 4;
const uint legalInformationSecureValue = 5;
// Mask out the program index part of the secure value
uint secureValue = _header.AesCtrUpperIv.SecureValue & 0xFFFFFF;
if (GetEncryptionType() == NcaFsHeader.EncryptionType.None)
{
if (secureValue != 0)
return ResultFs.InvalidNcaFsHeader.Log();
return Result.Success;
}
switch (contentType)
{
case NcaHeader.ContentType.Program:
switch (_fsIndex)
{
case 0:
if (secureValue != programSecureValue)
return ResultFs.InvalidNcaFsHeader.Log();
break;
case 1:
if (secureValue != dataSecureValue)
return ResultFs.InvalidNcaFsHeader.Log();
break;
default:
if (secureValue != 0)
return ResultFs.InvalidNcaFsHeader.Log();
break;
}
break;
case NcaHeader.ContentType.Manual:
if (secureValue != htmlDocumentSecureValue && secureValue != legalInformationSecureValue)
return ResultFs.InvalidNcaFsHeader.Log();
break;
default:
if (secureValue != 0)
return ResultFs.InvalidNcaFsHeader.Log();
break;
}
return Result.Success;
}
}

View file

@ -1,458 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Spl;
namespace LibHac.FsSystem;
public delegate Result NcaReaderInitializer(ref SharedRef<NcaReader17> outReader, in SharedRef<IStorage> baseStorage,
in NcaCompressionConfiguration compressionConfig, IHash256GeneratorFactorySelector hashGeneratorFactorySelector,
ContentAttributes contentAttributes);
/// <summary>
/// Handles reading information from an NCA's header.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class NcaReader17 : IDisposable
{
private RuntimeNcaHeader _header;
private SharedRef<IStorage> _bodyStorage;
private SharedRef<IStorage> _headerStorage;
private SharedRef<IAesCtrDecryptor> _aesCtrDecryptor;
private GetDecompressorFunction _getDecompressorFunc;
private IHash256GeneratorFactorySelector _hashGeneratorFactorySelector;
public NcaReader17(in RuntimeNcaHeader runtimeNcaHeader, ref readonly SharedRef<IStorage> notVerifiedHeaderStorage,
ref readonly SharedRef<IStorage> bodyStorage, ref readonly SharedRef<IAesCtrDecryptor> aesCtrDecryptor,
in NcaCompressionConfiguration compressionConfig, IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
{
Assert.SdkRequiresNotNull(in notVerifiedHeaderStorage);
Assert.SdkRequiresNotNull(in bodyStorage);
Assert.SdkRequiresNotNull(hashGeneratorFactorySelector);
_header = runtimeNcaHeader;
_headerStorage = SharedRef<IStorage>.CreateCopy(in notVerifiedHeaderStorage);
_bodyStorage = SharedRef<IStorage>.CreateCopy(in bodyStorage);
_aesCtrDecryptor = SharedRef<IAesCtrDecryptor>.CreateCopy(in aesCtrDecryptor);
_getDecompressorFunc = compressionConfig.GetDecompressorFunc;
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
}
public void Dispose()
{
_bodyStorage.Destroy();
_headerStorage.Destroy();
_aesCtrDecryptor.Destroy();
}
public Result ReadHeader(out NcaFsHeader outHeader, int index)
{
UnsafeHelpers.SkipParamInit(out outHeader);
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
long offset = _header.FsHeadersOffset + Unsafe.SizeOf<NcaFsHeader>() * (long)index;
return _headerStorage.Get.Read(offset, SpanHelpers.AsByteSpan(ref outHeader)).Ret();
}
public Result GetHeaderSign2(Span<byte> outBuffer)
{
Assert.SdkRequiresGreaterEqual((uint)outBuffer.Length, _header.Header2SignInfo.Size);
return _headerStorage.Get
.Read(_header.Header2SignInfo.Size, outBuffer.Slice(0, (int)_header.Header2SignInfo.Size)).Ret();
}
public void GetHeaderSign2TargetHash(Span<byte> outBuffer)
{
Assert.SdkRequiresEqual(outBuffer.Length, Unsafe.SizeOf<Hash>());
_header.Header2SignInfo.Hash.Value[..].CopyTo(outBuffer);
}
public SharedRef<IStorage> GetSharedBodyStorage()
{
Assert.SdkRequiresNotNull(_bodyStorage);
return SharedRef<IStorage>.CreateCopy(in _bodyStorage);
}
public NcaHeader.DistributionType GetDistributionType()
{
return _header.DistributionType;
}
public NcaHeader.ContentType GetContentType()
{
return _header.ContentType;
}
public byte GetKeyGeneration()
{
return _header.KeyGeneration;
}
public ulong GetProgramId()
{
return _header.ProgramId;
}
public void GetRightsId(Span<byte> outBuffer)
{
Assert.SdkRequiresGreaterEqual(outBuffer.Length, NcaHeader.RightsIdSize);
_header.RightsId[..].CopyTo(outBuffer);
}
public bool HasFsInfo(int index)
{
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return _header.FsInfos[index].StartSector != 0 || _header.FsInfos[index].EndSector != 0;
}
public void GetFsHeaderHash(out Hash outHash, int index)
{
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
outHash = _header.FsInfos[index].Hash;
}
public void GetFsInfo(out NcaHeader.FsInfo outFsInfo, int index)
{
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
outFsInfo = new NcaHeader.FsInfo
{
StartSector = _header.FsInfos[index].StartSector,
EndSector = _header.FsInfos[index].EndSector,
HashSectors = _header.FsInfos[index].HashSectors,
Reserved = 0
};
}
public ulong GetFsOffset(int index)
{
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return NcaHeader.SectorToByte(_header.FsInfos[index].StartSector);
}
public ulong GetFsEndOffset(int index)
{
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return NcaHeader.SectorToByte(_header.FsInfos[index].EndSector);
}
public ulong GetFsSize(int index)
{
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
return NcaHeader.SectorToByte(_header.FsInfos[index].EndSector - _header.FsInfos[index].StartSector);
}
public void PrioritizeSwAes()
{
if (_aesCtrDecryptor.HasValue)
{
_aesCtrDecryptor.Get.PrioritizeSw();
}
}
public void SetExternalDecryptionKey(in AccessKey keySource)
{
if (_aesCtrDecryptor.HasValue)
{
_aesCtrDecryptor.Get.SetExternalKeySource(in keySource);
}
}
public RuntimeNcaHeader GetHeader()
{
return _header;
}
public SharedRef<IAesCtrDecryptor> GetDecryptor()
{
return SharedRef<IAesCtrDecryptor>.CreateCopy(in _aesCtrDecryptor);
}
public GetDecompressorFunction GetDecompressor()
{
Assert.SdkRequiresNotNull(_getDecompressorFunc);
return _getDecompressorFunc;
}
public IHash256GeneratorFactorySelector GetHashGeneratorFactorySelector()
{
Assert.SdkRequiresNotNull(_hashGeneratorFactorySelector);
return _hashGeneratorFactorySelector;
}
public Result Verify()
{
Assert.SdkRequiresNotNull(_bodyStorage);
for (int fsIndex = 0; fsIndex < NcaHeader.FsCountMax; fsIndex++)
{
var reader = new NcaFsHeaderReader17();
if (HasFsInfo(fsIndex))
{
Result res = reader.Initialize(this, fsIndex);
if (res.IsFailure()) return res.Miss();
res = reader.Verify(_header.ContentType);
if (res.IsFailure()) return res.Miss();
}
else
{
Result res = ReadHeader(out NcaFsHeader header, fsIndex);
if (res.IsFailure()) return res.Miss();
NcaFsHeader zero = default;
if (!CryptoUtil.IsSameBytes(SpanHelpers.AsReadOnlyByteSpan(in header),
SpanHelpers.AsReadOnlyByteSpan(in zero), Unsafe.SizeOf<NcaFsHeader>()))
{
return ResultFs.InvalidNcaFsHeader.Log();
}
}
}
return Result.Success;
}
}
/// <summary>
/// Handles reading information from the <see cref="NcaFsHeader"/> of a file system inside an NCA file.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class NcaFsHeaderReader17
{
private NcaFsHeader _header;
private int _fsIndex;
public NcaFsHeaderReader17()
{
_fsIndex = -1;
}
public bool IsInitialized()
{
return _fsIndex >= 0;
}
public Result Initialize(NcaReader17 reader, int index)
{
_fsIndex = -1;
Result res = reader.ReadHeader(out _header, index);
if (res.IsFailure()) return res.Miss();
Unsafe.SkipInit(out Hash hash);
IHash256GeneratorFactory generator = reader.GetHashGeneratorFactorySelector().GetFactory(HashAlgorithmType.Sha2);
generator.GenerateHash(hash.Value, SpanHelpers.AsReadOnlyByteSpan(in _header));
reader.GetFsHeaderHash(out Hash fsHeaderHash, index);
if (!CryptoUtil.IsSameBytes(fsHeaderHash.Value, hash.Value, Unsafe.SizeOf<Hash>()))
{
return ResultFs.NcaFsHeaderHashVerificationFailed.Log();
}
_fsIndex = index;
return Result.Success;
}
public ref readonly NcaFsHeader.HashData GetHashData()
{
Assert.SdkRequires(IsInitialized());
return ref _header.HashDataValue;
}
public ushort GetVersion()
{
Assert.SdkRequires(IsInitialized());
return _header.Version;
}
public int GetFsIndex()
{
Assert.SdkRequires(IsInitialized());
return _fsIndex;
}
public NcaFsHeader.FsType GetFsType()
{
Assert.SdkRequires(IsInitialized());
return _header.FsTypeValue;
}
public NcaFsHeader.HashType GetHashType()
{
Assert.SdkRequires(IsInitialized());
return _header.HashTypeValue;
}
public NcaFsHeader.EncryptionType GetEncryptionType()
{
Assert.SdkRequires(IsInitialized());
return _header.EncryptionTypeValue;
}
public NcaFsHeader.MetaDataHashType GetPatchMetaHashType()
{
Assert.SdkRequires(IsInitialized());
return _header.MetaDataHashTypeValue;
}
public NcaFsHeader.MetaDataHashType GetSparseMetaHashType()
{
Assert.SdkRequires(IsInitialized());
return _header.MetaDataHashTypeValue;
}
public Result GetHashTargetOffset(out long outOffset)
{
Assert.SdkRequires(IsInitialized());
Result res = _header.GetHashTargetOffset(out outOffset);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
public bool IsSkipLayerHashEncryption()
{
Assert.SdkRequires(IsInitialized());
return _header.IsSkipLayerHashEncryption();
}
public ref readonly NcaPatchInfo GetPatchInfo()
{
Assert.SdkRequires(IsInitialized());
return ref _header.PatchInfo;
}
public NcaAesCtrUpperIv GetAesCtrUpperIv()
{
Assert.SdkRequires(IsInitialized());
return _header.AesCtrUpperIv;
}
public bool ExistsSparseLayer()
{
Assert.SdkRequires(IsInitialized());
return _header.SparseInfo.Generation != 0;
}
public ref readonly NcaSparseInfo GetSparseInfo()
{
Assert.SdkRequires(IsInitialized());
return ref _header.SparseInfo;
}
public bool ExistsCompressionLayer()
{
Assert.SdkRequires(IsInitialized());
return _header.CompressionInfo.TableOffset != 0 && _header.CompressionInfo.TableSize != 0;
}
public ref readonly NcaCompressionInfo GetCompressionInfo()
{
Assert.SdkRequires(IsInitialized());
return ref _header.CompressionInfo;
}
public bool ExistsPatchMetaHashLayer()
{
Assert.SdkRequires(IsInitialized());
return _header.MetaDataHashDataInfo.Size != 0 && GetPatchInfo().HasIndirectTable();
}
public bool ExistsSparseMetaHashLayer()
{
Assert.SdkRequires(IsInitialized());
return _header.MetaDataHashDataInfo.Size != 0 && ExistsSparseLayer();
}
public ref readonly NcaMetaDataHashDataInfo GetPatchMetaDataHashDataInfo()
{
Assert.SdkRequires(IsInitialized());
return ref _header.MetaDataHashDataInfo;
}
public ref readonly NcaMetaDataHashDataInfo GetSparseMetaDataHashDataInfo()
{
Assert.SdkRequires(IsInitialized());
return ref _header.MetaDataHashDataInfo;
}
public void GetRawData(Span<byte> outBuffer)
{
Assert.SdkRequires(IsInitialized());
Assert.SdkRequiresLessEqual(Unsafe.SizeOf<NcaFsHeader>(), outBuffer.Length);
SpanHelpers.AsReadOnlyByteSpan(in _header).CopyTo(outBuffer);
}
public Result Verify(NcaHeader.ContentType contentType)
{
Assert.SdkRequires(IsInitialized());
Assert.SdkRequiresWithinMinMax((int)contentType, (int)NcaHeader.ContentType.Program, (int)NcaHeader.ContentType.PublicData);
Result res = _header.Verify();
if (res.IsFailure()) return res.Miss();
const uint programSecureValue = 1;
const uint dataSecureValue = 2;
const uint htmlDocumentSecureValue = 4;
const uint legalInformationSecureValue = 5;
// Mask out the program index part of the secure value
uint secureValue = _header.AesCtrUpperIv.SecureValue & 0xFFFFFF;
if (GetEncryptionType() == NcaFsHeader.EncryptionType.None)
{
if (secureValue != 0)
return ResultFs.InvalidNcaFsHeader.Log();
return Result.Success;
}
switch (contentType)
{
case NcaHeader.ContentType.Program:
switch (_fsIndex)
{
case 0:
if (secureValue != programSecureValue)
return ResultFs.InvalidNcaFsHeader.Log();
break;
case 1:
if (secureValue != dataSecureValue)
return ResultFs.InvalidNcaFsHeader.Log();
break;
default:
if (secureValue != 0)
return ResultFs.InvalidNcaFsHeader.Log();
break;
}
break;
case NcaHeader.ContentType.Manual:
if (secureValue != htmlDocumentSecureValue && secureValue != legalInformationSecureValue)
return ResultFs.InvalidNcaFsHeader.Log();
break;
default:
if (secureValue != 0)
return ResultFs.InvalidNcaFsHeader.Log();
break;
}
return Result.Success;
}
}

View file

@ -253,21 +253,6 @@ public class TypeLayoutTests
Assert.Equal(0xC, GetOffset(in s, in s.Reserved));
}
[Fact]
public static void KeyType_Layout()
{
NcaCryptoConfiguration s = default;
Assert.Equal(NcaCryptoConfiguration.Header1SignatureKeyGenerationMax + 1, s.Header1SignKeyModuli.Length);
Assert.Equal(NcaCryptoConfiguration.Rsa2048KeyModulusSize, s.Header1SignKeyModuli[0].Length);
Assert.Equal(NcaCryptoConfiguration.Rsa2048KeyPublicExponentSize, s.Header1SignKeyPublicExponent.Length);
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount, s.KeyAreaEncryptionKeySources.Length);
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.KeyAreaEncryptionKeySources[0].Length);
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.HeaderEncryptionKeySource.Length);
Assert.Equal(NcaCryptoConfiguration.HeaderEncryptionKeyCount, s.HeaderEncryptedEncryptionKeys.Length);
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.HeaderEncryptedEncryptionKeys[0].Length);
}
[Fact]
public static void AesCtrCounterExtendedStorage_Entry_Layout()
{