Skip to content

Commit a46f69f

Browse files
committed
Make Version four parts; add compatibility for older save file versions
* Extended Version type to support build versions. * Removed special io.Stringer and marshallers on version64. * Added obsolete `stats` field for save files 0.13.0.42 and older. * Normalized `allowed_commands` values on save files 0.13.0.87 and older. * Added `OpenArchiveFile` function to open an archive sub file. * Altered `LoadModsFromSaveHandler` to use new `OpenArchiveFile` function.
1 parent 27d78ea commit a46f69f

File tree

4 files changed

+155
-93
lines changed

4 files changed

+155
-93
lines changed

src/factorio_save.go

Lines changed: 132 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,94 @@
11
package main
22

33
import (
4+
"archive/zip"
45
"encoding/binary"
6+
"errors"
57
"fmt"
68
"io"
79
)
810

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+
952
type SaveHeader struct {
10-
FactorioVersion version64 `json:"factorio_version"`
11-
Campaign string `json:"campaign"`
12-
Name string `json:"name"`
13-
BaseMod string `json:"base_mod"`
14-
Difficulty uint8 `json:"difficulty"`
15-
Finished bool `json:"finished"`
16-
PlayerWon bool `json:"player_won"`
17-
NextLevel string `json:"next_level"`
18-
CanContinue bool `json:"can_continue"`
19-
FinishedButContinuing bool `json:"finished_but_continuing"`
20-
SavingReplay bool `json:"saving_replay"`
21-
AllowNonAdminDebugOptions bool `json:"allow_non_admin_debug_options"`
22-
LoadedFrom version24 `json:"loaded_from"`
23-
LoadedFromBuild uint16 `json:"loaded_from_build"`
24-
AllowedCommands uint8 `json:"allowed_commands"`
25-
Mods []Mod `json:"mods"`
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"`
2670
}
2771

2872
type Mod struct {
29-
Name string `json:"name"`
30-
Version version24 `json:"version"`
31-
CRC uint32 `json:"crc"`
73+
Name string `json:"name"`
74+
Version Version `json:"version"`
75+
CRC uint32 `json:"crc"`
3276
}
3377

3478
func (h *SaveHeader) ReadFrom(r io.Reader) (err error) {
3579
var scratch [8]byte
3680

81+
var fv version64
3782
_, err = r.Read(scratch[:8])
3883
if err != nil {
3984
return err
4085
}
41-
if err := h.FactorioVersion.UnmarshalBinary(scratch[:8]); err != nil {
86+
if err := fv.UnmarshalBinary(scratch[:8]); err != nil {
4287
return fmt.Errorf("read FactorioVersion: %v", err)
4388
}
89+
h.FactorioVersion = Version(fv)
4490

45-
atLeast016 := !h.FactorioVersion.Less(Version{0, 16, 0})
91+
atLeast016 := !h.FactorioVersion.Less(Version{0, 16, 0, 0})
4692

4793
h.Campaign, err = readString(r, atLeast016)
4894
if err != nil {
@@ -82,7 +128,7 @@ func (h *SaveHeader) ReadFrom(r io.Reader) (err error) {
82128
return fmt.Errorf("read NextLevel: %v", err)
83129
}
84130

85-
if !h.FactorioVersion.Less(Version{0, 12, 0}) {
131+
if !h.FactorioVersion.Less(Version{0, 12, 0, 0}) {
86132
_, err = r.Read(scratch[:1])
87133
if err != nil {
88134
return fmt.Errorf("read CanContinue: %v", err)
@@ -110,13 +156,15 @@ func (h *SaveHeader) ReadFrom(r io.Reader) (err error) {
110156
h.AllowNonAdminDebugOptions = scratch[0] != 0
111157
}
112158

159+
var loadedFrom version24
113160
_, err = r.Read(scratch[:3])
114161
if err != nil {
115162
return err
116163
}
117-
if err := h.LoadedFrom.UnmarshalBinary(scratch[:3]); err != nil {
164+
if err := loadedFrom.UnmarshalBinary(scratch[:3]); err != nil {
118165
return fmt.Errorf("read LoadedFrom: %v", err)
119166
}
167+
h.LoadedFrom = Version(loadedFrom)
120168

121169
_, err = r.Read(scratch[:2])
122170
if err != nil {
@@ -129,6 +177,20 @@ func (h *SaveHeader) ReadFrom(r io.Reader) (err error) {
129177
return fmt.Errorf("read AllowedCommands: %v", err)
130178
}
131179
h.AllowedCommands = scratch[0]
180+
if h.FactorioVersion.Less(Version{0, 13, 0, 87}) {
181+
if h.AllowedCommands == 0 {
182+
h.AllowedCommands = 2
183+
} else {
184+
h.AllowedCommands = 1
185+
}
186+
}
187+
188+
if h.FactorioVersion.Less(Version{0, 13, 0, 42}) {
189+
h.Stats, err = h.readStats(r)
190+
if err != nil {
191+
return fmt.Errorf("read Stats: %v", err)
192+
}
193+
}
132194

133195
var n uint32
134196
if atLeast016 {
@@ -146,7 +208,7 @@ func (h *SaveHeader) ReadFrom(r io.Reader) (err error) {
146208

147209
for i := uint32(0); i < n; i++ {
148210
var m Mod
149-
if err = (&m).ReadFrom(r, h.FactorioVersion.Version); err != nil {
211+
if err = (&m).ReadFrom(r, Version(h.FactorioVersion)); err != nil {
150212
return fmt.Errorf("read mod: %v", err)
151213
}
152214
h.Mods = append(h.Mods, m)
@@ -197,22 +259,66 @@ func readString(r io.Reader, optimized bool) (s string, err error) {
197259
return string(d), nil
198260
}
199261

262+
func (h SaveHeader) readStats(r io.Reader) (stats map[byte][]map[uint16]uint32, err error) {
263+
var scratch [4]byte
264+
stats = make(map[byte][]map[uint16]uint32)
265+
266+
_, err = r.Read(scratch[:4])
267+
if err != nil {
268+
return nil, err
269+
}
270+
n := binary.LittleEndian.Uint32(scratch[:4])
271+
for i := uint32(0); i < n; i++ {
272+
_, err := r.Read(scratch[:1])
273+
if err != nil {
274+
return nil, fmt.Errorf("read stat %d force id: %v", i, err)
275+
}
276+
id := scratch[1]
277+
for j := 0; j < 3; j++ {
278+
st := make(map[uint16]uint32)
279+
_, err = r.Read(scratch[:4])
280+
if err != nil {
281+
return nil, fmt.Errorf("read stat %d (id %d) length: %v", i, id, err)
282+
}
283+
length := binary.LittleEndian.Uint32(scratch[:4])
284+
for k := uint32(0); k < length; k++ {
285+
_, err = r.Read(scratch[:2])
286+
if err != nil {
287+
return nil, fmt.Errorf("read stat %d (id %d; index %d) key: %v", i, id, k, err)
288+
}
289+
key := binary.LittleEndian.Uint16(scratch[:2])
290+
_, err = r.Read(scratch[:4])
291+
if err != nil {
292+
return nil, fmt.Errorf("read stat %d (id %d; index %d) val: %v", i, id, k, err)
293+
}
294+
val := binary.LittleEndian.Uint32(scratch[:4])
295+
st[key] = val
296+
}
297+
stats[id] = append(stats[id], st)
298+
}
299+
}
300+
301+
return stats, nil
302+
}
303+
200304
func (m *Mod) ReadFrom(r io.Reader, game Version) (err error) {
201305
m.Name, err = readString(r, true)
202306
if err != nil {
203307
return fmt.Errorf("read Name: %v", err)
204308
}
205309

206310
var scratch [4]byte
311+
var version version24
207312
_, err = r.Read(scratch[:3])
208313
if err != nil {
209314
return err
210315
}
211-
if err := m.Version.UnmarshalBinary(scratch[:3]); err != nil {
316+
if err := version.UnmarshalBinary(scratch[:3]); err != nil {
212317
return fmt.Errorf("read Version: %v", err)
213318
}
319+
m.Version = Version(version)
214320

215-
if game.Greater(Version{0, 15, 0}) {
321+
if game.Greater(Version{0, 15, 0, 91}) {
216322
_, err = r.Read(scratch[:4])
217323
if err != nil {
218324
return err

src/mods_handler.go

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -572,46 +572,20 @@ func LoadModsFromSaveHandler(w http.ResponseWriter, r *http.Request) {
572572
SaveFile := r.FormValue("saveFile")
573573

574574
path := filepath.Join(config.FactorioSavesDir, SaveFile)
575-
archive, err := zip.OpenReader(path)
575+
f, err := OpenArchiveFile(path, "level.dat")
576576
if err != nil {
577577
w.WriteHeader(http.StatusInternalServerError)
578-
log.Printf("cannot open save file: %v", err)
579-
resp.Data = "Error opening save file"
580-
if err := json.NewEncoder(w).Encode(resp); err != nil {
581-
log.Printf("Error in loadModsFromSave: %s", err)
582-
}
583-
return
584-
}
585-
defer archive.Close()
586-
587-
var data io.ReadCloser
588-
for _, file := range archive.File {
589-
if file.FileInfo().Name() == "level.dat" {
590-
data, err = file.Open()
591-
if err != nil {
592-
w.WriteHeader(http.StatusInternalServerError)
593-
log.Printf("cannot open save level file: %v", err)
594-
resp.Data = "Error opening save file"
595-
if err := json.NewEncoder(w).Encode(resp); err != nil {
596-
log.Printf("Error in loadModsFromSave: %s", err)
597-
}
598-
return
599-
}
600-
defer data.Close()
601-
}
602-
}
603-
if data == nil {
604-
w.WriteHeader(http.StatusInternalServerError)
605-
log.Println("could not find level.dat file in save file")
578+
log.Printf("cannot open save level file: %v", err)
606579
resp.Data = "Error opening save file"
607580
if err := json.NewEncoder(w).Encode(resp); err != nil {
608581
log.Printf("Error in loadModsFromSave: %s", err)
609582
}
610583
return
611584
}
585+
defer f.Close()
612586

613587
var header SaveHeader
614-
err = header.ReadFrom(data)
588+
err = header.ReadFrom(f)
615589
if err != nil {
616590
w.WriteHeader(http.StatusInternalServerError)
617591
log.Printf("cannot read save header: %v", err)

src/version.go

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import (
99
)
1010

1111
// NilVersion represents an empty version number
12-
var NilVersion = Version{0, 0, 0}
12+
var NilVersion = Version{0, 0, 0, 0}
1313

14-
// Version represents a semantic version
15-
type Version [3]uint
14+
// Version represents a semantic version and build number
15+
type Version [4]uint
1616

1717
func (v Version) String() string {
18-
return fmt.Sprintf("%d.%d.%d", v[0], v[1], v[2])
18+
return fmt.Sprintf("%d.%d.%d.%d", v[0], v[1], v[2], v[3])
1919
}
2020

2121
// MarshalText implements encoding.TextMarshaller for Version
@@ -25,7 +25,7 @@ func (v Version) MarshalText() (text []byte, err error) {
2525

2626
// UnmarshalText implements encoding.TextUnmarshaller for Version
2727
func (v *Version) UnmarshalText(text []byte) error {
28-
parts := strings.SplitN(string(text), ".", 3)
28+
parts := strings.SplitN(string(text), ".", 4)
2929
for i, part := range parts {
3030
p, err := strconv.ParseUint(part, 10, 32)
3131
if err != nil {
@@ -38,7 +38,7 @@ func (v *Version) UnmarshalText(text []byte) error {
3838

3939
// Equals returns true if both version are equal
4040
func (v Version) Equals(b Version) bool {
41-
return v[0] == b[0] && v[1] == b[1] && v[2] == b[2]
41+
return v[0] == b[0] && v[1] == b[1] && v[2] == b[2] && v[3] == b[3]
4242
}
4343

4444
// Less returns true if the receiver version is less than the argument version
@@ -50,6 +50,8 @@ func (v Version) Less(b Version) bool {
5050
return true
5151
case v[0] == b[0] && v[1] == b[1] && v[2] < b[2]:
5252
return true
53+
case v[0] == b[0] && v[1] == b[1] && v[2] == b[2] && v[3] < b[3]:
54+
return true
5355
default:
5456
return false
5557
}
@@ -122,43 +124,24 @@ func (v *version48) UnmarshalBinary(data []byte) error {
122124
}
123125

124126
// version64 is the 64-bit (16, 16, 16, 16) version structure with build component.
125-
type version64 struct {
126-
Version
127-
Build uint
128-
}
127+
type version64 Version
129128

130129
func (v version64) MarshalBinary() (data []byte, err error) {
131130
data = make([]byte, 8)
132-
binary.LittleEndian.PutUint16(data[0:2], uint16(v.Version[0]))
133-
binary.LittleEndian.PutUint16(data[2:4], uint16(v.Version[1]))
134-
binary.LittleEndian.PutUint16(data[4:6], uint16(v.Version[2]))
135-
binary.LittleEndian.PutUint16(data[6:8], uint16(v.Build))
131+
binary.LittleEndian.PutUint16(data[0:2], uint16(v[0]))
132+
binary.LittleEndian.PutUint16(data[2:4], uint16(v[1]))
133+
binary.LittleEndian.PutUint16(data[4:6], uint16(v[2]))
134+
binary.LittleEndian.PutUint16(data[6:8], uint16(v[3]))
136135
return data, nil
137136
}
138137

139138
func (v *version64) UnmarshalBinary(data []byte) error {
140139
if len(data) < 8 {
141140
return errors.New("version64.UnmarshalBinary: too few bytes")
142141
}
143-
v.Version[0] = uint(binary.LittleEndian.Uint16(data[0:2]))
144-
v.Version[1] = uint(binary.LittleEndian.Uint16(data[2:4]))
145-
v.Version[2] = uint(binary.LittleEndian.Uint16(data[4:6]))
146-
v.Build = uint(binary.LittleEndian.Uint16(data[6:8]))
147-
return nil
148-
}
149-
150-
func (v version64) String() string {
151-
return fmt.Sprintf("%d.%d.%d.%d", v.Version[0], v.Version[1], v.Version[2], v.Build)
152-
}
153-
154-
func (v *version64) UnmarshalText(text []byte) error {
155-
parts := strings.SplitN(string(text), ".", 4)
156-
for i, part := range parts {
157-
p, err := strconv.ParseUint(part, 10, 32)
158-
if err != nil {
159-
return err
160-
}
161-
v.Version[i] = uint(p)
162-
}
142+
v[0] = uint(binary.LittleEndian.Uint16(data[0:2]))
143+
v[1] = uint(binary.LittleEndian.Uint16(data[2:4]))
144+
v[2] = uint(binary.LittleEndian.Uint16(data[4:6]))
145+
v[3] = uint(binary.LittleEndian.Uint16(data[6:8]))
163146
return nil
164147
}

0 commit comments

Comments
 (0)