diff --git a/source/game/firm.c b/source/game/firm.c index b2f2e65..29a86be 100644 --- a/source/game/firm.c +++ b/source/game/firm.c @@ -264,3 +264,24 @@ u32 DecryptFirmSequential(void* data, u32 offset, u32 size) { return (a9lptr) ? DecryptFirm(data, offset, size, firmptr, a9lptr) : 0; } + +u32 DecryptFirmFull(void* data, u32 size) { + // this expects the full FIRM being in memory + FirmHeader* firm = (FirmHeader*) data; + FirmSectionHeader* arm9s = FindFirmArm9Section(firm); + if (ValidateFirmHeader(firm, size) != 0) return 1; // not a proper firm + if (!arm9s) return 0; // no ARM9 section -> not encrypted -> done + + FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) data + arm9s->offset); + if (ValidateFirmA9LHeader(a9l) != 0) return 0; // no ARM9bin -> not encrypted -> done + + // decrypt FIRM and ARM9loader header + if ((DecryptFirm(data, 0, size, firm, a9l) != 0) || (DecryptA9LHeader(a9l) != 0)) + return 1; + + // fix ARM9 section SHA and ARM9 entrypoint + sha_quick(arm9s->hash, (u8*) data + arm9s->offset, arm9s->size, SHA256_MODE); + firm->entry_arm9 = ARM9ENTRY_FIX(firm); + + return 0; +} diff --git a/source/game/firm.h b/source/game/firm.h index d11ddd0..160e2ff 100644 --- a/source/game/firm.h +++ b/source/game/firm.h @@ -7,11 +7,20 @@ #define FIRM_MAX_SIZE 0x400000 // 4MB, due to FIRM partition size #define ARM11NCCH_OFFSET 0, 0x2A000, 0x2B000, 0x2C000 #define ARM9BIN_OFFSET 0x800 +// ARM9 entrypoint after decryption - TWL/AGB and NATIVE/SAFE_MODE +// see: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L349 +// and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L424 +// and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L463 +#define ARM9ENTRY_FIX(firm) (((firm)->sections[3].offset) ? 0x801301C : 0x0801B01C) #define FIRM_NDMA_CPY 0 #define FIRM_XDMA_CPY 1 #define FIRM_CPU_MEMCPY 2 +#define IsInstallableFirm(firm, firm_size) (ValidateFirm(firm, firm_size, true) == 0) +#define IsBootableFirm(firm, firm_size) (ValidateFirm(firm, firm_size, false) == 0) + + // see: https://www.3dbrew.org/wiki/FIRM#Firmware_Section_Headers typedef struct { u32 offset; @@ -58,3 +67,4 @@ u32 DecryptA9LHeader(FirmA9LHeader* header); u32 DecryptFirm(void* data, u32 offset, u32 size, FirmHeader* firm, FirmA9LHeader* a9l); u32 DecryptArm9Binary(void* data, u32 offset, u32 size, FirmA9LHeader* a9l); u32 DecryptFirmSequential(void* data, u32 offset, u32 size); +u32 DecryptFirmFull(void* data, u32 size); diff --git a/source/godmode.c b/source/godmode.c index cb229f0..253cb3b 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -768,19 +768,28 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) { if (verbose && !ShowUnlockSequence(3, "%s (%dkB)\nBoot FIRM via chainloader?", pathstr, firm_size / 1024)) return 1; - if ((FileGetData(bootpath, TEMP_BUFFER, firm_size, 0) != firm_size) || - (ValidateFirm(TEMP_BUFFER, firm_size, false) != 0)) { - if (verbose) ShowPrompt(false, "%s\nNot a bootable FIRM", pathstr); + void* firm = (void*) TEMP_BUFFER; + if ((FileGetData(bootpath, firm, firm_size, 0) != firm_size) || + !IsBootableFirm(firm, firm_size)) { + if (verbose) ShowPrompt(false, "%s\nNot a bootable FIRM.", pathstr); return 1; } + // encrypted firm handling + FirmSectionHeader* arm9s = FindFirmArm9Section(firm); + FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) firm + arm9s->offset); + if (verbose && (ValidateFirmA9LHeader(a9l) == 0) && + ShowPrompt(true, "%s\nFIRM is encrypted.\n \nDecrypt before boot?", pathstr) && + (DecryptFirmFull(firm, firm_size) != 0)) + return 1; + // unsupported location handling char fixpath[256] = { 0 }; if (verbose && (*bootpath != '0') && (*bootpath != '1')) { const char* optionstr[2] = { "Make a copy at " OUTPUT_PATH "/temp.firm", "Try to boot anyways" }; - u32 user_select = ShowSelectPrompt(2, optionstr, "%s\nWarning: Trying to boot from\nan unsupported location.", pathstr); + u32 user_select = ShowSelectPrompt(2, optionstr, "%s\nWarning: Trying to boot from an\nunsupported location.", pathstr); if (user_select == 1) { - FileSetData(OUTPUT_PATH "/temp.firm", TEMP_BUFFER, firm_size, 0, true); + FileSetData(OUTPUT_PATH "/temp.firm", firm, firm_size, 0, true); bootpath = OUTPUT_PATH "/temp.firm"; } else if (!user_select) bootpath = ""; } @@ -793,7 +802,7 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) { // boot the FIRM (if we got a proper fixpath) if (*fixpath) { if (delete) PathDelete(bootpath); - BootFirm((FirmHeader*)(void*)TEMP_BUFFER, fixpath); + BootFirm((FirmHeader*) firm, fixpath); while(1); } @@ -836,8 +845,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool xorpadable = (FTYPE_XORPAD(filetype)); bool keyinitable = (FTYPE_KEYINIT(filetype)); bool scriptable = (FTYPE_SCRIPT(filetype)); - bool bootable = (FTYPE_BOOTABLE(filetype) && !(drvtype & DRV_VIRTUAL)); - bool installable = (FTYPE_INSTALLABLE(filetype) && !(drvtype & DRV_VIRTUAL)); + bool bootable = (FTYPE_BOOTABLE(filetype)); + bool installable = (FTYPE_INSTALLABLE(filetype)); bool agbexportable = (FTPYE_AGBSAVE(filetype)); bool agbimportable = (FTPYE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || bootable || scriptable || installable || agbexportable || agbimportable; @@ -1738,7 +1747,7 @@ u32 GodMode(bool is_b9s) { // bootloader handler if (bootloader) { const char* bootfirm_paths[] = { BOOTFIRM_PATHS }; - if (ValidateFirm(firm_in_mem, FIRM_MAX_SIZE, false) == 0) BootFirm(firm_in_mem, "0:/bootonce.firm"); + if (IsBootableFirm(firm_in_mem, FIRM_MAX_SIZE)) BootFirm(firm_in_mem, "0:/bootonce.firm"); for (u32 i = 0; i < sizeof(bootfirm_paths) / sizeof(char*); i++) { BootFirmHandler(bootfirm_paths[i], false, (BOOTFIRM_TEMPS >> i) & 0x1); } diff --git a/source/utils/gameutil.c b/source/utils/gameutil.c index 4481d7d..8e74c1f 100644 --- a/source/utils/gameutil.c +++ b/source/utils/gameutil.c @@ -908,10 +908,7 @@ u32 DecryptFirmFile(const char* orig, const char* dest) { // write back FIRM header fvx_lseek(&file, 0); memcpy(firm.dec_magic, dec_magic, sizeof(dec_magic)); - // see: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L349 - // and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L424 - // and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L463 - firm.entry_arm9 = (firm.sections[3].offset) ? 0x801301C : 0x0801B01C; + firm.entry_arm9 = ARM9ENTRY_FIX(&firm); if (fvx_write(&file, &firm, sizeof(FirmHeader), &btr) != FR_OK) { fvx_close(&file); return 1; diff --git a/source/utils/nandutil.c b/source/utils/nandutil.c index 6158959..f2106ef 100644 --- a/source/utils/nandutil.c +++ b/source/utils/nandutil.c @@ -452,8 +452,9 @@ u32 SafeInstallFirm(const char* path, u32 slots) { u8* firm = (u8*) TEMP_BUFFER; UINT firm_size; if ((fvx_qread(path, firm, 0, TEMP_BUFFER_SIZE, &firm_size) != FR_OK) || - !firm_size || (ValidateFirm(firm, firm_size, true) != 0)) { - ShowPrompt(false, "%s\nFIRM load/verify error.", pathstr); + !firm_size || !IsInstallableFirm(firm, firm_size)) { + ShowPrompt(false, IsBootableFirm(firm, firm_size) ? + "%s\nNot a installable FIRM." : "%s\nFIRM load/verify error.", pathstr); return 1; } diff --git a/source/utils/scripting.c b/source/utils/scripting.c index cf60911..f7b45d9 100644 --- a/source/utils/scripting.c +++ b/source/utils/scripting.c @@ -723,8 +723,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { } else if (id == CMD_ID_BOOT) { size_t firm_size = FileGetData(argv[0], TEMP_BUFFER, TEMP_BUFFER_SIZE, 0); - ret = firm_size && (firm_size < TEMP_BUFFER_SIZE) && - (ValidateFirm(TEMP_BUFFER, firm_size, false) == 0); + ret = firm_size && IsBootableFirm(TEMP_BUFFER, firm_size); if (ret) { char fixpath[256] = { 0 }; if ((*argv[0] == '0') || (*argv[0] == '1'))