public void TestSaveHeaderV5Writing() { using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { var header = new FSaveHeader { HeaderVersion = SaveHeaderV5HeaderVersion, SaveVersion = SaveHeaderV5SaveVersion, BuildVersion = SaveHeaderV5BuildVersion, MapName = SaveHeaderV5MapName, MapOptions = SaveHeaderV5MapOptions, SessionName = SaveHeaderV5SessionName, PlayDuration = SaveHeaderV5PlayDuration, SaveDateTime = SaveHeaderV5SaveDateTime, SessionVisibility = SaveHeaderV5SessionVisibility }; header.Serialize(writer); CollectionAssert.AreEqual(SaveHeaderV5Bytes, stream.ToArray()); } }
/// <summary> /// Open a savefile from disk /// </summary> /// <param name="file">Full path to the .sav file, usually found in %localappdata%/FactoryGame/Saved/SaveGames</param> public SatisfactorySave(string file) { log.Info($"Opening save file: {file}"); FileName = Environment.ExpandEnvironmentVariables(file); using (var stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var reader = new BinaryReader(stream)) { Header = FSaveHeader.Parse(reader); if (Header.SaveVersion < FSaveCustomVersion.SaveFileIsCompressed) { LoadData(reader); } else { using (var buffer = new MemoryStream()) { var uncompressedSize = 0L; while (stream.Position < stream.Length) { var header = reader.ReadChunkInfo(); Trace.Assert(header.CompressedSize == ChunkInfo.Magic); var summary = reader.ReadChunkInfo(); var subChunk = reader.ReadChunkInfo(); Trace.Assert(subChunk.UncompressedSize == summary.UncompressedSize); var startPosition = stream.Position; using (var zStream = new ZlibStream(stream, CompressionMode.Decompress, true)) { zStream.CopyTo(buffer); } // ZlibStream appears to read more bytes than it uses (because of buffering probably) so we need to manually fix the input stream position stream.Position = startPosition + subChunk.CompressedSize; uncompressedSize += subChunk.UncompressedSize; } buffer.Position = 0; using (var bufferReader = new BinaryReader(buffer)) { var dataLength = bufferReader.ReadInt32(); Trace.Assert(uncompressedSize == dataLength + 4); LoadData(bufferReader); } } } } }
public void TestSaveHeaderV4Reading() { using (var stream = new MemoryStream(SaveHeaderV4Bytes)) using (var reader = new BinaryReader(stream)) { var header = FSaveHeader.Parse(reader); Assert.AreEqual(SaveHeaderV4HeaderVersion, header.HeaderVersion); Assert.AreEqual(SaveHeaderV4SaveVersion, header.SaveVersion); Assert.AreEqual(SaveHeaderV4BuildVersion, header.BuildVersion); Assert.AreEqual(SaveHeaderV4MapName, header.MapName); Assert.AreEqual(SaveHeaderV4MapOptions, header.MapOptions); Assert.AreEqual(SaveHeaderV4SessionName, header.SessionName); Assert.AreEqual(SaveHeaderV4PlayDuration, header.PlayDuration); Assert.AreEqual(SaveHeaderV4SaveDateTime, header.SaveDateTime); Assert.AreEqual(stream.Length, stream.Position); } }
/// <summary> /// Open a savefile from disk /// </summary> /// <param name="file">Full path to the .sav file, usually found in %localappdata%/FactoryGame/Saved/SaveGames</param> public SatisfactorySave(string file) { log.Info($"Opening save file: {file}"); FileName = Environment.ExpandEnvironmentVariables(file); using (var stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var reader = new BinaryReader(stream)) { Header = FSaveHeader.Parse(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"); // Saved entities loop for (int i = 0; i < totalSaveObjects; i++) { var type = reader.ReadInt32(); switch (type) { case SaveEntity.TypeID: Entries.Add(new SaveEntity(reader)); break; case SaveComponent.TypeID: Entries.Add(new SaveComponent(reader)); break; default: throw new InvalidOperationException($"Unexpected type {type}"); } } var totalSaveObjectData = reader.ReadInt32(); log.Info($"Save contains {totalSaveObjectData} object data"); Trace.Assert(Entries.Count == totalSaveObjects); Trace.Assert(Entries.Count == totalSaveObjectData); for (int i = 0; i < Entries.Count; i++) { var len = reader.ReadInt32(); var before = reader.BaseStream.Position; #if DEBUG //log.Trace($"Reading {len} bytes @ {before} for {Entries[i].TypePath}"); #endif Entries[i].ParseData(len, reader); 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); } }
/// <summary> /// Open a savefile from disk /// </summary> /// <param name="file">Full path to the .sav file, usually found in %localappdata%/FactoryGame/Saved/SaveGames</param> public SatisfactorySave(string file) { log.Info($"Opening save file: {file}"); FileName = Environment.ExpandEnvironmentVariables(file); using (var stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var reader = new BinaryReader(stream)) { if (stream.Length == 0) { throw new Exception("Save file is completely empty"); } Header = FSaveHeader.Parse(reader); if (Header.SaveVersion < FSaveCustomVersion.SaveFileIsCompressed) { LoadData(reader); } else { using (var buffer = new MemoryStream()) { var uncompressedSize = 0L; while (stream.Position < stream.Length) { var header = reader.ReadChunkInfo(); Trace.Assert(header.CompressedSize == ChunkInfo.Magic); Trace.Assert(header.UncompressedSize == ChunkInfo.ChunkSize); var summary = reader.ReadChunkInfo(); var subChunk = reader.ReadChunkInfo(); Trace.Assert(subChunk.UncompressedSize == summary.UncompressedSize); var startPosition = stream.Position; using (var zStream = new ZLibStream(stream, CompressionMode.Decompress)) { zStream.CopyTo(buffer); } // ZlibStream appears to read more bytes than it uses (because of buffering probably) so we need to manually fix the input stream position stream.Position = startPosition + subChunk.CompressedSize; uncompressedSize += subChunk.UncompressedSize; } buffer.Position = 0; #if DEBUG File.WriteAllBytes(Path.Combine(Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file) + ".bin"), buffer.ToArray()); #endif using (var bufferReader = new BinaryReader(buffer)) { var dataLength = bufferReader.ReadInt32(); Trace.Assert(uncompressedSize == dataLength + 4); LoadData(bufferReader); } } } } }