mirror of
https://github.com/jakcron/nstool
synced 2024-12-28 16:11:13 +00:00
833 lines
24 KiB
C++
833 lines
24 KiB
C++
#include <iostream>
|
|
#include <sstream>
|
|
#include <fnd/SimpleTextOutput.h>
|
|
#include <nx/NcaUtils.h>
|
|
#include <nx/AesKeygen.h>
|
|
#include "NcaProcess.h"
|
|
#include "PfsProcess.h"
|
|
#include "RomfsProcess.h"
|
|
#include "NpdmProcess.h"
|
|
#include "OffsetAdjustedIFile.h"
|
|
#include "AesCtrWrappedIFile.h"
|
|
#include "HashTreeWrappedIFile.h"
|
|
|
|
const char* getFormatVersionStr(nx::NcaHeader::FormatVersion format_ver)
|
|
{
|
|
const char* str;
|
|
switch (format_ver)
|
|
{
|
|
case (nx::NcaHeader::NCA2_FORMAT):
|
|
str = "NCA2";
|
|
break;
|
|
case (nx::NcaHeader::NCA3_FORMAT):
|
|
str = "NCA3";
|
|
break;
|
|
default:
|
|
str = "Unknown";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
const char* getDistributionTypeStr(nx::nca::DistributionType dist_type)
|
|
{
|
|
const char* str;
|
|
switch (dist_type)
|
|
{
|
|
case (nx::nca::DIST_DOWNLOAD):
|
|
str = "Download";
|
|
break;
|
|
case (nx::nca::DIST_GAME_CARD):
|
|
str = "Game Card";
|
|
break;
|
|
default:
|
|
str = "Unknown";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
|
|
const char* getContentTypeStr(nx::nca::ContentType cont_type)
|
|
{
|
|
const char* str;
|
|
switch (cont_type)
|
|
{
|
|
case (nx::nca::TYPE_PROGRAM):
|
|
str = "Program";
|
|
break;
|
|
case (nx::nca::TYPE_META):
|
|
str = "Meta";
|
|
break;
|
|
case (nx::nca::TYPE_CONTROL):
|
|
str = "Control";
|
|
break;
|
|
case (nx::nca::TYPE_MANUAL):
|
|
str = "Manual";
|
|
break;
|
|
case (nx::nca::TYPE_DATA):
|
|
str = "Data";
|
|
break;
|
|
case (nx::nca::TYPE_PUBLIC_DATA):
|
|
str = "PublicData";
|
|
break;
|
|
default:
|
|
str = "Unknown";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
const char* getEncryptionTypeStr(nx::nca::EncryptionType enc_type)
|
|
{
|
|
const char* str;
|
|
switch (enc_type)
|
|
{
|
|
case (nx::nca::CRYPT_AUTO):
|
|
str = "Auto";
|
|
break;
|
|
case (nx::nca::CRYPT_NONE):
|
|
str = "None";
|
|
break;
|
|
case (nx::nca::CRYPT_AESXTS):
|
|
str = "AesXts";
|
|
break;
|
|
case (nx::nca::CRYPT_AESCTR):
|
|
str = "AesCtr";
|
|
break;
|
|
case (nx::nca::CRYPT_AESCTREX):
|
|
str = "AesCtrEx";
|
|
break;
|
|
default:
|
|
str = "Unknown";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
inline const char* getHashTypeStr(nx::nca::HashType hash_type)
|
|
{
|
|
const char* str;
|
|
switch (hash_type)
|
|
{
|
|
case (nx::nca::HASH_AUTO):
|
|
str = "Auto";
|
|
break;
|
|
case (nx::nca::HASH_NONE):
|
|
str = "None";
|
|
break;
|
|
case (nx::nca::HASH_HIERARCHICAL_SHA256):
|
|
str = "HierarchicalSha256";
|
|
break;
|
|
case (nx::nca::HASH_HIERARCHICAL_INTERGRITY):
|
|
str = "HierarchicalIntegrity";
|
|
break;
|
|
default:
|
|
str = "Unknown";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
inline const char* getFormatTypeStr(nx::nca::FormatType format_type)
|
|
{
|
|
const char* str;
|
|
switch (format_type)
|
|
{
|
|
case (nx::nca::FORMAT_ROMFS):
|
|
str = "RomFs";
|
|
break;
|
|
case (nx::nca::FORMAT_PFS0):
|
|
str = "PartitionFs";
|
|
break;
|
|
default:
|
|
str = "Unknown";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
inline const char* getKaekIndexStr(nx::nca::KeyAreaEncryptionKeyIndex keak_index)
|
|
{
|
|
const char* str;
|
|
switch (keak_index)
|
|
{
|
|
case (nx::nca::KAEK_IDX_APPLICATION):
|
|
str = "Application";
|
|
break;
|
|
case (nx::nca::KAEK_IDX_OCEAN):
|
|
str = "Ocean";
|
|
break;
|
|
case (nx::nca::KAEK_IDX_SYSTEM):
|
|
str = "System";
|
|
break;
|
|
default:
|
|
str = "Unknown";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
inline const char* getContentTypeForMountStr(nx::nca::ContentType cont_type)
|
|
{
|
|
const char* str;
|
|
switch (cont_type)
|
|
{
|
|
case (nx::nca::TYPE_PROGRAM):
|
|
str = "program";
|
|
break;
|
|
case (nx::nca::TYPE_META):
|
|
str = "meta";
|
|
break;
|
|
case (nx::nca::TYPE_CONTROL):
|
|
str = "control";
|
|
break;
|
|
case (nx::nca::TYPE_MANUAL):
|
|
str = "manual";
|
|
break;
|
|
case (nx::nca::TYPE_DATA):
|
|
str = "data";
|
|
break;
|
|
case (nx::nca::TYPE_PUBLIC_DATA):
|
|
str = "publicData";
|
|
break;
|
|
default:
|
|
str = "";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
const char* getProgramPartitionNameStr(size_t i)
|
|
{
|
|
const char* str;
|
|
switch (i)
|
|
{
|
|
case (nx::nca::PARTITION_CODE):
|
|
str = "code";
|
|
break;
|
|
case (nx::nca::PARTITION_DATA):
|
|
str = "data";
|
|
break;
|
|
case (nx::nca::PARTITION_LOGO):
|
|
str = "logo";
|
|
break;
|
|
default:
|
|
str = "";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
|
|
NcaProcess::NcaProcess() :
|
|
mFile(nullptr),
|
|
mOwnIFile(false),
|
|
mKeyset(nullptr),
|
|
mCliOutputMode(_BIT(OUTPUT_BASIC)),
|
|
mVerify(false),
|
|
mListFs(false)
|
|
{
|
|
for (size_t i = 0; i < nx::nca::kPartitionNum; i++)
|
|
{
|
|
mPartitionPath[i].doExtract = false;
|
|
mPartitions[i].reader = nullptr;
|
|
}
|
|
}
|
|
|
|
NcaProcess::~NcaProcess()
|
|
{
|
|
if (mOwnIFile)
|
|
{
|
|
delete mFile;
|
|
}
|
|
|
|
for (size_t i = 0; i < nx::nca::kPartitionNum; i++)
|
|
{
|
|
if (mPartitions[i].reader != nullptr)
|
|
{
|
|
delete mPartitions[i].reader;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NcaProcess::process()
|
|
{
|
|
fnd::MemoryBlob scratch;
|
|
|
|
if (mFile == nullptr)
|
|
{
|
|
throw fnd::Exception(kModuleName, "No file reader set.");
|
|
}
|
|
|
|
// read header block
|
|
mFile->read((byte_t*)&mHdrBlock, 0, sizeof(nx::sNcaHeaderBlock));
|
|
|
|
// decrypt header block
|
|
nx::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key);
|
|
|
|
// generate header hash
|
|
crypto::sha::Sha256((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader), mHdrHash.bytes);
|
|
|
|
// proccess main header
|
|
mHdr.importBinary((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader));
|
|
|
|
// determine keys
|
|
generateNcaBodyEncryptionKeys();
|
|
|
|
// import/generate fs header data
|
|
generatePartitionConfiguration();
|
|
|
|
// validate signatures
|
|
if (mVerify)
|
|
validateNcaSignatures();
|
|
|
|
// display header
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
|
|
displayHeader();
|
|
|
|
// process partition
|
|
processPartitions();
|
|
|
|
/*
|
|
NCA is a file container
|
|
A hashed and signed file container
|
|
|
|
To verify a NCA: (R=regular step)
|
|
1 - decrypt header (R)
|
|
2 - verify signature[0]
|
|
3 - validate hashes of fs_headers
|
|
4 - determine how to read/decrypt the partitions (R)
|
|
5 - validate the partitions depending on their hash method
|
|
6 - if this NCA is a Program or Patch, open main.npdm from partition0
|
|
7 - validate ACID
|
|
8 - use public key in ACID to verify NCA signature[1]
|
|
|
|
Things to consider
|
|
* because of the manditory steps between verifcation steps
|
|
the NCA should be ready to be pulled to pieces before any printing is done
|
|
so the verification text can be presented without interuption
|
|
|
|
*/
|
|
}
|
|
|
|
void NcaProcess::setInputFile(fnd::IFile* file, bool ownIFile)
|
|
{
|
|
mFile = file;
|
|
mOwnIFile = ownIFile;
|
|
}
|
|
|
|
void NcaProcess::setKeyset(const sKeyset* keyset)
|
|
{
|
|
mKeyset = keyset;
|
|
}
|
|
|
|
void NcaProcess::setCliOutputMode(CliOutputMode type)
|
|
{
|
|
mCliOutputMode = type;
|
|
}
|
|
|
|
void NcaProcess::setVerifyMode(bool verify)
|
|
{
|
|
mVerify = verify;
|
|
}
|
|
|
|
void NcaProcess::setPartition0ExtractPath(const std::string& path)
|
|
{
|
|
mPartitionPath[0].path = path;
|
|
mPartitionPath[0].doExtract = true;
|
|
}
|
|
|
|
void NcaProcess::setPartition1ExtractPath(const std::string& path)
|
|
{
|
|
mPartitionPath[1].path = path;
|
|
mPartitionPath[1].doExtract = true;
|
|
}
|
|
|
|
void NcaProcess::setPartition2ExtractPath(const std::string& path)
|
|
{
|
|
mPartitionPath[2].path = path;
|
|
mPartitionPath[2].doExtract = true;
|
|
}
|
|
|
|
void NcaProcess::setPartition3ExtractPath(const std::string& path)
|
|
{
|
|
mPartitionPath[3].path = path;
|
|
mPartitionPath[3].doExtract = true;
|
|
}
|
|
|
|
void NcaProcess::setListFs(bool list_fs)
|
|
{
|
|
mListFs = list_fs;
|
|
}
|
|
|
|
void NcaProcess::generateNcaBodyEncryptionKeys()
|
|
{
|
|
// create zeros key
|
|
crypto::aes::sAes128Key zero_aesctr_key;
|
|
memset(zero_aesctr_key.key, 0, sizeof(zero_aesctr_key));
|
|
crypto::aes::sAesXts128Key zero_aesxts_key;
|
|
memset(zero_aesxts_key.key, 0, sizeof(zero_aesxts_key));
|
|
|
|
// get key data from header
|
|
byte_t masterkey_rev = nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration());
|
|
byte_t keak_index = mHdr.getKaekIndex();
|
|
|
|
// process key area
|
|
sKeys::sKeyAreaKey keak;
|
|
for (size_t i = 0; i < nx::nca::kAesKeyNum; i++)
|
|
{
|
|
if (mHdr.getEncAesKeys()[i] != zero_aesctr_key)
|
|
{
|
|
keak.index = (byte_t)i;
|
|
keak.enc = mHdr.getEncAesKeys()[i];
|
|
if (i < 4 && mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_aesctr_key)
|
|
{
|
|
keak.decrypted = true;
|
|
nx::AesKeygen::generateKey(keak.dec.key, keak.enc.key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key);
|
|
}
|
|
else
|
|
{
|
|
keak.decrypted = false;
|
|
}
|
|
mBodyKeys.keak_list.addElement(keak);
|
|
}
|
|
}
|
|
|
|
// set flag to indicate that the keys are not available
|
|
mBodyKeys.aes_ctr.isSet = false;
|
|
mBodyKeys.aes_xts.isSet = false;
|
|
|
|
// if this has a rights id, the key needs to be sourced from a ticket
|
|
if (mHdr.hasRightsId() == true)
|
|
{
|
|
// if the titlekey_kek is available
|
|
if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_key)
|
|
{
|
|
// the title key is provided (sourced from ticket)
|
|
if (mKeyset->nca.manual_title_key_aesctr != zero_aesctr_key)
|
|
{
|
|
nx::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mKeyset->nca.manual_title_key_aesctr.key, mKeyset->ticket.titlekey_kek[masterkey_rev].key);
|
|
mBodyKeys.aes_ctr.isSet = true;
|
|
}
|
|
if (mKeyset->nca.manual_title_key_aesxts != zero_aesxts_key)
|
|
{
|
|
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mKeyset->nca.manual_title_key_aesxts.key[0], mKeyset->ticket.titlekey_kek[masterkey_rev].key);
|
|
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mKeyset->nca.manual_title_key_aesxts.key[1], mKeyset->ticket.titlekey_kek[masterkey_rev].key);
|
|
mBodyKeys.aes_xts.isSet = true;
|
|
}
|
|
}
|
|
}
|
|
// otherwise decrypt key area
|
|
else
|
|
{
|
|
crypto::aes::sAes128Key keak_aesctr_key = zero_aesctr_key;
|
|
crypto::aes::sAesXts128Key keak_aesxts_key = zero_aesxts_key;
|
|
for (size_t i = 0; i < mBodyKeys.keak_list.getSize(); i++)
|
|
{
|
|
if (mBodyKeys.keak_list[i].index == nx::nca::KEY_AESCTR && mBodyKeys.keak_list[i].decrypted)
|
|
{
|
|
keak_aesctr_key = mBodyKeys.keak_list[i].dec;
|
|
}
|
|
else if (mBodyKeys.keak_list[i].index == nx::nca::KEY_AESXTS_0 && mBodyKeys.keak_list[i].decrypted)
|
|
{
|
|
memcpy(keak_aesxts_key.key[0], mBodyKeys.keak_list[i].dec.key, sizeof(crypto::aes::sAes128Key));
|
|
}
|
|
else if (mBodyKeys.keak_list[i].index == nx::nca::KEY_AESXTS_1 && mBodyKeys.keak_list[i].decrypted)
|
|
{
|
|
memcpy(keak_aesxts_key.key[1], mBodyKeys.keak_list[i].dec.key, sizeof(crypto::aes::sAes128Key));
|
|
}
|
|
}
|
|
|
|
if (keak_aesctr_key != zero_aesctr_key)
|
|
{
|
|
mBodyKeys.aes_ctr = keak_aesctr_key;
|
|
}
|
|
if (keak_aesxts_key != zero_aesxts_key)
|
|
{
|
|
mBodyKeys.aes_xts = keak_aesxts_key;
|
|
}
|
|
}
|
|
|
|
// if the keys weren't generated, check if the keys were supplied by the user
|
|
if (mBodyKeys.aes_ctr.isSet == false && mKeyset->nca.manual_body_key_aesctr != zero_aesctr_key)
|
|
{
|
|
mBodyKeys.aes_ctr = mKeyset->nca.manual_body_key_aesctr;
|
|
}
|
|
if (mBodyKeys.aes_xts.isSet == false && mKeyset->nca.manual_body_key_aesxts != zero_aesxts_key)
|
|
{
|
|
mBodyKeys.aes_xts = mKeyset->nca.manual_body_key_aesxts;
|
|
}
|
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_KEY_DATA))
|
|
{
|
|
if (mBodyKeys.aes_ctr.isSet)
|
|
{
|
|
printf("[NCA Body Key]\n");
|
|
printf(" AES-CTR Key: ");
|
|
fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_ctr.var.key, sizeof(mBodyKeys.aes_ctr.var));
|
|
}
|
|
|
|
if (mBodyKeys.aes_xts.isSet)
|
|
{
|
|
printf("[NCA Body Key]\n");
|
|
printf(" AES-XTS Key0: ");
|
|
fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_xts.var.key[0], sizeof(mBodyKeys.aes_ctr.var));
|
|
printf(" AES-XTS Key1: ");
|
|
fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_xts.var.key[1], sizeof(mBodyKeys.aes_ctr.var));
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void NcaProcess::generatePartitionConfiguration()
|
|
{
|
|
std::stringstream error;
|
|
|
|
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
|
|
{
|
|
// get reference to relevant structures
|
|
const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
|
|
nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index];
|
|
|
|
// output structure
|
|
sPartitionInfo& info = mPartitions[partition.index];
|
|
|
|
// validate header hash
|
|
crypto::sha::sSha256Hash calc_hash;
|
|
crypto::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.index], sizeof(nx::sNcaFsHeader), calc_hash.bytes);
|
|
if (calc_hash.compare(partition.hash) == false)
|
|
{
|
|
error.clear();
|
|
error << "NCA FS Header [" << partition.index << "] Hash: FAIL \n";
|
|
throw fnd::Exception(kModuleName, error.str());
|
|
}
|
|
|
|
|
|
if (fs_header.version.get() != nx::nca::kDefaultFsHeaderVersion)
|
|
{
|
|
error.clear();
|
|
error << "NCA FS Header [" << partition.index << "] Version(" << fs_header.version.get() << "): UNSUPPORTED";
|
|
throw fnd::Exception(kModuleName, error.str());
|
|
}
|
|
|
|
// setup AES-CTR
|
|
nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, info.aes_ctr.iv);
|
|
|
|
// save partition config
|
|
info.reader = nullptr;
|
|
info.offset = partition.offset;
|
|
info.size = partition.size;
|
|
info.format_type = (nx::nca::FormatType)fs_header.format_type;
|
|
info.hash_type = (nx::nca::HashType)fs_header.hash_type;
|
|
info.enc_type = (nx::nca::EncryptionType)fs_header.encryption_type;
|
|
if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256)
|
|
info.hash_tree_meta.importHierarchicalSha256Header(nx::HierarchicalSha256Header(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen));
|
|
else if (info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY)
|
|
info.hash_tree_meta.importHierarchicalIntergityHeader(nx::HierarchicalIntegrityHeader(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen));
|
|
|
|
|
|
// create reader
|
|
try
|
|
{
|
|
// filter out unrecognised format types
|
|
switch (info.format_type)
|
|
{
|
|
case (nx::nca::FORMAT_PFS0):
|
|
case (nx::nca::FORMAT_ROMFS):
|
|
break;
|
|
default:
|
|
error.clear();
|
|
error << "FormatType(" << info.format_type << "): UNKNOWN";
|
|
throw fnd::Exception(kModuleName, error.str());
|
|
}
|
|
|
|
// create reader based on encryption type0
|
|
if (info.enc_type == nx::nca::CRYPT_NONE)
|
|
{
|
|
info.reader = new OffsetAdjustedIFile(mFile, SHARED_IFILE, info.offset, info.size);
|
|
}
|
|
else if (info.enc_type == nx::nca::CRYPT_AESCTR)
|
|
{
|
|
if (mBodyKeys.aes_ctr.isSet == false)
|
|
throw fnd::Exception(kModuleName, "AES-CTR Key was not determined");
|
|
info.reader = new OffsetAdjustedIFile(new AesCtrWrappedIFile(mFile, SHARED_IFILE, mBodyKeys.aes_ctr.var, info.aes_ctr), OWN_IFILE, info.offset, info.size);
|
|
}
|
|
else if (info.enc_type == nx::nca::CRYPT_AESXTS || info.enc_type == nx::nca::CRYPT_AESCTREX)
|
|
{
|
|
error.clear();
|
|
error << "EncryptionType(" << getEncryptionTypeStr(info.enc_type) << "): UNSUPPORTED";
|
|
throw fnd::Exception(kModuleName, error.str());
|
|
}
|
|
else
|
|
{
|
|
error.clear();
|
|
error << "EncryptionType(" << info.enc_type << "): UNKNOWN";
|
|
throw fnd::Exception(kModuleName, error.str());
|
|
}
|
|
|
|
// filter out unrecognised hash types, and hash based readers
|
|
if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256 || info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY)
|
|
{
|
|
fnd::IFile* tmp = info.reader;
|
|
info.reader = nullptr;
|
|
info.reader = new HashTreeWrappedIFile(tmp, OWN_IFILE, info.hash_tree_meta);
|
|
}
|
|
else if (info.hash_type != nx::nca::HASH_NONE)
|
|
{
|
|
error.clear();
|
|
error << "HashType(" << info.hash_type << "): UNKNOWN";
|
|
throw fnd::Exception(kModuleName, error.str());
|
|
}
|
|
}
|
|
catch (const fnd::Exception& e)
|
|
{
|
|
info.fail_reason = std::string(e.error());
|
|
if (info.reader != nullptr)
|
|
delete info.reader;
|
|
info.reader = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NcaProcess::validateNcaSignatures()
|
|
{
|
|
// validate signature[0]
|
|
if (crypto::rsa::pss::rsaVerify(mKeyset->nca.header_sign_key, crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0)
|
|
{
|
|
printf("[WARNING] NCA Header Main Signature: FAIL \n");
|
|
}
|
|
|
|
// validate signature[1]
|
|
if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM)
|
|
{
|
|
if (mPartitions[nx::nca::PARTITION_CODE].format_type == nx::nca::FORMAT_PFS0)
|
|
{
|
|
if (mPartitions[nx::nca::PARTITION_CODE].reader != nullptr)
|
|
{
|
|
PfsProcess exefs;
|
|
exefs.setInputFile(mPartitions[nx::nca::PARTITION_CODE].reader, SHARED_IFILE);
|
|
exefs.setCliOutputMode(0);
|
|
exefs.process();
|
|
|
|
// open main.npdm
|
|
if (exefs.getPfsHeader().getFileList().hasElement(kNpdmExefsPath) == true)
|
|
{
|
|
const nx::PfsHeader::sFile& file = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)];
|
|
|
|
NpdmProcess npdm;
|
|
npdm.setInputFile(new OffsetAdjustedIFile(mPartitions[nx::nca::PARTITION_CODE].reader, SHARED_IFILE, file.offset, file.size), OWN_IFILE);
|
|
npdm.setCliOutputMode(0);
|
|
npdm.process();
|
|
|
|
if (crypto::rsa::pss::rsaVerify(npdm.getNpdmBinary().getAcid().getNcaHeader2RsaKey(), crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0)
|
|
{
|
|
printf("[WARNING] NCA Header ACID Signature: FAIL \n");
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
printf("[WARNING] NCA Header ACID Signature: FAIL (\"%s\" not present in ExeFs)\n", kNpdmExefsPath.c_str());
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
printf("[WARNING] NCA Header ACID Signature: FAIL (ExeFs unreadable)\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("[WARNING] NCA Header ACID Signature: FAIL (No ExeFs partition)\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void NcaProcess::displayHeader()
|
|
{
|
|
#define _HEXDUMP_U(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02X", var[a__a__A]); } while(0)
|
|
#define _HEXDUMP_L(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02x", var[a__a__A]); } while(0)
|
|
|
|
printf("[NCA Header]\n");
|
|
printf(" Format Type: %s\n", getFormatVersionStr(mHdr.getFormatVersion()));
|
|
printf(" Dist. Type: %s\n", getDistributionTypeStr(mHdr.getDistributionType()));
|
|
printf(" Content Type: %s\n", getContentTypeStr(mHdr.getContentType()));
|
|
printf(" Key Generation: %d\n", mHdr.getKeyGeneration());
|
|
printf(" Kaek Index: %s (%d)\n", getKaekIndexStr((nx::nca::KeyAreaEncryptionKeyIndex)mHdr.getKaekIndex()), mHdr.getKaekIndex());
|
|
printf(" Size: 0x%" PRIx64 "\n", mHdr.getContentSize());
|
|
printf(" ProgID: 0x%016" PRIx64 "\n", mHdr.getProgramId());
|
|
printf(" Content Index: %" PRIu32 "\n", mHdr.getContentIndex());
|
|
#define _SPLIT_VER(ver) ( (ver>>24) & 0xff), ( (ver>>16) & 0xff), ( (ver>>8) & 0xff)
|
|
printf(" SdkAddon Ver.: v%" PRIu32 " (%d.%d.%d)\n", mHdr.getSdkAddonVersion(), _SPLIT_VER(mHdr.getSdkAddonVersion()));
|
|
#undef _SPLIT_VER
|
|
if (mHdr.hasRightsId())
|
|
{
|
|
printf(" RightsId: ");
|
|
fnd::SimpleTextOutput::hexDump(mHdr.getRightsId(), nx::nca::kRightsIdLen);
|
|
}
|
|
|
|
|
|
if (mBodyKeys.keak_list.getSize() > 0 && _HAS_BIT(mCliOutputMode, OUTPUT_KEY_DATA))
|
|
{
|
|
printf(" Key Area: \n");
|
|
printf(" <--------------------------------------------------------------------------->\n");
|
|
printf(" | IDX | ENCRYPTED KEY | DECRYPTED KEY |\n");
|
|
printf(" |-----|----------------------------------|----------------------------------|\n");
|
|
for (size_t i = 0; i < mBodyKeys.keak_list.getSize(); i++)
|
|
{
|
|
printf(" | %3d | ", mBodyKeys.keak_list[i].index);
|
|
|
|
_HEXDUMP_L(mBodyKeys.keak_list[i].enc.key, 16);
|
|
//for (size_t j = 0; j < 16; j++) printf("%02x", mBodyKeys.keak_list[i].enc.key[j]);
|
|
|
|
printf(" | ");
|
|
|
|
if (mBodyKeys.keak_list[i].decrypted)
|
|
_HEXDUMP_L(mBodyKeys.keak_list[i].dec.key, 16);
|
|
else
|
|
printf("<unable to decrypt> ");
|
|
|
|
printf(" |\n");
|
|
}
|
|
printf(" <--------------------------------------------------------------------------->\n");
|
|
}
|
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
|
|
{
|
|
printf(" Partitions:\n");
|
|
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
|
|
{
|
|
sPartitionInfo& info = mPartitions[i];
|
|
|
|
printf(" %d:\n", (int)i);
|
|
printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)info.offset);
|
|
printf(" Size: 0x%" PRIx64 "\n", (uint64_t)info.size);
|
|
printf(" Format Type: %s\n", getFormatTypeStr(info.format_type));
|
|
printf(" Hash Type: %s\n", getHashTypeStr(info.hash_type));
|
|
printf(" Enc. Type: %s\n", getEncryptionTypeStr(info.enc_type));
|
|
if (info.enc_type == nx::nca::CRYPT_AESCTR)
|
|
{
|
|
printf(" AES-CTR: ");
|
|
crypto::aes::sAesIvCtr ctr;
|
|
crypto::aes::AesIncrementCounter(info.aes_ctr.iv, info.offset>>4, ctr.iv);
|
|
fnd::SimpleTextOutput::hexDump(ctr.iv, sizeof(crypto::aes::sAesIvCtr));
|
|
}
|
|
if (info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY)
|
|
{
|
|
HashTreeMeta& hash_hdr = info.hash_tree_meta;
|
|
printf(" HierarchicalIntegrity Header:\n");
|
|
//printf(" TypeId: 0x%x\n", hash_hdr.type_id.get());
|
|
//printf(" MasterHashSize: 0x%x\n", hash_hdr.master_hash_size.get());
|
|
//printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize());
|
|
for (size_t j = 0; j < hash_hdr.getHashLayerInfo().getSize(); j++)
|
|
{
|
|
printf(" Hash Layer %d:\n", (int)j);
|
|
printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[j].offset);
|
|
printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[j].size);
|
|
printf(" BlockSize: 0x%" PRIx32 "\n", (uint32_t)hash_hdr.getHashLayerInfo()[j].block_size);
|
|
}
|
|
|
|
printf(" Data Layer:\n");
|
|
printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().offset);
|
|
printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().size);
|
|
printf(" BlockSize: 0x%" PRIx32 "\n", (uint32_t)hash_hdr.getDataLayer().block_size);
|
|
for (size_t j = 0; j < hash_hdr.getMasterHashList().getSize(); j++)
|
|
{
|
|
printf(" Master Hash %d: ", (int)j);
|
|
fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHashList()[j].bytes, sizeof(crypto::sha::sSha256Hash));
|
|
}
|
|
}
|
|
else if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256)
|
|
{
|
|
HashTreeMeta& hash_hdr = info.hash_tree_meta;
|
|
printf(" HierarchicalSha256 Header:\n");
|
|
printf(" Master Hash: ");
|
|
fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHashList()[0].bytes, sizeof(crypto::sha::sSha256Hash));
|
|
printf(" HashBlockSize: 0x%" PRIx32 "\n", (uint32_t)hash_hdr.getDataLayer().block_size);
|
|
//printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize());
|
|
printf(" Hash Layer:\n");
|
|
printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[0].offset);
|
|
printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[0].size);
|
|
printf(" Data Layer:\n");
|
|
printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().offset);
|
|
printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().size);
|
|
}
|
|
//else
|
|
//{
|
|
// printf(" Hash Superblock:\n");
|
|
// fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen);
|
|
//}
|
|
}
|
|
}
|
|
|
|
#undef _HEXDUMP_U
|
|
#undef _HEXDUMP_L
|
|
}
|
|
|
|
|
|
void NcaProcess::processPartitions()
|
|
{
|
|
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
|
|
{
|
|
size_t index = mHdr.getPartitions()[i].index;
|
|
struct sPartitionInfo& partition = mPartitions[index];
|
|
|
|
// if the reader is null, skip
|
|
if (partition.reader == nullptr)
|
|
{
|
|
printf("[WARNING] NCA Partition %d not readable.", (int)index);
|
|
if (partition.fail_reason.empty() == false)
|
|
{
|
|
printf(" (%s)", partition.fail_reason.c_str());
|
|
}
|
|
printf("\n");
|
|
continue;
|
|
}
|
|
|
|
if (partition.format_type == nx::nca::FORMAT_PFS0)
|
|
{
|
|
PfsProcess pfs;
|
|
pfs.setInputFile(partition.reader, SHARED_IFILE);
|
|
pfs.setCliOutputMode(mCliOutputMode);
|
|
pfs.setListFs(mListFs);
|
|
if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM)
|
|
{
|
|
pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(i)));
|
|
}
|
|
else
|
|
{
|
|
pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/");
|
|
}
|
|
|
|
if (mPartitionPath[index].doExtract)
|
|
pfs.setExtractPath(mPartitionPath[index].path);
|
|
//printf("pfs.process(%lx)\n",partition.data_offset);
|
|
pfs.process();
|
|
//printf("pfs.process() end\n");
|
|
}
|
|
else if (partition.format_type == nx::nca::FORMAT_ROMFS)
|
|
{
|
|
RomfsProcess romfs;
|
|
romfs.setInputFile(partition.reader, SHARED_IFILE);
|
|
romfs.setCliOutputMode(mCliOutputMode);
|
|
romfs.setListFs(mListFs);
|
|
if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM)
|
|
{
|
|
romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(i)));
|
|
}
|
|
else
|
|
{
|
|
romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/");
|
|
}
|
|
|
|
if (mPartitionPath[index].doExtract)
|
|
romfs.setExtractPath(mPartitionPath[index].path);
|
|
//printf("romfs.process(%lx)\n", partition.data_offset);
|
|
romfs.process();
|
|
//printf("romfs.process() end\n");
|
|
}
|
|
}
|
|
}
|