mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Add support for NCA0 files.
Any operation that is supported with other NCAs is supported NCA0s. This includes reading both encrypted and decrypted NCA0s, and encrypting and decrypting NCA0s.
This commit is contained in:
parent
6bab1d9273
commit
f20337d774
@ -90,10 +90,10 @@ namespace LibHac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static BigInteger GetBigInteger(byte[] bytes)
|
internal static BigInteger GetBigInteger(ReadOnlySpan<byte> bytes)
|
||||||
{
|
{
|
||||||
var signPadded = new byte[bytes.Length + 1];
|
var signPadded = new byte[bytes.Length + 1];
|
||||||
Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length);
|
bytes.CopyTo(signPadded.AsSpan(1));
|
||||||
Array.Reverse(signPadded);
|
Array.Reverse(signPadded);
|
||||||
return new BigInteger(signPadded);
|
return new BigInteger(signPadded);
|
||||||
}
|
}
|
||||||
@ -142,11 +142,18 @@ namespace LibHac
|
|||||||
{
|
{
|
||||||
using (var rsa = RSA.Create())
|
using (var rsa = RSA.Create())
|
||||||
{
|
{
|
||||||
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
|
try
|
||||||
|
{
|
||||||
|
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
|
||||||
|
|
||||||
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
|
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
|
||||||
? Validity.Valid
|
? Validity.Valid
|
||||||
: Validity.Invalid;
|
: Validity.Invalid;
|
||||||
|
}
|
||||||
|
catch (CryptographicException)
|
||||||
|
{
|
||||||
|
return Validity.Invalid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,20 +161,54 @@ namespace LibHac
|
|||||||
{
|
{
|
||||||
using (var rsa = RSA.Create())
|
using (var rsa = RSA.Create())
|
||||||
{
|
{
|
||||||
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
|
try
|
||||||
|
{
|
||||||
|
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
|
||||||
|
|
||||||
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)
|
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)
|
||||||
? Validity.Valid
|
? Validity.Valid
|
||||||
: Validity.Invalid;
|
: Validity.Invalid;
|
||||||
|
}
|
||||||
|
catch (CryptographicException)
|
||||||
|
{
|
||||||
|
return Validity.Invalid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] DecryptTitleKey(byte[] titleKeyblock, RSAParameters rsaParams)
|
public static byte[] DecryptRsaOaep(byte[] data, RSAParameters rsaParams)
|
||||||
{
|
{
|
||||||
var rsa = RSA.Create();
|
var rsa = RSA.Create();
|
||||||
|
|
||||||
rsa.ImportParameters(rsaParams);
|
rsa.ImportParameters(rsaParams);
|
||||||
return rsa.Decrypt(titleKeyblock, RSAEncryptionPadding.OaepSHA256);
|
return rsa.Decrypt(data, RSAEncryptionPadding.OaepSHA256);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DecryptRsaOaep(ReadOnlySpan<byte> data, Span<byte> destination, RSAParameters rsaParams, out int bytesWritten)
|
||||||
|
{
|
||||||
|
using (var rsa = RSA.Create())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rsa.ImportParameters(rsaParams);
|
||||||
|
|
||||||
|
return rsa.TryDecrypt(data, destination, RSAEncryptionPadding.OaepSHA256, out bytesWritten);
|
||||||
|
}
|
||||||
|
catch (CryptographicException)
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RSAParameters RecoverRsaParameters(ReadOnlySpan<byte> modulus, ReadOnlySpan<byte> exponent)
|
||||||
|
{
|
||||||
|
BigInteger dInt = GetBigInteger(exponent);
|
||||||
|
BigInteger nInt = GetBigInteger(modulus);
|
||||||
|
BigInteger eInt = GetBigInteger(new byte[] { 1, 0, 1 });
|
||||||
|
|
||||||
|
return RecoverRsaParameters(nInt, eInt, dInt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RSAParameters RecoverRsaParameters(BigInteger n, BigInteger e, BigInteger d)
|
private static RSAParameters RecoverRsaParameters(BigInteger n, BigInteger e, BigInteger d)
|
||||||
|
@ -40,7 +40,7 @@ namespace LibHac.FsSrv.Creators
|
|||||||
}
|
}
|
||||||
|
|
||||||
storage = storageTemp;
|
storage = storageTemp;
|
||||||
fsHeader = nca.Header.GetFsHeader(fsIndex);
|
fsHeader = nca.GetFsHeader(fsIndex);
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
|
using LibHac.Diag;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem.RomFs;
|
using LibHac.FsSystem.RomFs;
|
||||||
@ -14,6 +15,10 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
{
|
{
|
||||||
private Keyset Keyset { get; }
|
private Keyset Keyset { get; }
|
||||||
private bool IsEncrypted { get; set; }
|
private bool IsEncrypted { get; set; }
|
||||||
|
|
||||||
|
private byte[] Nca0KeyArea { get; set; }
|
||||||
|
private IStorage Nca0TransformedBody { get; set; }
|
||||||
|
|
||||||
public IStorage BaseStorage { get; }
|
public IStorage BaseStorage { get; }
|
||||||
|
|
||||||
public NcaHeader Header { get; }
|
public NcaHeader Header { get; }
|
||||||
@ -33,6 +38,12 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
{
|
{
|
||||||
if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index));
|
if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
|
||||||
|
// Handle old NCA0s that use different key area encryption
|
||||||
|
if (Header.FormatVersion == NcaVersion.Nca0FixedKey || Header.FormatVersion == NcaVersion.Nca0RsaOaep)
|
||||||
|
{
|
||||||
|
return GetDecryptedKeyAreaNca0().AsSpan(0x10 * index, 0x10).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
|
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
|
||||||
byte[] keyAreaKey = Keyset.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex];
|
byte[] keyAreaKey = Keyset.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex];
|
||||||
|
|
||||||
@ -94,7 +105,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
public bool CanOpenSection(int index)
|
public bool CanOpenSection(int index)
|
||||||
{
|
{
|
||||||
if (!SectionExists(index)) return false;
|
if (!SectionExists(index)) return false;
|
||||||
if (Header.GetFsHeader(index).EncryptionType == NcaEncryptionType.None) return true;
|
if (GetFsHeader(index).EncryptionType == NcaEncryptionType.None) return true;
|
||||||
|
|
||||||
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
|
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
|
||||||
|
|
||||||
@ -122,6 +133,14 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
return Header.IsSectionEnabled(index);
|
return Header.IsSectionEnabled(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NcaFsHeader GetFsHeader(int index)
|
||||||
|
{
|
||||||
|
if (Header.IsNca0())
|
||||||
|
return GetNca0FsHeader(index);
|
||||||
|
|
||||||
|
return Header.GetFsHeader(index);
|
||||||
|
}
|
||||||
|
|
||||||
private IStorage OpenSectionStorage(int index)
|
private IStorage OpenSectionStorage(int index)
|
||||||
{
|
{
|
||||||
if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
|
if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
|
||||||
@ -142,7 +161,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
private IStorage OpenDecryptedStorage(IStorage baseStorage, int index, bool decrypting)
|
private IStorage OpenDecryptedStorage(IStorage baseStorage, int index, bool decrypting)
|
||||||
{
|
{
|
||||||
NcaFsHeader header = Header.GetFsHeader(index);
|
NcaFsHeader header = GetFsHeader(index);
|
||||||
|
|
||||||
switch (header.EncryptionType)
|
switch (header.EncryptionType)
|
||||||
{
|
{
|
||||||
@ -174,7 +193,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
private IStorage OpenAesCtrStorage(IStorage baseStorage, int index)
|
private IStorage OpenAesCtrStorage(IStorage baseStorage, int index)
|
||||||
{
|
{
|
||||||
NcaFsHeader fsHeader = Header.GetFsHeader(index);
|
NcaFsHeader fsHeader = GetFsHeader(index);
|
||||||
byte[] key = GetContentKey(NcaKeyType.AesCtr);
|
byte[] key = GetContentKey(NcaKeyType.AesCtr);
|
||||||
byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, Header.GetSectionStartOffset(index));
|
byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, Header.GetSectionStartOffset(index));
|
||||||
|
|
||||||
@ -184,7 +203,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
private IStorage OpenAesCtrExStorage(IStorage baseStorage, int index, bool decrypting)
|
private IStorage OpenAesCtrExStorage(IStorage baseStorage, int index, bool decrypting)
|
||||||
{
|
{
|
||||||
NcaFsHeader fsHeader = Header.GetFsHeader(index);
|
NcaFsHeader fsHeader = GetFsHeader(index);
|
||||||
NcaFsPatchInfo info = fsHeader.GetPatchInfo();
|
NcaFsPatchInfo info = fsHeader.GetPatchInfo();
|
||||||
|
|
||||||
long sectionOffset = Header.GetSectionStartOffset(index);
|
long sectionOffset = Header.GetSectionStartOffset(index);
|
||||||
@ -233,6 +252,9 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
public IStorage OpenRawStorage(int index, bool openEncrypted)
|
public IStorage OpenRawStorage(int index, bool openEncrypted)
|
||||||
{
|
{
|
||||||
|
if (Header.IsNca0())
|
||||||
|
return OpenNca0RawStorage(index, openEncrypted);
|
||||||
|
|
||||||
IStorage storage = OpenSectionStorage(index);
|
IStorage storage = OpenSectionStorage(index);
|
||||||
|
|
||||||
if (IsEncrypted == openEncrypted)
|
if (IsEncrypted == openEncrypted)
|
||||||
@ -255,7 +277,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
patchStorage.GetSize(out long patchSize).ThrowIfFailure();
|
patchStorage.GetSize(out long patchSize).ThrowIfFailure();
|
||||||
baseStorage.GetSize(out long baseSize).ThrowIfFailure();
|
baseStorage.GetSize(out long baseSize).ThrowIfFailure();
|
||||||
|
|
||||||
NcaFsHeader header = patchNca.Header.GetFsHeader(index);
|
NcaFsHeader header = patchNca.GetFsHeader(index);
|
||||||
NcaFsPatchInfo patchInfo = header.GetPatchInfo();
|
NcaFsPatchInfo patchInfo = header.GetPatchInfo();
|
||||||
|
|
||||||
if (patchInfo.RelocationTreeSize == 0)
|
if (patchInfo.RelocationTreeSize == 0)
|
||||||
@ -286,7 +308,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel)
|
public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
IStorage rawStorage = OpenRawStorage(index);
|
IStorage rawStorage = OpenRawStorage(index);
|
||||||
NcaFsHeader header = Header.GetFsHeader(index);
|
NcaFsHeader header = GetFsHeader(index);
|
||||||
|
|
||||||
if (header.EncryptionType == NcaEncryptionType.AesCtrEx)
|
if (header.EncryptionType == NcaEncryptionType.AesCtrEx)
|
||||||
{
|
{
|
||||||
@ -307,7 +329,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
public IStorage OpenStorageWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel)
|
public IStorage OpenStorageWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
IStorage rawStorage = OpenRawStorageWithPatch(patchNca, index);
|
IStorage rawStorage = OpenRawStorageWithPatch(patchNca, index);
|
||||||
NcaFsHeader header = patchNca.Header.GetFsHeader(index);
|
NcaFsHeader header = patchNca.GetFsHeader(index);
|
||||||
|
|
||||||
switch (header.HashType)
|
switch (header.HashType)
|
||||||
{
|
{
|
||||||
@ -323,7 +345,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel)
|
public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
IStorage storage = OpenStorage(index, integrityCheckLevel);
|
IStorage storage = OpenStorage(index, integrityCheckLevel);
|
||||||
NcaFsHeader header = Header.GetFsHeader(index);
|
NcaFsHeader header = GetFsHeader(index);
|
||||||
|
|
||||||
return OpenFileSystem(storage, header);
|
return OpenFileSystem(storage, header);
|
||||||
}
|
}
|
||||||
@ -331,7 +353,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
public IFileSystem OpenFileSystemWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel)
|
public IFileSystem OpenFileSystemWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
IStorage storage = OpenStorageWithPatch(patchNca, index, integrityCheckLevel);
|
IStorage storage = OpenStorageWithPatch(patchNca, index, integrityCheckLevel);
|
||||||
NcaFsHeader header = patchNca.Header.GetFsHeader(index);
|
NcaFsHeader header = patchNca.GetFsHeader(index);
|
||||||
|
|
||||||
return OpenFileSystem(storage, header);
|
return OpenFileSystem(storage, header);
|
||||||
}
|
}
|
||||||
@ -392,6 +414,12 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
var builder = new ConcatenationStorageBuilder();
|
var builder = new ConcatenationStorageBuilder();
|
||||||
builder.Add(OpenHeaderStorage(openEncrypted), 0);
|
builder.Add(OpenHeaderStorage(openEncrypted), 0);
|
||||||
|
|
||||||
|
if (Header.IsNca0())
|
||||||
|
{
|
||||||
|
builder.Add(OpenNca0BodyStorage(openEncrypted), 0x400);
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < NcaHeader.SectionCount; i++)
|
for (int i = 0; i < NcaHeader.SectionCount; i++)
|
||||||
{
|
{
|
||||||
if (Header.IsSectionEnabled(i))
|
if (Header.IsSectionEnabled(i))
|
||||||
@ -566,6 +594,9 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
case 2:
|
case 2:
|
||||||
header = OpenNca2Header(headerSize, !openEncrypted);
|
header = OpenNca2Header(headerSize, !openEncrypted);
|
||||||
break;
|
break;
|
||||||
|
case 0:
|
||||||
|
header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException("Unsupported NCA version");
|
throw new NotSupportedException("Unsupported NCA version");
|
||||||
}
|
}
|
||||||
@ -588,6 +619,96 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
return new ConcatenationStorage(sources, true);
|
return new ConcatenationStorage(sources, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] GetDecryptedKeyAreaNca0()
|
||||||
|
{
|
||||||
|
if (Nca0KeyArea != null)
|
||||||
|
return Nca0KeyArea;
|
||||||
|
|
||||||
|
if (Header.FormatVersion == NcaVersion.Nca0FixedKey)
|
||||||
|
{
|
||||||
|
Nca0KeyArea = Header.GetKeyArea().ToArray();
|
||||||
|
}
|
||||||
|
else if (Header.FormatVersion == NcaVersion.Nca0RsaOaep)
|
||||||
|
{
|
||||||
|
Span<byte> keyArea = Header.GetKeyArea();
|
||||||
|
var decKeyArea = new byte[0x100];
|
||||||
|
|
||||||
|
if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, Keyset.Nca0RsaKeyAreaKey, out _))
|
||||||
|
{
|
||||||
|
Nca0KeyArea = decKeyArea;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Unable to decrypt NCA0 key area.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Nca0KeyArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IStorage OpenNca0BodyStorage(bool openEncrypted)
|
||||||
|
{
|
||||||
|
// NCA0 encrypts the entire NCA body using AES-XTS instead of
|
||||||
|
// using different encryption types and IVs for each section.
|
||||||
|
Assert.Equal(0, Header.Version);
|
||||||
|
|
||||||
|
if (openEncrypted == IsEncrypted)
|
||||||
|
{
|
||||||
|
return GetRawStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Nca0TransformedBody != null)
|
||||||
|
return Nca0TransformedBody;
|
||||||
|
|
||||||
|
byte[] key0 = GetContentKey(NcaKeyType.AesXts0);
|
||||||
|
byte[] key1 = GetContentKey(NcaKeyType.AesXts1);
|
||||||
|
|
||||||
|
Nca0TransformedBody = new CachedStorage(new Aes128XtsStorage(GetRawStorage(), key0, key1, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
|
||||||
|
return Nca0TransformedBody;
|
||||||
|
|
||||||
|
IStorage GetRawStorage()
|
||||||
|
{
|
||||||
|
BaseStorage.GetSize(out long ncaSize).ThrowIfFailure();
|
||||||
|
return BaseStorage.Slice(0x400, ncaSize - 0x400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IStorage OpenNca0RawStorage(int index, bool openEncrypted)
|
||||||
|
{
|
||||||
|
if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
|
||||||
|
|
||||||
|
long offset = Header.GetSectionStartOffset(index) - 0x400;
|
||||||
|
long size = Header.GetSectionSize(index);
|
||||||
|
|
||||||
|
IStorage bodyStorage = OpenNca0BodyStorage(openEncrypted);
|
||||||
|
|
||||||
|
bodyStorage.GetSize(out long baseSize).ThrowIfFailure();
|
||||||
|
|
||||||
|
if (!Utilities.IsSubRange(offset, size, baseSize))
|
||||||
|
{
|
||||||
|
throw new InvalidDataException(
|
||||||
|
$"Section offset (0x{offset + 0x400:x}) and length (0x{size:x}) fall outside the total NCA length (0x{baseSize + 0x400:x}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SubStorage(bodyStorage, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NcaFsHeader GetNca0FsHeader(int index)
|
||||||
|
{
|
||||||
|
// NCA0 stores the FS header in the first block of the section instead of the header
|
||||||
|
IStorage bodyStorage = OpenNca0BodyStorage(false);
|
||||||
|
long offset = Header.GetSectionStartOffset(index) - 0x400;
|
||||||
|
|
||||||
|
var fsHeaderData = new byte[0x200];
|
||||||
|
bodyStorage.Read(offset, fsHeaderData).ThrowIfFailure();
|
||||||
|
|
||||||
|
return new NcaFsHeader(fsHeaderData);
|
||||||
|
}
|
||||||
|
|
||||||
public Validity VerifyHeaderSignature()
|
public Validity VerifyHeaderSignature()
|
||||||
{
|
{
|
||||||
return Header.VerifySignature1(Keyset.NcaHdrFixedKeyModulus);
|
return Header.VerifySignature1(Keyset.NcaHdrFixedKeyModulus);
|
||||||
@ -598,7 +719,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
int counterType;
|
int counterType;
|
||||||
int counterVersion;
|
int counterVersion;
|
||||||
|
|
||||||
NcaFsHeader header = Header.GetFsHeader(sectionIndex);
|
NcaFsHeader header = GetFsHeader(sectionIndex);
|
||||||
if (header.EncryptionType != NcaEncryptionType.AesCtr &&
|
if (header.EncryptionType != NcaEncryptionType.AesCtr &&
|
||||||
header.EncryptionType != NcaEncryptionType.AesCtrEx) return;
|
header.EncryptionType != NcaEncryptionType.AesCtrEx) return;
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
if (!nca.SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
|
if (!nca.SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
|
||||||
if (!nca.CanOpenSection(index)) return Validity.MissingKey;
|
if (!nca.CanOpenSection(index)) return Validity.MissingKey;
|
||||||
|
|
||||||
NcaFsHeader header = nca.Header.GetFsHeader(index);
|
NcaFsHeader header = nca.GetFsHeader(index);
|
||||||
|
|
||||||
// The base data is needed to validate the hash, so use a trick involving the AES-CTR extended
|
// The base data is needed to validate the hash, so use a trick involving the AES-CTR extended
|
||||||
// encryption table to check if the decryption is invalid.
|
// encryption table to check if the decryption is invalid.
|
||||||
@ -116,7 +116,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
Debug.Assert(nca.CanOpenSection(index));
|
Debug.Assert(nca.CanOpenSection(index));
|
||||||
|
|
||||||
NcaFsPatchInfo header = nca.Header.GetFsHeader(index).GetPatchInfo();
|
NcaFsPatchInfo header = nca.GetFsHeader(index).GetPatchInfo();
|
||||||
IStorage decryptedStorage = nca.OpenRawStorage(index);
|
IStorage decryptedStorage = nca.OpenRawStorage(index);
|
||||||
|
|
||||||
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||||
@ -145,7 +145,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false)
|
public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false)
|
||||||
{
|
{
|
||||||
NcaFsHeader sect = nca.Header.GetFsHeader(index);
|
NcaFsHeader sect = nca.GetFsHeader(index);
|
||||||
NcaHashType hashType = sect.HashType;
|
NcaHashType hashType = sect.HashType;
|
||||||
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked;
|
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked;
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
public static Validity VerifySection(this Nca nca, Nca patchNca, int index, IProgressReport logger = null, bool quiet = false)
|
public static Validity VerifySection(this Nca nca, Nca patchNca, int index, IProgressReport logger = null, bool quiet = false)
|
||||||
{
|
{
|
||||||
NcaFsHeader sect = nca.Header.GetFsHeader(index);
|
NcaFsHeader sect = nca.GetFsHeader(index);
|
||||||
NcaHashType hashType = sect.HashType;
|
NcaHashType hashType = sect.HashType;
|
||||||
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked;
|
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using LibHac.Common;
|
||||||
using LibHac.Crypto;
|
using LibHac.Crypto;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
|
|
||||||
@ -16,15 +17,22 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
private readonly Memory<byte> _header;
|
private readonly Memory<byte> _header;
|
||||||
|
|
||||||
|
public NcaVersion FormatVersion { get; private set; }
|
||||||
|
|
||||||
public NcaHeader(IStorage headerStorage)
|
public NcaHeader(IStorage headerStorage)
|
||||||
{
|
{
|
||||||
_header = new byte[HeaderSize];
|
_header = new byte[HeaderSize];
|
||||||
headerStorage.Read(0, _header.Span);
|
Span<byte> headerSpan = _header.Span;
|
||||||
|
headerStorage.Read(0, headerSpan).ThrowIfFailure();
|
||||||
|
|
||||||
|
FormatVersion = DetectNcaVersion(headerSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NcaHeader(Keyset keyset, IStorage headerStorage)
|
public NcaHeader(Keyset keyset, IStorage headerStorage)
|
||||||
{
|
{
|
||||||
_header = DecryptHeader(keyset, headerStorage);
|
_header = DecryptHeader(keyset, headerStorage);
|
||||||
|
|
||||||
|
FormatVersion = DetectNcaVersion(_header.Span);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ref NcaHeaderStruct Header => ref Unsafe.As<byte, NcaHeaderStruct>(ref _header.Span[0]);
|
private ref NcaHeaderStruct Header => ref Unsafe.As<byte, NcaHeaderStruct>(ref _header.Span[0]);
|
||||||
@ -104,6 +112,11 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
|
|
||||||
public bool HasRightsId => !Utilities.IsEmpty(RightsId);
|
public bool HasRightsId => !Utilities.IsEmpty(RightsId);
|
||||||
|
|
||||||
|
public Span<byte> GetKeyArea()
|
||||||
|
{
|
||||||
|
return _header.Span.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize);
|
||||||
|
}
|
||||||
|
|
||||||
private ref NcaSectionEntryStruct GetSectionEntry(int index)
|
private ref NcaSectionEntryStruct GetSectionEntry(int index)
|
||||||
{
|
{
|
||||||
ValidateSectionIndex(index);
|
ValidateSectionIndex(index);
|
||||||
@ -161,7 +174,6 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
Span<byte> expectedHash = GetFsHeaderHash(index);
|
Span<byte> expectedHash = GetFsHeaderHash(index);
|
||||||
|
|
||||||
int offset = NcaHeaderStruct.FsHeadersOffset + NcaHeaderStruct.FsHeaderSize * index;
|
int offset = NcaHeaderStruct.FsHeadersOffset + NcaHeaderStruct.FsHeaderSize * index;
|
||||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
|
||||||
Memory<byte> headerData = _header.Slice(offset, NcaHeaderStruct.FsHeaderSize);
|
Memory<byte> headerData = _header.Slice(offset, NcaHeaderStruct.FsHeaderSize);
|
||||||
|
|
||||||
Span<byte> actualHash = stackalloc byte[Sha256.DigestSize];
|
Span<byte> actualHash = stackalloc byte[Sha256.DigestSize];
|
||||||
@ -223,7 +235,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
transform.TransformBlock(buf, i, HeaderSectorSize, 0);
|
transform.TransformBlock(buf, i, HeaderSectorSize, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (version != 0)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"NCA version {version} is not supported.");
|
throw new NotSupportedException($"NCA version {version} is not supported.");
|
||||||
}
|
}
|
||||||
@ -231,6 +243,39 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NcaVersion DetectNcaVersion(ReadOnlySpan<byte> header)
|
||||||
|
{
|
||||||
|
int version = header[0x203] - '0';
|
||||||
|
|
||||||
|
if (version == 3) return NcaVersion.Nca3;
|
||||||
|
if (version == 2) return NcaVersion.Nca2;
|
||||||
|
if (version != 0) return NcaVersion.Unknown;
|
||||||
|
|
||||||
|
// There are multiple versions of NCA0 that each encrypt the key area differently.
|
||||||
|
// Examine the key area to detect which version this NCA is.
|
||||||
|
ReadOnlySpan<byte> keyArea = header.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize);
|
||||||
|
|
||||||
|
// The end of the key area will only be non-zero if it's RSA-OAEP encrypted
|
||||||
|
var zeros = new Buffer16();
|
||||||
|
if (!keyArea.Slice(0x80, 0x10).SequenceEqual(zeros))
|
||||||
|
{
|
||||||
|
return NcaVersion.Nca0RsaOaep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key areas using fixed, unencrypted keys always use the same keys.
|
||||||
|
// Check for these keys by comparing the key area with the known hash of the fixed body keys.
|
||||||
|
Buffer32 hash = default;
|
||||||
|
Sha256.GenerateSha256Hash(keyArea.Slice(0, 0x20), hash);
|
||||||
|
|
||||||
|
if (Nca0FixedBodyKeySha256Hash.SequenceEqual(hash))
|
||||||
|
{
|
||||||
|
return NcaVersion.Nca0FixedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the key area is encrypted the same as modern NCAs.
|
||||||
|
return NcaVersion.Nca0;
|
||||||
|
}
|
||||||
|
|
||||||
public Validity VerifySignature1(byte[] modulus)
|
public Validity VerifySignature1(byte[] modulus)
|
||||||
{
|
{
|
||||||
return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature1.ToArray(), modulus);
|
return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature1.ToArray(), modulus);
|
||||||
@ -241,6 +286,14 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature2.ToArray(), modulus);
|
return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature2.ToArray(), modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsNca0() => FormatVersion >= NcaVersion.Nca0;
|
||||||
|
|
||||||
|
private static ReadOnlySpan<byte> Nca0FixedBodyKeySha256Hash => new byte[]
|
||||||
|
{
|
||||||
|
0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD,
|
||||||
|
0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB
|
||||||
|
};
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 0xC00)]
|
[StructLayout(LayoutKind.Explicit, Size = 0xC00)]
|
||||||
private struct NcaHeaderStruct
|
private struct NcaHeaderStruct
|
||||||
{
|
{
|
||||||
@ -250,6 +303,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
public const int FsHeaderHashOffset = 0x280;
|
public const int FsHeaderHashOffset = 0x280;
|
||||||
public const int FsHeaderHashSize = 0x20;
|
public const int FsHeaderHashSize = 0x20;
|
||||||
public const int KeyAreaOffset = 0x300;
|
public const int KeyAreaOffset = 0x300;
|
||||||
|
public const int KeyAreaSize = 0x100;
|
||||||
public const int FsHeadersOffset = 0x400;
|
public const int FsHeadersOffset = 0x400;
|
||||||
public const int FsHeaderSize = 0x200;
|
public const int FsHeaderSize = 0x200;
|
||||||
|
|
||||||
@ -277,4 +331,14 @@ namespace LibHac.FsSystem.NcaUtils
|
|||||||
public bool IsEnabled;
|
public bool IsEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum NcaVersion
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Nca3,
|
||||||
|
Nca2,
|
||||||
|
Nca0,
|
||||||
|
Nca0FixedKey,
|
||||||
|
Nca0RsaOaep
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using LibHac.Common;
|
using System;
|
||||||
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
|
|
||||||
@ -125,16 +126,30 @@ namespace LibHac.FsSystem.RomFs
|
|||||||
{
|
{
|
||||||
var reader = new FileReader(file);
|
var reader = new FileReader(file);
|
||||||
|
|
||||||
HeaderSize = reader.ReadInt64();
|
HeaderSize = reader.ReadInt32();
|
||||||
DirHashTableOffset = reader.ReadInt64();
|
|
||||||
DirHashTableSize = reader.ReadInt64();
|
Func<long> func;
|
||||||
DirMetaTableOffset = reader.ReadInt64();
|
|
||||||
DirMetaTableSize = reader.ReadInt64();
|
// Old pre-release romfs is exactly the same except the fields in the header are 32-bit instead of 64-bit
|
||||||
FileHashTableOffset = reader.ReadInt64();
|
if (HeaderSize == 0x28)
|
||||||
FileHashTableSize = reader.ReadInt64();
|
{
|
||||||
FileMetaTableOffset = reader.ReadInt64();
|
func = () => reader.ReadInt32();
|
||||||
FileMetaTableSize = reader.ReadInt64();
|
}
|
||||||
DataOffset = reader.ReadInt64();
|
else
|
||||||
|
{
|
||||||
|
func = reader.ReadInt64;
|
||||||
|
reader.Position += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirHashTableOffset = func();
|
||||||
|
DirHashTableSize = func();
|
||||||
|
DirMetaTableOffset = func();
|
||||||
|
DirMetaTableSize = func();
|
||||||
|
FileHashTableOffset = func();
|
||||||
|
FileHashTableSize = func();
|
||||||
|
FileMetaTableOffset = func();
|
||||||
|
FileMetaTableSize = func();
|
||||||
|
DataOffset = func();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,20 @@ namespace LibHac
|
|||||||
|
|
||||||
public RSAParameters EticketExtKeyRsa { get; set; }
|
public RSAParameters EticketExtKeyRsa { get; set; }
|
||||||
|
|
||||||
|
private RSAParameters? _nca0RsaKeyAreaKey;
|
||||||
|
public RSAParameters Nca0RsaKeyAreaKey
|
||||||
|
{
|
||||||
|
// Lazily init this key
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_nca0RsaKeyAreaKey.HasValue)
|
||||||
|
return _nca0RsaKeyAreaKey.Value;
|
||||||
|
|
||||||
|
_nca0RsaKeyAreaKey = CryptoOld.RecoverRsaParameters(BetaNca0Modulus, BetaNca0Exponent);
|
||||||
|
return _nca0RsaKeyAreaKey.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool KeysetForDev;
|
public bool KeysetForDev;
|
||||||
public byte[] NcaHdrFixedKeyModulus
|
public byte[] NcaHdrFixedKeyModulus
|
||||||
{
|
{
|
||||||
@ -118,6 +132,46 @@ namespace LibHac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly byte[] BetaNca0Modulus =
|
||||||
|
{
|
||||||
|
0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49,
|
||||||
|
0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD,
|
||||||
|
0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4,
|
||||||
|
0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B,
|
||||||
|
0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55,
|
||||||
|
0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4,
|
||||||
|
0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F,
|
||||||
|
0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1,
|
||||||
|
0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72,
|
||||||
|
0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D,
|
||||||
|
0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1,
|
||||||
|
0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7,
|
||||||
|
0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13,
|
||||||
|
0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7,
|
||||||
|
0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1,
|
||||||
|
0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5
|
||||||
|
};
|
||||||
|
|
||||||
|
public readonly byte[] BetaNca0Exponent =
|
||||||
|
{
|
||||||
|
0x3C, 0x66, 0x37, 0x44, 0x26, 0xAC, 0x63, 0xD1, 0x30, 0xE6, 0xD4, 0x68, 0xF9, 0xC4, 0xF0, 0xFA,
|
||||||
|
0x03, 0x16, 0xC6, 0x32, 0x81, 0xB0, 0x94, 0xC9, 0xF1, 0x26, 0xC5, 0xE2, 0x2D, 0xF4, 0xB6, 0x3E,
|
||||||
|
0xEB, 0x3D, 0x82, 0x18, 0xA7, 0xC9, 0x8B, 0xD1, 0x03, 0xDD, 0xF2, 0x09, 0x60, 0x02, 0x12, 0xFA,
|
||||||
|
0x8F, 0xE1, 0xA0, 0xF2, 0x82, 0xDC, 0x3D, 0x74, 0x01, 0xBA, 0x02, 0xF0, 0x8D, 0x5B, 0x89, 0x00,
|
||||||
|
0xD1, 0x0B, 0x8F, 0x1C, 0x4A, 0xF3, 0x6E, 0x96, 0x8E, 0x03, 0x19, 0xF0, 0x19, 0x66, 0x58, 0xE9,
|
||||||
|
0xB2, 0x24, 0x37, 0x4B, 0x0A, 0xC6, 0x06, 0x91, 0xBA, 0x92, 0x64, 0x13, 0x5F, 0xF1, 0x4A, 0xBC,
|
||||||
|
0xAB, 0x61, 0xE5, 0x20, 0x08, 0x62, 0xB7, 0x8E, 0x4D, 0x20, 0x30, 0xA7, 0x42, 0x0B, 0x53, 0x58,
|
||||||
|
0xEC, 0xBB, 0x70, 0xCB, 0x2A, 0x56, 0xC7, 0x0C, 0x8B, 0x89, 0xFB, 0x47, 0x6E, 0x58, 0x9C, 0xDD,
|
||||||
|
0xB2, 0xE5, 0x4F, 0x49, 0x52, 0x0B, 0xD9, 0x96, 0x30, 0x8D, 0xDE, 0xC9, 0x0F, 0x6A, 0x82, 0xC7,
|
||||||
|
0xE8, 0x20, 0xB6, 0xB3, 0x95, 0xDD, 0xEB, 0xDF, 0xF7, 0x25, 0x23, 0x6B, 0xF8, 0x5B, 0xD4, 0x81,
|
||||||
|
0x7A, 0xBC, 0x94, 0x13, 0x30, 0x59, 0x28, 0xC8, 0xC9, 0x3A, 0x5D, 0xCC, 0x8D, 0xFD, 0x1A, 0xE1,
|
||||||
|
0xCB, 0xA4, 0x1D, 0xD4, 0x45, 0xF1, 0xBF, 0x87, 0x6C, 0x0E, 0xB1, 0x44, 0xC7, 0x88, 0x62, 0x2B,
|
||||||
|
0x43, 0xAD, 0x75, 0xE6, 0x69, 0xFF, 0xD3, 0x39, 0xF5, 0x7F, 0x2A, 0xA2, 0x5F, 0x7A, 0x5E, 0xE6,
|
||||||
|
0xEF, 0xCB, 0x2F, 0x2C, 0x90, 0xE6, 0x4B, 0x2D, 0x94, 0x62, 0xE8, 0xEC, 0x54, 0x7B, 0x94, 0xB7,
|
||||||
|
0xFB, 0x72, 0x05, 0xFB, 0xB3, 0x23, 0xCA, 0xF8, 0xD4, 0x5C, 0xF6, 0xAC, 0x7D, 0xEC, 0x47, 0xC6,
|
||||||
|
0xD3, 0x22, 0x5D, 0x7C, 0x15, 0xDD, 0x48, 0xE9, 0xBF, 0xA8, 0x99, 0x33, 0x02, 0x79, 0xD3, 0x65
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly byte[] NcaHdrFixedKeyModulusProd =
|
private static readonly byte[] NcaHdrFixedKeyModulusProd =
|
||||||
{
|
{
|
||||||
0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F,
|
0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F,
|
||||||
|
@ -40,14 +40,25 @@ namespace LibHac.Npdm
|
|||||||
int kernelAccessControlOffset = reader.ReadInt32();
|
int kernelAccessControlOffset = reader.ReadInt32();
|
||||||
int kernelAccessControlSize = reader.ReadInt32();
|
int kernelAccessControlSize = reader.ReadInt32();
|
||||||
|
|
||||||
var accessHeader = new FsAccessHeader(stream, offset + fsAccessHeaderOffset);
|
if (fsAccessHeaderSize > 0)
|
||||||
|
{
|
||||||
|
var accessHeader = new FsAccessHeader(stream, offset + fsAccessHeaderOffset);
|
||||||
|
|
||||||
FsVersion = accessHeader.Version;
|
FsVersion = accessHeader.Version;
|
||||||
FsPermissionsBitmask = accessHeader.PermissionsBitmask;
|
FsPermissionsBitmask = accessHeader.PermissionsBitmask;
|
||||||
|
}
|
||||||
|
|
||||||
ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize);
|
if (serviceAccessControlSize > 0)
|
||||||
|
{
|
||||||
|
ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset,
|
||||||
|
serviceAccessControlSize);
|
||||||
|
}
|
||||||
|
|
||||||
KernelAccess = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize);
|
if (kernelAccessControlSize > 0)
|
||||||
|
{
|
||||||
|
KernelAccess =
|
||||||
|
new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ namespace LibHac
|
|||||||
return commonKey;
|
return commonKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CryptoOld.DecryptTitleKey(TitleKeyBlock, keyset.EticketExtKeyRsa);
|
return CryptoOld.DecryptRsaOaep(TitleKeyBlock, keyset.EticketExtKeyRsa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ namespace hactoolnet
|
|||||||
|
|
||||||
if (ctx.Options.Validate && nca.SectionExists(i))
|
if (ctx.Options.Validate && nca.SectionExists(i))
|
||||||
{
|
{
|
||||||
if (nca.Header.GetFsHeader(i).IsPatchSection() && baseNca != null)
|
if (nca.GetFsHeader(i).IsPatchSection() && baseNca != null)
|
||||||
{
|
{
|
||||||
ncaHolder.Validities[i] = baseNca.VerifySection(nca, i, ctx.Logger);
|
ncaHolder.Validities[i] = baseNca.VerifySection(nca, i, ctx.Logger);
|
||||||
}
|
}
|
||||||
@ -261,24 +261,55 @@ namespace hactoolnet
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KeyAreaKeyIndex);
|
PrintKeyArea();
|
||||||
sb.AppendLine("Key Area (Encrypted):");
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine("Key Area (Decrypted):");
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintSections();
|
PrintSections();
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|
||||||
|
void PrintKeyArea()
|
||||||
|
{
|
||||||
|
NcaVersion version = nca.Header.FormatVersion;
|
||||||
|
|
||||||
|
if (version == NcaVersion.Nca0RsaOaep)
|
||||||
|
{
|
||||||
|
sb.AppendLine("Key Area (Encrypted):");
|
||||||
|
PrintItem(sb, colLen, "Key (RSA-OAEP Encrypted):", nca.Header.GetKeyArea().ToArray());
|
||||||
|
|
||||||
|
sb.AppendLine("Key Area (Decrypted):");
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (version == NcaVersion.Nca0FixedKey)
|
||||||
|
{
|
||||||
|
sb.AppendLine("Key Area:");
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, $" Key {i}:", nca.Header.GetEncryptedKey(i).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int keyCount = version == NcaVersion.Nca0 ? 2 : 4;
|
||||||
|
|
||||||
|
PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KeyAreaKeyIndex);
|
||||||
|
sb.AppendLine("Key Area (Encrypted):");
|
||||||
|
for (int i = 0; i < keyCount; i++)
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("Key Area (Decrypted):");
|
||||||
|
for (int i = 0; i < keyCount; i++)
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PrintSections()
|
void PrintSections()
|
||||||
{
|
{
|
||||||
sb.AppendLine("Sections:");
|
sb.AppendLine("Sections:");
|
||||||
@ -287,13 +318,13 @@ namespace hactoolnet
|
|||||||
{
|
{
|
||||||
if (!nca.Header.IsSectionEnabled(i)) continue;
|
if (!nca.Header.IsSectionEnabled(i)) continue;
|
||||||
|
|
||||||
NcaFsHeader sectHeader = nca.Header.GetFsHeader(i);
|
NcaFsHeader sectHeader = nca.GetFsHeader(i);
|
||||||
bool isExefs = nca.Header.ContentType == NcaContentType.Program && i == 0;
|
bool isExefs = nca.Header.ContentType == NcaContentType.Program && i == 0;
|
||||||
|
|
||||||
sb.AppendLine($" Section {i}:");
|
sb.AppendLine($" Section {i}:");
|
||||||
PrintItem(sb, colLen, " Offset:", $"0x{nca.Header.GetSectionStartOffset(i):x12}");
|
PrintItem(sb, colLen, " Offset:", $"0x{nca.Header.GetSectionStartOffset(i):x12}");
|
||||||
PrintItem(sb, colLen, " Size:", $"0x{nca.Header.GetSectionSize(i):x12}");
|
PrintItem(sb, colLen, " Size:", $"0x{nca.Header.GetSectionSize(i):x12}");
|
||||||
PrintItem(sb, colLen, " Partition Type:", (isExefs ? "ExeFS" : sectHeader.FormatType.ToString()) + (sectHeader.IsPatchSection() ? " patch" : ""));
|
PrintItem(sb, colLen, " Partition Type:", GetPartitionType(sectHeader, isExefs, nca.Header.IsNca0()));
|
||||||
PrintItem(sb, colLen, " Section CTR:", $"{sectHeader.Counter:x16}");
|
PrintItem(sb, colLen, " Section CTR:", $"{sectHeader.Counter:x16}");
|
||||||
PrintItem(sb, colLen, " Section Validity:", $"{ncaHolder.Validities[i]}");
|
PrintItem(sb, colLen, " Section Validity:", $"{ncaHolder.Validities[i]}");
|
||||||
|
|
||||||
@ -314,12 +345,20 @@ namespace hactoolnet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static string GetPartitionType(NcaFsHeader fsHeader, bool isExefs, bool isNca0)
|
||||||
|
{
|
||||||
|
if (isExefs) return "ExeFS";
|
||||||
|
if (isNca0 && fsHeader.FormatType == NcaFormatType.Romfs) return "NCA0 RomFS";
|
||||||
|
|
||||||
|
return fsHeader.FormatType + (fsHeader.IsPatchSection() ? " patch" : "");
|
||||||
|
}
|
||||||
|
|
||||||
void PrintSha256Hash(NcaFsHeader sect, int index)
|
void PrintSha256Hash(NcaFsHeader sect, int index)
|
||||||
{
|
{
|
||||||
NcaFsIntegrityInfoSha256 hashInfo = sect.GetIntegrityInfoSha256();
|
NcaFsIntegrityInfoSha256 hashInfo = sect.GetIntegrityInfoSha256();
|
||||||
|
|
||||||
PrintItem(sb, colLen, $" Master Hash{nca.ValidateSectionMasterHash(index).GetValidityString()}:", hashInfo.MasterHash.ToArray());
|
PrintItem(sb, colLen, $" Master Hash{nca.ValidateSectionMasterHash(index).GetValidityString()}:", hashInfo.MasterHash.ToArray());
|
||||||
sb.AppendLine($" Hash Table:");
|
sb.AppendLine(" Hash Table:");
|
||||||
|
|
||||||
PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.GetLevelOffset(0):x12}");
|
PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.GetLevelOffset(0):x12}");
|
||||||
PrintItem(sb, colLen, " Size:", $"0x{hashInfo.GetLevelSize(0):x12}");
|
PrintItem(sb, colLen, " Size:", $"0x{hashInfo.GetLevelSize(0):x12}");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user