mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add NcaReader and some related NCA classes
This commit is contained in:
parent
cd2b8edfa8
commit
b9e2e0863b
17 changed files with 1184 additions and 54 deletions
31
src/LibHac/Common/FixedArrays/Array96.cs
Normal file
31
src/LibHac/Common/FixedArrays/Array96.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma warning disable CS0169, CS0649, IDE0051 // Field is never used, Field is never assigned to, Remove unused private members
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays;
|
||||
|
||||
public struct Array96<T>
|
||||
{
|
||||
public const int Length = 96;
|
||||
|
||||
private Array80<T> _0;
|
||||
private Array16<T> _80;
|
||||
|
||||
public ref T this[int i] => ref Items[i];
|
||||
|
||||
public Span<T> Items
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length);
|
||||
}
|
||||
|
||||
public readonly ReadOnlySpan<T> ItemsRo
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlySpan<T>(in Array96<T> value) => value.ItemsRo;
|
||||
}
|
|
@ -6,6 +6,9 @@ namespace LibHac.Crypto;
|
|||
|
||||
public static class Rsa
|
||||
{
|
||||
public static readonly int ModulusSize2048Pss = 256;
|
||||
public static readonly int MaximumExponentSize2048Pss = 3;
|
||||
|
||||
public static bool VerifyRsa2048PssSha256(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> modulus,
|
||||
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> message) =>
|
||||
VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pss);
|
||||
|
|
21
src/LibHac/Fs/Common.cs
Normal file
21
src/LibHac/Fs/Common.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Fs;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct Int64
|
||||
{
|
||||
private long _value;
|
||||
|
||||
public void Set(long value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public readonly long Get()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
public static implicit operator long(in Int64 value) => value.Get();
|
||||
}
|
|
@ -7,6 +7,7 @@ using LibHac.FsSystem;
|
|||
using LibHac.FsSystem.Impl;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader;
|
||||
|
||||
namespace LibHac.FsSrv.FsCreator;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ using LibHac.Os;
|
|||
using LibHac.Spl;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using LibHac.Util;
|
||||
using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader;
|
||||
using RightsId = LibHac.Fs.RightsId;
|
||||
using Utility = LibHac.FsSystem.Utility;
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace LibHac.FsSystem;
|
||||
using System;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public enum CompressionType : byte
|
||||
{
|
||||
|
@ -8,6 +10,9 @@ public enum CompressionType : byte
|
|||
Unknown = 4
|
||||
}
|
||||
|
||||
public delegate Result DecompressorFunction(Span<byte> destination, ReadOnlySpan<byte> source);
|
||||
public delegate DecompressorFunction GetDecompressorFunction(CompressionType compressionType);
|
||||
|
||||
public static class CompressionTypeUtility
|
||||
{
|
||||
public static bool IsBlockAlignmentRequired(CompressionType type)
|
||||
|
|
63
src/LibHac/FsSystem/NcaFileSystemDriver.cs
Normal file
63
src/LibHac/FsSystem/NcaFileSystemDriver.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Crypto;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public struct NcaCryptoConfiguration
|
||||
{
|
||||
public static readonly int Rsa2048KeyModulusSize = Rsa.ModulusSize2048Pss;
|
||||
public static readonly int Rsa2048KeyPublicExponentSize = Rsa.MaximumExponentSize2048Pss;
|
||||
public static readonly int Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize;
|
||||
|
||||
public static readonly int Aes128KeySize = Aes.KeySize128;
|
||||
|
||||
public static readonly int Header1SignatureKeyGenerationMax = 1;
|
||||
|
||||
public static readonly int KeyAreaEncryptionKeyIndexCount = 3;
|
||||
public static readonly int HeaderEncryptionKeyCount = 2;
|
||||
|
||||
public static readonly int KeyGenerationMax = 32;
|
||||
public static readonly 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 DecryptAesCtrFunction DecryptAesCtr;
|
||||
public DecryptAesCtrFunction DecryptAesCtrForExternalKey;
|
||||
public bool IsDev;
|
||||
}
|
||||
|
||||
public struct NcaCompressionConfiguration
|
||||
{
|
||||
public GetDecompressorFunction GetDecompressorFunc;
|
||||
}
|
||||
|
||||
public static class NcaKeyFunctions
|
||||
{
|
||||
public static bool IsInvalidKeyTypeValue(int keyType)
|
||||
{
|
||||
return keyType < 0;
|
||||
}
|
||||
|
||||
public static int GetKeyTypeValue(byte keyIndex, byte keyGeneration)
|
||||
{
|
||||
const int invalidKeyTypeValue = -1;
|
||||
|
||||
if (keyIndex >= NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount)
|
||||
return invalidKeyTypeValue;
|
||||
|
||||
return NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount * keyGeneration + keyIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public enum KeyType
|
||||
{
|
||||
NcaHeaderKey = 0x60,
|
||||
NcaExternalKey = 0x61,
|
||||
SaveDataDeviceUniqueMac = 0x62,
|
||||
SaveDataSeedUniqueMac = 0x63,
|
||||
SaveDataTransferMac = 0x64
|
||||
}
|
238
src/LibHac/FsSystem/NcaHeader.cs
Normal file
238
src/LibHac/FsSystem/NcaHeader.cs
Normal file
|
@ -0,0 +1,238 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common.FixedArrays;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public struct NcaHeader
|
||||
{
|
||||
public enum ContentType : byte
|
||||
{
|
||||
Program = 0,
|
||||
Meta = 1,
|
||||
Control = 2,
|
||||
Manual = 3,
|
||||
Data = 4,
|
||||
PublicData = 5
|
||||
}
|
||||
|
||||
public enum DistributionType : byte
|
||||
{
|
||||
Download = 0,
|
||||
GameCard = 1
|
||||
}
|
||||
|
||||
public enum EncryptionType : byte
|
||||
{
|
||||
Auto = 0,
|
||||
None = 1
|
||||
}
|
||||
|
||||
public enum DecryptionKey : byte
|
||||
{
|
||||
AesXts = 0,
|
||||
AesXts1 = 0,
|
||||
AesXts2 = 1,
|
||||
AesCtr = 2,
|
||||
AesCtrEx = 3,
|
||||
AesCtrHw = 4,
|
||||
Count
|
||||
}
|
||||
|
||||
public struct FsInfo
|
||||
{
|
||||
public uint StartSector;
|
||||
public uint EndSector;
|
||||
public uint HashSectors;
|
||||
public uint Reserved;
|
||||
}
|
||||
|
||||
public static readonly uint Magic0 = 0x3041434E; // NCA0
|
||||
public static readonly uint Magic1 = 0x3141434E; // NCA1
|
||||
public static readonly uint Magic2 = 0x3241434E; // NCA2
|
||||
public static readonly uint Magic3 = 0x3341434E; // NCA3
|
||||
|
||||
public static readonly uint CurrentMagic = Magic3;
|
||||
|
||||
public static readonly int Size = 0x400;
|
||||
public static readonly int FsCountMax = 4;
|
||||
public static readonly int HeaderSignCount = 2;
|
||||
public static readonly int HeaderSignSize = 0x100;
|
||||
public static readonly int EncryptedKeyAreaSize = 0x100;
|
||||
public static readonly int SectorSize = 0x200;
|
||||
public static readonly int SectorShift = 9;
|
||||
public static readonly int RightsIdSize = 0x10;
|
||||
public static readonly int XtsBlockSize = 0x200;
|
||||
public static readonly int CtrBlockSize = 0x10;
|
||||
|
||||
public Array256<byte> Signature1;
|
||||
public Array256<byte> Signature2;
|
||||
public uint Magic;
|
||||
public DistributionType DistributionTypeValue;
|
||||
public ContentType ContentTypeValue;
|
||||
public byte KeyGeneration1;
|
||||
public byte KeyAreaEncryptionKeyIndex;
|
||||
public ulong ContentSize;
|
||||
public ulong ProgramId;
|
||||
public uint ContentIndex;
|
||||
public uint SdkAddonVersion;
|
||||
public byte KeyGeneration2;
|
||||
public byte Header1SignatureKeyGeneration;
|
||||
public Array2<byte> Reserved222;
|
||||
public Array3<uint> Reserved224;
|
||||
public Array16<byte> RightsId;
|
||||
public Array4<FsInfo> FsInfos;
|
||||
public Array4<Hash> FsHeaderHashes;
|
||||
public Array256<byte> EncryptedKeys;
|
||||
|
||||
public static ulong SectorToByte(uint sectorIndex) => sectorIndex << SectorShift;
|
||||
public static uint ByteToSector(ulong byteIndex) => (uint)(byteIndex >> SectorShift);
|
||||
|
||||
public readonly byte GetProperKeyGeneration() => Math.Max(KeyGeneration1, KeyGeneration2);
|
||||
}
|
||||
|
||||
public struct NcaPatchInfo
|
||||
{
|
||||
public long IndirectOffset;
|
||||
public long IndirectSize;
|
||||
public Array16<byte> IndirectHeader;
|
||||
public long AesCtrExOffset;
|
||||
public long AesCtrExSize;
|
||||
public Array16<byte> AesCtrExHeader;
|
||||
|
||||
public readonly bool HasIndirectTable() => IndirectSize != 0;
|
||||
public readonly bool HasAesCtrExTable() => AesCtrExSize != 0;
|
||||
}
|
||||
|
||||
public struct NcaSparseInfo
|
||||
{
|
||||
public long MetaOffset;
|
||||
public long MetaSize;
|
||||
public Array16<byte> MetaHeader;
|
||||
public long PhysicalOffset;
|
||||
public ushort Generation;
|
||||
public Array6<byte> Reserved;
|
||||
|
||||
public readonly uint GetGeneration() => (uint)(Generation << 16);
|
||||
public readonly long GetPhysicalSize() => MetaOffset + MetaSize;
|
||||
|
||||
public readonly NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upperIv)
|
||||
{
|
||||
NcaAesCtrUpperIv sparseUpperIv = upperIv;
|
||||
sparseUpperIv.Generation = GetGeneration();
|
||||
return sparseUpperIv;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NcaCompressionInfo
|
||||
{
|
||||
public long TableOffset;
|
||||
public long TableSize;
|
||||
public Array16<byte> TableHeader;
|
||||
public ulong Reserved;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct NcaAesCtrUpperIv
|
||||
{
|
||||
[FieldOffset(0)] public ulong Value;
|
||||
|
||||
[FieldOffset(0)] public uint Generation;
|
||||
[FieldOffset(4)] public uint SecureValue;
|
||||
|
||||
internal NcaAesCtrUpperIv(ulong value)
|
||||
{
|
||||
Unsafe.SkipInit(out Generation);
|
||||
Unsafe.SkipInit(out SecureValue);
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NcaFsHeader
|
||||
{
|
||||
public ushort Version;
|
||||
public FsType FsTypeValue;
|
||||
public HashType HashTypeValue;
|
||||
public EncryptionType EncryptionTypeValue;
|
||||
public Array3<byte> Reserved;
|
||||
public HashData HashDataValue;
|
||||
public NcaPatchInfo PatchInfo;
|
||||
public NcaAesCtrUpperIv AesCtrUpperIv;
|
||||
public NcaSparseInfo SparseInfo;
|
||||
public NcaCompressionInfo CompressionInfo;
|
||||
public Array96<byte> Padding;
|
||||
|
||||
public enum FsType : byte
|
||||
{
|
||||
RomFs = 0,
|
||||
PartitionFs = 1
|
||||
}
|
||||
|
||||
public enum EncryptionType : byte
|
||||
{
|
||||
Auto = 0,
|
||||
None = 1,
|
||||
AesXts = 2,
|
||||
AesCtr = 3,
|
||||
AesCtrEx = 4
|
||||
}
|
||||
|
||||
public enum HashType : byte
|
||||
{
|
||||
Auto = 0,
|
||||
None = 1,
|
||||
HierarchicalSha256Hash = 2,
|
||||
HierarchicalIntegrityHash = 3
|
||||
}
|
||||
|
||||
public struct Region
|
||||
{
|
||||
public long Offset;
|
||||
public long Size;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0xF8)]
|
||||
public struct HashData
|
||||
{
|
||||
[FieldOffset(0)] public HierarchicalSha256Data HierarchicalSha256;
|
||||
[FieldOffset(0)] public IntegrityMetaInfo IntegrityMeta;
|
||||
|
||||
public struct HierarchicalSha256Data
|
||||
{
|
||||
public Hash MasterHash;
|
||||
public int BlockSize;
|
||||
public int LayerCount;
|
||||
public Array5<Region> LayerRegions;
|
||||
}
|
||||
|
||||
public struct IntegrityMetaInfo
|
||||
{
|
||||
public uint Magic;
|
||||
public uint Version;
|
||||
public uint MasterHashSize;
|
||||
public InfoLevelHash LevelHashInfo;
|
||||
public Hash MasterHash;
|
||||
|
||||
public struct InfoLevelHash
|
||||
{
|
||||
public int MaxLayers;
|
||||
public Array6<HierarchicalIntegrityVerificationLevelInformation> Layers;
|
||||
public SignatureSalt Salt;
|
||||
|
||||
public struct HierarchicalIntegrityVerificationLevelInformation
|
||||
{
|
||||
public Fs.Int64 Offset;
|
||||
public Fs.Int64 Size;
|
||||
public int OrderBlock;
|
||||
public Array4<byte> Reserved;
|
||||
}
|
||||
|
||||
public struct SignatureSalt
|
||||
{
|
||||
public Array32<byte> Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
561
src/LibHac/FsSystem/NcaReader.cs
Normal file
561
src/LibHac/FsSystem/NcaReader.cs
Normal file
|
@ -0,0 +1,561 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Crypto;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public delegate Result GenerateKeyFunction(Span<byte> destKey, ReadOnlySpan<byte> sourceKey, int keyType, in NcaCryptoConfiguration config);
|
||||
public delegate Result DecryptAesCtrFunction(Span<byte> dest, int keyType, ReadOnlySpan<byte> encryptedKey, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
|
||||
|
||||
/// <summary>
|
||||
/// Handles reading information from an NCA file's header.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
public class NcaReader : IDisposable
|
||||
{
|
||||
private const uint SdkAddonVersionMin = 0xB0000;
|
||||
|
||||
private NcaHeader _header;
|
||||
private Array5<Array16<byte>> _decryptionKeys;
|
||||
private SharedRef<IStorage> _bodyStorage;
|
||||
private UniqueRef<IStorage> _headerStorage;
|
||||
private Array16<byte> _externalDataDecryptionKey;
|
||||
private DecryptAesCtrFunction _decryptAesCtr;
|
||||
private DecryptAesCtrFunction _decryptAesCtrForExternalKey;
|
||||
private bool _isSoftwareAesPrioritized;
|
||||
private NcaHeader.EncryptionType _headerEncryptionType;
|
||||
private GetDecompressorFunction _getDecompressorFunc;
|
||||
private IHash256GeneratorFactory _hashGeneratorFactory;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_headerStorage.Destroy();
|
||||
_bodyStorage.Destroy();
|
||||
}
|
||||
|
||||
public Result Initialize(ref SharedRef<IStorage> baseStorage, in NcaCryptoConfiguration cryptoConfig,
|
||||
in NcaCompressionConfiguration compressionConfig, IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(in baseStorage);
|
||||
Assert.SdkRequiresNotNull(hashGeneratorFactorySelector);
|
||||
Assert.SdkRequiresNull(in _bodyStorage);
|
||||
|
||||
if (cryptoConfig.GenerateKey is null)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
using var headerStorage = new UniqueRef<IStorage>();
|
||||
|
||||
// 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].Items, cryptoConfig.HeaderEncryptedEncryptionKeys[i], 0x60,
|
||||
in cryptoConfig);
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
if (!headerStorage.HasValue)
|
||||
return ResultFs.AllocationMemoryFailedInNcaReaderA.Log();
|
||||
|
||||
// Read the decrypted header.
|
||||
Result rc = headerStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header));
|
||||
if (rc.IsFailure()) return rc.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.
|
||||
rc = baseStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header));
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
rc = CheckSignature(in _header);
|
||||
if (rc.IsFailure())
|
||||
return signatureResult.Miss();
|
||||
|
||||
// We have a plaintext header. Get an IStorage of just the header.
|
||||
rc = baseStorage.Get.GetSize(out long baseStorageSize);
|
||||
if (rc.IsFailure()) return rc.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> modulus = cryptoConfig.Header1SignKeyModuli[_header.Header1SignatureKeyGeneration];
|
||||
ReadOnlySpan<byte> exponent = cryptoConfig.Header1SignKeyPublicExponent;
|
||||
ReadOnlySpan<byte> message = SpanHelpers.AsReadOnlyByteSpan(in _header).Slice(signMessageOffset, signMessageSize);
|
||||
|
||||
if (!Rsa.VerifyRsa2048PssSha256(signature, modulus, exponent, message))
|
||||
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)
|
||||
return ResultFs.InvalidNcaKeyIndex.Log();
|
||||
|
||||
// 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.
|
||||
int keyType = NcaKeyFunctions.GetKeyTypeValue(_header.KeyAreaEncryptionKeyIndex, _header.GetProperKeyGeneration());
|
||||
ReadOnlySpan<byte> encryptedKeyCtr = _header.EncryptedKeys.ItemsRo.Slice((int)NcaHeader.DecryptionKey.AesCtr * Aes.KeySize128, Aes.KeySize128);
|
||||
ReadOnlySpan<byte> keyCtrHw = _header.EncryptedKeys.ItemsRo.Slice((int)NcaHeader.DecryptionKey.AesCtrHw * Aes.KeySize128, Aes.KeySize128);
|
||||
|
||||
cryptoConfig.GenerateKey(_decryptionKeys[(int)NcaHeader.DecryptionKey.AesCtr].Items, encryptedKeyCtr, keyType, in cryptoConfig);
|
||||
|
||||
// Copy the plaintext hardware key.
|
||||
keyCtrHw.CopyTo(_decryptionKeys[(int)NcaHeader.DecryptionKey.AesCtrHw].Items);
|
||||
}
|
||||
|
||||
_externalDataDecryptionKey.Items.Clear();
|
||||
|
||||
// Copy the configuration to the NcaReader.
|
||||
_decryptAesCtr = cryptoConfig.DecryptAesCtr;
|
||||
_decryptAesCtrForExternalKey = cryptoConfig.DecryptAesCtrForExternalKey;
|
||||
_getDecompressorFunc = compressionConfig.GetDecompressorFunc;
|
||||
_hashGeneratorFactory = hashGeneratorFactorySelector.GetFactory();
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactory);
|
||||
|
||||
_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 Result ReadHeader(out NcaFsHeader outHeader, int index)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out outHeader);
|
||||
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
long offset = Unsafe.SizeOf<NcaHeader>() + Unsafe.SizeOf<NcaFsHeader>() * index;
|
||||
return _headerStorage.Get.Read(offset, SpanHelpers.AsByteSpan(ref outHeader));
|
||||
}
|
||||
|
||||
public void GetHeaderSign2(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequiresEqual(NcaHeader.HeaderSignSize, outBuffer.Length);
|
||||
|
||||
_header.Signature2.ItemsRo.CopyTo(outBuffer);
|
||||
}
|
||||
|
||||
public void GetHeaderSign2TargetHash(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactory);
|
||||
Assert.SdkRequiresEqual(IHash256Generator.HashSize, outBuffer.Length);
|
||||
|
||||
int signTargetOffset = NcaHeader.HeaderSignSize * NcaHeader.HeaderSignCount;
|
||||
int signTargetSize = NcaHeader.Size - signTargetOffset;
|
||||
ReadOnlySpan<byte> signTarget =
|
||||
SpanHelpers.AsReadOnlyByteSpan(in _header).Slice(signTargetOffset, signTargetSize);
|
||||
|
||||
_hashGeneratorFactory.GenerateHash(outBuffer, signTarget);
|
||||
}
|
||||
|
||||
public SharedRef<IStorage> GetSharedBodyStorage()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_bodyStorage);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public NcaHeader.ContentType GetContentType()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_bodyStorage);
|
||||
return _header.ContentTypeValue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
_header.RightsId.ItemsRo.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 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];
|
||||
}
|
||||
|
||||
public void GetFsInfo(out NcaHeader.FsInfo outFsInfo, int index)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_bodyStorage);
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
outFsInfo = _header.FsInfos[index];
|
||||
}
|
||||
|
||||
public ulong GetFsOffset(int index)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_bodyStorage);
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
return NcaHeader.SectorToByte(_header.FsInfos[index].StartSector);
|
||||
}
|
||||
|
||||
public ulong GetFsEndOffset(int index)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_bodyStorage);
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
return NcaHeader.SectorToByte(_header.FsInfos[index].EndSector);
|
||||
}
|
||||
|
||||
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.ItemsRo.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.ItemsRo.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),
|
||||
Array16<byte>.Length);
|
||||
}
|
||||
|
||||
public bool IsSwAesPrioritized()
|
||||
{
|
||||
return _isSoftwareAesPrioritized;
|
||||
}
|
||||
|
||||
public void PrioritizeSwAes()
|
||||
{
|
||||
_isSoftwareAesPrioritized = true;
|
||||
}
|
||||
|
||||
public void SetExternalDecryptionKey(ReadOnlySpan<byte> key)
|
||||
{
|
||||
Assert.SdkRequiresEqual(_externalDataDecryptionKey.ItemsRo.Length, key.Length);
|
||||
|
||||
key.CopyTo(_externalDataDecryptionKey.Items);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetExternalDecryptionKey()
|
||||
{
|
||||
return _externalDataDecryptionKey.ItemsRo;
|
||||
}
|
||||
|
||||
public bool HasExternalDecryptionKey()
|
||||
{
|
||||
Array16<byte> zeroKey = default;
|
||||
return !CryptoUtil.IsSameBytes(zeroKey, GetExternalDecryptionKey(), Array16<byte>.Length);
|
||||
}
|
||||
|
||||
public void GetRawData(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequires(_bodyStorage.HasValue);
|
||||
Assert.SdkRequiresLessEqual(Unsafe.SizeOf<NcaHeader>(), outBuffer.Length);
|
||||
|
||||
SpanHelpers.AsReadOnlyByteSpan(_header).CopyTo(outBuffer);
|
||||
}
|
||||
|
||||
public DecryptAesCtrFunction GetExternalDecryptAesCtrFunction()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_decryptAesCtr);
|
||||
return _decryptAesCtr;
|
||||
}
|
||||
|
||||
public DecryptAesCtrFunction GetExternalDecryptAesCtrFunctionForExternalKey()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_decryptAesCtrForExternalKey);
|
||||
return _decryptAesCtrForExternalKey;
|
||||
}
|
||||
|
||||
public GetDecompressorFunction GetDecompressor()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_getDecompressorFunc);
|
||||
return _getDecompressorFunc;
|
||||
}
|
||||
|
||||
public IHash256GeneratorFactory GetHashGeneratorFactory()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactory);
|
||||
return _hashGeneratorFactory;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles reading information from the <see cref="NcaFsHeader"/> of a file system inside an NCA file.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
public class NcaFsHeaderReader
|
||||
{
|
||||
private NcaFsHeader _header;
|
||||
private int _fsIndex;
|
||||
|
||||
public NcaFsHeaderReader()
|
||||
{
|
||||
_fsIndex = -1;
|
||||
}
|
||||
|
||||
public bool IsInitialized()
|
||||
{
|
||||
return _fsIndex >= 0;
|
||||
}
|
||||
|
||||
public Result Initialize(NcaReader reader, int index)
|
||||
{
|
||||
_fsIndex = -1;
|
||||
|
||||
Result rc = reader.ReadHeader(out _header, index);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
Unsafe.SkipInit(out Hash hash);
|
||||
reader.GetHashGeneratorFactory().GenerateHash(hash.Value.Items, SpanHelpers.AsReadOnlyByteSpan(in _header));
|
||||
|
||||
if (!CryptoUtil.IsSameBytes(reader.GetFsHeaderHash(index).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 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 void GetRawData(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
Assert.SdkRequiresLessEqual(Unsafe.SizeOf<NcaFsHeader>(), outBuffer.Length);
|
||||
|
||||
SpanHelpers.AsReadOnlyByteSpan(in _header).CopyTo(outBuffer);
|
||||
}
|
||||
}
|
|
@ -1,51 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common.FixedArrays;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public struct NcaSparseInfo
|
||||
{
|
||||
public long MetaOffset;
|
||||
public long MetaSize;
|
||||
public Array16<byte> MetaHeader;
|
||||
public long PhysicalOffset;
|
||||
public ushort Generation;
|
||||
public Array6<byte> Reserved;
|
||||
|
||||
public readonly uint GetGeneration() => (uint)(Generation << 16);
|
||||
public readonly long GetPhysicalSize() => MetaOffset + MetaSize;
|
||||
|
||||
public readonly NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upperIv)
|
||||
{
|
||||
NcaAesCtrUpperIv sparseUpperIv = upperIv;
|
||||
sparseUpperIv.Generation = GetGeneration();
|
||||
return sparseUpperIv;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NcaCompressionInfo
|
||||
{
|
||||
public long MetaOffset;
|
||||
public long MetaSize;
|
||||
public Array16<byte> MetaHeader;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct NcaAesCtrUpperIv
|
||||
{
|
||||
[FieldOffset(0)] public ulong Value;
|
||||
|
||||
[FieldOffset(0)] public uint Generation;
|
||||
[FieldOffset(4)] public uint SecureValue;
|
||||
|
||||
internal NcaAesCtrUpperIv(ulong value)
|
||||
{
|
||||
Unsafe.SkipInit(out Generation);
|
||||
Unsafe.SkipInit(out SecureValue);
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public enum NcaSectionType
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
|
|||
using LibHac.Tools.FsSystem.Save;
|
||||
using LibHac.Tools.Ncm;
|
||||
using LibHac.Util;
|
||||
using KeyType = LibHac.Common.Keys.KeyType;
|
||||
|
||||
namespace LibHac.Tools.Fs;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ using LibHac.FsSystem;
|
|||
using LibHac.Spl;
|
||||
using LibHac.Tools.Crypto;
|
||||
using LibHac.Tools.FsSystem.RomFs;
|
||||
using KeyType = LibHac.Common.Keys.KeyType;
|
||||
|
||||
namespace LibHac.Tools.FsSystem.NcaUtils;
|
||||
|
||||
|
@ -416,13 +417,13 @@ public class Nca
|
|||
ref NcaCompressionInfo compressionInfo = ref header.GetCompressionInfo();
|
||||
|
||||
Unsafe.SkipInit(out BucketTree.Header bucketTreeHeader);
|
||||
compressionInfo.MetaHeader.ItemsRo.CopyTo(SpanHelpers.AsByteSpan(ref bucketTreeHeader));
|
||||
compressionInfo.TableHeader.ItemsRo.CopyTo(SpanHelpers.AsByteSpan(ref bucketTreeHeader));
|
||||
bucketTreeHeader.Verify().ThrowIfFailure();
|
||||
|
||||
long nodeStorageSize = CompressedStorage.QueryNodeStorageSize(bucketTreeHeader.EntryCount);
|
||||
long entryStorageSize = CompressedStorage.QueryEntryStorageSize(bucketTreeHeader.EntryCount);
|
||||
long tableOffset = compressionInfo.MetaOffset;
|
||||
long tableSize = compressionInfo.MetaSize;
|
||||
long tableOffset = compressionInfo.TableOffset;
|
||||
long tableSize = compressionInfo.TableSize;
|
||||
|
||||
if (entryStorageSize + nodeStorageSize > tableSize)
|
||||
throw new HorizonResultException(ResultFs.NcaInvalidCompressionInfo.Value);
|
||||
|
|
|
@ -81,7 +81,7 @@ public struct NcaFsHeader
|
|||
|
||||
public bool ExistsCompressionLayer()
|
||||
{
|
||||
return GetCompressionInfo().MetaOffset != 0 && GetCompressionInfo().MetaSize != 0;
|
||||
return GetCompressionInfo().TableOffset != 0 && GetCompressionInfo().TableSize != 0;
|
||||
}
|
||||
|
||||
public ulong Counter
|
||||
|
|
|
@ -11,6 +11,7 @@ using LibHac.Tools.FsSystem;
|
|||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using LibHac.Tools.Npdm;
|
||||
using static hactoolnet.Print;
|
||||
using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader;
|
||||
|
||||
namespace hactoolnet;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Impl;
|
||||
using Xunit;
|
||||
|
@ -471,4 +472,22 @@ public class TypeLayoutTests
|
|||
|
||||
Assert.Equal(0, GetOffset(in s, in s.Value));
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct Int64AlignmentTest
|
||||
{
|
||||
public int A;
|
||||
public Int64 B;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Int64Test_Layout()
|
||||
{
|
||||
var s = new Int64AlignmentTest();
|
||||
|
||||
Assert.Equal(12, Unsafe.SizeOf<Int64AlignmentTest>());
|
||||
|
||||
Assert.Equal(0, GetOffset(in s, in s.A));
|
||||
Assert.Equal(4, GetOffset(in s, in s.B));
|
||||
}
|
||||
}
|
|
@ -10,10 +10,241 @@ public class TypeLayoutTests
|
|||
[Fact]
|
||||
public static void Hash_Layout()
|
||||
{
|
||||
var s = new Hash();
|
||||
Hash s = default;
|
||||
|
||||
Assert.Equal(0x20, Unsafe.SizeOf<Hash>());
|
||||
|
||||
Assert.Equal(0x0, GetOffset(in s, in s.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaFsHeader_Layout()
|
||||
{
|
||||
NcaFsHeader s = default;
|
||||
|
||||
Assert.Equal(0x200, Unsafe.SizeOf<NcaFsHeader>());
|
||||
|
||||
Assert.Equal(0x000, GetOffset(in s, in s.Version));
|
||||
Assert.Equal(0x002, GetOffset(in s, in s.FsTypeValue));
|
||||
Assert.Equal(0x003, GetOffset(in s, in s.HashTypeValue));
|
||||
Assert.Equal(0x004, GetOffset(in s, in s.EncryptionTypeValue));
|
||||
Assert.Equal(0x005, GetOffset(in s, in s.Reserved));
|
||||
Assert.Equal(0x008, GetOffset(in s, in s.HashDataValue));
|
||||
Assert.Equal(0x100, GetOffset(in s, in s.PatchInfo));
|
||||
Assert.Equal(0x140, GetOffset(in s, in s.AesCtrUpperIv));
|
||||
Assert.Equal(0x148, GetOffset(in s, in s.SparseInfo));
|
||||
Assert.Equal(0x178, GetOffset(in s, in s.CompressionInfo));
|
||||
Assert.Equal(0x1A0, GetOffset(in s, in s.Padding));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaFsHeaderRegion_Layout()
|
||||
{
|
||||
NcaFsHeader.Region s = default;
|
||||
|
||||
Assert.Equal(0x10, Unsafe.SizeOf<NcaFsHeader.Region>());
|
||||
|
||||
Assert.Equal(0, GetOffset(in s, in s.Offset));
|
||||
Assert.Equal(8, GetOffset(in s, in s.Size));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void HashData_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData s = default;
|
||||
|
||||
Assert.Equal(0xF8, Unsafe.SizeOf<NcaFsHeader.HashData>());
|
||||
|
||||
Assert.Equal(0, GetOffset(in s, in s.HierarchicalSha256));
|
||||
Assert.Equal(0, GetOffset(in s, in s.IntegrityMeta));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void HierarchicalSha256Data_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData.HierarchicalSha256Data s = default;
|
||||
|
||||
Assert.Equal(0x78, Unsafe.SizeOf<NcaFsHeader.HashData.HierarchicalSha256Data>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.MasterHash));
|
||||
Assert.Equal(0x20, GetOffset(in s, in s.BlockSize));
|
||||
Assert.Equal(0x24, GetOffset(in s, in s.LayerCount));
|
||||
Assert.Equal(0x28, GetOffset(in s, in s.LayerRegions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void IntegrityMetaInfo_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData.IntegrityMetaInfo s = default;
|
||||
|
||||
Assert.Equal(0xE0, Unsafe.SizeOf<NcaFsHeader.HashData.IntegrityMetaInfo>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x04, GetOffset(in s, in s.Version));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.MasterHashSize));
|
||||
Assert.Equal(0x0C, GetOffset(in s, in s.LevelHashInfo));
|
||||
Assert.Equal(0xC0, GetOffset(in s, in s.MasterHash));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void InfoLevelHash_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash s = default;
|
||||
|
||||
Assert.Equal(0xB4, Unsafe.SizeOf<NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.MaxLayers));
|
||||
Assert.Equal(0x04, GetOffset(in s, in s.Layers));
|
||||
Assert.Equal(0x94, GetOffset(in s, in s.Salt));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void HierarchicalIntegrityVerificationLevelInformation_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash.HierarchicalIntegrityVerificationLevelInformation s = default;
|
||||
|
||||
Assert.Equal(0x18, Unsafe.SizeOf<NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash.HierarchicalIntegrityVerificationLevelInformation>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.Offset));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.Size));
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.OrderBlock));
|
||||
Assert.Equal(0x14, GetOffset(in s, in s.Reserved));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void SignatureSalt_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash.SignatureSalt s = default;
|
||||
|
||||
Assert.Equal(0x20, Unsafe.SizeOf<NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash.SignatureSalt>());
|
||||
|
||||
Assert.Equal(0, GetOffset(in s, in s.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaPatchInfo_Layout()
|
||||
{
|
||||
NcaPatchInfo s = default;
|
||||
|
||||
Assert.Equal(0x40, Unsafe.SizeOf<NcaPatchInfo>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.IndirectOffset));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.IndirectSize));
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.IndirectHeader));
|
||||
Assert.Equal(0x20, GetOffset(in s, in s.AesCtrExOffset));
|
||||
Assert.Equal(0x28, GetOffset(in s, in s.AesCtrExSize));
|
||||
Assert.Equal(0x30, GetOffset(in s, in s.AesCtrExHeader));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaSparseInfo_Layout()
|
||||
{
|
||||
NcaSparseInfo s = default;
|
||||
|
||||
Assert.Equal(0x30, Unsafe.SizeOf<NcaSparseInfo>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.MetaOffset));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.MetaSize));
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.MetaHeader));
|
||||
Assert.Equal(0x20, GetOffset(in s, in s.PhysicalOffset));
|
||||
Assert.Equal(0x28, GetOffset(in s, in s.Generation));
|
||||
Assert.Equal(0x2A, GetOffset(in s, in s.Reserved));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaCompressionInfo_Layout()
|
||||
{
|
||||
NcaCompressionInfo s = default;
|
||||
|
||||
Assert.Equal(0x28, Unsafe.SizeOf<NcaCompressionInfo>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.TableOffset));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.TableSize));
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.TableHeader));
|
||||
Assert.Equal(0x20, GetOffset(in s, in s.Reserved));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaAesCtrUpperIv_Layout()
|
||||
{
|
||||
NcaAesCtrUpperIv s = default;
|
||||
|
||||
Assert.Equal(8, Unsafe.SizeOf<NcaAesCtrUpperIv>());
|
||||
|
||||
Assert.Equal(0, GetOffset(in s, in s.Value));
|
||||
Assert.Equal(0, GetOffset(in s, in s.Generation));
|
||||
Assert.Equal(4, GetOffset(in s, in s.SecureValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaHeader_Layout()
|
||||
{
|
||||
NcaHeader s = default;
|
||||
|
||||
Assert.Equal(0x400, Unsafe.SizeOf<NcaHeader>());
|
||||
|
||||
Assert.Equal(0x000, GetOffset(in s, in s.Signature1));
|
||||
Assert.Equal(0x100, GetOffset(in s, in s.Signature2));
|
||||
Assert.Equal(0x200, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x204, GetOffset(in s, in s.DistributionTypeValue));
|
||||
Assert.Equal(0x205, GetOffset(in s, in s.ContentTypeValue));
|
||||
Assert.Equal(0x206, GetOffset(in s, in s.KeyGeneration1));
|
||||
Assert.Equal(0x207, GetOffset(in s, in s.KeyAreaEncryptionKeyIndex));
|
||||
Assert.Equal(0x208, GetOffset(in s, in s.ContentSize));
|
||||
Assert.Equal(0x210, GetOffset(in s, in s.ProgramId));
|
||||
Assert.Equal(0x218, GetOffset(in s, in s.ContentIndex));
|
||||
Assert.Equal(0x21C, GetOffset(in s, in s.SdkAddonVersion));
|
||||
Assert.Equal(0x220, GetOffset(in s, in s.KeyGeneration2));
|
||||
Assert.Equal(0x221, GetOffset(in s, in s.Header1SignatureKeyGeneration));
|
||||
Assert.Equal(0x222, GetOffset(in s, in s.Reserved222));
|
||||
Assert.Equal(0x224, GetOffset(in s, in s.Reserved224));
|
||||
Assert.Equal(0x230, GetOffset(in s, in s.RightsId));
|
||||
Assert.Equal(0x240, GetOffset(in s, in s.FsInfos));
|
||||
Assert.Equal(0x280, GetOffset(in s, in s.FsHeaderHashes));
|
||||
Assert.Equal(0x300, GetOffset(in s, in s.EncryptedKeys));
|
||||
|
||||
Assert.Equal(NcaHeader.Size, Unsafe.SizeOf<NcaHeader>());
|
||||
Assert.Equal(NcaHeader.SectorSize, 1 << NcaHeader.SectorShift);
|
||||
|
||||
Assert.Equal(NcaHeader.HeaderSignSize, s.Signature1.ItemsRo.Length);
|
||||
Assert.Equal(NcaHeader.HeaderSignSize, s.Signature2.ItemsRo.Length);
|
||||
Assert.Equal(NcaHeader.RightsIdSize, s.RightsId.ItemsRo.Length);
|
||||
Assert.Equal(NcaHeader.FsCountMax, s.FsInfos.ItemsRo.Length);
|
||||
Assert.Equal(NcaHeader.FsCountMax, s.FsHeaderHashes.ItemsRo.Length);
|
||||
Assert.Equal(NcaHeader.EncryptedKeyAreaSize, s.EncryptedKeys.ItemsRo.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaHeader_FsInfo_Layout()
|
||||
{
|
||||
NcaHeader.FsInfo s = default;
|
||||
|
||||
Assert.Equal(0x10, Unsafe.SizeOf<NcaHeader.FsInfo>());
|
||||
|
||||
Assert.Equal(0x0, GetOffset(in s, in s.StartSector));
|
||||
Assert.Equal(0x4, GetOffset(in s, in s.EndSector));
|
||||
Assert.Equal(0x8, GetOffset(in s, in s.HashSectors));
|
||||
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.ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.Rsa2048KeyModulusSize, s.Header1SignKeyModuli.ItemsRo[0].ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.Rsa2048KeyPublicExponentSize, s.Header1SignKeyPublicExponent.ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount, s.KeyAreaEncryptionKeySources.ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.KeyAreaEncryptionKeySources.ItemsRo[0].ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.HeaderEncryptionKeySource.ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.HeaderEncryptionKeyCount, s.HeaderEncryptedEncryptionKeys.ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.HeaderEncryptedEncryptionKeys.ItemsRo[0].ItemsRo.Length);
|
||||
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 0, (int)KeyType.NcaHeaderKey);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 1, (int)KeyType.NcaExternalKey);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 2, (int)KeyType.SaveDataDeviceUniqueMac);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 3, (int)KeyType.SaveDataSeedUniqueMac);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 4, (int)KeyType.SaveDataTransferMac);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue