mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 21:52:48 +00:00
Enable verification and decryption of FIRM files
This commit is contained in:
parent
0fffa3d2ce
commit
52ef9c2af3
@ -1,9 +1,11 @@
|
|||||||
#include "filetype.h"
|
#include "filetype.h"
|
||||||
#include "fsutil.h"
|
#include "fsutil.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "firm.h"
|
||||||
|
|
||||||
u32 IdentifyFileType(const char* path) {
|
u32 IdentifyFileType(const char* path) {
|
||||||
const u8 romfs_magic[] = { ROMFS_MAGIC };
|
const u8 romfs_magic[] = { ROMFS_MAGIC };
|
||||||
|
const u8 firm_magic[] = { FIRM_MAGIC };
|
||||||
u8 header[0x200] __attribute__((aligned(32))); // minimum required size
|
u8 header[0x200] __attribute__((aligned(32))); // minimum required size
|
||||||
size_t fsize = FileGetSize(path);
|
size_t fsize = FileGetSize(path);
|
||||||
if (FileGetData(path, header, 0x200, 0) != 0x200) return 0;
|
if (FileGetData(path, header, 0x200, 0) != 0x200) return 0;
|
||||||
@ -43,6 +45,8 @@ u32 IdentifyFileType(const char* path) {
|
|||||||
} else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) {
|
} else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) {
|
||||||
if (fsize >= TMD_SIZE_N(getbe16(header + 0x1DE)))
|
if (fsize >= TMD_SIZE_N(getbe16(header + 0x1DE)))
|
||||||
return GAME_TMD; // TMD file
|
return GAME_TMD; // TMD file
|
||||||
|
} else if (memcmp(header, firm_magic, sizeof(firm_magic)) == 0) {
|
||||||
|
return SYS_FIRM; // FIRM file
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -12,9 +12,11 @@
|
|||||||
#define GAME_EXEFS (1<<6)
|
#define GAME_EXEFS (1<<6)
|
||||||
#define GAME_ROMFS (1<<7)
|
#define GAME_ROMFS (1<<7)
|
||||||
|
|
||||||
|
#define SYS_FIRM (1<<8)
|
||||||
|
|
||||||
#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS)
|
#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS)
|
||||||
#define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD)
|
#define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM)
|
||||||
#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH)
|
#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM)
|
||||||
#define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD)
|
#define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD)
|
||||||
#define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD))
|
#define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD))
|
||||||
|
|
||||||
|
213
source/game/firm.c
Normal file
213
source/game/firm.c
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#include "firm.h"
|
||||||
|
#include "aes.h"
|
||||||
|
#include "sha.h"
|
||||||
|
#include "nand.h"
|
||||||
|
#include "keydb.h"
|
||||||
|
#include "ff.h"
|
||||||
|
|
||||||
|
// 0 -> pre 9.5 / 1 -> 9.5 / 2 -> post 9.5
|
||||||
|
#define A9L_CRYPTO_TYPE(hdr) ((hdr->k9l[3] == 0xFF) ? 0 : (hdr->k9l[3] == '1') ? 1 : 2)
|
||||||
|
|
||||||
|
u32 ValidateFirmHeader(FirmHeader* header) {
|
||||||
|
u8 magic[] = { FIRM_MAGIC };
|
||||||
|
return memcmp(header->magic, magic, sizeof(magic)); // duh
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 ValidateFirmA9LHeader(FirmA9LHeader* header) {
|
||||||
|
const u8 enckeyX0x15hash[0x20] = {
|
||||||
|
0x0A, 0x85, 0x20, 0x14, 0x8F, 0x7E, 0xB7, 0x21, 0xBF, 0xC6, 0xC8, 0x82, 0xDF, 0x37, 0x06, 0x3C,
|
||||||
|
0x0E, 0x05, 0x1D, 0x1E, 0xF3, 0x41, 0xE9, 0x80, 0x1E, 0xC9, 0x97, 0x82, 0xA0, 0x84, 0x43, 0x08
|
||||||
|
};
|
||||||
|
u8 hash[0x20];
|
||||||
|
sha_quick(hash, header->keyX0x15, 0x10, SHA256_MODE);
|
||||||
|
return memcmp(hash, enckeyX0x15hash, 0x20);
|
||||||
|
}
|
||||||
|
|
||||||
|
FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) {
|
||||||
|
for (u32 i = 0; i < 4; i++) {
|
||||||
|
FirmSectionHeader* section = firm->sections + i;
|
||||||
|
if (section->size && (section->type == 0))
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetArm9BinarySize(FirmA9LHeader* a9l) {
|
||||||
|
char* size_ascii = a9l->size_ascii;
|
||||||
|
u32 size = 0;
|
||||||
|
for (u32 i = 0; (i < 8) && *(size_ascii + i); i++)
|
||||||
|
size = (size * 10) + (*(size_ascii + i) - '0');
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SetupSecretKey(u32 keynum) {
|
||||||
|
const char* base[] = { INPUT_PATHS };
|
||||||
|
// from: https://github.com/AuroraWright/SafeA9LHInstaller/blob/master/source/installer.c#L9-L17
|
||||||
|
const u8 sectorHash[0x20] = {
|
||||||
|
0x82, 0xF2, 0x73, 0x0D, 0x2C, 0x2D, 0xA3, 0xF3, 0x01, 0x65, 0xF9, 0x87, 0xFD, 0xCC, 0xAC, 0x5C,
|
||||||
|
0xBA, 0xB2, 0x4B, 0x4E, 0x5F, 0x65, 0xC9, 0x81, 0xCD, 0x7B, 0xE6, 0xF4, 0x38, 0xE6, 0xD9, 0xD3
|
||||||
|
};
|
||||||
|
static u8 __attribute__((aligned(32))) sector[0x200];
|
||||||
|
u8 hash[0x20];
|
||||||
|
|
||||||
|
// safety check
|
||||||
|
if (keynum >= 0x200/0x10) return 1;
|
||||||
|
|
||||||
|
// secret sector already loaded?
|
||||||
|
sha_quick(hash, sector, 0x200, SHA256_MODE);
|
||||||
|
if (memcmp(hash, sectorHash, 0x20) == 0) {
|
||||||
|
setup_aeskey(0x11, sector + (keynum*0x10));
|
||||||
|
use_aeskey(0x11);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search for valid secret sector in SysNAND / EmuNAND
|
||||||
|
const u32 nand_src[] = { NAND_SYSNAND, NAND_EMUNAND };
|
||||||
|
for (u32 i = 0; i < sizeof(nand_src) / sizeof(u32); i++) {
|
||||||
|
ReadNandSectors(sector, 0x96, 1, 0x11, nand_src[i]);
|
||||||
|
sha_quick(hash, sector, 0x200, SHA256_MODE);
|
||||||
|
if (memcmp(hash, sectorHash, 0x20) != 0) continue;
|
||||||
|
setup_aeskey(0x11, sector + (keynum*0x10));
|
||||||
|
use_aeskey(0x11);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no luck? try searching for a file
|
||||||
|
for (u32 i = 0; i < (sizeof(base)/sizeof(char*)); i++) {
|
||||||
|
char path[64];
|
||||||
|
FIL fp;
|
||||||
|
UINT btr;
|
||||||
|
snprintf(path, 64, "%s/%s", base[i], SECTOR_NAME);
|
||||||
|
if (f_open(&fp, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) {
|
||||||
|
snprintf(path, 64, "%s/%s", base[i], SECRET_NAME);
|
||||||
|
if (f_open(&fp, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) continue;
|
||||||
|
}
|
||||||
|
f_read(&fp, sector, 0x200, &btr);
|
||||||
|
f_close(&fp);
|
||||||
|
sha_quick(hash, sector, 0x200, SHA256_MODE);
|
||||||
|
if (memcmp(hash, sectorHash, 0x20) != 0) continue;
|
||||||
|
setup_aeskey(0x11, sector + (keynum*0x10));
|
||||||
|
use_aeskey(0x11);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to load from key database
|
||||||
|
if ((keynum < 2) && (LoadKeyFromFile(NULL, 0x11, 'N', (keynum == 0) ? "95" : "96")))
|
||||||
|
return 0; // key found in keydb, done
|
||||||
|
|
||||||
|
// out of options
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 DecryptA9LHeader(FirmA9LHeader* header) {
|
||||||
|
u32 type = A9L_CRYPTO_TYPE(header);
|
||||||
|
|
||||||
|
if (SetupSecretKey(0) != 0) return 1;
|
||||||
|
aes_decrypt(header->keyX0x15, header->keyX0x15, 1, AES_CNT_ECB_DECRYPT_MODE);
|
||||||
|
if (type) {
|
||||||
|
if (SetupSecretKey((type == 1) ? 0 : 1) != 0) return 1;
|
||||||
|
aes_decrypt(header->keyX0x16, header->keyX0x16, 1, AES_CNT_ECB_DECRYPT_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SetupArm9BinaryCrypto(FirmA9LHeader* header) {
|
||||||
|
u32 type = A9L_CRYPTO_TYPE(header);
|
||||||
|
|
||||||
|
if (type == 0) {
|
||||||
|
u8 __attribute__((aligned(32))) keyX0x15[0x10];
|
||||||
|
memcpy(keyX0x15, header->keyX0x15, 0x10);
|
||||||
|
if (SetupSecretKey(0) != 0) return 1;
|
||||||
|
aes_decrypt(keyX0x15, keyX0x15, 1, AES_CNT_ECB_DECRYPT_MODE);
|
||||||
|
setup_aeskeyX(0x15, keyX0x15);
|
||||||
|
setup_aeskeyY(0x15, header->keyY0x150x16);
|
||||||
|
use_aeskey(0x15);
|
||||||
|
} else {
|
||||||
|
u8 __attribute__((aligned(32))) keyX0x16[0x10];
|
||||||
|
memcpy(keyX0x16, header->keyX0x16, 0x10);
|
||||||
|
if (SetupSecretKey((type == 1) ? 0 : 1) != 0) return 1;
|
||||||
|
aes_decrypt(keyX0x16, keyX0x16, 1, AES_CNT_ECB_DECRYPT_MODE);
|
||||||
|
setup_aeskeyX(0x16, keyX0x16);
|
||||||
|
setup_aeskeyY(0x16, header->keyY0x150x16);
|
||||||
|
use_aeskey(0x16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 DecryptArm9Binary(u8* data, u32 offset, u32 size, FirmA9LHeader* a9l) {
|
||||||
|
// offset == offset inside ARM9 binary
|
||||||
|
// ARM9 binary begins 0x800 byte after the ARM9 loader header
|
||||||
|
|
||||||
|
// only process actual ARM9 binary
|
||||||
|
u32 size_bin = GetArm9BinarySize(a9l);
|
||||||
|
if (offset >= size_bin) return 0;
|
||||||
|
else if (size >= size_bin - offset)
|
||||||
|
size = size_bin - offset;
|
||||||
|
|
||||||
|
// decrypt data
|
||||||
|
if (SetupArm9BinaryCrypto(a9l) != 0) return 1;
|
||||||
|
ctr_decrypt_byte(data, data, size, offset, AES_CNT_CTRNAND_MODE, a9l->ctr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 DecryptFirm(u8* data, u32 offset, u32 size, FirmHeader* firm, FirmA9LHeader* a9l) {
|
||||||
|
// ARM9 binary size / offset
|
||||||
|
FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
|
||||||
|
u32 offset_arm9bin = arm9s->offset + ARM9BIN_OFFSET;
|
||||||
|
u32 size_arm9bin = GetArm9BinarySize(a9l);
|
||||||
|
|
||||||
|
// sanity checks
|
||||||
|
if (!size_arm9bin || (size_arm9bin + ARM9BIN_OFFSET > arm9s->size))
|
||||||
|
return 1; // bad header / data
|
||||||
|
|
||||||
|
// check if ARM9 binary in data
|
||||||
|
if ((offset_arm9bin >= offset + size) ||
|
||||||
|
(offset >= offset_arm9bin + size_arm9bin))
|
||||||
|
return 0; // section not in data
|
||||||
|
|
||||||
|
// determine data / offset / size
|
||||||
|
u8* data_i = data;
|
||||||
|
u32 offset_i = 0;
|
||||||
|
u32 size_i = size_arm9bin;
|
||||||
|
if (offset_arm9bin < offset)
|
||||||
|
offset_i = offset - offset_arm9bin;
|
||||||
|
else data_i = data + (offset_arm9bin - offset);
|
||||||
|
size_i = size_arm9bin - offset_i;
|
||||||
|
if (size_i > size - (data_i - data))
|
||||||
|
size_i = size - (data_i - data);
|
||||||
|
|
||||||
|
return DecryptArm9Binary(data_i, offset_i, size_i, a9l);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 DecryptFirmSequential(u8* data, u32 offset, u32 size) {
|
||||||
|
// warning: this will only work for sequential processing
|
||||||
|
// unexpected results otherwise
|
||||||
|
static FirmHeader firm = { 0 };
|
||||||
|
static FirmA9LHeader a9l = { 0 };
|
||||||
|
static FirmHeader* firmptr = NULL;
|
||||||
|
static FirmA9LHeader* a9lptr = NULL;
|
||||||
|
static FirmSectionHeader* arm9s = NULL;
|
||||||
|
|
||||||
|
// fetch firm header from data
|
||||||
|
if ((offset == 0) && (size >= sizeof(FirmHeader))) {
|
||||||
|
memcpy(&firm, data, sizeof(FirmHeader));
|
||||||
|
firmptr = (ValidateFirmHeader(&firm) == 0) ? &firm : NULL;
|
||||||
|
arm9s = (firmptr) ? FindFirmArm9Section(firmptr) : NULL;
|
||||||
|
a9lptr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// safety check, firm header pointer
|
||||||
|
if (!firmptr) return 1;
|
||||||
|
|
||||||
|
// fetch ARM9 loader header from data
|
||||||
|
if (arm9s && !a9lptr && (offset <= arm9s->offset) &&
|
||||||
|
((offset + size) >= arm9s->offset + sizeof(FirmA9LHeader))) {
|
||||||
|
memcpy(&a9l, data + arm9s->offset - offset, sizeof(FirmA9LHeader));
|
||||||
|
a9lptr = (ValidateFirmA9LHeader(&a9l) == 0) ? &a9l : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (a9lptr) ? DecryptFirm(data, offset, size, firmptr, a9lptr) : 0;
|
||||||
|
}
|
52
source/game/firm.h
Normal file
52
source/game/firm.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#define FIRM_MAGIC 'F', 'I', 'R', 'M'
|
||||||
|
|
||||||
|
#define SECTOR_NAME "sector0x96.bin"
|
||||||
|
#define SECRET_NAME "secret_sector.bin"
|
||||||
|
|
||||||
|
#define ARM9BIN_OFFSET 0x800
|
||||||
|
|
||||||
|
// see: https://www.3dbrew.org/wiki/FIRM#Firmware_Section_Headers
|
||||||
|
typedef struct {
|
||||||
|
u32 offset;
|
||||||
|
u32 address;
|
||||||
|
u32 size;
|
||||||
|
u32 type;
|
||||||
|
u8 hash[0x20];
|
||||||
|
} __attribute__((packed)) FirmSectionHeader;
|
||||||
|
|
||||||
|
// see: https://www.3dbrew.org/wiki/FIRM#FIRM_Header
|
||||||
|
typedef struct {
|
||||||
|
u8 magic[4];
|
||||||
|
u8 dec_magic[4];
|
||||||
|
u32 entry_arm11;
|
||||||
|
u32 entry_arm9;
|
||||||
|
u8 reserved1[0x30];
|
||||||
|
FirmSectionHeader sections[4];
|
||||||
|
u8 signature[0x100];
|
||||||
|
} __attribute__((packed, aligned(16))) FirmHeader;
|
||||||
|
|
||||||
|
// see: https://www.3dbrew.org/wiki/FIRM#New_3DS_FIRM
|
||||||
|
typedef struct {
|
||||||
|
u8 keyX0x15[0x10]; // this is encrypted
|
||||||
|
u8 keyY0x150x16[0x10];
|
||||||
|
u8 ctr[0x10];
|
||||||
|
char size_ascii[0x8];
|
||||||
|
u8 reserved[0x8];
|
||||||
|
u8 control[0x10];
|
||||||
|
u8 k9l[0x10];
|
||||||
|
u8 keyX0x16[0x10]; // this is encrypted
|
||||||
|
u8 padding[0x1A0];
|
||||||
|
} __attribute__((packed, aligned(16))) FirmA9LHeader;
|
||||||
|
|
||||||
|
u32 ValidateFirmHeader(FirmHeader* header);
|
||||||
|
u32 ValidateFirmA9LHeader(FirmA9LHeader* header);
|
||||||
|
FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm);
|
||||||
|
|
||||||
|
u32 DecryptA9LHeader(FirmA9LHeader* header);
|
||||||
|
u32 DecryptFirm(u8* data, u32 offset, u32 size, FirmHeader* firm, FirmA9LHeader* a9l);
|
||||||
|
u32 DecryptArm9Binary(u8* data, u32 offset, u32 size, FirmA9LHeader* a9l);
|
||||||
|
u32 DecryptFirmSequential(u8* data, u32 offset, u32 size);
|
@ -1,5 +1,6 @@
|
|||||||
#include "gameutil.h"
|
#include "gameutil.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "firm.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "fsperm.h"
|
#include "fsperm.h"
|
||||||
#include "filetype.h"
|
#include "filetype.h"
|
||||||
@ -462,6 +463,48 @@ u32 VerifyTmdFile(const char* path) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 VerifyFirmFile(const char* path) {
|
||||||
|
FirmHeader header;
|
||||||
|
FIL file;
|
||||||
|
UINT btr;
|
||||||
|
|
||||||
|
// open file, get FIRM header
|
||||||
|
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||||
|
return 1;
|
||||||
|
f_lseek(&file, 0);
|
||||||
|
if ((fx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) ||
|
||||||
|
(ValidateFirmHeader(&header) != 0)) {
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hash verify all available sections
|
||||||
|
for (u32 i = 0; i < 4; i++) {
|
||||||
|
FirmSectionHeader* section = header.sections + i;
|
||||||
|
u32 size = section->size;
|
||||||
|
if (!size) continue;
|
||||||
|
f_lseek(&file, section->offset);
|
||||||
|
sha_init(SHA256_MODE);
|
||||||
|
for (u32 i = 0; i < size; i += MAIN_BUFFER_SIZE) {
|
||||||
|
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
|
||||||
|
fx_read(&file, MAIN_BUFFER, read_bytes, &btr);
|
||||||
|
sha_update(MAIN_BUFFER, read_bytes);
|
||||||
|
}
|
||||||
|
u8 hash[0x20];
|
||||||
|
sha_get(hash);
|
||||||
|
if (memcmp(hash, section->hash, 0x20) != 0) {
|
||||||
|
char pathstr[32 + 1];
|
||||||
|
TruncateString(pathstr, path, 32, 8);
|
||||||
|
ShowPrompt(false, "%s\nSection %u hash mismatch", pathstr, i);
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fx_close(&file);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
u32 VerifyGameFile(const char* path) {
|
u32 VerifyGameFile(const char* path) {
|
||||||
u32 filetype = IdentifyFileType(path);
|
u32 filetype = IdentifyFileType(path);
|
||||||
if (filetype == GAME_CIA)
|
if (filetype == GAME_CIA)
|
||||||
@ -472,25 +515,15 @@ u32 VerifyGameFile(const char* path) {
|
|||||||
return VerifyNcchFile(path, 0, 0);
|
return VerifyNcchFile(path, 0, 0);
|
||||||
else if (filetype == GAME_TMD)
|
else if (filetype == GAME_TMD)
|
||||||
return VerifyTmdFile(path);
|
return VerifyTmdFile(path);
|
||||||
|
else if (filetype == SYS_FIRM)
|
||||||
|
return VerifyFirmFile(path);
|
||||||
else return 1;
|
else return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 CheckEncryptedNcchFile(const char* path, u32 offset) {
|
u32 CheckEncryptedNcchFile(const char* path, u32 offset) {
|
||||||
NcchHeader ncch;
|
NcchHeader ncch;
|
||||||
FIL file;
|
if (LoadNcchHeaders(&ncch, NULL, NULL, path, offset) != 0)
|
||||||
UINT btr;
|
|
||||||
|
|
||||||
// open file, get NCCH header
|
|
||||||
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
|
||||||
return 1;
|
return 1;
|
||||||
f_lseek(&file, offset);
|
|
||||||
if ((fx_read(&file, &ncch, sizeof(NcchHeader), &btr) != FR_OK) ||
|
|
||||||
(ValidateNcchHeader(&ncch) != 0)) {
|
|
||||||
fx_close(&file);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
fx_close(&file);
|
|
||||||
|
|
||||||
return (NCCH_ENCRYPTED(&ncch)) ? 0 : 1;
|
return (NCCH_ENCRYPTED(&ncch)) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,6 +568,37 @@ u32 CheckEncryptedCiaFile(const char* path) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 CheckEncryptedFirmFile(const char* path) {
|
||||||
|
FirmHeader header;
|
||||||
|
FIL file;
|
||||||
|
UINT btr;
|
||||||
|
|
||||||
|
// open file, get FIRM header
|
||||||
|
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||||
|
return 1;
|
||||||
|
f_lseek(&file, 0);
|
||||||
|
if ((fx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) ||
|
||||||
|
(ValidateFirmHeader(&header) != 0)) {
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check ARM9 binary for ARM9 loader
|
||||||
|
FirmSectionHeader* arm9s = FindFirmArm9Section(&header);
|
||||||
|
if (arm9s) {
|
||||||
|
FirmA9LHeader a9l;
|
||||||
|
f_lseek(&file, arm9s->offset);
|
||||||
|
if ((fx_read(&file, &a9l, sizeof(FirmA9LHeader), &btr) == FR_OK) &&
|
||||||
|
(ValidateFirmA9LHeader(&a9l) == 0)) {
|
||||||
|
fx_close(&file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
u32 CheckEncryptedGameFile(const char* path) {
|
u32 CheckEncryptedGameFile(const char* path) {
|
||||||
u32 filetype = IdentifyFileType(path);
|
u32 filetype = IdentifyFileType(path);
|
||||||
if (filetype == GAME_CIA)
|
if (filetype == GAME_CIA)
|
||||||
@ -543,10 +607,12 @@ u32 CheckEncryptedGameFile(const char* path) {
|
|||||||
return CheckEncryptedNcsdFile(path);
|
return CheckEncryptedNcsdFile(path);
|
||||||
else if (filetype == GAME_NCCH)
|
else if (filetype == GAME_NCCH)
|
||||||
return CheckEncryptedNcchFile(path, 0);
|
return CheckEncryptedNcchFile(path, 0);
|
||||||
|
else if (filetype == SYS_FIRM)
|
||||||
|
return CheckEncryptedFirmFile(path);
|
||||||
else return 1;
|
else return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 DecryptNcchNcsdFile(const char* orig, const char* dest, u32 mode,
|
u32 DecryptNcchNcsdFirmFile(const char* orig, const char* dest, u32 mode,
|
||||||
u32 offset, u32 size, TmdContentChunk* chunk, const u8* titlekey) { // this line only for CIA contents
|
u32 offset, u32 size, TmdContentChunk* chunk, const u8* titlekey) { // this line only for CIA contents
|
||||||
// this will do a simple copy for unencrypted files
|
// this will do a simple copy for unencrypted files
|
||||||
bool inplace = (strncmp(orig, dest, 256) == 0);
|
bool inplace = (strncmp(orig, dest, 256) == 0);
|
||||||
@ -576,14 +642,15 @@ u32 DecryptNcchNcsdFile(const char* orig, const char* dest, u32 mode,
|
|||||||
if (!size) size = fsize;
|
if (!size) size = fsize;
|
||||||
|
|
||||||
u32 ret = 0;
|
u32 ret = 0;
|
||||||
if (mode & (GAME_NCCH|GAME_NCSD)) { // for NCCH / NCSD files
|
|
||||||
if (!ShowProgress(offset, fsize, dest)) ret = 1;
|
if (!ShowProgress(offset, fsize, dest)) ret = 1;
|
||||||
|
if (mode & (GAME_NCCH|GAME_NCSD|SYS_FIRM)) { // for NCCH / NCSD / FIRM files
|
||||||
for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) {
|
for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) {
|
||||||
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
|
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
|
||||||
UINT bytes_read, bytes_written;
|
UINT bytes_read, bytes_written;
|
||||||
if (fx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1;
|
if (fx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1;
|
||||||
if (((mode & GAME_NCCH) && (DecryptNcchSequential(MAIN_BUFFER, i, read_bytes) != 0)) ||
|
if (((mode & GAME_NCCH) && (DecryptNcchSequential(MAIN_BUFFER, i, read_bytes) != 0)) ||
|
||||||
((mode & GAME_NCSD) && (DecryptNcsdSequential(MAIN_BUFFER, i, read_bytes) != 0)))
|
((mode & GAME_NCSD) && (DecryptNcsdSequential(MAIN_BUFFER, i, read_bytes) != 0)) ||
|
||||||
|
((mode & SYS_FIRM) && (DecryptFirmSequential(MAIN_BUFFER, i, read_bytes) != 0)))
|
||||||
ret = 1;
|
ret = 1;
|
||||||
if (inplace) f_lseek(ofp, f_tell(ofp) - read_bytes);
|
if (inplace) f_lseek(ofp, f_tell(ofp) - read_bytes);
|
||||||
if (fx_write(dfp, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) ret = 1;
|
if (fx_write(dfp, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) ret = 1;
|
||||||
@ -591,7 +658,6 @@ u32 DecryptNcchNcsdFile(const char* orig, const char* dest, u32 mode,
|
|||||||
if (!ShowProgress(offset + i + read_bytes, fsize, dest)) ret = 1;
|
if (!ShowProgress(offset + i + read_bytes, fsize, dest)) ret = 1;
|
||||||
}
|
}
|
||||||
} else if (mode & GAME_CIA) { // for NCCHs inside CIAs
|
} else if (mode & GAME_CIA) { // for NCCHs inside CIAs
|
||||||
if (!ShowProgress(offset, fsize, dest)) ret = 1;
|
|
||||||
bool cia_crypto = getbe16(chunk->type) & 0x1;
|
bool cia_crypto = getbe16(chunk->type) & 0x1;
|
||||||
bool ncch_crypto; // find out by decrypting the NCCH header
|
bool ncch_crypto; // find out by decrypting the NCCH header
|
||||||
UINT bytes_read, bytes_written;
|
UINT bytes_read, bytes_written;
|
||||||
@ -654,7 +720,7 @@ u32 DecryptCiaFile(const char* orig, const char* dest) {
|
|||||||
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
|
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
|
||||||
TmdContentChunk* chunk = &(cia->content_list[i]);
|
TmdContentChunk* chunk = &(cia->content_list[i]);
|
||||||
u64 size = getbe64(chunk->size);
|
u64 size = getbe64(chunk->size);
|
||||||
if (DecryptNcchNcsdFile(orig, dest, GAME_CIA, next_offset, size, chunk, titlekey) != 0)
|
if (DecryptNcchNcsdFirmFile(orig, dest, GAME_CIA, next_offset, size, chunk, titlekey) != 0)
|
||||||
return 1;
|
return 1;
|
||||||
next_offset += size;
|
next_offset += size;
|
||||||
}
|
}
|
||||||
@ -666,6 +732,65 @@ u32 DecryptCiaFile(const char* orig, const char* dest) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 DecryptFirmFile(const char* orig, const char* dest) {
|
||||||
|
const u8 dec_magic[] = { 'D', 'E', 'C', '\0' }; // insert to decrypted firms
|
||||||
|
FirmHeader firm;
|
||||||
|
FIL file;
|
||||||
|
UINT btr;
|
||||||
|
|
||||||
|
// actual decryption
|
||||||
|
if (DecryptNcchNcsdFirmFile(orig, dest, SYS_FIRM, 0, 0, NULL, NULL) != 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// open destination file, get FIRM header
|
||||||
|
if (fx_open(&file, dest, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK)
|
||||||
|
return 1;
|
||||||
|
f_lseek(&file, 0);
|
||||||
|
if ((fx_read(&file, &firm, sizeof(FirmHeader), &btr) != FR_OK) ||
|
||||||
|
(ValidateFirmHeader(&firm) != 0)) {
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find ARM9 section
|
||||||
|
FirmSectionHeader* arm9s = FindFirmArm9Section(&firm);
|
||||||
|
if (!arm9s || !arm9s->size) return 1;
|
||||||
|
|
||||||
|
// decrypt ARM9 loader header
|
||||||
|
FirmA9LHeader a9l;
|
||||||
|
f_lseek(&file, arm9s->offset);
|
||||||
|
if ((fx_read(&file, &a9l, sizeof(FirmA9LHeader), &btr) != FR_OK) ||
|
||||||
|
(DecryptA9LHeader(&a9l) != 0) || (f_lseek(&file, arm9s->offset) != FR_OK) ||
|
||||||
|
(fx_write(&file, &a9l, sizeof(FirmA9LHeader), &btr) != FR_OK)) {
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate new hash for ARM9 section
|
||||||
|
f_lseek(&file, arm9s->offset);
|
||||||
|
sha_init(SHA256_MODE);
|
||||||
|
for (u32 i = 0; i < arm9s->size; i += MAIN_BUFFER_SIZE) {
|
||||||
|
u32 read_bytes = min(MAIN_BUFFER_SIZE, (arm9s->size - i));
|
||||||
|
if ((fx_read(&file, MAIN_BUFFER, read_bytes, &btr) != FR_OK) || (btr != read_bytes)) {
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
sha_update(MAIN_BUFFER, read_bytes);
|
||||||
|
}
|
||||||
|
sha_get(arm9s->hash);
|
||||||
|
|
||||||
|
// write back FIRM header
|
||||||
|
f_lseek(&file, 0);
|
||||||
|
memcpy(firm.dec_magic, dec_magic, sizeof(dec_magic));
|
||||||
|
if (fx_write(&file, &firm, sizeof(FirmHeader), &btr) != FR_OK) {
|
||||||
|
fx_close(&file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fx_close(&file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
u32 DecryptGameFile(const char* path, bool inplace) {
|
u32 DecryptGameFile(const char* path, bool inplace) {
|
||||||
u32 filetype = IdentifyFileType(path);
|
u32 filetype = IdentifyFileType(path);
|
||||||
char dest[256];
|
char dest[256];
|
||||||
@ -688,8 +813,10 @@ u32 DecryptGameFile(const char* path, bool inplace) {
|
|||||||
|
|
||||||
if (filetype & GAME_CIA)
|
if (filetype & GAME_CIA)
|
||||||
ret = DecryptCiaFile(path, destptr);
|
ret = DecryptCiaFile(path, destptr);
|
||||||
|
else if (filetype & SYS_FIRM)
|
||||||
|
ret = DecryptFirmFile(path, destptr);
|
||||||
else if (filetype & (GAME_NCCH|GAME_NCSD))
|
else if (filetype & (GAME_NCCH|GAME_NCSD))
|
||||||
ret = DecryptNcchNcsdFile(path, destptr, filetype, 0, 0, NULL, NULL);
|
ret = DecryptNcchNcsdFirmFile(path, destptr, filetype, 0, 0, NULL, NULL);
|
||||||
else ret = 1;
|
else ret = 1;
|
||||||
|
|
||||||
if (!inplace && (ret != 0))
|
if (!inplace && (ret != 0))
|
||||||
|
@ -599,7 +599,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
|
|||||||
(filetype == GAME_NCCH ) ? "NCCH image options..." :
|
(filetype == GAME_NCCH ) ? "NCCH image options..." :
|
||||||
(filetype == GAME_EXEFS) ? "Mount as EXEFS image" :
|
(filetype == GAME_EXEFS) ? "Mount as EXEFS image" :
|
||||||
(filetype == GAME_ROMFS) ? "Mount as ROMFS image" :
|
(filetype == GAME_ROMFS) ? "Mount as ROMFS image" :
|
||||||
(filetype == GAME_TMD) ? "TMD file options..." : "???";
|
(filetype == GAME_TMD) ? "TMD file options..." :
|
||||||
|
(filetype == SYS_FIRM) ? "FIRM image options..." : "???";
|
||||||
optionstr[hexviewer-1] = "Show in Hexeditor";
|
optionstr[hexviewer-1] = "Show in Hexeditor";
|
||||||
optionstr[calcsha-1] = "Calculate SHA-256";
|
optionstr[calcsha-1] = "Calculate SHA-256";
|
||||||
if (inject > 0) optionstr[inject-1] = "Inject data @offset";
|
if (inject > 0) optionstr[inject-1] = "Inject data @offset";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user