diff --git a/source/fs/filetype.c b/source/fs/filetype.c index 72acffc..23a5938 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -63,6 +63,8 @@ u32 IdentifyFileType(const char* path) { return SYS_TICKDB; // ticket.db } else if (memcmp(header, smdh_magic, sizeof(smdh_magic)) == 0) { return GAME_SMDH; // SMDH file + } else if (ValidateTwlHeader((TwlHeader*) data) == 0) { + return GAME_NDS; // NDS rom file } } if ((fsize > sizeof(BossHeader)) && diff --git a/source/fs/filetype.h b/source/fs/filetype.h index 3f4f627..d9cd92a 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -14,11 +14,12 @@ #define GAME_NUSCDN (1UL<<9) #define GAME_TICKET (1UL<<10) #define GAME_SMDH (1UL<<11) -#define SYS_FIRM (1UL<<12) -#define SYS_TICKDB (1UL<<13) -#define BIN_NCCHNFO (1UL<<14) -#define BIN_LAUNCH (1UL<<15) -#define BIN_SUPPORT (1UL<<16) +#define GAME_NDS (1UL<<12) +#define SYS_FIRM (1UL<<13) +#define SYS_TICKDB (1UL<<14) +#define BIN_NCCHNFO (1UL<<15) +#define BIN_LAUNCH (1UL<<16) +#define BIN_SUPPORT (1UL<<17) #define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types #define FLAG_CTR (1UL<<29) @@ -31,7 +32,7 @@ #define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS)) #define FTYPE_BUILDABLE(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) #define FTYPE_BUILDABLE_L(tp) (FTYPE_BUILDABLE(tp) && (tp&(GAME_TMD))) -#define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD)) +#define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS)) #define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR)) #define FTYPE_HSINJECTABLE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) #define FTYPE_RESTORABLE(tp) (tp&(IMG_NAND)) diff --git a/source/game/game.h b/source/game/game.h index af40958..b5f858f 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -8,4 +8,5 @@ #include "firm.h" #include "boss.h" #include "smdh.h" +#include "nds.h" #include "ncchinfo.h" diff --git a/source/game/gameutil.c b/source/game/gameutil.c index 8e3aada..7a3af30 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -1455,6 +1455,23 @@ u32 ShowTmdFileTitleInfo(const char* path) { return ShowGameFileTitleInfo(path_content); } +u32 ShowNdsFileTitleInfo(const char* path) { + const u32 lwrap = 24; + TwlIconData* twl_icon = (TwlIconData*) TEMP_BUFFER; + u8* icon = (u8*) (TEMP_BUFFER + sizeof(TwlIconData)); + char* desc = (char*) icon + TWLICON_SIZE_ICON; + if ((LoadTwlIconData(path, twl_icon) != 0) || + (GetTwlIcon(icon, twl_icon) != 0) || + (GetTwlTitle(desc, twl_icon) != 0)) + return 1; + WordWrapString(desc, lwrap); + ShowIconString(icon, TWLICON_DIM_ICON, TWLICON_DIM_ICON, "%s", desc); + InputWait(); + ClearScreenF(true, false, COLOR_STD_BG); + return 0; + +} + u32 ShowGameFileTitleInfo(const char* path) { u32 filetype = IdentifyFileType(path); u32 ret = 1; @@ -1470,6 +1487,8 @@ u32 ShowGameFileTitleInfo(const char* path) { ret = ShowCiaFileTitleInfo(path); } else if (filetype & GAME_TMD) { ret = ShowTmdFileTitleInfo(path); + } else if (filetype & GAME_NDS) { + ret = ShowNdsFileTitleInfo(path); } return ret; diff --git a/source/game/nds.c b/source/game/nds.c new file mode 100644 index 0000000..0a55b19 --- /dev/null +++ b/source/game/nds.c @@ -0,0 +1,75 @@ +#include "nds.h" +#include "vff.h" + +#define CRC16_TABVAL 0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400 + +// see: https://github.com/TASVideos/desmume/blob/master/desmume/src/bios.cpp#L1070tions +u16 crc16_quick(const void* src, u32 len) { + const u16 tabval[] = { CRC16_TABVAL }; + u16* data = (u16*) src; + u16 crc = 0xFFFF; + + for (len >>= 1; len; len--) { + u16 curr = *(data++); + for (u32 i = 0; i < 4; i++) { + u16 tval = tabval[crc&0xF]; + crc >>= 4; + crc ^= tval; + tval = tabval[(curr >> (4*i))&0xF]; + crc ^= tval; + } + } + + return crc; +} + +u32 ValidateTwlHeader(TwlHeader* twl) { + if (twl->logo_crc != NDS_LOGO_CRC16) return 1; + return (crc16_quick(twl->logo, sizeof(twl->logo)) == NDS_LOGO_CRC16) ? 0 : 1; +} + +u32 LoadTwlIconData(const char* path, TwlIconData* icon) { + u8 ntr_header[0x200]; // we only need the NTR header (ignore TWL stuff) + TwlHeader* twl = (TwlHeader*) ntr_header; + UINT br; + if ((fvx_qread(path, ntr_header, 0, 0x200, &br) != FR_OK) || (br != 0x200) || + (ValidateTwlHeader(twl) != 0)) + return 1; + // we also don't need anything beyond the v0x0001 icon, so ignore this, too + if ((fvx_qread(path, icon, twl->icon_offset, TWLICON_SIZE_DATA(0x0001), &br) != FR_OK) || (br != TWLICON_SIZE_DATA(0x0001)) || + (!TWLICON_SIZE_DATA(icon->version)) || (crc16_quick(((u8*) icon) + 0x20, TWLICON_SIZE_DATA(0x0001) - 0x20) != icon->crc_0x0020_0x0840)) + return 1; + icon->version = 0x0001; // just to be safe + return 0; +} + +// TWL title is max 128(+1) chars long +u32 GetTwlTitle(char* desc, const TwlIconData* twl_icon) { + const u16* title = twl_icon->title_eng; // english title + memset(desc, 0, TWLICON_SIZE_DESC + 1); + for (u32 i = 0; i < TWLICON_SIZE_DESC; i++) desc[i] = title[i]; + return 0; +} + +// TWL icon: 32x32 pixel, 8x8 tiles +u32 GetTwlIcon(u8* icon, const TwlIconData* twl_icon) { + const u32 h = TWLICON_DIM_ICON; // fixed size + const u32 w = TWLICON_DIM_ICON; // fixed size + u16* palette = twl_icon->palette; + u8* pix4 = (u8*) twl_icon->icon; + for (u32 y = 0; y < h; y += 8) { + for (u32 x = 0; x < w; x += 8) { + for (u32 i = 0; i < 8*8; i++) { + u32 ix = x + (i & 0x7); + u32 iy = y + (i >> 3); + u16 pix555 = palette[((i%2) ? (*pix4 >> 4) : *pix4) & 0xF]; + u8* pix888 = icon + ((iy * w) + ix) * 3; + *(pix888++) = ((pix555 >> 10) & 0x1F) << 3; // B + *(pix888++) = ((pix555 >> 5) & 0x1F) << 3; // G + *(pix888++) = ((pix555 >> 0) & 0x1F) << 3; // R + if (i % 2) pix4++; + } + } + } + return 0; +} diff --git a/source/game/nds.h b/source/game/nds.h new file mode 100644 index 0000000..87919e7 --- /dev/null +++ b/source/game/nds.h @@ -0,0 +1,115 @@ +#pragma once + +#include "common.h" + +// size of the icon struct: +// see: http://problemkaputt.de/gbatek.htm#dscartridgeicontitle +// v0x0001 -> 0x0840 byte (contains JAP, USA, FRE, GER, ITA, ESP titles) +// v0x0002 -> 0x0940 byte (adds CHN title) +// v0x0003 -> 0x0A40 byte (adds KOR title) +// v0x0103 -> 0x23C0 byte (adds TWL animated icon data) +#define TWLICON_SIZE_DATA(v) ((v == 0x0001) ? 0x0840 : (v == 0x0002) ? 0x0940 : \ + (v == 0x0003) ? 0x1240 : (v == 0x0103) ? 0x23C0 : 0x0000) +#define TWLICON_SIZE_DESC 128 +#define TWLICON_DIM_ICON 32 +#define TWLICON_SIZE_ICON (TWLICON_DIM_ICON * TWLICON_DIM_ICON * 3) // w * h * bpp (rgb888) +#define NDS_LOGO_CRC16 0xCF56 + +// see: http://problemkaputt.de/gbatek.htm#dscartridgeicontitle +typedef struct { + u16 version; + u16 crc_0x0020_0x0840; + u16 crc_0x0020_0x0940; + u16 crc_0x0020_0x0A40; + u16 crc_0x1240_0x23C0; + u8 reserved[0x16]; + u8 icon[0x200]; // 32x32x4bpp / 4x4 tiles + u16 palette[0x10]; // palette[0] is transparent + u16 title_jap[0x80]; + u16 title_eng[0x80]; + u16 title_fre[0x80]; + u16 title_ger[0x80]; + u16 title_ita[0x80]; + u16 title_spa[0x80]; + u16 title_chn[0x80]; + u16 title_kor[0x80]; + u16 title_reserved[0x8 * 0x80]; + u8 icon_anim[0x200 * 0x8]; // 32x32x4bpp / 8 frames + u16 palette_anim[0x10 * 0x8]; // 8 frames + u16 sequence_anim[0x40]; +} __attribute__((packed)) TwlIconData; + +// very limited, information taken from here: +// https://github.com/devkitPro/ndstool/blob/dsi-support/source/header.h +// http://problemkaputt.de/gbatek.htm#dscartridgeheader +// http://problemkaputt.de/gbatek.htm#dsicartridgeheader +typedef struct { + // common stuff (DS + DSi) + char game_title[12]; + char game_code[4]; + char maker_code[2]; + u8 unit_code; // (0x00=NDS, 0x02=NDS+DSi, 0x03=DSi) + u8 seed_select; + u8 device_capacity; // cartridge size: (128 * 1024) << this + u8 reserved0[7]; + u8 dsi_flags; + u8 nds_region; + u8 rom_version; + u8 autostart; // bit2: skip "press button" after Health & Safety + u32 arm9_rom_offset; + u32 arm9_entry_address; + u32 arm9_ram_address; + u32 arm9_size; + u32 arm7_rom_offset; + u32 arm7_entry_address; + u32 arm7_ram_address; + u32 arm7_size; + u32 fnt_offset; + u32 fnt_size; + u32 fat_offset; + u32 fat_size; + u32 arm9_overlay_offset; + u32 arm9_overlay_size; + u32 arm7_overlay_offset; + u32 arm7_overlay_size; + u32 rom_control_normal; // 0x00416657 for OneTimePROM + u32 rom_control_key1; // 0x081808F8 for OneTimePROM + u32 icon_offset; + u16 secure_area_crc; + u16 secure_area_delay; + u32 arm9_auto_load; + u32 arm7_auto_load; + u64 secure_area_disable; + u32 ntr_rom_size; // in byte + u32 header_size; + u8 reserved1[56]; + u8 logo[156]; + u16 logo_crc; + u16 header_crc; + u8 debugger_reserved[0x20]; + // extended mode stuff (DSi only) + u8 ignored0[0x40]; // ignored + u32 arm9i_rom_offset; + u32 reserved2; + u32 arm9i_load_adress; + u32 arm9i_size; + u32 arm7i_rom_offset; + u32 unknown1; + u32 arm7i_load_adress; + u32 arm7i_size; + u8 ignored1[0x30]; // ignored + u32 ntr_twl_rom_size; + u8 unknown2[12]; + u8 ignored2[0x10]; // ignored + u64 title_id; + u32 pubsav_size; + u32 prvsav_size; + u8 reserved3[176]; + u8 unknown3[0x10]; + u8 ignored3[0xD00]; // ignored +} __attribute__((packed)) TwlHeader; + +u32 ValidateTwlHeader(TwlHeader* twl); +u32 LoadTwlIconData(const char* path, TwlIconData* icon); +u32 GetTwlTitle(char* desc, const TwlIconData* twl_icon); +u32 GetTwlIcon(u8* icon, const TwlIconData* twl_icon); diff --git a/source/gamecart/gamecart.c b/source/gamecart/gamecart.c index d3fe2ac..3ad3746 100644 --- a/source/gamecart/gamecart.c +++ b/source/gamecart/gamecart.c @@ -3,7 +3,7 @@ #include "command_ctr.h" #include "command_ntr.h" #include "card_eeprom.h" -#include "ndsheader.h" +#include "nds.h" #include "ncch.h" #include "ncsd.h" diff --git a/source/gamecart/ndsheader.h b/source/gamecart/ndsheader.h deleted file mode 100644 index 8db89be..0000000 --- a/source/gamecart/ndsheader.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "common.h" - -// very limited, information taken from here: -// https://github.com/devkitPro/ndstool/blob/dsi-support/source/header.h -typedef struct { - // common stuff (DS + DSi) - char game_title[12]; - char game_code[4]; - char maker_code[2]; - u8 unit_code; // (0x00=NDS, 0x02=NDS+DSi, 0x03=DSi) - u8 seed_select; - u8 device_capacity; // cartridge size: (128 * 1024) << this - u8 reserved0[7]; - u8 unknown0[2]; - u8 rom_version; - u8 flags; - u8 ignored0[0x60]; // ignored - u32 ntr_rom_size; // in byte - u32 header_size; - u8 reserved1[56]; - u8 logo[156]; - u16 logo_crc; - u16 header_crc; - u8 debugger_reserved[0x20]; - // extended mode stuff (DSi only) - u8 ignored1[0x40]; // ignored - u32 arm9i_rom_offset; - u32 reserved2; - u32 arm9i_load_adress; - u32 arm9i_size; - u32 arm7i_rom_offset; - u32 unknown1; - u32 arm7i_load_adress; - u32 arm7i_size; - u8 ignored2[0x30]; // ignored - u32 ntr_twl_rom_size; - u8 unknown2[12]; - u8 ignored3[0x10]; // ignored - u64 title_id; - u32 pubsav_size; - u32 prvsav_size; - u8 reserved3[176]; - u8 unknown3[0x10]; - u8 ignored4[0xD00]; // ignored -} __attribute__((packed)) TwlHeader; diff --git a/source/godmode.c b/source/godmode.c index dc31e41..bc250ec 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -697,6 +697,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (filetype & GAME_BOSS ) ? "BOSS file options..." : (filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" : (filetype & GAME_SMDH) ? "Show SMDH title info" : + (filetype & GAME_NDS) ? "Show NDS title info" : (filetype & SYS_FIRM ) ? "FIRM image options..." : (filetype & SYS_TICKDB) ? "Mount as ticket.db" : (filetype & BIN_NCCHNFO)? "NCCHinfo options..." :