diff --git a/source/common/common.h b/source/common/common.h index 79e805b..77d18aa 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -50,7 +50,7 @@ // GodMode9 version -#define VERSION "1.0.9" +#define VERSION "1.1.0" // Maximum payload size (arbitrary value!) #define SELF_MAX_SIZE (320 * 1024) // 320kB diff --git a/source/crypto/keydb.h b/source/crypto/keydb.h index 6bf27c0..b5776b2 100644 --- a/source/crypto/keydb.h +++ b/source/crypto/keydb.h @@ -19,4 +19,5 @@ typedef struct { } __attribute__((packed)) AesKeyInfo; u32 GetUnitKeysType(void); +void CryptAesKeyInfo(AesKeyInfo* info); u32 LoadKeyFromFile(u8* key, u32 keyslot, char type, char* id); diff --git a/source/fs/filetype.h b/source/fs/filetype.h index e5a49bb..cac5c2c 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -32,11 +32,12 @@ #define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM|SYS_TICKDB)) #define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_BOSS|SYS_FIRM)) -#define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM)) -#define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS)) +#define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM|BIN_KEYDB)) +#define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB)) #define FTYPE_CIABUILD(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) #define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD))) #define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) +#define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY)) #define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS)) #define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR)) #define FTYPE_HSINJECTABLE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) diff --git a/source/game/keydbutil.c b/source/game/keydbutil.c new file mode 100644 index 0000000..448b334 --- /dev/null +++ b/source/game/keydbutil.c @@ -0,0 +1,119 @@ +#include "keydbutil.h" +#include "fsperm.h" +#include "filetype.h" +#include "unittype.h" +#include "vff.h" +#include "ui.h" + +#define MAX_KEYDB_SIZE (TEMP_BUFFER_SIZE) + +u32 CryptAesKeyDb(const char* path, bool inplace, bool encrypt) { + AesKeyInfo* keydb = (AesKeyInfo*) MAIN_BUFFER; + const char* path_out = (inplace) ? path : OUTPUT_PATH "/" KEYDB_NAME; + u32 n_keys; + UINT bt, btw; + + // write permissions + if (!CheckWritePermissions(path_out)) + return 1; + + // load key database + if ((fvx_qread(path, keydb, 0, MAIN_BUFFER_SIZE, &bt) != FR_OK) || + (bt % sizeof(AesKeyInfo)) || (bt >= MAIN_BUFFER_SIZE)) + return 1; + + // en-/decrypt keys + n_keys = bt / sizeof(AesKeyInfo); + for (u32 i = 0; i < n_keys; i++) { + if ((bool) keydb[i].isEncrypted == !encrypt) + CryptAesKeyInfo(&(keydb[i])); + } + + // dump key database + if (!inplace) f_unlink(path_out); + if ((fvx_qwrite(path_out, keydb, 0, bt, &btw) != FR_OK) || (bt != btw)) + return 1; + + return 0; +} + +u32 AddKeyToDb(AesKeyInfo* key_info, AesKeyInfo* key_entry) { + AesKeyInfo* key = key_info; + if (key_entry) { // key entry provided + for (; key->slot < 0x40; key++) { + if ((u8*) key - (u8*) key_info >= MAX_KEYDB_SIZE) return 1; + if ((key_entry->slot == key->slot) && (key_entry->type == key->type) && + (strncasecmp(key_entry->id, key->id, 10) == 0) && + ((bool) key_entry->isDevkitKey == (bool) key->isDevkitKey)) + return 0; // key already in db + } + memcpy(key++, key_entry, sizeof(AesKeyInfo)); + } + if ((u8*) key - (u8*) key_info >= MAX_KEYDB_SIZE) return 1; + memset(key, 0xFF, sizeof(AesKeyInfo)); // this used to signal keydb end + return 0; +} + +u32 BuildKeyDb(const char* path, bool dump) { + AesKeyInfo* key_info = (AesKeyInfo*) MAIN_BUFFER; + const char* path_out = OUTPUT_PATH "/" KEYDB_NAME; + const char* path_in = path; + UINT br; + + // write permissions + if (!CheckWritePermissions(path_out)) + return 1; + + if (!path_in && !dump) { // no input path given - initialize + AddKeyToDb(key_info, NULL); + if ((fvx_stat(path_out, NULL) == FR_OK) && + (ShowPrompt(true, "%s\nOutput file already exists.\nUpdate this?", path_out))) + path_in = path_out; + else return 0; + } + + u32 filetype = path_in ? IdentifyFileType(path_in) : 0; + if (filetype & BIN_KEYDB) { // AES key database + AesKeyInfo* key_info_merge = (AesKeyInfo*) TEMP_BUFFER; + if ((fvx_qread(path_in, key_info_merge, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) || + (br % sizeof(AesKeyInfo)) || (br >= MAIN_BUFFER_SIZE)) return 1; + u32 n_keys = br / sizeof(AesKeyInfo); + for (u32 i = 0; i < n_keys; i++) { + if (key_info_merge[i].isEncrypted) // build an unencrypted db + CryptAesKeyInfo(&(key_info_merge[i])); + if (AddKeyToDb(key_info, key_info_merge + i) != 0) return 1; + } + } else if (filetype & BIN_LEGKEY) { // legacy key file + AesKeyInfo key; + unsigned int keyslot = 0xFF; + char typestr[16] = { 0 }; + char* name_in = strrchr(path_in, '/'); + memset(&key, 0, sizeof(AesKeyInfo)); + key.type = 'N'; + if (!name_in || (strnlen(++name_in, 32) > 24)) return 1; // safety + if ((sscanf(name_in, "slot0x%02XKey%s", &keyslot, typestr) != 2) && + (sscanf(name_in, "slot0x%02Xkey%s", &keyslot, typestr) != 2)) return 1; + char* dot = strrchr(typestr, '.'); + if (!dot) return 1; + *dot = '\0'; + if ((typestr[1] == '\0') && ((*typestr == 'X') || (*typestr == 'Y'))) key.type = *typestr; + else strncpy(key.id, typestr, 10); + key.slot = keyslot; + key.isDevkitKey = (IS_DEVKIT) ? 1 : 0; // just assume it's a devkit key on devkit + if ((fvx_qread(path_in, key.key, 0, 16, &br) != FR_OK) || (br != 16)) return 1; + if (AddKeyToDb(key_info, &key) != 0) return 1; + } + + if (dump) { + u32 dump_size = 0; + for (AesKeyInfo* key = key_info; key->slot <= 0x40; key++) { + dump_size += sizeof(AesKeyInfo); + if (dump_size >= MAX_KEYDB_SIZE) return 1; + } + f_unlink(path_out); + if (!dump_size || (fvx_qwrite(path_out, key_info, 0, dump_size, &br) != FR_OK) || (br != dump_size)) + return 1; + } + + return 0; +} diff --git a/source/game/keydbutil.h b/source/game/keydbutil.h new file mode 100644 index 0000000..2efdc6e --- /dev/null +++ b/source/game/keydbutil.h @@ -0,0 +1,8 @@ +#pragma once + +#include "common.h" +#include "keydb.h" + +u32 CryptAesKeyDb(const char* path, bool inplace, bool encrypt); +u32 AddKeyToDb(AesKeyInfo* key_info, AesKeyInfo* key_entry); +u32 BuildKeyDb(const char* path, bool dump); diff --git a/source/godmode.c b/source/godmode.c index 1efaac4..5aeb3f2 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -6,6 +6,7 @@ #include "fsutil.h" #include "fsperm.h" #include "gameutil.h" +#include "keydbutil.h" #include "nandutil.h" #include "filetype.h" #include "unittype.h" @@ -655,6 +656,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool cia_buildable = (FTYPE_CIABUILD(filetype)); bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype)); bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; + bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path; bool titleinfo = (FTYPE_TITLEINFO(filetype)); bool transferable = (FTYPE_TRANSFERABLE(filetype) && IS_A9LH && (drvtype & DRV_FAT)); bool hsinjectable = (FTYPE_HSINJECTABLE(filetype)); @@ -663,7 +665,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool xorpadable = (FTYPE_XORPAD(filetype)); bool launchable = ((FTYPE_PAYLOAD(filetype)) && (drvtype & DRV_FAT)); bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || - tik_buildable || titleinfo || hsinjectable || restorable || xorpadable || launchable || ebackupable; + tik_buildable || key_buildable || titleinfo || hsinjectable || restorable || xorpadable || launchable || ebackupable; char pathstr[32+1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -704,6 +706,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (filetype & SYS_FIRM ) ? "FIRM image options..." : (filetype & SYS_TICKDB) ? (tik_buildable) ? "Ticket.db options..." : "Mount as ticket.db" : (filetype & BIN_TIKDB) ? "Titlekey options..." : + (filetype & BIN_KEYDB) ? "AESkeydb options..." : + (filetype & BIN_LEGKEY) ? "Build " KEYDB_NAME : (filetype & BIN_NCCHNFO)? "NCCHinfo options..." : (filetype & BIN_LAUNCH) ? "Launch as arm9 payload" : "???"; optionstr[hexviewer-1] = "Show in Hexeditor"; @@ -803,6 +807,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1; int tik_build_enc = (tik_buildable) ? ++n_opt : -1; int tik_build_dec = (tik_buildable) ? ++n_opt : -1; + int key_build = (key_buildable) ? ++n_opt : -1; int verify = (verificable) ? ++n_opt : -1; int ctrtransfer = (transferable) ? ++n_opt : -1; int hsinject = (hsinjectable) ? ++n_opt : -1; @@ -819,6 +824,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)"; if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC; if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC; + if (key_build > 0) optionstr[key_build-1] = "Build " KEYDB_NAME; if (verify > 0) optionstr[verify-1] = "Verify file"; if (ctrtransfer > 0) optionstr[ctrtransfer-1] = "Transfer image to CTRNAND"; if (hsinject > 0) optionstr[hsinject-1] = "Inject to H&S"; @@ -873,12 +879,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur n_other++; continue; } - if (CheckEncryptedGameFile(path) != 0) { + if (!(filetype & BIN_KEYDB) && (CheckEncryptedGameFile(path) != 0)) { n_unencrypted++; continue; } current_dir->entry[i].marked = false; - if (CryptGameFile(path, inplace, false) == 0) n_success++; + if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, false) == 0)) n_success++; + else if ((filetype & BIN_KEYDB) && (CryptAesKeyDb(path, inplace, false) == 0)) n_success++; else { // on failure: set cursor on failed title, break; *cursor = i; break; @@ -890,10 +897,11 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } else ShowPrompt(false, "%lu/%lu files decrypted ok", n_success, n_marked); if (!inplace && n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH); } else { - if (CheckEncryptedGameFile(curr_entry->path) != 0) { + if (!(filetype & BIN_KEYDB) && (CheckEncryptedGameFile(curr_entry->path) != 0)) { ShowPrompt(false, "%s\nFile is not encrypted", pathstr); } else { - u32 ret = CryptGameFile(curr_entry->path, inplace, false); + u32 ret = (filetype & BIN_KEYDB) ? CryptAesKeyDb(curr_entry->path, inplace, false) : + CryptGameFile(curr_entry->path, inplace, false); if (inplace || (ret != 0)) ShowPrompt(false, "%s\nDecryption %s", pathstr, (ret == 0) ? "success" : "failed"); else ShowPrompt(false, "%s\nDecrypted to %s", pathstr, OUTPUT_PATH); } @@ -921,7 +929,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur continue; } current_dir->entry[i].marked = false; - if (CryptGameFile(path, inplace, true) == 0) n_success++; + if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, true) == 0)) n_success++; + else if ((filetype & BIN_KEYDB) && (CryptAesKeyDb(path, inplace, true) == 0)) n_success++; else { // on failure: set cursor on failed title, break; *cursor = i; break; @@ -933,7 +942,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } else ShowPrompt(false, "%lu/%lu files encrypted ok", n_success, n_marked); if (!inplace && n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH); } else { - u32 ret = CryptGameFile(curr_entry->path, inplace, true); + u32 ret = (filetype & BIN_KEYDB) ? CryptAesKeyDb(curr_entry->path, inplace, true) : + CryptGameFile(curr_entry->path, inplace, true); if (inplace || (ret != 0)) ShowPrompt(false, "%s\nEncryption %s", pathstr, (ret == 0) ? "success" : "failed"); else ShowPrompt(false, "%s\nEncrypted to %s", pathstr, OUTPUT_PATH); } @@ -1029,10 +1039,36 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored", path_out, n_success, n_marked, n_other, n_marked); else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked); - } else ShowPrompt(false, "%s\nBuild database failed."); + } else ShowPrompt(false, "%s\nBuild database failed.", path_out); } else ShowPrompt(false, "%s\nBuild database %s.", path_out, (BuildTitleKeyInfo(curr_entry->path, dec, true) == 0) ? "success" : "failed"); return 0; + } else if (user_select == key_build) { // -> (Re)Build AES key database + const char* path_out = OUTPUT_PATH "/" KEYDB_NAME; + if (BuildKeyDb(NULL, false) != 0) return 1; // init database + ShowString("Building %s...", KEYDB_NAME); + if (n_marked > 1) { + u32 n_success = 0; + u32 n_other = 0; + for (u32 i = 0; i < current_dir->n_entries; i++) { + const char* path = current_dir->entry[i].path; + if (!current_dir->entry[i].marked) + continue; + if (!FTYPE_KEYBUILD(IdentifyFileType(path))) { + n_other++; + continue; + } + current_dir->entry[i].marked = false; + if (BuildKeyDb(path, false) == 0) n_success++; // ignore failures for now + } + if (BuildKeyDb(NULL, true) == 0) { + if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored", + path_out, n_success, n_marked, n_other, n_marked); + else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked); + } else ShowPrompt(false, "%s\nBuild database failed.", path_out); + } else ShowPrompt(false, "%s\nBuild database %s.", path_out, + (BuildKeyDb(curr_entry->path, true) == 0) ? "success" : "failed"); + return 0; } else if (user_select == show_info) { // -> Show title info if (ShowGameFileTitleInfo(curr_entry->path) != 0) ShowPrompt(false, "Title info: not found");