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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user