Skip to content

Commit 2ff2e46

Browse files
committed
add datetime and checkmark struct
1 parent f5eadff commit 2ff2e46

12 files changed

Lines changed: 90 additions & 23 deletions

File tree

docs/SATISFACTORY_SAVE.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Documentation of the Satisfactory save game file structure.
6464
- [FText](#ftext)
6565
- [FGuid](#fguid)
6666
- [FMD5Hash](#fmd5hash)
67+
- [FDateTime](#fdatetime)
6768
- [Satisfactory Objects](#satisfactory-objects)
6869
- [FObjectReferenceDisc](#fobjectreferencedisc)
6970
- [FFGDynamicStruct](#ffgdynamicstruct)
@@ -149,7 +150,7 @@ The save header has the following structure:
149150
| if SaveHeaderVersion >= 3: | |
150151
| int32 | PlayDurationSeconds |
151152
| if SaveHeaderVersion >= 4: | |
152-
| int64 | SaveDateTime |
153+
| FDateTime | SaveDateTime |
153154
| if SaveHeaderVersion >= 5: | |
154155
| int8 | SessionVisibility | value is not used anymore
155156
| if SaveHeaderVersion >= 7: | |
@@ -174,9 +175,6 @@ For Update 8 and 1.0 the header version was `13`, for 1.1 it is `14`.
174175
Internally, an enum `Type` is used for this number, see `FGSaveManagerInterface.h` distributed with the game files.
175176
The variable names are taken from the struct `FSaveHeader` in `FGSaveManagerInterface.h`.
176177

177-
`SaveDateTime` is the serialization of an [FDateTime object](https://docs.unrealengine.com/en-US/API/Runtime/Core/Misc/FDateTime/index.html).
178-
Ticks since 0001-01-01 00:00, where 1 tick is 100 nanoseconds. Satisfactory seems to use the UTC time zone.
179-
180178
> Source Reference: `FGSaveManagerInterface.h` - `struct FSaveHeader`
181179
182180
### Chunks
@@ -955,6 +953,7 @@ The following struct names were observed to be property structs:
955953
`FoliageRemovalSaveDataPerCell`,
956954
`FoliageRemovalUnresolvedSaveDataPerCell`,
957955
`FoundationSideSelectionFlags`,
956+
`GCheckmarkUnlockData`,
958957
`GlobalColorPreset`,
959958
`HardDriveData`,
960959
`HeadlightParams`,
@@ -1168,6 +1167,17 @@ FName is serialized as [`FString`](#fstring) in the save game.
11681167
+---------------+----------+
11691168
```
11701169

1170+
#### FDateTime
1171+
1172+
```
1173+
+-------+-------+
1174+
| int64 | Ticks |
1175+
+-------+-------+
1176+
```
1177+
1178+
[Unreal Docs](https://docs.unrealengine.com/en-US/API/Runtime/Core/Misc/FDateTime/index.html)
1179+
Ticks since 0001-01-01 00:00, where 1 tick is 100 nanoseconds. Satisfactory seems to use the UTC time zone.
1180+
11711181
### Satisfactory Objects
11721182

11731183
#### FObjectReferenceDisc

libsave/include/SatisfactorySave/GameTypes/FactoryGame/FGSaveManagerInterface.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <string>
55

66
#include "../../IO/Archive/Archive.h"
7+
#include "../UE/Core/Misc/DateTime.h"
78
#include "../UE/Core/Misc/SecureHash.h"
89
#include "satisfactorysave_export.h"
910

@@ -39,7 +40,7 @@ namespace SatisfactorySave {
3940
std::string MapOptions;
4041
std::string SessionName;
4142
int32_t PlayDurationSeconds = 0;
42-
int64_t SaveDateTime = 0;
43+
FDateTime SaveDateTime;
4344
int8_t SessionVisibility = 0;
4445
int32_t EditorObjectVersion = 0;
4546
std::string ModMetadata;

libsave/include/SatisfactorySave/GameTypes/Structs/Base/StructAll.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "../BoxStruct.h"
44
#include "../ClientIdentityInfoStruct.h"
55
#include "../ColorStruct.h"
6+
#include "../DateTimeStruct.h"
67
#include "../FluidBoxStruct.h"
78
#include "../GuidStruct.h"
89
#include "../IntPointStruct.h"

libsave/include/SatisfactorySave/GameTypes/Structs/Base/StructVisitor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace SatisfactorySave {
77
class BoxStruct;
88
class ClientIdentityInfoStruct;
99
class ColorStruct;
10+
class DateTimeStruct;
1011
class FluidBoxStruct;
1112
class GuidStruct;
1213
class IntPointStruct;
@@ -32,6 +33,7 @@ namespace SatisfactorySave {
3233
virtual void visit(BoxStruct& s) = 0;
3334
virtual void visit(ClientIdentityInfoStruct& s) = 0;
3435
virtual void visit(ColorStruct& s) = 0;
36+
virtual void visit(DateTimeStruct& s) = 0;
3537
virtual void visit(FluidBoxStruct& s) = 0;
3638
virtual void visit(GuidStruct& s) = 0;
3739
virtual void visit(IntPointStruct& s) = 0;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include "../UE/Core/Misc/DateTime.h"
4+
#include "Base/StructImpl.h"
5+
6+
namespace SatisfactorySave {
7+
8+
class SATISFACTORYSAVE_API DateTimeStruct final : public StructImpl<DateTimeStruct, FDateTime> {
9+
public:
10+
static constexpr std::string_view TypeName = "DateTime";
11+
12+
using StructImpl<DateTimeStruct, FDateTime>::StructImpl;
13+
};
14+
} // namespace SatisfactorySave
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <chrono>
5+
#include <ctime>
6+
#include <string>
7+
8+
#include "../../../../IO/Archive/Archive.h"
9+
#include "satisfactorysave_export.h"
10+
11+
namespace SatisfactorySave {
12+
13+
class SATISFACTORYSAVE_API FDateTime {
14+
public:
15+
int64_t Ticks = 0;
16+
17+
void serialize(Archive& ar) {
18+
ar << Ticks;
19+
}
20+
21+
[[nodiscard]] std::string toString() const {
22+
// save_date_time is integer ticks since 0001-01-01 00:00, where 1 tick is 100 nano seconds.
23+
// See: https://docs.unrealengine.com/en-US/API/Runtime/Core/Misc/FDateTime/index.html
24+
// Satisfactory seems to use UTC.
25+
// Convert to unix timestamp:
26+
// Python: (datetime.datetime(1970, 1, 1) - datetime.datetime(1, 1, 1)).total_seconds()
27+
// => 62135596800.0 seconds
28+
std::time_t save_date_time = (Ticks - 621355968000000000) / 10000000;
29+
30+
// Convert values
31+
std::string save_date_str(20, '\0');
32+
std::strftime(save_date_str.data(), save_date_str.size(), "%F %T", std::localtime(&save_date_time));
33+
save_date_str.erase(std::find(save_date_str.begin(), save_date_str.end(), '\0'), save_date_str.end());
34+
return save_date_str;
35+
}
36+
};
37+
} // namespace SatisfactorySave

libsave/src/GameTypes/FactoryGame/FGSaveManagerInterface.cpp

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
#include "GameTypes/FactoryGame/FGSaveManagerInterface.h"
22

3-
#include <algorithm>
4-
#include <chrono>
5-
#include <ctime>
63
#include <iostream>
74
#include <sstream>
85
#include <stdexcept>
6+
#include <string>
97

108
void SatisfactorySave::FSaveHeader::serialize(Archive& ar) {
119
ar << SaveHeaderVersion;
@@ -71,20 +69,6 @@ void SatisfactorySave::FSaveHeader::serialize(Archive& ar) {
7169
}
7270

7371
std::string SatisfactorySave::FSaveHeader::toString() const {
74-
// save_date_time is integer ticks since 0001-01-01 00:00, where 1 tick is 100 nano seconds.
75-
// See: https://docs.unrealengine.com/en-US/API/Runtime/Core/Misc/FDateTime/index.html
76-
// Satisfactory seems to use UTC.
77-
// Convert to unix timestamp:
78-
// Python: (datetime.datetime(1970, 1, 1) - datetime.datetime(1, 1, 1)).total_seconds()
79-
// => 62135596800.0 seconds
80-
std::time_t save_date_time = (SaveDateTime - 621355968000000000) / 10000000;
81-
82-
// Convert values
83-
std::string save_date_str(20, '\0');
84-
std::strftime(save_date_str.data(), save_date_str.size(), "%F %T", std::localtime(&save_date_time));
85-
save_date_str.erase(std::find(save_date_str.begin(), save_date_str.end(), '\0'), save_date_str.end());
86-
87-
// Build string
8872
std::stringstream s;
8973
s << "SaveHeaderVersion: " << SaveHeaderVersion << std::endl;
9074
s << "SaveVersion: " << SaveVersion << std::endl;
@@ -94,7 +78,7 @@ std::string SatisfactorySave::FSaveHeader::toString() const {
9478
s << "MapOptions: " << MapOptions << std::endl;
9579
s << "SessionName: " << SessionName << std::endl;
9680
s << "PlayDurationSeconds: " << PlayDurationSeconds << " (" << PlayDurationSeconds / 3600.0 << " h)" << std::endl;
97-
s << "SaveDateTime: " << save_date_time << " (" << save_date_str << ")" << std::endl;
81+
s << "SaveDateTime: " << SaveDateTime.toString() << std::endl;
9882
s << "SessionVisibility: " << static_cast<bool>(SessionVisibility) << std::endl;
9983
s << "EditorObjectVersion: " << EditorObjectVersion << std::endl;
10084
s << "ModMetadata: " << ModMetadata << std::endl;

libsave/src/GameTypes/Structs/Base/Struct.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ std::shared_ptr<SatisfactorySave::Struct> SatisfactorySave::Struct::create(const
4040
"FoliageRemovalSaveDataPerCell",
4141
"FoliageRemovalUnresolvedSaveDataPerCell",
4242
"FoundationSideSelectionFlags",
43+
"GCheckmarkUnlockData",
4344
"GlobalColorPreset",
4445
"HardDriveData",
4546
"HeadlightParams",
@@ -117,6 +118,8 @@ std::shared_ptr<SatisfactorySave::Struct> SatisfactorySave::Struct::create(const
117118
s = std::make_shared<ClientIdentityInfoStruct>();
118119
} else if (struct_name == ColorStruct::TypeName) {
119120
s = std::make_shared<ColorStruct>();
121+
} else if (struct_name == DateTimeStruct::TypeName) {
122+
s = std::make_shared<DateTimeStruct>();
120123
} else if (struct_name == FluidBoxStruct::TypeName) {
121124
s = std::make_shared<FluidBoxStruct>();
122125
} else if (struct_name == GuidStruct::TypeName) {

libsavepy/src/GameTypes/Structs.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ void init_GameTypes_Structs(py::module_& m) {
1919
.def(py::init<>())
2020
.def_readwrite("Data", &s::ColorStruct::Data);
2121

22+
py::class_<s::DateTimeStruct, s::Struct, std::shared_ptr<s::DateTimeStruct>>(m, "DateTimeStruct")
23+
.def(py::init<>())
24+
.def_readwrite("Data", &s::DateTimeStruct::Data);
25+
2226
py::class_<s::FluidBoxStruct, s::Struct, std::shared_ptr<s::FluidBoxStruct>>(m, "FluidBoxStruct")
2327
.def(py::init<>())
2428
.def_readwrite("Data", &s::FluidBoxStruct::Data);

libsavepy/src/GameTypes/UE.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "SatisfactorySave/GameTypes/UE/Core/Math/Vector.h"
1010
#include "SatisfactorySave/GameTypes/UE/Core/Math/Vector2D.h"
1111
#include "SatisfactorySave/GameTypes/UE/Core/Math/Vector4.h"
12+
#include "SatisfactorySave/GameTypes/UE/Core/Misc/DateTime.h"
1213
#include "SatisfactorySave/GameTypes/UE/Core/Misc/Guid.h"
1314
#include "SatisfactorySave/GameTypes/UE/Core/Misc/SecureHash.h"
1415
#include "SatisfactorySave/GameTypes/UE/Core/UObject/NameTypes.h"
@@ -210,6 +211,11 @@ void init_GameTypes_UE(py::module_& m) {
210211

211212
// Core/Misc
212213

214+
py::class_<s::FDateTime>(m, "FDateTime")
215+
.def(py::init<>())
216+
.def_readwrite("Ticks", &s::FDateTime::Ticks)
217+
.def("toString", &s::FDateTime::toString);
218+
213219
py::class_<s::FGuid>(m, "FGuid")
214220
.def(py::init<>())
215221
.def("isZero", &s::FGuid::isZero)

0 commit comments

Comments
 (0)