mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Remove the old NcaReader
This commit is contained in:
parent
e73e2861bc
commit
7f7c8b6578
10 changed files with 252 additions and 1086 deletions
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
@ -275,6 +273,7 @@ public class NcaFileSystemServiceImpl
|
|||
IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed);
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
}
|
||||
|
||||
outFileSystem.SetByMove(ref subDirFs.Ref);
|
||||
|
||||
return Result.Success;
|
||||
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
_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();
|
||||
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue