diff --git a/source/fs/filetype.c b/source/fs/filetype.c index 3aa8706..c2575ef 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -6,6 +6,7 @@ u32 IdentifyFileType(const char* path) { const u8 romfs_magic[] = { ROMFS_MAGIC }; + const u8 tickdb_magic[] = { TICKDB_MAGIC }; u8 header[0x200] __attribute__((aligned(32))); // minimum required size void* data = (void*) header; size_t fsize = FileGetSize(path); @@ -54,6 +55,8 @@ u32 IdentifyFileType(const char* path) { return GAME_TICKET; // Ticket file (not used for anything right now) } else if (ValidateFirmHeader((FirmHeader*) data, fsize) == 0) { return SYS_FIRM; // FIRM file + } else if (memcmp(header + 0x100, tickdb_magic, sizeof(tickdb_magic)) == 0) { + return SYS_TICKDB; // ticket.db } } if ((fsize > sizeof(BossHeader)) && diff --git a/source/fs/filetype.h b/source/fs/filetype.h index aabb198..fe52954 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -14,14 +14,15 @@ #define GAME_NUSCDN (1<<9) #define GAME_TICKET (1<<10) #define SYS_FIRM (1<<11) -#define BIN_NCCHNFO (1<<12) -#define BIN_LAUNCH (1<<13) +#define SYS_TICKDB (1<<12) +#define BIN_NCCHNFO (1<<13) +#define BIN_LAUNCH (1<<14) #define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types #define FLAG_NUSCDN (1<<30) #define FLAG_CXI (1<<31) -#define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM)) +#define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM|SYS_TICKDB)) #define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_BOSS|SYS_FIRM)) #define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM)) #define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS)) diff --git a/source/fs/fsdrive.c b/source/fs/fsdrive.c index 24469f0..4a0c0f7 100644 --- a/source/fs/fsdrive.c +++ b/source/fs/fsdrive.c @@ -45,6 +45,8 @@ int DriveType(const char* path) { type = DRV_VIRTUAL | DRV_MEMORY; } else if (vsrc == VRT_GAME) { type = DRV_VIRTUAL | DRV_GAME | DRV_IMAGE; + } else if (vsrc == VRT_TICKDB) { // GAME (???) + type = DRV_VIRTUAL | DRV_GAME | DRV_IMAGE; } else if (vsrc == VRT_CART) { type = DRV_VIRTUAL | DRV_CART; } diff --git a/source/fs/fsdrive.h b/source/fs/fsdrive.h index af3ee48..5d31741 100644 --- a/source/fs/fsdrive.h +++ b/source/fs/fsdrive.h @@ -5,7 +5,7 @@ #define NORM_FS 10 #define IMGN_FS 3 // image normal filesystems -#define VIRT_FS 10 +#define VIRT_FS 11 // primary drive types #define DRV_UNKNOWN (0<<0) @@ -31,13 +31,13 @@ "EMUNAND CTRNAND", "EMUNAND TWLN", "EMUNAND TWLP", "EMUNAND SD", "EMUNAND VIRTUAL", \ "IMGNAND CTRNAND", "IMGNAND TWLN", "IMGNAND TWLP", "IMGNAND VIRTUAL", \ "GAMECART", \ - "GAME IMAGE", \ + "GAME IMAGE", "TICKET.DB IMAGE", \ "MEMORY VIRTUAL", \ "NAND XORPADS", \ "LAST SEARCH" \ #define FS_DRVNUM \ - "0:", "1:", "2:", "3:", "A:", "S:", "4:", "5:", "6:", "B:", "E:", "7:", "8:", "9:", "I:", "C:", "G:", "M:", "X:", "Z:" + "0:", "1:", "2:", "3:", "A:", "S:", "4:", "5:", "6:", "B:", "E:", "7:", "8:", "9:", "I:", "C:", "G:", "T:", "M:", "X:", "Z:" /** Function to identify the type of a drive **/ int DriveType(const char* path); diff --git a/source/game/ticket.c b/source/game/ticket.c index 8f429c0..141b923 100644 --- a/source/game/ticket.c +++ b/source/game/ticket.c @@ -72,6 +72,17 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) { return 0; } +Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls) { + // chunk must be aligned to 0x200 byte in file and at least 0x400 byte big + Ticket* tick = (Ticket*) (chunk + 0x18); + if ((getle32(chunk + 0x10) == 0) || (getle32(chunk + 0x14) != sizeof(Ticket))) return NULL; + if (ValidateTicket(tick) != 0) return NULL; // ticket not validated + if (title_id && (memcmp(title_id, tick->title_id, 8) != 0)) return NULL; // title id not matching + if (legit_pls && (getbe64(tick->ticket_id) == 0)) return NULL; // legit check, not perfect + + return tick; +} + u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand) { const char* path_db = TICKDB_PATH(emunand); // EmuNAND / SysNAND const u32 area_offsets[] = { TICKDB_AREA_OFFSETS }; @@ -86,13 +97,10 @@ u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand) { for (u32 p = 0; p < 2; p++) { u32 area_offset = area_offsets[p]; for (u32 i = 0; !found && (i < TICKDB_AREA_SIZE); i += 0x200) { - Ticket* tick = (Ticket*) (data + 0x18); f_lseek(&file, area_offset + i); if ((f_read(&file, data, 0x400, &btr) != FR_OK) || (btr != 0x400)) break; - if ((getle32(data + 0x10) == 0) || (getle32(data + 0x14) != sizeof(Ticket))) continue; - if (ValidateTicket(tick) != 0) continue; // ticket not validated - if (memcmp(title_id, tick->title_id, 8) != 0) continue; // title id not matching - if (force_legit && (getbe64(tick->ticket_id) == 0)) continue; // legit check + Ticket* tick = TicketFromTickDbChunk(data, title_id, force_legit); + if (!tick) continue; memcpy(ticket, tick, sizeof(Ticket)); found = true; break; diff --git a/source/game/ticket.h b/source/game/ticket.h index e8973d4..1678771 100644 --- a/source/game/ticket.h +++ b/source/game/ticket.h @@ -16,7 +16,7 @@ #define TICKDB_PATH(emu) ((emu) ? "4:/dbs/ticket.db" : "1:/dbs/ticket.db") // EmuNAND / SysNAND #define TICKDB_AREA_OFFSETS 0x0137F000, 0x001C0C00 // second partition is more likely to be in use -#define TICKDB_AREA_SIZE 0x00200000 // the actual area size is around 0x0010C600 +#define TICKDB_AREA_SIZE 0x00180000 // the actual area size is around 0x0010C600 #define TICKDB_MAGIC 0x44, 0x49, 0x46, 0x46, 0x00, 0x00, 0x03, 0x00, \ 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ @@ -58,6 +58,7 @@ typedef struct { u32 ValidateTicket(Ticket* ticket); u32 GetTitleKey(u8* titlekey, Ticket* ticket); +Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls); u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand); u32 FindTitleKey(Ticket* ticket, u8* title_id); u32 BuildFakeTicket(Ticket* ticket, u8* title_id); diff --git a/source/godmode.c b/source/godmode.c index 3bef2be..84139dd 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -653,6 +653,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 & SYS_FIRM) ? "FIRM image options..." : + (filetype & SYS_TICKDB) ? "Mount as ticket.db" : (filetype & BIN_NCCHNFO)? "NCCHinfo options..." : (filetype & BIN_LAUNCH) ? "Launch as arm9 payload" : "???"; optionstr[hexviewer-1] = "Show in Hexeditor"; @@ -795,7 +796,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE)) clipboard->n_entries = 0; // remove last mounted image clipboard entries InitImgFS(curr_entry->path); - if (!(DriveType("7:")||DriveType("G:"))) { + if (!(DriveType("7:")||DriveType("G:")||DriveType("T:"))) { ShowPrompt(false, "Mounting image: failed"); InitImgFS(NULL); } else { @@ -803,7 +804,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur *current_path = '\0'; GetDirContents(current_dir, current_path); for (u32 i = 0; i < current_dir->n_entries; i++) { - if (strspn(current_dir->entry[i].path, "7GI") == 0) + if (strspn(current_dir->entry[i].path, "7GTI") == 0) continue; strncpy(current_path, current_dir->entry[i].path, 256); GetDirContents(current_dir, current_path); diff --git a/source/virtual/virtual.c b/source/virtual/virtual.c index daaa7ff..e0062fd 100644 --- a/source/virtual/virtual.c +++ b/source/virtual/virtual.c @@ -2,6 +2,7 @@ #include "vnand.h" #include "vmem.h" #include "vgame.h" +#include "vtickdb.h" #include "vcart.h" typedef struct { @@ -22,7 +23,7 @@ u32 GetVirtualSource(const char* path) { } bool InitVirtualImageDrive(void) { - return InitVGameDrive(); + return InitVGameDrive() || InitVTickDbDrive(); } bool CheckVirtualDrive(const char* path) { @@ -31,7 +32,9 @@ bool CheckVirtualDrive(const char* path) { return CheckVNandDrive(virtual_src); // check virtual NAND drive for EmuNAND / ImgNAND else if (virtual_src & VRT_GAME) return CheckVGameDrive(); - return virtual_src; // this is safe for SysNAND, memory & cart + else if (virtual_src & VRT_TICKDB) + return CheckVTickDbDrive(); + return virtual_src; // this is safe for SysNAND & memory } bool ReadVirtualDir(VirtualFile* vfile, VirtualDir* vdir) { @@ -43,6 +46,8 @@ bool ReadVirtualDir(VirtualFile* vfile, VirtualDir* vdir) { ret = ReadVMemDir(vfile, vdir); } else if (virtual_src & VRT_GAME) { ret = ReadVGameDir(vfile, vdir); + } else if (virtual_src & VRT_TICKDB) { + ret = ReadVTickDbDir(vfile, vdir); } else if (virtual_src & VRT_CART) { ret = ReadVCartDir(vfile, vdir); } @@ -170,6 +175,8 @@ int ReadVirtualFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count, return ReadVMemFile(vfile, buffer, offset, count); } else if (vfile->flags & VRT_GAME) { return ReadVGameFile(vfile, buffer, offset, count); + } else if (vfile->flags & VRT_TICKDB) { + return ReadVTickDbFile(vfile, buffer, offset, count); } else if (vfile->flags & VRT_CART) { return ReadVCartFile(vfile, buffer, offset, count); } @@ -189,7 +196,7 @@ int WriteVirtualFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 return WriteVNandFile(vfile, buffer, offset, count); } else if (vfile->flags & VRT_MEMORY) { return WriteVMemFile(vfile, buffer, offset, count); - } // no write support for virtual game / cart files + } // no write support for virtual game / tickdb / cart files return -1; } @@ -200,6 +207,8 @@ u64 GetVirtualDriveSize(const char* path) { return GetVNandDriveSize(virtual_src); else if (virtual_src & VRT_GAME) return GetVGameDriveSize(); + else if (virtual_src & VRT_TICKDB) + return GetVTickDbDriveSize(); else if (virtual_src & VRT_CART) return GetVCartDriveSize(); return 0; diff --git a/source/virtual/virtual.h b/source/virtual/virtual.h index 1ea52f8..5dffb13 100644 --- a/source/virtual/virtual.h +++ b/source/virtual/virtual.h @@ -10,9 +10,10 @@ #define VRT_XORPAD NAND_ZERONAND #define VRT_MEMORY (1<<10) #define VRT_GAME (1<<11) -#define VRT_CART (1<<12) +#define VRT_TICKDB (1<<12) +#define VRT_CART (1<<13) -#define VRT_SOURCE (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD|VRT_MEMORY|VRT_GAME|VRT_CART) +#define VRT_SOURCE (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD|VRT_MEMORY|VRT_GAME|VRT_TICKDB|VRT_CART) #define VFLAG_DIR (1<<16) #define VFLAG_ROOT (1<<17) @@ -20,7 +21,7 @@ #define VFLAG_LV3 (1<<19) #define VRT_DRIVES {'S', VRT_SYSNAND}, {'E', VRT_EMUNAND}, {'I', VRT_IMGNAND}, {'X', VRT_XORPAD }, \ - {'M', VRT_MEMORY}, {'G', VRT_GAME}, {'C', VRT_CART} + {'M', VRT_MEMORY}, {'G', VRT_GAME}, {'T', VRT_TICKDB}, {'C', VRT_CART} // virtual file flag (subject to change): // bits 0...9 : reserved for NAND virtual sources and info diff --git a/source/virtual/vtickdb.c b/source/virtual/vtickdb.c new file mode 100644 index 0000000..5791a0c --- /dev/null +++ b/source/virtual/vtickdb.c @@ -0,0 +1,141 @@ +#include "vtickdb.h" +#include "image.h" +#include "ticket.h" + +#define VFLAG_UNKNOWN (1<<29) +#define VFLAG_ESHOP (1<<30) +#define VFLAG_SYSTEM (1<<31) +#define VFLAG_TICKDIR (VFLAG_UNKNOWN|VFLAG_ESHOP|VFLAG_SYSTEM) + +#define NAME_TIK "%02lX.%016llX.%08lX" // index / title id / console id + +typedef struct { + u32 commonkey_idx; + u32 offset; + u8 title_id[8]; + u8 titlekey[16]; + u8 ticket_id[8]; + u8 console_id[4]; + u8 eshop_id[4]; +} __attribute__((packed)) TickDbEntry; + +typedef struct { + u32 n_entries; + u8 reserved[12]; + TickDbEntry entries[256]; // this number is only a placeholder +} __attribute__((packed)) TickDbInfo; + +// only for the main directory +static const VirtualFile vTickDbFileTemplates[] = { + { "system" , 0x00000000, 0x00000000, 0xFF, VFLAG_DIR | VFLAG_SYSTEM }, + { "eshop" , 0x00000000, 0x00000000, 0xFF, VFLAG_DIR | VFLAG_ESHOP }, + { "unknown" , 0x00000000, 0x00000000, 0xFF, VFLAG_DIR | VFLAG_UNKNOWN } +}; + +static TickDbInfo* tick_info = (TickDbInfo*) VGAME_BUFFER; // full 1MB reserved (enough for ~20000 entries) + +u32 AddTickDbInfo(TickDbInfo* info, Ticket* ticket, u32 offset) { + if (ValidateTicket(ticket) != 0) return 1; + + // build ticket entry + TickDbEntry* entry = info->entries + info->n_entries; + entry->commonkey_idx = ticket->commonkey_idx; + entry->offset = offset; + memcpy(entry->title_id, ticket->title_id, 8); + memcpy(entry->titlekey, ticket->titlekey, 16); + memcpy(entry->ticket_id, ticket->ticket_id, 8); + memcpy(entry->console_id, ticket->console_id, 4); + memcpy(entry->eshop_id, ticket->eshop_id, 4); + + // check for duplicate + u32 t = 0; + for (; t < info->n_entries; t++) { + TickDbEntry* entry0 = info->entries + t; + if (memcmp(entry->title_id, entry0->title_id, 8) != 0) continue; + if (!getbe64(entry0->console_id)) // replace this + memcpy(entry0, entry, sizeof(TickDbEntry)); + break; + } + if (t >= info->n_entries) + info->n_entries++; // new entry + + return 0; +} + +u32 InitVTickDbDrive(void) { // prerequisite: ticket.db mounted as image + const u32 area_offsets[] = { TICKDB_AREA_OFFSETS }; + if (!(GetMountState() & SYS_TICKDB)) return 1; + + // reset internal db + memset(tick_info, 0, 16); + + // parse file, sector by sector + for (u32 p = 0; p < sizeof(area_offsets) / sizeof(u32); p++) { + u32 offset_area = area_offsets[p]; + for (u32 i = 0; i < TICKDB_AREA_SIZE; i += (TEMP_BUFFER_SIZE - 0x200)) { + u32 read_bytes = min(TEMP_BUFFER_SIZE, TICKDB_AREA_SIZE - i); + u8* data = (u8*) TEMP_BUFFER; + if (ReadImageBytes(data, offset_area + i, read_bytes) != 0) { + tick_info->n_entries = 0; + return 0; + } + for (; data + TICKET_SIZE < ((u8*) TEMP_BUFFER) + read_bytes; data += 0x200) { + Ticket* ticket = TicketFromTickDbChunk(data, NULL, false); + if (!ticket) continue; + AddTickDbInfo(tick_info, ticket, offset_area + i + 0x18); + } + } + } + + return (tick_info->n_entries) ? SYS_TICKDB : 0; +} + +u32 CheckVTickDbDrive(void) { + if ((GetMountState() & SYS_TICKDB) && tick_info->n_entries) // very basic sanity check + return SYS_TICKDB; + tick_info->n_entries = 0; + return 0; +} + +bool ReadVTickDbDir(VirtualFile* vfile, VirtualDir* vdir) { + if (vdir->flags & VFLAG_TICKDIR) { // ticket dir + while (++vdir->index < (int) tick_info->n_entries) { + TickDbEntry* tick_entry = tick_info->entries + vdir->index; + + u64 ticket_id = getbe64(tick_entry->ticket_id); + u32 ck_idx = tick_entry->commonkey_idx; + if (!(((vdir->flags & VFLAG_SYSTEM) && ticket_id && (ck_idx == 1)) || + ((vdir->flags & VFLAG_ESHOP) && ticket_id && (ck_idx < 6)) || + ((vdir->flags & VFLAG_UNKNOWN) && (!ticket_id || (ck_idx >= 6))))) + continue; + + memset(vfile, 0, sizeof(VirtualFile)); + snprintf(vfile->name, 32, NAME_TIK, tick_entry->commonkey_idx, getbe64(tick_entry->title_id), getbe32(tick_entry->console_id)); + vfile->offset = tick_entry->offset; + vfile->size = sizeof(Ticket); + vfile->keyslot = 0xFF; + vfile->flags = vdir->flags & ~VFLAG_DIR; + + return true; // found + } + } else { // root dir + int n_templates = sizeof(vTickDbFileTemplates) / sizeof(VirtualFile); + const VirtualFile* templates = vTickDbFileTemplates; + while (++vdir->index < n_templates) { + // copy current template to vfile + memcpy(vfile, templates + vdir->index, sizeof(VirtualFile)); + return true; // found + } + } + + return false; +} + +int ReadVTickDbFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count) { + u32 foffset = vfile->offset + offset; + return (ReadImageBytes(buffer, foffset, count) == 0) ? 0 : 1; +} + +u64 GetVTickDbDriveSize(void) { + return (tick_info->n_entries) ? GetMountSize() : 0; +} diff --git a/source/virtual/vtickdb.h b/source/virtual/vtickdb.h new file mode 100644 index 0000000..257f07f --- /dev/null +++ b/source/virtual/vtickdb.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common.h" +#include "virtual.h" + +u32 InitVTickDbDrive(void); +u32 CheckVTickDbDrive(void); + +bool ReadVTickDbDir(VirtualFile* vfile, VirtualDir* vdir); +int ReadVTickDbFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count); +// int WriteVTickDbFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count); // no writing +u64 GetVTickDbDriveSize(void);