diff --git a/src/openrct2/core/Compression.cpp b/src/openrct2/core/Compression.cpp new file mode 100644 index 0000000000..23c5885a3e --- /dev/null +++ b/src/openrct2/core/Compression.cpp @@ -0,0 +1,176 @@ +/***************************************************************************** + * Copyright (c) 2014-2024 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. + *****************************************************************************/ + +#include "Compression.h" + +#include "../Diagnostic.h" +#include "zlib.h" + +#include +#include +#include + +namespace OpenRCT2::Compression +{ + constexpr size_t CHUNK = 128 * 1024; + + // Compress the source to gzip-compatible stream, write to dest. + // Mainly used for compressing the crashdumps + bool gzipCompress(FILE* source, FILE* dest) + { + if (source == nullptr || dest == nullptr) + { + return false; + } + int ret, flush; + size_t have; + z_stream strm{}; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + int windowBits = 15; + int GZIP_ENCODING = 16; + ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits | GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) + { + LOG_ERROR("Failed to initialise stream"); + return false; + } + do + { + strm.avail_in = uInt(fread(in, 1, CHUNK, source)); + if (ferror(source)) + { + deflateEnd(&strm); + LOG_ERROR("Failed to read data from source"); + return false; + } + flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; + strm.next_in = in; + do + { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = deflate(&strm, flush); + if (ret == Z_STREAM_ERROR) + { + LOG_ERROR("Failed to compress data"); + return false; + } + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) + { + deflateEnd(&strm); + LOG_ERROR("Failed to write data to destination"); + return false; + } + } while (strm.avail_out == 0); + } while (flush != Z_FINISH); + deflateEnd(&strm); + 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; + } +} // namespace OpenRCT2::Compression diff --git a/src/openrct2/core/Compression.h b/src/openrct2/core/Compression.h new file mode 100644 index 0000000000..999d05db9b --- /dev/null +++ b/src/openrct2/core/Compression.h @@ -0,0 +1,21 @@ +/***************************************************************************** + * Copyright (c) 2014-2024 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 +#include +#include + +namespace OpenRCT2::Compression +{ + bool gzipCompress(FILE* source, FILE* dest); + std::vector gzip(const void* data, const size_t dataLen); + std::vector ungzip(const void* data, const size_t dataLen); +} // namespace OpenRCT2::Compression diff --git a/src/openrct2/core/OrcaStream.hpp b/src/openrct2/core/OrcaStream.hpp index 3a54b81039..257c8952a5 100644 --- a/src/openrct2/core/OrcaStream.hpp +++ b/src/openrct2/core/OrcaStream.hpp @@ -9,7 +9,7 @@ #pragma once -#include "../util/Util.h" +#include "../core/Compression.h" #include "../world/Location.hpp" #include "Crypt.h" #include "FileStream.h" @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -99,7 +100,7 @@ namespace OpenRCT2 // Uncompress if (_header.Compression == COMPRESSION_GZIP) { - auto uncompressedData = Ungzip(_buffer.GetData(), _buffer.GetLength()); + auto uncompressedData = Compression::ungzip(_buffer.GetData(), _buffer.GetLength()); if (_header.UncompressedSize != uncompressedData.size()) { // Warning? @@ -135,7 +136,7 @@ namespace OpenRCT2 std::optional> compressedBytes; if (_header.Compression == COMPRESSION_GZIP) { - compressedBytes = Gzip(uncompressedData, uncompressedSize); + compressedBytes = Compression::gzip(uncompressedData, uncompressedSize); if (compressedBytes) { _header.CompressedSize = compressedBytes->size(); diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 74ea55ec6e..c1268858a1 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -186,6 +186,7 @@ + @@ -745,6 +746,7 @@ + diff --git a/src/openrct2/platform/Crash.cpp b/src/openrct2/platform/Crash.cpp index f5a0add067..6fb17cbdde 100644 --- a/src/openrct2/platform/Crash.cpp +++ b/src/openrct2/platform/Crash.cpp @@ -31,6 +31,7 @@ # include "../PlatformEnvironment.h" # include "../Version.h" # include "../config/Config.h" +# include "../core/Compression.h" # include "../core/Console.hpp" # include "../core/Guard.hpp" # include "../core/Path.hpp" @@ -139,7 +140,7 @@ static bool OnCrash( FILE* input = _wfopen(dumpFilePath, L"rb"); FILE* dest = _wfopen(dumpFilePathGZIP, L"wb"); - if (UtilGzipCompress(input, dest)) + if (Compression::gzipCompress(input, dest)) { // TODO: enable upload of gzip-compressed dumps once supported on // backtrace.io (uncomment the line below). For now leave compression diff --git a/src/openrct2/util/Util.cpp b/src/openrct2/util/Util.cpp index 0ac29fe1ef..b82533016b 100644 --- a/src/openrct2/util/Util.cpp +++ b/src/openrct2/util/Util.cpp @@ -16,7 +16,6 @@ #include "../interface/Window.h" #include "../platform/Platform.h" #include "../scenes/title/TitleScene.h" -#include "zlib.h" #include #include @@ -225,162 +224,6 @@ float UtilRandNormalDistributed() return _distributor(_prng); } -constexpr size_t CHUNK = 128 * 1024; - -// Compress the source to gzip-compatible stream, write to dest. -// Mainly used for compressing the crashdumps -bool UtilGzipCompress(FILE* source, FILE* dest) -{ - if (source == nullptr || dest == nullptr) - { - return false; - } - int ret, flush; - size_t have; - z_stream strm{}; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - unsigned char in[CHUNK]; - unsigned char out[CHUNK]; - int windowBits = 15; - int GZIP_ENCODING = 16; - ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits | GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY); - if (ret != Z_OK) - { - LOG_ERROR("Failed to initialise stream"); - return false; - } - do - { - strm.avail_in = uInt(fread(in, 1, CHUNK, source)); - if (ferror(source)) - { - deflateEnd(&strm); - LOG_ERROR("Failed to read data from source"); - return false; - } - flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; - strm.next_in = in; - do - { - strm.avail_out = CHUNK; - strm.next_out = out; - ret = deflate(&strm, flush); - if (ret == Z_STREAM_ERROR) - { - LOG_ERROR("Failed to compress data"); - return false; - } - have = CHUNK - strm.avail_out; - if (fwrite(out, 1, have, dest) != have || ferror(dest)) - { - deflateEnd(&strm); - LOG_ERROR("Failed to write data to destination"); - return false; - } - } while (strm.avail_out == 0); - } while (flush != Z_FINISH); - deflateEnd(&strm); - 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; -} - uint8_t Lerp(uint8_t a, uint8_t b, float t) { if (t <= 0) diff --git a/src/openrct2/util/Util.h b/src/openrct2/util/Util.h index 3db5be95c2..94bea63235 100644 --- a/src/openrct2/util/Util.h +++ b/src/openrct2/util/Util.h @@ -38,10 +38,6 @@ char* SafeStrCat(char* destination, const char* source, size_t size); uint32_t UtilRand(); float UtilRandNormalDistributed(); -bool UtilGzipCompress(FILE* source, FILE* dest); -std::vector Gzip(const void* data, const size_t dataLen); -std::vector Ungzip(const void* data, const size_t dataLen); - template constexpr T AddClamp(T value, T valueToAdd) {