public void Serialize(Stream output) { var saveGame = this.SaveGame; if (IsSupportedPlatform(this.Platform) == false) { throw new InvalidOperationException("unsupported platform"); } var endian = this.Platform.GetEndian(); var compressionScheme = this.Platform.GetCompressionScheme(); byte[] innerUncompressedBytes; using (var innerUncompressedData = new MemoryStream()) { if (this.PlayerStats != null) { saveGame.StatsData = this.PlayerStats.Serialize(endian); } ProtoSerializer.Serialize(innerUncompressedData, saveGame); innerUncompressedData.Position = 0; innerUncompressedBytes = innerUncompressedData.ReadBytes((int)innerUncompressedData.Length); } byte[] innerCompressedBytes; using (var innerCompressedData = new MemoryStream()) { var hash = CRC32.Hash(innerUncompressedBytes, 0, innerUncompressedBytes.Length); innerCompressedData.WriteValueS32(0, Endian.Big); innerCompressedData.WriteString("WSG"); innerCompressedData.WriteValueU32(2, endian); innerCompressedData.WriteValueU32(hash, endian); innerCompressedData.WriteValueS32(innerUncompressedBytes.Length, endian); var encoder = new Huffman.Encoder(); encoder.Build(innerUncompressedBytes); innerCompressedData.WriteBytes(encoder.Encode(innerUncompressedBytes)); innerCompressedData.Position = 0; innerCompressedData.WriteValueU32((uint)(innerCompressedData.Length - 4), Endian.Big); innerCompressedData.Position = 0; innerCompressedBytes = innerCompressedData.ReadBytes((int)innerCompressedData.Length); } byte[] compressedBytes; if (innerCompressedBytes.Length <= BlockSize) { if (compressionScheme == CompressionScheme.LZO) { compressedBytes = new byte[innerCompressedBytes.Length + (innerCompressedBytes.Length / 16) + 64 + 3]; var actualCompressedSize = compressedBytes.Length; var result = MiniLZO.LZO.Compress( innerCompressedBytes, 0, innerCompressedBytes.Length, compressedBytes, 0, ref actualCompressedSize, new MiniLZO.CompressWorkBuffer()); if (result != MiniLZO.ErrorCode.Success) { throw new SaveCorruptionException($"LZO compression failure ({result})"); } Array.Resize(ref compressedBytes, actualCompressedSize); } else if (compressionScheme == CompressionScheme.Zlib) { using (var temp = new MemoryStream()) { var zlib = new DeflaterOutputStream(temp); zlib.WriteBytes(innerCompressedBytes); zlib.Finish(); temp.Flush(); temp.Position = 0; compressedBytes = temp.ReadBytes((int)temp.Length); } } else { throw new InvalidOperationException("unsupported compression scheme"); } } else { if (compressionScheme == CompressionScheme.LZO) { int innerCompressedOffset = 0; int innerCompressedSizeLeft = innerCompressedBytes.Length; using (var blockData = new MemoryStream()) { var blockCount = (innerCompressedSizeLeft + BlockSize) / BlockSize; blockData.WriteValueS32(blockCount, Endian.Big); blockData.Position = 4 + (blockCount * 8); var blockInfos = new List <Tuple <uint, uint> >(); while (innerCompressedSizeLeft > 0) { var blockUncompressedSize = Math.Min(BlockSize, innerCompressedSizeLeft); compressedBytes = new byte[blockUncompressedSize + (blockUncompressedSize / 16) + 64 + 3]; var actualCompressedSize = compressedBytes.Length; var result = MiniLZO.LZO.Compress( innerCompressedBytes, innerCompressedOffset, blockUncompressedSize, compressedBytes, 0, ref actualCompressedSize, new MiniLZO.CompressWorkBuffer()); if (result != MiniLZO.ErrorCode.Success) { throw new SaveCorruptionException($"LZO compression failure ({result})"); } blockData.Write(compressedBytes, 0, actualCompressedSize); blockInfos.Add(new Tuple <uint, uint>((uint)actualCompressedSize, BlockSize)); innerCompressedOffset += blockUncompressedSize; innerCompressedSizeLeft -= blockUncompressedSize; } blockData.Position = 4; foreach (var blockInfo in blockInfos) { blockData.WriteValueU32(blockInfo.Item1, Endian.Big); blockData.WriteValueU32(blockInfo.Item2, Endian.Big); } blockData.Position = 0; compressedBytes = blockData.ReadBytes((int)blockData.Length); } } else if (compressionScheme == CompressionScheme.Zlib) { int innerCompressedOffset = 0; int innerCompressedSizeLeft = innerCompressedBytes.Length; using (var blockData = new MemoryStream()) { var blockCount = (innerCompressedSizeLeft + BlockSize) / BlockSize; blockData.WriteValueS32(blockCount, Endian.Big); blockData.Position = 4 + (blockCount * 8); var blockInfos = new List <Tuple <uint, uint> >(); while (innerCompressedSizeLeft > 0) { var blockUncompressedSize = Math.Min(BlockSize, innerCompressedSizeLeft); using (var temp = new MemoryStream()) { var zlib = new DeflaterOutputStream(temp); zlib.Write(innerCompressedBytes, innerCompressedOffset, blockUncompressedSize); zlib.Finish(); temp.Flush(); temp.Position = 0; compressedBytes = temp.ReadBytes((int)temp.Length); } blockData.WriteBytes(compressedBytes); blockInfos.Add(new Tuple <uint, uint>((uint)compressedBytes.Length, BlockSize)); innerCompressedOffset += blockUncompressedSize; innerCompressedSizeLeft -= blockUncompressedSize; } blockData.Position = 4; foreach (var blockInfo in blockInfos) { blockData.WriteValueU32(blockInfo.Item1, Endian.Big); blockData.WriteValueU32(blockInfo.Item2, Endian.Big); } blockData.Position = 0; compressedBytes = blockData.ReadBytes((int)blockData.Length); } } else { throw new InvalidOperationException("unsupported platform"); } } byte[] uncompressedBytes; using (var uncompressedData = new MemoryStream()) { uncompressedData.WriteValueS32(innerCompressedBytes.Length, Endian.Big); uncompressedData.WriteBytes(compressedBytes); uncompressedData.Position = 0; uncompressedBytes = uncompressedData.ReadBytes((int)uncompressedData.Length); } byte[] computedHash; using (var sha1 = new System.Security.Cryptography.SHA1Managed()) { computedHash = sha1.ComputeHash(uncompressedBytes); } output.WriteBytes(computedHash); output.WriteBytes(uncompressedBytes); }
public void Serialize(Stream output) { var saveGame = this.SaveGame; if (IsSupportedPlatform(this.Platform) == false) { throw new InvalidOperationException("unsupported platform"); } var endian = this.Platform.GetEndian(); var compressionScheme = this.Platform.GetCompressionScheme(); byte[] innerUncompressedBytes; using (var innerUncompressedData = new MemoryStream()) { if (this.PlayerStats != null) { saveGame.StatsData = this.PlayerStats.Serialize(endian); } ProtoSerializer.Serialize(innerUncompressedData, saveGame); innerUncompressedData.Position = 0; innerUncompressedBytes = innerUncompressedData.ReadBytes((int)innerUncompressedData.Length); } byte[] innerCompressedBytes; using (var innerCompressedData = new MemoryStream()) { var hash = CRC32.Hash(innerUncompressedBytes, 0, innerUncompressedBytes.Length); if (compressionScheme != CompressionScheme.None) { innerCompressedData.WriteValueS32(0, Endian.Big); } innerCompressedData.WriteString("WSG"); innerCompressedData.WriteValueU32(2, endian); innerCompressedData.WriteValueU32(hash, endian); innerCompressedData.WriteValueS32(innerUncompressedBytes.Length, endian); var encoder = new Huffman.Encoder(); encoder.Build(innerUncompressedBytes); innerCompressedData.WriteBytes(encoder.Encode(innerUncompressedBytes)); if (compressionScheme != CompressionScheme.None) { innerCompressedData.Position = 0; innerCompressedData.WriteValueU32((uint)(innerCompressedData.Length - 4), Endian.Big); } innerCompressedData.Position = 0; innerCompressedBytes = innerCompressedData.ReadBytes((int)innerCompressedData.Length); } if (compressionScheme == CompressionScheme.None) { output.WriteBytes(innerCompressedBytes); } else { Wrap(output, innerCompressedBytes, compressionScheme); } }