diff --git a/ext/tmxlite/CMakeLists.txt b/ext/tmxlite/CMakeLists.txt index 4db2f36..56e52e3 100644 --- a/ext/tmxlite/CMakeLists.txt +++ b/ext/tmxlite/CMakeLists.txt @@ -3,15 +3,16 @@ project(tmxlite VERSION 1.3.1) # includes the list of source files in the src directory set(PROJECT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) file(GLOB PROJECT_SRC ${PROJECT_DIR}/*.cpp) +file(GLOB PROJECT_SRC_DETAIL ${PROJECT_DIR}/detail/*.cpp) file(GLOB PROJECT_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/tmxlite/*.hpp) file(GLOB PROJECT_HEADERS_INL ${CMAKE_CURRENT_SOURCE_DIR}/include/tmxlite/*.inl) file(GLOB PROJECT_HEADERS_DETAIL ${CMAKE_CURRENT_SOURCE_DIR}/include/tmxlite/detail/*.hpp) -list(APPEND PROJECT_SRC ${PROJECT_HEADERS} ${PROJECT_HEADERS_INL} ${PROJECT_HEADERS_DETAIL}) +list(APPEND PROJECT_SRC ${PROJECT_SRC_DETAIL} ${PROJECT_HEADERS} ${PROJECT_HEADERS_INL} ${PROJECT_HEADERS_DETAIL}) add_library(${PROJECT_NAME} STATIC ${PROJECT_SRC}) set_target_properties(${PROJECT_NAME} PROPERTIES - CXX_STANDARD 14 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/ext/tmxlite/include/tmxlite/detail/gzip.hpp b/ext/tmxlite/include/tmxlite/detail/gzip.hpp new file mode 100644 index 0000000..f883fd4 --- /dev/null +++ b/ext/tmxlite/include/tmxlite/detail/gzip.hpp @@ -0,0 +1,39 @@ +// gzip.hpp - portable memory miniz based gzip reader +// SPDX-License-Identifier: Zlib +// SPDX-FileCopyrightText: (c) 2024 a dinosaur + +#ifndef GZIP_HPP +#define GZIP_HPP + +#include "miniz.h" +#include +#include + + +class GZipReader +{ + static constexpr uint8_t + FTEXT = 1, FHCRC = 1<<1, FEXTRA = 1<<2, FNAME = 1<<3, FCOMMENT = 1<<4; + + static constexpr uint8_t XFL_BEST = 2, XFL_FASTEST = 4; + + tinfl_decompressor mState; + std::span::iterator mIt; + + size_t mSourceLen, mBytesRead; + uint32_t mModificationTime, mCrc, mInputSize, mComputedCrc; + uint16_t crc16; + uint8_t mFlags, mXflags, mOsId; + +public: + GZipReader() noexcept; + + constexpr size_t SourceLength() const noexcept { return mSourceLen; } + constexpr uint32_t OutputLength() const noexcept { return mInputSize; } + + bool OpenMemory(const std::span source) noexcept; + bool Read(std::span out) noexcept; + bool Check() const noexcept; +}; + +#endif//GZIP_HPP diff --git a/ext/tmxlite/src/TileLayer.cpp b/ext/tmxlite/src/TileLayer.cpp index bddce9d..a08f30a 100644 --- a/ext/tmxlite/src/TileLayer.cpp +++ b/ext/tmxlite/src/TileLayer.cpp @@ -29,10 +29,14 @@ source distribution. #include "tmxlite/FreeFuncs.hpp" #include "tmxlite/TileLayer.hpp" #include "tmxlite/detail/Log.hpp" +#ifndef USE_ZLIB +# include "tmxlite/detail/gzip.hpp" +#endif #include #include #include +#include using namespace tmx; @@ -140,8 +144,18 @@ void TileLayer::parseBase64(const pugi::xml_node& node) break; case CompressionType::GZip: #ifndef USE_ZLIB - Logger::log("Library must be built with USE_ZLIB for GZip compression", Logger::Type::Error); - return {}; + { + byteData.resize(expectedSize); + const auto source = std::span(reinterpret_cast(dataString.data()), dataString.size()); + + GZipReader reader; + if (!reader.OpenMemory(source) || !reader.Read(byteData) || !reader.Check()) + { + LOG("Failed to decompress layer data, node skipped.", Logger::Type::Error); + return {}; + } + } + break; #endif //[[fallthrough]]; case CompressionType::Zlib: diff --git a/ext/tmxlite/src/detail/gzip.cpp b/ext/tmxlite/src/detail/gzip.cpp new file mode 100644 index 0000000..d0f13bb --- /dev/null +++ b/ext/tmxlite/src/detail/gzip.cpp @@ -0,0 +1,125 @@ +// gzip.cpp - portable memory miniz based gzip reader +// SPDX-License-Identifier: Zlib +// SPDX-FileCopyrightText: (c) 2024 a dinosaur + +#include "tmxlite/detail/gzip.hpp" +#include + + +GZipReader::GZipReader() noexcept : + mSourceLen(0), mBytesRead(0), + mModificationTime(0), mCrc(0), mInputSize(0), mComputedCrc(0), + crc16(0), mFlags(0), mXflags(0), mOsId(0) +{ + tinfl_init(&mState); + mComputedCrc = static_cast(mz_crc32(0, nullptr, 0)); +} + +bool GZipReader::OpenMemory(const std::span source) noexcept +{ + if (source.size() < 20) + return false; + + auto it = std::cbegin(source), end = std::cend(source); + + constexpr uint8_t magic[2] = { 0x1F, 0x8B }; + if (*it++ != magic[0] || *it++ != magic[1]) + return false; + + constexpr uint8_t CM_DEFLATE = 8; + uint8_t compression = *it++; + if (compression != CM_DEFLATE) + return false; + + mFlags = *it++; + mModificationTime = *it++; + mModificationTime |= *it++ << 8; + mModificationTime |= *it++ << 16; + mModificationTime |= *it++ << 24; + mXflags = *it++; + mOsId = *it++; + + if (mFlags & FEXTRA) + { + // Skip "extra" field + if (it + 2 >= end) + return false; + uint16_t extraLen = *it++; + extraLen = *it++ << 8; + if (it + extraLen >= end) + return false; + it += extraLen; + } + if (mFlags & FNAME) + { + // Skip null-terminated name string + do + { + if (++it == end) + return false; + } while (*it != '\0'); + if (++it == end) + return false; + } + if (mFlags & FCOMMENT) + { + // Skip null-terminated comment string + do + { + if (++it == end) + return false; + } while (*it != '\0'); + if (++it == end) + return false; + } + if (mFlags & FHCRC) + { + if (it + 2 >= end) + return false; + crc16 = *it++; + crc16 |= *it++; + } + + mIt = it; + mSourceLen = end - it - 8; + + it += mSourceLen; + mCrc = *it++; + mCrc |= *it++ << 8; + mCrc |= *it++ << 16; + mCrc |= *it++ << 24; + mInputSize = *it++; + mInputSize |= *it++ << 8; + mInputSize |= *it++ << 16; + mInputSize |= *it++ << 24; + + return true; +} + +bool GZipReader::Read(std::span out) noexcept +{ + size_t outLen = out.size(); + auto res = tinfl_decompress(&mState, + static_cast(&*mIt), &mSourceLen, + static_cast(out.data()), static_cast(out.data()), &outLen, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if (res != TINFL_STATUS_DONE) + return false; + + mIt += outLen; + mBytesRead += outLen; + mComputedCrc = static_cast(mz_crc32(static_cast(mComputedCrc), out.data(), outLen)); + + return true; +} + +bool GZipReader::Check() const noexcept +{ + if (mComputedCrc != mCrc) + return false; + + if (static_cast(mBytesRead & UINT32_MAX) != mInputSize) + return false; + + return true; +}