From 487886695ea9a86b436fa097148959c283d11b00 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Tue, 29 Oct 2024 22:02:41 +0100 Subject: [PATCH] Move SawyerChunk units into OpenRCT2 namespace --- src/openrct2/rct12/SawyerChunk.cpp | 13 +- src/openrct2/rct12/SawyerChunk.h | 69 +-- src/openrct2/rct12/SawyerChunkReader.cpp | 517 ++++++++++++----------- src/openrct2/rct12/SawyerChunkReader.h | 130 +++--- src/openrct2/rct12/SawyerChunkWriter.cpp | 171 ++++---- src/openrct2/rct12/SawyerChunkWriter.h | 76 ++-- 6 files changed, 491 insertions(+), 485 deletions(-) diff --git a/src/openrct2/rct12/SawyerChunk.cpp b/src/openrct2/rct12/SawyerChunk.cpp index 667821ec6e..07393c8e17 100644 --- a/src/openrct2/rct12/SawyerChunk.cpp +++ b/src/openrct2/rct12/SawyerChunk.cpp @@ -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 diff --git a/src/openrct2/rct12/SawyerChunk.h b/src/openrct2/rct12/SawyerChunk.h index fb7fa47966..67615ec1d5 100644 --- a/src/openrct2/rct12/SawyerChunk.h +++ b/src/openrct2/rct12/SawyerChunk.h @@ -13,39 +13,42 @@ #include -/** - * 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 diff --git a/src/openrct2/rct12/SawyerChunkReader.cpp b/src/openrct2/rct12/SawyerChunkReader.cpp index 6af96998f7..07d2fd95b4 100644 --- a/src/openrct2/rct12/SawyerChunkReader.cpp +++ b/src/openrct2/rct12/SawyerChunkReader.cpp @@ -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(); - _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 SawyerChunkReader::ReadChunk() -{ - uint64_t originalPosition = _stream->GetPosition(); - try + void SawyerChunkReader::SkipChunk() { - auto header = _stream->ReadValue(); - if (header.length >= MAX_UNCOMPRESSED_CHUNK_SIZE) - throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE); + uint64_t originalPosition = _stream->GetPosition(); + try + { + auto header = _stream->ReadValue(); + _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 SawyerChunkReader::ReadChunk() + { + uint64_t originalPosition = _stream->GetPosition(); + try + { + auto header = _stream->ReadValue(); + 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(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(static_cast(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 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::max()) + { + throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK); + } + uint32_t compressedDataLength = compressedDataLength64; + auto compressedData = std::make_unique(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(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(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(dst) + chunkLength; + std::fill_n(offset, remainingLength, 0x00); + } + } + } + + static MemoryStream DecodeChunkRLE(const void* src, size_t srcLength) + { + MemoryStream buf; + + auto src8 = static_cast(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(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(src8[i] >> 3) - 32; + const uint8_t* copySrc = static_cast(buf.GetData()) + (buf.GetLength() + offset); + + if (copySrc < static_cast(buf.GetData()) + || copySrc + count > static_cast(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(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(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(static_cast(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 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::max()) - { - throw SawyerChunkException(EXCEPTION_MSG_ZERO_SIZED_CHUNK); - } - uint32_t compressedDataLength = compressedDataLength64; - auto compressedData = std::make_unique(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(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(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(dst) + chunkLength; - std::fill_n(offset, remainingLength, 0x00); - } - } -} - -static MemoryStream DecodeChunkRLE(const void* src, size_t srcLength) -{ - MemoryStream buf; - - auto src8 = static_cast(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(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(src8[i] >> 3) - 32; - const uint8_t* copySrc = static_cast(buf.GetData()) + (buf.GetLength() + offset); - - if (copySrc < static_cast(buf.GetData()) - || copySrc + count > static_cast(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(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 diff --git a/src/openrct2/rct12/SawyerChunkReader.h b/src/openrct2/rct12/SawyerChunkReader.h index 2ebf43dbe7..e9b51b9029 100644 --- a/src/openrct2/rct12/SawyerChunkReader.h +++ b/src/openrct2/rct12/SawyerChunkReader.h @@ -17,73 +17,73 @@ #include #include -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 ReadChunk(); - - /** - * As above but for chunks without a header - */ - [[nodiscard]] std::shared_ptr 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 - 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 ReadChunk(); + + /** + * As above but for chunks without a header + */ + [[nodiscard]] std::shared_ptr 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 + T ReadChunkAs() + { + T result; + ReadChunk(&result, sizeof(result)); + return result; + } + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/rct12/SawyerChunkWriter.cpp b/src/openrct2/rct12/SawyerChunkWriter.cpp index 96bcc90f04..7589371ab1 100644 --- a/src/openrct2/rct12/SawyerChunkWriter.cpp +++ b/src/openrct2/rct12/SawyerChunkWriter.cpp @@ -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(encoding); - header.length = static_cast(length); - - auto data = std::make_unique(MAX_COMPRESSED_CHUNK_SIZE); - size_t dataLength = SawyerCoding::WriteChunkBuffer(data.get(), static_cast(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(encoding); + header.length = static_cast(length); + + auto data = std::make_unique(MAX_COMPRESSED_CHUNK_SIZE); + size_t dataLength = SawyerCoding::WriteChunkBuffer(data.get(), static_cast(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(MAX_COMPRESSED_CHUNK_SIZE); + size_t dataLength = EncodeChunkRLE(static_cast(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(checksum); } - return dst - dst_buffer; -} - -void SawyerChunkWriter::WriteChunkTrack(const void* src, size_t length) -{ - auto data = std::make_unique(MAX_COMPRESSED_CHUNK_SIZE); - size_t dataLength = EncodeChunkRLE(static_cast(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(checksum); -} +} // namespace OpenRCT2 diff --git a/src/openrct2/rct12/SawyerChunkWriter.h b/src/openrct2/rct12/SawyerChunkWriter.h index 5a31116624..725a08ea48 100644 --- a/src/openrct2/rct12/SawyerChunkWriter.h +++ b/src/openrct2/rct12/SawyerChunkWriter.h @@ -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 - 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 + void WriteChunk(const T* src, SAWYER_ENCODING encoding) + { + WriteChunk(src, sizeof(T), encoding); + } + }; +} // namespace OpenRCT2