forked from Galaco/bsp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
reader.go
147 lines (126 loc) · 3.48 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
package bsp
import (
"bytes"
"encoding/binary"
"errors"
"github.com/galaco/bsp/lumps"
"io"
"os"
"unsafe"
)
// Reader is a Bsp File reader.
type Reader struct {
stream io.Reader
}
// Read reads the BSP into internal byte structure
// Note that parsing is somewhat lazy. Proper data structures are only generated for
// lumps that are requested at a later time. This generated the header, then []byte
// data for each lump
func (r *Reader) Read() (bsp *Bsp, err error) {
defer func() {
if r := recover(); r != nil {
bsp = nil
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknown panic")
}
}
}()
bsp = &Bsp{}
buf := bytes.Buffer{}
_, err = buf.ReadFrom(r.stream)
if err != nil {
return nil, err
}
reader := bytes.NewReader(buf.Bytes())
//Create Header
h, err := r.readHeader(reader, bsp.header)
if err != nil {
return nil, err
}
bsp.header = *h
// Create lumps from header data
for index := range bsp.header.Lumps {
lp, err := r.readLump(reader, bsp.header, index)
if err != nil {
return nil, err
}
bsp.lumps[index].SetId(LumpId(index))
bsp.lumps[index].SetRawContents(lp)
refLump, err := getReferenceLumpByIndex(index, bsp.header.Version)
// There are specific rules for the game lump that requires some extra information
// Game lump lumps have offset data relative to file start, not lump start
// This will correct the offsets to the start of the lump.
// @NOTE: Portal2 console uses relative offsets. This game+platform are not supported currently
if index == int(LumpGame) {
refLump.(*lumps.Game).UpdateInternalOffsets(bsp.header.Lumps[index].Offset)
}
if err != nil {
return nil, err
}
bsp.lumps[index].SetContents(refLump)
}
return bsp, err
}
// readHeader Parses header from the bsp file.
func (r *Reader) readHeader(reader *bytes.Reader, header Header) (*Header, error) {
headerSize := unsafe.Sizeof(header)
headerBytes := make([]byte, headerSize)
sectionReader := io.NewSectionReader(reader, 0, int64(len(headerBytes)))
_, err := sectionReader.Read(headerBytes)
if err != nil {
return nil, err
}
err = binary.Read(bytes.NewBuffer(headerBytes), binary.LittleEndian, &header)
if err != nil {
return nil, err
}
return &header, nil
}
// readLump Reads a single lumps data
// Expect a byte reader containing the lump data, as well as the
// header and lump identifier (id)
func (r *Reader) readLump(reader *bytes.Reader, header Header, index int) ([]byte, error) {
//Limit lump data to declared size
lumpHeader := header.Lumps[index]
raw := make([]byte, lumpHeader.Length)
// Skip reading for empty lump
if lumpHeader.Length > 0 {
sectionReader := io.NewSectionReader(reader, int64(lumpHeader.Offset), int64(lumpHeader.Length))
_, err := sectionReader.Read(raw)
if err != nil {
return nil, err
}
}
return raw, nil
}
// ReadFromFile Wraps ReadFromStream to control the file access as well.
// Use ReadFromStream if you already have a file handle
func ReadFromFile(filepath string) (*Bsp, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, err
}
b, err := ReadFromStream(f)
if err != nil {
err2 := f.Close()
if err2 != nil {
return nil, err2
}
return nil, err
}
err = f.Close()
return b, err
}
// ReadFromStream Reads from any struct that implements io.Reader
// handy for passing in a string/bytes/other stream
func ReadFromStream(reader io.Reader) (*Bsp, error) {
r := &Reader{
reader,
}
return r.Read()
}