1 | /** |
2 | * Copyright (c) 2006-2023 LOVE Development Team |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * |
8 | * Permission is granted to anyone to use this software for any purpose, |
9 | * including commercial applications, and to alter it and redistribute it |
10 | * freely, subject to the following restrictions: |
11 | * |
12 | * 1. The origin of this software must not be misrepresented; you must not |
13 | * claim that you wrote the original software. If you use this software |
14 | * in a product, an acknowledgment in the product documentation would be |
15 | * appreciated but is not required. |
16 | * 2. Altered source versions must be plainly marked as such, and must not be |
17 | * misrepresented as being the original software. |
18 | * 3. This notice may not be removed or altered from any source distribution. |
19 | **/ |
20 | |
21 | // LOVE |
22 | #include "Compressor.h" |
23 | #include "common/config.h" |
24 | #include "common/int.h" |
25 | |
26 | #include "libraries/lz4/lz4.h" |
27 | #include "libraries/lz4/lz4hc.h" |
28 | |
29 | #include <zlib.h> |
30 | |
31 | namespace love |
32 | { |
33 | namespace data |
34 | { |
35 | |
36 | class LZ4Compressor : public Compressor |
37 | { |
38 | public: |
39 | |
40 | char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) override |
41 | { |
42 | if (format != FORMAT_LZ4) |
43 | throw love::Exception("Invalid format (expecting LZ4)" ); |
44 | |
45 | if (dataSize > LZ4_MAX_INPUT_SIZE) |
46 | throw love::Exception("Data is too large for LZ4 compressor." ); |
47 | |
48 | // We use a custom header to store some info with the compressed data. |
49 | const size_t = sizeof(uint32); |
50 | |
51 | int maxdestsize = LZ4_compressBound((int) dataSize); |
52 | size_t maxsize = headersize + (size_t) maxdestsize; |
53 | char *compressedbytes = nullptr; |
54 | |
55 | try |
56 | { |
57 | compressedbytes = new char[maxsize]; |
58 | } |
59 | catch (std::bad_alloc &) |
60 | { |
61 | throw love::Exception("Out of memory." ); |
62 | } |
63 | |
64 | // Store the size of the uncompressed data as a header. |
65 | #ifdef LOVE_BIG_ENDIAN |
66 | // Make sure it's little-endian for storage. |
67 | *(uint32 *) compressedbytes = swapuint32((uint32) dataSize); |
68 | #else |
69 | *(uint32 *) compressedbytes = (uint32) dataSize; |
70 | #endif |
71 | |
72 | // Use LZ4-HC for compression level 9 and higher. |
73 | int csize = 0; |
74 | if (level > 8) |
75 | csize = LZ4_compress_HC(data, compressedbytes + headersize, (int) dataSize, maxdestsize, LZ4HC_CLEVEL_DEFAULT); |
76 | else |
77 | csize = LZ4_compress_default(data, compressedbytes + headersize, (int) dataSize, maxdestsize); |
78 | |
79 | if (csize <= 0) |
80 | { |
81 | delete[] compressedbytes; |
82 | throw love::Exception("Could not LZ4-compress data." ); |
83 | } |
84 | |
85 | // We allocated space for the maximum possible amount of data, but the |
86 | // actual compressed size might be much smaller, so we should shrink the |
87 | // data buffer if so. |
88 | if ((double) maxsize / (double) (csize + headersize) >= 1.2) |
89 | { |
90 | char *cbytes = new (std::nothrow) char[csize + headersize]; |
91 | if (cbytes) |
92 | { |
93 | memcpy(cbytes, compressedbytes, csize + headersize); |
94 | delete[] compressedbytes; |
95 | compressedbytes = cbytes; |
96 | } |
97 | } |
98 | |
99 | compressedSize = (size_t) csize + headersize; |
100 | return compressedbytes; |
101 | } |
102 | |
103 | char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) override |
104 | { |
105 | if (format != FORMAT_LZ4) |
106 | throw love::Exception("Invalid format (expecting LZ4)" ); |
107 | |
108 | const size_t = sizeof(uint32); |
109 | char *rawbytes = nullptr; |
110 | |
111 | if (dataSize < headersize) |
112 | throw love::Exception("Invalid LZ4-compressed data size." ); |
113 | |
114 | // Extract the original uncompressed size (stored in our custom header.) |
115 | #ifdef LOVE_BIG_ENDIAN |
116 | // Convert from stored little-endian to big-endian. |
117 | uint32 rawsize = swapuint32(*(uint32 *) data); |
118 | #else |
119 | uint32 rawsize = *(uint32 *) data; |
120 | #endif |
121 | |
122 | try |
123 | { |
124 | rawbytes = new char[rawsize]; |
125 | } |
126 | catch (std::bad_alloc &) |
127 | { |
128 | throw love::Exception("Out of memory." ); |
129 | } |
130 | |
131 | // If the uncompressed size is passed in as an argument (non-zero) and |
132 | // it matches the header's stored size, then we assume it's 100% accurate |
133 | // and we use a more efficient decompression function. |
134 | if (decompressedSize > 0 && decompressedSize == (size_t) rawsize) |
135 | { |
136 | // We don't use the header here, but we need to account for its size. |
137 | if (LZ4_decompress_fast(data + headersize, rawbytes, (int) decompressedSize) < 0) |
138 | { |
139 | delete[] rawbytes; |
140 | throw love::Exception("Could not decompress LZ4-compressed data." ); |
141 | } |
142 | } |
143 | else |
144 | { |
145 | // Account for our custom header's size in the decompress arguments. |
146 | int result = LZ4_decompress_safe(data + headersize, rawbytes, |
147 | (int) (dataSize - headersize), rawsize); |
148 | |
149 | if (result < 0) |
150 | { |
151 | delete[] rawbytes; |
152 | throw love::Exception("Could not decompress LZ4-compressed data." ); |
153 | } |
154 | |
155 | decompressedSize = (size_t) result; |
156 | } |
157 | |
158 | return rawbytes; |
159 | } |
160 | |
161 | bool isSupported(Format format) const override |
162 | { |
163 | return format == FORMAT_LZ4; |
164 | } |
165 | |
166 | }; // LZ4Compressor |
167 | |
168 | |
169 | class zlibCompressor : public Compressor |
170 | { |
171 | private: |
172 | |
173 | // The following three functions are mostly copied from the zlib source |
174 | // (compressBound, compress2, and uncompress), but modified to support both |
175 | // zlib and gzip. |
176 | |
177 | uLong zlibCompressBound(Format format, uLong sourceLen) |
178 | { |
179 | uLong size = sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13; |
180 | |
181 | // The gzip header is slightly larger than the zlib header. |
182 | if (format == FORMAT_GZIP) |
183 | size += 18 - 6; |
184 | |
185 | return size; |
186 | } |
187 | |
188 | int zlibCompress(Format format, Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level) |
189 | { |
190 | z_stream stream = {}; |
191 | |
192 | stream.next_in = (Bytef *) source; |
193 | stream.avail_in = (uInt) sourceLen; |
194 | |
195 | stream.next_out = dest; |
196 | stream.avail_out = (uInt) (*destLen); |
197 | |
198 | int windowbits = 15; |
199 | if (format == FORMAT_GZIP) |
200 | windowbits += 16; // This tells zlib to use a gzip header. |
201 | else if (format == FORMAT_DEFLATE) |
202 | windowbits = -windowbits; |
203 | |
204 | int err = deflateInit2(&stream, level, Z_DEFLATED, windowbits, 8, Z_DEFAULT_STRATEGY); |
205 | |
206 | if (err != Z_OK) |
207 | return err; |
208 | |
209 | err = deflate(&stream, Z_FINISH); |
210 | |
211 | if (err != Z_STREAM_END) |
212 | { |
213 | deflateEnd(&stream); |
214 | return err == Z_OK ? Z_BUF_ERROR : err; |
215 | } |
216 | |
217 | *destLen = stream.total_out; |
218 | |
219 | return deflateEnd(&stream); |
220 | } |
221 | |
222 | int zlibDecompress(Format format, Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen) |
223 | { |
224 | z_stream stream = {}; |
225 | |
226 | stream.next_in = (Bytef *) source; |
227 | stream.avail_in = (uInt) sourceLen; |
228 | |
229 | stream.next_out = dest; |
230 | stream.avail_out = (uInt) (*destLen); |
231 | |
232 | // 15 is the default. Adding 32 makes zlib auto-detect the header type. |
233 | int windowbits = 15 + 32; |
234 | |
235 | if (format == FORMAT_DEFLATE) |
236 | windowbits = -15; |
237 | |
238 | int err = inflateInit2(&stream, windowbits); |
239 | |
240 | if (err != Z_OK) |
241 | return err; |
242 | |
243 | err = inflate(&stream, Z_FINISH); |
244 | |
245 | if (err != Z_STREAM_END) |
246 | { |
247 | inflateEnd(&stream); |
248 | if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) |
249 | return Z_DATA_ERROR; |
250 | return err; |
251 | } |
252 | |
253 | *destLen = stream.total_out; |
254 | |
255 | return inflateEnd(&stream); |
256 | } |
257 | |
258 | public: |
259 | |
260 | char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) override |
261 | { |
262 | if (!isSupported(format)) |
263 | throw love::Exception("Invalid format (expecting zlib or gzip)" ); |
264 | |
265 | if (level < 0) |
266 | level = Z_DEFAULT_COMPRESSION; |
267 | else if (level > 9) |
268 | level = 9; |
269 | |
270 | uLong maxsize = zlibCompressBound(format, (uLong) dataSize); |
271 | char *compressedbytes = nullptr; |
272 | |
273 | try |
274 | { |
275 | compressedbytes = new char[maxsize]; |
276 | } |
277 | catch (std::bad_alloc &) |
278 | { |
279 | throw love::Exception("Out of memory." ); |
280 | } |
281 | |
282 | uLongf destlen = maxsize; |
283 | int status = zlibCompress(format, (Bytef *) compressedbytes, &destlen, (const Bytef *) data, (uLong) dataSize, level); |
284 | |
285 | if (status != Z_OK) |
286 | { |
287 | delete[] compressedbytes; |
288 | throw love::Exception("Could not zlib/gzip-compress data." ); |
289 | } |
290 | |
291 | // We allocated space for the maximum possible amount of data, but the |
292 | // actual compressed size might be much smaller, so we should shrink the |
293 | // data buffer if so. |
294 | if ((double) maxsize / (double) destlen >= 1.3) |
295 | { |
296 | char *cbytes = new (std::nothrow) char[destlen]; |
297 | if (cbytes) |
298 | { |
299 | memcpy(cbytes, compressedbytes, destlen); |
300 | delete[] compressedbytes; |
301 | compressedbytes = cbytes; |
302 | } |
303 | } |
304 | |
305 | compressedSize = (size_t) destlen; |
306 | return compressedbytes; |
307 | } |
308 | |
309 | char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) override |
310 | { |
311 | if (!isSupported(format)) |
312 | throw love::Exception("Invalid format (expecting zlib or gzip)" ); |
313 | |
314 | char *rawbytes = nullptr; |
315 | |
316 | // We might know the output size before decompression. If not, we guess. |
317 | size_t rawsize = decompressedSize > 0 ? decompressedSize : dataSize * 2; |
318 | |
319 | // Repeatedly try to decompress with an increasingly large output buffer. |
320 | while (true) |
321 | { |
322 | try |
323 | { |
324 | rawbytes = new char[rawsize]; |
325 | } |
326 | catch (std::bad_alloc &) |
327 | { |
328 | throw love::Exception("Out of memory." ); |
329 | } |
330 | |
331 | uLongf destLen = (uLongf) rawsize; |
332 | int status = zlibDecompress(format, (Bytef *) rawbytes, &destLen, (const Bytef *) data, (uLong) dataSize); |
333 | |
334 | if (status == Z_OK) |
335 | { |
336 | decompressedSize = (size_t) destLen; |
337 | break; |
338 | } |
339 | else if (status != Z_BUF_ERROR) |
340 | { |
341 | // For any error other than "not enough room", throw an exception. |
342 | delete[] rawbytes; |
343 | throw love::Exception("Could not decompress zlib/gzip-compressed data." ); |
344 | } |
345 | |
346 | // Not enough room in the output buffer: try again with a larger size. |
347 | delete[] rawbytes; |
348 | rawsize *= 2; |
349 | } |
350 | |
351 | return rawbytes; |
352 | } |
353 | |
354 | bool isSupported(Format format) const override |
355 | { |
356 | return format == FORMAT_ZLIB || format == FORMAT_GZIP || format == FORMAT_DEFLATE; |
357 | } |
358 | |
359 | }; // zlibCompressor |
360 | |
361 | Compressor *Compressor::getCompressor(Format format) |
362 | { |
363 | static LZ4Compressor lz4compressor; |
364 | static zlibCompressor zlibcompressor; |
365 | |
366 | Compressor *compressors[] = {&lz4compressor, &zlibcompressor}; |
367 | |
368 | for (Compressor *c : compressors) |
369 | { |
370 | if (c->isSupported(format)) |
371 | return c; |
372 | } |
373 | |
374 | return nullptr; |
375 | } |
376 | |
377 | bool Compressor::getConstant(const char *in, Format &out) |
378 | { |
379 | return formatNames.find(in, out); |
380 | } |
381 | |
382 | bool Compressor::getConstant(Format in, const char *&out) |
383 | { |
384 | return formatNames.find(in, out); |
385 | } |
386 | |
387 | std::vector<std::string> Compressor::getConstants(Format) |
388 | { |
389 | return formatNames.getNames(); |
390 | } |
391 | |
392 | StringMap<Compressor::Format, Compressor::FORMAT_MAX_ENUM>::Entry Compressor::formatEntries[] = |
393 | { |
394 | { "lz4" , FORMAT_LZ4 }, |
395 | { "zlib" , FORMAT_ZLIB }, |
396 | { "gzip" , FORMAT_GZIP }, |
397 | { "deflate" , FORMAT_DEFLATE }, |
398 | }; |
399 | |
400 | StringMap<Compressor::Format, Compressor::FORMAT_MAX_ENUM> Compressor::formatNames(Compressor::formatEntries, sizeof(Compressor::formatEntries)); |
401 | |
402 | } // data |
403 | } // love |
404 | |