Limited GBA rom image support

... support for:
* proper extension when extracting from .code
* showing title info
* renaming cartridge images
This commit is contained in:
d0k3 2017-10-16 02:02:24 +02:00
parent 5462f2d5e8
commit 5d4d122543
21 changed files with 263 additions and 141 deletions

View File

@ -2,7 +2,7 @@
#include "vff.h" #include "vff.h"
static FIL mount_file; static FIL mount_file;
static u32 mount_state = 0; static u64 mount_state = 0;
static char mount_path[256] = { 0 }; static char mount_path[256] = { 0 };
@ -47,7 +47,7 @@ u64 GetMountSize(void) {
return mount_state ? fvx_size(&mount_file) : 0; return mount_state ? fvx_size(&mount_file) : 0;
} }
u32 GetMountState(void) { u64 GetMountState(void) {
return mount_state; return mount_state;
} }
@ -55,8 +55,8 @@ const char* GetMountPath(void) {
return mount_path; return mount_path;
} }
u32 MountImage(const char* path) { u64 MountImage(const char* path) {
u32 type = (path) ? IdentifyFileType(path) : 0; u64 type = (path) ? IdentifyFileType(path) : 0;
if (mount_state) { if (mount_state) {
fvx_close(&mount_file); fvx_close(&mount_file);
mount_state = 0; mount_state = 0;

View File

@ -10,6 +10,6 @@ int WriteImageSectors(const void* buffer, u32 sector, u32 count);
int SyncImage(void); int SyncImage(void);
u64 GetMountSize(void); u64 GetMountSize(void);
u32 GetMountState(void); u64 GetMountState(void);
const char* GetMountPath(void); const char* GetMountPath(void);
u32 MountImage(const char* path); u64 MountImage(const char* path);

View File

@ -3,12 +3,11 @@
#include "fatmbr.h" #include "fatmbr.h"
#include "nand.h" #include "nand.h"
#include "game.h" #include "game.h"
#include "agbsave.h"
#include "keydb.h" #include "keydb.h"
#include "ctrtransfer.h" #include "ctrtransfer.h"
#include "scripting.h" #include "scripting.h"
u32 IdentifyFileType(const char* path) { u64 IdentifyFileType(const char* path) {
const u8 romfs_magic[] = { ROMFS_MAGIC }; const u8 romfs_magic[] = { ROMFS_MAGIC };
const u8 tickdb_magic[] = { TICKDB_MAGIC }; const u8 tickdb_magic[] = { TICKDB_MAGIC };
const u8 smdh_magic[] = { SMDH_MAGIC }; const u8 smdh_magic[] = { SMDH_MAGIC };
@ -20,7 +19,7 @@ u32 IdentifyFileType(const char* path) {
char* ext = (fname) ? strrchr(++fname, '.') : NULL; char* ext = (fname) ? strrchr(++fname, '.') : NULL;
u32 id = 0; u32 id = 0;
if (ext) ext++; 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) return 0;
if (fsize >= 0x200) { if (fsize >= 0x200) {
@ -49,7 +48,15 @@ u32 IdentifyFileType(const char* path) {
return GAME_NCSD; // NCSD (".3DS") file return GAME_NCSD; // NCSD (".3DS") file
} else if (ValidateNcchHeader((NcchHeader*) data) == 0) { } else if (ValidateNcchHeader((NcchHeader*) data) == 0) {
NcchHeader* ncch = (NcchHeader*) data; 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)) if (fsize >= (ncch->size * NCCH_MEDIA_UNIT))
return type; // NCCH (".APP") file return type; // NCCH (".APP") file
} else if (ValidateExeFsHeader((ExeFsHeader*) data, fsize) == 0) { } 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)) { (ValidateBossHeader((BossHeader*) data, fsize) == 0)) {
return GAME_BOSS; // BOSS (SpotPass) file return GAME_BOSS; // BOSS (SpotPass) file
} else if ((fsize > sizeof(NcchInfoHeader)) && } else if ((fsize > sizeof(NcchInfoHeader)) &&
(GetNcchInfoVersion((NcchInfoHeader*) data)) && (GetNcchInfoVersion((NcchInfoHeader*) data)) &&
fname && (strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) { fname && (strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) {
return BIN_NCCHNFO; // ncchinfo.bin file return BIN_NCCHNFO; // ncchinfo.bin file
} else if (ext && ((strncasecmp(ext, "cdn", 4) == 0) || (strncasecmp(ext, "nus", 4) == 0))) { } else if (ext && ((strncasecmp(ext, "cdn", 4) == 0) || (strncasecmp(ext, "nus", 4) == 0))) {
char path_cetk[256]; char path_cetk[256];
char* ext_cetk = path_cetk + (ext - path); char* ext_cetk = path_cetk + (ext - path);

View File

@ -2,36 +2,38 @@
#include "common.h" #include "common.h"
#define IMG_FAT (1UL<<0) #define IMG_FAT (1ULL<<0)
#define IMG_NAND (1UL<<1) #define IMG_NAND (1ULL<<1)
#define GAME_CIA (1UL<<2) #define GAME_CIA (1ULL<<2)
#define GAME_NCSD (1UL<<3) #define GAME_NCSD (1ULL<<3)
#define GAME_NCCH (1UL<<4) #define GAME_NCCH (1ULL<<4)
#define GAME_TMD (1UL<<5) #define GAME_TMD (1ULL<<5)
#define GAME_EXEFS (1UL<<6) #define GAME_EXEFS (1ULL<<6)
#define GAME_ROMFS (1UL<<7) #define GAME_ROMFS (1ULL<<7)
#define GAME_BOSS (1UL<<8) #define GAME_BOSS (1ULL<<8)
#define GAME_NUSCDN (1UL<<9) #define GAME_NUSCDN (1ULL<<9)
#define GAME_TICKET (1UL<<10) #define GAME_TICKET (1ULL<<10)
#define GAME_SMDH (1UL<<11) #define GAME_SMDH (1ULL<<11)
#define GAME_NDS (1UL<<12) #define GAME_NDS (1ULL<<12)
#define SYS_FIRM (1UL<<13) #define GAME_GBA (1ULL<<13)
#define SYS_AGBSAVE (1UL<<14) #define SYS_FIRM (1ULL<<14)
#define SYS_TICKDB (1UL<<15) #define SYS_AGBSAVE (1ULL<<15)
#define BIN_NCCHNFO (1UL<<16) #define SYS_TICKDB (1ULL<<16)
#define BIN_TIKDB (1UL<<17) #define BIN_NCCHNFO (1ULL<<17)
#define BIN_KEYDB (1UL<<18) #define BIN_TIKDB (1ULL<<18)
#define BIN_LEGKEY (1UL<<19) #define BIN_KEYDB (1ULL<<19)
#define TXT_SCRIPT (1UL<<20) #define BIN_LEGKEY (1ULL<<20)
#define TXT_GENERIC (1UL<<21) #define TXT_SCRIPT (1ULL<<21)
#define NOIMG_NAND (1UL<<22) #define TXT_GENERIC (1ULL<<22)
#define HDR_NAND (1UL<<23) #define NOIMG_NAND (1ULL<<23)
#define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types #define HDR_NAND (1ULL<<24)
#define TYPE_BASE 0xFFFFFFFFULL // 32 bit reserved for base types
#define FLAG_ENC (1UL<<28) #define FLAG_GBAVC (1ULL<<59)
#define FLAG_CTR (1UL<<29) #define FLAG_ENC (1ULL<<60)
#define FLAG_NUSCDN (1UL<<30) #define FLAG_CTR (1ULL<<61)
#define FLAG_CXI (1UL<<31) #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 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)) #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_CXIDUMP(tp) (tp&(GAME_TMD))
#define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) #define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB))
#define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY)) #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_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)) #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_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR))
#define FTYPE_NCSDFIXABLE(tp) (tp&(HDR_NAND|NOIMG_NAND)) #define FTYPE_NCSDFIXABLE(tp) (tp&(HDR_NAND|NOIMG_NAND))
#define FTYPE_HASCODE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) #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 FTYPE_INSTALLABLE(tp) (tp&(SYS_FIRM))
#define FTPYE_AGBSAVE(tp) (tp&(SYS_AGBSAVE)) #define FTPYE_AGBSAVE(tp) (tp&(SYS_AGBSAVE))
u32 IdentifyFileType(const char* path); u64 IdentifyFileType(const char* path);

View File

@ -44,7 +44,7 @@ bool InitImgFS(const char* path) {
// deinit image filesystem // deinit image filesystem
DismountDriveType(DRV_IMAGE); DismountDriveType(DRV_IMAGE);
// (re)mount image, done if path == NULL // (re)mount image, done if path == NULL
u32 type = MountImage(path); u64 type = MountImage(path);
InitVirtualImageDrive(); InitVirtualImageDrive();
if ((type&IMG_NAND) && (drv_i < NORM_FS)) drv_i = NORM_FS; 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; else if ((type&IMG_FAT) && (drv_i < NORM_FS - IMGN_FS + 1)) drv_i = NORM_FS - IMGN_FS + 1;

View File

@ -10,4 +10,5 @@
#include "smdh.h" #include "smdh.h"
#include "codelzss.h" #include "codelzss.h"
#include "nds.h" #include "nds.h"
#include "gba.h"
#include "ncchinfo.h" #include "ncchinfo.h"

51
source/game/gba.c Normal file
View File

@ -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;
}

94
source/game/gba.h Normal file
View File

@ -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);

View File

@ -10,6 +10,7 @@
#define NCCH_ENCRYPTED(ncch) (!((ncch)->flags[7] & 0x04)) #define NCCH_ENCRYPTED(ncch) (!((ncch)->flags[7] & 0x04))
#define NCCH_IS_CXI(ncch) ((ncch)->flags[5] & 0x02) #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_NOCRYPTO 0x0004
#define NCCH_STDCRYPTO 0x0000 #define NCCH_STDCRYPTO 0x0000

View File

@ -835,7 +835,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
// check for file lock // check for file lock
if (!FileUnlock(curr_entry->path)) return 1; 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); u32 drvtype = DriveType(curr_entry->path);
bool in_output_path = (strncmp(current_path, OUTPUT_PATH, 256) == 0); 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_NUSCDN)? "Decrypt NUS/CDN file" :
(filetype & GAME_SMDH) ? "Show SMDH title info" : (filetype & GAME_SMDH) ? "Show SMDH title info" :
(filetype & GAME_NDS) ? "NDS image options..." : (filetype & GAME_NDS) ? "NDS image options..." :
(filetype & GAME_GBA) ? "GBA image options..." :
(filetype & GAME_TICKET)? "Ticket options..." : (filetype & GAME_TICKET)? "Ticket options..." :
(filetype & SYS_FIRM ) ? "FIRM image options..." : (filetype & SYS_FIRM ) ? "FIRM image options..." :
(filetype & SYS_AGBSAVE)? (agbimportable) ? "AGBSAVE options..." : "Dump GBA VC save" : (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; return 0;
} }
else if (user_select == extrcode) { // -> Extract code else if (user_select == extrcode) { // -> Extract code
char extstr[8] = { 0 };
ShowString("%s\nExtracting .code, please wait...", pathstr); ShowString("%s\nExtracting .code, please wait...", pathstr);
if (ExtractCodeFromCxiFile(curr_entry->path, NULL) == 0) { if (ExtractCodeFromCxiFile(curr_entry->path, NULL, extstr) == 0) {
ShowPrompt(false, "%s\n.code extracted to " OUTPUT_PATH, pathstr); ShowPrompt(false, "%s\n%s extracted to " OUTPUT_PATH, pathstr, extstr);
} else ShowPrompt(false, "%s\n.code extract failed", pathstr); } else ShowPrompt(false, "%s\n.code extract failed", pathstr);
return 0; return 0;
} }

View File

@ -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;
}

View File

@ -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);

View File

@ -1,6 +1,6 @@
#include "nandcmac.h" #include "nandcmac.h"
#include "fsperm.h" #include "fsperm.h"
#include "agbsave.h" #include "gba.h"
#include "sha.h" #include "sha.h"
#include "aes.h" #include "aes.h"
#include "vff.h" #include "vff.h"

View File

@ -595,7 +595,7 @@ u32 VerifyBossFile(const char* path) {
} }
u32 VerifyGameFile(const char* path) { u32 VerifyGameFile(const char* path) {
u32 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
if (filetype & GAME_CIA) if (filetype & GAME_CIA)
return VerifyCiaFile(path); return VerifyCiaFile(path);
else if (filetype & GAME_NCSD) else if (filetype & GAME_NCSD)
@ -704,7 +704,7 @@ u32 CheckEncryptedBossFile(const char* path) {
} }
u32 CheckEncryptedGameFile(const char* path) { u32 CheckEncryptedGameFile(const char* path) {
u32 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
if (filetype & GAME_CIA) if (filetype & GAME_CIA)
return CheckEncryptedCiaFile(path); return CheckEncryptedCiaFile(path);
else if (filetype & GAME_NCSD) 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 CryptGameFile(const char* path, bool inplace, bool encrypt) {
u32 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
u16 crypto = encrypt ? CRYPTO_ENCRYPT : CRYPTO_DECRYPT; u16 crypto = encrypt ? CRYPTO_ENCRYPT : CRYPTO_DECRYPT;
char dest[256]; char dest[256];
char* destptr = (char*) path; 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 BuildCiaFromGameFile(const char* path, bool force_legit) {
u32 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
char dest[256]; char dest[256];
u32 ret = 0; u32 ret = 0;
@ -1388,7 +1388,7 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
// this has very limited uses right now // this has very limited uses right now
u32 DumpCxiSrlFromTmdFile(const char* path) { u32 DumpCxiSrlFromTmdFile(const char* path) {
u32 filetype = 0; u64 filetype = 0;
char path_cxi[256]; char path_cxi[256];
char dest[256]; char dest[256];
@ -1412,7 +1412,7 @@ u32 DumpCxiSrlFromTmdFile(const char* path) {
return 0; 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; u8* code = (u8*) TEMP_BUFFER;
u32 code_max_size = TEMP_BUFFER_EXTSIZE; // uses the extended temp buffer size 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 // load ncch, exthdr, .code
u32 code_size; u32 code_size;
if ((LoadNcchHeaders(&ncch, &exthdr, NULL, path, 0) != 0) || 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; return 1;
// decompress code (only if required) // decompress code (only if required)
if ((exthdr.flag & 0x1) && (DecompressCodeLzss(code, &code_size, code_max_size) != 0)) if ((exthdr.flag & 0x1) && (DecompressCodeLzss(code, &code_size, code_max_size) != 0))
return 1; 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 // build or take over output path
char dest[256]; char dest[256];
if (!path_out) { if (!path_out) {
// ensure the output dir exists // ensure the output dir exists
if (fvx_rmkdir(OUTPUT_PATH) != FR_OK) return 1; 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); } else strncpy(dest, path_out, 256);
if (!CheckWritePermissions(dest)) return 1; 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 LoadSmdhFromGameFile(const char* path, Smdh* smdh) {
u32 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
if (filetype & GAME_SMDH) { // SMDH file if (filetype & GAME_SMDH) { // SMDH file
UINT btr; UINT btr;
@ -1496,6 +1505,17 @@ u32 ShowSmdhTitleInfo(Smdh* smdh) {
return 0; 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) { u32 ShowNdsFileTitleInfo(const char* path) {
const u32 lwrap = 24; const u32 lwrap = 24;
TwlIconData* twl_icon = (TwlIconData*) TEMP_BUFFER; TwlIconData* twl_icon = (TwlIconData*) TEMP_BUFFER;
@ -1522,10 +1542,11 @@ u32 ShowGameFileTitleInfo(const char* path) {
path = path_content; path = path_content;
} }
// try loading SMDH, then try NDS // try loading SMDH, then try NDS / GBA
if (LoadSmdhFromGameFile(path, smdh) == 0) if (LoadSmdhFromGameFile(path, smdh) == 0)
return ShowSmdhTitleInfo(smdh); 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) { u32 BuildNcchInfoXorpads(const char* destdir, const char* path) {
@ -1715,7 +1736,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
else return 0; else return 0;
} }
u32 filetype = path_in ? IdentifyFileType(path_in) : 0; u64 filetype = path_in ? IdentifyFileType(path_in) : 0;
if (filetype & GAME_TICKET) { if (filetype & GAME_TICKET) {
Ticket* ticket = (Ticket*) TEMP_BUFFER; Ticket* ticket = (Ticket*) TEMP_BUFFER;
if ((fvx_qread(path_in, ticket, 0, TICKET_SIZE, &br) != FR_OK) || (br != TICKET_SIZE) || 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 LoadNcchFromGameFile(const char* path, NcchHeader* ncch) {
u32 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
UINT br; UINT br;
if (filetype & GAME_NCCH) { if (filetype & GAME_NCCH) {
@ -1899,14 +1920,16 @@ u32 GetGoodName(char* name, const char* path, bool quick) {
// name scheme (TWL+ICON): <title_id> <title_name> (<product_code>) (<DSi unitcode>) (<region>).<extension> // name scheme (TWL+ICON): <title_id> <title_name> (<product_code>) (<DSi unitcode>) (<region>).<extension>
// name scheme (NTR): <name_short> (<product_code>).<extension> // name scheme (NTR): <name_short> (<product_code>).<extension>
// name scheme (TWL): <title_id> (<product_code>).<extension> // name scheme (TWL): <title_id> (<product_code>).<extension>
// name scheme (AGB): <name_short> (<product_code>).<extension>
const char* path_donor = path; const char* path_donor = path;
u32 type_donor = IdentifyFileType(path); u64 type_donor = IdentifyFileType(path);
char* ext = char* ext =
(type_donor & GAME_CIA) ? "cia" : (type_donor & GAME_CIA) ? "cia" :
(type_donor & GAME_NCSD) ? "3ds" : (type_donor & GAME_NCSD) ? "3ds" :
(type_donor & GAME_NCCH) ? ((type_donor & FLAG_CXI) ? "cxi" : "cfa") : (type_donor & GAME_NCCH) ? ((type_donor & FLAG_CXI) ? "cxi" : "cfa") :
(type_donor & GAME_NDS) ? "nds" : (type_donor & GAME_NDS) ? "nds" :
(type_donor & GAME_GBA) ? "gba" :
(type_donor & GAME_TMD) ? "tmd" : ""; (type_donor & GAME_TMD) ? "tmd" : "";
if (!*ext) return 1; if (!*ext) return 1;
@ -1925,7 +1948,11 @@ u32 GetGoodName(char* name, const char* path, bool quick) {
type_donor = IdentifyFileType(path_donor); 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; TwlHeader* twl = (TwlHeader*) TEMP_BUFFER;
TwlIconData* icon = (TwlIconData*) (TEMP_BUFFER + sizeof(TwlHeader)); TwlIconData* icon = (TwlIconData*) (TEMP_BUFFER + sizeof(TwlHeader));
if (LoadTwlMetaData(path_donor, twl, quick ? NULL : icon) != 0) return 1; if (LoadTwlMetaData(path_donor, twl, quick ? NULL : icon) != 0) return 1;

View File

@ -7,7 +7,7 @@ u32 CheckEncryptedGameFile(const char* path);
u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 CryptGameFile(const char* path, bool inplace, bool encrypt);
u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 BuildCiaFromGameFile(const char* path, bool force_legit);
u32 DumpCxiSrlFromTmdFile(const char* path); 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 ShowGameFileTitleInfo(const char* path);
u32 BuildNcchInfoXorpads(const char* destdir, const char* path); u32 BuildNcchInfoXorpads(const char* destdir, const char* path);
u32 CheckHealthAndSafetyInject(const char* hsdrv); u32 CheckHealthAndSafetyInject(const char* hsdrv);

View File

@ -78,7 +78,7 @@ u32 BuildKeyDb(const char* path, bool dump) {
else return 0; 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 if (filetype & BIN_KEYDB) { // AES key database
AesKeyInfo* key_info_merge = (AesKeyInfo*) TEMP_BUFFER; AesKeyInfo* key_info_merge = (AesKeyInfo*) TEMP_BUFFER;
if ((fvx_qread(path_in, key_info_merge, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) || if ((fvx_qread(path_in, key_info_merge, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) ||

View File

@ -5,7 +5,7 @@
#include "fatmbr.h" #include "fatmbr.h"
#include "essentials.h" // for essential backup struct #include "essentials.h" // for essential backup struct
#include "nandcmac.h" #include "nandcmac.h"
#include "agbsave.h" #include "gba.h"
#include "image.h" #include "image.h"
#include "fsinit.h" #include "fsinit.h"
#include "fsperm.h" #include "fsperm.h"

View File

@ -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"); if (err_str) snprintf(err_str, _ERR_STR_LEN, "fixcmac failed");
} }
else if (id == CMD_ID_VERIFY) { 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); if (filetype & IMG_NAND) ret = (ValidateNandDump(argv[0]) == 0);
else ret = (VerifyGameFile(argv[0]) == 0); else ret = (VerifyGameFile(argv[0]) == 0);
if (err_str) snprintf(err_str, _ERR_STR_LEN, "verification failed"); if (err_str) snprintf(err_str, _ERR_STR_LEN, "verification failed");
} }
else if (id == CMD_ID_DECRYPT) { 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); 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) == 0);
if (err_str) snprintf(err_str, _ERR_STR_LEN, "decrypt failed"); if (err_str) snprintf(err_str, _ERR_STR_LEN, "decrypt failed");
} }
else if (id == CMD_ID_ENCRYPT) { 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); 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) == 0);
if (err_str) snprintf(err_str, _ERR_STR_LEN, "encrypt failed"); 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"); if (err_str) snprintf(err_str, _ERR_STR_LEN, "build CIA failed");
} }
else if (id == CMD_ID_EXTRCODE) { 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)) { if ((filetype&(GAME_NCCH|FLAG_CXI)) != (GAME_NCCH|FLAG_CXI)) {
ret = false; ret = false;
if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a CXI file"); if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a CXI file");
} else { } else {
ShowString("Extracting .code, please wait..."); 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"); if (err_str) snprintf(err_str, _ERR_STR_LEN, "extract .code failed");
} }
} }

View File

@ -61,7 +61,7 @@
#define NAME_NDS_DATADIR "data" #define NAME_NDS_DATADIR "data"
static u32 vgame_type = 0; static u64 vgame_type = 0;
static u32 base_vdir = 0; static u32 base_vdir = 0;
static VirtualFile* templates_cia = (VirtualFile*) VGAME_BUFFER; // first 56kb reserved (enough for 1024 entries) static VirtualFile* templates_cia = (VirtualFile*) VGAME_BUFFER; // first 56kb reserved (enough for 1024 entries)
@ -634,8 +634,8 @@ bool BuildVGameFirmDir(void) {
return true; return true;
} }
u32 InitVGameDrive(void) { // prerequisite: game file mounted as image u64 InitVGameDrive(void) { // prerequisite: game file mounted as image
u32 type = GetMountState(); u64 type = GetMountState();
vgame_type = 0; vgame_type = 0;
offset_firm = (u64) -1; offset_firm = (u64) -1;

View File

@ -4,7 +4,7 @@
#include "filetype.h" #include "filetype.h"
#include "virtual.h" #include "virtual.h"
u32 InitVGameDrive(void); u64 InitVGameDrive(void);
u32 CheckVGameDrive(void); u32 CheckVGameDrive(void);
bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry); bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry);

View File

@ -1,6 +1,5 @@
#include "vnand.h" #include "vnand.h"
#include "nand.h" #include "nand.h"
#include "agbsave.h"
#include "essentials.h" #include "essentials.h"
#include "unittype.h" #include "unittype.h"