mirror of
https://github.com/DarkStore-3DS/Project_CTR.git
synced 2026-07-03 16:59:04 +00:00
Add source code for ctrtool
This commit is contained in:
@@ -0,0 +1,968 @@
|
||||
#include "NcchProcess.h"
|
||||
#include "lzss.h"
|
||||
#include <tc/io.h>
|
||||
#include <tc/cli.h>
|
||||
#include <tc/crypto.h>
|
||||
#include <tc/ArgumentNullException.h>
|
||||
|
||||
#include <ntd/n3ds/CtrKeyGenerator.h>
|
||||
#include <ntd/n3ds/exheader.h>
|
||||
#include "ExHeaderProcess.h"
|
||||
#include "ExeFsProcess.h"
|
||||
#include "IvfcProcess.h"
|
||||
|
||||
ctrtool::NcchProcess::NcchProcess() :
|
||||
mModuleLabel("ctrtool::NcchProcess"),
|
||||
mInputStream(),
|
||||
mVerbose(false),
|
||||
mVerify(false),
|
||||
mRaw(false),
|
||||
mPlain(false),
|
||||
mShowSyscallNames(false),
|
||||
mContentSize(0),
|
||||
mBlockSize(0),
|
||||
mDecompressExeFsCode(false)
|
||||
{
|
||||
memset((byte_t*)&mHeader, 0, sizeof(ntd::n3ds::NcchHeader));
|
||||
for (size_t i = 0; i < NcchRegionNum; i++)
|
||||
{
|
||||
mRegionOpt[i].show_info = true;
|
||||
mRegionOpt[i].show_fs = false;
|
||||
mRegionOpt[i].bin_extract_path.makeNull();
|
||||
mRegionOpt[i].fs_extract_path.makeNull();
|
||||
|
||||
mRegionInfo[i].valid = ValidState::Unchecked;
|
||||
mRegionInfo[i].offset = 0;
|
||||
mRegionInfo[i].size = 0;
|
||||
mRegionInfo[i].hashed_offset = 0;
|
||||
mRegionInfo[i].hashed_size = 0;
|
||||
mRegionInfo[i].raw_stream = nullptr;
|
||||
mRegionInfo[i].ready_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
|
||||
{
|
||||
mInputStream = input_stream;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
|
||||
{
|
||||
mKeyBag = key_bag;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::setVerboseMode(bool verbose)
|
||||
{
|
||||
mVerbose = verbose;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::setVerifyMode(bool verify)
|
||||
{
|
||||
mVerify = verify;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::setRawMode(bool raw)
|
||||
{
|
||||
mRaw = raw;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::setPlainMode(bool plain)
|
||||
{
|
||||
mPlain = plain;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::setShowSyscallName(bool show_name)
|
||||
{
|
||||
mShowSyscallNames = show_name;
|
||||
}
|
||||
|
||||
|
||||
void ctrtool::NcchProcess::setRegionProcessOutputMode(NcchRegion region, bool show_info, bool show_fs, const tc::Optional<tc::io::Path>& bin_extract_path, const tc::Optional<tc::io::Path>& fs_extract_path)
|
||||
{
|
||||
mRegionOpt[region].show_info = show_info;
|
||||
mRegionOpt[region].show_fs = show_fs;
|
||||
mRegionOpt[region].bin_extract_path = bin_extract_path;
|
||||
mRegionOpt[region].fs_extract_path = fs_extract_path;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::process()
|
||||
{
|
||||
importHeader();
|
||||
determineRegionLayout();
|
||||
determineRegionEncryption();
|
||||
if (mVerify)
|
||||
verifyRegions();
|
||||
if (mRegionOpt[NcchRegion_Header].show_info)
|
||||
printHeader();
|
||||
extractRegionBinaries();
|
||||
processRegions();
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::importHeader()
|
||||
{
|
||||
// validate input stream
|
||||
if (mInputStream == nullptr)
|
||||
{
|
||||
throw tc::ArgumentNullException(mModuleLabel, "Input stream was null.");
|
||||
}
|
||||
if (mInputStream->canRead() == false || mInputStream->canSeek() == false)
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, "Input stream requires read/seek permissions.");
|
||||
}
|
||||
|
||||
// import header
|
||||
if (mInputStream->length() < sizeof(ntd::n3ds::NcchHeader))
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small.");
|
||||
}
|
||||
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::NcchHeader));
|
||||
|
||||
// check the struct magic
|
||||
if (mHeader.header.struct_magic.unwrap() != ntd::n3ds::NcchCommonHeader::kStructMagic)
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, "NcchHeader is corrupted (Bad struct magic).");
|
||||
}
|
||||
|
||||
|
||||
// determine block size
|
||||
switch (mHeader.header.format_version.unwrap())
|
||||
{
|
||||
// CFA
|
||||
case ntd::n3ds::NcchCommonHeader::FormatVersion_CFA:
|
||||
// CXI
|
||||
case ntd::n3ds::NcchCommonHeader::FormatVersion_CXI:
|
||||
mBlockSize = static_cast<int64_t>(1) << (mHeader.header.flags.block_size_log + 9);
|
||||
break;
|
||||
// Prototype
|
||||
case ntd::n3ds::NcchCommonHeader::FormatVersion_CXI_PROTOTYPE:
|
||||
mBlockSize = static_cast<int64_t>(1);
|
||||
break;
|
||||
default:
|
||||
throw tc::InvalidOperationException(mModuleLabel, fmt::format("NcchHeader has unsupported format version. (0x{:02x})", mHeader.header.format_version.unwrap()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (mHeader.header.exhdr_size.unwrap() != 0 && mHeader.header.exhdr_size.unwrap() != sizeof(ntd::n3ds::ExtendedHeader))
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, fmt::format("NcchHeader has invalid ExHeader size. (0x{:02x})", mHeader.header.exhdr_size.unwrap()));
|
||||
}
|
||||
|
||||
// get content size
|
||||
mContentSize = int64_t(mHeader.header.content_blk_size.unwrap()) * mBlockSize;
|
||||
|
||||
if (mInputStream->length() < mContentSize)
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small.");
|
||||
}
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::determineRegionLayout()
|
||||
{
|
||||
// get region layout
|
||||
mRegionInfo[NcchRegion_Header].offset = 0;
|
||||
mRegionInfo[NcchRegion_Header].size = sizeof(ntd::n3ds::NcchHeader);
|
||||
mRegionInfo[NcchRegion_Header].hashed_offset = sizeof(ntd::n3ds::NcchHeader::signature);
|
||||
mRegionInfo[NcchRegion_Header].hashed_size = sizeof(ntd::n3ds::NcchCommonHeader);
|
||||
|
||||
if (mHeader.header.exhdr_size.unwrap() > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_ExHeader].offset = sizeof(ntd::n3ds::NcchHeader);
|
||||
mRegionInfo[NcchRegion_ExHeader].size = mHeader.header.exhdr_size.unwrap() + sizeof(ntd::n3ds::AccessDescriptor);
|
||||
mRegionInfo[NcchRegion_ExHeader].hashed_offset = 0;
|
||||
mRegionInfo[NcchRegion_ExHeader].hashed_size = mHeader.header.exhdr_size.unwrap();
|
||||
}
|
||||
if (mHeader.header.format_version.unwrap() == ntd::n3ds::NcchCommonHeader::FormatVersion_CXI_PROTOTYPE && mHeader.header.exhdr_hash[0] != 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_ExHeader].offset = sizeof(ntd::n3ds::NcchHeader);
|
||||
mRegionInfo[NcchRegion_ExHeader].size = sizeof(ntd::n3ds::ExtendedHeader) + sizeof(ntd::n3ds::AccessDescriptor);
|
||||
mRegionInfo[NcchRegion_ExHeader].hashed_offset = 0;
|
||||
mRegionInfo[NcchRegion_ExHeader].hashed_size = mHeader.header.exhdr_size.unwrap();
|
||||
}
|
||||
if (mHeader.header.plain_region_blk_size.unwrap() > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_PlainRegion].offset = mHeader.header.plain_region_blk_offset.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_PlainRegion].size = mHeader.header.plain_region_blk_size.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_PlainRegion].hashed_offset = 0;
|
||||
mRegionInfo[NcchRegion_PlainRegion].hashed_size = 0;
|
||||
}
|
||||
if (mHeader.header.logo_blk_size.unwrap() > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_Logo].offset = mHeader.header.logo_blk_offset.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_Logo].size = mHeader.header.logo_blk_size.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_Logo].hashed_offset = 0;
|
||||
mRegionInfo[NcchRegion_Logo].hashed_size = mRegionInfo[NcchRegion_Logo].size;
|
||||
}
|
||||
if (mHeader.header.exefs_blk_size.unwrap() > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_ExeFs].offset = mHeader.header.exefs_blk_offset.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_ExeFs].size = mHeader.header.exefs_blk_size.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_ExeFs].hashed_offset = 0;
|
||||
mRegionInfo[NcchRegion_ExeFs].hashed_size = mHeader.header.exefs_prot_blk_size.unwrap() * mBlockSize;
|
||||
}
|
||||
if (mHeader.header.romfs_blk_size.unwrap() > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_RomFs].offset = mHeader.header.romfs_blk_offset.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_RomFs].size = mHeader.header.romfs_blk_size.unwrap() * mBlockSize;
|
||||
mRegionInfo[NcchRegion_RomFs].hashed_offset = 0;
|
||||
mRegionInfo[NcchRegion_RomFs].hashed_size = mHeader.header.romfs_prot_blk_size.unwrap() * mBlockSize;
|
||||
}
|
||||
|
||||
// create raw streams
|
||||
for (size_t i = 0; i < NcchRegionNum; i++)
|
||||
{
|
||||
if (mRegionInfo[i].size)
|
||||
{
|
||||
mRegionInfo[i].raw_stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mInputStream, mRegionInfo[i].offset, mRegionInfo[i].size));
|
||||
// plain region & header are never encrypted
|
||||
if (i == NcchRegion_PlainRegion || i == NcchRegion_Header || i == NcchRegion_Logo)
|
||||
{
|
||||
mRegionInfo[i].ready_stream = mRegionInfo[i].raw_stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ctrtool::NcchProcess::determineRegionEncryption()
|
||||
{
|
||||
// quick test to determine if the crypto layer has been stripped by tools like GodMode9, not strictly required, but this matches classic ctrtool behaviour
|
||||
bool crypto_is_stripped = false;
|
||||
if (mRegionInfo[NcchRegion_ExHeader].size >= sizeof(ntd::n3ds::ExtendedHeader) && mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_NoEncryption) == false)
|
||||
{
|
||||
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> exheader_hash;
|
||||
tc::ByteData exheader_data = tc::ByteData(sizeof(ntd::n3ds::ExtendedHeader));
|
||||
mRegionInfo[NcchRegion_ExHeader].raw_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
mRegionInfo[NcchRegion_ExHeader].raw_stream->read(exheader_data.data(), exheader_data.size());
|
||||
|
||||
tc::crypto::GenerateSha256Hash(exheader_hash.data(), exheader_data.data(), exheader_data.size());
|
||||
crypto_is_stripped = memcmp(exheader_hash.data(), mHeader.header.exhdr_hash.data(), exheader_hash.size()) == 0;
|
||||
}
|
||||
if (crypto_is_stripped)
|
||||
{
|
||||
fmt::print("[LOG/NCCH] NCCH appears to be decrypted, contrary to header flags.\n");
|
||||
}
|
||||
|
||||
// determine encryption mode
|
||||
if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_NoEncryption) || mPlain || crypto_is_stripped)
|
||||
{
|
||||
// set ready streams to be not encrypted
|
||||
for (size_t i = 0; i < NcchRegionNum; i++)
|
||||
{
|
||||
if (mRegionInfo[i].size && mRegionInfo[i].ready_stream == nullptr && mRegionInfo[i].raw_stream != nullptr)
|
||||
{
|
||||
mRegionInfo[i].ready_stream = mRegionInfo[i].raw_stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct KeySlot
|
||||
{
|
||||
byte_t valid_x;
|
||||
KeyBag::Aes128Key x;
|
||||
byte_t valid_y;
|
||||
KeyBag::Aes128Key y;
|
||||
byte_t valid_key;
|
||||
KeyBag::Aes128Key key;
|
||||
} keyslot[2];
|
||||
|
||||
keyslot[0].valid_x = ValidState::Unchecked;
|
||||
keyslot[0].valid_y = ValidState::Unchecked;
|
||||
keyslot[0].valid_key = ValidState::Unchecked;
|
||||
keyslot[1].valid_x = ValidState::Unchecked;
|
||||
keyslot[1].valid_y = ValidState::Unchecked;
|
||||
keyslot[1].valid_key = ValidState::Unchecked;
|
||||
|
||||
// if fixed AES key
|
||||
if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_FixedAesKey))
|
||||
{
|
||||
// set AES keys to fixed key
|
||||
|
||||
// load aes keys
|
||||
auto key_itr = mKeyBag.ncch_fixed_key.find(isSystemTitle() ? mKeyBag.NCCH_SYSTEM_FIXED_KEY : mKeyBag.NCCH_APPLICATION_FIXED_KEY);
|
||||
if (key_itr == mKeyBag.ncch_fixed_key.end())
|
||||
{
|
||||
keyslot[0].valid_key = ValidState::Fail;
|
||||
keyslot[1].valid_key = ValidState::Fail;
|
||||
|
||||
fmt::print(stderr, "Could not read {} fixed key.\n", (isSystemTitle()? "system" : "application"));
|
||||
}
|
||||
|
||||
// save keys
|
||||
keyslot[0].key = key_itr->second;
|
||||
keyslot[1].key = key_itr->second;
|
||||
|
||||
keyslot[0].valid_key = ValidState::Good;
|
||||
keyslot[1].valid_key = ValidState::Good;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// keyslot[0]
|
||||
// load key_x
|
||||
auto keyx_itr = mKeyBag.ncch_secure_key_x.find(0);
|
||||
if (keyx_itr == mKeyBag.ncch_secure_key_x.end())
|
||||
{
|
||||
keyslot[0].valid_x = ValidState::Fail;
|
||||
|
||||
fmt::print(stderr, "Could not read secure key_x[0x{:02x}].\n", 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
keyslot[0].x = keyx_itr->second;
|
||||
|
||||
keyslot[0].valid_x = ValidState::Good;
|
||||
}
|
||||
|
||||
// load key_y
|
||||
memcpy(keyslot[0].y.data(), mHeader.signature.data(), keyslot[0].y.size());
|
||||
keyslot[0].valid_y = ValidState::Good;
|
||||
|
||||
// keyslot[1]
|
||||
// load key_x
|
||||
byte_t security_version = mHeader.header.flags.security_version;
|
||||
keyx_itr = mKeyBag.ncch_secure_key_x.find(security_version);
|
||||
if (keyx_itr == mKeyBag.ncch_secure_key_x.end())
|
||||
{
|
||||
keyslot[1].valid_x = ValidState::Fail;
|
||||
|
||||
fmt::print(stderr, "Could not read secure key_x[0x{:02x}].\n", security_version);
|
||||
}
|
||||
else
|
||||
{
|
||||
keyslot[1].x = keyx_itr->second;
|
||||
|
||||
keyslot[1].valid_x = ValidState::Good;
|
||||
}
|
||||
|
||||
memcpy(keyslot[1].x.data(), keyx_itr->second.data(), keyslot[1].x.size());
|
||||
|
||||
// load key_y
|
||||
if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_SeededAesKeyY))
|
||||
{
|
||||
tc::crypto::Sha256Generator hashgen;
|
||||
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
|
||||
|
||||
// import seed
|
||||
KeyBag::Aes128Key seed;
|
||||
auto seed_itr = mKeyBag.seed_db.find(mHeader.header.program_id.unwrap());
|
||||
if (seed_itr != mKeyBag.seed_db.end())
|
||||
{
|
||||
memcpy(seed.data(), seed_itr->second.data(), seed.size());
|
||||
}
|
||||
else if (mKeyBag.fallback_seed.isSet())
|
||||
{
|
||||
memcpy(seed.data(), mKeyBag.fallback_seed.get().data(), seed.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
keyslot[1].valid_y = ValidState::Fail;
|
||||
|
||||
fmt::print(stderr, "This title uses seed crypto, but no seed is set, unable to decrypt.\n");
|
||||
fmt::print(stderr, "Use -p to avoid decryption or use --seeddb=dbfile or --seed=SEEDHERE.\n");
|
||||
}
|
||||
|
||||
if (keyslot[1].valid_y != ValidState::Fail)
|
||||
{
|
||||
// validate seed
|
||||
hashgen.initialize();
|
||||
hashgen.update(seed.data(), seed.size());
|
||||
hashgen.update((byte_t*)&mHeader.header.program_id, sizeof(mHeader.header.program_id));
|
||||
hashgen.getHash(hash.data());
|
||||
if (memcmp(hash.data(), mHeader.header.seed_checksum.data(), mHeader.header.seed_checksum.size()) != 0)
|
||||
{
|
||||
keyslot[1].valid_y = ValidState::Fail;
|
||||
|
||||
fmt::print(stderr, "Seed check mismatch. (Got {:08x}, expected: {:08x})\n",
|
||||
((tc::bn::be32<uint32_t>*)hash.data())->unwrap(),
|
||||
((tc::bn::be32<uint32_t>*)mHeader.header.seed_checksum.data())->unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
if (keyslot[1].valid_y != ValidState::Fail)
|
||||
{
|
||||
// generate seeded key_y
|
||||
hashgen.initialize();
|
||||
hashgen.update(keyslot[0].y.data(), keyslot[0].y.size());
|
||||
hashgen.update(seed.data(), seed.size());
|
||||
hashgen.getHash(hash.data());
|
||||
|
||||
memcpy(keyslot[1].y.data(), hash.data(), keyslot[1].y.size());
|
||||
keyslot[1].valid_y = ValidState::Good;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(keyslot[1].y.data(), mHeader.signature.data(), keyslot[1].y.size());
|
||||
keyslot[1].valid_y = ValidState::Good;
|
||||
}
|
||||
|
||||
// generate secure keys
|
||||
if (keyslot[0].valid_x == ValidState::Good && keyslot[0].valid_y == ValidState::Good)
|
||||
{
|
||||
ntd::n3ds::CtrKeyGenerator::GenerateKey(keyslot[0].x.data(), keyslot[0].y.data(), keyslot[0].key.data());
|
||||
keyslot[0].valid_key = ValidState::Good;
|
||||
}
|
||||
else
|
||||
{
|
||||
keyslot[0].valid_key = ValidState::Fail;
|
||||
}
|
||||
|
||||
if (keyslot[1].valid_x == ValidState::Good && keyslot[1].valid_y == ValidState::Good)
|
||||
{
|
||||
ntd::n3ds::CtrKeyGenerator::GenerateKey(keyslot[1].x.data(), keyslot[1].y.data(), keyslot[1].key.data());
|
||||
keyslot[1].valid_key = ValidState::Good;
|
||||
}
|
||||
else
|
||||
{
|
||||
keyslot[1].valid_key = ValidState::Fail;
|
||||
}
|
||||
}
|
||||
|
||||
// output keys if required
|
||||
if (mVerbose)
|
||||
{
|
||||
fmt::print("[LOG/NCCH] NCCH AES Key0 {}\n", (keyslot[0].valid_key ? tc::cli::FormatUtil::formatBytesAsString(keyslot[0].key.data(), keyslot[0].key.size(), true, "") : "could not be determined"));
|
||||
fmt::print("[LOG/NCCH] NCCH AES Key1 {}\n", (keyslot[1].valid_key ? tc::cli::FormatUtil::formatBytesAsString(keyslot[1].key.data(), keyslot[1].key.size(), true, "") : "could not be determined"));
|
||||
}
|
||||
|
||||
// generate aes counter
|
||||
KeyBag::Aes128Key exheader_aesctr, exefs_aesctr, romfs_aesctr;
|
||||
if (mRegionInfo[NcchRegion_ExHeader].size)
|
||||
{
|
||||
getAesCounter(exheader_aesctr.data(), NcchRegion_ExHeader);
|
||||
|
||||
if (mVerbose)
|
||||
{
|
||||
fmt::print("[LOG/NCCH] NCCH ExHeader AES Counter {}\n", tc::cli::FormatUtil::formatBytesAsString(exheader_aesctr.data(), exheader_aesctr.size(), true, ""));
|
||||
}
|
||||
}
|
||||
if (mRegionInfo[NcchRegion_ExeFs].size)
|
||||
{
|
||||
getAesCounter(exefs_aesctr.data(), NcchRegion_ExeFs);
|
||||
|
||||
if (mVerbose)
|
||||
{
|
||||
fmt::print("[LOG/NCCH] NCCH ExeFS AES Counter {}\n", tc::cli::FormatUtil::formatBytesAsString(exefs_aesctr.data(), exefs_aesctr.size(), true, ""));
|
||||
}
|
||||
}
|
||||
if (mRegionInfo[NcchRegion_RomFs].size)
|
||||
{
|
||||
getAesCounter(romfs_aesctr.data(), NcchRegion_RomFs);
|
||||
|
||||
if (mVerbose)
|
||||
{
|
||||
fmt::print("[LOG/NCCH] NCCH RomFS AES Counter {}\n", tc::cli::FormatUtil::formatBytesAsString(romfs_aesctr.data(), romfs_aesctr.size(), true, ""));
|
||||
}
|
||||
}
|
||||
|
||||
// prepare ready_stream using encryption streams if keys are available
|
||||
if (mRegionInfo[NcchRegion_ExHeader].size && keyslot[0].valid_key == ValidState::Good)
|
||||
{
|
||||
mRegionInfo[NcchRegion_ExHeader].ready_stream = std::shared_ptr<tc::crypto::Aes128CtrEncryptedStream>(new tc::crypto::Aes128CtrEncryptedStream(mRegionInfo[NcchRegion_ExHeader].raw_stream, keyslot[0].key, exheader_aesctr));
|
||||
}
|
||||
if (mRegionInfo[NcchRegion_ExeFs].size && keyslot[0].valid_key == ValidState::Good)
|
||||
{
|
||||
// if key[1] is valid create the correct mixed key stream
|
||||
if (keyslot[1].valid_key == ValidState::Good)
|
||||
{
|
||||
// if the keys are the same, don't over complicate the encrypted stream
|
||||
if (false)//(memcmp(keyslot[0].key.data(), keyslot[1].key.data(), keyslot[0].key.size()) == 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_ExeFs].ready_stream = std::shared_ptr<tc::crypto::Aes128CtrEncryptedStream>(new tc::crypto::Aes128CtrEncryptedStream(mRegionInfo[NcchRegion_ExeFs].raw_stream, keyslot[0].key, exefs_aesctr));
|
||||
}
|
||||
else
|
||||
{
|
||||
// import ExeFs header
|
||||
if (mRegionInfo[NcchRegion_ExeFs].raw_stream->length() < sizeof(ntd::n3ds::ExeFsHeader))
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, "Stream is too small (cannot import ExeFsHeader).");
|
||||
}
|
||||
ntd::n3ds::ExeFsHeader exefs_hdr;
|
||||
mRegionInfo[NcchRegion_ExeFs].raw_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
mRegionInfo[NcchRegion_ExeFs].raw_stream->read((byte_t*)&exefs_hdr, sizeof(exefs_hdr));
|
||||
|
||||
tc::crypto::DecryptAes128Ctr((byte_t*)&exefs_hdr, (byte_t*)&exefs_hdr, sizeof(exefs_hdr), 0, keyslot[0].key.data(), keyslot[0].key.size(), exefs_aesctr.data(), exefs_aesctr.size());
|
||||
|
||||
// quick header validation
|
||||
if (exefs_hdr.file_table[0].name[0] == 0 ||
|
||||
exefs_hdr.file_table[0].offset.unwrap() != 0 ||
|
||||
exefs_hdr.getFileHash(0)->operator[](0) == 0)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "ExeFsHeader is corrupted (Bad first entry).");
|
||||
}
|
||||
|
||||
// create key maps
|
||||
std::vector<std::shared_ptr<tc::io::IStream>> exefs_streams;
|
||||
exefs_streams.push_back(std::make_shared<tc::crypto::Aes128CtrEncryptedStream>(tc::crypto::Aes128CtrEncryptedStream(std::make_shared<tc::io::SubStream>(tc::io::SubStream(mRegionInfo[NcchRegion_ExeFs].raw_stream, 0x0, sizeof(ntd::n3ds::ExeFsHeader))), keyslot[0].key, exefs_aesctr)));
|
||||
byte_t keyslot_index;
|
||||
for (size_t i = 0; i < exefs_hdr.kFileNum; i++)
|
||||
{
|
||||
std::string file_name = exefs_hdr.file_table[i].name.decode();
|
||||
int64_t file_offset = sizeof(ntd::n3ds::ExeFsHeader) + int64_t(exefs_hdr.file_table[i].offset.unwrap());
|
||||
int64_t file_length = align<int64_t>(exefs_hdr.file_table[i].size.unwrap(), exefs_hdr.kExeFsSectionAlignSize);
|
||||
|
||||
// skip empty sections
|
||||
if (exefs_hdr.file_table[i].size.unwrap() == 0)
|
||||
continue;
|
||||
|
||||
// determine range based on name
|
||||
if (file_name == "icon" || file_name == "banner")
|
||||
{
|
||||
// old key
|
||||
keyslot_index = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// new key
|
||||
keyslot_index = 1;
|
||||
}
|
||||
|
||||
tc::crypto::Aes128CtrEncryptedStream::counter_t file_counter;
|
||||
memcpy(file_counter.data(), exefs_aesctr.data(), exefs_aesctr.size());
|
||||
tc::crypto::IncrementCounterAes128Ctr(file_counter.data(), file_offset >> 4);
|
||||
exefs_streams.push_back(std::make_shared<tc::crypto::Aes128CtrEncryptedStream>(tc::crypto::Aes128CtrEncryptedStream(std::make_shared<tc::io::SubStream>(tc::io::SubStream(mRegionInfo[NcchRegion_ExeFs].raw_stream, file_offset, file_length)), keyslot[keyslot_index].key, file_counter)));
|
||||
}
|
||||
mRegionInfo[NcchRegion_ExeFs].ready_stream = std::make_shared<tc::io::ConcatenatedStream>(tc::io::ConcatenatedStream(exefs_streams));
|
||||
}
|
||||
}
|
||||
// otherwise use the "best-effort" single key stream (only icon and banner will be decrypt properly)
|
||||
else
|
||||
{
|
||||
fmt::print(stderr, "Only NCCH key0 was determined, so ExeFS will be partially decrypted\n");
|
||||
mRegionInfo[NcchRegion_ExeFs].ready_stream = std::shared_ptr<tc::crypto::Aes128CtrEncryptedStream>(new tc::crypto::Aes128CtrEncryptedStream(mRegionInfo[NcchRegion_ExeFs].raw_stream, keyslot[0].key, exefs_aesctr));
|
||||
}
|
||||
}
|
||||
if (mRegionInfo[NcchRegion_RomFs].size && keyslot[1].valid_key == ValidState::Good)
|
||||
{
|
||||
mRegionInfo[NcchRegion_RomFs].ready_stream = std::shared_ptr<tc::crypto::Aes128CtrEncryptedStream>(new tc::crypto::Aes128CtrEncryptedStream(mRegionInfo[NcchRegion_RomFs].raw_stream, keyslot[1].key, romfs_aesctr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ctrtool::NcchProcess::verifyRegions()
|
||||
{
|
||||
tc::crypto::Sha256Generator hash_calc;
|
||||
using Sha256Hash = std::array<byte_t, hash_calc.kHashSize>;
|
||||
std::array<Sha256Hash, NcchRegionNum> region_hashes;
|
||||
tc::ByteData cache = tc::ByteData(0x10000);
|
||||
size_t cache_read_len = 0;
|
||||
|
||||
// generate region sha2-256 hashes
|
||||
for (size_t i = 0; i < mRegionInfo.size(); i++)
|
||||
{
|
||||
if (mRegionInfo[i].hashed_size > 0 && mRegionInfo[i].ready_stream != nullptr)
|
||||
{
|
||||
// seek ready_stream to hashed_offset
|
||||
mRegionInfo[i].ready_stream->seek(mRegionInfo[i].hashed_offset, tc::io::SeekOrigin::Begin);
|
||||
|
||||
// init hash calc
|
||||
hash_calc.initialize();
|
||||
|
||||
// update hash with cache reads
|
||||
for (int64_t remaining_data = mRegionInfo[i].hashed_size; remaining_data > 0; remaining_data -= int64_t(cache_read_len))
|
||||
{
|
||||
cache_read_len = size_t(std::min<int64_t>(cache.size(), remaining_data));
|
||||
cache_read_len = mRegionInfo[i].ready_stream->read(cache.data(), cache_read_len);
|
||||
if (cache_read_len == 0)
|
||||
{
|
||||
throw tc::io::IOException(mModuleLabel, "Failed to read from NCCH region file.");
|
||||
}
|
||||
|
||||
hash_calc.update(cache.data(), cache_read_len);
|
||||
}
|
||||
|
||||
// save hash
|
||||
hash_calc.getHash(region_hashes[i].data());
|
||||
}
|
||||
}
|
||||
|
||||
// header signature
|
||||
if (mRegionInfo[NcchRegion_Header].hashed_size > 0)
|
||||
{
|
||||
// verify hash using CFA key
|
||||
if (mHeader.header.flags.content_flag.form_type == mHeader.header.FormType_SimpleContent)
|
||||
{
|
||||
auto rsakey_itr = mKeyBag.rsa_key.find(mKeyBag.RSAKEY_CFA_CCI);
|
||||
if (rsakey_itr != mKeyBag.rsa_key.end())
|
||||
{
|
||||
tc::crypto::RsaKey pubkey = rsakey_itr->second;
|
||||
|
||||
mRegionInfo[NcchRegion_Header].valid = tc::crypto::VerifyRsa2048Pkcs1Sha256(mHeader.signature.data(), region_hashes[NcchRegion_Header].data(), pubkey) ? ValidState::Good : ValidState::Fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
// cannot locate rsa key to verify
|
||||
fmt::print(stderr, "Could not read CFA public key.\n");
|
||||
mRegionInfo[NcchRegion_Header].valid = ValidState::Fail;
|
||||
}
|
||||
|
||||
}
|
||||
// verify hash using CXI key from exheader
|
||||
else
|
||||
{
|
||||
if (mRegionInfo[NcchRegion_ExHeader].size > 0 && mRegionInfo[NcchRegion_ExHeader].ready_stream != nullptr)
|
||||
{
|
||||
// import exheader
|
||||
tc::ByteData exheader_data = tc::ByteData(mRegionInfo[NcchRegion_ExHeader].size);
|
||||
mRegionInfo[NcchRegion_ExHeader].ready_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
mRegionInfo[NcchRegion_ExHeader].ready_stream->read(exheader_data.data(), exheader_data.size());
|
||||
ntd::n3ds::AccessDescriptor* accessdesc = (ntd::n3ds::AccessDescriptor*)(exheader_data.data() + sizeof(ntd::n3ds::ExtendedHeader));
|
||||
|
||||
// create public key from access desc
|
||||
tc::crypto::RsaKey pubkey = tc::crypto::RsaPublicKey(accessdesc->ncch_rsa_modulus.data(), accessdesc->ncch_rsa_modulus.size());
|
||||
|
||||
// verify header signature
|
||||
mRegionInfo[NcchRegion_Header].valid = tc::crypto::VerifyRsa2048Pkcs1Sha256(mHeader.signature.data(), region_hashes[NcchRegion_Header].data(), pubkey) ? ValidState::Good : ValidState::Fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
// cannot locate rsa key to verify
|
||||
fmt::print(stderr, "Could not read CXI public key from AccessDescriptor.\n");
|
||||
mRegionInfo[NcchRegion_Header].valid = ValidState::Fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// exheader hash
|
||||
if (mRegionInfo[NcchRegion_ExHeader].hashed_size > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_ExHeader].valid = memcmp(region_hashes[NcchRegion_ExHeader].data(), mHeader.header.exhdr_hash.data(), region_hashes[NcchRegion_ExHeader].size()) == 0 ? ValidState::Good : ValidState::Fail;
|
||||
}
|
||||
|
||||
// logo hash
|
||||
if (mRegionInfo[NcchRegion_Logo].hashed_size > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_Logo].valid = memcmp(region_hashes[NcchRegion_Logo].data(), mHeader.header.logo_hash.data(), region_hashes[NcchRegion_Logo].size()) == 0 ? ValidState::Good : ValidState::Fail;
|
||||
}
|
||||
|
||||
// exefs hash
|
||||
if (mRegionInfo[NcchRegion_ExeFs].hashed_size > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_ExeFs].valid = memcmp(region_hashes[NcchRegion_ExeFs].data(), mHeader.header.exefs_prot_hash.data(), region_hashes[NcchRegion_ExeFs].size()) == 0 ? ValidState::Good : ValidState::Fail;
|
||||
}
|
||||
|
||||
// romfs hash
|
||||
if (mRegionInfo[NcchRegion_RomFs].hashed_size > 0)
|
||||
{
|
||||
mRegionInfo[NcchRegion_RomFs].valid = memcmp(region_hashes[NcchRegion_RomFs].data(), mHeader.header.romfs_prot_hash.data(), region_hashes[NcchRegion_RomFs].size()) == 0 ? ValidState::Good : ValidState::Fail;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::printHeader()
|
||||
{
|
||||
fmt::print("\n");
|
||||
fmt::print("NCCH:\n");
|
||||
fmt::print("Header: {}\n", "NCCH");
|
||||
fmt::print("Signature: {:6} {}", getValidString(mRegionInfo[NcchRegion_Header].valid), tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHeader.signature.data(), mHeader.signature.size(), true, "", 0x20, 24, false));
|
||||
fmt::print("Content size: 0x{:08x}\n", mHeader.header.content_blk_size.unwrap() * mBlockSize);
|
||||
fmt::print("Title id: {:016x}\n", mHeader.header.content_id.unwrap());
|
||||
fmt::print("Maker code: {}\n", mHeader.header.maker_code.decode());
|
||||
fmt::print("FormatVersion: {:d}\n", mHeader.header.format_version.unwrap());
|
||||
fmt::print("Title seed check: {}\n", tc::cli::FormatUtil::formatBytesAsString(mHeader.header.seed_checksum.data(), mHeader.header.seed_checksum.size(), true, ""));
|
||||
fmt::print("Program id: {:016x}\n", mHeader.header.program_id.unwrap());
|
||||
fmt::print("Logo hash: {:6} {}\n", getValidString(mRegionInfo[NcchRegion_Logo].valid), tc::cli::FormatUtil::formatBytesAsString(mHeader.header.logo_hash.data(), mHeader.header.logo_hash.size(), true, ""));
|
||||
fmt::print("Product code: {}\n", mHeader.header.product_code.decode());
|
||||
fmt::print("Exheader size: 0x{:x}\n", mHeader.header.exhdr_size.unwrap());
|
||||
fmt::print("Exheader hash: {:6} {}\n", getValidString(mRegionInfo[NcchRegion_ExHeader].valid), tc::cli::FormatUtil::formatBytesAsString(mHeader.header.exhdr_hash.data(), mHeader.header.exhdr_hash.size(), true, ""));
|
||||
fmt::print("Flags: {}\n", tc::cli::FormatUtil::formatBytesAsString(mHeader.header.flags.data(), mHeader.header.flags.size(), true, ""));
|
||||
|
||||
// crypto key
|
||||
fmt::print(" > Crypto Key ");
|
||||
if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_NoEncryption))
|
||||
{
|
||||
fmt::print("None");
|
||||
}
|
||||
else if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_FixedAesKey))
|
||||
{
|
||||
fmt::print("Fixed ({})", (isSystemTitle() ? "System" : "Application"));
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::print("Secure ({:d})", (uint32_t)mHeader.header.flags.security_version);
|
||||
if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_SeededAesKeyY))
|
||||
{
|
||||
fmt::print(" (KeyY seeded)");
|
||||
}
|
||||
}
|
||||
fmt::print("\n");
|
||||
|
||||
std::vector<std::string> content_platforms;
|
||||
for (size_t bit = 0; bit < mHeader.header.flags.content_platform.bit_size(); bit++)
|
||||
{
|
||||
if (mHeader.header.flags.content_platform.test(bit))
|
||||
{
|
||||
content_platforms.push_back(getContentPlatformString(bit));
|
||||
}
|
||||
}
|
||||
fmt::print(" > ContentPlatorm: {}", tc::cli::FormatUtil::formatListWithLineLimit(content_platforms, 4, 24, false));
|
||||
|
||||
fmt::print(" > FormType: {}\n", getFormTypeString(mHeader.header.flags.content_flag.form_type));
|
||||
fmt::print(" > ContentType: {}\n", getContentTypeString(mHeader.header.flags.content_flag.content_type));
|
||||
fmt::print(" > BlockSize: 0x{:x}\n", mBlockSize);
|
||||
if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_NoMountRomFS))
|
||||
fmt::print(" > No RomFS mount\n");
|
||||
if (mHeader.header.flags.other_flag.test(ntd::n3ds::NcchCommonHeader::OtherFlag_ManualDisclosure))
|
||||
fmt::print(" > Disclose eManual\n");
|
||||
|
||||
fmt::print("Plain region offset: 0x{:08x}\n", mHeader.header.plain_region_blk_offset.unwrap() * mBlockSize);
|
||||
fmt::print("Plain region size: 0x{:08x}\n", mHeader.header.plain_region_blk_size.unwrap() * mBlockSize);
|
||||
fmt::print("Logo offset: 0x{:08x}\n", mHeader.header.logo_blk_offset.unwrap() * mBlockSize);
|
||||
fmt::print("Logo size: 0x{:08x}\n", mHeader.header.logo_blk_size.unwrap() * mBlockSize);
|
||||
fmt::print("ExeFS offset: 0x{:08x}\n", mHeader.header.exefs_blk_offset.unwrap() * mBlockSize);
|
||||
fmt::print("ExeFS size: 0x{:08x}\n", mHeader.header.exefs_blk_size.unwrap() * mBlockSize);
|
||||
fmt::print("ExeFS hash region size: 0x{:08x}\n", mHeader.header.exefs_prot_blk_size.unwrap() * mBlockSize);
|
||||
fmt::print("RomFS offset: 0x{:08x}\n", mHeader.header.romfs_blk_offset.unwrap() * mBlockSize);
|
||||
fmt::print("RomFS size: 0x{:08x}\n", mHeader.header.romfs_blk_size.unwrap() * mBlockSize);
|
||||
fmt::print("RomFS hash region size: 0x{:08x}\n", mHeader.header.romfs_prot_blk_size.unwrap() * mBlockSize);
|
||||
fmt::print("ExeFS hash: {:6} {}\n", getValidString(mRegionInfo[NcchRegion_ExeFs].valid), tc::cli::FormatUtil::formatBytesAsString(mHeader.header.exefs_prot_hash.data(), mHeader.header.exefs_prot_hash.size(), true, ""));
|
||||
fmt::print("RomFS hash: {:6} {}\n", getValidString(mRegionInfo[NcchRegion_RomFs].valid), tc::cli::FormatUtil::formatBytesAsString(mHeader.header.romfs_prot_hash.data(), mHeader.header.romfs_prot_hash.size(), true, ""));
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::extractRegionBinaries()
|
||||
{
|
||||
/*
|
||||
original order
|
||||
ncch_save(ctx, NCCHTYPE_EXEFS, actions);
|
||||
ncch_save(ctx, NCCHTYPE_ROMFS, actions);
|
||||
ncch_save(ctx, NCCHTYPE_EXHEADER, actions);
|
||||
ncch_save(ctx, NCCHTYPE_LOGO, actions);
|
||||
ncch_save(ctx, NCCHTYPE_PLAINRGN, actions);
|
||||
*/
|
||||
|
||||
tc::io::LocalFileSystem local_fs;
|
||||
tc::ByteData cache = tc::ByteData(0x10000);
|
||||
size_t cache_read_len;
|
||||
std::shared_ptr<tc::io::IStream> in_stream;
|
||||
std::shared_ptr<tc::io::IStream> out_stream;
|
||||
for (size_t i = 0; i < NcchRegionNum; i++)
|
||||
{
|
||||
if (mRegionOpt[i].bin_extract_path.isSet() && mRegionInfo[i].ready_stream != nullptr)
|
||||
{
|
||||
switch(i)
|
||||
{
|
||||
case NcchRegion_Header: fmt::print("Saving Header...\n"); break;
|
||||
case NcchRegion_ExHeader: fmt::print("Saving Extended Header...\n"); break;
|
||||
case NcchRegion_PlainRegion: fmt::print("Saving Plain Region...\n"); break;
|
||||
case NcchRegion_Logo: fmt::print("Saving Logo...\n"); break;
|
||||
case NcchRegion_ExeFs: fmt::print("Saving ExeFS...\n"); break;
|
||||
case NcchRegion_RomFs: fmt::print("Saving RomFS...\n"); break;
|
||||
}
|
||||
|
||||
in_stream = mRegionInfo[i].ready_stream;
|
||||
local_fs.openFile(mRegionOpt[i].bin_extract_path.get(), tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write, out_stream);
|
||||
|
||||
in_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
out_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
for (int64_t remaining_data = in_stream->length(); remaining_data > 0;)
|
||||
{
|
||||
cache_read_len = in_stream->read(cache.data(), cache.size());
|
||||
if (cache_read_len == 0)
|
||||
{
|
||||
throw tc::io::IOException(mModuleLabel, "Failed to read from NCCH Region.");
|
||||
}
|
||||
|
||||
out_stream->write(cache.data(), cache_read_len);
|
||||
|
||||
remaining_data -= int64_t(cache_read_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::processRegions()
|
||||
{
|
||||
if (mRegionInfo[NcchRegion_ExHeader].size != 0 && mRegionInfo[NcchRegion_ExHeader].ready_stream != nullptr)
|
||||
{
|
||||
ctrtool::ExHeaderProcess proc;
|
||||
proc.setInputStream(mRegionInfo[NcchRegion_ExHeader].ready_stream);
|
||||
proc.setKeyBag(mKeyBag);
|
||||
proc.setCliOutputMode(mRegionOpt[NcchRegion_ExHeader].show_info);
|
||||
proc.setVerboseMode(mVerbose);
|
||||
proc.setVerifyMode(mVerify);
|
||||
proc.setShowSyscallName(mShowSyscallNames);
|
||||
|
||||
proc.process();
|
||||
}
|
||||
if (mRegionInfo[NcchRegion_ExeFs].size != 0 && mRegionInfo[NcchRegion_ExeFs].ready_stream != nullptr)
|
||||
{
|
||||
ctrtool::ExeFsProcess proc;
|
||||
proc.setInputStream(mRegionInfo[NcchRegion_ExeFs].ready_stream);
|
||||
proc.setCliOutputMode(mRegionOpt[NcchRegion_ExeFs].show_info, mRegionOpt[NcchRegion_ExeFs].show_fs);
|
||||
proc.setVerboseMode(mVerbose);
|
||||
proc.setVerifyMode(mVerify);
|
||||
proc.setRawMode(mRaw);
|
||||
proc.setDecompressCode(mDecompressExeFsCode);
|
||||
if (mRegionOpt[NcchRegion_ExeFs].fs_extract_path.isSet())
|
||||
{
|
||||
proc.setExtractPath(mRegionOpt[NcchRegion_ExeFs].fs_extract_path.get());
|
||||
}
|
||||
proc.process();
|
||||
}
|
||||
if (mRegionInfo[NcchRegion_RomFs].size != 0 && mRegionInfo[NcchRegion_RomFs].ready_stream != nullptr)
|
||||
{
|
||||
ctrtool::IvfcProcess proc;
|
||||
proc.setInputStream(mRegionInfo[NcchRegion_RomFs].ready_stream);
|
||||
proc.setKeyBag(mKeyBag);
|
||||
proc.setCliOutputMode(mRegionOpt[NcchRegion_RomFs].show_info, mRegionOpt[NcchRegion_RomFs].show_fs);
|
||||
proc.setVerboseMode(mVerbose);
|
||||
proc.setVerifyMode(mVerify);
|
||||
if (mRegionOpt[NcchRegion_RomFs].fs_extract_path.isSet())
|
||||
{
|
||||
proc.setExtractPath(mRegionOpt[NcchRegion_RomFs].fs_extract_path.get());
|
||||
}
|
||||
proc.process();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ctrtool::NcchProcess::getValidString(byte_t validstate)
|
||||
{
|
||||
std::string ret_str;
|
||||
|
||||
switch (validstate)
|
||||
{
|
||||
case Unchecked:
|
||||
ret_str = "";
|
||||
break;
|
||||
case Good:
|
||||
ret_str = "(GOOD)";
|
||||
break;
|
||||
case Fail:
|
||||
default:
|
||||
ret_str = "(FAIL)";
|
||||
break;
|
||||
}
|
||||
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
std::string ctrtool::NcchProcess::getContentPlatformString(size_t bit)
|
||||
{
|
||||
std::string ret_str;
|
||||
|
||||
switch(bit)
|
||||
{
|
||||
case ntd::n3ds::NcchCommonHeader::ContentPlatform_CTR :
|
||||
ret_str = "CTR";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::ContentPlatform_SNAKE :
|
||||
ret_str = "SNAKE";
|
||||
break;
|
||||
default:
|
||||
ret_str = fmt::format("Unknown (bit {:d}", bit);
|
||||
}
|
||||
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
std::string ctrtool::NcchProcess::getFormTypeString(byte_t var)
|
||||
{
|
||||
std::string ret_str;
|
||||
|
||||
switch(var)
|
||||
{
|
||||
case ntd::n3ds::NcchCommonHeader::FormType_Unassigned :
|
||||
ret_str = "Not Assigned";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::FormType_SimpleContent :
|
||||
ret_str = "Simple Content";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::FormType_ExecutableWithoutRomFS :
|
||||
ret_str = "Executable (without RomFS)";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::FormType_Executable :
|
||||
ret_str = "Executable";
|
||||
break;
|
||||
default:
|
||||
ret_str = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
std::string ctrtool::NcchProcess::getContentTypeString(byte_t var)
|
||||
{
|
||||
std::string ret_str;
|
||||
|
||||
switch(var)
|
||||
{
|
||||
case ntd::n3ds::NcchCommonHeader::ContentType_Application :
|
||||
ret_str = "Application";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::ContentType_SystemUpdate :
|
||||
ret_str = "System Update (CTR)";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::ContentType_Manual :
|
||||
ret_str = "Manual";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::ContentType_Child :
|
||||
ret_str = "Child";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::ContentType_Trial :
|
||||
ret_str = "Trial";
|
||||
break;
|
||||
case ntd::n3ds::NcchCommonHeader::ContentType_ExtendedSystemUpdate :
|
||||
ret_str = "System Update (SNAKE)";
|
||||
break;
|
||||
default:
|
||||
ret_str = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
|
||||
bool ctrtool::NcchProcess::isSystemTitle()
|
||||
{
|
||||
return (mHeader.header.content_id.unwrap() & 0x0000001000000000) != 0;
|
||||
}
|
||||
|
||||
void ctrtool::NcchProcess::getAesCounter(byte_t* counter, byte_t ncch_region)
|
||||
{
|
||||
uint16_t version = mHeader.header.format_version.unwrap();
|
||||
|
||||
struct AesCounter_v0_v2
|
||||
{
|
||||
enum NcchTypeForCtr
|
||||
{
|
||||
NcchTypeForCtr_ExHeader = 1,
|
||||
NcchTypeForCtr_ExeFs = 2,
|
||||
NcchTypeForCtr_RomFs = 3
|
||||
};
|
||||
|
||||
tc::bn::be64<uint64_t> title_id;
|
||||
byte_t type;
|
||||
std::array<byte_t, 7> block_bytes;
|
||||
};
|
||||
|
||||
struct AesCounter_v1
|
||||
{
|
||||
tc::bn::le64<uint64_t> title_id;
|
||||
tc::bn::be64<uint64_t> begin_offset;
|
||||
};
|
||||
|
||||
if (version == ntd::n3ds::NcchCommonHeader::FormatVersion_CFA || version == ntd::n3ds::NcchCommonHeader::FormatVersion_CXI)
|
||||
{
|
||||
AesCounter_v0_v2* tmp = (AesCounter_v0_v2*)counter;
|
||||
tmp->title_id.wrap(mHeader.header.content_id.unwrap());
|
||||
if (ncch_region == NcchRegion_ExHeader)
|
||||
tmp->type = tmp->NcchTypeForCtr_ExHeader;
|
||||
else if (ncch_region == NcchRegion_ExeFs)
|
||||
tmp->type = tmp->NcchTypeForCtr_ExeFs;
|
||||
else if (ncch_region == NcchRegion_RomFs)
|
||||
tmp->type = tmp->NcchTypeForCtr_RomFs;
|
||||
memset(tmp->block_bytes.data(), 0, tmp->block_bytes.size());
|
||||
}
|
||||
else if (version == ntd::n3ds::NcchCommonHeader::FormatVersion_CXI_PROTOTYPE)
|
||||
{
|
||||
AesCounter_v1* tmp = (AesCounter_v1*)counter;
|
||||
tmp->title_id.wrap(mHeader.header.content_id.unwrap());
|
||||
tmp->begin_offset.wrap(mRegionInfo[ncch_region].offset);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user