Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions SatisfactorySaveParser/PropertyTypes/SetProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ public static SetProperty Parse(string propertyName, int index, BinaryReader rea
}
}
break;
case StructProperty.TypeName:
{
if (propertyName != "mRemovalLocations")
{
throw new NotImplementedException("Parsing a set of StructProperty other than mRemovalLocations is not yet supported");
}
var locationsCount = reader.ReadInt32();
for (var i = 0; i < locationsCount; i++)
{
var location = new Structs.Vector(reader);
var element = new StructProperty("location", i);
element.Data = location;
result.Elements.Add(element);
}
}
break;
default:
throw new NotImplementedException($"Parsing a set of {result.Type} is not yet supported.");
}
Expand Down
90 changes: 67 additions & 23 deletions SatisfactorySaveParser/SatisfactorySave.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NLog;
using SatisfactorySaveParser.Exceptions;
using SatisfactorySaveParser.Save;
using SatisfactorySaveParser.Structures;
using SatisfactorySaveParser.ZLib;
Expand Down Expand Up @@ -113,59 +114,92 @@ public SatisfactorySave(string file)

private void LoadData(BinaryReader reader)
{
// Does not need to be a public property because it's equal to Entries.Count
var totalSaveObjects = reader.ReadUInt32();
log.Info($"Save contains {totalSaveObjects} object headers");
// FIXME currenty ONLY update 6 savefile can be loaded
if (Header.SaveVersion != FSaveCustomVersion.LatestVersion)
{
throw new UnknownBuildVersionException(Header.SaveVersion);
}
if (Header.HeaderVersion != SaveHeaderVersion.LatestVersion)
{
throw new UnknownSaveVersionException(Header.HeaderVersion);
}

var sublevelCount = reader.ReadInt32();
for (int sublevelIndex = 0; sublevelIndex <= sublevelCount; sublevelIndex++) // sublevels and the "persistent level" at the end!
{
var levelName = (sublevelIndex == sublevelCount) ? Header.MapName : reader.ReadLengthPrefixedString(); // use MapName for the persistent level
var levelEntries = LoadLevelEntries(reader);
log.Info($"Level '{levelName}' contains {levelEntries.Count} entries");
Entries.AddRange(levelEntries);

var levelCollectedObjects = LoadLevelCollectedObjects(reader);
log.Info($"Level '{levelName}' contains {levelCollectedObjects.Count} collected objects");
CollectedObjects.AddRange(levelCollectedObjects);

ParseLevelEntries(levelEntries, reader);
LoadLevelCollectedObjects(reader); // skip second collectables
}
}

private List<SaveObject> LoadLevelEntries(BinaryReader reader)
{
reader.ReadInt32(); // skip "object header and collectables size"
var levelEntries = new List<SaveObject>();
var totalSaveObjects = reader.ReadInt32();

// Saved entities loop
for (int i = 0; i < totalSaveObjects; i++)
{
var type = reader.ReadInt32();
switch (type)
{
case SaveEntity.TypeID:
Entries.Add(new SaveEntity(reader));
levelEntries.Add(new SaveEntity(reader));
break;
case SaveComponent.TypeID:
Entries.Add(new SaveComponent(reader));
levelEntries.Add(new SaveComponent(reader));
break;
default:
throw new InvalidOperationException($"Unexpected type {type}");
}
}
return levelEntries;
}

var totalSaveObjectData = reader.ReadInt32();
log.Info($"Save contains {totalSaveObjectData} object data");
Trace.Assert(Entries.Count == totalSaveObjects);
Trace.Assert(Entries.Count == totalSaveObjectData);
private List<ObjectReference> LoadLevelCollectedObjects(BinaryReader reader)
{
var levelCollectedObjects = new List<ObjectReference>();
var collectedObjectsCount = reader.ReadInt32();

for (int i = 0; i < Entries.Count; i++)
for (int i = 0; i < collectedObjectsCount; i++)
{
levelCollectedObjects.Add(new ObjectReference(reader));
}
return levelCollectedObjects;
}

private void ParseLevelEntries(List<SaveObject> levelEntries, BinaryReader reader)
{
reader.ReadInt32(); // skip "objects size"
var objectCount = reader.ReadInt32();
Trace.Assert(levelEntries.Count == objectCount);

for (int i = 0; i < objectCount; i++)
{
var len = reader.ReadInt32();
var before = reader.BaseStream.Position;

#if DEBUG
//log.Trace($"Reading {len} bytes @ {before} for {Entries[i].TypePath}");
//log.Trace($"Reading {len} bytes @ {before} for {levelEntries[i].TypePath}");
#endif

Entries[i].ParseData(len, reader, Header.BuildVersion);
levelEntries[i].ParseData(len, reader, Header.BuildVersion);
var after = reader.BaseStream.Position;

if (before + len != after)
{
throw new InvalidOperationException($"Expected {len} bytes read but got {after - before}");
}
}

var collectedObjectsCount = reader.ReadInt32();
log.Info($"Save contains {collectedObjectsCount} collected objects");
for (int i = 0; i < collectedObjectsCount; i++)
{
CollectedObjects.Add(new ObjectReference(reader));
}

log.Debug($"Read {reader.BaseStream.Position} of total {reader.BaseStream.Length} bytes");
Trace.Assert(reader.BaseStream.Position == reader.BaseStream.Length);
}

public void Save()
Expand All @@ -177,6 +211,16 @@ public void Save(string file)
{
log.Info($"Writing save file: {file}");

// FIXME update 6 savefile can be loaded, but not saved yet
if (Header.SaveVersion > FSaveCustomVersion.TrainBlueprintClassAdded)
{
throw new UnknownBuildVersionException(Header.SaveVersion);
}
if (Header.HeaderVersion > SaveHeaderVersion.UE426EngineUpdate)
{
throw new UnknownSaveVersionException(Header.HeaderVersion);
}

FileName = Environment.ExpandEnvironmentVariables(file);
using (var stream = new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.Write))
using (var writer = new BinaryWriter(stream))
Expand Down
6 changes: 6 additions & 0 deletions SatisfactorySaveParser/Save/FSaveCustomVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ public enum FSaveCustomVersion
// 2021-09-21 Migrate FGTrain from native only to a blueprint class BP_Train.
TrainBlueprintClassAdded,

// 2021-12-03: Added sublevel streaming support
AddedSublevelStreaming,

// 2022-07-28: Added Coloring support to concrete pillars, in post load we check if the swatch if the default one, if so we swap it with concrete.
AddedColoringSupportToConcretePillars,

// -----<new versions can be added above this line>-------------------------------------------------
VersionPlusOne,
LatestVersion = VersionPlusOne - 1
Expand Down
11 changes: 11 additions & 0 deletions SatisfactorySaveParser/Save/FSaveHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public class FSaveHeader
/// </summary>
public bool IsModdedSave { get; set; }

/// <summary>
/// a unique identifier for this save, for analytics purposes
/// </summary>
public string SaveIdentifier { get; set; }

public void Serialize(BinaryWriter writer)
{
writer.Write((int)HeaderVersion);
Expand Down Expand Up @@ -137,6 +142,12 @@ public static FSaveHeader Parse(BinaryReader reader)
log.Debug($"ModMetadata={header.ModMetadata}, IsModdedSave={header.IsModdedSave}");
}

if (header.HeaderVersion >= SaveHeaderVersion.AddedSaveIdentifier)
{
header.SaveIdentifier = reader.ReadLengthPrefixedString();
log.Debug($"SaveIdentifier={header.SaveIdentifier}");
}

return header;
}
}
Expand Down
3 changes: 3 additions & 0 deletions SatisfactorySaveParser/Save/SaveHeaderVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public enum SaveHeaderVersion
// @2021-04-15 UE4.26 Engine Upgrade. FEditorObjectVersion Changes occurred
UE426EngineUpdate,

// @2022-03-22 Added GUID to identify saves, it is for analytics purposes.
AddedSaveIdentifier,

// -----<new versions can be added above this line>-----
VersionPlusOne,
LatestVersion = VersionPlusOne - 1 // Last version to use
Expand Down