diff options
-rw-r--r-- | COPYING | 0 | ||||
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | README | 18 | ||||
-rw-r--r-- | TODO | 77 | ||||
-rw-r--r-- | crypto.c | 70 | ||||
-rw-r--r-- | crypto.h | 19 | ||||
-rw-r--r-- | main.c | 178 | ||||
-rw-r--r-- | patcher.c | 80 | ||||
-rw-r--r-- | patcher.h | 14 | ||||
-rw-r--r-- | types.c | 50 | ||||
-rw-r--r-- | types.h | 42 | ||||
-rw-r--r-- | ucs.c | 71 | ||||
-rw-r--r-- | ucs.h | 14 | ||||
-rw-r--r-- | wii_imet.h | 44 | ||||
-rw-r--r-- | wii_tik.c | 56 | ||||
-rw-r--r-- | wii_tik.h | 43 | ||||
-rw-r--r-- | wii_tmd.c | 254 | ||||
-rw-r--r-- | wii_tmd.h | 64 | ||||
-rw-r--r-- | wii_wad.c | 137 | ||||
-rw-r--r-- | wii_wad.h | 43 |
20 files changed, 1286 insertions, 0 deletions
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) @@ -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). @@ -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 <stddef.h> +#ifdef __WIN32__ +/* todo */ +#else +#include <openssl/md5.h> +#include <openssl/sha.h> +#include <openssl/aes.h> +#endif +#include <stdlib.h> +#include <string.h> +#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 @@ -0,0 +1,178 @@ +/** + * Wii WAD Tool + */ + +#include <stdio.h> +#include <string.h> +#include <wchar.h> +#include <locale.h> +#include <stdlib.h> + +#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 <string.h> +#include <stdlib.h> + +#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 <stdio.h> +#include "types.h" + +int trucha_sign_ticket(u8 *ticket, u32 ticket_len); +int change_region_code(FILE *f, u32 tmd_len); + +#endif @@ -0,0 +1,50 @@ +/** + * types.c + */ + +#include <stdio.h> +#include <limits.h> +#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; @@ -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 @@ -0,0 +1,71 @@ +/** + * ucs.c + */ + +#include <stdio.h> +#include <stddef.h> +#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++; + } +} + @@ -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 <stdio.h> +#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 <stdio.h> +#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 <stdio.h> +#include <stdlib.h> +#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 <stdio.h> +#include <string.h> +#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 <stdio.h> +#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 |