/** * wii_tmd.c */ #include #include "wii_tmd.h" const char *region_names[] = { "JAP/NTSC-J", "NTSC-U", "PAL", "REGION FREE", "UNKNOWN", NULL }; typedef struct { u16 id; char *name; } group; static group group_names[] = { {0x0001, "Nintendo"}, {0x3031, "Nintendo"} }; typedef struct { u32 id; char *name; } title_category; static title_category title_category_names[] = { {0x00000001, "System"}, {0x00010000, "Savedata"}, {0x00010001, "Channel"}, {0x00010002, "System Channel"}, {0x00010004, "Game Channel"}, {0x00010005, "Game Content"}, {0x00010008, "Hidden Channel"} }; char *tmd_lookup_title_category_name(u32 category) { u32 i; const title_category *c; for (i = 0; i < (sizeof(title_category_names)/sizeof(title_category)); i++) { c = &title_category_names[i]; if (c->id == category) return c->name; } return "Unknown"; } char *tmd_lookup_group_name(u16 group_id) { u32 i; const group *g; for (i = 0; i < (sizeof(group_names)/sizeof(group)); i++) { g = &group_names[i]; if (g->id == group_id) return g->name; } return "Unknown"; } typedef struct { u32 id; char *format; u8 replace; } system_title_name; static system_title_name system_title_names[] = { {0x00000001, "BOOT2", 0}, {0x00000002, "System Menu", 0}, {0x00000100, "BC", 0}, {0x00000101, "MIOS", 0}, {0x00000000, "IOS%d", 1}, /* last item is default */ }; int tmd_get_system_title_name(u32 title_id, char *name) { u32 i; const system_title_name *t; for (i = 0; i < (sizeof(system_title_names)/sizeof(system_title_name)); i++) { t = &system_title_names[i]; if(t->id == title_id) { break; } } if(t->replace == 1) return sprintf(name, t->format, title_id); else return sprintf(name, t->format); } typedef struct { u8 id; char *name; } system_code_name; static system_code_name system_code_names[] = { {'C', "Commodore 64 Virtual Console"}, {'E', "NeoGeo Virtual Console"}, {'F', "Nintendo Virtual Console"}, {'H', "General channel"}, {'J', "Super Nintendo Virtual Console"}, {'L', "Sega Master System Virtual Console"}, {'M', "Sega Megadrive Virtual Console"}, {'N', "Nintendo 64 Virtual Console"}, {'P', "TurboGraFX Virtual Console"}, {'Q', "TurboGraFX CD Virtual Console"}, {'R', "Wii Disc"}, {'W', "WiiWare"} }; char *tmd_lookup_system_code_name(u32 title_id) { u32 i; const system_code_name *s; char *unknown = "Unknown \"?\""; for (i = 0; i < (sizeof(system_code_names)/sizeof(system_code_name)); i++) { s = &system_code_names[i]; if (s->id == (title_id & 0xff)) return s->name; } unknown[9] = (u8)(title_id & 0xff); return unknown; } void dump_tmd_raw(u8 *tmd) { u32 i, n; u8 *p; printf("issuer : %s\n", tmd + 0x140); printf("sys_version : %016llx\n", be64(tmd + 0x0184)); printf("title_id : %04x (%4s)\n", be16(tmd + 0x018c), tmd + 0x0190); printf("title_type : %08x\n", be32(tmd + 0x0194)); printf("group_id : %04x\n", be16(tmd + 0x0198)); printf("region : %s\n", region_names[tmd[OFFSET_REGION]]); printf("title_version: %04x\n", be16(tmd + 0x01dc)); printf("num_contents : %04x\n", be16(tmd + 0x01de)); printf("boot_index : %04x\n", be16(tmd + 0x01e0)); n = be16(tmd + 0x01de); p = tmd + 0x01e4; for (i = 0; i < n; i++) { printf("cid %08x index %04x type %04x size %08llx\n", be32(p), be16(p + 4), be16(p + 6), be64(p + 8)); p += 0x24; } } void print_tmd(tmd_header *tmd) { printf("- tmd_header\n"); printf("sig_type : %08x (%s)\n", tmd->sig_type, (tmd->sig_type == RSA_2048 ? "RSA_2048": "RSA_4096")); printf("issuer : %.64s\n", tmd->issuer); printf("version : %x\n", tmd->version); printf("ca_crl_version : %x\n", tmd->ca_crl_version); printf("signer_crl_version: %x\n", tmd->signer_crl_version); printf("sys_version : %016llx (IOS%llu)\n", tmd->sys_version, tmd->sys_version & 0xff); printf("title_category : %08x (%s)\n", tmd->title_category, tmd_lookup_title_category_name(tmd->title_category)); printf("title_id : %08x ", tmd->title_id); if(tmd->title_category == TITLE_CATEGORY_SYSTEM) { printf("(IOS%d)\n", be32((u8*)&tmd->title_id)); } else { printf("(%.4s)\n", (char*)&tmd->title_id); printf("system : %s\n", tmd_lookup_system_code_name(tmd->title_id)); } printf("title_type : %08x\n", tmd->title_type); printf("group_id : %04x (%s)\n", tmd->group_id, tmd_lookup_group_name(tmd->group_id)); printf("region_code : %s (%d)\n", region_names[tmd->region_code], tmd->region_code); printf("access_rights : %08x\n", tmd->access_rights); printf("title_version : %d (%04x)\n", tmd->title_version, tmd->title_version); printf("num_contents : %04x\n", tmd->num_contents); printf("boot_index : %04x\n", tmd->boot_index); } void print_tmd_content_records(tmd_header *tmd, tmd_content_record *tmd_content) { u8 i, n; tmd_content_record *cr; for (i = 0; i < tmd->num_contents; i++) { cr = &tmd_content[i]; printf("- tmd_content_record[%d]\n", i); printf("cid : %08x\n", cr->cid); printf("index: %04x\n", cr->index); printf("type : %04x\n", cr->type); printf("size : %1$016llx (%1$llu)\n", cr->size); printf("hash : "); for (n = 0; n < 20; n++) printf("%02x", cr->hash[n]); printf("\n"); } } u32 tmd_get_block_size(tmd_header *tmd, tmd_content_record *tmd_content) { u32 blocks = 0; u8 i; u64 install_size = 0; tmd_content_record *cr; for (i = 0; i < tmd->num_contents; i++) { cr = &tmd_content[i]; if (cr->type == 1) { install_size += cr->size; } } blocks = (install_size/WII_BLOCK_SIZE) + ((install_size % WII_BLOCK_SIZE) == 0 ? 0: 1); return blocks; } void tmd_read(FILE *f, tmd_header *tmd) { /* read head into struct */ fread(tmd, sizeof(tmd_header), 1, f); /* convert to big endian */ tmd->sig_type = be32((u8*)&tmd->sig_type); tmd->title_category = be32((u8*)&tmd->title_category); tmd->sys_version = be64((u8*)&tmd->sys_version); tmd->title_type = be32((u8*)&tmd->title_type); tmd->group_id = be16((u8*)&tmd->group_id); tmd->fill4 = be16((u8*)&tmd->fill4); tmd->region_code = be16((u8*)&tmd->region_code); tmd->access_rights = be32((u8*)&tmd->access_rights); tmd->title_version = be16((u8*)&tmd->title_version); tmd->num_contents = be16((u8*)&tmd->num_contents); tmd->boot_index = be16((u8*)&tmd->boot_index); tmd->fill6 = be16((u8*)&tmd->fill6); } void tmd_read_content_records(FILE *f, tmd_header *tmd, tmd_content_record *tmd_content) { u8 i; tmd_content_record *cr; fseek(f, OFFSET_TMD + sizeof(tmd_header), SEEK_SET); for (i = 0; i < tmd->num_contents; i++) { cr = tmd_content + i; fread(cr, sizeof(tmd_content_record), 1, f); /* convert to big endian */ cr->cid = be32((u8*)&cr->cid); cr->index = be16((u8*)&cr->index); cr->type = be16((u8*)&cr->type); cr->size = be64((u8*)&cr->size); } }