TMD
In den Title-Metadaten (Title Metadata) werden Informationen über einen Titel (Spiele, Kanäle, IOS, Systemsoftware, etc.) und alle installierten Inhalte gespeichert. Sie dient vereinfacht gesagt auch als eine Art Inhaltsverzeichnis, denn sie zeigt alle zugehörigen Inhalte inklusive ihrer Position, Größe und SHA1-Hash an. Sie können vom NUS heruntergeladen werden (bspw. http://nus.cdn.shop.wii.com/ccs/download/000000010000003A/tmd für die TMD des IOS58). Ohne Versionsnummer kommt die neueste Fassung; hängt man eine an, bekommt man genau diese Version (tmd.6176).
Im Dateisystem liegen TMDs in /title/<TitleID-high>/<TitleID-low>/content/title.tmd, dort ohne Zertifikate: ES prüft die Signatur mit der Kette aus /sys/cert.sys. Beim NUS hängen die passenden Zertifikate dagegen hinter der TMD (erst das CP-, dann das CA-Zertifikat), und in einer WAD stehen sie als eigener Block vor Ticket und TMD.
Datenstruktur
Die ersten 0x180 Bytes sind der gleiche Signaturblock wie beim Ticket und bei Zertifikaten. Es gibt nur TMDs mit einer RSA-2048-Signatur.
| Start | Ende | Länge in Byte | Beschreibung |
|---|---|---|---|
| 0x0000 | 0x0003 | 4 | Signatur-Typ (immer 0x10001 für RSA-2048) |
| 0x0004 | 0x0103 | 256 | Signatur |
| 0x0104 | 0x013F | 60 | Padding (immer 0; alles nach diesem Feld wird von der obigen Signatur abgedeckt) |
| 0x0140 | 0x017F | 64 | Aussteller, z.B. Root-CA00000001-CP00000004. Der hintere Teil ist der Name des Zertifikats, mit dem geprüft wird, und muss bei einer TMD mit CP beginnen
|
| 0x0180 | 0x0180 | 1 | Version der Datenstruktur (immer 0) |
| 0x0181 | 0x0181 | 1 | CA-CRL-Version. Wird von ES nicht ausgewertet |
| 0x0182 | 0x0182 | 1 | Signer-CRL-Version. Wird von ES nicht ausgewertet |
| 0x0183 | 0x0183 | 1 | Padding. Bei vWii-Titeln der Wii U steht hier 1, bei Wii-Titeln immer 0 |
| 0x0184 | 0x018B | 8 | Benötigter Titel, also die Title ID des benötigten IOS. Bei IOS selbst 0 |
| 0x018C | 0x0193 | 8 | Title ID |
| 0x0194 | 0x0197 | 4 | Title-Typ |
| 0x0198 | 0x0199 | 2 | Gruppen-ID (Publisher) |
| 0x019A | 0x019B | 2 | Reserviert |
| 0x019C | 0x019D | 2 | Region (0 = Japan, 1 = USA, 2 = Europa, 3 = Regionsfrei, 4 = Korea). Nur bei Kanälen gesetzt; IOS, Systemmenü und andere Systemtitel haben hier 0 |
| 0x019E | 0x01AD | 16 | Alterseinstufungen, ein Byte je Bewertungssystem[1]. Titel ohne Einstufung haben hier nur 0x80-Bytes |
| 0x01AE | 0x01B9 | 12 | Reserviert |
| 0x01BA | 0x01C5 | 12 | IPC-Zugriffsmaske. ES reicht das Feld beim Start des Titels an den Kernel weiter (IOS_SetIpcAccessRights, Syscall 89). Titel ohne besondere Rechte haben hier nur Nullen
|
| 0x01C6 | 0x01D7 | 18 | Reserviert |
| 0x01D8 | 0x01DB | 4 | Zugriffsrechte. Bit 0 = voller PPC-Hardware-Zugriff (AHBPROT), Bit 1 = DVD-Video-Zugriff |
| 0x01DC | 0x01DD | 2 | Title-Version |
| 0x01DE | 0x01DF | 2 | Anzahl der Inhalte |
| 0x01E0 | 0x01E1 | 2 | Boot-Index (Index des zu startenden Inhalts) |
| 0x01E2 | 0x01E3 | 2 | Padding |
| 0x01E4 | 36 × Anzahl der Inhalte | Inhalte |
Inhalt
Ab 0x01E4 folgt für jeden Inhalt ein 36 Byte großer Eintrag (Content Metadata, CMD).
| Start | Ende | Länge in Byte | Beschreibung |
|---|---|---|---|
| 0x00 | 0x03 | 4 | Inhalts-ID. Bei titeleigenen Inhalten zugleich der Dateiname im Content-Ordner (<ID>.app)
|
| 0x04 | 0x05 | 2 | Index |
| 0x06 | 0x07 | 2 | Typ (0x0001 = Normal, 0x4001 = optional, wird für DLC benutzt, 0x8001 = Shared)
|
| 0x08 | 0x0F | 8 | Größe des unverschlüsselten Inhalts |
| 0x10 | 0x23 | 20 | SHA1-Hash des unverschlüsselten Inhalts |
Shared-Inhalte liegen nicht beim Titel, sondern in /shared1. Ihre Dateinamen werden fortlaufend vergeben, die Zuordnung steht in /shared1/content.map.
Der Index dient als Initialisierungsvektor für die AES-CBC-Verschlüsselung des Inhalts (2 Byte Index, dann 14 Nullbytes).
Title-Typ
Das Feld bei 0x0194 ist eine Bitmaske.
| Wert | Bedeutung |
|---|---|
| 0x01 | Regulärer Titel |
| 0x02 | DS-Titel |
| 0x04 | Stream-Titel |
| 0x08 | Daten-Titel |
| 0x10 | Zwischenspeicherbarer Daten-Titel |
- Stream-Titel
- Diese Inhalte werden nicht installiert, sondern aus dem Netz gestreamt. Der laufende Titel bekommt von ES über
ES_SetupStreamKeyeinen Schlüssel, mit dem er die ankommenden Daten entschlüsselt. Fehlt das Bit in der TMD, lehnt ES die Anfrage ab. Wer das Ticket dafür benutzen darf, regeln die Felder erlaubte Title-ID und Maske im Ticket. - Daten-Titel
- Kennzeichnet Titel, die keinen Programmcode enthalten, sondern herunterladbare Zusatzinhalte. Sie haben zusätzlich das Bit für zwischenspeicherbare Daten gesetzt.
Normalerweise verschlüsselt ES beim Kopieren von Daten-Titeln auf die SD-Karte diese mit einem konsoleneigenen Backup-Schlüssel. Dies gilt aber nicht für die Zusatzinhalte von Guitar Hero World Tour und Rock Band 2. Wenn die TMD sowohl das Bit für Daten-Titel als auch das für zwischenspeicherbare Daten gesetzt hat und die Title ID zu einer der folgenden passt, wird stattdessen ein Schlüssel verwendet, der nur aus Nullen besteht:
| Title ID | Spiel |
|---|---|
00010005735841xx
|
Guitar Hero World Tour |
00010005735A41xx bis 00010005735A48xx
|
Rock Band 2 |
Damit können diese Spiele DLC-Daten laden, wenn sie vom Disc-Kanalaus gestartet werden.
Signatur
Signiert wird der SHA1-Hash über den Bereich vom Aussteller (0x0140) bis zum Ende des letzten Inhalts-Eintrags. Geprüft wird er mit dem CP-Zertifikat. Beim Download vom NUS hängt die Kette hinter der TMD, im NAND nimmt ES sie aus /sys/cert.sys.
Fakesigned heißt: die Signatur ist genullt und die reservierten Bereiche ab 0x01AE und 0x01C6 sind so lange verändert worden, bis der SHA1-Hash der TMD mit 00 beginnt. Frühe IOS-Versionen verglichen den Hash mit strncmp, das beim ersten Nullbyte aufhört zu vergleichen, und nahmen so eine TMD als gültig an (siehe Trucha Bug).
ES prüft Ticket und Tmd getrennt, für einen selbst installierten Titel muss also beides fakesigned sein. Moderne WAD-Manager erledigen dies direkt auf der Konsole.
TMD-View
Ein laufender Titel bekommt über ES_GetTmdView nicht die ganze TMD, sondern eine gekürzte Fassung ohne Signatur, Aussteller, CRL-Versionen, Zugriffsrechte, Boot-Index und Hashes. Der Kopf ist 0x5C Byte groß, danach folgen 16 Byte pro Inhalt.
| Start | Ende | Länge in Byte | Beschreibung |
|---|---|---|---|
| 0x0000 | 0x0000 | 1 | Version der Datenstruktur |
| 0x0001 | 0x0003 | 3 | Padding |
| 0x0004 | 0x000B | 8 | Benötigter Titel |
| 0x000C | 0x0013 | 8 | Title ID |
| 0x0014 | 0x0017 | 4 | Title-Typ |
| 0x0018 | 0x0019 | 2 | Gruppen-ID |
| 0x001A | 0x0057 | 62 | Die gleichen 62 Byte wie ab 0x019A der TMD, also inklusive Region und Alterseinstufungen |
| 0x0058 | 0x0059 | 2 | Title-Version |
| 0x005A | 0x005B | 2 | Anzahl der Inhalte |
| 0x005C | 16 × Anzahl der Inhalte | TMD-View Inhalte |
Inhalt
Ab 0x005C folgt für jeden Inhalt ein 16 Byte großer Eintrag (Content Metadata, CMD). Im Gegensatz zur normalen Inhaltsstruktur werden hier keine Hashes mitgeliefert.
| Start | Ende | Länge in Byte | Beschreibung |
|---|---|---|---|
| 0x00 | 0x03 | 4 | Inhalts-ID. Bei titeleigenen Inhalten zugleich der Dateiname im Content-Ordner (<ID>.app)
|
| 0x04 | 0x05 | 2 | Index |
| 0x06 | 0x07 | 2 | Typ (0x0001 = Normal, 0x4001 = optional, wird für DLC benutzt, 0x8001 = Shared)
|
| 0x08 | 0x0F | 8 | Größe des unverschlüsselten Inhalts |
Anwendungsbeispiel
Siehe auch: WADGEN
Dieses Code-Beispiel in Python 3 parst eine TMD.
from binascii import hexlify
from struct import unpack
def parse_tmd(tmd):
# Title ID
tmd.seek(0x18C)
titleid = tmd.read(8)
print("Title ID: " + hexlify(titleid).decode())
# Title Version
tmd.seek(0x1DC)
tmd_title_version = unpack('>H', tmd.read(2))[0]
print("Title version: " + str(tmd_title_version))
# Number of Contents
tmd.seek(0x1DE)
count = unpack(">H", tmd.read(2))[0]
print("Content count: " + str(count))
# Contents
tmd.seek(0x1E4)
# Loop over every content
info = []
for i in range(count):
# This will extract content id, index, type, size and SHA1 hash and store them in a list
# https://docs.python.org/3/library/struct.html#format-characters ("IHHQ": 4, 2, 2, 8)
data = [a for a in unpack(">IHHQ", tmd.read(16))]
data.append(hexlify(tmd.read(20)))
info.append(data)
print("%08X" % data[0] + ": " + str(data[3]) + " bytes")
return info # List with every content
Gibt für das IOS58 auf der Konsole aus:
Title ID: 000000010000003A Title version: 6176 Content count: 19 00000018: 64 bytes ...
Gibt die info-Liste zurück:
[24, 0, 1, 64, b'bcd51d83d9479e44c622c92ebf49bb2c6529d695'] ...
wobei 24 im Hexadezimalsystem = 18, durch "%08X" % 24 werden Nullen vorangestellt (das X bedeutet Hexadezimal, vgl. StackOverflow).
Fußnoten
- ↑ todo
Siehe auch