1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-23 15:52:55 +01:00

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`
This commit is contained in:
Tom Lankhorst
2019-02-06 10:12:17 +01:00
parent c436a656ad
commit f48f347798

View File

@@ -23,6 +23,7 @@
#include "object/ObjectRepository.h"
#include "rct2/S6Exporter.h"
#include "world/Park.h"
#include "zlib.h"
#include <chrono>
#include <vector>
@@ -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<unsigned char[]>(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<unsigned char[]>(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;