Codebase cleanup.

Remove legacy code and trailing whitespace from all files.
This commit is contained in:
Pablo Curiel 2022-07-05 03:04:28 +02:00
parent 91dc20b7f3
commit 942a407247
88 changed files with 4680 additions and 19299 deletions

1
.gitignore vendored
View file

@ -18,4 +18,3 @@ host/nxdumptool
*.exe
main.cpp
/code_templates/tmp/*
/galgun

File diff suppressed because it is too large Load diff

View file

@ -1,71 +0,0 @@
#pragma once
#ifndef __DUMPER_H__
#define __DUMPER_H__
#include <switch.h>
#include "util.h"
#define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF // 4 GiB - 1 (4294967295 bytes)
#define SPLIT_FILE_XCI_PART_SIZE (u64)0xFFFF8000 // 4 GiB - 0x8000 (4294934528 bytes) (based on XCI-Cutter)
#define SPLIT_FILE_NSP_PART_SIZE (u64)0xFFFF0000 // 4 GiB - 0x10000 (4294901760 bytes) (based on splitNSP.py)
#define SPLIT_FILE_GENERIC_PART_SIZE SPLIT_FILE_NSP_PART_SIZE
#define SPLIT_FILE_SEQUENTIAL_SIZE (u64)0x40000000 // 1 GiB (used for sequential dumps when there's not enough storage space available)
#define CERT_OFFSET 0x7000
#define CERT_SIZE 0x200
typedef struct {
bool keepCert; // Original value for the "Keep certificate" option. Overrides the selected setting in the current session
bool trimDump; // Original value for the "Trim output dump" option. Overrides the selected setting in the current session
bool calcCrc; // "CRC32 checksum calculation + dump verification" option
u8 partNumber; // Next part number
u32 partitionIndex; // IStorage partition index
u64 partitionOffset; // IStorage partition offset
u32 certCrc; // CRC32 checksum accumulator (XCI with cert). Only used if calcCrc == true and keepCert == true
u32 certlessCrc; // CRC32 checksum accumulator (certless XCI). Only used if calcCrc == true
} PACKED sequentialXciCtx;
// This struct is followed by 'ncaCount' SHA-256 checksums in the output file and 'programNcaModCount' modified + reencrypted Program NCA headers
// The modified NCA headers are only needed if their NPDM signature is replaced (it uses cryptographically secure random numbers). The RSA public key used in the ACID section from the main.npdm file is constant, so we don't need to keep track of that
typedef struct {
NcmStorageId storageId; // Source storage from which the data is dumped
bool removeConsoleData; // Original value for the "Remove console specific data" option. Overrides the selected setting in the current session
bool tiklessDump; // Original value for the "Generate ticket-less dump" option. Overrides the selected setting in the current session. Ignored if removeConsoleData == false
bool npdmAcidRsaPatch; // Original value for the "Change NPDM RSA key/sig in Program NCA" option. Overrides the selected setting in the current session
bool preInstall; // Indicates if we're dealing with a preinstalled title - e.g. if the user already accepted the missing ticket prompt
u8 partNumber; // Next part number
u32 pfs0FileCount; // PFS0 file count
u32 ncaCount; // NCA count
u32 programNcaModCount; // Program NCA mod count
u32 fileIndex; // Current PFS0 file entry index
u64 fileOffset; // Current PFS0 file entry offset
Sha256Context hashCtx; // Current NCA SHA-256 checksum context. Only used when dealing with the same NCA between different parts
} PACKED sequentialNspCtx;
typedef struct {
bool enabled;
nspDumpType titleType;
u32 titleIndex;
u64 contentSize;
char *contentSizeStr;
char nspFilename[NAME_BUF_LEN];
char truncatedNspFilename[NAME_BUF_LEN];
} batchEntry;
bool dumpNXCardImage(xciOptions *xciDumpCfg);
int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, nspOptions *nspDumpCfg, bool batch);
int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg);
bool dumpRawHfs0Partition(u32 partition, bool doSplitting);
bool dumpHfs0PartitionData(u32 partition, bool doSplitting);
bool dumpFileFromHfs0Partition(u32 partition, u32 fileIndex, char *filename, bool doSplitting);
bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg);
bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg);
bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpGameCardCertificate();
bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOptions *tikDumpCfg);
#endif

View file

@ -1,933 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mbedtls/base64.h>
#include "keys.h"
#include "util.h"
#include "ui.h"
#include "rsa.h"
#include "nso.h"
/* Extern variables */
extern int breaks;
extern int font_height;
extern exefs_ctx_t exeFsContext;
extern romfs_ctx_t romFsContext;
extern bktr_ctx_t bktrContext;
extern nca_keyset_t nca_keyset;
extern u8 *ncaCtrBuf;
bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize)
{
if (!input || !outBuf || !outBufSize || outBufSize < NCA_FULL_HEADER_LENGTH || (__builtin_bswap32(input->magic) != NCA3_MAGIC && __builtin_bswap32(input->magic) != NCA2_MAGIC))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header encryption parameters.", __func__);
return false;
}
if (!loadNcaKeyset()) return false;
u32 i;
size_t crypt_res;
Aes128XtsContext hdr_aes_ctx;
u8 header_key_0[16];
u8 header_key_1[16];
memcpy(header_key_0, nca_keyset.header_key, 16);
memcpy(header_key_1, nca_keyset.header_key + 16, 16);
aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, true);
if (__builtin_bswap32(input->magic) == NCA3_MAGIC)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_FULL_HEADER_LENGTH, 0, true);
if (crypt_res != NCA_FULL_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res);
return false;
}
} else
if (__builtin_bswap32(input->magic) == NCA2_MAGIC)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_HEADER_LENGTH, 0, true);
if (crypt_res != NCA_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res);
return false;
}
for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), &(input->fs_headers[i]), NCA_SECTION_HEADER_LENGTH, 0, true);
if (crypt_res != NCA_SECTION_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res);
return false;
}
}
} else {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid decrypted NCA magic word! (0x%08X)", __func__, __builtin_bswap32(input->magic));
return false;
}
return true;
}
bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData)
{
if (!ncaBuf || !ncaBufSize || ncaBufSize < NCA_FULL_HEADER_LENGTH || !out || !decrypted_nca_keys)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header decryption parameters!", __func__);
return false;
}
if (!loadNcaKeyset()) return false;
int ret;
u32 i;
size_t crypt_res;
Aes128XtsContext hdr_aes_ctx;
u8 header_key_0[16];
u8 header_key_1[16];
bool has_rights_id = false;
memcpy(header_key_0, nca_keyset.header_key, 16);
memcpy(header_key_1, nca_keyset.header_key + 16, 16);
aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, false);
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_HEADER_LENGTH, 0, false);
if (crypt_res != NCA_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res);
return false;
}
if (__builtin_bswap32(out->magic) == NCA3_MAGIC)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_FULL_HEADER_LENGTH, 0, false);
if (crypt_res != NCA_FULL_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res);
return false;
}
} else
if (__builtin_bswap32(out->magic) == NCA2_MAGIC)
{
for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
if (out->fs_headers[i]._0x148[0] != 0 || memcmp(out->fs_headers[i]._0x148, out->fs_headers[i]._0x148 + 1, 0xB7))
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(out->fs_headers[i]), ncaBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), NCA_SECTION_HEADER_LENGTH, 0, false);
if (crypt_res != NCA_SECTION_HEADER_LENGTH)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res);
return false;
}
} else {
memset(&(out->fs_headers[i]), 0, sizeof(nca_fs_header_t));
}
}
} else {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA magic word! Wrong header key? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(out->magic));
return false;
}
for(i = 0; i < 0x10; i++)
{
if (out->rights_id[i] != 0)
{
has_rights_id = true;
break;
}
}
if (has_rights_id)
{
if (rights_info != NULL)
{
// If we're dealing with a rights info context, retrieve the ticket for the current title
if (!rights_info->has_rights_id)
{
rights_info->has_rights_id = true;
memcpy(rights_info->rights_id, out->rights_id, 16);
convertDataToHexString(out->rights_id, 16, rights_info->rights_id_str, 33);
sprintf(rights_info->tik_filename, "%s.tik", rights_info->rights_id_str);
sprintf(rights_info->cert_filename, "%s.cert", rights_info->rights_id_str);
if (retrieveTitleKeyData)
{
ret = retrieveNcaTikTitleKey(out, (u8*)(&(rights_info->tik_data)), rights_info->enc_titlekey, rights_info->dec_titlekey);
if (ret >= 0)
{
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
rights_info->retrieved_tik = true;
} else {
if (ret == -2)
{
// We are probably dealing with a pre-installed title
// Let's enable our missing ticket flag - we'll use it to display a prompt asking the user if they want to proceed anyway
rights_info->missing_tik = true;
} else {
return false;
}
}
}
} else {
// Copy what we already have
if (retrieveTitleKeyData && rights_info->retrieved_tik)
{
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
}
}
} else {
// Otherwise, only retrieve the decrypted titlekey. This is used with ExeFS/RomFS section parsing for SD/eMMC titles
if (retrieveTitleKeyData)
{
u8 tmp_dec_titlekey[0x10];
if (retrieveNcaTikTitleKey(out, NULL, NULL, tmp_dec_titlekey) < 0) return false;
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), tmp_dec_titlekey, 0x10);
}
}
} else {
if (!decryptNcaKeyArea(out, decrypted_nca_keys)) return false;
}
return true;
}
bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys)
{
if (!rights_info || !rights_info->has_rights_id || !strlen(rights_info->tik_filename) || !decrypted_nca_keys)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve titlekey from gamecard ticket!", __func__);
return false;
}
// Check if the ticket has already been retrieved from the HFS0 partition in the gamecard
if (rights_info->retrieved_tik)
{
// Save the decrypted NCA key area keys
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
return true;
}
// Load external keys
if (!loadExternalKeys()) return false;
// Retrieve ticket
if (!readFileFromSecureHfs0PartitionByName(rights_info->tik_filename, 0, &(rights_info->tik_data), ETICKET_TIK_FILE_SIZE)) return false;
// Save encrypted titlekey
memcpy(rights_info->enc_titlekey, rights_info->tik_data.titlekey_block, 0x10);
// Decrypt titlekey
u8 crypto_type = rights_info->rights_id[0x0F];
if (crypto_type) crypto_type--;
if (crypto_type >= 0x20)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__);
return false;
}
Aes128Context titlekey_aes_ctx;
aes128ContextCreate(&titlekey_aes_ctx, nca_keyset.titlekeks[crypto_type], false);
aes128DecryptBlock(&titlekey_aes_ctx, rights_info->dec_titlekey, rights_info->enc_titlekey);
// Update retrieved ticket flag
rights_info->retrieved_tik = true;
// Save the decrypted NCA key area keys
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
return true;
}
bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx)
{
if (!ncmStorage || !ncaId || !dec_nca_header || !xml_content_info || !output || !cur_mod_cnt)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process Program NCA!", __func__);
return false;
}
if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__);
return false;
}
if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__);
return false;
}
if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type);
return false;
}
u32 i;
u64 section_offset;
u64 hash_table_offset;
u64 nca_pfs0_offset;
pfs0_header nca_pfs0_header;
pfs0_file_entry *nca_pfs0_entries = NULL;
u64 nca_pfs0_data_offset;
npdm_t npdm_header;
bool found_meta = false;
u64 meta_offset;
u64 acid_pubkey_offset;
u64 block_hash_table_offset;
u64 block_hash_table_end_offset;
u64 block_start_offset[2] = { 0, 0 };
u64 block_size[2] = { 0, 0 };
u8 block_hash[2][SHA256_HASH_SIZE];
u8 *block_data[2] = { NULL, NULL };
u64 sig_write_size[2] = { 0, 0 };
u8 *hash_table = NULL;
Aes128CtrContext aes_ctx;
section_offset = ((u64)dec_nca_header->section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE);
hash_table_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_offset);
nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_offset);
if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !hash_table_offset || hash_table_offset < section_offset || !nca_pfs0_offset || nca_pfs0_offset <= hash_table_offset)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__);
return false;
}
// Generate initial CTR
unsigned char ctr[0x10];
u64 ofs = (section_offset >> 4);
for(i = 0; i < 0x8; i++)
{
ctr[i] = dec_nca_header->fs_headers[0].section_ctr[0x08 - i - 1];
ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
u8 ctr_key[NCA_KEY_AREA_KEY_SIZE];
memcpy(ctr_key, xml_content_info->decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE);
aes128CtrContextCreate(&aes_ctx, ctr_key, ctr);
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__);
return false;
}
if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic));
return false;
}
if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__);
return false;
}
nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry));
if (!nca_pfs0_entries)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__);
return false;
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__);
free(nca_pfs0_entries);
return false;
}
nca_pfs0_data_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)) + (u64)nca_pfs0_header.str_table_size);
// Looking for META magic
for(i = 0; i < nca_pfs0_header.file_cnt; i++)
{
u64 nca_pfs0_cur_file_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset);
// Read and decrypt NPDM header
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_cur_file_offset, &npdm_header, sizeof(npdm_t), false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 entry #%u!", __func__, i);
free(nca_pfs0_entries);
return false;
}
if (__builtin_bswap32(npdm_header.magic) == META_MAGIC)
{
found_meta = true;
meta_offset = nca_pfs0_cur_file_offset;
acid_pubkey_offset = (meta_offset + (u64)npdm_header.acid_offset + (u64)NPDM_SIGNATURE_SIZE);
break;
}
}
free(nca_pfs0_entries);
if (!found_meta)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find NPDM entry in Program NCA section #0 PFS0 partition!", __func__);
return false;
}
// Calculate block offsets
block_hash_table_offset = (hash_table_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)) * (u64)SHA256_HASH_SIZE);
block_hash_table_end_offset = (hash_table_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)SHA256_HASH_SIZE));
block_start_offset[0] = (nca_pfs0_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size));
// Make sure our block doesn't pass PFS0 end offset
if ((block_start_offset[0] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size)
{
block_size[0] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[0] - nca_pfs0_offset));
} else {
block_size[0] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size;
}
block_data[0] = malloc(block_size[0]);
if (!block_data[0])
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 0!", __func__);
return false;
}
// Read and decrypt block
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 0!", __func__);
free(block_data[0]);
return false;
}
// Make sure that 1 block will cover all patched bytes, otherwise we'll have to recalculate another hash block
if (block_hash_table_offset != block_hash_table_end_offset)
{
sig_write_size[1] = (acid_pubkey_offset - block_start_offset[0] + (u64)NPDM_SIGNATURE_SIZE - block_size[0]);
sig_write_size[0] = ((u64)NPDM_SIGNATURE_SIZE - sig_write_size[1]);
} else {
sig_write_size[0] = (u64)NPDM_SIGNATURE_SIZE;
}
// Patch ACID public key changing it to a self-generated pubkey
memcpy(block_data[0] + (acid_pubkey_offset - block_start_offset[0]), rsa_get_public_key(), sig_write_size[0]);
// Calculate new block hash
sha256CalculateHash(block_hash[0], block_data[0], block_size[0]);
if (block_hash_table_offset != block_hash_table_end_offset)
{
block_start_offset[1] = (nca_pfs0_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size));
if ((block_start_offset[1] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size)
{
block_size[1] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[1] - nca_pfs0_offset));
} else {
block_size[1] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size;
}
block_data[1] = malloc(block_size[1]);
if (!block_data[1])
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 1!", __func__);
free(block_data[0]);
return false;
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 1!", __func__);
free(block_data[0]);
free(block_data[1]);
return false;
}
memcpy(block_data[1], rsa_get_public_key() + sig_write_size[0], sig_write_size[1]);
sha256CalculateHash(block_hash[1], block_data[1], block_size[1]);
}
hash_table = malloc(dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size);
if (!hash_table)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 hash table!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
return false;
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 hash table!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
// Update block hashes
memcpy(hash_table + (block_hash_table_offset - hash_table_offset), block_hash[0], SHA256_HASH_SIZE);
if (block_hash_table_offset != block_hash_table_end_offset) memcpy(hash_table + (block_hash_table_end_offset - hash_table_offset), block_hash[1], SHA256_HASH_SIZE);
// Calculate PFS0 superblock master hash
sha256CalculateHash(dec_nca_header->fs_headers[0].pfs0_superblock.master_hash, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size);
// Calculate section hash
sha256CalculateHash(dec_nca_header->section_hashes[0], &(dec_nca_header->fs_headers[0]), sizeof(nca_fs_header_t));
// Recreate NPDM signature
if (!rsa_sign(&(dec_nca_header->magic), NPDM_SIGNATURE_AREA_SIZE, dec_nca_header->npdm_key_sig, NPDM_SIGNATURE_SIZE))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to recreate Program NCA NPDM signature!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
// Reencrypt relevant data blocks
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], true))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 0!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
if (block_hash_table_offset != block_hash_table_end_offset)
{
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], true))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 1!", __func__);
free(block_data[0]);
free(block_data[1]);
free(hash_table);
return false;
}
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, true))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 hash table!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
// Save data to the output struct so we can write it later
// The caller function must free these data pointers
nca_program_mod_data *tmp_mod_data = realloc(*output, (*cur_mod_cnt + 1) * sizeof(nca_program_mod_data));
if (!tmp_mod_data)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to reallocate Program NCA mod data buffer!", __func__);
free(block_data[0]);
if (block_data[1]) free(block_data[1]);
free(hash_table);
return false;
}
memset(&(tmp_mod_data[*cur_mod_cnt]), 0, sizeof(nca_program_mod_data));
tmp_mod_data[*cur_mod_cnt].nca_index = idx;
tmp_mod_data[*cur_mod_cnt].hash_table = hash_table;
tmp_mod_data[*cur_mod_cnt].hash_table_offset = hash_table_offset;
tmp_mod_data[*cur_mod_cnt].hash_table_size = dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size;
tmp_mod_data[*cur_mod_cnt].block_mod_cnt = (block_hash_table_offset != block_hash_table_end_offset ? 2 : 1);
tmp_mod_data[*cur_mod_cnt].block_data[0] = block_data[0];
tmp_mod_data[*cur_mod_cnt].block_offset[0] = block_start_offset[0];
tmp_mod_data[*cur_mod_cnt].block_size[0] = block_size[0];
if (block_hash_table_offset != block_hash_table_end_offset)
{
tmp_mod_data[*cur_mod_cnt].block_data[1] = block_data[1];
tmp_mod_data[*cur_mod_cnt].block_offset[1] = block_start_offset[1];
tmp_mod_data[*cur_mod_cnt].block_size[1] = block_size[1];
}
*output = tmp_mod_data;
tmp_mod_data = NULL;
*cur_mod_cnt += 1;
return true;
}
bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info)
{
if (!ncaBuf || !xml_program_info || !xml_content_info || !output || !rights_info)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve CNMT NCA!", __func__);
return false;
}
nca_header_t dec_header;
u32 i, j, k = 0;
u64 section_offset;
u64 section_size;
u8 *section_data = NULL;
Aes128CtrContext aes_ctx;
u64 nca_pfs0_offset;
u64 nca_pfs0_str_table_offset;
u64 nca_pfs0_data_offset;
pfs0_header nca_pfs0_header;
pfs0_file_entry *nca_pfs0_entries = NULL;
bool found_cnmt = false;
u64 title_cnmt_offset;
u64 title_cnmt_size;
cnmt_header title_cnmt_header;
cnmt_extended_header title_cnmt_extended_header;
u64 digest_offset;
// Generate filename for our required CNMT file
char cnmtFileName[50] = {'\0'};
snprintf(cnmtFileName, MAX_CHARACTERS(cnmtFileName), "%s_%016lx.cnmt", getTitleType(xml_program_info->type), xml_program_info->title_id);
// Decrypt the NCA header
// Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard
if (!decryptNcaHeader(ncaBuf, xml_content_info[cnmtNcaIndex].size, &dec_header, rights_info, xml_content_info[cnmtNcaIndex].decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard))) return false;
if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 doesn't hold a PFS0 partition!", __func__);
return false;
}
if (!dec_header.fs_headers[0].pfs0_superblock.pfs0_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in CNMT NCA section #0!", __func__);
return false;
}
if (dec_header.fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for CNMT NCA section #0! (0x%02X)", __func__, dec_header.fs_headers[0].crypt_type);
return false;
}
bool has_rights_id = false;
for(i = 0; i < 0x10; i++)
{
if (dec_header.rights_id[i] != 0)
{
has_rights_id = true;
break;
}
}
// CNMT NCAs never use titlekey crypto
if (has_rights_id)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in CNMT NCA header not empty!", __func__);
return false;
}
// Modify distribution type
if (curStorageId == NcmStorageId_GameCard) dec_header.distribution = 0;
section_offset = ((u64)dec_header.section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE);
section_size = (((u64)dec_header.section_entries[0].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset);
if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for CNMT NCA section #0!", __func__);
return false;
}
// Generate initial CTR
unsigned char ctr[0x10];
u64 ofs = (section_offset >> 4);
for(i = 0; i < 0x8; i++)
{
ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1];
ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
u8 ctr_key[NCA_KEY_AREA_KEY_SIZE];
memcpy(ctr_key, xml_content_info[cnmtNcaIndex].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE);
aes128CtrContextCreate(&aes_ctx, ctr_key, ctr);
section_data = malloc(section_size);
if (!section_data)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the decrypted CNMT NCA section #0!", __func__);
return false;
}
aes128CtrCrypt(&aes_ctx, section_data, ncaBuf + section_offset, section_size);
nca_pfs0_offset = dec_header.fs_headers[0].pfs0_superblock.pfs0_offset;
memcpy(&nca_pfs0_header, section_data + nca_pfs0_offset, sizeof(pfs0_header));
if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic));
free(section_data);
return false;
}
if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__);
free(section_data);
return false;
}
nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry));
if (!nca_pfs0_entries)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for CNMT NCA section #0 PFS0 partition entries!", __func__);
free(section_data);
return false;
}
memcpy(nca_pfs0_entries, section_data + nca_pfs0_offset + sizeof(pfs0_header), (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry));
nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)));
nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size);
// Look for the CNMT
for(i = 0; i < nca_pfs0_header.file_cnt; i++)
{
u64 filename_offset = (nca_pfs0_str_table_offset + nca_pfs0_entries[i].filename_offset);
if (!strncasecmp((char*)section_data + filename_offset, cnmtFileName, strlen(cnmtFileName)))
{
found_cnmt = true;
title_cnmt_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset);
title_cnmt_size = nca_pfs0_entries[i].file_size;
break;
}
}
free(nca_pfs0_entries);
if (!found_cnmt)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find file \"%s\" in PFS0 partition from CNMT NCA section #0!", __func__, cnmtFileName);
free(section_data);
return false;
}
memcpy(&title_cnmt_header, section_data + title_cnmt_offset, sizeof(cnmt_header));
memcpy(&title_cnmt_extended_header, section_data + title_cnmt_offset + sizeof(cnmt_header), sizeof(cnmt_extended_header));
// Fill information for our CNMT XML
digest_offset = (title_cnmt_offset + title_cnmt_size - (u64)SHA256_HASH_SIZE);
memcpy(xml_program_info->digest, section_data + digest_offset, SHA256_HASH_SIZE);
convertDataToHexString(xml_program_info->digest, SHA256_HASH_SIZE, xml_program_info->digest_str, (SHA256_HASH_SIZE * 2) + 1);
xml_content_info[cnmtNcaIndex].keyblob = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type);
xml_program_info->required_dl_sysver = title_cnmt_header.required_dl_sysver;
xml_program_info->min_keyblob = (rights_info->has_rights_id ? rights_info->rights_id[15] : xml_content_info[cnmtNcaIndex].keyblob);
xml_program_info->min_sysver = title_cnmt_extended_header.min_sysver;
xml_program_info->patch_tid = title_cnmt_extended_header.patch_tid;
xml_program_info->min_appver = title_cnmt_extended_header.min_appver;
// Retrieve the ID offset and content record offset for each of our NCAs (except the CNMT NCA)
// Also wipe each of the content records we're gonna replace
for(i = 0; i < (xml_program_info->nca_cnt - 1); i++) // Discard CNMT NCA
{
for(j = 0; j < title_cnmt_header.content_cnt; j++)
{
cnmt_content_record cnt_record;
memcpy(&cnt_record, section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), sizeof(cnmt_content_record));
if (memcmp(xml_content_info[i].nca_id, cnt_record.nca_id, SHA256_HASH_SIZE / 2) != 0) continue;
// Save content record offset
xml_content_info[i].cnt_record_offset = (j * sizeof(cnmt_content_record));
// Empty CNMT content record
memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), 0, sizeof(cnmt_content_record));
// Increase counter
k++;
break;
}
}
// Verify counter
if (k != (xml_program_info->nca_cnt - 1))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid content record entries in the CNMT NCA!", __func__);
free(section_data);
return false;
}
// Replace input buffer data in-place
memcpy(ncaBuf, &dec_header, NCA_FULL_HEADER_LENGTH);
memcpy(ncaBuf + section_offset, section_data, section_size);
free(section_data);
// Update offsets
nca_pfs0_offset += section_offset;
title_cnmt_offset += section_offset;
// Save data to output struct
output->section_offset = section_offset;
output->section_size = section_size;
output->hash_table_offset = (section_offset + dec_header.fs_headers[0].pfs0_superblock.hash_table_offset);
output->hash_block_size = dec_header.fs_headers[0].pfs0_superblock.block_size;
output->hash_block_cnt = (dec_header.fs_headers[0].pfs0_superblock.hash_table_size / SHA256_HASH_SIZE);
output->pfs0_offset = nca_pfs0_offset;
output->pfs0_size = dec_header.fs_headers[0].pfs0_superblock.pfs0_size;
output->title_cnmt_offset = title_cnmt_offset;
output->title_cnmt_size = title_cnmt_size;
return true;
}
bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod)
{
if (!ncaBuf || !ncaBufSize || !xml_program_info || xml_program_info->nca_cnt <= 1 || !xml_content_info || !cnmt_mod)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to patch CNMT NCA!", __func__);
return false;
}
u32 i;
u32 nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA
cnmt_header title_cnmt_header;
cnmt_content_record title_cnmt_content_record;
u64 title_cnmt_content_records_offset;
nca_header_t dec_header;
Aes128CtrContext aes_ctx;
// Copy CNMT header
memcpy(&title_cnmt_header, ncaBuf + cnmt_mod->title_cnmt_offset, sizeof(cnmt_header));
// Calculate the start offset for the content records
title_cnmt_content_records_offset = (cnmt_mod->title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size);
// Write content records
for(i = 0; i < nca_cnt; i++)
{
memset(&title_cnmt_content_record, 0, sizeof(cnmt_content_record));
memcpy(title_cnmt_content_record.hash, xml_content_info[i].hash, SHA256_HASH_SIZE);
memcpy(title_cnmt_content_record.nca_id, xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2);
convertU64ToNcaSize(xml_content_info[i].size, title_cnmt_content_record.size);
title_cnmt_content_record.type = xml_content_info[i].type;
title_cnmt_content_record.id_offset = xml_content_info[i].id_offset;
memcpy(ncaBuf + title_cnmt_content_records_offset + xml_content_info[i].cnt_record_offset, &title_cnmt_content_record, sizeof(cnmt_content_record));
}
// Recalculate block hashes
for(i = 0; i < cnmt_mod->hash_block_cnt; i++)
{
u64 blk_offset = ((u64)i * cnmt_mod->hash_block_size);
u64 rest_size = (cnmt_mod->pfs0_size - blk_offset);
u64 blk_size = (rest_size > cnmt_mod->hash_block_size ? cnmt_mod->hash_block_size : rest_size);
sha256CalculateHash(ncaBuf + cnmt_mod->hash_table_offset + (i * SHA256_HASH_SIZE), ncaBuf + cnmt_mod->pfs0_offset + blk_offset, blk_size);
}
// Copy header to struct
memcpy(&dec_header, ncaBuf, sizeof(nca_header_t));
// Calculate PFS0 superblock master hash
sha256CalculateHash(dec_header.fs_headers[0].pfs0_superblock.master_hash, ncaBuf + cnmt_mod->hash_table_offset, dec_header.fs_headers[0].pfs0_superblock.hash_table_size);
// Calculate section hash
sha256CalculateHash(dec_header.section_hashes[0], &(dec_header.fs_headers[0]), sizeof(nca_fs_header_t));
// Generate initial CTR
unsigned char ctr[0x10];
u64 ofs = (cnmt_mod->section_offset >> 4);
for(i = 0; i < 0x8; i++)
{
ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1];
ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
u8 ctr_key[NCA_KEY_AREA_KEY_SIZE];
memcpy(ctr_key, xml_content_info[xml_program_info->nca_cnt - 1].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE);
aes128CtrContextCreate(&aes_ctx, ctr_key, ctr);
// Reencrypt CNMT NCA
if (!encryptNcaHeader(&dec_header, ncaBuf, ncaBufSize))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt modified CNMT NCA header!", __func__);
return false;
}
aes128CtrCrypt(&aes_ctx, ncaBuf + cnmt_mod->section_offset, ncaBuf + cnmt_mod->section_offset, cnmt_mod->section_size);
// Calculate CNMT NCA SHA-256 checksum and fill information for our CNMT XML
sha256CalculateHash(xml_content_info[xml_program_info->nca_cnt - 1].hash, ncaBuf, ncaBufSize);
convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE, xml_content_info[xml_program_info->nca_cnt - 1].hash_str, (SHA256_HASH_SIZE * 2) + 1);
memcpy(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE / 2);
convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[xml_program_info->nca_cnt - 1].nca_id_str, SHA256_HASH_SIZE + 1);
return true;
}

View file

@ -1,368 +0,0 @@
#pragma once
#ifndef __NCA_H__
#define __NCA_H__
#include <switch.h>
#define NCA3_MAGIC (u32)0x4E434133 // "NCA3"
#define NCA2_MAGIC (u32)0x4E434132 // "NCA2"
#define NCA_HEADER_LENGTH 0x400
#define NCA_SECTION_HEADER_LENGTH 0x200
#define NCA_SECTION_HEADER_CNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT))
#define NCA_AES_XTS_SECTOR_SIZE 0x200
#define NCA_KEY_AREA_KEY_CNT 4
#define NCA_KEY_AREA_KEY_SIZE 0x10
#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_CNT * NCA_KEY_AREA_KEY_SIZE)
#define NCA_FS_HEADER_PARTITION_PFS0 0x01
#define NCA_FS_HEADER_FSTYPE_PFS0 0x02
#define NCA_FS_HEADER_PARTITION_ROMFS 0x00
#define NCA_FS_HEADER_FSTYPE_ROMFS 0x03
#define NCA_FS_HEADER_CRYPT_NONE 0x01
#define NCA_FS_HEADER_CRYPT_XTS 0x02
#define NCA_FS_HEADER_CRYPT_CTR 0x03
#define NCA_FS_HEADER_CRYPT_BKTR 0x04
#define PFS0_MAGIC (u32)0x50465330 // "PFS0"
#define IVFC_MAGIC (u32)0x49564643 // "IVFC"
#define IVFC_MAX_LEVEL 6
#define BKTR_MAGIC (u32)0x424B5452 // "BKTR"
#define ROMFS_HEADER_SIZE 0x50
#define ROMFS_ENTRY_EMPTY (u32)0xFFFFFFFF
#define ROMFS_NONAME_DIRENTRY_SIZE 0x18
#define ROMFS_NONAME_FILEENTRY_SIZE 0x20
#define ROMFS_ENTRY_DIR 1
#define ROMFS_ENTRY_FILE 2
#define META_MAGIC (u32)0x4D455441 // "META"
#define NPDM_SIGNATURE_SIZE 0x100
#define NPDM_SIGNATURE_AREA_SIZE 0x200
#define NSP_NCA_FILENAME_LENGTH 0x25 // NCA ID + ".nca" + NULL terminator
#define NSP_CNMT_FILENAME_LENGTH 0x2A // NCA ID + ".cnmt.nca" / ".cnmt.xml" + NULL terminator
#define NSP_PROGRAM_XML_FILENAME_LENGTH 0x31 // NCA ID + ".programinfo.xml" + NULL terminator
#define NSP_NACP_XML_FILENAME_LENGTH 0x2A // NCA ID + ".nacp.xml" + NULL terminator
#define NSP_LEGAL_XML_FILENAME_LENGTH 0x2F // NCA ID + ".legalinfo.xml" + NULL terminator
#define NSP_TIK_FILENAME_LENGTH 0x25 // Rights ID + ".tik" + NULL terminator
#define NSP_CERT_FILENAME_LENGTH 0x26 // Rights ID + ".cert" + NULL terminator
#define ETICKET_ENTRY_SIZE 0x400
#define ETICKET_TITLEKEY_OFFSET 0x180
#define ETICKET_RIGHTSID_OFFSET 0x2A0
#define ETICKET_UNKNOWN_FIELD_SIZE 0x140
#define ETICKET_DATA_OFFSET 0x140
#define ETICKET_CA_CERT_SIZE 0x400
#define ETICKET_XS_CERT_SIZE 0x300
#define ETICKET_TIK_FILE_SIZE (ETICKET_ENTRY_SIZE - 0x140)
#define ETICKET_CERT_FILE_SIZE (ETICKET_CA_CERT_SIZE + ETICKET_XS_CERT_SIZE)
#define ETICKET_TITLEKEY_COMMON 0
#define ETICKET_TITLEKEY_PERSONALIZED 1
typedef enum {
DUMP_APP_NSP = 0,
DUMP_PATCH_NSP,
DUMP_ADDON_NSP
} nspDumpType;
typedef struct {
u32 magic;
u32 file_cnt;
u32 str_table_size;
u32 reserved;
} PACKED pfs0_header;
typedef struct {
u64 file_offset;
u64 file_size;
u32 filename_offset;
u32 reserved;
} PACKED pfs0_file_entry;
typedef struct {
u32 media_start_offset;
u32 media_end_offset;
u8 _0x8[0x8]; /* Padding. */
} PACKED nca_section_entry_t;
typedef struct {
u8 master_hash[0x20]; /* SHA-256 hash of the hash table. */
u32 block_size; /* In bytes. */
u32 always_2;
u64 hash_table_offset; /* Normally zero. */
u64 hash_table_size;
u64 pfs0_offset;
u64 pfs0_size;
u8 _0x48[0xF0];
} PACKED pfs0_superblock_t;
typedef struct {
u64 logical_offset;
u64 hash_data_size;
u32 block_size;
u32 reserved;
} PACKED ivfc_level_hdr_t;
typedef struct {
u32 magic;
u32 id;
u32 master_hash_size;
u32 num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
u8 _0xA0[0x20];
u8 master_hash[0x20];
} PACKED ivfc_hdr_t;
typedef struct {
ivfc_hdr_t ivfc_header;
u8 _0xE0[0x58];
} PACKED romfs_superblock_t;
typedef struct {
u64 offset;
u64 size;
u32 magic; /* "BKTR" */
u32 _0x14; /* Version? */
u32 num_entries;
u32 _0x1C; /* Reserved? */
} PACKED bktr_header_t;
typedef struct {
ivfc_hdr_t ivfc_header;
u8 _0xE0[0x18];
bktr_header_t relocation_header;
bktr_header_t subsection_header;
} PACKED bktr_superblock_t;
/* NCA FS header. */
typedef struct {
u8 _0x0;
u8 _0x1;
u8 partition_type;
u8 fs_type;
u8 crypt_type;
u8 _0x5[0x3];
union { /* FS-specific superblock. Size = 0x138. */
pfs0_superblock_t pfs0_superblock;
romfs_superblock_t romfs_superblock;
bktr_superblock_t bktr_superblock;
};
union {
u8 section_ctr[0x8];
struct {
u32 section_ctr_low;
u32 section_ctr_high;
};
};
u8 _0x148[0xB8]; /* Padding. */
} PACKED nca_fs_header_t;
/* Nintendo content archive header. */
typedef struct {
u8 fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */
u8 npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */
u32 magic;
u8 distribution; /* System vs gamecard. */
u8 content_type;
u8 crypto_type; /* Which keyblob (field 1) */
u8 kaek_ind; /* Which kaek index? */
u64 nca_size; /* Entire archive size. */
u64 title_id;
u8 _0x218[0x4]; /* Padding. */
union {
u32 sdk_version; /* What SDK was this built with? */
struct {
u8 sdk_revision;
u8 sdk_micro;
u8 sdk_minor;
u8 sdk_major;
};
};
u8 crypto_type2; /* Which keyblob (field 2) */
u8 fixed_key_generation;
u8 _0x222[0xE]; /* Padding. */
u8 rights_id[0x10]; /* Rights ID (for titlekey crypto). */
nca_section_entry_t section_entries[4]; /* Section entry metadata. */
u8 section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */
u8 nca_keys[4][0x10]; /* Key area (encrypted) */
u8 _0x340[0xC0]; /* Padding. */
nca_fs_header_t fs_headers[4]; /* FS section headers. */
} PACKED nca_header_t;
typedef struct {
u32 magic;
u32 _0x4;
u32 _0x8;
u8 mmu_flags;
u8 _0xD;
u8 main_thread_prio;
u8 default_cpuid;
u64 _0x10;
u32 process_category;
u32 main_stack_size;
char title_name[0x50];
u32 aci0_offset;
u32 aci0_size;
u32 acid_offset;
u32 acid_size;
} PACKED npdm_t;
typedef struct {
u64 title_id;
u32 version;
u8 type;
u8 unk1;
u16 extended_header_size;
u16 content_cnt;
u16 content_meta_cnt;
u8 attr;
u8 unk2[0x03];
u32 required_dl_sysver;
u8 unk3[0x04];
} PACKED cnmt_header;
typedef struct {
u64 patch_tid; /* Patch TID / Application TID */
u32 min_sysver; /* Minimum system/application version */
u32 min_appver; /* Minimum application version (only for base applications) */
} PACKED cnmt_extended_header;
typedef struct {
u8 hash[0x20];
u8 nca_id[0x10];
u8 size[6];
u8 type;
u8 id_offset;
} PACKED cnmt_content_record;
typedef struct {
u8 type;
u64 title_id;
u32 version;
u32 required_dl_sysver;
u32 nca_cnt;
u8 digest[SHA256_HASH_SIZE];
char digest_str[(SHA256_HASH_SIZE * 2) + 1];
u8 min_keyblob;
u32 min_sysver;
u64 patch_tid;
u32 min_appver;
} cnmt_xml_program_info;
typedef struct {
u8 type;
u8 nca_id[SHA256_HASH_SIZE / 2];
char nca_id_str[SHA256_HASH_SIZE + 1];
u64 size;
u8 hash[SHA256_HASH_SIZE];
char hash_str[(SHA256_HASH_SIZE * 2) + 1];
u8 keyblob;
u8 id_offset;
u64 cnt_record_offset; // Relative to the start of the content records section in the CNMT
u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE];
u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH];
} cnmt_xml_content_info;
typedef struct {
u32 nca_index;
u8 *hash_table;
u64 hash_table_offset; // Relative to NCA start
u64 hash_table_size;
u8 block_mod_cnt;
u8 *block_data[2];
u64 block_offset[2]; // Relative to NCA start
u64 block_size[2];
} nca_program_mod_data;
typedef struct {
u32 nca_index;
u64 xml_size;
char *xml_data;
u8 nacp_icon_cnt; // Only used with Control NCAs
nacp_icons_ctx *nacp_icons; // Only used with Control NCAs
} xml_record_info;
typedef struct {
u64 section_offset; // Relative to NCA start
u64 section_size;
u64 hash_table_offset; // Relative to NCA start
u64 hash_block_size;
u32 hash_block_cnt;
u64 pfs0_offset; // Relative to NCA start
u64 pfs0_size;
u64 title_cnmt_offset; // Relative to NCA start
u64 title_cnmt_size;
} nca_cnmt_mod_data;
typedef struct {
u32 sig_type;
u8 signature[0x100];
u8 padding[0x3C];
char sig_issuer[0x40];
u8 titlekey_block[0x100];
u8 unk1;
u8 titlekey_type;
u8 unk2[0x03];
u8 master_key_rev;
u8 unk3[0x0A];
u64 ticket_id;
u64 device_id;
u8 rights_id[0x10];
u32 account_id;
u8 unk4[0x0C];
} PACKED rsa2048_sha256_ticket;
typedef struct {
bool has_rights_id;
u8 rights_id[0x10];
char rights_id_str[33];
char tik_filename[37];
char cert_filename[38];
u8 enc_titlekey[0x10];
u8 dec_titlekey[0x10];
u8 cert_data[ETICKET_CERT_FILE_SIZE];
rsa2048_sha256_ticket tik_data;
bool retrieved_tik;
bool missing_tik;
} title_rights_ctx;
// Used in HFS0 / ExeFS / RomFS browsers
typedef struct {
u64 size;
char sizeStr[32];
} browser_entry_size_info;
typedef struct {
u8 type; // 1 = Dir, 2 = File
u64 offset; // Relative to directory/file table, depending on type
browser_entry_size_info sizeInfo; // Only used if type == 2
} romfs_browser_entry;
bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize);
bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData);
bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys);
bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx);
bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info);
bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod);
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,206 +0,0 @@
#pragma once
#ifndef __UI_H__
#define __UI_H__
#define FB_WIDTH 1280
#define FB_HEIGHT 720
#define CHAR_PT_SIZE 12
#define SCREEN_DPI_CNT 96
#define LINE_HEIGHT (font_height + (font_height / 4))
#define LINE_STRING_OFFSET (font_height / 8)
#define STRING_DEFAULT_POS 8, 8
#define STRING_X_POS 8
#define STRING_Y_POS(x) (8 + ((x) * LINE_HEIGHT) + ((x) > 0 ? LINE_STRING_OFFSET : 0))
#define BG_COLOR_RGB 50, 50, 50
#define FONT_COLOR_RGB 255, 255, 255
#define HIGHLIGHT_BG_COLOR_RGB 33, 34, 39
#define HIGHLIGHT_FONT_COLOR_RGB 0, 255, 197
#define FONT_COLOR_SUCCESS_RGB 0, 255, 0
#define FONT_COLOR_ERROR_RGB 255, 0, 0
#define FONT_COLOR_TITLE_RGB 115, 115, 255
#define EMPTY_BAR_COLOR_RGB 0, 0, 0
#define COMMON_MAX_ELEMENTS 9
#define HFS0_MAX_ELEMENTS 14
#define ROMFS_MAX_ELEMENTS 12
#define SDCARD_MAX_ELEMENTS 3
#define ORPHAN_MAX_ELEMENTS 12
#define BATCH_MAX_ELEMENTS 14
#define OPTIONS_X_START_POS (35 * CHAR_PT_SIZE)
#define OPTIONS_X_END_POS (OPTIONS_X_START_POS + (6 * CHAR_PT_SIZE))
#define OPTIONS_X_END_POS_NSP (FB_WIDTH - (4 * CHAR_PT_SIZE))
#define TAB_WIDTH 4
#define BROWSER_ICON_DIMENSION 16
// UTF-8 sequences
#define UPWARDS_ARROW "\xE2\x86\x91"
#define DOWNWARDS_ARROW "\xE2\x86\x93"
#define NINTENDO_FONT_A "\xEE\x82\xA0"
#define NINTENDO_FONT_B "\xEE\x82\xA1"
#define NINTENDO_FONT_X "\xEE\x82\xA2"
#define NINTENDO_FONT_Y "\xEE\x82\xA3"
#define NINTENDO_FONT_L "\xEE\x82\xA4"
#define NINTENDO_FONT_R "\xEE\x82\xA5"
#define NINTENDO_FONT_ZL "\xEE\x82\xA6"
#define NINTENDO_FONT_ZR "\xEE\x82\xA7"
#define NINTENDO_FONT_DPAD "\xEE\x82\xAA"
#define NINTENDO_FONT_PLUS "\xEE\x82\xB5"
#define NINTENDO_FONT_HOME "\xEE\x82\xB9"
#define NINTENDO_FONT_LSTICK "\xEE\x83\x81"
#define NINTENDO_FONT_RSTICK "\xEE\x83\x82"
typedef enum {
resultNone,
resultShowMainMenu,
resultShowGameCardMenu,
resultShowXciDumpMenu,
resultDumpXci,
resultShowNspDumpMenu,
resultShowNspAppDumpMenu,
resultShowNspPatchDumpMenu,
resultShowNspAddOnDumpMenu,
resultDumpNsp,
resultShowHfs0Menu,
resultShowRawHfs0PartitionDumpMenu,
resultDumpRawHfs0Partition,
resultShowHfs0PartitionDataDumpMenu,
resultDumpHfs0PartitionData,
resultShowHfs0BrowserMenu,
resultHfs0BrowserGetList,
resultShowHfs0Browser,
resultHfs0BrowserCopyFile,
resultShowExeFsMenu,
resultShowExeFsSectionDataDumpMenu,
resultDumpExeFsSectionData,
resultShowExeFsSectionBrowserMenu,
resultExeFsSectionBrowserGetList,
resultShowExeFsSectionBrowser,
resultExeFsSectionBrowserCopyFile,
resultShowRomFsMenu,
resultShowRomFsSectionDataDumpMenu,
resultDumpRomFsSectionData,
resultShowRomFsSectionBrowserMenu,
resultRomFsSectionBrowserGetEntries,
resultShowRomFsSectionBrowser,
resultRomFsSectionBrowserChangeDir,
resultRomFsSectionBrowserCopyFile,
resultRomFsSectionBrowserCopyDir,
resultDumpGameCardCertificate,
resultShowSdCardEmmcMenu,
resultShowSdCardEmmcTitleMenu,
resultShowSdCardEmmcOrphanPatchAddOnMenu,
resultShowSdCardEmmcBatchModeMenu,
resultSdCardEmmcBatchDump,
resultShowTicketMenu,
resultDumpTicket,
resultShowUpdateMenu,
resultUpdateNSWDBXml,
resultUpdateApplication,
resultExit
} UIResult;
typedef enum {
stateMainMenu,
stateGameCardMenu,
stateXciDumpMenu,
stateDumpXci,
stateNspDumpMenu,
stateNspAppDumpMenu,
stateNspPatchDumpMenu,
stateNspAddOnDumpMenu,
stateDumpNsp,
stateHfs0Menu,
stateRawHfs0PartitionDumpMenu,
stateDumpRawHfs0Partition,
stateHfs0PartitionDataDumpMenu,
stateDumpHfs0PartitionData,
stateHfs0BrowserMenu,
stateHfs0BrowserGetList,
stateHfs0Browser,
stateHfs0BrowserCopyFile,
stateExeFsMenu,
stateExeFsSectionDataDumpMenu,
stateDumpExeFsSectionData,
stateExeFsSectionBrowserMenu,
stateExeFsSectionBrowserGetList,
stateExeFsSectionBrowser,
stateExeFsSectionBrowserCopyFile,
stateRomFsMenu,
stateRomFsSectionDataDumpMenu,
stateDumpRomFsSectionData,
stateRomFsSectionBrowserMenu,
stateRomFsSectionBrowserGetEntries,
stateRomFsSectionBrowser,
stateRomFsSectionBrowserChangeDir,
stateRomFsSectionBrowserCopyFile,
stateRomFsSectionBrowserCopyDir,
stateDumpGameCardCertificate,
stateSdCardEmmcMenu,
stateSdCardEmmcTitleMenu,
stateSdCardEmmcOrphanPatchAddOnMenu,
stateSdCardEmmcBatchModeMenu,
stateSdCardEmmcBatchDump,
stateTicketMenu,
stateDumpTicket,
stateUpdateMenu,
stateUpdateNSWDBXml,
stateUpdateApplication
} UIState;
typedef enum {
MENUTYPE_MAIN = 0,
MENUTYPE_GAMECARD,
MENUTYPE_SDCARD_EMMC
} curMenuType;
void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b);
void uiDrawIcon(const u8 *icon, int width, int height, int x, int y);
bool uiLoadJpgFromMem(u8 *rawJpg, size_t rawJpgSize, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
void uiDrawString(int x, int y, u8 r, u8 g, u8 b, const char *fmt, ...);
u32 uiGetStrWidth(const char *fmt, ...);
void uiRefreshDisplay();
void uiStatusMsg(const char *fmt, ...);
void uiUpdateStatusMsg();
void uiClearStatusMsg();
void uiPleaseWait(u8 wait);
void uiClearScreen();
void uiPrintHeadline();
bool uiInit();
void uiDeinit();
void uiSetState(UIState state);
UIState uiGetState();
UIResult uiProcess();
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,293 +0,0 @@
#pragma once
#ifndef __UTIL_H__
#define __UTIL_H__
#include <switch.h>
#include "nca.h"
#define HBLOADER_BASE_PATH "sdmc:/switch/"
#define APP_BASE_PATH HBLOADER_BASE_PATH APP_TITLE "/"
#define XCI_DUMP_PATH APP_BASE_PATH "XCI/"
#define NSP_DUMP_PATH APP_BASE_PATH "NSP/"
#define HFS0_DUMP_PATH APP_BASE_PATH "HFS0/"
#define EXEFS_DUMP_PATH APP_BASE_PATH "ExeFS/"
#define ROMFS_DUMP_PATH APP_BASE_PATH "RomFS/"
#define CERT_DUMP_PATH APP_BASE_PATH "Certificate/"
#define BATCH_OVERRIDES_PATH NSP_DUMP_PATH "BatchOverrides/"
#define TICKET_PATH APP_BASE_PATH "Ticket/"
#define CONFIG_PATH APP_BASE_PATH "config.bin"
#define NRO_NAME APP_TITLE ".nro"
#define NRO_PATH APP_BASE_PATH NRO_NAME
#define NSWDB_XML_PATH APP_BASE_PATH "NSWreleases.xml"
#define KEYS_FILE_PATH HBLOADER_BASE_PATH "prod.keys"
#define CFW_PATH_ATMOSPHERE "sdmc:/atmosphere/contents/"
#define CFW_PATH_SXOS "sdmc:/sxos/titles/"
#define CFW_PATH_REINX "sdmc:/ReiNX/titles/"
#define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)"
#define GITHUB_API_URL "https://api.github.com/repos/DarkMatterCore/nxdumptool/releases/latest"
#define GITHUB_API_JSON_RELEASE_NAME "name"
#define GITHUB_API_JSON_ASSETS "assets"
#define GITHUB_API_JSON_ASSETS_NAME "name"
#define GITHUB_API_JSON_ASSETS_DL_URL "browser_download_url"
#define NOINTRO_DOM_CHECK_URL "https://datomatic.no-intro.org/qchknsw.php"
#define NSWDB_XML_URL "http://nswdb.com/xml.php"
#define NSWDB_XML_ROOT "releases"
#define NSWDB_XML_CHILD "release"
#define NSWDB_XML_CHILD_TITLEID "titleid"
#define NSWDB_XML_CHILD_IMGCRC "imgcrc"
#define NSWDB_XML_CHILD_RELEASENAME "releasename"
#define LOCKPICK_RCM_URL "https://github.com/shchmue/Lockpick_RCM"
#define KiB (1024.0)
#define MiB (1024.0 * KiB)
#define GiB (1024.0 * MiB)
#define NAME_BUF_LEN 2048
#define DUMP_BUFFER_SIZE (u64)0x400000 // 4 MiB (4194304 bytes)
#define GAMECARD_READ_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes)
#define NCA_CTR_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes)
#define NSP_XML_BUFFER_SIZE (u64)0xA00000 // 10 MiB (10485760 bytes)
#define APPLICATION_PATCH_BITMASK (u64)0x800
#define APPLICATION_ADDON_BITMASK (u64)0xFFFFFFFFFFFF0000
#define NACP_APPNAME_LEN 0x200
#define NACP_AUTHOR_LEN 0x100
#define VERSION_STR_LEN 0x40
#define MEDIA_UNIT_SIZE 0x200
#define ISTORAGE_PARTITION_CNT 2
#define GAMECARD_WAIT_TIME 3 // 3 seconds
#define GAMECARD_HEADER_MAGIC (u32)0x48454144 // "HEAD"
#define GAMECARD_SIZE_1GiB (u64)0x40000000
#define GAMECARD_SIZE_2GiB (u64)0x80000000
#define GAMECARD_SIZE_4GiB (u64)0x100000000
#define GAMECARD_SIZE_8GiB (u64)0x200000000
#define GAMECARD_SIZE_16GiB (u64)0x400000000
#define GAMECARD_SIZE_32GiB (u64)0x800000000
#define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816
#define GAMECARD_ECC_BLOCK_SIZE (u64)0x200 // 512 bytes
#define GAMECARD_ECC_DATA_SIZE (u64)0x24 // 36 bytes
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "secure" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "secure" (3)
#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown"))
#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown")))
#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown"))))
#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown"))
#define HFS0_MAGIC (u32)0x48465330 // "HFS0"
#define HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < (GAMECARD_TYPE1_PARTITION_CNT - 1) ? 0 : 1) : ((y) < (GAMECARD_TYPE2_PARTITION_CNT - 1) ? 0 : 1))
#define NACP_ICON_SQUARE_DIMENSION 256
#define NACP_ICON_DOWNSCALED 96
#define round_up(x, y) ((x) + (((y) - ((x) % (y))) % (y))) // Aligns 'x' bytes to a 'y' bytes boundary
#define ORPHAN_ENTRY_TYPE_PATCH 1
#define ORPHAN_ENTRY_TYPE_ADDON 2
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) // Returns the max number of elements that can be stored in an array
#define MAX_CHARACTERS(x) (MAX_ELEMENTS((x)) - 1) // Returns the max number of characters that can be stored in char array while also leaving space for a NULL terminator
#define BIS_MOUNT_NAME "sys:"
#define BIS_CERT_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e0"
#define BIS_COMMON_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e1"
#define BIS_PERSONALIZED_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e2"
#define SMOOTHING_FACTOR (double)0.1
#define CANCEL_BTN_SEC_HOLD 2 // The cancel button must be held for at least CANCEL_BTN_SEC_HOLD seconds to cancel an ongoing operation
typedef struct {
u64 titleId;
u32 version;
u32 ncmIndex;
NcmStorageId storageId;
char name[NACP_APPNAME_LEN];
char fixedName[NACP_APPNAME_LEN];
char author[NACP_AUTHOR_LEN];
char versionStr[VERSION_STR_LEN];
u8 *icon;
u64 contentSize;
char contentSizeStr[32];
} base_app_ctx_t;
typedef struct {
u64 titleId;
u32 version;
u32 ncmIndex;
NcmStorageId storageId;
char versionStr[VERSION_STR_LEN];
u64 contentSize;
char contentSizeStr[32];
} patch_addon_ctx_t;
typedef struct {
u32 index;
u8 type; // 1 = Patch, 2 = AddOn
char name[NACP_APPNAME_LEN];
char fixedName[NACP_APPNAME_LEN];
char orphanListStr[NACP_APPNAME_LEN * 2];
} orphan_patch_addon_entry;
typedef struct {
int line_offset;
u64 totalSize;
char totalSizeStr[32];
u64 curOffset;
char curOffsetStr[32];
u64 seqDumpCurOffset;
u8 progress;
u64 start;
u64 now;
u64 remainingTime;
char etaInfo[32];
double lastSpeed;
double averageSpeed;
u32 cancelBtnState;
u32 cancelBtnStatePrev;
u64 cancelStartTmr;
u64 cancelEndTmr;
} progress_ctx_t;
typedef enum {
ROMFS_TYPE_APP = 0,
ROMFS_TYPE_PATCH,
ROMFS_TYPE_ADDON
} selectedRomFsType;
typedef enum {
TICKET_TYPE_APP = 0,
TICKET_TYPE_PATCH,
TICKET_TYPE_ADDON
} selectedTicketType;
typedef struct {
bool isFat32;
bool setXciArchiveBit;
bool keepCert;
bool trimDump;
bool calcCrc;
bool useNoIntroLookup;
bool useBrackets;
} PACKED xciOptions;
typedef struct {
bool isFat32;
bool useNoIntroLookup;
bool removeConsoleData;
bool tiklessDump;
bool npdmAcidRsaPatch;
bool dumpDeltaFragments;
bool useBrackets;
} PACKED nspOptions;
typedef enum {
BATCH_SOURCE_ALL = 0,
BATCH_SOURCE_SDCARD,
BATCH_SOURCE_EMMC,
BATCH_SOURCE_CNT
} batchModeSourceStorage;
typedef struct {
bool dumpAppTitles;
bool dumpPatchTitles;
bool dumpAddOnTitles;
bool isFat32;
bool removeConsoleData;
bool tiklessDump;
bool npdmAcidRsaPatch;
bool dumpDeltaFragments;
bool skipDumpedTitles;
bool rememberDumpedTitles;
bool haltOnErrors;
bool useBrackets;
batchModeSourceStorage batchModeSrc;
} PACKED batchOptions;
typedef struct {
bool removeConsoleData;
} PACKED ticketOptions;
typedef struct {
bool isFat32;
bool useLayeredFSDir;
} PACKED ncaFsOptions;
typedef struct {
xciOptions xciDumpCfg;
nspOptions nspDumpCfg;
batchOptions batchDumpCfg;
ticketOptions tikDumpCfg;
ncaFsOptions exeFsDumpCfg;
ncaFsOptions romFsDumpCfg;
} PACKED dumpOptions;
void loadConfig();
void saveConfig();
void freeFilenameBuffer(void);
void freeRomFsBrowserEntries();
void freeHfs0ExeFsEntriesSizes();
bool initApplicationResources(int argc, char **argv);
void deinitApplicationResources();
void appletModeOperationWarning();
void formatETAString(u64 curTime, char *out, size_t outSize);
void generateSdCardEmmcTitleList();
void truncateBrowserEntryName(char *str);
bool getHfs0FileList(u32 partition);
bool readNcaExeFsSection(u32 titleIndex, bool usePatch);
int readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, int desiredIdOffset);
bool getExeFsFileList();
bool getRomFsFileList(u32 dir_offset, bool usePatch);
void printProgressBar(progress_ctx_t *progressCtx, bool calcData, u64 chunkSize);
void setProgressBarError(progress_ctx_t *progressCtx);
bool cancelProcessCheck(progress_ctx_t *progressCtx);
bool yesNoPrompt(const char *message);
bool checkIfDumpedXciContainsCertificate(const char *xciPath);
bool checkIfDumpedNspContainsConsoleData(const char *nspPath);
void removeDirectoryWithVerbose(const char *path, const char *msg);
void gameCardDumpNSWDBCheck(u32 crc);
void noIntroDumpCheck(bool isDigital, u32 crc);
#endif