-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathwad.go
172 lines (146 loc) · 4.84 KB
/
wad.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package wadlib
import (
"bytes"
"errors"
"io/ioutil"
)
// WAD describes the structure enclosing information in a typical WAD's format.
type WAD struct {
Header WADHeader
CertificateChain []byte
CertificateRevocationList []byte
Ticket Ticket
TMD TMD
Data []WADFile
Meta []byte
}
// getPadding returns the given size, padded to the nearest 0x40/64-byte boundary.
// This is useful as many types of contents are padded to such.
func getPadding(size uint32) uint32 {
// Empty things aren't padded.
if size == 0 {
return 0
}
// We can calculate padding from the remainder.
leftover := size % 64
if leftover == 0 {
return 0
} else {
return 64 - leftover
}
}
func pad(src []byte) []byte {
length := (uint32)(len(src))
paddedSize := getPadding(length)
resized := make([]byte, paddedSize+length)
copy(resized, src)
return resized
}
// You use readable when you want to stagger contents and not use scary splice related methods.
type readable struct {
data []byte
amountRead uint32
}
// getRange returns a range of data for a size. By default, it is padded to the closest 64 bytes.
func (r *readable) getRange(size uint32) []byte {
current := r.amountRead
// We'll want to return the range with actual data by size.
selectedRange := r.data[current : current+size]
// Then, we'll want to increment amountRead by the padded size.
r.amountRead += size + getPadding(size)
return selectedRange
}
// LoadWADFromFile takes a path, loads it, and parses the given binary WAD.
func LoadWADFromFile(filePath string) (*WAD, error) {
contents, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
return LoadWAD(contents)
}
// LoadWAD takes contents and parses the given binary WAD.
func LoadWAD(contents []byte) (*WAD, error) {
// Read the given header. Per Nintendo's configuration, this should only be 32 bytes.
// The first u32 should be from the header, describing its own size.
// It's important to check the exact order of these bytes to determine endianness.
if !bytes.Equal(contents[0:4], []byte{0x00, 0x00, 0x00, 0x20}) {
return nil, errors.New("header should be 32 bytes in default Nintendo configuration")
}
r := readable{
data: contents,
}
wad := WAD{}
// We'll read the header first as this is in order of the file.
// We determined above that the header is 0x20 in length.
err := wad.LoadHeader(r.getRange(0x20))
if err != nil {
return nil, err
}
// Simple sanity check.
header := wad.Header
if int(header.CertificateSize+header.CRLSize+header.TicketSize+header.TMDSize+header.DataSize+header.MetaSize) > len(contents) {
return nil, errors.New("contents as described in header were in sum larger than contents passed")
}
// Next, the certificate section and CRL following.
// As observed on the Wii, the CRL section is always 0,
// along with any references to its version.
wad.CertificateChain = r.getRange(header.CertificateSize)
wad.CertificateRevocationList = r.getRange(header.CRLSize)
// We'll next load a ticket from our contents into the struct.
err = wad.LoadTicket(r.getRange(header.TicketSize))
if err != nil {
return nil, err
}
// Load the TMD following from our contents into the struct.
// It needs a separate function to handle dynamic contents listed.
err = wad.LoadTMD(r.getRange(header.TMDSize))
if err != nil {
return nil, err
}
// For each content, we want to separate the raw data.
err = wad.LoadDataSection(r.getRange(header.DataSize))
if err != nil {
return nil, err
}
// We're at the very end and can safely read to the very end of meta, ignoring subsequent data.
wad.Meta = r.getRange(header.MetaSize)
return &wad, nil
}
// GetWAD returns the bytes necessary for a usable WAD.
func (w *WAD) GetWAD(wadType WADType) ([]byte, error) {
tmd, err := w.GetTMD()
if err != nil {
return nil, err
}
ticket, err := w.GetTicket()
if err != nil {
return nil, err
}
data := w.GetDataSection()
// Create a header with our sourced content.
header := WADHeader{
// This size is fixed, and matches Nintendo's size.
HeaderSize: 0x20,
WADType: wadType,
CertificateSize: (uint32)(len(w.CertificateChain)),
CRLSize: (uint32)(len(w.CertificateRevocationList)),
TicketSize: (uint32)(len(ticket)),
TMDSize: (uint32)(len(tmd)),
DataSize: (uint32)(len(data)),
MetaSize: (uint32)(len(w.Meta)),
}
w.Header = header
headerContents, err := w.GetHeader()
if err != nil {
return nil, err
}
var final []byte
final = append(final, pad(headerContents)...)
final = append(final, pad(w.CertificateChain)...)
final = append(final, pad(w.CertificateRevocationList)...)
final = append(final, pad(ticket)...)
final = append(final, pad(tmd)...)
final = append(final, pad(data)...)
final = append(final, pad(w.Meta)...)
return final, nil
}