-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathreader.go
383 lines (308 loc) · 7.91 KB
/
reader.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
package rvz
import (
"bytes"
"crypto/aes"
"crypto/sha1" //nolint:gosec
"encoding/binary"
"errors"
"io"
"github.com/bodgit/plumbing"
"github.com/bodgit/rvz/internal/packed"
"github.com/bodgit/rvz/internal/util"
)
const (
// Extension is the conventional file extension used.
Extension = ".rvz"
rvzMagic uint32 = 0x52565a01 // 'R', 'V', 'Z', 0x01
)
const (
gameCube = iota + 1
wii
)
// A Reader has Read and Size methods.
type Reader interface {
io.Reader
Size() int64
}
//nolint:maligned
type header struct {
Magic uint32
Version uint32
VersionCompatible uint32
DiscSize uint32
DiscHash [sha1.Size]byte
IsoFileSize uint64
RvzFileSize uint64
FileHeadHash [sha1.Size]byte
}
func (h *header) discReader(ra io.ReaderAt) io.Reader {
return io.NewSectionReader(ra, int64(binary.Size(h)), int64(h.DiscSize))
}
type disc struct {
DiscType uint32
Compression uint32
ComprLevel int32
ChunkSize uint32
Header [0x80]byte
NumPart uint32
PartSize uint32
PartOff uint64
PartHash [sha1.Size]byte
NumRawData uint32
RawDataOff uint64
RawDataSize uint32
NumGroup uint32
GroupOff uint64
GroupSize uint32
ComprDataLen byte
ComprData [7]byte
}
func (d *disc) partReader(ra io.ReaderAt) io.Reader {
return io.NewSectionReader(ra, int64(d.PartOff), int64(d.NumPart*d.PartSize))
}
func (d *disc) rawReader(ra io.ReaderAt) io.Reader {
return io.NewSectionReader(ra, int64(d.RawDataOff), int64(d.RawDataSize))
}
func (d *disc) groupReader(ra io.ReaderAt) io.Reader {
return io.NewSectionReader(ra, int64(d.GroupOff), int64(d.GroupSize))
}
func (d *disc) chunkSize(partition bool) int64 {
if partition {
return int64(d.ChunkSize) / util.SectorSize * (util.SectorSize - hashSize)
}
return int64(d.ChunkSize)
}
func (d *disc) sectorsPerChunk() int {
return int(d.ChunkSize) / util.SectorSize
}
type partData struct {
FirstSector uint32
NumSector uint32
GroupIndex uint32
NumGroup uint32
}
type part struct {
Key [aes.BlockSize]byte
Data [2]partData
}
type raw struct {
RawDataOff uint64
RawDataSize uint64
GroupIndex uint32
NumGroup uint32
}
type group struct {
Offset uint32
Size uint32
PackedSize uint32
}
func (g *group) offset() int64 {
return int64(g.Offset << 2)
}
const (
compressed uint32 = 1 << 31
compressedMask = compressed - 1
)
func (g *group) compressed() bool {
return g.Size&compressed == compressed
}
func (g *group) size() int64 {
return int64(g.Size & compressedMask)
}
type except struct {
Offset uint16
Hash [sha1.Size]byte
}
type reader struct {
ra io.ReaderAt
header header
disc disc
part []part
raw []raw
group []group
r io.Reader
offset int64
}
func (r *reader) decompressor(reader io.Reader) (io.ReadCloser, error) {
dcomp := decompressor(r.disc.Compression)
if dcomp == nil {
return nil, errors.New("rvz: unsupported algorithm")
}
return dcomp(r.disc.ComprData[0:r.disc.ComprDataLen], reader)
}
//nolint:cyclop,unparam
func (r *reader) groupReader(g int, offset int64, partition bool) (rc io.ReadCloser, exceptions []except, err error) {
group := r.group[g]
switch {
case group.compressed():
rc, err = r.decompressor(io.NewSectionReader(r.ra, group.offset(), group.size()))
if err != nil {
return nil, nil, err
}
case group.size() == 0:
rc = io.NopCloser(io.LimitReader(plumbing.DevZero(), r.disc.chunkSize(partition)))
default:
rc = io.NopCloser(io.NewSectionReader(r.ra, group.offset(), group.size()))
}
//nolint:nestif
if partition {
wc := new(plumbing.WriteCounter)
tr := io.TeeReader(rc, wc)
var numExceptions uint16
if err = binary.Read(tr, binary.BigEndian, &numExceptions); err != nil {
return nil, nil, err
}
if numExceptions > 0 {
return nil, nil, errors.New("TODO handle exceptions")
}
// No compression, data starts on the next 4 byte boundary
if !group.compressed() {
if _, err = io.CopyN(io.Discard, rc, (group.offset()+int64(wc.Count()))%4); err != nil {
return nil, nil, err
}
}
}
if group.PackedSize != 0 {
rc, err = packed.NewReadCloser(rc, offset)
if err != nil {
return nil, nil, err
}
}
return rc, nil, nil
}
func (r *reader) nextReader() (err error) {
for i, x := range r.raw {
if r.offset == int64(x.RawDataOff) {
r.r = newRawReader(r, i)
return
}
}
for i, x := range r.part {
for j := range x.Data {
if r.offset == int64(x.Data[j].FirstSector*util.SectorSize) && x.Data[j].NumSector > 0 {
r.r = newPartReader(r, i, j)
return
}
}
}
return errors.New("rvz: cannot find reader")
}
func (r *reader) Read(p []byte) (n int, err error) {
if r.offset == int64(r.header.IsoFileSize) {
return 0, io.EOF
}
if r.r == nil {
if err = r.nextReader(); err != nil {
return
}
}
n, err = r.r.Read(p)
r.offset += int64(n)
if err != nil {
if !errors.Is(err, io.EOF) {
return
}
r.r, err = nil, nil
}
return
}
func (r *reader) Size() int64 {
return int64(r.header.IsoFileSize)
}
func (r *reader) readRaw() error {
cr, err := r.decompressor(r.disc.rawReader(r.ra))
if err != nil {
return err
}
defer cr.Close()
r.raw = make([]raw, r.disc.NumRawData)
if err = binary.Read(cr, binary.BigEndian, &r.raw); err != nil {
return err
}
// Make sure every area starts on a sector boundary, which is mostly
// for the benefit of the area at the beginning of the disc
for i := range r.raw {
remain := r.raw[i].RawDataOff % util.SectorSize
r.raw[i].RawDataOff -= remain
r.raw[i].RawDataSize += remain
}
return nil
}
func (r *reader) readGroup() error {
cr, err := r.decompressor(r.disc.groupReader(r.ra))
if err != nil {
return err
}
defer cr.Close()
r.group = make([]group, r.disc.NumGroup)
return binary.Read(cr, binary.BigEndian, &r.group)
}
// NewReader returns a new io.Reader that reads and decompresses from ra.
//
//nolint:cyclop,funlen
func NewReader(ra io.ReaderAt) (Reader, error) {
r := new(reader)
r.ra = ra
h := sha1.New() //nolint:gosec
size := int64(binary.Size(r.header)) - sha1.Size
// Create a reader that can read the whole struct, but the SHA1 hash at the end is excluded
mr := io.MultiReader(io.TeeReader(io.NewSectionReader(ra, 0, size), h), io.NewSectionReader(ra, size, sha1.Size))
if err := binary.Read(mr, binary.BigEndian, &r.header); err != nil {
return nil, err
}
if r.header.Magic != rvzMagic {
return nil, errors.New("rvz: bad magic")
}
if !bytes.Equal(r.header.FileHeadHash[:], h.Sum(nil)) {
return nil, errors.New("rvz: header hash doesn't match")
}
h.Reset()
if int(r.header.DiscSize) != binary.Size(r.disc) {
return nil, errors.New("rvz: disc struct has wrong size")
}
if err := binary.Read(io.TeeReader(r.header.discReader(ra), h), binary.BigEndian, &r.disc); err != nil {
return nil, err
}
if !bytes.Equal(r.header.DiscHash[:], h.Sum(nil)) {
return nil, errors.New("rvz: disc hash doesn't match")
}
switch r.disc.DiscType {
case gameCube:
case wii:
break
default:
return nil, errors.New("rvz: invalid disc type")
}
switch r.disc.ChunkSize {
case util.SectorSize << 0: // 32 KiB
case util.SectorSize << 1: // 64 KiB
case util.SectorSize << 2: // 128 KiB
case util.SectorSize << 3: // 256 KiB
case util.SectorSize << 4: // 512 KiB
case util.SectorSize << 5: // 1 MiB
case util.SectorSize << 6: // 2 MiB
break
default:
return nil, errors.New("rvz: bad chunk size")
}
h.Reset()
if r.disc.NumPart > 0 {
r.part = make([]part, r.disc.NumPart)
if int(r.disc.PartSize) != binary.Size(r.part[0]) {
return nil, errors.New("rvz: part struct has wrong size")
}
if err := binary.Read(io.TeeReader(r.disc.partReader(ra), h), binary.BigEndian, &r.part); err != nil {
return nil, err
}
}
if !bytes.Equal(r.disc.PartHash[:], h.Sum(nil)) {
return nil, errors.New("rvz: partition hash doesn't match")
}
if err := r.readRaw(); err != nil {
return nil, err
}
if err := r.readGroup(); err != nil {
return nil, err
}
return r, nil
}