From 995df94c64805c01f7cb01761af41fe41a80351d Mon Sep 17 00:00:00 2001 From: d0k3 Date: Wed, 24 Aug 2016 01:32:58 +0300 Subject: [PATCH] Added cart dumping options ... also bringing a major restructuring of the code --- Makefile | 2 +- README.md | 1 + source/common.h | 2 +- source/decryptor/game.c | 1162 ++++++++++++++++++++++++++++++++ source/decryptor/game.h | 203 ++++++ source/decryptor/injector.c | 607 ----------------- source/decryptor/injector.h | 73 -- source/decryptor/nandfat.c | 333 +++++++++ source/decryptor/nandfat.h | 22 + source/gamecart/card_ntr.c | 150 +++++ source/gamecart/card_ntr.h | 11 + source/gamecart/command_ctr.c | 57 ++ source/gamecart/command_ctr.h | 14 + source/gamecart/command_ntr.c | 67 ++ source/gamecart/command_ntr.h | 19 + source/gamecart/delay.h | 9 + source/gamecart/iodelay.s | 17 + source/gamecart/nds/bios.h | 0 source/gamecart/nds/card.h | 76 +++ source/gamecart/nds/dma.h | 0 source/gamecart/nds/memory.h | 0 source/gamecart/protocol.c | 239 +++++++ source/gamecart/protocol.h | 25 + source/gamecart/protocol_ctr.c | 183 +++++ source/gamecart/protocol_ctr.h | 42 ++ source/gamecart/protocol_ntr.c | 150 +++++ source/gamecart/protocol_ntr.h | 65 ++ source/gamecart/secure_ntr.c | 320 +++++++++ source/gamecart/secure_ntr.h | 19 + source/main.c | 19 +- 30 files changed, 3202 insertions(+), 685 deletions(-) create mode 100644 source/decryptor/game.c create mode 100644 source/decryptor/game.h delete mode 100644 source/decryptor/injector.c delete mode 100644 source/decryptor/injector.h create mode 100644 source/decryptor/nandfat.c create mode 100644 source/decryptor/nandfat.h create mode 100644 source/gamecart/card_ntr.c create mode 100644 source/gamecart/card_ntr.h create mode 100644 source/gamecart/command_ctr.c create mode 100644 source/gamecart/command_ctr.h create mode 100644 source/gamecart/command_ntr.c create mode 100644 source/gamecart/command_ntr.h create mode 100644 source/gamecart/delay.h create mode 100644 source/gamecart/iodelay.s create mode 100644 source/gamecart/nds/bios.h create mode 100644 source/gamecart/nds/card.h create mode 100644 source/gamecart/nds/dma.h create mode 100644 source/gamecart/nds/memory.h create mode 100644 source/gamecart/protocol.c create mode 100644 source/gamecart/protocol.h create mode 100644 source/gamecart/protocol_ctr.c create mode 100644 source/gamecart/protocol_ctr.h create mode 100644 source/gamecart/protocol_ntr.c create mode 100644 source/gamecart/protocol_ntr.h create mode 100644 source/gamecart/secure_ntr.c create mode 100644 source/gamecart/secure_ntr.h diff --git a/Makefile b/Makefile index 67a81d1..73702ba 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ include $(DEVKITARM)/ds_rules #--------------------------------------------------------------------------------- export TARGET := Hourglass9 BUILD := build -SOURCES := source source/fatfs source/decryptor +SOURCES := source source/fatfs source/decryptor source/gamecart DATA := data INCLUDES := source source/font source/fatfs diff --git a/README.md b/README.md index 18b10b8..ea7bf5a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Hourglass9 is nothing new - all the functionality found within it is in Decrypt9 * __Restore your SysNAND / EmuNAND__ - to return to an earlier state. _This will never overwrite your existing arm9loaderhax installation_. * __Validate existing NAND dumps__ - to make sure they are ready to restore. * __Dump & Inject the Health and Safety app__ - to setup a CIA installer in your system. More info [here](https://gbatemp.net/threads/release-inject-any-app-into-health-safety-o3ds-n3ds-cfw-only.402236/). +* Dump retail game cartridges to .3DS / .CIA / .NDS. * __A nice dragon logo on the bottom screen__ - you wouldn't have expected this, right? While the stuff written above should be enough for the average user, advanced users will still need to use Decrypt9 for more specific modifications of their console OS. Also keep in mind that __you alone or responsible for keeping your backups safe and not losing them__. diff --git a/source/common.h b/source/common.h index 19dd274..5390d90 100644 --- a/source/common.h +++ b/source/common.h @@ -42,7 +42,7 @@ #define BUFFER_MAX_SIZE ((u32) (1 * 1024 * 1024)) // info / log file name -#define VERSION_NAME "Hourglass9 v1.10" +#define VERSION_NAME "Hourglass9 v1.20" #define LOG_FILE "Hourglass9.log" static inline u32 strchrcount(const char* str, char symbol) { diff --git a/source/decryptor/game.c b/source/decryptor/game.c new file mode 100644 index 0000000..90288e1 --- /dev/null +++ b/source/decryptor/game.c @@ -0,0 +1,1162 @@ +#include "fs.h" +#include "draw.h" +#include "hid.h" +#include "platform.h" +#include "gamecart/protocol.h" +#include "gamecart/command_ctr.h" +#include "gamecart/command_ntr.h" +#include "decryptor/aes.h" +#include "decryptor/sha.h" +#include "decryptor/decryptor.h" +#include "decryptor/hashfile.h" +#include "decryptor/keys.h" +#include "decryptor/nandfat.h" +#include "decryptor/nand.h" +#include "decryptor/game.h" + +#define CART_CHUNK_SIZE (u32) (1*1024*1024) + + +u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 sub_id) { + memset(ctr, 0x00, 16); + if (ncch->version == 1) { + memcpy(ctr, &(ncch->partitionId), 8); + if (sub_id == 1) { // exHeader ctr + add_ctr(ctr, 0x200); + } else if (sub_id == 2) { // exeFS ctr + add_ctr(ctr, ncch->offset_exefs * 0x200); + } else if (sub_id == 3) { // romFS ctr + add_ctr(ctr, ncch->offset_romfs * 0x200); + } + } else { + for (u32 i = 0; i < 8; i++) + ctr[i] = ((u8*) &(ncch->partitionId))[7-i]; + ctr[8] = sub_id; + } + + return 0; +} + +u32 CryptSdToSd(const char* filename, u32 offset, u32 size, CryptBufferInfo* info, bool handle_offset16) +{ + u8* buffer = BUFFER_ADDRESS; + u32 offset_16 = (handle_offset16) ? offset % 16 : 0; + u32 result = 0; + + // no DebugFileOpen() - at this point the file has already been checked enough + if (!FileOpen(filename)) + return 1; + + info->buffer = buffer; + if (offset_16) { // handle offset alignment / this assumes the data is >= 16 byte + if(!DebugFileRead(buffer + offset_16, 16 - offset_16, offset)) { + result = 1; + } + info->size = 16; + CryptBuffer(info); + if(!DebugFileWrite(buffer + offset_16, 16 - offset_16, offset)) { + result = 1; + } + } + for (u32 i = (offset_16) ? (16 - offset_16) : 0; i < size; i += BUFFER_MAX_SIZE) { + u32 read_bytes = min(BUFFER_MAX_SIZE, (size - i)); + ShowProgress(i, size); + if(!DebugFileRead(buffer, read_bytes, offset + i)) { + result = 1; + break; + } + info->size = read_bytes; + CryptBuffer(info); + if(!DebugFileWrite(buffer, read_bytes, offset + i)) { + result = 1; + break; + } + } + + ShowProgress(0, 0); + FileClose(); + + return result; +} + +u32 VerifyNcch(const char* filename, u32 offset) +{ + NcchHeader* ncch = (NcchHeader*) 0x20316200; + u8* exefs = (u8*) 0x20316400; + char* status_str[3] = { "OK", "Fail", "-" }; + u32 ver_exthdr = 2; + u32 ver_exefs = 2; + u32 ver_romfs = 2; + + // some basic checks included - this only verifies decrypted NCCHs + if (FileGetData(filename, (void*) ncch, 0x200, offset) != 0x200) + return 1; + if ((memcmp(ncch->magic, "NCCH", 4) != 0) || (!(ncch->flags[7] & 0x04))) + return 1; + + // base hash checks for ExHeader / ExeFS / RomFS + if (ncch->size_exthdr > 0) + ver_exthdr = CheckHashFromFile(filename, offset + 0x200, 0x400, ncch->hash_exthdr); + if (ncch->size_exefs_hash > 0) + ver_exefs = CheckHashFromFile(filename, offset + (ncch->offset_exefs * 0x200), ncch->size_exefs_hash * 0x200, ncch->hash_exefs); + if (ncch->size_romfs_hash > 0) + ver_romfs = CheckHashFromFile(filename, offset + (ncch->offset_romfs * 0x200), ncch->size_romfs_hash * 0x200, ncch->hash_romfs); + + // thorough exefs verification + if (ncch->size_exefs > 0) { + u32 offset_byte = ncch->offset_exefs * 0x200; + if (FileGetData(filename, exefs, 0x200, offset + offset_byte) != 0x200) + ver_exefs = 1; + for (u32 i = 0; (i < 10) && (ver_exefs != 1); i++) { + u32 offset_exefs_file = offset_byte + getle32(exefs + (i*0x10) + 0x8) + 0x200; + u32 size_exefs_file = getle32(exefs + (i*0x10) + 0xC); + u8* hash_exefs_file = exefs + 0x200 - ((i+1)*0x20); + if (size_exefs_file == 0) + break; + ver_exefs = CheckHashFromFile(filename, offset + offset_exefs_file, size_exefs_file, hash_exefs_file); + } + } + + // output results + Debug("Verify ExHdr/ExeFS/RomFS: %s/%s/%s", status_str[ver_exthdr], status_str[ver_exefs], status_str[ver_romfs]); + + return (((ver_exthdr | ver_exefs | ver_romfs) & 1) == 0) ? 0 : 1; +} + +u32 CryptNcch(const char* filename, u32 offset, u32 size, u64 seedId, u8* encrypt_flags) +{ + NcchHeader* ncch = (NcchHeader*) 0x20316200; + u8* buffer = (u8*) 0x20316400; + CryptBufferInfo info0 = {.setKeyY = 1, .keyslot = 0x2C, .mode = AES_CNT_CTRNAND_MODE}; + CryptBufferInfo info1 = {.setKeyY = 1, .mode = AES_CNT_CTRNAND_MODE}; + u8 seedKeyY[16] = { 0x00 }; + u32 result = 0; + + if (FileGetData(filename, (void*) ncch, 0x200, offset) != 0x200) + return 1; // it's impossible to fail here anyways + + // check (again) for magic number + if (memcmp(ncch->magic, "NCCH", 4) != 0) { + Debug("Not a NCCH container"); + return 2; // not an actual error + } + + // size plausibility check + u32 size_sum = 0x200 + ((ncch->size_exthdr) ? 0x800 : 0x0) + 0x200 * + (ncch->size_plain + ncch->size_logo + ncch->size_exefs + ncch->size_romfs); + if (ncch->size * 0x200 < size_sum) { + Debug("Probably not a NCCH container"); + return 2; // not an actual error + } + + // check if encrypted + if (!encrypt_flags && (ncch->flags[7] & 0x04)) { + Debug("NCCH is not encrypted"); + return 2; // not an actual error + } else if (encrypt_flags && !(ncch->flags[7] & 0x04)) { + Debug("NCCH is already encrypted"); + return 2; // not an actual error + } else if (encrypt_flags && (encrypt_flags[7] & 0x04)) { + Debug("Nothing to do!"); + return 2; // not an actual error + } + + // check size + if ((size > 0) && (ncch->size * 0x200 > size)) { + Debug("NCCH size is out of bounds"); + return 1; + } + + // select correct title ID for seed crypto + if (seedId == 0) seedId = ncch->programId; + + // copy over encryption parameters (if applicable) + if (encrypt_flags) { + ncch->flags[3] = encrypt_flags[3]; + ncch->flags[7] &= (0x01|0x20|0x04)^0xFF; + ncch->flags[7] |= (0x01|0x20)&encrypt_flags[7]; + } + + // check crypto type + bool uses7xCrypto = ncch->flags[3]; + bool usesSeedCrypto = ncch->flags[7] & 0x20; + bool usesSec3Crypto = (ncch->flags[3] == 0x0A); + bool usesSec4Crypto = (ncch->flags[3] == 0x0B); + bool usesFixedKey = ncch->flags[7] & 0x01; + + Debug("Code / Crypto: %.16s / %s%s%s%s", ncch->productcode, (usesFixedKey) ? "FixedKey " : "", (usesSec4Crypto) ? "Secure4 " : (usesSec3Crypto) ? "Secure3 " : (uses7xCrypto) ? "7x " : "", (usesSeedCrypto) ? "Seed " : "", (!uses7xCrypto && !usesSeedCrypto && !usesFixedKey) ? "Standard" : ""); + + // setup zero key crypto + if (usesFixedKey) { + // from https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h + u8 zeroKey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + u8 sysKey[16] = {0x52, 0x7C, 0xE6, 0x30, 0xA9, 0xCA, 0x30, 0x5F, 0x36, 0x96, 0xF3, 0xCD, 0xE9, 0x54, 0x19, 0x4B}; + uses7xCrypto = usesSeedCrypto = usesSec3Crypto = usesSec4Crypto = false; + info1.setKeyY = info0.setKeyY = 0; + info1.keyslot = info0.keyslot = 0x11; + setup_aeskey(0x11, (ncch->programId & ((u64) 0x10 << 32)) ? sysKey : zeroKey); + } + + // check 7x crypto + if (uses7xCrypto && (CheckKeySlot(0x25, 'X') != 0)) { + Debug("slot0x25KeyX not set up"); + Debug("This won't work on O3DS < 7.x or A9LH"); + return 1; + } + + // check Secure3 crypto on O3DS + if (usesSec3Crypto && (CheckKeySlot(0x18, 'X') != 0)) { + Debug("slot0x18KeyX not set up"); + Debug("Secure3 crypto is not available"); + return 1; + } + + // check Secure4 crypto + if (usesSec4Crypto && (CheckKeySlot(0x1B, 'X') != 0)) { + Debug("slot0x1BKeyX not set up"); + Debug("Secure4 crypto is not available"); + return 1; + } + + // check / setup seed crypto + if (usesSeedCrypto) { + if (FileOpen("seeddb.bin")) { + SeedInfoEntry* entry = (SeedInfoEntry*) buffer; + u32 found = 0; + for (u32 i = 0x10;; i += 0x20) { + if (FileRead(entry, 0x20, i) != 0x20) + break; + if (entry->titleId == seedId) { + u8 keydata[32]; + memcpy(keydata, ncch->signature, 16); + memcpy(keydata + 16, entry->external_seed, 16); + u8 sha256sum[32]; + sha_quick(sha256sum, keydata, 32, SHA256_MODE); + memcpy(seedKeyY, sha256sum, 16); + found = 1; + } + } + FileClose(); + if (!found) { + Debug("Seed not found in seeddb.bin!"); + return 1; + } + } else { + Debug("Need seeddb.bin for seed crypto!"); + return 1; + } + Debug("Loading seed from seeddb.bin: ok"); + } + + // basic setup of CryptBufferInfo structs + memcpy(info0.keyY, ncch->signature, 16); + memcpy(info1.keyY, (usesSeedCrypto) ? seedKeyY : ncch->signature, 16); + info1.keyslot = (usesSec4Crypto) ? 0x1B : ((usesSec3Crypto) ? 0x18 : ((uses7xCrypto) ? 0x25 : info0.keyslot)); + + Debug("%s ExHdr/ExeFS/RomFS (%ukB/%ukB/%uMB)", + (encrypt_flags) ? "Encrypt" : "Decrypt", + (ncch->size_exthdr > 0) ? 0x800 / 1024 : 0, + (ncch->size_exefs * 0x200) / 1024, + (ncch->size_romfs * 0x200) / (1024*1024)); + + // process ExHeader + if (ncch->size_exthdr > 0) { + GetNcchCtr(info0.ctr, ncch, 1); + result |= CryptSdToSd(filename, offset + 0x200, 0x800, &info0, true); + } + + // process ExeFS + if (ncch->size_exefs > 0) { + u32 offset_byte = ncch->offset_exefs * 0x200; + u32 size_byte = ncch->size_exefs * 0x200; + if (uses7xCrypto || usesSeedCrypto) { + GetNcchCtr(info0.ctr, ncch, 2); + if (!encrypt_flags) // decrypt this first (when decrypting) + result |= CryptSdToSd(filename, offset + offset_byte, 0x200, &info0, true); + if (FileGetData(filename, buffer, 0x200, offset + offset_byte) != 0x200) // get exeFS header + return 1; + if (encrypt_flags) // encrypt this last (when encrypting) + result |= CryptSdToSd(filename, offset + offset_byte, 0x200, &info0, true); + // special ExeFS decryption routine ("banner" and "icon" use standard crypto) + for (u32 i = 0; i < 10; i++) { + char* name_exefs_file = (char*) buffer + (i*0x10); + u32 offset_exefs_file = getle32(buffer + (i*0x10) + 0x8) + 0x200; + u32 size_exefs_file = align(getle32(buffer + (i*0x10) + 0xC), 0x200); + CryptBufferInfo* infoExeFs = ((strncmp(name_exefs_file, "banner", 8) == 0) || + (strncmp(name_exefs_file, "icon", 8) == 0)) ? &info0 : &info1; + if (size_exefs_file == 0) + continue; + if (offset_exefs_file % 16) { + Debug("ExeFS file offset not aligned!"); + result |= 1; + break; // this should not happen + } + GetNcchCtr(infoExeFs->ctr, ncch, 2); + add_ctr(infoExeFs->ctr, offset_exefs_file / 0x10); + infoExeFs->setKeyY = 1; + result |= CryptSdToSd(filename, offset + offset_byte + offset_exefs_file, + align(size_exefs_file, 16), infoExeFs, true); + } + } else { + GetNcchCtr(info0.ctr, ncch, 2); + result |= CryptSdToSd(filename, offset + offset_byte, size_byte, &info0, true); + } + } + + // process RomFS + if (ncch->size_romfs > 0) { + GetNcchCtr(info1.ctr, ncch, 3); + if (!usesFixedKey) + info1.setKeyY = 1; + result |= CryptSdToSd(filename, offset + (ncch->offset_romfs * 0x200), ncch->size_romfs * 0x200, &info1, true); + } + + // set NCCH header flags + if (!encrypt_flags) { + ncch->flags[3] = 0x00; + ncch->flags[7] &= (0x01|0x20)^0xFF; + ncch->flags[7] |= 0x04; + } + + // write header back + if (!FileOpen(filename)) + return 1; + if (!DebugFileWrite((void*) ncch, 0x200, offset)) { + FileClose(); + return 1; + } + FileClose(); + + + return ((result == 0) && !encrypt_flags) ? VerifyNcch(filename, offset) : result; +} + +u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) +{ + info->offset_cert = align(header->size_header, 64); + info->offset_ticket = info->offset_cert + align(header->size_cert, 64); + info->offset_tmd = info->offset_ticket + align(header->size_ticket, 64); + info->offset_content = info->offset_tmd + align(header->size_tmd, 64); + info->offset_meta = (header->size_meta) ? info->offset_content + align(header->size_content, 64) : 0; + info->offset_ticktmd = info->offset_ticket; + info->offset_content_list = info->offset_tmd + sizeof(TitleMetaData); + + info->size_cert = header->size_cert; + info->size_ticket = header->size_ticket; + info->size_tmd = header->size_tmd; + info->size_content = header->size_content; + info->size_meta = header->size_meta; + info->size_ticktmd = info->offset_content - info->offset_ticket; + info->size_content_list = info->size_tmd - sizeof(TitleMetaData); + info->size_cia = (header->size_meta) ? info->offset_meta + info->size_meta : + info->offset_content + info->size_content; + + return 0; +} + +u32 BuildCiaStub(u8* stub, u8* ncchncsd) +{ + // stub should have at least room for 16KiB (0x4000) + const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; + u64 content_size[3] = { 0 }; + u8 content_type[3] = { 0x00 }; + u8 title_id[8] = { 0x00 }; + u32 content_count = 0; + u8 cia_cnt_index = 0; + CiaInfo cia; + + + // set everything zero for a clean start + memset(stub, 0, 0x4000); + + // check type of provided ncchncsd header + if (memcmp(ncchncsd + 0x100, "NCCH", 4) == 0) { + NcchHeader* ncch = (NcchHeader*) ncchncsd; + cia_cnt_index = 1 << 7; + content_count = 1; + content_size[0] = ncch->size * 0x200; + content_type[0] = 0; + for (u32 i = 0; i < 8; i++) + title_id[i] = ((u8*) &(ncch->partitionId))[7-i]; + } else if (memcmp(ncchncsd + 0x100, "NCSD", 4) == 0) { + NcsdHeader* ncsd = (NcsdHeader*) ncchncsd; + for (u32 p = 0; p < 3; p++) { + if (ncsd->partitions[p].size) { + cia_cnt_index |= (1 << (7-p)); // <-- might not be right + content_size[content_count] = ncsd->partitions[p].size * 0x200; + content_type[content_count++] = p; + } + } + for (u32 i = 0; i < 8; i++) + title_id[i] = ((u8*) &(ncsd->mediaId))[7-i]; + } else { + Debug("Bad NCCH/NCSD header"); // meaning: developer did not pay attention + return 0; + } + + // CIA header + CiaHeader* header = (CiaHeader*) stub; + header->size_header = sizeof(CiaHeader); + header->size_cert = CIA_CERT_SIZE; + header->size_ticket = sizeof(Ticket); + header->size_tmd = sizeof(TitleMetaData) + (content_count * sizeof(TmdContentChunk)); + header->size_content = content_size[0] + content_size[1] + content_size[2]; + header->size_meta = sizeof(CiaMeta); + header->content_index[0] = cia_cnt_index; + GetCiaInfo(&cia, header); + + // Certificate chain + // Thanks go to ihaveamac for discovering this and the offsets + const u8 cert_hash_expected[0x20] = { + 0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A, + 0x10, 0x78, 0xDF, 0x42, 0x99, 0x80, 0x3A, 0x68, 0x58, 0xF0, 0x41, 0xF9, 0xCB, 0x10, 0xE6, 0x35 + }; + u8* cert = (u8*) (stub + cia.offset_cert); + u8* cert_db = (u8*) 0x20400000; // should be okay to use this area + PartitionInfo* p_ctrnand = GetPartitionInfo(P_CTRNAND); + u32 offset_db, size_db; + if ((SeekFileInNand(&offset_db, &size_db, "DBS CERTS DB ", p_ctrnand) != 0) || (size_db != 0x6000)){ + Debug("certs.db not found or bad size"); + return 1; + } + if (DecryptNandToMem(cert_db, offset_db, size_db, p_ctrnand) != 0) + return 0; + memcpy(cert + 0x000, cert_db + 0x0C10, 0x1F0); + memcpy(cert + 0x1F0, cert_db + 0x3A00, 0x210); + memcpy(cert + 0x400, cert_db + 0x3F10, 0x300); + memcpy(cert + 0x700, cert_db + 0x3C10, 0x300); + u8 cert_hash[0x20]; + sha_quick(cert_hash, cert, CIA_CERT_SIZE, SHA256_MODE); + if (memcmp(cert_hash, cert_hash_expected, 0x20) != 0) { + Debug("Error generating certificate chain"); + return 1; + } + + // Ticket + u8 ticket_cnt_index[] = { // whatever this is + 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84, + 0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + Ticket* ticket = (Ticket*) (stub + cia.offset_ticket); + memcpy(ticket->sig_type, sig_type, 4); + memset(ticket->signature, 0xFF, 0x100); + snprintf((char*) ticket->issuer, 0x40, "Root-CA00000003-XS0000000c"); + memset(ticket->ecdsa, 0xFF, 0x3C); + ticket->version = 0x01; + memset(ticket->titlekey, 0xFF, 16); + memcpy(ticket->title_id, title_id, 8); + ticket->commonkey_idx = 0x01; + ticket->unknown_buf[0x2F] = 0x01; // whatever + memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index)); + + // TMD + TitleMetaData* tmd = (TitleMetaData*) (stub + cia.offset_tmd); + memcpy(tmd->sig_type, sig_type, 4); + memset(tmd->signature, 0xFF, 0x100); + snprintf((char*) tmd->issuer, 0x40, "Root-CA00000003-CP0000000b"); + tmd->version = 0x01; + memcpy(tmd->title_id, title_id, 8); + tmd->title_type[3] = 0x40; // whatever + memset(tmd->save_size, 0x00, 4); // placeholder + tmd->content_count[1] = (u8) content_count; + memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash) + tmd->contentinfo[0].cmd_count[1] = (u8) content_count; + memset(tmd->contentinfo[0].hash, 0xFF, 0x20); // placeholder (hash) + + // TMD content list + TmdContentChunk* content_list = (TmdContentChunk*) (stub + cia.offset_content_list); + for (u32 i = 0; i < content_count; i++) { + content_list[i].id[3] = i; + content_list[i].index[1] = content_type[i]; + for (u32 j = 0; j < 8; j++) // content size + content_list[i].size[j] = (u8) (content_size[i] >> (8*(7-j))); + memset(content_list[i].hash, 0xFF, 0x20); // placeholder (content hash) + } + + return cia.offset_content; +} + +u32 FixCiaFile(const char* filename) +{ + u8* buffer = (u8*) 0x20316000; + NcchHeader* ncch = (NcchHeader*) (0x20316000 + 0x4000); + u8* exthdr = (u8*) (0x20316000 + 0x4200); // we only need the first 0x400 byte + CiaMeta* meta = (CiaMeta*) 0x20400000; + CiaInfo cia; + + + // fetch CIA info, Ticket, TMD, content_list + if ((FileGetData(filename, buffer, 0x4000, 0) != 0x4000) || (memcmp(buffer, "\x20\x20", 2) != 0)) { + Debug("This does not look like a CIA file"); // checking an arbitrary size here + return 1; + } + GetCiaInfo(&cia, (CiaHeader*) buffer); + if (cia.offset_content > 0x4000) { + Debug("CIA stub has bad size (%lu)", cia.offset_content); + return 1; + } + TitleMetaData* tmd = (TitleMetaData*) (buffer + cia.offset_tmd); + TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); + + u32 next_offset = cia.offset_content; + u32 content_count = getbe16(tmd->content_count); + for (u32 i = 0; i < content_count; i++) { + u32 size = (u32) getbe64(content_list[i].size); + u32 offset = next_offset; + next_offset = offset + size; + + // Fix NCCH ExHeader, build metadata (only for first content CXI) + if ((i == 0) && (getbe16(content_list[i].index) == 0)) { + if ((FileGetData(filename, ncch, 0x600, offset) != 0x600) || (memcmp(ncch->magic, "NCCH", 4) != 0)) { + Debug("Failed reading NCCH content"); + return 1; + } + + // init metadata with all zeroes + memset(meta, 0x00, sizeof(CiaMeta)); + meta->core_version = 2; + + // prepare crypto stuff (even if it may not get used) + CryptBufferInfo info = {.setKeyY = 1, .keyslot = 0x2C, .buffer = exthdr, .size = 0x400, .mode = AES_CNT_CTRNAND_MODE}; + memcpy(info.keyY, ncch->signature, 16); + if (ncch->flags[7] & 0x01) { // set up zerokey crypto instead + __attribute__((aligned(16))) u8 zeroKey[16] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + info.setKeyY = 0; + info.keyslot = 0x11; + setup_aeskey(0x11, zeroKey); + use_aeskey(0x11); + } + + // process extheader + if (ncch->size_exthdr > 0) { + if (!(ncch->flags[7] & 0x04)) { // encrypted NCCH + GetNcchCtr(info.ctr, ncch, 1); + CryptBuffer(&info); + } + exthdr[0xD] |= (1<<1); // set SD flag + memcpy(tmd->save_size, exthdr + 0x1C0, 4); // get save size for CXI + sha_quick(ncch->hash_exthdr, exthdr, 0x400, SHA256_MODE); // fix exheader hash + memcpy(meta->dependencies, exthdr + 0x40, 0x180); // copy dependencies to meta + if (!(ncch->flags[7] & 0x04)) { // encrypted NCCH + GetNcchCtr(info.ctr, ncch, 1); + CryptBuffer(&info); + } + } + + // process ExeFS (for SMDH) + if (ncch->size_exefs > 0) { + u8 exefs_hdr[0x200]; + u32 offset_exefs = ncch->offset_exefs * 0x200; + if (FileGetData(filename, exefs_hdr, 0x200, offset + offset_exefs) != 0x200) { + Debug("Failed reading NCCH ExeFS content"); + return 1; + } + if (!(ncch->flags[7] & 0x04)) { // encrypted ExeFS + info.buffer = exefs_hdr; + info.size = 0x200; + GetNcchCtr(info.ctr, ncch, 2); + CryptBuffer(&info); + } + for (u32 j = 0; j < 10; j++) { // search for icon + char* name_exefs_file = (char*) exefs_hdr + (j*0x10); + u32 offset_exefs_file = getle32(exefs_hdr + (j*0x10) + 0x8) + 0x200; + u32 size_exefs_file = align(getle32(exefs_hdr + (j*0x10) + 0xC), 0x10); + if ((size_exefs_file > 0) && (size_exefs_file <= 0x36C0) && !(offset_exefs_file % 16) && + (strncmp(name_exefs_file, "icon", 8) == 0)) { + if (FileGetData(filename, meta->smdh, size_exefs_file, + offset + offset_exefs + offset_exefs_file) != size_exefs_file) { + Debug("Failed reading NCCH ExeFS SMDH"); + return 1; + } + if (!(ncch->flags[7] & 0x04)) { // encrypted ExeFS SMDH + info.buffer = meta->smdh; + info.size = size_exefs_file; + GetNcchCtr(info.ctr, ncch, 2); + add_ctr(info.ctr, offset_exefs_file / 0x10); + CryptBuffer(&info); + } + break; + } + } + } + + // inject NCCH / exthdr back & append metadata + if (!FileOpen(filename)) + return 1; + if (!DebugFileWrite(ncch, 0x400, offset) || !DebugFileWrite(meta, sizeof(CiaMeta), cia.offset_meta)) { + FileClose(); + return 1; + } + FileClose(); + } + + // (re)calculate hash + if (GetHashFromFile(filename, offset, size, content_list[i].hash) != 0) { + Debug("Hash recalculation failed!"); + return 1; + } + } + + // fix other TMD hashes + for (u32 i = 0, kc = 0; i < 64 && kc < content_count; i++) { + TmdContentInfo* cntinfo = tmd->contentinfo + i; + u32 k = getbe16(cntinfo->cmd_count); + sha_quick(cntinfo->hash, content_list + kc, k * sizeof(TmdContentChunk), SHA256_MODE); + kc += k; + } + sha_quick(tmd->contentinfo_hash, (u8*)tmd->contentinfo, 64 * sizeof(TmdContentInfo), SHA256_MODE); + + // inject fixed TMD back to CIA file + if (!FileOpen(filename)) + return 1; + if (!DebugFileWrite(tmd, cia.size_tmd, cia.offset_tmd)) { + FileClose(); + return 1; + } + FileClose(); + + + return 0; +} + +static u32 DumpCartToFile(u32 offset_cart, u32 offset_file, u32 size, u32 total, CryptBufferInfo* info, u8* out) +{ + // this assumes cart dumping initialized & file open for writing + // also, careful, uses standard buffer + u8* buffer = BUFFER_ADDRESS; + u32 result = 0; + + if (info) { + info->buffer = buffer; + } + + // if offset_cart does not start at sector boundary + if (offset_cart % 0x200) { + u32 read_bytes = 0x200 - (offset_cart % 0x200); + Cart_Dummy(); + Cart_Dummy(); + CTR_CmdReadData(offset_cart / 0x200, 0x200, 1, buffer); + memmove(buffer, buffer + (offset_cart % 0x200), read_bytes); + if (info) { + info->size = read_bytes; + CryptBuffer(info); + } + if (out) { + memcpy(out, buffer, read_bytes); + out += read_bytes; + } + if (!DebugFileWrite(buffer, read_bytes, offset_file)) + return 1; + offset_cart += read_bytes; + offset_file += read_bytes; + } + + for (u64 i = 0; i < size; i += CART_CHUNK_SIZE) { + u32 read_bytes = min(CART_CHUNK_SIZE, (size - i)); + if (total) + ShowProgress(offset_file + i, total); + Cart_Dummy(); + Cart_Dummy(); + CTR_CmdReadData((offset_cart + i) / 0x200, 0x200, (read_bytes + 0x1FF) / 0x200, buffer); + if (info) { + info->size = read_bytes; + CryptBuffer(info); + } + if (out) { + memcpy(out, buffer, read_bytes); + out += read_bytes; + } + if (!DebugFileWrite(buffer, read_bytes, offset_file + i)) { + result = 1; + break; + } + } + ShowProgress(0, 0); + + return result; +} + +static u32 DecryptCartNcchToFile(u32 offset_cart, u32 offset_file, u32 size, u32 total) +{ + // this assumes cart dumping to be initialized already and file open for writing(!) + // algorithm is simplified / slimmed when compared to CryptNcch() + // and only has required capabilities + NcchHeader* ncch = (NcchHeader*) 0x20317000; + u8* exefs = (u8*) 0x20317200; + CryptBufferInfo info = {.setKeyY = 0, .mode = AES_CNT_CTRNAND_MODE}; + u32 slot_base = 0x2C; + u32 slot_7x = 0x2C; + + // read header + Cart_Dummy(); + Cart_Dummy(); + CTR_CmdReadData(offset_cart / 0x200, 0x200, 1, ncch); + + // check header, set up stuff + if ((memcmp(ncch->magic, "NCCH", 4) != 0) || (ncch->size > (size / 0x200))) { + Debug("Error reading partition NCCH header"); + return 1; + } + + // check crypto, setup crypto + if (ncch->flags[7] & 0x04) { // for unencrypted partitions... + Debug("Not encrypted, dumping instead..."); + return DumpCartToFile(offset_cart, offset_file, size, total, NULL, NULL); + } else if (ncch->flags[7] & 0x1) { // zeroKey / fixedKey crypto + // from https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h + u8 zeroKey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + u8 sysKey[16] = {0x52, 0x7C, 0xE6, 0x30, 0xA9, 0xCA, 0x30, 0x5F, 0x36, 0x96, 0xF3, 0xCD, 0xE9, 0x54, 0x19, 0x4B}; + slot_base = slot_7x = 0x11; + setup_aeskey(0x11, (ncch->programId & ((u64) 0x10 << 32)) ? sysKey : zeroKey); + use_aeskey(0x11); + } else if (ncch->flags[3]) { // 7x crypto type + slot_7x = (ncch->flags[3] == 0x0A) ? 0x18 : (ncch->flags[3] == 0x0B) ? 0x1B : 0x25; + if (CheckKeySlot(slot_7x, 'X') != 0) { + Debug("Slot0x%02XKeyX not set up", slot_7x); + return 1; + } + setup_aeskeyY(slot_7x, ncch->signature); + use_aeskey(slot_7x); + setup_aeskeyY(slot_base, ncch->signature); + use_aeskey(slot_base); + } else { // standard crypto type (pre 7x) + setup_aeskeyY(slot_base, ncch->signature); + use_aeskey(slot_base); + } + + // disable crypto in header, write to file + ncch->flags[3] = 0x00; + ncch->flags[7] &= (0x01|0x20)^0xFF; + ncch->flags[7] |= 0x04; + if (!DebugFileWrite(ncch, 0x200, offset_file)) + return 1; + + // process ExHeader + if (ncch->size_exthdr > 0) { + GetNcchCtr(info.ctr, ncch, 1); + info.keyslot = slot_base; + if (DumpCartToFile(offset_cart + 0x200, offset_file + 0x200, 0x800, total, &info, NULL) != 0) + return 1; + } + + // logo region / plain region + if (ncch->offset_exefs > 5) { + if (DumpCartToFile(offset_cart + 0xA00, offset_file + 0xA00, (ncch->offset_exefs - 5) * 0x200, total, NULL, NULL) != 0) + return 1; + } + + // process ExeFS + if (ncch->size_exefs > 0) { + u32 offset_exefs = ncch->offset_exefs * 0x200; + // include space between ExeFS and RomFS if possible + u32 size_exefs = ((ncch->offset_romfs) ? (ncch->offset_romfs - ncch->offset_exefs) : + ncch->size_exefs) * 0x200; + // dump the whole thing encrypted, then overwrite with decrypted + if (DumpCartToFile(offset_cart + offset_exefs, offset_file + offset_exefs, size_exefs, total, NULL, NULL) != 0) + return 1; + // using 7x crypto routines for everything + GetNcchCtr(info.ctr, ncch, 2); + info.keyslot = slot_base; + if (DumpCartToFile(offset_cart + offset_exefs, offset_file + offset_exefs, 0x200, total, &info, exefs) != 0) + return 1; + for (u32 i = 0; i < 10; i++) { + char* name = (char*) exefs + (i*0x10); + u32 offset_exefs_file = getle32(exefs + (i*0x10) + 0x8) + 0x200; + u32 size_exefs_file = align(getle32(exefs + (i*0x10) + 0xC), 0x200); + if (!size_exefs_file) + continue; + GetNcchCtr(info.ctr, ncch, 2); + add_ctr(info.ctr, offset_exefs_file / 0x10); + info.keyslot = ((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? slot_base : slot_7x; + if (DumpCartToFile(offset_cart + offset_exefs + offset_exefs_file, + offset_file + offset_exefs + offset_exefs_file, size_exefs_file, total, &info, NULL) != 0) + return 1; + } + } + + // process RomFS + if (ncch->size_romfs > 0) { + GetNcchCtr(info.ctr, ncch, 3); + info.keyslot = slot_7x; + if (DumpCartToFile(offset_cart + (ncch->offset_romfs * 0x200), + offset_file + (ncch->offset_romfs * 0x200), (ncch->size_romfs * 0x200), total, &info, NULL) != 0) + return 1; + } + + return 0; +} + +u32 DumpCtrGameCart(u32 param) +{ + NcsdHeader* ncsd = (NcsdHeader*) 0x20316000; + NcchHeader* ncch = (NcchHeader*) 0x20317000; + CiaHeader* cia_stub = (CiaHeader*) 0x2031A000; + CiaInfo cia; + char filename[64]; + u64 cart_size = 0; + u64 data_size = 0; + u64 dump_size = 0; + u32 result = 0; + + // read cartridge NCCH header + CTR_CmdReadHeader(ncch); + if (memcmp(ncch->magic, "NCCH", 4) != 0) { + Debug("Error reading cart NCCH header"); + return 1; + } + + // secure init + u32 sec_keys[4]; + Cart_Secure_Init((u32*) ncch, sec_keys); + + // read NCSD header + Cart_Dummy(); + CTR_CmdReadData(0, 0x200, 0x1000 / 0x200, ncsd); + if (memcmp(ncsd->magic, "NCSD", 4) != 0) { + Debug("Error reading cart NCSD header"); + return 1; + } + + // check NCSD partition table + cart_size = (u64) ncsd->size * 0x200; + for (u32 i = 0; i < 8; i++) { + NcchPartition* partition = ncsd->partitions + i; + if ((partition->offset == 0) && (partition->size == 0)) + continue; + if (partition->offset < (data_size / 0x200)) { + Debug("Overlapping partitions in NCSD table"); + return 1; // should never happen + } + data_size = (u64) (partition->offset + partition->size) * 0x200; + } + + // output some info + Debug("Product ID: %.16s", ncch->productcode); + Debug("Cartridge data size: %lluMB", cart_size / 0x100000); + Debug("Cartridge used size: %lluMB", data_size / 0x100000); + if (data_size > cart_size) { + Debug("Used size exceeds cartridge size"); + return 1; // should never happen + } else if ((data_size < 0x4000) || (cart_size < 0x4000)) { + Debug("Bad cartridge size"); + return 1; // should never happen + } + if (param & CD_MAKECIA) { + if (BuildCiaStub((u8*) cia_stub, (u8*) ncsd) == 0) + return 1; + GetCiaInfo(&cia, cia_stub); + dump_size = cia.size_cia; + Debug("Cartridge CIA size : %lluMB", dump_size / 0x100000); + if (dump_size >= 0x100000000) { // should not happen + Debug("Error: Too big for the FAT32 file system"); + return 1; + } + } else { + dump_size = (param & CD_TRIM) ? data_size : cart_size; + Debug("Cartridge dump size: %lluMB", dump_size / 0x100000); + if ((dump_size == 0x100000000) && (data_size < dump_size)) { + dump_size -= 0x200; // silently remove the last sector for 4GB ROMs + } else if (dump_size >= 0x100000000) { // should not happen + Debug("Error: Too big for the FAT32 file system"); + if (!(param & CD_TRIM)) + Debug("(maybe try dumping trimmed?)"); + return 1; + } + } + + if (!DebugCheckFreeSpace((size_t) dump_size)) + return 1; + + // create file, write CIA / NCSD header + Debug(""); + snprintf(filename, 64, "%.16s%s.%s", + ncch->productcode, (param & CD_DECRYPT) ? "-dec" : "", (param & CD_MAKECIA) ? "cia" : "3ds"); + if (!FileCreate(filename, true)) { + Debug("Could not create output file on SD"); + return 1; + } + if (param & CD_MAKECIA) { // CIA stub, including header, cert, ticket, TMD + if (!DebugFileWrite((void*) cia_stub, cia.offset_content, 0)) { + FileClose(); + return 1; + } + } else { // NCSD: first 0x4000 byte, including NCSD header + memset(((u8*) ncsd) + 0x1200, 0xFF, 0x4000 - 0x1200); + if (!DebugFileWrite((void*) ncsd, 0x4000, 0)) { + FileClose(); + return 1; + } + } + + if (param & CD_MAKECIA) { + u32 next_offset = cia.offset_content; + u32 p; + for (p = 0; p < 3; p++) { + u32 size = ncsd->partitions[p].size * 0x200; + u32 offset_cart = ncsd->partitions[p].offset * 0x200; + u32 offset_file = next_offset; + if (size == 0) + continue; + next_offset += size; + if (param & CD_DECRYPT) { + Debug("Decrypting partition #%lu (%luMB)...", p, size / 0x100000); + if (DecryptCartNcchToFile(offset_cart, offset_file, size, dump_size) != 0) + break; + } else { + Debug("Dumping partition #%lu (%luMB)...", p, size / 0x100000); + if (DumpCartToFile(offset_cart, offset_file, size, dump_size, NULL, NULL) != 0) + break; + } + } + if (param & CD_DECRYPT) + Debug("Decryption %s!", (p == 3) ? "success!" : "failed!"); + if (p != 3) + result = 1; + } else if (!(param & CD_DECRYPT)) { // dump the encrypted cart + Debug("Dumping cartridge %.16s (%lluMB)...", ncch->productcode, dump_size / 0x100000); + result = DumpCartToFile(0x4000, 0x4000, dump_size - 0x4000, dump_size, NULL, NULL); + } else { // dump decrypted partitions + u32 p; + for (p = 0; p < 8; p++) { + u32 offset = ncsd->partitions[p].offset * 0x200; + u32 size = ncsd->partitions[p].size * 0x200; + if (size == 0) + continue; + Debug("Decrypting partition #%lu (%luMB)...", p, size / 0x100000); + if (DecryptCartNcchToFile(offset, offset, size, dump_size) != 0) + break; + } + if (p == 8) { + Debug("Decryption success!"); + } else { + Debug("Decryption failed!"); + result = 1; + } + + if ((result == 0) && (dump_size > data_size)) { + Debug("Dumping padding (%lluMB)...", (dump_size - data_size) / 0x100000); + result = DumpCartToFile(data_size, data_size, dump_size - data_size, dump_size, NULL, NULL); + } + } + FileClose(); + + if ((param & CD_MAKECIA) && (result == 0)) { + Debug("Finalizing CIA file..."); + if (FixCiaFile(filename) != 0) + result = 1; + } + + // verify decrypted ROM + if ((result == 0) && (param & CD_DECRYPT)) { + Debug(""); + if (param & CD_MAKECIA) { + u32 next_offset = cia.offset_content; + for (u32 p = 0; p < 3; p++) { + u32 offset = next_offset; + u32 size = ncsd->partitions[p].size * 0x200; + if (size == 0) + continue; + next_offset += size; + Debug("Verifiying partition #%lu (%luMB)...", p, size / 0x100000); + if (VerifyNcch(filename, offset) != 0) + result = 1; + } + } else { + for (u32 p = 0; p < 8; p++) { + u32 offset = ncsd->partitions[p].offset * 0x200; + u32 size = ncsd->partitions[p].size * 0x200; + if (size == 0) + continue; + Debug("Verifiying partition #%lu (%luMB)...", p, size / 0x100000); + if (VerifyNcch(filename, offset) != 0) + result = 1; + } + } + Debug("Verification %s", (result == 0) ? "success!" : "failed!"); + } + + + return result; +} + +u32 DumpTwlGameCart(u32 param) +{ + char filename[64]; + u64 cart_size = 0; + u64 data_size = 0; + u64 dump_size = 0; + u8* dsibuff = BUFFER_ADDRESS; + u8* buff = BUFFER_ADDRESS+0x8000; + u64 offset = 0x8000; + char name[16]; + u32 arm9iromOffset = -1; + int isDSi = 0; + + memset (buff, 0x00, 0x8000); + + + NTR_CmdReadHeader (buff); + if (buff[0] == 0x00) { + Debug("Error reading cart header"); + return 1; + } + + memset (name, 0x00, sizeof (name)); + memcpy (name, &buff[0x00], 12); + Debug("Product name: %s", name); + + memset (name, 0x00, sizeof (name)); + memcpy (name, &buff[0x0C], 4 + 2); + Debug("Product ID: %s", name); + + cart_size = (128 * 1024) << buff[0x14]; + data_size = *((u32*)&buff[0x80]);; + dump_size = (param & CD_TRIM) ? data_size : cart_size; + Debug("Cartridge data size: %lluMB", cart_size / 0x100000); + Debug("Cartridge used size: %lluMB", data_size / 0x100000); + Debug("Cartridge dump size: %lluMB", dump_size / 0x100000); + + if (!NTR_Secure_Init (buff, Cart_GetID(), 0)) { + Debug("Error reading secure data"); + return 1; + } + + Debug(""); + snprintf(filename, 64, "%s.nds", name); + + if (!DebugFileCreate(filename, true)) + return 1; + if (!DebugFileWrite(buff, 0x8000, 0)) { + FileClose(); + return 1; + } + + // Unitcode (00h=NDS, 02h=NDS+DSi, 03h=DSi) (bit1=DSi) + if (buff[0x12] != 0x00) { + isDSi = 1; + + // initialize cartridge + Cart_Init(); + //Cart_GetID(); + + NTR_CmdReadHeader (dsibuff); + + if (!NTR_Secure_Init (dsibuff, Cart_GetID(), 1)) { + Debug("Error reading dsi secure data"); + //return 1; + } + + arm9iromOffset = *((u32*)&dsibuff[0x1C0]); + } + + u32 stop = 0; + for (offset=0x8000;offset < dump_size;offset+=CART_CHUNK_SIZE) { + if( (offset + CART_CHUNK_SIZE) > dump_size) + stop = (offset + CART_CHUNK_SIZE)-dump_size; // correct over-sized writes with "stop" variable + for(u32 i=0; i < CART_CHUNK_SIZE; i += 0x200) { + NTR_CmdReadData (offset+i, buff+i); + } + if (!DebugFileWrite((void*) buff, CART_CHUNK_SIZE - stop, offset)) { + FileClose(); + return 1; + } + ShowProgress(offset, dump_size); + } + + if (isDSi && !DebugFileWrite(dsibuff+0x4000, 0x4000, arm9iromOffset)) { + FileClose(); + return 1; + } + + FileClose (); + ShowProgress(0, 0); + return 0; +} + +u32 DumpGameCart(u32 param) +{ + u32 cartId; + + // check if cartridge inserted + if (REG_CARDCONF2 & 0x1) { + Debug("Cartridge was not detected"); + return 1; + } + + // initialize cartridge + Cart_Init(); + cartId = Cart_GetID(); + Debug("Cartridge ID: %08X", Cart_GetID()); + Debug("Cartridge Type: %s", (cartId & 0x10000000) ? "CTR" : "NTR/TWL"); + + // check options vs. cartridge type + if (!(cartId & 0x10000000) && (param & CD_MAKECIA)) { + Debug("NTR/TWL carts can't be dumped to CIA"); + return 1; + } + if (!(cartId & 0x10000000) && (param & CD_DECRYPT)) { + Debug("NTR/TWL carts are not encrypted, won't decrypt"); + } + + return (cartId & 0x10000000) ? DumpCtrGameCart(param) : DumpTwlGameCart(param); +} + +u32 DumpPrivateHeader(u32 param) +{ + (void) param; + NcchHeader* ncch = (NcchHeader*) 0x20317000; + u8 privateHeader[0x50] = { 0xFF }; + u32 cartId = 0; + char filename[64]; + + + // check if cartridge inserted + if (REG_CARDCONF2 & 0x1) { + Debug("Cartridge was not detected"); + return 1; + } + + // initialize cartridge + Cart_Init(); + cartId = Cart_GetID(); + Debug("Cartridge ID: %08X", cartId); + *(u32*) (privateHeader + 0x40) = cartId; + *(u32*) (privateHeader + 0x44) = 0x00000000; + *(u32*) (privateHeader + 0x48) = 0xFFFFFFFF; + *(u32*) (privateHeader + 0x4C) = 0xFFFFFFFF; + + // check for NTR cartridge + if (!(cartId & 0x10000000)) { + Debug("Error: NTR carts have no private headers"); + return 1; + } + + // read cartridge NCCH header + CTR_CmdReadHeader(ncch); + if (memcmp(ncch->magic, "NCCH", 4) != 0) { + Debug("Error reading cart NCCH header"); + return 1; + } + + // secure init + u32 sec_keys[4]; + Cart_Secure_Init((u32*) ncch, sec_keys); + + // get private header + CTR_CmdReadUniqueID(privateHeader); + Debug("Unique ID:"); + Debug("%016llX%016llX", getbe64(privateHeader), getbe64(privateHeader + 0x08)); + + // dump to file + snprintf(filename, 64, "%.16s-private.bin", ncch->productcode); + if (FileDumpData(filename, privateHeader, 0x50) != 0x50) { + Debug("Could not create output file on SD"); + return 1; + } + + return 0; +} diff --git a/source/decryptor/game.h b/source/decryptor/game.h new file mode 100644 index 0000000..7af537f --- /dev/null +++ b/source/decryptor/game.h @@ -0,0 +1,203 @@ +#pragma once + +#include "common.h" +#include "decryptor/decryptor.h" + +#define GC_NCCH_PROCESS (1<<0) +#define GC_CIA_PROCESS (1<<1) +#define GC_CIA_DEEP (1<<2) +#define GC_NCCH_ENC0x2C (1<<3) +#define GC_NCCH_ENCZERO (1<<4) +#define GC_CIA_ENCRYPT (1<<5) +#define GC_CXI_ONLY (1<<6) +#define GC_BOSS_PROCESS (1<<7) +#define GC_BOSS_ENCRYPT (1<<8) + +#define CD_TRIM (1<<0) +#define CD_DECRYPT (1<<1) +#define CD_MAKECIA (1<<2) + +#define MAX_ENTRIES 1024 +#define CIA_CERT_SIZE 0xA00 + +typedef struct { + u64 titleId; + u8 external_seed[16]; + u8 reserved[8]; +} __attribute__((packed)) SeedInfoEntry; + +typedef struct { + u32 n_entries; + u8 padding[12]; + SeedInfoEntry entries[MAX_ENTRIES]; +} __attribute__((packed)) SeedInfo; + +typedef struct { + u32 offset; + u32 size; +} __attribute__((packed)) NcchPartition; + +typedef struct { + u8 signature[0x100]; + u8 magic[4]; + u32 size; + u64 mediaId; + u8 partitions_fs_type[8]; + u8 partitions_crypto_type[8]; + NcchPartition partitions[8]; + u8 hash_exthdr[0x20]; + u8 size_addhdr[0x4]; + u8 sector_zero_offset[0x4]; + u8 partition_flags[8]; + u8 partitionId_table[8][8]; + u8 reserved[0x40]; +} __attribute__((packed, aligned(16))) NcsdHeader; + +typedef struct { + u8 signature[0x100]; + u8 magic[0x4]; + u32 size; + u64 partitionId; + u16 makercode; + u16 version; + u8 reserved0[0x4]; + u64 programId; + u8 reserved1[0x10]; + u8 hash_logo[0x20]; + char productcode[0x10]; + u8 hash_exthdr[0x20]; + u32 size_exthdr; + u8 reserved2[0x4]; + u8 flags[0x8]; + u32 offset_plain; + u32 size_plain; + u32 offset_logo; + u32 size_logo; + u32 offset_exefs; + u32 size_exefs; + u32 size_exefs_hash; + u8 reserved3[0x4]; + u32 offset_romfs; + u32 size_romfs; + u32 size_romfs_hash; + u8 reserved4[0x4]; + u8 hash_exefs[0x20]; + u8 hash_romfs[0x20]; +} __attribute__((packed, aligned(16))) NcchHeader; + +// see: https://www.3dbrew.org/wiki/CIA#Meta +typedef struct { + u8 dependencies[0x180]; // from ExtHeader + u8 reserved0[0x180]; + u32 core_version; // 2 normally + u8 reserved1[0xFC]; + u8 smdh[0x36C0]; // from ExeFS +} __attribute__((packed)) CiaMeta; + +// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tik.h#L15-L39 +typedef struct { + u8 sig_type[4]; + u8 signature[0x100]; + u8 padding1[0x3C]; + u8 issuer[0x40]; + u8 ecdsa[0x3C]; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 titlekey[0x10]; + u8 reserved0; + u8 ticket_id[8]; + u8 console_id[4]; + u8 title_id[8]; + u8 sys_access[2]; + u8 ticket_version[2]; + u8 time_mask[4]; + u8 permit_mask[4]; + u8 title_export; + u8 commonkey_idx; + u8 unknown_buf[0x30]; + u8 content_permissions[0x40]; + u8 reserved1[2]; + u8 timelimits[0x40]; + u8 content_index[0xAC]; +} __attribute__((packed)) Ticket; + +// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tmd.h#L18-L59; +typedef struct { + u8 id[4]; + u8 index[2]; + u8 type[2]; + u8 size[8]; + u8 hash[0x20]; +} __attribute__((packed)) TmdContentChunk; + +typedef struct { + u8 index[2]; + u8 cmd_count[2]; + u8 hash[0x20]; +} __attribute__((packed)) TmdContentInfo; + +typedef struct { + u8 sig_type[4]; + u8 signature[0x100]; + u8 padding[0x3C]; + u8 issuer[0x40]; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 reserved0; + u8 system_version[8]; + u8 title_id[8]; + u8 title_type[4]; + u8 group_id[2]; + u8 save_size[4]; + u8 twl_privsave_size[4]; + u8 reserved1[4]; + u8 twl_flag; + u8 reserved2[0x31]; + u8 access_rights[4]; + u8 title_version[2]; + u8 content_count[2]; + u8 boot_content[2]; + u8 reserved3[2]; + u8 contentinfo_hash[0x20]; + TmdContentInfo contentinfo[64]; +} __attribute__((packed)) TitleMetaData; + +typedef struct { + u32 offset_cert; + u32 offset_ticktmd; + u32 offset_ticket; + u32 offset_tmd; + u32 offset_content_list; + u32 offset_meta; + u32 offset_content; + u32 size_cert; + u32 size_ticktmd; + u32 size_ticket; + u32 size_tmd; + u32 size_content_list; + u32 size_meta; + u64 size_content; + u64 size_cia; +} __attribute__((packed)) CiaInfo; + +typedef struct { + u32 size_header; + u16 type; + u16 version; + u32 size_cert; + u32 size_ticket; + u32 size_tmd; + u32 size_meta; + u64 size_content; + u8 content_index[0x2000]; +} __attribute__((packed)) CiaHeader; + +u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 sub_id); +u32 CryptSdToSd(const char* filename, u32 offset, u32 size, CryptBufferInfo* info, bool handle_offset16); +u32 CryptNcch(const char* filename, u32 offset, u32 size, u64 seedId, u8* encrypt_flags); + +// --> FEATURE FUNCTIONS <-- +u32 DumpGameCart(u32 param); +u32 DumpPrivateHeader(u32 param); diff --git a/source/decryptor/injector.c b/source/decryptor/injector.c deleted file mode 100644 index 348f094..0000000 --- a/source/decryptor/injector.c +++ /dev/null @@ -1,607 +0,0 @@ -#include "fs.h" -#include "draw.h" -#include "platform.h" -#include "decryptor/aes.h" -#include "decryptor/sha.h" -#include "decryptor/keys.h" -#include "decryptor/hashfile.h" -#include "decryptor/nand.h" -#include "decryptor/injector.h" - -// only a subset, see http://3dbrew.org/wiki/Title_list -// regions: JPN, USA, EUR, CHN, KOR, TWN -TitleListInfo titleList[] = { - { "Health&Safety" , 0x00040010, { 0x00020300, 0x00021300, 0x00022300, 0x00026300, 0x00027300, 0x00028300 } }, - { "Health&Safety (N3DS)" , 0x00040010, { 0x20020300, 0x20021300, 0x20022300, 0x00000000, 0x00000000, 0x00000000 } } -}; - -u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 sub_id) { - memset(ctr, 0x00, 16); - if (ncch->version == 1) { - memcpy(ctr, &(ncch->partitionId), 8); - if (sub_id == 1) { // exHeader ctr - add_ctr(ctr, 0x200); - } else if (sub_id == 2) { // exeFS ctr - add_ctr(ctr, ncch->offset_exefs * 0x200); - } else if (sub_id == 3) { // romFS ctr - add_ctr(ctr, ncch->offset_romfs * 0x200); - } - } else { - for (u32 i = 0; i < 8; i++) - ctr[i] = ((u8*) &(ncch->partitionId))[7-i]; - ctr[8] = sub_id; - } - - return 0; -} - -u32 CryptSdToSd(const char* filename, u32 offset, u32 size, CryptBufferInfo* info, bool handle_offset16) -{ - u8* buffer = BUFFER_ADDRESS; - u32 offset_16 = (handle_offset16) ? offset % 16 : 0; - u32 result = 0; - - // no DebugFileOpen() - at this point the file has already been checked enough - if (!FileOpen(filename)) - return 1; - - info->buffer = buffer; - if (offset_16) { // handle offset alignment / this assumes the data is >= 16 byte - if(!DebugFileRead(buffer + offset_16, 16 - offset_16, offset)) { - result = 1; - } - info->size = 16; - CryptBuffer(info); - if(!DebugFileWrite(buffer + offset_16, 16 - offset_16, offset)) { - result = 1; - } - } - for (u32 i = (offset_16) ? (16 - offset_16) : 0; i < size; i += BUFFER_MAX_SIZE) { - u32 read_bytes = min(BUFFER_MAX_SIZE, (size - i)); - ShowProgress(i, size); - if(!DebugFileRead(buffer, read_bytes, offset + i)) { - result = 1; - break; - } - info->size = read_bytes; - CryptBuffer(info); - if(!DebugFileWrite(buffer, read_bytes, offset + i)) { - result = 1; - break; - } - } - - ShowProgress(0, 0); - FileClose(); - - return result; -} - -u32 CryptNcch(const char* filename, u32 offset, u32 size, u64 seedId, u8* encrypt_flags) -{ - NcchHeader* ncch = (NcchHeader*) 0x20316200; - u8* buffer = (u8*) 0x20316400; - CryptBufferInfo info0 = {.setKeyY = 1, .keyslot = 0x2C, .mode = AES_CNT_CTRNAND_MODE}; - CryptBufferInfo info1 = {.setKeyY = 1, .mode = AES_CNT_CTRNAND_MODE}; - u8 seedKeyY[16] = { 0x00 }; - u32 result = 0; - - if (FileGetData(filename, (void*) ncch, 0x200, offset) != 0x200) - return 1; // it's impossible to fail here anyways - - // check (again) for magic number - if (memcmp(ncch->magic, "NCCH", 4) != 0) { - Debug("Not a NCCH container"); - return 2; // not an actual error - } - - // size plausibility check - u32 size_sum = 0x200 + ((ncch->size_exthdr) ? 0x800 : 0x0) + 0x200 * - (ncch->size_plain + ncch->size_logo + ncch->size_exefs + ncch->size_romfs); - if (ncch->size * 0x200 < size_sum) { - Debug("Probably not a NCCH container"); - return 2; // not an actual error - } - - // check if encrypted - if (!encrypt_flags && (ncch->flags[7] & 0x04)) { - Debug("NCCH is not encrypted"); - return 2; // not an actual error - } else if (encrypt_flags && !(ncch->flags[7] & 0x04)) { - Debug("NCCH is already encrypted"); - return 2; // not an actual error - } else if (encrypt_flags && (encrypt_flags[7] & 0x04)) { - Debug("Nothing to do!"); - return 2; // not an actual error - } - - // check size - if ((size > 0) && (ncch->size * 0x200 > size)) { - Debug("NCCH size is out of bounds"); - return 1; - } - - // select correct title ID for seed crypto - if (seedId == 0) seedId = ncch->programId; - - // copy over encryption parameters (if applicable) - if (encrypt_flags) { - ncch->flags[3] = encrypt_flags[3]; - ncch->flags[7] &= (0x01|0x20|0x04)^0xFF; - ncch->flags[7] |= (0x01|0x20)&encrypt_flags[7]; - } - - // check crypto type - bool uses7xCrypto = ncch->flags[3]; - bool usesSeedCrypto = ncch->flags[7] & 0x20; - bool usesSec3Crypto = (ncch->flags[3] == 0x0A); - bool usesSec4Crypto = (ncch->flags[3] == 0x0B); - bool usesFixedKey = ncch->flags[7] & 0x01; - - Debug("Code / Crypto: %.16s / %s%s%s%s", ncch->productCode, (usesFixedKey) ? "FixedKey " : "", (usesSec4Crypto) ? "Secure4 " : (usesSec3Crypto) ? "Secure3 " : (uses7xCrypto) ? "7x " : "", (usesSeedCrypto) ? "Seed " : "", (!uses7xCrypto && !usesSeedCrypto && !usesFixedKey) ? "Standard" : ""); - - // setup zero key crypto - if (usesFixedKey) { - // from https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h - u8 zeroKey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - u8 sysKey[16] = {0x52, 0x7C, 0xE6, 0x30, 0xA9, 0xCA, 0x30, 0x5F, 0x36, 0x96, 0xF3, 0xCD, 0xE9, 0x54, 0x19, 0x4B}; - uses7xCrypto = usesSeedCrypto = usesSec3Crypto = usesSec4Crypto = false; - info1.setKeyY = info0.setKeyY = 0; - info1.keyslot = info0.keyslot = 0x11; - setup_aeskey(0x11, (ncch->programId & ((u64) 0x10 << 32)) ? sysKey : zeroKey); - } - - // check 7x crypto - if (uses7xCrypto && (CheckKeySlot(0x25, 'X') != 0)) { - Debug("slot0x25KeyX not set up"); - Debug("This won't work on O3DS < 7.x or A9LH"); - return 1; - } - - // check Secure3 crypto on O3DS - if (usesSec3Crypto && (CheckKeySlot(0x18, 'X') != 0)) { - Debug("slot0x18KeyX not set up"); - Debug("Secure3 crypto is not available"); - return 1; - } - - // check Secure4 crypto - if (usesSec4Crypto && (CheckKeySlot(0x1B, 'X') != 0)) { - Debug("slot0x1BKeyX not set up"); - Debug("Secure4 crypto is not available"); - return 1; - } - - // check / setup seed crypto - if (usesSeedCrypto) { - if (FileOpen("seeddb.bin")) { - SeedInfoEntry* entry = (SeedInfoEntry*) buffer; - u32 found = 0; - for (u32 i = 0x10;; i += 0x20) { - if (FileRead(entry, 0x20, i) != 0x20) - break; - if (entry->titleId == seedId) { - u8 keydata[32]; - memcpy(keydata, ncch->signature, 16); - memcpy(keydata + 16, entry->external_seed, 16); - u8 sha256sum[32]; - sha_quick(sha256sum, keydata, 32, SHA256_MODE); - memcpy(seedKeyY, sha256sum, 16); - found = 1; - } - } - FileClose(); - if (!found) { - Debug("Seed not found in seeddb.bin!"); - return 1; - } - } else { - Debug("Need seeddb.bin for seed crypto!"); - return 1; - } - Debug("Loading seed from seeddb.bin: ok"); - } - - // basic setup of CryptBufferInfo structs - memcpy(info0.keyY, ncch->signature, 16); - memcpy(info1.keyY, (usesSeedCrypto) ? seedKeyY : ncch->signature, 16); - info1.keyslot = (usesSec4Crypto) ? 0x1B : ((usesSec3Crypto) ? 0x18 : ((uses7xCrypto) ? 0x25 : info0.keyslot)); - - Debug("%s ExHdr/ExeFS/RomFS (%ukB/%ukB/%uMB)", - (encrypt_flags) ? "Encrypt" : "Decrypt", - (ncch->size_exthdr > 0) ? 0x800 / 1024 : 0, - (ncch->size_exefs * 0x200) / 1024, - (ncch->size_romfs * 0x200) / (1024*1024)); - - // process ExHeader - if (ncch->size_exthdr > 0) { - GetNcchCtr(info0.ctr, ncch, 1); - result |= CryptSdToSd(filename, offset + 0x200, 0x800, &info0, true); - } - - // process ExeFS - if (ncch->size_exefs > 0) { - u32 offset_byte = ncch->offset_exefs * 0x200; - u32 size_byte = ncch->size_exefs * 0x200; - if (uses7xCrypto || usesSeedCrypto) { - GetNcchCtr(info0.ctr, ncch, 2); - if (!encrypt_flags) // decrypt this first (when decrypting) - result |= CryptSdToSd(filename, offset + offset_byte, 0x200, &info0, true); - if (FileGetData(filename, buffer, 0x200, offset + offset_byte) != 0x200) // get exeFS header - return 1; - if (encrypt_flags) // encrypt this last (when encrypting) - result |= CryptSdToSd(filename, offset + offset_byte, 0x200, &info0, true); - // special ExeFS decryption routine ("banner" and "icon" use standard crypto) - for (u32 i = 0; i < 10; i++) { - char* name_exefs_file = (char*) buffer + (i*0x10); - u32 offset_exefs_file = getle32(buffer + (i*0x10) + 0x8) + 0x200; - u32 size_exefs_file = getle32(buffer + (i*0x10) + 0xC); - CryptBufferInfo* infoExeFs = ((strncmp(name_exefs_file, "banner", 8) == 0) || - (strncmp(name_exefs_file, "icon", 8) == 0)) ? &info0 : &info1; - if (size_exefs_file == 0) - continue; - if (offset_exefs_file % 16) { - Debug("ExeFS file offset not aligned!"); - result |= 1; - break; // this should not happen - } - GetNcchCtr(infoExeFs->ctr, ncch, 2); - add_ctr(infoExeFs->ctr, offset_exefs_file / 0x10); - infoExeFs->setKeyY = 1; - result |= CryptSdToSd(filename, offset + offset_byte + offset_exefs_file, - align(size_exefs_file, 16), infoExeFs, true); - } - } else { - GetNcchCtr(info0.ctr, ncch, 2); - result |= CryptSdToSd(filename, offset + offset_byte, size_byte, &info0, true); - } - } - - // process RomFS - if (ncch->size_romfs > 0) { - GetNcchCtr(info1.ctr, ncch, 3); - if (!usesFixedKey) - info1.setKeyY = 1; - result |= CryptSdToSd(filename, offset + (ncch->offset_romfs * 0x200), ncch->size_romfs * 0x200, &info1, true); - } - - // set NCCH header flags - if (!encrypt_flags) { - ncch->flags[3] = 0x00; - ncch->flags[7] &= (0x01|0x20)^0xFF; - ncch->flags[7] |= 0x04; - } - - // write header back - if (!FileOpen(filename)) - return 1; - if (!DebugFileWrite((void*) ncch, 0x200, offset)) { - FileClose(); - return 1; - } - FileClose(); - - // verify decryption - if ((result == 0) && !encrypt_flags) { - char* status_str[3] = { "OK", "Fail", "-" }; - u32 ver_exthdr = 2; - u32 ver_exefs = 2; - u32 ver_romfs = 2; - - if (ncch->size_exthdr > 0) - ver_exthdr = CheckHashFromFile(filename, offset + 0x200, 0x400, ncch->hash_exthdr); - if (ncch->size_exefs_hash > 0) - ver_exefs = CheckHashFromFile(filename, offset + (ncch->offset_exefs * 0x200), ncch->size_exefs_hash * 0x200, ncch->hash_exefs); - if (ncch->size_romfs_hash > 0) - ver_romfs = CheckHashFromFile(filename, offset + (ncch->offset_romfs * 0x200), ncch->size_romfs_hash * 0x200, ncch->hash_romfs); - - if (ncch->size_exefs > 0) { // thorough exefs verification - u32 offset_byte = ncch->offset_exefs * 0x200; - if (FileGetData(filename, buffer, 0x200, offset + offset_byte) != 0x200) - ver_exefs = 1; - for (u32 i = 0; (i < 10) && (ver_exefs != 1); i++) { - u32 offset_exefs_file = offset_byte + getle32(buffer + (i*0x10) + 0x8) + 0x200; - u32 size_exefs_file = getle32(buffer + (i*0x10) + 0xC); - u8* hash_exefs_file = buffer + 0x200 - ((i+1)*0x20); - if (size_exefs_file == 0) - break; - ver_exefs = CheckHashFromFile(filename, offset + offset_exefs_file, size_exefs_file, hash_exefs_file); - } - } - - Debug("Verify ExHdr/ExeFS/RomFS: %s/%s/%s", status_str[ver_exthdr], status_str[ver_exefs], status_str[ver_romfs]); - result = (((ver_exthdr | ver_exefs | ver_romfs) & 1) == 0) ? 0 : 1; - } - - - return result; -} - -u32 SeekFileInNand(u32* offset, u32* size, const char* path, PartitionInfo* partition) -{ - // poor mans NAND FAT file seeker: - // - path must be in FAT 8+3 format, without dots or slashes - // example: DIR1_______DIR2_______FILENAMEEXT - // - can't handle long filenames - // - dirs must not exceed 1024 entries - // - fragmentation not supported - - u8* buffer = BUFFER_ADDRESS; - u32 p_size = partition->size; - u32 p_offset = partition->offset; - u32 fat_pos = 0; - bool found = false; - - if (strnlen(path, 256) % (8+3) != 0) - return 1; - - if (DecryptNandToMem(buffer, p_offset, NAND_SECTOR_SIZE, partition) != 0) - return 1; - - // good FAT header description found here: http://www.compuphase.com/mbr_fat.htm - u32 fat_start = NAND_SECTOR_SIZE * getle16(buffer + 0x0E); - u32 fat_count = buffer[0x10]; - u32 fat_size = NAND_SECTOR_SIZE * getle16(buffer + 0x16) * fat_count; - u32 root_size = getle16(buffer + 0x11) * 0x20; - u32 cluster_start = fat_start + fat_size + root_size; - u32 cluster_size = buffer[0x0D] * NAND_SECTOR_SIZE; - - for (*offset = p_offset + fat_start + fat_size; strnlen(path, 256) >= 8+3; path += 8+3) { - if (*offset - p_offset > p_size) - return 1; - found = false; - if (DecryptNandToMem(buffer, *offset, cluster_size, partition) != 0) - return 1; - for (u32 i = 0x00; i < cluster_size; i += 0x20) { - static const char zeroes[8+3] = { 0x00 }; - // skip invisible, deleted and lfn entries - if ((buffer[i] == '.') || (buffer[i] == 0xE5) || (buffer[i+0x0B] == 0x0F)) - continue; - else if (memcmp(buffer + i, zeroes, 8+3) == 0) - return 1; - u32 p; // search for path in fat folder structure, accept '?' wildcards - for (p = 0; (p < 8+3) && (path[p] == '?' || buffer[i+p] == path[p]); p++); - if (p != 8+3) continue; - // entry found, store offset and move on - fat_pos = getle16(buffer + i + 0x1A); - *offset = p_offset + cluster_start + (fat_pos - 2) * cluster_size; - *size = getle32(buffer + i + 0x1C); - found = true; - break; - } - if (!found) break; - } - - // check for fragmentation - if (found && (*size > cluster_size)) { - if (fat_size / fat_count > 0x100000) // prevent buffer overflow - return 1; // fishy FAT table size - should never happen - if (DecryptNandToMem(buffer, p_offset + fat_start, fat_size / fat_count, partition) != 0) - return 1; - for (u32 i = 0; i < (*size - 1) / cluster_size; i++) { - if (*(((u16*) buffer) + fat_pos + i) != fat_pos + i + 1) - return 1; - } // no need to check the files last FAT table entry - } - - return (found) ? 0 : 1; -} - -u32 SeekTitleInNandDb(u32* tid_low, u32* tmd_id, TitleListInfo* title_info) -{ - PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); - u8* titledb = (u8*) 0x20316000; - - u32 offset_db; - u32 size_db; - if (SeekFileInNand(&offset_db, &size_db, "DBS TITLE DB ", ctrnand_info) != 0) - return 1; // database not found - if (size_db != 0x64C00) - return 1; // bad database size - if (DecryptNandToMem(titledb, offset_db, size_db, ctrnand_info) != 0) - return 1; - - u8* entry_table = titledb + 0x39A80; - u8* info_data = titledb + 0x44B80; - if ((getle32(entry_table + 0) != 2) || (getle32(entry_table + 4) != 3)) - return 1; // magic number not found - *tid_low = 0; - for (u32 i = 0; i < 1000; i++) { - u8* entry = entry_table + 0xA8 + (0x2C * i); - u8* info = info_data + (0x80 * i); - u32 r; - if (getle32(entry + 0xC) != title_info->tid_high) continue; // not a title id match - if (getle32(entry + 0x4) != 1) continue; // not an active entry - if ((getle32(entry + 0x18) - i != 0x162) || (getle32(entry + 0x1C) != 0x80) || (getle32(info + 0x08) != 0x40)) continue; // fishy title info / offset - for (r = 0; r < 6; r++) { - if ((title_info->tid_low[r] != 0) && (getle32(entry + 0x8) == title_info->tid_low[r])) break; - } - if (r >= 6) continue; - *tmd_id = getle32(info + 0x14); - *tid_low = title_info->tid_low[r]; - break; - } - - return (*tid_low) ? 0 : 1; -} - -u32 DebugSeekTitleInNand(u32* offset_tmd, u32* size_tmd, u32* offset_app, u32* size_app, TitleListInfo* title_info, u32 max_cnt) -{ - PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); - u8* buffer = (u8*) 0x20316000; - u32 cnt_count = 0; - u32 tid_low = 0; - u32 tmd_id = 0; - - Debug("Searching title \"%s\"...", title_info->name); - Debug("Method 1: Search in title.db..."); - if (SeekTitleInNandDb(&tid_low, &tmd_id, title_info) == 0) { - char path[64]; - sprintf(path, "TITLE %08X %08X CONTENT %08XTMD", (unsigned int) title_info->tid_high, (unsigned int) tid_low, (unsigned int) tmd_id); - if (SeekFileInNand(offset_tmd, size_tmd, path, ctrnand_info) != 0) - tid_low = 0; - } - if (!tid_low) { - Debug("Method 2: Search in file system..."); - for (u32 i = 0; i < 6; i++) { - char path[64]; - if (title_info->tid_low[i] == 0) - continue; - sprintf(path, "TITLE %08X %08X CONTENT ????????TMD", (unsigned int) title_info->tid_high, (unsigned int) title_info->tid_low[i]); - if (SeekFileInNand(offset_tmd, size_tmd, path, ctrnand_info) == 0) { - tid_low = title_info->tid_low[i]; - break; - } - } - } - if (!tid_low) { - Debug("Failed!"); - return 1; - } - Debug("Found title %08X%08X", title_info->tid_high, tid_low); - - Debug("TMD0 found at %08X, size %ub", *offset_tmd, *size_tmd); - if ((*size_tmd < 0xC4 + (0x40 * 0x24)) || (*size_tmd > 0x4000)) { - Debug("TMD has bad size!"); - return 1; - } - if (DecryptNandToMem(buffer, *offset_tmd, *size_tmd, ctrnand_info) != 0) - return 1; - u32 size_sig = (buffer[3] == 3) ? 0x240 : (buffer[3] == 4) ? 0x140 : (buffer[3] == 5) ? 0x80 : 0; - if ((size_sig == 0) || (memcmp(buffer, "\x00\x01\x00", 3) != 0)) { - Debug("Unknown signature type: %08X", getbe32(buffer)); - return 1; - } - cnt_count = getbe16(buffer + size_sig + 0x9E); - u32 size_tmd_expected = size_sig + 0xC4 + (0x40 * 0x24) + (cnt_count * 0x30); - if (*size_tmd < size_tmd_expected) { - Debug("TMD bad size (expected %ub)!", size_tmd_expected ); - return 1; - } - buffer += size_sig + 0xC4 + (0x40 * 0x24); - - for (u32 i = 0; i < cnt_count && i < max_cnt; i++) { - char path[64]; - u32 cnt_id = getbe32(buffer + (0x30 * i)); - if (i >= max_cnt) { - Debug("APP%i was skipped", i); - continue; - } - sprintf(path, "TITLE %08X %08X CONTENT %08XAPP", (unsigned int) title_info->tid_high, (unsigned int) tid_low, (unsigned int) cnt_id); - if (SeekFileInNand(offset_app + i, size_app + i, path, ctrnand_info) != 0) { - Debug("APP%i not found or fragmented!", i); - return 1; - } - Debug("APP%i found at %08X, size %ukB", i, offset_app[i], size_app[i] / 1024); - } - - return 0; -} - -u32 DumpHealthAndSafety(u32 param) -{ - (void) (param); // param is unused here - PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); - TitleListInfo* health = titleList + ((GetUnitPlatform() == PLATFORM_3DS) ? 0 : 1); - TitleListInfo* health_alt = (GetUnitPlatform() == PLATFORM_N3DS) ? titleList + 0 : NULL; - char filename[64]; - u32 offset_app[4]; - u32 size_app[4]; - u32 offset_tmd; - u32 size_tmd; - - - if ((DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health, 4) != 0) && (!health_alt || - (DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health_alt, 4) != 0))) - return 1; - if (OutputFileNameSelector(filename, "hs.app", NULL) != 0) - return 1; - - Debug("Dumping & decrypting APP0..."); - if (DecryptNandToFile(filename, offset_app[0], size_app[0], ctrnand_info) != 0) - return 1; - if (CryptNcch(filename, 0, 0, 0, NULL) != 0) - return 1; - - return 0; -} - -u32 InjectHealthAndSafety(u32 param) -{ - u8* buffer = BUFFER_ADDRESS; - PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); - TitleListInfo* health = titleList + ((GetUnitPlatform() == PLATFORM_3DS) ? 0 : 1); - TitleListInfo* health_alt = (GetUnitPlatform() == PLATFORM_N3DS) ? titleList + 0 : NULL; - NcchHeader* ncch = (NcchHeader*) 0x20316000; - char filename[64]; - u32 offset_app[4]; - u32 size_app[4]; - u32 offset_tmd; - u32 size_tmd; - u32 size_hs; - - - if (!(param & N_NANDWRITE)) // developer screwup protection - return 1; - - if ((DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health, 4) != 0) && (!health_alt || - (DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health_alt, 4) != 0))) - return 1; - if (size_app[0] > 0x400000) { - Debug("H&S system app is too big!"); - return 1; - } - if (DecryptNandToMem((void*) ncch, offset_app[0], 0x200, ctrnand_info) != 0) - return 1; - if (InputFileNameSelector(filename, NULL, "app", ncch->signature, 0x100, 0, false) != 0) - return 1; - - if (!DebugFileOpen(filename)) - return 1; - size_hs = FileGetSize(); - memset(buffer, 0, size_app[0]); - if (size_hs > size_app[0]) { - Debug("H&S inject app is too big!"); - FileClose(); - return 1; - } - if (FileCopyTo("hs.enc", buffer, size_hs) != size_hs) { - Debug("Error copying to hs.enc"); - FileClose(); - return 1; - } - FileClose(); - - if (CryptNcch("hs.enc", 0, 0, 0, ncch->flags) != 0) - return 1; - - Debug("Injecting H&S app..."); - if (EncryptFileToNand("hs.enc", offset_app[0], size_hs, ctrnand_info) != 0) - return 1; - - Debug("Fixing TMD..."); - u8* tmd_data = (u8*) 0x20316000; - if (DecryptNandToMem(tmd_data, offset_tmd, size_tmd, ctrnand_info) != 0) - return 1; - tmd_data += (tmd_data[3] == 3) ? 0x240 : (tmd_data[3] == 4) ? 0x140 : 0x80; - u8* content_list = tmd_data + 0xC4 + (64 * 0x24); - u32 cnt_count = getbe16(tmd_data + 0x9E); - if (GetHashFromFile("hs.enc", 0, size_app[0], content_list + 0x10) != 0) { - Debug("Failed!"); - return 1; - } - for (u32 i = 0, kc = 0; i < 64 && kc < cnt_count; i++) { - u32 k = getbe16(tmd_data + 0xC4 + (i * 0x24) + 0x02); - u8* chunk_hash = tmd_data + 0xC4 + (i * 0x24) + 0x04; - sha_quick(chunk_hash, content_list + kc * 0x30, k * 0x30, SHA256_MODE); - kc += k; - } - u8* tmd_hash = tmd_data + 0xA4; - sha_quick(tmd_hash, tmd_data + 0xC4, 64 * 0x24, SHA256_MODE); - tmd_data = (u8*) 0x20316000; - if (EncryptMemToNand(tmd_data, offset_tmd, size_tmd, ctrnand_info) != 0) - return 1; - - - return 0; -} diff --git a/source/decryptor/injector.h b/source/decryptor/injector.h deleted file mode 100644 index 5519e01..0000000 --- a/source/decryptor/injector.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "decryptor/decryptor.h" -#include "decryptor/nand.h" -#include "common.h" - -#define MAX_ENTRIES 1024 - -typedef struct { - u64 titleId; - u8 external_seed[16]; - u8 reserved[8]; -} __attribute__((packed)) SeedInfoEntry; - -typedef struct { - u32 n_entries; - u8 padding[12]; - SeedInfoEntry entries[MAX_ENTRIES]; -} __attribute__((packed)) SeedInfo; - -typedef struct { - u8 signature[0x100]; - u8 magic[0x4]; - u32 size; - u64 partitionId; - u16 makercode; - u16 version; - u8 reserved0[0x4]; - u64 programId; - u8 reserved1[0x10]; - u8 hash_logo[0x20]; - char productCode[0x10]; - u8 hash_exthdr[0x20]; - u32 size_exthdr; - u8 reserved2[0x4]; - u8 flags[0x8]; - u32 offset_plain; - u32 size_plain; - u32 offset_logo; - u32 size_logo; - u32 offset_exefs; - u32 size_exefs; - u32 size_exefs_hash; - u8 reserved3[0x4]; - u32 offset_romfs; - u32 size_romfs; - u32 size_romfs_hash; - u8 reserved4[0x4]; - u8 hash_exefs[0x20]; - u8 hash_romfs[0x20]; -} __attribute__((packed, aligned(16))) NcchHeader; - -typedef struct { - char name[32]; - u32 tid_high; - u32 tid_low[6]; -} TitleListInfo; - -// Crypto stuff -u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 sub_id); -u32 CryptSdToSd(const char* filename, u32 offset, u32 size, CryptBufferInfo* info, bool handle_offset16); -u32 GetHashFromFile(const char* filename, u32 offset, u32 size, u8* hash); -u32 CheckHashFromFile(const char* filename, u32 offset, u32 size, const u8* hash); -u32 CryptNcch(const char* filename, u32 offset, u32 size, u64 seedId, u8* encrypt_flags); - -// NAND FAT stuff -u32 SeekFileInNand(u32* offset, u32* size, const char* path, PartitionInfo* partition); -u32 SeekTitleInNandDb(u32* tid_low, u32* tmd_id, TitleListInfo* title_info); -u32 DebugSeekTitleInNand(u32* offset_tmd, u32* size_tmd, u32* offset_app, u32* size_app, TitleListInfo* title_info, u32 max_cnt); - -// --> FEATURE FUNCTIONS <-- -u32 DumpHealthAndSafety(u32 param); -u32 InjectHealthAndSafety(u32 param); diff --git a/source/decryptor/nandfat.c b/source/decryptor/nandfat.c new file mode 100644 index 0000000..e81ca57 --- /dev/null +++ b/source/decryptor/nandfat.c @@ -0,0 +1,333 @@ +#include "fs.h" +#include "draw.h" +#include "platform.h" +#include "decryptor/aes.h" +#include "decryptor/sha.h" +#include "decryptor/hashfile.h" +#include "decryptor/game.h" +#include "decryptor/keys.h" +#include "decryptor/nand.h" +#include "decryptor/nandfat.h" + +// title list shortcuts - will change if the array changes! +#define TL_HS (titleList + 3) +#define TL_HS_N (titleList + 4) + +// only a subset, see http://3dbrew.org/wiki/Title_list +// regions: JPN, USA, EUR, CHN, KOR, TWN +TitleListInfo titleList[] = { + { "System Settings" , 0x00040010, { 0x00020000, 0x00021000, 0x00022000, 0x00026000, 0x00027000, 0x00028000 } }, + { "Download Play" , 0x00040010, { 0x00020100, 0x00021100, 0x00022100, 0x00026100, 0x00027100, 0x00028100 } }, + { "Activity Log" , 0x00040010, { 0x00020200, 0x00021200, 0x00022200, 0x00026200, 0x00027200, 0x00028200 } }, + { "Health&Safety" , 0x00040010, { 0x00020300, 0x00021300, 0x00022300, 0x00026300, 0x00027300, 0x00028300 } }, + { "Health&Safety (N3DS)" , 0x00040010, { 0x20020300, 0x20021300, 0x20022300, 0x00000000, 0x00000000, 0x00000000 } }, + { "3DS Camera" , 0x00040010, { 0x00020400, 0x00021400, 0x00022400, 0x00026400, 0x00027400, 0x00028400 } }, + { "3DS Sound" , 0x00040010, { 0x00020500, 0x00021500, 0x00022500, 0x00026500, 0x00027500, 0x00028500 } }, + { "Mii Maker" , 0x00040010, { 0x00020700, 0x00021700, 0x00022700, 0x00026700, 0x00027700, 0x00028700 } }, + { "Streetpass Mii Plaza" , 0x00040010, { 0x00020800, 0x00021800, 0x00022800, 0x00026800, 0x00027800, 0x00028800 } }, + { "3DS eShop" , 0x00040010, { 0x00020900, 0x00021900, 0x00022900, 0x00000000, 0x00027900, 0x00028900 } }, + { "Nintendo Zone" , 0x00040010, { 0x00020B00, 0x00021B00, 0x00022B00, 0x00000000, 0x00000000, 0x00000000 } } +}; + +u32 SeekFileInNand(u32* offset, u32* size, const char* path, PartitionInfo* partition) +{ + // poor mans NAND FAT file seeker: + // - path must be in FAT 8+3 format, without dots or slashes + // example: DIR1_______DIR2_______FILENAMEEXT + // - can't handle long filenames + // - dirs must not exceed 1024 entries + // - fragmentation not supported + + u8* buffer = BUFFER_ADDRESS; + u32 p_size = partition->size; + u32 p_offset = partition->offset; + u32 fat_pos = 0; + bool found = false; + + if (strnlen(path, 256) % (8+3) != 0) + return 1; + + if (DecryptNandToMem(buffer, p_offset, NAND_SECTOR_SIZE, partition) != 0) + return 1; + + // good FAT header description found here: http://www.compuphase.com/mbr_fat.htm + u32 fat_start = NAND_SECTOR_SIZE * getle16(buffer + 0x0E); + u32 fat_count = buffer[0x10]; + u32 fat_size = NAND_SECTOR_SIZE * getle16(buffer + 0x16) * fat_count; + u32 root_size = getle16(buffer + 0x11) * 0x20; + u32 cluster_start = fat_start + fat_size + root_size; + u32 cluster_size = buffer[0x0D] * NAND_SECTOR_SIZE; + + for (*offset = p_offset + fat_start + fat_size; strnlen(path, 256) >= 8+3; path += 8+3) { + if (*offset - p_offset > p_size) + return 1; + found = false; + if (DecryptNandToMem(buffer, *offset, cluster_size, partition) != 0) + return 1; + for (u32 i = 0x00; i < cluster_size; i += 0x20) { + static const char zeroes[8+3] = { 0x00 }; + // skip invisible, deleted and lfn entries + if ((buffer[i] == '.') || (buffer[i] == 0xE5) || (buffer[i+0x0B] == 0x0F)) + continue; + else if (memcmp(buffer + i, zeroes, 8+3) == 0) + return 1; + u32 p; // search for path in fat folder structure, accept '?' wildcards + for (p = 0; (p < 8+3) && (path[p] == '?' || buffer[i+p] == path[p]); p++); + if (p != 8+3) continue; + // entry found, store offset and move on + fat_pos = getle16(buffer + i + 0x1A); + *offset = p_offset + cluster_start + (fat_pos - 2) * cluster_size; + *size = getle32(buffer + i + 0x1C); + found = true; + break; + } + if (!found) break; + } + + // check for fragmentation + if (found && (*size > cluster_size)) { + if (fat_size / fat_count > 0x100000) // prevent buffer overflow + return 1; // fishy FAT table size - should never happen + if (DecryptNandToMem(buffer, p_offset + fat_start, fat_size / fat_count, partition) != 0) + return 1; + for (u32 i = 0; i < (*size - 1) / cluster_size; i++) { + if (*(((u16*) buffer) + fat_pos + i) != fat_pos + i + 1) + return 1; + } // no need to check the files last FAT table entry + } + + return (found) ? 0 : 1; +} + +u32 SeekTitleInNandDb(u32 tid_high, u32 tid_low, u32* tmd_id) +{ + PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); + u8* titledb = (u8*) 0x20316000; + + u32 offset_db; + u32 size_db; + if (SeekFileInNand(&offset_db, &size_db, "DBS TITLE DB ", ctrnand_info) != 0) + return 1; // database not found + if (size_db != 0x64C00) + return 1; // bad database size + if (DecryptNandToMem(titledb, offset_db, size_db, ctrnand_info) != 0) + return 1; + + u8* entry_table = titledb + 0x39A80; + u8* info_data = titledb + 0x44B80; + if ((getle32(entry_table + 0) != 2) || (getle32(entry_table + 4) != 3)) + return 1; // magic number not found + *tmd_id = (u32) -1; + for (u32 i = 0; i < 1000; i++) { + u8* entry = entry_table + 0xA8 + (0x2C * i); + u8* info = info_data + (0x80 * i); + if ((getle32(entry + 0xC) != tid_high) || (getle32(entry + 0x8) != tid_low)) continue; // not a title id match + if (getle32(entry + 0x4) != 1) continue; // not an active entry + if ((getle32(entry + 0x18) - i != 0x162) || (getle32(entry + 0x1C) != 0x80) || (getle32(info + 0x08) != 0x40)) continue; // fishy title info / offset + *tmd_id = getle32(info + 0x14); + break; + } + + return (*tmd_id != (u32) -1) ? 0 : 1; +} + +u32 DebugSeekTitleInNand(u32* offset_tmd, u32* size_tmd, u32* offset_app, u32* size_app, TitleListInfo* title_info, u32 max_cnt) +{ + PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); + u8* buffer = (u8*) 0x20316000; + u32 cnt_count = 0; + u32 tid_high = title_info->tid_high; + u32 tid_low = 0; + u32 tmd_id = (u32) -1; + + // get correct title id + u32 region = GetRegion(); + if (region > 6) + return 1; + tid_low = title_info->tid_low[(region >= 3) ? region - 1 : region]; + if (tid_low == 0) { + Debug("%s not available for region", title_info->name); + return 1; + } + + Debug("Searching title \"%s\"...", title_info->name); + Debug("Method 1: Search in title.db..."); + if (SeekTitleInNandDb(tid_high, tid_low, &tmd_id) == 0) { + char path[64]; + sprintf(path, "TITLE %08lX %08lX CONTENT %08lXTMD", tid_high, tid_low, tmd_id); + if (SeekFileInNand(offset_tmd, size_tmd, path, ctrnand_info) != 0) + tmd_id = (u32) -1; + } + if (tmd_id == (u32) -1) { + Debug("Method 2: Search in file system..."); + char path[64]; + sprintf(path, "TITLE %08lX %08lX CONTENT ????????TMD", tid_high, tid_low); + if (SeekFileInNand(offset_tmd, size_tmd, path, ctrnand_info) != 0) { + Debug("Failed!"); + return 1; + } + } + Debug("Found title %08X%08X", title_info->tid_high, tid_low); + + Debug("TMD0 found at %08X, size %ub", *offset_tmd, *size_tmd); + if ((*size_tmd < 0xC4 + (0x40 * 0x24)) || (*size_tmd > 0x4000)) { + Debug("TMD has bad size!"); + return 1; + } + if (DecryptNandToMem(buffer, *offset_tmd, *size_tmd, ctrnand_info) != 0) + return 1; + u32 size_sig = (buffer[3] == 3) ? 0x240 : (buffer[3] == 4) ? 0x140 : (buffer[3] == 5) ? 0x80 : 0; + if ((size_sig == 0) || (memcmp(buffer, "\x00\x01\x00", 3) != 0)) { + Debug("Unknown signature type: %08X", getbe32(buffer)); + return 1; + } + cnt_count = getbe16(buffer + size_sig + 0x9E); + u32 size_tmd_expected = size_sig + 0xC4 + (0x40 * 0x24) + (cnt_count * 0x30); + if (*size_tmd < size_tmd_expected) { + Debug("TMD bad size (expected %ub)!", size_tmd_expected ); + return 1; + } + buffer += size_sig + 0xC4 + (0x40 * 0x24); + + for (u32 i = 0; i < cnt_count && i < max_cnt; i++) { + char path[64]; + u32 cnt_id = getbe32(buffer + (0x30 * i)); + if (i >= max_cnt) { + Debug("APP%i was skipped", i); + continue; + } + sprintf(path, "TITLE %08X %08X CONTENT %08XAPP", (unsigned int) title_info->tid_high, (unsigned int) tid_low, (unsigned int) cnt_id); + if (SeekFileInNand(offset_app + i, size_app + i, path, ctrnand_info) != 0) { + Debug("APP%i not found or fragmented!", i); + return 1; + } + Debug("APP%i found at %08X, size %ukB", i, offset_app[i], size_app[i] / 1024); + } + + return 0; +} + +u32 GetRegion(void) +{ + PartitionInfo* p_info = GetPartitionInfo(P_CTRNAND); + u8 secureinfo[0x200]; + u32 offset; + u32 size; + + if ((SeekFileInNand(&offset, &size, "RW SYS SECURE~? ", p_info) != 0) || + (DecryptNandToMem(secureinfo, offset, size, p_info) != 0)) + return 0xF; + + return (u32) secureinfo[0x100]; +} + +u32 DumpHealthAndSafety(u32 param) +{ + (void) (param); // param is unused here + PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); + TitleListInfo* health = (GetUnitPlatform() == PLATFORM_3DS) ? TL_HS : TL_HS_N; + TitleListInfo* health_alt = (GetUnitPlatform() == PLATFORM_N3DS) ? TL_HS : NULL; + char filename[64]; + u32 offset_app[4]; + u32 size_app[4]; + u32 offset_tmd; + u32 size_tmd; + + + if ((DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health, 4) != 0) && (!health_alt || + (DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health_alt, 4) != 0))) + return 1; + if (OutputFileNameSelector(filename, "hs.app", NULL) != 0) + return 1; + + Debug("Dumping & decrypting APP0..."); + if (DecryptNandToFile(filename, offset_app[0], size_app[0], ctrnand_info) != 0) + return 1; + if (CryptNcch(filename, 0, 0, 0, NULL) != 0) + return 1; + + + return 0; +} + +u32 InjectHealthAndSafety(u32 param) +{ + u8* buffer = BUFFER_ADDRESS; + PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); + TitleListInfo* health = (GetUnitPlatform() == PLATFORM_3DS) ? TL_HS : TL_HS_N; + TitleListInfo* health_alt = (GetUnitPlatform() == PLATFORM_N3DS) ? TL_HS : NULL; + NcchHeader* ncch = (NcchHeader*) 0x20316000; + char filename[64]; + u32 offset_app[4]; + u32 size_app[4]; + u32 offset_tmd; + u32 size_tmd; + u32 size_hs; + + + if (!(param & N_NANDWRITE)) // developer screwup protection + return 1; + + if ((DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health, 4) != 0) && (!health_alt || + (DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health_alt, 4) != 0))) + return 1; + if (size_app[0] > 0x400000) { + Debug("H&S system app is too big!"); + return 1; + } + if (DecryptNandToMem((void*) ncch, offset_app[0], 0x200, ctrnand_info) != 0) + return 1; + if (InputFileNameSelector(filename, NULL, "app", ncch->signature, 0x100, 0, false) != 0) + return 1; + + if (!DebugFileOpen(filename)) + return 1; + size_hs = FileGetSize(); + memset(buffer, 0, size_app[0]); + if (size_hs > size_app[0]) { + Debug("H&S inject app is too big!"); + FileClose(); + return 1; + } + if (FileCopyTo("hs.enc", buffer, size_hs) != size_hs) { + Debug("Error copying to hs.enc"); + FileClose(); + return 1; + } + FileClose(); + + if (CryptNcch("hs.enc", 0, 0, 0, ncch->flags) != 0) + return 1; + + Debug("Injecting H&S app..."); + if (EncryptFileToNand("hs.enc", offset_app[0], size_hs, ctrnand_info) != 0) + return 1; + + Debug("Fixing TMD..."); + TitleMetaData* tmd = (TitleMetaData*) 0x20316000; + TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); + const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; + if (DecryptNandToMem((u8*) tmd, offset_tmd, size_tmd, ctrnand_info) != 0) + return 1; + u32 cnt_count = getbe16(tmd->content_count); + if (memcmp(tmd->sig_type, sig_type, 4) != 0) { + Debug("Bad TMD signature type"); + return 1; // this should never happen + } + if (GetHashFromFile("hs.enc", 0, size_app[0], content_list->hash) != 0) { + Debug("Failed!"); + return 1; + } + for (u32 i = 0, kc = 0; i < 64 && kc < cnt_count; i++) { + TmdContentInfo* cntinfo = tmd->contentinfo + i; + u32 k = getbe16(cntinfo->cmd_count); + sha_quick(cntinfo->hash, content_list + kc, k * sizeof(TmdContentChunk), SHA256_MODE); + kc += k; + } + sha_quick(tmd->contentinfo_hash, (u8*)tmd->contentinfo, 64 * sizeof(TmdContentInfo), SHA256_MODE); + if (EncryptMemToNand((u8*) tmd, offset_tmd, size_tmd, ctrnand_info) != 0) + return 1; + + + return 0; +} diff --git a/source/decryptor/nandfat.h b/source/decryptor/nandfat.h new file mode 100644 index 0000000..0c5d052 --- /dev/null +++ b/source/decryptor/nandfat.h @@ -0,0 +1,22 @@ +#pragma once + +#include "decryptor/nand.h" +#include "common.h" + +#define MAX_ENTRIES 1024 + +typedef struct { + char name[32]; + u32 tid_high; + u32 tid_low[6]; +} TitleListInfo; + +u32 SeekFileInNand(u32* offset, u32* size, const char* path, PartitionInfo* partition); +u32 SeekTitleInNandDb(u32 tid_high, u32 tid_low, u32* tmd_id); +u32 DebugSeekTitleInNand(u32* offset_tmd, u32* size_tmd, u32* offset_app, u32* size_app, TitleListInfo* title_info, u32 max_cnt); +u32 GetRegion(void); + +// --> FEATURE FUNCTIONS <-- +u32 DumpHealthAndSafety(u32 param); +u32 InjectHealthAndSafety(u32 param); +u32 DumpNcchFirms(u32 param); diff --git a/source/gamecart/card_ntr.c b/source/gamecart/card_ntr.c new file mode 100644 index 0000000..f8bcfa9 --- /dev/null +++ b/source/gamecart/card_ntr.c @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------- + + Copyright (C) 2005 + Michael Noland (joat) + Jason Rogers (dovoto) + Dave Murphy (WinterMute) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. + + +---------------------------------------------------------------------------------*/ +#include "nds/card.h" +#include "nds/dma.h" +#include "nds/memory.h" +#include "nds/bios.h" + + +//--------------------------------------------------------------------------------- +void cardWriteCommand(const u8 *command) { +//--------------------------------------------------------------------------------- + int index; + + REG_AUXSPICNTH = CARD_CR1_ENABLE | CARD_CR1_IRQ; + + for (index = 0; index < 8; index++) { + CARD_COMMAND[7-index] = command[index]; + } +} + + +//--------------------------------------------------------------------------------- +void cardPolledTransfer(u32 flags, u32 *destination, u32 length, const u8 *command) { +//--------------------------------------------------------------------------------- + u32 data; + cardWriteCommand(command); + REG_ROMCTRL = flags; + u32 * target = destination + length; + do { + // Read data if available + if (REG_ROMCTRL & CARD_DATA_READY) { + data=CARD_DATA_RD; + if (destination < target) + *destination = data; + destination++; + } + } while (REG_ROMCTRL & CARD_BUSY); +} + + +//--------------------------------------------------------------------------------- +void cardStartTransfer(const u8 *command, u32 *destination, int channel, u32 flags) { +//--------------------------------------------------------------------------------- + cardWriteCommand(command); + + // Set up a DMA channel to transfer a word every time the card makes one + DMA_SRC(channel) = (u32)&CARD_DATA_RD; + DMA_DEST(channel) = (u32)destination; + DMA_CR(channel) = DMA_ENABLE | DMA_START_CARD | DMA_32_BIT | DMA_REPEAT | DMA_SRC_FIX | 0x0001; + + REG_ROMCTRL = flags; +} + + +//--------------------------------------------------------------------------------- +u32 cardWriteAndRead(const u8 *command, u32 flags) { +//--------------------------------------------------------------------------------- + cardWriteCommand(command); + REG_ROMCTRL = flags | CARD_ACTIVATE | CARD_nRESET | CARD_BLK_SIZE(7); + while (!(REG_ROMCTRL & CARD_DATA_READY)) ; + return CARD_DATA_RD; +} + +//--------------------------------------------------------------------------------- +void cardParamCommand (u8 command, u32 parameter, u32 flags, u32 *destination, u32 length) { +//--------------------------------------------------------------------------------- + u8 cmdData[8]; + + cmdData[7] = (u8) command; + cmdData[6] = (u8) (parameter >> 24); + cmdData[5] = (u8) (parameter >> 16); + cmdData[4] = (u8) (parameter >> 8); + cmdData[3] = (u8) (parameter >> 0); + cmdData[2] = 0; + cmdData[1] = 0; + cmdData[0] = 0; + + cardPolledTransfer(flags, destination, length, cmdData); +} + +//--------------------------------------------------------------------------------- +void cardReadHeader(u8 *header) { +//--------------------------------------------------------------------------------- + REG_ROMCTRL=0; + REG_AUXSPICNTH=0; + swiDelay(167550); + REG_AUXSPICNTH=CARD_CR1_ENABLE|CARD_CR1_IRQ; + REG_ROMCTRL=CARD_nRESET|CARD_SEC_SEED; + while(REG_ROMCTRL&CARD_BUSY) ; + cardReset(); + while(REG_ROMCTRL&CARD_BUSY) ; + + cardParamCommand(CARD_CMD_HEADER_READ,0,CARD_ACTIVATE|CARD_nRESET|CARD_CLK_SLOW|CARD_BLK_SIZE(1)|CARD_DELAY1(0x1FFF)|CARD_DELAY2(0x3F),(u32*)header,512/4); +} + + +//--------------------------------------------------------------------------------- +u32 cardReadID(u32 flags) { +//--------------------------------------------------------------------------------- + const u8 command[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, CARD_CMD_HEADER_CHIPID}; + return cardWriteAndRead(command, flags); +} + + +//--------------------------------------------------------------------------------- +void cardReset() { +//--------------------------------------------------------------------------------- + const u8 cmdData[8]={0,0,0,0,0,0,0,CARD_CMD_DUMMY}; + cardWriteCommand(cmdData); + REG_ROMCTRL=CARD_ACTIVATE|CARD_nRESET|CARD_CLK_SLOW|CARD_BLK_SIZE(5)|CARD_DELAY2(0x18); + u32 read=0; + + do { + if(REG_ROMCTRL&CARD_DATA_READY) { + if(read<0x2000) { + u32 data=CARD_DATA_RD; + (void)data; + read+=4; + } + } + } while(REG_ROMCTRL&CARD_BUSY); +} + + + + diff --git a/source/gamecart/card_ntr.h b/source/gamecart/card_ntr.h new file mode 100644 index 0000000..5921bcb --- /dev/null +++ b/source/gamecart/card_ntr.h @@ -0,0 +1,11 @@ + +#pragma once + +void cardWriteCommand(const u8 *command); +void cardPolledTransfer(u32 flags, u32 *destination, u32 length, const u8 *command); +void cardStartTransfer(const u8 *command, u32 *destination, int channel, u32 flags); +u32 cardWriteAndRead(const u8 *command, u32 flags); +void cardParamCommand (u8 command, u32 parameter, u32 flags, u32 *destination, u32 length); +void cardReadHeader(u8 *header); +u32 cardReadID(u32 flags); +void cardReset(); diff --git a/source/gamecart/command_ctr.c b/source/gamecart/command_ctr.c new file mode 100644 index 0000000..49c8829 --- /dev/null +++ b/source/gamecart/command_ctr.c @@ -0,0 +1,57 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "command_ctr.h" + +#include "protocol_ctr.h" + +static int read_count = 0; + +static void CTR_CmdC5() +{ + static const u32 c5_cmd[4] = { 0xC5000000, 0x00000000, 0x00000000, 0x00000000 }; + CTR_SendCommand(c5_cmd, 0, 1, 0x100002C, NULL); +} + +void CTR_CmdReadData(u32 sector, u32 length, u32 blocks, void* buffer) +{ + if(read_count++ > 10000) + { + CTR_CmdC5(); + read_count = 0; + } + + const u32 read_cmd[4] = { + (0xBF000000 | (u32)(sector >> 23)), + (u32)((sector << 9) & 0xFFFFFFFF), + 0x00000000, 0x00000000 + }; + CTR_SendCommand(read_cmd, length, blocks, 0x704822C, buffer); +} + +void CTR_CmdReadHeader(void* buffer) +{ + static const u32 readheader_cmd[4] = { 0x82000000, 0x00000000, 0x00000000, 0x00000000 }; + CTR_SendCommand(readheader_cmd, 0x200, 1, 0x704802C, buffer); +} + +void CTR_CmdReadUniqueID(void* buffer) +{ + static const u32 readheader_cmd[4] = { 0xC6000000, 0x00000000, 0x00000000, 0x00000000 }; + CTR_SendCommand(readheader_cmd, 0x40, 1, 0x903002C, buffer); +} + +u32 CTR_CmdGetSecureId(u32 rand1, u32 rand2) +{ + u32 id = 0; + const u32 getid_cmd[4] = { 0xA2000000, 0x00000000, rand1, rand2 }; + CTR_SendCommand(getid_cmd, 0x4, 1, 0x701002C, &id); + return id; +} + +void CTR_CmdSeed(u32 rand1, u32 rand2) +{ + const u32 seed_cmd[4] = { 0x83000000, 0x00000000, rand1, rand2 }; + CTR_SendCommand(seed_cmd, 0, 1, 0x700822C, NULL); +} diff --git a/source/gamecart/command_ctr.h b/source/gamecart/command_ctr.h new file mode 100644 index 0000000..b2a5a43 --- /dev/null +++ b/source/gamecart/command_ctr.h @@ -0,0 +1,14 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common.h" + +void CTR_CmdReadSectorSD(u8* aBuffer, u32 aSector); +void CTR_CmdReadData(u32 sector, u32 length, u32 blocks, void* buffer); +void CTR_CmdReadHeader(void* buffer); +void CTR_CmdReadUniqueID(void* buffer); +u32 CTR_CmdGetSecureId(u32 rand1, u32 rand2); +void CTR_CmdSeed(u32 rand1, u32 rand2); diff --git a/source/gamecart/command_ntr.c b/source/gamecart/command_ntr.c new file mode 100644 index 0000000..ab47c14 --- /dev/null +++ b/source/gamecart/command_ntr.c @@ -0,0 +1,67 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +// +// modifyed by osilloscopion (2 Jul 2016) +// + +#include "command_ntr.h" +#include "protocol_ntr.h" +#include "card_ntr.h" +#include "delay.h" + + +u32 ReadDataFlags = 0; + +void NTR_CmdReset(void) +{ + cardReset (); + ioDelay(0xF000); +} + +u32 NTR_CmdGetCartId(void) +{ + return cardReadID (0); +} + +void NTR_CmdEnter16ByteMode(void) +{ + static const u32 enter16bytemode_cmd[2] = { 0x3E000000, 0x00000000 }; + NTR_SendCommand(enter16bytemode_cmd, 0x0, 0, NULL); +} + +void NTR_CmdReadHeader (u8* buffer) +{ + REG_NTRCARDROMCNT=0; + REG_NTRCARDMCNT=0; + ioDelay(167550); + REG_NTRCARDMCNT=NTRCARD_CR1_ENABLE|NTRCARD_CR1_IRQ; + REG_NTRCARDROMCNT=NTRCARD_nRESET|NTRCARD_SEC_SEED; + while(REG_NTRCARDROMCNT&NTRCARD_BUSY) ; + cardReset(); + while(REG_NTRCARDROMCNT&NTRCARD_BUSY) ; + u32 iCardId=cardReadID(NTRCARD_CLK_SLOW); + while(REG_NTRCARDROMCNT&NTRCARD_BUSY) ; + + u32 iCheapCard=iCardId&0x80000000; + + if(iCheapCard) + { + //this is magic of wood goblins + for(size_t ii=0;ii<8;++ii) + cardParamCommand(NTRCARD_CMD_HEADER_READ,ii*0x200,NTRCARD_ACTIVATE|NTRCARD_nRESET|NTRCARD_CLK_SLOW|NTRCARD_BLK_SIZE(1)|NTRCARD_DELAY1(0x1FFF)|NTRCARD_DELAY2(0x3F),(u32*)(buffer+ii*0x200),0x200/sizeof(u32)); + } + else + { + //0xac3f1fff + cardParamCommand(NTRCARD_CMD_HEADER_READ,0,NTRCARD_ACTIVATE|NTRCARD_nRESET|NTRCARD_CLK_SLOW|NTRCARD_BLK_SIZE(4)|NTRCARD_DELAY1(0x1FFF)|NTRCARD_DELAY2(0x3F),(u32*)buffer,0x1000/sizeof(u32)); + } + //cardReadHeader (buffer); +} + +void NTR_CmdReadData (u32 offset, void* buffer) +{ + cardParamCommand (NTRCARD_CMD_DATA_READ, offset, ReadDataFlags | NTRCARD_ACTIVATE | NTRCARD_nRESET | NTRCARD_BLK_SIZE(1), (u32*)buffer, 0x200 / 4); +} + + diff --git a/source/gamecart/command_ntr.h b/source/gamecart/command_ntr.h new file mode 100644 index 0000000..559dc33 --- /dev/null +++ b/source/gamecart/command_ntr.h @@ -0,0 +1,19 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +// +// modifyed by osilloscopion (2 Jul 2016) +// + +#pragma once + +#include "common.h" + +void NTR_CmdReset(void); +u32 NTR_CmdGetCartId(void); +void NTR_CmdEnter16ByteMode(void); +void NTR_CmdReadHeader (u8* buffer); +void NTR_CmdReadData (u32 offset, void* buffer); + +bool NTR_Secure_Init (u8* buffer, u32 CartID, int iCardDevice); + diff --git a/source/gamecart/delay.h b/source/gamecart/delay.h new file mode 100644 index 0000000..afad438 --- /dev/null +++ b/source/gamecart/delay.h @@ -0,0 +1,9 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common.h" + +void ioDelay(u32 us); diff --git a/source/gamecart/iodelay.s b/source/gamecart/iodelay.s new file mode 100644 index 0000000..b3baccd --- /dev/null +++ b/source/gamecart/iodelay.s @@ -0,0 +1,17 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +.arm +.global ioDelay +.type ioDelay STT_FUNC + +@ioDelay ( u32 us ) +ioDelay: + ldr r1, =0x18000000 @ VRAM +1: + @ Loop doing uncached reads from VRAM to make loop timing more reliable + ldr r2, [r1] + subs r0, #1 + bgt 1b + bx lr diff --git a/source/gamecart/nds/bios.h b/source/gamecart/nds/bios.h new file mode 100644 index 0000000..e69de29 diff --git a/source/gamecart/nds/card.h b/source/gamecart/nds/card.h new file mode 100644 index 0000000..af5fc4e --- /dev/null +++ b/source/gamecart/nds/card.h @@ -0,0 +1,76 @@ + +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +// +// modifyed by osilloscopion (2 Jul 2016) +// + +#pragma once +#include +#include "../delay.h" + +#define u8 uint8_t +#define u16 uint16_t +#define u32 uint32_t +#define u64 uint64_t + +#define vu8 volatile u8 +#define vu16 volatile u16 +#define vu32 volatile u32 +#define vu64 volatile u64 + +#define REG_AUXSPICNTH (*(vu16*)0x10164000) +#define REG_ROMCTRL (*(vu32*)0x10164004) +#define CARD_COMMAND ((vu8*)0x10164008) +#define CARD_DATA_RD (*(vu32*)0x1016401C) + +#define CARD_ACTIVATE (1u<<31) // when writing, get the ball rolling +#define CARD_WR (1u<<30) // Card write enable +#define CARD_nRESET (1u<<29) // value on the /reset pin (1 = high out, not a reset state, 0 = low out = in reset) +#define CARD_SEC_LARGE (1u<<28) // Use "other" secure area mode, which tranfers blocks of 0x1000 bytes at a time +#define CARD_CLK_SLOW (1u<<27) // Transfer clock rate (0 = 6.7MHz, 1 = 4.2MHz) +#define CARD_BLK_SIZE(n) (((n)&0x7u)<<24) // Transfer block size, (0 = None, 1..6 = (0x100 << n) bytes, 7 = 4 bytes) +#define CARD_SEC_CMD (1u<<22) // The command transfer will be hardware encrypted (KEY2) +#define CARD_DELAY2(n) (((n)&0x3Fu)<<16) // Transfer delay length part 2 +#define CARD_SEC_SEED (1u<<15) // Apply encryption (KEY2) seed to hardware registers +#define CARD_SEC_EN (1u<<14) // Security enable +#define CARD_SEC_DAT (1u<<13) // The data transfer will be hardware encrypted (KEY2) +#define CARD_DELAY1(n) ((n)&0x1FFFu) // Transfer delay length part 1 + +// 3 bits in b10..b8 indicate something +// read bits +#define CARD_BUSY (1u<<31) // when reading, still expecting incomming data? +#define CARD_DATA_READY (1u<<23) // when reading, REG_NTRCARDFIFO has another word of data and is good to go + +// Card commands +#define CARD_CMD_DUMMY 0x9Fu +#define CARD_CMD_HEADER_READ 0x00u +#define CARD_CMD_HEADER_CHIPID 0x90u +#define CARD_CMD_ACTIVATE_BF 0x3Cu // Go into blowfish (KEY1) encryption mode +#define CARD_CMD_ACTIVATE_BF2 0x3Du // Go into blowfish (KEY1) encryption mode +#define CARD_CMD_ACTIVATE_SEC 0x40u // Go into hardware (KEY2) encryption mode +#define CARD_CMD_SECURE_CHIPID 0x10u +#define CARD_CMD_SECURE_READ 0x20u +#define CARD_CMD_DISABLE_SEC 0x60u // Leave hardware (KEY2) encryption mode +#define CARD_CMD_DATA_MODE 0xA0u +#define CARD_CMD_DATA_READ 0xB7u +#define CARD_CMD_DATA_CHIPID 0xB8u + +#define CARD_CR1_ENABLE 0x8000u +#define CARD_CR1_IRQ 0x4000u + + +#define swiDelay(n) ioDelay(n) + +#define DMA_SRC(n) (*(vu32*)(0x10002004 + (n * 0x1c))) +#define DMA_DEST(n) (*(vu32*)(0x10002008 + (n * 0x1c))) +#define DMA_CR(n) (*(vu32*)(0x1000201C + (n * 0x1c))) + +#define DMA_ENABLE (1u << 31) +#define DMA_START_CARD (5u << 27) +#define DMA_32_BIT (1u << 26) +#define DMA_REPEAT (1u << 25) +#define DMA_SRC_FIX (1u << 24) + +void cardReset(); diff --git a/source/gamecart/nds/dma.h b/source/gamecart/nds/dma.h new file mode 100644 index 0000000..e69de29 diff --git a/source/gamecart/nds/memory.h b/source/gamecart/nds/memory.h new file mode 100644 index 0000000..e69de29 diff --git a/source/gamecart/protocol.c b/source/gamecart/protocol.c new file mode 100644 index 0000000..c78b520 --- /dev/null +++ b/source/gamecart/protocol.c @@ -0,0 +1,239 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "protocol.h" + +#include +#include +#include + +#include "common.h" +#include "protocol_ctr.h" +#include "protocol_ntr.h" +#include "command_ctr.h" +#include "command_ntr.h" +#include "delay.h" + +// could have been done better, but meh... +#define REG_AESCNT (*(vu32*)0x10009000) +#define REG_AESBLKCNT (*(vu32*)0x10009004) +#define REG_AESWRFIFO (*(vu32*)0x10009008) +#define REG_AESRDFIFO (*(vu32*)0x1000900C) +#define REG_AESKEYSEL (*(vu8*)0x10009010) +#define REG_AESKEYCNT (*(vu8*)0x10009011) +#define REG_AESCTR ((vu32*)0x10009020) // 16 +#define REG_AESMAC ((vu32*)0x10009030) // 16 +#define REG_AESKEYFIFO (*(vu32*)0x10009100) +#define REG_AESKEYXFIFO (*(vu32*)0x10009104) +#define REG_AESKEYYFIFO (*(vu32*)0x10009108) + +extern u8* bottomScreen; + +u32 CartID = 0xFFFFFFFFu; +u32 CartType = 0; + +static u32 A0_Response = 0xFFFFFFFFu; +static u32 rand1 = 0; +static u32 rand2 = 0; + +u32 BSWAP32(u32 val) { + return (((val >> 24) & 0xFF)) | + (((val >> 16) & 0xFF) << 8) | + (((val >> 8) & 0xFF) << 16) | + ((val & 0xFF) << 24); +} + +// TODO: Verify +static void ResetCartSlot(void) +{ + REG_CARDCONF2 = 0x0C; + REG_CARDCONF &= ~3; + + if (REG_CARDCONF2 == 0xC) { + while (REG_CARDCONF2 != 0); + } + + if (REG_CARDCONF2 != 0) + return; + + REG_CARDCONF2 = 0x4; + while(REG_CARDCONF2 != 0x4); + + REG_CARDCONF2 = 0x8; + while(REG_CARDCONF2 != 0x8); +} + +static void SwitchToNTRCARD(void) +{ + REG_NTRCARDROMCNT = 0x20000000; + REG_CARDCONF &= ~3; + REG_CARDCONF &= ~0x100; + REG_NTRCARDMCNT = NTRCARD_CR1_ENABLE; +} + +static void SwitchToCTRCARD(void) +{ + REG_CTRCARDCNT = 0x10000000; + REG_CARDCONF = (REG_CARDCONF & ~3) | 2; +} + +int Cart_IsInserted(void) +{ + return (0x9000E2C2 == CTR_CmdGetSecureId(rand1, rand2) ); +} + +u32 Cart_GetID(void) +{ + return CartID; +} + +void Cart_Init(void) +{ + ResetCartSlot(); //Seems to reset the cart slot? + + REG_CTRCARDSECCNT &= 0xFFFFFFFB; + ioDelay(0x40000); + + SwitchToNTRCARD(); + ioDelay(0x40000); + + REG_NTRCARDROMCNT = 0; + REG_NTRCARDMCNT &= 0xFF; + ioDelay(0x40000); + + REG_NTRCARDMCNT |= (NTRCARD_CR1_ENABLE | NTRCARD_CR1_IRQ); + REG_NTRCARDROMCNT = NTRCARD_nRESET | NTRCARD_SEC_SEED; + while (REG_NTRCARDROMCNT & NTRCARD_BUSY); + + // Reset + NTR_CmdReset(); + ioDelay(0x40000); + CartID = NTR_CmdGetCartId(); + + // 3ds + if (CartID & 0x10000000) { + u32 unknowna0_cmd[2] = { 0xA0000000, 0x00000000 }; + NTR_SendCommand(unknowna0_cmd, 0x4, 0, &A0_Response); + + NTR_CmdEnter16ByteMode(); + SwitchToCTRCARD(); + ioDelay(0xF000); + + REG_CTRCARDBLKCNT = 0; + } +} + +static void AES_SetKeyControl(u32 a) { + REG_AESKEYCNT = (REG_AESKEYCNT & 0xC0) | a | 0x80; +} + +//returns 1 if MAC valid otherwise 0 +static u8 card_aes(u32 *out, u32 *buff, size_t size) { // note size param ignored + (void) size; + REG_AESCNT = 0x10C00; //flush r/w fifo macsize = 001 + + (*(vu8*)0x10000008) |= 0x0C; //??? + + REG_AESCNT |= 0x2800000; + + //const u8 is_dev_unit = *(vu8*)0x10010010; + //if(is_dev_unit) //Dev unit + const u8 is_dev_cart = (A0_Response&3)==3; + if(is_dev_cart) //Dev unit + { + AES_SetKeyControl(0x11); + REG_AESKEYFIFO = 0; + REG_AESKEYFIFO = 0; + REG_AESKEYFIFO = 0; + REG_AESKEYFIFO = 0; + REG_AESKEYSEL = 0x11; + } + else + { + AES_SetKeyControl(0x3B); + REG_AESKEYYFIFO = buff[0]; + REG_AESKEYYFIFO = buff[1]; + REG_AESKEYYFIFO = buff[2]; + REG_AESKEYYFIFO = buff[3]; + REG_AESKEYSEL = 0x3B; + } + + REG_AESCNT = 0x4000000; + REG_AESCNT &= 0xFFF7FFFF; + REG_AESCNT |= 0x2970000; + REG_AESMAC[0] = buff[11]; + REG_AESMAC[1] = buff[10]; + REG_AESMAC[2] = buff[9]; + REG_AESMAC[3] = buff[8]; + REG_AESCNT |= 0x2800000; + REG_AESCTR[0] = buff[14]; + REG_AESCTR[1] = buff[13]; + REG_AESCTR[2] = buff[12]; + REG_AESBLKCNT = 0x10000; + + u32 v11 = ((REG_AESCNT | 0x80000000) & 0xC7FFFFFF); //Start and clear mode (ccm decrypt) + u32 v12 = v11 & 0xBFFFFFFF; //Disable Interrupt + REG_AESCNT = ((((v12 | 0x3000) & 0xFD7F3FFF) | (5 << 23)) & 0xFEBFFFFF) | (5 << 22); + + //REG_AESCNT = 0x83D73C00; + REG_AESWRFIFO = buff[4]; + REG_AESWRFIFO = buff[5]; + REG_AESWRFIFO = buff[6]; + REG_AESWRFIFO = buff[7]; + while (((REG_AESCNT >> 5) & 0x1F) <= 3); + out[0] = REG_AESRDFIFO; + out[1] = REG_AESRDFIFO; + out[2] = REG_AESRDFIFO; + out[3] = REG_AESRDFIFO; + return ((REG_AESCNT >> 21) & 1); +} + +void Cart_Secure_Init(u32 *buf, u32 *out) +{ + card_aes(out, buf, 0x200); +// u8 mac_valid = card_aes(out, buf, 0x200); + +// if (!mac_valid) +// ClearScreen(bottomScreen, RGB(255, 0, 0)); + + ioDelay(0xF0000); + + CTR_SetSecKey(A0_Response); + CTR_SetSecSeed(out, true); + + rand1 = 0x42434445;//*((vu32*)0x10011000); + rand2 = 0x46474849;//*((vu32*)0x10011010); + + CTR_CmdSeed(rand1, rand2); + + out[3] = BSWAP32(rand2); + out[2] = BSWAP32(rand1); + CTR_SetSecSeed(out, false); + + u32 test = 0; + const u32 A2_cmd[4] = { 0xA2000000, 0x00000000, rand1, rand2 }; + CTR_SendCommand(A2_cmd, 4, 1, 0x701002C, &test); + + u32 test2 = 0; + const u32 A3_cmd[4] = { 0xA3000000, 0x00000000, rand1, rand2 }; + CTR_SendCommand(A3_cmd, 4, 1, 0x701002C, &test2); + + if(test==CartID && test2==A0_Response) + { + const u32 C5_cmd[4] = { 0xC5000000, 0x00000000, rand1, rand2 }; + CTR_SendCommand(C5_cmd, 0, 1, 0x100002C, NULL); + } + + for (int i = 0; i < 5; ++i) { + CTR_SendCommand(A2_cmd, 4, 1, 0x701002C, &test); + ioDelay(0xF0000); + } +} + +void Cart_Dummy(void) { + // Sends a dummy command to skip encrypted responses some problematic carts send. + u32 test; + const u32 A2_cmd[4] = { 0xA2000000, 0x00000000, rand1, rand2 }; + CTR_SendCommand(A2_cmd, 4, 1, 0x701002C, &test); +} diff --git a/source/gamecart/protocol.h b/source/gamecart/protocol.h new file mode 100644 index 0000000..7a12407 --- /dev/null +++ b/source/gamecart/protocol.h @@ -0,0 +1,25 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "common.h" + +#define REG_CARDCONF (*(vu16*)0x1000000C) +#define REG_CARDCONF2 (*(vu8*)0x10000010) + +//REG_AUXSPICNT +#define CARD_ENABLE (1u<<15) +#define CARD_SPI_ENABLE (1u<<13) +#define CARD_SPI_BUSY (1u<<7) +#define CARD_SPI_HOLD (1u<<6) + +#define LATENCY 0x822Cu + +u32 BSWAP32(u32 val); + +void Cart_Init(void); +int Cart_IsInserted(void); +u32 Cart_GetID(void); +void Cart_Secure_Init(u32* buf, u32* out); +void Cart_Dummy(void); diff --git a/source/gamecart/protocol_ctr.c b/source/gamecart/protocol_ctr.c new file mode 100644 index 0000000..0f24952 --- /dev/null +++ b/source/gamecart/protocol_ctr.c @@ -0,0 +1,183 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "protocol_ctr.h" + +#include "protocol.h" +#include "delay.h" +#ifdef VERBOSE_COMMANDS +#include "draw.h" +#endif + +void CTR_SetSecKey(u32 value) { + REG_CTRCARDSECCNT |= ((value & 3) << 8) | 4; + while (!(REG_CTRCARDSECCNT & 0x4000)); +} + +void CTR_SetSecSeed(const u32* seed, bool flag) { + REG_CTRCARDSECSEED = BSWAP32(seed[3]); + REG_CTRCARDSECSEED = BSWAP32(seed[2]); + REG_CTRCARDSECSEED = BSWAP32(seed[1]); + REG_CTRCARDSECSEED = BSWAP32(seed[0]); + REG_CTRCARDSECCNT |= 0x8000; + + while (!(REG_CTRCARDSECCNT & 0x4000)); + + if (flag) { + (*(vu32*)0x1000400C) = 0x00000001; // Enable cart command encryption? + } +} + +void CTR_SendCommand(const u32 command[4], u32 pageSize, u32 blocks, u32 latency, void* buffer) +{ +#ifdef VERBOSE_COMMANDS + Debug("C> %08X %08X %08X %08X", command[0], command[1], command[2], command[3]); +#endif + + REG_CTRCARDCMD[0] = command[3]; + REG_CTRCARDCMD[1] = command[2]; + REG_CTRCARDCMD[2] = command[1]; + REG_CTRCARDCMD[3] = command[0]; + + //Make sure this never happens + if(blocks == 0) blocks = 1; + + pageSize -= pageSize & 3; // align to 4 byte + u32 pageParam = CTRCARD_PAGESIZE_4K; + u32 transferLength = 4096; + // make zero read and 4 byte read a little special for timing optimization(and 512 too) + switch(pageSize) { + case 0: + transferLength = 0; + pageParam = CTRCARD_PAGESIZE_0; + break; + case 4: + transferLength = 4; + pageParam = CTRCARD_PAGESIZE_4; + break; + case 64: + transferLength = 64; + pageParam = CTRCARD_PAGESIZE_64; + break; + case 512: + transferLength = 512; + pageParam = CTRCARD_PAGESIZE_512; + break; + case 1024: + transferLength = 1024; + pageParam = CTRCARD_PAGESIZE_1K; + break; + case 2048: + transferLength = 2048; + pageParam = CTRCARD_PAGESIZE_2K; + break; + case 4096: + transferLength = 4096; + pageParam = CTRCARD_PAGESIZE_4K; + break; + default: + break; //Defaults already set + } + + REG_CTRCARDBLKCNT = blocks - 1; + transferLength *= blocks; + + // go + REG_CTRCARDCNT = 0x10000000; + REG_CTRCARDCNT = /*CTRKEY_PARAM | */CTRCARD_ACTIVATE | CTRCARD_nRESET | pageParam | latency; + + u8 * pbuf = (u8 *)buffer; + u32 * pbuf32 = (u32 * )buffer; + bool useBuf = ( NULL != pbuf ); + bool useBuf32 = (useBuf && (0 == (3 & ((u32)buffer)))); + + u32 count = 0; + u32 cardCtrl = REG_CTRCARDCNT; + + if(useBuf32) + { + while( (cardCtrl & CTRCARD_BUSY) && count < transferLength) + { + cardCtrl = REG_CTRCARDCNT; + if( cardCtrl & CTRCARD_DATA_READY ) { + u32 data = REG_CTRCARDFIFO; + *pbuf32++ = data; + count += 4; + } + } + } + else if(useBuf) + { + while( (cardCtrl & CTRCARD_BUSY) && count < transferLength) + { + cardCtrl = REG_CTRCARDCNT; + if( cardCtrl & CTRCARD_DATA_READY ) { + u32 data = REG_CTRCARDFIFO; + pbuf[0] = (unsigned char) (data >> 0); + pbuf[1] = (unsigned char) (data >> 8); + pbuf[2] = (unsigned char) (data >> 16); + pbuf[3] = (unsigned char) (data >> 24); + pbuf += sizeof (unsigned int); + count += 4; + } + } + } + else + { + while( (cardCtrl & CTRCARD_BUSY) && count < transferLength) + { + cardCtrl = REG_CTRCARDCNT; + if( cardCtrl & CTRCARD_DATA_READY ) { + u32 data = REG_CTRCARDFIFO; + (void)data; + count += 4; + } + } + } + + // if read is not finished, ds will not pull ROM CS to high, we pull it high manually + if( count != transferLength ) { + // MUST wait for next data ready, + // if ds pull ROM CS to high during 4 byte data transfer, something will mess up + // so we have to wait next data ready + do { cardCtrl = REG_CTRCARDCNT; } while(!(cardCtrl & CTRCARD_DATA_READY)); + // and this tiny delay is necessary + ioDelay(33); + // pull ROM CS high + REG_CTRCARDCNT = 0x10000000; + REG_CTRCARDCNT = CTRKEY_PARAM | CTRCARD_ACTIVATE | CTRCARD_nRESET; + } + // wait rom cs high + do { cardCtrl = REG_CTRCARDCNT; } while( cardCtrl & CTRCARD_BUSY ); + //lastCmd[0] = command[0];lastCmd[1] = command[1]; + +#ifdef VERBOSE_COMMANDS + if (!useBuf) { + Debug("C< NULL"); + } else if (!useBuf32) { + Debug("C< non32"); + } else { + u32* p = (u32*)buffer; + int transferWords = count / 4; + for (int i = 0; i < transferWords && i < 4*4; i += 4) { + switch (transferWords - i) { + case 0: + break; + case 1: + Debug("C< %08X", p[i+0]); + break; + case 2: + Debug("C< %08X %08X", p[i+0], p[i+1]); + break; + case 3: + Debug("C< %08X %08X %08X", p[i+0], p[i+1], p[i+2]); + break; + default: + Debug("C< %08X %08X %08X %08X", p[i+0], p[i+1], p[i+2], p[i+3]); + break; + } + } + } +#endif +} diff --git a/source/gamecart/protocol_ctr.h b/source/gamecart/protocol_ctr.h new file mode 100644 index 0000000..cba469e --- /dev/null +++ b/source/gamecart/protocol_ctr.h @@ -0,0 +1,42 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common.h" + +#define REG_CTRCARDCNT (*(vu32*)0x10004000) +#define REG_CTRCARDBLKCNT (*(vu32*)0x10004004) +#define REG_CTRCARDSECCNT (*(vu32*)0x10004008) +#define REG_CTRCARDSECSEED (*(vu32*)0x10004010) +#define REG_CTRCARDCMD ((vu32*)0x10004020) +#define REG_CTRCARDFIFO (*(vu32*)0x10004030) + +#define CTRCARD_PAGESIZE_0 (0<<16) +#define CTRCARD_PAGESIZE_4 (1u<<16) +#define CTRCARD_PAGESIZE_16 (2u<<16) +#define CTRCARD_PAGESIZE_64 (3u<<16) +#define CTRCARD_PAGESIZE_512 (4u<<16) +#define CTRCARD_PAGESIZE_1K (5u<<16) +#define CTRCARD_PAGESIZE_2K (6u<<16) +#define CTRCARD_PAGESIZE_4K (7u<<16) +#define CTRCARD_PAGESIZE_16K (8u<<16) +#define CTRCARD_PAGESIZE_64K (9u<<16) + +#define CTRCARD_CRC_ERROR (1u<<4) +#define CTRCARD_ACTIVATE (1u<<31) // when writing, get the ball rolling +#define CTRCARD_IE (1u<<30) // Interrupt enable +#define CTRCARD_WR (1u<<29) // Card write enable +#define CTRCARD_nRESET (1u<<28) // value on the /reset pin (1 = high out, not a reset state, 0 = low out = in reset) +#define CTRCARD_BLK_SIZE(n) (((n)&0xFu)<<16) // Transfer block size + +#define CTRCARD_BUSY (1u<<31) // when reading, still expecting incomming data? +#define CTRCARD_DATA_READY (1u<<27) // when reading, REG_CTRCARDFIFO has another word of data and is good to go + +#define CTRKEY_PARAM 0x1000000u + +void CTR_SetSecKey(u32 value); +void CTR_SetSecSeed(const u32* seed, bool flag); + +void CTR_SendCommand(const u32 command[4], u32 pageSize, u32 blocks, u32 latency, void* buffer); diff --git a/source/gamecart/protocol_ntr.c b/source/gamecart/protocol_ntr.c new file mode 100644 index 0000000..1715437 --- /dev/null +++ b/source/gamecart/protocol_ntr.c @@ -0,0 +1,150 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "protocol_ntr.h" +#ifdef VERBOSE_COMMANDS +#include "draw.h" +#endif + +void NTR_SendCommand(const u32 command[2], u32 pageSize, u32 latency, void* buffer) +{ +#ifdef VERBOSE_COMMANDS + Debug("N> %08X %08X", command[0], command[1]); +#endif + + REG_NTRCARDMCNT = NTRCARD_CR1_ENABLE; + + for( u32 i=0; i<2; ++i ) + { + REG_NTRCARDCMD[i*4+0] = command[i]>>24; + REG_NTRCARDCMD[i*4+1] = command[i]>>16; + REG_NTRCARDCMD[i*4+2] = command[i]>>8; + REG_NTRCARDCMD[i*4+3] = command[i]>>0; + } + + pageSize -= pageSize & 3; // align to 4 byte + + u32 pageParam = NTRCARD_PAGESIZE_4K; + u32 transferLength = 4096; + + // make zero read and 4 byte read a little special for timing optimization(and 512 too) + switch (pageSize) { + case 0: + transferLength = 0; + pageParam = NTRCARD_PAGESIZE_0; + break; + case 4: + transferLength = 4; + pageParam = NTRCARD_PAGESIZE_4; + break; + case 512: + transferLength = 512; + pageParam = NTRCARD_PAGESIZE_512; + break; + case 8192: + transferLength = 8192; + pageParam = NTRCARD_PAGESIZE_8K; + break; + default: + break; //Using 4K pagesize and transfer length by default + } + + // go + REG_NTRCARDROMCNT = 0x10000000; + REG_NTRCARDROMCNT = NTRKEY_PARAM | NTRCARD_ACTIVATE | NTRCARD_nRESET | pageParam | latency; + + u8 * pbuf = (u8 *)buffer; + u32 * pbuf32 = (u32 * )buffer; + bool useBuf = ( NULL != pbuf ); + bool useBuf32 = (useBuf && (0 == (3 & ((u32)buffer)))); + + u32 count = 0; + u32 cardCtrl = REG_NTRCARDROMCNT; + + if(useBuf32) + { + while( (cardCtrl & NTRCARD_BUSY) && count < pageSize) + { + cardCtrl = REG_NTRCARDROMCNT; + if( cardCtrl & NTRCARD_DATA_READY ) { + u32 data = REG_NTRCARDFIFO; + *pbuf32++ = data; + count += 4; + } + } + } + else if(useBuf) + { + while( (cardCtrl & NTRCARD_BUSY) && count < pageSize) + { + cardCtrl = REG_NTRCARDROMCNT; + if( cardCtrl & NTRCARD_DATA_READY ) { + u32 data = REG_NTRCARDFIFO; + pbuf[0] = (unsigned char) (data >> 0); + pbuf[1] = (unsigned char) (data >> 8); + pbuf[2] = (unsigned char) (data >> 16); + pbuf[3] = (unsigned char) (data >> 24); + pbuf += sizeof (unsigned int); + count += 4; + } + } + } + else + { + while( (cardCtrl & NTRCARD_BUSY) && count < pageSize) + { + cardCtrl = REG_NTRCARDROMCNT; + if( cardCtrl & NTRCARD_DATA_READY ) { + u32 data = REG_NTRCARDFIFO; + (void)data; + count += 4; + } + } + } + + // if read is not finished, ds will not pull ROM CS to high, we pull it high manually + if( count != transferLength ) { + // MUST wait for next data ready, + // if ds pull ROM CS to high during 4 byte data transfer, something will mess up + // so we have to wait next data ready + do { cardCtrl = REG_NTRCARDROMCNT; } while(!(cardCtrl & NTRCARD_DATA_READY)); + // and this tiny delay is necessary + //ioAK2Delay(33); + // pull ROM CS high + REG_NTRCARDROMCNT = 0x10000000; + REG_NTRCARDROMCNT = NTRKEY_PARAM | NTRCARD_ACTIVATE | NTRCARD_nRESET/* | 0 | 0x0000*/; + } + // wait rom cs high + do { cardCtrl = REG_NTRCARDROMCNT; } while( cardCtrl & NTRCARD_BUSY ); + //lastCmd[0] = command[0];lastCmd[1] = command[1]; + +#ifdef VERBOSE_COMMANDS + if (!useBuf) { + Debug("N< NULL"); + } else if (!useBuf32) { + Debug("N< non32"); + } else { + u32* p = (u32*)buffer; + int transferWords = count / 4; + for (int i = 0; i < transferWords && i < 4*4; i += 4) { + switch (transferWords - i) { + case 0: + break; + case 1: + Debug("N< %08X", p[i+0]); + break; + case 2: + Debug("N< %08X %08X", p[i+0], p[i+1]); + break; + case 3: + Debug("N< %08X %08X %08X", p[i+0], p[i+1], p[i+2]); + break; + default: + Debug("N< %08X %08X %08X %08X", p[i+0], p[i+1], p[i+2], p[i+3]); + break; + } + } + } +#endif +} diff --git a/source/gamecart/protocol_ntr.h b/source/gamecart/protocol_ntr.h new file mode 100644 index 0000000..536e7fc --- /dev/null +++ b/source/gamecart/protocol_ntr.h @@ -0,0 +1,65 @@ +// Copyright 2014 Normmatt +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common.h" + +#define REG_NTRCARDMCNT (*(vu16*)0x10164000) +#define REG_NTRCARDMDATA (*(vu16*)0x10164002) +#define REG_NTRCARDROMCNT (*(vu32*)0x10164004) +#define REG_NTRCARDCMD ((vu8*)0x10164008) +#define REG_NTRCARDSEEDX_L (*(vu32*)0x10164010) +#define REG_NTRCARDSEEDY_L (*(vu32*)0x10164014) +#define REG_NTRCARDSEEDX_H (*(vu16*)0x10164018) +#define REG_NTRCARDSEEDY_H (*(vu16*)0x1016401A) +#define REG_NTRCARDFIFO (*(vu32*)0x1016401C) + +#define NTRCARD_PAGESIZE_0 (0<<24) +#define NTRCARD_PAGESIZE_4 (7u<<24) +#define NTRCARD_PAGESIZE_512 (1u<<24) +#define NTRCARD_PAGESIZE_1K (2u<<24) +#define NTRCARD_PAGESIZE_2K (3u<<24) +#define NTRCARD_PAGESIZE_4K (4u<<24) +#define NTRCARD_PAGESIZE_8K (5u<<24) +#define NTRCARD_PAGESIZE_16K (6u<<24) + +#define NTRCARD_ACTIVATE (1u<<31) // when writing, get the ball rolling +#define NTRCARD_WR (1u<<30) // Card write enable +#define NTRCARD_nRESET (1u<<29) // value on the /reset pin (1 = high out, not a reset state, 0 = low out = in reset) +#define NTRCARD_SEC_LARGE (1u<<28) // Use "other" secure area mode, which tranfers blocks of 0x1000 bytes at a time +#define NTRCARD_CLK_SLOW (1u<<27) // Transfer clock rate (0 = 6.7MHz, 1 = 4.2MHz) +#define NTRCARD_BLK_SIZE(n) (((n)&0x7u)<<24) // Transfer block size, (0 = None, 1..6 = (0x100 << n) bytes, 7 = 4 bytes) +#define NTRCARD_SEC_CMD (1u<<22) // The command transfer will be hardware encrypted (KEY2) +#define NTRCARD_DELAY2(n) (((n)&0x3Fu)<<16) // Transfer delay length part 2 +#define NTRCARD_SEC_SEED (1u<<15) // Apply encryption (KEY2) seed to hardware registers +#define NTRCARD_SEC_EN (1u<<14) // Security enable +#define NTRCARD_SEC_DAT (1u<<13) // The data transfer will be hardware encrypted (KEY2) +#define NTRCARD_DELAY1(n) ((n)&0x1FFFu) // Transfer delay length part 1 + +// 3 bits in b10..b8 indicate something +// read bits +#define NTRCARD_BUSY (1u<<31) // when reading, still expecting incomming data? +#define NTRCARD_DATA_READY (1u<<23) // when reading, REG_NTRCARDFIFO has another word of data and is good to go + +// Card commands +#define NTRCARD_CMD_DUMMY 0x9Fu +#define NTRCARD_CMD_HEADER_READ 0x00u +#define NTRCARD_CMD_HEADER_CHIPID 0x90u +#define NTRCARD_CMD_ACTIVATE_BF 0x3Cu // Go into blowfish (KEY1) encryption mode +#define NTRCARD_CMD_ACTIVATE_BF2 0x3Du // Go into blowfish (KEY1) encryption mode +#define NTRCARD_CMD_ACTIVATE_SEC 0x40u // Go into hardware (KEY2) encryption mode +#define NTRCARD_CMD_SECURE_CHIPID 0x10u +#define NTRCARD_CMD_SECURE_READ 0x20u +#define NTRCARD_CMD_DISABLE_SEC 0x60u // Leave hardware (KEY2) encryption mode +#define NTRCARD_CMD_DATA_MODE 0xA0u +#define NTRCARD_CMD_DATA_READ 0xB7u +#define NTRCARD_CMD_DATA_CHIPID 0xB8u + +#define NTRCARD_CR1_ENABLE 0x8000u +#define NTRCARD_CR1_IRQ 0x4000u + +#define NTRKEY_PARAM 0x3F1FFFu + +void NTR_SendCommand(const u32 command[2], u32 pageSize, u32 latency, void* buffer); diff --git a/source/gamecart/secure_ntr.c b/source/gamecart/secure_ntr.c new file mode 100644 index 0000000..4fb233d --- /dev/null +++ b/source/gamecart/secure_ntr.c @@ -0,0 +1,320 @@ +/* + card_access.cpp + Copyright (C) 2010 yellow wood goblin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// modifyed by osilloscopion (2 Jul 2016) + +#include "protocol_ntr.h" +#include "secure_ntr.h" +#include "card_ntr.h" +#include "draw.h" +#include "delay.h" + +#define BSWAP32(val) ((((val >> 24) & 0xFF)) | (((val >> 16) & 0xFF) << 8) | (((val >> 8) & 0xFF) << 16) | ((val & 0xFF) << 24)) + +extern u32 ReadDataFlags; + +void NTR_CryptUp (u32* pCardHash, u32* aPtr) +{ + u32 x = aPtr[1]; + u32 y = aPtr[0]; + u32 z; + + for(int ii=0;ii<0x10;++ii) + { + z = pCardHash[ii] ^ x; + x = pCardHash[0x012 + ((z >> 24) & 0xff)]; + x = pCardHash[0x112 + ((z >> 16) & 0xff)] + x; + x = pCardHash[0x212 + ((z >> 8) & 0xff)] ^ x; + x = pCardHash[0x312 + ((z >> 0) & 0xff)] + x; + x = y ^ x; + y = z; + } + aPtr[0] = x ^ pCardHash[0x10]; + aPtr[1] = y ^ pCardHash[0x11]; +} + +void NTR_CryptDown(u32* pCardHash, u32* aPtr) +{ + u32 x = aPtr[1]; + u32 y = aPtr[0]; + u32 z; + + for(int ii=0x11;ii>0x01;--ii) + { + z = pCardHash[ii] ^ x; + x = pCardHash[0x012 + ((z >> 24) & 0xff)]; + x = pCardHash[0x112 + ((z >> 16) & 0xff)] + x; + x = pCardHash[0x212 + ((z >> 8) & 0xff)] ^ x; + x = pCardHash[0x312 + ((z >> 0) & 0xff)] + x; + x = y ^ x; + y = z; + } + aPtr[0] = x ^ pCardHash[0x01]; + aPtr[1] = y ^ pCardHash[0x00]; +} + +// chosen by fair dice roll. +// guaranteed to be random. +#define getRandomNumber() (4) + +void NTR_InitKey1 (u8* aCmdData, IKEY1* pKey1, int iCardDevice) +{ + pKey1->iii = getRandomNumber() & 0x00000fff; + pKey1->jjj = getRandomNumber() & 0x00000fff; + pKey1->kkkkk = getRandomNumber() & 0x000fffff; + pKey1->llll = getRandomNumber() & 0x0000ffff; + pKey1->mmm = getRandomNumber() & 0x00000fff; + pKey1->nnn = getRandomNumber() & 0x00000fff; + + if(iCardDevice) //DSi + aCmdData[7]=NTRCARD_CMD_ACTIVATE_BF2; //0x3D + else + aCmdData[7]=NTRCARD_CMD_ACTIVATE_BF; + + aCmdData[6] = (u8)(pKey1->iii >> 4); + aCmdData[5] = (u8)((pKey1->iii << 4) | (pKey1->jjj >> 8)); + aCmdData[4] = (u8)pKey1->jjj; + aCmdData[3] = (u8)(pKey1->kkkkk >> 16); + aCmdData[2] = (u8)(pKey1->kkkkk >> 8); + aCmdData[1] = (u8)pKey1->kkkkk; + aCmdData[0] = (u8)getRandomNumber(); +} + +void NTR_ApplyKey (u32* pCardHash, int nCardHash, u32* pKeyCode) +{ + u32 scratch[2]; + + NTR_CryptUp (pCardHash, &pKeyCode[1]); + NTR_CryptUp (pCardHash, &pKeyCode[0]); + memset(scratch, 0, sizeof (scratch)); + + for(int ii=0;ii<0x12;++ii) + { + pCardHash[ii] = pCardHash[ii] ^ BSWAP32 (pKeyCode[ii%2]); + } + + for(int ii=0;ii= 1) NTR_ApplyKey (pCardHash, nCardHash, pKeyCode); + if (level >= 2) NTR_ApplyKey (pCardHash, nCardHash, pKeyCode); + + pKeyCode[1] = pKeyCode[1]*2; + pKeyCode[2] = pKeyCode[2]/2; + + if (level >= 3) NTR_ApplyKey (pCardHash, nCardHash, pKeyCode); +} + +void NTR_CreateEncryptedCommand (u8 aCommand, u32* pCardHash, u8* aCmdData, IKEY1* pKey1, u32 aBlock) +{ + u32 iii,jjj; + if(aCommand!=NTRCARD_CMD_SECURE_READ) aBlock=pKey1->llll; + if(aCommand==NTRCARD_CMD_ACTIVATE_SEC) + { + iii=pKey1->mmm; + jjj=pKey1->nnn; + } + else + { + iii=pKey1->iii; + jjj=pKey1->jjj; + } + aCmdData[7]=(u8)(aCommand|(aBlock>>12)); + aCmdData[6]=(u8)(aBlock>>4); + aCmdData[5]=(u8)((aBlock<<4)|(iii>>8)); + aCmdData[4]=(u8)iii; + aCmdData[3]=(u8)(jjj>>4); + aCmdData[2]=(u8)((jjj<<4)|(pKey1->kkkkk>>16)); + aCmdData[1]=(u8)(pKey1->kkkkk>>8); + aCmdData[0]=(u8)pKey1->kkkkk; + + NTR_CryptUp(pCardHash, (u32*)aCmdData); + + pKey1->kkkkk+=1; +} + +void NTR_DecryptSecureArea (u32 aGameCode, u32* pCardHash, int nCardHash, u32* pKeyCode, u32* pSecureArea, int iCardDevice) +{ + NTR_InitKey (aGameCode, pCardHash, nCardHash, pKeyCode, 2, iCardDevice); + NTR_CryptDown(pCardHash, pSecureArea); + NTR_InitKey(aGameCode, pCardHash, nCardHash, pKeyCode, 3, iCardDevice); + for(int ii=0;ii<0x200;ii+=2) NTR_CryptDown (pCardHash, pSecureArea + ii); +} + + +u32 NTR_GetIDSafe (u32 flags, const u8* command, u32 Delay) +{ + u32 data = 0; + Delay = ((Delay & 0x3fff) * 1000) / 0x83; + ioDelay (Delay); + cardWriteCommand(command); + REG_NTRCARDROMCNT = flags | NTRCARD_BLK_SIZE(7); + + do + { + if (REG_NTRCARDROMCNT & NTRCARD_DATA_READY) + { + data = REG_NTRCARDFIFO; + } + } + while(REG_NTRCARDROMCNT & NTRCARD_BUSY); + + return data; +} + +void NTR_CmdSecure (u32 flags, void* buffer, u32 length, u8* pcmd, u32 Delay) +{ + Delay = ((Delay & 0x3fff) * 1000) / 0x83; + ioDelay (Delay); + cardPolledTransfer (flags, buffer, length, pcmd); +} + +bool NTR_Secure_Init (u8* header, u32 CartID, int iCardDevice) +{ + u32 iGameCode; + u32 iCardHash[0x412] = {0}; + u32 iKeyCode[3] = {0}; + u32* secureArea=(u32*)(header + 0x4000); + u8 cmdData[8] __attribute__((aligned(32))); + const u8 cardSeedBytes[]={0xE8,0x4D,0x5A,0xB1,0x17,0x8F,0x99,0xD5}; + IKEY1 iKey1 ={0}; + bool iCheapCard = (CartID & 0x80000000) != 0; + u32 cardControl13 = *((u32*)&header[0x60]); + u32 cardControlBF = *((u32*)&header[0x64]); + u16 readTimeout = *((u16*)&header[0x6E]); readTimeout*=8; + u8 deviceType = header[0x13]; + int nCardHash = sizeof (iCardHash) / sizeof (iCardHash[0]); + u32 flagsKey1=NTRCARD_ACTIVATE|NTRCARD_nRESET|(cardControl13&(NTRCARD_WR|NTRCARD_CLK_SLOW))|((cardControlBF&(NTRCARD_CLK_SLOW|NTRCARD_DELAY1(0x1FFF)))+((cardControlBF&NTRCARD_DELAY2(0x3F))>>16)); + u32 flagsSec=(cardControlBF&(NTRCARD_CLK_SLOW|NTRCARD_DELAY1(0x1FFF)|NTRCARD_DELAY2(0x3F)))|NTRCARD_ACTIVATE|NTRCARD_nRESET|NTRCARD_SEC_EN|NTRCARD_SEC_DAT; + + iGameCode = *((u32*)&header[0x0C]); + ReadDataFlags = cardControl13 & ~ NTRCARD_BLK_SIZE(7); + NTR_InitKey (iGameCode, iCardHash, nCardHash, iKeyCode, iCardDevice?1:2, iCardDevice); + + if(!iCheapCard) flagsKey1 |= NTRCARD_SEC_LARGE; + //Debug("iCheapCard=%d, readTimeout=%d", iCheapCard, readTimeout); + + NTR_InitKey1 (cmdData, &iKey1, iCardDevice); + //Debug("cmdData=%02X %02X %02X %02X %02X %02X %02X %02X ", cmdData[0], cmdData[1], cmdData[2], cmdData[3], cmdData[4], cmdData[5], cmdData[6], cmdData[7]); + //Debug("iKey1=%08X %08X %08X", iKey1.iii, iKey1. jjj, iKey1. kkkkk); + //Debug("iKey1=%08X %08X %08X", iKey1. llll, iKey1. mmm, iKey1. nnn); + + NTR_CmdSecure ((cardControl13 & (NTRCARD_WR | NTRCARD_nRESET | NTRCARD_CLK_SLOW)) | NTRCARD_ACTIVATE, NULL, 0, cmdData, 0); + + NTR_CreateEncryptedCommand (NTRCARD_CMD_ACTIVATE_SEC, iCardHash, cmdData, &iKey1, 0); + //Debug("cmdData=%02X %02X %02X %02X %02X %02X %02X %02X ", cmdData[0], cmdData[1], cmdData[2], cmdData[3], cmdData[4], cmdData[5], cmdData[6], cmdData[7]); + if(iCheapCard) + { + NTR_CmdSecure (flagsKey1, NULL, 0, cmdData, 0); + } + NTR_CmdSecure (flagsKey1, NULL, 0, cmdData, readTimeout); + + REG_NTRCARDROMCNT = 0; + REG_NTRCARDSEEDX_L = cardSeedBytes[deviceType & 0x07] | (iKey1.nnn << 15) | (iKey1.mmm << 27) | 0x6000; + REG_NTRCARDSEEDY_L = 0x879b9b05; + REG_NTRCARDSEEDX_H = iKey1.mmm >> 5; + REG_NTRCARDSEEDY_H = 0x5c; + REG_NTRCARDROMCNT = NTRCARD_nRESET | NTRCARD_SEC_SEED | NTRCARD_SEC_EN | NTRCARD_SEC_DAT; + + flagsKey1 |= NTRCARD_SEC_EN | NTRCARD_SEC_DAT; + NTR_CreateEncryptedCommand(NTRCARD_CMD_SECURE_CHIPID, iCardHash, cmdData, &iKey1, 0); + //Debug("cmdData=%02X %02X %02X %02X %02X %02X %02X %02X ", cmdData[0], cmdData[1], cmdData[2], cmdData[3], cmdData[4], cmdData[5], cmdData[6], cmdData[7]); + u32 SecureCartID = 0; + if(iCheapCard) + { + NTR_CmdSecure (flagsKey1, NULL, 0, cmdData, 0); + } + + //NTR_CmdSecure (flagsKey1, &SecureCartID, sizeof (SecureCartID), cmdData, readTimeout); + SecureCartID = NTR_GetIDSafe (flagsKey1, cmdData, readTimeout); + + if (SecureCartID != CartID) + { + Debug ("Invalid SecureCartID. (%08X != %08X)", SecureCartID, CartID); + return false; + } + + int secureAreaOffset = 0; + for(int secureBlockNumber=4;secureBlockNumber<8;++secureBlockNumber) + { + NTR_CreateEncryptedCommand (NTRCARD_CMD_SECURE_READ, iCardHash, cmdData, &iKey1, secureBlockNumber); + if (iCheapCard) + { + NTR_CmdSecure (flagsSec, NULL, 0, cmdData, 0); + for(int ii=8;ii>0;--ii) + { + NTR_CmdSecure (flagsSec | NTRCARD_BLK_SIZE(1), secureArea + secureAreaOffset, 0x200, cmdData, readTimeout); + secureAreaOffset += 0x200 / sizeof (u32); + } + } + else + { + NTR_CmdSecure (flagsSec | NTRCARD_BLK_SIZE(4) | NTRCARD_SEC_LARGE, secureArea + secureAreaOffset, 0x1000, cmdData, readTimeout); + secureAreaOffset += 0x1000 / sizeof (u32); + } + } + + NTR_CreateEncryptedCommand (NTRCARD_CMD_DATA_MODE, iCardHash, cmdData, &iKey1, 0); + if(iCheapCard) + { + NTR_CmdSecure (flagsKey1, NULL, 0, cmdData, 0); + } + NTR_CmdSecure (flagsKey1, NULL, 0, cmdData, readTimeout); + + if(!iCardDevice) //CycloDS doesn't like the dsi secure area being decrypted + { + NTR_DecryptSecureArea (iGameCode, iCardHash, nCardHash, iKeyCode, secureArea, iCardDevice); + } + + //Debug("secure area %08X %08X", secureArea[0], secureArea[1]); + if(secureArea[0] == 0x72636e65/*'encr'*/ && secureArea[1] == 0x6a624f79/*'yObj'*/) + { + secureArea[0] = 0xe7ffdeff; + secureArea[1] = 0xe7ffdeff; + } + else + { + Debug("Invalid secure area.(%08X %08X)", secureArea[0], secureArea[1]); + //dragon quest 5 has invalid secure area. really. + //return false; + } + + return true; +} diff --git a/source/gamecart/secure_ntr.h b/source/gamecart/secure_ntr.h new file mode 100644 index 0000000..7ef5779 --- /dev/null +++ b/source/gamecart/secure_ntr.h @@ -0,0 +1,19 @@ +#pragma once + +#include "common.h" + + +typedef struct _IKEY1{ + u32 iii; + u32 jjj; + u32 kkkkk; + u32 llll; + u32 mmm; + u32 nnn; +} IKEY1, *PIKEY1; + +void NTR_InitKey (u32 aGameCode, u32* pCardHash, int nCardHash, u32* pKeyCode, int level, int iCardDevice); +void NTR_InitKey1 (u8* aCmdData, IKEY1* pKey1, int iCardDevice); + +void NTR_CreateEncryptedCommand (u8 aCommand, u32* pCardHash, u8* aCmdData, IKEY1* pKey1, u32 aBlock); +void NTR_DecryptSecureArea (u32 aGameCode, u32* pCardHash, int nCardHash, u32* pKeyCode, u32* pSecureArea, int iCardDevice); diff --git a/source/main.c b/source/main.c index 90dc372..0ac7830 100644 --- a/source/main.c +++ b/source/main.c @@ -7,7 +7,8 @@ #include "i2c.h" #include "decryptor/keys.h" #include "decryptor/nand.h" -#include "decryptor/injector.h" +#include "decryptor/nandfat.h" +#include "decryptor/game.h" #include "bottomlogo_bgr.h" #define SUBMENU_START 1 @@ -16,13 +17,14 @@ MenuInfo menu[] = { { #ifndef VERSION_NAME - "Hourglass9 Main Menu", 3, + "Hourglass9 Main Menu", 4, #else - VERSION_NAME, 3, + VERSION_NAME, 4, #endif { { "SysNAND Backup/Restore...", NULL, SUBMENU_START + 0 }, { "EmuNAND Backup/Restore...", NULL, SUBMENU_START + 1 }, + { "Gamecart Dumper...", NULL, SUBMENU_START + 2 }, { "Validate NAND Dump", &ValidateNandDump, 0 } } }, @@ -44,6 +46,17 @@ MenuInfo menu[] = { "Health&Safety Inject", &InjectHealthAndSafety, N_NANDWRITE | N_EMUNAND } } }, + { + "Gamecart Dumper Options", 6, // ID 2 + { + { "Dump Cart (full)", &DumpGameCart, 0 }, + { "Dump Cart (trim)", &DumpGameCart, CD_TRIM }, + { "Dump & Decrypt Cart (full)", &DumpGameCart, CD_DECRYPT }, + { "Dump & Decrypt Cart (trim)", &DumpGameCart, CD_DECRYPT | CD_TRIM }, + { "Dump Cart to CIA", &DumpGameCart, CD_DECRYPT | CD_MAKECIA }, + { "Dump Private Header", &DumpPrivateHeader, 0 } + } + }, { NULL, 0, { { 0 } } // empty menu to signal end }