From ce1d3e9c832c84920d6ff764f20a108c9c891de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= Date: Wed, 29 Sep 2021 23:06:46 +0200 Subject: [PATCH 1/3] Minor fixes (#15491) --- src/openrct2-ui/scripting/CustomWindow.cpp | 1 + src/openrct2/core/Crypt.OpenRCT2.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index d522e0b35e..d8803a3ed0 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -28,6 +28,7 @@ # include # include # include +# include # include using namespace OpenRCT2; diff --git a/src/openrct2/core/Crypt.OpenRCT2.cpp b/src/openrct2/core/Crypt.OpenRCT2.cpp index f3ad2d17d5..ba48bd5d15 100644 --- a/src/openrct2/core/Crypt.OpenRCT2.cpp +++ b/src/openrct2/core/Crypt.OpenRCT2.cpp @@ -17,7 +17,7 @@ using namespace Crypt; -class OpenRCT2FNV1aAlgorithm : public FNV1aAlgorithm +class OpenRCT2FNV1aAlgorithm final : public FNV1aAlgorithm { private: static constexpr uint64_t Offset = 0xCBF29CE484222325ULL; From bfe7caacd7385e48e656445b7cc9505a453d4038 Mon Sep 17 00:00:00 2001 From: Michael Steenbeek Date: Thu, 30 Sep 2021 08:40:58 +0200 Subject: [PATCH 2/3] Fix #15487: map animations do not work correctly in vanilla RCT2 (#15492) Co-authored-by: duncanspumpkin Co-authored-by: duncanspumpkin --- distribution/changelog.txt | 1 + src/openrct2/rct2/S6Exporter.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index ceee8213e9..14ded1411b 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -27,6 +27,7 @@ - Fix: [#15255] Tile Inspector shows banner information on walls that do not contain one. - Fix: [#15257] Chat icon shows in scenario/track editor. Other icons don't disable when deactivated in options menu. - Fix: [#15289] Unexpected behavior with duplicated banners which also caused desyncs in multiplayer. +- Fix: [#15487] Map animations do not work correctly when loading an exported SV6 file in vanilla RCT2. - Improved: [#3417] Crash dumps are now placed in their own folder. - Change: [#8601] Revert ToonTower base block fix to re-enable support blocking. - Change: [#15174] [Plugin] Deprecate the type "peep" and add support to target a specific scripting api version. diff --git a/src/openrct2/rct2/S6Exporter.cpp b/src/openrct2/rct2/S6Exporter.cpp index 47ee85946f..620277ab85 100644 --- a/src/openrct2/rct2/S6Exporter.cpp +++ b/src/openrct2/rct2/S6Exporter.cpp @@ -1761,9 +1761,10 @@ void S6Exporter::ExportMapAnimations() auto& dst = _s6.map_animations[i]; dst.type = src.type; + // In RCT12MapAnimation, the x and y coordinates use big coords, while the z coordinate uses small coords. dst.x = src.location.x; dst.y = src.location.y; - dst.baseZ = src.location.z; + dst.baseZ = src.location.z / COORDS_Z_STEP; } } From e01c9a3afa2196c650da9fd6aaea3da8a55512ed Mon Sep 17 00:00:00 2001 From: Duncan Date: Thu, 30 Sep 2021 13:02:54 +0100 Subject: [PATCH 3/3] Stream changes from NSF (#15446) * Bring over NSF changes to stream classes * Add orca stream to project files * Bring over util changes as well. * Add const to util and fix util ungzip * Add const and apply review comments * Apply review comments --- OpenRCT2.xcodeproj/project.pbxproj | 5 +- src/openrct2/core/FileStream.cpp | 6 + src/openrct2/core/FileStream.h | 1 + src/openrct2/core/IStream.cpp | 11 + src/openrct2/core/IStream.hpp | 2 + src/openrct2/core/MemoryStream.cpp | 6 + src/openrct2/core/MemoryStream.h | 2 + src/openrct2/core/OrcaStream.hpp | 679 +++++++++++++++++++++++++++++ src/openrct2/libopenrct2.vcxproj | 1 + src/openrct2/util/Util.cpp | 96 ++++ src/openrct2/util/Util.h | 2 + 11 files changed, 810 insertions(+), 1 deletion(-) create mode 100644 src/openrct2/core/OrcaStream.hpp diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 2277e03052..db876478bd 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -805,6 +805,7 @@ 7CDC7EE9B12E40FB9FE78546 /* Crypt.OpenRCT2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4248E4E4394842D4AF6119DA /* Crypt.OpenRCT2.cpp */; }; 4653963391E945D397BCCA0C /* ChangeMapSizeAction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76CE81CEAF7F4538976F7C4C /* ChangeMapSizeAction.cpp */; }; F1BE1CB5525C4FF794A3F3CE /* ChangeMapSizeAction.h in Headers */ = {isa = PBXBuildFile; fileRef = C49050C073DB4CB980E1EC5A /* ChangeMapSizeAction.h */; }; + B769EFE0AA1149AE93C15DED /* OrcaStream.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F9061C16C67A45D7915FA229 /* OrcaStream.hpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1913,6 +1914,7 @@ 4248E4E4394842D4AF6119DA /* Crypt.OpenRCT2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Crypt.OpenRCT2.cpp; path = src/openrct2/core/Crypt.OpenRCT2.cpp; sourceTree = SOURCE_ROOT; }; 76CE81CEAF7F4538976F7C4C /* ChangeMapSizeAction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ChangeMapSizeAction.cpp; path = src/openrct2/actions/ChangeMapSizeAction.cpp; sourceTree = SOURCE_ROOT; }; C49050C073DB4CB980E1EC5A /* ChangeMapSizeAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ChangeMapSizeAction.h; path = src/openrct2/actions/ChangeMapSizeAction.h; sourceTree = SOURCE_ROOT; }; + F9061C16C67A45D7915FA229 /* OrcaStream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = OrcaStream.hpp; path = src/openrct2/core/OrcaStream.hpp; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2567,7 +2569,6 @@ F76C838C1EC4E7CC00FA49E2 /* MemoryStream.cpp */, F76C838D1EC4E7CC00FA49E2 /* MemoryStream.h */, 2ADE2F24224418B2002598AF /* Meta.hpp */, - F76C838E1EC4E7CC00FA49E2 /* Nullable.hpp */, 2ADE2F23224418B1002598AF /* Numerics.hpp */, F76C838F1EC4E7CC00FA49E2 /* Path.cpp */, F76C83901EC4E7CC00FA49E2 /* Path.hpp */, @@ -2587,6 +2588,7 @@ F4D523B8782E4C458AF1490D /* GroupVector.hpp */, F28A181D311D4E078FDB090C /* ZipStream.hpp */, 4248E4E4394842D4AF6119DA /* Crypt.OpenRCT2.cpp */, + F9061C16C67A45D7915FA229 /* OrcaStream.hpp */, ); path = core; sourceTree = ""; @@ -3672,6 +3674,7 @@ C8D612EB56BD4214BEC0F7FF /* GroupVector.hpp in Headers */, B2F44E535BD14A03BE8B9D14 /* ZipStream.hpp in Headers */, F1BE1CB5525C4FF794A3F3CE /* ChangeMapSizeAction.h in Headers */, + B769EFE0AA1149AE93C15DED /* OrcaStream.hpp in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/openrct2/core/FileStream.cpp b/src/openrct2/core/FileStream.cpp index 68c0b52692..b95b220fe5 100644 --- a/src/openrct2/core/FileStream.cpp +++ b/src/openrct2/core/FileStream.cpp @@ -13,6 +13,7 @@ #include "String.hpp" #include +#include #ifndef _WIN32 # include @@ -42,6 +43,11 @@ namespace OpenRCT2 { } + FileStream::FileStream(std::string_view path, int32_t fileMode) + : FileStream(std::string(path), fileMode) + { + } + FileStream::FileStream(const utf8* path, int32_t fileMode) { const char* mode; diff --git a/src/openrct2/core/FileStream.h b/src/openrct2/core/FileStream.h index d9fdcffd3d..572bd80c55 100644 --- a/src/openrct2/core/FileStream.h +++ b/src/openrct2/core/FileStream.h @@ -38,6 +38,7 @@ namespace OpenRCT2 public: FileStream(const fs::path& path, int32_t fileMode); FileStream(const std::string& path, int32_t fileMode); + FileStream(std::string_view path, int32_t fileMode); FileStream(const utf8* path, int32_t fileMode); ~FileStream() override; diff --git a/src/openrct2/core/IStream.cpp b/src/openrct2/core/IStream.cpp index 88bf74d02e..995b79feda 100644 --- a/src/openrct2/core/IStream.cpp +++ b/src/openrct2/core/IStream.cpp @@ -57,6 +57,17 @@ namespace OpenRCT2 } } + void IStream::WriteString(const std::string_view str) + { + for (const auto c : str) + { + if (c == '\0') + break; + WriteValue(c); + } + WriteValue(0); + } + void IStream::WriteString(const std::string& str) { WriteString(str.c_str()); diff --git a/src/openrct2/core/IStream.hpp b/src/openrct2/core/IStream.hpp index 815990d8dc..58449729c2 100644 --- a/src/openrct2/core/IStream.hpp +++ b/src/openrct2/core/IStream.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #ifdef __WARN_SUGGEST_FINAL_METHODS__ @@ -207,6 +208,7 @@ namespace OpenRCT2 utf8* ReadString(); std::string ReadStdString(); void WriteString(const utf8* str); + void WriteString(const std::string_view string); void WriteString(const std::string& string); }; diff --git a/src/openrct2/core/MemoryStream.cpp b/src/openrct2/core/MemoryStream.cpp index 79329aaf7b..ea898fcc75 100644 --- a/src/openrct2/core/MemoryStream.cpp +++ b/src/openrct2/core/MemoryStream.cpp @@ -262,6 +262,12 @@ namespace OpenRCT2 Write<16>(buffer); } + void MemoryStream::Clear() + { + _dataSize = 0; + SetPosition(0); + } + void MemoryStream::EnsureCapacity(size_t capacity) { if (_dataCapacity < capacity) diff --git a/src/openrct2/core/MemoryStream.h b/src/openrct2/core/MemoryStream.h index 918d0a6f6d..f3dba1d69d 100644 --- a/src/openrct2/core/MemoryStream.h +++ b/src/openrct2/core/MemoryStream.h @@ -112,6 +112,8 @@ namespace OpenRCT2 uint64_t TryRead(void* buffer, uint64_t length) override; + void Clear(); + private: void EnsureCapacity(size_t capacity); }; diff --git a/src/openrct2/core/OrcaStream.hpp b/src/openrct2/core/OrcaStream.hpp new file mode 100644 index 0000000000..30f25ba4ba --- /dev/null +++ b/src/openrct2/core/OrcaStream.hpp @@ -0,0 +1,679 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "../world/Location.hpp" +#include "Crypt.h" +#include "FileStream.h" +#include "MemoryStream.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OpenRCT2 +{ + class OrcaStream + { + public: + enum class Mode + { + READING, + WRITING, + }; + + static constexpr uint32_t COMPRESSION_NONE = 0; + static constexpr uint32_t COMPRESSION_GZIP = 1; + + private: +#pragma pack(push, 1) + struct Header + { + uint32_t Magic{}; + uint32_t TargetVersion{}; + uint32_t MinVersion{}; + uint32_t NumChunks{}; + uint64_t UncompressedSize{}; + uint32_t Compression{}; + uint64_t CompressedSize{}; + std::array FNV1a{}; + uint8_t padding[20]; + }; + static_assert(sizeof(Header) == 64, "Header should be 64 bytes"); + + struct ChunkEntry + { + uint32_t Id{}; + uint64_t Offset{}; + uint64_t Length{}; + }; +#pragma pack(pop) + + IStream* _stream; + Mode _mode; + Header _header; + std::vector _chunks; + MemoryStream _buffer; + ChunkEntry _currentChunk; + + public: + OrcaStream(IStream& stream, const Mode mode) + { + _stream = &stream; + _mode = mode; + if (mode == Mode::READING) + { + _header = _stream->ReadValue
(); + + _chunks.clear(); + for (uint32_t i = 0; i < _header.NumChunks; i++) + { + auto entry = _stream->ReadValue(); + _chunks.push_back(entry); + } + + // Read compressed data into buffer (read in blocks) + _buffer = MemoryStream{}; + uint8_t temp[2048]; + uint64_t bytesLeft = _header.CompressedSize; + do + { + auto readLen = std::min(size_t(bytesLeft), sizeof(temp)); + _stream->Read(temp, readLen); + _buffer.Write(temp, readLen); + bytesLeft -= readLen; + } while (bytesLeft > 0); + + // Uncompress + if (_header.Compression == COMPRESSION_GZIP) + { + auto uncompressedData = Ungzip(_buffer.GetData(), _buffer.GetLength()); + if (_header.UncompressedSize != uncompressedData.size()) + { + // Warning? + } + _buffer.Clear(); + _buffer.Write(uncompressedData.data(), uncompressedData.size()); + } + } + else + { + _header = {}; + _header.Compression = COMPRESSION_GZIP; + + _buffer = MemoryStream{}; + } + } + + OrcaStream(const OrcaStream&) = delete; + + ~OrcaStream() + { + if (_mode == Mode::WRITING) + { + const void* uncompressedData = _buffer.GetData(); + const uint64_t uncompressedSize = _buffer.GetLength(); + + _header.NumChunks = static_cast(_chunks.size()); + _header.UncompressedSize = uncompressedSize; + _header.CompressedSize = uncompressedSize; + _header.FNV1a = Crypt::FNV1a(uncompressedData, uncompressedSize); + + // Compress data + std::optional> compressedBytes; + if (_header.Compression == COMPRESSION_GZIP) + { + compressedBytes = Gzip(uncompressedData, uncompressedSize); + if (compressedBytes) + { + _header.CompressedSize = compressedBytes->size(); + } + else + { + // Compression failed + _header.Compression = COMPRESSION_NONE; + } + } + + // Write header and chunk table + _stream->WriteValue(_header); + for (const auto& chunk : _chunks) + { + _stream->WriteValue(chunk); + } + + // Write chunk data + if (compressedBytes) + { + _stream->Write(compressedBytes->data(), compressedBytes->size()); + } + else + { + _stream->Write(uncompressedData, uncompressedSize); + } + } + } + + Mode GetMode() const + { + return _mode; + } + + Header& GetHeader() + { + return _header; + } + + const Header& GetHeader() const + { + return _header; + } + + template bool ReadWriteChunk(const uint32_t chunkId, TFunc f) + { + if (_mode == Mode::READING) + { + if (SeekChunk(chunkId)) + { + ChunkStream stream(_buffer, _mode); + f(stream); + return true; + } + + return false; + } + + _currentChunk.Id = chunkId; + _currentChunk.Offset = _buffer.GetPosition(); + _currentChunk.Length = 0; + ChunkStream stream(_buffer, _mode); + f(stream); + _currentChunk.Length = static_cast(_buffer.GetPosition()) - _currentChunk.Offset; + _chunks.push_back(_currentChunk); + return true; + } + + private: + bool SeekChunk(const uint32_t id) + { + const auto result = std::find_if(_chunks.begin(), _chunks.end(), [id](const ChunkEntry& e) { return e.Id == id; }); + if (result != _chunks.end()) + { + const auto offset = result->Offset; + _buffer.SetPosition(offset); + return true; + } + return false; + } + + public: + class ChunkStream + { + private: + struct ArrayState + { + std::streampos StartPos{}; + std::streampos LastPos{}; + size_t Count{}; + size_t ElementSize{}; + }; + + MemoryStream& _buffer; + Mode _mode; + std::stack _arrayStack; + + public: + ChunkStream(MemoryStream& buffer, const Mode mode) + : _buffer(buffer) + , _mode(mode) + { + } + + Mode GetMode() const + { + return _mode; + } + + MemoryStream& GetStream() + { + return _buffer; + } + + void ReadWrite(void* addr, const size_t len) + { + if (_mode == Mode::READING) + { + ReadBuffer(addr, len); + } + else + { + WriteBuffer(addr, len); + } + } + + void Read(void* addr, const size_t len) + { + if (_mode == Mode::READING) + { + ReadBuffer(addr, len); + } + else + { + throw std::runtime_error("Incorrect mode"); + } + } + + void Write(const void* addr, const size_t len) + { + if (_mode == Mode::READING) + { + throw std::runtime_error("Incorrect mode"); + } + else + { + WriteBuffer(addr, len); + } + } + + template::value, bool> = true> void ReadWrite(T& v) + { + if (_mode == Mode::READING) + { + v = ReadInteger(); + } + else + { + WriteInteger(v); + } + } + + template::value, bool> = true> void ReadWrite(T& v) + { + using underlying = typename std::underlying_type::type; + if (_mode == Mode::READING) + { + v = static_cast(ReadInteger()); + } + else + { + WriteInteger(static_cast(v)); + } + } + + void ReadWrite(bool& value) + { + uint8_t value8 = value ? 1 : 0; + ReadWrite(&value8, sizeof(value8)); + value = value8 != 0; + } + + void ReadWrite(CoordsXY& coords) + { + ReadWrite(coords.x); + ReadWrite(coords.y); + } + + void ReadWrite(CoordsXYZ& coords) + { + ReadWrite(coords.x); + ReadWrite(coords.y); + ReadWrite(coords.z); + } + + void ReadWrite(CoordsXYZD& coords) + { + ReadWrite(coords.x); + ReadWrite(coords.y); + ReadWrite(coords.z); + ReadWrite(coords.direction); + } + + void ReadWrite(TileCoordsXY& coords) + { + ReadWrite(coords.x); + ReadWrite(coords.y); + } + + void ReadWrite(TileCoordsXYZ& coords) + { + ReadWrite(coords.x); + ReadWrite(coords.y); + ReadWrite(coords.z); + } + + void ReadWrite(TileCoordsXYZD& coords) + { + ReadWrite(coords.x); + ReadWrite(coords.y); + ReadWrite(coords.z); + ReadWrite(coords.direction); + } + + template::value>> T Read() + { + T v{}; + ReadWrite(v); + return v; + } + + void ReadWrite(std::string& v) + { + if (_mode == Mode::READING) + { + v = ReadString(); + } + else + { + WriteString(v); + } + } + + template::value>> void Write(T v) + { + if (_mode == Mode::READING) + { + T temp{}; + ReadWrite(temp); + } + else + { + ReadWrite(v); + } + } + + void Write(const char* v) + { + std::string_view sv; + if (v != nullptr) + sv = v; + Write(sv); + } + + void Write(const std::string_view v) + { + if (_mode == Mode::READING) + { + std::string temp; + ReadWrite(temp); + } + else + { + WriteString(v); + } + } + + void Write(const std::string& v) + { + Write(std::string_view(v)); + } + + template void ReadWriteVector(TVec& vec, TFunc f) + { + if (_mode == Mode::READING) + { + const auto count = BeginArray(); + vec.clear(); + for (size_t i = 0; i < count; i++) + { + auto& el = vec.emplace_back(); + f(el); + NextArrayElement(); + } + EndArray(); + } + else + { + BeginArray(); + for (auto& el : vec) + { + f(el); + NextArrayElement(); + } + EndArray(); + } + } + + template void ReadWriteArray(TArr (&arr)[TArrSize], TFunc f) + { + auto& arr2 = *(reinterpret_cast*>(arr)); + ReadWriteArray(arr2, f); + } + + template + void ReadWriteArray(std::array& arr, TFunc f) + { + if (_mode == Mode::READING) + { + const auto count = BeginArray(); + for (auto& el : arr) + { + el = {}; + } + for (size_t i = 0; i < count; i++) + { + if (i < TArrSize) + { + f(arr[i]); + } + NextArrayElement(); + } + EndArray(); + } + else + { + BeginArray(); + for (auto& el : arr) + { + if (f(el)) + { + NextArrayElement(); + } + } + EndArray(); + } + } + + template void Ignore() + { + T value{}; + ReadWrite(value); + } + + private: + void ReadBuffer(void* dst, const size_t len) + { + _buffer.Read(dst, len); + } + + void WriteBuffer(const void* buffer, const size_t len) + { + _buffer.Write(buffer, len); + } + + template::value>> T ReadInteger() + { + if constexpr (sizeof(T) > 4) + { + if constexpr (std::is_signed()) + { + int64_t raw{}; + Read(&raw, sizeof(raw)); + return static_cast(raw); + } + + uint64_t raw{}; + Read(&raw, sizeof(raw)); + return static_cast(raw); + } + else + { + if constexpr (std::is_signed()) + { + int32_t raw{}; + Read(&raw, sizeof(raw)); + if (raw < std::numeric_limits::min() || raw > std::numeric_limits::max()) + { + throw std::runtime_error("Value is incompatible with internal type."); + } + return static_cast(raw); + } + else + { + uint32_t raw{}; + Read(&raw, sizeof(raw)); + if (raw > std::numeric_limits::max()) + { + throw std::runtime_error("Value is incompatible with internal type."); + } + return static_cast(raw); + } + } + } + + template::value>> void WriteInteger(const T value) + { + if constexpr (sizeof(T) > 4) + { + if constexpr (std::is_signed()) + { + auto raw = static_cast(value); + Write(&raw, sizeof(raw)); + } + else + { + auto raw = static_cast(value); + Write(&raw, sizeof(raw)); + } + } + else + { + if constexpr (std::is_signed()) + { + auto raw = static_cast(value); + Write(&raw, sizeof(raw)); + } + else + { + auto raw = static_cast(value); + Write(&raw, sizeof(raw)); + } + } + } + + std::string ReadString() + { + std::string buffer; + buffer.reserve(64); + while (true) + { + char c{}; + ReadBuffer(&c, sizeof(c)); + if (c == '\0') + { + break; + } + buffer.push_back(c); + } + buffer.shrink_to_fit(); + return buffer; + } + + void WriteString(const std::string_view s) + { + const char nullt = '\0'; + auto len = s.find('\0'); + if (len == std::string_view::npos) + { + len = s.size(); + } + _buffer.Write(s.data(), len); + _buffer.Write(&nullt, sizeof(nullt)); + } + + size_t BeginArray() + { + auto& arrayState = _arrayStack.emplace(); + if (_mode == Mode::READING) + { + arrayState.Count = Read(); + arrayState.ElementSize = Read(); + arrayState.LastPos = _buffer.GetPosition(); + return arrayState.Count; + } + + arrayState.Count = 0; + arrayState.ElementSize = 0; + arrayState.StartPos = _buffer.GetPosition(); + Write(0); + Write(0); + arrayState.LastPos = _buffer.GetPosition(); + return 0; + } + + bool NextArrayElement() + { + auto& arrayState = _arrayStack.top(); + if (_mode == Mode::READING) + { + if (arrayState.Count == 0) + { + return false; + } + if (arrayState.ElementSize != 0) + { + arrayState.LastPos += arrayState.ElementSize; + _buffer.SetPosition(arrayState.LastPos); + } + arrayState.Count--; + return arrayState.Count == 0; + } + + const auto lastElSize = static_cast(_buffer.GetPosition()) - arrayState.LastPos; + if (arrayState.Count == 0) + { + // Set array element size based on first element size + arrayState.ElementSize = lastElSize; + } + else if (arrayState.ElementSize != lastElSize) + { + // Array element size was different from first element so reset it + // to dynamic + arrayState.ElementSize = 0; + } + arrayState.Count++; + arrayState.LastPos = _buffer.GetPosition(); + return true; + } + + void EndArray() + { + auto& arrayState = _arrayStack.top(); + if (_mode == Mode::WRITING) + { + const size_t backupPos = _buffer.GetPosition(); + if (backupPos != static_cast(arrayState.StartPos) + 8 && arrayState.Count == 0) + { + throw std::runtime_error("Array data was written but no elements were added."); + } + _buffer.SetPosition(arrayState.StartPos); + Write(static_cast(arrayState.Count)); + Write(static_cast(arrayState.ElementSize)); + _buffer.SetPosition(backupPos); + } + _arrayStack.pop(); + } + }; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 94467bebb8..2e110ea5d6 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -177,6 +177,7 @@ + diff --git a/src/openrct2/util/Util.cpp b/src/openrct2/util/Util.cpp index 67e08c80d7..1894dd4472 100644 --- a/src/openrct2/util/Util.cpp +++ b/src/openrct2/util/Util.cpp @@ -693,6 +693,102 @@ bool util_gzip_compress(FILE* source, FILE* dest) return true; } +std::vector Gzip(const void* data, const size_t dataLen) +{ + assert(data != nullptr); + + std::vector output; + z_stream strm{}; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + { + const auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) + { + throw std::runtime_error("deflateInit2 failed with error " + std::to_string(ret)); + } + } + + int flush = 0; + const auto* src = static_cast(data); + size_t srcRemaining = dataLen; + do + { + const auto nextBlockSize = std::min(srcRemaining, CHUNK); + srcRemaining -= nextBlockSize; + + flush = srcRemaining == 0 ? Z_FINISH : Z_NO_FLUSH; + strm.avail_in = static_cast(nextBlockSize); + strm.next_in = const_cast(src); + do + { + output.resize(output.size() + nextBlockSize); + strm.avail_out = static_cast(nextBlockSize); + strm.next_out = &output[output.size() - nextBlockSize]; + const auto ret = deflate(&strm, flush); + if (ret == Z_STREAM_ERROR) + { + throw std::runtime_error("deflate failed with error " + std::to_string(ret)); + } + output.resize(output.size() - strm.avail_out); + } while (strm.avail_out == 0); + + src += nextBlockSize; + } while (flush != Z_FINISH); + deflateEnd(&strm); + return output; +} + +std::vector Ungzip(const void* data, const size_t dataLen) +{ + assert(data != nullptr); + + std::vector output; + z_stream strm{}; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + { + const auto ret = inflateInit2(&strm, 15 | 16); + if (ret != Z_OK) + { + throw std::runtime_error("inflateInit2 failed with error " + std::to_string(ret)); + } + } + + int flush = 0; + const auto* src = static_cast(data); + size_t srcRemaining = dataLen; + do + { + const auto nextBlockSize = std::min(srcRemaining, CHUNK); + srcRemaining -= nextBlockSize; + + flush = srcRemaining == 0 ? Z_FINISH : Z_NO_FLUSH; + strm.avail_in = static_cast(nextBlockSize); + strm.next_in = const_cast(src); + do + { + output.resize(output.size() + nextBlockSize); + strm.avail_out = static_cast(nextBlockSize); + strm.next_out = &output[output.size() - nextBlockSize]; + const auto ret = inflate(&strm, flush); + if (ret == Z_STREAM_ERROR) + { + throw std::runtime_error("deflate failed with error " + std::to_string(ret)); + } + output.resize(output.size() - strm.avail_out); + } while (strm.avail_out == 0); + + src += nextBlockSize; + } while (flush != Z_FINISH); + inflateEnd(&strm); + return output; +} + // Type-independent code left as macro to reduce duplicate code. #define add_clamp_body(value, value_to_add, min_cap, max_cap) \ if ((value_to_add > 0) && (value > (max_cap - (value_to_add)))) \ diff --git a/src/openrct2/util/Util.h b/src/openrct2/util/Util.h index 678da94245..5acfa086fd 100644 --- a/src/openrct2/util/Util.h +++ b/src/openrct2/util/Util.h @@ -58,6 +58,8 @@ uint32_t util_rand(); std::optional> util_zlib_deflate(const uint8_t* data, size_t data_in_size); uint8_t* util_zlib_inflate(const uint8_t* data, size_t data_in_size, size_t* data_out_size); bool util_gzip_compress(FILE* source, FILE* dest); +std::vector Gzip(const void* data, const size_t dataLen); +std::vector Ungzip(const void* data, const size_t dataLen); int8_t add_clamp_int8_t(int8_t value, int8_t value_to_add); int16_t add_clamp_int16_t(int16_t value, int16_t value_to_add);