Skip to content

Commit

Permalink
add FFGDynamicStruct, update FInventoryItem, add object save version …
Browse files Browse the repository at this point in the history
…to archive
  • Loading branch information
moritz-h committed Sep 30, 2024
1 parent 4ab228e commit 081bc07
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 20 deletions.
37 changes: 31 additions & 6 deletions docs/SATISFACTORY_SAVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ The following structs are binary structs with the type described in the table:
| `Guid` | `FGuid` | [FGuid](#fguid) | |
| `IntPoint` | `FIntPoint` | `int32 X`<br>`int32 Y` | |
| `IntVector` | `FIntVector` | `int32 X`<br>`int32 Y`<br>`int32 Z` | |
| `InventoryItem` | `FInventoryItem` | `FObjectReferenceDisc ItemClass`<br>`FObjectReferenceDisc ItemState` | |
| `InventoryItem` | `FInventoryItem` | [FInventoryItem](#finventoryitem) | |
| `LBBalancerIndexing` | `FLBBalancerIndexing` | `int32 mNormalIndex`<br>`int32 mOverflowIndex`<br>`int32 mFilterIndex` | (Mod LoadBalancers) |
| `LinearColor` | `FLinearColor` | `float R`<br>`float G`<br>`float B`<br>`float A` | |
| `Quat` | `FQuat` | `double X`<br>`double Y`<br>`double Z`<br>`double W` | |
Expand All @@ -703,11 +703,6 @@ The following structs are binary structs with the type described in the table:
| `Vector2D` | `FVector2D` | `double X`<br>`double Y` | |
| `Vector` | `FVector` | `double X`<br>`double Y`<br>`double Z` | |

> Notes on `FInventoryItem`:
> The internally used types are `TSubclassOf<class UFGItemDescriptor> ItemClass` and `FSharedInventoryStatePtr ItemState`.
> The struct can be found in `FGInventoryComponent.h`.
> Both types are serialized as `FObjectReferenceDisc`.
## Class-Specific Binary Data

TODO
Expand Down Expand Up @@ -842,6 +837,22 @@ Another common type used within the save data is an `FObjectReferenceDisc`, defi

- Satisfactory internal struct, header file: `FGObjectReference.h`

#### FFGDynamicStruct

```
+--------------------------+------------------+
| bool | bHasValidStruct |
| if bHasValidStruct: | |
| FObjectReferenceDisc | ScriptStruct |
| int32 | savedPayloadSize | (size of StructInstance)
| List of Properties | StructInstance |
+--------------------------+------------------+
```

> Unreal archives internally can select binary, native or tagged property serialization for the content of StructInstance, see
> https://github.com/EpicGames/UnrealEngine/blob/5.3.2-release/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L2761.
> Here is assumed that the game will always use tagged properties.
#### FWorldPartitionValidationData

```
Expand Down Expand Up @@ -932,3 +943,17 @@ Defined in `FGActorSaveHeaderTypes.h`.
| TArray<FObjectReferenceDisc> | DestroyedActors |
+------------------------------+-----------------+
```

#### FInventoryItem

```
+--------------------------+----------------------+
| FObjectReferenceDisc | ItemClass |
| if SaveVersion >= 43: | | (see SaveVersion from parent object)
| FFGDynamicStruct | ItemState |
| else: | |
| FObjectReferenceDisc | LegacyItemStateActor |
+--------------------------+----------------------+
```

> The internal type for ItemClass is `TSubclassOf<class UFGItemDescriptor>` which is serialized as `FObjectReferenceDisc`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include "../../../IO/Archive/Archive.h"
#include "../../Properties/Base/PropertyList.h"
#include "ObjectReference.h"
#include "satisfactorysave_export.h"

namespace SatisfactorySave {

struct SATISFACTORYSAVE_API FFGDynamicStruct {
public:
FObjectReferenceDisc ScriptStruct;
PropertyList StructInstance;

void serialize(Archive& ar) {
if (ar.isIArchive()) {
bool bHasValidStruct = false;
ar << bHasValidStruct;

if (bHasValidStruct) {
ar << ScriptStruct;

int32_t savedPayloadSize = -1;
ar << savedPayloadSize;

const auto pos_before = ar.tell();

// Archive has flags to switch between binary, native and tagged property serialization, see
// https://github.com/EpicGames/UnrealEngine/blob/5.3.2-release/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L2761
// We assume tagged properties here.
ar << StructInstance;

if (ar.tell() - pos_before != savedPayloadSize) {
throw std::runtime_error("Invalid FFGDynamicStruct!");
}
}
} else {
bool bHasValidStruct = !ScriptStruct.PathName.empty();
ar << bHasValidStruct;

if (bHasValidStruct) {
ar << ScriptStruct;

const auto offsetBeforeSavedPayloadSize = ar.tell();

int32_t savedPayloadSize = -1;
ar << savedPayloadSize;

const auto offsetBeforeStructPayload = ar.tell();

ar << StructInstance;

savedPayloadSize = static_cast<int32_t>(ar.tell() - offsetBeforeStructPayload);
const auto currentArchiveOffset = ar.tell();
ar.seek(offsetBeforeSavedPayloadSize);
ar << savedPayloadSize;
ar.seek(currentArchiveOffset);
}
}
}
};
} // namespace SatisfactorySave
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
#pragma once

#include "../../../IO/Archive/Archive.h"
#include "FGDynamicStruct.h"
#include "ObjectReference.h"
#include "satisfactorysave_export.h"

namespace SatisfactorySave {

// FInventoryItem
struct SATISFACTORYSAVE_API FInventoryItem {
public:
FObjectReferenceDisc ItemClass;
FObjectReferenceDisc ItemState;
FFGDynamicStruct ItemState;
FObjectReferenceDisc LegacyItemStateActor;

void serialize(Archive& ar) {
ar << ItemClass;
ar << ItemState;
if (ar.getSaveVersion() >= 43) {
ar << ItemState;
} else {
ar << LegacyItemStateActor;
}
}
};
} // namespace SatisfactorySave
15 changes: 15 additions & 0 deletions libsave/include/SatisfactorySave/IO/Archive/Archive.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <concepts>
#include <filesystem>
#include <optional>
#include <stack>
#include <stdexcept>
#include <type_traits>
#include <vector>
Expand Down Expand Up @@ -116,6 +117,18 @@ namespace SatisfactorySave {
virtual std::size_t tell() = 0;
virtual void seek(std::size_t pos) = 0;

inline void pushSaveVersion(auto saveVersion) {
save_version_stack_.push(saveVersion);
}

inline void popSaveVersion() {
save_version_stack_.pop();
}

inline int32_t getSaveVersion() {
return !save_version_stack_.empty() ? save_version_stack_.top() : 0;
}

protected:
Archive() = default;
~Archive() = default;
Expand All @@ -125,5 +138,7 @@ namespace SatisfactorySave {
virtual void serializeName(FName& n);
virtual void serializeObjectReference(FObjectReferenceDisc& ref);
virtual void validateReadLimit(std::size_t) {};

std::stack<int32_t> save_version_stack_;
};
} // namespace SatisfactorySave
3 changes: 3 additions & 0 deletions libsave/src/GameTypes/Save/SaveGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ SatisfactorySave::SaveGame::SaveGame(const std::filesystem::path& filepath) {
const auto file_data_blob_size = file_data_blob->size();
IStreamArchive ar(std::make_unique<MemIStream>(std::move(file_data_blob)));

// Store header SaveVersion as first stack entry.
ar.pushSaveVersion(header_.SaveVersion);

// Validate blob size
if (file_data_blob_size - sizeof(int64_t) != ar.read<int64_t>()) {
throw std::runtime_error("Bad blob size!");
Expand Down
9 changes: 9 additions & 0 deletions libsave/src/GameTypes/Save/SerializationUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace SatisfactorySave {
if constexpr (object_headers) {
ar << saveObjects[i]->SaveVersion;
ar << saveObjects[i]->ShouldMigrateObjectRefsToPersistent;
ar.pushSaveVersion(saveObjects[i]->SaveVersion);
}

// Check stream pos to validate parser.
Expand All @@ -64,6 +65,9 @@ namespace SatisfactorySave {
if (pos_after - pos_before != length) {
throw std::runtime_error("Error parsing object data!");
}
if constexpr (object_headers) {
ar.popSaveVersion();
}
}
TIME_MEASURE_END("ObjProp");

Expand Down Expand Up @@ -111,6 +115,7 @@ namespace SatisfactorySave {
if constexpr (object_headers) {
ar << obj->SaveVersion;
ar << obj->ShouldMigrateObjectRefsToPersistent;
ar.pushSaveVersion(obj->SaveVersion);
}

auto pos_size = ar.tell();
Expand All @@ -123,6 +128,10 @@ namespace SatisfactorySave {
ar.seek(pos_size);
ar.write(static_cast<int32_t>(pos_after - pos_before));
ar.seek(pos_after);

if constexpr (object_headers) {
ar.popSaveVersion();
}
}

auto blob_pos_after = ar.tell();
Expand Down
8 changes: 7 additions & 1 deletion libsavepy/GameTypes/UE/Satisfactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,20 @@ void init_GameTypes_UE_Satisfactory(py::module_& m) {
.def_readwrite("Cost", &s::FBlueprintHeader::Cost)
.def_readwrite("RecipeRefs", &s::FBlueprintHeader::RecipeRefs);

py::class_<s::FFGDynamicStruct>(m, "FFGDynamicStruct")
.def(py::init<>())
.def_readwrite("ScriptStruct", &s::FFGDynamicStruct::ScriptStruct)
.def_readwrite("StructInstance", &s::FFGDynamicStruct::StructInstance);

py::class_<s::FFluidBox>(m, "FFluidBox")
.def(py::init<>())
.def_readwrite("Value", &s::FFluidBox::Value);

py::class_<s::FInventoryItem>(m, "FInventoryItem")
.def(py::init<>())
.def_readwrite("ItemClass", &s::FInventoryItem::ItemClass)
.def_readwrite("ItemState", &s::FInventoryItem::ItemState);
.def_readwrite("ItemState", &s::FInventoryItem::ItemState)
.def_readwrite("LegacyItemStateActor", &s::FInventoryItem::LegacyItemStateActor);

py::class_<s::FObjectReferenceDisc>(m, "FObjectReferenceDisc")
.def(py::init<>())
Expand Down
20 changes: 15 additions & 5 deletions map/src/MapWindow/UI/PropertyTableEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,24 @@ namespace {
}

void visit(SatisfactorySave::InventoryItemStruct& s) override {
ImGui::Text("ItemClass Lvl: %s", s.Data.ItemClass.LevelName.c_str());
ImGui::Text("ItemClass Path:");
ImGui::Text("ItemClass:");
ImGui::Text("Lvl: %s", s.Data.ItemClass.LevelName.c_str());
ImGui::Text("Path:");
ImGui::SameLine();
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.ItemClass.PathName, callback_);
ImGui::Text("ItemState Lvl: %s", s.Data.ItemState.LevelName.c_str());
ImGui::Text("ItemState Path:");
ImGui::Text("ItemState.ScriptStruct:");
ImGui::Text("Lvl: %s", s.Data.ItemState.ScriptStruct.LevelName.c_str());
ImGui::Text("Path:");
ImGui::SameLine();
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.ItemState.ScriptStruct.PathName, callback_);
ImGui::Text("ItemState.StructInstance:");
Satisfactory3DMap::PropertyTableEditor r;
r.renderGui(s.Data.ItemState.StructInstance, callback_);
ImGui::Text("LegacyItemStateActor:");
ImGui::Text("Lvl: %s", s.Data.LegacyItemStateActor.LevelName.c_str());
ImGui::Text("Path:");
ImGui::SameLine();
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.ItemState.PathName, callback_);
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.LegacyItemStateActor.PathName, callback_);
}

void visit(SatisfactorySave::LBBalancerIndexingStruct& s) override {
Expand Down
20 changes: 15 additions & 5 deletions map/src/MapWindow/UI/PropertyTableGuiRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,24 @@ namespace {
}

void visit(SatisfactorySave::InventoryItemStruct& s) override {
ImGui::Text("ItemClass Lvl: %s", s.Data.ItemClass.LevelName.c_str());
ImGui::Text("ItemClass Path:");
ImGui::Text("ItemClass:");
ImGui::Text("Lvl: %s", s.Data.ItemClass.LevelName.c_str());
ImGui::Text("Path:");
ImGui::SameLine();
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.ItemClass.PathName, callback_);
ImGui::Text("ItemState Lvl: %s", s.Data.ItemState.LevelName.c_str());
ImGui::Text("ItemState Path:");
ImGui::Text("ItemState.ScriptStruct:");
ImGui::Text("Lvl: %s", s.Data.ItemState.ScriptStruct.LevelName.c_str());
ImGui::Text("Path:");
ImGui::SameLine();
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.ItemState.ScriptStruct.PathName, callback_);
ImGui::Text("ItemState.StructInstance:");
Satisfactory3DMap::PropertyTableGuiRenderer r;
r.renderGui(s.Data.ItemState.StructInstance, callback_);
ImGui::Text("LegacyItemStateActor:");
ImGui::Text("Lvl: %s", s.Data.LegacyItemStateActor.LevelName.c_str());
ImGui::Text("Path:");
ImGui::SameLine();
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.ItemState.PathName, callback_);
Satisfactory3DMap::ImGuiUtil::PathLink(s.Data.LegacyItemStateActor.PathName, callback_);
}

void visit(SatisfactorySave::LBBalancerIndexingStruct& s) override {
Expand Down

0 comments on commit 081bc07

Please sign in to comment.