Skip to content

Commit 890bd00

Browse files
authored
Merge pull request #127 from sean-callahan/feature/save-refactor
Mod loading from save refactor
2 parents 663bd85 + cdef0c4 commit 890bd00

File tree

14 files changed

+670
-468
lines changed

14 files changed

+670
-468
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ install:
1212
- go get github.com/hpcloud/tail
1313
- go get github.com/gorilla/websocket
1414
- go get github.com/majormjr/rcon
15-
- go get github.com/Masterminds/semver
1615
- export GOPATH="$HOME/gopath/src/github.com/mroote/factorio-server-manager/:$GOPATH"
1716

1817
script:

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ go get github.com/gorilla/mux
155155
go get github.com/hpcloud/tail
156156
go get github.com/gorilla/websocket
157157
go get github.com/majormjr/rcon
158-
go get github.com/Masterminds/semver
159158
```
160159

161160
3. Now you will want to go into the src folder for example "C:\FS\factorio-server-manager\src" once there hold down left shift and right click an empty area of the folder. Then click "Open command windows here"

gopkglist

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ github.com/go-ini/ini
33
github.com/gorilla/mux
44
github.com/gorilla/websocket
55
github.com/hpcloud/tail
6-
github.com/majormjr/rcon
7-
github.com/Masterminds/semver
6+
github.com/majormjr/rcon

src/factorio_save.go

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
package main
2+
3+
import (
4+
"archive/zip"
5+
"encoding/binary"
6+
"errors"
7+
"fmt"
8+
"io"
9+
)
10+
11+
type archiveFile struct {
12+
io.ReadCloser
13+
archive io.Closer
14+
}
15+
16+
func (af *archiveFile) Close() error {
17+
if af.ReadCloser != nil {
18+
if err := af.ReadCloser.Close(); err != nil {
19+
return err
20+
}
21+
}
22+
if af.archive != nil {
23+
if err := af.archive.Close(); err != nil {
24+
return err
25+
}
26+
}
27+
return nil
28+
}
29+
30+
func OpenArchiveFile(path string, name string) (r io.ReadCloser, err error) {
31+
archive, err := zip.OpenReader(path)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
f := &archiveFile{archive: archive}
37+
38+
for _, file := range archive.File {
39+
if file.FileInfo().Name() == name {
40+
f.ReadCloser, err = file.Open()
41+
if err != nil {
42+
archive.Close()
43+
return nil, err
44+
}
45+
return f, nil
46+
}
47+
}
48+
49+
return nil, errors.New("file not found")
50+
}
51+
52+
type SaveHeader struct {
53+
FactorioVersion Version `json:"factorio_version"`
54+
Campaign string `json:"campaign"`
55+
Name string `json:"name"`
56+
BaseMod string `json:"base_mod"`
57+
Difficulty uint8 `json:"difficulty"`
58+
Finished bool `json:"finished"`
59+
PlayerWon bool `json:"player_won"`
60+
NextLevel string `json:"next_level"`
61+
CanContinue bool `json:"can_continue"`
62+
FinishedButContinuing bool `json:"finished_but_continuing"`
63+
SavingReplay bool `json:"saving_replay"`
64+
AllowNonAdminDebugOptions bool `json:"allow_non_admin_debug_options"`
65+
LoadedFrom Version `json:"loaded_from"`
66+
LoadedFromBuild uint16 `json:"loaded_from_build"`
67+
AllowedCommands uint8 `json:"allowed_commands"`
68+
Stats map[byte][]map[uint16]uint32 `json:"stats,omitempty"`
69+
Mods []Mod `json:"mods"`
70+
}
71+
72+
type Mod struct {
73+
Name string `json:"name"`
74+
Version Version `json:"version"`
75+
CRC uint32 `json:"crc"`
76+
}
77+
78+
func (h *SaveHeader) ReadFrom(r io.Reader) (err error) {
79+
var scratch [8]byte
80+
81+
var fv version64
82+
_, err = r.Read(scratch[:8])
83+
if err != nil {
84+
return err
85+
}
86+
if err := fv.UnmarshalBinary(scratch[:8]); err != nil {
87+
return fmt.Errorf("read FactorioVersion: %v", err)
88+
}
89+
h.FactorioVersion = Version(fv)
90+
91+
atLeast016 := !h.FactorioVersion.Less(Version{0, 16, 0, 0})
92+
93+
h.Campaign, err = readString(r, Version(h.FactorioVersion), false)
94+
if err != nil {
95+
return fmt.Errorf("read Campaign: %v", err)
96+
}
97+
98+
h.Name, err = readString(r, Version(h.FactorioVersion), false)
99+
if err != nil {
100+
return fmt.Errorf("read Name: %v", err)
101+
}
102+
103+
h.BaseMod, err = readString(r, Version(h.FactorioVersion), false)
104+
if err != nil {
105+
return fmt.Errorf("read BaseMod: %v", err)
106+
}
107+
108+
_, err = r.Read(scratch[:1])
109+
if err != nil {
110+
return fmt.Errorf("read Difficulty: %v", err)
111+
}
112+
h.Difficulty = scratch[0]
113+
114+
_, err = r.Read(scratch[:1])
115+
if err != nil {
116+
return fmt.Errorf("read Finished: %v", err)
117+
}
118+
h.Finished = scratch[0] != 0
119+
120+
_, err = r.Read(scratch[:1])
121+
if err != nil {
122+
return fmt.Errorf("read PlayerWon: %v", err)
123+
}
124+
h.PlayerWon = scratch[0] != 0
125+
126+
h.NextLevel, err = readString(r, Version(h.FactorioVersion), false)
127+
if err != nil {
128+
return fmt.Errorf("read NextLevel: %v", err)
129+
}
130+
131+
if !h.FactorioVersion.Less(Version{0, 12, 0, 0}) {
132+
_, err = r.Read(scratch[:1])
133+
if err != nil {
134+
return fmt.Errorf("read CanContinue: %v", err)
135+
}
136+
h.CanContinue = scratch[0] != 0
137+
138+
_, err = r.Read(scratch[:1])
139+
if err != nil {
140+
return fmt.Errorf("read FinishedButContinuing: %v", err)
141+
}
142+
h.FinishedButContinuing = scratch[0] != 0
143+
}
144+
145+
_, err = r.Read(scratch[:1])
146+
if err != nil {
147+
return fmt.Errorf("read SavingReplay: %v", err)
148+
}
149+
h.SavingReplay = scratch[0] != 0
150+
151+
if atLeast016 {
152+
_, err = r.Read(scratch[:1])
153+
if err != nil {
154+
return fmt.Errorf("read AllowNonAdminDebugOptions: %v", err)
155+
}
156+
h.AllowNonAdminDebugOptions = scratch[0] != 0
157+
}
158+
159+
var loadedFrom version48
160+
err = loadedFrom.ReadFrom(r, Version(h.FactorioVersion))
161+
if err != nil {
162+
return fmt.Errorf("read LoadedFrom: %v", err)
163+
}
164+
h.LoadedFrom = Version(loadedFrom)
165+
166+
_, err = r.Read(scratch[:2])
167+
if err != nil {
168+
return fmt.Errorf("read LoadedFromBuild: %v", err)
169+
}
170+
h.LoadedFromBuild = binary.LittleEndian.Uint16(scratch[:2])
171+
172+
_, err = r.Read(scratch[:1])
173+
if err != nil {
174+
return fmt.Errorf("read AllowedCommands: %v", err)
175+
}
176+
h.AllowedCommands = scratch[0]
177+
if h.FactorioVersion.Less(Version{0, 13, 0, 87}) {
178+
if h.AllowedCommands == 0 {
179+
h.AllowedCommands = 2
180+
} else {
181+
h.AllowedCommands = 1
182+
}
183+
}
184+
185+
if h.FactorioVersion.Less(Version{0, 13, 0, 42}) {
186+
h.Stats, err = h.readStats(r)
187+
if err != nil {
188+
return fmt.Errorf("read Stats: %v", err)
189+
}
190+
}
191+
192+
var n uint32
193+
if atLeast016 {
194+
n, err = readOptimUint(r, Version(h.FactorioVersion), 32)
195+
if err != nil {
196+
return fmt.Errorf("read num mods: %v", err)
197+
}
198+
} else {
199+
_, err = r.Read(scratch[:4])
200+
if err != nil {
201+
return fmt.Errorf("read num mods: %v", err)
202+
}
203+
n = binary.LittleEndian.Uint32(scratch[:4])
204+
}
205+
206+
for i := uint32(0); i < n; i++ {
207+
var m Mod
208+
if err = (&m).ReadFrom(r, Version(h.FactorioVersion)); err != nil {
209+
return fmt.Errorf("read mod: %v", err)
210+
}
211+
h.Mods = append(h.Mods, m)
212+
}
213+
214+
return nil
215+
}
216+
217+
func readOptimUint(r io.Reader, v Version, bitSize int) (uint32, error) {
218+
var b [4]byte
219+
if !v.Less(Version{0, 14, 14, 0}) {
220+
_, err := r.Read(b[:1])
221+
if err != nil {
222+
return 0, err
223+
}
224+
if b[0] != 0xFF {
225+
return uint32(b[0]), nil
226+
}
227+
}
228+
229+
if bitSize < 0 || bitSize > 64 || (bitSize%8 != 0) {
230+
panic("invalid bit size")
231+
}
232+
233+
_, err := r.Read(b[:bitSize/8])
234+
if err != nil {
235+
return 0, err
236+
}
237+
238+
switch bitSize {
239+
case 16:
240+
return uint32(binary.LittleEndian.Uint16(b[:2])), nil
241+
case 32:
242+
return binary.LittleEndian.Uint32(b[:4]), nil
243+
default:
244+
panic("invalid bit size")
245+
}
246+
}
247+
248+
func readString(r io.Reader, game Version, forceOptimized bool) (s string, err error) {
249+
var n uint32
250+
251+
if !game.Less(Version{0, 16, 0, 0}) || forceOptimized {
252+
n, err = readOptimUint(r, game, 32)
253+
if err != nil {
254+
return "", err
255+
}
256+
} else {
257+
var b [4]byte
258+
_, err := r.Read(b[:])
259+
if err != nil {
260+
return "", fmt.Errorf("failed to read string length: %v", err)
261+
}
262+
n = uint32(binary.LittleEndian.Uint32(b[:]))
263+
}
264+
265+
d := make([]byte, n)
266+
_, err = r.Read(d)
267+
if err != nil {
268+
return "", fmt.Errorf("failed to read string: %v", err)
269+
}
270+
271+
return string(d), nil
272+
}
273+
274+
func (h SaveHeader) readStats(r io.Reader) (stats map[byte][]map[uint16]uint32, err error) {
275+
var scratch [4]byte
276+
stats = make(map[byte][]map[uint16]uint32)
277+
278+
_, err = r.Read(scratch[:4])
279+
if err != nil {
280+
return nil, err
281+
}
282+
n := binary.LittleEndian.Uint32(scratch[:4])
283+
for i := uint32(0); i < n; i++ {
284+
_, err := r.Read(scratch[:1])
285+
if err != nil {
286+
return nil, fmt.Errorf("read stat %d force id: %v", i, err)
287+
}
288+
id := scratch[1]
289+
for j := 0; j < 3; j++ {
290+
st := make(map[uint16]uint32)
291+
_, err = r.Read(scratch[:4])
292+
if err != nil {
293+
return nil, fmt.Errorf("read stat %d (id %d) length: %v", i, id, err)
294+
}
295+
length := binary.LittleEndian.Uint32(scratch[:4])
296+
for k := uint32(0); k < length; k++ {
297+
_, err = r.Read(scratch[:2])
298+
if err != nil {
299+
return nil, fmt.Errorf("read stat %d (id %d; index %d) key: %v", i, id, k, err)
300+
}
301+
key := binary.LittleEndian.Uint16(scratch[:2])
302+
_, err = r.Read(scratch[:4])
303+
if err != nil {
304+
return nil, fmt.Errorf("read stat %d (id %d; index %d) val: %v", i, id, k, err)
305+
}
306+
val := binary.LittleEndian.Uint32(scratch[:4])
307+
st[key] = val
308+
}
309+
stats[id] = append(stats[id], st)
310+
}
311+
}
312+
313+
return stats, nil
314+
}
315+
316+
func (m *Mod) ReadFrom(r io.Reader, game Version) (err error) {
317+
m.Name, err = readString(r, game, true)
318+
if err != nil {
319+
return fmt.Errorf("read Name: %v", err)
320+
}
321+
322+
var version version48
323+
err = version.ReadFrom(r, game)
324+
if err != nil {
325+
return err
326+
}
327+
m.Version = Version(version)
328+
329+
var scratch [4]byte
330+
if game.Greater(Version{0, 15, 0, 91}) {
331+
_, err = r.Read(scratch[:4])
332+
if err != nil {
333+
return err
334+
}
335+
m.CRC = binary.LittleEndian.Uint32(scratch[:4])
336+
}
337+
338+
return nil
339+
}

0 commit comments

Comments
 (0)