nstool/src/NcaProcess.cpp

861 lines
32 KiB
C++
Raw Normal View History

#include "NcaProcess.h"
#include "MetaProcess.h"
#include "util.h"
#include <tc/io/StreamUtil.h>
#include <nn/hac/ContentArchiveUtil.h>
2019-01-31 09:10:19 +00:00
#include <nn/hac/AesKeygen.h>
2021-10-16 06:13:11 +00:00
#include <nn/hac/HierarchicalSha256Stream.h>
#include <nn/hac/HierarchicalIntegrityStream.h>
2022-03-12 05:30:43 +00:00
#include <nn/hac/PartitionFsSnapshotGenerator.h>
#include <nn/hac/RomFsSnapshotGenerator.h>
#include <nn/hac/CombinedFsSnapshotGenerator.h>
2021-10-16 06:13:11 +00:00
2021-11-14 09:58:37 +00:00
#include <nn/hac/BucketTree.h>
#include <nn/hac/define/indirectstorage.h>
#include <nn/hac/SparseStorageStream.h>
namespace nstool { namespace utils {
class ZerosStream : public tc::io::IStream
{
public:
ZerosStream() : mPosition(0) {}
bool canRead() const { return true; }
bool canWrite() const { return false; }
bool canSeek() const { return true; }
int64_t length() { return std::numeric_limits<int64_t>::max(); }
int64_t position() { return mPosition; }
size_t read(byte_t* ptr, size_t count);
size_t write(const byte_t* ptr, size_t count);
int64_t seek(int64_t offset, tc::io::SeekOrigin origin);
void setLength(int64_t length) { return; }
void flush() { return; }
void dispose() { return; }
private:
int64_t mPosition;
};
size_t ZerosStream::read(byte_t* ptr, size_t count)
{
// track read_count
size_t data_read_count = 0;
// get predicted read count
data_read_count = tc::io::IOUtil::getReadableCount(this->length(), this->position(), count);
// if count is 0 just return
if (data_read_count == 0) return data_read_count;
// read zeros
memset(ptr, 0x00, data_read_count);
// update stream positiion
this->seek(data_read_count, tc::io::SeekOrigin::Current);
// return data read count
return data_read_count;
}
size_t ZerosStream::write(const byte_t* ptr, size_t count)
{
throw tc::NotImplementedException("ZerosStream::write()", "write is not supported for ZerosStream");
}
int64_t ZerosStream::seek(int64_t offset, tc::io::SeekOrigin origin)
{
mPosition = tc::io::StreamUtil::getSeekResult(offset, origin, this->position(), this->length());
return mPosition;
}
}}
nstool::NcaProcess::NcaProcess() :
2021-10-15 09:29:29 +00:00
mModuleName("nstool::NcaProcess"),
2019-01-31 09:10:19 +00:00
mFile(),
mCliOutputMode(true, false, false, false),
2019-01-31 09:10:19 +00:00
mVerify(false),
2021-10-15 09:29:29 +00:00
mFileSystem(),
mFsProcess()
2019-01-31 09:10:19 +00:00
{
}
void nstool::NcaProcess::process()
2019-01-31 09:10:19 +00:00
{
// import header
importHeader();
// determine keys
generateNcaBodyEncryptionKeys();
// import/generate fs header data
generatePartitionConfiguration();
// validate signatures
if (mVerify)
validateNcaSignatures();
// display header
if (mCliOutputMode.show_basic_info)
2019-01-31 09:10:19 +00:00
displayHeader();
// process partition
processPartitions();
}
void nstool::NcaProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
2019-01-31 09:10:19 +00:00
{
mFile = file;
}
void nstool::NcaProcess::setKeyCfg(const KeyBag& keycfg)
2019-01-31 09:10:19 +00:00
{
mKeyCfg = keycfg;
}
void nstool::NcaProcess::setCliOutputMode(CliOutputMode type)
2019-01-31 09:10:19 +00:00
{
mCliOutputMode = type;
}
void nstool::NcaProcess::setVerifyMode(bool verify)
2019-01-31 09:10:19 +00:00
{
mVerify = verify;
}
void nstool::NcaProcess::setArchiveJobs(const std::vector<nstool::ArchiveJob>& jobs)
{
mFsProcess.setArchiveJobs(jobs);
}
2021-10-15 09:29:29 +00:00
void nstool::NcaProcess::setShowFsTree(bool show_fs_tree)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mFsProcess.setShowFsTree(show_fs_tree);
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
void nstool::NcaProcess::setFsRootLabel(const std::string& root_label)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mFsProcess.setFsRootLabel(root_label);
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
void nstool::NcaProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mFsProcess.setExtractJobs(extract_jobs);
2019-01-31 09:10:19 +00:00
}
2022-03-12 05:30:43 +00:00
const std::shared_ptr<tc::io::IFileSystem>& nstool::NcaProcess::getFileSystem() const
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
return mFileSystem;
2019-01-31 09:10:19 +00:00
}
void nstool::NcaProcess::importHeader()
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if (mFile == nullptr)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc::Exception(mModuleName, "No file reader set.");
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
2019-01-31 09:10:19 +00:00
// read header block
2021-10-15 09:29:29 +00:00
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sContentArchiveHeaderBlock)))
{
throw tc::Exception(mModuleName, "Corrupt NCA: File too small.");
}
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read((byte_t*)(&mHdrBlock), sizeof(nn::hac::sContentArchiveHeaderBlock));
2019-01-31 09:10:19 +00:00
// decrypt header block
2021-10-15 09:29:29 +00:00
if (mKeyCfg.nca_header_key.isNull())
{
throw tc::Exception(mModuleName, "Failed to decrypt NCA header. (nca_header_key could not be loaded)");
}
nn::hac::ContentArchiveUtil::decryptContentArchiveHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyCfg.nca_header_key.get());
2019-01-31 09:10:19 +00:00
// generate header hash
2021-10-15 09:29:29 +00:00
tc::crypto::GenerateSha256Hash(mHdrHash.data(), (byte_t*)&mHdrBlock.header, sizeof(nn::hac::sContentArchiveHeader));
2019-01-31 09:10:19 +00:00
// proccess main header
mHdr.fromBytes((byte_t*)&mHdrBlock.header, sizeof(nn::hac::sContentArchiveHeader));
}
void nstool::NcaProcess::generateNcaBodyEncryptionKeys()
2019-01-31 09:10:19 +00:00
{
// create zeros key
KeyBag::aes128_key_t zero_aesctr_key;
2021-10-15 09:29:29 +00:00
memset(zero_aesctr_key.data(), 0, zero_aesctr_key.size());
2019-01-31 09:10:19 +00:00
// get key data from header
byte_t masterkey_rev = nn::hac::AesKeygen::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration());
2019-01-31 09:10:19 +00:00
byte_t keak_index = mHdr.getKeyAreaEncryptionKeyIndex();
// process key area
sKeys::sKeyAreaKey kak;
2021-10-16 06:13:11 +00:00
for (size_t i = 0; i < mHdr.getKeyArea().size(); i++)
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
if (mHdr.getKeyArea()[i] != zero_aesctr_key)
2019-01-31 09:10:19 +00:00
{
kak.index = (byte_t)i;
2021-10-16 06:13:11 +00:00
kak.enc = mHdr.getKeyArea()[i];
2021-10-15 09:29:29 +00:00
kak.decrypted = false;
2019-01-31 09:10:19 +00:00
// key[0-3]
2021-10-15 09:29:29 +00:00
if (i < 4 && mKeyCfg.nca_key_area_encryption_key[keak_index].find(masterkey_rev) != mKeyCfg.nca_key_area_encryption_key[keak_index].end())
2019-01-31 09:10:19 +00:00
{
kak.decrypted = true;
2021-10-15 09:29:29 +00:00
nn::hac::AesKeygen::generateKey(kak.dec.data(), kak.enc.data(), mKeyCfg.nca_key_area_encryption_key[keak_index][masterkey_rev].data());
2019-01-31 09:10:19 +00:00
}
// key[KEY_AESCTR_HW]
2021-10-15 09:29:29 +00:00
else if (i == nn::hac::nca::KEY_AESCTR_HW && mKeyCfg.nca_key_area_encryption_key_hw[keak_index].find(masterkey_rev) != mKeyCfg.nca_key_area_encryption_key_hw[keak_index].end())
2019-01-31 09:10:19 +00:00
{
kak.decrypted = true;
2021-10-15 09:29:29 +00:00
nn::hac::AesKeygen::generateKey(kak.dec.data(), kak.enc.data(), mKeyCfg.nca_key_area_encryption_key_hw[keak_index][masterkey_rev].data());
2019-01-31 09:10:19 +00:00
}
else
{
kak.decrypted = false;
}
mContentKey.kak_list.push_back(kak);
2019-01-31 09:10:19 +00:00
}
}
2021-10-15 09:29:29 +00:00
// clear content key
mContentKey.aes_ctr = tc::Optional<nn::hac::detail::aes128_key_t>();
2019-01-31 09:10:19 +00:00
// if this has a rights id, the key needs to be sourced from a ticket
if (mHdr.hasRightsId() == true)
{
KeyBag::aes128_key_t tmp_key;
2021-10-15 09:29:29 +00:00
if (mKeyCfg.external_content_keys.find(mHdr.getRightsId()) != mKeyCfg.external_content_keys.end())
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mContentKey.aes_ctr = mKeyCfg.external_content_keys[mHdr.getRightsId()];
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
else if (mKeyCfg.fallback_content_key.isSet())
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mContentKey.aes_ctr = mKeyCfg.fallback_content_key.get();
}
else if (mKeyCfg.fallback_enc_content_key.isSet())
{
tmp_key = mKeyCfg.fallback_enc_content_key.get();
if (mKeyCfg.etik_common_key.find(masterkey_rev) != mKeyCfg.etik_common_key.end())
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
nn::hac::AesKeygen::generateKey(tmp_key.data(), tmp_key.data(), mKeyCfg.etik_common_key[masterkey_rev].data());
mContentKey.aes_ctr = tmp_key;
2019-01-31 09:10:19 +00:00
}
}
}
2021-10-15 09:29:29 +00:00
// otherwise used decrypt key area
2019-01-31 09:10:19 +00:00
else
{
for (size_t i = 0; i < mContentKey.kak_list.size(); i++)
{
if (mContentKey.kak_list[i].index == nn::hac::nca::KEY_AESCTR && mContentKey.kak_list[i].decrypted)
{
2021-10-15 09:29:29 +00:00
mContentKey.aes_ctr = mContentKey.kak_list[i].dec;
2019-01-31 09:10:19 +00:00
}
}
}
// if the keys weren't generated, check if the keys were supplied by the user
if (mContentKey.aes_ctr.isNull())
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if (mKeyCfg.fallback_content_key.isSet())
{
mContentKey.aes_ctr = mKeyCfg.fallback_content_key.get();
}
2019-01-31 09:10:19 +00:00
}
if (mCliOutputMode.show_keydata)
2019-01-31 09:10:19 +00:00
{
if (mContentKey.aes_ctr.isSet())
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt::print("[NCA Content Key]\n");
fmt::print(" AES-CTR Key: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mContentKey.aes_ctr.get().data(), mContentKey.aes_ctr.get().size(), true, ""));
2019-01-31 09:10:19 +00:00
}
}
}
void nstool::NcaProcess::generatePartitionConfiguration()
2019-01-31 09:10:19 +00:00
{
for (size_t i = 0; i < mHdr.getPartitionEntryList().size(); i++)
{
// get reference to relevant structures
const nn::hac::ContentArchiveHeader::sPartitionEntry& partition = mHdr.getPartitionEntryList()[i];
nn::hac::sContentArchiveFsHeader& fs_header = mHdrBlock.fs_header[partition.header_index];
2019-01-31 09:10:19 +00:00
// output structure
sPartitionInfo& info = mPartitions[partition.header_index];
// validate header hash
2021-10-15 09:29:29 +00:00
nn::hac::detail::sha256_hash_t fs_header_hash;
tc::crypto::GenerateSha256Hash(fs_header_hash.data(), (const byte_t*)&mHdrBlock.fs_header[partition.header_index], sizeof(nn::hac::sContentArchiveFsHeader));
if (fs_header_hash != partition.fs_header_hash)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc::Exception(mModuleName, fmt::format("NCA FS Header [{:d}] Hash: FAIL", partition.header_index));
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
if (fs_header.version.unwrap() != nn::hac::nca::kDefaultFsHeaderVersion)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc::Exception(mModuleName, fmt::format("NCA FS Header [{:d}] Version({:d}): UNSUPPORTED", partition.header_index, fs_header.version.unwrap()));
2019-01-31 09:10:19 +00:00
}
// setup AES-CTR
2021-10-15 09:29:29 +00:00
nn::hac::ContentArchiveUtil::getNcaPartitionAesCtr(&fs_header, info.aes_ctr.data());
2019-01-31 09:10:19 +00:00
2021-10-16 06:13:11 +00:00
// save partition configinfo
info.offset = partition.offset;
info.size = partition.size;
2019-01-31 09:10:19 +00:00
info.format_type = (nn::hac::nca::FormatType)fs_header.format_type;
info.hash_type = (nn::hac::nca::HashType)fs_header.hash_type;
info.enc_type = (nn::hac::nca::EncryptionType)fs_header.encryption_type;
info.metadata_hash_type = (nn::hac::nca::MetaDataHashType)fs_header.meta_data_hash_type;
if (info.hash_type == nn::hac::nca::HashType::HierarchicalSha256)
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
info.hierarchicalsha256_hdr.fromBytes(fs_header.hash_info.data(), fs_header.hash_info.size());
2019-01-31 09:10:19 +00:00
}
else if (info.hash_type == nn::hac::nca::HashType::HierarchicalIntegrity)
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
info.hierarchicalintegrity_hdr.fromBytes(fs_header.hash_info.data(), fs_header.hash_info.size());
2019-01-31 09:10:19 +00:00
}
// create reader
try
{
// handle partition encryption and partition compaction (sparse layer)
if (fs_header.sparse_info.generation.unwrap() != 0)
2019-01-31 09:10:19 +00:00
{
2021-11-14 09:58:37 +00:00
if (fs_header.sparse_info.bucket.header.st_magic.unwrap() != nn::hac::bktr::kBktrStructMagic)
{
throw tc::Exception(mModuleName, fmt::format("SparseInfo BucketTree header had invalid magic ID."));
}
if (fs_header.sparse_info.bucket.header.version.unwrap() != nn::hac::bktr::kBktrVersion)
{
throw tc::Exception(mModuleName, fmt::format("SparseInfo BucketTree header had unsupported format version."));
}
if (fs_header.sparse_info.bucket.header.entry_count.unwrap() == 0)
{
throw tc::Exception(mModuleName, fmt::format("CompactedStream is not present in file."));
2021-11-14 09:58:37 +00:00
}
else
{
int64_t phys_base_offset = fs_header.sparse_info.physical_offset.unwrap();
int64_t data_stream_offset = 0;
int64_t data_stream_size = fs_header.sparse_info.bucket.offset.unwrap(); // end of image is where BKTR data begins (ASSUMPTION)
int64_t bktr_stream_offset = fs_header.sparse_info.bucket.offset.unwrap();
int64_t bktr_stream_size = fs_header.sparse_info.bucket.size.unwrap();
/*
fmt::print("SparseLayer overview:\n");
fmt::print("phys_base_offset = 0x{:016x}\n", phys_base_offset);
fmt::print("data_stream_offset = 0x{:016x}\n", data_stream_offset);
fmt::print("data_stream_size = 0x{:016x}\n", data_stream_size);
fmt::print("bktr_stream_offset = 0x{:016x}\n", bktr_stream_offset);
fmt::print("bktr_stream_size = 0x{:016x}\n", bktr_stream_size);
*/
std::shared_ptr<tc::io::IStream> data_stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, phys_base_offset + data_stream_offset, data_stream_size));
std::shared_ptr<tc::io::IStream> zeros_stream = std::make_shared<utils::ZerosStream>(utils::ZerosStream());
2021-11-14 09:58:37 +00:00
std::shared_ptr<tc::io::IStream> bktr_stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, phys_base_offset + bktr_stream_offset, bktr_stream_size));
// determine encryption cfg
nn::hac::detail::aes128_key_t partition_key;
nn::hac::detail::aes_iv_t data_region_ctr;
if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
2021-11-14 09:58:37 +00:00
{
if (mContentKey.aes_ctr.isNull())
throw tc::Exception(mModuleName, "AES-CTR Key was not determined");
// get partition key
partition_key = mContentKey.aes_ctr.get();
2021-11-14 09:58:37 +00:00
}
else if (info.enc_type == nn::hac::nca::EncryptionType::AesXts || info.enc_type == nn::hac::nca::EncryptionType::AesCtrEx)
{
throw tc::Exception(mModuleName, fmt::format("EncryptionType({:s}): UNSUPPORTED", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type)));
}
else if (info.enc_type != nn::hac::nca::EncryptionType::None)
2021-11-14 09:58:37 +00:00
{
throw tc::Exception(mModuleName, fmt::format("EncryptionType({:s}): UNKNOWN", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type)));
}
// decrypt bucket tree if required
if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
{
// get partition counter
nn::hac::detail::aes_iv_t bktr_stream_ctr;
nn::hac::ContentArchiveUtil::getNcaPartitionAesCtr(uint32_t(fs_header.sparse_info.generation.unwrap()) << 16, fs_header.secure_value.unwrap(), bktr_stream_ctr.data());
tc::crypto::IncrementCounterAes128Ctr(bktr_stream_ctr.data(), (phys_base_offset + bktr_stream_offset)>>4);
2021-11-14 09:58:37 +00:00
// create decryption stream
bktr_stream = std::make_shared<tc::crypto::Aes128CtrEncryptedStream>(tc::crypto::Aes128CtrEncryptedStream(bktr_stream, partition_key, bktr_stream_ctr));
}
// process bucket tree
nn::hac::BucketTree bucket_tree;
2021-11-14 09:58:37 +00:00
bucket_tree.parse(bktr_stream, nn::hac::indirectstorage::kNodeSize, sizeof(nn::hac::sIndirectStorageEntry), fs_header.sparse_info.bucket.header.entry_count.unwrap());
// construct un-sparsified stream
std::vector<std::shared_ptr<tc::io::IStream>> unsparse_stream_list;
enum FindState {
FindState_Nothing,
FindState_Data,
FindState_Zeros
};
enum StorageIndex {
StorageIndex_Data = 0,
StorageIndex_Zeros = 1,
};
FindState find_state = FindState_Nothing;
int64_t region_phys_offset = 0;
int64_t region_phys_size = 0;
int64_t region_virt_offset = 0;
for (auto itr = bucket_tree.begin(); itr != bucket_tree.end(); itr++)
2021-11-14 09:58:37 +00:00
{
nn::hac::sIndirectStorageEntry* entry = (nn::hac::sIndirectStorageEntry*)itr->data();
// check this entry is valid
if (entry->storage_index.unwrap() > 1 || entry->virt_offset.unwrap() >= info.size)
{
throw tc::Exception("BucketTree IndirectStorageEntry data was invalid.");
}
2021-11-14 09:58:37 +00:00
if (entry->virt_offset.unwrap() && entry->storage_index.unwrap() == StorageIndex_Zeros)
2021-11-14 09:58:37 +00:00
{
throw tc::Exception("CompactedStream is incomplete.");
}
2021-11-14 09:58:37 +00:00
/*
fmt::print("IndirectStorageEntry\n");
fmt::print(" virt_offset = 0x{:016x}\n", entry->virt_offset.unwrap());
fmt::print(" phys_offset = 0x{:016x}\n", entry->phys_offset.unwrap());
fmt::print(" storage_index = {:s}\n", entry->storage_index.unwrap() == StorageIndex_Data ? "DataStorage" : "ZeroStorage");
*/
// process find state if ready...
if (find_state == FindState_Data)
{
// data region followed by another data region (shouldn't happen)
if (entry->storage_index.unwrap() == StorageIndex_Data)
2021-11-14 09:58:37 +00:00
{
throw tc::Exception("SparseStorage: A DataRegion was followed by a second DataRegion.");
2021-11-14 09:58:37 +00:00
}
// data region followed by zeros region
else if (entry->storage_index.unwrap() == StorageIndex_Zeros)
2021-11-14 09:58:37 +00:00
{
region_phys_size = entry->virt_offset.unwrap() - region_virt_offset;
if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
2021-11-14 09:58:37 +00:00
{
//fmt::print("Add (enc) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list.push_back(std::make_shared<tc::crypto::Aes128CtrEncryptedStream>(tc::crypto::Aes128CtrEncryptedStream(std::make_shared<tc::io::SubStream>(tc::io::SubStream(data_stream, region_phys_offset, region_phys_size)), partition_key, data_region_ctr)));
2021-11-14 09:58:37 +00:00
}
else
2021-11-14 09:58:37 +00:00
{
//fmt::print("Add (pln) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list.push_back(std::make_shared<tc::io::SubStream>(tc::io::SubStream(data_stream, region_phys_offset, region_phys_size)));
}
// reset find state
find_state = FindState_Nothing;
2021-11-14 09:58:37 +00:00
}
}
else if (find_state == FindState_Zeros)
2021-11-14 09:58:37 +00:00
{
// zeros region followed by another zeros region (shouldn't happen)
if (entry->storage_index.unwrap() == StorageIndex_Zeros)
{
throw tc::Exception("SparseStorage: A ZerosRegion was followed by a second ZerosRegion.");
}
// zeros region followed by data region
else if (entry->storage_index.unwrap() == StorageIndex_Data)
{
region_phys_size = entry->virt_offset.unwrap() - region_virt_offset;
2021-11-14 09:58:37 +00:00
//fmt::print("Add zeros slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list.push_back(std::make_shared<tc::io::SubStream>(tc::io::SubStream(zeros_stream, region_phys_offset, region_phys_size)));
// reset find state
find_state = FindState_Nothing;
}
2021-11-14 09:58:37 +00:00
}
// populate find state if empty
if (find_state == FindState_Nothing)
2021-11-14 09:58:37 +00:00
{
// found begin of data region
find_state = entry->storage_index.unwrap() == StorageIndex_Data? FindState_Data : FindState_Zeros;
region_phys_offset = entry->phys_offset.unwrap();
region_virt_offset = entry->virt_offset.unwrap();
if (find_state == FindState_Data && info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
{
data_region_ctr = info.aes_ctr;
tc::crypto::IncrementCounterAes128Ctr(data_region_ctr.data(), (phys_base_offset + region_virt_offset)>>4);
}
2021-11-14 09:58:37 +00:00
}
2021-11-14 09:58:37 +00:00
}
// follow up on trailing data/zeros state
if (find_state == FindState_Data)
2021-11-14 09:58:37 +00:00
{
region_phys_size = info.size - region_virt_offset;
if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
{
//fmt::print("Add (enc) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list.push_back(std::make_shared<tc::crypto::Aes128CtrEncryptedStream>(tc::crypto::Aes128CtrEncryptedStream(std::make_shared<tc::io::SubStream>(tc::io::SubStream(data_stream, region_phys_offset, region_phys_size)), partition_key, data_region_ctr)));
}
else
{
//fmt::print("Add (pln) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list.push_back(std::make_shared<tc::io::SubStream>(tc::io::SubStream(data_stream, region_phys_offset, region_phys_size)));
}
find_state = FindState_Nothing;
2021-11-14 09:58:37 +00:00
}
else if (find_state == FindState_Zeros)
2021-11-14 09:58:37 +00:00
{
region_phys_size = info.size - region_virt_offset;
//fmt::print("Add zeros slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list.push_back(std::make_shared<tc::io::SubStream>(tc::io::SubStream(zeros_stream, region_phys_offset, region_phys_size)));
find_state = FindState_Nothing;
2021-11-14 09:58:37 +00:00
}
2021-11-14 09:58:37 +00:00
// create logical partition
info.reader = std::make_shared<tc::io::ConcatenatedStream>(tc::io::ConcatenatedStream(unsparse_stream_list));
2021-11-14 09:58:37 +00:00
}
2019-01-31 09:10:19 +00:00
}
else
{
// create raw partition
info.reader = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, info.offset, info.size));
// handle encryption if required reader based on encryption type
if (info.enc_type == nn::hac::nca::EncryptionType::None)
{
// no encryption so do nothing
//info.reader = info.reader;
}
else if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
{
if (mContentKey.aes_ctr.isNull())
throw tc::Exception(mModuleName, "AES-CTR Key was not determined");
// get partition key
nn::hac::detail::aes128_key_t partition_key = mContentKey.aes_ctr.get();
// get partition counter
nn::hac::detail::aes_iv_t partition_ctr = info.aes_ctr;
tc::crypto::IncrementCounterAes128Ctr(partition_ctr.data(), info.offset>>4);
// create decryption stream
info.reader = std::make_shared<tc::crypto::Aes128CtrEncryptedStream>(tc::crypto::Aes128CtrEncryptedStream(info.reader, partition_key, partition_ctr));
}
else if (info.enc_type == nn::hac::nca::EncryptionType::AesXts || info.enc_type == nn::hac::nca::EncryptionType::AesCtrEx)
{
throw tc::Exception(mModuleName, fmt::format("EncryptionType({:s}): UNSUPPORTED", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type)));
}
else
{
throw tc::Exception(mModuleName, fmt::format("EncryptionType({:s}): UNKNOWN", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type)));
}
2019-01-31 09:10:19 +00:00
}
// filter out unrecognised hash types, and hash based readers
2021-10-16 06:13:11 +00:00
switch (info.hash_type)
2019-01-31 09:10:19 +00:00
{
2021-10-16 07:41:45 +00:00
case (nn::hac::nca::HashType::None):
break;
2021-10-16 06:13:11 +00:00
case (nn::hac::nca::HashType::HierarchicalSha256):
info.reader = std::make_shared<nn::hac::HierarchicalSha256Stream>(nn::hac::HierarchicalSha256Stream(info.reader, info.hierarchicalsha256_hdr));
break;
case (nn::hac::nca::HashType::HierarchicalIntegrity):
info.reader = std::make_shared<nn::hac::HierarchicalIntegrityStream>(nn::hac::HierarchicalIntegrityStream(info.reader, info.hierarchicalintegrity_hdr));
break;
default:
2021-10-15 09:29:29 +00:00
throw tc::Exception(mModuleName, fmt::format("HashType({:s}): UNKNOWN", nn::hac::ContentArchiveUtil::getHashTypeAsString(info.hash_type)));
}
// filter out unrecognised format types
switch (info.format_type)
{
case (nn::hac::nca::FormatType::PartitionFs):
2022-03-12 05:30:43 +00:00
info.fs_snapshot = nn::hac::PartitionFsSnapshotGenerator(info.reader);
info.fs_reader = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(info.fs_snapshot));
2021-10-15 09:29:29 +00:00
break;
case (nn::hac::nca::FormatType::RomFs):
2022-03-12 05:30:43 +00:00
info.fs_snapshot = nn::hac::RomFsSnapshotGenerator(info.reader);
info.fs_reader = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(info.fs_snapshot));
2021-10-15 09:29:29 +00:00
break;
default:
throw tc::Exception(mModuleName, fmt::format("FormatType({:s}): UNKNOWN", nn::hac::ContentArchiveUtil::getFormatTypeAsString(info.format_type)));
2019-01-31 09:10:19 +00:00
}
}
catch (const tc::Exception& e)
2019-01-31 09:10:19 +00:00
{
info.fail_reason = std::string(e.error());
}
}
}
void nstool::NcaProcess::validateNcaSignatures()
2019-01-31 09:10:19 +00:00
{
// validate signature[0]
2021-10-15 09:29:29 +00:00
if (mKeyCfg.nca_header_sign0_key.find(mHdr.getSignatureKeyGeneration()) != mKeyCfg.nca_header_sign0_key.end())
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if (tc::crypto::VerifyRsa2048PssSha256(mHdrBlock.signature_main.data(), mHdrHash.data(), mKeyCfg.nca_header_sign0_key[mHdr.getSignatureKeyGeneration()]) == false)
{
fmt::print("[WARNING] NCA Header Main Signature: FAIL\n");
}
}
else
{
fmt::print("[WARNING] NCA Header Main Signature: FAIL (could not load header key)\n");
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
2019-01-31 09:10:19 +00:00
// validate signature[1]
if (mHdr.getContentType() == nn::hac::nca::ContentType::Program)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
try {
if (mPartitions[nn::hac::nca::PARTITION_CODE].format_type == nn::hac::nca::FormatType::PartitionFs)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if (mPartitions[nn::hac::nca::PARTITION_CODE].fs_reader != nullptr)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
std::shared_ptr<tc::io::IStream> npdm_file;
try {
mPartitions[nn::hac::nca::PARTITION_CODE].fs_reader->openFile(tc::io::Path(kNpdmExefsPath), tc::io::FileMode::Open, tc::io::FileAccess::Read, npdm_file);
}
catch (tc::io::FileNotFoundException&) {
throw tc::Exception(fmt::format("\"{:s}\" not present in ExeFs", kNpdmExefsPath));
}
2019-01-31 09:10:19 +00:00
MetaProcess npdm;
2021-10-15 09:29:29 +00:00
npdm.setInputFile(npdm_file);
npdm.setKeyCfg(mKeyCfg);
npdm.setVerifyMode(true);
2021-10-15 09:29:29 +00:00
npdm.setCliOutputMode(CliOutputMode(false, false, false, false));
2019-01-31 09:10:19 +00:00
npdm.process();
2021-10-15 09:29:29 +00:00
if (tc::crypto::VerifyRsa2048PssSha256(mHdrBlock.signature_acid.data(), mHdrHash.data(), npdm.getMeta().getAccessControlInfoDesc().getContentArchiveHeaderSignature2Key()) == false)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc::Exception("Bad signature");
2019-01-31 09:10:19 +00:00
}
}
else
{
2021-10-15 09:29:29 +00:00
throw tc::Exception("ExeFs was not mounted");
2019-01-31 09:10:19 +00:00
}
}
else
{
2021-10-15 09:29:29 +00:00
throw tc::Exception("No ExeFs partition");
2019-01-31 09:10:19 +00:00
}
}
2021-10-15 09:29:29 +00:00
catch (tc::Exception& e) {
fmt::print("[WARNING] NCA Header ACID Signature: FAIL ({:s})\n", e.error());
2019-01-31 09:10:19 +00:00
}
}
}
void nstool::NcaProcess::displayHeader()
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt::print("[NCA Header]\n");
fmt::print(" Format Type: {:s}\n", nn::hac::ContentArchiveUtil::getFormatHeaderVersionAsString((nn::hac::nca::HeaderFormatVersion)mHdr.getFormatVersion()));
fmt::print(" Dist. Type: {:s}\n", nn::hac::ContentArchiveUtil::getDistributionTypeAsString(mHdr.getDistributionType()));
fmt::print(" Content Type: {:s}\n", nn::hac::ContentArchiveUtil::getContentTypeAsString(mHdr.getContentType()));
fmt::print(" Key Generation: {:d}\n", mHdr.getKeyGeneration());
fmt::print(" Sig. Generation: {:d}\n", mHdr.getSignatureKeyGeneration());
fmt::print(" Kaek Index: {:s} ({:d})\n", nn::hac::ContentArchiveUtil::getKeyAreaEncryptionKeyIndexAsString((nn::hac::nca::KeyAreaEncryptionKeyIndex)mHdr.getKeyAreaEncryptionKeyIndex()), mHdr.getKeyAreaEncryptionKeyIndex());
fmt::print(" Size: 0x{:x}\n", mHdr.getContentSize());
fmt::print(" ProgID: 0x{:016x}\n", mHdr.getProgramId());
fmt::print(" Content Index: {:d}\n", mHdr.getContentIndex());
fmt::print(" SdkAddon Ver.: {:s} (v{:d})\n", nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getSdkAddonVersion()), mHdr.getSdkAddonVersion());
2019-01-31 09:10:19 +00:00
if (mHdr.hasRightsId())
{
2021-10-15 09:29:29 +00:00
fmt::print(" RightsId: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getRightsId().data(), mHdr.getRightsId().size(), true, ""));
2019-01-31 09:10:19 +00:00
}
if (mContentKey.kak_list.size() > 0 && mCliOutputMode.show_keydata)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt::print(" Key Area:\n");
fmt::print(" <--------------------------------------------------------------------------------------------------------->\n");
fmt::print(" | IDX | ENCRYPTED KEY | DECRYPTED KEY |\n");
fmt::print(" |-----|-------------------------------------------------|-------------------------------------------------|\n");
2019-01-31 09:10:19 +00:00
for (size_t i = 0; i < mContentKey.kak_list.size(); i++)
{
fmt::print(" | {:3d} | {:s} | ", mContentKey.kak_list[i].index, tc::cli::FormatUtil::formatBytesAsString(mContentKey.kak_list[i].enc.data(), mContentKey.kak_list[i].enc.size(), true, ""));
2021-10-15 09:29:29 +00:00
2019-01-31 09:10:19 +00:00
if (mContentKey.kak_list[i].decrypted)
fmt::print("{:s}", tc::cli::FormatUtil::formatBytesAsString(mContentKey.kak_list[i].dec.data(), mContentKey.kak_list[i].dec.size(), true, ""));
2019-01-31 09:10:19 +00:00
else
2021-10-16 06:28:35 +00:00
fmt::print("<unable to decrypt> ");
2019-01-31 09:10:19 +00:00
2021-10-15 09:29:29 +00:00
fmt::print(" |\n");
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
fmt::print(" <--------------------------------------------------------------------------------------------------------->\n");
2019-01-31 09:10:19 +00:00
}
if (mCliOutputMode.show_layout)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt::print(" Partitions:\n");
2019-01-31 09:10:19 +00:00
for (size_t i = 0; i < mHdr.getPartitionEntryList().size(); i++)
{
uint32_t index = mHdr.getPartitionEntryList()[i].header_index;
sPartitionInfo& info = mPartitions[index];
if (info.size == 0) continue;
2021-10-15 09:29:29 +00:00
fmt::print(" {:d}:\n", index);
fmt::print(" Offset: 0x{:x}\n", info.offset);
fmt::print(" Size: 0x{:x}\n", info.size);
fmt::print(" Format Type: {:s}\n", nn::hac::ContentArchiveUtil::getFormatTypeAsString(info.format_type));
fmt::print(" Hash Type: {:s}\n", nn::hac::ContentArchiveUtil::getHashTypeAsString(info.hash_type));
fmt::print(" Enc. Type: {:s}\n", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type));
if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
nn::hac::detail::aes_iv_t aes_ctr;
2021-10-16 06:13:11 +00:00
memcpy(aes_ctr.data(), info.aes_ctr.data(), aes_ctr.size());
tc::crypto::IncrementCounterAes128Ctr(aes_ctr.data(), info.offset>>4);
2021-10-15 09:29:29 +00:00
fmt::print(" AesCtr Counter:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(aes_ctr.data(), aes_ctr.size(), true, ""));
2019-01-31 09:10:19 +00:00
}
if (info.hash_type == nn::hac::nca::HashType::HierarchicalIntegrity)
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
auto hash_hdr = info.hierarchicalintegrity_hdr;
2021-10-15 09:29:29 +00:00
fmt::print(" HierarchicalIntegrity Header:\n");
2021-10-16 06:13:11 +00:00
for (size_t j = 0; j < hash_hdr.getLayerInfo().size(); j++)
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
if (j+1 == hash_hdr.getLayerInfo().size())
{
fmt::print(" Data Layer:\n");
}
else
{
fmt::print(" Hash Layer {:d}:\n", j);
}
fmt::print(" Offset: 0x{:x}\n", hash_hdr.getLayerInfo()[j].offset);
fmt::print(" Size: 0x{:x}\n", hash_hdr.getLayerInfo()[j].size);
fmt::print(" BlockSize: 0x{:x}\n", hash_hdr.getLayerInfo()[j].block_size);
2019-01-31 09:10:19 +00:00
}
2021-10-16 06:13:11 +00:00
for (size_t j = 0; j < hash_hdr.getMasterHashList().size(); j++)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt::print(" Master Hash {:d}:\n", j);
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHashList()[j].data(), 0x10, true, ""));
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHashList()[j].data()+0x10, 0x10, true, ""));
2019-01-31 09:10:19 +00:00
}
}
else if (info.hash_type == nn::hac::nca::HashType::HierarchicalSha256)
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
auto hash_hdr = info.hierarchicalsha256_hdr;
2021-10-15 09:29:29 +00:00
fmt::print(" HierarchicalSha256 Header:\n");
fmt::print(" Master Hash:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHash().data(), 0x10, true, ""));
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHash().data()+0x10, 0x10, true, ""));
2021-10-16 06:13:11 +00:00
fmt::print(" HashBlockSize: 0x{:x}\n", hash_hdr.getHashBlockSize());
for (size_t j = 0; j < hash_hdr.getLayerInfo().size(); j++)
{
if (j+1 == hash_hdr.getLayerInfo().size())
{
fmt::print(" Data Layer:\n");
}
else
{
fmt::print(" Hash Layer {:d}:\n", j);
}
fmt::print(" Offset: 0x{:x}\n", hash_hdr.getLayerInfo()[j].offset);
fmt::print(" Size: 0x{:x}\n", hash_hdr.getLayerInfo()[j].size);
}
2019-01-31 09:10:19 +00:00
}
}
}
}
void nstool::NcaProcess::processPartitions()
2019-01-31 09:10:19 +00:00
{
2022-03-12 05:30:43 +00:00
std::vector<nn::hac::CombinedFsSnapshotGenerator::MountPointInfo> mount_points;
2021-10-16 06:13:11 +00:00
2019-01-31 09:10:19 +00:00
for (size_t i = 0; i < mHdr.getPartitionEntryList().size(); i++)
{
uint32_t index = mHdr.getPartitionEntryList()[i].header_index;
struct sPartitionInfo& partition = mPartitions[index];
// if the reader is null, skip
2021-10-15 09:29:29 +00:00
if (partition.fs_reader == nullptr)
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt::print("[WARNING] NCA Partition {:d} not readable.", index);
2019-01-31 09:10:19 +00:00
if (partition.fail_reason.empty() == false)
{
2021-10-15 09:29:29 +00:00
fmt::print(" ({:s})", partition.fail_reason);
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
fmt::print("\n");
2019-01-31 09:10:19 +00:00
continue;
}
2021-10-16 06:13:11 +00:00
std::string mount_point_name;
/*
if (mHdr.getContentType() == nn::hac::nca::ContentType::Program)
{
mount_point_name = nn::hac::ContentArchiveUtil::getProgramContentParititionIndexAsString((nn::hac::nca::ProgramContentPartitionIndex)index);
}
else
*/
{
mount_point_name = fmt::format("{:d}", index);
}
2022-03-12 05:30:43 +00:00
mount_points.push_back( { mount_point_name, partition.fs_snapshot } );
2019-01-31 09:10:19 +00:00
}
2021-10-16 06:13:11 +00:00
2022-03-12 05:30:43 +00:00
tc::io::VirtualFileSystem::FileSystemSnapshot fs_snapshot = nn::hac::CombinedFsSnapshotGenerator(mount_points);
2021-10-16 06:13:11 +00:00
2022-03-12 05:30:43 +00:00
std::shared_ptr<tc::io::IFileSystem> nca_fs = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(fs_snapshot));
2021-10-16 06:13:11 +00:00
mFsProcess.setInputFileSystem(nca_fs);
mFsProcess.setFsFormatName("ContentArchive");
mFsProcess.setFsRootLabel(getContentTypeForMountStr(mHdr.getContentType()));
mFsProcess.process();
2019-01-31 09:10:19 +00:00
}
2021-10-16 06:13:11 +00:00
std::string nstool::NcaProcess::getContentTypeForMountStr(nn::hac::nca::ContentType cont_type) const
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
std::string str;
2019-01-31 09:10:19 +00:00
switch (cont_type)
{
case (nn::hac::nca::ContentType::Program):
2019-01-31 09:10:19 +00:00
str = "program";
break;
case (nn::hac::nca::ContentType::Meta):
2019-01-31 09:10:19 +00:00
str = "meta";
break;
case (nn::hac::nca::ContentType::Control):
2019-01-31 09:10:19 +00:00
str = "control";
break;
case (nn::hac::nca::ContentType::Manual):
2019-01-31 09:10:19 +00:00
str = "manual";
break;
case (nn::hac::nca::ContentType::Data):
2019-01-31 09:10:19 +00:00
str = "data";
break;
case (nn::hac::nca::ContentType::PublicData):
2019-01-31 09:10:19 +00:00
str = "publicdata";
break;
default:
str = "";
break;
}
return str;
}