Add source code for ctrtool

This commit is contained in:
jakcron
2022-03-12 16:00:33 +08:00
parent 6ad2f13c50
commit 800f5776bc
681 changed files with 219734 additions and 0 deletions
+567
View File
@@ -0,0 +1,567 @@
#include "CciProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>
#include <ntd/n3ds/CciFsSnapshotGenerator.h>
#include <ntd/n3ds/CtrKeyGenerator.h>
#include <mbedtls/ccm.h>
ctrtool::CciProcess::CciProcess() :
mModuleLabel("ctrtool::CciProcess"),
mInputStream(),
mKeyBag(),
mShowHeaderInfo(false),
mShowFs(false),
mVerbose(false),
mVerify(false),
mExtractPath(),
mContentIndex(0),
mBlockSize(0),
mUsedImageSize(0),
mValidSignature(ValidState::Unchecked),
mValidInitialDataMac(ValidState::Unchecked),
mDecryptedTitleKey(),
mNcchProcess(),
mFsReader()
{
memset(&mHeader, 0, sizeof(mHeader));
}
void ctrtool::CciProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::CciProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
mKeyBag = key_bag;
mNcchProcess.setKeyBag(key_bag);
}
void ctrtool::CciProcess::setCliOutputMode(bool show_header_info, bool show_fs)
{
mShowHeaderInfo = show_header_info;
mShowFs = show_fs;
}
void ctrtool::CciProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
mNcchProcess.setVerboseMode(verbose);
}
void ctrtool::CciProcess::setVerifyMode(bool verify)
{
mVerify = verify;
mNcchProcess.setVerifyMode(verify);
}
void ctrtool::CciProcess::setExtractPath(const tc::io::Path& extract_path)
{
mExtractPath = extract_path;
}
void ctrtool::CciProcess::setContentIndex(size_t index)
{
mContentIndex = index;
}
void ctrtool::CciProcess::setRawMode(bool raw)
{
mNcchProcess.setRawMode(raw);
}
void ctrtool::CciProcess::setPlainMode(bool plain)
{
mNcchProcess.setPlainMode(plain);
}
void ctrtool::CciProcess::setShowSyscallName(bool show_name)
{
mNcchProcess.setShowSyscallName(show_name);
}
void ctrtool::CciProcess::setNcchRegionProcessOutputMode(NcchProcess::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)
{
mNcchProcess.setRegionProcessOutputMode(region, show_info, show_fs, bin_extract_path, fs_extract_path);
}
void ctrtool::CciProcess::process()
{
importHeader();
if (mVerify)
verifyHeader();
if (mShowHeaderInfo)
printHeader();
if (mShowFs)
printFs();
if (mExtractPath.isSet())
extractFs();
processContent();
}
void ctrtool::CciProcess::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::CciHeader))
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small. (Too small to read header).");
}
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::CciHeader));
// check the struct magic
if (mHeader.ncsd_header.struct_magic.unwrap() != mHeader.ncsd_header.kStructMagic)
{
throw tc::InvalidOperationException(mModuleLabel, "NcsdCommonHeader is corrupted (Bad struct magic).");
}
// check supported media types
if (mHeader.ncsd_header.flags.media_type != mHeader.ncsd_header.MediaType_Card1 && mHeader.ncsd_header.flags.media_type != mHeader.ncsd_header.MediaType_Card2)
{
throw tc::InvalidOperationException(mModuleLabel, "NcsdCommonHeader has an unsupported MediaType.");
}
// determine block size
int64_t block_shift = (mHeader.ncsd_header.flags.block_size_log + 9);
mBlockSize = static_cast<int64_t>(1) << block_shift;
// determine used image size
int64_t pos = 0;
for (size_t i = 0; i < mHeader.ncsd_header.partition_offsetsize.size(); i++)
{
int64_t offset = mHeader.ncsd_header.partition_offsetsize[i].blk_offset.unwrap() * mBlockSize;
int64_t size = mHeader.ncsd_header.partition_offsetsize[i].blk_size.unwrap() * mBlockSize;
if (size != 0)
{
if (offset < pos)
{
throw tc::InvalidOperationException(mModuleLabel, "NcsdCommonHeader has an poorly aligned content offsets.");
}
pos = offset + size;
}
}
mUsedImageSize = pos;
if (mUsedImageSize > (mHeader.ncsd_header.image_blk_size.unwrap() * mBlockSize))
{
throw tc::InvalidOperationException(mModuleLabel, "NcsdCommonHeader content geometry exceeded media size.");
}
// check input stream is large enough
if (mInputStream->length() < mUsedImageSize)
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small. (Too small for total used image size).");
}
// decrypt title key
ctrtool::KeyBag::Aes128Key initial_data_key;
bool initial_data_key_available = false;
// crypto_type 0 is the normal "secure" initial data key
if (mHeader.card_info.flag.crypto_type == ntd::n3ds::CciHeader::CryptoType_Secure)
{
if (mKeyBag.brom_static_key_x.find(mKeyBag.KEYSLOT_INITIAL_DATA) != mKeyBag.brom_static_key_x.end())
{
ntd::n3ds::CtrKeyGenerator::GenerateKey(mKeyBag.brom_static_key_x[mKeyBag.KEYSLOT_INITIAL_DATA].data(), mHeader.initial_data.key_source.data(), initial_data_key.data());
initial_data_key_available = true;
}
else
{
initial_data_key_available = false;
}
}
// crypto_type 3 zeros initial_data key (used in developer roms mostly)
else if (mHeader.card_info.flag.crypto_type == ntd::n3ds::CciHeader::CryptoType_FixedKey)
{
memset(initial_data_key.data(), 0, initial_data_key.size());
initial_data_key_available = true;
}
else
{
fmt::print("[WARNING] Unsupported CardInfo::CryptoType ({})\n", (uint32_t)mHeader.card_info.flag.crypto_type);
}
if (initial_data_key_available)
{
// initialise ccm context
mbedtls_ccm_context ccm_ctx;
mbedtls_ccm_init(&ccm_ctx);
mbedtls_ccm_setkey(&ccm_ctx, MBEDTLS_CIPHER_ID_AES, initial_data_key.data(), 128);
// decrypt titlekey
ctrtool::KeyBag::Aes128Key decrypted_title_key;
int dec_result = mbedtls_ccm_auth_decrypt(&ccm_ctx, decrypted_title_key.size(), mHeader.initial_data.nonce.data(), mHeader.initial_data.nonce.size(), nullptr, 0, mHeader.initial_data.encrypted_title_key.data(), decrypted_title_key.data(), mHeader.initial_data.mac.data(), mHeader.initial_data.mac.size());
// dec_result will be non-zero if MAC was invalid
if (dec_result == 0)
{
mDecryptedTitleKey = decrypted_title_key;
}
/*
// test encrypt
ctrtool::KeyBag::Aes128Key enc_title_key, mac;
mbedtls_ccm_encrypt_and_tag(&ccm_ctx, enc_title_key.size(), mHeader.initial_data.nonce.data(), mHeader.initial_data.nonce.size(), nullptr, 0, decrypted_title_key.data(), enc_title_key.data(), mac.data(), mac.size());
std::cout << "enc key: " << tc::cli::FormatUtil::formatBytesAsString(enc_title_key.data(), enc_title_key.size(), true, "") << std::endl;
std::cout << "mac: " << tc::cli::FormatUtil::formatBytesAsString(mac.data(), mac.size(), true, "") << std::endl;
*/
mbedtls_ccm_free(&ccm_ctx);
}
// open fs reader
mFsReader = std::shared_ptr<tc::io::VirtualFileSystem>(new tc::io::VirtualFileSystem(ntd::n3ds::CciFsShapshotGenerator(mInputStream)));
}
void ctrtool::CciProcess::verifyHeader()
{
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> ncsd_header_hash;
tc::crypto::GenerateSha256Hash(ncsd_header_hash.data(), (byte_t*)(&mHeader.ncsd_header), sizeof(mHeader.ncsd_header));
if (mKeyBag.rsa_key.find(mKeyBag.RSAKEY_CFA_CCI) != mKeyBag.rsa_key.end())
{
tc::crypto::RsaKey pubkey = mKeyBag.rsa_key[mKeyBag.RSAKEY_CFA_CCI];
mValidSignature = tc::crypto::VerifyRsa2048Pkcs1Sha256(mHeader.signature.data(), ncsd_header_hash.data(), pubkey) ? ValidState::Good : ValidState::Fail;
}
else
{
fmt::print(stderr, "Could not read static CFA_CCI public key.\n");
mValidSignature = ValidState::Fail;
}
mValidInitialDataMac = mDecryptedTitleKey.isSet() ? ValidState::Good : ValidState::Fail;
}
void ctrtool::CciProcess::printHeader()
{
fmt::print("\n");
fmt::print("[CCI]\n");
// NCSD common header
fmt::print("NcsdCommonHeader:\n");
fmt::print(" Header: {}\n", "NCSD");
fmt::print(" Signature: {:6} {}", getValidString(mValidSignature), tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHeader.signature.data(), mHeader.signature.size(), true, "", 0x20, 25, false));
fmt::print(" RomSize: {} (Used: 0x{:X})\n", getRomSizeString(mHeader.ncsd_header.image_blk_size.unwrap()), mUsedImageSize);
fmt::print(" TitleId: {:016x}\n", mHeader.ncsd_header.title_id.unwrap());
fmt::print("\n");
for (size_t i = 0; i < ntd::n3ds::NcsdCommonHeader::kPartitionNum; i++)
{
int64_t offset = mHeader.ncsd_header.partition_offsetsize[i].blk_offset.unwrap() * mBlockSize;
int64_t size = mHeader.ncsd_header.partition_offsetsize[i].blk_size.unwrap() * mBlockSize;
byte_t fs_type = mHeader.ncsd_header.partition_fs_type[i];
byte_t crypto_type = mHeader.ncsd_header.partition_crypto_type[i];
uint64_t id = mHeader.ncsd_header.card_ext.partition_id[i].unwrap();
if (size != 0)
{
fmt::print(" Partition {}\n", i);
fmt::print(" Id: {:016x}\n", id);
fmt::print(" Area: 0x{:08X}-0x{:08X}\n", offset, (offset + size));
fmt::print(" FsType: {:02X}\n", fs_type);
fmt::print(" CryptoType: {:02X}\n", crypto_type);
fmt::print("\n");
}
}
fmt::print(" Flags: {}\n", tc::cli::FormatUtil::formatBytesAsString(mHeader.ncsd_header.flags.data(), mHeader.ncsd_header.flags.size(), true, ""));
fmt::print(" BackupWriteWaitTime: {:02x}\n", (uint32_t)mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_BackupWriteWaitTime]);
fmt::print(" BackupSecurityVersion: {:02x}\n", (uint32_t)mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_BackupSecurityVersion]);
fmt::print(" CardInfo: {:02x}\n", (uint32_t)mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_CardInfo]);
byte_t card_device = (mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_CardDevice] | mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_CardDevice_Deprecated]);
fmt::print(" CardDevice: {:02x} ({})\n", (uint32_t)card_device, getCardDeviceString(card_device));
fmt::print(" MediaPlatform: {:02x}", (uint32_t)mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_MediaPlatform]);
for (size_t bit = 0; bit < mHeader.ncsd_header.flags.media_platform.bit_size(); bit++)
{
if (mHeader.ncsd_header.flags.media_platform.test(bit))
{
fmt::print(" [{}]", getPlatformString(bit));
}
}
fmt::print("\n");
fmt::print(" MediaType: {:02x} ({})\n", (uint32_t)mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_MediaType], getMediaTypeString(mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_MediaType]));
fmt::print(" MediaBlockSize: {:02x} (0x{:x})\n", (uint32_t)mHeader.ncsd_header.flags[mHeader.NcsdFlagIndex_MediaBlockSize], mBlockSize);
// card info
fmt::print("CardInfo:\n");
fmt::print(" WriteableRegion: 0x{:08X}\n", mHeader.card_info.writable_region.unwrap());
fmt::print(" CardType: {} ({:X})\n", getCardTypeString(mHeader.card_info.flag.card_type), (uint32_t)mHeader.card_info.flag.card_type);
fmt::print(" CryptoType: {} ({:X})\n", getCryptoTypeString(mHeader.card_info.flag.crypto_type), (uint32_t)mHeader.card_info.flag.crypto_type);
// mastering metadata
fmt::print("MasteringMetadata:\n");
fmt::print(" MediaSizeUsed: 0x{:08X}\n", mHeader.mastering_info.media_size_used.unwrap());
fmt::print(" TitleVersion: {} (v{:d})\n", getTitleVersionString(mHeader.mastering_info.title_version.unwrap()), mHeader.mastering_info.title_version.unwrap());
fmt::print(" CardRevision: {:d}\n", (uint32_t)mHeader.mastering_info.card_revision.unwrap());
fmt::print(" CVer TitleId: {:016x}\n", mHeader.mastering_info.cver_title_id.unwrap());
fmt::print(" CVer Version: {} (v{:d})\n", getTitleVersionString(mHeader.mastering_info.cver_title_version.unwrap()), mHeader.mastering_info.cver_title_version.unwrap());
// initial data
fmt::print("InitialData:\n");
fmt::print(" KeySource: {}\n", tc::cli::FormatUtil::formatBytesAsString(mHeader.initial_data.key_source.data(), mHeader.initial_data.key_source.size(), true, ""));
fmt::print(" Enc TitleKey: {}", tc::cli::FormatUtil::formatBytesAsString(mHeader.initial_data.encrypted_title_key.data(), mHeader.initial_data.encrypted_title_key.size(), true, ""));
if (mDecryptedTitleKey.isSet())
{
fmt::print(" (decrypted: {})", tc::cli::FormatUtil::formatBytesAsString(mDecryptedTitleKey.get().data(), mDecryptedTitleKey.get().size(), true, ""));
}
fmt::print("\n");
fmt::print(" MAC: {:6}"" {}\n", getValidString(mValidInitialDataMac), tc::cli::FormatUtil::formatBytesAsString(mHeader.initial_data.mac.data(), mHeader.initial_data.mac.size(), true, ""));
// card device info
fmt::print("CardDeviceInfo:\n");
fmt::print(" TitleKey: {}\n", tc::cli::FormatUtil::formatBytesAsString(mHeader.card_device_info.title_key.data(), mHeader.card_device_info.title_key.size(), true, ""));
}
void ctrtool::CciProcess::printFs()
{
tc::io::sDirectoryListing dir;
mFsReader->getDirectoryListing(tc::io::Path("/"), dir);
fmt::print("[CCI Filesystem]\n");
fmt::print(" CCI:/\n");
for (auto itr = dir.file_list.begin(); itr != dir.file_list.end(); itr++)
{
fmt::print(" {}\n", *itr);
}
}
void ctrtool::CciProcess::extractFs()
{
tc::io::LocalFileSystem local_fs;
tc::io::sDirectoryListing dir;
mFsReader->getDirectoryListing(tc::io::Path("/"), dir);
local_fs.createDirectory(mExtractPath.get());
// iterate thru child files
tc::ByteData cache = tc::ByteData(0x10000);
size_t cache_read_len;
tc::io::Path out_path;
std::shared_ptr<tc::io::IStream> in_stream;
std::shared_ptr<tc::io::IStream> out_stream;
for (auto itr = dir.file_list.begin(); itr != dir.file_list.end(); itr++)
{
// build out path
out_path = mExtractPath.get() + *itr;
fmt::print("Saving {}...\n", out_path.to_string());
// begin export
mFsReader->openFile(*itr, tc::io::FileMode::Open, tc::io::FileAccess::Read, in_stream);
local_fs.openFile(out_path, 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 RomFs file.");
}
out_stream->write(cache.data(), cache_read_len);
remaining_data -= int64_t(cache_read_len);
}
}
}
void ctrtool::CciProcess::processContent()
{
if (mContentIndex >= ntd::n3ds::NcsdCommonHeader::kPartitionNum)
{
fmt::print(stderr, "Content index {:d} isn't valid for CCI, use index 0-7, defaulting to 0 now.\n", mContentIndex);
mContentIndex = 0;
}
if (mHeader.ncsd_header.partition_offsetsize[mContentIndex].blk_size.unwrap() != 0)
{
mNcchProcess.setInputStream(std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mHeader.ncsd_header.partition_offsetsize[mContentIndex].blk_offset.unwrap() * mBlockSize, mHeader.ncsd_header.partition_offsetsize[mContentIndex].blk_size.unwrap() * mBlockSize)));
mNcchProcess.process();
}
}
std::string ctrtool::CciProcess::getValidString(byte_t validstate)
{
std::string ret_str;
switch (validstate)
{
case ValidState::Unchecked:
ret_str = "";
break;
case ValidState::Good:
ret_str = "(GOOD)";
break;
case ValidState::Fail:
default:
ret_str = "(FAIL)";
break;
}
return ret_str;
}
std::string ctrtool::CciProcess::getRomSizeString(uint32_t rom_blk_size)
{
std::string ret_str;
switch (rom_blk_size)
{
case ntd::n3ds::CciHeader::RomSize_128MB :
ret_str = "128MB";
break;
case ntd::n3ds::CciHeader::RomSize_256MB :
ret_str = "256MB";
break;
case ntd::n3ds::CciHeader::RomSize_512MB :
ret_str = "512MB";
break;
case ntd::n3ds::CciHeader::RomSize_1GB :
ret_str = "1GB";
break;
case ntd::n3ds::CciHeader::RomSize_2GB :
ret_str = "2GB";
break;
case ntd::n3ds::CciHeader::RomSize_4GB :
ret_str = "4GB";
break;
default:
ret_str = fmt::format("0x{:08X} blocks", rom_blk_size);
}
return ret_str;
}
std::string ctrtool::CciProcess::getMediaTypeString(byte_t media_type)
{
std::string ret_str;
switch (media_type)
{
case ntd::n3ds::NcsdCommonHeader::MediaType_InnerDevice :
ret_str = "Inner Device";
break;
case ntd::n3ds::NcsdCommonHeader::MediaType_Card1 :
ret_str = "CARD1";
break;
case ntd::n3ds::NcsdCommonHeader::MediaType_Card2 :
ret_str = "CARD2";
break;
case ntd::n3ds::NcsdCommonHeader::MediaType_ExtendedDevice :
ret_str = "Extended Device";
break;
default:
ret_str = fmt::format("Unknown (0x{:02x})", media_type);
}
return ret_str;
}
std::string ctrtool::CciProcess::getCardDeviceString(byte_t card_device)
{
std::string ret_str;
switch (card_device)
{
case ntd::n3ds::CciHeader::CardDevice_Unspecified :
ret_str = "Not Specified";
break;
case ntd::n3ds::CciHeader::CardDevice_NorFlash :
ret_str = "NorFlash";
break;
case ntd::n3ds::CciHeader::CardDevice_None :
ret_str = "None";
break;
case ntd::n3ds::CciHeader::CardDevice_BT :
ret_str = "BT";
break;
default:
ret_str = fmt::format("Unknown (0x{:02x})", card_device);
}
return ret_str;
}
std::string ctrtool::CciProcess::getPlatformString(size_t bit)
{
std::string ret_str;
switch(bit)
{
case ntd::n3ds::NcsdCommonHeader::MediaPlatform_CTR :
ret_str = "CTR";
break;
case ntd::n3ds::NcsdCommonHeader::MediaPlatform_SNAKE :
ret_str = "SNAKE";
break;
default:
ret_str = fmt::format("Unknown (bit {:d})", bit);
}
return ret_str;
}
std::string ctrtool::CciProcess::getCardTypeString(byte_t card_type)
{
std::string ret_str;
switch(card_type)
{
case ntd::n3ds::CciHeader::CardType_S1 :
ret_str = "S1";
break;
case ntd::n3ds::CciHeader::CardType_S2 :
ret_str = "S2";
break;
default:
ret_str = "Unknown";
break;
}
return ret_str;
}
std::string ctrtool::CciProcess::getCryptoTypeString(byte_t crypto_type)
{
std::string ret_str;
switch(crypto_type)
{
case ntd::n3ds::CciHeader::CryptoType_Secure :
ret_str = "Secure";
break;
case ntd::n3ds::CciHeader::CryptoType_FixedKey :
ret_str = "FixedKey";
break;
default:
ret_str = "Unknown";
break;
}
return ret_str;
}
std::string ctrtool::CciProcess::getTitleVersionString(uint16_t version)
{
return fmt::format("{major:d}.{minor:d}.{build:d}", fmt::arg("major", (uint32_t)((version >> 10) & 0x3F)), fmt::arg("minor", (uint32_t)((version >> 4) & 0x3F)), fmt::arg("build", (uint32_t)(version & 0xF)));
}
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include "NcchProcess.h"
#include <tc/Optional.h>
#include <tc/io/IFileSystem.h>
#include <ntd/n3ds/cci.h>
namespace ctrtool {
class CciProcess
{
public:
CciProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setCliOutputMode(bool show_header_info, bool show_fs);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setExtractPath(const tc::io::Path& extract_path);
void setContentIndex(size_t index);
// ncch settings passed on
void setRawMode(bool raw);
void setPlainMode(bool plain);
void setShowSyscallName(bool show_name);
void setNcchRegionProcessOutputMode(NcchProcess::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);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mShowHeaderInfo;
bool mShowFs;
bool mVerbose;
bool mVerify;
tc::Optional<tc::io::Path> mExtractPath;
size_t mContentIndex;
int64_t mBlockSize;
int64_t mUsedImageSize;
byte_t mValidSignature;
byte_t mValidInitialDataMac;
ntd::n3ds::CciHeader mHeader;
tc::Optional<KeyBag::Aes128Key> mDecryptedTitleKey;
ctrtool::NcchProcess mNcchProcess;
std::shared_ptr<tc::io::IFileSystem> mFsReader;
void importHeader();
void verifyHeader();
void printHeader();
void printFs();
void extractFs();
void processContent();
// string utils
std::string getValidString(byte_t validstate);
std::string getRomSizeString(uint32_t rom_blk_size);
std::string getMediaTypeString(byte_t media_type);
std::string getCardDeviceString(byte_t card_device);
std::string getPlatformString(size_t bit);
std::string getCardTypeString(byte_t card_type);
std::string getCryptoTypeString(byte_t crypto_type);
std::string getTitleVersionString(uint16_t version);
};
}
+913
View File
@@ -0,0 +1,913 @@
#include "CiaProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>
#include <tc/NotSupportedException.h>
#include <ntd/n3ds/CiaFsSnapshotGenerator.h>
#include <ntd/n3ds/CtrKeyGenerator.h>
ctrtool::CiaProcess::CiaProcess() :
mModuleLabel("ctrtool::CiaProcess"),
mInputStream(),
mKeyBag(),
mShowHeaderInfo(false),
mShowFs(false),
mVerbose(false),
mVerify(false),
mCertExtractPath(),
mTikExtractPath(),
mTmdExtractPath(),
mContentExtractPath(),
mFooterExtractPath(),
mContentIndex(0),
mIssuerSigner(),
mCertChain(),
mCertSigValid(),
mTicket(),
mTicketSigValid(ValidState::Unchecked),
mTitleMetaData(),
mTitleMetaDataSigValid(ValidState::Unchecked),
mDecryptedTitleKey(),
mIsTwlTitle(false),
mNcchProcess(),
mFsReader()
{
memset(&mHeader, 0, sizeof(mHeader));
}
void ctrtool::CiaProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::CiaProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
mKeyBag = key_bag;
mNcchProcess.setKeyBag(key_bag);
}
void ctrtool::CiaProcess::setCliOutputMode(bool show_header_info, bool show_fs)
{
mShowHeaderInfo = show_header_info;
mShowFs = show_fs;
}
void ctrtool::CiaProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
mNcchProcess.setVerboseMode(verbose);
}
void ctrtool::CiaProcess::setVerifyMode(bool verify)
{
mVerify = verify;
mNcchProcess.setVerifyMode(verify);
}
void ctrtool::CiaProcess::setCertExtractPath(const tc::io::Path& extract_path)
{
mCertExtractPath = extract_path;
}
void ctrtool::CiaProcess::setTikExtractPath(const tc::io::Path& extract_path)
{
mTikExtractPath = extract_path;
}
void ctrtool::CiaProcess::setTmdExtractPath(const tc::io::Path& extract_path)
{
mTmdExtractPath = extract_path;
}
void ctrtool::CiaProcess::setContentExtractPath(const tc::io::Path& extract_path)
{
mContentExtractPath = extract_path;
}
void ctrtool::CiaProcess::setFooterExtractPath(const tc::io::Path& extract_path)
{
mFooterExtractPath = extract_path;
}
void ctrtool::CiaProcess::setContentIndex(size_t index)
{
mContentIndex = index;
}
void ctrtool::CiaProcess::setRawMode(bool raw)
{
mNcchProcess.setRawMode(raw);
}
void ctrtool::CiaProcess::setPlainMode(bool plain)
{
mNcchProcess.setPlainMode(plain);
}
void ctrtool::CiaProcess::setShowSyscallName(bool show_name)
{
mNcchProcess.setShowSyscallName(show_name);
}
void ctrtool::CiaProcess::setNcchRegionProcessOutputMode(NcchProcess::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)
{
mNcchProcess.setRegionProcessOutputMode(region, show_info, show_fs, bin_extract_path, fs_extract_path);
}
void ctrtool::CiaProcess::process()
{
importIssuerProfiles();
importHeader();
if (mVerify)
{
verifyMetadata();
verifyContent();
}
if (mShowHeaderInfo)
{
printHeader();
}
extractCia();
processContent();
}
void ctrtool::CiaProcess::importIssuerProfiles()
{
// import issuer profiles from keybag
for (auto itr = mKeyBag.broadon_rsa_signer.begin(); itr != mKeyBag.broadon_rsa_signer.end(); itr++)
{
brd::es::ESSigType sigType = itr->first == "Root" ? brd::es::ESSigType::RSA4096_SHA256 : brd::es::ESSigType::RSA2048_SHA256;
mIssuerSigner.insert(std::pair<std::string, std::shared_ptr<ntd::n3ds::es::ISigner>>(itr->first, std::make_shared<ntd::n3ds::es::RsaSigner>(ntd::n3ds::es::RsaSigner(sigType, itr->first, itr->second.key))));
}
}
void ctrtool::CiaProcess::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::CiaHeader))
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small. (Too small to read header).");
}
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::CiaHeader));
// check the header size
if (mHeader.header_size.unwrap() != sizeof(ntd::n3ds::CiaHeader))
{
throw tc::InvalidOperationException(mModuleLabel, "CiaHeader is corrupted (Bad header size).");
}
// check cia type
if (mHeader.type.unwrap() != mHeader.Type_Normal)
{
throw tc::InvalidOperationException(mModuleLabel, fmt::format("CiaHeader has an unsupported Type (0x{:04x}).", mHeader.type.unwrap()));
}
// check format versions
if (mHeader.format_version.unwrap() != mHeader.FormatVersion_Default && mHeader.format_version.unwrap() != mHeader.FormatVersion_SimpleCia)
{
throw tc::InvalidOperationException(mModuleLabel, fmt::format("CiaHeader has an unsupported FormatVersion (0x{:04x}).", mHeader.format_version.unwrap()));
}
// determine expected CIA size
int64_t pos = 0;
// add header size
pos += mHeader.header_size.unwrap();
// add cert size
if (mHeader.certificate_size.unwrap())
{
pos = align<int64_t>(pos, ntd::n3ds::CiaHeader::kCiaSectionAlignment);
mCertSizeInfo.offset = pos;
mCertSizeInfo.size = mHeader.certificate_size.unwrap();
pos += mCertSizeInfo.size;
}
// add ticket size
if (mHeader.ticket_size.unwrap())
{
pos = align<int64_t>(pos, ntd::n3ds::CiaHeader::kCiaSectionAlignment);
mTikSizeInfo.offset = pos;
mTikSizeInfo.size = mHeader.ticket_size.unwrap();
pos += mTikSizeInfo.size;
}
// add tmd size
if (mHeader.tmd_size.unwrap())
{
pos = align<int64_t>(pos, ntd::n3ds::CiaHeader::kCiaSectionAlignment);
mTmdSizeInfo.offset = pos;
mTmdSizeInfo.size = mHeader.tmd_size.unwrap();
pos += mTmdSizeInfo.size;
}
// add content size
if (mHeader.content_size.unwrap())
{
pos = align<int64_t>(pos, ntd::n3ds::CiaHeader::kCiaSectionAlignment);
mContentSizeInfo.offset = pos;
mContentSizeInfo.size = mHeader.content_size.unwrap();
pos += mContentSizeInfo.size;
}
// add footer size
if (mHeader.footer_size.unwrap())
{
pos = align<int64_t>(pos, ntd::n3ds::CiaHeader::kCiaSectionAlignment);
mFooterSizeInfo.offset = pos;
mFooterSizeInfo.size = mHeader.footer_size.unwrap();
pos += mFooterSizeInfo.size;
}
if (mInputStream->length() < pos)
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small, given calculated CIA size.");
}
// process CIA
if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_Default)
{
std::shared_ptr<tc::io::IStream> cert_stream;
if (mCertSizeInfo.size > 0)
{
cert_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mCertSizeInfo.offset, mCertSizeInfo.size));
if (mCertSizeInfo.size < 0x1000000)
{
tc::ByteData cert_data = tc::ByteData(mCertSizeInfo.size);
cert_stream->seek(0, tc::io::SeekOrigin::Begin);
cert_stream->read(cert_data.data(), cert_data.size());
for (size_t certchain_pos = 0; certchain_pos < cert_data.size();)
{
size_t certbin_size = ntd::n3ds::es::getCertificateSize(cert_data.data() + certchain_pos);
if (certbin_size == 0)
{
throw tc::InvalidOperationException(mModuleLabel, "Certificate chain had invalid data.");
}
mCertChain.push_back(ntd::n3ds::es::CertificateDeserialiser(std::make_shared<tc::io::SubStream>(tc::io::SubStream(cert_stream, certchain_pos, certbin_size))));
mCertSigValid.push_back(ValidState::Unchecked);
certchain_pos += certbin_size;
}
}
else
{
fmt::print("[LOG] Certificate chain too large, cannot verify Ticket or TitleMetaData.\n");
mCertSizeInfo.size = 0;
mCertSizeInfo.offset = 0;
}
}
// TODO load certificates from KeyBag
else
{
fmt::print("[LOG] CIA has no Certificate, cannot verify Ticket or TitleMetaData.\n");
}
if (mTikSizeInfo.size > 0)
{
mTicket = ntd::n3ds::es::TicketDeserialiser(std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mTikSizeInfo.offset, mTikSizeInfo.size)));
// determine title key
if (mKeyBag.fallback_title_key.isSet())
{
fmt::print("[LOG] Using fallback titlekey.\n");
mDecryptedTitleKey = mKeyBag.fallback_title_key.get();
}
else if (mKeyBag.common_key.find(mTicket.key_id) != mKeyBag.common_key.end())
{
fmt::print("[LOG] Decrypting titlekey from ticket.\n");
// get common key
auto common_key = mKeyBag.common_key[mTicket.key_id];
// initialise iv
std::array<byte_t, 16> title_key_iv;
memset(title_key_iv.data(), 0, title_key_iv.size());
((tc::bn::be64<uint64_t>*)(&(title_key_iv[0])))->wrap(mTicket.title_id);
// decrypt title key
std::array<byte_t, 16> title_key;
tc::crypto::DecryptAes128Cbc(title_key.data(), mTicket.title_key.data(), title_key.size(), common_key.data(), common_key.size(), title_key_iv.data(), title_key_iv.size());
mDecryptedTitleKey = title_key;
}
else
{
fmt::print("[LOG] Cannot determine titlekey.\n");
}
}
else
{
throw tc::InvalidOperationException(mModuleLabel, "CIA has no Ticket.");
}
if (mTmdSizeInfo.size > 0)
{
mTitleMetaData = ntd::n3ds::es::TitleMetaDataDeserialiser(std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mTmdSizeInfo.offset, mTmdSizeInfo.size)));
mIsTwlTitle = isTwlTitle(mTitleMetaData.title_id);
}
else
{
throw tc::InvalidOperationException(mModuleLabel, "CIA has no TitleMetaData.");
}
if (mContentSizeInfo.size > 0)
{
int64_t content_pos = 0;
for (auto itr = mTitleMetaData.content_info.begin(); itr != mTitleMetaData.content_info.end(); itr++)
{
// skip content not included
if (mHeader.content_bitarray.test(itr->index) == false) continue;
ContentInfo cnt;
cnt.offset = mContentSizeInfo.offset + align<int64_t>(content_pos, (int64_t)ntd::n3ds::CiaHeader::kCiaContentAlignment);
cnt.size = itr->size;
cnt.cid = itr->id;
cnt.cindex = itr->index;
cnt.is_encrypted = itr->is_encrypted;
cnt.is_hashed = true;
cnt.hash = itr->hash;
content_pos += cnt.size;
mContentInfo[cnt.cindex] = std::move(cnt);
}
}
else
{
throw tc::InvalidOperationException(mModuleLabel, "CIA has no Content.");
}
}
else if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_SimpleCia)
{
if (mContentSizeInfo.size > 0)
{
ContentInfo cnt;
cnt.offset = mContentSizeInfo.offset;
cnt.size = mContentSizeInfo.size;
cnt.cid = 0;
cnt.cindex = 0;
cnt.is_encrypted = false;
cnt.is_hashed = false;
memset(cnt.hash.data(), 0, cnt.hash.size());
mContentInfo[cnt.cindex] = std::move(cnt);
mIsTwlTitle = false;
}
else
{
throw tc::InvalidOperationException(mModuleLabel, "CIA has no Content.");
}
}
else
{
throw tc::NotSupportedException(mModuleLabel, fmt::format("Unsupported CIA format version: 0x{:04x}.", mHeader.format_version.unwrap()));
}
}
void ctrtool::CiaProcess::verifyMetadata()
{
// validate signatures
if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_Default && mCertSizeInfo.size > 0)
{
// verify cert
for (size_t i = 0; i < mCertChain.size(); i++)
{
auto issuer_itr = mIssuerSigner.find(mCertChain[i].signature.issuer);
if (issuer_itr != mIssuerSigner.end() && issuer_itr->second->getSigType() == mCertChain[i].signature.sig_type)
{
//fmt::print("CertHash[{:d}]: {}\n", i, getTruncatedBytesString(mCertChain[i].calculated_hash.data(), mCertChain[i].calculated_hash.size(), mVerbose));
mCertSigValid[i] = issuer_itr->second->verifyHash(mCertChain[i].calculated_hash.data(), mCertChain[i].signature.sig.data()) ? ValidState::Good : ValidState::Fail;
}
else
{
// cannot locate rsa key to verify
fmt::print(stderr, "Could not read public key for \"{}\" (certificate).\n", mCertChain[i].signature.issuer);
mCertSigValid[i] = ValidState::Fail;
}
}
}
if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_Default && mTikSizeInfo.size > 0)
{
// verify ticket
auto issuer_itr = mIssuerSigner.find(mTicket.signature.issuer);
if (issuer_itr != mIssuerSigner.end() && issuer_itr->second->getSigType() == mTicket.signature.sig_type)
{
//fmt::print("TikHash: {}\n", getTruncatedBytesString(mTicket.calculated_hash.data(), mTicket.calculated_hash.size(), mVerbose));
mTicketSigValid = issuer_itr->second->verifyHash(mTicket.calculated_hash.data(), mTicket.signature.sig.data()) ? ValidState::Good : ValidState::Fail;
}
else
{
// cannot locate rsa key to verify
fmt::print(stderr, "Could not read public key for \"{}\" (ticket).\n", mTicket.signature.issuer);
mTicketSigValid = ValidState::Fail;
}
}
if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_Default && mTmdSizeInfo.size > 0)
{
// verify tmd
auto issuer_itr = mIssuerSigner.find(mTitleMetaData.signature.issuer);
if (issuer_itr != mIssuerSigner.end() && issuer_itr->second->getSigType() == mTitleMetaData.signature.sig_type)
{
//fmt::print("TmdHash: {}\n", getTruncatedBytesString(mTitleMetaData.calculated_hash.data(), mTitleMetaData.calculated_hash.size(), mVerbose));
mTitleMetaDataSigValid = issuer_itr->second->verifyHash(mTitleMetaData.calculated_hash.data(), mTitleMetaData.signature.sig.data()) ? ValidState::Good : ValidState::Fail;
}
else
{
// cannot locate rsa key to verify
fmt::print(stderr, "Could not read public key for \"{}\" (tmd).\n", mTitleMetaData.signature.issuer);
mTitleMetaDataSigValid = ValidState::Fail;
}
}
}
void ctrtool::CiaProcess::verifyContent()
{
std::shared_ptr<tc::io::IStream> content_stream;
tc::ByteData cache = tc::ByteData(0x10000);
size_t cache_read_len;
tc::crypto::Sha256Generator sha256_calc;
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> sha256_hash;
if (mContentInfo.size() > 0)
{
for (auto itr = mContentInfo.begin(); itr != mContentInfo.end(); itr++)
{
// skip unhashed content
if (itr->second.is_hashed == false) continue;
content_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, itr->second.offset, itr->second.size));
if (itr->second.is_encrypted && mDecryptedTitleKey.isSet())
{
tc::crypto::Aes128CbcEncryptedStream::iv_t content_iv;
createContentIv(content_iv, itr->second.cindex);
content_stream = std::shared_ptr<tc::crypto::Aes128CbcEncryptedStream>(new tc::crypto::Aes128CbcEncryptedStream(content_stream, mDecryptedTitleKey.get(), content_iv));
}
sha256_calc.initialize();
memset(sha256_hash.data(), 0, sha256_hash.size());
content_stream->seek(0, tc::io::SeekOrigin::Begin);
for (int64_t remaining_data = content_stream->length(); remaining_data > 0;)
{
cache_read_len = content_stream->read(cache.data(), cache.size());
if (cache_read_len == 0)
{
throw tc::io::IOException(mModuleLabel, "Failed to read from source file.");
}
sha256_calc.update(cache.data(), cache_read_len);
remaining_data -= int64_t(cache_read_len);
}
sha256_calc.getHash(sha256_hash.data());
itr->second.valid_state = memcmp(sha256_hash.data(), itr->second.hash.data(), sha256_hash.size()) == 0 ? ValidState::Good : ValidState::Fail;
}
}
}
void ctrtool::CiaProcess::printHeader()
{
{
fmt::print("CiaHeader:\n");
fmt::print("|- HeaderSize: 0x{:x}\n", mHeader.header_size.unwrap());
fmt::print("|- Type: {} (0x{:04x})\n", getCiaTypeString(mHeader.type.unwrap()), mHeader.type.unwrap());
fmt::print("|- FormatVersion: {} (0x{:04x})\n", getFormatVersionString(mHeader.format_version.unwrap()), mHeader.format_version.unwrap());
fmt::print("|- CertificateSize: 0x{:x}\n", mHeader.certificate_size.unwrap());
fmt::print("|- TicketSize: 0x{:x}\n", mHeader.ticket_size.unwrap());
fmt::print("|- TitleMetaSize: 0x{:x}\n", mHeader.tmd_size.unwrap());
fmt::print("|- FooterSize: 0x{:x}\n", mHeader.footer_size.unwrap());
fmt::print("|- ContentSize: 0x{:x}\n", mHeader.content_size.unwrap());
fmt::print("\\- EnabledContent:\n");
std::vector<size_t> enabled_content;
for (size_t i = 0; i < mHeader.content_bitarray.bit_size(); i++)
{
if (mHeader.content_bitarray.test(i))
{
enabled_content.push_back(i);
}
}
for (size_t i = 0; i < enabled_content.size(); i++)
{
fmt::print(" {:1}- 0x{:04x}\n", (i+1 < enabled_content.size() ? "|" : "\\"), enabled_content[i]);
}
}
if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_Default && mCertSizeInfo.size > 0)
{
fmt::print("Certificate Chain:\n");
for (size_t i = 0; i < mCertChain.size(); i++)
{
#define _CERT_FORMAT_MACRO(x,y) (i+1 < mCertChain.size() ? (x) : (y))
fmt::print("{:1}- Certificate {:d}:\n", _CERT_FORMAT_MACRO("|","\\"), i);
fmt::print("{:1} |- DigitalSignature: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getValidString(mCertSigValid[i]));
fmt::print("{:1} | |- SigType: {:s} (0x{:x})\n", _CERT_FORMAT_MACRO("|"," "), getSigTypeString(mCertChain[i].signature.sig_type), (uint32_t)mCertChain[i].signature.sig_type);
fmt::print("{:1} | |- Issuer: {:s}\n", _CERT_FORMAT_MACRO("|"," "), mCertChain[i].signature.issuer);
fmt::print("{:1} | \\- Signature: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getTruncatedBytesString(mCertChain[i].signature.sig.data(), mCertChain[i].signature.sig.size(), mVerbose));
fmt::print("{:1} |- Subject: {:s}\n", _CERT_FORMAT_MACRO("|"," "), mCertChain[i].subject);
//fmt::print("{:1} |- Date: {:d}\n", _CERT_FORMAT_MACRO("|"," "), mCertChain[i].date);
fmt::print("{:1} \\- PublicKey: {:s} (0x{:x})\n", _CERT_FORMAT_MACRO("|"," "), getCertificatePublicKeyTypeString(mCertChain[i].public_key_type), (uint32_t)mCertChain[i].public_key_type);
switch (mCertChain[i].public_key_type)
{
case brd::es::ESCertPubKeyType::RSA4096:
fmt::print("{:1} |- m: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getTruncatedBytesString(mCertChain[i].rsa4096_public_key.m.data(), mCertChain[i].rsa4096_public_key.m.size(), mVerbose));
fmt::print("{:1} \\- e: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getTruncatedBytesString(mCertChain[i].rsa4096_public_key.e.data(), mCertChain[i].rsa4096_public_key.e.size(), mVerbose));
break;
case brd::es::ESCertPubKeyType::RSA2048:
fmt::print("{:1} |- m: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getTruncatedBytesString(mCertChain[i].rsa2048_public_key.m.data(), mCertChain[i].rsa2048_public_key.m.size(), mVerbose));
fmt::print("{:1} \\- e: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getTruncatedBytesString(mCertChain[i].rsa2048_public_key.e.data(), mCertChain[i].rsa2048_public_key.e.size(), mVerbose));
break;
case brd::es::ESCertPubKeyType::ECC:
fmt::print("{:1} |- x: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getTruncatedBytesString(mCertChain[i].ecc233_public_key.x.data(), mCertChain[i].ecc233_public_key.x.size(), mVerbose));
fmt::print("{:1} \\- y: {:s}\n", _CERT_FORMAT_MACRO("|"," "), getTruncatedBytesString(mCertChain[i].ecc233_public_key.y.data(), mCertChain[i].ecc233_public_key.y.size(), mVerbose));
break;
default:
break;
}
#undef _CERT_FORMAT_MACRO
}
}
if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_Default && mTikSizeInfo.size > 0)
{
fmt::print("Ticket:\n");
fmt::print("|- DigitalSignature: {:s} \n", getValidString(mTicketSigValid));
fmt::print("| |- SigType: {:s} (0x{:x})\n", getSigTypeString(mTicket.signature.sig_type), (uint32_t)mTicket.signature.sig_type);
fmt::print("| |- Issuer: {:s}\n", mTicket.signature.issuer);
fmt::print("| \\- Signature: {:s}\n", getTruncatedBytesString(mTicket.signature.sig.data(), mTicket.signature.sig.size(), mVerbose));
fmt::print("|- TitleKey: {}", tc::cli::FormatUtil::formatBytesAsString(mTicket.title_key.data(), mTicket.title_key.size(), true, ""));
if (mDecryptedTitleKey.isSet())
{
fmt::print(" (decrypted: {})", tc::cli::FormatUtil::formatBytesAsString(mDecryptedTitleKey.get().data(), mDecryptedTitleKey.get().size(), true, ""));
}
fmt::print("\n");
fmt::print("|- TicketId: {:016x}\n", mTicket.ticket_id);
fmt::print("|- DeviceId: {:08x}\n", mTicket.device_id);
fmt::print("|- TitleId: {:016x}\n", mTicket.title_id);
fmt::print("|- TicketVersion: {} ({:d})\n", getTitleVersionString(mTicket.ticket_version), mTicket.ticket_version);
fmt::print("|- LicenseType: {:02x}\n", mTicket.license_type);
fmt::print("|- KeyId: {:02x}\n", mTicket.key_id);
fmt::print("|- ECAccountID: {:08x}\n", mTicket.ec_account_id);
fmt::print("|- DemoLaunchCnt: {:d}\n", mTicket.launch_count);
fmt::print("\\- EnabledContent:\n");
std::vector<size_t> enabled_content;
for (size_t i = 0; i < mTicket.enabled_content.size(); i++)
{
if (mTicket.enabled_content.test(i))
{
enabled_content.push_back(i);
}
}
for (size_t i = 0; i < enabled_content.size(); i++)
{
fmt::print(" {:1}- 0x{:04x}\n", (i+1 < enabled_content.size() ? "|" : "\\"), enabled_content[i]);
}
}
if (mHeader.format_version.unwrap() == ntd::n3ds::CiaHeader::FormatVersion_Default && mTmdSizeInfo.size > 0)
{
fmt::print("TitleMetaData:\n");
fmt::print("|- DigitalSignature: {:s}\n", getValidString(mTitleMetaDataSigValid));
fmt::print("| |- SigType: {:s} (0x{:x})\n", getSigTypeString(mTitleMetaData.signature.sig_type), (uint32_t)mTitleMetaData.signature.sig_type);
fmt::print("| |- Issuer: {:s}\n", mTitleMetaData.signature.issuer);
fmt::print("| \\- Signature: {:s}\n", getTruncatedBytesString(mTitleMetaData.signature.sig.data(), mTitleMetaData.signature.sig.size(), mVerbose));
fmt::print("|- TitleId: {:016x}\n", mTitleMetaData.title_id);
fmt::print("|- TitleVersion: {} ({:d})\n", getTitleVersionString(mTitleMetaData.title_version), mTitleMetaData.title_version);
fmt::print("|- CustomData:\n");
// TWL Title
if (isTwlTitle(mTitleMetaData.title_id))
{
fmt::print("| |- PublicSaveDataSize: 0x{:x}\n", mTitleMetaData.twl_custom_data.public_save_data_size);
fmt::print("| |- PrivateSaveDataSize: 0x{:x}\n", mTitleMetaData.twl_custom_data.private_save_data_size);
fmt::print("| \\- Flag: 0x{:02x}\n", mTitleMetaData.twl_custom_data.flag);
}
// CTR
else
{
fmt::print("| |- SaveDataSize: 0x{:x}\n", mTitleMetaData.ctr_custom_data.save_data_size);
fmt::print("| \\- IsSnakeOnly: {}\n", mTitleMetaData.ctr_custom_data.is_snake_only);
}
fmt::print("\\- ContentInfo:\n");
for (size_t i = 0; i < mTitleMetaData.content_info.size(); i++)
{
fmt::print(" {:1}- 0x{:04x}:\n", (i+1 < mTitleMetaData.content_info.size() ? "|" : "\\"), mTitleMetaData.content_info[i].index);
fmt::print(" {:1} |- ContentId: 0x{:08x}\n", (i+1 < mTitleMetaData.content_info.size() ? "|" : ""), mTitleMetaData.content_info[i].id);
fmt::print(" {:1} |- Encrypted: {}\n", (i+1 < mTitleMetaData.content_info.size() ? "|" : ""), (mTitleMetaData.content_info[i].is_encrypted ? "YES" : "NO"));
fmt::print(" {:1} |- Optional: {}\n", (i+1 < mTitleMetaData.content_info.size() ? "|" : ""), (mTitleMetaData.content_info[i].is_optional ? "YES" : "NO"));
fmt::print(" {:1} |- Size: 0x{:x}\n", (i+1 < mTitleMetaData.content_info.size() ? "|" : ""), mTitleMetaData.content_info[i].size);
fmt::print(" {:1} \\- Hash: {:6} {}\n", (i+1 < mTitleMetaData.content_info.size() ? "|" : ""),
getValidString(mContentInfo.find(mTitleMetaData.content_info[i].index) != mContentInfo.end() ? mContentInfo[mTitleMetaData.content_info[i].index].valid_state : ValidState::Unchecked),
tc::cli::FormatUtil::formatBytesAsString(mTitleMetaData.content_info[i].hash.data(), mTitleMetaData.content_info[i].hash.size(), true, ""));
}
}
}
void ctrtool::CiaProcess::extractCia()
{
tc::io::Path out_path;
std::shared_ptr<tc::io::IStream> in_stream;
std::shared_ptr<tc::io::IStream> out_stream;
if (mCertExtractPath.isSet() && mCertSizeInfo.size > 0)
{
out_path = mCertExtractPath.get();
in_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mCertSizeInfo.offset, mCertSizeInfo.size));
out_stream = std::shared_ptr<tc::io::FileStream>(new tc::io::FileStream(out_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write));
fmt::print("Saving certs to {}...\n", out_path.to_string());
copyStream(in_stream, out_stream);
}
if (mTikExtractPath.isSet() && mTikSizeInfo.size > 0)
{
out_path = mTikExtractPath.get();
in_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mTikSizeInfo.offset, mTikSizeInfo.size));
out_stream = std::shared_ptr<tc::io::FileStream>(new tc::io::FileStream(out_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write));
fmt::print("Saving tik to {}...\n", out_path.to_string());
copyStream(in_stream, out_stream);
}
if (mTmdExtractPath.isSet() && mTmdSizeInfo.size > 0)
{
out_path = mTmdExtractPath.get();
in_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mTmdSizeInfo.offset, mTmdSizeInfo.size));
out_stream = std::shared_ptr<tc::io::FileStream>(new tc::io::FileStream(out_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write));
fmt::print("Saving tmd to {}...\n", out_path.to_string());
copyStream(in_stream, out_stream);
}
if (mFooterExtractPath.isSet() && mFooterSizeInfo.size > 0)
{
out_path = mFooterExtractPath.get();
in_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mFooterSizeInfo.offset, mFooterSizeInfo.size));
out_stream = std::shared_ptr<tc::io::FileStream>(new tc::io::FileStream(out_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write));
fmt::print("Saving meta to {}...\n", out_path.to_string());
copyStream(in_stream, out_stream);
}
if (mContentExtractPath.isSet() && mContentInfo.size() > 0)
{
for (auto itr = mContentInfo.begin(); itr != mContentInfo.end(); itr++)
{
out_path = mContentExtractPath.get();
out_path.back() = fmt::format("{}.{:04x}.{:08x}", out_path.back(), itr->second.cindex, itr->second.cid);
in_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, itr->second.offset, itr->second.size));
if (itr->second.is_encrypted && mDecryptedTitleKey.isSet())
{
tc::crypto::Aes128CbcEncryptedStream::iv_t content_iv;
createContentIv(content_iv, itr->second.cindex);
in_stream = std::shared_ptr<tc::crypto::Aes128CbcEncryptedStream>(new tc::crypto::Aes128CbcEncryptedStream(in_stream, mDecryptedTitleKey.get(), content_iv));
}
out_stream = std::shared_ptr<tc::io::FileStream>(new tc::io::FileStream(out_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write));
fmt::print("Saving content {:04x} to {}...\n", itr->second.cindex, out_path.to_string());
copyStream(in_stream, out_stream);
}
}
}
void ctrtool::CiaProcess::copyStream(const std::shared_ptr<tc::io::IStream>& in, const std::shared_ptr<tc::io::IStream>& out)
{
tc::ByteData cache = tc::ByteData(0x10000);
size_t cache_read_len;
in->seek(0, tc::io::SeekOrigin::Begin);
out->seek(0, tc::io::SeekOrigin::Begin);
for (int64_t remaining_data = in->length(); remaining_data > 0;)
{
cache_read_len = in->read(cache.data(), cache.size());
if (cache_read_len == 0)
{
throw tc::io::IOException(mModuleLabel, "Failed to read from source file.");
}
out->write(cache.data(), cache_read_len);
remaining_data -= int64_t(cache_read_len);
}
}
void ctrtool::CiaProcess::processContent()
{
if (mContentIndex >= ntd::n3ds::CiaHeader::kCiaMaxContentNum)
{
fmt::print(stderr, "Content index {:d} isn't valid for CIA, use index 0-{:d}, defaulting to 0 now.\n", mContentIndex, ((size_t)ntd::n3ds::CiaHeader::kCiaMaxContentNum)-1);
mContentIndex = 0;
}
if (mContentInfo.find(mContentIndex) != mContentInfo.end() && mContentInfo[mContentIndex].size != 0)
{
std::shared_ptr<tc::io::IStream> content_stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mInputStream, mContentInfo[mContentIndex].offset, mContentInfo[mContentIndex].size));
if (mContentInfo[mContentIndex].is_encrypted && mDecryptedTitleKey.isSet())
{
tc::crypto::Aes128CbcEncryptedStream::iv_t content_iv;
createContentIv(content_iv, mContentInfo[mContentIndex].cindex);
content_stream = std::shared_ptr<tc::crypto::Aes128CbcEncryptedStream>(new tc::crypto::Aes128CbcEncryptedStream(content_stream, mDecryptedTitleKey.get(), content_iv));
}
if (mIsTwlTitle == false)
{
mNcchProcess.setInputStream(content_stream);
mNcchProcess.process();
}
else
{
fmt::print("[LOG] TWL title processing not supported\n");
}
}
}
void ctrtool::CiaProcess::createContentIv(std::array<byte_t, 16>& content_iv, uint16_t index)
{
memset(content_iv.data(), 0, content_iv.size());
((tc::bn::be16<uint16_t>*)&content_iv[0])->wrap(index);
}
bool ctrtool::CiaProcess::isTwlTitle(uint64_t title_id)
{
return ((title_id >> 47) & 1) == 1;
}
std::string ctrtool::CiaProcess::getValidString(byte_t validstate)
{
std::string ret_str;
switch (validstate)
{
case ValidState::Unchecked:
ret_str = "";
break;
case ValidState::Good:
ret_str = "(GOOD)";
break;
case ValidState::Fail:
default:
ret_str = "(FAIL)";
break;
}
return ret_str;
}
std::string ctrtool::CiaProcess::getCiaTypeString(uint16_t type)
{
std::string ret_str;
switch (type)
{
case ntd::n3ds::CiaHeader::Type_Normal:
ret_str = "Normal";
break;
default:
ret_str = "Unknown";
break;
}
return ret_str;
}
std::string ctrtool::CiaProcess::getFormatVersionString(uint16_t format_version)
{
std::string ret_str;
switch (format_version)
{
case ntd::n3ds::CiaHeader::FormatVersion_Default:
ret_str = "Cia";
break;
case ntd::n3ds::CiaHeader::FormatVersion_SimpleCia:
ret_str = "SimpleCia";
break;
default:
ret_str = "Unknown";
break;
}
return ret_str;
}
std::string ctrtool::CiaProcess::getTruncatedBytesString(const byte_t* data, size_t len, bool do_not_truncate)
{
if (data == nullptr) { return fmt::format(""); }
std::string str = "";
if (len <= 8 || do_not_truncate)
{
str = tc::cli::FormatUtil::formatBytesAsString(data, len, true, "");
}
else
{
str = fmt::format("{:02X}{:02X}{:02X}{:02X}...{:02X}{:02X}{:02X}{:02X}", data[0], data[1], data[2], data[3], data[len-4], data[len-3], data[len-2], data[len-1]);
}
return str;
}
std::string ctrtool::CiaProcess::getSigTypeString(brd::es::ESSigType sig_type)
{
std::string ret_str;
switch (sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
ret_str = "RSA-4096-SHA1";
break;
case brd::es::ESSigType::RSA2048_SHA1:
ret_str = "RSA-2048-SHA1";
break;
case brd::es::ESSigType::ECC_SHA1:
ret_str = "ECDSA-233-SHA1";
break;
case brd::es::ESSigType::RSA4096_SHA256:
ret_str = "RSA-4096-SHA256";
break;
case brd::es::ESSigType::RSA2048_SHA256:
ret_str = "RSA-2048-SHA256";
break;
case brd::es::ESSigType::ECC_SHA256:
ret_str = "ECDSA-233-SHA256";
break;
default:
ret_str = fmt::format("0x{:x}", (uint32_t)sig_type);
}
return ret_str;
}
std::string ctrtool::CiaProcess::getCertificatePublicKeyTypeString(brd::es::ESCertPubKeyType public_key_type)
{
std::string ret_str;
switch (public_key_type)
{
case brd::es::ESCertPubKeyType::RSA4096:
ret_str = "RSA-4096";
break;
case brd::es::ESCertPubKeyType::RSA2048:
ret_str = "RSA-2048";
break;
case brd::es::ESCertPubKeyType::ECC:
ret_str = "ECC-233";
break;
default:
ret_str = fmt::format("0x{:x}", (uint32_t)public_key_type);
}
return ret_str;
}
std::string ctrtool::CiaProcess::getTitleVersionString(uint16_t version)
{
return fmt::format("{major:d}.{minor:d}.{build:d}", fmt::arg("major", (uint32_t)((version >> 10) & 0x3F)), fmt::arg("minor", (uint32_t)((version >> 4) & 0x3F)), fmt::arg("build", (uint32_t)(version & 0xF)));
}
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include "NcchProcess.h"
#include <tc/Optional.h>
#include <tc/io/IFileSystem.h>
#include <ntd/n3ds/cia.h>
#include <ntd/n3ds/es/RsaSigner.h>
#include <ntd/n3ds/es/Certificate.h>
#include <ntd/n3ds/es/Ticket.h>
#include <ntd/n3ds/es/TitleMetaData.h>
namespace ctrtool {
class CiaProcess
{
public:
CiaProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setCliOutputMode(bool show_header_info, bool show_fs);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setCertExtractPath(const tc::io::Path& extract_path);
void setTikExtractPath(const tc::io::Path& extract_path);
void setTmdExtractPath(const tc::io::Path& extract_path);
void setContentExtractPath(const tc::io::Path& extract_path);
void setFooterExtractPath(const tc::io::Path& extract_path);
void setContentIndex(size_t index);
// ncch settings passed on
void setRawMode(bool raw);
void setPlainMode(bool plain);
void setShowSyscallName(bool show_name);
void setNcchRegionProcessOutputMode(NcchProcess::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);
void process();
private:
std::string mModuleLabel;
// input args
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mShowHeaderInfo;
bool mShowFs;
bool mVerbose;
bool mVerify;
tc::Optional<tc::io::Path> mCertExtractPath;
tc::Optional<tc::io::Path> mTikExtractPath;
tc::Optional<tc::io::Path> mTmdExtractPath;
tc::Optional<tc::io::Path> mContentExtractPath;
tc::Optional<tc::io::Path> mFooterExtractPath;
size_t mContentIndex;
// process variables
ntd::n3ds::CiaHeader mHeader;
std::map<std::string, std::shared_ptr<ntd::n3ds::es::ISigner>> mIssuerSigner;
std::vector<ntd::n3ds::es::Certificate> mCertChain;
std::vector<ValidState> mCertSigValid;
ntd::n3ds::es::Ticket mTicket;
ValidState mTicketSigValid;
ntd::n3ds::es::TitleMetaData mTitleMetaData;
ValidState mTitleMetaDataSigValid;
tc::Optional<KeyBag::Aes128Key> mDecryptedTitleKey;
bool mIsTwlTitle;
ctrtool::NcchProcess mNcchProcess;
std::shared_ptr<tc::io::IFileSystem> mFsReader;
struct CiaSectionInfo
{
int64_t offset;
int64_t size;
CiaSectionInfo() : offset(0), size(0) {}
} mCertSizeInfo, mTikSizeInfo, mTmdSizeInfo, mContentSizeInfo, mFooterSizeInfo;
struct ContentInfo
{
int64_t offset;
int64_t size;
uint32_t cid;
uint16_t cindex;
bool is_encrypted;
bool is_hashed;
std::array<byte_t, 32> hash;
int valid_state;
ContentInfo() :
offset(0),
size(0),
cid(0),
cindex(0),
is_encrypted(false),
is_hashed(false),
valid_state(ValidState::Unchecked)
{
memset(hash.data(), 0, hash.size());
}
};
std::map<size_t, ContentInfo> mContentInfo;
// helper methods
void importIssuerProfiles();
void importHeader();
void verifyMetadata();
void verifyContent();
void printHeader();
void extractCia();
void copyStream(const std::shared_ptr<tc::io::IStream>& in, const std::shared_ptr<tc::io::IStream>& out);
void processContent();
void createContentIv(std::array<byte_t, 16>& content_iv, uint16_t index);
bool isTwlTitle(uint64_t title_id);
// string utils
std::string getValidString(byte_t validstate);
std::string getCiaTypeString(uint16_t type);
std::string getFormatVersionString(uint16_t format_version);
std::string getTruncatedBytesString(const byte_t* data, size_t len, bool do_not_truncate = false);
std::string getSigTypeString(brd::es::ESSigType sig_type);
std::string getCertificatePublicKeyTypeString(brd::es::ESCertPubKeyType public_key_type);
std::string getTitleVersionString(uint16_t version);
};
}
+192
View File
@@ -0,0 +1,192 @@
#include "CrrProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>
ctrtool::CrrProcess::CrrProcess() :
mModuleLabel("ctrtool::CrrProcess"),
mInputStream(),
mKeyBag(),
mShowInfo(false),
mVerbose(false),
mVerify(false),
mCrrData(false),
mValidCertificateSignature(ValidState::Unchecked),
mValidBodySignature(ValidState::Unchecked),
mValidUniqueId(ValidState::Unchecked)
{
memset((byte_t*)&mHeader, 0, sizeof(mHeader));
memset((byte_t*)&mBodyHeader, 0, sizeof(mBodyHeader));
}
void ctrtool::CrrProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::CrrProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
mKeyBag = key_bag;
}
void ctrtool::CrrProcess::setCliOutputMode(bool show_info)
{
mShowInfo = show_info;
}
void ctrtool::CrrProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
}
void ctrtool::CrrProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void ctrtool::CrrProcess::process()
{
// begin processing
importData();
if (mVerify)
verifyData();
if (mShowInfo)
printData();
}
void ctrtool::CrrProcess::importData()
{
// 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::CrrHeader) + sizeof(ntd::n3ds::CrrBodyHeader)))
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small to import header.");
}
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(mHeader));
mInputStream->read((byte_t*)&mBodyHeader, sizeof(mBodyHeader));
if (mHeader.struct_magic.unwrap() != mHeader.kStructMagic)
{
throw tc::InvalidOperationException(mModuleLabel, "Invalid struct magic.");
}
if (mBodyHeader.size.unwrap() % 0x1000 != 0)
{
throw tc::InvalidOperationException(mModuleLabel, "CRR file size was not aligned to 0x1000 bytes.");
}
if (((mBodyHeader.num_hash.unwrap() * 0x20) + mBodyHeader.hash_offset.unwrap()) > mBodyHeader.size.unwrap())
{
throw tc::InvalidOperationException(mModuleLabel, "CRR invalid hash geometry.");
}
if ((mBodyHeader.module_id_offset.unwrap() + mBodyHeader.module_id_size.unwrap()) > mBodyHeader.size.unwrap())
{
throw tc::InvalidOperationException(mModuleLabel, "CRR invalid module_id geometry.");
}
if (mInputStream->length() < int64_t(mBodyHeader.size.unwrap()))
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small for logical file size.");
}
mCrrData = tc::ByteData(mBodyHeader.size.unwrap());
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read(mCrrData.data(), mCrrData.size());
}
void ctrtool::CrrProcess::verifyData()
{
// validate certificate signature
if (mKeyBag.rsa_key.find(mKeyBag.RSAKEY_CRR) != mKeyBag.rsa_key.end())
{
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
tc::crypto::RsaKey pubkey = mKeyBag.rsa_key[mKeyBag.RSAKEY_CRR];
// generate hash
size_t offset = sizeof(mHeader) - sizeof(mHeader.body_certificate);
size_t size = sizeof(mHeader.body_certificate) - sizeof(mHeader.body_certificate.signature);
tc::crypto::GenerateSha256Hash(hash.data(), mCrrData.data() + offset, size);
// validate signature
mValidCertificateSignature = tc::crypto::VerifyRsa2048Pkcs1Sha256(mHeader.body_certificate.signature.data(), hash.data(), pubkey) ? ValidState::Good : ValidState::Fail;
}
else
{
fmt::print(stderr, "Could not read static CRR public key.\n");
mValidCertificateSignature = ValidState::Fail;
}
// validate body signature
{
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
tc::crypto::RsaKey pubkey = tc::crypto::RsaPublicKey(mHeader.body_certificate.crr_body_public_key.data(), mHeader.body_certificate.crr_body_public_key.size());
// generate hash
size_t offset = sizeof(mHeader) + sizeof(mBodyHeader.signature);
size_t size = (mBodyHeader.hash_offset.unwrap() + (mBodyHeader.num_hash.unwrap() * 0x20)) - offset;
tc::crypto::GenerateSha256Hash(hash.data(), mCrrData.data() + offset, size);
mValidBodySignature = tc::crypto::VerifyRsa2048Pkcs1Sha256(mBodyHeader.signature.data(), hash.data(), pubkey) ? ValidState::Good : ValidState::Fail;
}
// validate unique id
{
mValidUniqueId = ((mBodyHeader.unique_id.unwrap() & mHeader.body_certificate.unique_id_mask.unwrap()) == 0) ? ValidState::Good : ValidState::Fail;
}
}
void ctrtool::CrrProcess::printData()
{
fmt::print("\n");
fmt::print("CRR:\n");
fmt::print("Magic: {}\n", "CRR0");
fmt::print("DebugInfo Offset: 0x{:08x}\n", mHeader.debug_info_offset.unwrap());
fmt::print("DebugInfo Size: 0x{:08x}\n", mHeader.debug_info_size.unwrap());
fmt::print("\n");
fmt::print("CRR certificate:\n");
fmt::print("UniqueIdMask: 0x{:08x}\n", mHeader.body_certificate.unique_id_mask.unwrap());
fmt::print("UniqueIdPattern: 0x{:08x}\n", mHeader.body_certificate.unique_id_pattern.unwrap());
fmt::print("PublicKey: {}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHeader.body_certificate.crr_body_public_key.data(), mHeader.body_certificate.crr_body_public_key.size(), true, "", 0x20, 24, false));
fmt::print("Signature: {:6} {}", getValidString(mValidCertificateSignature), tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHeader.body_certificate.signature.data(), mHeader.body_certificate.signature.size(), true, "", 0x20, 24, false));
fmt::print("\n");
fmt::print("CRR body header:\n");
fmt::print("Signature: {:6} {}", getValidString(mValidBodySignature), tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mBodyHeader.signature.data(), mBodyHeader.signature.size(), true, "", 0x20, 24, false));
fmt::print("UniqueId: {:6} 0x{:08x}\n", getValidString(mValidUniqueId), mBodyHeader.unique_id.unwrap());
fmt::print("CRR Size: 0x{:08x}\n", mBodyHeader.size.unwrap());
fmt::print("Hash Offset: 0x{:08x}\n", mBodyHeader.hash_offset.unwrap());
fmt::print("Hash Num: {:d}\n", mBodyHeader.num_hash.unwrap());
fmt::print("ModuleId Offset: 0x{:08x}\n", mBodyHeader.module_id_offset.unwrap());
fmt::print("ModuleId Size: 0x{:08x}\n", mBodyHeader.module_id_size.unwrap());
}
std::string ctrtool::CrrProcess::getValidString(byte_t validstate)
{
std::string ret_str;
switch (validstate)
{
case ValidState::Unchecked:
ret_str = "";
break;
case ValidState::Good:
ret_str = "(GOOD)";
break;
case ValidState::Fail:
default:
ret_str = "(FAIL)";
break;
}
return ret_str;
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include <tc/Optional.h>
#include <ntd/n3ds/crr.h>
namespace ctrtool {
class CrrProcess
{
public:
CrrProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setCliOutputMode(bool show_info);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mShowInfo;
bool mVerbose;
bool mVerify;
ntd::n3ds::CrrHeader mHeader;
ntd::n3ds::CrrBodyHeader mBodyHeader;
tc::ByteData mCrrData;
byte_t mValidCertificateSignature;
byte_t mValidBodySignature;
byte_t mValidUniqueId;
void importData();
void verifyData();
void printData();
// string utils
std::string getValidString(byte_t validstate);
};
}
+993
View File
@@ -0,0 +1,993 @@
#include "ExHeaderProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>
ctrtool::ExHeaderProcess::ExHeaderProcess() :
mModuleLabel("ctrtool::ExHeaderProcess"),
mInputStream(),
mKeyBag(),
mShowInfo(false),
mVerbose(false),
mVerify(false),
mShowSyscallNames(false),
mValidSignature(ValidState::Unchecked),
mValidLocalCaps()
{
memset((byte_t*)&mHeader, 0, sizeof(ntd::n3ds::ExtendedHeader));
memset((byte_t*)&mDesc, 0, sizeof(ntd::n3ds::AccessDescriptor));
}
void ctrtool::ExHeaderProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::ExHeaderProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
mKeyBag = key_bag;
}
void ctrtool::ExHeaderProcess::setCliOutputMode(bool show_info)
{
mShowInfo = show_info;
}
void ctrtool::ExHeaderProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
}
void ctrtool::ExHeaderProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void ctrtool::ExHeaderProcess::setShowSyscallName(bool show_name)
{
mShowSyscallNames = show_name;
}
void ctrtool::ExHeaderProcess::process()
{
// begin processing
importExHeader();
if (mVerify)
verifyExHeader();
if (mShowInfo)
printExHeader();
}
void ctrtool::ExHeaderProcess::importExHeader()
{
// 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::ExtendedHeader) + sizeof(ntd::n3ds::AccessDescriptor)))
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small.");
}
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::ExtendedHeader));
mInputStream->read((byte_t*)&mDesc, sizeof(ntd::n3ds::AccessDescriptor));
}
void ctrtool::ExHeaderProcess::verifyExHeader()
{
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> desc_hash;
tc::crypto::GenerateSha256Hash(desc_hash.data(), (byte_t*)&mDesc.ncch_rsa_modulus, sizeof(mDesc) - sizeof(mDesc.signature));
if (mKeyBag.rsa_key.find(mKeyBag.RSAKEY_ACCESSDESC) != mKeyBag.rsa_key.end())
{
tc::crypto::RsaKey pubkey = mKeyBag.rsa_key[mKeyBag.RSAKEY_ACCESSDESC];
mValidSignature = tc::crypto::VerifyRsa2048Pkcs1Sha256(mDesc.signature.data(), desc_hash.data(), pubkey) ? ValidState::Good : ValidState::Fail;
}
else
{
fmt::print(stderr, "Could not read AccessDescriptor public key.\n");
mValidSignature = ValidState::Fail;
}
mValidLocalCaps.system_save_id[0] = ValidState::Good;
mValidLocalCaps.system_save_id[1] = ValidState::Good;
mValidLocalCaps.access_info = ValidState::Good;
mValidLocalCaps.core_version = ValidState::Good;
mValidLocalCaps.program_id = ValidState::Good;
mValidLocalCaps.priority = ValidState::Good;
mValidLocalCaps.affinity_mask = ValidState::Good;
mValidLocalCaps.ideal_processor = ValidState::Good;
mValidLocalCaps.old3ds_system_mode = ValidState::Good;
mValidLocalCaps.new3ds_system_mode = ValidState::Good;
mValidLocalCaps.enable_l2_cache = ValidState::Good;
mValidLocalCaps.new3ds_cpu_speed = ValidState::Good;
mValidLocalCaps.service_control = ValidState::Good;
byte_t* exhdr_program_id = (byte_t*)&mHeader.access_control_info.program_id;
byte_t* desc_program_id = (byte_t*)&mDesc.access_control_info.program_id;
for (size_t i = 0; i < sizeof(uint64_t); i++)
{
if (exhdr_program_id[i] == desc_program_id[i] || desc_program_id[i] == 0xFF)
continue;
mValidLocalCaps.program_id = ValidState::Fail;
break;
}
/*
// this does not appear to be correct given working examples: SystemUpdater exhdr: 0x1, desc: 0x2
auto exhdr_core_version = mHeader.access_control_info.core_version.unwrap();
auto desc_core_version = mDesc.access_control_info.core_version.unwrap();
if (exhdr_core_version != desc_core_version)
mValidLocalCaps.core_version = ValidState::Fail;
*/
auto exhdr_thread_priority = mHeader.access_control_info.flags.unwrap().thread_priority;
auto desc_thread_priority = mDesc.access_control_info.flags.unwrap().thread_priority;
if (exhdr_thread_priority < desc_thread_priority)
mValidLocalCaps.priority = ValidState::Fail;
auto exhdr_ideal_processor = mHeader.access_control_info.flags.unwrap().ideal_processor;
auto desc_ideal_processor = mDesc.access_control_info.flags.unwrap().ideal_processor;
if((1<<exhdr_ideal_processor & desc_ideal_processor) == 0)
mValidLocalCaps.ideal_processor = ValidState::Fail;
auto exhdr_affinity_mask = mHeader.access_control_info.flags.unwrap().affinity_mask;
auto desc_affinity_mask = mDesc.access_control_info.flags.unwrap().affinity_mask;
if (exhdr_affinity_mask & ~desc_affinity_mask)
mValidLocalCaps.affinity_mask = ValidState::Fail;
auto exhdr_system_mode = mHeader.access_control_info.flags.unwrap().system_mode;
auto desc_system_mode = mDesc.access_control_info.flags.unwrap().system_mode;
if (exhdr_system_mode > desc_system_mode)
mValidLocalCaps.old3ds_system_mode = ValidState::Fail;
auto exhdr_system_mode_ext = mHeader.access_control_info.flags.unwrap().system_mode_ext;
auto desc_system_mode_ext = mDesc.access_control_info.flags.unwrap().system_mode_ext;
if (exhdr_system_mode_ext > desc_system_mode_ext)
mValidLocalCaps.new3ds_system_mode = ValidState::Fail;
auto exhdr_enable_l2_cache = mHeader.access_control_info.flags.unwrap().enable_l2_cache;
auto desc_enable_l2_cache = mDesc.access_control_info.flags.unwrap().enable_l2_cache;
if (exhdr_enable_l2_cache != desc_enable_l2_cache)
mValidLocalCaps.enable_l2_cache = ValidState::Fail;
auto exhdr_cpu_speed = mHeader.access_control_info.flags.unwrap().cpu_speed;
auto desc_cpu_speed = mDesc.access_control_info.flags.unwrap().cpu_speed;
if (exhdr_cpu_speed != desc_cpu_speed)
mValidLocalCaps.new3ds_cpu_speed = ValidState::Fail;
// Storage Info Verify
auto exhdr_system_savedata_id = mHeader.access_control_info.system_savedata_id;
auto desc_system_savedata_id = mDesc.access_control_info.system_savedata_id;
if(exhdr_system_savedata_id[0].unwrap() & ~desc_system_savedata_id[0].unwrap())
mValidLocalCaps.system_save_id[0] = ValidState::Fail;
if(exhdr_system_savedata_id[1].unwrap() & ~desc_system_savedata_id[1].unwrap())
mValidLocalCaps.system_save_id[1] = ValidState::Fail;
auto exhdr_fs_access = mHeader.access_control_info.fs_access;
auto desc_fs_access = mDesc.access_control_info.fs_access;
for (size_t fs_bit = 0; fs_bit < exhdr_fs_access.bit_size(); fs_bit++)
{
if (exhdr_fs_access.test(fs_bit) == true && desc_fs_access.test(fs_bit) == false)
{
mValidLocalCaps.access_info = ValidState::Fail;
if (mVerbose)
{
fmt::print("[LOG/ExHeader] FsAccess Bit {:d} was not permitted\n", fs_bit);
}
}
}
// Service Access Control
auto exhdr_service_access_control = mHeader.access_control_info.service_access_control;
auto desc_service_access_control = mDesc.access_control_info.service_access_control;
bool found_service;
for (size_t i = 0, j; i < exhdr_service_access_control.size(); i++) {
// skip if empty string
if (exhdr_service_access_control[i].decode().empty())
break;
found_service = false;
// locate entry in desc
for (j = 0; j < desc_service_access_control.size(); j++) {
if (exhdr_service_access_control[i].decode() == desc_service_access_control[j].decode())
found_service = true;
}
if (found_service == false)
{
mValidLocalCaps.service_control = Fail;
if (mVerbose)
{
fmt::print("[LOG/ExHeader] Service \"{}\" was not permitted\n", exhdr_service_access_control[i].decode());
}
}
}
}
void ctrtool::ExHeaderProcess::printExHeader()
{
fmt::print("\n");
fmt::print("Extended header:\n");
fmt::print("Signature: {:6} {}", getValidString(mValidSignature), tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mDesc.signature.data(), mDesc.signature.size(), true, "", 0x20, 24, false));
fmt::print("NCCH Hdr RSA Modulus: {}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mDesc.ncch_rsa_modulus.data(), mDesc.ncch_rsa_modulus.size(), true, "", 0x20, 24, false));
printSystemControlInfo(mHeader.system_control_info);
printARM11SystemLocalCapabilities(mHeader.access_control_info, mValidLocalCaps);
printARM11KernelCapabilities(mHeader.access_control_info);
printARM9AccessControlInfo(mHeader.access_control_info);
}
void ctrtool::ExHeaderProcess::printSystemControlInfo(const ntd::n3ds::SystemControlInfo& info)
{
//fmt::print("[SystemControlInfo]\n");
// basic info
fmt::print("Name: {}\n", info.name.decode());
fmt::print("Flags: {:02X}", (uint32_t)info.flags.raw);
if (info.flags.bitarray.test(info.Flags_CompressExefsPartition0))
fmt::print(" [compressed]");
if (info.flags.bitarray.test(info.Flags_SdmcApplication))
fmt::print(" [sd app]");
fmt::print("\n");
fmt::print("Remaster version: {:04x}\n", (uint32_t)info.remaster_version.unwrap());
// code set info
fmt::print("Code text address: 0x{:08X}\n", info.text.address.unwrap());
fmt::print("Code text size: 0x{:08X}\n", info.text.code_size.unwrap());
fmt::print("Code text max pages: 0x{:08X} (0x{:08X})\n", info.text.num_max_pages.unwrap(), info.text.num_max_pages.unwrap() * 0x1000);
fmt::print("Code ro address: 0x{:08X}\n", info.rodata.address.unwrap());
fmt::print("Code ro size: 0x{:08X}\n", info.rodata.code_size.unwrap());
fmt::print("Code ro max pages: 0x{:08X} (0x{:08X})\n", info.rodata.num_max_pages.unwrap(), info.rodata.num_max_pages.unwrap() * 0x1000);
fmt::print("Code data address: 0x{:08X}\n", info.data.address.unwrap());
fmt::print("Code data size: 0x{:08X}\n", info.data.code_size.unwrap());
fmt::print("Code data max pages: 0x{:08X} (0x{:08X})\n", info.data.num_max_pages.unwrap(), info.data.num_max_pages.unwrap() * 0x1000);
fmt::print("Code bss size: 0x{:08X}\n", info.bss_size.unwrap());
fmt::print("Code stack size: 0x{:08X}\n", info.stack_size.unwrap());
// Dependency
for (size_t i = 0; i < info.dependency_list.size(); i++)
{
if (info.dependency_list[i].unwrap() != 0)
{
fmt::print("Dependency: {:016x}\n", info.dependency_list[i].unwrap());
}
}
// savedata size
fmt::print("Savedata size: ");
uint64_t savedata_size = info.savedata_size.unwrap();
if (savedata_size < (1024)) // < KB
{
fmt::print("0x{:x}", savedata_size);
}
else if (savedata_size < (1024 * 1024)) // < MB
{
fmt::print("{:d}K", (savedata_size >> 10));
}
else
{
fmt::print("{:d}M", (savedata_size >> 20));
}
fmt::print("\n");
fmt::print("Jump id: {:016x}\n", info.jump_id.unwrap());
}
void ctrtool::ExHeaderProcess::printARM11SystemLocalCapabilities(const ntd::n3ds::AccessControlInfo& info, const ValidARM11SystemLocalCapabilities& valid)
{
//fmt::print("[ARM11SystemLocalCapabilities]\n");
fmt::print("Program id: {:016x} {}\n", info.program_id.unwrap(), getValidString(valid.program_id));
fmt::print("Core version: 0x{:08x}\n", info.core_version.unwrap());
fmt::print("System mode: {} (AppMemory: {}) {}\n", getSystemModeString(info.flags.unwrap().system_mode), getSystemModeAppMemorySizeString(info.flags.unwrap().system_mode), getValidString(valid.old3ds_system_mode));
fmt::print("System mode (New3DS): {} (AppMemory: {}) {}\n", getSystemModeExtString(info.flags.unwrap().system_mode_ext, info.flags.unwrap().system_mode), getSystemModeExtAppMemorySizeString(info.flags.unwrap().system_mode_ext, info.flags.unwrap().system_mode), getValidString(valid.new3ds_system_mode));
fmt::print("CPU Speed (New3DS): {} {}\n", (info.flags.unwrap().cpu_speed ? "804MHz" : "268MHz"), getValidString(valid.new3ds_cpu_speed));
fmt::print("Enable L2 Cache: {} {}\n", (info.flags.unwrap().enable_l2_cache ? "YES" : "NO"), getValidString(valid.enable_l2_cache));
fmt::print("Ideal processor: {:d} {}\n", (uint32_t)info.flags.unwrap().ideal_processor, getValidString(valid.ideal_processor));
fmt::print("Affinity mask: {:d} {}\n", (uint32_t)info.flags.unwrap().affinity_mask, getValidString(valid.affinity_mask));
fmt::print("Main thread priority: {:d} {}\n", (uint32_t)info.flags.unwrap().thread_priority, getValidString(valid.priority));
fmt::print("MaxCpu: {:d}\n", info.resource_limit_descriptor[info.ResourceLimitDescriptorIndex_MaxCpu].unwrap());
std::vector<uint32_t> accessible_save_ids;
uint64_t ext_savedata_id = 0;
std::array<uint32_t, 3> other_user_save_ids = {0, 0, 0};
bool use_other_variation_savedata = false;
if (info.other_attributes.test(info.OtherAttribute_UseExtendedSavedataAccessControl))
{
uint32_t id;
if (0 != (id = info.accessible_unique_ids_0.unwrap().save_id0))
accessible_save_ids.push_back(id);
if (0 != (id = info.accessible_unique_ids_0.unwrap().save_id1))
accessible_save_ids.push_back(id);
if (0 != (id = info.accessible_unique_ids_0.unwrap().save_id2))
accessible_save_ids.push_back(id);
if (0 != (id = info.accessible_unique_ids_1.unwrap().save_id0))
accessible_save_ids.push_back(id);
if (0 != (id = info.accessible_unique_ids_1.unwrap().save_id1))
accessible_save_ids.push_back(id);
if (0 != (id = info.accessible_unique_ids_1.unwrap().save_id2))
accessible_save_ids.push_back(id);
}
else
{
ext_savedata_id = info.ext_savedata_id.unwrap();
other_user_save_ids[0] = info.accessible_unique_ids_0.unwrap().save_id0;
other_user_save_ids[1] = info.accessible_unique_ids_0.unwrap().save_id1;
other_user_save_ids[2] = info.accessible_unique_ids_0.unwrap().save_id2;
}
use_other_variation_savedata = info.accessible_unique_ids_0.unwrap().flag;
fmt::print("Ext savedata id: 0x{:016x}\n", ext_savedata_id);
for (size_t i = 0; i < info.system_savedata_id.size(); i++)
{
fmt::print("System savedata id {:d}: 0x{:08x} {}\n", i+1, info.system_savedata_id[i].unwrap(), getValidString(valid.system_save_id[i]));
}
for (size_t i = 0; i < other_user_save_ids.size(); i++)
{
fmt::print("OtherUserSaveDataId{:d}: 0x{:05x}\n", i+1, other_user_save_ids[i]);
}
fmt::print("Accessible Savedata Ids: {}\n", (accessible_save_ids.size() == 0 ? "None" : ""));
for (size_t i = 0; i < accessible_save_ids.size(); i++)
{
fmt::print(" > 0x{:05x}\n", accessible_save_ids[i]);
}
fmt::print("Other Variation Saves: {}\n", (use_other_variation_savedata ? "Accessible" : "Inaccessible"));
uint64_t fs_access_raw = ((((tc::bn::le64<uint64_t>*)&info.fs_access)->unwrap() << 8) >> 8); // clearing the upper 8 bits since fs_access is 56 bits
fmt::print("Access info: {:6} 0x{:014x}\n", getValidString(valid.access_info), fs_access_raw);
for (size_t i = 0; i < info.fs_access.bit_size(); i++)
{
if (info.fs_access.test(i))
{
fmt::print(" > {}\n", getFsAccessBitString(i));
}
}
fmt::print("Service access: {}\n", getValidString(mValidLocalCaps.service_control));
auto& service_access = info.service_access_control;
for (size_t i = 0; i < service_access.size(); i++)
{
if (service_access[i].decode().empty())
break;
fmt::print(" > {}\n", service_access[i].decode());
}
fmt::print("Reslimit category: {:02X}\n", (uint32_t)info.resource_limit_category);
}
void ctrtool::ExHeaderProcess::printARM11KernelCapabilities(const ntd::n3ds::AccessControlInfo& info)
{
size_t i, j;
std::vector<byte_t> syscall_list;
std::vector<byte_t> interrupt_list;
std::vector<uint32_t> unknown_desc_list;
union KernDesc
{
uint32_t raw;
tc::bn::bitarray<4> bits;
ntd::n3ds::AccessControlInfo::InterruptDescriptor interrupt;
ntd::n3ds::AccessControlInfo::SystemCallDescriptor syscall;
ntd::n3ds::AccessControlInfo::ReleaseKernelVersionDescriptor kernel_ver;
ntd::n3ds::AccessControlInfo::HandleTableSizeDescriptor handle_table;
ntd::n3ds::AccessControlInfo::OtherCapabilitiesDescriptor other_cap;
ntd::n3ds::AccessControlInfo::MappingStaticDescriptor mapping_static;
ntd::n3ds::AccessControlInfo::MappingIODescriptor mapping_io;
};
KernDesc prev_desc;
prev_desc.raw = 0;
for (i = 0; i < info.kernel_descriptors.size(); i++)
{
KernDesc desc;
desc.raw = info.kernel_descriptors[i].unwrap();
uint32_t prefix_bits;
for (prefix_bits = 0; prefix_bits < 32; prefix_bits++)
{
if (desc.bits.test(31 - prefix_bits) == false)
{
break;
}
}
if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_InterruptNumList)
{
if (desc.interrupt.interrupt_0 != 0)
interrupt_list.push_back(desc.interrupt.interrupt_0);
if (desc.interrupt.interrupt_1 != 0)
interrupt_list.push_back(desc.interrupt.interrupt_1);
if (desc.interrupt.interrupt_2 != 0)
interrupt_list.push_back(desc.interrupt.interrupt_2);
if (desc.interrupt.interrupt_3 != 0)
interrupt_list.push_back(desc.interrupt.interrupt_3);
}
else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_SysCallList)
{
for (j = 0; j < 24; j++)
{
if ((desc.syscall.systemcall_lower_bitarray >> j) & 1)
syscall_list.push_back(byte_t(desc.syscall.systemcall_upper * 24) + byte_t(j));
}
}
else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_ReleaseKernelVersion)
{
fmt::print("Kernel release version: {:d}.{:d}\n", ((desc.kernel_ver.version >> 8) & 0xFF), ((desc.kernel_ver.version >> 0) & 0xFF));
}
else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_HandleTableSize)
{
fmt::print("Handle table size: 0x{:X}\n", (uint32_t)desc.handle_table.size);
}
else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_OtherCapabilities)
{
fmt::print("Kernel flags: \n");
fmt::print(" > Allow debug: {}\n", (desc.other_cap.permit_debug ? "YES" : "NO"));
fmt::print(" > Force debug: {}\n", (desc.other_cap.force_debug ? "YES" : "NO"));
fmt::print(" > Allow non-alphanum: {}\n", (desc.other_cap.can_use_non_alphabet_and_number ? "YES" : "NO"));
fmt::print(" > Shared page writing: {}\n", (desc.other_cap.can_write_shared_page ? "YES" : "NO"));
fmt::print(" > Privilege priority: {}\n", (desc.other_cap.can_use_privileged_priority ? "YES" : "NO"));
fmt::print(" > Allow main() args: {}\n", (desc.other_cap.permit_main_function_argument ? "YES" : "NO"));
fmt::print(" > Shared device mem: {}\n", (desc.other_cap.can_share_device_memory ? "YES" : "NO"));
fmt::print(" > Memory Type: {}\n", getMemoryTypeString(desc.other_cap.memory_type));
fmt::print(" > Runnable on sleep: {}\n", (desc.other_cap.runnable_on_sleep ? "YES" : "NO"));
fmt::print(" > Special memory: {}\n", (desc.other_cap.special_memory_layout ? "YES" : "NO"));
fmt::print(" > Access Core 2: {}\n", (desc.other_cap.can_access_core2 ? "YES" : "NO"));
}
else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_MappingStatic)
{
if (prev_desc.raw == 0)
{
prev_desc.raw = desc.raw;
}
else
{
fmt::print("{:24}0x{:X}-0x{:X}{}\n",
(desc.mapping_static.flag ? "StaticMapping:" : "IoMapping:"),
(prev_desc.mapping_static.page << 12),
(desc.mapping_static.page << 12)-1,
(prev_desc.mapping_static.flag ? ":r" : "")
);
prev_desc.raw = 0;
}
}
else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_MappingIo)
{
fmt::print("IoMapping: 0x{:X}\n", (desc.mapping_io.page << 12));
}
else if (prefix_bits == 32)
{
continue;
}
else
{
unknown_desc_list.push_back(desc.raw);
}
}
fmt::print("Allowed systemcalls: ");
if (syscall_list.size() > 0)
{
if (!mShowSyscallNames)
{
std::vector<std::string> string_list;
for (size_t i = 0; i < syscall_list.size(); i++)
{
string_list.push_back(getByteHexString(syscall_list[i]));
}
fmt::print("{}", tc::cli::FormatUtil::formatListWithLineLimit(string_list, 46, 24, false));
}
else
{
fmt::print("\n");
for (size_t i = 0; i < syscall_list.size(); i++)
{
fmt::print(" > {} {}\n", getByteHexString(syscall_list[i]), getSysCallName(syscall_list[i]));
}
}
}
else
{
fmt::print("none\n");
}
fmt::print("Allowed interrupts: ");
if (interrupt_list.size() > 0)
{
std::vector<std::string> string_list;
for (size_t i = 0; i < interrupt_list.size(); i++)
{
string_list.push_back(getByteHexString(interrupt_list[i]));
}
fmt::print("{}", tc::cli::FormatUtil::formatListWithLineLimit(string_list, 46, 24, false));
}
else
{
fmt::print("none\n");
}
for (i = 0; i < unknown_desc_list.size(); i++)
{
fmt::print("Unknown descriptor: {:08X}\n", unknown_desc_list[i]);
}
}
void ctrtool::ExHeaderProcess::printARM9AccessControlInfo(const ntd::n3ds::AccessControlInfo& info)
{
//fmt::print("[ARM9AccessControlInfo]\n");
// collect arm9 caps as a vector of strings
std::vector<std::string> arm9_caps_str;
for (size_t i = 0; i < info.arm9_access_control.bit_size(); i++)
{
if (info.arm9_access_control.test(i))
{
arm9_caps_str.push_back(getArm9CapabilityBitString(i));
}
}
// print arm9 caps
fmt::print("Arm9Capability: {}\n", (arm9_caps_str.size() == 0 ? "none" : ""));
for (size_t i = 0; i < arm9_caps_str.size(); i++)
{
fmt::print(" > {}\n", arm9_caps_str[i]);
}
// print desc version
fmt::print("Desc Version: 0x{:x}\n", (uint32_t)info.desc_version);
}
std::string ctrtool::ExHeaderProcess::getValidString(byte_t validstate)
{
std::string ret_str;
switch (validstate)
{
case ValidState::Unchecked:
ret_str = "";
break;
case ValidState::Good:
ret_str = "(GOOD)";
break;
case ValidState::Fail:
default:
ret_str = "(FAIL)";
break;
}
return ret_str;
}
std::string ctrtool::ExHeaderProcess::getSystemModeString(byte_t system_mode)
{
std::string str;
switch (system_mode)
{
case ntd::n3ds::AccessControlInfo::SystemMode_PROD :
str = "prod";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV1 :
str = "dev1";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV2 :
str = "dev2";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV3 :
str = "dev3";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV4 :
str = "dev4";
break;
default:
str = fmt::format("Unknown (0x{:x})", (uint32_t)system_mode);
break;
}
return str;
}
std::string ctrtool::ExHeaderProcess::getSystemModeExtString(byte_t system_mode_ext, byte_t system_mode)
{
std::string str;
switch (system_mode_ext)
{
case ntd::n3ds::AccessControlInfo::SystemModeExt_LEGACY :
//str = "Legacy";
str = fmt::format("ctr {}", getSystemModeString(system_mode));
break;
case ntd::n3ds::AccessControlInfo::SystemModeExt_PROD :
str = "snake prod";
break;
case ntd::n3ds::AccessControlInfo::SystemModeExt_DEV1 :
str = "snake dev1";
break;
default:
str = fmt::format("Unknown (0x{:x})", (uint32_t)system_mode_ext);
break;
}
return str;
}
std::string ctrtool::ExHeaderProcess::getSystemModeAppMemorySizeString(byte_t system_mode)
{
std::string str;
switch (system_mode)
{
case ntd::n3ds::AccessControlInfo::SystemMode_PROD :
str = "64MB";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV1 :
str = "96MB";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV2 :
str = "80MB";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV3 :
str = "72MB";
break;
case ntd::n3ds::AccessControlInfo::SystemMode_DEV4 :
str = "32MB";
break;
default:
str = "Unknown";
break;
}
return str;
}
std::string ctrtool::ExHeaderProcess::getSystemModeExtAppMemorySizeString(byte_t system_mode_ext, byte_t system_mode)
{
std::string str;
switch (system_mode_ext)
{
case ntd::n3ds::AccessControlInfo::SystemModeExt_LEGACY :
str = getSystemModeAppMemorySizeString(system_mode);
break;
case ntd::n3ds::AccessControlInfo::SystemModeExt_PROD :
str = "124MB";
break;
case ntd::n3ds::AccessControlInfo::SystemModeExt_DEV1 :
str = "178MB";
break;
default:
str = "Unknown";
break;
}
return str;
}
std::string ctrtool::ExHeaderProcess::getFsAccessBitString(size_t bit)
{
std::string str;
switch(bit)
{
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategorySystemApplication :
str = "Category System Application";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategoryHardwareCheck :
str = "Category Hardware Check";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategoryFileSystemTool :
str = "Category File System Tool";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_Debug :
str = "Debug";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_TwlCardBackup :
str = "TWL Card Backup";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_TwlNandData :
str = "TWL Nand Data";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_Boss :
str = "BOSS";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_DirectSdmc :
str = "Direct SDMC";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_Core :
str = "Core";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CtrNandRo :
str = "CTR NAND RO";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CtrNandRw :
str = "CTR NAND RW";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CtrNandRoWrite :
str = "CTR NAND RO (Write Access)";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategorySystemSettings :
str = "Category System Settings";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CardBoard :
str = "CARD BOARD";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_ExportImportIvs :
str = "Export Import IVS";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_DirectSdmcWrite :
str = "Direct SDMC (Write Only)";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_SwitchCleanup :
str = "Switch Cleanup";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_SaveDataMove :
str = "Save Data Move";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_Shop :
str = "Shop";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_Shell :
str = "Shell";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategoryHomeMenu :
str = "Category HomeMenu";
break;
case ntd::n3ds::AccessControlInfo::FileSystemAccess_ExternalSeed :
str = "External Seed (Seed DB)";
break;
default :
str = fmt::format("Bit {:d} (unknown)", bit);
break;
}
return str;
}
std::string ctrtool::ExHeaderProcess::getMemoryTypeString(byte_t memory_type)
{
std::string str;
switch(memory_type)
{
case ntd::n3ds::AccessControlInfo::MemoryType_Application :
str = "APPLICATION";
break;
case ntd::n3ds::AccessControlInfo::MemoryType_System :
str = "SYSTEM";
break;
case ntd::n3ds::AccessControlInfo::MemoryType_Base :
str = "BASE";
break;
default :
str = fmt::format("Unknown ({:d})", memory_type);
break;
}
return str;
}
std::string ctrtool::ExHeaderProcess::getByteHexString(byte_t byte)
{
return fmt::format("{:02X}", byte);
}
std::string ctrtool::ExHeaderProcess::getSysCallName(byte_t syscall)
{
// List of 3DS system calls. NULL indicates unknown.
static const size_t kSysCallNum = 128;
static const char *const kSysCallList[kSysCallNum] =
{
NULL, // 00
"ControlMemory", // 01
"QueryMemory", // 02
"ExitProcess", // 03
"GetProcessAffinityMask", // 04
"SetProcessAffinityMask", // 05
"GetProcessIdealProcessor", // 06
"SetProcessIdealProcessor", // 07
"CreateThread", // 08
"ExitThread", // 09
"SleepThread", // 0A
"GetThreadPriority", // 0B
"SetThreadPriority", // 0C
"GetThreadAffinityMask", // 0D
"SetThreadAffinityMask", // 0E
"GetThreadIdealProcessor", // 0F
"SetThreadIdealProcessor", // 10
"GetCurrentProcessorNumber", // 11
"Run", // 12
"CreateMutex", // 13
"ReleaseMutex", // 14
"CreateSemaphore", // 15
"ReleaseSemaphore", // 16
"CreateEvent", // 17
"SignalEvent", // 18
"ClearEvent", // 19
"CreateTimer", // 1A
"SetTimer", // 1B
"CancelTimer", // 1C
"ClearTimer", // 1D
"CreateMemoryBlock", // 1E
"MapMemoryBlock", // 1F
"UnmapMemoryBlock", // 20
"CreateAddressArbiter", // 21
"ArbitrateAddress", // 22
"CloseHandle", // 23
"WaitSynchronization1", // 24
"WaitSynchronizationN", // 25
"SignalAndWait", // 26
"DuplicateHandle", // 27
"GetSystemTick", // 28
"GetHandleInfo", // 29
"GetSystemInfo", // 2A
"GetProcessInfo", // 2B
"GetThreadInfo", // 2C
"ConnectToPort", // 2D
"SendSyncRequest1", // 2E
"SendSyncRequest2", // 2F
"SendSyncRequest3", // 30
"SendSyncRequest4", // 31
"SendSyncRequest", // 32
"OpenProcess", // 33
"OpenThread", // 34
"GetProcessId", // 35
"GetProcessIdOfThread", // 36
"GetThreadId", // 37
"GetResourceLimit", // 38
"GetResourceLimitLimitValues", // 39
"GetResourceLimitCurrentValues", // 3A
"GetThreadContext", // 3B
"Break", // 3C
"OutputDebugString", // 3D
"ControlPerformanceCounter", // 3E
NULL, // 3F
NULL, // 40
NULL, // 41
NULL, // 42
NULL, // 43
NULL, // 44
NULL, // 45
NULL, // 46
"CreatePort", // 47
"CreateSessionToPort", // 48
"CreateSession", // 49
"AcceptSession", // 4A
"ReplyAndReceive1", // 4B
"ReplyAndReceive2", // 4C
"ReplyAndReceive3", // 4D
"ReplyAndReceive4", // 4E
"ReplyAndReceive", // 4F
"BindInterrupt", // 50
"UnbindInterrupt", // 51
"InvalidateProcessDataCache", // 52
"StoreProcessDataCache", // 53
"FlushProcessDataCache", // 54
"StartInterProcessDma", // 55
"StopDma", // 56
"GetDmaState", // 57
"RestartDma", // 58
"SetGpuProt", // 59
"SetWifiEnabled", // 5A
NULL, // 5B
NULL, // 5C
NULL, // 5D
NULL, // 5E
NULL, // 5F
"DebugActiveProcess", // 60
"BreakDebugProcess", // 61
"TerminateDebugProcess", // 62
"GetProcessDebugEvent", // 63
"ContinueDebugEvent", // 64
"GetProcessList", // 65
"GetThreadList", // 66
"GetDebugThreadContext", // 67
"SetDebugThreadContext", // 68
"QueryDebugProcessMemory", // 69
"ReadProcessMemory", // 6A
"WriteProcessMemory", // 6B
"SetHardwareBreakPoint", // 6C
"GetDebugThreadParam", // 6D
NULL, // 6E
NULL, // 6F
"ControlProcessMemory", // 70
"MapProcessMemory", // 71
"UnmapProcessMemory", // 72
"CreateCodeSet", // 73
NULL, // 74
"CreateProcess", // 75
"TerminateProcess", // 76
"SetProcessResourceLimits", // 77
"CreateResourceLimit", // 78
"SetResourceLimitValues", // 79
"AddCodeSegment", // 7A
"Backdoor", // 7B
"KernelSetState", // 7C
"QueryProcessMemory", // 7D
NULL, // 7E
NULL, // 7F
};
std::string str;
if (syscall >= kSysCallNum)
return std::string();
if (kSysCallList[syscall] != nullptr)
{
str = kSysCallList[syscall];
}
else
{
str = fmt::format("Unknown {:02X}", syscall);;
}
return str;
}
std::string ctrtool::ExHeaderProcess::getArm9CapabilityBitString(size_t bit)
{
std::string str;
switch(bit)
{
case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountNand :
str = "FsMountNand";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountNandRoWrite :
str = "FsMountNandRoWrite";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountTwln :
str = "FsMountTwln";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountWnand :
str = "FsMountWnand";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountCardSpi :
str = "FsMountCardSpi";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_UseSdif3 :
str = "UseSdif3";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_CreateSeed :
str = "CreateSeed";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_UseCardSpi :
str = "UseCardSpi";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_SdApplication :
str = "SdApplication";
break;
case ntd::n3ds::AccessControlInfo::Arm9Capability_UseDirectSdmc :
str = "UseDirectSdmc";
break;
default :
str = fmt::format("Bit {:d} (unknown)", bit);
break;
}
return str;
}
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include <tc/Optional.h>
#include <ntd/n3ds/exheader.h>
namespace ctrtool {
class ExHeaderProcess
{
public:
ExHeaderProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setCliOutputMode(bool show_info);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setShowSyscallName(bool show_name);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mShowInfo;
bool mVerbose;
bool mVerify;
bool mShowSyscallNames;
ntd::n3ds::ExtendedHeader mHeader;
ntd::n3ds::AccessDescriptor mDesc;
byte_t mValidSignature;
struct ValidARM11SystemLocalCapabilities
{
ValidARM11SystemLocalCapabilities()
{
system_save_id[0] = ValidState::Unchecked;
system_save_id[1] = ValidState::Unchecked;
access_info = ValidState::Unchecked;
core_version = ValidState::Unchecked;
program_id = ValidState::Unchecked;
priority = ValidState::Unchecked;
affinity_mask = ValidState::Unchecked;
ideal_processor = ValidState::Unchecked;
old3ds_system_mode = ValidState::Unchecked;
new3ds_system_mode = ValidState::Unchecked;
enable_l2_cache = ValidState::Unchecked;
new3ds_cpu_speed = ValidState::Unchecked;
service_control = ValidState::Unchecked;
}
std::array<byte_t, 2> system_save_id;
byte_t access_info;
byte_t core_version;
byte_t program_id;
byte_t priority;
byte_t affinity_mask;
byte_t ideal_processor;
byte_t old3ds_system_mode;
byte_t new3ds_system_mode;
byte_t enable_l2_cache;
byte_t new3ds_cpu_speed;
byte_t service_control;
} mValidLocalCaps;
void importExHeader();
void verifyExHeader();
void printExHeader();
void printSystemControlInfo(const ntd::n3ds::SystemControlInfo& info);
void printARM11SystemLocalCapabilities(const ntd::n3ds::AccessControlInfo& info, const ValidARM11SystemLocalCapabilities& valid);
void printARM11KernelCapabilities(const ntd::n3ds::AccessControlInfo& info);
void printARM9AccessControlInfo(const ntd::n3ds::AccessControlInfo& info);
// string utils
std::string getValidString(byte_t validstate);
std::string getSystemModeString(byte_t system_mode);
std::string getSystemModeExtString(byte_t system_mode_ext, byte_t system_mode);
std::string getSystemModeAppMemorySizeString(byte_t system_mode);
std::string getSystemModeExtAppMemorySizeString(byte_t system_mode_ext, byte_t system_mode);
std::string getFsAccessBitString(size_t bit);
std::string getMemoryTypeString(byte_t memory_type);
std::string getByteHexString(byte_t byte);
std::string getSysCallName(byte_t syscall);
std::string getArm9CapabilityBitString(size_t bit);
};
}
+275
View File
@@ -0,0 +1,275 @@
#include "ExeFsProcess.h"
#include "lzss.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>
#include <ntd/n3ds/ExeFsSnapshotGenerator.h>
ctrtool::ExeFsProcess::ExeFsProcess() :
mModuleLabel("ctrtool::ExeFsProcess"),
mInputStream(),
mShowHeaderInfo(false),
mShowFs(false),
mVerbose(false),
mVerify(false),
mRaw(false),
mDecompressCode(false),
mExtractPath(),
mFsReader()
{
memset((byte_t*)&mHeader, 0, sizeof(ntd::n3ds::ExeFsHeader));
memset(mSectionValidation.data(), ValidState::Unchecked, mSectionValidation.size());
}
void ctrtool::ExeFsProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::ExeFsProcess::setCliOutputMode(bool show_header_info, bool show_fs)
{
mShowHeaderInfo = show_header_info;
mShowFs = show_fs;
}
void ctrtool::ExeFsProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
}
void ctrtool::ExeFsProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void ctrtool::ExeFsProcess::setRawMode(bool raw)
{
mRaw = raw;
}
void ctrtool::ExeFsProcess::setDecompressCode(bool decompress)
{
mDecompressCode = decompress;
}
void ctrtool::ExeFsProcess::setExtractPath(const tc::io::Path& extract_path)
{
mExtractPath = extract_path;
}
void ctrtool::ExeFsProcess::process()
{
// begin processing
importHeader();
if (mVerify)
verifyFs();
if (mShowHeaderInfo)
printHeader();
if (mShowFs)
printFs();
if (mExtractPath.isSet())
extractFs();
}
void ctrtool::ExeFsProcess::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::ExeFsHeader))
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small.");
}
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::ExeFsHeader));
// do some simple checks to verify if this is an EXEFS header
if (mHeader.file_table[0].name[0] == 0 || mHeader.file_table[0].offset.unwrap() != 0 || mHeader.hash_table[ntd::n3ds::ExeFsHeader::kFileNum - 1][0] == 0)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "ExeFsHeader is corrupted (Bad first entry).");
}
// create FileSystem reader (but don't verify the hashes, we'll do this if necessary to match ctrtool behaviour)
mFsReader = std::shared_ptr<tc::io::VirtualFileSystem>(new tc::io::VirtualFileSystem(ntd::n3ds::ExeFsSnapshotGenerator(mInputStream, false)));
}
void ctrtool::ExeFsProcess::verifyFs()
{
tc::crypto::Sha256Generator hash_calc;
std::array<byte_t, hash_calc.kHashSize> hash;
tc::ByteData cache = tc::ByteData(0x10000);
for (size_t i = 0; i < ntd::n3ds::ExeFsHeader::kFileNum; i++)
{
if (mHeader.file_table[i].size.unwrap() > 0)
{
auto offset = mHeader.file_table[i].offset.unwrap() + sizeof(ntd::n3ds::ExeFsHeader);
auto size = mHeader.file_table[i].size.unwrap();
auto& hdr_hash = mHeader.hash_table[ntd::n3ds::ExeFsHeader::kFileNum - 1 - i];
mInputStream->seek(offset, tc::io::SeekOrigin::Begin);
hash_calc.initialize();
for (size_t i = size; i > 0;)
{
size_t read_len = std::min<size_t>(i, cache.size());
read_len = mInputStream->read(cache.data(), read_len);
hash_calc.update(cache.data(), read_len);
i -= read_len;
}
hash_calc.getHash(hash.data());
mSectionValidation[i] = memcmp(hash.data(), hdr_hash.data(), hash.size()) == 0? Good : Fail;
if (mVerbose)
{
fmt::print("[LOG/ExeFs] File: \"{}\" {} hash validation\n", mHeader.file_table[i].name.decode(), (mSectionValidation[i] == ValidState::Good ? "passed" : "failed"));
}
}
}
}
void ctrtool::ExeFsProcess::printHeader()
{
fmt::print("\n");
fmt::print("ExeFS:\n");
for (size_t i = 0; i < ntd::n3ds::ExeFsHeader::kFileNum; i++)
{
if (mHeader.file_table[i].size.unwrap() > 0)
{
const auto& name = mHeader.file_table[i].name;
const auto& offset = mHeader.file_table[i].offset;
const auto& size = mHeader.file_table[i].size;
const auto& hash = mHeader.hash_table[ntd::n3ds::ExeFsHeader::kFileNum - 1 - i];
fmt::print("Section name: {}\n", name.decode());
fmt::print("Section offset: 0x{:08x}\n", offset.unwrap() + sizeof(ntd::n3ds::ExeFsHeader));
fmt::print("Section size: 0x{:08x}\n", size.unwrap());
fmt::print("Section hash: {:6} {}\n", getValidString(mSectionValidation[i]), tc::cli::FormatUtil::formatBytesAsString(hash.data(), hash.size(), true, ""));
}
}
}
void ctrtool::ExeFsProcess::printFs()
{
tc::io::sDirectoryListing dir;
mFsReader->getDirectoryListing(tc::io::Path("/"), dir);
fmt::print("[ExeFs Filesystem]\n");
fmt::print(" ExeFs:/\n");
for (auto itr = dir.file_list.begin(); itr != dir.file_list.end(); itr++)
{
fmt::print(" {}\n", *itr);
}
}
void ctrtool::ExeFsProcess::extractFs()
{
tc::io::sDirectoryListing dir;
mFsReader->getDirectoryListing(tc::io::Path("/"), dir);
tc::io::LocalFileSystem local_fs;
std::shared_ptr<tc::io::IStream> in_stream;
std::shared_ptr<tc::io::IStream> out_stream;
for (auto itr = dir.file_list.begin(); itr != dir.file_list.end(); itr++)
{
// open input stream
mFsReader->openFile(*itr, tc::io::FileMode::Open, tc::io::FileAccess::Read, in_stream);
// create output file name
std::string f_name;
if (itr->at(0) == '.')
f_name = itr->substr(1, std::string::npos) + ".bin";
else
f_name = *itr + ".bin";
// create output file path
tc::io::Path f_path = mExtractPath.get() + f_name;
// open out stream
local_fs.createDirectory(mExtractPath.get());
local_fs.openFile(f_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write, out_stream);
if (*itr == ".code" && mDecompressCode && !mRaw)
{
tc::ByteData compdata = tc::ByteData(in_stream->length());
in_stream->seek(0, tc::io::SeekOrigin::Begin);
in_stream->read(compdata.data(), compdata.size());
// get code hash, only decompress if hash is valid
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
tc::crypto::GenerateSha256Hash(hash.data(), compdata.data(), compdata.size());
const byte_t* test_hash = nullptr;
for (size_t i = 0; i < ntd::n3ds::ExeFsHeader::kFileNum; i++)
{
if (mHeader.file_table[i].name.decode() == *itr)
{
test_hash = mHeader.getFileHash(i)->data();
break;
}
}
if (test_hash != nullptr && memcmp(test_hash, hash.data(), hash.size()) == 0)
{
fmt::print("Decompressing section {} to {}...\n", *itr, f_path.to_string());
tc::ByteData decompdata = tc::ByteData(lzss_get_decompressed_size(compdata.data(), compdata.size()));
lzss_decompress(compdata.data(), compdata.size(), decompdata.data(), decompdata.size());
out_stream->seek(0, tc::io::SeekOrigin::Begin);
out_stream->write(decompdata.data(), decompdata.size());
}
else
{
fmt::print("Saving section {} to {}...\n", *itr, f_path.to_string());
out_stream->seek(0, tc::io::SeekOrigin::Begin);
out_stream->write(compdata.data(), compdata.size());
}
}
else
{
fmt::print("Saving section {} to {}...\n", *itr, f_path.to_string());
tc::ByteData filedata = tc::ByteData(in_stream->length());
in_stream->seek(0, tc::io::SeekOrigin::Begin);
in_stream->read(filedata.data(), filedata.size());
out_stream->seek(0, tc::io::SeekOrigin::Begin);
out_stream->write(filedata.data(), filedata.size());
}
}
}
std::string ctrtool::ExeFsProcess::getValidString(byte_t validstate)
{
std::string ret_str;
switch (validstate)
{
case ValidState::Unchecked:
ret_str = "";
break;
case ValidState::Good:
ret_str = "(GOOD)";
break;
case ValidState::Fail:
default:
ret_str = "(FAIL)";
break;
}
return ret_str;
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include "types.h"
#include <tc/Optional.h>
#include <tc/io/IFileSystem.h>
#include <ntd/n3ds/exefs.h>
namespace ctrtool {
class ExeFsProcess
{
public:
ExeFsProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setCliOutputMode(bool show_header_info, bool show_fs);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setRawMode(bool raw);
void setDecompressCode(bool decompress_code);
void setExtractPath(const tc::io::Path& extract_path);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
bool mShowHeaderInfo;
bool mShowFs;
bool mVerbose;
bool mVerify;
bool mRaw;
bool mDecompressCode;
tc::Optional<tc::io::Path> mExtractPath;
ntd::n3ds::ExeFsHeader mHeader;
std::shared_ptr<tc::io::IFileSystem> mFsReader;
std::array<byte_t, ntd::n3ds::ExeFsHeader::kFileNum> mSectionValidation;
void importHeader();
void verifyFs();
void printHeader();
void printFs();
void extractFs();
// string utils
std::string getValidString(byte_t validstate);
};
}
+396
View File
@@ -0,0 +1,396 @@
#include "FirmProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>
#include <tc/crypto/Aes128CbcEncryptedStream.h>
ctrtool::FirmProcess::FirmProcess() :
mModuleLabel("ctrtool::FirmProcess"),
mInputStream(),
mKeyBag(),
mShowInfo(false),
mVerbose(false),
mVerify(false),
mExtractPath(),
mFirmwareType(FirmwareType_Nand),
mSignatureState(SignatureState_Unchecked)
{
memset((byte_t*)&mHeader, 0, sizeof(mHeader));
memset(mValidFirmSectionHash.data(), ValidState::Unchecked, mValidFirmSectionHash.size());
}
void ctrtool::FirmProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::FirmProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
mKeyBag = key_bag;
}
void ctrtool::FirmProcess::setCliOutputMode(bool show_info)
{
mShowInfo = show_info;
}
void ctrtool::FirmProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
}
void ctrtool::FirmProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void ctrtool::FirmProcess::setExtractPath(const tc::io::Path& extract_path)
{
mExtractPath = extract_path;
}
void ctrtool::FirmProcess::setFirmwareType(FirmwareType type)
{
mFirmwareType = type;
}
void ctrtool::FirmProcess::process()
{
// begin processing
importHeader();
generateSectionStreams();
if (mVerify)
{
verifyHashes();
verifySignature();
}
if (mShowInfo)
printData();
if (mExtractPath.isSet())
extractSections();
}
void ctrtool::FirmProcess::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::FirmwareHeader)))
{
throw tc::InvalidOperationException(mModuleLabel, "Input stream too small to import header.");
}
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(mHeader));
if (mHeader.struct_magic.unwrap() != mHeader.kStructMagic)
{
throw tc::InvalidOperationException(mModuleLabel, "Invalid struct magic.");
}
}
void ctrtool::FirmProcess::generateSectionStreams()
{
tc::crypto::Aes128CbcEncryptedStream::key_t aes_key;
tc::crypto::Aes128CbcEncryptedStream::iv_t aes_iv;
// generate AES key
memset(aes_key.data(), 0, aes_key.size());
memset(aes_iv.data(), 0, aes_iv.size());
if (mFirmwareType == FirmwareType_Ngc)
{
auto key_itr = mKeyBag.firmware_key.find(mKeyBag.FIRM_NGC_KEY);
if (key_itr != mKeyBag.firmware_key.end())
{
memcpy(aes_key.data(), key_itr->second.data(), 16);
}
}
else if (mFirmwareType == FirmwareType_Nor)
{
auto key_itr = mKeyBag.firmware_key.find(mKeyBag.FIRM_NOR_KEY);
if (key_itr != mKeyBag.firmware_key.end())
{
memcpy(aes_key.data(), key_itr->second.data(), 16);
}
}
else if (mFirmwareType == FirmwareType_Sdmc)
{
auto key_itr = mKeyBag.firmware_key.find(mKeyBag.FIRM_SD_KEY);
if (key_itr != mKeyBag.firmware_key.end())
{
memcpy(aes_key.data(), key_itr->second.data(), 16);
}
}
for (size_t i = 0; i < mHeader.section.size(); i++)
{
if (mHeader.section[i].size.unwrap() > 0)
{
std::shared_ptr<tc::io::IStream> raw_stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mInputStream, mHeader.section[i].offset.unwrap(), mHeader.section[i].size.unwrap()));
switch (mFirmwareType)
{
case FirmwareType_Nand:
mSectionStreams[i] = raw_stream;
break;
case FirmwareType_Ngc:
case FirmwareType_Nor:
case FirmwareType_Sdmc:
createSectionAesIv(aes_iv, mHeader.section[i]);
mSectionStreams[i] = std::make_shared<tc::crypto::Aes128CbcEncryptedStream>(tc::crypto::Aes128CbcEncryptedStream(raw_stream, aes_key, aes_iv));
break;
}
}
else
{
mSectionStreams[i] = nullptr;
}
}
}
void ctrtool::FirmProcess::verifyHashes()
{
tc::crypto::Sha256Generator hash_calc;
std::array<byte_t, hash_calc.kHashSize> hash;
tc::ByteData cache = tc::ByteData(0x10000);
// get encryption key
for (size_t i = 0; i < mHeader.section.size(); i++)
{
if (mHeader.section[i].size.unwrap() > 0 && mSectionStreams[i] != nullptr)
{
auto& hdr_hash = mHeader.section[i].hash;
mSectionStreams[i]->seek(0, tc::io::SeekOrigin::Begin);
hash_calc.initialize();
for (size_t j = mSectionStreams[i]->length(); j > 0;)
{
size_t read_len = std::min<size_t>(j, cache.size());
read_len = mSectionStreams[i]->read(cache.data(), read_len);
hash_calc.update(cache.data(), read_len);
j -= read_len;
}
hash_calc.getHash(hash.data());
mValidFirmSectionHash[i] = memcmp(hash.data(), hdr_hash.data(), hash.size()) == 0? ValidState::Good : ValidState::Fail;
}
}
}
void ctrtool::FirmProcess::verifySignature()
{
byte_t key_id = 0;
switch (mFirmwareType)
{
case FirmwareType_Nand:
case FirmwareType_Sdmc:
key_id = mKeyBag.RSAKEY_FIRM_NAND;
break;
case FirmwareType_Ngc:
case FirmwareType_Nor:
key_id = mKeyBag.RSAKEY_FIRM_RECOVERY;
}
byte_t valid_signature = ValidState::Unchecked;
bool is_sighax = false;
// validate header signature
if (mKeyBag.rsa_key.find(key_id) != mKeyBag.rsa_key.end())
{
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
tc::crypto::RsaKey pubkey = mKeyBag.rsa_key[key_id];
// generate hash
size_t offset = 0;
size_t size = sizeof(mHeader) - sizeof(mHeader.signature);
tc::crypto::GenerateSha256Hash(hash.data(), ((byte_t*)&mHeader) + offset, size);
// validate signature
valid_signature = tc::crypto::VerifyRsa2048Pkcs1Sha256(mHeader.signature.data(), hash.data(), pubkey) ? ValidState::Good : ValidState::Fail;
}
else
{
fmt::print(stderr, "Could not read static rsa_key {}.\n", key_id == mKeyBag.RSAKEY_FIRM_NAND ? "RSAKEY_FIRM_NAND" : "RSAKEY_FIRM_RECOVERY");
valid_signature = ValidState::Fail;
}
// check if signature is sighax
if (mKeyBag.rsa_sighax_signature.find(key_id) != mKeyBag.rsa_sighax_signature.end())
{
auto signature = mKeyBag.rsa_sighax_signature[key_id];
is_sighax = memcmp(signature.data(), mHeader.signature.data(), mHeader.signature.size()) == 0;
}
else
{
fmt::print(stderr, "Could not read rsa_sighax_signature for {}.\n", key_id == mKeyBag.RSAKEY_FIRM_NAND ? "RSAKEY_FIRM_NAND" : "RSAKEY_FIRM_RECOVERY");
is_sighax = false;
}
// test if signature was valid
if (valid_signature == ValidState::Good)
{
mSignatureState = SignatureState_Good;
}
// check if sighax
else if (valid_signature == ValidState::Fail && is_sighax == true)
{
mSignatureState = SignatureState_SigHax;
}
else
{
mSignatureState = SignatureState_Fail;
}
}
void ctrtool::FirmProcess::printData()
{
fmt::print("\n");
fmt::print("FIRM:\n");
fmt::print("Signature: {:8} {}\n", getSignatureStateString(mSignatureState) , tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHeader.signature.data(), mHeader.signature.size(), true, "", 0x20, 24, false));
fmt::print("Magic: {}\n", "FIRM");
fmt::print("Priority: {:d}\n", mHeader.priority.unwrap());
fmt::print("Entrypoint ARM11: 0x{:08x}\n", mHeader.entrypoint_arm11.unwrap());
fmt::print("Entrypoint ARM9: 0x{:08x}\n", mHeader.entrypoint_arm9.unwrap());
fmt::print("\n");
for (size_t i = 0; i < mHeader.section.size(); i++)
{
if (mHeader.section[i].size.unwrap() == 0) continue;
fmt::print("Section {:d}\n", i);
fmt::print(" Offset: 0x{:08x}\n", mHeader.section[i].offset.unwrap());
fmt::print(" Address: 0x{:08x}\n", mHeader.section[i].address.unwrap());
fmt::print(" Size: 0x{:08x}\n", mHeader.section[i].size.unwrap());
fmt::print(" Copy Method: {} (0x{:08x})\n", getCopyMethodString(mHeader.section[i].copy_method.unwrap()), mHeader.section[i].copy_method.unwrap());
fmt::print(" Hash: {:6} {}\n", getValidString(mValidFirmSectionHash[i]), tc::cli::FormatUtil::formatBytesAsString(mHeader.section[i].hash.data(), mHeader.section[i].hash.size(), true, ""));
}
}
void ctrtool::FirmProcess::extractSections()
{
tc::io::LocalFileSystem local_fs;
std::shared_ptr<tc::io::IStream> in_stream;
std::shared_ptr<tc::io::IStream> out_stream;
for (size_t i = 0; i < mHeader.section.size(); i++)
{
if (mHeader.section[i].size.unwrap() > 0 && mSectionStreams[i] != nullptr)
{
in_stream = mSectionStreams[i];
// create output file name
std::string f_name = fmt::format("firm_{:d}_{:08x}.bin", i, mHeader.section[i].address.unwrap());
// create output file path
tc::io::Path f_path = mExtractPath.get() + f_name;
// save output file path string
std::string f_path_str;
tc::io::PathUtil::pathToUnixUTF8(f_path, f_path_str);
// open out stream
local_fs.createDirectory(mExtractPath.get());
local_fs.openFile(f_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write, out_stream);
fmt::print("Saving section {} to {}...\n", i, f_path_str);
tc::ByteData filedata = tc::ByteData(in_stream->length());
in_stream->seek(0, tc::io::SeekOrigin::Begin);
in_stream->read(filedata.data(), filedata.size());
out_stream->seek(0, tc::io::SeekOrigin::Begin);
out_stream->write(filedata.data(), filedata.size());
}
}
}
void ctrtool::FirmProcess::createSectionAesIv(std::array<byte_t, 16>& iv, const ntd::n3ds::FirmwareHeader::SectionHeader& section)
{
tc::bn::le32<uint32_t>* aes_iv_words = (tc::bn::le32<uint32_t>*)(iv.data());
aes_iv_words[0].wrap(section.offset.unwrap());
aes_iv_words[1].wrap(section.address.unwrap());
aes_iv_words[2].wrap(section.size.unwrap());
aes_iv_words[3].wrap(section.size.unwrap());
}
std::string ctrtool::FirmProcess::getValidString(byte_t validstate)
{
std::string ret_str;
switch (validstate)
{
case ValidState::Unchecked:
ret_str = "";
break;
case ValidState::Good:
ret_str = "(GOOD)";
break;
case ValidState::Fail:
default:
ret_str = "(FAIL)";
break;
}
return ret_str;
}
std::string ctrtool::FirmProcess::getSignatureStateString(byte_t signature_state)
{
std::string ret_str;
switch(signature_state)
{
case SignatureState_Unchecked:
ret_str = "";
break;
case SignatureState_Good:
ret_str = "(GOOD)";
break;
case SignatureState_Fail:
ret_str = "(FAIL)";
break;
case SignatureState_SigHax:
ret_str = "(SIGHAX)";
break;
}
return ret_str;
}
std::string ctrtool::FirmProcess::getCopyMethodString(uint32_t method)
{
std::string ret_str;
switch(method)
{
case ntd::n3ds::FirmwareHeader::SectionHeader::CopyMethod_NDMA :
ret_str = "NDMA";
break;
case ntd::n3ds::FirmwareHeader::SectionHeader::CopyMethod_XDMA :
ret_str = "XDMA";
break;
case ntd::n3ds::FirmwareHeader::SectionHeader::CopyMethod_MEMCPY :
ret_str = "MEMCPY";
break;
default:
ret_str = "Unknown";
break;
}
return ret_str;
}
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include <tc/io/IStream.h>
#include <ntd/n3ds/firm.h>
namespace ctrtool {
class FirmProcess
{
public:
enum FirmwareType
{
FirmwareType_Nand = 0, // NAND signature, sections not encrypted
FirmwareType_Ngc = 1, // Recovery Signature, but sections are encrypted
FirmwareType_Nor = 2, // Recovery Signature like NGC, but different section encryption key.
FirmwareType_Sdmc = 3, // NAND signature, but sections are encrypted.
};
FirmProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setCliOutputMode(bool show_info);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setExtractPath(const tc::io::Path& extract_path);
void setFirmwareType(FirmwareType type);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mShowInfo;
bool mVerbose;
bool mVerify;
tc::Optional<tc::io::Path> mExtractPath;
FirmwareType mFirmwareType;
ntd::n3ds::FirmwareHeader mHeader;
enum SignatureState
{
SignatureState_Unchecked = 0,
SignatureState_Good = 1,
SignatureState_Fail = 2,
SignatureState_SigHax = 3,
};
byte_t mSignatureState;
std::array<std::shared_ptr<tc::io::IStream>, 4> mSectionStreams;
std::array<byte_t, 4> mValidFirmSectionHash;
void importHeader();
void generateSectionStreams();
void verifyHashes();
void verifySignature();
void printData();
void extractSections();
void createSectionAesIv(std::array<byte_t, 16>& iv, const ntd::n3ds::FirmwareHeader::SectionHeader& section);
// string utils
std::string getValidString(byte_t validstate);
std::string getSignatureStateString(byte_t signature_state);
std::string getCopyMethodString(uint32_t method);
};
}
+199
View File
@@ -0,0 +1,199 @@
#include "IvfcProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>
ctrtool::IvfcProcess::IvfcProcess() :
mModuleLabel("ctrtool::IvfcProcess"),
mInputStream(),
mKeyBag(),
mShowHeaderInfo(false),
mShowFs(false),
mVerbose(false),
mVerify(),
mExtractPath(),
mRomFsProcess()
{
memset((byte_t*)&mHeader, 0, sizeof(ntd::n3ds::IvfcCtrRomfsHeader));
mMasterHashOffset = 0;
for (size_t i = 0; i < ntd::n3ds::IvfcCtrRomfsHeader::kLevelNum; i++)
{
mActualLevelOffsets[i] = 0;
mLevelValidation[i] = ValidState::Unchecked;
}
}
void ctrtool::IvfcProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::IvfcProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
mKeyBag = key_bag;
}
void ctrtool::IvfcProcess::setCliOutputMode(bool show_header_info, bool show_fs)
{
mShowHeaderInfo = show_header_info;
mShowFs = show_fs;
}
void ctrtool::IvfcProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
}
void ctrtool::IvfcProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void ctrtool::IvfcProcess::setExtractPath(const tc::io::Path& extract_path)
{
mExtractPath = extract_path;
}
void ctrtool::IvfcProcess::process()
{
// begin processing
processHeader();
if (mVerify)
verifyLevels();
if (mShowHeaderInfo)
printHeader();
processRomFs();
}
void ctrtool::IvfcProcess::processHeader()
{
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
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::IvfcCtrRomfsHeader));
// do some simple checks to verify if this is an IVFC header
if (mHeader.head.struct_magic.unwrap() != mHeader.head.kStructMagic ||
mHeader.head.type_id.unwrap() != mHeader.head.TypeId_A ||
mHeader.header_size.unwrap() != sizeof(ntd::n3ds::IvfcCtrRomfsHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "IvfcCtrRomfsHeader is corrupted.");
}
mMasterHashOffset = align<int64_t>(sizeof(ntd::n3ds::IvfcCtrRomfsHeader), ntd::n3ds::IvfcCtrRomfsHeader::kHeaderAlign);
mActualLevelOffsets[2] = align<int64_t>(mMasterHashOffset + static_cast<int64_t>(mHeader.master_hash_size.unwrap()), static_cast<int64_t>(1) << static_cast<int64_t>(mHeader.level[1].block_size_log2.unwrap()));
mActualLevelOffsets[0] = align<int64_t>(mActualLevelOffsets[2] + static_cast<int64_t>(mHeader.level[2].size.unwrap()), static_cast<int64_t>(1) << static_cast<int64_t>(mHeader.level[2].block_size_log2.unwrap()));
mActualLevelOffsets[1] = align<int64_t>(mActualLevelOffsets[0] + static_cast<int64_t>(mHeader.level[0].size.unwrap()), static_cast<int64_t>(1) << static_cast<int64_t>(mHeader.level[0].block_size_log2.unwrap()));
}
void ctrtool::IvfcProcess::verifyLevels()
{
size_t blk_num;
size_t blk_sz;
int64_t hash_base_offset;
size_t hash_sz;
tc::crypto::Sha256Generator hashgen;
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> calc_hash;
for (size_t i = 0; i < mActualLevelOffsets.size(); i++)
{
blk_sz = size_t(1) << size_t(mHeader.level[i].block_size_log2.unwrap());
blk_num = (mHeader.level[i].size.unwrap() / blk_sz) + ((mHeader.level[i].size.unwrap() % blk_sz) ? 1 : 0);
hash_base_offset = (i == 0) ? mMasterHashOffset : mActualLevelOffsets[i-1];
hash_sz = blk_num * tc::crypto::Sha256Generator::kHashSize;
tc::ByteData test_hash = tc::ByteData(hash_sz);
mInputStream->seek(hash_base_offset, tc::io::SeekOrigin::Begin);
mInputStream->read(test_hash.data(), test_hash.size());
tc::ByteData block = tc::ByteData(blk_sz);
mInputStream->seek(mActualLevelOffsets[i], tc::io::SeekOrigin::Begin);
size_t bad_blocks = blk_num;
for (size_t j = 0; j < blk_num; j++)
{
mInputStream->read(block.data(), block.size());
hashgen.initialize();
hashgen.update(block.data(), block.size());
hashgen.getHash(calc_hash.data());
if (memcmp(calc_hash.data(), test_hash.data() + j*tc::crypto::Sha256Generator::kHashSize, tc::crypto::Sha256Generator::kHashSize) != 0)
{
if (mVerbose)
{
fmt::print("[LOG/IVFC] IVFC Layer {:d}, Block {:d} failed validation\n", i, j);
}
}
else
{
bad_blocks -= 1;
}
}
mLevelValidation[i] = bad_blocks == 0? ValidState::Good : ValidState::Fail;
if (mVerbose)
{
fmt::print("[LOG/IVFC] IVFC Layer {:d} {} validation\n", i, (mLevelValidation[i] == ValidState::Good ? "passed" : "failed"));
}
}
}
void ctrtool::IvfcProcess::printHeader()
{
fmt::print("\n");
fmt::print("IVFC:\n");
fmt::print("Header: {}\n", "IVFC");
fmt::print("Id: {:08x}\n", mHeader.head.type_id.unwrap());
fmt::print("Master hash size: 0x{:08x}\n", mHeader.master_hash_size.unwrap());
fmt::print("Header size: 0x{:08x}\n", mHeader.header_size.unwrap());
for (size_t i = 0; i < ntd::n3ds::IvfcCtrRomfsHeader::kLevelNum; i++)
{
fmt::print("\n");
fmt::print("Level {:d}: {}\n", i, getValidString(mLevelValidation[i]));
fmt::print(" Offset: 0x{:08x} (Actual: 0x{:08x})\n", mHeader.level[i].offset.unwrap(), mActualLevelOffsets[i]);
fmt::print(" Size: 0x{:08x}\n", mHeader.level[i].size.unwrap());
fmt::print(" BlockSizeLog2: 0x{:08x} (BlockSize: 0x{:08x})\n", mHeader.level[i].block_size_log2.unwrap(), 1 << mHeader.level[i].block_size_log2.unwrap());
}
}
void ctrtool::IvfcProcess::processRomFs()
{
std::shared_ptr<ntd::n3ds::IvfcStream> data_layer = std::shared_ptr<ntd::n3ds::IvfcStream>(new ntd::n3ds::IvfcStream(mInputStream));
mRomFsProcess.setInputStream(data_layer);
mRomFsProcess.setKeyBag(mKeyBag);
mRomFsProcess.setCliOutputMode(mShowHeaderInfo, mShowFs);
mRomFsProcess.setVerboseMode(mVerbose);
mRomFsProcess.setVerifyMode(mVerify);
if (mExtractPath.isSet())
mRomFsProcess.setExtractPath(mExtractPath.get());
mRomFsProcess.process();
}
std::string ctrtool::IvfcProcess::getValidString(byte_t validstate)
{
std::string ret_str;
switch (validstate)
{
case ValidState::Unchecked:
ret_str = "";
break;
case ValidState::Good:
ret_str = "(GOOD)";
break;
case ValidState::Fail:
default:
ret_str = "(FAIL)";
break;
}
return ret_str;
}
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include "RomFsProcess.h"
#include <tc/Optional.h>
#include <ntd/n3ds/ivfc.h>
#include <ntd/n3ds/IvfcStream.h>
namespace ctrtool {
class IvfcProcess
{
public:
IvfcProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setCliOutputMode(bool show_header_info, bool show_fs);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setExtractPath(const tc::io::Path& extract_path);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mShowHeaderInfo;
bool mShowFs;
bool mVerbose;
bool mVerify;
tc::Optional<tc::io::Path> mExtractPath;
ntd::n3ds::IvfcCtrRomfsHeader mHeader;
ctrtool::RomFsProcess mRomFsProcess;
int64_t mMasterHashOffset;
std::array<int64_t, ntd::n3ds::IvfcCtrRomfsHeader::kLevelNum> mActualLevelOffsets;
std::array<byte_t, ntd::n3ds::IvfcCtrRomfsHeader::kLevelNum> mLevelValidation;
void processHeader();
void verifyLevels();
void printHeader();
void processRomFs();
// string utils
std::string getValidString(byte_t validstate);
};
}
+945
View File
@@ -0,0 +1,945 @@
#include "KeyBag.h"
#include <tc/cli/FormatUtil.h>
ctrtool::KeyBagInitializer::KeyBagInitializer(bool isDev, const tc::Optional<std::string>& fallback_title_key_str, const tc::Optional<tc::io::Path>& seed_db_path, const tc::Optional<std::string>& fallback_seed_str)
{
// add static data
addStaticAesKeys(isDev);
addStaticRsaKeys(isDev);
addSigHaxSignatures(isDev);
// import seed database
if (seed_db_path.isSet())
{
auto file_source = std::shared_ptr<tc::io::StreamSource>();
try {
auto file_stream = std::shared_ptr<tc::io::FileStream>(new tc::io::FileStream(seed_db_path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read));
file_source = std::shared_ptr<tc::io::StreamSource>(new tc::io::StreamSource(file_stream));
}
catch (tc::io::FileNotFoundException&) {
throw tc::ArgumentException("ctrtool::KeyBagInitializer", "Failed to open seed database file.");
}
importSeedDb(file_source);
}
// import fallback keys
if (fallback_title_key_str.isSet())
{
if (importFallbackKey(fallback_title_key, fallback_title_key_str.get()) == false)
{
throw tc::ArgumentOutOfRangeException("ctrtool::KeyBagInitializer", "Fallback title key failed to import.");
}
}
if (fallback_seed_str.isSet())
{
if (importFallbackKey(fallback_seed, fallback_seed_str.get()) == false)
{
throw tc::ArgumentOutOfRangeException("ctrtool::KeyBagInitializer", "Fallback seed failed to import.");
}
}
}
void ctrtool::KeyBagInitializer::addStaticAesKeys(bool isDev)
{
struct DefaultAesKey
{
byte_t key_index;
KeyBag::Aes128Key key;
};
static const size_t kDefaultNcchFixedKeyNum = 2;
const DefaultAesKey default_ncch_fixed_aes_keys[kDefaultNcchFixedKeyNum][2] =
{
{
{NCCH_APPLICATION_FIXED_KEY, {0}},
{NCCH_APPLICATION_FIXED_KEY, {0}},
},
{
{NCCH_SYSTEM_FIXED_KEY, {0x52, 0x7c, 0xe6, 0x30, 0xa9, 0xca, 0x30, 0x5f, 0x36, 0x96, 0xf3, 0xcd, 0xe9, 0x54, 0x19, 0x4b}},
{NCCH_SYSTEM_FIXED_KEY, {0x52, 0x7c, 0xe6, 0x30, 0xa9, 0xca, 0x30, 0x5f, 0x36, 0x96, 0xf3, 0xcd, 0xe9, 0x54, 0x19, 0x4b}},
}
};
static const size_t kDefaultNcchSecureKeyXNum = 4;
const DefaultAesKey default_ncch_secure_aes_keys_x[kDefaultNcchSecureKeyXNum][2] =
{
{
// retail
{NCCH_SECURE_KEY_FW1, {0xb9, 0x8e, 0x95, 0xce, 0xca, 0x3e, 0x4d, 0x17, 0x1f, 0x76, 0xa9, 0x4d, 0xe9, 0x34, 0xc0, 0x53}},
// dev
{NCCH_SECURE_KEY_FW1, {0x51, 0x02, 0x07, 0x51, 0x55, 0x07, 0xcb, 0xb1, 0x8e, 0x24, 0x3d, 0xcb, 0x85, 0xe2, 0x3a, 0x1d}},
},
{
// retail
{NCCH_SECURE_KEY_FW7, {0xce, 0xe7, 0xd8, 0xab, 0x30, 0xc0, 0x0d, 0xae, 0x85, 0x0e, 0xf5, 0xe3, 0x82, 0xac, 0x5a, 0xf3}},
// dev
{NCCH_SECURE_KEY_FW7, {0x81, 0x90, 0x7a, 0x4b, 0x6f, 0x1b, 0x47, 0x32, 0x3a, 0x67, 0x79, 0x74, 0xce, 0x4a, 0xd7, 0x1b}},
},
{
// retail
{NCCH_SECURE_KEY_FW9_3, {0x82, 0xe9, 0xc9, 0xbe, 0xbf, 0xb8, 0xbd, 0xb8, 0x75, 0xec, 0xc0, 0xa0, 0x7d, 0x47, 0x43, 0x74}},
// dev
{NCCH_SECURE_KEY_FW9_3, {0x30, 0x4b, 0xf1, 0x46, 0x83, 0x72, 0xee, 0x64, 0x11, 0x5e, 0xbd, 0x40, 0x93, 0xd8, 0x42, 0x76}},
},
{
// retail
{NCCH_SECURE_KEY_FW9_6, {0x45, 0xad, 0x04, 0x95, 0x39, 0x92, 0xc7, 0xc8, 0x93, 0x72, 0x4a, 0x9a, 0x7b, 0xce, 0x61, 0x82}},
// dev
{NCCH_SECURE_KEY_FW9_6, {0x6c, 0x8b, 0x29, 0x44, 0xa0, 0x72, 0x60, 0x35, 0xf9, 0x41, 0xdf, 0xc0, 0x18, 0x52, 0x4f, 0xb6}},
},
};
static const size_t kDefaultCommonKeyNum = 6;
const DefaultAesKey default_common_aes_keys[kDefaultCommonKeyNum][2] =
{
{
// retail
{COMMONKEY_APPLICATION, {0x64, 0xC5, 0xFD, 0x55, 0xDD, 0x3A, 0xD9, 0x88, 0x32, 0x5B, 0xAA, 0xEC, 0x52, 0x43, 0xDB, 0x98}},
// dev
{COMMONKEY_APPLICATION, {0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B}},
},
{
// retail
{COMMONKEY_SYSTEM, {0x4A, 0xAA, 0x3D, 0x0E, 0x27, 0xD4, 0xD7, 0x28, 0xD0, 0xB1, 0xB4, 0x33, 0xF0, 0xF9, 0xCB, 0xC8}},
// dev
{COMMONKEY_SYSTEM, {0x44, 0x34, 0xED, 0x14, 0x82, 0x0C, 0xA1, 0xEB, 0xAB, 0x82, 0xC1, 0x6E, 0x7B, 0xEF, 0x0C, 0x25}},
},
{
// retail
{COMMONKEY_UNUSED_2, {0xFB, 0xB0, 0xEF, 0x8C, 0xDB, 0xB0, 0xD8, 0xE4, 0x53, 0xCD, 0x99, 0x34, 0x43, 0x71, 0x69, 0x7F}},
// dev
{COMMONKEY_UNUSED_2, {0xF6, 0x2E, 0x3F, 0x95, 0x8E, 0x28, 0xA2, 0x1F, 0x28, 0x9E, 0xEC, 0x71, 0xA8, 0x66, 0x29, 0xDC}},
},
{
// retail
{COMMONKEY_UNUSED_3, {0x25, 0x95, 0x9B, 0x7A, 0xD0, 0x40, 0x9F, 0x72, 0x68, 0x41, 0x98, 0xBA, 0x2E, 0xCD, 0x7D, 0xC6}},
// dev
{COMMONKEY_UNUSED_3, {0x2B, 0x49, 0xCB, 0x6F, 0x99, 0x98, 0xD9, 0xAD, 0x94, 0xF2, 0xED, 0xE7, 0xB5, 0xDA, 0x3E, 0x27}},
},
{
// retail
{COMMONKEY_UNUSED_4, {0x7A, 0xDA, 0x22, 0xCA, 0xFF, 0xC4, 0x76, 0xCC, 0x82, 0x97, 0xA0, 0xC7, 0xCE, 0xEE, 0xEE, 0xBE}},
// dev
{COMMONKEY_UNUSED_4, {0x75, 0x05, 0x52, 0xBF, 0xAA, 0x1C, 0x04, 0x07, 0x55, 0xC8, 0xD5, 0x9A, 0x55, 0xF9, 0xAD, 0x1F}},
},
{
// retail
{COMMONKEY_UNUSED_4, {0xA5, 0x05, 0x1C, 0xA1, 0xB3, 0x7D, 0xCF, 0x3A, 0xFB, 0xCF, 0x8C, 0xC1, 0xED, 0xD9, 0xCE, 0x02}},
// dev
{COMMONKEY_UNUSED_4, {0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63}},
}
};
// ncch fixed keys
for (size_t i = 0; i < kDefaultNcchFixedKeyNum; i++)
{
ncch_fixed_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(default_ncch_fixed_aes_keys[i][isDev].key_index, default_ncch_fixed_aes_keys[i][isDev].key));
}
// ncch secure key x
for (size_t i = 0; i < kDefaultNcchSecureKeyXNum; i++)
{
ncch_secure_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(default_ncch_secure_aes_keys_x[i][isDev].key_index, default_ncch_secure_aes_keys_x[i][isDev].key));
}
// ticket common key
for (size_t i = 0; i < kDefaultCommonKeyNum; i++)
{
common_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(default_common_aes_keys[i][isDev].key_index, default_common_aes_keys[i][isDev].key));
}
// populate aes keyslots
enum StaticKeyXSlots
{
KEYX_2C_2F,
KEYX_30_33,
KEYX_34_37,
KEYX_38_3B,
KEYX_3C,
KEYX_3D,
KEYX_3E,
KEYX_3F,
BROM_KEYX_NUM
};
const DefaultAesKey default_brom_static_keyx[BROM_KEYX_NUM][2] =
{
{
// retail
{KEYX_2C_2F, {0xB9, 0x8E, 0x95, 0xCE, 0xCA, 0x3E, 0x4D, 0x17, 0x1F, 0x76, 0xA9, 0x4D, 0xE9, 0x34, 0xC0, 0x53}},
// dev
{KEYX_2C_2F, {0x51, 0x02, 0x07, 0x51, 0x55, 0x07, 0xCB, 0xB1, 0x8E, 0x24, 0x3D, 0xCB, 0x85, 0xE2, 0x3A, 0x1D}},
},
{
// retail
{KEYX_30_33, {0xC6, 0x6E, 0x23, 0x12, 0x8F, 0x28, 0x91, 0x33, 0xF0, 0x4C, 0xDB, 0x87, 0x7A, 0x37, 0x49, 0xF2}},
// dev
{KEYX_30_33, {0x3F, 0x05, 0x4E, 0x66, 0x3B, 0x3E, 0xF7, 0x28, 0xC8, 0x98, 0x4D, 0x20, 0xC4, 0xAF, 0xD5, 0xA0}},
},
{
// retail
{KEYX_34_37, {0x6F, 0xBB, 0x01, 0xF8, 0x72, 0xCA, 0xF9, 0xC0, 0x18, 0x34, 0xEE, 0xC0, 0x40, 0x65, 0xEE, 0x53}},
// dev
{KEYX_34_37, {0x7B, 0xFB, 0x77, 0xBC, 0xBC, 0x05, 0x9A, 0x06, 0xAC, 0xAD, 0x88, 0xEF, 0x2F, 0xCA, 0xBE, 0xDB}},
},
{
// retail
{KEYX_38_3B, {0xB5, 0x29, 0x22, 0x1C, 0xDD, 0xB5, 0xDB, 0x5A, 0x1B, 0xF2, 0x6E, 0xFF, 0x20, 0x41, 0xE8, 0x75}},
// dev
{KEYX_38_3B, {0x5C, 0x3D, 0x38, 0xAC, 0x17, 0x40, 0x99, 0x4E, 0xFC, 0x8F, 0xD0, 0xBE, 0x8D, 0x80, 0x97, 0xB3}},
},
{
// retail
{KEYX_3C, {0xC3, 0x5D, 0x6D, 0x15, 0x68, 0x0B, 0x1A, 0xD4, 0xE9, 0x12, 0xA3, 0x41, 0x83, 0x61, 0x21, 0xB3}},
// dev
{KEYX_3C, {0x61, 0xBF, 0x11, 0x37, 0x0B, 0x29, 0x2F, 0xFA, 0xB3, 0x88, 0x51, 0xEC, 0x5D, 0xAE, 0x5D, 0xEC}},
},
// commonkey
{
// retail
{KEYX_3D, {0x61, 0x70, 0x85, 0x71, 0x9B, 0x7C, 0xFB, 0x31, 0x6D, 0xF4, 0xDF, 0x2E, 0x83, 0x62, 0xC6, 0xE2}},
// dev
{KEYX_3D, {0xBD, 0x4F, 0xE7, 0xE7, 0x33, 0xC7, 0x55, 0xFC, 0xE7, 0x54, 0x0E, 0xAB, 0xBD, 0x8A, 0xC3, 0xD3}},
},
{
// retail
{KEYX_3E, {0x24, 0xBA, 0xF6, 0x28, 0xD0, 0x68, 0x89, 0xBF, 0x28, 0x2D, 0x0A, 0xA3, 0x5D, 0xC5, 0x56, 0x50}},
// dev
{KEYX_3E, {0x28, 0x87, 0xA4, 0xD4, 0x28, 0xF6, 0xF2, 0x24, 0xB0, 0x3A, 0xB3, 0x36, 0xE2, 0x2C, 0x61, 0x1E}},
},
{
// retail
{KEYX_3F, {0xA3, 0x12, 0x33, 0x28, 0x0B, 0xB4, 0xDA, 0xA7, 0x76, 0x13, 0x93, 0xF7, 0x8C, 0x42, 0x49, 0x52}},
// dev
{KEYX_3F, {0xBE, 0x66, 0x5D, 0xE6, 0xFB, 0x8C, 0x3F, 0x0A, 0x98, 0x71, 0x96, 0x0A, 0xD7, 0xCF, 0xBE, 0x79}},
}
};
// 0x2C-0x2F
for (size_t i = 0; i < 4; i++)
{
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x2C + i, default_brom_static_keyx[KEYX_2C_2F][isDev].key));
}
// 0x30-0x33
for (size_t i = 0; i < 4; i++)
{
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x30 + i, default_brom_static_keyx[KEYX_30_33][isDev].key));
}
// 0x34-0x37
for (size_t i = 0; i < 4; i++)
{
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x34 + i, default_brom_static_keyx[KEYX_34_37][isDev].key));
}
// 0x38-0x3B
for (size_t i = 0; i < 4; i++)
{
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x38 + i, default_brom_static_keyx[KEYX_38_3B][isDev].key));
}
// 0x3C
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3C, default_brom_static_keyx[KEYX_3C][isDev].key));
// 0x3D common key
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3D, default_brom_static_keyx[KEYX_3D][isDev].key));
// 0x3E
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3E, default_brom_static_keyx[KEYX_3E][isDev].key));
// 0x3F
brom_static_key_x.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3F, default_brom_static_keyx[KEYX_3F][isDev].key));
enum StaticKeyYSlots
{
KEYY_04,
KEYY_05,
KEYY_06,
KEYY_07,
KEYY_08,
KEYY_09,
KEYY_0A,
KEYY_0B,
BROM_KEYY_NUM
};
const DefaultAesKey default_brom_static_keyy[BROM_KEYY_NUM][2] =
{
{
// retail
{KEYY_04, {0xFF, 0x33, 0x88, 0xEC, 0xD2, 0x17, 0x05, 0xBB, 0x33, 0x9E, 0x96, 0x79, 0x86, 0xDC, 0x49, 0x07}},
// dev
{KEYY_04, {0x5C, 0xE6, 0xB1, 0xEC, 0x3F, 0x5F, 0x9D, 0x7B, 0x2E, 0x81, 0xE2, 0x21, 0x45, 0xA5, 0x76, 0x8D}},
},
{
// retail
{KEYY_05, {0x54, 0xEF, 0x03, 0x5F, 0x30, 0x26, 0x0E, 0x0E, 0x9B, 0x5E, 0x00, 0x4F, 0xC9, 0x85, 0xDC, 0x22}},
// dev
{KEYY_05, {0x28, 0x5B, 0x2E, 0x23, 0xA3, 0xC3, 0x0E, 0x54, 0x8F, 0x24, 0xEE, 0x8D, 0x28, 0x7C, 0x26, 0x42}},
},
{
// retail
{KEYY_06, {0x24, 0xB0, 0x5A, 0xAA, 0xAC, 0x0B, 0x09, 0x92, 0x52, 0x03, 0x0C, 0x02, 0xD1, 0x04, 0x03, 0x17}},
// dev
{KEYY_06, {0xAE, 0x94, 0xFC, 0x90, 0xBE, 0x6B, 0x6C, 0xD5, 0xF1, 0xB0, 0xCB, 0x55, 0x95, 0x07, 0x22, 0x0E}},
},
{
// retail
{KEYY_07, {0xE9, 0xAC, 0xC5, 0xAB, 0xD4, 0xAD, 0x3F, 0x06, 0x60, 0xC8, 0x3C, 0x89, 0x34, 0x88, 0x2F, 0x3F}},
// dev
{KEYY_07, {0x49, 0x97, 0x4D, 0x47, 0xB6, 0xE2, 0xC9, 0xD9, 0x19, 0x4A, 0x2D, 0x97, 0xFD, 0xF2, 0x83, 0xBE}},
},
{
// retail
{KEYY_08, {0x48, 0x03, 0x05, 0x01, 0x06, 0xD4, 0x82, 0xDC, 0xD7, 0x5F, 0x85, 0xC5, 0xAA, 0xDF, 0xF9, 0xB3}},
// dev
{KEYY_08, {0x25, 0xA6, 0x19, 0x50, 0x4F, 0x07, 0xD0, 0x68, 0x19, 0x03, 0x34, 0xA8, 0x14, 0x09, 0xC2, 0x08}},
},
{
// retail
{KEYY_09, {0xAF, 0x63, 0x46, 0xEF, 0xDD, 0xDF, 0xA9, 0x80, 0x6E, 0x3C, 0x6B, 0x68, 0x55, 0xB7, 0x89, 0x30}},
// dev
{KEYY_09, {0x65, 0x7B, 0xBD, 0x65, 0x2E, 0x8B, 0xF3, 0x0F, 0x37, 0x40, 0xC4, 0x8F, 0xAC, 0x6C, 0xC5, 0x9E}},
},
{
// retail
{KEYY_0A, {0x0A, 0x87, 0x0A, 0x2C, 0x4B, 0x2F, 0xC3, 0x17, 0x2E, 0x5F, 0x03, 0x35, 0xD8, 0xC5, 0x08, 0x5D}},
// dev
{KEYY_0A, {0x14, 0x7A, 0xD1, 0x4A, 0xC2, 0x06, 0xB1, 0x00, 0xE2, 0x00, 0x2A, 0x7B, 0x1A, 0x0D, 0xDD, 0x3D}},
},
{
// retail
{KEYY_0B, {0xFD, 0xA0, 0x15, 0x2F, 0xCD, 0x6D, 0xDB, 0x31, 0x33, 0xB8, 0x87, 0xBA, 0x72, 0x7C, 0x0A, 0xDA}},
// dev
{KEYY_0B, {0xA3, 0xD0, 0x0D, 0x9E, 0x2C, 0x5E, 0xDF, 0x30, 0x86, 0x64, 0x86, 0x61, 0x1C, 0xE0, 0x8D, 0x25}},
}
};
// 0x04
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x04, default_brom_static_keyy[KEYY_04][isDev].key));
// 0x05
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x05, default_brom_static_keyy[KEYY_05][isDev].key));
// 0x06
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x06, default_brom_static_keyy[KEYY_06][isDev].key));
// 0x07
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x07, default_brom_static_keyy[KEYY_07][isDev].key));
// 0x08
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x08, default_brom_static_keyy[KEYY_08][isDev].key));
// 0x09
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x09, default_brom_static_keyy[KEYY_09][isDev].key));
// 0x0A
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x0A, default_brom_static_keyy[KEYY_0A][isDev].key));
// 0x0B
brom_static_key_y.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x0B, default_brom_static_keyy[KEYY_0B][isDev].key));
enum StaticKeySlots
{
KEY_0C_0F,
KEY_10_13,
KEY_14,
KEY_15,
KEY_16,
KEY_17,
KEY_18_1B,
KEY_1C_1F,
KEY_20_23,
KEY_24_27,
KEY_28,
KEY_29,
KEY_2A,
KEY_2B,
KEY_2C_2F,
KEY_30_33,
KEY_34_37,
KEY_38_3B,
KEY_3C,
KEY_3D,
KEY_3E,
KEY_3F,
BROM_KEY_NUM
};
const DefaultAesKey default_brom_static_key[BROM_KEY_NUM][2] =
{
{
// retail
{KEY_0C_0F, {0xE7, 0xC9, 0xFF, 0x9D, 0x4F, 0x5B, 0x6F, 0x4D, 0xC5, 0xE2, 0xF5, 0x0E, 0x85, 0x6F, 0x0A, 0xB2}},
// dev
{KEY_0C_0F, {0x25, 0xC6, 0x26, 0x59, 0x55, 0xA4, 0xFC, 0x6A, 0xC7, 0xE8, 0x58, 0x08, 0x7B, 0xD3, 0x09, 0x71}},
},
{
// retail
{KEY_10_13, {0x28, 0x57, 0x13, 0xDB, 0x53, 0x05, 0x1C, 0x08, 0x9B, 0xDF, 0xB3, 0xB6, 0xAA, 0x63, 0x8F, 0xDA}},
// dev
{KEY_10_13, {0x29, 0x72, 0xAF, 0xF5, 0x0F, 0xEE, 0x9F, 0x6F, 0x7B, 0x44, 0x3E, 0xC3, 0x4C, 0x7F, 0xDE, 0xAD}},
},
{
// retail
{KEY_14, {0x2A, 0xF3, 0xBB, 0xD3, 0x2C, 0xD5, 0x9C, 0x06, 0xFD, 0x4A, 0xBE, 0x58, 0x65, 0x19, 0x87, 0xAD}},
// dev
{KEY_14, {0x09, 0x71, 0x2F, 0x77, 0xEF, 0x36, 0x7C, 0xC0, 0xC9, 0x68, 0x41, 0x93, 0x8D, 0xC0, 0xB3, 0xA1}},
},
{
// retail
{KEY_15, {0xBE, 0x28, 0x36, 0x75, 0x1C, 0x73, 0x4B, 0xA8, 0xDA, 0x18, 0xE1, 0x88, 0x7F, 0x88, 0x8B, 0xD6}},
// dev
{KEY_15, {0x62, 0xEE, 0x74, 0x6F, 0x91, 0xE0, 0x2B, 0x1D, 0xD2, 0xF1, 0x7D, 0x46, 0xBB, 0xC2, 0xC9, 0xC5}},
},
{
// retail
{KEY_16, {0xE3, 0xA1, 0x8E, 0xB1, 0xC1, 0xDC, 0x8A, 0x3D, 0x27, 0xC3, 0x96, 0x7E, 0x6E, 0x36, 0x2D, 0xE3}},
// dev
{KEY_16, {0x41, 0x97, 0x20, 0xED, 0x83, 0x04, 0xAC, 0x7C, 0x38, 0xE7, 0x30, 0xC6, 0xF4, 0x4E, 0xAC, 0x7C}},
},
{
// retail
{KEY_17, {0xD0, 0x29, 0x4C, 0xFB, 0x7B, 0xE0, 0xB4, 0xFB, 0x73, 0x24, 0xD9, 0x86, 0xFD, 0x39, 0x93, 0xBB}},
// dev
{KEY_17, {0xC8, 0xE1, 0x94, 0x3B, 0x63, 0x9A, 0x02, 0xF1, 0x48, 0xB4, 0xFC, 0x99, 0xDF, 0xCF, 0x64, 0x6D}},
},
{
// retail
{KEY_18_1B, {0x5D, 0xDD, 0x47, 0x39, 0x03, 0x7B, 0xC6, 0xA8, 0x70, 0xE6, 0x20, 0xB7, 0x0F, 0x67, 0x35, 0x04}},
// dev
{KEY_18_1B, {0x76, 0xF8, 0x45, 0x68, 0x11, 0x32, 0xBB, 0x31, 0xB6, 0xCF, 0x9E, 0x2E, 0x39, 0x48, 0x99, 0x3A}},
},
{
// retail
{KEY_1C_1F, {0x59, 0xF4, 0x39, 0x9C, 0x2F, 0x95, 0xA4, 0x12, 0x8A, 0x1F, 0xE4, 0x9D, 0x4D, 0xB6, 0x86, 0xDD}},
// dev
{KEY_1C_1F, {0x18, 0x5C, 0x51, 0x17, 0x0D, 0x68, 0x16, 0xF2, 0xE4, 0xA5, 0x63, 0x22, 0xFA, 0xBB, 0xB3, 0x9D}},
},
{
// retail
{KEY_20_23, {0x7C, 0x92, 0xF6, 0x27, 0x25, 0x51, 0xC4, 0x61, 0x4D, 0xB0, 0xB3, 0x45, 0xED, 0xD2, 0xE8, 0x69}},
// dev
{KEY_20_23, {0xE3, 0x86, 0x81, 0x5A, 0xA0, 0x4F, 0xEE, 0x3A, 0x23, 0xAE, 0x8E, 0x5A, 0xD7, 0xC5, 0x0F, 0x48}},
},
{
// retail
{KEY_24_27, {0xBB, 0xE8, 0xB4, 0xE0, 0x9D, 0x09, 0x37, 0x81, 0x6B, 0x23, 0x4D, 0x8E, 0xB3, 0xCD, 0x3C, 0xA2}},
// dev
{KEY_24_27, {0xD9, 0xC0, 0x1E, 0xC5, 0x68, 0xE9, 0xC5, 0x85, 0x08, 0x27, 0xEE, 0xED, 0x59, 0xCC, 0x10, 0x57}},
},
{
// retail
{KEY_28, {0x52, 0x18, 0x12, 0x7E, 0x13, 0x3C, 0xE3, 0xB8, 0x5B, 0xB8, 0xC0, 0x18, 0xCE, 0x76, 0xB7, 0xE2}},
// dev
{KEY_28, {0x29, 0x62, 0xF3, 0x47, 0xB1, 0xF9, 0x8A, 0x69, 0x7C, 0x68, 0x94, 0xA8, 0xBA, 0xBD, 0x15, 0xA2}},
},
{
// retail
{KEY_29, {0x4A, 0x42, 0x64, 0xCF, 0x32, 0xE8, 0x41, 0x70, 0x66, 0x6F, 0x29, 0xAC, 0x88, 0xEF, 0x3F, 0x7E}},
// dev
{KEY_29, {0xEB, 0xCD, 0xE8, 0x6C, 0x34, 0xBE, 0x2D, 0x9E, 0xB9, 0x5E, 0x18, 0xB0, 0x3D, 0x8A, 0x41, 0x68}},
},
{
// retail
{KEY_2A, {0x51, 0xAF, 0x6C, 0x4C, 0x8B, 0x13, 0xDA, 0x32, 0x28, 0xBD, 0x29, 0xB3, 0x71, 0xCF, 0x84, 0xE1}},
// dev
{KEY_2A, {0x35, 0x08, 0xCF, 0xD3, 0xEA, 0xE5, 0xA4, 0xDA, 0x14, 0xAA, 0xCD, 0xD7, 0x37, 0x26, 0x3F, 0x77}},
},
{
// retail
{KEY_2B, {0x3E, 0xD6, 0xF5, 0xCF, 0x2C, 0xC3, 0x7C, 0x54, 0x65, 0x50, 0x00, 0xB7, 0xC8, 0xB5, 0x2E, 0x0D}},
// dev
{KEY_2B, {0x4F, 0x04, 0xC8, 0xB6, 0x7F, 0xEA, 0x1F, 0x5F, 0x3D, 0x9F, 0xFE, 0xCD, 0x13, 0x95, 0xF9, 0xDD}},
},
{
// retail
{KEY_2C_2F, {0xB8, 0x7E, 0x64, 0x01, 0x8B, 0x19, 0x0F, 0xFE, 0x04, 0x8A, 0x81, 0x24, 0xC6, 0x45, 0x41, 0x96}},
// dev
{KEY_2C_2F, {0x8A, 0xFE, 0x0F, 0x82, 0x69, 0x90, 0x1A, 0xD9, 0x31, 0x9A, 0x4C, 0xA7, 0x0F, 0xB0, 0x97, 0x0C}},
},
{
// retail
{KEY_30_33, {0x28, 0xC0, 0xD5, 0x9B, 0x73, 0x66, 0x57, 0xBC, 0xDF, 0x50, 0xFF, 0x17, 0x49, 0x79, 0x95, 0x8A}},
// dev
{KEY_30_33, {0xFA, 0xD5, 0xB8, 0x49, 0x64, 0x08, 0x96, 0xC3, 0x4E, 0xAC, 0xDB, 0x2C, 0x72, 0xD3, 0x71, 0x3A}},
},
{
// retail
{KEY_34_37, {0x6E, 0x78, 0xA3, 0xBE, 0x9B, 0xDD, 0xDA, 0x09, 0xBF, 0xD5, 0x69, 0x48, 0x3F, 0x24, 0xFC, 0xE0}},
// dev
{KEY_34_37, {0xA9, 0xBE, 0x6A, 0xB4, 0x31, 0x6C, 0xA5, 0x8A, 0x00, 0xC2, 0xA2, 0x3B, 0xE7, 0x57, 0xDF, 0x39}},
},
{
// retail
{KEY_38_3B, {0x13, 0xE6, 0x2E, 0x5D, 0x6F, 0xB1, 0x65, 0x6B, 0x24, 0xDD, 0x33, 0x4B, 0xF1, 0x54, 0x68, 0xC3}},
// dev
{KEY_38_3B, {0xD1, 0xF8, 0xB1, 0x55, 0x29, 0xD6, 0x4B, 0x64, 0xE0, 0xD7, 0x4F, 0x98, 0xB5, 0xF8, 0xCC, 0x24}},
},
{
// retail
{KEY_3C, {0x85, 0xDB, 0x63, 0x07, 0x7C, 0x50, 0x11, 0x6B, 0x94, 0x90, 0xD4, 0xFA, 0xD6, 0x1A, 0xB2, 0x41}},
// dev
{KEY_3C, {0x82, 0x22, 0x6A, 0x9B, 0x55, 0x4A, 0x47, 0xEB, 0xA3, 0x8B, 0xD4, 0x04, 0x98, 0xED, 0x3F, 0x38}},
},
{
// retail
{KEY_3D, {0xA3, 0x08, 0xEB, 0x30, 0x64, 0x11, 0x42, 0x13, 0xA6, 0x0C, 0x56, 0x15, 0x8F, 0x5C, 0x49, 0x63}},
// dev
{KEY_3D, {0xE3, 0x03, 0x88, 0x02, 0xDE, 0x96, 0x9E, 0x1D, 0x5E, 0x5D, 0xB5, 0xB9, 0xF4, 0x54, 0x41, 0xBF}},
},
{
// retail
{KEY_3E, {0x07, 0x01, 0x98, 0xA2, 0xE4, 0x19, 0x4B, 0x9D, 0x02, 0x27, 0x92, 0x18, 0x35, 0xE4, 0x10, 0x6F}},
// dev
{KEY_3E, {0xE9, 0x86, 0xEC, 0xDF, 0x2C, 0x45, 0x07, 0x30, 0xB4, 0x17, 0x61, 0xE0, 0x37, 0x42, 0x58, 0x6D}},
},
{
// retail
{KEY_3F, {0x8E, 0xB7, 0x46, 0xF5, 0x18, 0xF7, 0xA8, 0xD8, 0x0E, 0x06, 0x91, 0xC8, 0x64, 0x1A, 0x56, 0xC5}},
// dev
{KEY_3F, {0xE5, 0x88, 0x3F, 0x09, 0xC9, 0x81, 0x17, 0x9B, 0x76, 0x8C, 0xB1, 0x33, 0x2C, 0xC6, 0xBB, 0xAC}},
},
};
// 0x0C-0x0F
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x0C + i, default_brom_static_key[KEY_0C_0F][isDev].key));
}
// 0x10-0x13
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x10 + i, default_brom_static_key[KEY_10_13][isDev].key));
}
// 0x14
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x14, default_brom_static_key[KEY_14][isDev].key));
// 0x15
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x15, default_brom_static_key[KEY_15][isDev].key));
// 0x16
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x16, default_brom_static_key[KEY_16][isDev].key));
// 0x17
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x17, default_brom_static_key[KEY_17][isDev].key));
// 0x18-0x1B
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x18 + i, default_brom_static_key[KEY_18_1B][isDev].key));
}
// 0x1C-0x1F
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x1C + i, default_brom_static_key[KEY_1C_1F][isDev].key));
}
// 0x20-0x23
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x20 + i, default_brom_static_key[KEY_20_23][isDev].key));
}
// 0x24-0x27
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x24 + i, default_brom_static_key[KEY_24_27][isDev].key));
}
// 0x28
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x28, default_brom_static_key[KEY_28][isDev].key));
// 0x29
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x29, default_brom_static_key[KEY_29][isDev].key));
// 0x2A
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x2A, default_brom_static_key[KEY_2A][isDev].key));
// 0x2B
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x2B, default_brom_static_key[KEY_2B][isDev].key));
// 0x2C-0x2F
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x2C + i, default_brom_static_key[KEY_2C_2F][isDev].key));
}
// 0x30-0x33
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x30 + i, default_brom_static_key[KEY_30_33][isDev].key));
}
// 0x34-0x37
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x34 + i, default_brom_static_key[KEY_34_37][isDev].key));
}
// 0x38-0x3B
for (size_t i = 0; i < 4; i++)
{
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x38 + i, default_brom_static_key[KEY_38_3B][isDev].key));
}
// 0x3C
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3C, default_brom_static_key[KEY_3C][isDev].key));
// 0x3D
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3D, default_brom_static_key[KEY_3D][isDev].key));
// 0x3E
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3E, default_brom_static_key[KEY_3E][isDev].key));
// 0x3F
brom_static_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(0x3F, default_brom_static_key[KEY_3F][isDev].key));
// Add Firmware AES keys
static const size_t kFirmwareAesKeyNum = 3;
const DefaultAesKey default_firmware_key[kFirmwareAesKeyNum][2] =
{
{
// retail
{FIRM_NGC_KEY, {0x07, 0x55, 0x0C, 0x97, 0x0C, 0x3D, 0xBD, 0x9E, 0xDD, 0xA9, 0xFB, 0x5D, 0x4C, 0x7F, 0xB7, 0x13}},
// dev
{FIRM_NGC_KEY, {0x4D, 0xAD, 0x21, 0x24, 0xC2, 0xD3, 0x29, 0x73, 0x10, 0x0F, 0xBF, 0xBD, 0x16, 0x04, 0xC6, 0xF1}},
},
{
// retail
{FIRM_NOR_KEY, {0x19, 0x2F, 0x08, 0x61, 0x03, 0x07, 0x35, 0xF2, 0x60, 0xB0, 0x64, 0x0B, 0xC0, 0x74, 0xEF, 0x93}},
// dev
{FIRM_NOR_KEY, {0xC0, 0x85, 0xC3, 0x07, 0x26, 0xFC, 0x4E, 0xD9, 0x1D, 0xD9, 0x75, 0x67, 0xC0, 0x41, 0xA9, 0x9C}},
},
{
// retail
{FIRM_SD_KEY, {0x85, 0xDB, 0x63, 0x07, 0x7C, 0x50, 0x11, 0x6B, 0x94, 0x90, 0xD4, 0xFA, 0xD6, 0x1A, 0xB2, 0x41}},
// dev
{FIRM_SD_KEY, {0x82, 0x22, 0x6A, 0x9B, 0x55, 0x4A, 0x47, 0xEB, 0xA3, 0x8B, 0xD4, 0x04, 0x98, 0xED, 0x3F, 0x38}},
},
};
for (size_t i = 0; i < kFirmwareAesKeyNum; i++)
{
firmware_key.insert(std::pair<byte_t, KeyBag::Aes128Key>(default_firmware_key[i][isDev].key_index, default_firmware_key[i][isDev].key));
}
}
void ctrtool::KeyBagInitializer::addStaticRsaKeys(bool isDev)
{
auto public_exp = tc::ByteData({0x01,0x00,0x01});
struct DefaultRsaKey
{
byte_t key_index;
tc::crypto::RsaKey key;
};
static const size_t kDefaultRsaKeyNum = 9;
DefaultRsaKey default_rsa_keys[kDefaultRsaKeyNum][2] =
{
{
// retail
{RSAKEY_FIRM_NAND, {tc::cli::FormatUtil::hexStringToBytes("DECFB6FC3D33E955FDAC90E88817B003A16B9AAB72707932A2A08CBB336FB076962EC4E92ED88F92C02D4D410FDE451B253CBE376B458221E64DB1238182B68162B730F4604BC7F7F0170CB575887793526370F00BC6734341EEE4F071ECC8C132C4DCA9991D31B8A47EDD19040F02A81AAFB3489A29295E4984E09411D17EABB2C0447EA11B5E9D0D1AF9029A2E53032D48967C2CA6D7ACF1ED2B18BB01CB13B9ACA6EE5500377C696162890154779F075D26343AA949A5AFF25E0651B71CE0DEDA5C0B9F98C215FDBAD8A99900ABA48E4A169D662AE85664B2B6C093AF4D38A0165CE4BD62C2466BC95A594A7258FDB2CC36873085E8A1045BE0179BD0EC9B"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_FIRM_NAND, {tc::cli::FormatUtil::hexStringToBytes("C11E84E4DAD7EDC8A5D9CB0BE93B9DBC1569F795F68FB7912419BE8FFEBBC11D09F0E378A20CC00B8ECDBA0E694C614ABD13EAE223E40B621F9B631B3134B71273646B4E60410A3D5486FA49EC2D77AD1DBC48E7FB07EF48C3BEC3D6E15B3DCD5E6B46086A3A0EAE254944A5400D945318A48F578EF4CDB4D34EE7159B11958500C49294B8BEFECBB750A0BE8AF5FDC80E4BA941136248CD1AF3C86BA5A0DFF19D3DFDE4D17C1F000D99726BA3057F8638BE4D5EAB93DFF3EEA19F2250A87F31AA2B03719B14C43088426FD52C7B03643B14EC4633CC2C959F5C7B83C567DD7CA12DD6EC170B23C7B19A72BA78CB39685CB6B461E198CF1D69F9D7B0A05E9CEB"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_FIRM_RECOVERY, {tc::cli::FormatUtil::hexStringToBytes("D29FF5678F4BE3760C5FB1A6F846B4AE7D95CC50ED6515EE5BBDB9F0BCAB7E2B736B7EE1F623C473C7C3F84E680486DBBEBC8084EC5413CA21633CF4437636C05921F5D25953F0C1B38B07C94F03108BE9F9F791F67F15FD9B74F36220DAC235733BCCB3405B41040E41FF440DA9C5F76F9A64F210C6FC7A5BA84486575DB8B3A9949C536CCD81B446A917E3788D93113B638C253A5CF9FE94E6064DC828C83A124D2650D49B8CD9974D64B23D8DC4D3C6B28C1A07F074274ED34E208DB943ADD13A721B2C59745852A041506E1CB64C9D65275528AB0C931289FBCC8799C11C6AE7FC28659C7D4730FE8F605F7A1D6828DCF01D39A148C376BE8869B769FCE5"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_FIRM_RECOVERY, {tc::cli::FormatUtil::hexStringToBytes("C0C6E3B52B1EBA2233CA36F0AA3FE3EF69AC8191CB71FA20798E15F88F8725AD92F200D9128B35BEE7A989448C794B929A76DA20B0EE4596C01F51C618F4DA71BB0209FE5577599C68AA6D9A8DF93A921B7F9B9F4B318917225F29EEE3A8A018B33B8822057A324F56037FC90FCC68D0C6E753BE48F5189464E0AC91CAFF95101BA52A73AC196862DEA7701FD00C071EB176F3E6436531B5CFAC0594D40F3B9CBC0F10E30DA06484692A382F3B70135BFB9C37FEB3CB27AC291D4694442AD351E2C3CDE94C21CDE9BB0BB8850CA67F5F9DC930425CAB834471AEB418EAF9EE52EE86249270220AECDFBF3978F0FB024C89555E0EB66FA9A067B1DE847CE2200B"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_NCSD_NAND, {tc::cli::FormatUtil::hexStringToBytes("AA948C55BE3CAAFD5B53E5A9F583EB6D02EE4F72365A81CAFAE0E0D95869C5E65DFE389CA16D7AA00DA999341614B88FDDD8C0DB82E929500B6C9C2728AA3B2C9AEE1A5C3080BE97FDFCF55EBC75A9CF06FE7915F43D7A6B50B553B0A7DF112632FAB970C688F0AF36BE9101788D2C13313D28AE17AF6A80114CA6E06092D82C8061FB2814A4938556FF80B7D1AB4DFC2587F81A3E4EB9CD66EF322AA3D3DCDC3C04B94D6A4B47AB43941BEA092ED688EDF845F14163606AFBD881E7A7473656D873034B28E13D120181DA07BC6763476F2D3A52D19A44EF5960F6FB027E0103A4E0F9202791D4F90F867E56015DFCD42D8C913DD90D1565E65614B1E45C6831"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_NCSD_NAND, {tc::cli::FormatUtil::hexStringToBytes("C767DA7B195440553720B382CF7496B9AE884AF5711CBA4E1AEC6D62078DDBAA4B594A3D63CF6CC0BF8DFB32430CCE7C0EE24FDF049F3C650A0EA7A8E91AC44414CF845E1C7B9EDADD6C076B7CC4412841A35B8F4A11C126F8A23C12F23DF88D71D478363DBDF6479B8D5BBBBA04A8A24D274881AB2F2121E9049F5706488F6565D7EA2BC6BDFC529EDCE1C385FC0C66CD1B4D2D882AB431F0665B9F0E86CA44529A8160095D54417D16C623A08DA1DC3FBDAE705D862F025B033591B8F4D7A7928DCE3556FD48A54C0A47E440BD31E224AE86A7BDC94C0E4B124B5AC8F6F08F84FAD15C674D0CE39E706119C108F01EFEA58B63A99C5BB9949782EE0DE71653"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_CFA_CCI, {tc::cli::FormatUtil::hexStringToBytes("FBDEB82B40930FF6B19A08061B86FED0DF1079173D8CE27ACE8F2345B90A6DED300EC1A892C4BD1ACEA7AC77AA47E5204A4491DF1CFE8628122D66DFBEAD9661EDF2F7417B57886B241E7DECBE986565366599A9FE24678599EE2AAEEEB1811A22E36D756E21BCEF115C61AF0C3000B6A223EDFE7015DA52E1E62DCE34E8AA4CF1D6675657D3DBC090496F4573934E303070F5C98F3125F2C2E7337F4EB6F52ADF2000E579B2D0F917F77E1690400057914478EF1CE08509DAF4147E4BD735D687548F2AB5A76F50D0F7D1F119C9AC227E0511F5F26DEE9227575FE5150D2768BF52657473A6586D7918AC31DDDD808B7524E117E195251629AB6969C828EE5D"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_CFA_CCI, {tc::cli::FormatUtil::hexStringToBytes("B90CC4C678F86E300528C1CBD2CFA7805C574D169CAFA6CD01BB8333AD03BB0663D817F5E3DFDA0D3B860EA2804794446FD9977E786AC39393EF02FC229F80778C70921C43B1374C76E0573BAB89FFEFE5BB3EAB9139B8D9660B64289192E9D0B3DFD14BC173B53F56A04010FE152B1FA27ADE31B02640C357FD35CBF0FAFFFB6FDBCD341D512D2D8118FF0C0851D5B44B5616029F4E6ADF066ECB7285E92E43A208780C389C19BD7B747468C42DC1359E653BD899041C8B938E7E927CBBDD60ECE7FE0E9D4F3646E6F15C9470EE675F362B70448DCA09B95867D29FAD1F135474ADA6844428F3DE7E4C202BC5E912E95EFB8D77A9A4D20D3C3824BEF58AB5F5"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_ACCESSDESC, {tc::cli::FormatUtil::hexStringToBytes("B1E3E35F013980D156789DB706F71DBF3E2276EDF95DA236B630610596D300B9EDF1D7E01DA04FB7CF5A198775498840EDE36F7C904A644598D704B95A6B45AA7E94C0B3B7DB7B665920B708E2F383A37FE32021A0EBB7280FF32B15A4C9D0AB8939997E765F9E4D1E01228D74A6EB9AA39D45E510616E20FD2375C0C50503C54C024F544B5708B446C32CF1F9526CCD1455A855926DE24A4146EB08C5F3B48D0D5E21EAAF4D274DDE779397E2C76B661FDB2D6EA95F6114177B2B665AB50189F2237525259C869A89FF641D5BCED77E3F2DA8DAB55AC55F5920B0ED1C91FFA327B88ECF8215E549EFE458E15F8F53B9332A5624AAA1D36E471A634419B38EA5"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_ACCESSDESC, {tc::cli::FormatUtil::hexStringToBytes("F43C4582FBF8905D07029F2A988B63B7D38F3CE2E0E093BFDF32434DBEF4D17A3A4E5431D773AE994CC41F3C3EF05705A38A455460D88FD91D680D0E2EEFC8E83DC919F3731E2DDA77883ECA5E25704BF77095835424E0C31A75DF613DD142EC351B38D6C1F67E182A8485DD57741F0A2EF6B294A23EE9A1D009F73A998005AF5755EF52FA243E7FD47C41447B067FB95B2E8E96AE46124D6421E50F85CCEB92E5F0F5A742273BECF8E781756F630A8B0D773851E66633BA79DC2F2C8FC32806BB039CDBD1640A66F0F8C12A491D0C6E35BBEAB35C0DE9957C67BE6577EC07C023050A724886E99EFC2515E7C82165E01BD5D50ED31154BB2978BF2A3C3BB6B1"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_CRR, {tc::cli::FormatUtil::hexStringToBytes("D00E6756858ABCF213D87FBB29D192349EE42743D1111DFE9593C4783F4A2D2AB1CA6FB0BA32A2242E2BE5726EF0B49768CCDA171514F074D435B4EE04D89CA9ECDBCB29E895F257BABD65473D6CE50DE5103EB5C37302AEC893777CC523220171CCC60BEACAA0FED4CDA949655A3B0D5BB7B2EA329A43856CCA045C900D9E34B9EF802B2E6D96BC31157E1401C000E0197A2E17E58BA37AD2C247779A8B832E9866A1A1020F0299E7D79FDCC852A22823081B87FFE4BA90025015C720A0AB76607D1FC1BF339D66246A803BF4A27F15B10C36EF44E8D036949A7484488789499A0B7E10F27355623D68AC9E5FAE1D54F8BA628E29BC49E7FDEEBD3D7BCCF8BF"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_CRR, {tc::cli::FormatUtil::hexStringToBytes("C5F709805FDADCBD460752AA6DCD72B2500977478A4F092AE291B45F04975175C9196F95BB23147D34DF777807E8D11011AFCE02B873A9FB64B76587E567DD673775FD0FE297BC79A8CCF9FA18B2624DF7536B9C0E1AAB90E65286C81C922C6153A901DA4393D0422EDDD54C8C4CE89156ECEE12700B64F00AD6AFF860C2A8267AC8BA559AA144FD094726A0C1999EF124DFA3C2BBFF0745D9D6A4C0FF6C6C787B6D708C7444B095E6C6665E7EBE71C5913473A7D44C0DFCA921499492A16A4D30A3D69F6C60400CEEEBF89922E16FFC3C9623AA1134004EFC2D604145E35D7806B1F1C307B9D347D0F18C26331F6B46DDF3E38464A7F15311E1534E99DBAC53"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_SECUREINFO, {tc::cli::FormatUtil::hexStringToBytes("B1791A6D1EADD429BA89A1CD433630174BC68730C5E70560197B50D8C4546710A6E8A101BC2CEB0376F005C70CE0B6D6DFFD26DF33468BDBB2391E7EC01AA1A5A091E807DA378676BA390A25429D5961E161D40485A74BB20186BEB11A3572C1C2EA28AB7A1015325C9E712B7DF965EAE6C6FB8BAED76C2A94A6C5ECE40EAF987E06F20F884FD20635A476E9F70ABA5C5B1461520054044593E468270435355AAD5809D1193F5A0728D6DB6B551F77945DC3BE6FAE5BCC0863E476DFA29B36EA853403E616EAA905E07F3A3E7E7077CF166A61D17E4D354C744485D4F67B0EEE32F1C2D5790248E9621A33BAA39B02B02294057FF6B43888E301E55A237C9C0B"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_SECUREINFO, {tc::cli::FormatUtil::hexStringToBytes("B1AA6C553CA84D833C2E9756B52BD3701D0FD4D1EEF171F4FD95961D52BF7563B89D2FF5F815E40A76E20F551163E9E98568415A283122E199DEEC771712C678DA0BB4DD50F30C615FA57DEA74D71D1187BFEBC333D7350EDD45981BEF6FB973E8359CE5B0C8FF5C429BA790AEF9B7625604B1B0B244D68634E62F794D9CAFB59A3BFAC88103966F9BDC878B323C37EBCD21C8B9276FFCC84702FF87D1D02F64D436D48501AD70F3A2B10D13EF5594A0238171F94AD20158906013FB6DB6183831DF1144B59649A35308B264C1EF119E1D17179A8744173A73A2F7D9961A79E1F9866EEE6FBBD2DCCF3B0DC4E276D1D0C03798BEC1BCD9646FC4CB46BB5FF555"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_LOCALFRIENDCODESEED, {tc::cli::FormatUtil::hexStringToBytes("A3759A3546CFA7FE30EC55A1B64E08E9449D0C72FCD191FD610A288975BCE6A9B21556E9C7670255ADFC3CEE5EDB78259A4B221B71E7E9515B2A6793B21868CE5E5E12FFD86806AF318D56F9549902346A17E7837496A05AAF6EFDE6BED686AAFD7A65A8EBE11C983A15C17AB540C23D9B7CFDD463C5E6DEB77824C629473335B2E937E054EE9FA53DD793CA3EAE4DB60F5A11E70CDFBA03B21E2B31B65906DB5F940BF76E74CAD4AB55D940058F10FE06050C81BB422190BA4F5C5382E1E10FBC949F60695D1303AAE2E0C108424C200B9BAA552D55276E24E5D60457588FF75F0CEC819F6D2D28F31055F83B7662D4E4A69369B5DA6B4023AF07EB9CBFA9C9"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_LOCALFRIENDCODESEED, {tc::cli::FormatUtil::hexStringToBytes("985992B9651768F5E79F6AE500CC57418E6B8DF9D4409CEAFD9627C425D616C2BC31CC23206B0A494931AA567E6EAE1C550663A8721DEB16C51E0023E0BABD26166519CEFEDB7F4299CEE5CECCB2C518957E4BDBB6567D7D73ED9CD594CE265D8BCA6635CB60CAA3C1B652212FF4FF45C73BEBF4CDB6463D0761D661C349AFBCF55BAC71CD0668462FD12E82C23FEB57CF622EE7E2721E73707AF4F7B01B8ADFC28D59C2D440CCF14150D391A1F95D9D45BC5A74641E22CE10D62CE35C7DD2B0EBC516645C9ADC6093A44E7500C1BE16E9D7D35FB869E62C143851B31515577FEAFE7450E36D6027AE8C9CAACABE78EAE4A48A655B39650B7F0BDBC073E091BB"), tc::ByteData(), public_exp}},
},
{
// retail
{RSAKEY_DSP, {tc::cli::FormatUtil::hexStringToBytes("CB6537CA75D0AC0085B52E5C93491BA09B7BAE24D6B3252C707F513439463B55C5DBFB97422A04D15C1363E4EBF4AF3ABFF447D09D25C819F99DE9632FA2B88186A7A1F4D83456228A6BCAA8B30F56D95F070C98139D1C3E59811C33C6AA707E41B8B9AAA0B41251C61E233876A208C332413B57ABCAD004CBCAD4A8EEA93CED2BE1E61196B7C829C93C70BBCD7955A20258CDD17D6476C7A98DA5CCF339E5B51204BE6AF85237EAAC4CF63863F618637A2760E0B78565E9483B107F1D7917498A211CEC01AAA4232FC50AEFE74CE2056A09ED720D3F877B3EF9A30003191751E969EC10E7E0891C2B46611816858458E279F6F4A7286246E94958DCF94765D9"), tc::ByteData(), public_exp}},
// dev
{RSAKEY_DSP, {tc::cli::FormatUtil::hexStringToBytes("CB6537CA75D0AC0085B52E5C93491BA09B7BAE24D6B3252C707F513439463B55C5DBFB97422A04D15C1363E4EBF4AF3ABFF447D09D25C819F99DE9632FA2B88186A7A1F4D83456228A6BCAA8B30F56D95F070C98139D1C3E59811C33C6AA707E41B8B9AAA0B41251C61E233876A208C332413B57ABCAD004CBCAD4A8EEA93CED2BE1E61196B7C829C93C70BBCD7955A20258CDD17D6476C7A98DA5CCF339E5B51204BE6AF85237EAAC4CF63863F618637A2760E0B78565E9483B107F1D7917498A211CEC01AAA4232FC50AEFE74CE2056A09ED720D3F877B3EF9A30003191751E969EC10E7E0891C2B46611816858458E279F6F4A7286246E94958DCF94765D9"), tc::ByteData(), public_exp}},
},
};
for (size_t i = 0; i < kDefaultRsaKeyNum; i++)
{
rsa_key.insert(std::pair<byte_t, tc::crypto::RsaKey>(default_rsa_keys[i][isDev].key_index, default_rsa_keys[i][isDev].key));
}
struct BroadonRsaSignerProfile
{
std::string issuer;
KeyBag::BroadOnRsaSignerProfile profile;
};
static const size_t kDefaultBroadonProfileNum = 4;
BroadonRsaSignerProfile default_broadon_profile[kDefaultBroadonProfileNum][2] =
{
// root
{
// retail
{"Root", {tc::ByteData(), {tc::cli::FormatUtil::hexStringToBytes("F8246C58BAE7500301FBB7C2EBE0010571DA922378F0514EC0031DD0D21ED3D07EFC852069B5DE9BB951A8BC90A244926D379295AE9436AAA6A302510C7B1DEDD5FB20869D7F3016F6BE65D383A16DB3321B95351890B17002937EE193F57E99A2474E9D3824C7AEE38541F567E7518C7A0E38E7EBAF41191BCFF17B42A6B4EDE6CE8DE7318F7F5204B3990E226745AFD485B24493008B08C7F6B7E56B02B3E8FE0C9D859CB8B68223B8AB27EE5F6538078B2DB91E2A153E85818072A23B6DD93281054F6FB0F6F5AD283ECA0B7AF35455E03DA7B68326F3EC834AF314048AC6DF20D28508673CAB62A2C7BC131A533E0B66806B1C30664B372331BDC4B0CAD8D11EE7BBD9285548AAEC1F66E821B3C8A0476900C5E688E80CCE3C61D69CBBA137C6604F7A72DD8C7B3E3D51290DAA6A597B081F9D3633A3467A356109ACA7DD7D2E2FB2C1AEB8E20F4892D8B9F8B46F4E3C11F4F47D8B757DFEFEA3899C33595C5EFDEBCBABE8413E3A9A803C69356EB2B2AD5CC4C858455EF5F7B30644B47C64068CDF809F76025A2DB446E03D7CF62F34E702457B02A4CF5D9DD53CA53A7CA629788C67CA08BFECCA43A957AD16C94E1CD875CA107DCE7E0118F0DF6BFEE51DDBD991C26E60CD4858AA592C820075F29F526C917C6FE5403EA7D4A50CEC3B7384DE886E82D2EB4D4E42B5F2B149A81EA7CE7144DC2994CFC44E1F91CBD495"), tc::ByteData(), public_exp}}},
// dev
{"Root", {tc::ByteData(), {tc::cli::FormatUtil::hexStringToBytes("D01FE100D43556B24B56DAE971B5A5D384B93003BE1BBF28A2305B060645467D5B0251D2561A274F9E9F9CEC646150AB3D2AE3366866ACA4BAE81AE3D79AA6B04A8BCBA7E6FB648945EBDFDB85BA091FD7D114B5A3A780E3A22E6ECD87B5A4C6F910E4032208814B0CEEA1A17DF739695F617EF63528DB949637A056037F7B32413895C0A8F1982E1565E38EEDC22E590EE2677B8609F48C2E303FBC405CAC18042F822084E4936803DA7F41349248562B8EE12F78F803246330BC7BE7EE724AF458A472E7AB46A1A7C10C2F18FA07C3DDD89806A11C9CC130B247A33C8D47DE67F29E5577B11C43493D5BBA7634A7E4E71531B7DF5981FE24A114554CBD8F005CE1DB35085CCFC77806B6DE254068A26CB5492D4580438FE1E5A9ED75C5ED451DCE789439CCC3BA28A2312A1B8719EF0F73B713950C02591A7462A607F37C0AA7A18FA943A36D752A5F4192F0136100AA9CB41BBE14BEB1F9FC692FDFA09446DE5A9DDE2CA5F68C1C0C21429287CB2DAAA3D263752F73E09FAF4479D2817429F69800AFDE6B592DC19882BDF581CCABF2CB91029EF35C4CFDBBFF49C1FA1B2FE31DE7A560ECB47EBCFE32425B956F81B69917487E3B789151DB2E78B1FD2EBE7E626B3EA165B4FB00CCB751AF507329C4A3939EA6DD9C50A0E7386B0145796B41AF61F78555944F3BC22DC3BD0D00F8798A42B1AAA08320659AC7395AB4F329"), tc::ByteData(), public_exp}}},
},
// ca
{
// retail
{"Root-CA00000003", {tc::cli::FormatUtil::hexStringToBytes("00010003704138EFBBBDA16A987DD901326D1C9459484C88A2861B91A312587AE70EF6237EC50E1032DC39DDE89A96A8E859D76A98A6E7E36A0CFE352CA893058234FF833FCB3B03811E9F0DC0D9A52F8045B4B2F9411B67A51C44B5EF8CE77BD6D56BA75734A1856DE6D4BED6D3A242C7C8791B3422375E5C779ABF072F7695EFA0F75BCB83789FC30E3FE4CC8392207840638949C7F688565F649B74D63D8D58FFADDA571E9554426B1318FC468983D4C8A5628B06B6FC5D507C13E7A18AC1511EB6D62EA5448F83501447A9AFB3ECC2903C9DD52F922AC9ACDBEF58C6021848D96E208732D3D1D9D9EA440D91621C7A99DB8843C59C1F2E2C7D9B577D512C166D6F7E1AAD4A774A37447E78FE2021E14A95D112A068ADA019F463C7A55685AABB6888B9246483D18B9C806F474918331782344A4B8531334B26303263D9D2EB4F4BB99602B352F6AE4046C69A5E7E8E4A18EF9BC0A2DED61310417012FD824CC116CFB7C4C1F7EC7177A17446CBDE96F3EDD88FCD052F0B888A45FDAF2B631354F40D16E5FA9C2C4EDA98E798D15E6046DC5363F3096B2C607A9D8DD55B1502A6AC7D3CC8D8C575998E7D796910C804C495235057E91ECD2637C9C1845151AC6B9A0490AE3EC6F47740A0DB0BA36D075956CEE7354EA3E9A4F2720B26550C7D394324BC0CB7E9317D8A8661F42191FF10B08256CE3FD25B745E5194906B4D61CB4C2E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001434130303030303030330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007BE8EF6CB279C9E2EEE121C6EAF44FF639F88F078B4B77ED9F9560B0358281B50E55AB721115A177703C7A30FE3AE9EF1C60BC1D974676B23A68CC04B198525BC968F11DE2DB50E4D9E7F071E562DAE2092233E9D363F61DD7C19FF3A4A91E8F6553D471DD7B84B9F1B8CE7335F0F5540563A1EAB83963E09BE901011F99546361287020E9CC0DAB487F140D6626A1836D27111F2068DE4772149151CF69C61BA60EF9D949A0F71F5499F2D39AD28C7005348293C431FFBD33F6BCA60DC7195EA2BCC56D200BAF6D06D09C41DB8DE9C720154CA4832B69C08C69CD3B073A0063602F462D338061A5EA6C915CD5623579C3EB64CE44EF586D14BAAA8834019B3EEBEED3790001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), {tc::cli::FormatUtil::hexStringToBytes("B279C9E2EEE121C6EAF44FF639F88F078B4B77ED9F9560B0358281B50E55AB721115A177703C7A30FE3AE9EF1C60BC1D974676B23A68CC04B198525BC968F11DE2DB50E4D9E7F071E562DAE2092233E9D363F61DD7C19FF3A4A91E8F6553D471DD7B84B9F1B8CE7335F0F5540563A1EAB83963E09BE901011F99546361287020E9CC0DAB487F140D6626A1836D27111F2068DE4772149151CF69C61BA60EF9D949A0F71F5499F2D39AD28C7005348293C431FFBD33F6BCA60DC7195EA2BCC56D200BAF6D06D09C41DB8DE9C720154CA4832B69C08C69CD3B073A0063602F462D338061A5EA6C915CD5623579C3EB64CE44EF586D14BAAA8834019B3EEBEED379"), tc::ByteData(), public_exp}}},
// dev
{"Root-CA00000004", {tc::cli::FormatUtil::hexStringToBytes("000100031949429D1E58A62E7E8B56D1B76AE302FD8B97491F778745F75388C4DD0BEB1DF122FB9642151497764A53CF78151845E42CA8FDE486FD2A4F53F8A1BA008A7485FF73B3BF7E3C980729D0656B693219ADE835EB5FFFFCCB7CBB5E307FE0688B888EF2D2053FB7E791E985FD15EF10D79CCA88D6BB15E8E4714A98EE09BF7B8AF053232B6450E6D5FDFFC20A6D1EA6A23812E1014525D56D4082703B86986959A73CD1A14364D2C2DAEA96B095F76C46E4FF4155465E70EF1ED31053D97011E010CC93E7914013687FA3A802996D1E557B1CCC7A7E8F5865C1742E28E26DEF38A93AB5D82D43ECCCBF0BEF22E1FD57E2864333582FEDEABC012F986DDFC3E9447973470308455BDC57AA170B84427F73A29B48F6DA135F66C745C142A84AFB0E6A5EED85D7B9719936F8CE2B621F395F40DC03BEF8854C1117FF0C128641CC7843B97B4346DB226F6026ACB56C278B8E0EA79A2D65EF798E1078AD80ED4B9604D2F08B2CD64A23A3DB270833B402F80851F35BED3EE4577C6660FBF16D9413E09C917A49D42C6DA375BC27F0230DB98F8973AB027B522CD57EC03D25E8B3FC3494C97FB108FE18C68A4336E46C26B6F280D27E34BE287C3E4687BC9D776B76D928D1B6352EC0347D7294AA9360268D26F5F652064AF240D7D00C7C5EA3C32DE62D9B5C4B4CAB6FD7BD371D57C2166095910E4AD8E9ED181EF761936153892D77000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F74000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014341303030303030303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081122A46C9CC2DC4DF2930E4DF3F8C70A078948775AD5E9AA604C5B4D8EAFF2AA1D214676564EFCA28CC00154554A1A3EA1379E9E6CAACED1593FE88D89AC6B8ACCCAB6E207CEB7CCA29809E2980440662B7D4382A15DA43085745A9AAE59AA05BDB32F66869A2DD4295386C87ECDD3508A2CF60D01E23EC2FE698F470D6001549A2F06759131E534C7006057DEF1D18A83F0AC79CFE80FF5A91F2BED4A0837061190A0329902165403C9A908FB615739F3CE33BF1BAEA16C25BCED7963FACC9D24D9C0AD76FC020B2C4B84C10A741A2CC7D9BAC3AACCCA3529BAC316A9AA75D2A26C7D7D288CBA466C5FE5F454AE679744A90A15772DB3B0E47A49AF031D16DBEAB332B0001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), {tc::cli::FormatUtil::hexStringToBytes("C9CC2DC4DF2930E4DF3F8C70A078948775AD5E9AA604C5B4D8EAFF2AA1D214676564EFCA28CC00154554A1A3EA1379E9E6CAACED1593FE88D89AC6B8ACCCAB6E207CEB7CCA29809E2980440662B7D4382A15DA43085745A9AAE59AA05BDB32F66869A2DD4295386C87ECDD3508A2CF60D01E23EC2FE698F470D6001549A2F06759131E534C7006057DEF1D18A83F0AC79CFE80FF5A91F2BED4A0837061190A0329902165403C9A908FB615739F3CE33BF1BAEA16C25BCED7963FACC9D24D9C0AD76FC020B2C4B84C10A741A2CC7D9BAC3AACCCA3529BAC316A9AA75D2A26C7D7D288CBA466C5FE5F454AE679744A90A15772DB3B0E47A49AF031D16DBEAB332B"), tc::ByteData(), public_exp}}},
},
// tmd
{
// retail
{"Root-CA00000003-CP0000000b", {tc::cli::FormatUtil::hexStringToBytes("000100042EA66C66CFF335797D0497B77A197F9FE51AB5A41375DC73FD9E0B10669B1B9A5B7E8AB28F01B67B6254C14AA1331418F25BA549004C378DD72F0CE63B1F7091AAFE3809B7AC6C2876A61D60516C43A63729162D280BE21BE8E2FE057D8EB6E204242245731AB6FEE30E5335373EEBA970D531BBA2CB222D9684387D5F2A1BF75200CE0656E390CE19135B59E14F0FA5C1281A7386CCD1C8EC3FAD70FBCE74DEEE1FD05F46330B51F9B79E1DDBF4E33F14889D05282924C5F5DC2766EF0627D7EEDC736E67C2E5B93834668072216D1C78B823A072D34FF3ECF9BD11A29AF16C33BD09AFB2D74D534E027C19240D595A68EBB305ACC44AB38AB820C6D426560C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D43413030303030303033000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000143503030303030303062000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000137A080BA689C590FD0B2F0D4F56B632FB934ED0739517B33A79DE040EE92DC31D37C7F73BF04BD3E44E20AB5A6FEAF5984CC1F6062E9A9FE56C3285DC6F25DDD5D0BF9FE2EFE835DF2634ED937FAB0214D104809CF74B860E6B0483F4CD2DAB2A9602BC56F0D6BD946AED6E0BE4F08F26686BD09EF7DB325F82B18F6AF2ED525BFD828B653FEE6ECE400D5A48FFE22D538BB5335B4153342D4335ACF590D0D30AE2043C7F5AD214FC9C0FE6FA40A5C86506CA6369BCEE44A32D9E695CF00B4FD79ADB568D149C2028A14C9D71B850CA365B37F70B657791FC5D728C4E18FD22557C4062D74771533C70179D3DAE8F92B117E45CB332F3B3C2A22E705CFEC66F6DA3772B0001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), {tc::cli::FormatUtil::hexStringToBytes("A689C590FD0B2F0D4F56B632FB934ED0739517B33A79DE040EE92DC31D37C7F73BF04BD3E44E20AB5A6FEAF5984CC1F6062E9A9FE56C3285DC6F25DDD5D0BF9FE2EFE835DF2634ED937FAB0214D104809CF74B860E6B0483F4CD2DAB2A9602BC56F0D6BD946AED6E0BE4F08F26686BD09EF7DB325F82B18F6AF2ED525BFD828B653FEE6ECE400D5A48FFE22D538BB5335B4153342D4335ACF590D0D30AE2043C7F5AD214FC9C0FE6FA40A5C86506CA6369BCEE44A32D9E695CF00B4FD79ADB568D149C2028A14C9D71B850CA365B37F70B657791FC5D728C4E18FD22557C4062D74771533C70179D3DAE8F92B117E45CB332F3B3C2A22E705CFEC66F6DA3772B"), tc::ByteData(), public_exp}}},
// dev
{"Root-CA00000004-CP0000000a", {tc::cli::FormatUtil::hexStringToBytes("000100045005D75E6DDEB8783C81E9EF0D17CD58F59426A3FD6F6990E8F83287122EC25CA14B99242337BA91A75B0F7C59FBF7D1892722C4E6AFC7DEC74A6E007F434A888A8215E8DF2B52ED4200BC69B4DA7FEB746C7A2D96565B45597B8FAEB16BDC76C1C80C47F50DA9C3E1FE28501C26A2D1544BD1604A9E8F322AEF315FEA48226722B7CB372FF35F5E616A5344A585E5A08A2E1777572B7A9AF7D2D8C49CD0A054BF8A9DB49FC660617CB8354E257F68682F94B3CC538C426F88C5485CBEC1D0480474965A7E8259AA9FB66146CE5921C6F0C1751F21917F2496CB0C70157AB7BB3A9F5756565C38922EFDC8F170B9AEA1AE36F55E3526630ABAB2050FF00CDCBB000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014350303030303030306100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018A34D5BAA7F9380289BE89863107AE10C592C2F7CFFBDAADD74F4A2FBACD76F00934206347156D84049729F3E24FA5E19D15B635CD2EF09DE32EE6B6FC8FA328E2E96B99441047D076295DA0D91D80935D0DE8E6BC6AB1427019CFE4996FC9B54794DEBD7C66673A6DD3A77654794EC1C87AA46D978A97DDB11226ED412C2784B218392C710C77419FFAAF60B75D823DD33C3A15BA72D30A5A4D8F80FD673FD26CB29A6EF5039E25F5961846BDA2EC7CBE4384B28FB0DD58E7CAA7D4B373AD781DD73E30993BDBD7E08554A8CA5C9842D7101A22A01B015FB3078B913F4C73FB5A6F1A25E22B002B6E009547F0FBDF0FEA5501D9315F93D830F0F0E3DE23D96E709D9770001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), {tc::cli::FormatUtil::hexStringToBytes("AA7F9380289BE89863107AE10C592C2F7CFFBDAADD74F4A2FBACD76F00934206347156D84049729F3E24FA5E19D15B635CD2EF09DE32EE6B6FC8FA328E2E96B99441047D076295DA0D91D80935D0DE8E6BC6AB1427019CFE4996FC9B54794DEBD7C66673A6DD3A77654794EC1C87AA46D978A97DDB11226ED412C2784B218392C710C77419FFAAF60B75D823DD33C3A15BA72D30A5A4D8F80FD673FD26CB29A6EF5039E25F5961846BDA2EC7CBE4384B28FB0DD58E7CAA7D4B373AD781DD73E30993BDBD7E08554A8CA5C9842D7101A22A01B015FB3078B913F4C73FB5A6F1A25E22B002B6E009547F0FBDF0FEA5501D9315F93D830F0F0E3DE23D96E709D977"), tc::ByteData(), public_exp}}},
},
// tik
{
// retail
{"Root-CA00000003-XS0000000c", {tc::cli::FormatUtil::hexStringToBytes("00010004919EBE464AD0F552CD1B72E7884910CF55A9F02E50789641D896683DC005BD0AEA87079D8AC284C675065F74C8BF37C88044409502A022980BB8AD48383F6D28A79DE39626CCB2B22A0F19E41032F094B39FF0133146DEC8F6C1A9D55CD28D9E1C47B3D11F4F5426C2C780135A2775D3CA679BC7E834F0E0FB58E68860A71330FC95791793C8FBA935A7A6908F229DEE2A0CA6B9B23B12D495A6FE19D0D72648216878605A66538DBF376899905D3445FC5C727A0E13E0E2C8971C9CFA6C60678875732A4E75523D2F562F12AABD1573BF06C94054AEFA81A71417AF9A4A066D0FFC5AD64BAB28B1FF60661F4437D49E1E0D9412EB4BCACF4CFD6A3408847982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D43413030303030303033000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000158533030303030303063000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000137A0894AD505BB6C67E2E5BDD6A3BEC43D910C772E9CC290DA58588B77DCC11680BB3E29F4EABBB26E98C2601985C041BB14378E689181AAD770568E928A2B98167EE3E10D072BEEF1FA22FA2AA3E13F11E1836A92A4281EF70AAF4E462998221C6FBB9BDD017E6AC590494E9CEA9859CEB2D2A4C1766F2C33912C58F14A803E36FCCDCCCDC13FD7AE77C7A78D997E6ACC35557E0D3E9EB64B43C92F4C50D67A602DEB391B06661CD32880BD64912AF1CBCB7162A06F02565D3B0ECE4FCECDDAE8A4934DB8EE67F3017986221155D131C6C3F09AB1945C206AC70C942B36F49A1183BCD78B6E4B47C6C5CAC0F8D62F897C6953DD12F28B70C5B7DF751819A98346526250001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), {tc::cli::FormatUtil::hexStringToBytes("AD505BB6C67E2E5BDD6A3BEC43D910C772E9CC290DA58588B77DCC11680BB3E29F4EABBB26E98C2601985C041BB14378E689181AAD770568E928A2B98167EE3E10D072BEEF1FA22FA2AA3E13F11E1836A92A4281EF70AAF4E462998221C6FBB9BDD017E6AC590494E9CEA9859CEB2D2A4C1766F2C33912C58F14A803E36FCCDCCCDC13FD7AE77C7A78D997E6ACC35557E0D3E9EB64B43C92F4C50D67A602DEB391B06661CD32880BD64912AF1CBCB7162A06F02565D3B0ECE4FCECDDAE8A4934DB8EE67F3017986221155D131C6C3F09AB1945C206AC70C942B36F49A1183BCD78B6E4B47C6C5CAC0F8D62F897C6953DD12F28B70C5B7DF751819A9834652625"), tc::ByteData(), public_exp}}},
// dev
{"Root-CA00000004-XS00000009", {tc::cli::FormatUtil::hexStringToBytes("0001000463805A351A437BA24319BB3A777B7AF35E724B150A06396C5FEC3845B18876268D5EDAE62F14BA02FAD6FC3B2BBE8707638E55BF055AFCFCB347691189DB1CAF4B4376623E30890A9D3BBB3E50BDF7A6C0F7F8BB0DB56ABBC6C350C888BB9DF09BD130646069DD3467A700EBDCF98CB0F7930E81FE98D972458B947E59E2BE4E912D75CA1B8E2EF46D73B16B35B5670D632D51385328191D9DAE8DC661CCEFA4ABE2F3B04C7BE271B5F92CFA55CD888B72CCBE67FADFEF6B533C45D8CBDFB2764146D6C26F2716C507F3F44466A315D277F289DAFDD550CFA49BEACAC97BE5460EED9BFB04A9DA1958D92A208AACC1F48EE914D88AD741D55B9B6422D8AFAEC7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015853303030303030303900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018A347A4C0844CEB7EB0CFF0AEB777698593E4995A954E581738CED681B0BD7709E7F89ADFAD054883F6C3FDDF7B83E00C2681544329EA826C89F0A67442864D3260327DA77A13406659DA3E416B2794034FAA229DD55452DB270A6AA23D19B1661B197DABC70E881791A12AB43C6CCBF5AA7C3ADD36FB35717B20015900D6F69039354131F8C1C0573A35185890B1AD9A0EECE0F47A7DA52748C972AB0D087B62354091142BB11D1AFAF9CD5C1713535271CAE22A78B17F4ACD59D8BA1D7D705F781B9F9D37188ED7CD0D49577469883A6B8E4E1B85DDBE39450589561297599A09A4C82D2FF5CFB47370DB581EB24E776FA47E62DFB705E880425CB87887977F662C5F0001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), {tc::cli::FormatUtil::hexStringToBytes("C0844CEB7EB0CFF0AEB777698593E4995A954E581738CED681B0BD7709E7F89ADFAD054883F6C3FDDF7B83E00C2681544329EA826C89F0A67442864D3260327DA77A13406659DA3E416B2794034FAA229DD55452DB270A6AA23D19B1661B197DABC70E881791A12AB43C6CCBF5AA7C3ADD36FB35717B20015900D6F69039354131F8C1C0573A35185890B1AD9A0EECE0F47A7DA52748C972AB0D087B62354091142BB11D1AFAF9CD5C1713535271CAE22A78B17F4ACD59D8BA1D7D705F781B9F9D37188ED7CD0D49577469883A6B8E4E1B85DDBE39450589561297599A09A4C82D2FF5CFB47370DB581EB24E776FA47E62DFB705E880425CB87887977F662C5F"), tc::ByteData(), public_exp}}},
}
};
for (size_t i = 0; i < kDefaultBroadonProfileNum; i++)
{
broadon_rsa_signer.insert(std::pair<std::string, KeyBag::BroadOnRsaSignerProfile>(default_broadon_profile[i][isDev].issuer, default_broadon_profile[i][isDev].profile));
}
}
void ctrtool::KeyBagInitializer::addSigHaxSignatures(bool isDev)
{
struct SigHaxSignature
{
byte_t key_index;
KeyBag::Rsa2048Signature signature;
};
static const size_t kSigHaxSignatureNum = 3;
SigHaxSignature sighax_signatures[kSigHaxSignatureNum][2] =
{
{
// retail
{RSAKEY_FIRM_NAND, {0xB6, 0x72, 0x45, 0x31, 0xC4, 0x48, 0x65, 0x7A, 0x2A, 0x2E, 0xE3, 0x06,
0x45, 0x7E, 0x35, 0x0A, 0x10, 0xD5, 0x44, 0xB4, 0x28, 0x59, 0xB0, 0xE5,
0xB0, 0xBE, 0xD2, 0x75, 0x34, 0xCC, 0xCC, 0x2A, 0x4D, 0x47, 0xED, 0xEA,
0x60, 0xA7, 0xDD, 0x99, 0x93, 0x99, 0x50, 0xA6, 0x35, 0x7B, 0x1E, 0x35,
0xDF, 0xC7, 0xFA, 0xC7, 0x73, 0xB7, 0xE1, 0x2E, 0x7C, 0x14, 0x81, 0x23,
0x4A, 0xF1, 0x41, 0xB3, 0x1C, 0xF0, 0x8E, 0x9F, 0x62, 0x29, 0x3A, 0xA6,
0xBA, 0xAE, 0x24, 0x6C, 0x15, 0x09, 0x5F, 0x8B, 0x78, 0x40, 0x2A, 0x68,
0x4D, 0x85, 0x2C, 0x68, 0x05, 0x49, 0xFA, 0x5B, 0x3F, 0x14, 0xD9, 0xE8,
0x38, 0xA2, 0xFB, 0x9C, 0x09, 0xA1, 0x5A, 0xBB, 0x40, 0xDC, 0xA2, 0x5E,
0x40, 0xA3, 0xDD, 0xC1, 0xF5, 0x8E, 0x79, 0xCE, 0xC9, 0x01, 0x97, 0x43,
0x63, 0xA9, 0x46, 0xE9, 0x9B, 0x43, 0x46, 0xE8, 0xA3, 0x72, 0xB6, 0xCD,
0x55, 0xA7, 0x07, 0xE1, 0xEA, 0xB9, 0xBE, 0xC0, 0x20, 0x0B, 0x5B, 0xA0,
0xB6, 0x61, 0x23, 0x6A, 0x87, 0x08, 0xD7, 0x04, 0x51, 0x7F, 0x43, 0xC6,
0xC3, 0x8E, 0xE9, 0x56, 0x01, 0x11, 0xE1, 0x40, 0x5E, 0x5E, 0x8E, 0xD3,
0x56, 0xC4, 0x9C, 0x4F, 0xF6, 0x82, 0x3D, 0x12, 0x19, 0xAF, 0xAE, 0xEB,
0x3D, 0xF3, 0xC3, 0x6B, 0x62, 0xBB, 0xA8, 0x8F, 0xC1, 0x5B, 0xA8, 0x64,
0x8F, 0x93, 0x33, 0xFD, 0x9F, 0xC0, 0x92, 0xB8, 0x14, 0x6C, 0x3D, 0x90,
0x8F, 0x73, 0x15, 0x5D, 0x48, 0xBE, 0x89, 0xD7, 0x26, 0x12, 0xE1, 0x8E,
0x4A, 0xA8, 0xEB, 0x9B, 0x7F, 0xD2, 0xA5, 0xF7, 0x32, 0x8C, 0x4E, 0xCB,
0xFB, 0x00, 0x83, 0x83, 0x3C, 0xBD, 0x5C, 0x98, 0x3A, 0x25, 0xCE, 0xB8,
0xB9, 0x41, 0xCC, 0x68, 0xEB, 0x01, 0x7C, 0xE8, 0x7F, 0x5D, 0x79, 0x3A,
0xCA, 0x09, 0xAC, 0xF7}},
// dev
{RSAKEY_FIRM_NAND, {0x88, 0x69, 0x7C, 0xDC, 0xA9, 0xD1, 0xEA, 0x31, 0x82, 0x56, 0xFC, 0xD9,
0xCE, 0xD4, 0x29, 0x64, 0xC1, 0xE9, 0x8A, 0xBC, 0x64, 0x86, 0xB2, 0xF1,
0x28, 0xEC, 0x02, 0xE7, 0x1C, 0x5A, 0xE3, 0x5D, 0x63, 0xD3, 0xBF, 0x12,
0x46, 0x13, 0x40, 0x81, 0xAF, 0x68, 0x75, 0x47, 0x87, 0xFC, 0xB9, 0x22,
0x57, 0x1D, 0x7F, 0x61, 0xA3, 0x0D, 0xE4, 0xFC, 0xFA, 0x82, 0x93, 0xA9,
0xDA, 0x51, 0x23, 0x96, 0xF1, 0x31, 0x9A, 0x36, 0x49, 0x68, 0x46, 0x4C,
0xA9, 0x80, 0x6E, 0x0A, 0x52, 0x56, 0x74, 0x86, 0x75, 0x4C, 0xDD, 0xD4,
0xC3, 0xA6, 0x2B, 0xDC, 0xE2, 0x55, 0xE0, 0xDE, 0xEC, 0x23, 0x01, 0x29,
0xC1, 0xBA, 0xE1, 0xAE, 0x95, 0xD7, 0x86, 0x86, 0x56, 0x37, 0xC1, 0xE6,
0x5F, 0xAE, 0x83, 0xED, 0xF8, 0xE7, 0xB0, 0x7D, 0x17, 0xC0, 0xAA, 0xDA,
0x8F, 0x05, 0x5B, 0x64, 0x0D, 0x45, 0xAB, 0x0B, 0xAC, 0x76, 0xFF, 0x7B,
0x34, 0x39, 0xF5, 0xA4, 0xBF, 0xE8, 0xF7, 0xE0, 0xE1, 0x03, 0xBC, 0xE9,
0x95, 0xFA, 0xD9, 0x13, 0xFB, 0x72, 0x9D, 0x3D, 0x03, 0x0B, 0x26, 0x44,
0xEC, 0x48, 0x39, 0x64, 0x24, 0xE0, 0x56, 0x3A, 0x1B, 0x3E, 0x6A, 0x1F,
0x68, 0x0B, 0x39, 0xFC, 0x14, 0x61, 0x88, 0x6F, 0xA7, 0xA6, 0x0B, 0x6B,
0x56, 0xC5, 0xA8, 0x46, 0x55, 0x4A, 0xE6, 0x48, 0xFC, 0x46, 0xE3, 0x0E,
0x24, 0x67, 0x8F, 0xAF, 0x1D, 0xC3, 0xCE, 0xB1, 0x0C, 0x2A, 0x95, 0x0F,
0x4F, 0xFA, 0x20, 0x83, 0x23, 0x4E, 0xD8, 0xDC, 0xC3, 0x58, 0x7A, 0x6D,
0x75, 0x1A, 0x7E, 0x9A, 0xFA, 0x06, 0x15, 0x69, 0x55, 0x08, 0x4F, 0xF2,
0x72, 0x5B, 0x69, 0x8E, 0xB1, 0x74, 0x54, 0xD9, 0xB0, 0x2B, 0x6B, 0x76,
0xBE, 0x47, 0xAB, 0xBE, 0x20, 0x62, 0x94, 0x36, 0x69, 0x87, 0xA4, 0xCA,
0xB4, 0x2C, 0xBD, 0x0B}},
},
{
// retail
{RSAKEY_FIRM_RECOVERY, {0x37, 0xE9, 0x6B, 0x10, 0xBA, 0xF2, 0x8C, 0x74, 0xA7, 0x10, 0xEF, 0x35,
0x82, 0x4C, 0x93, 0xF5, 0xFB, 0xB3, 0x41, 0xCE, 0xE4, 0xFB, 0x44, 0x6C,
0xE4, 0xD2, 0x90, 0xAB, 0xFC, 0xEF, 0xAC, 0xB0, 0x63, 0xA9, 0xB5, 0x5B,
0x3E, 0x8A, 0x65, 0x51, 0x1D, 0x90, 0x0C, 0x5A, 0x6E, 0x94, 0x03, 0xAA,
0xB5, 0x94, 0x3C, 0xEF, 0x3A, 0x1E, 0x88, 0x2B, 0x77, 0xD2, 0x34, 0x79,
0x42, 0xB9, 0xE9, 0xEB, 0x0D, 0x75, 0x66, 0x37, 0x0F, 0x0C, 0xB7, 0x31,
0x0C, 0x38, 0xCB, 0x4A, 0xC9, 0x40, 0xD1, 0xA6, 0xBB, 0x47, 0x6B, 0xCC,
0x2C, 0x48, 0x7D, 0x1C, 0x53, 0x21, 0x20, 0xF1, 0xD2, 0xA3, 0x7D, 0xDB,
0x3E, 0x36, 0xF8, 0xA2, 0x94, 0x5B, 0xD8, 0xB1, 0x6F, 0xB3, 0x54, 0x98,
0x03, 0x84, 0x99, 0x8E, 0xCC, 0x38, 0x0C, 0xD5, 0xCF, 0x85, 0x30, 0xF1,
0xDA, 0xD2, 0xFD, 0x74, 0xBA, 0x35, 0xAC, 0xB9, 0xC9, 0xDA, 0x2C, 0x13,
0x1C, 0xB2, 0x95, 0x73, 0x6A, 0xE7, 0xEF, 0xA0, 0xD2, 0x68, 0xEE, 0x01,
0x87, 0x2E, 0xF0, 0x33, 0x05, 0x8A, 0xBA, 0x07, 0xB5, 0xC6, 0x84, 0xEA,
0xD6, 0x0D, 0x76, 0xEA, 0x84, 0xA1, 0x8D, 0x86, 0x63, 0x07, 0xAA, 0xAA,
0xB7, 0x64, 0x78, 0x6E, 0x39, 0x6F, 0x2F, 0x8B, 0x63, 0x0E, 0x60, 0xE3,
0x0E, 0x3F, 0x1C, 0xD8, 0xA6, 0x7D, 0x02, 0xF0, 0xA8, 0x81, 0x52, 0xDE,
0x7A, 0x9E, 0x0D, 0xD5, 0xE6, 0x4A, 0xB7, 0x59, 0x3A, 0x37, 0x01, 0xE4,
0x84, 0x6B, 0x6F, 0x33, 0x8D, 0x22, 0xFD, 0x45, 0x5D, 0x45, 0xDF, 0x21,
0x2C, 0x55, 0x77, 0x26, 0x6A, 0xA8, 0xC3, 0x67, 0xAE, 0x6E, 0x4C, 0xE8,
0x9D, 0xF4, 0x16, 0x91, 0xBF, 0x1F, 0x7F, 0xE5, 0x8F, 0x22, 0x61, 0xF5,
0xD2, 0x51, 0xDF, 0x36, 0xDE, 0x9F, 0x5A, 0xF1, 0xF3, 0x68, 0xE6, 0x50,
0xD5, 0x76, 0x81, 0x0B}},
// dev
{RSAKEY_FIRM_RECOVERY, {0x18, 0x72, 0x2B, 0xC7, 0x6D, 0xC3, 0x60, 0x2E, 0x2C, 0x01, 0x71, 0xF3,
0xBC, 0xA1, 0x2A, 0xB4, 0x0E, 0xA6, 0xD1, 0x12, 0xAE, 0xFB, 0xEC, 0xF4,
0xBE, 0x7A, 0x2A, 0x58, 0xFF, 0x75, 0x90, 0x58, 0xA9, 0x3C, 0x95, 0xCD,
0xA9, 0xB3, 0xB6, 0x76, 0xD0, 0x9A, 0x4E, 0x4C, 0x9E, 0x84, 0x2E, 0x5C,
0x68, 0x22, 0x9A, 0x6A, 0x9D, 0x77, 0xFA, 0xC7, 0x64, 0x45, 0xE7, 0x8E,
0xB5, 0xB3, 0x63, 0xF8, 0xC6, 0x6B, 0x16, 0x6B, 0xE6, 0x5A, 0xFA, 0xE4,
0x0A, 0x14, 0x85, 0xA3, 0x64, 0xC2, 0xC1, 0x3B, 0x85, 0x5C, 0xEE, 0xDE,
0x3D, 0xFE, 0xAC, 0xEC, 0x68, 0xDD, 0x6B, 0x86, 0x87, 0xDD, 0x6D, 0xF8,
0xB6, 0xD3, 0x21, 0x3F, 0x72, 0x25, 0x2E, 0x7C, 0x03, 0xC0, 0x27, 0xEE,
0x60, 0x79, 0xF9, 0xC5, 0xE0, 0x29, 0x0E, 0x5D, 0xB8, 0xCA, 0x0B, 0xBC,
0xF3, 0x0F, 0xCA, 0xD7, 0x2E, 0xB6, 0x37, 0xA1, 0x70, 0xC4, 0xA2, 0xF4,
0x1D, 0x96, 0xBF, 0x7D, 0x51, 0x7A, 0x2F, 0x4F, 0x33, 0x59, 0x30, 0xDC,
0x5E, 0x97, 0x92, 0xD7, 0x8E, 0xDF, 0xB5, 0x1D, 0xC7, 0x9A, 0xD9, 0xD7,
0xA4, 0xE7, 0xF1, 0xED, 0x4D, 0x5A, 0x5C, 0x62, 0x1B, 0x62, 0x45, 0xA7,
0xF1, 0x65, 0x22, 0x56, 0x01, 0x1D, 0xC3, 0x2C, 0x49, 0xB9, 0x55, 0x30,
0x4A, 0x42, 0x30, 0x09, 0xE2, 0xB7, 0x80, 0x72, 0xCE, 0xBC, 0x12, 0xB3,
0x85, 0xB7, 0x2F, 0x92, 0x6F, 0x19, 0x31, 0x8D, 0x64, 0x07, 0x5F, 0x09,
0x27, 0x8F, 0xBA, 0x84, 0x48, 0xFD, 0x24, 0x84, 0xB8, 0x26, 0x54, 0xA5,
0x5D, 0x06, 0x45, 0x42, 0xA8, 0xF5, 0xD9, 0xF9, 0x82, 0x8C, 0xDA, 0x5E,
0x60, 0xD3, 0x1A, 0x40, 0xCF, 0x8E, 0xF1, 0x8D, 0x02, 0x73, 0x10, 0xDA,
0x4F, 0x80, 0x79, 0x88, 0xBC, 0x75, 0x3C, 0x1E, 0xB3, 0xB3, 0xFC, 0x06,
0x20, 0x7E, 0x84, 0xDE}},
},
{
// retail
{RSAKEY_NCSD_NAND, {0x6C, 0xF5, 0x2F, 0x89, 0xF3, 0x78, 0x12, 0x0B, 0xFA, 0x4E, 0x10, 0x61,
0xD7, 0x36, 0x16, 0x34, 0xD9, 0xA2, 0x54, 0xA4, 0xF5, 0x7A, 0xA5, 0xBD,
0x9F, 0x2C, 0x30, 0x93, 0x4F, 0x0E, 0x68, 0xCB, 0xE6, 0x61, 0x1D, 0x90,
0xD7, 0x4C, 0xAA, 0xAC, 0xB6, 0xA9, 0x95, 0x56, 0x56, 0x47, 0x33, 0x3D,
0xC1, 0x70, 0x92, 0xD3, 0x20, 0x13, 0x10, 0x89, 0xCC, 0xCD, 0x63, 0x31,
0xCB, 0x3A, 0x59, 0x5D, 0x1B, 0xA2, 0x99, 0xA3, 0x2F, 0xF4, 0xD8, 0xE5,
0xDD, 0x1E, 0xB4, 0x6A, 0x2A, 0x57, 0x93, 0x5F, 0x6F, 0xE6, 0x37, 0x32,
0x2D, 0x3B, 0xC4, 0xF6, 0x7C, 0xFE, 0xD6, 0xC2, 0x25, 0x4C, 0x08, 0x9C,
0x62, 0xFA, 0x11, 0xD0, 0x82, 0x4A, 0x84, 0x4C, 0x79, 0xEE, 0x5A, 0x4F,
0x27, 0x3D, 0x46, 0xC2, 0x3B, 0xBB, 0xF0, 0xA2, 0xAF, 0x6A, 0xCA, 0xDB,
0xE6, 0x46, 0xF4, 0x6B, 0x86, 0xD1, 0x28, 0x9C, 0x7F, 0xF7, 0xE8, 0x16,
0xCF, 0xDA, 0x4B, 0xC3, 0x3D, 0xFF, 0x9D, 0x17, 0x5A, 0xC6, 0x9F, 0x72,
0x40, 0x6C, 0x07, 0x1B, 0x51, 0xF4, 0x5A, 0x1A, 0xCB, 0x87, 0xF1, 0x68,
0xC1, 0x77, 0xCB, 0x9B, 0xE6, 0xC3, 0x92, 0xF0, 0x34, 0x18, 0x49, 0xAE,
0x5D, 0x51, 0x0D, 0x26, 0xEE, 0xC1, 0x09, 0x7B, 0xEB, 0xFB, 0x9D, 0x14,
0x4A, 0x16, 0x47, 0x30, 0x1B, 0xEA, 0xF9, 0x52, 0x0D, 0x22, 0xC5, 0x5A,
0xF4, 0x6D, 0x49, 0x28, 0x4C, 0xC7, 0xF9, 0xFB, 0xBA, 0x37, 0x1A, 0x6D,
0x6E, 0x4C, 0x55, 0xF1, 0xE5, 0x36, 0xD6, 0x23, 0x7F, 0xFF, 0x54, 0xB3,
0xE9, 0xC1, 0x1A, 0x20, 0xCF, 0xCC, 0xAC, 0x0C, 0x6B, 0x06, 0xF6, 0x95,
0x76, 0x6A, 0xCE, 0xB1, 0x8B, 0xE3, 0x32, 0x99, 0xA9, 0x4C, 0xFC, 0xA7,
0xE2, 0x58, 0x81, 0x86, 0x52, 0xF7, 0x52, 0x6B, 0x30, 0x6B, 0x52, 0xE0,
0xAE, 0xD0, 0x42, 0x18}},
// dev
{RSAKEY_NCSD_NAND, {0x53, 0xCB, 0x0E, 0x4E, 0xB1, 0xA6, 0xFF, 0x84, 0x28, 0x4B, 0xE0, 0xE7,
0x38, 0x5A, 0xB4, 0xA6, 0x86, 0xA8, 0xBB, 0xCB, 0xC1, 0x61, 0x02, 0x47,
0x92, 0x80, 0xE0, 0x58, 0x36, 0x55, 0xD2, 0x71, 0x3F, 0xE5, 0x06, 0xFA,
0xEE, 0x74, 0xF8, 0xD1, 0x0F, 0x12, 0x20, 0x44, 0x1C, 0xC2, 0xFF, 0x5D,
0x6D, 0xDE, 0x99, 0xBE, 0x79, 0xC1, 0x9B, 0x38, 0x6C, 0xAF, 0x68, 0xD5,
0xEB, 0x8C, 0xED, 0x1A, 0xAB, 0x4D, 0x24, 0x3C, 0x5F, 0x39, 0x86, 0x80,
0xD3, 0x1C, 0xD2, 0xE3, 0xC9, 0xDD, 0x56, 0x70, 0xF2, 0xA8, 0x8D, 0x56,
0x3B, 0x8F, 0x65, 0xF5, 0xB2, 0x34, 0xFD, 0x2E, 0xBB, 0x3B, 0xE4, 0x4A,
0x3B, 0x6C, 0x30, 0x27, 0x22, 0xA2, 0xAD, 0xFB, 0x56, 0xAE, 0x3E, 0x1F,
0x64, 0x17, 0xBD, 0xEC, 0x1E, 0x5A, 0x86, 0xAA, 0xBB, 0xAF, 0xBE, 0x94,
0x19, 0xAC, 0xA8, 0xFD, 0xCD, 0x45, 0xE2, 0xCD, 0xF1, 0xEB, 0x69, 0x5F,
0x6E, 0xA8, 0x78, 0x16, 0x12, 0x2D, 0x7B, 0xE9, 0x8E, 0xEF, 0x92, 0xC0,
0x81, 0x4B, 0x16, 0xB2, 0x15, 0xB3, 0x1D, 0x8C, 0x81, 0x3B, 0xB3, 0x55,
0xCE, 0xA8, 0x13, 0x8F, 0xB3, 0xBF, 0x23, 0x74, 0x24, 0x68, 0x42, 0xCD,
0x91, 0xE1, 0xF9, 0xAA, 0xFF, 0x76, 0x87, 0x86, 0x17, 0xCE, 0x02, 0x06,
0x47, 0x77, 0xAE, 0xA0, 0x87, 0x6A, 0x2C, 0x24, 0x5C, 0x78, 0x43, 0x41,
0xCD, 0xEE, 0x90, 0xD6, 0x91, 0x74, 0x59, 0x08, 0xA6, 0xFF, 0x9C, 0xE7,
0x81, 0x16, 0x67, 0x96, 0xF9, 0xF1, 0x23, 0x8F, 0x88, 0x4C, 0x84, 0xD6,
0xF1, 0xEE, 0xBB, 0x2E, 0x40, 0xB4, 0xBC, 0xA0, 0x0A, 0x7B, 0x1E, 0x91,
0x3E, 0x09, 0x80, 0xD2, 0x9F, 0xF6, 0x06, 0x1D, 0x8A, 0xA9, 0x44, 0xC6,
0x63, 0xF2, 0x63, 0x81, 0x27, 0xF7, 0xCC, 0xAB, 0x6F, 0xC7, 0x15, 0x38,
0x47, 0x1A, 0x51, 0x38}},
},
};
for (size_t i = 0; i < kSigHaxSignatureNum; i++)
{
rsa_sighax_signature.insert(std::pair<byte_t, KeyBag::Rsa2048Signature>(sighax_signatures[i][isDev].key_index, sighax_signatures[i][isDev].signature));
}
}
void ctrtool::KeyBagInitializer::importSeedDb(const std::shared_ptr<tc::io::ISource>& seed_db_source)
{
// validate source exists
if (seed_db_source == nullptr)
{
throw tc::ArgumentNullException("ctrtool::KeyBagInitializer", "SeedDb source is null.");
}
// validate source has minimum size
if (seed_db_source->length() < (sizeof(SeedDbHeader) + sizeof(SeedDbEntry)))
{
throw tc::ArgumentNullException("ctrtool::KeyBagInitializer", "SeedDb source is too small.");
}
// read header
auto hdr_data = seed_db_source->pullData(0, sizeof(SeedDbHeader));
SeedDbHeader* hdr = (SeedDbHeader*)hdr_data.data();
// check padding
for (size_t i = 0; i < hdr->padding.size(); i++)
{
if (hdr->padding[i] != 0)
{
throw tc::ArgumentNullException("ctrtool::KeyBagInitializer", "SeedDb header had invalid padding.");
}
}
// get entry num
size_t n_entries = hdr->n_entries.unwrap();
// check size of source fits expected total seeddb size
if (seed_db_source->length() < static_cast<int64_t>(sizeof(SeedDbHeader) + n_entries * sizeof(SeedDbEntry)))
{
throw tc::ArgumentNullException("ctrtool::KeyBagInitializer", "SeedDb source is too small.");
}
// read entries
auto entry_data = seed_db_source->pullData(sizeof(SeedDbHeader), sizeof(SeedDbEntry) * n_entries);
SeedDbEntry* entry = (SeedDbEntry*)entry_data.data();
// import entries
for (size_t i = 0; i < n_entries; i++)
{
seed_db.insert(std::pair<byte_t, KeyBag::Aes128Key>(entry[i].title_id.unwrap(), entry[i].seed));
}
}
bool ctrtool::KeyBagInitializer::importFallbackKey(tc::Optional<KeyBag::Aes128Key>& key, const std::string& key_str)
{
// check key_str size
if (key_str.size() != sizeof(KeyBag::Aes128Key)*2) { return false; }
// convert to keydata
auto keydata = tc::cli::FormatUtil::hexStringToBytes(key_str);
// check keydata size
if (keydata.size() != sizeof(KeyBag::Aes128Key)) { return false; }
// save key
KeyBag::Aes128Key tmp;
memcpy(tmp.data(), keydata.data(), tmp.size());
key = tmp;
return true;
}
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include <string>
#include <vector>
#include <array>
#include <map>
#include <tc/Optional.h>
#include <tc/io.h>
#include <tc/crypto/RsaKey.h>
namespace ctrtool {
struct KeyBag
{
using Aes128Key = std::array<byte_t, 16>;
// NCCH encryption keys
enum NcchFixedKeyIndex
{
NCCH_APPLICATION_FIXED_KEY = 0,
NCCH_SYSTEM_FIXED_KEY = 1,
};
enum NcchSecureKeyIndex
{
NCCH_SECURE_KEY_FW1 = 0,
NCCH_SECURE_KEY_FW7 = 1,
NCCH_SECURE_KEY_FW9_3 = 10,
NCCH_SECURE_KEY_FW9_6 = 11
};
std::map<byte_t, Aes128Key> ncch_fixed_key;
std::map<byte_t, Aes128Key> ncch_secure_key_x;
// ticket
enum CommonKeyIndex
{
COMMONKEY_APPLICATION = 0,
COMMONKEY_SYSTEM = 1,
COMMONKEY_UNUSED_2 = 2,
COMMONKEY_UNUSED_3 = 3,
COMMONKEY_UNUSED_4 = 4,
COMMONKEY_UNUSED_5 = 5
};
std::map<byte_t, Aes128Key> common_key;
tc::Optional<Aes128Key> fallback_title_key;
// NCCH seed
std::map<uint64_t, Aes128Key> seed_db;
tc::Optional<Aes128Key> fallback_seed;
// BootROM Initialized Keyslots
enum AesKeySlot
{
KEYSLOT_INITIAL_DATA = 0x3B,
KEYSLOT_ES_COMMON_KEY = 0x3D
};
std::map<byte_t, Aes128Key> brom_static_key_x;
std::map<byte_t, Aes128Key> brom_static_key_y;
std::map<byte_t, Aes128Key> brom_static_key;
// Firmware Keys
enum FirmwareKeyIndex
{
FIRM_NGC_KEY = 0,
FIRM_NOR_KEY = 1,
FIRM_SD_KEY = 2,
};
std::map<byte_t, Aes128Key> firmware_key;
// Normal RSA Keys
enum RsaKeyIndex
{
RSAKEY_FIRM_NAND = 0, // normal: read from NCSD partition
RSAKEY_FIRM_RECOVERY = 1, // recovery: read from NWN SPI / NTRCARD
RSAKEY_NCSD_NAND = 2,
RSAKEY_CFA_CCI = 3,
RSAKEY_ACCESSDESC = 4,
RSAKEY_CRR = 5,
RSAKEY_SECUREINFO = 6,
RSAKEY_LOCALFRIENDCODESEED = 7,
RSAKEY_DSP = 8,
};
std::map<byte_t, tc::crypto::RsaKey> rsa_key;
// SigHax Signatures
using Rsa2048Signature = std::array<byte_t, 0x100>;
std::map<byte_t, Rsa2048Signature> rsa_sighax_signature;
// BroadOn RSA Keys
struct BroadOnRsaSignerProfile
{
tc::ByteData certificate;
tc::crypto::RsaKey key;
};
std::map<std::string, BroadOnRsaSignerProfile> broadon_rsa_signer;
};
class KeyBagInitializer : public KeyBag
{
public:
KeyBagInitializer(bool isDev, const tc::Optional<std::string>& fallback_title_key_str, const tc::Optional<tc::io::Path>& seed_db_path, const tc::Optional<std::string>& fallback_seed_str);
private:
KeyBagInitializer();
void addStaticAesKeys(bool isDev);
void addStaticRsaKeys(bool isDev);
void addSigHaxSignatures(bool isDev);
void importSeedDb(const std::shared_ptr<tc::io::ISource>& seed_db_source);
bool importFallbackKey(tc::Optional<KeyBag::Aes128Key>& key, const std::string& key_str);
#pragma pack(push,1)
struct SeedDbHeader
{
tc::bn::le32<uint32_t> n_entries;
std::array<byte_t, 0xC> padding;
};
static_assert(sizeof(SeedDbHeader) == 0x10, "Size of Entry was incorrect.");
struct SeedDbEntry
{
tc::bn::le64<uint64_t> title_id;
KeyBag::Aes128Key seed;
std::array<byte_t, 8> padding;
};
static_assert(sizeof(SeedDbEntry) == 0x20, "Size of SeedDbEntry was incorrect.");
#pragma pack(pop)
};
}
+54
View File
@@ -0,0 +1,54 @@
#include "LzssProcess.h"
#include "lzss.h"
#include <tc/io.h>
ctrtool::LzssProcess::LzssProcess() :
mModuleLabel("ctrtool::LzssProcess"),
mInputStream(),
mExtractPath()
{
}
void ctrtool::LzssProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::LzssProcess::setExtractPath(const tc::io::Path& extract_path)
{
mExtractPath = extract_path;
}
void ctrtool::LzssProcess::process()
{
if (mExtractPath.isSet())
{
// read input file into memory
// check if input file is too large
if (tc::is_int64_t_too_large_for_size_t(mInputStream->length()))
{
throw tc::InvalidOperationException(mModuleLabel, "Input file is too large.");
}
// allocate memory for file
auto compressed_data = tc::ByteData((size_t)mInputStream->length());
// read file
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read(compressed_data.data(), compressed_data.size());
// decompress
tc::ByteData decompressed_data = tc::ByteData(lzss_get_decompressed_size(compressed_data.data(), compressed_data.size()));
lzss_decompress(compressed_data.data(), compressed_data.size(), decompressed_data.data(), decompressed_data.size());
// open output file
tc::io::LocalFileSystem local_fs;
std::shared_ptr<tc::io::IStream> output_stream;
local_fs.openFile(mExtractPath.get(), tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write, output_stream);
// write decompressed data
output_stream->seek(0, tc::io::SeekOrigin::Begin);
output_stream->write(decompressed_data.data(), decompressed_data.size());
}
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "types.h"
#include <tc/Optional.h>
#include <tc/io/Path.h>
#include <tc/io/IStream.h>
namespace ctrtool {
class LzssProcess
{
public:
LzssProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setExtractPath(const tc::io::Path& extract_path);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
tc::Optional<tc::io::Path> mExtractPath;
};
}
+968
View File
@@ -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);
}
}
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include <tc/Optional.h>
#include <ntd/n3ds/ncch.h>
namespace ctrtool {
class NcchProcess
{
public:
enum NcchRegion
{
NcchRegion_Header,
NcchRegion_ExHeader,
NcchRegion_PlainRegion,
NcchRegion_Logo,
NcchRegion_ExeFs,
NcchRegion_RomFs,
NcchRegionNum
};
NcchProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setRawMode(bool raw);
void setPlainMode(bool plain);
void setShowSyscallName(bool show_name);
void 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);
void process();
private:
std::string mModuleLabel;
// Options
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mVerbose;
bool mVerify;
bool mRaw;
bool mPlain;
bool mShowSyscallNames;
struct NcchRegionOpt
{
bool show_info;
bool show_fs;
tc::Optional<tc::io::Path> bin_extract_path;
tc::Optional<tc::io::Path> fs_extract_path;
};
std::array<NcchRegionOpt, NcchRegionNum> mRegionOpt;
// BEGIN Runtime NCCH info
ntd::n3ds::NcchHeader mHeader;
int64_t mContentSize; // determined in ncch header processing
int64_t mBlockSize; // determined in ncch header processing
bool mDecompressExeFsCode; // determined in ncch exheader processing
struct NcchRegionInfo
{
byte_t valid;
int64_t offset;
int64_t size;
int64_t hashed_offset;
int64_t hashed_size;
std::shared_ptr<tc::io::IStream> raw_stream;
std::shared_ptr<tc::io::IStream> ready_stream;
};
std::array<NcchRegionInfo, NcchRegionNum> mRegionInfo;
void importHeader();
void determineRegionLayout();
void determineRegionEncryption();
void verifyRegions();
void printHeader();
void extractRegionBinaries();
void processRegions();
// string utils
std::string getValidString(byte_t validstate);
std::string getContentPlatformString(size_t bit);
std::string getFormTypeString(byte_t var);
std::string getContentTypeString(byte_t var);
// utils
bool isSystemTitle();
void getAesCounter(byte_t* counter, byte_t ncch_region);
};
}
+231
View File
@@ -0,0 +1,231 @@
#include "RomFsProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/ArgumentNullException.h>
#include <ntd/n3ds/RomFsSnapshotGenerator.h>
#include "CrrProcess.h"
ctrtool::RomFsProcess::RomFsProcess() :
mModuleLabel("ctrtool::RomFsProcess"),
mInputStream(),
mKeyBag(),
mShowHeaderInfo(false),
mShowFs(false),
mVerbose(false),
mVerify(false),
mExtractPath(),
mFsReader(),
mStaticCrr()
{
memset((byte_t*)&mHeader, 0, sizeof(ntd::n3ds::RomFsHeader));
}
void ctrtool::RomFsProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
mInputStream = input_stream;
}
void ctrtool::RomFsProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
mKeyBag = key_bag;
}
void ctrtool::RomFsProcess::setCliOutputMode(bool show_header_info, bool show_fs)
{
mShowHeaderInfo = show_header_info;
mShowFs = show_fs;
}
void ctrtool::RomFsProcess::setVerboseMode(bool verbose)
{
mVerbose = verbose;
}
void ctrtool::RomFsProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void ctrtool::RomFsProcess::setExtractPath(const tc::io::Path& extract_path)
{
mExtractPath = extract_path;
}
void ctrtool::RomFsProcess::process()
{
// begin processing
processHeader();
if (mShowHeaderInfo)
printHeader();
if (mStaticCrr != nullptr)
processCrr();
if (mShowFs)
printFs();
if (mExtractPath.isSet())
extractFs();
}
void ctrtool::RomFsProcess::processHeader()
{
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.");
}
if (mInputStream->length() < sizeof(ntd::n3ds::RomFsHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Input stream is too small.");
}
// import header
mInputStream->seek(0, tc::io::SeekOrigin::Begin);
mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::RomFsHeader));
/*
std::cout << "mHeader.header_size : " << mHeader.header_size.unwrap() << std::endl;
std::cout << "sizeof(ntd::n3ds::RomFsHeader) : " << sizeof(ntd::n3ds::RomFsHeader) << std::endl;
std::cout << "mHeader.dir_hash_bucket.offset : " << mHeader.dir_hash_bucket.offset.unwrap() << std::endl;
std::cout << "mHeader.data_offset : " << mHeader.data_offset.unwrap() << std::endl;
std::cout << "expected data offset : " << align<uint32_t>(mHeader.file_entry.offset.unwrap() + mHeader.file_entry.size.unwrap(), ntd::n3ds::RomFsHeader::kRomFsDataAlignSize) << std::endl;
*/
// do some simple checks to verify if this is an ROMFS header
if (mHeader.header_size.unwrap() != sizeof(ntd::n3ds::RomFsHeader) ||
mHeader.dir_hash_bucket.offset.unwrap() != sizeof(ntd::n3ds::RomFsHeader) ||
mHeader.data_offset.unwrap() != align<uint32_t>(mHeader.file_entry.offset.unwrap() + mHeader.file_entry.size.unwrap(), ntd::n3ds::RomFsHeader::kRomFsDataAlignSize))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "RomFsHeader is corrupted.");
}
// create FileSystem reader
mFsReader = std::shared_ptr<tc::io::VirtualFileSystem>(new tc::io::VirtualFileSystem(ntd::n3ds::RomFsSnapshotGenerator(mInputStream)));
// Open romfs:/.crr/static.crr
if (mFsReader != nullptr)
{
try {
mFsReader->openFile(tc::io::Path("/.crr/static.crr"), tc::io::FileMode::Open, tc::io::FileAccess::Read, mStaticCrr);
} catch (const tc::io::FileNotFoundException&) {
// do nothing
}
}
}
void ctrtool::RomFsProcess::printHeader()
{
fmt::print("\n");
fmt::print("RomFS:\n");
fmt::print("Header size: 0x{:08x}\n", mHeader.header_size.unwrap());
fmt::print("DirHashBucket offset: 0x{:08x}\n", mHeader.dir_hash_bucket.offset.unwrap());
fmt::print("DirHashBucket size: 0x{:08x}\n", mHeader.dir_hash_bucket.size.unwrap());
fmt::print("DirEntryTable offset: 0x{:08x}\n", mHeader.dir_entry.offset.unwrap());
fmt::print("DirEntryTable size: 0x{:08x}\n", mHeader.dir_entry.size.unwrap());
fmt::print("FileHashBucket offset: 0x{:08x}\n", mHeader.file_hash_bucket.offset.unwrap());
fmt::print("FileHashBucket size: 0x{:08x}\n", mHeader.file_hash_bucket.size.unwrap());
fmt::print("FileEntryTable offset: 0x{:08x}\n", mHeader.file_entry.offset.unwrap());
fmt::print("FileEntryTable size: 0x{:08x}\n", mHeader.file_entry.size.unwrap());
fmt::print("Data offset: 0x{:08x}\n", mHeader.data_offset.unwrap());
}
void ctrtool::RomFsProcess::processCrr()
{
CrrProcess crr_proc;
crr_proc.setInputStream(mStaticCrr);
crr_proc.setKeyBag(mKeyBag);
crr_proc.setCliOutputMode(mShowHeaderInfo);
crr_proc.setVerboseMode(mVerbose);
crr_proc.setVerifyMode(mVerify);
crr_proc.process();
}
void ctrtool::RomFsProcess::printFs()
{
fmt::print("[RomFs Filesystem]\n");
visitDir(tc::io::Path("/"),tc::io::Path("/"), false, true);
}
void ctrtool::RomFsProcess::extractFs()
{
visitDir(tc::io::Path("/"), mExtractPath.get(), true, false);
}
void ctrtool::RomFsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path& l_path, bool extract_fs, bool print_fs)
{
tc::io::LocalFileSystem local_fs;
// get listing for directory
tc::io::sDirectoryListing info;
mFsReader->getDirectoryListing(v_path, info);
if (print_fs)
{
for (size_t i = 0; i < v_path.size(); i++)
fmt::print(" ");
fmt::print("{}/\n", (v_path.size() == 1) ? "RomFs:" : v_path.back());
}
if (extract_fs)
{
// create local dir
local_fs.createDirectory(l_path);
}
// iterate thru child files
tc::ByteData cache = tc::ByteData(0x10000);
size_t cache_read_len;
tc::io::Path out_path;
std::shared_ptr<tc::io::IStream> in_stream;
std::shared_ptr<tc::io::IStream> out_stream;
for (auto itr = info.file_list.begin(); itr != info.file_list.end(); itr++)
{
if (print_fs)
{
for (size_t i = 0; i < v_path.size(); i++)
fmt::print(" ");
fmt::print(" {}\n", *itr);
}
if (extract_fs)
{
// build out path
out_path = l_path + *itr;
fmt::print("Saving {}...\n", out_path.to_string());
// begin export
mFsReader->openFile(v_path + *itr, tc::io::FileMode::Open, tc::io::FileAccess::Read, in_stream);
local_fs.openFile(out_path, 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 RomFs file.");
}
out_stream->write(cache.data(), cache_read_len);
remaining_data -= int64_t(cache_read_len);
}
}
}
// iterate thru child dirs
for (auto itr = info.dir_list.begin(); itr != info.dir_list.end(); itr++)
{
visitDir(v_path + *itr, l_path + *itr, extract_fs, print_fs);
}
}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include <tc/Optional.h>
#include <tc/io/IFileSystem.h>
#include <ntd/n3ds/romfs.h>
namespace ctrtool {
class RomFsProcess
{
public:
RomFsProcess();
void setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream);
void setKeyBag(const ctrtool::KeyBag& key_bag);
void setCliOutputMode(bool show_header_info, bool show_fs);
void setVerboseMode(bool verbose);
void setVerifyMode(bool verify);
void setExtractPath(const tc::io::Path& extract_path);
void process();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStream> mInputStream;
ctrtool::KeyBag mKeyBag;
bool mShowHeaderInfo;
bool mShowFs;
bool mVerbose;
bool mVerify;
tc::Optional<tc::io::Path> mExtractPath;
ntd::n3ds::RomFsHeader mHeader;
std::shared_ptr<tc::io::IFileSystem> mFsReader;
std::shared_ptr<tc::io::IStream> mStaticCrr;
void processHeader();
void printHeader();
void processCrr();
void printFs();
void extractFs();
void visitDir(const tc::io::Path& v_path, const tc::io::Path& l_path, bool extract_fs, bool print_fs);
};
}
+704
View File
@@ -0,0 +1,704 @@
#include <tc/cli.h>
#include <tc/ArgumentException.h>
#include <tc/io/StreamSource.h>
#include "types.h"
#include "version.h"
#include "Settings.h"
#include <ntd/n3ds/cci.h>
#include <ntd/n3ds/cia.h>
#include <ntd/n3ds/cro.h>
#include <ntd/n3ds/crr.h>
#include <ntd/n3ds/exefs.h>
#include <ntd/n3ds/firm.h>
#include <ntd/n3ds/ivfc.h>
#include <ntd/n3ds/ncch.h>
#include <ntd/n3ds/romfs.h>
#include <ntd/n3ds/smdh.h>
#include <brd/es/es_cert.h>
#include <brd/es/es_ticket.h>
#include <brd/es/es_tmd.h>
class UnkOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
UnkOptionHandler(const std::string& module_label) : mModuleLabel(module_label)
{}
const std::vector<std::string>& getOptionStrings() const
{
throw tc::InvalidOperationException("getOptionStrings() not defined for UnkOptionHandler.");
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
throw tc::InvalidOperationException("getOptionRegexPatterns() not defined for UnkOptionHandler.");
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
throw tc::Exception(mModuleLabel, "Unrecognized option: \"" + option + "\"");
}
private:
std::string mModuleLabel;
};
class DeprecatedOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
DeprecatedOptionHandler(const std::string& warn_message, const std::vector<std::string>& opts) :
mWarnMessage(warn_message),
mOptStrings(opts),
mOptRegexPatterns()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegexPatterns;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
fmt::print("[WARNING] Option \"{}\" is deprecated.{}{}\n", option, (mWarnMessage.empty() ? "" : " "), mWarnMessage);
}
private:
std::string mWarnMessage;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegexPatterns;
};
class FlagOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
FlagOptionHandler(bool& flag, const std::vector<std::string>& opts) :
mFlag(flag),
mOptStrings(opts),
mOptRegexPatterns()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegexPatterns;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 0)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" is a flag, that takes no parameters.", option));
}
mFlag = true;
}
private:
bool& mFlag;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegexPatterns;
};
class SingleParamStringOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
SingleParamStringOptionHandler(tc::Optional<std::string>& param, const std::vector<std::string>& opts) :
mParam(param),
mOptStrings(opts),
mOptRegexPatterns()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegexPatterns;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option));
}
mParam = params[0];
}
private:
tc::Optional<std::string>& mParam;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegexPatterns;
};
class SingleParamPathOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
SingleParamPathOptionHandler(tc::Optional<tc::io::Path>& param, const std::vector<std::string>& opts) :
mParam(param),
mOptStrings(opts),
mOptRegexPatterns()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegexPatterns;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option));
}
mParam = params[0];
}
private:
tc::Optional<tc::io::Path>& mParam;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegexPatterns;
};
class SingleParamSizetOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
SingleParamSizetOptionHandler(size_t& param, const std::vector<std::string>& opts) :
mParam(param),
mOptStrings(opts),
mOptRegexPatterns()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegexPatterns;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option));
}
mParam = strtoul(params[0].c_str(), nullptr, 0);
}
private:
size_t& mParam;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegexPatterns;
};
class FirmTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
FirmTypeOptionHandler(ctrtool::FirmProcess::FirmwareType& param, const std::vector<std::string>& opts) :
mParam(param),
mOptStrings(opts),
mOptRegexPatterns()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegexPatterns;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option));
}
if (params[0] == "nand" \
|| params[0] == "normal")
{
mParam = ctrtool::FirmProcess::FirmwareType_Nand;
}
else if (params[0] == "ngc" \
|| params[0] == "ntr" \
|| params[0] == "ntrboot")
{
mParam = ctrtool::FirmProcess::FirmwareType_Ngc;
}
else if (params[0] == "nor")
{
mParam = ctrtool::FirmProcess::FirmwareType_Nor;
}
else if (params[0] == "sdmc")
{
mParam = ctrtool::FirmProcess::FirmwareType_Sdmc;
}
else
{
throw tc::ArgumentException(fmt::format("Firmware type \"{}\" unrecognised.", params[0]));
}
}
private:
ctrtool::FirmProcess::FirmwareType& mParam;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegexPatterns;
};
class FileTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
FileTypeOptionHandler(ctrtool::Settings::FileType& param, const std::vector<std::string>& opts) :
mParam(param),
mOptStrings(opts),
mOptRegexPatterns()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegexPatterns;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option));
}
if (params[0] == "ncsd" \
|| params[0] == "cci" \
|| params[0] == "csu" \
|| params[0] == "3ds" \
|| params[0] == "3dx")
{
mParam = ctrtool::Settings::FILE_TYPE_NCSD;
}
else if (params[0] == "cia")
{
mParam = ctrtool::Settings::FILE_TYPE_CIA;
}
else if (params[0] == "ncch" \
|| params[0] == "cxi" \
|| params[0] == "cfa")
{
mParam = ctrtool::Settings::FILE_TYPE_NCCH;
}
else if (params[0] == "exheader")
{
mParam = ctrtool::Settings::FILE_TYPE_EXHEADER;
}
else if (params[0] == "exefs")
{
mParam = ctrtool::Settings::FILE_TYPE_EXEFS;
}
else if (params[0] == "romfs")
{
mParam = ctrtool::Settings::FILE_TYPE_ROMFS;
}
else if (params[0] == "firm")
{
mParam = ctrtool::Settings::FILE_TYPE_FIRM;
}
else if (params[0] == "cert")
{
mParam = ctrtool::Settings::FILE_TYPE_CERT;
}
else if (params[0] == "tik")
{
mParam = ctrtool::Settings::FILE_TYPE_TIK;
}
else if (params[0] == "tmd")
{
mParam = ctrtool::Settings::FILE_TYPE_TMD;
}
else if (params[0] == "lzss")
{
mParam = ctrtool::Settings::FILE_TYPE_LZSS;
}
else
{
throw tc::ArgumentException(fmt::format("File type \"{}\" unrecognised.", params[0]));
}
}
private:
ctrtool::Settings::FileType& mParam;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegexPatterns;
};
ctrtool::SettingsInitializer::SettingsInitializer(const std::vector<std::string>& args) :
Settings(),
mModuleLabel("ctrtool::SettingsInitializer"),
mFallBackTitleKey(),
mFallBackSeed(),
mSeedDbPath()
{
// parse input arguments
parse_args(args);
if (infile.path.isNull())
throw tc::ArgumentException(mModuleLabel, "No input file was specified.");
opt.keybag = KeyBagInitializer(opt.is_dev, mFallBackTitleKey, mSeedDbPath, mFallBackSeed);
// determine filetype if not manually specified
if (infile.filetype == FILE_TYPE_ERROR)
{
determine_filetype();
if (infile.filetype == FILE_TYPE_ERROR)
{
throw tc::ArgumentException(mModuleLabel, "Input file type was undetermined.");
}
}
}
void ctrtool::SettingsInitializer::parse_args(const std::vector<std::string>& args)
{
// check for minimum arguments
if (args.size() < 2)
{
usage_text();
throw tc::ArgumentException(mModuleLabel, "Not enough arguments.");
}
// detect request for help
for (auto itr = ++(args.begin()); itr != args.end(); itr++)
{
if (*itr == "-h" || *itr == "--help" || *itr == "-help")
{
usage_text();
throw tc::ArgumentException(mModuleLabel, "Help required.");
}
}
// save input file
infile.path = tc::io::Path(args.back());
// test new option parser
tc::cli::OptionParser opts;
// register unk option handler
opts.registerUnrecognisedOptionHandler(std::shared_ptr<UnkOptionHandler>(new UnkOptionHandler(mModuleLabel)));
// register handler for deprecated options DeprecatedOptionHandler
opts.registerOptionHandler(std::shared_ptr<DeprecatedOptionHandler>(new DeprecatedOptionHandler("Generic AES/RSA keys are initialised internally.", {"-k", "--keyset"})));
opts.registerOptionHandler(std::shared_ptr<DeprecatedOptionHandler>(new DeprecatedOptionHandler("", {"--unitsize"})));
opts.registerOptionHandler(std::shared_ptr<DeprecatedOptionHandler>(new DeprecatedOptionHandler("All common keys are initialised internally.", {"--commonkey"})));
opts.registerOptionHandler(std::shared_ptr<DeprecatedOptionHandler>(new DeprecatedOptionHandler("All secure NCCH keys are initialised internally.", {"--ncchkey"})));
opts.registerOptionHandler(std::shared_ptr<DeprecatedOptionHandler>(new DeprecatedOptionHandler("The NCCH system key is initialised internally.", {"--ncchsyskey"})));
// get option flags
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(opt.plain, {"-p", "--plain"})));
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(opt.raw, {"-r", "--raw"})));
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(opt.verbose, {"-v", "--verbose"})));
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(opt.verify, {"-y", "--verify"})));
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(opt.is_dev, {"-d", "--dev"})));
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(opt.show_keys, {"--showkeys"})));
// get user-provided keydata
opts.registerOptionHandler(std::shared_ptr<SingleParamStringOptionHandler>(new SingleParamStringOptionHandler(mFallBackTitleKey, {"--titlekey"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mSeedDbPath, {"--seeddb"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamStringOptionHandler>(new SingleParamStringOptionHandler(mFallBackSeed, {"--seed"})));
// lzss options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(lzss.extract_path, {"--lzssout"})));
// rom options
opts.registerOptionHandler(std::shared_ptr<SingleParamSizetOptionHandler>(new SingleParamSizetOptionHandler(rom.content_process_index, {"-n", "--ncch", "--cidx"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(rom.content_extract_path, {"--contents"})));
// ncch options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(ncch.exheader_path, {"--exheader"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(ncch.logo_path, {"--logo"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(ncch.plainregion_path, {"--plainrgn", "--plainregion"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(ncch.exefs_path, {"--exefs"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(ncch.romfs_path, {"--romfs"})));
// exheader options
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(exheader.show_syscalls_as_names, {"--showsyscalls"})));
// exefs options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(exefs.extract_path, {"--exefsdir"})));
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(exefs.decompress_code_partition, {"--decompresscode"})));
//opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(exefs.list_fs, {"--listexefs"})));
exefs.list_fs = false;
// romfs options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(romfs.extract_path, {"--romfsdir"})));
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(romfs.list_fs, {"--listromfs"})));
// cia specific options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(cia.certs_path, {"--certs"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(cia.tik_path, {"--tik"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(cia.tmd_path, {"--tmd"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(cia.meta_path, {"--meta"})));
// firm options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(firm.extract_path, {"--firmdir"})));
opts.registerOptionHandler(std::shared_ptr<FirmTypeOptionHandler>(new FirmTypeOptionHandler(firm.firm_type, {"--firmtype"})));
// wav options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(cwav.extract_path, {"--wav"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamSizetOptionHandler>(new SingleParamSizetOptionHandler(cwav.wav_loops, {"--wavloops"})));
// process input file type
opts.registerOptionHandler(std::shared_ptr<FileTypeOptionHandler>(new FileTypeOptionHandler(infile.filetype, {"-t", "--intype"})));
opts.processOptions(args, 1, args.size() - 2);
}
void ctrtool::SettingsInitializer::determine_filetype()
{
auto file = tc::io::StreamSource(std::make_shared<tc::io::FileStream>(tc::io::FileStream(infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read)));
auto raw_data = file.pullData(0, 0x1000);
#define _TYPE_PTR(st) ((st*)(raw_data.data()))
#define _ASSERT_FILE_SIZE(sz) (file.length() >= (sz))
// do tests
if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::CciHeader))
&& _TYPE_PTR(ntd::n3ds::CciHeader)->ncsd_header.struct_magic.unwrap() == ntd::n3ds::NcsdCommonHeader::kStructMagic)
{
infile.filetype = FILE_TYPE_NCSD;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::CiaHeader))
&& _TYPE_PTR(ntd::n3ds::CiaHeader)->header_size.unwrap() == sizeof(ntd::n3ds::CiaHeader))
{
infile.filetype = FILE_TYPE_CIA;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::CrrHeader))
&& _TYPE_PTR(ntd::n3ds::CrrHeader)->struct_magic.unwrap() == ntd::n3ds::CrrHeader::kStructMagic)
{
infile.filetype = FILE_TYPE_CRR;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::CroHeader))
&& _TYPE_PTR(ntd::n3ds::CroHeader)->struct_magic.unwrap() == ntd::n3ds::CroHeader::kStructMagic)
{
infile.filetype = FILE_TYPE_CRO;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::ExeFsHeader))
&& _TYPE_PTR(ntd::n3ds::ExeFsHeader)->file_table[0].offset.unwrap() == 0
&& _TYPE_PTR(ntd::n3ds::ExeFsHeader)->file_table[0].size.unwrap() != 0
&& _TYPE_PTR(ntd::n3ds::ExeFsHeader)->file_table[0].name[0] == '.')
{
infile.filetype = FILE_TYPE_EXEFS;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::FirmwareHeader))
&& _TYPE_PTR(ntd::n3ds::FirmwareHeader)->struct_magic.unwrap() == ntd::n3ds::FirmwareHeader::kStructMagic)
{
infile.filetype = FILE_TYPE_FIRM;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::IvfcHeader))
&& _TYPE_PTR(ntd::n3ds::IvfcHeader)->struct_magic.unwrap() == ntd::n3ds::IvfcHeader::kStructMagic)
{
infile.filetype = FILE_TYPE_IVFC;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::NcchHeader))
&& _TYPE_PTR(ntd::n3ds::NcchHeader)->header.struct_magic.unwrap() == ntd::n3ds::NcchCommonHeader::kStructMagic)
{
infile.filetype = FILE_TYPE_NCCH;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::RomFsHeader))
&& _TYPE_PTR(ntd::n3ds::RomFsHeader)->header_size.unwrap() == sizeof(ntd::n3ds::RomFsHeader))
{
infile.filetype = FILE_TYPE_ROMFS;
}
else if (_ASSERT_FILE_SIZE(sizeof(ntd::n3ds::SystemMenuDataHeader))
&& _TYPE_PTR(ntd::n3ds::SystemMenuDataHeader)->struct_magic.unwrap() == sizeof(ntd::n3ds::SystemMenuDataHeader::kStructMagic))
{
infile.filetype = FILE_TYPE_SMDH;
}
// CTR CA cert
else if (_ASSERT_FILE_SIZE(sizeof(brd::es::ESCACert))
&& _TYPE_PTR(brd::es::ESCACert)->sig.sigType.unwrap() == brd::es::ESSigType::RSA4096_SHA256
&& _TYPE_PTR(brd::es::ESCACert)->sig.issuer.decode() == "Root"
&& _TYPE_PTR(brd::es::ESCACert)->head.pubKeyType.unwrap() == brd::es::ESCertPubKeyType::RSA2048
&& _TYPE_PTR(brd::es::ESCACert)->head.name.serverId.decode().substr(0, 2) == "CA")
{
infile.filetype = FILE_TYPE_CERT;
}
// CTR CA-signed cert
else if (_ASSERT_FILE_SIZE(sizeof(brd::es::ESCASignedCert))
&& _TYPE_PTR(brd::es::ESCASignedCert)->sig.sigType.unwrap() == brd::es::ESSigType::RSA2048_SHA256
&& _TYPE_PTR(brd::es::ESCASignedCert)->sig.issuer.decode().substr(0, 5) == "Root-CA"
&& _TYPE_PTR(brd::es::ESCASignedCert)->head.pubKeyType.unwrap() == brd::es::ESCertPubKeyType::RSA2048)
{
infile.filetype = FILE_TYPE_CERT;
}
// detect ticket
else if (_ASSERT_FILE_SIZE(sizeof(brd::es::ESV1Ticket))
&& _TYPE_PTR(brd::es::ESV1Ticket)->head.sig.sigType.unwrap() == brd::es::ESSigType::RSA2048_SHA256
&& _TYPE_PTR(brd::es::ESV1Ticket)->head.sig.issuer.decode().substr(0, 5) == "Root-"
&& _TYPE_PTR(brd::es::ESV1Ticket)->head.sig.issuer.decode().substr(16, 2) == "XS")
{
infile.filetype = FILE_TYPE_TIK;
}
// detect tmd
else if (_ASSERT_FILE_SIZE(sizeof(brd::es::ESV1TitleMeta))
&& _TYPE_PTR(brd::es::ESV1TitleMeta)->sig.sigType.unwrap() == brd::es::ESSigType::RSA2048_SHA256
&& _TYPE_PTR(brd::es::ESV1TitleMeta)->sig.issuer.decode().substr(0, 5) == "Root-"
&& _TYPE_PTR(brd::es::ESV1TitleMeta)->sig.issuer.decode().substr(16, 2) == "CP")
{
infile.filetype = FILE_TYPE_TMD;
}
#undef _TYPE_PTR
#undef _ASSERT_FILE_SIZE
}
void ctrtool::SettingsInitializer::usage_text()
{
fmt::print(stderr, "{:s} v{:d}.{:d}.{:d} (C) {:s}\n", APP_NAME, VER_MAJOR, VER_MINOR, VER_PATCH, AUTHORS);
fmt::print(stderr, "Built: {:s} {:s}\n\n", __TIME__, __DATE__);
fmt::print(stderr, "Usage: {:s} [options... ] <file>\n", BIN_NAME);
fmt::print(stderr,
"Options:\n"
" -i, --info Show file info.\n"
" This is the default action.\n"
" -x, --extract Extract data from file.\n"
" This is also the default action.\n"
" -p, --plain Extract data without decrypting.\n"
" -r, --raw Keep raw data, don't unpack.\n"
//" -k, --keyset=file Specify keyset file.\n"
" -v, --verbose Give verbose output.\n"
" -y, --verify Verify hashes and signatures.\n"
" -d, --dev Decrypt with development keys instead of retail.\n"
//" --unitsize=size Set media unit size (default 0x200).\n"
//" --commonkey=key Set common key.\n"
" --titlekey=key Set tik title key.\n"
//" --ncchkey=key Set ncch key.\n"
//" --ncchsyskey=key Set ncch fixed system key.\n"
" --seeddb=file Set seeddb for ncch seed crypto.\n"
" --seed=key Set specific seed for ncch seed crypto.\n"
" --showkeys Show the keys being used.\n"
" --showsyscalls Show system call names instead of numbers.\n"
" -t, --intype=type Specify input file type [ncsd, ncch, exheader, cia, tmd, lzss,\n"
" firm, cwav, exefs, romfs]\n"
"LZSS options:\n"
" --lzssout=file Specify lzss output file\n"
"CXI/CCI options:\n"
" -n, --ncch=index Specify NCCH partition index.\n"
" --exheader=file Specify Extended Header file path.\n"
" --logo=file Specify Logo file path.\n"
" --plainrgn=file Specify Plain region file path\n"
" --exefs=file Specify ExeFS file path.\n"
" --exefsdir=dir Specify ExeFS directory path.\n"
" --romfs=file Specify RomFS file path.\n"
" --romfsdir=dir Specify RomFS directory path.\n"
" --listromfs List files in RomFS.\n"
"CIA options:\n"
" --certs=file Specify Certificate chain file path.\n"
" --tik=file Specify Ticket file path.\n"
" --tmd=file Specify TMD file path.\n"
" --contents=file Specify Contents file path.\n"
" --meta=file Specify Meta file path.\n"
"FIRM options:\n"
" --firmdir=dir Specify Firm directory path.\n"
" --firmtype=type Specify Firm location type, this determines encryption/signing.\n"
" - nand: (default) FIRM images installed to internal NAND,\n"
" - ngc: FIRM images loaded from NTR game card at boot,\n"
" - nor: FIRM images loaded from WiFi board NOR at boot,\n"
" - sdmc: FIRM images installed from SD card by FIRM installers (internal dev tool).\n"
"CWAV options:\n"
" --wav=file Specify wav output file.\n"
" --wavloops=count Specify wav loop count, default 0.\n"
"EXEFS options:\n"
" --decompresscode Decompress .code section\n"
" (only needed when using raw EXEFS file)\n");
/*
std::cerr <<
"Options:\n"
" -i, --info Show file info.\n"
" This is the default action.\n"
" -x, --extract Extract data from file.\n"
" This is also the default action.\n"
" -p, --plain Extract data without decrypting.\n"
" -r, --raw Keep raw data, don't unpack.\n"
//" -k, --keyset=file Specify keyset file.\n"
" -v, --verbose Give verbose output.\n"
" -y, --verify Verify hashes and signatures.\n"
" -d, --dev Decrypt with development keys instead of retail.\n"
//" --unitsize=size Set media unit size (default 0x200).\n"
//" --commonkey=key Set common key.\n"
" --titlekey=key Set tik title key.\n"
//" --ncchkey=key Set ncch key.\n"
//" --ncchsyskey=key Set ncch fixed system key.\n"
" --seeddb=file Set seeddb for ncch seed crypto.\n"
" --seed=key Set specific seed for ncch seed crypto.\n"
" --showkeys Show the keys being used.\n"
" --showsyscalls Show system call names instead of numbers.\n"
" -t, --intype=type Specify input file type [ncsd, ncch, exheader, cia, tmd, lzss,\n"
" firm, cwav, exefs, romfs]\n"
"LZSS options:\n"
" --lzssout=file Specify lzss output file\n"
"CCI/CIA options:\n"
" -n, --ncch=index Specify NCCH partition index.\n"
" --contents=dir Specify Contents directory path.\n"
"CCI options:\n"
" --initdata=file Specify Initial Data file path.\n"
"CIA options:\n"
" --certs=file Specify Certificate chain file path.\n"
" --tik=file Specify Ticket file path.\n"
" --tmd=file Specify TMD file path.\n"
" --footer=file Specify Footer file path.\n"
"NCCH options:\n"
" --exheader=file Specify Extended Header file path.\n"
" --logo=file Specify Logo file path.\n"
" --plainrgn=file Specify Plain region file path\n"
" --exefs=file Specify ExeFS file path.\n"
" --romfs=file Specify RomFS file path.\n"
"EXEFS options:\n"
" --exefsdir=dir Specify ExeFS directory path.\n"
" --listexefs List files in ExeFS.\n"
" --decompresscode Decompress .code section\n"
" (only needed when using raw ExeFS file)\n"
"ROMFS options:\n"
" --romfsdir=dir Specify RomFS directory path.\n"
" --listromfs List files in RomFS.\n"
"FIRM options:\n"
" --firmdir=dir Specify Firm directory path.\n"
"CWAV options:\n"
" --wav=file Specify wav output file.\n"
" --wavloops=count Specify wav loop count, default 0.\n"
<< std::flush;
*/
}
+175
View File
@@ -0,0 +1,175 @@
#pragma once
#include <string>
#include <vector>
#include <tc/Optional.h>
#include <tc/io.h>
#include "FirmProcess.h"
#include "KeyBag.h"
namespace ctrtool {
struct Settings
{
enum FileType
{
FILE_TYPE_ERROR,
FILE_TYPE_NCSD,
FILE_TYPE_CIA,
FILE_TYPE_NCCH,
FILE_TYPE_EXHEADER,
FILE_TYPE_EXEFS,
FILE_TYPE_ROMFS,
FILE_TYPE_FIRM,
FILE_TYPE_CERT,
FILE_TYPE_TIK,
FILE_TYPE_TMD,
FILE_TYPE_LZSS,
FILE_TYPE_CRR,
FILE_TYPE_CRO,
FILE_TYPE_IVFC,
FILE_TYPE_SMDH
};
struct InputFileOptions
{
FileType filetype;
tc::Optional<tc::io::Path> path;
} infile;
struct Options
{
bool plain;
bool raw;
bool verbose;
bool verify;
bool show_keys;
bool is_dev;
KeyBag keybag;
} opt;
// LZSS options
struct LzssOptions
{
tc::Optional<tc::io::Path> extract_path;
} lzss;
// NCCH options
struct NcchOptions
{
tc::Optional<tc::io::Path> exheader_path;
tc::Optional<tc::io::Path> logo_path;
tc::Optional<tc::io::Path> plainregion_path;
tc::Optional<tc::io::Path> exefs_path;
tc::Optional<tc::io::Path> romfs_path;
} ncch;
// ExHeader options
struct ExheaderOptions
{
bool show_syscalls_as_names;
} exheader;
// ExeFs options
struct ExefsOptions
{
tc::Optional<tc::io::Path> extract_path;
bool list_fs;
bool decompress_code_partition;
} exefs;
// RomFs options
struct RomfsOptions
{
tc::Optional<tc::io::Path> extract_path;
bool list_fs;
} romfs;
// CCI/CIA options
struct RomOptions
{
size_t content_process_index;
tc::Optional<tc::io::Path> content_extract_path;
} rom;
// CIA options
struct CiaOptions
{
tc::Optional<tc::io::Path> certs_path;
tc::Optional<tc::io::Path> tik_path;
tc::Optional<tc::io::Path> tmd_path;
tc::Optional<tc::io::Path> meta_path;
} cia;
// FIRM options
struct FirmOptions
{
tc::Optional<tc::io::Path> extract_path;
FirmProcess::FirmwareType firm_type;
} firm;
// CWAV options
struct CwavOptions
{
tc::Optional<tc::io::Path> extract_path;
size_t wav_loops;
} cwav;
Settings()
{
infile.filetype = FILE_TYPE_ERROR;
infile.path = tc::Optional<tc::io::Path>();
opt.plain = false;
opt.raw = false;
opt.verbose = false;
opt.verify = false;
opt.show_keys = false;
opt.is_dev = false;
opt.keybag = KeyBag();
exheader.show_syscalls_as_names = false;
exefs.extract_path = tc::Optional<tc::io::Path>();
exefs.list_fs = false;
exefs.decompress_code_partition = false;
romfs.extract_path = tc::Optional<tc::io::Path>();
romfs.list_fs = false;
rom.content_process_index = 0;
rom.content_extract_path = tc::Optional<tc::io::Path>();
cia.certs_path = tc::Optional<tc::io::Path>();
cia.tik_path = tc::Optional<tc::io::Path>();
cia.tmd_path = tc::Optional<tc::io::Path>();
cia.meta_path = tc::Optional<tc::io::Path>();
firm.extract_path = tc::Optional<tc::io::Path>();
firm.firm_type = FirmProcess::FirmwareType_Nand;
cwav.extract_path = tc::Optional<tc::io::Path>();
cwav.wav_loops = 0;
}
};
class SettingsInitializer : public Settings
{
public:
SettingsInitializer(const std::vector<std::string>& args);
private:
void parse_args(const std::vector<std::string>& args);
void register_option_handlers();
void determine_filetype();
void usage_text();
std::string mModuleLabel;
bool mShowKeys;
tc::Optional<std::string> mFallBackTitleKey;
tc::Optional<std::string> mFallBackSeed;
tc::Optional<tc::io::Path> mSeedDbPath;
};
}
+152
View File
@@ -0,0 +1,152 @@
#pragma once
#include "types.h"
enum CWAV_ENCODING
{
CWAV_ENCODING_PCM8 = 0,
CWAV_ENCODING_PCM16 = 1,
CWAV_ENCODING_DSPADPCM = 2,
CWAV_ENCODING_IMAADPCM = 3,
};
enum CWAV_REFTYPE
{
CWAV_REFTYPE_DSP_ADPCM_INFO = 0x0300,
CWAV_REFTYPE_IMA_ADPCM_INFO = 0x0301,
CWAV_REFTYPE_SAMPLE_DATA = 0x1F00,
CWAV_REFTYPE_INFO_BLOCK = 0x7000,
CWAV_REFTYPE_DATA_BLOCK = 0x7001,
CWAV_REFTYPE_CHANNEL_INFO = 0x7100,
};
struct cwav_reference
{
// 0x00
tc::bn::le16<uint16_t> idtype; // See CWAV_REFTYPE
// 0x02
tc::bn::pad<2> padding;
// 0x04
tc::bn::le32<uint32_t> offset;
// 0x08
};
struct cwav_sizedreference
{
// 0x00
tc::bn::le16<uint16_t> idtype; // See CWAV_REFTYPE
// 0x02
tc::bn::pad<2> padding;
// 0x04
tc::bn::le32<uint32_t> offset;
// 0x08
tc::bn::le32<uint32_t> size;
// 0x0C
};
struct cwav_header
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("CWAV");
// 0x00
tc::bn::le32<uint32_t> magic;
// 0x04
tc::bn::le16<uint16_t> byteordermark; // byte_t[2]{0xFF,0xFE} == little endian, byte_t[2]{0xFE,0xFF} == big endian
// 0x06
tc::bn::le16<uint16_t> headersize;
// 0x08
tc::bn::le32<uint32_t> version;
// 0x0C
tc::bn::le32<uint32_t> totalsize;
// 0x10
tc::bn::le16<uint16_t> datablocks;
// 0x12
tc::bn::pad<2> reserved;
// 0x14
cwav_sizedreference infoblockref;
// 0x20
cwav_sizedreference datablockref;
// 0x2C
};
struct cwav_infoheader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("INFO");
// 0x00
tc::bn::le32<uint32_t> magic;
// 0x04
tc::bn::le32<uint32_t> size;
// 0x08
byte_t encoding; // See CWAV_ENCODING
// 0x09
byte_t looped; // 0 = no loop, 1 = loop
// 0x0A
tc::bn::pad<2> padding;
// 0x0C
tc::bn::le32<uint32_t> samplerate;
// 0x10
tc::bn::le32<uint32_t> loopstart;
// 0x14
tc::bn::le32<uint32_t> loopend;
// 0x18
tc::bn::pad<4> reserved;
// 0x1C
//tc::bn::le32<uint32_t> channelcount;
// 0x20
};
struct cwav_referencetable
{
// 0x00
tc::bn::le32<uint32_t> ref_count;
// 0x04
cwav_reference[] ref_entry;
};
struct cwav_channelinfo
{
// 0x00
cwav_reference sampleref;
// 0x08
cwav_reference codecref;
// 0x10
tc::bn::pad<4> reserved;
// 0x14
};
struct cwav_dspadpcminfo
{
// 0x00
std::array<tc::bn::le16<uint16_t>, 16> coef;
// 0x20
tc::bn::le16<uint16_t> scale;
// 0x22
tc::bn::le16<uint16_t> yn1;
// 0x24
tc::bn::le16<uint16_t> yn2;
// 0x26
tc::bn::le16<uint16_t> loopscale;
// 0x28
tc::bn::le16<uint16_t> loopyn1;
// 0x2A
tc::bn::le16<uint16_t> loopyn2;
// 0x2C
};
struct cwav_imaadpcminfo
{
// 0x00
tc::bn::le16<uint16_t> data;
// 0x02
byte_t tableindex;
// 0x03
byte_t padding;
// 0x04
tc::bn::le16<uint16_t> loopdata;
// 0x06
byte_t looptableindex;
// 0x07
byte_t looppadding;
// 0x08
};
+107
View File
@@ -0,0 +1,107 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "lzss.h"
uint32_t getle32(const uint8_t* p)
{
return (p[0]<<0) | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
}
uint32_t lzss_get_decompressed_size(uint8_t* compressed, uint32_t compressedsize)
{
uint8_t* footer = compressed + compressedsize - 8;
//uint32_t buffertopandbottom = getle32(footer+0);
uint32_t originalbottom = getle32(footer+4);
return originalbottom + compressedsize;
}
int lzss_decompress(uint8_t* compressed, uint32_t compressedsize, uint8_t* decompressed, uint32_t decompressedsize)
{
uint8_t* footer = compressed + compressedsize - 8;
uint32_t buffertopandbottom = getle32(footer+0);
//uint32_t originalbottom = getle32(footer+4);
uint32_t i, j;
uint32_t out = decompressedsize;
uint32_t index = compressedsize - ((buffertopandbottom>>24)&0xFF);
uint32_t segmentoffset;
uint32_t segmentsize;
uint8_t control;
uint32_t stopindex = compressedsize - (buffertopandbottom&0xFFFFFF);
memset(decompressed, 0, decompressedsize);
memcpy(decompressed, compressed, compressedsize);
while(index > stopindex)
{
control = compressed[--index];
for(i=0; i<8; i++)
{
if (index <= stopindex)
break;
if (index <= 0)
break;
if (out <= 0)
break;
if (control & 0x80)
{
if (index < 2)
{
fprintf(stderr, "Error, compression out of bounds\n");
goto clean;
}
index -= 2;
segmentoffset = compressed[index] | (compressed[index+1]<<8);
segmentsize = ((segmentoffset >> 12)&15)+3;
segmentoffset &= 0x0FFF;
segmentoffset += 2;
if (out < segmentsize)
{
fprintf(stderr, "Error, compression out of bounds\n");
goto clean;
}
for(j=0; j<segmentsize; j++)
{
uint8_t data;
if (out+segmentoffset >= decompressedsize)
{
fprintf(stderr, "Error, compression out of bounds\n");
goto clean;
}
data = decompressed[out+segmentoffset];
decompressed[--out] = data;
}
}
else
{
if (out < 1)
{
fprintf(stderr, "Error, compression out of bounds\n");
goto clean;
}
decompressed[--out] = compressed[--index];
}
control <<= 1;
}
}
return 1;
clean:
return 0;
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
uint32_t lzss_get_decompressed_size(uint8_t* compressed, uint32_t compressedsize);
int lzss_decompress(uint8_t* compressed, uint32_t compressedsize, uint8_t* decompressed, uint32_t decompressedsize);
#ifdef __cplusplus
}
#endif
+245
View File
@@ -0,0 +1,245 @@
#include <tc.h>
#include <tc/os/UnicodeMain.h>
#include "Settings.h"
#include "ExeFsProcess.h"
#include "RomFsProcess.h"
#include "IvfcProcess.h"
#include "NcchProcess.h"
#include "ExHeaderProcess.h"
#include "CciProcess.h"
#include "CiaProcess.h"
#include "LzssProcess.h"
#include "CrrProcess.h"
#include "FirmProcess.h"
#include <tc/io/SubStream.h>
#include <ntd/n3ds/IvfcStream.h>
int umain(const std::vector<std::string>& args, const std::vector<std::string>& env)
{
try
{
ctrtool::Settings set = ctrtool::SettingsInitializer(args);
std::shared_ptr<tc::io::IStream> infile_stream = std::make_shared<tc::io::FileStream>(tc::io::FileStream(set.infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read));
if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_EXEFS)
{
ctrtool::ExeFsProcess proc;
proc.setInputStream(infile_stream);
proc.setCliOutputMode(true, set.exefs.list_fs);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
proc.setRawMode(set.opt.raw);
proc.setDecompressCode(set.exefs.decompress_code_partition);
if (set.exefs.extract_path.isSet())
{
proc.setExtractPath(set.exefs.extract_path.get());
}
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_ROMFS)
{
ctrtool::RomFsProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setCliOutputMode(true, set.romfs.list_fs);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
if (set.romfs.extract_path.isSet())
{
proc.setExtractPath(set.romfs.extract_path.get());
}
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_IVFC)
{
ctrtool::IvfcProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setCliOutputMode(true, set.romfs.list_fs);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
if (set.romfs.extract_path.isSet())
{
proc.setExtractPath(set.romfs.extract_path.get());
}
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_NCCH)
{
ctrtool::NcchProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
proc.setRawMode(set.opt.raw);
proc.setPlainMode(set.opt.raw);
proc.setShowSyscallName(set.exheader.show_syscalls_as_names);
proc.setRegionProcessOutputMode(proc.NcchRegion_Header, true, false, tc::Optional<tc::io::Path>(), tc::Optional<tc::io::Path>());
proc.setRegionProcessOutputMode(proc.NcchRegion_ExHeader, true, false, set.ncch.exheader_path, tc::Optional<tc::io::Path>());
proc.setRegionProcessOutputMode(proc.NcchRegion_PlainRegion, false, false, set.ncch.plainregion_path, tc::Optional<tc::io::Path>());
proc.setRegionProcessOutputMode(proc.NcchRegion_Logo, false, false, set.ncch.logo_path, tc::Optional<tc::io::Path>());
proc.setRegionProcessOutputMode(proc.NcchRegion_ExeFs, true, set.exefs.list_fs, set.ncch.exefs_path, set.exefs.extract_path);
proc.setRegionProcessOutputMode(proc.NcchRegion_RomFs, true, set.romfs.list_fs, set.ncch.romfs_path, set.romfs.extract_path);
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_EXHEADER)
{
ctrtool::ExHeaderProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setCliOutputMode(true);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
proc.setShowSyscallName(set.exheader.show_syscalls_as_names);
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_NCSD)
{
ctrtool::CciProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setCliOutputMode(true, false);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
if (set.rom.content_extract_path.isSet())
proc.setExtractPath(set.rom.content_extract_path.get());
proc.setContentIndex(set.rom.content_process_index);
proc.setRawMode(set.opt.raw);
proc.setPlainMode(set.opt.plain);
proc.setShowSyscallName(set.exheader.show_syscalls_as_names);
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_Header, true, false, tc::Optional<tc::io::Path>(), tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_ExHeader, true, false, set.ncch.exheader_path, tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_PlainRegion, false, false, set.ncch.plainregion_path, tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_Logo, false, false, set.ncch.logo_path, tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_ExeFs, true, set.exefs.list_fs, set.ncch.exefs_path, set.exefs.extract_path);
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_RomFs, true, set.romfs.list_fs, set.ncch.romfs_path, set.romfs.extract_path);
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_CIA)
{
ctrtool::CiaProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setCliOutputMode(true, false);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
if (set.rom.content_extract_path.isSet())
proc.setContentExtractPath(set.rom.content_extract_path.get());
proc.setContentIndex(set.rom.content_process_index);
if (set.cia.certs_path.isSet())
proc.setCertExtractPath(set.cia.certs_path.get());
if (set.cia.tik_path.isSet())
proc.setTikExtractPath(set.cia.tik_path.get());
if (set.cia.tmd_path.isSet())
proc.setTmdExtractPath(set.cia.tmd_path.get());
if (set.cia.meta_path.isSet())
proc.setFooterExtractPath(set.cia.meta_path.get());
proc.setRawMode(set.opt.raw);
proc.setPlainMode(set.opt.plain);
proc.setShowSyscallName(set.exheader.show_syscalls_as_names);
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_Header, true, false, tc::Optional<tc::io::Path>(), tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_ExHeader, true, false, set.ncch.exheader_path, tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_PlainRegion, false, false, set.ncch.plainregion_path, tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_Logo, false, false, set.ncch.logo_path, tc::Optional<tc::io::Path>());
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_ExeFs, true, set.exefs.list_fs, set.ncch.exefs_path, set.exefs.extract_path);
proc.setNcchRegionProcessOutputMode(ctrtool::NcchProcess::NcchRegion_RomFs, true, set.romfs.list_fs, set.ncch.romfs_path, set.romfs.extract_path);
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_LZSS)
{
ctrtool::LzssProcess proc;
proc.setInputStream(infile_stream);
if (set.lzss.extract_path.isSet())
proc.setExtractPath(set.lzss.extract_path.get());
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_CRR)
{
ctrtool::CrrProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setCliOutputMode(true);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
proc.process();
}
else if (set.infile.filetype == ctrtool::Settings::FILE_TYPE_FIRM)
{
ctrtool::FirmProcess proc;
proc.setInputStream(infile_stream);
proc.setKeyBag(set.opt.keybag);
proc.setCliOutputMode(true);
proc.setVerboseMode(set.opt.verbose);
proc.setVerifyMode(set.opt.verify);
if (set.firm.extract_path.isSet())
{
proc.setExtractPath(set.firm.extract_path.get());
}
proc.setFirmwareType(set.firm.firm_type);
proc.process();
}
switch (set.infile.filetype)
{
case ctrtool::Settings::FILE_TYPE_NCSD :
fmt::print("## FILE_TYPE_NCSD ##\n");
break;
case ctrtool::Settings::FILE_TYPE_CIA :
fmt::print("## FILE_TYPE_CIA ##\n");
break;
case ctrtool::Settings::FILE_TYPE_NCCH :
fmt::print("## FILE_TYPE_NCCH ##\n");
break;
case ctrtool::Settings::FILE_TYPE_EXHEADER :
fmt::print("## FILE_TYPE_EXHEADER ##\n");
break;
case ctrtool::Settings::FILE_TYPE_EXEFS :
fmt::print("## FILE_TYPE_EXEFS ##\n");
break;
case ctrtool::Settings::FILE_TYPE_ROMFS :
fmt::print("## FILE_TYPE_ROMFS ##\n");
break;
case ctrtool::Settings::FILE_TYPE_FIRM :
fmt::print("## FILE_TYPE_FIRM ##\n");
break;
case ctrtool::Settings::FILE_TYPE_CERT :
fmt::print("## FILE_TYPE_CERT ##\n");
break;
case ctrtool::Settings::FILE_TYPE_TIK :
fmt::print("## FILE_TYPE_TIK ##\n");
break;
case ctrtool::Settings::FILE_TYPE_TMD :
fmt::print("## FILE_TYPE_TMD ##\n");
break;
case ctrtool::Settings::FILE_TYPE_LZSS :
fmt::print("## FILE_TYPE_LZSS ##\n");
break;
case ctrtool::Settings::FILE_TYPE_CRR :
fmt::print("## FILE_TYPE_CRR ##\n");
break;
case ctrtool::Settings::FILE_TYPE_CRO :
fmt::print("## FILE_TYPE_CRO ##\n");
break;
case ctrtool::Settings::FILE_TYPE_IVFC :
fmt::print("## FILE_TYPE_IVFC ##\n");
break;
case ctrtool::Settings::FILE_TYPE_SMDH :
fmt::print("## FILE_TYPE_SMDH ##\n");
break;
default:
fmt::print("## unknown({}) ##\n", (int)set.infile.filetype);
break;
}
}
catch (tc::Exception& e)
{
fmt::print("[{0}{1}ERROR] {2}\n", e.module(), (strlen(e.module()) != 0 ? " ": ""), e.error());
return 1;
}
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include <tc/types.h>
#include <fmt/core.h>
namespace ctrtool {
enum ValidState : byte_t
{
Unchecked,
Good,
Fail
};
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#define APP_NAME "CTRTool"
#define BIN_NAME "ctrtool"
#define VER_MAJOR 1
#define VER_MINOR 0
#define VER_PATCH 0
#define AUTHORS "jakcron"