mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-24 00:03:11 +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 "object/ObjectRepository.h"
|
||||||
#include "rct2/S6Exporter.h"
|
#include "rct2/S6Exporter.h"
|
||||||
#include "world/Park.h"
|
#include "world/Park.h"
|
||||||
|
#include "zlib.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -77,6 +78,14 @@ namespace OpenRCT2
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ReplayRecordFile
|
||||||
|
{
|
||||||
|
uint32_t magic;
|
||||||
|
uint16_t version;
|
||||||
|
uint64_t uncompressedSize;
|
||||||
|
MemoryStream data;
|
||||||
|
};
|
||||||
|
|
||||||
struct ReplayRecordData
|
struct ReplayRecordData
|
||||||
{
|
{
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
@@ -97,8 +106,9 @@ namespace OpenRCT2
|
|||||||
|
|
||||||
class ReplayManager final : public IReplayManager
|
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 uint32_t ReplayMagic = 0x5243524F; // ORCR.
|
||||||
|
static constexpr int ReplayCompressionLevel = 9;
|
||||||
|
|
||||||
enum class ReplayMode
|
enum class ReplayMode
|
||||||
{
|
{
|
||||||
@@ -284,19 +294,41 @@ namespace OpenRCT2
|
|||||||
_currentRecording->tickEnd = gCurrentTicks;
|
_currentRecording->tickEnd = gCurrentTicks;
|
||||||
|
|
||||||
// Serialise Body.
|
// Serialise Body.
|
||||||
DataSerialiser serialiser(true);
|
DataSerialiser recSerialiser(true);
|
||||||
Serialise(serialiser, *_currentRecording);
|
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;
|
const std::string& outFile = _currentRecording->filePath;
|
||||||
|
|
||||||
FILE* fp = fopen(outFile.c_str(), "wb");
|
FILE* fp = fopen(outFile.c_str(), "wb");
|
||||||
if (fp)
|
if (fp)
|
||||||
{
|
{
|
||||||
const auto& stream = serialiser.GetStream();
|
const auto& fileStream = fileSerialiser.GetStream();
|
||||||
fwrite(stream.GetData(), 1, stream.GetLength(), fp);
|
fwrite(fileStream.GetData(), 1, fileStream.GetLength(), fp);
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
result = true;
|
result = true;
|
||||||
@@ -556,10 +588,42 @@ namespace OpenRCT2
|
|||||||
return true;
|
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)
|
bool ReadReplayData(const std::string& file, ReplayRecordData& data)
|
||||||
{
|
{
|
||||||
MemoryStream stream;
|
MemoryStream stream;
|
||||||
DataSerialiser serialiser(false, stream);
|
|
||||||
|
|
||||||
std::string fileName = file;
|
std::string fileName = file;
|
||||||
if (fileName.size() < 5 || fileName.substr(fileName.size() - 5) != ".sv6r")
|
if (fileName.size() < 5 || fileName.substr(fileName.size() - 5) != ".sv6r")
|
||||||
@@ -584,8 +648,11 @@ namespace OpenRCT2
|
|||||||
if (!loaded)
|
if (!loaded)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
stream.SetPosition(0);
|
if (!TryDecompress(stream))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
stream.SetPosition(0);
|
||||||
|
DataSerialiser serialiser(false, stream);
|
||||||
if (!Serialise(serialiser, data))
|
if (!Serialise(serialiser, data))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -671,6 +738,14 @@ namespace OpenRCT2
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Compatible(ReplayRecordData& data)
|
||||||
|
{
|
||||||
|
if (data.version == 1 && ReplayVersion == 2)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool Serialise(DataSerialiser& serialiser, ReplayRecordData& data)
|
bool Serialise(DataSerialiser& serialiser, ReplayRecordData& data)
|
||||||
{
|
{
|
||||||
serialiser << data.magic;
|
serialiser << data.magic;
|
||||||
@@ -680,7 +755,7 @@ namespace OpenRCT2
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
serialiser << data.version;
|
serialiser << data.version;
|
||||||
if (data.version != ReplayVersion)
|
if (data.version != ReplayVersion && !Compatible(data))
|
||||||
{
|
{
|
||||||
log_error("Invalid version detected %04X, expected: %04X", data.version, ReplayVersion);
|
log_error("Invalid version detected %04X, expected: %04X", data.version, ReplayVersion);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user