summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--COPYING0
-rw-r--r--Makefile12
-rw-r--r--README18
-rw-r--r--TODO77
-rw-r--r--crypto.c70
-rw-r--r--crypto.h19
-rw-r--r--main.c178
-rw-r--r--patcher.c80
-rw-r--r--patcher.h14
-rw-r--r--types.c50
-rw-r--r--types.h42
-rw-r--r--ucs.c71
-rw-r--r--ucs.h14
-rw-r--r--wii_imet.h44
-rw-r--r--wii_tik.c56
-rw-r--r--wii_tik.h43
-rw-r--r--wii_tmd.c254
-rw-r--r--wii_tmd.h64
-rw-r--r--wii_wad.c137
-rw-r--r--wii_wad.h43
20 files changed, 1286 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/COPYING
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 <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
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 <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
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 <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;
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 <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++;
+ }
+}
+
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 <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