Put everything back.

This commit is contained in:
jakcron
2022-04-16 21:27:49 +08:00
parent 5d62e839e7
commit bc04de6d09
844 changed files with 114383 additions and 29 deletions
@@ -0,0 +1,131 @@
#include <ntd/n3ds/CciFsSnapshotGenerator.h>
#include <tc/io/SubStream.h>
#include <tc/crypto/Sha256Generator.h>
#include <ntd/n3ds/cci.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::CciFsShapshotGenerator::CciFsShapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream) :
FileSystemSnapshot(),
mBaseStream(stream),
mCurDir(0)
{
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::CciFsShapshotGenerator", "Failed to open input stream.");
}
if (mBaseStream->canRead() == false || mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::CciFsShapshotGenerator", "Input stream requires read/seek permissions.");
}
// validate and read CCI header
ntd::n3ds::CciHeader hdr;
if (mBaseStream->length() < sizeof(ntd::n3ds::CciHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "Input stream is too small.");
}
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::CciHeader));
if (hdr.ncsd_header.struct_magic.unwrap() != hdr.ncsd_header.kStructMagic)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had invalid struct magic.");
}
if (hdr.ncsd_header.flags.media_type != hdr.ncsd_header.MediaType_Card1 && hdr.ncsd_header.flags.media_type != hdr.ncsd_header.MediaType_Card2)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had unexpected media type.");
}
if (hdr.ncsd_header.flags.block_size_log != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had unexpected block size.");
}
if (hdr.ncsd_header.flags.media_platform.test(hdr.ncsd_header.MediaPlatform_CTR) == false)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had unexpected supported platforms.");
}
// parse header partitions
struct PartitionInformation
{
int64_t offset;
int64_t size;
uint64_t title_id;
};
std::array<PartitionInformation, ntd::n3ds::NcsdCommonHeader::kPartitionNum> partition;
int64_t used_image_size = 0;
for (size_t i = 0; i < partition.size(); i++)
{
if (hdr.ncsd_header.partition_fs_type[i] != hdr.ncsd_header.PartitionFsType_None)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI partition had unexpected fs type.");
}
if (hdr.ncsd_header.partition_crypto_type[i] != hdr.ncsd_header.PartitionCryptoType_None)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI partition had unexpected crypto type.");
}
partition[i].offset = int64_t(hdr.ncsd_header.partition_offsetsize[i].blk_offset.unwrap()) << 9;
partition[i].size = int64_t(hdr.ncsd_header.partition_offsetsize[i].blk_size.unwrap()) << 9;
partition[i].title_id = hdr.ncsd_header.card_ext.partition_id[i].unwrap();
used_image_size = std::max<int64_t>((partition[i].offset + partition[i].size), used_image_size);
}
if (stream->length() < used_image_size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "Input stream is too small, given calculated CCI partition geometry.");
}
// Add root directory
dir_entries.push_back(DirEntry());
mCurDir = dir_entries.size() - 1;
dir_entries[mCurDir].dir_listing.abs_path = tc::io::Path("/");
dir_entry_path_map[tc::io::Path("/")] = mCurDir;
// populate virtual filesystem
// initial data
//addFile("initial_data.bin", sizeof(ntd::n3ds::NcsdCommonHeader) + 0xe00, sizeof(ntd::n3ds::CardInfoHeader::InitialData));
// NCCH partitions
for (size_t i = 0; i < partition.size(); i++)
{
if (partition[i].size != 0)
{
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2) << i;
ss << "_";
ss << std::hex << std::setfill('0') << std::setw(16) << partition[i].title_id;
ss << ".app";
addFile(ss.str(), partition[i].offset, partition[i].size);
}
}
}
void ntd::n3ds::CciFsShapshotGenerator::addFile(const std::string& name, int64_t offset, int64_t size)
{
FileEntry tmp;
tmp.stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mBaseStream, offset, size));
// create file path
tc::io::Path file_path = dir_entries[mCurDir].dir_listing.abs_path + std::string(name);
// add file entry to list
file_entries.push_back(std::move(tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size()-1;
// add name to parent directory listing
dir_entries[mCurDir].dir_listing.file_list.push_back(name);
}
@@ -0,0 +1,241 @@
#include <ntd/n3ds/CiaFsSnapshotGenerator.h>
#include <tc/io/SubStream.h>
#include <tc/crypto/Sha256Generator.h>
#include <ntd/n3ds/cia.h>
#include <brd/es/es_tmd.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::CiaFsSnapshotGenerator::CiaFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream) :
FileSystemSnapshot()
{
mBaseStream = stream;
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::CiaFsSnapshotGenerator", "Failed to open input stream.");
}
if (mBaseStream->canRead() == false || mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::CiaFsSnapshotGenerator", "Input stream requires read/seek permissions.");
}
// validate and read CIA header
ntd::n3ds::CiaHeader hdr;
if (mBaseStream->length() < sizeof(ntd::n3ds::CiaHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "Input stream is too small.");
}
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::CiaHeader));
if (hdr.header_size.unwrap() != sizeof(ntd::n3ds::CiaHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had unexpected size.");
}
if (hdr.format_version.unwrap() != ntd::n3ds::CiaHeader::FormatVersion_Default)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had unexpected format version.");
}
if (hdr.type.unwrap() != ntd::n3ds::CiaHeader::Type_Normal)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had unexpected type.");
}
// parse header sections
enum CiaSectionIndex
{
Header,
Certificate,
Tik,
Tmd,
Content,
Footer
};
struct CiaSectionInformation
{
int64_t offset;
int64_t size;
};
std::array<CiaSectionInformation, 6> section;
section[Header].size = hdr.header_size.unwrap();
section[Header].offset = 0;
section[Certificate].size = hdr.certificate_size.unwrap();
section[Certificate].offset = align<int64_t>(section[Header].offset + section[Header].size, CiaHeader::kCiaSectionAlignment);
section[Tik].size = hdr.ticket_size.unwrap();
section[Tik].offset = align<int64_t>(section[Certificate].offset + section[Certificate].size, CiaHeader::kCiaSectionAlignment);
section[Tmd].size = hdr.tmd_size.unwrap();
section[Tmd].offset = align<int64_t>(section[Tik].offset + section[Tik].size, CiaHeader::kCiaSectionAlignment);
if (uint64_t(std::numeric_limits<int64_t>::max()) < hdr.content_size.unwrap())
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had too large content section.");
}
section[Content].size = hdr.content_size.unwrap();
section[Content].offset = align<int64_t>(section[Tmd].offset + section[Tmd].size, CiaHeader::kCiaSectionAlignment);
section[Footer].size = hdr.footer_size.unwrap();
section[Footer].offset = align<int64_t>(section[Content].offset + section[Content].size, CiaHeader::kCiaSectionAlignment);
int64_t total_size = section[Footer].offset + section[Footer].size;
if (stream->length() < total_size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "Input stream is too small, given calculated CIA section geometry.");
}
// Add root directory
dir_entries.push_back(DirEntry());
mCurDir = dir_entries.size() - 1;
dir_entries[mCurDir].dir_listing.abs_path = tc::io::Path("/");
dir_entry_path_map[tc::io::Path("/")] = mCurDir;
// populate virtual filesystem
if (section[Certificate].size != 0)
{
addFile("cert", section[Certificate].offset, section[Certificate].size);
}
if (section[Tik].size != 0)
{
addFile("tik", section[Tik].offset, section[Tik].size);
}
if (section[Tmd].size != 0)
{
addFile("tmd", section[Tmd].offset, section[Tmd].size);
}
if (section[Footer].size != 0)
{
addFile("footer", section[Footer].offset, section[Footer].size);
}
if (section[Content].size != 0)
{
// if TMD exists, add the (included) <cid>.app files to the file system
if (section[Tmd].size != 0)
{
// test size before reading
if (section[Tmd].size < (sizeof(brd::es::ESV1TitleMeta) + sizeof(brd::es::ESV1ContentMeta)))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD was too small.");
}
// import TMD v1 data
if (tc::is_int64_t_too_large_for_size_t(section[Tmd].size))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD was too large to read into memory.");
}
tc::ByteData tmd_data = tc::ByteData(static_cast<size_t>(section[Tmd].size));
mBaseStream->seek(section[Tmd].offset, tc::io::SeekOrigin::Begin);
if (mBaseStream->read(tmd_data.data(), tmd_data.size()) < tmd_data.size())
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected size after reading.");
}
// get pointer
brd::es::ESV1TitleMeta* tmd = (brd::es::ESV1TitleMeta*)tmd_data.data();
if (tmd->sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected signature type.");
}
if (tmd->head.version != 1)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected format version.");
}
// hash for staged validiation
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
// TODO validate signature
/*
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->sig.issuer, (size_t)((byte_t*)&tmd->v1Head.cmdGroups - (byte_t*)&tmd->sig.issuer));
if (tc::crypto::VerifyRsa2048Pkcs1Sha256(tmd->sig.sig.data(), hash.data(), <rsa key here>))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsMetaGenerator", "TMD had invalid signature.");
}
*/
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->v1Head.cmdGroups, sizeof(tmd->v1Head.cmdGroups));
if (memcmp(hash.data(), tmd->v1Head.hash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had invalid CMD group hash.");
}
/*
std::cout << "[Tmd]" << std::endl;
std::cout << " > Issuer: " << tmd->sig.issuer.data() << std::endl;
std::cout << " > Version: " << (int)tmd->head.version << std::endl;
std::cout << " > CACrl Version: " << (int)tmd->head.caCrlVersion << std::endl;
std::cout << " > SignerCrl Version: " << (int)tmd->head.signerCrlVersion << std::endl;
std::cout << " > TitleId: " << std::hex << std::setfill('0') << std::setw(16) << tmd->head.titleId.unwrap() << std::endl;
*/
size_t cmd_table_num = tmd->v1Head.cmdGroups[0].nCmds.unwrap();
if (tmd_data.size() != (sizeof(brd::es::ESV1TitleMeta) + (cmd_table_num * sizeof(brd::es::ESV1ContentMeta))))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected size");
}
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->contents, cmd_table_num * sizeof(brd::es::ESV1ContentMeta));
if (memcmp(hash.data(), tmd->v1Head.cmdGroups[0].groupHash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had invalid CMD group[0] hash.");
}
// TODO SHA2 cmd_table before processing
int64_t content_offset = 0;
for (size_t i = 0; i < cmd_table_num; i++)
{
/*
std::cout << "Found content ";
std::cout << "cid(" << std::hex << std::setfill('0') << std::setw(8) << tmd->contents[i].cid.unwrap() << "), ";
std::cout << "index(" << std::hex << std::setfill('0') << std::setw(4) << tmd->contents[i].index.unwrap() << "), ";
std::cout << "type(" << std::hex << std::setfill('0') << std::setw(4) << tmd->contents[i].type.unwrap() << "), ";
std::cout << "size(" << std::hex << std::setfill('0') << std::setw(16) << tmd->contents[i].size.unwrap() << "), ";
std::cout << "" << std::endl;
*/
if (hdr.content_bitarray.test(tmd->contents[i].index.unwrap()))
{
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(8) << tmd->contents[i].cid.unwrap() << ".app";
int64_t content_size = align<int64_t>(tmd->contents[i].size.unwrap(), CiaHeader::kCiaContentAlignment);
addFile(ss.str(), section[Content].offset + content_offset, content_size);
content_offset += content_size;
}
}
}
// otherwise include the content blob as one file (00000000.app)
else
{
addFile("00000000.app", section[Content].offset, section[Content].size);
}
}
}
void ntd::n3ds::CiaFsSnapshotGenerator::addFile(const std::string& name, int64_t offset, int64_t size)
{
FileEntry tmp;
tmp.stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mBaseStream, offset, size));
// create file path
tc::io::Path file_path = dir_entries[mCurDir].dir_listing.abs_path + std::string(name);
// add file entry to list
file_entries.push_back(std::move(tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size()-1;
// add name to parent directory listing
dir_entries[mCurDir].dir_listing.file_list.push_back(name);
}
@@ -0,0 +1,99 @@
#include <ntd/n3ds/CtrKeyGenerator.h>
void ntd::n3ds::CtrKeyGenerator::GenerateKey(const uint8_t *x, const uint8_t *y, uint8_t *key)
{
static const uint8_t KEYGEN_CONST[16] = { 0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A };
// overall algo:
// key = (((x <<< 2) ^ y) + KEYGEN_CONST) >>> 41
uint8_t x_rot[16], key_xy[16], key_xyc[16];
// x_rot = x <<< 2
n128_lrot(x, 2, x_rot);
// key_xy = x_rot ^ y
n128_xor(x_rot, y, key_xy);
// key_xyc = key_xy + KEYGEN_CONST
n128_add(key_xy, KEYGEN_CONST, key_xyc);
n128_rrot(key_xyc, 41, key);
}
// 128bit wrap-around math
int32_t ntd::n3ds::CtrKeyGenerator::wrap_index(int32_t i)
{
return i < 0 ? ((i % 16) + 16) % 16 : (i > 15 ? i % 16 : i);
}
void ntd::n3ds::CtrKeyGenerator::n128_rrot(const uint8_t *in, uint32_t rot, uint8_t *out)
{
uint32_t bit_shift, byte_shift;
rot = rot % 128;
byte_shift = rot / 8;
bit_shift = rot % 8;
for (int32_t i = 0; i < 16; i++) {
out[i] = (in[wrap_index(i - byte_shift)] >> bit_shift) | (in[wrap_index(i - byte_shift - 1)] << (8 - bit_shift));
}
}
void ntd::n3ds::CtrKeyGenerator::n128_lrot(const uint8_t *in, uint32_t rot, uint8_t *out)
{
uint32_t bit_shift, byte_shift;
rot = rot % 128;
byte_shift = rot / 8;
bit_shift = rot % 8;
for (int32_t i = 0; i < 16; i++) {
out[i] = (in[wrap_index(i + byte_shift)] << bit_shift) | (in[wrap_index(i + byte_shift + 1)] >> (8 - bit_shift));
}
}
/* out = a + b
*/
void ntd::n3ds::CtrKeyGenerator::n128_add(const uint8_t *a, const uint8_t *b, uint8_t *out)
{
uint8_t carry = 0;
uint32_t sum = 0;
for (int i = 15; i >= 0; i--) {
sum = a[i] + b[i] + carry;
carry = sum >> 8;
out[i] = sum & 0xff;
}
}
/* out = a - b
*/
void ntd::n3ds::CtrKeyGenerator::n128_sub(const uint8_t *a, const uint8_t *b, uint8_t *out)
{
uint8_t carry = 0;
uint32_t sum = 0;
for (int i = 15; i >= 0; i--) {
sum = a[i] - (b[i] + carry);
// check to see if anything was borrowed from next byte
if (a[i] < (b[i] + carry)) {
sum += 0x100;
carry = 1;
}
else {
carry = 0;
}
// set value
out[i] = sum & 0xff;
}
}
void ntd::n3ds::CtrKeyGenerator::n128_xor(const uint8_t *a, const uint8_t *b, uint8_t *out)
{
for (int i = 0; i < 16; i++) {
out[i] = a[i] ^ b[i];
}
}
@@ -0,0 +1,122 @@
#include <ntd/n3ds/ExeFsSnapshotGenerator.h>
#include <tc/io/SubStream.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/crypto/CryptoException.h>
#include <tc/io/MemoryStream.h>
#include <ntd/n3ds/exefs.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::ExeFsSnapshotGenerator::ExeFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream, bool verify_hashes) :
FileSystemSnapshot()
{
// validate stream properties
if (stream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::ExeFsSnapshotGenerator", "Failed to open input stream.");
}
if (stream->canRead() == false || stream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::ExeFsSnapshotGenerator", "Input stream requires read/seek permissions.");
}
// validate and read EXEFS header
ntd::n3ds::ExeFsHeader hdr;
if (stream->length() < sizeof(ntd::n3ds::ExeFsHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::ExeFsSnapshotGenerator", "Input stream is too small.");
}
stream->seek(0, tc::io::SeekOrigin::Begin);
stream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::ExeFsHeader));
if (hdr.file_table[0].name[0] == 0 || hdr.file_table[0].offset.unwrap() != 0 || hdr.hash_table[ntd::n3ds::ExeFsHeader::kFileNum - 1][0] == 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::ExeFsSnapshotGenerator", "ExeFsHeader is corrupted (Bad first entry).");
}
// parse header sections
struct SectionInformation
{
std::string name;
uint32_t offset;
uint32_t size;
std::array<byte_t, 32> hash;
};
std::vector<SectionInformation> section;
int64_t pos = 0;
for (size_t i = 0; i < ntd::n3ds::ExeFsHeader::kFileNum; i++)
{
// skip empty entry
if (hdr.file_table[i].name.size() == 0 || hdr.file_table[i].size.unwrap() == 0) { continue; }
if (hdr.file_table[i].offset.unwrap() != pos)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::ExeFsSnapshotGenerator", "ExeFs section had unexpected offset.");
}
SectionInformation tmp;
tmp.name = hdr.file_table[i].name.decode();
tmp.offset = hdr.file_table[i].offset.unwrap();
tmp.size = hdr.file_table[i].size.unwrap();
tmp.hash = hdr.hash_table[ntd::n3ds::ExeFsHeader::kFileNum - 1 - i];
pos = align<int64_t>((static_cast<int64_t>(tmp.offset) + static_cast<int64_t>(tmp.size)), ntd::n3ds::ExeFsHeader::kExeFsSectionAlignSize);
section.push_back(std::move(tmp));
}
// Add root directory
dir_entries.push_back(DirEntry());
auto cur_dir = &dir_entries.front();
cur_dir->dir_listing.abs_path = tc::io::Path("/");
dir_entry_path_map[tc::io::Path("/")] = dir_entries.size()-1;
// populate virtual filesystem
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash_tmp;
for (size_t i = 0; i < section.size(); i++)
{
if (section[i].size != 0)
{
FileEntry tmp;
// if we verify the hashes, we import and validate file, after validation creating a memorystream
if (verify_hashes)
{
auto tmp_data = tc::ByteData(section[i].size);
stream->seek(section[i].offset + sizeof(ntd::n3ds::ExeFsHeader), tc::io::SeekOrigin::Begin);
stream->read(tmp_data.data(), tmp_data.size());
tc::crypto::GenerateSha256Hash(hash_tmp.data(), tmp_data.data(), tmp_data.size());
if (memcmp(hash_tmp.data(), section[i].hash.data(), hash_tmp.size()) != 0)
{
throw tc::crypto::CryptoException("ntd::n3ds::ExeFsSnapshotGenerator", "File failed hash check.");
}
tmp.stream = std::make_shared<tc::io::MemoryStream>(tc::io::MemoryStream(std::move(tmp_data)));
}
// otherwise we just create a substream
else
{
tmp.stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(stream, static_cast<int64_t>(section[i].offset) + static_cast<int64_t>(sizeof(ntd::n3ds::ExeFsHeader)), static_cast<int64_t>(section[i].size)));
}
// create file path
tc::io::Path file_path = cur_dir->dir_listing.abs_path + section[i].name;
// add file entry to list
file_entries.push_back(std::move(tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size()-1;
// add name to parent directory listing
cur_dir->dir_listing.file_list.push_back(section[i].name);
}
}
}
@@ -0,0 +1,467 @@
#include <ntd/n3ds/IvfcStream.h>
#include <tc/io/SubStream.h>
#include <tc/io/IOUtil.h>
#include <tc/io/StreamUtil.h>
#include <ntd/n3ds/ivfc.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::IvfcStream::IvfcStream() :
mModuleLabel("ntd::n3ds::IvfcStream"),
mBaseStream(),
mDataStreamBlockSize(0),
mDataStreamLogicalLength(0),
mDataStream(),
mHashCache()
{
}
ntd::n3ds::IvfcStream::IvfcStream(const std::shared_ptr<tc::io::IStream>& stream) :
IvfcStream()
{
mBaseStream = stream;
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::IvfcStream", "stream is null.");
}
if (mBaseStream->canRead() == false)
{
throw tc::InvalidOperationException("ntd::n3ds::IvfcStream", "stream does not support reading.");
}
if (mBaseStream->canSeek() == false)
{
throw tc::InvalidOperationException("ntd::n3ds::IvfcStream", "stream does not support seeking.");
}
// validate and read IVFC header
if (mBaseStream->length() < sizeof(ntd::n3ds::IvfcCtrRomfsHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "stream is too small.");
}
ntd::n3ds::IvfcCtrRomfsHeader hdr;
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)&hdr, sizeof(ntd::n3ds::IvfcCtrRomfsHeader));
if (hdr.head.struct_magic.unwrap() != ntd::n3ds::IvfcHeader::kStructMagic)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC header had invalid struct magic.");
}
if (hdr.head.type_id.unwrap() != ntd::n3ds::IvfcHeader::TypeId_A)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC header had unexpected type id.");
}
enum LevelIndex
{
MasterHash,
HashLevel0,
HashLevel1,
DataLevel2
};
// parse level layout
struct LevelInfo
{
int64_t offset;
int64_t size;
size_t block_size;
size_t block_num;
};
std::array<LevelInfo, 4> section;
for (size_t i = 0; i < section.size(); i++)
{
if (i == MasterHash)
{
section[i].offset = int64_t(align<size_t>(sizeof(ntd::n3ds::IvfcCtrRomfsHeader), ntd::n3ds::IvfcCtrRomfsHeader::kHeaderAlign));
section[i].size = int64_t(hdr.master_hash_size.unwrap());
section[i].block_size = 0;
section[i].block_num = 0;
}
else
{
if (tc::is_uint64_t_too_large_for_int64_t(hdr.level[i-1].offset.unwrap()))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC layer offset too large.");
}
if (tc::is_uint64_t_too_large_for_int64_t(hdr.level[i-1].size.unwrap()))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC layer size too large.");
}
section[i].offset = int64_t(hdr.level[i-1].offset.unwrap());
section[i].size = int64_t(hdr.level[i-1].size.unwrap());
section[i].block_size = size_t(size_t(1) << size_t(hdr.level[i-1].block_size_log2.unwrap()));
section[i].block_num = size_t(section[i].size / int64_t(section[i].block_size)) + (size_t(section[i].size % int64_t(section[i].block_size)) ? 1 : 0);
/*
std::cout << "i: " << std::dec << i << std::endl;
std::cout << "size : " << std::dec << section[i].size << std::endl;
std::cout << "block_size_log2: " << std::dec << hdr.level[i-1].block_size_log2.unwrap() << std::endl;
std::cout << "block_size : " << std::dec << section[i].block_size << std::endl;
std::cout << "block_num : " << std::dec << section[i].block_num << std::endl;
*/
}
}
// set actual offsets
section[DataLevel2].offset = align<int64_t>(section[MasterHash].offset + section[MasterHash].size, section[DataLevel2].block_size);
section[HashLevel0].offset = align<int64_t>(section[DataLevel2].offset + section[DataLevel2].size, section[HashLevel0].block_size);
section[HashLevel1].offset = align<int64_t>(section[HashLevel0].offset + section[HashLevel0].size, section[HashLevel1].block_size);
// verify that the hash tables can be read into memory
if (tc::is_int64_t_too_large_for_size_t(section[MasterHash].size))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC master hash table too large.");
}
if (tc::is_int64_t_too_large_for_size_t(section[HashLevel0].size))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC level0 hash table too large.");
}
if (tc::is_int64_t_too_large_for_size_t(section[HashLevel1].size))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC level1 hash table too large.");
}
// validate hash tree
if ((section[DataLevel2].block_num * tc::crypto::Sha256Generator::kHashSize) != section[HashLevel1].size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC level1 hash table had unexpected size.");
}
if ((section[HashLevel1].block_num * tc::crypto::Sha256Generator::kHashSize) != section[HashLevel0].size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC level0 hash table had unexpected size.");
}
if ((section[HashLevel0].block_num * tc::crypto::Sha256Generator::kHashSize) != section[MasterHash].size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC master hash table had unexpected size.");
}
auto master_hash_data = tc::ByteData(static_cast<size_t>(section[MasterHash].size));
auto hash_level0_data = tc::ByteData(align<size_t>(static_cast<size_t>(section[HashLevel0].size), section[HashLevel0].block_size));
auto hash_level1_data = tc::ByteData(align<size_t>(static_cast<size_t>(section[HashLevel1].size), section[HashLevel1].block_size));
mBaseStream->seek(section[MasterHash].offset, tc::io::SeekOrigin::Begin);
mBaseStream->read(master_hash_data.data(), master_hash_data.size());
mBaseStream->seek(section[HashLevel0].offset, tc::io::SeekOrigin::Begin);
mBaseStream->read(hash_level0_data.data(), hash_level0_data.size());
mBaseStream->seek(section[HashLevel1].offset, tc::io::SeekOrigin::Begin);
mBaseStream->read(hash_level1_data.data(), hash_level1_data.size());
/*
std::cout << "Master Hash:" << std::endl;
std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(master_hash_data);
std::cout << "Level 0:" << std::endl;
std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(hash_level0_data);
std::cout << "Level 1:" << std::endl;
std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(hash_level1_data);
*/
// validate level0
if (validateLayerBlocksWithHashLayer(hash_level0_data.data(), section[HashLevel0].block_size, section[HashLevel0].block_num, master_hash_data.data()) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Hash layer0 failed hash validation.");
}
// validate level1
if (validateLayerBlocksWithHashLayer(hash_level1_data.data(), section[HashLevel1].block_size, section[HashLevel1].block_num, hash_level0_data.data()) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Hash layer0 failed hash validation.");
}
// save hash level1 data for data layer
mHashCache = hash_level1_data;
// create data layer
mDataStreamBlockSize = section[DataLevel2].block_size;
mDataStreamLogicalLength = section[DataLevel2].size;
mDataStream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mBaseStream, section[DataLevel2].offset, tc::io::IOUtil::castSizeToInt64(section[DataLevel2].block_num) * tc::io::IOUtil::castSizeToInt64(section[DataLevel2].block_size)));
}
bool ntd::n3ds::IvfcStream::canRead() const
{
return mDataStream == nullptr ? false : mDataStream->canRead();
}
bool ntd::n3ds::IvfcStream::canWrite() const
{
return false; // always false this is a read-only stream
}
bool ntd::n3ds::IvfcStream::canSeek() const
{
return mDataStream == nullptr ? false : mDataStream->canSeek();
}
int64_t ntd::n3ds::IvfcStream::length()
{
return mDataStream == nullptr ? 0 : mDataStreamLogicalLength;
}
int64_t ntd::n3ds::IvfcStream::position()
{
return mDataStream == nullptr ? 0 : mDataStream->position();
}
size_t ntd::n3ds::IvfcStream::read(byte_t* ptr, size_t count)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::read()", "Failed to read from stream (stream is disposed)");
}
// track read_count
size_t data_read_count = 0;
// get predicted read count
count = tc::io::IOUtil::getReadableCount(this->length(), this->position(), count);
// if count is 0 just return
if (count == 0) return data_read_count;
// determine begin & end offsets
int64_t begin_read_offset = this->position();
int64_t end_read_offset = begin_read_offset + tc::io::IOUtil::castSizeToInt64(count);
int64_t begin_aligned_offset = begin_read_offset - offsetInBlock(begin_read_offset);
int64_t end_aligned_offset = end_read_offset - offsetInBlock(end_read_offset) + (offsetInBlock(end_read_offset) ? mDataStreamBlockSize : 0x0);
size_t block_num = offsetToBlock(end_aligned_offset - begin_aligned_offset);
bool read_partial_begin_block = false;
size_t partial_begin_block = offsetToBlock(begin_read_offset);
size_t partial_begin_block_offset = 0;
size_t partial_begin_block_size = mDataStreamBlockSize;
bool read_partial_end_block = false;
size_t partial_end_block = offsetToBlock(end_read_offset);
size_t partial_end_block_offset = 0;
size_t partial_end_block_size = mDataStreamBlockSize;
if (offsetInBlock(begin_read_offset) != 0)
{
read_partial_begin_block = true;
partial_begin_block_offset += offsetInBlock(begin_read_offset);
partial_begin_block_size -= partial_begin_block_offset;
}
if (offsetInBlock(end_read_offset) != 0)
{
if (partial_begin_block == partial_end_block)
{
read_partial_begin_block = true;
partial_begin_block_size -= (mDataStreamBlockSize - offsetInBlock(end_read_offset));
}
else
{
read_partial_end_block = true;
partial_end_block_size = offsetInBlock(end_read_offset);
}
}
size_t continuous_block_num = block_num - (size_t)read_partial_begin_block - (size_t)read_partial_end_block;
size_t continuous_begin_block = (continuous_block_num == 0) ? 0 : (offsetToBlock(begin_aligned_offset) + (size_t)read_partial_begin_block);
/*
std::cout << "##############################################" << std::endl;
std::cout << "count: 0x" << std::hex << count << std::endl;
std::cout << "begin_read_offset: 0x" << std::hex << begin_read_offset << std::endl;
std::cout << "end_read_offset: 0x" << std::hex << end_read_offset << std::endl;
std::cout << "begin_aligned_offset: 0x" << std::hex << begin_aligned_offset << std::endl;
std::cout << "end_aligned_offset: 0x" << std::hex << end_aligned_offset << std::endl;
std::cout << "block_num: 0x" << std::hex << block_num << std::endl;
std::cout << "partial_begin:" << std::endl;
std::cout << " read_block: " << std::boolalpha << read_partial_begin_block << std::endl;
std::cout << " block: 0x" << std::hex << partial_begin_block << std::endl;
std::cout << " offset: 0x" << std::hex << partial_begin_block_offset << std::endl;
std::cout << " size: 0x" << std::hex << partial_begin_block_size << std::endl;
std::cout << "partial_end:" << std::endl;
std::cout << " read_block: " << std::boolalpha << read_partial_end_block << std::endl;
std::cout << " block: 0x" << std::hex << partial_end_block << std::endl;
std::cout << " offset: 0x" << std::hex << partial_end_block_offset << std::endl;
std::cout << " size: 0x" << std::hex << partial_end_block_size << std::endl;
std::cout << "continuous:" << std::endl;
std::cout << " block: 0x" << std::hex << continuous_begin_block << std::endl;
std::cout << " block_num: 0x" << std::hex << continuous_block_num << std::endl;
*/
if (block_num == 0)
{
tc::InvalidOperationException("ntd::n3ds::IvfcStream", "Invalid block number (0 blocks, would have returned before now if count==0)");
}
if (block_num < continuous_block_num)
{
tc::InvalidOperationException("ntd::n3ds::IvfcStream", "Invalid block number (underflow error)");
}
// allocate memory for partial block
tc::ByteData partial_block = tc::ByteData(mDataStreamBlockSize);
// read un-aligned begin block
if (read_partial_begin_block)
{
// read block
this->seek(blockToOffset(partial_begin_block), tc::io::SeekOrigin::Begin);
mDataStream->read(partial_block.data(), partial_block.size());
// verify block
if (validateLayerBlocksWithHashLayer(partial_block.data(), mDataStreamBlockSize, 1, getBlockHash(partial_begin_block)) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Data layer block(s) failed hash validation.");
}
// copy out block carving
memcpy(ptr + data_read_count, partial_block.data() + partial_begin_block_offset, partial_begin_block_size);
// increment data read count
data_read_count += partial_begin_block_size;
}
// read continous blocks
if (continuous_block_num > 0)
{
// read blocks
this->seek(blockToOffset(continuous_begin_block), tc::io::SeekOrigin::Begin);
mDataStream->read(ptr + data_read_count, continuous_block_num * mDataStreamBlockSize);
// verify blocks
if (validateLayerBlocksWithHashLayer(ptr + data_read_count, mDataStreamBlockSize, continuous_block_num, getBlockHash(continuous_begin_block)) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Data layer block(s) failed hash validation.");
}
// increment data read count
data_read_count += continuous_block_num * mDataStreamBlockSize;
}
// read un-aligned end block
if (read_partial_end_block)
{
// read block
this->seek(blockToOffset(partial_end_block), tc::io::SeekOrigin::Begin);
mDataStream->read(partial_block.data(), partial_block.size());
// verify block
if (validateLayerBlocksWithHashLayer(partial_block.data(), mDataStreamBlockSize, 1, getBlockHash(partial_end_block)) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Data layer block(s) failed hash validation.");
}
// copy out block carving
memcpy(ptr + data_read_count, partial_block.data() + partial_end_block_offset, partial_end_block_size);
// increment
data_read_count += partial_end_block_size;
}
// restore expected logical position
this->seek(begin_read_offset + tc::io::IOUtil::castSizeToInt64(data_read_count), tc::io::SeekOrigin::Begin);
// return data read count
return data_read_count;
}
size_t ntd::n3ds::IvfcStream::write(const byte_t* ptr, size_t count)
{
throw tc::NotImplementedException(mModuleLabel+"::write()", "write is not supported for IvfcStream");
}
int64_t ntd::n3ds::IvfcStream::seek(int64_t offset, tc::io::SeekOrigin origin)
{
if (mDataStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::seek()", "Failed to set stream position (stream is disposed)");
}
return mDataStream->seek(offset, origin);
}
void ntd::n3ds::IvfcStream::setLength(int64_t length)
{
if (mDataStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::setLength()", "Failed to set stream length (stream is disposed)");
}
throw tc::NotSupportedException(mModuleLabel+"::setLength()", "setLength is not supported for IvfcStream");
}
void ntd::n3ds::IvfcStream::flush()
{
if (mDataStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::seek()", "Failed to flush stream (stream is disposed)");
}
mDataStream->flush();
mBaseStream->flush();
}
void ntd::n3ds::IvfcStream::dispose()
{
if (mDataStream.get() != nullptr)
{
// dispose data stream
mDataStream->dispose();
// release ptr
mDataStream.reset();
}
if (mBaseStream.get() != nullptr)
{
// dispose base stream
mBaseStream->dispose();
// release ptr
mBaseStream.reset();
}
// clear hash cache
mHashCache = tc::ByteData();
}
bool ntd::n3ds::IvfcStream::validateLayerBlocksWithHashLayer(const byte_t* layer, size_t block_size, size_t block_num, const byte_t* hash_layer)
{
size_t bad_block = block_num;
for (size_t i = 0; i < block_num; i++)
{
const byte_t* blk_ptr = layer + (block_size * i);
size_t blk_size = block_size;
const byte_t* blk_hash_ptr = hash_layer + (mHashCalc.kHashSize * i);
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(blk_hash_ptr, block_size);
mHashCalc.initialize();
mHashCalc.update(blk_ptr, blk_size);
mHashCalc.getHash(mHash.data());
//std::cout << "test hash: " << tc::cli::FormatUtil::formatBytesAsString(blk_hash_ptr, 32, true, ":") << std::endl;
//std::cout << "calc hash: " << tc::cli::FormatUtil::formatBytesAsString(mHash.data(), 32, true, ":") << std::endl;
// if good hash, reduce bad block count
if (memcmp(mHash.data(), blk_hash_ptr, mHashCalc.kHashSize) == 0)
{
bad_block -= 1;
}
else
{
//std::cout << "BadBlock:" << std::endl;
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(blk_ptr, block_size);
}
}
return bad_block == 0;
}
@@ -0,0 +1,360 @@
#include <ntd/n3ds/RomFsSnapshotGenerator.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli.h>
#include <tc/io.h>
#include <tc/string.h>
ntd::n3ds::RomFsSnapshotGenerator::RomFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream) :
FileSystemSnapshot(),
mBaseStream(stream),
mDataOffset(0),
mDirEntryTable(),
mDirParentVaddrMap(),
mFileEntryTable()
{
//std::cout << "RomFsSnapshotGenerator begin" << std::endl;
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::RomFsSnapshotGenerator", "Failed to open input stream.");
}
if (mBaseStream->canRead() == false || mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::RomFsSnapshotGenerator", "Input stream requires read/seek permissions.");
}
//std::cout << "pos() -> " << mBaseStream->position() << std::endl;
// validate and read ROMFS header
ntd::n3ds::RomFsHeader hdr;
if (mBaseStream->length() < sizeof(ntd::n3ds::RomFsHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::RomFsSnapshotGenerator", "Input stream is too small.");
}
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::RomFsHeader));
/*
std::cout << "hdr.header_size : " << hdr.header_size.unwrap() << std::endl;
std::cout << "sizeof(ntd::n3ds::RomFsHeader) : " << sizeof(ntd::n3ds::RomFsHeader) << std::endl;
std::cout << "hdr.dir_hash_bucket.offset : " << hdr.dir_hash_bucket.offset.unwrap() << std::endl;
std::cout << "hdr.data_offset : " << hdr.data_offset.unwrap() << std::endl;
std::cout << "expected data offset : " << align<uint32_t>(hdr.file_entry.offset.unwrap() + hdr.file_entry.size.unwrap(), ntd::n3ds::RomFsHeader::kRomFsDataAlignSize) << std::endl;
*/
if (hdr.header_size.unwrap() != sizeof(ntd::n3ds::RomFsHeader) ||
hdr.dir_hash_bucket.offset.unwrap() != sizeof(ntd::n3ds::RomFsHeader) ||
hdr.data_offset.unwrap() != align<uint32_t>(hdr.file_entry.offset.unwrap() + hdr.file_entry.size.unwrap(), ntd::n3ds::RomFsHeader::kRomFsDataAlignSize))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::RomFsSnapshotGenerator", "RomFsHeader is corrupted.");
}
// save data offset
mDataOffset = hdr.data_offset.unwrap();
// get dir entry ptr
mDirEntryTable = tc::ByteData(hdr.dir_entry.size.unwrap());
mBaseStream->seek(hdr.dir_entry.offset.unwrap(), tc::io::SeekOrigin::Begin);
mBaseStream->read(mDirEntryTable.data(), mDirEntryTable.size());
// get file entry ptr
mFileEntryTable = tc::ByteData(hdr.file_entry.size.unwrap());
mBaseStream->seek(hdr.file_entry.offset.unwrap(), tc::io::SeekOrigin::Begin);
mBaseStream->read(mFileEntryTable.data(), mFileEntryTable.size());
//std::cout << "DirTable:" << std::endl;
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(mDirEntryTable.data(), mDirEntryTable.size());
/*
for (uint32_t v_addr = 0; v_addr < mDirEntryTable.size();)
{
std::cout << "Dir: 0x" << std::hex << v_addr << std::endl;
std::cout << " > parent: 0x" << std::hex << getDirEntry(v_addr)->parent_offset.unwrap() << std::endl;
std::cout << " > sibling: 0x" << std::hex << getDirEntry(v_addr)->sibling_offset.unwrap() << std::endl;
std::cout << " > child_offset: 0x" << std::hex << getDirEntry(v_addr)->child_offset.unwrap() << std::endl;
std::cout << " > file_offset: 0x" << std::hex << getDirEntry(v_addr)->file_offset.unwrap() << std::endl;
std::cout << " > hash_sibling: 0x" << std::hex << getDirEntry(v_addr)->hash_sibling_offset.unwrap() << std::endl;
std::cout << " > name_size: 0x" << std::hex << getDirEntry(v_addr)->name_size.unwrap() << std::endl;
uint32_t total_size = sizeof(ntd::n3ds::RomFsDirectoryEntry) + align<uint32_t>(getDirEntry(v_addr)->name_size.unwrap(), 4);
std::cout << " > entry_size: 0x" << std::hex << total_size << std::endl;
if (getDirEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
std::cout << "DirEntry looks sus" << std::endl;
break;
}
v_addr += total_size;
}
*/
//std::cout << "FileTable:" << std::endl;
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(mFileEntryTable.data(), mFileEntryTable.size());
/*
for (uint32_t v_addr = 0; v_addr < mFileEntryTable.size();)
{
std::cout << "File: 0x" << std::hex << v_addr << std::endl;
std::cout << " > parent: 0x" << std::hex << getFileEntry(v_addr)->parent_offset.unwrap() << std::endl;
std::cout << " > sibling: 0x" << std::hex << getFileEntry(v_addr)->sibling_offset.unwrap() << std::endl;
std::cout << " > data_offset: 0x" << std::hex << getFileEntry(v_addr)->data_offset.unwrap() << std::endl;
std::cout << " > data_size: 0x" << std::hex << getFileEntry(v_addr)->data_size.unwrap() << std::endl;
std::cout << " > hash_sibling: 0x" << std::hex << getFileEntry(v_addr)->hash_sibling_offset.unwrap() << std::endl;
std::cout << " > name_size: 0x" << std::hex << getFileEntry(v_addr)->name_size.unwrap() << std::endl;
uint32_t total_size = sizeof(ntd::n3ds::RomFsFileEntry) + align<uint32_t>(getFileEntry(v_addr)->name_size.unwrap(), 4);
std::cout << " > entry_size: 0x" << std::hex << total_size << std::endl;
if (getFileEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
std::cout << "FileEntry looks sus" << std::endl;
break;
}
v_addr += total_size;
}
*/
if (getDirEntry(0)->parent_offset.unwrap() != 0 ||
getDirEntry(0)->sibling_offset.unwrap() != 0xffffffff ||
getDirEntry(0)->name_size.unwrap() != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::RomFsSnapshotGenerator", "Root RomFsDirectoryEntry corrupted.");
}
// add/index directories
DirEntry dir_tmp;
for (uint32_t v_addr = 0; v_addr < mDirEntryTable.size();)
{
// create root entry
if (v_addr == 0)
{
// create dir path
tc::io::Path dir_path = tc::io::Path("/");
dir_tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(dir_tmp);
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
mDirParentVaddrMap[v_addr] = dir_entries.size() - 1;
}
// else create a regular entry
else
{
// check parent is in map
if (mDirParentVaddrMap.find(getDirEntry(v_addr)->parent_offset.unwrap()) == mDirParentVaddrMap.end())
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "Directory had invalid parent");
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = getDirEntry(v_addr)->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(getDirEntry(v_addr)->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// create dir path
size_t parent_index = mDirParentVaddrMap[getDirEntry(v_addr)->parent_offset.unwrap()];
tc::io::Path dir_path = dir_entries[parent_index].dir_listing.abs_path + utf8_string;
dir_tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(std::move(dir_tmp));
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
mDirParentVaddrMap[v_addr] = dir_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_index].dir_listing.dir_list.push_back(utf8_string);
}
uint32_t total_size = sizeof(ntd::n3ds::RomFsDirectoryEntry) + align<uint32_t>(getDirEntry(v_addr)->name_size.unwrap(), 4);
if (getDirEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "Possibly corrupted directory entry");
}
v_addr += total_size;
}
// add files
FileEntry file_tmp;
for (uint32_t v_addr = 0; v_addr < mFileEntryTable.size();)
{
// check parent is in map
if (mDirParentVaddrMap.find(getFileEntry(v_addr)->parent_offset.unwrap()) == mDirParentVaddrMap.end())
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "File had invalid parent");
if (getFileEntry(v_addr)->data_size.unwrap() != 0)
{
// substream
//std::cout << "trying to add file" << std::endl;
//std::cout << "offset " << std::hex << getFileEntry(v_addr)->data_offset.unwrap() << std::endl;
//std::cout << "size " << std::hex << getFileEntry(v_addr)->data_size.unwrap() << std::endl;
file_tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mBaseStream, mDataOffset + getFileEntry(v_addr)->data_offset.unwrap(), getFileEntry(v_addr)->data_size.unwrap()));
//std::cout << "file was added" << std::endl;
}
else
{
// empty stream
file_tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream());
}
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = getFileEntry(v_addr)->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(getFileEntry(v_addr)->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// create file path
size_t parent_index = mDirParentVaddrMap[getFileEntry(v_addr)->parent_offset.unwrap()];
tc::io::Path file_path = dir_entries[parent_index].dir_listing.abs_path + utf8_string;
// add file entry to list
file_entries.push_back(std::move(file_tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_index].dir_listing.file_list.push_back(utf8_string);
uint32_t total_size = sizeof(ntd::n3ds::RomFsFileEntry) + align<uint32_t>(getFileEntry(v_addr)->name_size.unwrap(), 4);
if (getFileEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "Possibly corrupted file entry");
}
v_addr += total_size;
}
// old style recursive add
//addDirectory(getDirEntry(0), 0);
}
void ntd::n3ds::RomFsSnapshotGenerator::addFile(const ntd::n3ds::RomFsFileEntry* file_entry, size_t parent_dir)
{
// create file entry
FileEntry tmp;
if (file_entry->data_size.unwrap() != 0)
{
// substream
//std::cout << "trying to add file" << std::endl;
//std::cout << "offset " << std::hex << file_entry->data_offset.unwrap() << std::endl;
//std::cout << "size " << std::hex << file_entry->data_size.unwrap() << std::endl;
tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mBaseStream, mDataOffset + file_entry->data_offset.unwrap(), file_entry->data_size.unwrap()));
//std::cout << "file was added" << std::endl;
}
else
{
// empty stream
tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream());
}
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = file_entry->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(file_entry->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// create file path
tc::io::Path file_path = dir_entries[parent_dir].dir_listing.abs_path + utf8_string;
// add file entry to list
file_entries.push_back(tmp);
// add file entry to map
file_entry_path_map[file_path] = file_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_dir].dir_listing.file_list.push_back(utf8_string);
}
void ntd::n3ds::RomFsSnapshotGenerator::addDirectory(const ntd::n3ds::RomFsDirectoryEntry* dir_entry, size_t parent_dir)
{
// create dir entry
DirEntry tmp;
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = dir_entry->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(dir_entry->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// this is the root entry
if (dir_entry->parent_offset.unwrap() == 0 && dir_entry->sibling_offset.unwrap() == 0xffffffff && dir_entry->name_size.unwrap() == 0)
{
// create dir path
tc::io::Path dir_path = tc::io::Path("/");
tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(tmp);
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
}
// this is a regular directory
else
{
// create dir path
tc::io::Path dir_path = dir_entries[parent_dir].dir_listing.abs_path + utf8_string;
tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(tmp);
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_dir].dir_listing.dir_list.push_back(utf8_string);
}
// get cur_dir pointer
auto cur_dir = dir_entries.size() - 1;
// add file children
for (uint32_t child = dir_entry->file_offset.unwrap();
child != 0xffffffff;
child = getFileEntry(child)->sibling_offset.unwrap())
{
//std::cout << "file child addr :" << std::hex << std::setw(8) << std::setfill('0') << child << std::endl;
addFile(getFileEntry(child), cur_dir);
}
// add dir children
for (uint32_t child = dir_entry->child_offset.unwrap();
child != 0xffffffff;
child = getDirEntry(child)->sibling_offset.unwrap())
{
addDirectory(getDirEntry(child), cur_dir);
}
}
@@ -0,0 +1,200 @@
#include <ntd/n3ds/es/Certificate.h>
#include <fmt/core.h>
#include <brd/es/es_cert.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/cli.h>
size_t ntd::n3ds::es::getCertificateSize(byte_t* data)
{
size_t signature_size = 0;
size_t public_key_size = 0;
if (data == nullptr) { return 0; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return 0; }
brd::es::ESCertHeader* header = (brd::es::ESCertHeader*)(data + signature_size);
brd::es::ESCertPubKeyType public_key_type = header->pubKeyType.unwrap();
switch (public_key_type)
{
case brd::es::ESCertPubKeyType::RSA4096:
public_key_size = sizeof(brd::es::ESCertRsa4096PublicKey);
break;
case brd::es::ESCertPubKeyType::RSA2048:
public_key_size = sizeof(brd::es::ESCertRsa2048PublicKey);
break;
case brd::es::ESCertPubKeyType::ECC:
public_key_size = sizeof(brd::es::ESCertEcc233PublicKey);
break;
default:
return 0;
}
return signature_size + sizeof(brd::es::ESCertHeader) + public_key_size;
}
size_t ntd::n3ds::es::getCertificateSignatureSize(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return 0; }
brd::es::ESSigType sig_type = ((tc::bn::be32<brd::es::ESSigType>*)data)->unwrap();
switch (sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
signature_size = sizeof(brd::es::ESSigRsa4096);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
signature_size = sizeof(brd::es::ESSigRsa2048);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
signature_size = sizeof(brd::es::ESSigEcc233);
break;
default:
return 0;
}
return signature_size;
}
size_t ntd::n3ds::es::getCertificateSignableOffset(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return 0; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return 0; }
return signature_size - sizeof(brd::es::ESIssuer);
}
size_t ntd::n3ds::es::getCertificateSignableSize(byte_t* data)
{
return getCertificateSize(data) - getCertificateSignableOffset(data);
}
void* ntd::n3ds::es::getCertificateHeaderPtr(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return nullptr; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return nullptr; }
return (data + signature_size);
}
void* ntd::n3ds::es::getCertificatePublicKeyPtr(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return nullptr; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return nullptr; }
return (data + signature_size + sizeof(brd::es::ESCertHeader));
}
ntd::n3ds::es::CertificateDeserialiser::CertificateDeserialiser(const std::shared_ptr<tc::io::IStream>& cert_stream) :
Certificate(),
mModuleLabel("ntd::n3ds::es::CertificateDeserialiser")
{
if (cert_stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
}
// process signature
this->signature = SignatureDeserialiser(cert_stream);
switch (this->signature.sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
break;
default:
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected signature type.");
}
size_t signature_size = getSignatureSizeFromSigType(this->signature.sig_type);
// get certificate header
brd::es::ESCertHeader header;
cert_stream->seek(signature_size, tc::io::SeekOrigin::Begin);
if (cert_stream->read((byte_t*)&header, sizeof(brd::es::ESCertHeader)) < sizeof(brd::es::ESCertHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
}
// get public key
union CertificatePublicKey
{
brd::es::ESCertRsa4096PublicKey rsa4096;
brd::es::ESCertRsa2048PublicKey rsa2048;
brd::es::ESCertEcc233PublicKey ecc233;
} public_key;
size_t public_key_size;
switch (header.pubKeyType.unwrap())
{
case brd::es::ESCertPubKeyType::RSA4096:
public_key_size = sizeof(brd::es::ESCertRsa4096PublicKey);
break;
case brd::es::ESCertPubKeyType::RSA2048:
public_key_size = sizeof(brd::es::ESCertRsa2048PublicKey);
break;
case brd::es::ESCertPubKeyType::ECC:
public_key_size = sizeof(brd::es::ESCertEcc233PublicKey);
break;
default:
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected public key type.");
}
cert_stream->seek(signature_size + sizeof(brd::es::ESCertHeader), tc::io::SeekOrigin::Begin);
if (cert_stream->read((byte_t*)&public_key, public_key_size) < public_key_size)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
}
// calculate hash for optional signature validation later
tc::ByteData total_cert_data = tc::ByteData(signature_size + sizeof(brd::es::ESCertHeader) + public_key_size);
cert_stream->seek(0, tc::io::SeekOrigin::Begin);
if (cert_stream->read(total_cert_data.data(), total_cert_data.size()) < total_cert_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
}
tc::crypto::GenerateSha256Hash(calculated_hash.data(), total_cert_data.data() + getCertificateSignableOffset(total_cert_data.data()), ntd::n3ds::es::getCertificateSignableSize(total_cert_data.data()));
// store properties
//this->signature = SignatureDeserialiser(cert_stream);
this->subject = header.name.deviceId.decode();
this->date = header.date.unwrap();
this->public_key_type = header.pubKeyType.unwrap();
switch (header.pubKeyType.unwrap())
{
case brd::es::ESCertPubKeyType::RSA4096:
memcpy(&this->rsa4096_public_key, &public_key.rsa4096.pubKey, sizeof(brd::es::Rsa4096PublicKey));
break;
case brd::es::ESCertPubKeyType::RSA2048:
memcpy(&this->rsa2048_public_key, &public_key.rsa2048.pubKey, sizeof(brd::es::Rsa2048PublicKey));
break;
case brd::es::ESCertPubKeyType::ECC:
memcpy(&this->ecc233_public_key, &public_key.ecc233.pubKey, sizeof(brd::es::Ecc233PublicKey));
break;
}
}
@@ -0,0 +1,84 @@
#include <ntd/n3ds/es/RsaSigner.h>
#include <tc/crypto/RsaPkcs1Sha1Signer.h>
#include <tc/crypto/RsaPkcs1Sha256Signer.h>
ntd::n3ds::es::RsaSigner::RsaSigner(brd::es::ESSigType sig_type, const std::string& issuer, const tc::crypto::RsaKey& rsa_key) :
mSigType(sig_type),
mIssuer(issuer),
mRsaKey(rsa_key)
{
switch (mSigType) {
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
if ((mRsaKey.n.size() << 3) != 4096) throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "Key size inferred from SigType did not match actual key size.");
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
if ((mRsaKey.n.size() << 3) != 2048) throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "Key size inferred from SigType did not match actual key size.");
break;
default:
throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "SigType not supported for RsaSigner.");
break;
}
}
const std::string& ntd::n3ds::es::RsaSigner::getIssuer()
{
return mIssuer;
}
brd::es::ESSigType ntd::n3ds::es::RsaSigner::getSigType()
{
return mSigType;
}
bool ntd::n3ds::es::RsaSigner::signHash(const byte_t* hash, byte_t* signature)
{
bool signSucceed = false;
switch (mSigType) {
case brd::es::ESSigType::RSA4096_SHA1:
signSucceed = tc::crypto::SignRsa4096Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA1:
signSucceed = tc::crypto::SignRsa2048Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA4096_SHA256:
signSucceed = tc::crypto::SignRsa4096Pkcs1Sha256(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA256:
signSucceed = tc::crypto::SignRsa2048Pkcs1Sha256(signature, hash, mRsaKey);
break;
default:
signSucceed = false;
break;
}
return signSucceed;
}
bool ntd::n3ds::es::RsaSigner::verifyHash(const byte_t* hash, const byte_t* signature)
{
bool verifySucceed = false;
switch (mSigType) {
case brd::es::ESSigType::RSA4096_SHA1:
verifySucceed = tc::crypto::VerifyRsa4096Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA1:
verifySucceed = tc::crypto::VerifyRsa2048Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA4096_SHA256:
verifySucceed = tc::crypto::VerifyRsa4096Pkcs1Sha256(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA256:
verifySucceed = tc::crypto::VerifyRsa2048Pkcs1Sha256(signature, hash, mRsaKey);
break;
default:
verifySucceed = false;
break;
}
return verifySucceed;
}
@@ -0,0 +1,138 @@
#include <ntd/n3ds/es/Signature.h>
#include <fmt/core.h>
#include <brd/es/es_cert.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/cli.h>
size_t ntd::n3ds::es::getSignatureSizeFromSigType(brd::es::ESSigType sig_type)
{
size_t signature_size = 0;
switch (sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
signature_size = sizeof(brd::es::ESSigRsa4096);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
signature_size = sizeof(brd::es::ESSigRsa2048);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
signature_size = sizeof(brd::es::ESSigEcc233);
break;
default:
signature_size = 0;
}
return signature_size;
}
size_t ntd::n3ds::es::getSignatureIssuerOffset(brd::es::ESSigType sig_type)
{
size_t issuer_offset = 0;
switch (sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
issuer_offset = sizeof(brd::es::ESSigRsa4096) - sizeof(brd::es::ESIssuer);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
issuer_offset = sizeof(brd::es::ESSigRsa2048) - sizeof(brd::es::ESIssuer);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
issuer_offset = sizeof(brd::es::ESSigEcc233) - sizeof(brd::es::ESIssuer);
break;
default:
issuer_offset = 0;
}
return issuer_offset;
}
ntd::n3ds::es::SignatureDeserialiser::SignatureDeserialiser(const std::shared_ptr<tc::io::IStream>& stream) :
Signature(),
mModuleLabel("ntd::n3ds::es::SignatureDeserialiser")
{
if (stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
}
// must have at least 4 bytes for signature magic code
if (stream->length() < sizeof(uint32_t))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small to import signature.");
}
// get signature
enum class SignType {
RSA4096,
RSA2048,
ECDSA233,
};
union SignatureSignature
{
tc::bn::be32<brd::es::ESSigType> sigType;
brd::es::ESSigRsa4096 rsa4096;
brd::es::ESSigRsa2048 rsa2048;
brd::es::ESSigEcc233 ecdsa233;
} signature_data;
size_t signature_size = 0;
stream->seek(0, tc::io::SeekOrigin::Begin);
if (stream->read((byte_t*)&signature_data, sizeof(uint32_t)) < sizeof(uint32_t))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected size after reading.");
}
switch (signature_data.sigType.unwrap())
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
signature_size = sizeof(brd::es::ESSigRsa4096);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
signature_size = sizeof(brd::es::ESSigRsa2048);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
signature_size = sizeof(brd::es::ESSigEcc233);
break;
default:
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected signature type.");
}
stream->seek(0, tc::io::SeekOrigin::Begin);
if (stream->read((byte_t*)&signature_data, signature_size) < signature_size)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected size after reading.");
}
// store properties
this->sig_type = signature_data.sigType.unwrap();
switch (signature_data.sigType.unwrap())
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
this->sig = tc::ByteData(signature_data.rsa4096.sig.data(), signature_data.rsa4096.sig.size());
this->issuer = signature_data.rsa4096.issuer.decode();
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
this->sig = tc::ByteData(signature_data.rsa2048.sig.data(), signature_data.rsa2048.sig.size());
this->issuer = signature_data.rsa2048.issuer.decode();
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
this->sig = tc::ByteData((byte_t*)&signature_data.ecdsa233.sig, sizeof(brd::es::Ecc233Sig));
this->issuer = signature_data.ecdsa233.issuer.decode();
break;
default:
break;
}
}
@@ -0,0 +1,182 @@
#include <ntd/n3ds/es/Ticket.h>
#include <fmt/core.h>
#include <brd/es/es_ticket.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/cli.h>
ntd::n3ds::es::TicketDeserialiser::TicketDeserialiser(const std::shared_ptr<tc::io::IStream>& tik_stream) :
Ticket(),
mModuleLabel("ntd::n3ds::es::TicketDeserialiser")
{
if (tik_stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
}
if (tik_stream->length() < sizeof(brd::es::ESV1Ticket))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small to import ticket.");
}
// import TIK v1 data (less the section headers)
tc::ByteData tik_data = tc::ByteData(sizeof(brd::es::ESV1Ticket));
tik_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tik_stream->read(tik_data.data(), tik_data.size()) < tik_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected size after reading.");
}
// get pointer
brd::es::ESV1Ticket* tik = (brd::es::ESV1Ticket*)tik_data.data();
if (tik->head.sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected signature type.");
}
if (tik->head.version != 1)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected format version.");
}
if (tik->v1Head.hdrVersion.unwrap() != 1)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had unexpected format version.");
}
if (tik->v1Head.hdrSize.unwrap() != sizeof(brd::es::ESV1TicketHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had header size.");
}
if (tik->v1Head.sectHdrOfst.unwrap() != sizeof(brd::es::ESV1TicketHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had poorly aligned sectHdrOfst.");
}
if (tik->v1Head.sectHdrEntrySize.unwrap() != sizeof(brd::es::ESV1SectionHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had unexpected size for section headers.");
}
if (tik_stream->length() < static_cast<int64_t>(sizeof(brd::es::ESTicket) + tik->v1Head.ticketSize.unwrap()))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small for calculated ticket size.");
}
// import full TIK v1 data
tik_data = tc::ByteData(sizeof(brd::es::ESTicket) + tik->v1Head.ticketSize.unwrap());
tik_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tik_stream->read(tik_data.data(), tik_data.size()) < tik_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected size after reading.");
}
// update pointer
tik = (brd::es::ESV1Ticket*)tik_data.data();
/*
fmt::print("ESTicket:\n");
fmt::print("sigType: {:x}\n", tik->head.sig.sigType.unwrap());
fmt::print("issuer: {}\n", tik->head.sig.issuer.str());
fmt::print("version: {:d}\n", tik->head.version);
fmt::print("caCrlVersion: {:d}\n", tik->head.caCrlVersion);
fmt::print("signerCrlVersion: {:d}\n", tik->head.signerCrlVersion);
fmt::print("titleKey: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.titleKey.data(), tik->head.titleKey.size(), true, ""));
fmt::print("ticketId: {:016x}\n", tik->head.ticketId.unwrap());
fmt::print("deviceId: {:08x}\n", tik->head.deviceId.unwrap());
fmt::print("titleId: {:016x}\n", tik->head.titleId.unwrap());
fmt::print("sysAccessMask: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.sysAccessMask.data(), tik->head.sysAccessMask.size(), true, ""));
fmt::print("ticketVersion: {:d}\n", tik->head.ticketVersion.unwrap());
fmt::print("accessTitleId: {:08x}\n", tik->head.accessTitleId.unwrap());
fmt::print("accessTitleMask: {:08x}\n", tik->head.accessTitleMask.unwrap());
fmt::print("licenseType: {:02x}\n", tik->head.licenseType);
fmt::print("keyId: {:02x}\n", tik->head.keyId);
fmt::print("propertyMask: {:04x}\n", tik->head.propertyMask.unwrap());
fmt::print("customData: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.customData.data(), tik->head.customData.size(), true, ""));
fmt::print("audit: {:02x}\n", tik->head.audit);
fmt::print("cidxMask: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.cidxMask.data(), tik->head.cidxMask.size(), true, ""));
for (size_t i = 0; i < tik->head.limits.size(); i++)
{
fmt::print("lp entry {}: code={:08x}, limit={:08x}\n", i, tik->head.limits[i].code.unwrap(), tik->head.limits[i].limit.unwrap());
}
fmt::print("ESV1TicketHeader:\n");
fmt::print("hdrVersion: {:04x}\n", tik->v1Head.hdrVersion.unwrap());
fmt::print("hdrSize: {:04x}\n", tik->v1Head.hdrSize.unwrap());
fmt::print("ticketSize: {:08x}\n", tik->v1Head.ticketSize.unwrap());
fmt::print("sectHdrOfst: {:08x}\n", tik->v1Head.sectHdrOfst.unwrap());
fmt::print("nSectHdrs: {:04x}\n", tik->v1Head.nSectHdrs.unwrap());
fmt::print("sectHdrEntrySize: {:04x}\n", tik->v1Head.sectHdrEntrySize.unwrap());
fmt::print("flags: {:08x}\n", tik->v1Head.flags.unwrap());
size_t sect_hdr_num = tik->v1Head.nSectHdrs.unwrap();
//brd::es::ESV1SectionHeader* sect_hdr = (brd::es::ESV1SectionHeader*)(tik_data.data() + sizeof(brd::es::ESTicket) + tik->v1Head.sectHdrOfst.unwrap());
for (size_t i = 0; i < sect_hdr_num; i++)
{
fmt::print("sectHdr {:d}:\n", i);
fmt::print(" sectOfst: {:08x}\n", tik->sectHdrs[i].sectOfst.unwrap());
fmt::print(" nRecords: {:08x}\n", tik->sectHdrs[i].nRecords.unwrap());
fmt::print(" recordSize: {:08x}\n", tik->sectHdrs[i].recordSize.unwrap());
fmt::print(" sectionSize: {:08x}\n", tik->sectHdrs[i].sectionSize.unwrap());
fmt::print(" sectionType: {:04x}\n", tik->sectHdrs[i].sectionType.unwrap());
fmt::print(" flags: {:04x}\n", tik->sectHdrs[i].flags.unwrap());
}
*/
struct TicketReservedForCtr
{
tc::bn::pad<0x14> reserved_00;
tc::bn::le32<uint32_t> ec_account_id;
tc::bn::pad<0x01> reserved_01;
};
// generate hash
byte_t* tik_hash_begin = (byte_t*)&tik->head.sig.issuer;
size_t tik_hash_size = tik_data.size() - size_t(tik_hash_begin - tik_data.data());
tc::crypto::GenerateSha256Hash(this->calculated_hash.data(), tik_hash_begin, tik_hash_size);
// basic fields
this->signature.sig_type = tik->head.sig.sigType.unwrap();
this->signature.sig = tc::ByteData(tik->head.sig.sig.data(), tik->head.sig.sig.size());
this->signature.issuer = tik->head.sig.issuer.decode();
this->title_key = tik->head.titleKey;
this->ticket_id = tik->head.ticketId.unwrap();
this->device_id = tik->head.deviceId.unwrap();
this->title_id = tik->head.titleId.unwrap();
this->ticket_version = tik->head.ticketVersion.unwrap();
this->license_type = tik->head.licenseType;
this->key_id = tik->head.keyId;
// process data from reserved field
auto custom_data = (TicketReservedForCtr*)tik->head.reserved.data();
this->ec_account_id = custom_data->ec_account_id.unwrap();
// find the demo launch limit
for (size_t i = 0; i < tik->head.limits.size(); i++)
{
if (tik->head.limits[i].code.unwrap() == (uint32_t)brd::es::ESLimitCode::NUM_LAUNCH)
{
launch_count = tik->head.limits[i].limit.unwrap();
}
}
// process content index
for (size_t i = 0; i < tik->v1Head.nSectHdrs.unwrap(); i++)
{
if (tik->sectHdrs[i].sectionType.unwrap() == (uint32_t)brd::es::ESItemType::CONTENT)
{
if (tik->sectHdrs[i].recordSize.unwrap() != sizeof(brd::es::ESV1ContentRecord))
{
throw tc::InvalidOperationException(mModuleLabel, fmt::format("Invalid size for ESV1ContentRecord. (expected: 0x{:x}, got 0x{:x})", sizeof(brd::es::ESV1ContentRecord), tik->sectHdrs[i].recordSize.unwrap()));
}
brd::es::ESV1ContentRecord* content_records = (brd::es::ESV1ContentRecord*)(tik_data.data() + sizeof(brd::es::ESTicket) + tik->sectHdrs[i].sectOfst.unwrap());
for (size_t j = 0; j < tik->sectHdrs[i].nRecords.unwrap(); j++)
{
tc::bn::bitarray<0x80>* access_mask = (tc::bn::bitarray<0x80>*)content_records[j].accessMask.data();
for (size_t bit = 0; bit < access_mask->bit_size(); bit++)
{
if (access_mask->test(bit))
{
this->enabled_content.set(content_records[j].offset.unwrap() + bit);
}
}
}
}
}
}
@@ -0,0 +1,143 @@
#include <ntd/n3ds/es/TitleMetaData.h>
#include <fmt/core.h>
#include <brd/es/es_tmd.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
ntd::n3ds::es::TitleMetaDataDeserialiser::TitleMetaDataDeserialiser(const std::shared_ptr<tc::io::IStream>& tmd_stream) :
TitleMetaData(),
mModuleLabel("ntd::n3ds::es::TitleMetaDataDeserialiser")
{
if (tmd_stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "TMD stream was null.");
}
if (tmd_stream->length() < (sizeof(brd::es::ESV1TitleMeta) + sizeof(brd::es::ESV1ContentMeta)))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD was too small.");
}
// import TMD v1 data (less the ESV1ContentMeta data)
tc::ByteData tmd_data = tc::ByteData(sizeof(brd::es::ESV1TitleMeta));
tmd_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tmd_stream->read(tmd_data.data(), tmd_data.size()) < tmd_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected size after reading.");
}
// get pointer
brd::es::ESV1TitleMeta* tmd = (brd::es::ESV1TitleMeta*)tmd_data.data();
if (tmd->sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected signature type.");
}
if (tmd->head.version != 1)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected format version.");
}
/*
std::cout << "[Tmd]" << std::endl;
std::cout << " > Issuer: " << tmd->sig.issuer.data() << std::endl;
std::cout << " > Version: " << (int)tmd->head.version << std::endl;
std::cout << " > CACrl Version: " << (int)tmd->head.caCrlVersion << std::endl;
std::cout << " > SignerCrl Version: " << (int)tmd->head.signerCrlVersion << std::endl;
std::cout << " > TitleId: " << std::hex << std::setfill('0') << std::setw(16) << tmd->head.titleId.unwrap() << std::endl;
*/
// determine if the TMD is the correct size given expected number of ESV1ContentMeta entries
size_t cmd_table_num = tmd->v1Head.cmdGroups[0].nCmds.unwrap();
size_t tmd_full_size = sizeof(brd::es::ESV1TitleMeta) + (cmd_table_num * sizeof(brd::es::ESV1ContentMeta));
if (tmd_stream->length() < (tmd_full_size))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD was too small.");
}
// import full TMD v1 data
tmd_data = tc::ByteData(tmd_full_size);
tmd_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tmd_stream->read(tmd_data.data(), tmd_data.size()) < tmd_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected size after reading.");
}
// update tmd pointer
tmd = (brd::es::ESV1TitleMeta*)tmd_data.data();
// hash for staged validiation
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
// calculate hash for optional signature validation later
tc::crypto::GenerateSha256Hash(calculated_hash.data(), (byte_t*)&tmd->sig.issuer, (size_t)((byte_t*)&tmd->v1Head.cmdGroups - (byte_t*)&tmd->sig.issuer));
// verify v1 ESV1ContentMetaGroup array using hash in v1 header
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->v1Head.cmdGroups, sizeof(tmd->v1Head.cmdGroups));
if (memcmp(hash.data(), tmd->v1Head.hash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had invalid CMD group hash.");
}
// verify ESV1ContentMeta array
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->contents, cmd_table_num * sizeof(brd::es::ESV1ContentMeta));
if (memcmp(hash.data(), tmd->v1Head.cmdGroups[0].groupHash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had invalid CMD group[0] hash.");
}
// verify other fields
if (tmd->head.sysVersion.unwrap() != 0)
{
throw tc::InvalidOperationException(mModuleLabel, "TMD sysVersion had unexpected value.");
}
if (tmd->head.type.unwrap() != brd::es::ESTitleType::CT_TITLE)
{
throw tc::InvalidOperationException(mModuleLabel, "TMD type had unexpected value.");
}
// process header now that it is verified
this->signature.sig_type = tmd->sig.sigType.unwrap();
this->signature.sig = tc::ByteData(tmd->sig.sig.data(), tmd->sig.sig.size());
this->signature.issuer = tmd->sig.issuer.decode();
this->title_id = tmd->head.titleId.unwrap();
this->title_version = tmd->head.titleVersion.unwrap();
struct TmdCustomDataForCtr
{
struct TwlCustomData
{
tc::bn::le32<uint32_t> public_save_data_size;
tc::bn::le32<uint32_t> private_save_data_size;
tc::bn::pad<4> padding;
byte_t flag;
};
struct CtrCustomData
{
tc::bn::le32<uint32_t> save_data_size;
tc::bn::bitarray<1> flag;
};
union {
TwlCustomData twl;
CtrCustomData ctr;
};
};
auto custom_data = (TmdCustomDataForCtr*)tmd->head.customData.data();
this->twl_custom_data.public_save_data_size = custom_data->twl.public_save_data_size;
this->twl_custom_data.private_save_data_size = custom_data->twl.private_save_data_size;
this->twl_custom_data.flag = custom_data->twl.flag;
this->ctr_custom_data.save_data_size = custom_data->ctr.save_data_size;
this->ctr_custom_data.is_snake_only = custom_data->ctr.flag.test(0);
// process ESV1ContentMeta entries
for (size_t i = 0; i < cmd_table_num; i++)
{
this->content_info.push_back(ContentInfo(
tmd->contents[i].cid.unwrap(),
tmd->contents[i].index.unwrap(),
(tmd->contents[i].type.unwrap() & (uint16_t)brd::es::ESContentType_ENCRYPTED) == (uint16_t)brd::es::ESContentType_ENCRYPTED,
(tmd->contents[i].type.unwrap() & (uint16_t)brd::es::ESContentType_OPTIONAL) == (uint16_t)brd::es::ESContentType_OPTIONAL,
(int64_t)tmd->contents[i].size.unwrap(),
tmd->contents[i].hash));
}
}