Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/io/compression.cpp
9973 views
1
/**************************************************************************/
2
/* compression.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "compression.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/io/zip_io.h"
35
36
#include "thirdparty/misc/fastlz.h"
37
38
#include <zstd.h>
39
40
#ifdef BROTLI_ENABLED
41
#include <brotli/decode.h>
42
#endif
43
44
// Caches for zstd.
45
static BinaryMutex mutex;
46
static ZSTD_DCtx *current_zstd_d_ctx = nullptr;
47
static bool current_zstd_long_distance_matching;
48
static int current_zstd_window_log_size;
49
50
int64_t Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int64_t p_src_size, Mode p_mode) {
51
switch (p_mode) {
52
case MODE_BROTLI: {
53
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
54
} break;
55
case MODE_FASTLZ: {
56
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a FastLZ/LZ77 file 2 GiB or larger. LZ77 supports larger files, but FastLZ's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
57
if (p_src_size < 16) {
58
uint8_t src[16];
59
memset(&src[p_src_size], 0, 16 - p_src_size);
60
memcpy(src, p_src, p_src_size);
61
return fastlz_compress(src, 16, p_dst);
62
} else {
63
return fastlz_compress(p_src, p_src_size, p_dst);
64
}
65
66
} break;
67
case MODE_DEFLATE:
68
case MODE_GZIP: {
69
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a Deflate or GZip file 2 GiB or larger. Deflate and GZip are both limited to 4 GiB, and ZLib's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
70
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
71
72
z_stream strm;
73
strm.zalloc = zipio_alloc;
74
strm.zfree = zipio_free;
75
strm.opaque = Z_NULL;
76
int level = p_mode == MODE_DEFLATE ? zlib_level : gzip_level;
77
int err = deflateInit2(&strm, level, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
78
if (err != Z_OK) {
79
return -1;
80
}
81
82
strm.avail_in = p_src_size;
83
int aout = deflateBound(&strm, p_src_size);
84
strm.avail_out = aout;
85
strm.next_in = (Bytef *)p_src;
86
strm.next_out = p_dst;
87
deflate(&strm, Z_FINISH);
88
aout = aout - strm.avail_out;
89
deflateEnd(&strm);
90
return aout;
91
92
} break;
93
case MODE_ZSTD: {
94
ZSTD_CCtx *cctx = ZSTD_createCCtx();
95
ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, zstd_level);
96
if (zstd_long_distance_matching) {
97
ZSTD_CCtx_setParameter(cctx, ZSTD_c_enableLongDistanceMatching, 1);
98
ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, zstd_window_log_size);
99
}
100
const int64_t max_dst_size = get_max_compressed_buffer_size(p_src_size, MODE_ZSTD);
101
const size_t ret = ZSTD_compressCCtx(cctx, p_dst, max_dst_size, p_src, p_src_size, zstd_level);
102
ZSTD_freeCCtx(cctx);
103
return (int64_t)ret;
104
} break;
105
}
106
107
ERR_FAIL_V(-1);
108
}
109
110
int64_t Compression::get_max_compressed_buffer_size(int64_t p_src_size, Mode p_mode) {
111
switch (p_mode) {
112
case MODE_BROTLI: {
113
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
114
} break;
115
case MODE_FASTLZ: {
116
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a FastLZ/LZ77 file 2 GiB or larger. LZ77 supports larger files, but FastLZ's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
117
int ss = p_src_size + p_src_size * 6 / 100;
118
if (ss < 66) {
119
ss = 66;
120
}
121
return ss;
122
123
} break;
124
case MODE_DEFLATE:
125
case MODE_GZIP: {
126
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a Deflate or GZip file 2 GiB or larger. Deflate and GZip are both limited to 4 GiB, and ZLib's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
127
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
128
129
z_stream strm;
130
strm.zalloc = zipio_alloc;
131
strm.zfree = zipio_free;
132
strm.opaque = Z_NULL;
133
int err = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
134
if (err != Z_OK) {
135
return -1;
136
}
137
int aout = deflateBound(&strm, p_src_size);
138
deflateEnd(&strm);
139
return aout;
140
} break;
141
case MODE_ZSTD: {
142
return ZSTD_compressBound(p_src_size);
143
} break;
144
}
145
146
ERR_FAIL_V(-1);
147
}
148
149
int64_t Compression::decompress(uint8_t *p_dst, int64_t p_dst_max_size, const uint8_t *p_src, int64_t p_src_size, Mode p_mode) {
150
switch (p_mode) {
151
case MODE_BROTLI: {
152
#ifdef BROTLI_ENABLED
153
size_t ret_size = p_dst_max_size;
154
BrotliDecoderResult res = BrotliDecoderDecompress(p_src_size, p_src, &ret_size, p_dst);
155
ERR_FAIL_COND_V(res != BROTLI_DECODER_RESULT_SUCCESS, -1);
156
return ret_size;
157
#else
158
ERR_FAIL_V_MSG(-1, "Godot was compiled without brotli support.");
159
#endif
160
} break;
161
case MODE_FASTLZ: {
162
ERR_FAIL_COND_V_MSG(p_dst_max_size > INT32_MAX, -1, "Cannot decompress a FastLZ/LZ77 file 2 GiB or larger. LZ77 supports larger files, but FastLZ's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
163
int ret_size = 0;
164
165
if (p_dst_max_size < 16) {
166
uint8_t dst[16];
167
fastlz_decompress(p_src, p_src_size, dst, 16);
168
memcpy(p_dst, dst, p_dst_max_size);
169
ret_size = p_dst_max_size;
170
} else {
171
ret_size = fastlz_decompress(p_src, p_src_size, p_dst, p_dst_max_size);
172
}
173
return ret_size;
174
} break;
175
case MODE_DEFLATE:
176
case MODE_GZIP: {
177
ERR_FAIL_COND_V_MSG(p_dst_max_size > INT32_MAX, -1, "Cannot decompress a Deflate or GZip file 2 GiB or larger. Deflate and GZip are both limited to 4 GiB, and ZLib's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
178
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
179
180
z_stream strm;
181
strm.zalloc = zipio_alloc;
182
strm.zfree = zipio_free;
183
strm.opaque = Z_NULL;
184
strm.avail_in = 0;
185
strm.next_in = Z_NULL;
186
int err = inflateInit2(&strm, window_bits);
187
ERR_FAIL_COND_V(err != Z_OK, -1);
188
189
strm.avail_in = p_src_size;
190
strm.avail_out = p_dst_max_size;
191
strm.next_in = (Bytef *)p_src;
192
strm.next_out = p_dst;
193
194
err = inflate(&strm, Z_FINISH);
195
int total = strm.total_out;
196
inflateEnd(&strm);
197
ERR_FAIL_COND_V(err != Z_STREAM_END, -1);
198
return total;
199
} break;
200
case MODE_ZSTD: {
201
MutexLock lock(mutex);
202
203
if (!current_zstd_d_ctx || current_zstd_long_distance_matching != zstd_long_distance_matching || current_zstd_window_log_size != zstd_window_log_size) {
204
if (current_zstd_d_ctx) {
205
ZSTD_freeDCtx(current_zstd_d_ctx);
206
}
207
208
current_zstd_d_ctx = ZSTD_createDCtx();
209
if (zstd_long_distance_matching) {
210
ZSTD_DCtx_setParameter(current_zstd_d_ctx, ZSTD_d_windowLogMax, zstd_window_log_size);
211
}
212
current_zstd_long_distance_matching = zstd_long_distance_matching;
213
current_zstd_window_log_size = zstd_window_log_size;
214
}
215
216
size_t ret = ZSTD_decompressDCtx(current_zstd_d_ctx, p_dst, p_dst_max_size, p_src, p_src_size);
217
return (int64_t)ret;
218
} break;
219
}
220
221
ERR_FAIL_V(-1);
222
}
223
224
/**
225
This will handle both Gzip and Deflate streams. It will automatically allocate the output buffer into the provided p_dst_vect Vector.
226
This is required for compressed data whose final uncompressed size is unknown, as is the case for HTTP response bodies.
227
This is much slower however than using Compression::decompress because it may result in multiple full copies of the output buffer.
228
*/
229
int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int64_t p_max_dst_size, const uint8_t *p_src, int64_t p_src_size, Mode p_mode) {
230
uint8_t *dst = nullptr;
231
int out_mark = 0;
232
233
ERR_FAIL_COND_V(p_src_size <= 0, Z_DATA_ERROR);
234
235
if (p_mode == MODE_BROTLI) {
236
#ifdef BROTLI_ENABLED
237
BrotliDecoderResult ret;
238
BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
239
ERR_FAIL_NULL_V(state, Z_DATA_ERROR);
240
241
// Setup the stream inputs.
242
const uint8_t *next_in = p_src;
243
size_t avail_in = p_src_size;
244
uint8_t *next_out = nullptr;
245
size_t avail_out = 0;
246
size_t total_out = 0;
247
248
// Ensure the destination buffer is empty.
249
p_dst_vect->clear();
250
251
// Decompress until stream ends or end of file.
252
do {
253
// Add another chunk size to the output buffer.
254
// This forces a copy of the whole buffer.
255
p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
256
// Get pointer to the actual output buffer.
257
dst = p_dst_vect->ptrw();
258
259
// Set the stream to the new output stream.
260
// Since it was copied, we need to reset the stream to the new buffer.
261
next_out = &(dst[out_mark]);
262
avail_out += gzip_chunk;
263
264
ret = BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out, &next_out, &total_out);
265
if (ret == BROTLI_DECODER_RESULT_ERROR) {
266
WARN_PRINT(BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state)));
267
BrotliDecoderDestroyInstance(state);
268
p_dst_vect->clear();
269
return Z_DATA_ERROR;
270
}
271
272
out_mark += gzip_chunk - avail_out;
273
274
// Enforce max output size.
275
if (p_max_dst_size > -1 && total_out > (uint64_t)p_max_dst_size) {
276
BrotliDecoderDestroyInstance(state);
277
p_dst_vect->clear();
278
return Z_BUF_ERROR;
279
}
280
} while (ret != BROTLI_DECODER_RESULT_SUCCESS);
281
282
// If all done successfully, resize the output if it's larger than the actual output.
283
if ((unsigned long)p_dst_vect->size() > total_out) {
284
p_dst_vect->resize(total_out);
285
}
286
287
// Clean up and return.
288
BrotliDecoderDestroyInstance(state);
289
return Z_OK;
290
#else
291
ERR_FAIL_V_MSG(Z_ERRNO, "Godot was compiled without brotli support.");
292
#endif
293
} else {
294
// This function only supports GZip and Deflate.
295
ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
296
297
int ret;
298
z_stream strm;
299
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
300
301
// Initialize the stream.
302
strm.zalloc = Z_NULL;
303
strm.zfree = Z_NULL;
304
strm.opaque = Z_NULL;
305
strm.avail_in = 0;
306
strm.next_in = Z_NULL;
307
308
int err = inflateInit2(&strm, window_bits);
309
ERR_FAIL_COND_V(err != Z_OK, -1);
310
311
// Setup the stream inputs.
312
strm.next_in = (Bytef *)p_src;
313
strm.avail_in = p_src_size;
314
315
// Ensure the destination buffer is empty.
316
p_dst_vect->clear();
317
318
// Decompress until deflate stream ends or end of file.
319
do {
320
// Add another chunk size to the output buffer.
321
// This forces a copy of the whole buffer.
322
p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
323
// Get pointer to the actual output buffer.
324
dst = p_dst_vect->ptrw();
325
326
// Set the stream to the new output stream.
327
// Since it was copied, we need to reset the stream to the new buffer.
328
strm.next_out = &(dst[out_mark]);
329
strm.avail_out = gzip_chunk;
330
331
// Run inflate() on input until output buffer is full and needs to be resized or input runs out.
332
do {
333
ret = inflate(&strm, Z_SYNC_FLUSH);
334
335
switch (ret) {
336
case Z_NEED_DICT:
337
ret = Z_DATA_ERROR;
338
[[fallthrough]];
339
case Z_DATA_ERROR:
340
case Z_MEM_ERROR:
341
case Z_STREAM_ERROR:
342
case Z_BUF_ERROR:
343
if (strm.msg) {
344
WARN_PRINT(strm.msg);
345
}
346
(void)inflateEnd(&strm);
347
p_dst_vect->clear();
348
return ret;
349
}
350
} while (strm.avail_out > 0 && strm.avail_in > 0);
351
352
out_mark += gzip_chunk;
353
354
// Enforce max output size.
355
if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) {
356
(void)inflateEnd(&strm);
357
p_dst_vect->clear();
358
return Z_BUF_ERROR;
359
}
360
} while (ret != Z_STREAM_END);
361
362
// If all done successfully, resize the output if it's larger than the actual output.
363
if ((unsigned long)p_dst_vect->size() > strm.total_out) {
364
p_dst_vect->resize(strm.total_out);
365
}
366
367
// Clean up and return.
368
(void)inflateEnd(&strm);
369
return Z_OK;
370
}
371
}
372
373