2014-02-09 22:10:30 -03:00
/**************************************************************************/
/* compression.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2014-02-15 21:16:33 -03:00
# include "compression.h"
2018-09-11 18:13:45 +02:00
2020-11-07 19:33:38 -03:00
# include "core/config/project_settings.h"
2018-09-11 18:13:45 +02:00
# include "core/io/zip_io.h"
2014-02-15 21:16:33 -03:00
2017-04-28 18:29:15 +02:00
# include "thirdparty/misc/fastlz.h"
2017-09-23 23:46:47 -04:00
# include <zstd.h>
2017-04-28 18:29:15 +02:00
2023-07-06 14:46:34 +02:00
# ifdef BROTLI_ENABLED
# include <brotli/decode.h>
# endif
2025-03-15 15:39:18 +01:00
// Caches for zstd.
static BinaryMutex mutex ;
static ZSTD_DCtx * current_zstd_d_ctx = nullptr ;
static bool current_zstd_long_distance_matching ;
static int current_zstd_window_log_size ;
2025-05-08 14:13:30 -07:00
int64_t Compression : : compress ( uint8_t * p_dst , const uint8_t * p_src , int64_t p_src_size , Mode p_mode ) {
2014-02-15 21:16:33 -03:00
switch ( p_mode ) {
2023-03-29 09:31:25 +03:00
case MODE_BROTLI : {
ERR_FAIL_V_MSG ( - 1 , " Only brotli decompression is supported. " ) ;
} break ;
2014-02-15 21:16:33 -03:00
case MODE_FASTLZ : {
2025-05-08 14:13:30 -07:00
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. " ) ;
2014-02-15 21:16:33 -03:00
if ( p_src_size < 16 ) {
uint8_t src [ 16 ] ;
2021-04-27 16:19:21 +02:00
memset ( & src [ p_src_size ] , 0 , 16 - p_src_size ) ;
memcpy ( src , p_src , p_src_size ) ;
2014-02-15 21:16:33 -03:00
return fastlz_compress ( src , 16 , p_dst ) ;
} else {
return fastlz_compress ( p_src , p_src_size , p_dst ) ;
}
} break ;
2017-07-13 14:41:10 -03:00
case MODE_DEFLATE :
case MODE_GZIP : {
2025-05-08 14:13:30 -07:00
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. " ) ;
2017-07-13 14:41:10 -03:00
int window_bits = p_mode = = MODE_DEFLATE ? 15 : 15 + 16 ;
2014-02-15 21:16:33 -03:00
z_stream strm ;
strm . zalloc = zipio_alloc ;
strm . zfree = zipio_free ;
strm . opaque = Z_NULL ;
2017-07-17 21:05:38 -03:00
int level = p_mode = = MODE_DEFLATE ? zlib_level : gzip_level ;
2017-07-13 14:41:10 -03:00
int err = deflateInit2 ( & strm , level , Z_DEFLATED , window_bits , 8 , Z_DEFAULT_STRATEGY ) ;
2020-05-14 16:41:43 +02:00
if ( err ! = Z_OK ) {
2014-02-15 21:16:33 -03:00
return - 1 ;
2020-05-14 16:41:43 +02:00
}
2014-02-15 21:16:33 -03:00
strm . avail_in = p_src_size ;
2017-01-14 18:03:38 +01:00
int aout = deflateBound ( & strm , p_src_size ) ;
2014-02-15 21:16:33 -03:00
strm . avail_out = aout ;
strm . next_in = ( Bytef * ) p_src ;
strm . next_out = p_dst ;
deflate ( & strm , Z_FINISH ) ;
aout = aout - strm . avail_out ;
deflateEnd ( & strm ) ;
return aout ;
} break ;
2017-06-08 20:43:56 -05:00
case MODE_ZSTD : {
2017-10-26 16:42:02 -04:00
ZSTD_CCtx * cctx = ZSTD_createCCtx ( ) ;
2019-01-03 22:30:03 -02:00
ZSTD_CCtx_setParameter ( cctx , ZSTD_c_compressionLevel , zstd_level ) ;
2017-10-26 16:42:02 -04:00
if ( zstd_long_distance_matching ) {
2019-01-03 22:30:03 -02:00
ZSTD_CCtx_setParameter ( cctx , ZSTD_c_enableLongDistanceMatching , 1 ) ;
ZSTD_CCtx_setParameter ( cctx , ZSTD_c_windowLog , zstd_window_log_size ) ;
2017-10-26 16:42:02 -04:00
}
2025-05-08 14:13:30 -07:00
const int64_t max_dst_size = get_max_compressed_buffer_size ( p_src_size , MODE_ZSTD ) ;
const size_t ret = ZSTD_compressCCtx ( cctx , p_dst , max_dst_size , p_src , p_src_size , zstd_level ) ;
2017-10-26 16:42:02 -04:00
ZSTD_freeCCtx ( cctx ) ;
2025-05-08 14:13:30 -07:00
return ( int64_t ) ret ;
2017-06-08 20:43:56 -05:00
} break ;
2014-02-15 21:16:33 -03:00
}
ERR_FAIL_V ( - 1 ) ;
}
2025-05-08 14:13:30 -07:00
int64_t Compression : : get_max_compressed_buffer_size ( int64_t p_src_size , Mode p_mode ) {
2014-02-15 21:16:33 -03:00
switch ( p_mode ) {
2023-03-29 09:31:25 +03:00
case MODE_BROTLI : {
ERR_FAIL_V_MSG ( - 1 , " Only brotli decompression is supported. " ) ;
} break ;
2014-02-15 21:16:33 -03:00
case MODE_FASTLZ : {
2025-05-08 14:13:30 -07:00
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. " ) ;
2014-02-15 21:16:33 -03:00
int ss = p_src_size + p_src_size * 6 / 100 ;
2020-05-14 16:41:43 +02:00
if ( ss < 66 ) {
2014-02-15 21:16:33 -03:00
ss = 66 ;
2020-05-14 16:41:43 +02:00
}
2014-02-15 21:16:33 -03:00
return ss ;
} break ;
2017-07-13 14:41:10 -03:00
case MODE_DEFLATE :
case MODE_GZIP : {
2025-05-08 14:13:30 -07:00
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. " ) ;
2017-07-13 14:41:10 -03:00
int window_bits = p_mode = = MODE_DEFLATE ? 15 : 15 + 16 ;
2014-02-15 21:16:33 -03:00
z_stream strm ;
strm . zalloc = zipio_alloc ;
strm . zfree = zipio_free ;
strm . opaque = Z_NULL ;
2017-07-13 14:41:10 -03:00
int err = deflateInit2 ( & strm , Z_DEFAULT_COMPRESSION , Z_DEFLATED , window_bits , 8 , Z_DEFAULT_STRATEGY ) ;
2020-05-14 16:41:43 +02:00
if ( err ! = Z_OK ) {
2014-02-15 21:16:33 -03:00
return - 1 ;
2020-05-14 16:41:43 +02:00
}
2014-02-15 21:16:33 -03:00
int aout = deflateBound ( & strm , p_src_size ) ;
deflateEnd ( & strm ) ;
return aout ;
} break ;
2017-06-08 20:43:56 -05:00
case MODE_ZSTD : {
return ZSTD_compressBound ( p_src_size ) ;
} break ;
2014-02-15 21:16:33 -03:00
}
ERR_FAIL_V ( - 1 ) ;
}
2025-05-08 14:13:30 -07:00
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 ) {
2014-02-15 21:16:33 -03:00
switch ( p_mode ) {
2023-03-29 09:31:25 +03:00
case MODE_BROTLI : {
# ifdef BROTLI_ENABLED
size_t ret_size = p_dst_max_size ;
BrotliDecoderResult res = BrotliDecoderDecompress ( p_src_size , p_src , & ret_size , p_dst ) ;
ERR_FAIL_COND_V ( res ! = BROTLI_DECODER_RESULT_SUCCESS , - 1 ) ;
return ret_size ;
# else
ERR_FAIL_V_MSG ( - 1 , " Godot was compiled without brotli support. " ) ;
# endif
} break ;
2014-02-15 21:16:33 -03:00
case MODE_FASTLZ : {
2025-05-08 14:13:30 -07:00
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. " ) ;
2016-08-22 01:14:08 -03:00
int ret_size = 0 ;
2014-02-15 21:16:33 -03:00
if ( p_dst_max_size < 16 ) {
uint8_t dst [ 16 ] ;
2021-07-13 16:07:42 +08:00
fastlz_decompress ( p_src , p_src_size , dst , 16 ) ;
2021-04-27 16:19:21 +02:00
memcpy ( p_dst , dst , p_dst_max_size ) ;
2021-07-13 16:07:42 +08:00
ret_size = p_dst_max_size ;
2014-02-15 21:16:33 -03:00
} else {
2016-08-22 01:14:08 -03:00
ret_size = fastlz_decompress ( p_src , p_src_size , p_dst , p_dst_max_size ) ;
2014-02-15 21:16:33 -03:00
}
2016-08-22 01:14:08 -03:00
return ret_size ;
2014-02-15 21:16:33 -03:00
} break ;
2017-07-13 14:41:10 -03:00
case MODE_DEFLATE :
case MODE_GZIP : {
2025-05-08 14:13:30 -07:00
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. " ) ;
2017-07-13 14:41:10 -03:00
int window_bits = p_mode = = MODE_DEFLATE ? 15 : 15 + 16 ;
2014-02-15 21:16:33 -03:00
z_stream strm ;
strm . zalloc = zipio_alloc ;
strm . zfree = zipio_free ;
strm . opaque = Z_NULL ;
strm . avail_in = 0 ;
strm . next_in = Z_NULL ;
2017-07-13 14:41:10 -03:00
int err = inflateInit2 ( & strm , window_bits ) ;
2016-08-22 01:14:08 -03:00
ERR_FAIL_COND_V ( err ! = Z_OK , - 1 ) ;
2014-02-15 21:16:33 -03:00
strm . avail_in = p_src_size ;
strm . avail_out = p_dst_max_size ;
strm . next_in = ( Bytef * ) p_src ;
strm . next_out = p_dst ;
err = inflate ( & strm , Z_FINISH ) ;
2016-08-22 01:14:08 -03:00
int total = strm . total_out ;
2014-02-15 21:16:33 -03:00
inflateEnd ( & strm ) ;
2016-08-22 01:14:08 -03:00
ERR_FAIL_COND_V ( err ! = Z_STREAM_END , - 1 ) ;
return total ;
2014-02-15 21:16:33 -03:00
} break ;
2017-06-08 20:43:56 -05:00
case MODE_ZSTD : {
2025-03-15 15:39:18 +01:00
MutexLock lock ( mutex ) ;
if ( ! current_zstd_d_ctx | | current_zstd_long_distance_matching ! = zstd_long_distance_matching | | current_zstd_window_log_size ! = zstd_window_log_size ) {
if ( current_zstd_d_ctx ) {
ZSTD_freeDCtx ( current_zstd_d_ctx ) ;
}
current_zstd_d_ctx = ZSTD_createDCtx ( ) ;
if ( zstd_long_distance_matching ) {
ZSTD_DCtx_setParameter ( current_zstd_d_ctx , ZSTD_d_windowLogMax , zstd_window_log_size ) ;
}
current_zstd_long_distance_matching = zstd_long_distance_matching ;
current_zstd_window_log_size = zstd_window_log_size ;
2019-04-18 13:17:34 +02:00
}
2025-03-15 15:39:18 +01:00
2025-05-08 14:13:30 -07:00
size_t ret = ZSTD_decompressDCtx ( current_zstd_d_ctx , p_dst , p_dst_max_size , p_src , p_src_size ) ;
return ( int64_t ) ret ;
2017-06-08 20:43:56 -05:00
} break ;
2014-02-15 21:16:33 -03:00
}
2016-08-22 01:14:08 -03:00
ERR_FAIL_V ( - 1 ) ;
2014-02-15 21:16:33 -03:00
}
2017-07-17 21:05:38 -03:00
2020-05-21 01:05:32 -07:00
/**
2021-03-12 19:05:16 +05:30
This will handle both Gzip and Deflate streams . It will automatically allocate the output buffer into the provided p_dst_vect Vector .
This is required for compressed data whose final uncompressed size is unknown , as is the case for HTTP response bodies .
2020-05-21 01:05:32 -07:00
This is much slower however than using Compression : : decompress because it may result in multiple full copies of the output buffer .
*/
2025-05-08 14:13:30 -07:00
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 ) {
2020-05-21 01:05:32 -07:00
uint8_t * dst = nullptr ;
int out_mark = 0 ;
ERR_FAIL_COND_V ( p_src_size < = 0 , Z_DATA_ERROR ) ;
2023-03-29 09:31:25 +03:00
if ( p_mode = = MODE_BROTLI ) {
# ifdef BROTLI_ENABLED
BrotliDecoderResult ret ;
BrotliDecoderState * state = BrotliDecoderCreateInstance ( nullptr , nullptr , nullptr ) ;
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL_V ( state , Z_DATA_ERROR ) ;
2023-03-29 09:31:25 +03:00
// Setup the stream inputs.
const uint8_t * next_in = p_src ;
size_t avail_in = p_src_size ;
uint8_t * next_out = nullptr ;
size_t avail_out = 0 ;
size_t total_out = 0 ;
// Ensure the destination buffer is empty.
p_dst_vect - > clear ( ) ;
// Decompress until stream ends or end of file.
2020-05-21 01:05:32 -07:00
do {
2023-03-29 09:31:25 +03:00
// Add another chunk size to the output buffer.
// This forces a copy of the whole buffer.
p_dst_vect - > resize ( p_dst_vect - > size ( ) + gzip_chunk ) ;
// Get pointer to the actual output buffer.
dst = p_dst_vect - > ptrw ( ) ;
// Set the stream to the new output stream.
// Since it was copied, we need to reset the stream to the new buffer.
next_out = & ( dst [ out_mark ] ) ;
avail_out + = gzip_chunk ;
ret = BrotliDecoderDecompressStream ( state , & avail_in , & next_in , & avail_out , & next_out , & total_out ) ;
if ( ret = = BROTLI_DECODER_RESULT_ERROR ) {
WARN_PRINT ( BrotliDecoderErrorString ( BrotliDecoderGetErrorCode ( state ) ) ) ;
BrotliDecoderDestroyInstance ( state ) ;
p_dst_vect - > clear ( ) ;
return Z_DATA_ERROR ;
2020-05-21 01:05:32 -07:00
}
2023-03-29 09:31:25 +03:00
out_mark + = gzip_chunk - avail_out ;
2020-05-21 01:05:32 -07:00
2023-03-29 09:31:25 +03:00
// Enforce max output size.
if ( p_max_dst_size > - 1 & & total_out > ( uint64_t ) p_max_dst_size ) {
BrotliDecoderDestroyInstance ( state ) ;
p_dst_vect - > clear ( ) ;
return Z_BUF_ERROR ;
}
} while ( ret ! = BROTLI_DECODER_RESULT_SUCCESS ) ;
// If all done successfully, resize the output if it's larger than the actual output.
if ( ( unsigned long ) p_dst_vect - > size ( ) > total_out ) {
p_dst_vect - > resize ( total_out ) ;
2020-05-21 01:05:32 -07:00
}
2023-03-29 09:31:25 +03:00
// Clean up and return.
BrotliDecoderDestroyInstance ( state ) ;
return Z_OK ;
# else
ERR_FAIL_V_MSG ( Z_ERRNO , " Godot was compiled without brotli support. " ) ;
# endif
} else {
// This function only supports GZip and Deflate.
ERR_FAIL_COND_V ( p_mode ! = MODE_DEFLATE & & p_mode ! = MODE_GZIP , Z_ERRNO ) ;
int ret ;
z_stream strm ;
int window_bits = p_mode = = MODE_DEFLATE ? 15 : 15 + 16 ;
// Initialize the stream.
strm . zalloc = Z_NULL ;
strm . zfree = Z_NULL ;
strm . opaque = Z_NULL ;
strm . avail_in = 0 ;
strm . next_in = Z_NULL ;
int err = inflateInit2 ( & strm , window_bits ) ;
ERR_FAIL_COND_V ( err ! = Z_OK , - 1 ) ;
// Setup the stream inputs.
strm . next_in = ( Bytef * ) p_src ;
strm . avail_in = p_src_size ;
// Ensure the destination buffer is empty.
p_dst_vect - > clear ( ) ;
// Decompress until deflate stream ends or end of file.
do {
// Add another chunk size to the output buffer.
// This forces a copy of the whole buffer.
p_dst_vect - > resize ( p_dst_vect - > size ( ) + gzip_chunk ) ;
// Get pointer to the actual output buffer.
dst = p_dst_vect - > ptrw ( ) ;
// Set the stream to the new output stream.
// Since it was copied, we need to reset the stream to the new buffer.
strm . next_out = & ( dst [ out_mark ] ) ;
strm . avail_out = gzip_chunk ;
// Run inflate() on input until output buffer is full and needs to be resized or input runs out.
do {
ret = inflate ( & strm , Z_SYNC_FLUSH ) ;
switch ( ret ) {
case Z_NEED_DICT :
ret = Z_DATA_ERROR ;
[[fallthrough]] ;
case Z_DATA_ERROR :
case Z_MEM_ERROR :
case Z_STREAM_ERROR :
case Z_BUF_ERROR :
if ( strm . msg ) {
WARN_PRINT ( strm . msg ) ;
}
( void ) inflateEnd ( & strm ) ;
p_dst_vect - > clear ( ) ;
return ret ;
}
} while ( strm . avail_out > 0 & & strm . avail_in > 0 ) ;
out_mark + = gzip_chunk ;
// Enforce max output size.
if ( p_max_dst_size > - 1 & & strm . total_out > ( uint64_t ) p_max_dst_size ) {
( void ) inflateEnd ( & strm ) ;
p_dst_vect - > clear ( ) ;
return Z_BUF_ERROR ;
}
} while ( ret ! = Z_STREAM_END ) ;
// If all done successfully, resize the output if it's larger than the actual output.
if ( ( unsigned long ) p_dst_vect - > size ( ) > strm . total_out ) {
p_dst_vect - > resize ( strm . total_out ) ;
}
2020-05-21 01:05:32 -07:00
2023-03-29 09:31:25 +03:00
// Clean up and return.
( void ) inflateEnd ( & strm ) ;
return Z_OK ;
}
2020-05-21 01:05:32 -07:00
}