11package main
22
33import (
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+
952type 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
2872type 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
3478func (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+
200304func (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
0 commit comments