diff --git a/src/modules/math/Compressor.cpp b/src/modules/math/Compressor.cpp index 2a15fba64..6a199b036 100644 --- a/src/modules/math/Compressor.cpp +++ b/src/modules/math/Compressor.cpp @@ -39,8 +39,11 @@ class LZ4Compressor : public Compressor { public: - char *compress(const char *data, size_t dataSize, int level, size_t &compressedSize) override + char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) override { + if (format != FORMAT_LZ4) + throw love::Exception("Invalid format (expecting LZ4)"); + if (dataSize > LZ4_MAX_INPUT_SIZE) throw love::Exception("Data is too large for LZ4 compressor."); @@ -99,8 +102,11 @@ public: return compressedbytes; } - char *decompress(const char *data, size_t dataSize, size_t &decompressedSize) override + char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) override { + if (format != FORMAT_LZ4) + throw love::Exception("Invalid format (expecting LZ4)"); + const size_t headersize = sizeof(uint32); char *rawbytes = nullptr; @@ -154,23 +160,111 @@ public: return rawbytes; } - Compressor::Format getFormat() const override { return FORMAT_LZ4; } + bool isSupported(Format format) const override + { + return format == FORMAT_LZ4; + } }; // LZ4Compressor class zlibCompressor : public Compressor { +private: + + // The following three functions are mostly copied from the zlib source + // (compressBound, compress2, and uncompress), but modified to support both + // zlib and gzip. + + uLong zlibCompressBound(Format format, uLong sourceLen) + { + uLong size = sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13; + + // The gzip header is slightly larger than the zlib header. + if (format == FORMAT_GZIP) + size += 18 - 6; + + return size; + } + + int zlibCompress(Format format, Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level) + { + z_stream stream = {}; + + stream.next_in = (Bytef *) source; + stream.avail_in = (uInt) sourceLen; + + stream.next_out = dest; + stream.avail_out = (uInt) (*destLen); + + int windowbits = 15; + if (format == FORMAT_GZIP) + windowbits += 16; // This tells zlib to use a gzip header. + + int err = deflateInit2(&stream, level, Z_DEFLATED, windowbits, 8, Z_DEFAULT_STRATEGY); + + if (err != Z_OK) + return err; + + err = deflate(&stream, Z_FINISH); + + if (err != Z_STREAM_END) + { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + + *destLen = stream.total_out; + + return deflateEnd(&stream); + } + + int zlibDecompress(Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen) + { + z_stream stream = {}; + + stream.next_in = (Bytef *) source; + stream.avail_in = (uInt) sourceLen; + + stream.next_out = dest; + stream.avail_out = (uInt) (*destLen); + + // 15 is the default. Adding 32 makes zlib auto-detect the header type. + int windowbits = 15 + 32; + + int err = inflateInit2(&stream, windowbits); + + if (err != Z_OK) + return err; + + err = inflate(&stream, Z_FINISH); + + if (err != Z_STREAM_END) + { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; + } + + *destLen = stream.total_out; + + return inflateEnd(&stream); + } + public: - char *compress(const char *data, size_t dataSize, int level, size_t &compressedSize) override + char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) override { + if (!isSupported(format)) + throw love::Exception("Invalid format (expecting zlib or gzip)"); + if (level < 0) level = Z_DEFAULT_COMPRESSION; else if (level > 9) level = 9; - uLong maxsize = compressBound((uLong) dataSize); + uLong maxsize = zlibCompressBound(format, (uLong) dataSize); char *compressedbytes = nullptr; try @@ -183,18 +277,18 @@ public: } uLongf destlen = maxsize; - int status = compress2((Bytef *) compressedbytes, &destlen, (const Bytef *) data, (uLong) dataSize, level); + int status = zlibCompress(format, (Bytef *) compressedbytes, &destlen, (const Bytef *) data, (uLong) dataSize, level); if (status != Z_OK) { delete[] compressedbytes; - throw love::Exception("Could not zlib-compress data."); + throw love::Exception("Could not zlib/gzip-compress data."); } // We allocated space for the maximum possible amount of data, but the // actual compressed size might be much smaller, so we should shrink the // data buffer if so. - if ((double) maxsize / (double) destlen >= 1.2) + if ((double) maxsize / (double) destlen >= 1.3) { char *cbytes = new (std::nothrow) char[destlen]; if (cbytes) @@ -209,8 +303,11 @@ public: return compressedbytes; } - char *decompress(const char *data, size_t dataSize, size_t &decompressedSize) override + char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) override { + if (!isSupported(format)) + throw love::Exception("Invalid format (expecting zlib or gzip)"); + char *rawbytes = nullptr; // We might know the output size before decompression. If not, we guess. @@ -229,7 +326,7 @@ public: } uLongf destLen = (uLongf) rawsize; - int status = uncompress((Bytef *) rawbytes, &destLen, (const Bytef *) data, (uLong) dataSize); + int status = zlibDecompress((Bytef *) rawbytes, &destLen, (const Bytef *) data, (uLong) dataSize); if (status == Z_OK) { @@ -240,7 +337,7 @@ public: { // For any error other than "not enough room", throw an exception. delete[] rawbytes; - throw love::Exception("Could not decompress zlib-compressed data."); + throw love::Exception("Could not decompress zlib/gzip-compressed data."); } // Not enough room in the output buffer: try again with a larger size. @@ -251,23 +348,27 @@ public: return rawbytes; } - Compressor::Format getFormat() const override { return FORMAT_ZLIB; } + bool isSupported(Format format) const override + { + return format == FORMAT_ZLIB || format == FORMAT_GZIP; + } }; // zlibCompressor - -Compressor *Compressor::Create(Format format) +Compressor *Compressor::getCompressor(Format format) { - switch (format) + static LZ4Compressor lz4compressor; + static zlibCompressor zlibcompressor; + + Compressor *compressors[] = {&lz4compressor, &zlibcompressor}; + + for (Compressor *c : compressors) { - case FORMAT_LZ4: - return new LZ4Compressor; - case FORMAT_ZLIB: - return new zlibCompressor; - default: - throw love::Exception("Invalid compressor format."); - return nullptr; + if (c->isSupported(format)) + return c; } + + return nullptr; } bool Compressor::getConstant(const char *in, Format &out) @@ -282,8 +383,9 @@ bool Compressor::getConstant(Format in, const char *&out) StringMap::Entry Compressor::formatEntries[] = { - {"lz4", Compressor::FORMAT_LZ4}, - {"zlib", Compressor::FORMAT_ZLIB}, + {"lz4", FORMAT_LZ4}, + {"zlib", FORMAT_ZLIB}, + {"gzip", FORMAT_GZIP}, }; StringMap Compressor::formatNames(Compressor::formatEntries, sizeof(Compressor::formatEntries)); diff --git a/src/modules/math/Compressor.h b/src/modules/math/Compressor.h index d253e62c5..bb879e8e1 100644 --- a/src/modules/math/Compressor.h +++ b/src/modules/math/Compressor.h @@ -24,6 +24,9 @@ // LOVE #include "common/StringMap.h" +// C++ +#include + namespace love { namespace math @@ -40,20 +43,22 @@ public: { FORMAT_LZ4, FORMAT_ZLIB, + FORMAT_GZIP, FORMAT_MAX_ENUM }; /** - * Creates a new Compressor that can compress and decompress a specific - * format. + * Gets a Compressor that can compress and decompress a specific format. + * Returns null if there are no supported compressors for the given format. **/ - static Compressor *Create(Format format); + static Compressor *getCompressor(Format format); virtual ~Compressor() {} /** * Compresses input data, and returns the compressed result. * + * @param[in] format The format to compress to. * @param[in] data The input (uncompressed) data. * @param[in] dataSize The size in bytes of the input data. * @param[in] level The amount of compression to apply (between 0 and 9.) @@ -63,11 +68,12 @@ public: * * @return The newly compressed data (allocated with new[]). **/ - virtual char *compress(const char *data, size_t dataSize, int level, size_t &compressedSize) = 0; + virtual char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) = 0; /** * Decompresses compressed data, and returns the decompressed result. * + * @param[in] format The format the compressed data is in. * @param[in] data The input (compressed) data. * @param[in] dataSize The size in bytes of the compressed data. * @param[in,out] decompressedSize On input, the size in bytes of the @@ -76,18 +82,20 @@ public: * * @return The decompressed data (allocated with new[]). **/ - virtual char *decompress(const char *data, size_t dataSize, size_t &decompressedSize) = 0; + virtual char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) = 0; /** - * Gets the compression format implemented by this backend. - * - * @return The supported format. + * Gets whether a specific format is supported by this backend. **/ - virtual Format getFormat() const = 0; + virtual bool isSupported(Format format) const = 0; static bool getConstant(const char *in, Format &out); static bool getConstant(Format in, const char *&out); +protected: + + Compressor() {} + private: static StringMap::Entry formatEntries[]; diff --git a/src/modules/math/MathModule.cpp b/src/modules/math/MathModule.cpp index 71c3459df..61e6f7f69 100644 --- a/src/modules/math/MathModule.cpp +++ b/src/modules/math/MathModule.cpp @@ -86,19 +86,13 @@ Math Math::instance; Math::Math() : rng() - , compressors() { // prevent the runtime from free()-ing this retain(); - - for (int i = 0; i < (int) Compressor::FORMAT_MAX_ENUM; i++) - compressors[i] = Compressor::Create((Compressor::Format) i); } Math::~Math() { - for (Compressor *c : compressors) - delete c; } RandomGenerator *Math::newRandomGenerator() @@ -232,13 +226,13 @@ CompressedData *Math::compress(Compressor::Format format, love::Data *rawdata, i CompressedData *Math::compress(Compressor::Format format, const char *rawbytes, size_t rawsize, int level) { - if (format == Compressor::FORMAT_MAX_ENUM || !compressors[format]) + Compressor *compressor = Compressor::getCompressor(format); + + if (compressor == nullptr) throw love::Exception("Invalid compression format."); size_t compressedsize = 0; - Compressor *compressor = compressors[format]; - - char *cbytes = compressor->compress(rawbytes, rawsize, level, compressedsize); + char *cbytes = compressor->compress(format, rawbytes, rawsize, level, compressedsize); CompressedData *data = nullptr; @@ -268,10 +262,12 @@ char *Math::decompress(CompressedData *data, size_t &decompressedsize) char *Math::decompress(Compressor::Format format, const char *cbytes, size_t compressedsize, size_t &rawsize) { - if (format == Compressor::FORMAT_MAX_ENUM || !compressors[format]) + Compressor *compressor = Compressor::getCompressor(format); + + if (compressor == nullptr) throw love::Exception("Invalid compression format."); - return compressors[format]->decompress(cbytes, compressedsize, rawsize); + return compressor->decompress(format, cbytes, compressedsize, rawsize); } } // math diff --git a/src/modules/math/MathModule.h b/src/modules/math/MathModule.h index b2af72659..0f0edf807 100644 --- a/src/modules/math/MathModule.h +++ b/src/modules/math/MathModule.h @@ -205,8 +205,6 @@ private: Math(); - Compressor *compressors[Compressor::FORMAT_MAX_ENUM]; - }; // Math inline float Math::noise(float x) const