Expose brotli decompression to the scripting API.
This commit is contained in:
parent
c29866dbc0
commit
0e4bd964cc
|
@ -181,6 +181,7 @@ opts.Add(BoolVariable("production", "Set defaults to build Godot for use in prod
|
|||
opts.Add(BoolVariable("deprecated", "Enable compatibility code for deprecated and removed features", True))
|
||||
opts.Add(EnumVariable("precision", "Set the floating-point precision level", "single", ("single", "double")))
|
||||
opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", True))
|
||||
opts.Add(BoolVariable("brotli", "Enable Brotli for decompresson and WOFF2 fonts support", True))
|
||||
opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver", False))
|
||||
opts.Add(BoolVariable("vulkan", "Enable the vulkan rendering driver", True))
|
||||
opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True))
|
||||
|
@ -855,6 +856,8 @@ if selected_platform in platform_list:
|
|||
env.Append(CPPDEFINES=["ADVANCED_GUI_DISABLED"])
|
||||
if env["minizip"]:
|
||||
env.Append(CPPDEFINES=["MINIZIP_ENABLED"])
|
||||
if env["brotli"]:
|
||||
env.Append(CPPDEFINES=["BROTLI_ENABLED"])
|
||||
|
||||
if not env["verbose"]:
|
||||
methods.no_verbose(sys, env)
|
||||
|
|
25
core/SCsub
25
core/SCsub
|
@ -64,6 +64,31 @@ thirdparty_misc_sources = [
|
|||
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)
|
||||
|
||||
# Brotli
|
||||
if env["brotli"]:
|
||||
thirdparty_brotli_dir = "#thirdparty/brotli/"
|
||||
thirdparty_brotli_sources = [
|
||||
"common/constants.c",
|
||||
"common/context.c",
|
||||
"common/dictionary.c",
|
||||
"common/platform.c",
|
||||
"common/shared_dictionary.c",
|
||||
"common/transform.c",
|
||||
"dec/bit_reader.c",
|
||||
"dec/decode.c",
|
||||
"dec/huffman.c",
|
||||
"dec/state.c",
|
||||
]
|
||||
thirdparty_brotli_sources = [thirdparty_brotli_dir + file for file in thirdparty_brotli_sources]
|
||||
|
||||
env_thirdparty.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
|
||||
env.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
|
||||
|
||||
if env.get("use_ubsan") or env.get("use_asan") or env.get("use_tsan") or env.get("use_lsan") or env.get("use_msan"):
|
||||
env_thirdparty.Append(CPPDEFINES=["BROTLI_BUILD_PORTABLE"])
|
||||
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources)
|
||||
|
||||
# Zlib library, can be unbundled
|
||||
if env["builtin_zlib"]:
|
||||
thirdparty_zlib_dir = "#thirdparty/zlib/"
|
||||
|
|
|
@ -35,11 +35,18 @@
|
|||
|
||||
#include "thirdparty/misc/fastlz.h"
|
||||
|
||||
#ifdef BROTLI_ENABLED
|
||||
#include "thirdparty/brotli/include/brotli/decode.h"
|
||||
#endif
|
||||
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
if (p_src_size < 16) {
|
||||
uint8_t src[16];
|
||||
|
@ -95,6 +102,9 @@ int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size,
|
|||
|
||||
int Compression::get_max_compressed_buffer_size(int p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
int ss = p_src_size + p_src_size * 6 / 100;
|
||||
if (ss < 66) {
|
||||
|
@ -129,6 +139,16 @@ int Compression::get_max_compressed_buffer_size(int p_src_size, Mode p_mode) {
|
|||
|
||||
int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
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;
|
||||
case MODE_FASTLZ: {
|
||||
int ret_size = 0;
|
||||
|
||||
|
@ -186,87 +206,147 @@ int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p
|
|||
This is much slower however than using Compression::decompress because it may result in multiple full copies of the output buffer.
|
||||
*/
|
||||
int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
|
||||
int ret;
|
||||
uint8_t *dst = nullptr;
|
||||
int out_mark = 0;
|
||||
z_stream strm;
|
||||
|
||||
ERR_FAIL_COND_V(p_src_size <= 0, Z_DATA_ERROR);
|
||||
|
||||
// This function only supports GZip and Deflate
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
|
||||
if (p_mode == MODE_BROTLI) {
|
||||
#ifdef BROTLI_ENABLED
|
||||
BrotliDecoderResult ret;
|
||||
BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
|
||||
ERR_FAIL_COND_V(state == nullptr, Z_DATA_ERROR);
|
||||
|
||||
// Initialize the stream
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
// 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;
|
||||
|
||||
int err = inflateInit2(&strm, window_bits);
|
||||
ERR_FAIL_COND_V(err != Z_OK, -1);
|
||||
// Ensure the destination buffer is empty.
|
||||
p_dst_vect->clear();
|
||||
|
||||
// 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
|
||||
// Decompress until stream ends or end of file.
|
||||
do {
|
||||
ret = inflate(&strm, Z_SYNC_FLUSH);
|
||||
// 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();
|
||||
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
} while (strm.avail_out > 0 && strm.avail_in > 0);
|
||||
|
||||
out_mark += gzip_chunk;
|
||||
out_mark += gzip_chunk - avail_out;
|
||||
|
||||
// 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;
|
||||
// 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);
|
||||
}
|
||||
} 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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Clean up and return.
|
||||
(void)inflateEnd(&strm);
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
// clean up and return
|
||||
(void)inflateEnd(&strm);
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
int Compression::zlib_level = Z_DEFAULT_COMPRESSION;
|
||||
|
|
|
@ -47,7 +47,8 @@ public:
|
|||
MODE_FASTLZ,
|
||||
MODE_DEFLATE,
|
||||
MODE_ZSTD,
|
||||
MODE_GZIP
|
||||
MODE_GZIP,
|
||||
MODE_BROTLI
|
||||
};
|
||||
|
||||
static int compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode = MODE_ZSTD);
|
||||
|
|
|
@ -871,4 +871,5 @@ void FileAccess::_bind_methods() {
|
|||
BIND_ENUM_CONSTANT(COMPRESSION_DEFLATE);
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_ZSTD);
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_GZIP);
|
||||
BIND_ENUM_CONSTANT(COMPRESSION_BROTLI);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ public:
|
|||
COMPRESSION_FASTLZ = Compression::MODE_FASTLZ,
|
||||
COMPRESSION_DEFLATE = Compression::MODE_DEFLATE,
|
||||
COMPRESSION_ZSTD = Compression::MODE_ZSTD,
|
||||
COMPRESSION_GZIP = Compression::MODE_GZIP
|
||||
COMPRESSION_GZIP = Compression::MODE_GZIP,
|
||||
COMPRESSION_BROTLI = Compression::MODE_BROTLI,
|
||||
};
|
||||
|
||||
typedef void (*FileCloseFailNotify)(const String &);
|
||||
|
|
|
@ -492,5 +492,8 @@
|
|||
<constant name="COMPRESSION_GZIP" value="3" enum="CompressionMode">
|
||||
Uses the [url=https://www.gzip.org/]gzip[/url] compression method.
|
||||
</constant>
|
||||
<constant name="COMPRESSION_BROTLI" value="4" enum="CompressionMode">
|
||||
Uses the [url=https://github.com/google/brotli]brotli[/url] compression method (only decompression is supported).
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
|
|
@ -181,7 +181,7 @@
|
|||
<param index="0" name="max_output_size" type="int" />
|
||||
<param index="1" name="compression_mode" type="int" default="0" />
|
||||
<description>
|
||||
Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts gzip and deflate compression modes.[/b]
|
||||
Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts brotli, gzip, and deflate compression modes.[/b]
|
||||
This method is potentially slower than [code]decompress[/code], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [code]decompress[/code] knows it's output buffer size from the beginning.
|
||||
GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [param max_output_size]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned.
|
||||
</description>
|
||||
|
|
|
@ -59,25 +59,7 @@ if env["builtin_freetype"]:
|
|||
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||
|
||||
if env["brotli"]:
|
||||
thirdparty_brotli_dir = "#thirdparty/brotli/"
|
||||
thirdparty_brotli_sources = [
|
||||
"common/constants.c",
|
||||
"common/context.c",
|
||||
"common/dictionary.c",
|
||||
"common/platform.c",
|
||||
"common/shared_dictionary.c",
|
||||
"common/transform.c",
|
||||
"dec/bit_reader.c",
|
||||
"dec/decode.c",
|
||||
"dec/huffman.c",
|
||||
"dec/state.c",
|
||||
]
|
||||
thirdparty_sources += [thirdparty_brotli_dir + file for file in thirdparty_brotli_sources]
|
||||
env_freetype.Append(CPPDEFINES=["FT_CONFIG_OPTION_USE_BROTLI"])
|
||||
env_freetype.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
|
||||
|
||||
if env.get("use_ubsan") or env.get("use_asan") or env.get("use_tsan") or env.get("use_lsan") or env.get("use_msan"):
|
||||
env_freetype.Append(CPPDEFINES=["BROTLI_BUILD_PORTABLE"])
|
||||
|
||||
if env["platform"] == "uwp":
|
||||
# Include header for UWP to fix build issues
|
||||
|
|
|
@ -2,13 +2,5 @@ def can_build(env, platform):
|
|||
return True
|
||||
|
||||
|
||||
def get_opts(platform):
|
||||
from SCons.Variables import BoolVariable
|
||||
|
||||
return [
|
||||
BoolVariable("brotli", "Enable Brotli decompressor for WOFF2 fonts support", True),
|
||||
]
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
|
|
Loading…
Reference in New Issue