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);
                            }
                        }
                    }
                }
        }