From f48f347798675b2ca6c5215682cd265b51f58520 Mon Sep 17 00:00:00 2001 From: Tom Lankhorst Date: Wed, 6 Feb 2019 10:12:17 +0100 Subject: [PATCH] Internally compress (zlib lvl.9) replay files This effort reduces the size of replays by a factor 2 to 10 depending on the park complexity. ZLIB is used as the compression backend. - The replay file version is bumped to 2. - An `sv6r` file still starts with a magic number and a version. - If version = 1, the file stream is consumed by the ReplayRecordData serializer. - If version > 1, the file stream is decompressed and the resulting stream is consumed by the ReplayRecordData serializer. - Introduced `Compatible` that indicates whether a file with a version mismatch is compatible. E.g.: v1 is compatible with this `ReplayManager` --- src/openrct2/ReplayManager.cpp | 95 ++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/src/openrct2/ReplayManager.cpp b/src/openrct2/ReplayManager.cpp index 0f97788f83..33658c3111 100644 --- a/src/openrct2/ReplayManager.cpp +++ b/src/openrct2/ReplayManager.cpp @@ -23,6 +23,7 @@ #include "object/ObjectRepository.h" #include "rct2/S6Exporter.h" #include "world/Park.h" +#include "zlib.h" #include #include @@ -77,6 +78,14 @@ namespace OpenRCT2 } }; + struct ReplayRecordFile + { + uint32_t magic; + uint16_t version; + uint64_t uncompressedSize; + MemoryStream data; + }; + struct ReplayRecordData { uint32_t magic; @@ -97,8 +106,9 @@ namespace OpenRCT2 class ReplayManager final : public IReplayManager { - static constexpr uint16_t ReplayVersion = 1; + static constexpr uint16_t ReplayVersion = 2; static constexpr uint32_t ReplayMagic = 0x5243524F; // ORCR. + static constexpr int ReplayCompressionLevel = 9; enum class ReplayMode { @@ -284,19 +294,41 @@ namespace OpenRCT2 _currentRecording->tickEnd = gCurrentTicks; // Serialise Body. - DataSerialiser serialiser(true); - Serialise(serialiser, *_currentRecording); + DataSerialiser recSerialiser(true); + Serialise(recSerialiser, *_currentRecording); - bool result = true; + const auto& stream = recSerialiser.GetStream(); + size_t streamLength = stream.GetLength(); + size_t compressLength = compressBound(streamLength); + + MemoryStream data(compressLength); + + ReplayRecordFile file{ .magic = _currentRecording->magic, + .version = _currentRecording->version, + .uncompressedSize = streamLength, + .data = data }; + + auto compressBuf = std::make_unique(compressLength); + compress2( + compressBuf.get(), &compressLength, (unsigned char*)stream.GetData(), stream.GetLength(), + ReplayCompressionLevel); + file.data.Write(compressBuf.get(), compressLength); + + DataSerialiser fileSerialiser(true); + fileSerialiser << file.magic; + fileSerialiser << file.version; + fileSerialiser << file.uncompressedSize; + fileSerialiser << file.data; + + bool result = false; const std::string& outFile = _currentRecording->filePath; FILE* fp = fopen(outFile.c_str(), "wb"); if (fp) { - const auto& stream = serialiser.GetStream(); - fwrite(stream.GetData(), 1, stream.GetLength(), fp); - + const auto& fileStream = fileSerialiser.GetStream(); + fwrite(fileStream.GetData(), 1, fileStream.GetLength(), fp); fclose(fp); result = true; @@ -556,10 +588,42 @@ namespace OpenRCT2 return true; } + /** + * Returns true if decompression was not needed or succeeded + * @param stream + * @return + */ + bool TryDecompress(MemoryStream& stream) + { + ReplayRecordFile recFile; + stream.SetPosition(0); + DataSerialiser fileSerializer(false, stream); + fileSerializer << recFile.magic; + fileSerializer << recFile.version; + + if (recFile.version >= 2) + { + fileSerializer << recFile.uncompressedSize; + fileSerializer << recFile.data; + + auto buff = std::make_unique(recFile.uncompressedSize); + unsigned long outSize = recFile.uncompressedSize; + uncompress( + (unsigned char*)buff.get(), &outSize, (unsigned char*)recFile.data.GetData(), recFile.data.GetLength()); + if (outSize != recFile.uncompressedSize) + { + return false; + } + stream.SetPosition(0); + stream.Write(buff.get(), outSize); + } + + return true; + } + bool ReadReplayData(const std::string& file, ReplayRecordData& data) { MemoryStream stream; - DataSerialiser serialiser(false, stream); std::string fileName = file; if (fileName.size() < 5 || fileName.substr(fileName.size() - 5) != ".sv6r") @@ -584,8 +648,11 @@ namespace OpenRCT2 if (!loaded) return false; - stream.SetPosition(0); + if (!TryDecompress(stream)) + return false; + stream.SetPosition(0); + DataSerialiser serialiser(false, stream); if (!Serialise(serialiser, data)) { return false; @@ -671,6 +738,14 @@ namespace OpenRCT2 return true; } + bool Compatible(ReplayRecordData& data) + { + if (data.version == 1 && ReplayVersion == 2) + return true; + + return false; + } + bool Serialise(DataSerialiser& serialiser, ReplayRecordData& data) { serialiser << data.magic; @@ -680,7 +755,7 @@ namespace OpenRCT2 return false; } serialiser << data.version; - if (data.version != ReplayVersion) + if (data.version != ReplayVersion && !Compatible(data)) { log_error("Invalid version detected %04X, expected: %04X", data.version, ReplayVersion); return false;