z_mirror/fmm

git clone https://git.raiguard.me/z_mirror/fmm

Caleb HeuerSimplify detection of zip file names20525de

trunk
2.9 KiB114 linesraw
1package fmm
2
3import (
4	"archive/zip"
5	"compress/zlib"
6	"errors"
7	"path"
8)
9
10type SaveFileInfo struct {
11	MapVersion                Version
12	BranchVersion             uint8
13	CampaignName              string
14	LevelName                 string
15	ScenarioModName           string
16	Difficulty                uint8
17	Finished                  bool
18	PlayerWon                 bool
19	NextLevel                 string
20	CanContinue               bool
21	FinishedButContinuing     bool
22	SavingReplay              bool
23	AllowNonAdminDebugOptions bool
24	ScenarioVersion           Version
25	ScenarioBuildVersion      uint32
26	AllowedCommands           AllowedCommands
27	LargeBlueprintSize        uint32
28	ActiveMods                []ModIdent
29	StartupModSettingsCrc     uint32
30	StartupModSettings        PropertyTree
31}
32
33// Returns information about the given save file.
34func ParseSaveFile(filepath string) (SaveFileInfo, error) {
35	info := SaveFileInfo{}
36	zipReader, err := zip.OpenReader(filepath)
37	if err != nil {
38		return info, err
39	}
40	defer zipReader.Close()
41
42	var dat *zip.File
43	compressed := false
44	for _, file := range zipReader.File {
45		name := path.Base(file.Name)
46		if name == "level.dat" || name == "level.dat0" {
47			dat = file
48			compressed = name == "level.dat0"
49			break
50		}
51	}
52	if dat == nil {
53		return info, errors.New("invalid save file: could not locate level data")
54	}
55
56	rawReader, err := dat.Open()
57	if err != nil {
58		return info, err
59	}
60	if compressed {
61		rawReader, err = zlib.NewReader(rawReader)
62		if err != nil {
63			return info, err
64		}
65	}
66	defer rawReader.Close()
67
68	// TODO: Add tests for various save files.
69	r := newDatReader(rawReader)
70
71	info.MapVersion = r.ReadVersionUnoptimized()
72	info.BranchVersion = r.ReadUint8()
73	info.CampaignName = r.ReadString()
74	info.LevelName = r.ReadString()
75	info.ScenarioModName = r.ReadString()
76	info.Difficulty = r.ReadUint8()
77	info.Finished = r.ReadBool()
78	info.PlayerWon = r.ReadBool()
79	info.NextLevel = r.ReadString()
80	info.CanContinue = r.ReadBool()
81	info.FinishedButContinuing = r.ReadBool()
82	info.SavingReplay = r.ReadBool()
83	info.AllowNonAdminDebugOptions = r.ReadBool()
84	info.ScenarioVersion = r.ReadVersionOptimized(false)
85
86	// Why is this uint32/uint16 instead of uint16/uint8!?
87	if info.MapVersion.Cmp(&Version{1, 2, 0, 127}) == VersionGt {
88		info.ScenarioBuildVersion = r.ReadUint32()
89	} else {
90		info.ScenarioBuildVersion = uint32(r.ReadUint16())
91	}
92
93	info.AllowedCommands = r.ReadAllowedCommands()
94
95	if (info.MapVersion.Cmp(&Version{1, 2, 8, 8}) == VersionGt) {
96		info.LargeBlueprintSize = r.ReadUint32()
97	}
98
99	numMods := r.ReadUint32Optimized()
100	info.ActiveMods = make([]ModIdent, numMods)
101	for i := uint32(0); i < numMods; i++ {
102		info.ActiveMods[i] = r.ReadModWithCRC()
103	}
104
105	info.StartupModSettingsCrc = r.ReadUint32()
106
107	info.StartupModSettings = r.ReadPropertyTree()
108
109	if r.err != nil {
110		return info, errors.Join(errors.New("failed to parse save file"), r.err)
111	}
112
113	return info, nil
114}