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,26 @@
#pragma once
// definitions
#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/exheader.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>
// Wrapped IStream
#include <ntd/n3ds/IvfcStream.h>
// Utilities
#include <ntd/n3ds/CtrKeyGenerator.h>
// VirtualFileSystem metadata generators
#include <ntd/n3ds/ExeFsMetaGenerator.h>
#include <ntd/n3ds/RomFsMetaGenerator.h>
#include <ntd/n3ds/CciFsMetaGenerator.h>
#include <ntd/n3ds/CiaFsMetaGenerator.h>
@@ -0,0 +1,19 @@
#pragma once
#include <tc/io/VirtualFileSystem.h>
namespace ntd { namespace n3ds {
struct CciFsShapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
CciFsShapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream);
private:
CciFsShapshotGenerator();
std::shared_ptr<tc::io::IStream> mBaseStream;
size_t mCurDir;
void addFile(const std::string& name, int64_t offset, int64_t size);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,19 @@
#pragma once
#include <tc/io/VirtualFileSystem.h>
namespace ntd { namespace n3ds {
struct CiaFsSnapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
CiaFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream);
private:
CiaFsSnapshotGenerator();
std::shared_ptr<tc::io::IStream> mBaseStream;
size_t mCurDir;
void addFile(const std::string& name, int64_t offset, int64_t size);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,19 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
class CtrKeyGenerator
{
public:
static void GenerateKey(const uint8_t *x, const uint8_t *y, uint8_t *key);
private:
static int32_t wrap_index(int32_t i);
static void n128_rrot(const uint8_t *in, uint32_t rot, uint8_t *out);
static void n128_lrot(const uint8_t *in, uint32_t rot, uint8_t *out);
static void n128_add(const uint8_t *a, const uint8_t *b, uint8_t *out);
static void n128_sub(const uint8_t *a, const uint8_t *b, uint8_t *out);
static void n128_xor(const uint8_t *a, const uint8_t *b, uint8_t *out);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,14 @@
#pragma once
#include <tc/io/VirtualFileSystem.h>
namespace ntd { namespace n3ds {
struct ExeFsSnapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
ExeFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream, bool verify_hashes = true);
private:
ExeFsSnapshotGenerator();
};
}} // namespace ntd::n3ds
@@ -0,0 +1,141 @@
#pragma once
#include <tc/ByteData.h>
#include <tc/io/IStream.h>
#include <tc/io/IOUtil.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/crypto/CryptoException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/ObjectDisposedException.h>
namespace ntd { namespace n3ds {
class IvfcStream : public tc::io::IStream
{
public:
IvfcStream();
IvfcStream(const std::shared_ptr<tc::io::IStream>& stream);
/**
* @brief Indicates whether the current stream supports reading.
**/
bool canRead() const;
/**
* @brief Indicates whether the current stream supports writing.
**/
bool canWrite() const;
/**
* @brief Indicates whether the current stream supports seeking.
**/
bool canSeek() const;
/**
* @brief Gets the length in bytes of the stream.
**/
int64_t length();
/**
* @brief Gets the position within the current stream.
*
* @return This is returns the current position within the stream.
**/
int64_t position();
/**
* @brief Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
*
* @param[out] ptr Pointer to an array of bytes. When this method returns, @p ptr contains the specified byte array with the values between 0 and (@p count - 1) replaced by the bytes read from the current source.
* @param[in] count The maximum number of bytes to be read from the current stream.
*
* @return The total number of bytes read into @p ptr. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
*
* @pre A stream must support reading for @ref read to work.
* @note Use @ref canRead to determine if this stream supports reading.
* @note Exceptions thrown by the base stream are not altered/intercepted, refer to that module's documentation for those exceptions.
*
* @throw tc::ArgumentOutOfRangeException @p count exceeds the length of readable data in the sub stream.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
size_t read(byte_t* ptr, size_t count);
/**
* @brief Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
*
* @param[in] ptr Pointer to an array of bytes. This method copies @p count bytes from @p ptr to the current stream.
* @param[in] count The number of bytes to be written to the current stream.
*
* @return The total number of bytes written to the stream. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
*
* @pre A stream must support writing for @ref write to work.
* @note Use @ref canWrite to determine if this stream supports writing.
* @note Exceptions thrown by the base stream are not altered/intercepted, refer to that module's documentation for those exceptions.
*
* @throw tc::ArgumentOutOfRangeException @p count exceeds the length of writeable data in the sub stream.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
size_t write(const byte_t* ptr, size_t count);
/**
* @brief Sets the position within the current stream.
*
* @param[in] offset A byte offset relative to the origin parameter.
* @param[in] origin A value of type @ref tc::io::SeekOrigin indicating the reference point used to obtain the new position.
*
* @return The new position within the current stream.
*
* @pre A stream must support seeking for @ref seek to work.
* @note Use @ref canSeek to determine if this stream supports seeking.
* @note Exceptions thrown by the base stream are not altered/intercepted, refer to that module's documentation for those exceptions.
*
* @throw tc::ArgumentOutOfRangeException @p origin contains an invalid value.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
int64_t seek(int64_t offset, tc::io::SeekOrigin origin);
/**
* @brief Sets the length of the current stream. This is not implemented for @ref SubStream.
* @throw tc::NotImplementedException @ref setLength is not implemented for @ref SubStream
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
void setLength(int64_t length);
/**
* @brief Clears all buffers for this and the base stream and causes any buffered data to be written to the underlying device.
*
* @throw tc::io::IOException An I/O error occurs.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
void flush();
/**
* @brief Releases internal resources including base stream and clears internal state.
**/
void dispose();
private:
std::string mModuleLabel;
// base stream
std::shared_ptr<tc::io::IStream> mBaseStream;
// data layer stream
size_t mDataStreamBlockSize;
int64_t mDataStreamLogicalLength;
std::shared_ptr<tc::io::IStream> mDataStream;
inline size_t offsetToBlock(int64_t offset) { return tc::io::IOUtil::castInt64ToSize(offset / tc::io::IOUtil::castSizeToInt64(mDataStreamBlockSize)); };
inline size_t offsetInBlock(int64_t offset) { return tc::io::IOUtil::castInt64ToSize(offset % tc::io::IOUtil::castSizeToInt64(mDataStreamBlockSize)); };
inline int64_t blockToOffset(size_t block) { return tc::io::IOUtil::castSizeToInt64(block) * tc::io::IOUtil::castSizeToInt64(mDataStreamBlockSize); };
// hash cache for data layer
tc::ByteData mHashCache;
inline byte_t* getBlockHash(size_t begin_block) { return mHashCache.data() + (begin_block * tc::crypto::Sha256Generator::kHashSize); }
// hash calc temp
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> mHash;
tc::crypto::Sha256Generator mHashCalc;
bool validateLayerBlocksWithHashLayer(const byte_t* layer, size_t block_size, size_t block_num, const byte_t* hash_layer);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,30 @@
#pragma once
#include <tc/ByteData.h>
#include <tc/io/VirtualFileSystem.h>
#include <ntd/n3ds/romfs.h>
namespace ntd { namespace n3ds {
struct RomFsSnapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
RomFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream);
private:
RomFsSnapshotGenerator();
std::shared_ptr<tc::io::IStream> mBaseStream;
int64_t mDataOffset;
tc::ByteData mDirEntryTable;
std::map<uint32_t, size_t> mDirParentVaddrMap;
inline ntd::n3ds::RomFsDirectoryEntry* getDirEntry(uint32_t vaddr) { return (ntd::n3ds::RomFsDirectoryEntry*)(mDirEntryTable.data() + vaddr); }
tc::ByteData mFileEntryTable;
inline ntd::n3ds::RomFsFileEntry* getFileEntry(uint32_t vaddr) { return (ntd::n3ds::RomFsFileEntry*)(mFileEntryTable.data() + vaddr); }
void addFile(const ntd::n3ds::RomFsFileEntry* file_entry, size_t parent_dir);
void addDirectory(const ntd::n3ds::RomFsDirectoryEntry* dir_entry, size_t parent_dir);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,212 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
namespace bcwav {
static const uint32_t kCwavMagic = tc::bn::make_struct_magic_uint32("CWAV");
static const uint32_t kCwavVersion = 0x02010000;
static const uint32_t kInfoMagic = tc::bn::make_struct_magic_uint32("INFO");
static const uint32_t kDataMagic = tc::bn::make_struct_magic_uint32("DATA");
enum TypeId
{
TYPE_ID_DSP_ADPCM = 0x300,
TYPE_ID_IMA_ADPCM = 0x301,
TYPE_ID_INFO_BLOCK = 0x7000,
TYPE_ID_DATA_BLOCK = 0x7001,
TYPE_ID_CHANNEL_REF = 0x7100,
};
enum Encoding
{
ENCODING_PCM8 = 0,
ENCODING_PCM16 = 1,
ENCODING_DSP_ADPCM = 2,
ENCODING_IMA_ADPCM = 3
};
enum ChannelIndex
{
CHANNEL_INDEX_L = 0,
CHANNEL_INDEX_R = 1,
};
#pragma pack(push,1)
struct Reference
{
// 0x00
tc::bn::le16<uint16_t> type_id;
// 0x02
tc::bn::pad<2> padding;
// 0x04
tc::bn::le32<uint32_t> offset;
// 0x08
};
struct ReferenceWithSize : public Reference
{
// 0x08
tc::bn::le32<uint32_t> size;
// 0x0C
};
template <typename T>
struct Table
{
// 0x00
tc::bn::le32<uint32_t> count;
// 0x04
T item[];
};
struct FileHeader
{
// 0x00
tc::bn::le32<uint32_t> signature; // 'CWAV'
// 0x04
tc::bn::le16<uint16_t> byte_order_mark; // 0xfeff (always little endian)
// 0x06
tc::bn::le16<uint16_t> header_size;
// 0x08
tc::bn::le32<uint32_t> version; // 2.1.0.0 (0x02010000)
// 0x0C
tc::bn::le32<uint32_t> file_size;
// 0x10
tc::bn::le16<uint16_t> data_blocks;
// 0x12
tc::bn::pad<2> reserved;
// 0x14
};
struct BlockInfo
{
// 0x00
ReferenceWithSize info_block_reference;
// 0x0C
ReferenceWithSize data_block_reference;
// 0x18
};
struct FileInfo
{
// 0x00
FileHeader header;
// 0x14
BlockInfo block_info;
// 0x2C
};
struct BlockHeader
{
// 0x00
tc::bn::le32<uint32_t> kind; // 'INFO' or 'DATA'
// 0x04
tc::bn::le32<uint32_t> size;
// 0x08
};
struct WaveInfo
{
// 0x00
byte_t encoding; // see Encoding
// 0x01
byte_t is_loop; // 0: noloop, 1: loop
// 0x02
tc::bn::pad<2> padding;
// 0x04
tc::bn::le32<uint32_t> sample_rate;
// 0x08
tc::bn::le32<uint32_t> loop_start_frame;
// 0x0C
tc::bn::le32<uint32_t> loop_end_frame;
// 0x10
};
struct InfoBlockBody
{
// 0x00
WaveInfo wave_info;
// 0x10
tc::bn::pad<4> reserved;
// 0x14
Table<Reference> channel_info_reference_table;
// 0x18
};
struct ChannelInfo
{
// 0x00
Reference to_samples;
// 0x08
Reference to_adpcm_info;
// 0x10
tc::bn::pad<4> reserved;
// 0x14
};
struct AdpcmParam
{
// 0x00
std::array<tc::bn::le16<uint16_t>, 16> coef;
// 0x20
};
struct AdpcmContext
{
// 0x00
tc::bn::le16<uint16_t> pred_scale; // Stores the predicted value (4bit) and the scale value (4bit) of Adpcm. The upper 8 bits are not referenced
// 0x02
tc::bn::le16<int16_t> yn1; // Historical data (1st sample value)
// 0x04
tc::bn::le16<int16_t> yn2; // Historical data (2nd sample value)
// 0x06
};
struct DspAdpcmInfo
{
// 0x00
AdpcmParam param;
// 0x20
AdpcmContext context;
// 0x26
AdpcmContext loop_context;
// 0x2C
};
struct ImaAdpcmContext
{
// 0x00
tc::bn::le16<uint16_t> data;
// 0x02
byte_t table_index;
// 0x03
byte_t padding;
// 0x04
};
struct ImaAdpcmInfo
{
// 0x00
ImaAdpcmContext context;
// 0x04
ImaAdpcmContext loop_context;
// 0x08
};
struct InfoBlock
{
// 0x00
BlockHeader header;
// 0x08
InfoBlockBody body;
// 0x20
};
#pragma pack(pop)
} // namespace ntd::n3ds::bcwav
}} // namespace ntd::n3ds
@@ -0,0 +1,260 @@
#pragma once
#include <tc/types.h>
#include <array>
#include <ntd/n3ds/ncch.h>
namespace ntd { namespace n3ds {
#pragma pack(push,8)
/**
* @struct NcsdCommonHeader
* @brief NCSD header is used in both NAND storage and Gamecard Images
*/
struct NcsdCommonHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("NCSD");
static const size_t kPartitionNum = 8;
enum PartitionFsType
{
PartitionFsType_None = 0,
PartitionFsType_Normal = 1,
PartitionFsType_FIRM = 3,
PartitionFsType_AGBSave = 4,
};
enum PartitionCryptoType
{
PartitionCryptoType_None = 0x00,
PartitionCryptoType_TWL = 0x01,
PartitionCryptoType_CTR = 0x02,
PartitionCryptoType_SNAKE = 0x03,
};
enum MediaPlatform : byte_t
{
MediaPlatform_CTR = 0,
MediaPlatform_SNAKE = 1,
};
enum MediaType : byte_t
{
MediaType_InnerDevice = 0,
MediaType_Card1 = 1,
MediaType_Card2 = 2,
MediaType_ExtendedDevice = 3,
};
enum FlagIndex
{
FlagIndex_Platform = 4,
FlagIndex_Type = 5,
FlagIndex_BlockSizeLog = 6,
};
struct NcsdFlags
{
tc::bn::pad<4> reserved;
tc::bn::bitarray<1> media_platform;
byte_t media_type;
byte_t block_size_log; // this flag determines the block_size = 1 << (block_size_log + 9), alternatively this is always 0, and the blocksize is always 0x200
tc::bn::bitarray<1> reserved_07;
// raw byte access
byte_t& operator[](size_t index) { return ((byte_t*)this)[index]; }
const byte_t& operator[](size_t index) const { return ((const byte_t*)this)[index]; }
const byte_t* data() const { return ((const byte_t*)this); }
const size_t size() const { return sizeof(uint64_t); }
};
static_assert(sizeof(NcsdFlags) == 8, "NcsdFlags had incorrect size.");
struct OffsetSize
{
tc::bn::le32<uint32_t> blk_offset;
tc::bn::le32<uint32_t> blk_size;
};
static_assert(sizeof(OffsetSize) == 8, "OffsetSize had incorrect size.");
struct GameCardExtendedHeader
{
// 0x90
std::array<tc::bn::le64<uint64_t>, kPartitionNum> partition_id;
// 0xD0
tc::bn::pad<0x30> reserved;
};
struct NandExtendedHeader
{
// 0x90
tc::bn::pad<0x70> reserved;
};
// 0x00
tc::bn::le32<uint32_t> struct_magic; // NCSD
tc::bn::le32<uint32_t> image_blk_size;
tc::bn::le64<uint64_t> title_id;
// 0x10
std::array<byte_t, kPartitionNum> partition_fs_type;
std::array<byte_t, kPartitionNum> partition_crypto_type;
// 0x20
std::array<OffsetSize, kPartitionNum> partition_offsetsize;
// 0x60
/*
std::array<byte_t, 0x20> extended_header_hash; // unused
// 0x80
tc::bn::le32<uint32_t> additional_header_size; // unused
tc::bn::le32<uint32_t> sector0_offset; // unused
*/
tc::bn::pad<0x28> reserved_0x60;
// 0x88
NcsdFlags flags;
// 0x90
union {
GameCardExtendedHeader card_ext;
NandExtendedHeader nand_ext;
};
};
static_assert(sizeof(NcsdCommonHeader) == 0x100, "NcsdCommonHeader had incorrect size.");
struct CciHeader
{
enum CardDevice : byte_t
{
CardDevice_Unspecified = 0,
CardDevice_NorFlash = 1,
CardDevice_None = 2,
CardDevice_BT = 3,
};
enum PartitionIndex : byte_t
{
PartitionIndex_Application = 0,
PartitionIndex_Manual = 1,
PartitionIndex_DlpChild = 2,
PartitionIndex_SnakeCup = 6,
PartitionIndex_CtrCup = 7,
};
enum RomSize : uint32_t
{
RomSize_128MB = 0x40000,
RomSize_256MB = 0x80000,
RomSize_512MB = 0x100000,
RomSize_1GB = 0x200000,
RomSize_2GB = 0x400000,
RomSize_4GB = 0x800000,
};
enum NcsdFlagIndex
{
NcsdFlagIndex_BackupWriteWaitTime = 0,
NcsdFlagIndex_BackupSecurityVersion = 1,
NcsdFlagIndex_CardInfo = 2,
NcsdFlagIndex_CardDevice = 3,
NcsdFlagIndex_MediaPlatform = 4,
NcsdFlagIndex_MediaType = 5,
NcsdFlagIndex_MediaBlockSize = 6,
NcsdFlagIndex_CardDevice_Deprecated = 7,
};
enum CardType
{
CardType_S1 = 0,
CardType_S2 = 1,
};
enum CryptoType
{
CryptoType_Secure0 = 0, // Secure initial data key (keyX bootrom, keyY initial data seed) (used in production ROMs)
CryptoType_Secure1 = 1, // Secure initial data key (keyX bootrom, keyY initial data seed) (used in production ROMs)
CryptoType_Secure2 = 2, // Secure initial data key (keyX bootrom, keyY initial data seed) (used in production ROMs)
CryptoType_FixedKey = 3, // Zeros initial data key (used in non-HSM enviroments like development)
};
struct CardInfo
{
struct Flag
{
byte_t reserved : 5;
byte_t card_type : 1; // see CardType
byte_t crypto_type : 2; // see CryptoType
};
tc::bn::le32<uint32_t> writable_region; // offset in blocks
tc::bn::pad<3> padding;
Flag flag;
tc::bn::pad<0xf8> reserved;
};
static_assert(sizeof(CardInfo) == 0x100, "CardInfo had incorrect size.");
struct MasteringMetadata
{
tc::bn::le64<uint64_t> media_size_used;
tc::bn::pad<0x8> padding0;
tc::bn::le16<uint16_t> title_version;
tc::bn::le16<uint16_t> card_revision;
tc::bn::pad<0xc> padding1;
tc::bn::le64<uint64_t> cver_title_id;
tc::bn::le16<uint16_t> cver_title_version;
tc::bn::pad<0x6> padding2;
tc::bn::pad<0xd0> reserved;
};
static_assert(sizeof(MasteringMetadata) == 0x100, "MasteringMetadata had incorrect size.");
struct InitialData
{
std::array<byte_t, 16> key_source;
std::array<byte_t, 16> encrypted_title_key;
std::array<byte_t, 16> mac;
std::array<byte_t, 12> nonce;
tc::bn::pad<4> padding;
tc::bn::pad<0xc0> reserved;
};
static_assert(sizeof(InitialData) == 0x100, "InitialData had incorrect size.");
struct CardDeviceInfo
{
tc::bn::pad<0x200> card_device_reserved_0;
std::array<byte_t, 16> title_key;
tc::bn::pad<0xf0> card_device_reserved_1;
};
static_assert(sizeof(InitialData) == 0x100, "InitialData had incorrect size.");
public:
// 0x0000 - 0x00ff: RSA2048-PKCS1-SHA2-256 over 0x0100-0x01ff
std::array<byte_t, 0x100> signature;
// 0x0100 - 0x01ff: NcsdCommonHeader
NcsdCommonHeader ncsd_header;
// 0x0200 - 0x02ff: CardInfo
CardInfo card_info;
// 0x0300 - 0x03ff: Mastering metadata
MasteringMetadata mastering_info;
// 0x0400 - 0x0fff: Reserved
tc::bn::pad<0xc00> reserved_00;
// 0x1000 - 0x10ff: Initial Data
InitialData initial_data;
// 0x1100 - 0x11ff: Partition0 NcchCommonHeader
NcchCommonHeader ncch_header;
// 0x1200 - 0x14ff: Contains decrypted titlekey for programming cards
CardDeviceInfo card_device_info;
// 0x1500 - 0x3fff: Reserved
tc::bn::pad<0x2B00> reserved_01;
};
static_assert(sizeof(CciHeader) == 0x4000, "CciHeader had incorrect size.");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,61 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct CiaHeader
{
static const size_t kCiaMaxContentNum = 0x10000;
static const size_t kCiaSectionAlignment = 64;
static const size_t kCiaContentAlignment = 16;
enum Type : uint16_t
{
Type_Normal = 0x0
};
enum FormatVersion : uint16_t
{
FormatVersion_Default = 0x0,
FormatVersion_SimpleCia = 0xFF,
};
tc::bn::le32<uint32_t> header_size;
tc::bn::le16<uint16_t> type;
tc::bn::le16<uint16_t> format_version;
tc::bn::le32<uint32_t> certificate_size;
tc::bn::le32<uint32_t> ticket_size;
tc::bn::le32<uint32_t> tmd_size;
tc::bn::le32<uint32_t> footer_size;
tc::bn::le64<uint64_t> content_size;
tc::bn::bitarray<kCiaMaxContentNum/8, true, false> content_bitarray;
//std::array<byte_t, kCiaMaxContentNum/8> content_bitarray;
};
static_assert(sizeof(CiaHeader) == 0x2020, "CiaHeader had invalid size");
/**
* @struct CiaFooter
* @brief This is an optional section of a CIA file that includes "Lot Check" metadata for CTR Titles (Not TWL titles).
*
* @details
* The CIA footer contains the following metadata
* * List of title dependencies (titleids)
* * The CoreVersion (titleid lower of the target firmware)
* * (Optionally) SystemMenuData blob
*/
struct CiaFooter
{
static const size_t kMaxDependencyNum = 48;
std::array<tc::bn::le64<uint64_t>, kMaxDependencyNum> dependency_list;
tc::bn::pad<0x180> padding0;
tc::bn::le32<uint32_t> firmware_title_id_lower; // aka "core version"
tc::bn::pad<0xfc> padding1;
};
static_assert(sizeof(CiaFooter) == 0x400, "CiaFooter had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,160 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct CroHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("CRO0");
struct SectionEntry
{
tc::bn::le32<uint32_t> offset;
union
{
tc::bn::le32<uint32_t> size;
tc::bn::le32<uint32_t> num;
};
};
static_assert(sizeof(SectionEntry) == 0x8, "SectionEntry had invalid size");
using Hash = std::array<byte_t, 0x20>;
enum CoreFunctionIndex
{
CoreFunctionIndex_nnroControlObject_ = 0, // 0xFFFFFFFF in CRS
CoreFunctionIndex_OnLoad = 1, // will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.
CoreFunctionIndex_OnExit = 2, // will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.
CoreFunctionIndex_OnUnresolved = 3, // will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.
};
enum SectionIndex
{
SectionIndex_Code = 0,
SectionIndex_Data = 1,
SectionIndex_ModuleName = 2,
SectionIndex_SegmentTable = 3, // (size = num*12)
SectionIndex_NamedExportTable = 4, // (size = num * 8)
SectionIndex_IndexedExportTable = 5, // (size = num * 4)
SectionIndex_ExportStrings = 6,
SectionIndex_ExportTree = 7, // fast lookups based on a trie-like structure, (size = num * 8)
SectionIndex_ImportModuleTable = 8, // (size = num * 20)
SectionIndex_ImportPatches = 9, // (size = num * 12)
SectionIndex_NamedImportTable = 10, // (size = num * 8)
SectionIndex_IndexedImportTable = 11, // (size = num * 8)
SectionIndex_AnonymousImportTable = 12, // (size = num * 8)
SectionIndex_ImportStrings = 13,
SectionIndex_Unk14 = 14,
SectionIndex_RelocationPatches = 15, // (size = num * 12)
SectionIndex_Unk16 = 16
};
std::array<Hash, 4> hash_table;
tc::bn::le32<uint32_t> struct_magic;
tc::bn::le32<uint32_t> name_offset;
tc::bn::le32<uint32_t> node0; // Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)
tc::bn::le32<uint32_t> node1; // Previous loaded CRO pointer, set by RO during loading
tc::bn::le32<uint32_t> file_size;
tc::bn::le32<uint32_t> bss_size;
tc::bn::le32<uint32_t> unk0;
tc::bn::le32<uint32_t> unk1;
std::array<tc::bn::le32<uint32_t>, 4> core_function_segment_offset;
std::array<SectionEntry, 17> section;
};
static_assert(sizeof(CroHeader) == 0x138, "CroHeader had invalid size");
struct CroSegmentOffset
{
uint32_t segment_index : 4; // Segment index for table
uint32_t segment_offset : 28; // Offset into segment
};
static_assert(sizeof(CroSegmentOffset) == 0x4, "CroSegmentOffset had invalid size");
struct CroSegmentTableEntry
{
enum SegmentId : uint32_t
{
SegmentId_Text = 0,
SegmentId_RoData = 1,
SegmentId_Data = 2,
SegmentId_Bss = 4
};
tc::bn::le32<uint32_t> segment_offset;
tc::bn::le32<uint32_t> segment_size;
tc::bn::le32<SegmentId> segment_id;
};
static_assert(sizeof(CroSegmentTableEntry) == 0xC, "CroSegmentTableEntry had invalid size");
struct CroNamedExportTableEntry
{
tc::bn::le32<uint32_t> name_offset;
tc::bn::le32<CroSegmentOffset> segment_offset_for_export;
};
static_assert(sizeof(CroNamedExportTableEntry) == 0x8, "CroNamedExportTableEntry had invalid size");
struct CroIndexedExportTableEntry
{
tc::bn::le32<CroSegmentOffset> segment_offset_for_export;
};
static_assert(sizeof(CroIndexedExportTableEntry) == 0x4, "CroIndexedExportTableEntry had invalid size");
struct CroNamedImportTableEntry
{
tc::bn::le32<uint32_t> name_offset;
tc::bn::le32<uint32_t> import_patch_list_offset; // Offset of the head of a linear list that contains the patches for this import
};
static_assert(sizeof(CroNamedImportTableEntry) == 0x8, "CroNamedImportTableEntry had invalid size");
struct CroIndexedImportTableEntry
{
tc::bn::le32<uint32_t> export_symbol_index; // index of the export symbol
tc::bn::le32<uint32_t> import_patch_list_offset; // Offset of the head of a linear list that contains the patches for this import
};
static_assert(sizeof(CroIndexedImportTableEntry) == 0x8, "CroIndexedImportTableEntry had invalid size");
struct CroAnonynousImportTableEntry
{
tc::bn::le32<CroSegmentOffset> export_symbol_segment_offset;
tc::bn::le32<uint32_t> import_patch_list_offset; // Offset of the head of a linear list that contains the patches for this import
};
static_assert(sizeof(CroAnonynousImportTableEntry) == 0x8, "CroAnonynousImportTableEntry had invalid size");
struct CroImportModuleTableEntry
{
tc::bn::le32<uint32_t> module_name_offset;
tc::bn::le32<uint32_t> indexed_import_num;
tc::bn::le32<uint32_t> indexed_import_patch_list_offset; // Offset of the head of a sub list in Indexed Import Table
tc::bn::le32<uint32_t> anonynous_import_num;
tc::bn::le32<uint32_t> anonynous_import_patch_list_offset; // Offset of the head of a sub list in Anonymous Import Table
};
static_assert(sizeof(CroImportModuleTableEntry) == 0x14, "CroImportModuleTableEntry had invalid size");
struct CroPatchEntry
{
enum PatchType : byte_t
{
PatchType_Ignore = 0,
PatchType_WriteU32Absolute = 2, // (base+addend)
PatchType_WriteU32Relative = 3, // (base+addend-in_ptr)
PatchType_ThumbBranch = 10,
PatchType_ARM32Branch = 28,
PatchType_ModifyARM32BranchOffset = 29,
PatchType_WriteU32Absolute_2 = 38, // duplicate of PatchType_WriteU32Absolute
PatchType_WriteU32Relative_2 = 42, // (((signed int)base*2)/2+addend-in_ptr), otherwise err) (This is apparently a subset of relocation type for ARM ELF)
};
tc::bn::le32<CroSegmentOffset> segment_offset_for_output;
PatchType patch_type;
byte_t unk0; // For import patches, non-zero if last entry; for relocation patches, this is the referred segment index
byte_t unk1; // For import patches, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for relocation patches
byte_t unk2; // Unknown (padding?)
tc::bn::le32<uint32_t> addend;
};
static_assert(sizeof(CroPatchEntry) == 0xC, "CroPatchEntry had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,48 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct CrrHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("CRR0");
struct Certificate
{
tc::bn::le32<uint32_t> unique_id_mask;
tc::bn::le32<uint32_t> unique_id_pattern;
tc::bn::pad<0x18> reserved0;
std::array<byte_t, 0x100> crr_body_public_key;
std::array<byte_t, 0x100> signature; // PKCS1-RSA2048-SHA2-256 over 0x000-0x11f
};
static_assert(sizeof(Certificate) == 0x220, "Certificate had invalid size");
tc::bn::le32<uint32_t> struct_magic;
tc::bn::pad<4> reserved0;
tc::bn::le32<uint32_t> node0; // prev CRR (0 in file, set by RO module when loaded in memory)
tc::bn::le32<uint32_t> node1; // next CRR (0 in file, set by RO module when loaded in memory)
tc::bn::le32<int32_t> debug_info_offset;
tc::bn::le32<int32_t> debug_info_size;
tc::bn::pad<8> reserved1;
Certificate body_certificate;
};
static_assert(sizeof(CrrHeader) == 0x240, "CrrHeader had invalid size");
struct CrrBodyHeader
{
std::array<byte_t, 0x100> signature; // PKCS1-RSA2048-SHA2-256 over 0x000 - end of hashes (according to 3dbrew a fixed size of 0x358 bytes). CRR0 files must be stored under "romfs:/.crr/". The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.
tc::bn::le32<uint32_t> unique_id;
tc::bn::le32<uint32_t> size;
tc::bn::pad<8> reserved0;
tc::bn::le32<uint32_t> hash_offset;
tc::bn::le32<uint32_t> num_hash;
tc::bn::le32<uint32_t> module_id_offset;
tc::bn::le32<uint32_t> module_id_size;
};
static_assert(sizeof(CrrBodyHeader) == 0x120, "CrrBodyHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,5 @@
#pragma once
//#include <ntd/n3ds/es/Certficate.h>
#include <ntd/n3ds/es/Ticket.h>
#include <ntd/n3ds/es/TitleMetaData.h>
@@ -0,0 +1,117 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <tc/crypto/RsaKey.h>
#include <brd/es/es_cert.h>
#include <ntd/n3ds/es/Signature.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
/**
* @brief Get total size of certificate, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Size in bytes of certificate data, 0 if invalid data.
*/
size_t getCertificateSize(byte_t* data);
/**
* @brief Get total size of certificate signature structure, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Size in bytes of certificate signature structure, 0 if invalid data.
*/
size_t getCertificateSignatureSize(byte_t* data);
/**
* @brief Get offset of certificate signed data, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Offset in bytes of signed certificate data, 0 if invalid data.
*/
size_t getCertificateSignableOffset(byte_t* data);
/**
* @brief Get size of certificate signed data, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Size in bytes of signed certificate data, 0 if invalid data.
*/
size_t getCertificateSignableSize(byte_t* data);
/**
* @brief Get pointer to certificate header, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Void pointer to certificate header. See @ref ntd::es::ESCertHeader.
*/
void* getCertificateHeaderPtr(byte_t* data);
/**
* @brief Get pointer to certificate public key, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Void pointer to certificate public. See @ref ntd::es::ESCertRsa4096PublicKey @ref ntd::es::ESCertRsa2048PublicKey @ref ntd::es::ESCertEcc233PublicKey.
*/
void* getCertificatePublicKeyPtr(byte_t* data);
struct Certificate
{
public:
Certificate() :
signature(),
subject(),
date(),
public_key_type(),
rsa4096_public_key(),
rsa2048_public_key(),
ecc233_public_key()
{
memset(calculated_hash.data(), 0, calculated_hash.size());
}
public:
// these fields are only used when deserialised
ntd::n3ds::es::Signature signature; // This includes the signature type, signature data, and issuer
std::array<byte_t, 32> calculated_hash; // This hash is calculated when deserialised so that signature validation can be performed.
// these fields are used in both deserialisation & serialisation
std::string subject;
uint32_t date; // 32bit unix timestamp, not always set
// public key
brd::es::ESCertPubKeyType public_key_type;
brd::es::Rsa4096PublicKey rsa4096_public_key;
brd::es::Rsa2048PublicKey rsa2048_public_key;
brd::es::Ecc233PublicKey ecc233_public_key;
};
class CertificateDeserialiser : public Certificate
{
public:
// cert stream
CertificateDeserialiser(const std::shared_ptr<tc::io::IStream>& cert_stream);
private:
std::string mModuleLabel;
};
/*
class CertificateSerialiser : public tc::io::IStream
{
public:
CertificateSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
@@ -0,0 +1,18 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <brd/es/es_sign.h>
namespace ntd { namespace n3ds { namespace es {
class ISigner
{
public:
virtual ~ISigner() = default;
virtual const std::string& getIssuer() = 0;
virtual brd::es::ESSigType getSigType() = 0;
virtual bool signHash(const byte_t* hash, byte_t* signature) = 0;
virtual bool verifyHash(const byte_t* hash, const byte_t* signature) = 0;
};
}}} // namespace ntd::n3ds::es
@@ -0,0 +1,30 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/crypto/RsaKey.h>
#include <ntd/n3ds/es/ISigner.h>
#include <tc/InvalidOperationException.h>
#include <tc/ArgumentOutOfRangeException.h>
namespace ntd { namespace n3ds { namespace es {
class RsaSigner : public ntd::n3ds::es::ISigner
{
public:
RsaSigner(brd::es::ESSigType sig_type, const std::string& issuer, const tc::crypto::RsaKey& rsa_key);
const std::string& getIssuer();
brd::es::ESSigType getSigType();
bool signHash(const byte_t* hash, byte_t* signature);
bool verifyHash(const byte_t* hash, const byte_t* signature);
private:
brd::es::ESSigType mSigType;
std::string mIssuer;
tc::crypto::RsaKey mRsaKey;
};
}}} // namespace ntd::n3ds::es
@@ -0,0 +1,50 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <tc/crypto/RsaKey.h>
#include <brd/es/es_sign.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
size_t getSignatureSizeFromSigType(brd::es::ESSigType sig_type);
size_t getSignatureIssuerOffset(brd::es::ESSigType sig_type);
struct Signature
{
public:
Signature() :
sig_type(),
sig(),
issuer()
{}
public:
brd::es::ESSigType sig_type;
tc::ByteData sig;
std::string issuer;
};
class SignatureDeserialiser : public Signature
{
public:
// input stream
SignatureDeserialiser(const std::shared_ptr<tc::io::IStream>& stream);
private:
std::string mModuleLabel;
};
/*
class SignatureSerialiser : public tc::io::IStream
{
public:
SignatureSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
@@ -0,0 +1,72 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <ntd/n3ds/es/Signature.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
struct Ticket
{
public:
Ticket() :
signature(),
ticket_id(0),
device_id(0),
title_id(0),
ticket_version(0),
license_type(0),
key_id(0),
ec_account_id(0),
launch_count(0),
enabled_content()
{
memset(calculated_hash.data(), 0, calculated_hash.size());
memset(title_key.data(), 0, title_key.size());
}
public:
// these fields are only used in deserialisation
ntd::n3ds::es::Signature signature;
std::array<byte_t, 32> calculated_hash;
// these fields are used in both deserialisation & serialisation
std::array<byte_t, 16> title_key;
uint64_t ticket_id;
uint32_t device_id;
uint64_t title_id;
uint16_t ticket_version;
byte_t license_type;
byte_t key_id;
// reserved region data
uint32_t ec_account_id;
// lp record
uint32_t launch_count; // 0 = unlimited, x = limited to x launches
std::bitset<0x10000> enabled_content;
};
class TicketDeserialiser : public Ticket
{
public:
// tik stream
TicketDeserialiser(const std::shared_ptr<tc::io::IStream>& tik_stream);
private:
std::string mModuleLabel;
};
/*
class TicketSerialiser : public tc::io::IStream
{
public:
// tmd Ticket, issuer, RsaKey
TicketSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
@@ -0,0 +1,106 @@
#pragma once
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <ntd/n3ds/es/Signature.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
struct TitleMetaData
{
public:
TitleMetaData() :
signature(),
title_id(0),
title_version(0),
content_info()
{
memset(calculated_hash.data(), 0, calculated_hash.size());
twl_custom_data.public_save_data_size = 0;
twl_custom_data.private_save_data_size = 0;
twl_custom_data.flag = 0;
ctr_custom_data.save_data_size = 0;
ctr_custom_data.is_snake_only = false;
}
struct ContentInfo
{
public:
ContentInfo() :
id(0),
index(0),
is_encrypted(0),
is_optional(0),
size(0)
{
memset(hash.data(), 0, hash.size());
}
ContentInfo(uint32_t id, uint16_t index, bool is_encrypted, bool is_optional, int64_t size, std::array<byte_t, 32>& hash) :
id(id),
index(index),
is_encrypted(is_encrypted),
is_optional(is_optional),
size(size),
hash(hash)
{
}
public:
uint32_t id;
uint16_t index;
bool is_encrypted;
bool is_optional;
int64_t size;
std::array<byte_t, 32> hash;
};
public:
// these fields are only used in deserialisation
ntd::n3ds::es::Signature signature;
std::array<byte_t, 32> calculated_hash;
// these fields are used in both deserialisation & serialisation
uint64_t title_id;
uint16_t title_version;
struct TwlCustomData
{
uint32_t public_save_data_size;
uint32_t private_save_data_size;
uint8_t flag;
} twl_custom_data;
struct CtrCustomData
{
uint32_t save_data_size;
bool is_snake_only;
} ctr_custom_data;
std::vector<ContentInfo> content_info;
};
class TitleMetaDataDeserialiser : public TitleMetaData
{
public:
// tmd stream
TitleMetaDataDeserialiser(const std::shared_ptr<tc::io::IStream>& tmd_stream);
private:
TitleMetaDataDeserialiser();
std::string mModuleLabel;
};
/*
class TitleMetaDataSerialiser : public tc::io::IStream
{
public:
// tmd TitleMetaData, issuer, RsaKey
TitleMetaDataSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
@@ -0,0 +1,33 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct ExeFsHeader
{
static const size_t kFileNum = 8;
static const size_t kExeFsSectionAlignSize = 0x200;
struct FileEntry
{
tc::bn::string<8> name;
tc::bn::le32<uint32_t> offset;
tc::bn::le32<uint32_t> size;
};
using FileHash = std::array<byte_t, 0x20>;
std::array<FileEntry, kFileNum> file_table;
tc::bn::pad<0x80> reserved;
std::array<FileHash, kFileNum> hash_table;
// inline method to get pointer to file hash, since the ordering is unintuitive
FileHash* getFileHash(size_t index) { return (index < kFileNum) ? &hash_table[kFileNum - 1 - index] : nullptr; }
};
static_assert(sizeof(ExeFsHeader) == 0x200, "ExeFsHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,317 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct SystemControlInfo
{
enum Flags
{
Flags_CompressExefsPartition0 = 0,
Flags_SdmcApplication = 1,
};
struct CodeSegmentInfo
{
tc::bn::le32<uint32_t> address;
tc::bn::le32<uint32_t> num_max_pages;
tc::bn::le32<uint32_t> code_size;
};
static_assert(sizeof(CodeSegmentInfo) == 0xC, "CodeSegmentInfo had incorrect size.");
public:
tc::bn::string<8> name;
tc::bn::pad<5> padding0;
union
{
tc::bn::bitarray<1> bitarray;
byte_t raw;
} flags;
tc::bn::le16<uint16_t> remaster_version;
CodeSegmentInfo text;
tc::bn::le32<uint32_t> stack_size;
CodeSegmentInfo rodata;
tc::bn::pad<4> padding1;
CodeSegmentInfo data;
tc::bn::le32<uint32_t> bss_size;
std::array<tc::bn::le64<uint64_t>, 0x30> dependency_list;
tc::bn::le64<uint64_t> savedata_size;
tc::bn::le64<uint64_t> jump_id;
tc::bn::pad<0x30> padding2;
};
static_assert(sizeof(SystemControlInfo) == 0x200, "SystemControlInfo had incorrect size.");
struct AccessControlInfo
{
enum CpuSpeed
{
CpuSpeed_268MHz = 0,
CpuSpeed_804MHz = 1,
};
enum SystemMode : byte_t
{
SystemMode_PROD = 0, // For Apps 64MB memory
SystemMode_DEV1 = 2, // For Apps 96MB memory
SystemMode_DEV2 = 3, // For Apps 80MB memory
SystemMode_DEV3 = 4, // For Apps 72MB memory
SystemMode_DEV4 = 5, // For Apps 32MB memory
};
enum SystemModeExt : byte_t
{
SystemModeExt_LEGACY = 0, // Use SystemMode setting
SystemModeExt_PROD = 1, // For Apps 124MB memory
SystemModeExt_DEV1 = 2, // For Apps 178MB memory
//SystemModeExt_DEV2 = 3, // Unknown App memory allocation (devunit only?, doesn't exist?)
};
enum FileSystemAccess
{
FileSystemAccess_CategorySystemApplication,
FileSystemAccess_CategoryHardwareCheck,
FileSystemAccess_CategoryFileSystemTool,
FileSystemAccess_Debug,
FileSystemAccess_TwlCardBackup,
FileSystemAccess_TwlNandData,
FileSystemAccess_Boss,
FileSystemAccess_DirectSdmc,
FileSystemAccess_Core,
FileSystemAccess_CtrNandRo,
FileSystemAccess_CtrNandRw,
FileSystemAccess_CtrNandRoWrite,
FileSystemAccess_CategorySystemSettings,
FileSystemAccess_CardBoard,
FileSystemAccess_ExportImportIvs,
FileSystemAccess_DirectSdmcWrite,
FileSystemAccess_SwitchCleanup,
FileSystemAccess_SaveDataMove,
FileSystemAccess_Shop,
FileSystemAccess_Shell,
FileSystemAccess_CategoryHomeMenu,
FileSystemAccess_ExternalSeed,
};
enum OtherAttribute
{
OtherAttribute_NotUseRomFs,
OtherAttribute_UseExtendedSavedataAccessControl,
};
enum ResourceLimitDescriptorIndex
{
ResourceLimitDescriptorIndex_MaxCpu = 0,
};
enum ResourceLimitCategory : byte_t
{
ResourceLimitCategory_Application = 0,
ResourceLimitCategory_SysApplet = 1,
ResourceLimitCategory_LibApplet = 2,
ResourceLimitCategory_Other = 3
};
struct Flags
{
uint32_t enable_l2_cache : 1; // 0=disable, 1=enable
uint32_t cpu_speed : 1; // CpuSpeed
uint32_t : 6;
uint32_t system_mode_ext : 4; // SystemModeExt
uint32_t : 4;
uint32_t ideal_processor : 2; // 0-3, default=0
uint32_t affinity_mask : 2; // 0-3, default=0
uint32_t system_mode : 4; // SystemMode
int32_t thread_priority : 8;
};
static_assert(sizeof(Flags) == sizeof(uint32_t), "Flags had incorrect size.");
// the reason for the reverse ordering is because the normal serialization of this field is: "field |= save_id", then "field = field << 20"
struct AccessibleUniqueIds
{
uint64_t save_id2 : 20;
uint64_t save_id1 : 20;
uint64_t save_id0 : 20;
uint64_t flag : 1;
uint64_t : 0;
};
static_assert(sizeof(AccessibleUniqueIds) == sizeof(uint64_t), "AccessibleUniqueIds had incorrect size.");
using ServiceName = tc::bn::string<8>; // Those char[8] server names
enum DescriptorPrefix
{
DescriptorPrefix_InterruptNumList = 3,
DescriptorPrefix_SysCallList = 4,
DescriptorPrefix_ReleaseKernelVersion = 6,
DescriptorPrefix_HandleTableSize = 7,
DescriptorPrefix_OtherCapabilities = 8,
DescriptorPrefix_MappingStatic = 9,
DescriptorPrefix_MappingIo = 11
};
static const size_t kMaxSerializableInterruptCount = 32; // number of serializable interupts
static const size_t kMaxInterruptValue = 0x80; // system maximum total interupts
static const size_t kMaxInterruptDescriptorNum = 8;
struct InterruptDescriptor
{
uint32_t interrupt_0 : 7;
uint32_t interrupt_1 : 7;
uint32_t interrupt_2 : 7;
uint32_t interrupt_3 : 7;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 3; // all ones
};
static_assert(sizeof(InterruptDescriptor) == sizeof(uint32_t), "InterruptDescriptor had incorrect size.");
static const size_t kMaxSerializableSystemCallCount = 0xC0; // this is the physical maximum storable system calls
static const size_t kMaxSystemCallValue = 0x7d; // this is the maximum value for a system call according to the system
static const size_t kMaxSystemCallDescriptorNum = 8;
struct SystemCallDescriptor
{
uint32_t systemcall_lower_bitarray : 24;
uint32_t systemcall_upper : 3;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 4; // all ones
};
static_assert(sizeof(SystemCallDescriptor) == sizeof(uint32_t), "SystemCallDescriptor had incorrect size.");
struct ReleaseKernelVersionDescriptor
{
uint32_t version : 16;
uint32_t : 9;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 6; // all ones
};
static_assert(sizeof(ReleaseKernelVersionDescriptor) == sizeof(uint32_t), "ReleaseKernelVersionDescriptor had incorrect size.");
struct HandleTableSizeDescriptor
{
uint32_t size : 16;
uint32_t : 8;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 7; // all ones
};
static_assert(sizeof(HandleTableSizeDescriptor) == sizeof(uint32_t), "HandleTableSizeDescriptor had incorrect size.");
enum MemoryType
{
MemoryType_Application = 1,
MemoryType_System = 2,
MemoryType_Base = 3
};
// KernelFlags
struct OtherCapabilitiesDescriptor
{
uint32_t permit_debug : 1;
uint32_t force_debug : 1;
uint32_t can_use_non_alphabet_and_number : 1;
uint32_t can_write_shared_page : 1;
uint32_t can_use_privileged_priority : 1;
uint32_t permit_main_function_argument : 1;
uint32_t can_share_device_memory : 1;
uint32_t runnable_on_sleep : 1;
uint32_t memory_type : 4; // MemoryType
uint32_t special_memory_layout : 1;
uint32_t can_access_core2 : 1;
uint32_t : 9;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 8; // all ones
};
static_assert(sizeof(OtherCapabilitiesDescriptor) == sizeof(uint32_t), "OtherCapabilitiesDescriptor had incorrect size.");
// MappingStaticDescriptor come in pairs: [begin_page,end_page) where begin.flag == (true: range read-only, false: range is read-write), and end.flag == (true: static mapping, false: io mapping)
struct MappingStaticDescriptor
{
uint32_t page : 20; // page2addr: addr = page << 20
uint32_t flag : 1;
uint32_t : 1;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 9; // all ones
};
static_assert(sizeof(MappingStaticDescriptor) == sizeof(uint32_t), "MappingStaticDescriptor had incorrect size.");
// MappingIODescriptor describe single page IO mappings, these are always read-write permissions. [begin_page)
// When a multi-page IO mapping is required, these is indicated with two MappingStaticDescriptor
struct MappingIODescriptor
{
uint32_t page : 20; // page2addr: addr = page << 20
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 11; // all ones
};
static_assert(sizeof(MappingIODescriptor) == sizeof(uint32_t), "MappingIODescriptor had incorrect size.");
enum Arm9Capability
{
Arm9Capability_FsMountNand = 0,
Arm9Capability_FsMountNandRoWrite = 1,
Arm9Capability_FsMountTwln = 2,
Arm9Capability_FsMountWnand = 3,
Arm9Capability_FsMountCardSpi = 4,
Arm9Capability_UseSdif3 = 5,
Arm9Capability_CreateSeed = 6,
Arm9Capability_UseCardSpi = 7,
Arm9Capability_SdApplication = 8,
Arm9Capability_UseDirectSdmc = 9,
};
public:
// begin ARM11 System
tc::bn::le64<uint64_t> program_id;
tc::bn::le32<uint32_t> core_version;
tc::bn::le32<Flags> flags;
std::array<tc::bn::le16<uint16_t>, 16> resource_limit_descriptor; // for each descriptor use, see ResourceLimitDescriptorIndex
union
{
tc::bn::le64<uint64_t> ext_savedata_id; // this field is used when other_attributes.test(USE_EXTENDED_SAVEDATA_ACCESS_CONTROL) == false
tc::bn::le64<AccessibleUniqueIds> accessible_unique_ids_1; // this field is used when other_attributes.test(USE_EXTENDED_SAVEDATA_ACCESS_CONTROL) == true
};
std::array<tc::bn::le32<uint32_t>, 2> system_savedata_id;
tc::bn::le64<AccessibleUniqueIds> accessible_unique_ids_0; // other_attributes.test(USE_EXTENDED_SAVEDATA_ACCESS_CONTROL): true = accessible_save_ids, false: other_user_save_ids
tc::bn::bitarray<7> fs_access; // FileSystemAccess
tc::bn::bitarray<1> other_attributes; // OtherAttribute
std::array<ServiceName, 34> service_access_control; // In firmware versions prior to 9.2.0(?) this array is only 32 elements long
tc::bn::pad<0xf> padding0;
byte_t resource_limit_category; // ResourceLimitCategory
// end ARM11 System
// begin ARM11 Kernel
std::array<tc::bn::le32<uint32_t>, 28> kernel_descriptors; // Descripters are a collection of u32s, with masks to differentiate between different descs. Impl'd Order: SysCallControl, InteruptNumList, AddressMapping(ExHdr order: IoMaps, MemMaps, AccDesc order: MemMaps, IoMaps), OtherCapabilities, HandleTableSize, ReleaseKernelVersion
tc::bn::pad<0x10> padding1;
// end ARM11 Kernel
// begin ARM9
tc::bn::bitarray<0xf> arm9_access_control;
byte_t desc_version;
// end ARM9
};
static_assert(sizeof(AccessControlInfo) == 0x200, "AccessControlInfo had incorrect size.");
struct ExtendedHeader
{
SystemControlInfo system_control_info;
AccessControlInfo access_control_info;
};
static_assert(sizeof(ExtendedHeader) == 0x400, "ExtendedHeader had invalid size");
struct AccessDescriptor
{
using Rsa2048Block = std::array<byte_t, 0x100>;
Rsa2048Block signature;
Rsa2048Block ncch_rsa_modulus;
AccessControlInfo access_control_info;
};
static_assert(sizeof(AccessDescriptor) == 0x400, "AccessDescriptor had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,40 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct FirmwareHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("FIRM");
struct SectionHeader
{
enum CopyMethod
{
CopyMethod_NDMA = 0,
CopyMethod_XDMA = 1,
CopyMethod_MEMCPY = 2
};
tc::bn::le32<uint32_t> offset;
tc::bn::le32<uint32_t> address;
tc::bn::le32<uint32_t> size;
tc::bn::le32<uint32_t> copy_method;
std::array<byte_t, 0x20> hash; // SHA2-256 over 0x000+offset - 0x000+offset+size-1
};
tc::bn::le32<uint32_t> struct_magic; // FIRM
tc::bn::le32<uint32_t> priority;
tc::bn::le32<uint32_t> entrypoint_arm11;
tc::bn::le32<uint32_t> entrypoint_arm9;
tc::bn::pad<0x30> reserved1;
std::array<SectionHeader, 4> section;
std::array<byte_t, 0x100> signature; // RSA2048-PKCS1-SHA2-256 over 0x000-0x0ff
};
static_assert(sizeof(FirmwareHeader) == 0x200, "FirmwareHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,70 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct IvfcHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("IVFC");
enum TypeId : uint32_t
{
TypeId_A = 0x10000, // CTR RomFS
TypeId_B = 0x20000, // CTR SaveData
};
tc::bn::le32<uint32_t> struct_magic;
tc::bn::le32<TypeId> type_id;
};
static_assert(sizeof(IvfcHeader) == 0x8, "IvfcHeader had invalid size");
struct IvfcLevelEntry
{
tc::bn::le64<uint64_t> offset;
tc::bn::le64<uint64_t> size;
tc::bn::le32<uint32_t> block_size_log2;
tc::bn::pad<4> reserved;
};
static_assert(sizeof(IvfcLevelEntry) == 0x18, "IvfcLevelEntry had invalid size");
/**
* @struct IvfcCtrRomfsHeader
* @details
* IVFC for CTR uses 4 levels (1 master, 3 auxillary)
*
* * The master level follows the IvfcCtrRomfsHeader aligned to 0x10 bytes (0x60 bytes total), for head.master_hash_size bytes, validating aux level 0
* * Aux level 0 validates aux level 1
* * Aux level 1 validates aux level 2
* * Aux level 2 is RomFS data
*/
struct IvfcCtrRomfsHeader
{
static const size_t kLevelNum = 3;
static const size_t kHeaderAlign = 0x10;
static const size_t kDefaultRomFsBlockSize = 0x1000;
IvfcHeader head;
tc::bn::le32<uint32_t> master_hash_size;
std::array<IvfcLevelEntry, kLevelNum> level;
tc::bn::le32<uint32_t> header_size; // header_size == 0x5c
tc::bn::pad<4> reserved;
};
static_assert(sizeof(IvfcCtrRomfsHeader) == 0x5C, "IvfcCtrRomfsHeader had invalid size");
struct IvfcCtrSavedataHeader
{
static const size_t kLevelNum = 4;
IvfcHeader head;
tc::bn::le64<uint64_t> master_hash_size;
std::array<IvfcLevelEntry, kLevelNum> level;
tc::bn::le32<uint32_t> header_size; // header_size == 0x78
std::array<byte_t, 4> reserved;
};
static_assert(sizeof(IvfcCtrSavedataHeader) == 0x78, "IvfcCtrSavedataHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,158 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,4)
struct NcchCommonHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("NCCH");
enum FormatVersion
{
FormatVersion_CFA = 0,
FormatVersion_CXI_PROTOTYPE = 1,
FormatVersion_CXI = 2,
};
enum FlagIndex
{
FlagIndex_SecurityVersion = 3, // this flag determines the security version for secure crypto
FlagIndex_ContentPlatform = 4,
FlagIndex_ContentTypeFlag = 5,
FlagIndex_BlockSizeLog = 6, // this flag determines the block_size = 1 << (block_size_log + 9), alternatively this is always 0, and the blocksize is always 0x200
FlagIndex_OtherFlag = 7,
};
enum ContentPlatform
{
ContentPlatform_CTR = 0,
ContentPlatform_SNAKE = 1,
};
enum FormType
{
FormType_Unassigned = 0, // invalid
FormType_SimpleContent = 1, // CFA (Non-executable data archive)
FormType_ExecutableWithoutRomFS = 2, // CXI (ExeFS Only)
FormType_Executable = 3, // CXI (ExeFS & RomFS)
};
enum ContentType
{
ContentType_Application = 0,
ContentType_SystemUpdate = 1, // CTR update
ContentType_Manual = 2,
ContentType_Child = 3,
ContentType_Trial = 4,
ContentType_ExtendedSystemUpdate = 5, // SNAKE update
};
enum OtherFlag
{
OtherFlag_FixedAesKey = 0,
OtherFlag_NoMountRomFS = 1,
OtherFlag_NoEncryption = 2,
OtherFlag_SeededAesKeyY = 5,
OtherFlag_ManualDisclosure = 6,
};
struct NcchFlags
{
struct ContentTypeFlag
{
byte_t form_type : 2;
byte_t content_type : 6;
};
static_assert(sizeof(ContentTypeFlag) == 1, "ContentTypeFlag had incorrect size.");
tc::bn::pad<3> reserved;
byte_t security_version; // this determines the secure crypto mode, where != 0 this uses a different key for: romfs, & non .icon&.banner exefs
tc::bn::bitarray<1> content_platform;
ContentTypeFlag content_flag;
byte_t block_size_log; // this flag determines the block_size = 1 << (block_size_log + 9), alternatively this is always 0, and the blocksize is always 0x200
tc::bn::bitarray<1> other_flag;
// raw byte access
byte_t& operator[](size_t index) { return ((byte_t*)this)[index]; }
const byte_t& operator[](size_t index) const { return ((const byte_t*)this)[index]; }
const byte_t* data() const { return ((const byte_t*)this); }
const size_t size() const { return sizeof(uint64_t); }
};
static_assert(sizeof(NcchFlags) == 8, "NcchFlags had incorrect size.");
// 0x00
tc::bn::le32<uint32_t> struct_magic; // NCCH
tc::bn::le32<uint32_t> content_blk_size;
tc::bn::le64<uint64_t> content_id;
// 0x10
tc::bn::string<2> maker_code;
tc::bn::le16<uint16_t> format_version;
std::array<byte_t, 4> seed_checksum;
tc::bn::le64<uint64_t> program_id;
// 0x20
tc::bn::pad<16> reserved_00;
// 0x30
std::array<byte_t, 0x20> logo_hash; // SHA-256 over the entire logo region
// 0x50
tc::bn::string<16> product_code;
// 0x60
std::array<byte_t, 0x20> exhdr_hash; // SHA-256 over exhdr_size of the exhdr region
// 0x80
tc::bn::le32<uint32_t> exhdr_size; // note that this size does not include the access_desc binary that follows the exheader
tc::bn::pad<4> reserved_01;
NcchFlags flags;
// 0x90
tc::bn::le32<uint32_t> plain_region_blk_offset;
tc::bn::le32<uint32_t> plain_region_blk_size;
tc::bn::le32<uint32_t> logo_blk_offset;
tc::bn::le32<uint32_t> logo_blk_size;
// 0xA0
tc::bn::le32<uint32_t> exefs_blk_offset;
tc::bn::le32<uint32_t> exefs_blk_size;
tc::bn::le32<uint32_t> exefs_prot_blk_size;
tc::bn::pad<4> reserved_02;
// 0xB0
tc::bn::le32<uint32_t> romfs_blk_offset;
tc::bn::le32<uint32_t> romfs_blk_size;
tc::bn::le32<uint32_t> romfs_prot_blk_size;
tc::bn::pad<4> reserved_03;
// 0xC0
std::array<byte_t, 0x20> exefs_prot_hash;
// 0xE0
std::array<byte_t, 0x20> romfs_prot_hash;
// 0x100
};
static_assert(sizeof(NcchCommonHeader) == 0x100, "NcchCommonHeader had incorrect size.");
struct NcchHeader
{
// 0x000-0x0FF : RSA2048-PKCS1-SHA2-256 signature over 0x100-0x1FF
std::array<byte_t, 0x100> signature;
// 0x100-0x1FF : NcchCommonHeader
NcchCommonHeader header;
// 0x200
};
static_assert(sizeof(NcchHeader) == 0x200, "NcchHeader had incorrect size.");
struct CxiHeader
{
// 0x000-0x0FF : RSA2048-PKCS1-SHA2-256 signature over 0x100-0x1FF
std::array<byte_t, 0x100> signature;
// 0x100-0x1FF : NcchCommonHeader
NcchCommonHeader header;
// 0x200-0x5FF : NcchExtendedHeader
std::array<byte_t, 0x400> extended_header;
// 0x600-0x9FF : NcchAccessControlExtended
std::array<byte_t, 0x400> access_ctrl_ext;
// 0xA00
};
static_assert(sizeof(CxiHeader) == 0xA00, "CxiHeader had incorrect size.");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,62 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct RomFsHeader
{
static const size_t kSectionNum = 4;
static const size_t kRomFsDataAlignSize = 0x10;
struct SectionGeometry
{
tc::bn::le32<uint32_t> offset;
tc::bn::le32<uint32_t> size;
};
tc::bn::le32<uint32_t> header_size;
SectionGeometry dir_hash_bucket;
SectionGeometry dir_entry;
SectionGeometry file_hash_bucket;
SectionGeometry file_entry;
tc::bn::le32<uint32_t> data_offset;
};
static_assert(sizeof(RomFsHeader) == 0x28, "RomFsHeader had invalid size");
#ifdef _WIN32
#pragma warning(disable : 4200) // silence warnings for usage of empty arrays in stucts (for name[])
#endif
struct RomFsDirectoryEntry
{
tc::bn::le32<uint32_t> parent_offset; // parent directory
tc::bn::le32<uint32_t> sibling_offset; // next sibling directory
tc::bn::le32<uint32_t> child_offset; // directory child
tc::bn::le32<uint32_t> file_offset; // file child
tc::bn::le32<uint32_t> hash_sibling_offset; // hashtable sibling directory
tc::bn::le32<uint32_t> name_size; // size of name field
tc::bn::le16<char16_t> name[]; // variable length le-16bit unicode name
};
static_assert(sizeof(RomFsDirectoryEntry) == 0x18, "RomFsDirectoryEntry had invalid size");
struct RomFsFileEntry
{
tc::bn::le32<uint32_t> parent_offset; // parent directory
tc::bn::le32<uint32_t> sibling_offset; // next sibling file
tc::bn::le64<uint64_t> data_offset; // file data offset
tc::bn::le64<uint64_t> data_size; // file data size
tc::bn::le32<uint32_t> hash_sibling_offset; // hashtable sibling file
tc::bn::le32<uint32_t> name_size; // size of name field
tc::bn::le16<char16_t> name[]; // variable length le-16bit unicode name
};
static_assert(sizeof(RomFsFileEntry) == 0x20, "RomFsFileEntry had invalid size");
#ifdef _WIN32
#pragma warning(default : 4200)
#endif
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,128 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct SystemMenuDataHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("SMDH");
static const uint16_t kFormatVersion = 0;
static const size_t kTitleNum = 16;
struct ApplicationTitle
{
static const size_t kShortDescriptionLength = 0x40;
static const size_t kLongDescriptionLength = 0x80;
static const size_t kPublisherLength = 0x40;
std::array<tc::bn::le16<char16_t>, kShortDescriptionLength> short_description;
std::array<tc::bn::le16<char16_t>, kLongDescriptionLength> long_description;
std::array<tc::bn::le16<char16_t>, kPublisherLength> publisher;
};
static_assert(sizeof(ApplicationTitle) == 0x200, "ApplicationTitle had invalid size");
enum Language
{
Language_Japanese = 0,
Language_English = 1,
Language_French = 2,
Language_German = 3,
Language_Italian = 4,
Language_Spanish = 5,
Language_ChineseSimplified = 6,
Language_Korean = 7,
Language_Dutch = 8,
Language_Portuguese = 9,
Language_Russian = 10,
Language_ChineseTraditional = 11,
};
struct ApplicationSettings
{
static const size_t kAgeRatingNum = 16;
enum Organisation
{
Organisation_CERO = 0, // Japan
Organisation_ESRB = 1, // North America (USA?)
Organisation_USK = 3, // Germany
Organisation_PEGI_GEN = 4, // Europe
Organisation_PEGI_PRT = 6, // Portugal
Organisation_PEGI_BBFC = 7, // UK
Organisation_COB = 8, // Australia (& NZ?)
Organisation_GRB = 9, // South Korea
Organisation_CGSRR = 10 // Taiwan
};
enum RegionFlag
{
RegionFlag_Japan = 0,
RegionFlag_NorthAmerica = 1,
RegionFlag_Europe = 2,
RegionFlag_Australia = 3,
RegionFlag_China = 4,
RegionFlag_Korea = 5,
RegionFlag_Taiwan = 6
};
enum AppFlag
{
AppFlag_Visible = 0,
AppFlag_Autoboot = 1,
AppFlag_Use3dEffect = 2,
AppFlag_RequireAcceptEula = 3,
AppFlag_AutosaveOnExit = 4,
AppFlag_UseExtendedBanner = 5,
AppFlag_RatingUsed = 6,
AppFlag_UsesSaveData = 7,
AppFlag_RecordUsage = 8,
AppFlag_DisableSaveDataBackup = 10,
AppFlag_EnableMiiverseJumpArgs = 11,
AppFlag_SnakeOnly = 12,
AppFlag_DepositSale = 13
};
struct AgeRating
{
byte_t age : 5;
byte_t no_age_restriction : 1;
byte_t rating_pending : 1;
byte_t enabled_rating : 1;
};
static_assert(sizeof(AgeRating) == 0x1, "AgeRating had invalid size");
struct EulaVersion
{
uint32_t minor : 8;
uint32_t major : 8;
uint32_t : 0;
};
static_assert(sizeof(EulaVersion) == 0x4, "EulaVersion had invalid size");
std::array<AgeRating, kAgeRatingNum> age_rating;
tc::bn::bitarray<sizeof(uint32_t)> region_lockout; // see RegionFlag
tc::bn::le32<uint32_t> match_maker_id;
tc::bn::le64<uint64_t> match_maker_bit_id;
tc::bn::bitarray<sizeof(uint32_t)> flags; // see AppFlag
tc::bn::le32<EulaVersion> eula_version;
tc::bn::le32<float> optiminal_animation_frame; // for banner
tc::bn::le32<uint32_t> cec_id;
};
static_assert(sizeof(ApplicationSettings) == 0x30, "ApplicationSettings had invalid size");
tc::bn::le32<uint32_t> struct_magic; // SMDH
tc::bn::le16<uint16_t> version;
tc::bn::pad<2> reserved0;
std::array<ApplicationTitle, kTitleNum> title;
ApplicationSettings settings;
tc::bn::pad<8> reserved1;
std::array<byte_t, 0x480> small_icon_data;
std::array<byte_t, 0x1200> large_icon_data;
};
static_assert(sizeof(SystemMenuDataHeader) == 0x36C0, "SystemMenuDataHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
@@ -0,0 +1,129 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct ContentAddr
{
tc::bn::le32<uint32_t> begin;
tc::bn::le32<uint32_t> end;
};
static_assert(sizeof(ContentAddr) == 0x8, "ContentAddr had invalid size");
struct ContHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("CONT");
static const uint32_t kFormatVersion = 4;
// 0x000
tc::bn::le32<uint32_t> struct_magic; // CONT
tc::bn::le32<uint32_t> format_version; // 0x4
tc::bn::pad<0xBF8> padding_08;
// 0xC00
ContentAddr content_addr[];
};
static_assert(sizeof(ContHeader) == 0xC00, "ContHeader had invalid size");
struct LegacyUpdaterConfig
{
ContentAddr unk_addr;
tc::bn::le64<uint64_t> update_kernel_id;
};
static_assert(sizeof(LegacyUpdaterConfig) == 0x10, "LegacyUpdaterConfig had invalid size");
struct UpdaterConfig
{
tc::bn::le64<uint64_t> update_kernel_id;
ContentAddr updater_info_addr;
ContentAddr twl_font_addr;
ContentAddr cup_list_addr;
};
static_assert(sizeof(UpdaterConfig) == 0x20, "UpdaterConfig had invalid size");
struct UpdaterInfo
{
tc::bn::le32<uint32_t> firm_ver_major;
tc::bn::le32<uint32_t> firm_ver_minor;
tc::bn::le32<uint32_t> firm_ver_build;
tc::bn::le32<uint32_t> rel_step;
tc::bn::le32<uint32_t> firm_revision;
tc::bn::pad<4> reserved;
tc::bn::string<0x20> patch_info;
tc::bn::le32<uint32_t> kernel_ver_major;
tc::bn::le32<uint32_t> kernel_ver_minor;
};
static_assert(sizeof(UpdaterInfo) == 0x40, "UpdaterInfo had invalid size");
struct UpdaterSpec
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("SPEC");
static const uint32_t kFormatVersion = 0;
enum Region
{
Region_JP = 0,
Region_US = 1,
Region_EU = 2,
Region_CN = 4,
Region_KR = 5,
Region_TW = 6
};
enum SystemMode
{
SystemMode_prod = 0,
SystemMode_dev1 = 2,
SystemMode_dev2 = 3,
SystemMode_dev3 = 4
};
enum Flag
{
Flag_AutoRun = 0,
Flag_SkipImport = 1,
Flag_TmpUpdater = 2,
Flag_ForceChangeSystemMode = 3,
// SystemUpdater sets this flag at run-time if "rom:/contents/ClearProgram" is present
Flag_ClearProgramBeforeImport = 27,
// SystemUpdater sets this flag at run-time if "rom:/contents/ClearTicket" is present
Flag_ClearTickets = 28,
// SystemUpdater sets this flag at run-time
Flag_RebootAfterUpdate = 29,
// SystemUpdater sets these flags at run-time from key-combos
Flag_NotIncrementSeed = 30,
Flag_SdmcLog = 31
};
// 0x000
tc::bn::le32<uint32_t> struct_magic; // SPEC
tc::bn::le32<uint32_t> format_version; // 0x0
byte_t region;
byte_t system_mode;
tc::bn::pad<6> padding;
tc::bn::bitarray<4> flags;
};
static_assert(sizeof(UpdaterSpec) == 0x14, "UpdaterSpec had invalid size");
struct DeleteSpec
{
enum Flag
{
Flag_DeleteNonSystemTitles = 0, // this deletes all titles from NAND without the system app bit, excluding "Self", see below
Flag_DeleteSelf = 1 // This refers to when the systemupdater was installed to NAND (old install method), the titleID was 0x000400000fd00200
};
tc::bn::le32<uint32_t> id_list_length;
tc::bn::bitarray<4> flags;
tc::bn::pad<8> padding;
tc::bn::le64<uint64_t> id_list[];
};
static_assert(sizeof(DeleteSpec) == 0x10, "DeleteSpec had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds