Skip to content

Commit 27d78ea

Browse files
committed
Refactored mod loading from save file
* Removed dependency on semver library * Moved to new version struct types in level.dat structure - added version24, version48, and version64 types and marshallers * Fixed UI to reflect new json schema - removed `num_mods` - versions are now 3D arrays instead of objects * Fixed read optimized uint implementation
1 parent 663bd85 commit 27d78ea

File tree

9 files changed

+350
-454
lines changed

9 files changed

+350
-454
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: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package main
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"io"
7+
)
8+
9+
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"`
26+
}
27+
28+
type Mod struct {
29+
Name string `json:"name"`
30+
Version version24 `json:"version"`
31+
CRC uint32 `json:"crc"`
32+
}
33+
34+
func (h *SaveHeader) ReadFrom(r io.Reader) (err error) {
35+
var scratch [8]byte
36+
37+
_, err = r.Read(scratch[:8])
38+
if err != nil {
39+
return err
40+
}
41+
if err := h.FactorioVersion.UnmarshalBinary(scratch[:8]); err != nil {
42+
return fmt.Errorf("read FactorioVersion: %v", err)
43+
}
44+
45+
atLeast016 := !h.FactorioVersion.Less(Version{0, 16, 0})
46+
47+
h.Campaign, err = readString(r, atLeast016)
48+
if err != nil {
49+
return fmt.Errorf("read Campaign: %v", err)
50+
}
51+
52+
h.Name, err = readString(r, atLeast016)
53+
if err != nil {
54+
return fmt.Errorf("read Name: %v", err)
55+
}
56+
57+
h.BaseMod, err = readString(r, atLeast016)
58+
if err != nil {
59+
return fmt.Errorf("read BaseMod: %v", err)
60+
}
61+
62+
_, err = r.Read(scratch[:1])
63+
if err != nil {
64+
return fmt.Errorf("read Difficulty: %v", err)
65+
}
66+
h.Difficulty = scratch[0]
67+
68+
_, err = r.Read(scratch[:1])
69+
if err != nil {
70+
return fmt.Errorf("read Finished: %v", err)
71+
}
72+
h.Finished = scratch[0] != 0
73+
74+
_, err = r.Read(scratch[:1])
75+
if err != nil {
76+
return fmt.Errorf("read PlayerWon: %v", err)
77+
}
78+
h.PlayerWon = scratch[0] != 0
79+
80+
h.NextLevel, err = readString(r, atLeast016)
81+
if err != nil {
82+
return fmt.Errorf("read NextLevel: %v", err)
83+
}
84+
85+
if !h.FactorioVersion.Less(Version{0, 12, 0}) {
86+
_, err = r.Read(scratch[:1])
87+
if err != nil {
88+
return fmt.Errorf("read CanContinue: %v", err)
89+
}
90+
h.CanContinue = scratch[0] != 0
91+
92+
_, err = r.Read(scratch[:1])
93+
if err != nil {
94+
return fmt.Errorf("read FinishedButContinuing: %v", err)
95+
}
96+
h.FinishedButContinuing = scratch[0] != 0
97+
}
98+
99+
_, err = r.Read(scratch[:1])
100+
if err != nil {
101+
return fmt.Errorf("read SavingReplay: %v", err)
102+
}
103+
h.SavingReplay = scratch[0] != 0
104+
105+
if atLeast016 {
106+
_, err = r.Read(scratch[:1])
107+
if err != nil {
108+
return fmt.Errorf("read AllowNonAdminDebugOptions: %v", err)
109+
}
110+
h.AllowNonAdminDebugOptions = scratch[0] != 0
111+
}
112+
113+
_, err = r.Read(scratch[:3])
114+
if err != nil {
115+
return err
116+
}
117+
if err := h.LoadedFrom.UnmarshalBinary(scratch[:3]); err != nil {
118+
return fmt.Errorf("read LoadedFrom: %v", err)
119+
}
120+
121+
_, err = r.Read(scratch[:2])
122+
if err != nil {
123+
return fmt.Errorf("read LoadedFromBuild: %v", err)
124+
}
125+
h.LoadedFromBuild = binary.LittleEndian.Uint16(scratch[:2])
126+
127+
_, err = r.Read(scratch[:1])
128+
if err != nil {
129+
return fmt.Errorf("read AllowedCommands: %v", err)
130+
}
131+
h.AllowedCommands = scratch[0]
132+
133+
var n uint32
134+
if atLeast016 {
135+
n, err = readOptimUint32(r)
136+
if err != nil {
137+
return fmt.Errorf("read num mods: %v", err)
138+
}
139+
} else {
140+
_, err = r.Read(scratch[:4])
141+
if err != nil {
142+
return fmt.Errorf("read num mods: %v", err)
143+
}
144+
n = binary.LittleEndian.Uint32(scratch[:4])
145+
}
146+
147+
for i := uint32(0); i < n; i++ {
148+
var m Mod
149+
if err = (&m).ReadFrom(r, h.FactorioVersion.Version); err != nil {
150+
return fmt.Errorf("read mod: %v", err)
151+
}
152+
h.Mods = append(h.Mods, m)
153+
}
154+
155+
return nil
156+
}
157+
158+
func readOptimUint32(r io.Reader) (uint32, error) {
159+
var b [4]byte
160+
_, err := r.Read(b[:1])
161+
if err != nil {
162+
return 0, err
163+
}
164+
if b[0] != 0xFF {
165+
return uint32(b[0]), nil
166+
}
167+
_, err = r.Read(b[:4])
168+
if err != nil {
169+
return 0, err
170+
}
171+
return binary.LittleEndian.Uint32(b[:4]), nil
172+
}
173+
174+
func readString(r io.Reader, optimized bool) (s string, err error) {
175+
var n uint32
176+
177+
if optimized {
178+
n, err = readOptimUint32(r)
179+
if err != nil {
180+
return "", err
181+
}
182+
} else {
183+
var b [4]byte
184+
_, err := r.Read(b[:])
185+
if err != nil {
186+
return "", fmt.Errorf("failed to read string length: %v", err)
187+
}
188+
n = uint32(binary.LittleEndian.Uint32(b[:]))
189+
}
190+
191+
d := make([]byte, n)
192+
_, err = r.Read(d)
193+
if err != nil {
194+
return "", fmt.Errorf("failed to read string: %v", err)
195+
}
196+
197+
return string(d), nil
198+
}
199+
200+
func (m *Mod) ReadFrom(r io.Reader, game Version) (err error) {
201+
m.Name, err = readString(r, true)
202+
if err != nil {
203+
return fmt.Errorf("read Name: %v", err)
204+
}
205+
206+
var scratch [4]byte
207+
_, err = r.Read(scratch[:3])
208+
if err != nil {
209+
return err
210+
}
211+
if err := m.Version.UnmarshalBinary(scratch[:3]); err != nil {
212+
return fmt.Errorf("read Version: %v", err)
213+
}
214+
215+
if game.Greater(Version{0, 15, 0}) {
216+
_, err = r.Read(scratch[:4])
217+
if err != nil {
218+
return err
219+
}
220+
m.CRC = binary.LittleEndian.Uint32(scratch[:4])
221+
}
222+
223+
return nil
224+
}

src/mods_handler.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8-
"github.com/gorilla/mux"
98
"io"
109
"lockfile"
1110
"log"
1211
"net/http"
1312
"os"
1413
"path/filepath"
15-
"factorioSave"
1614
"time"
15+
16+
"github.com/gorilla/mux"
1717
)
1818

1919
type ModPortalStruct struct {
@@ -571,26 +571,58 @@ func LoadModsFromSaveHandler(w http.ResponseWriter, r *http.Request) {
571571
//Get Data out of the request
572572
SaveFile := r.FormValue("saveFile")
573573

574-
SaveFileComplete := filepath.Join(config.FactorioSavesDir, SaveFile)
575-
resp.Data, err = factorioSave.ReadHeader(SaveFileComplete)
574+
path := filepath.Join(config.FactorioSavesDir, SaveFile)
575+
archive, err := zip.OpenReader(path)
576+
if err != nil {
577+
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()
576586

577-
if err == factorioSave.ErrorIncompatible {
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 {
578604
w.WriteHeader(http.StatusInternalServerError)
579-
resp.Data = fmt.Sprintf("%s<br>Only can read 0.16.x save files", err)
605+
log.Println("could not find level.dat file in save file")
606+
resp.Data = "Error opening save file"
580607
if err := json.NewEncoder(w).Encode(resp); err != nil {
581608
log.Printf("Error in loadModsFromSave: %s", err)
582609
}
583610
return
584611
}
612+
613+
var header SaveHeader
614+
err = header.ReadFrom(data)
585615
if err != nil {
586616
w.WriteHeader(http.StatusInternalServerError)
587-
resp.Data = fmt.Sprintf("Error in searchModPortal: %s", err)
617+
log.Printf("cannot read save header: %v", err)
618+
resp.Data = "Error reading save file"
588619
if err := json.NewEncoder(w).Encode(resp); err != nil {
589620
log.Printf("Error in loadModsFromSave: %s", err)
590621
}
591622
return
592623
}
593624

625+
resp.Data = header
594626
resp.Success = true
595627

596628
if err := json.NewEncoder(w).Encode(resp); err != nil {

0 commit comments

Comments
 (0)