From 5d4d1225437816a7ea819869824a9d8895730415 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Mon, 16 Oct 2017 02:02:24 +0200 Subject: [PATCH] Limited GBA rom image support ... support for: * proper extension when extracting from .code * showing title info * renaming cartridge images --- source/fatfs/image.c | 8 ++-- source/fatfs/image.h | 4 +- source/filesys/filetype.c | 21 ++++++--- source/filesys/filetype.h | 66 ++++++++++++++------------- source/filesys/fsinit.c | 2 +- source/game/game.h | 1 + source/game/gba.c | 51 +++++++++++++++++++++ source/game/gba.h | 94 +++++++++++++++++++++++++++++++++++++++ source/game/ncch.h | 1 + source/godmode.c | 8 ++-- source/nand/agbsave.c | 23 ---------- source/nand/agbsave.h | 41 ----------------- source/nand/nandcmac.c | 2 +- source/utils/gameutil.c | 57 +++++++++++++++++------- source/utils/gameutil.h | 2 +- source/utils/keydbutil.c | 2 +- source/utils/nandutil.c | 2 +- source/utils/scripting.c | 10 ++--- source/virtual/vgame.c | 6 +-- source/virtual/vgame.h | 2 +- source/virtual/vnand.c | 1 - 21 files changed, 263 insertions(+), 141 deletions(-) create mode 100644 source/game/gba.c create mode 100644 source/game/gba.h delete mode 100644 source/nand/agbsave.c delete mode 100644 source/nand/agbsave.h diff --git a/source/fatfs/image.c b/source/fatfs/image.c index 923604d..09a4279 100644 --- a/source/fatfs/image.c +++ b/source/fatfs/image.c @@ -2,7 +2,7 @@ #include "vff.h" static FIL mount_file; -static u32 mount_state = 0; +static u64 mount_state = 0; static char mount_path[256] = { 0 }; @@ -47,7 +47,7 @@ u64 GetMountSize(void) { return mount_state ? fvx_size(&mount_file) : 0; } -u32 GetMountState(void) { +u64 GetMountState(void) { return mount_state; } @@ -55,8 +55,8 @@ const char* GetMountPath(void) { return mount_path; } -u32 MountImage(const char* path) { - u32 type = (path) ? IdentifyFileType(path) : 0; +u64 MountImage(const char* path) { + u64 type = (path) ? IdentifyFileType(path) : 0; if (mount_state) { fvx_close(&mount_file); mount_state = 0; diff --git a/source/fatfs/image.h b/source/fatfs/image.h index b94137a..0c14a58 100644 --- a/source/fatfs/image.h +++ b/source/fatfs/image.h @@ -10,6 +10,6 @@ int WriteImageSectors(const void* buffer, u32 sector, u32 count); int SyncImage(void); u64 GetMountSize(void); -u32 GetMountState(void); +u64 GetMountState(void); const char* GetMountPath(void); -u32 MountImage(const char* path); +u64 MountImage(const char* path); diff --git a/source/filesys/filetype.c b/source/filesys/filetype.c index bada2d8..419dc2f 100644 --- a/source/filesys/filetype.c +++ b/source/filesys/filetype.c @@ -3,12 +3,11 @@ #include "fatmbr.h" #include "nand.h" #include "game.h" -#include "agbsave.h" #include "keydb.h" #include "ctrtransfer.h" #include "scripting.h" -u32 IdentifyFileType(const char* path) { +u64 IdentifyFileType(const char* path) { const u8 romfs_magic[] = { ROMFS_MAGIC }; const u8 tickdb_magic[] = { TICKDB_MAGIC }; const u8 smdh_magic[] = { SMDH_MAGIC }; @@ -20,7 +19,7 @@ u32 IdentifyFileType(const char* path) { char* ext = (fname) ? strrchr(++fname, '.') : NULL; u32 id = 0; if (ext) ext++; - if (FileGetData(path, header, 0x200, 0) < ((fsize > 0x200) ? 0x200 : fsize)) return 0; + if (FileGetData(path, header, 0x200, 0) < min(0x200, fsize)) return 0; if (!fsize) return 0; if (fsize >= 0x200) { @@ -49,7 +48,15 @@ u32 IdentifyFileType(const char* path) { return GAME_NCSD; // NCSD (".3DS") file } else if (ValidateNcchHeader((NcchHeader*) data) == 0) { NcchHeader* ncch = (NcchHeader*) data; - u32 type = GAME_NCCH | (NCCH_IS_CXI(ncch) ? FLAG_CXI : 0); + u32 type = GAME_NCCH; + if (NCCH_IS_CXI(ncch)) { + type |= FLAG_CXI; + NcchExtHeader exhdr; + if ((FileGetData(path, &exhdr, 0x400, 0x200) == 0x400) && // read only what we need + (DecryptNcch(&exhdr, 0x200, 0x400, ncch, NULL) == 0) && + NCCH_IS_GBAVC(&exhdr)) + type |= FLAG_GBAVC; + } if (fsize >= (ncch->size * NCCH_MEDIA_UNIT)) return type; // NCCH (".APP") file } else if (ValidateExeFsHeader((ExeFsHeader*) data, fsize) == 0) { @@ -77,14 +84,16 @@ u32 IdentifyFileType(const char* path) { } } - if ((fsize > sizeof(BossHeader)) && + if ((fsize > sizeof(AgbHeader)) && + (ValidateAgbHeader((AgbHeader*) data) == 0)) { + return GAME_GBA; + } else if ((fsize > sizeof(BossHeader)) && (ValidateBossHeader((BossHeader*) data, fsize) == 0)) { return GAME_BOSS; // BOSS (SpotPass) file } else if ((fsize > sizeof(NcchInfoHeader)) && (GetNcchInfoVersion((NcchInfoHeader*) data)) && fname && (strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) { return BIN_NCCHNFO; // ncchinfo.bin file - } else if (ext && ((strncasecmp(ext, "cdn", 4) == 0) || (strncasecmp(ext, "nus", 4) == 0))) { char path_cetk[256]; char* ext_cetk = path_cetk + (ext - path); diff --git a/source/filesys/filetype.h b/source/filesys/filetype.h index 058c53c..42cdb20 100644 --- a/source/filesys/filetype.h +++ b/source/filesys/filetype.h @@ -2,36 +2,38 @@ #include "common.h" -#define IMG_FAT (1UL<<0) -#define IMG_NAND (1UL<<1) -#define GAME_CIA (1UL<<2) -#define GAME_NCSD (1UL<<3) -#define GAME_NCCH (1UL<<4) -#define GAME_TMD (1UL<<5) -#define GAME_EXEFS (1UL<<6) -#define GAME_ROMFS (1UL<<7) -#define GAME_BOSS (1UL<<8) -#define GAME_NUSCDN (1UL<<9) -#define GAME_TICKET (1UL<<10) -#define GAME_SMDH (1UL<<11) -#define GAME_NDS (1UL<<12) -#define SYS_FIRM (1UL<<13) -#define SYS_AGBSAVE (1UL<<14) -#define SYS_TICKDB (1UL<<15) -#define BIN_NCCHNFO (1UL<<16) -#define BIN_TIKDB (1UL<<17) -#define BIN_KEYDB (1UL<<18) -#define BIN_LEGKEY (1UL<<19) -#define TXT_SCRIPT (1UL<<20) -#define TXT_GENERIC (1UL<<21) -#define NOIMG_NAND (1UL<<22) -#define HDR_NAND (1UL<<23) -#define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types +#define IMG_FAT (1ULL<<0) +#define IMG_NAND (1ULL<<1) +#define GAME_CIA (1ULL<<2) +#define GAME_NCSD (1ULL<<3) +#define GAME_NCCH (1ULL<<4) +#define GAME_TMD (1ULL<<5) +#define GAME_EXEFS (1ULL<<6) +#define GAME_ROMFS (1ULL<<7) +#define GAME_BOSS (1ULL<<8) +#define GAME_NUSCDN (1ULL<<9) +#define GAME_TICKET (1ULL<<10) +#define GAME_SMDH (1ULL<<11) +#define GAME_NDS (1ULL<<12) +#define GAME_GBA (1ULL<<13) +#define SYS_FIRM (1ULL<<14) +#define SYS_AGBSAVE (1ULL<<15) +#define SYS_TICKDB (1ULL<<16) +#define BIN_NCCHNFO (1ULL<<17) +#define BIN_TIKDB (1ULL<<18) +#define BIN_KEYDB (1ULL<<19) +#define BIN_LEGKEY (1ULL<<20) +#define TXT_SCRIPT (1ULL<<21) +#define TXT_GENERIC (1ULL<<22) +#define NOIMG_NAND (1ULL<<23) +#define HDR_NAND (1ULL<<24) +#define TYPE_BASE 0xFFFFFFFFULL // 32 bit reserved for base types -#define FLAG_ENC (1UL<<28) -#define FLAG_CTR (1UL<<29) -#define FLAG_NUSCDN (1UL<<30) -#define FLAG_CXI (1UL<<31) +#define FLAG_GBAVC (1ULL<<59) +#define FLAG_ENC (1ULL<<60) +#define FLAG_CTR (1ULL<<61) +#define FLAG_NUSCDN (1ULL<<62) +#define FLAG_CXI (1ULL<<63) #define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|GAME_NDS|SYS_FIRM|SYS_TICKDB|BIN_KEYDB)) #define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_BOSS|SYS_FIRM)) @@ -42,8 +44,8 @@ #define FTYPE_CXIDUMP(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_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS)) +#define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA)) +#define FTYPE_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS|GAME_GBA)) #define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR)) #define FTYPE_NCSDFIXABLE(tp) (tp&(HDR_NAND|NOIMG_NAND)) #define FTYPE_HASCODE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) @@ -56,4 +58,4 @@ #define FTYPE_INSTALLABLE(tp) (tp&(SYS_FIRM)) #define FTPYE_AGBSAVE(tp) (tp&(SYS_AGBSAVE)) -u32 IdentifyFileType(const char* path); +u64 IdentifyFileType(const char* path); diff --git a/source/filesys/fsinit.c b/source/filesys/fsinit.c index 9567a0d..893c368 100644 --- a/source/filesys/fsinit.c +++ b/source/filesys/fsinit.c @@ -44,7 +44,7 @@ bool InitImgFS(const char* path) { // deinit image filesystem DismountDriveType(DRV_IMAGE); // (re)mount image, done if path == NULL - u32 type = MountImage(path); + u64 type = MountImage(path); InitVirtualImageDrive(); if ((type&IMG_NAND) && (drv_i < NORM_FS)) drv_i = NORM_FS; else if ((type&IMG_FAT) && (drv_i < NORM_FS - IMGN_FS + 1)) drv_i = NORM_FS - IMGN_FS + 1; diff --git a/source/game/game.h b/source/game/game.h index 3ec667c..6aa11d8 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -10,4 +10,5 @@ #include "smdh.h" #include "codelzss.h" #include "nds.h" +#include "gba.h" #include "ncchinfo.h" diff --git a/source/game/gba.c b/source/game/gba.c new file mode 100644 index 0000000..46aaa04 --- /dev/null +++ b/source/game/gba.c @@ -0,0 +1,51 @@ +#include "gba.h" +#include "sha.h" +#include "sdmmc.h" + +#define AGBLOGO_SHA256 \ + 0x08, 0xA0, 0x15, 0x3C, 0xFD, 0x6B, 0x0E, 0xA5, 0x4B, 0x93, 0x8F, 0x7D, 0x20, 0x99, 0x33, 0xFA, \ + 0x84, 0x9D, 0xA0, 0xD5, 0x6F, 0x5A, 0x34, 0xC4, 0x81, 0x06, 0x0C, 0x9F, 0xF2, 0xFA, 0xD8, 0x18 + +u32 ValidateAgbSaveHeader(AgbSaveHeader* header) { + u8 magic[] = { AGBSAVE_MAGIC }; + + // basic checks + if ((memcmp(header->magic, magic, sizeof(magic)) != 0) || + (header->unknown0 != 1) || (header->save_start != 0x200) || + (header->save_size > AGBSAVE_MAX_SSIZE) || !(GBASAVE_VALID(header->save_size))) + return 1; + + // reserved area checks + for (u32 i = 0; i < sizeof(header->reserved0); i++) if (header->reserved0[i] != 0xFF) return 1; + for (u32 i = 0; i < sizeof(header->reserved1); i++) if (header->reserved1[i] != 0xFF) return 1; + for (u32 i = 0; i < sizeof(header->reserved2); i++) if (header->reserved2[i] != 0xFF) return 1; + for (u32 i = 0; i < sizeof(header->reserved3); i++) if (header->reserved3[i] != 0xFF) return 1; + + // all fine if arriving here + return 0; +} + +// http://problemkaputt.de/gbatek.htm#gbacartridgeheader +u32 ValidateAgbHeader(AgbHeader* agb) { + const u8 logo_sha[0x20] = { AGBLOGO_SHA256 }; + u8 logo[0x9C]; + + // check fixed value + if (agb->fixed != 0x96) return 1; + + // header checksum + u8* hdr = (u8*) agb; + u8 checksum = 0x00 - 0x19; + for (u32 i = 0xA0; i < 0xBD; i++) + checksum -= hdr[i]; + if (agb->checksum != checksum) return 1; + + // logo SHA check + memcpy(logo, agb->logo, 0x9C); + logo[0x98] &= ~0x84; + logo[0x9A] &= ~0x03; + if (sha_cmp(logo_sha, logo, 0x9C, SHA256_MODE) != 0) + return 1; + + return 0; +} diff --git a/source/game/gba.h b/source/game/gba.h new file mode 100644 index 0000000..d96229b --- /dev/null +++ b/source/game/gba.h @@ -0,0 +1,94 @@ +#pragma once + +#include "common.h" +#include "gba.h" + +#define GBAVC_MAGIC '.', 'C', 'A', 'A' +#define AGBSAVE_MAGIC '.', 'S', 'A', 'V' +#define AGBSAVE_MAX_SIZE (0x000180 * 0x200) // standard size of the NAND partition +#define AGBSAVE_MAX_SSIZE (AGBSAVE_MAX_SIZE - sizeof(AgbSaveHeader)) + +// see: http://3dbrew.org/wiki/3DS_Virtual_Console#Footer +#define GBASAVE_EEPROM_512 (512) +#define GBASAVE_EEPROM_8K (8 * 1024) +#define GBASAVE_SRAM_32K (32 * 1024) +#define GBASAVE_FLASH_64K (64 * 1024) +#define GBASAVE_FLASH_128K (128 * 1024) + +#define GBASAVE_SIZE(tp) \ + (((tp == 0x0) || (tp == 0x1)) ? GBASAVE_EEPROM_512 : \ + ((tp == 0x2) || (tp == 0x3)) ? GBASAVE_EEPROM_8K : \ + ((tp >= 0x4) && (tp <= 0x9)) ? GBASAVE_FLASH_64K : \ + ((tp >= 0xA) && (tp <= 0xD)) ? GBASAVE_FLASH_128K : \ + (tp == 0xE) ? GBASAVE_SRAM_32K : 0); // last one means invalid + +#define GBASAVE_VALID(size) \ + (((size) == GBASAVE_EEPROM_512) || \ + ((size) == GBASAVE_EEPROM_8K) || \ + ((size) == GBASAVE_SRAM_32K) || \ + ((size) == GBASAVE_FLASH_64K) || \ + ((size) == GBASAVE_FLASH_128K)) + +// see: http://problemkaputt.de/gbatek.htm#gbacartridgeheader +#define AGB_DESTSTR(code) \ + (((code)[3] == 'J') ? "Japan" : \ + ((code)[3] == 'E') ? "USA/English" : \ + ((code)[3] == 'P') ? "Europe/Elsewhere" : \ + ((code)[3] == 'D') ? "German" : \ + ((code)[3] == 'F') ? "French" : \ + ((code)[3] == 'I') ? "Italian" : \ + ((code)[3] == 'S') ? "Spanish" : "Unknown") + + +// see: http://3dbrew.org/wiki/3DS_Virtual_Console#Footer +// still a lot of unknowns in here, also redundant stuff left out +typedef struct { + u8 unknown0[4]; + u32 rom_size; + u32 save_type; + u8 unknown1[20]; + u32 lcd_ghosting; + u8 video_lut[0x300]; + u8 unknown2[44]; + u8 magic[4]; // ".CAA" + u8 unknown3[12]; +} __attribute__((packed)) AgbVcFooter; + +// see: http://3dbrew.org/wiki/3DS_Virtual_Console#NAND_Savegame +typedef struct { + u8 magic[4]; // ".SAV" + u8 reserved0[0xC]; // always 0xFF + u8 cmac[0x10]; + u8 reserved1[0x10]; // always 0xFF + u32 unknown0; // always 0x01 + u32 times_saved; + u64 title_id; + u8 sd_cid[0x10]; + u32 save_start; // always 0x200 + u32 save_size; + u8 reserved2[0x8]; // always 0xFF + u32 unknown1; // has to do with ARM7? + u32 unknown2; // has to do with ARM7? + u8 reserved3[0x198]; // always 0xFF +} __attribute__((packed)) AgbSaveHeader; + +// see: http://problemkaputt.de/gbatek.htm#gbacartridgeheader +typedef struct { + u32 arm7_rom_entry; + u8 logo[0x9C]; + char game_title[12]; + char game_code[4]; + char maker_code[2]; + u8 fixed; // 0x96, required! + u8 unit_code; // 0x00 for current GBA + u8 device_type; // 0x00 usually + u8 reserved0[7]; // always 0x00 + u8 software_version; // 0x00 usually + u8 checksum; // header checksum, required + u8 reserved[2]; // always 0x00 + // stuff for multiboot not included +} __attribute__((packed)) AgbHeader; + + +u32 ValidateAgbSaveHeader(AgbSaveHeader* header); +u32 ValidateAgbHeader(AgbHeader* agb); diff --git a/source/game/ncch.h b/source/game/ncch.h index 24789a8..3b0eb71 100644 --- a/source/game/ncch.h +++ b/source/game/ncch.h @@ -10,6 +10,7 @@ #define NCCH_ENCRYPTED(ncch) (!((ncch)->flags[7] & 0x04)) #define NCCH_IS_CXI(ncch) ((ncch)->flags[5] & 0x02) +#define NCCH_IS_GBAVC(exhdr) (getle32((exhdr)->aci_data + 8) == 0x00000202) #define NCCH_NOCRYPTO 0x0004 #define NCCH_STDCRYPTO 0x0000 diff --git a/source/godmode.c b/source/godmode.c index 234e626..8f1d8ec 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -835,7 +835,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan // check for file lock if (!FileUnlock(curr_entry->path)) return 1; - u32 filetype = IdentifyFileType(curr_entry->path); + u64 filetype = IdentifyFileType(curr_entry->path); u32 drvtype = DriveType(curr_entry->path); bool in_output_path = (strncmp(current_path, OUTPUT_PATH, 256) == 0); @@ -905,6 +905,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan (filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" : (filetype & GAME_SMDH) ? "Show SMDH title info" : (filetype & GAME_NDS) ? "NDS image options..." : + (filetype & GAME_GBA) ? "GBA image options..." : (filetype & GAME_TICKET)? "Ticket options..." : (filetype & SYS_FIRM ) ? "FIRM image options..." : (filetype & SYS_AGBSAVE)? (agbimportable) ? "AGBSAVE options..." : "Dump GBA VC save" : @@ -1398,9 +1399,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan return 0; } else if (user_select == extrcode) { // -> Extract code + char extstr[8] = { 0 }; ShowString("%s\nExtracting .code, please wait...", pathstr); - if (ExtractCodeFromCxiFile(curr_entry->path, NULL) == 0) { - ShowPrompt(false, "%s\n.code extracted to " OUTPUT_PATH, pathstr); + if (ExtractCodeFromCxiFile(curr_entry->path, NULL, extstr) == 0) { + ShowPrompt(false, "%s\n%s extracted to " OUTPUT_PATH, pathstr, extstr); } else ShowPrompt(false, "%s\n.code extract failed", pathstr); return 0; } diff --git a/source/nand/agbsave.c b/source/nand/agbsave.c deleted file mode 100644 index 185a4d4..0000000 --- a/source/nand/agbsave.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "agbsave.h" -#include "sha.h" -#include "aes.h" - - -u32 ValidateAgbSaveHeader(AgbSaveHeader* header) { - u8 magic[] = { AGBSAVE_MAGIC }; - - // basic checks - if ((memcmp(header->magic, magic, sizeof(magic)) != 0) || - (header->unknown0 != 1) || (header->save_start != 0x200) || - (header->save_size > AGBSAVE_MAX_SSIZE) || !(GBASAVE_VALID(header->save_size))) - return 1; - - // reserved area checks - for (u32 i = 0; i < sizeof(header->reserved0); i++) if (header->reserved0[i] != 0xFF) return 1; - for (u32 i = 0; i < sizeof(header->reserved1); i++) if (header->reserved1[i] != 0xFF) return 1; - for (u32 i = 0; i < sizeof(header->reserved2); i++) if (header->reserved2[i] != 0xFF) return 1; - for (u32 i = 0; i < sizeof(header->reserved3); i++) if (header->reserved3[i] != 0xFF) return 1; - - // all fine if arriving here - return 0; -} diff --git a/source/nand/agbsave.h b/source/nand/agbsave.h deleted file mode 100644 index cbd4b4b..0000000 --- a/source/nand/agbsave.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "common.h" -#include "nand.h" - -#define AGBSAVE_MAGIC '.', 'S', 'A', 'V' -#define AGBSAVE_MAX_SIZE (0x000180 * 0x200) // standard size of the NAND partition -#define AGBSAVE_MAX_SSIZE (AGBSAVE_MAX_SIZE - sizeof(AgbSaveHeader)) - -// see: http://3dbrew.org/wiki/3DS_Virtual_Console#GBA_VC -#define GBASAVE_EEPROM_512 (512) -#define GBASAVE_EEPROM_8K (8 * 1024) -#define GBASAVE_SRAM_32K (32 * 1024) -#define GBASAVE_FLASH_64K (64 * 1024) -#define GBASAVE_FLASH_128K (128 * 1024) -#define GBASAVE_VALID(size) \ - (((size) == GBASAVE_EEPROM_512) || \ - ((size) == GBASAVE_EEPROM_8K) || \ - ((size) == GBASAVE_SRAM_32K) || \ - ((size) == GBASAVE_FLASH_64K) || \ - ((size) == GBASAVE_FLASH_128K)) - -// see: http://3dbrew.org/wiki/3DS_Virtual_Console#NAND_Savegame -typedef struct { - u8 magic[4]; // ".SAV" - u8 reserved0[0xC]; // always 0xFF - u8 cmac[0x10]; - u8 reserved1[0x10]; // always 0xFF - u32 unknown0; // always 0x01 - u32 times_saved; - u64 title_id; - u8 sd_cid[0x10]; - u32 save_start; // always 0x200 - u32 save_size; - u8 reserved2[0x8]; // always 0xFF - u32 unknown1; // has to do with ARM7? - u32 unknown2; // has to do with ARM7? - u8 reserved3[0x198]; // always 0xFF -} __attribute__((packed)) AgbSaveHeader; - -u32 ValidateAgbSaveHeader(AgbSaveHeader* header); diff --git a/source/nand/nandcmac.c b/source/nand/nandcmac.c index 9b74a4f..bd2ae85 100644 --- a/source/nand/nandcmac.c +++ b/source/nand/nandcmac.c @@ -1,6 +1,6 @@ #include "nandcmac.h" #include "fsperm.h" -#include "agbsave.h" +#include "gba.h" #include "sha.h" #include "aes.h" #include "vff.h" diff --git a/source/utils/gameutil.c b/source/utils/gameutil.c index 8e74c1f..a440889 100644 --- a/source/utils/gameutil.c +++ b/source/utils/gameutil.c @@ -595,7 +595,7 @@ u32 VerifyBossFile(const char* path) { } u32 VerifyGameFile(const char* path) { - u32 filetype = IdentifyFileType(path); + u64 filetype = IdentifyFileType(path); if (filetype & GAME_CIA) return VerifyCiaFile(path); else if (filetype & GAME_NCSD) @@ -704,7 +704,7 @@ u32 CheckEncryptedBossFile(const char* path) { } u32 CheckEncryptedGameFile(const char* path) { - u32 filetype = IdentifyFileType(path); + u64 filetype = IdentifyFileType(path); if (filetype & GAME_CIA) return CheckEncryptedCiaFile(path); else if (filetype & GAME_NCSD) @@ -985,7 +985,7 @@ u32 CryptCdnFile(const char* orig, const char* dest, u16 crypto) { } u32 CryptGameFile(const char* path, bool inplace, bool encrypt) { - u32 filetype = IdentifyFileType(path); + u64 filetype = IdentifyFileType(path); u16 crypto = encrypt ? CRYPTO_ENCRYPT : CRYPTO_DECRYPT; char dest[256]; char* destptr = (char*) path; @@ -1345,7 +1345,7 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { } u32 BuildCiaFromGameFile(const char* path, bool force_legit) { - u32 filetype = IdentifyFileType(path); + u64 filetype = IdentifyFileType(path); char dest[256]; u32 ret = 0; @@ -1388,7 +1388,7 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { // this has very limited uses right now u32 DumpCxiSrlFromTmdFile(const char* path) { - u32 filetype = 0; + u64 filetype = 0; char path_cxi[256]; char dest[256]; @@ -1412,7 +1412,7 @@ u32 DumpCxiSrlFromTmdFile(const char* path) { return 0; } -u32 ExtractCodeFromCxiFile(const char* path, const char* path_out) { +u32 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr) { u8* code = (u8*) TEMP_BUFFER; u32 code_max_size = TEMP_BUFFER_EXTSIZE; // uses the extended temp buffer size @@ -1422,19 +1422,28 @@ u32 ExtractCodeFromCxiFile(const char* path, const char* path_out) { // load ncch, exthdr, .code u32 code_size; if ((LoadNcchHeaders(&ncch, &exthdr, NULL, path, 0) != 0) || - (LoadExeFsFile(code, path, 0, EXEFS_CODE_NAME, code_max_size, &code_size))) + ((LoadExeFsFile(code, path, 0, EXEFS_CODE_NAME, code_max_size, &code_size) != 0) && + (LoadExeFsFile(code, path, 0, ".firm", code_max_size, &code_size) != 0))) return 1; // decompress code (only if required) if ((exthdr.flag & 0x1) && (DecompressCodeLzss(code, &code_size, code_max_size) != 0)) return 1; + // decide extension + char* ext = EXEFS_CODE_NAME; + if (code_size >= 0x200) { + if (ValidateFirmHeader((FirmHeader*)(void*) code, code_size) == 0) ext = ".firm"; + else if (ValidateAgbHeader((AgbHeader*)(void*) code) == 0) ext = ".gba"; + } + if (extstr) strncpy(extstr, ext, 7); + // build or take over output path char dest[256]; if (!path_out) { // ensure the output dir exists if (fvx_rmkdir(OUTPUT_PATH) != FR_OK) return 1; - snprintf(dest, 256, OUTPUT_PATH "/%016llX%s%s", ncch.programId, (exthdr.flag & 0x1) ? ".dec" : "", EXEFS_CODE_NAME); + snprintf(dest, 256, OUTPUT_PATH "/%016llX%s%s", ncch.programId, (exthdr.flag & 0x1) ? ".dec" : "", ext); } else strncpy(dest, path_out, 256); if (!CheckWritePermissions(dest)) return 1; @@ -1449,7 +1458,7 @@ u32 ExtractCodeFromCxiFile(const char* path, const char* path_out) { } u32 LoadSmdhFromGameFile(const char* path, Smdh* smdh) { - u32 filetype = IdentifyFileType(path); + u64 filetype = IdentifyFileType(path); if (filetype & GAME_SMDH) { // SMDH file UINT btr; @@ -1496,6 +1505,17 @@ u32 ShowSmdhTitleInfo(Smdh* smdh) { return 0; } +u32 ShowGbaFileTitleInfo(const char* path) { + AgbHeader agb; + if ((fvx_qread(path, &agb, 0, sizeof(AgbHeader), NULL) != FR_OK) || + (ValidateAgbHeader(&agb) != 0)) return 1; + ShowString("%.12s (AGB-%.4s)\n%s", agb.game_title, agb.game_code, AGB_DESTSTR(agb.game_code)); + InputWait(0); + ClearScreenF(true, false, COLOR_STD_BG); + return 0; + +} + u32 ShowNdsFileTitleInfo(const char* path) { const u32 lwrap = 24; TwlIconData* twl_icon = (TwlIconData*) TEMP_BUFFER; @@ -1522,10 +1542,11 @@ u32 ShowGameFileTitleInfo(const char* path) { path = path_content; } - // try loading SMDH, then try NDS + // try loading SMDH, then try NDS / GBA if (LoadSmdhFromGameFile(path, smdh) == 0) return ShowSmdhTitleInfo(smdh); - else return ShowNdsFileTitleInfo(path); + else if (ShowNdsFileTitleInfo(path) == 0) return 0; + else return ShowGbaFileTitleInfo(path); } u32 BuildNcchInfoXorpads(const char* destdir, const char* path) { @@ -1715,7 +1736,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) { else return 0; } - u32 filetype = path_in ? IdentifyFileType(path_in) : 0; + u64 filetype = path_in ? IdentifyFileType(path_in) : 0; if (filetype & GAME_TICKET) { Ticket* ticket = (Ticket*) TEMP_BUFFER; if ((fvx_qread(path_in, ticket, 0, TICKET_SIZE, &br) != FR_OK) || (br != TICKET_SIZE) || @@ -1852,7 +1873,7 @@ u32 BuildSeedInfo(const char* path, bool dump) { } u32 LoadNcchFromGameFile(const char* path, NcchHeader* ncch) { - u32 filetype = IdentifyFileType(path); + u64 filetype = IdentifyFileType(path); UINT br; if (filetype & GAME_NCCH) { @@ -1899,14 +1920,16 @@ u32 GetGoodName(char* name, const char* path, bool quick) { // name scheme (TWL+ICON): () () (). // name scheme (NTR): (). // name scheme (TWL): (). + // name scheme (AGB): (). const char* path_donor = path; - u32 type_donor = IdentifyFileType(path); + u64 type_donor = IdentifyFileType(path); char* ext = (type_donor & GAME_CIA) ? "cia" : (type_donor & GAME_NCSD) ? "3ds" : (type_donor & GAME_NCCH) ? ((type_donor & FLAG_CXI) ? "cxi" : "cfa") : (type_donor & GAME_NDS) ? "nds" : + (type_donor & GAME_GBA) ? "gba" : (type_donor & GAME_TMD) ? "tmd" : ""; if (!*ext) return 1; @@ -1925,7 +1948,11 @@ u32 GetGoodName(char* name, const char* path, bool quick) { type_donor = IdentifyFileType(path_donor); } - if (type_donor & GAME_NDS) { // NTR or TWL + if (type_donor & GAME_GBA) { // AGB + AgbHeader* agb = (AgbHeader*) TEMP_BUFFER; + if (fvx_qread(path_donor, agb, 0, sizeof(AgbHeader), NULL) != FR_OK) return 1; + snprintf(name, 128, "%.12s (AGB-%.4s).%s", agb->game_title, agb->game_code, ext); + } else if (type_donor & GAME_NDS) { // NTR or TWL TwlHeader* twl = (TwlHeader*) TEMP_BUFFER; TwlIconData* icon = (TwlIconData*) (TEMP_BUFFER + sizeof(TwlHeader)); if (LoadTwlMetaData(path_donor, twl, quick ? NULL : icon) != 0) return 1; diff --git a/source/utils/gameutil.h b/source/utils/gameutil.h index a5c8b7d..b8a047b 100644 --- a/source/utils/gameutil.h +++ b/source/utils/gameutil.h @@ -7,7 +7,7 @@ u32 CheckEncryptedGameFile(const char* path); u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 DumpCxiSrlFromTmdFile(const char* path); -u32 ExtractCodeFromCxiFile(const char* path, const char* path_out); +u32 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr); u32 ShowGameFileTitleInfo(const char* path); u32 BuildNcchInfoXorpads(const char* destdir, const char* path); u32 CheckHealthAndSafetyInject(const char* hsdrv); diff --git a/source/utils/keydbutil.c b/source/utils/keydbutil.c index 554a663..6d34ee2 100644 --- a/source/utils/keydbutil.c +++ b/source/utils/keydbutil.c @@ -78,7 +78,7 @@ u32 BuildKeyDb(const char* path, bool dump) { else return 0; } - u32 filetype = path_in ? IdentifyFileType(path_in) : 0; + u64 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) || diff --git a/source/utils/nandutil.c b/source/utils/nandutil.c index 8a413e5..53c0ca6 100644 --- a/source/utils/nandutil.c +++ b/source/utils/nandutil.c @@ -5,7 +5,7 @@ #include "fatmbr.h" #include "essentials.h" // for essential backup struct #include "nandcmac.h" -#include "agbsave.h" +#include "gba.h" #include "image.h" #include "fsinit.h" #include "fsperm.h" diff --git a/source/utils/scripting.c b/source/utils/scripting.c index 1f424e6..417edb0 100644 --- a/source/utils/scripting.c +++ b/source/utils/scripting.c @@ -689,19 +689,19 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { if (err_str) snprintf(err_str, _ERR_STR_LEN, "fixcmac failed"); } else if (id == CMD_ID_VERIFY) { - u32 filetype = IdentifyFileType(argv[0]); + u64 filetype = IdentifyFileType(argv[0]); if (filetype & IMG_NAND) ret = (ValidateNandDump(argv[0]) == 0); else ret = (VerifyGameFile(argv[0]) == 0); if (err_str) snprintf(err_str, _ERR_STR_LEN, "verification failed"); } else if (id == CMD_ID_DECRYPT) { - u32 filetype = IdentifyFileType(argv[0]); + u64 filetype = IdentifyFileType(argv[0]); if (filetype & BIN_KEYDB) ret = (CryptAesKeyDb(argv[0], true, false) == 0); else ret = (CryptGameFile(argv[0], true, false) == 0); if (err_str) snprintf(err_str, _ERR_STR_LEN, "decrypt failed"); } else if (id == CMD_ID_ENCRYPT) { - u32 filetype = IdentifyFileType(argv[0]); + u64 filetype = IdentifyFileType(argv[0]); if (filetype & BIN_KEYDB) ret = (CryptAesKeyDb(argv[0], true, true) == 0); else ret = (CryptGameFile(argv[0], true, true) == 0); if (err_str) snprintf(err_str, _ERR_STR_LEN, "encrypt failed"); @@ -711,13 +711,13 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { if (err_str) snprintf(err_str, _ERR_STR_LEN, "build CIA failed"); } else if (id == CMD_ID_EXTRCODE) { - u32 filetype = IdentifyFileType(argv[0]); + u64 filetype = IdentifyFileType(argv[0]); if ((filetype&(GAME_NCCH|FLAG_CXI)) != (GAME_NCCH|FLAG_CXI)) { ret = false; if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a CXI file"); } else { ShowString("Extracting .code, please wait..."); - ret = (ExtractCodeFromCxiFile(argv[0], argv[1]) == 0); + ret = (ExtractCodeFromCxiFile(argv[0], argv[1], NULL) == 0); if (err_str) snprintf(err_str, _ERR_STR_LEN, "extract .code failed"); } } diff --git a/source/virtual/vgame.c b/source/virtual/vgame.c index fdd2d9d..8010bfc 100644 --- a/source/virtual/vgame.c +++ b/source/virtual/vgame.c @@ -61,7 +61,7 @@ #define NAME_NDS_DATADIR "data" -static u32 vgame_type = 0; +static u64 vgame_type = 0; static u32 base_vdir = 0; static VirtualFile* templates_cia = (VirtualFile*) VGAME_BUFFER; // first 56kb reserved (enough for 1024 entries) @@ -634,8 +634,8 @@ bool BuildVGameFirmDir(void) { return true; } -u32 InitVGameDrive(void) { // prerequisite: game file mounted as image - u32 type = GetMountState(); +u64 InitVGameDrive(void) { // prerequisite: game file mounted as image + u64 type = GetMountState(); vgame_type = 0; offset_firm = (u64) -1; diff --git a/source/virtual/vgame.h b/source/virtual/vgame.h index f7b8c02..63362fe 100644 --- a/source/virtual/vgame.h +++ b/source/virtual/vgame.h @@ -4,7 +4,7 @@ #include "filetype.h" #include "virtual.h" -u32 InitVGameDrive(void); +u64 InitVGameDrive(void); u32 CheckVGameDrive(void); bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry); diff --git a/source/virtual/vnand.c b/source/virtual/vnand.c index f2c04e8..323fef8 100644 --- a/source/virtual/vnand.c +++ b/source/virtual/vnand.c @@ -1,6 +1,5 @@ #include "vnand.h" #include "nand.h" -#include "agbsave.h" #include "essentials.h" #include "unittype.h"