From 34ca0fc3a7dfc8b1653133eb85aa08f9631c65a4 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Mon, 20 Oct 2025 16:53:42 +0200 Subject: [PATCH] Enable restore of original compression for NCSD & NCCH --- arm9/source/filesys/filetype.h | 1 + arm9/source/game/ncch.c | 46 ++++++++++++++++++++++++++++++++- arm9/source/game/ncch.h | 3 ++- arm9/source/godmode.c | 19 +++++++++----- arm9/source/lua/gm9title.c | 4 +-- arm9/source/utils/gameutil.c | 6 ++++- arm9/source/utils/gameutil.h | 2 +- arm9/source/utils/scripting.c | 4 +-- resources/languages/source.json | 5 +++- 9 files changed, 75 insertions(+), 15 deletions(-) diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index bef05b5..ea9007e 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -54,6 +54,7 @@ #define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_CDNTMD|GAME_TWLTMD|GAME_TIE|GAME_TAD|GAME_TICKET|GAME_BOSS|SYS_FIRM)) #define FTYPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM|BIN_KEYDB)) #define FTYPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB)) +#define FTYPE_CRYPTOFIXABLE(tp) (tp&(GAME_NCSD|GAME_NCCH)) #define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_CDNTMD|GAME_TWLTMD|GAME_TIE|GAME_TAD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) #define FTYPE_CIABUILD_L(tp) (tp&(GAME_TMD|GAME_CDNTMD|GAME_TIE|GAME_TAD)) #define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) diff --git a/arm9/source/game/ncch.c b/arm9/source/game/ncch.c index 112ed1e..a92b09b 100644 --- a/arm9/source/game/ncch.c +++ b/arm9/source/game/ncch.c @@ -228,14 +228,58 @@ u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e return 0; } +u32 BruteForceNcchCrypto(void* data, u16* crypto) { + // data must contain NCCH header and (if available) ExtHeader (0xA00 byte total) + NcchHeader ncch; + NcchExtHeader exthdr; + + memcpy(&ncch, data, sizeof(NcchHeader)); + if (ValidateNcchHeader(&ncch)) + return 1; // not an NCCH header + + if (ncch.size_exthdr) { + memcpy(&exthdr, ((u8*)data + NCCH_EXTHDR_OFFSET), sizeof(NcchExtHeader)); + if ((NCCH_ENCRYPTED(&ncch)) && + (DecryptNcch((u8*) &exthdr, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), &ncch, NULL) != 0)) + return 1; // could not decrypt the extHeader + } + + // the brute forcing part.. + for (u32 i = 0x00; i < 0x20; i++) { + const u8 flag3_settings[4] = { 0x00, 0x01, 0x0A, 0x0B }; + const u8 flag7_mask = 0b11011010; + ncch.flags[3] = flag3_settings[i & 0b11]; + ncch.flags[7] = + (ncch.flags[7] & flag7_mask) | + (((i>>2)&0b1)<<5) | // seed crypto flag + (((i>>3)&0b1)<<0) | // fixed crypto flag + (((i>>4)&0b1)<<2); // no crypto flag + if (ValidateNcchSignature(&ncch, (ncch.size_exthdr) ? &exthdr : NULL) == 0) { + *crypto = NCCH_GET_CRYPTO(&ncch); + return 0; + } + } + + // if we get here, we didn't find a the crypto flags + return 1; +} + // on the fly de- / encryptor for NCCH - sequential -u32 CryptNcchSequential(void* data, u32 offset, u32 size, u16 crypt_to) { +u32 CryptNcchSequential(void* data, u32 offset, u32 size, u16 crypto) { // warning: this will only work for sequential processing // unexpected results otherwise static NcchHeader ncch = { 0 }; static ExeFsHeader exefs = { 0 }; static NcchHeader* ncchptr = NULL; static ExeFsHeader* exefsptr = NULL; + static u16 crypt_to = NCCH_NOCRYPTO; + + // brute force original crypto + if (crypto == NCCH_BFCRYPTO) { + if ((offset == 0) && + ((size < 0xA00) || (BruteForceNcchCrypto(data, &crypt_to) != 0))) + return 1; + } else crypt_to = crypto; // fetch ncch header from data if ((offset == 0) && (size >= sizeof(NcchHeader))) { diff --git a/arm9/source/game/ncch.h b/arm9/source/game/ncch.h index 54e69dc..390b162 100644 --- a/arm9/source/game/ncch.h +++ b/arm9/source/game/ncch.h @@ -16,6 +16,7 @@ #define NCCH_NOCRYPTO 0x0004 #define NCCH_STDCRYPTO 0x0000 +#define NCCH_BFCRYPTO 0xFFFF #define NCCH_GET_CRYPTO(ncch) (!NCCH_ENCRYPTED(ncch) ? NCCH_NOCRYPTO : (((ncch)->flags[3] << 8) | ((ncch)->flags[7]&(0x01|0x20)))) // wrapper defines @@ -87,7 +88,7 @@ u32 ValidateNcchHeader(NcchHeader* header); u32 ValidateNcchSignature(NcchHeader* header, NcchExtHeader* exthdr); u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid); u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to); -u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypto); +u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypt_to); u32 CryptNcchSequential(void* data, u32 offset, u32 size, u16 crypto); u32 SetNcchSdFlag(void* data); u32 SetupSystemForNcch(NcchHeader* ncch, bool to_emunand); diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 895486e..10ccc35 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -1213,6 +1213,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool verificable = (FTYPE_VERIFICABLE(filetype)); bool decryptable = (FTYPE_DECRYPTABLE(filetype)); bool encryptable = (FTYPE_ENCRYPTABLE(filetype)); + bool crypto_fixable = (FTYPE_CRYPTOFIXABLE(filetype)); bool cryptable_inplace = ((encryptable||decryptable) && !in_output_path && (*current_path == '0')); bool cia_buildable = (FTYPE_CIABUILD(filetype)); bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype)); @@ -1626,7 +1627,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan continue; } DrawDirContents(current_dir, (*cursor = i), scroll); - if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, false) == 0)) n_success++; + if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, false, false) == 0)) n_success++; else if ((filetype & BIN_KEYDB) && (CryptAesKeyDb(path, inplace, false) == 0)) n_success++; else { // on failure: show error, continue char lpathstr[UTF_BUFFER_BYTESIZE(32)]; @@ -1646,7 +1647,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan ShowPrompt(false, "%s\n%s", pathstr, STR_FILE_NOT_ENCRYPTED); } else { u32 ret = (filetype & BIN_KEYDB) ? CryptAesKeyDb(file_path, inplace, false) : - CryptGameFile(file_path, inplace, false); + CryptGameFile(file_path, inplace, false, false); if (inplace || (ret != 0)) ShowPrompt(false, "%s\n%s", pathstr, (ret == 0) ? STR_DECRYPTION_SUCCESS : STR_DECRYPTION_FAILED); else ShowPrompt(false, STR_PATH_DECRYPTED_TO_OUT, pathstr, OUTPUT_PATH); } @@ -1654,7 +1655,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan return 0; } else if (user_select == encrypt) { // -> encrypt game file - if (cryptable_inplace) { + if (crypto_fixable) { + optionstr[0] = STR_STANDARD_CRYPTO; + optionstr[1] = STR_ORIGINAL_CRYPTO; + user_select = (int) ShowSelectPrompt(2, optionstr, "%s", STR_SELECT_TYPE_OF_ENCRYPTION); + } else (user_select = 1); + bool restore = (user_select == 2); + if (user_select && cryptable_inplace) { char encryptToOut[UTF_BUFFER_BYTESIZE(64)]; snprintf(encryptToOut, sizeof(encryptToOut), STR_ENCRYPT_TO_OUT, OUTPUT_PATH); optionstr[0] = encryptToOut; @@ -1662,7 +1669,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan user_select = (int) ((n_marked > 1) ? ShowSelectPrompt(2, optionstr, STR_PATH_N_FILES_SELECTED, pathstr, n_marked) : ShowSelectPrompt(2, optionstr, "%s%s", pathstr, tidstr)); - } else user_select = 1; + } else if (user_select) user_select = 1; bool inplace = (user_select == 2); if (!user_select) { // do nothing when no choice is made } else if ((n_marked > 1) && ShowPrompt(true, STR_TRY_TO_ENCRYPT_N_SELECTED_FILES, n_marked)) { @@ -1678,7 +1685,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan continue; } DrawDirContents(current_dir, (*cursor = i), scroll); - if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, true) == 0)) n_success++; + if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, true, restore) == 0)) n_success++; else if ((filetype & BIN_KEYDB) && (CryptAesKeyDb(path, inplace, true) == 0)) n_success++; else { // on failure: show error, continue char lpathstr[UTF_BUFFER_BYTESIZE(32)]; @@ -1695,7 +1702,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (!inplace && n_success) ShowPrompt(false, STR_N_FILES_WRITTEN_TO_OUT, n_success, OUTPUT_PATH); } else { u32 ret = (filetype & BIN_KEYDB) ? CryptAesKeyDb(file_path, inplace, true) : - CryptGameFile(file_path, inplace, true); + CryptGameFile(file_path, inplace, true, restore); if (inplace || (ret != 0)) ShowPrompt(false, "%s\n%s", pathstr, (ret == 0) ? STR_ENCRYPTION_SUCCESS : STR_ENCRYPTION_FAILED); else ShowPrompt(false, STR_PATH_ENCRYPTED_TO_OUT, pathstr, OUTPUT_PATH); } diff --git a/arm9/source/lua/gm9title.c b/arm9/source/lua/gm9title.c index 66ac074..6394130 100644 --- a/arm9/source/lua/gm9title.c +++ b/arm9/source/lua/gm9title.c @@ -19,7 +19,7 @@ static int title_decrypt(lua_State* L) { ret = (CryptAesKeyDb(path, true, false) == 0); whichfailed = "CryptAesKeyDb"; } else { - ret = (CryptGameFile(path, true, false) == 0); + ret = (CryptGameFile(path, true, false, false) == 0); whichfailed = "CryptGameFile"; } @@ -41,7 +41,7 @@ static int title_encrypt(lua_State* L) { ret = (CryptAesKeyDb(path, true, true) == 0); whichfailed = "CryptAesKeyDb"; } else { - ret = (CryptGameFile(path, true, true) == 0); + ret = (CryptGameFile(path, true, true, false) == 0); whichfailed = "CryptGameFile"; } diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index abce190..325d38b 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -15,6 +15,7 @@ // use NCCH crypto defines for everything #define CRYPTO_DECRYPT NCCH_NOCRYPTO #define CRYPTO_ENCRYPT NCCH_STDCRYPTO +#define CRYPTO_RESTORE NCCH_BFCRYPTO // partitionA path #define PART_PATH "D:/partitionA.bin" @@ -1462,13 +1463,16 @@ u32 CryptCdnFile(const char* orig, const char* dest, u16 crypto) { return ret; } -u32 CryptGameFile(const char* path, bool inplace, bool encrypt) { +u32 CryptGameFile(const char* path, bool inplace, bool encrypt, bool restore) { u64 filetype = IdentifyFileType(path); u16 crypto = encrypt ? CRYPTO_ENCRYPT : CRYPTO_DECRYPT; char dest[256]; char* destptr = (char*) path; u32 ret = 0; + if (restore && (filetype & (GAME_NCCH|GAME_NCSD))) + crypto = CRYPTO_RESTORE; + if (!inplace) { // build output name // build output name snprintf(dest, sizeof(dest), OUTPUT_PATH "/"); diff --git a/arm9/source/utils/gameutil.h b/arm9/source/utils/gameutil.h index 97e7547..05e3689 100644 --- a/arm9/source/utils/gameutil.h +++ b/arm9/source/utils/gameutil.h @@ -4,7 +4,7 @@ u32 VerifyGameFile(const char* path, bool sig_check); u32 CheckEncryptedGameFile(const char* path); -u32 CryptGameFile(const char* path, bool inplace, bool encrypt); +u32 CryptGameFile(const char* path, bool inplace, bool encrypt, bool restore); u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 InstallGameFile(const char* path, bool to_emunand); u32 InstallCifinishFile(const char* path, bool to_emunand); diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index 6016a34..461496c 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -1352,13 +1352,13 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { else if (id == CMD_ID_DECRYPT) { u64 filetype = IdentifyFileType(argv[0]); if (filetype & BIN_KEYDB) ret = (CryptAesKeyDb(argv[0], true, false) == 0); - else ret = (CryptGameFile(argv[0], true, false) == 0); + else ret = (CryptGameFile(argv[0], true, false, false) == 0); if (err_str) snprintf(err_str, _ERR_STR_LEN, "%s", STR_SCRIPTERR_DECRYPT_FAILED); } else if (id == CMD_ID_ENCRYPT) { u64 filetype = IdentifyFileType(argv[0]); if (filetype & BIN_KEYDB) ret = (CryptAesKeyDb(argv[0], true, true) == 0); - else ret = (CryptGameFile(argv[0], true, true) == 0); + else ret = (CryptGameFile(argv[0], true, true, false) == 0); if (err_str) snprintf(err_str, _ERR_STR_LEN, "%s", STR_SCRIPTERR_ENCRYPT_FAILED); } else if (id == CMD_ID_BUILDCIA) { diff --git a/resources/languages/source.json b/resources/languages/source.json index fcd9054..faeed2a 100644 --- a/resources/languages/source.json +++ b/resources/languages/source.json @@ -816,5 +816,8 @@ "ERROR_SIGNATURE_CHECK_FAILED": "Error: Signature check failed", "USE_SIGNATURE_VERIFICATION": "Use signature verification?", "IGNORE_SIGNATURES": "Ignore signatures", - "VERIFY_SIGNATURES": "Verify signatures" + "VERIFY_SIGNATURES": "Verify signatures", + "STANDARD_CRYPTO": "Standard encryption", + "ORIGINAL_CRYPTO": "Original encryption", + "SELECT_TYPE_OF_ENCRYPTION": "Select type of encryption" }