diff --git a/SatisfactorySaveParser/PropertyTypes/SetProperty.cs b/SatisfactorySaveParser/PropertyTypes/SetProperty.cs index e8120b5..a0351fb 100644 --- a/SatisfactorySaveParser/PropertyTypes/SetProperty.cs +++ b/SatisfactorySaveParser/PropertyTypes/SetProperty.cs @@ -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."); } diff --git a/SatisfactorySaveParser/SatisfactorySave.cs b/SatisfactorySaveParser/SatisfactorySave.cs index 6f062cf..9c14aba 100644 --- a/SatisfactorySaveParser/SatisfactorySave.cs +++ b/SatisfactorySaveParser/SatisfactorySave.cs @@ -1,4 +1,5 @@ using NLog; +using SatisfactorySaveParser.Exceptions; using SatisfactorySaveParser.Save; using SatisfactorySaveParser.Structures; using SatisfactorySaveParser.ZLib; @@ -113,42 +114,85 @@ 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 LoadLevelEntries(BinaryReader reader) + { + reader.ReadInt32(); // skip "object header and collectables size" + var levelEntries = new List(); + 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 LoadLevelCollectedObjects(BinaryReader reader) + { + var levelCollectedObjects = new List(); + 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 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) @@ -156,16 +200,6 @@ private void LoadData(BinaryReader reader) 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() @@ -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)) diff --git a/SatisfactorySaveParser/Save/FSaveCustomVersion.cs b/SatisfactorySaveParser/Save/FSaveCustomVersion.cs index 170efb5..1767362 100644 --- a/SatisfactorySaveParser/Save/FSaveCustomVersion.cs +++ b/SatisfactorySaveParser/Save/FSaveCustomVersion.cs @@ -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, + // ------------------------------------------------------ VersionPlusOne, LatestVersion = VersionPlusOne - 1 diff --git a/SatisfactorySaveParser/Save/FSaveHeader.cs b/SatisfactorySaveParser/Save/FSaveHeader.cs index df8898b..64cd679 100644 --- a/SatisfactorySaveParser/Save/FSaveHeader.cs +++ b/SatisfactorySaveParser/Save/FSaveHeader.cs @@ -68,6 +68,11 @@ public class FSaveHeader /// public bool IsModdedSave { get; set; } + /// + /// a unique identifier for this save, for analytics purposes + /// + public string SaveIdentifier { get; set; } + public void Serialize(BinaryWriter writer) { writer.Write((int)HeaderVersion); @@ -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; } } diff --git a/SatisfactorySaveParser/Save/SaveHeaderVersion.cs b/SatisfactorySaveParser/Save/SaveHeaderVersion.cs index 1e30851..d9785a9 100644 --- a/SatisfactorySaveParser/Save/SaveHeaderVersion.cs +++ b/SatisfactorySaveParser/Save/SaveHeaderVersion.cs @@ -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, + // ---------- VersionPlusOne, LatestVersion = VersionPlusOne - 1 // Last version to use