Line 1: |
Line 1: |
− | /**************************************************************/
| + | '''Title metadata''' is a format used to store information about a title (a single standalone game, channel, etc.) and all its installed contents, including which contents they consist of and their SHA1 hashes. |
− | //
| |
− | // Nintendo Wii Virtual Console tmd-file structure
| |
− | //
| |
− | // v.0.1
| |
− | //
| |
− | // by Lockhool for #wiidev at efnet
| |
− | // 14.12.2007
| |
− | /**************************************************************/
| |
− |
| |
− | // Common labels used in this file:
| |
− | // magicnum - control ints or shorts found in all tmds
| |
− | // zeros - bytes all filled with zeros
| |
− | // undef - non-zero data of unknown purpose
| |
− | // sign - zero-terminated string padded to 64 bytes
| |
− | // probably used in the signing process
| |
− | // hash - probably 20, 512 or 1024 byte hashes
| |
− | //
| |
− | // # in fornt of a comment signifies entires that are
| |
− | // the same in each tmd-files the author checked
| |
− | //
| |
− | // ! Remember the Wii has a LITTLE-ENDIAN processor. When !
| |
− | // ! working with ints or shorts on none-LE machines !
| |
− | // ! (e.g. PPC Mac) you need to swap the byteorder. !
| |
| | | |
| + | Many operations are done in terms of 64-byte blocks, which means you will often see padding out to the nearest 64-byte boundary at the end of a field. |
| + | |
| + | == Structure == |
| + | === Header === |
| + | {| class="wikitable" |
| + | |- style="background-color: #ddd;" |
| + | ! Start |
| + | ! Length |
| + | ! Description |
| + | |- |
| + | | 0x000 |
| + | | 4 |
| + | | Signature type |
| + | |- |
| + | | 0x004 |
| + | | 256 |
| + | | Signature |
| + | |- |
| + | | 0x104 |
| + | | 60 |
| + | | Padding modulo 64 |
| + | |- |
| + | | 0x140 |
| + | | 64 |
| + | | Issuer |
| + | |- |
| + | | 0x180 |
| + | | 1 |
| + | | Version |
| + | |- |
| + | | 0x181 |
| + | | 1 |
| + | | ca_crl_version |
| + | |- |
| + | | 0x182 |
| + | | 1 |
| + | | signer_crl_version |
| + | |- |
| + | | 0x183 |
| + | | 1 |
| + | | Padding modulo 64 |
| + | |- |
| + | | 0x184 |
| + | | 8 |
| + | | System Version (the ios that the title needs) |
| + | |- |
| + | | 0x18C |
| + | | 8 |
| + | | Title ID |
| + | |- |
| + | | 0x194 |
| + | | 4 |
| + | | Title type |
| + | |- |
| + | | 0x198 |
| + | | 2 |
| + | | Group ID |
| + | |- |
| + | | 0x19A |
| + | | 2 |
| + | | Zero |
| + | |- |
| + | | 0x19C |
| + | | 2 |
| + | | Region |
| + | |- |
| + | | 0x19E |
| + | | 16 |
| + | | Ratings |
| + | |- |
| + | | 0x1AE |
| + | | 12 |
| + | | Reserved |
| + | |- |
| + | | 0x1BA |
| + | | 12 |
| + | | IPC Mask |
| + | |- |
| + | | 0x1C6 |
| + | | 18 |
| + | | Reserved |
| + | |- |
| + | | 0x1D8 |
| + | | 4 |
| + | | Access rights (flags for [[DVDX|DVD-video access]] and [http://hackmii.com/2009/08/of-tmds-and-hardware/ full PPC hardware access]) |
| + | |- |
| + | | 0x1DC |
| + | | 2 |
| + | | Title version |
| + | |- |
| + | | 0x1DE |
| + | | 2 |
| + | | Number of contents (nbr_cont) |
| + | |- |
| + | | 0x1E0 |
| + | | 2 |
| + | | boot index |
| + | |- |
| + | | 0x1E2 |
| + | | 2 |
| + | | Padding modulo 64 |
| + | |- |
| + | | 0x1E4 |
| + | | 36*nbr_cont |
| + | | Contents |
| + | |} |
| + | |
| + | === Content === |
| + | {| class="wikitable" |
| + | |- |
| + | ! Start |
| + | ! Length |
| + | ! Description |
| + | |- |
| + | | 0x00 |
| + | | 4 |
| + | | Content ID |
| + | |- |
| + | | 0x04 |
| + | | 2 |
| + | | Index |
| + | |- |
| + | | 0x06 |
| + | | 2 |
| + | | Type |
| + | |- |
| + | | 0x08 |
| + | | 8 |
| + | | Size |
| + | |- |
| + | | 0x10 |
| + | | 20 |
| + | | SHA1 hash |
| + | |} |
| + | |
| + | === Certificates === |
| + | {| class="wikitable" |
| + | |- |
| + | ! Start |
| + | ! Length |
| + | ! Description |
| + | |- |
| + | | 0x000 |
| + | | 4 |
| + | | Signature type |
| + | |- |
| + | | 0x004 |
| + | | 256 |
| + | | Signature |
| + | |- |
| + | | 0x104 |
| + | | 64 |
| + | | Issuer |
| + | |- |
| + | | 0x124 |
| + | | 4 |
| + | | Tag |
| + | |- |
| + | | 0x128 |
| + | | 64 |
| + | | Name |
| + | |- |
| + | | 0x168 |
| + | | |
| + | | Key |
| + | |} |
| + | |
| + | == Example code application == |
| + | <source lang="c"> |
| typedef unsigned char u8; | | typedef unsigned char u8; |
| typedef unsigned short u16; | | typedef unsigned short u16; |
− | typedef unsigned int u32; | + | typedef unsigned int u32; |
| + | /* On a 32bit system, long is only 4 bytes- use long long instead */ |
| + | typedef unsigned long u64; |
| + | </source> |
| + | <source lang="c"> |
| + | typedef struct { |
| + | u32 cid; // content id |
| + | u16 index; // # number of the file |
| + | u16 type; // normal: 0x0001; shared: 0x8001 |
| + | u64 size; |
| + | u8 hash [20]; // SHA1 hash content |
| + | } content_record; // size: 0x24 bytes |
| + | </source> |
| + | <source lang="c"> |
| + | enum sig_type { |
| + | RSA_2048 = 0x00010001, |
| + | RSA_4096 = 0x00010000 |
| + | }; |
| + | |
| + | // High 32 bits of the title ID |
| + | enum title_type : u32 { |
| + | System = 0x00000001, |
| + | Game = 0x00010000, |
| + | Channel = 0x00010001, |
| + | SystemChannel = 0x00010002, |
| + | GameWithChannel = 0x00010004, |
| + | DLC = 0x00010005, |
| + | HiddenChannel = 0x00010008, |
| + | }; |
| + | |
| + | // title_type (offset 0x194) |
| + | enum title_flags { |
| + | // All official titles have this flag set. |
| + | Default = 0x1, |
| + | Unknown_0x4 = 0x4, |
| + | // Used for DLC titles. |
| + | Data = 0x8, |
| + | Unknown_0x10 = 0x10, |
| + | // Seems to be used for WFS titles. |
| + | Maybe_WFS = 0x20, |
| + | Unknown_CT = 0x40, |
| + | }; |
| + | </source> |
| + | <source lang="c"> |
| + | typedef struct { |
| + | u32 sig_type; |
| + | u8 sig[256]; |
| + | u8 fill1[60]; |
| + | u8 issuer[64]; // Root-CA%08x-CP%08x |
| + | u8 version; |
| + | u8 ca_crl_version; |
| + | u8 signer_crl_version; |
| + | u8 fill2; |
| + | u64 sys_version; |
| + | u64 title_id; |
| + | u32 title_type; |
| + | u16 group_id; // publisher |
| + | u8 reserved[62]; |
| + | u32 access_rights; |
| + | u16 title_version; |
| + | u16 num_contents; |
| + | u16 boot_index; |
| + | u16 fill3; |
| + | content_record contents[num_contents]; |
| + | } tmd; |
| + | </source> |
| + | The tmd is then followed by a chain of certificates, where each certificate is of the general form |
| + | <source lang="c"> |
| + | u32 sig_type; // |
| + | u8 sig[256]; // 256 for RSA_2048, 512 for RSA_4096 |
| + | u8 issuer[32]; |
| + | u32 tag; // identifies what is being signed |
| + | u8 name[64]; // name of thing being signed |
| + | u8 key[...]; |
| + | </source> |
| + | |
| + | There is also a structure called a TmdView which is select sections of the Tmd. It has a length of 0x60+0x10*number_of_contents. The structure is somewhat like this [as returned by ES_GetTmdView] : |
| + | <source lang="c"> |
| + | struct tmd_view_content_t |
| + | { |
| + | uint32_t id; |
| + | uint16_t index; |
| + | uint16_t type; |
| + | uint64_t size; |
| + | }; |
| | | |
− | typedef struct{
| + | struct tmd_view_t |
− | u32 file [0x1]; // name of the file to download
| + | { |
− | u16 num [0x1]; // # number of the file
| + | uint8_t version; // 0x0000; |
− | u8 undef0 [0x1]; // might be 0x00 or 0x80
| + | uint8_t filler[3]; |
− | u8 undef1 [0x1]; // # 0x01
| + | uint64_t ios_title_id; //0x0004 |
− | u8 zeros0 [0x4]; // # 0
| + | uint64_t title_id; // 0x00c |
− | u32 length [0x1]; // non-padded filelength
| + | uint32_t title_type; //0x0014 |
− | u8 hash [0x14]; // ? maybe SHA1-hash
| + | uint16_t group_id; //0x0018 |
− | }VC_TMD_FILE; // size: 0x30 bytes
| + | uint8_t reserved[0x3e]; //0x001a this is the same reserved 0x3e bytes from the tmd |
| + | uint16_t title_version; //0x0058 |
| + | uint16_t number_contents; //0x005a |
| + | tmd_view_content_t contents[]; //0x005c |
| + | }; |
| + | </source> |
| | | |
− | typedef struct{
| + | [[Category:File formats]] |
− | u32 magicnum0 [0x1]; // # 0x00010001
| |
− | u8 hash0 [0x100]; // ?
| |
− | u8 zeros0 [0x3C]; // # 0
| |
− | u8 sign0 [0x40]; // # Root-CA-CP
| |
− | u8 undef0 [0x10]; // # ?
| |
− | u8 code [0x4]; // game code in ascii
| |
− | u32 magicnum1 [0x1]; // # 0x00000001
| |
− | u8 publ [0x2]; // publisher id
| |
− | u8 zeros1 [0x2]; // # 0
| |
− | u8 undef1 [0x4]; // ?
| |
− | u8 undef2 [0xE]; // ? 0x80
| |
− | u8 zeros2 [0x2E]; // # 0
| |
− | u8 undef3 [0x8]; // ?
| |
− | VC_TMD_FILE files[7]; // 7 file entries
| |
− | u32 magicnum2 [0x1]; // # 0x00010001
| |
− | u8 hash1 [0x100]; // # ?
| |
− | u8 zeros3 [0x3C]; // # 0
| |
− | u8 sign1 [0x40]; // # Root-CA
| |
− | u32 magicnum3 [0x1]; // # 0x00000001
| |
− | u8 sign2 [0x40]; // # CP
| |
− | u8 hash2 [0x104]; // # ?
| |
− | u32 magicnum4 [0x1]; // # 0x00010001
| |
− | u8 zeros4 [0x34]; // # 0
| |
− | u32 magicnum5 [0x1]; // # 0x00100000
| |
− | u8 hash3 [0x200]; // # ?
| |
− | u8 zeros5 [0x3C]; // # 0
| |
− | u8 sign3 [0x40]; // # Root
| |
− | u32 magicnum6 [0x1]; // # 0x00000001
| |
− | u8 sign4 [0x40]; // # CA
| |
− | u8 hash4 [0x104]; // # ?
| |
− | u32 magicnum7 [0x1]; // # 0x00010001
| |
− | u8 zeros6 [0x34]; // # 0
| |
− | }VC_TMD; // size: 09E0 bytes
| |