mirror of
https://github.com/DarkStore-3DS/Project_CTR.git
synced 2026-07-03 08:49:03 +00:00
Add source code for ctrtool
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
*/
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <tc/types.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
|
||||
namespace ctrtool {
|
||||
|
||||
enum ValidState : byte_t
|
||||
{
|
||||
Unchecked,
|
||||
Good,
|
||||
Fail
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user