mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-24 15:24:30 +01:00
Move SawyerChunk units into OpenRCT2 namespace
This commit is contained in:
@@ -11,10 +11,11 @@
|
||||
|
||||
#include "SawyerChunkReader.h"
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
SawyerChunk::SawyerChunk(SAWYER_ENCODING encoding, MemoryStream&& data)
|
||||
: _data(std::move(data))
|
||||
, _encoding(encoding)
|
||||
namespace OpenRCT2
|
||||
{
|
||||
}
|
||||
SawyerChunk::SawyerChunk(SAWYER_ENCODING encoding, MemoryStream&& data)
|
||||
: _data(std::move(data))
|
||||
, _encoding(encoding)
|
||||
{
|
||||
}
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -13,39 +13,42 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* The type of encoding / compression for a sawyer encoded chunk.
|
||||
*/
|
||||
enum class SAWYER_ENCODING : uint8_t
|
||||
namespace OpenRCT2
|
||||
{
|
||||
NONE,
|
||||
RLE,
|
||||
RLECOMPRESSED,
|
||||
ROTATE,
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a sawyer encoded chunk.
|
||||
*/
|
||||
class SawyerChunk final
|
||||
{
|
||||
private:
|
||||
OpenRCT2::MemoryStream _data;
|
||||
SAWYER_ENCODING _encoding = SAWYER_ENCODING::NONE;
|
||||
|
||||
public:
|
||||
const void* GetData() const
|
||||
/**
|
||||
* The type of encoding / compression for a sawyer encoded chunk.
|
||||
*/
|
||||
enum class SAWYER_ENCODING : uint8_t
|
||||
{
|
||||
return _data.GetData();
|
||||
}
|
||||
size_t GetLength() const
|
||||
{
|
||||
return _data.GetLength();
|
||||
}
|
||||
SAWYER_ENCODING GetEncoding() const
|
||||
{
|
||||
return _encoding;
|
||||
}
|
||||
NONE,
|
||||
RLE,
|
||||
RLECOMPRESSED,
|
||||
ROTATE,
|
||||
};
|
||||
|
||||
SawyerChunk(SAWYER_ENCODING encoding, OpenRCT2::MemoryStream&& data);
|
||||
};
|
||||
/**
|
||||
* Represents a sawyer encoded chunk.
|
||||
*/
|
||||
class SawyerChunk final
|
||||
{
|
||||
private:
|
||||
OpenRCT2::MemoryStream _data;
|
||||
SAWYER_ENCODING _encoding = SAWYER_ENCODING::NONE;
|
||||
|
||||
public:
|
||||
const void* GetData() const
|
||||
{
|
||||
return _data.GetData();
|
||||
}
|
||||
size_t GetLength() const
|
||||
{
|
||||
return _data.GetLength();
|
||||
}
|
||||
SAWYER_ENCODING GetEncoding() const
|
||||
{
|
||||
return _encoding;
|
||||
}
|
||||
|
||||
SawyerChunk(SAWYER_ENCODING encoding, OpenRCT2::MemoryStream&& data);
|
||||
};
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -13,278 +13,279 @@
|
||||
#include "../core/MemoryStream.h"
|
||||
#include "../core/Numerics.hpp"
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
// Allow chunks to be uncompressed to a maximum of 16 MiB
|
||||
constexpr size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
constexpr const char* EXCEPTION_MSG_CORRUPT_CHUNK_SIZE = "Corrupt chunk size.";
|
||||
constexpr const char* EXCEPTION_MSG_CORRUPT_RLE = "Corrupt RLE compression data.";
|
||||
constexpr const char* EXCEPTION_MSG_DESTINATION_TOO_SMALL = "Chunk data larger than allocated destination capacity.";
|
||||
constexpr const char* EXCEPTION_MSG_INVALID_CHUNK_ENCODING = "Invalid chunk encoding.";
|
||||
constexpr const char* EXCEPTION_MSG_ZERO_SIZED_CHUNK = "Encountered zero-sized chunk.";
|
||||
|
||||
static MemoryStream DecodeChunk(const void* src, const SawyerCoding::ChunkHeader& header);
|
||||
|
||||
SawyerChunkReader::SawyerChunkReader(OpenRCT2::IStream* stream)
|
||||
: _stream(stream)
|
||||
namespace OpenRCT2
|
||||
{
|
||||
}
|
||||
// Allow chunks to be uncompressed to a maximum of 16 MiB
|
||||
constexpr size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
void SawyerChunkReader::SkipChunk()
|
||||
{
|
||||
uint64_t originalPosition = _stream->GetPosition();
|
||||
try
|
||||
constexpr const char* EXCEPTION_MSG_CORRUPT_CHUNK_SIZE = "Corrupt chunk size.";
|
||||
constexpr const char* EXCEPTION_MSG_CORRUPT_RLE = "Corrupt RLE compression data.";
|
||||
constexpr const char* EXCEPTION_MSG_DESTINATION_TOO_SMALL = "Chunk data larger than allocated destination capacity.";
|
||||
constexpr const char* EXCEPTION_MSG_INVALID_CHUNK_ENCODING = "Invalid chunk encoding.";
|
||||
constexpr const char* EXCEPTION_MSG_ZERO_SIZED_CHUNK = "Encountered zero-sized chunk.";
|
||||
|
||||
static MemoryStream DecodeChunk(const void* src, const SawyerCoding::ChunkHeader& header);
|
||||
|
||||
SawyerChunkReader::SawyerChunkReader(OpenRCT2::IStream* stream)
|
||||
: _stream(stream)
|
||||
{
|
||||
auto header = _stream->ReadValue<SawyerCoding::ChunkHeader>();
|
||||
_stream->Seek(header.length, OpenRCT2::STREAM_SEEK_CURRENT);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
// Rewind stream back to original position
|
||||
_stream->SetPosition(originalPosition);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SawyerChunk> SawyerChunkReader::ReadChunk()
|
||||
{
|
||||
uint64_t originalPosition = _stream->GetPosition();
|
||||
try
|
||||
void SawyerChunkReader::SkipChunk()
|
||||
{
|
||||
auto header = _stream->ReadValue<SawyerCoding::ChunkHeader>();
|
||||
if (header.length >= MAX_UNCOMPRESSED_CHUNK_SIZE)
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE);
|
||||
uint64_t originalPosition = _stream->GetPosition();
|
||||
try
|
||||
{
|
||||
auto header = _stream->ReadValue<SawyerCoding::ChunkHeader>();
|
||||
_stream->Seek(header.length, OpenRCT2::STREAM_SEEK_CURRENT);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
// Rewind stream back to original position
|
||||
_stream->SetPosition(originalPosition);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SawyerChunk> SawyerChunkReader::ReadChunk()
|
||||
{
|
||||
uint64_t originalPosition = _stream->GetPosition();
|
||||
try
|
||||
{
|
||||
auto header = _stream->ReadValue<SawyerCoding::ChunkHeader>();
|
||||
if (header.length >= MAX_UNCOMPRESSED_CHUNK_SIZE)
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE);
|
||||
|
||||
switch (header.encoding)
|
||||
{
|
||||
case CHUNK_ENCODING_NONE:
|
||||
case CHUNK_ENCODING_RLE:
|
||||
case CHUNK_ENCODING_RLECOMPRESSED:
|
||||
case CHUNK_ENCODING_ROTATE:
|
||||
{
|
||||
auto compressedData = std::make_unique<uint8_t[]>(header.length);
|
||||
if (_stream->TryRead(compressedData.get(), header.length) != header.length)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
auto buffer = DecodeChunk(compressedData.get(), header);
|
||||
if (buffer.GetLength() == 0)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK);
|
||||
}
|
||||
|
||||
return std::make_shared<SawyerChunk>(static_cast<SAWYER_ENCODING>(header.encoding), std::move(buffer));
|
||||
}
|
||||
default:
|
||||
throw SawyerChunkException(EXCEPTION_MSG_INVALID_CHUNK_ENCODING);
|
||||
}
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
// Rewind stream back to original position
|
||||
_stream->SetPosition(originalPosition);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SawyerChunk> SawyerChunkReader::ReadChunkTrack()
|
||||
{
|
||||
uint64_t originalPosition = _stream->GetPosition();
|
||||
try
|
||||
{
|
||||
// Remove 4 as we don't want to touch the checksum at the end of the file
|
||||
int64_t compressedDataLength64 = _stream->GetLength() - _stream->GetPosition() - 4;
|
||||
if (compressedDataLength64 < 0 || compressedDataLength64 > std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK);
|
||||
}
|
||||
uint32_t compressedDataLength = compressedDataLength64;
|
||||
auto compressedData = std::make_unique<uint8_t[]>(compressedDataLength);
|
||||
|
||||
if (_stream->TryRead(compressedData.get(), compressedDataLength) != compressedDataLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
SawyerCoding::ChunkHeader header{ CHUNK_ENCODING_RLE, compressedDataLength };
|
||||
auto buffer = DecodeChunk(compressedData.get(), header);
|
||||
if (buffer.GetLength() == 0)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK);
|
||||
}
|
||||
return std::make_shared<SawyerChunk>(SAWYER_ENCODING::RLE, std::move(buffer));
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
// Rewind stream back to original position
|
||||
_stream->SetPosition(originalPosition);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SawyerChunkReader::ReadChunk(void* dst, size_t length)
|
||||
{
|
||||
auto chunk = ReadChunk();
|
||||
auto chunkData = static_cast<const uint8_t*>(chunk->GetData());
|
||||
auto chunkLength = chunk->GetLength();
|
||||
if (chunkLength > length)
|
||||
{
|
||||
std::memcpy(dst, chunkData, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(dst, chunkData, chunkLength);
|
||||
auto remainingLength = length - chunkLength;
|
||||
if (remainingLength > 0)
|
||||
{
|
||||
auto offset = static_cast<uint8_t*>(dst) + chunkLength;
|
||||
std::fill_n(offset, remainingLength, 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRLE(const void* src, size_t srcLength)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
auto src8 = static_cast<const uint8_t*>(src);
|
||||
for (size_t i = 0; i < srcLength; i++)
|
||||
{
|
||||
uint8_t rleCodeByte = src8[i];
|
||||
if (rleCodeByte & 128)
|
||||
{
|
||||
i++;
|
||||
size_t count = 257 - rleCodeByte;
|
||||
|
||||
if (i >= srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
if (buf.GetLength() + count > MAX_UNCOMPRESSED_CHUNK_SIZE)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL);
|
||||
}
|
||||
|
||||
for (size_t n = 0; n < count; n++)
|
||||
{
|
||||
buf.Write1(src8 + i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto len = rleCodeByte + 1;
|
||||
|
||||
if (i + 1 >= srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
if (buf.GetLength() + len > MAX_UNCOMPRESSED_CHUNK_SIZE)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL);
|
||||
}
|
||||
if (i + 1 + len > srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
|
||||
const auto* pos = src8 + i + 1;
|
||||
|
||||
buf.Write(pos, len);
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRepeat(const void* src, size_t srcLength)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
auto src8 = static_cast<const uint8_t*>(src);
|
||||
for (size_t i = 0; i < srcLength; i++)
|
||||
{
|
||||
if (src8[i] == 0xFF)
|
||||
{
|
||||
if (i + 1 >= srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
i++;
|
||||
buf.Write1(src8 + i);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t count = (src8[i] & 7) + 1;
|
||||
int32_t offset = static_cast<int32_t>(src8[i] >> 3) - 32;
|
||||
const uint8_t* copySrc = static_cast<const uint8_t*>(buf.GetData()) + (buf.GetLength() + offset);
|
||||
|
||||
if (copySrc < static_cast<const uint8_t*>(buf.GetData())
|
||||
|| copySrc + count > static_cast<const uint8_t*>(buf.GetData()) + buf.GetLength())
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
|
||||
// We need a temporary buffer as the vector might invalidate the pointer.
|
||||
uint8_t temp[16];
|
||||
std::memcpy(temp, copySrc, count);
|
||||
|
||||
buf.Write(temp, count);
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRLERepeat(const void* src, size_t srcLength)
|
||||
{
|
||||
auto tempBuf = DecodeChunkRLE(src, srcLength);
|
||||
return DecodeChunkRepeat(tempBuf.GetData(), tempBuf.GetLength());
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRotate(const void* src, size_t srcLength)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
auto src8 = static_cast<const uint8_t*>(src);
|
||||
|
||||
uint8_t code = 1;
|
||||
for (size_t i = 0; i < srcLength; i++)
|
||||
{
|
||||
uint8_t temp = Numerics::ror8(src8[i], code);
|
||||
buf.Write1(&temp);
|
||||
code = (code + 2) % 8;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunk(const void* src, const SawyerCoding::ChunkHeader& header)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
switch (header.encoding)
|
||||
{
|
||||
case CHUNK_ENCODING_NONE:
|
||||
buf.Write(src, header.length);
|
||||
break;
|
||||
case CHUNK_ENCODING_RLE:
|
||||
buf = DecodeChunkRLE(src, header.length);
|
||||
break;
|
||||
case CHUNK_ENCODING_RLECOMPRESSED:
|
||||
buf = DecodeChunkRLERepeat(src, header.length);
|
||||
break;
|
||||
case CHUNK_ENCODING_ROTATE:
|
||||
{
|
||||
auto compressedData = std::make_unique<uint8_t[]>(header.length);
|
||||
if (_stream->TryRead(compressedData.get(), header.length) != header.length)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
auto buffer = DecodeChunk(compressedData.get(), header);
|
||||
if (buffer.GetLength() == 0)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK);
|
||||
}
|
||||
|
||||
return std::make_shared<SawyerChunk>(static_cast<SAWYER_ENCODING>(header.encoding), std::move(buffer));
|
||||
}
|
||||
buf = DecodeChunkRotate(src, header.length);
|
||||
break;
|
||||
default:
|
||||
throw SawyerChunkException(EXCEPTION_MSG_INVALID_CHUNK_ENCODING);
|
||||
}
|
||||
|
||||
// Return the stream with the position at the beginning.
|
||||
buf.SetPosition(0);
|
||||
|
||||
return buf;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
// Rewind stream back to original position
|
||||
_stream->SetPosition(originalPosition);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SawyerChunk> SawyerChunkReader::ReadChunkTrack()
|
||||
{
|
||||
uint64_t originalPosition = _stream->GetPosition();
|
||||
try
|
||||
{
|
||||
// Remove 4 as we don't want to touch the checksum at the end of the file
|
||||
int64_t compressedDataLength64 = _stream->GetLength() - _stream->GetPosition() - 4;
|
||||
if (compressedDataLength64 < 0 || compressedDataLength64 > std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK);
|
||||
}
|
||||
uint32_t compressedDataLength = compressedDataLength64;
|
||||
auto compressedData = std::make_unique<uint8_t[]>(compressedDataLength);
|
||||
|
||||
if (_stream->TryRead(compressedData.get(), compressedDataLength) != compressedDataLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
SawyerCoding::ChunkHeader header{ CHUNK_ENCODING_RLE, compressedDataLength };
|
||||
auto buffer = DecodeChunk(compressedData.get(), header);
|
||||
if (buffer.GetLength() == 0)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK);
|
||||
}
|
||||
return std::make_shared<SawyerChunk>(SAWYER_ENCODING::RLE, std::move(buffer));
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
// Rewind stream back to original position
|
||||
_stream->SetPosition(originalPosition);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SawyerChunkReader::ReadChunk(void* dst, size_t length)
|
||||
{
|
||||
auto chunk = ReadChunk();
|
||||
auto chunkData = static_cast<const uint8_t*>(chunk->GetData());
|
||||
auto chunkLength = chunk->GetLength();
|
||||
if (chunkLength > length)
|
||||
{
|
||||
std::memcpy(dst, chunkData, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(dst, chunkData, chunkLength);
|
||||
auto remainingLength = length - chunkLength;
|
||||
if (remainingLength > 0)
|
||||
{
|
||||
auto offset = static_cast<uint8_t*>(dst) + chunkLength;
|
||||
std::fill_n(offset, remainingLength, 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRLE(const void* src, size_t srcLength)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
auto src8 = static_cast<const uint8_t*>(src);
|
||||
for (size_t i = 0; i < srcLength; i++)
|
||||
{
|
||||
uint8_t rleCodeByte = src8[i];
|
||||
if (rleCodeByte & 128)
|
||||
{
|
||||
i++;
|
||||
size_t count = 257 - rleCodeByte;
|
||||
|
||||
if (i >= srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
if (buf.GetLength() + count > MAX_UNCOMPRESSED_CHUNK_SIZE)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL);
|
||||
}
|
||||
|
||||
for (size_t n = 0; n < count; n++)
|
||||
{
|
||||
buf.Write1(src8 + i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto len = rleCodeByte + 1;
|
||||
|
||||
if (i + 1 >= srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
if (buf.GetLength() + len > MAX_UNCOMPRESSED_CHUNK_SIZE)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL);
|
||||
}
|
||||
if (i + 1 + len > srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
|
||||
const auto* pos = src8 + i + 1;
|
||||
|
||||
buf.Write(pos, len);
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRepeat(const void* src, size_t srcLength)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
auto src8 = static_cast<const uint8_t*>(src);
|
||||
for (size_t i = 0; i < srcLength; i++)
|
||||
{
|
||||
if (src8[i] == 0xFF)
|
||||
{
|
||||
if (i + 1 >= srcLength)
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
i++;
|
||||
buf.Write1(src8 + i);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t count = (src8[i] & 7) + 1;
|
||||
int32_t offset = static_cast<int32_t>(src8[i] >> 3) - 32;
|
||||
const uint8_t* copySrc = static_cast<const uint8_t*>(buf.GetData()) + (buf.GetLength() + offset);
|
||||
|
||||
if (copySrc < static_cast<const uint8_t*>(buf.GetData())
|
||||
|| copySrc + count > static_cast<const uint8_t*>(buf.GetData()) + buf.GetLength())
|
||||
{
|
||||
throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE);
|
||||
}
|
||||
|
||||
// We need a temporary buffer as the vector might invalidate the pointer.
|
||||
uint8_t temp[16];
|
||||
std::memcpy(temp, copySrc, count);
|
||||
|
||||
buf.Write(temp, count);
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRLERepeat(const void* src, size_t srcLength)
|
||||
{
|
||||
auto tempBuf = DecodeChunkRLE(src, srcLength);
|
||||
return DecodeChunkRepeat(tempBuf.GetData(), tempBuf.GetLength());
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunkRotate(const void* src, size_t srcLength)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
auto src8 = static_cast<const uint8_t*>(src);
|
||||
|
||||
uint8_t code = 1;
|
||||
for (size_t i = 0; i < srcLength; i++)
|
||||
{
|
||||
uint8_t temp = Numerics::ror8(src8[i], code);
|
||||
buf.Write1(&temp);
|
||||
code = (code + 2) % 8;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static MemoryStream DecodeChunk(const void* src, const SawyerCoding::ChunkHeader& header)
|
||||
{
|
||||
MemoryStream buf;
|
||||
|
||||
switch (header.encoding)
|
||||
{
|
||||
case CHUNK_ENCODING_NONE:
|
||||
buf.Write(src, header.length);
|
||||
break;
|
||||
case CHUNK_ENCODING_RLE:
|
||||
buf = DecodeChunkRLE(src, header.length);
|
||||
break;
|
||||
case CHUNK_ENCODING_RLECOMPRESSED:
|
||||
buf = DecodeChunkRLERepeat(src, header.length);
|
||||
break;
|
||||
case CHUNK_ENCODING_ROTATE:
|
||||
buf = DecodeChunkRotate(src, header.length);
|
||||
break;
|
||||
default:
|
||||
throw SawyerChunkException(EXCEPTION_MSG_INVALID_CHUNK_ENCODING);
|
||||
}
|
||||
|
||||
// Return the stream with the position at the beginning.
|
||||
buf.SetPosition(0);
|
||||
|
||||
return buf;
|
||||
}
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -17,73 +17,73 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class SawyerChunkException : public IOException
|
||||
{
|
||||
public:
|
||||
explicit SawyerChunkException(const char* message)
|
||||
: IOException(message)
|
||||
{
|
||||
}
|
||||
explicit SawyerChunkException(const std::string& message)
|
||||
: IOException(message)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
namespace OpenRCT2
|
||||
{
|
||||
struct IStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads sawyer encoding chunks from a data stream. This can be used to read
|
||||
* SC6, SV6 and RCT2 objects. persistentChunks is a hint to the reader that the chunk will be preserved,
|
||||
* and thus the chunk memory should be shrunk.
|
||||
*/
|
||||
class SawyerChunkReader final
|
||||
{
|
||||
private:
|
||||
OpenRCT2::IStream* const _stream = nullptr;
|
||||
|
||||
public:
|
||||
explicit SawyerChunkReader(OpenRCT2::IStream* stream);
|
||||
|
||||
/**
|
||||
* Skips the next chunk in the stream without decoding or reading its data
|
||||
* into RAM.
|
||||
*/
|
||||
void SkipChunk();
|
||||
|
||||
/**
|
||||
* Reads the next chunk from the stream.
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<SawyerChunk> ReadChunk();
|
||||
|
||||
/**
|
||||
* As above but for chunks without a header
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<SawyerChunk> ReadChunkTrack();
|
||||
|
||||
/**
|
||||
* Reads the next chunk from the stream and copies it directly to the
|
||||
* destination buffer. If the chunk is larger than length, only length
|
||||
* is copied. If the chunk is smaller than length, the remaining space
|
||||
* is padded with zero.
|
||||
* @param dst The destination buffer.
|
||||
* @param length The size of the destination buffer.
|
||||
*/
|
||||
void ReadChunk(void* dst, size_t length);
|
||||
|
||||
/**
|
||||
* Reads the next chunk from the stream into a buffer returned as the
|
||||
* specified type. If the chunk is smaller than the size of the type
|
||||
* then the remaining space is padded with zero.
|
||||
*/
|
||||
template<typename T>
|
||||
T ReadChunkAs()
|
||||
class SawyerChunkException : public IOException
|
||||
{
|
||||
T result;
|
||||
ReadChunk(&result, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
public:
|
||||
explicit SawyerChunkException(const char* message)
|
||||
: IOException(message)
|
||||
{
|
||||
}
|
||||
explicit SawyerChunkException(const std::string& message)
|
||||
: IOException(message)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads sawyer encoding chunks from a data stream. This can be used to read
|
||||
* SC6, SV6 and RCT2 objects. persistentChunks is a hint to the reader that the chunk will be preserved,
|
||||
* and thus the chunk memory should be shrunk.
|
||||
*/
|
||||
class SawyerChunkReader final
|
||||
{
|
||||
private:
|
||||
OpenRCT2::IStream* const _stream = nullptr;
|
||||
|
||||
public:
|
||||
explicit SawyerChunkReader(OpenRCT2::IStream* stream);
|
||||
|
||||
/**
|
||||
* Skips the next chunk in the stream without decoding or reading its data
|
||||
* into RAM.
|
||||
*/
|
||||
void SkipChunk();
|
||||
|
||||
/**
|
||||
* Reads the next chunk from the stream.
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<SawyerChunk> ReadChunk();
|
||||
|
||||
/**
|
||||
* As above but for chunks without a header
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<SawyerChunk> ReadChunkTrack();
|
||||
|
||||
/**
|
||||
* Reads the next chunk from the stream and copies it directly to the
|
||||
* destination buffer. If the chunk is larger than length, only length
|
||||
* is copied. If the chunk is smaller than length, the remaining space
|
||||
* is padded with zero.
|
||||
* @param dst The destination buffer.
|
||||
* @param length The size of the destination buffer.
|
||||
*/
|
||||
void ReadChunk(void* dst, size_t length);
|
||||
|
||||
/**
|
||||
* Reads the next chunk from the stream into a buffer returned as the
|
||||
* specified type. If the chunk is smaller than the size of the type
|
||||
* then the remaining space is padded with zero.
|
||||
*/
|
||||
template<typename T>
|
||||
T ReadChunkAs()
|
||||
{
|
||||
T result;
|
||||
ReadChunk(&result, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -13,99 +13,100 @@
|
||||
#include "../core/Numerics.hpp"
|
||||
#include "../core/SawyerCoding.h"
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
// Maximum buffer size to store compressed data, maximum of 16 MiB
|
||||
constexpr size_t MAX_COMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
SawyerChunkWriter::SawyerChunkWriter(OpenRCT2::IStream* stream)
|
||||
: _stream(stream)
|
||||
namespace OpenRCT2
|
||||
{
|
||||
}
|
||||
// Maximum buffer size to store compressed data, maximum of 16 MiB
|
||||
constexpr size_t MAX_COMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
void SawyerChunkWriter::WriteChunk(const SawyerChunk* chunk)
|
||||
{
|
||||
WriteChunk(chunk->GetData(), chunk->GetLength(), chunk->GetEncoding());
|
||||
}
|
||||
|
||||
void SawyerChunkWriter::WriteChunk(const void* src, size_t length, SAWYER_ENCODING encoding)
|
||||
{
|
||||
SawyerCoding::ChunkHeader header;
|
||||
header.encoding = static_cast<uint8_t>(encoding);
|
||||
header.length = static_cast<uint32_t>(length);
|
||||
|
||||
auto data = std::make_unique<uint8_t[]>(MAX_COMPRESSED_CHUNK_SIZE);
|
||||
size_t dataLength = SawyerCoding::WriteChunkBuffer(data.get(), static_cast<const uint8_t*>(src), header);
|
||||
|
||||
_stream->Write(data.get(), dataLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure dst_buffer is bigger than src_buffer then resize afterwards
|
||||
* returns length of dst_buffer
|
||||
*/
|
||||
static size_t EncodeChunkRLE(const uint8_t* src_buffer, uint8_t* dst_buffer, size_t length)
|
||||
{
|
||||
const uint8_t* src = src_buffer;
|
||||
uint8_t* dst = dst_buffer;
|
||||
const uint8_t* end_src = src + length;
|
||||
uint8_t count = 0;
|
||||
const uint8_t* src_norm_start = src;
|
||||
|
||||
while (src < end_src - 1)
|
||||
SawyerChunkWriter::SawyerChunkWriter(OpenRCT2::IStream* stream)
|
||||
: _stream(stream)
|
||||
{
|
||||
if ((count && *src == src[1]) || count > 125)
|
||||
}
|
||||
|
||||
void SawyerChunkWriter::WriteChunk(const SawyerChunk* chunk)
|
||||
{
|
||||
WriteChunk(chunk->GetData(), chunk->GetLength(), chunk->GetEncoding());
|
||||
}
|
||||
|
||||
void SawyerChunkWriter::WriteChunk(const void* src, size_t length, SAWYER_ENCODING encoding)
|
||||
{
|
||||
SawyerCoding::ChunkHeader header;
|
||||
header.encoding = static_cast<uint8_t>(encoding);
|
||||
header.length = static_cast<uint32_t>(length);
|
||||
|
||||
auto data = std::make_unique<uint8_t[]>(MAX_COMPRESSED_CHUNK_SIZE);
|
||||
size_t dataLength = SawyerCoding::WriteChunkBuffer(data.get(), static_cast<const uint8_t*>(src), header);
|
||||
|
||||
_stream->Write(data.get(), dataLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure dst_buffer is bigger than src_buffer then resize afterwards
|
||||
* returns length of dst_buffer
|
||||
*/
|
||||
static size_t EncodeChunkRLE(const uint8_t* src_buffer, uint8_t* dst_buffer, size_t length)
|
||||
{
|
||||
const uint8_t* src = src_buffer;
|
||||
uint8_t* dst = dst_buffer;
|
||||
const uint8_t* end_src = src + length;
|
||||
uint8_t count = 0;
|
||||
const uint8_t* src_norm_start = src;
|
||||
|
||||
while (src < end_src - 1)
|
||||
{
|
||||
if ((count && *src == src[1]) || count > 125)
|
||||
{
|
||||
*dst++ = count - 1;
|
||||
std::memcpy(dst, src_norm_start, count);
|
||||
dst += count;
|
||||
src_norm_start += count;
|
||||
count = 0;
|
||||
}
|
||||
if (*src == src[1])
|
||||
{
|
||||
for (; (count < 125) && ((src + count) < end_src); count++)
|
||||
{
|
||||
if (*src != src[count])
|
||||
break;
|
||||
}
|
||||
*dst++ = 257 - count;
|
||||
*dst++ = *src;
|
||||
src += count;
|
||||
src_norm_start = src;
|
||||
count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
count++;
|
||||
src++;
|
||||
}
|
||||
}
|
||||
if (src == end_src - 1)
|
||||
count++;
|
||||
if (count)
|
||||
{
|
||||
*dst++ = count - 1;
|
||||
std::memcpy(dst, src_norm_start, count);
|
||||
dst += count;
|
||||
src_norm_start += count;
|
||||
count = 0;
|
||||
}
|
||||
if (*src == src[1])
|
||||
{
|
||||
for (; (count < 125) && ((src + count) < end_src); count++)
|
||||
{
|
||||
if (*src != src[count])
|
||||
break;
|
||||
}
|
||||
*dst++ = 257 - count;
|
||||
*dst++ = *src;
|
||||
src += count;
|
||||
src_norm_start = src;
|
||||
count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
count++;
|
||||
src++;
|
||||
}
|
||||
return dst - dst_buffer;
|
||||
}
|
||||
if (src == end_src - 1)
|
||||
count++;
|
||||
if (count)
|
||||
|
||||
void SawyerChunkWriter::WriteChunkTrack(const void* src, size_t length)
|
||||
{
|
||||
*dst++ = count - 1;
|
||||
std::memcpy(dst, src_norm_start, count);
|
||||
dst += count;
|
||||
auto data = std::make_unique<uint8_t[]>(MAX_COMPRESSED_CHUNK_SIZE);
|
||||
size_t dataLength = EncodeChunkRLE(static_cast<const uint8_t*>(src), data.get(), length);
|
||||
|
||||
uint32_t checksum = 0;
|
||||
for (size_t i = 0; i < dataLength; i++)
|
||||
{
|
||||
uint8_t newByte = ((checksum & 0xFF) + data[i]) & 0xFF;
|
||||
checksum = (checksum & 0xFFFFFF00) + newByte;
|
||||
checksum = Numerics::rol32(checksum, 3);
|
||||
}
|
||||
checksum -= 0x1D4C1;
|
||||
|
||||
_stream->Write(data.get(), dataLength);
|
||||
_stream->WriteValue<uint32_t>(checksum);
|
||||
}
|
||||
return dst - dst_buffer;
|
||||
}
|
||||
|
||||
void SawyerChunkWriter::WriteChunkTrack(const void* src, size_t length)
|
||||
{
|
||||
auto data = std::make_unique<uint8_t[]>(MAX_COMPRESSED_CHUNK_SIZE);
|
||||
size_t dataLength = EncodeChunkRLE(static_cast<const uint8_t*>(src), data.get(), length);
|
||||
|
||||
uint32_t checksum = 0;
|
||||
for (size_t i = 0; i < dataLength; i++)
|
||||
{
|
||||
uint8_t newByte = ((checksum & 0xFF) + data[i]) & 0xFF;
|
||||
checksum = (checksum & 0xFFFFFF00) + newByte;
|
||||
checksum = Numerics::rol32(checksum, 3);
|
||||
}
|
||||
checksum -= 0x1D4C1;
|
||||
|
||||
_stream->Write(data.get(), dataLength);
|
||||
_stream->WriteValue<uint32_t>(checksum);
|
||||
}
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -16,45 +16,45 @@
|
||||
namespace OpenRCT2
|
||||
{
|
||||
struct IStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes sawyer encoding chunks to a data stream. This can be used to write
|
||||
* SC6 and SV6 files.
|
||||
*/
|
||||
class SawyerChunkWriter final
|
||||
{
|
||||
private:
|
||||
OpenRCT2::IStream* const _stream = nullptr;
|
||||
|
||||
public:
|
||||
explicit SawyerChunkWriter(OpenRCT2::IStream* stream);
|
||||
|
||||
/**
|
||||
* Writes a chunk to the stream.
|
||||
* Writes sawyer encoding chunks to a data stream. This can be used to write
|
||||
* SC6 and SV6 files.
|
||||
*/
|
||||
void WriteChunk(const SawyerChunk* chunk);
|
||||
|
||||
/**
|
||||
* Writes a chunk to the stream containing the given buffer.
|
||||
* @param src The source buffer.
|
||||
* @param length The size of the source buffer.
|
||||
*/
|
||||
void WriteChunk(const void* src, size_t length, SAWYER_ENCODING encoding);
|
||||
|
||||
/**
|
||||
* Writes a track chunk to the stream containing the given buffer.
|
||||
* @param src The source buffer.
|
||||
* @param length The size of the source buffer.
|
||||
*/
|
||||
void WriteChunkTrack(const void* src, size_t length);
|
||||
|
||||
/**
|
||||
* Writes a chunk to the stream containing the given type.
|
||||
*/
|
||||
template<typename T>
|
||||
void WriteChunk(const T* src, SAWYER_ENCODING encoding)
|
||||
class SawyerChunkWriter final
|
||||
{
|
||||
WriteChunk(src, sizeof(T), encoding);
|
||||
}
|
||||
};
|
||||
private:
|
||||
OpenRCT2::IStream* const _stream = nullptr;
|
||||
|
||||
public:
|
||||
explicit SawyerChunkWriter(OpenRCT2::IStream* stream);
|
||||
|
||||
/**
|
||||
* Writes a chunk to the stream.
|
||||
*/
|
||||
void WriteChunk(const SawyerChunk* chunk);
|
||||
|
||||
/**
|
||||
* Writes a chunk to the stream containing the given buffer.
|
||||
* @param src The source buffer.
|
||||
* @param length The size of the source buffer.
|
||||
*/
|
||||
void WriteChunk(const void* src, size_t length, SAWYER_ENCODING encoding);
|
||||
|
||||
/**
|
||||
* Writes a track chunk to the stream containing the given buffer.
|
||||
* @param src The source buffer.
|
||||
* @param length The size of the source buffer.
|
||||
*/
|
||||
void WriteChunkTrack(const void* src, size_t length);
|
||||
|
||||
/**
|
||||
* Writes a chunk to the stream containing the given type.
|
||||
*/
|
||||
template<typename T>
|
||||
void WriteChunk(const T* src, SAWYER_ENCODING encoding)
|
||||
{
|
||||
WriteChunk(src, sizeof(T), encoding);
|
||||
}
|
||||
};
|
||||
} // namespace OpenRCT2
|
||||
|
||||
Reference in New Issue
Block a user