From 9c1ded3b0ae8e540177ee0c0baa1f9c8fcf91989 Mon Sep 17 00:00:00 2001 From: Martin Szulecki Date: Wed, 18 Mar 2009 20:52:11 +0100 Subject: Initial commit of sources --- COPYING | 0 Makefile | 12 +++ README | 18 +++++ TODO | 77 +++++++++++++++++++ crypto.c | 70 +++++++++++++++++ crypto.h | 19 +++++ main.c | 178 +++++++++++++++++++++++++++++++++++++++++++ patcher.c | 80 +++++++++++++++++++ patcher.h | 14 ++++ types.c | 50 ++++++++++++ types.h | 42 ++++++++++ ucs.c | 71 +++++++++++++++++ ucs.h | 14 ++++ wii_imet.h | 44 +++++++++++ wii_tik.c | 56 ++++++++++++++ wii_tik.h | 43 +++++++++++ wii_tmd.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ wii_tmd.h | 64 ++++++++++++++++ wii_wad.c | 137 +++++++++++++++++++++++++++++++++ wii_wad.h | 43 +++++++++++ 20 files changed, 1286 insertions(+) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 crypto.c create mode 100644 crypto.h create mode 100644 main.c create mode 100644 patcher.c create mode 100644 patcher.h create mode 100644 types.c create mode 100644 types.h create mode 100644 ucs.c create mode 100644 ucs.h create mode 100644 wii_imet.h create mode 100644 wii_tik.c create mode 100644 wii_tik.h create mode 100644 wii_tmd.c create mode 100644 wii_tmd.h create mode 100644 wii_wad.c create mode 100644 wii_wad.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5090652 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +CC=gcc +CFLAGS=-g -O2 -Wall +LIBS=-lcrypto +TARGET=wii-wad-tool +OBJECTS=main.o types.o patcher.o wii_tmd.o wii_wad.o wii_tik.o crypto.o ucs.o +HEADERS=types.h patcher.h wii_tmd.h wii_wad.h wii_imet.h wii_tik.h crypto.h ucs.h + +$(TARGET): $(OBJECTS) $(HEADERS) + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +clean: + rm -rf $(OBJECTS) $(TARGET) diff --git a/README b/README new file mode 100644 index 0000000..c8ba6c9 --- /dev/null +++ b/README @@ -0,0 +1,18 @@ +Wii WAD Tool +-------------------------------------------------------------------------------- + +Utility to work with WAD files used by the Wii. + +Features: +- dumps basic or detailed information about a WAD file +- shows interesting details like: + * title name and game code + * region code + * blocks needed to install + * firmware required + * much more +- allows to change the region code setting + +It was made for educational purposes about the Wii. + +This software is licensed along the terms of the GPL 2 or higher (see COPYING). diff --git a/TODO b/TODO new file mode 100644 index 0000000..35441e7 --- /dev/null +++ b/TODO @@ -0,0 +1,77 @@ +- Improve error checking and be strict on parsing errors +OK - Do not show extended information on WADs we can not parse like system IOS WADs +- Rewrite patcher to use Wii related structures to change data +- Allow unpacking and packing WAD files +- New API: + +--- + +wad_header +cert_chain +tik_header +tmd_header +tmd_content_records +content0 +build_info +imet +u8 +content1 +content2 +... +footer + +--- + +typedef struct { + build_info bi; + imet imet; + u8_archive *u8_archive; +} app_file; + +typedef struct { + tmd_header header; + tmd_content_record *content_record[]; +} tmd_file; + +typdef struct { + wad_header header; + char cert_chain; + tik tik; + tmd_file tmd_file; + char *content; + char *footer; +} wad_file; + +void wad_header_read(FILE *f, wad_header *header); +void wad_header_write(FILE *f, wad_header *header); + u32 wad_header_get_section_offset(wad_header *header, u8 section); +void wad_seek_to_section(FILE *f, u8 section); + +void cert_chain_read(FILE *f, char *cert_chain, u32 size); +void cert_chain_write(FILE *f, char *cert_chain, u32 size); + +void ticket_read(FILE *f, wii_tik *ticket); +void ticket_write(FILE *f, wii_tik *ticket); + +void tmd_read(FILE *f, char *tmd, u32 size); +void tmd_write(FILE *f, char *tmd, u32 size); +void tmd_free(char *tmd); + u32 tmd_content_records_calculate_content_block_size(tmd_content_record *records, u16 num_records); + +void content_read(FILE *f, char *content, u32 size); +void content_write(FILE *f, char *content, u32 size); +void content_free(char *content); + +void content_decrypt(char *content, tmd_content_record *record, tik *tik); +void content_encrypt(char *content, tmd_content_record *record, tik *tik); + +void footer_read(FILE *f, char *footer, u32 size); +void footer_write(FILE *f, char *footer, u32 size); +void footer_free(char *footer); + +void build_info_read(FILE *f, build_info *bi); +void build_info_write(FILE *f, build_info *bi); + +void imet_read(FILE *f, imet *imet); +void imet_write(FILE *f, imet *imet); + diff --git a/crypto.c b/crypto.c new file mode 100644 index 0000000..f8d4b1f --- /dev/null +++ b/crypto.c @@ -0,0 +1,70 @@ +/** + * crypto.c + */ + +#include +#ifdef __WIN32__ +/* todo */ +#else +#include +#include +#include +#endif +#include +#include +#include "crypto.h" +#include "wii_tik.h" + +void get_common_key(u8 *key) { + u8 i; + u8 table[16] = { + 0xC1, 0xce, 0x00, 0x08, + 0x74, 0xaf, 0xb9, 0xce, + 0x62, 0xf3, 0xef, 0x6f, + 0x59, 0xab, 0x80, 0xdd + }; + u8 v = 42; + + for (i = 0; i < 16; i++) + { + key[i] = v ^ table[i]; + } +} + +void md5(u8 *data, u32 len, u8 *hash) { + MD5(data, len, hash); +} + +void sha(u8 *data, u32 len, u8 *hash) { + SHA1(data, len, hash); +} + +void aes_cbc_dec(u8 *key, u8 *iv, u8 *in, u32 len, u8 *out) { + AES_KEY aes_key; + + AES_set_decrypt_key(key, 128, &aes_key); + AES_cbc_encrypt(in, out, len, &aes_key, iv, AES_DECRYPT); +} + +void aes_cbc_enc(u8 *key, u8 *iv, u8 *in, u32 len, u8 *out) { + AES_KEY aes_key; + + AES_set_encrypt_key(key, 128, &aes_key); + AES_cbc_encrypt(in, out, len, &aes_key, iv, AES_ENCRYPT); +} + +void decrypt_title_key(wii_tik *tik, u8 *title_key) { + u8 common_key[16]; + u8 iv[16]; + u64 title_id_le; + + get_common_key(common_key); + + title_id_le = ((u64)tik->title_id << 32); + title_id_le += be32((u8*)&tik->title_category); + + memset(iv, 0, sizeof(iv)); + memcpy(iv, &title_id_le, 8); + + aes_cbc_dec(common_key, iv, (u8*)tik->title_key_enc, 16, title_key); +} diff --git a/crypto.h b/crypto.h new file mode 100644 index 0000000..d6b42fe --- /dev/null +++ b/crypto.h @@ -0,0 +1,19 @@ +/** + * crypto.h + */ + +#ifndef __WII_CRYPTO_H__ +#define __WII_CRYPTO_H__ + +#include "types.h" +#include "wii_tik.h" + +void get_common_key(u8 *key); + +void md5(u8 *data, u32 len, u8 *hash); +void sha(u8 *data, u32 len, u8 *hash); +void aes_cbc_dec(u8 *key, u8 *iv, u8 *in, u32 len, u8 *out); +void aes_cbc_enc(u8 *key, u8 *iv, u8 *in, u32 len, u8 *out); +void decrypt_title_key(wii_tik *tik, u8 *title_key); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..4410b6a --- /dev/null +++ b/main.c @@ -0,0 +1,178 @@ +/** + * Wii WAD Tool + */ + +#include +#include +#include +#include +#include + +#include "types.h" +#include "patcher.h" +#include "wii_wad.h" +#include "ucs.h" + +#define VERSION "1.0.0" + +void print_usage(int argc, char **argv) +{ + printf("Usage: %s [OPTIONS] FILE\n", argv[0]); + printf("Modify/show information about Wii WAD FILE.\n\n"); + printf(" -d, --dump\t\tprint detailed information about WAD file\n"); + printf(" -r, --region\t\tchange the region code of the WAD file\n"); + printf(" -v, --version\t\toutput version information and exit\n"); + printf(" -h, --help\t\tprints usage information\n"); + printf("\n"); +} + +enum mode_type { + INFO = 0, + DUMP = 1, + CHANGE_REGION = 2 +}; + +char *basename(char *name) { + return strrchr(name, '/')+1; +}; + +int main(int argc, char **argv) +{ + FILE *f; + wad_header wad; + tmd_header tmd; + wii_tik tik; + wii_imet imet; + wii_build_info bi; + int i; + char mode = INFO; + char *wad_filename = NULL; + + if (argc < 2) { + print_usage(argc, argv); + return 0; + } + + setlocale(LC_ALL,""); + + /* parse cmdline args */ + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { + printf("Wii WAD Tool %s\n", VERSION); + return 0; + } + else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--dump")) { + mode = DUMP; + continue; + } + else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--region")) { + mode = CHANGE_REGION; + continue; + } + else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + print_usage(argc, argv); + return 0; + } + else { + if(wad_filename != NULL) { + print_usage(argc, argv); + return 0; + } + wad_filename = argv[i]; + } + } + + if (wad_filename == NULL) { + print_usage(argc, argv); + return 0; + } + + f = fopen(wad_filename, "r+"); + if (f == NULL) { + fprintf(stderr, "Failed to open file %s\n", basename(wad_filename)); + return 0; + } + + if(wad_read(f, &wad) == 0) { + fprintf(stderr, "Invalid WAD file %s\n", basename(wad_filename)); + return 0; + }; + wad_read_tmd(f, &wad, &tmd); + tmd_content_record tmd_content[tmd.num_contents+1]; + + switch(mode) + { + case INFO: + tmd_read_content_records(f, &tmd, tmd_content); + wad_read_tik(f, &wad, &tmd, &tik); + + if(tmd.title_category != TITLE_CATEGORY_SYSTEM) { + wad_read_app_info(f, &wad, &tmd_content[0], &tik, &bi, &imet); + } + + printf("- information\n"); + printf("File : %s\n", basename(wad_filename)); + + if(tmd.title_category != TITLE_CATEGORY_SYSTEM) { + printf("System : %s\n", tmd_lookup_system_code_name(tmd.title_id)); + printf("Title : "); print_ucs2_as_utf8(imet.name[IMET_LANG_EN]); + printf("\n"); + } + + printf("Category : %s (%08x)\n", tmd_lookup_title_category_name(tmd.title_category), tmd.title_category); + + if(tmd.title_category == TITLE_CATEGORY_SYSTEM) { + printf("System : %s\n", "Firmware"); + char name[12]; + tmd_get_system_title_name(be32((u8*)&tmd.title_id), name); + printf("Title ID : %s (%08x)\n", name, tmd.title_id); + } + else + { + printf("Title ID : %.4s\n", (char*)&tmd.title_id); + } + + printf("Region Code : %s (%d)\n", region_names[tmd.region_code], tmd.region_code); + printf("Blocks : %d\n", tmd_get_block_size(&tmd, tmd_content)); + printf("Publisher ID: %s (%04x)\n", tmd_lookup_group_name(tmd.group_id), tmd.group_id); + if(tmd.sys_version) + printf("Firmware : IOS%llu\n", tmd.sys_version & 0xff); + printf("Version : %d\n", tmd.title_version); + break; + case CHANGE_REGION: + change_region_code(f, wad.tmd_size); + break; + case DUMP: + /* dump all information we can parse */ + print_wad(&wad); + print_tmd(&tmd); + + tmd_read_content_records(f, &tmd, tmd_content); + print_tmd_content_records(&tmd, tmd_content); + + /* read ticket */ + wad_read_tik(f, &wad, &tmd, &tik); + print_tik(&tik); + + if(tmd.title_category != TITLE_CATEGORY_SYSTEM) { + /* read imet tag from first app content */ + printf("- application info\n"); + printf("Blocks : %d\n", tmd_get_block_size(&tmd, tmd_content)); + wad_read_app_info(f, &wad, &tmd_content[0], &tik, &bi, &imet); + printf("Dirname : %s\n", bi.dirname); + printf("Build Host : %s\n", bi.host); + printf("Unknown Flag 0: %04x\n", bi.unk_flag0); + printf("Unknown Flag 1: %04x\n", bi.unk_flag1); + for (i = 0; i < IMET_NAME_COUNT; i++) { + printf("Name[%d] : ", i); + print_ucs2_as_utf8(imet.name[i]); + printf("\n"); + } + } + break; + } + + fclose(f); + + return 0; +} diff --git a/patcher.c b/patcher.c new file mode 100644 index 0000000..30e4620 --- /dev/null +++ b/patcher.c @@ -0,0 +1,80 @@ +/** + * patcher.c + */ + +#include +#include + +#include "crypto.h" +#include "patcher.h" +#include "wii_tmd.h" + +int trucha_sign_ticket(u8 *ticket, u32 ticket_len) +{ + u8 sha1[20]; + u16 i; + + for(i=0; i<65535; i++) { + memcpy(ticket+OFFSET_BF, &i, sizeof(u16)); + sha(ticket+0x140, ticket_len-0x140, sha1); + if (sha1[0] == 0x00) break; + } + + if (i == 65535) + return 0; + else return 1; +} + +int change_region_code(FILE *f, u32 tmd_len) +{ + u8 *tmd; + u8 region; + char input[10]; + + fseek(f, OFFSET_TMD, SEEK_SET); + tmd = (u8*)malloc(tmd_len); + if (tmd == NULL) { + fprintf(stderr, "mem error\n"); + return 0; + } + + fread((u8*)tmd, tmd_len, 1, f); + + dump_tmd_raw((u8*)tmd); + + region = tmd[OFFSET_REGION]; + int i; + do { + printf("Region is set to %s\n",region_names[region]); + printf("New region: \n"); + for(i=0; i<4; i++) + printf("%d- %s\n",i, region_names[i]); + printf("Enter your new choice:"); + fflush(stdout); + fgets(input, 9, stdin); + region = atoi(input); + if (region > 4) + region = 0x04; + + printf("New region is set to %s, do you agree? (y/n) ",region_names[region]); + fflush(stdout); + fgets(input, 9, stdin); + } while(input[0] != 'y'); + + tmd[OFFSET_REGION] = region; + + printf("\tSigning...\n"); + if (trucha_sign_ticket(tmd, tmd_len) == 0) { + fprintf(stderr, "Error signing TMD\n"); + return 0; + } + printf("\tdone.\n\n"); + + printf("Writing changes... \n"); + fseek(f, OFFSET_TMD, SEEK_SET); + fwrite((u8*)tmd, tmd_len, 1, f); + + free(tmd); + return 1; +} + diff --git a/patcher.h b/patcher.h new file mode 100644 index 0000000..fd91815 --- /dev/null +++ b/patcher.h @@ -0,0 +1,14 @@ +/** + * patcher.h + */ + +#ifndef __PATCHER_H__ +#define __PATCHER_H__ + +#include +#include "types.h" + +int trucha_sign_ticket(u8 *ticket, u32 ticket_len); +int change_region_code(FILE *f, u32 tmd_len); + +#endif diff --git a/types.c b/types.c new file mode 100644 index 0000000..7cf13c4 --- /dev/null +++ b/types.c @@ -0,0 +1,50 @@ +/** + * types.c + */ + +#include +#include +#include "types.h" + +u16 be16(const u8 *p) { + return (p[0] << 8) | p[1]; +} + +u32 be32(const u8 *p) { + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} + +u64 be64(const u8 *p) { + return ((u64)be32(p) << 32) | be32(p + 4); +} + +void wbe16(u8 *p, u16 x) { + p[0] = x >> 8; + p[1] = x; +} + +void wbe32(u8 *p, u32 x) { + wbe16(p, x >> 16); + wbe16(p + 2, x); +} + +void wbe64(u8 *p, u64 x) { + wbe32(p, x >> 32); + wbe32(p + 4, x); +} + +void hexdump(u8 *x, u32 n) { + u32 i, j; + + for (i = 0; i < n; i += 16) { + fprintf(stderr, "%04x:", i); + for (j = 0; j < 16 && i + j < n; j++) { + if ((j & 3) == 0) + fprintf(stderr, " "); + fprintf(stderr, "%02x", *x++); + } + fprintf(stderr, "\n"); + } +} + +const u32 WII_BLOCK_SIZE = 1024*128; diff --git a/types.h b/types.h new file mode 100644 index 0000000..d14f887 --- /dev/null +++ b/types.h @@ -0,0 +1,42 @@ +/** + * types.h + */ + +#ifndef __WII_TYPES_H__ +#define __WII_TYPES_H__ + +enum sig_type { + RSA_4096 = 0x00010000, + RSA_2048 = 0x00010001 +}; + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; + +u16 be16(const u8 *p); +u32 be32(const u8 *p); +u64 be64(const u8 *p); +void wbe16(u8 *p, u16 x); +void wbe32(u8 *p, u32 x); +void wbe64(u8 *p, u64 x); + +#define round_up(x,n) (-(-(x) & -(n))) + +void hexdump(u8 *data, u32 len); + +/* Wii */ + +extern const u32 WII_BLOCK_SIZE; + +enum title_categories { + TITLE_CATEGORY_SYSTEM = 0x00001, + TITLE_CATEGORY_SAVEDATA = 0x10000, + TITLE_CATEGORY_CHANNEL = 0x10001, + TITLE_CATEGORY_SYSTEM_CHANNEL = 0x10002, + TITLE_CATEGORY_GAME_CHANNEL = 0x10004, + TITLE_CATEGORY_HIDDEN_CHANNEL = 0x10008 +}; + +#endif diff --git a/ucs.c b/ucs.c new file mode 100644 index 0000000..cd5a9f7 --- /dev/null +++ b/ucs.c @@ -0,0 +1,71 @@ +/** + * ucs.c + */ + +#include +#include +#include "ucs.h" + +u32 beucs2(u16 *ucs2) { + u16 ch; + u16 *ptr; + u32 len; + + ptr = ucs2; + len = 0; + while ((ch = be16((u8*)ptr)) != 0) { + *ptr = ch; + ptr++; + len++; + } + + return len; +} + +u32 ucs2len(u16 *ucs2) { + u32 len = 0; + while (ucs2[len] != 0) { + len++; + } + + return len; +} + +char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + +/* Convert UCS-2 string to UTF-8 */ +void print_ucs2_as_utf8(u16 *ucs2) { + const u32 byteMask = 0xBF; + const u32 byteMark = 0x80; + u32 blen = 0; + u32 i = 0; + u16 *ptr_ucs; + u16 ch; + char utf8[8]= {0,0,0,0,0,0,0,0}; + + ptr_ucs = ucs2; + while (*ptr_ucs != 0) { + ch = *ptr_ucs; + + if (ch < 0x80) { + blen = 1; + } else if (ch < 0x800) { + blen = 2; + } else { + blen = 3; + } + + i = blen; + utf8[i--] = 0; + switch (blen) { + case 3: utf8[i--] = ((ch | byteMark) & byteMask); ch >>= 6; + case 2: utf8[i--] = ((ch | byteMark) & byteMask); ch >>= 6; + case 1: utf8[i--] = (ch | firstByteMark[blen]); + } + + fwrite(utf8, blen, 1, stdout); + + ptr_ucs++; + } +} + diff --git a/ucs.h b/ucs.h new file mode 100644 index 0000000..f824bc0 --- /dev/null +++ b/ucs.h @@ -0,0 +1,14 @@ +/** + * ucs.c + */ + +#ifndef __UCS_H__ +#define __UCS_H__ + +#include "types.h" + +u32 beucs2(u16 *ucs2); +u32 ucs2len(u16 *ucs2); +void print_ucs2_as_utf8(u16 *ucs2); + +#endif diff --git a/wii_imet.h b/wii_imet.h new file mode 100644 index 0000000..ba2d76a --- /dev/null +++ b/wii_imet.h @@ -0,0 +1,44 @@ +/** + * wii_imet.h + */ + +#ifndef __WII_IMET_H__ +#define __WII_IMET_H__ + +#include "types.h" + +#define IMET_NAME_SIZE 42 +#define IMET_NAME_COUNT 7 + +#define IMET_OFFSET 0x80 + +enum IMET_LANGUAGES { + IMET_LANG_JP = 0, + IMET_LANG_EN = 1, + IMET_LANG_DE = 2, + IMET_LANG_FR = 3, + IMET_LANG_ES = 4, + IMET_LANG_IT = 5, + IMET_LANG_NL = 6 +}; + +typedef struct { + char magic[4]; /* "IMET" */ + u32 ukn0; /* Always 00 00 06 00 */ + u32 ukn1; /* Always 00 00 00 03 */ + u32 ukn_size1; + u32 ukn_size2; + u32 ukn_size3; + u32 ukn_flags; + u16 name[IMET_NAME_COUNT][IMET_NAME_SIZE]; +} wii_imet; + +typedef struct { + char dirname[0x20]; + u32 ukn_pad[3]; + u16 unk_flag0; + u16 unk_flag1; + char host[0x10]; +} wii_build_info; + +#endif diff --git a/wii_tik.c b/wii_tik.c new file mode 100644 index 0000000..ca36e6c --- /dev/null +++ b/wii_tik.c @@ -0,0 +1,56 @@ +/** + * wii_tik.c + */ + +#include "wii_tik.h" + +void tik_read(FILE *f, wii_tik *tik) { + fread(tik, sizeof(wii_tik), 1, f); + + /* convert to big endian */ + tik->sig_type = be32((u8*)&tik->sig_type); + tik->ticket_id = be64((u8*)&tik->ticket_id); + tik->console_id = be32((u8*)&tik->console_id); + tik->title_category = be32((u8*)&tik->title_category); + tik->bought_dlc_contents = be16((u8*)&tik->bought_dlc_contents); + tik->unk7 = be16((u8*)&tik->unk7); + tik->has_time_limit = be32((u8*)&tik->has_time_limit); + tik->time_limit_sec = be32((u8*)&tik->time_limit_sec); +} + +void print_tik(wii_tik *tik) { + u32 i; + + printf("- ticket\n"); + printf("sig_type : %08x (%s)\n", tik->sig_type, (tik->sig_type == RSA_2048 ? "RSA_2048": "RSA_4096")); + printf("signature :\n"); + + for (i = 0; i < 0x100; i++) { + printf("%02x", tik->signature[i]); + if (((i+1)%8) == 0) printf(" "); + if (((i+1)%16) == 0) printf("\n"); + } + printf("issuer : %.64s\n", tik->issuer); + printf("title_key_enc : "); + for (i = 0; i < 0x10; i++) { + printf("%02x", tik->title_key_enc[i]); + } + printf("\n"); + + printf("unknown : %02x\n", tik->unk2); + + printf("ticket_id : %016llx\n", tik->ticket_id); + printf("console_id : %08x\n", tik->console_id); + printf("title_id : %08x-%.4s\n", tik->title_category, (char*)&tik->title_id); + + printf("access_mask : %04x\n", tik->access_mask); + printf("bought_dlc_contents: %04x\n", tik->bought_dlc_contents); + + printf("unknown : %016llx\n", tik->unk4); + printf("common_key_index : %02x\n", tik->common_key_index); + + printf("unknown : %04x\n", tik->unk7); + + printf("has_time_limit : %08x\n", tik->has_time_limit); + printf("time_limit_sec : %08x\n", tik->time_limit_sec); +} diff --git a/wii_tik.h b/wii_tik.h new file mode 100644 index 0000000..962a256 --- /dev/null +++ b/wii_tik.h @@ -0,0 +1,43 @@ +/** + * wii_tik.h + */ + +#ifndef __WII_TIK_H__ +#define __WII_TIK_H__ + +#include +#include "types.h" + +typedef struct { + u32 tag; + u32 value; +} wii_tik_limit; + +typedef struct { + u32 sig_type; + u8 signature[0x100]; + u8 ukn0[0x3c]; + char issuer[64]; + u8 ukn1[0x3f]; + u8 title_key_enc[0x10]; + u8 unk2; + u64 ticket_id; + u32 console_id; + u32 title_category; + u32 title_id; + u16 access_mask; + u16 bought_dlc_contents; + u64 unk4; + u8 common_key_index; /* 1 = Korean Key, 0 = Common Key */ + u8 unk5[0x30]; + u8 unk6[0x20]; + u16 unk7; + u32 has_time_limit; + u32 time_limit_sec; /* Seconds */ + u8 unk8[0x58]; +} wii_tik; + +void tik_read(FILE *f, wii_tik *tik); +void print_tik(wii_tik *tik); + +#endif diff --git a/wii_tmd.c b/wii_tmd.c new file mode 100644 index 0000000..8f4002d --- /dev/null +++ b/wii_tmd.c @@ -0,0 +1,254 @@ +/** + * 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); + } +} diff --git a/wii_tmd.h b/wii_tmd.h new file mode 100644 index 0000000..4bd77af --- /dev/null +++ b/wii_tmd.h @@ -0,0 +1,64 @@ +/** + * wii_tmd.h + */ + +#ifndef __WII_TMD_H__ +#define __WII_TMD_H__ + +#include +#include +#include "types.h" + +#define OFFSET_TMD (0xd00) +#define OFFSET_TITLE_ID (0x18c) +#define OFFSET_REGION (0x19d) +#define OFFSET_BF (0x1c1) + +extern const char *region_names[]; + +typedef struct { + u32 cid; /* content id */ + u16 index; /* # number of the file */ + u16 type; + u64 size; + u8 hash[20]; /* SHA1 hash content */ +} tmd_content_record; /* size: 0x24 bytes */ + +typedef struct { + u32 sig_type; + u8 sig[256]; + u8 fill1[60]; + char issuer[64]; /* Root-CA%08x-CP%08x */ + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 fill2; + u64 sys_version; + u32 title_category; + u32 title_id; + u32 title_type; + u16 group_id; /* Publisher ID */ + u16 fill4; + u16 region_code; + u8 reserved[58]; + u32 access_rights; + u16 title_version; + u16 num_contents; + u16 boot_index; + u16 fill6; +} tmd_header; + +void dump_tmd_raw(u8 *tmd); +void print_tmd(tmd_header *tmd); +void print_tmd_content_records(tmd_header *tmd, tmd_content_record *tmd_content); + +void tmd_read(FILE *f, tmd_header *tmd); +void tmd_read_content_records(FILE *f, tmd_header *tmd, tmd_content_record *tmd_content); + u32 tmd_get_block_size(tmd_header *tmd, tmd_content_record *tmd_content); + +char*tmd_lookup_group_name(u16 group_id); +char*tmd_lookup_title_category_name(u32 category); +char*tmd_lookup_system_code_name(u32 title_id); + int tmd_get_system_title_name(u32 title_id, char *name); + +#endif diff --git a/wii_wad.c b/wii_wad.c new file mode 100644 index 0000000..792c84c --- /dev/null +++ b/wii_wad.c @@ -0,0 +1,137 @@ +/** + * wii_wad.c + */ + +#include +#include +#include "wii_wad.h" +#include "crypto.h" +#include "ucs.h" + +void print_wad(wad_header *wad) { + printf("- wad_header\n"); + printf("size : %1$08x (%1$d)\n", wad->size); + printf("type : %04x (%.2s)\n", wad->type, (char*)&wad->type); + printf("unknown : %04x\n", wad->unk0); + printf("cert_chain_size : %1$08x (%1$d)\n", wad->cert_chain_size); + printf("crl_size : %1$08x (%1$d)\n", wad->crl_size); + printf("ticket_size : %1$08x (%1$d)\n", wad->ticket_size); + printf("tmd_size : %1$08x (%1$d)\n", wad->tmd_size); + printf("data_size : %1$08x (%1$d)\n", wad->data_size); + printf("footer_size : %1$08x (%1$d)\n", wad->footer_size); +} + +int wad_read(FILE *f, wad_header *wad) { + /* read head into struct */ + fread(wad, sizeof(wad_header), 1, f); + + /* convert to big endian */ + wad->size = be32((u8*)&wad->size); + if (wad->size != 0x20) + return 0; + + //wad->type = be16((u8*)&wad->type); + wad->unk0 = be16((u8*)&wad->unk0); + wad->cert_chain_size = be32((u8*)&wad->cert_chain_size); + wad->crl_size = be32((u8*)&wad->crl_size); + wad->ticket_size = be32((u8*)&wad->ticket_size); + wad->tmd_size = be32((u8*)&wad->tmd_size); + wad->data_size = be32((u8*)&wad->data_size); + wad->footer_size = be32((u8*)&wad->footer_size); + + if (wad->ticket_size == 0) + return 0; + + if (wad->tmd_size == 0) + return 0; + + if (wad->data_size == 0) + return 0; + + if (wad->footer_size == 0) + return 0; + + return 1; +} + +void wad_read_tmd(FILE *f, wad_header *wad, tmd_header *tmd) { + u32 rounded_len; + rounded_len = round_up(wad->size + wad->cert_chain_size + wad->ticket_size, 0x40); + + /* seek to tmd start offset */ + fseek(f, rounded_len, SEEK_SET); + tmd_read(f, tmd); +} + +void wad_read_tik(FILE *f, wad_header *wad, tmd_header *tmd, wii_tik *tik) { + u32 rounded_len; + rounded_len = round_up(wad->size + wad->cert_chain_size, 0x40); + + /* seek to tik start offset */ + fseek(f, rounded_len, SEEK_SET); + tik_read(f, tik); +} + +void wad_read_app_info(FILE *f, wad_header *wad, tmd_content_record *tmd_content, wii_tik *tik, wii_build_info *bi, wii_imet *imet) { + u8 title_key[16]; + u8 iv[16]; + u32 rounded_len; + u32 data_len; + u8 *data; + u16 i; + + /* decrypt_title_key */ + decrypt_title_key(tik, title_key); + + /* read wad data chunk */ + data_len = round_up(wad->data_size, 0x40); + data = malloc(data_len); + rounded_len = wad_get_section_offset(wad, SECTION_DATA); + + fseek(f, rounded_len, SEEK_SET); + fread(data, data_len, 1, f); + + rounded_len = round_up(tmd_content->size, 0x40); + + i = be16((u8*)&tmd_content->index); + + /* decrypt first content */ + memset(iv, 0, sizeof(iv)); + memcpy(iv, &i, 2); + aes_cbc_dec(title_key, iv, data, rounded_len, data); + + /* read buildinfo */ + memset(bi, 0, sizeof(wii_build_info)); + memcpy((u8*)bi, data, sizeof(wii_build_info)); + + /*. parse first content's IMET tag */ + memset(imet, 0, sizeof(wii_imet)); + memcpy((u8*)imet, data + IMET_OFFSET, sizeof(wii_imet)); + + for (i = 0; i < IMET_NAME_COUNT; i++) { + beucs2(imet->name[i]); + } + + free(data); +} + +u32 wad_get_section_offset(wad_header *wad, u8 section) { + u32 ret = 0; + + switch(section) { + case SECTION_FOOTER: + ret += round_up(wad->data_size, 0x40); + case SECTION_DATA: + ret += round_up(wad->tmd_size, 0x40); + case SECTION_TMD: + ret += round_up(wad->ticket_size, 0x40); + case SECTION_TICKET: + ret += round_up(wad->cert_chain_size, 0x40); + case SECTION_CERT_CHAIN: + ret += round_up(wad->size, 0x40); + case SECTION_WAD_HEADER: + break; + } + + return ret; +} diff --git a/wii_wad.h b/wii_wad.h new file mode 100644 index 0000000..3407219 --- /dev/null +++ b/wii_wad.h @@ -0,0 +1,43 @@ +/** + * wii_wad.h + */ + +#ifndef __WII_WAD_H__ +#define __WII_WAD_H__ + +#include +#include "types.h" +#include "wii_tmd.h" +#include "wii_tik.h" +#include "wii_imet.h" + +enum wad_section { + SECTION_WAD_HEADER = 0, + SECTION_CERT_CHAIN = 1, + SECTION_TICKET = 2, + SECTION_TMD = 3, + SECTION_DATA = 4, + SECTION_FOOTER = 5 +}; + +typedef struct { + u32 size; /* header size, always 0x20 */ + u16 type; + u16 unk0; + u32 cert_chain_size; + u32 crl_size; + u32 ticket_size; + u32 tmd_size; + u32 data_size; + u32 footer_size; +} wad_header; + +void print_wad(wad_header *wad); + +int wad_read(FILE *f, wad_header *wad); +void wad_read_tmd(FILE *f, wad_header *wad, tmd_header *tmd); +void wad_read_tik(FILE *f, wad_header *wad, tmd_header *tmd, wii_tik *tik); +void wad_read_app_info(FILE *f, wad_header *wad, tmd_content_record *tmd_content, wii_tik *tik, wii_build_info *bi, wii_imet *imet); +u32 wad_get_section_offset(wad_header *wad, u8 section); + +#endif -- cgit v1.1-32-gdbae