From fc8328d5f15edbfcb36aba6b07b5e4cc5e086195 Mon Sep 17 00:00:00 2001 From: LuoZhihao Date: Sun, 25 May 2025 23:42:23 +0800 Subject: [PATCH] C#: Expose byte array compress and decompress --- .../glue/GodotSharp/GodotSharp/Core/GD.cs | 48 +++++++++++++++++++ .../Core/NativeInterop/NativeFuncs.cs | 6 +++ modules/mono/glue/runtime_interop.cpp | 44 +++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 33ebb8171e8..b6073c5cfc9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -682,5 +682,53 @@ namespace Godot { return Marshaling.ConvertManagedTypeToVariantType(type, out bool _); } + + /// + /// Returns a new byte array with the data compressed. + /// + /// The byte array to compress. + /// The compression mode, one of + /// The compressed byte array. + public static byte[] Compress(this byte[] instance, FileAccess.CompressionMode compressionMode = 0) + { + using godot_packed_byte_array src = Marshaling.ConvertSystemArrayToNativePackedByteArray(instance); + NativeFuncs.godotsharp_packed_byte_array_compress(src, (int)compressionMode, out var ret); + using (ret) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(ret); + } + + /// + /// Returns a new byte array with the data decompressed. + /// Note: Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header. + /// + /// The byte array to decompress. + /// The size of the uncompressed data. + /// The compression mode, one of + /// The decompressed byte array. + public static byte[] Decompress(this byte[] instance, long bufferSize, FileAccess.CompressionMode compressionMode = 0) + { + using godot_packed_byte_array src = Marshaling.ConvertSystemArrayToNativePackedByteArray(instance); + NativeFuncs.godotsharp_packed_byte_array_decompress(src, bufferSize, (int)compressionMode, out var ret); + using (ret) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(ret); + } + + /// + /// Returns a new byte array with the data decompressed. This method only accepts brotli, gzip, and deflate compression modes. + /// This method is potentially slower than , as it may have to re-allocate its output buffer multiple times while decompressing, whereas 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. + /// Note: Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header. + /// + /// The byte array to decompress. + /// The maximum size this function is allowed to allocate in bytes. + /// The compression mode, one of + /// The decompressed byte array. + public static byte[] DecompressDynamic(this byte[] instance, long maxOutputSize, FileAccess.CompressionMode compressionMode = 0) + { + using godot_packed_byte_array src = Marshaling.ConvertSystemArrayToNativePackedByteArray(instance); + NativeFuncs.godotsharp_packed_byte_array_decompress_dynamic(src, maxOutputSize, (int)compressionMode, out var ret); + using (ret) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(ret); + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index d42d101a11a..25b343c8ef9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -433,6 +433,12 @@ namespace Godot.NativeInterop public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str); + public static partial void godotsharp_packed_byte_array_compress(scoped in godot_packed_byte_array p_src, int p_mode, out godot_packed_byte_array r_dst); + + public static partial void godotsharp_packed_byte_array_decompress(scoped in godot_packed_byte_array p_src, long p_buffer_size, int p_mode, out godot_packed_byte_array r_dst); + + public static partial void godotsharp_packed_byte_array_decompress_dynamic(scoped in godot_packed_byte_array p_src, long p_buffer_size, int p_mode, out godot_packed_byte_array r_dst); + // Dictionary public static partial godot_bool godotsharp_dictionary_try_get_value(scoped ref godot_dictionary p_self, diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index abe174306eb..d4d762f08ce 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -1168,6 +1168,47 @@ void godotsharp_array_to_string(const Array *p_self, String *r_str) { *r_str = Variant(*p_self).operator String(); } +void godotsharp_packed_byte_array_compress(const PackedByteArray *p_src, int p_mode, PackedByteArray *r_dst) { + if (p_src->size() > 0) { + Compression::Mode mode = (Compression::Mode)(p_mode); + r_dst->resize(Compression::get_max_compressed_buffer_size(p_src->size(), mode)); + int result = Compression::compress(r_dst->ptrw(), p_src->ptr(), p_src->size(), mode); + + result = result >= 0 ? result : 0; + r_dst->resize(result); + } +} + +void godotsharp_packed_byte_array_decompress(const PackedByteArray *p_src, int64_t p_buffer_size, int p_mode, PackedByteArray *r_dst) { + int64_t buffer_size = p_buffer_size; + Compression::Mode mode = (Compression::Mode)(p_mode); + + if (buffer_size <= 0) { + ERR_FAIL_MSG("Decompression buffer size must be greater than zero."); + } + if (p_src->size() == 0) { + ERR_FAIL_MSG("Compressed buffer size must be greater than zero."); + } + + r_dst->resize(buffer_size); + int result = Compression::decompress(r_dst->ptrw(), buffer_size, p_src->ptr(), p_src->size(), mode); + + result = result >= 0 ? result : 0; + r_dst->resize(result); +} + +void godotsharp_packed_byte_array_decompress_dynamic(const PackedByteArray *p_src, int64_t p_max_output_size, int p_mode, PackedByteArray *r_dst) { + int64_t max_output_size = p_max_output_size; + Compression::Mode mode = (Compression::Mode)(p_mode); + + int result = Compression::decompress_dynamic(r_dst, max_output_size, p_src->ptr(), p_src->size(), mode); + + if (result != OK) { + r_dst->clear(); + ERR_FAIL_MSG("Decompression failed."); + } +} + // Dictionary bool godotsharp_dictionary_try_get_value(const Dictionary *p_self, const Variant *p_key, Variant *r_value) { @@ -1683,6 +1724,9 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_array_slice, (void *)godotsharp_array_sort, (void *)godotsharp_array_to_string, + (void *)godotsharp_packed_byte_array_compress, + (void *)godotsharp_packed_byte_array_decompress, + (void *)godotsharp_packed_byte_array_decompress_dynamic, (void *)godotsharp_dictionary_try_get_value, (void *)godotsharp_dictionary_set_value, (void *)godotsharp_dictionary_keys,