libclod
C library for interacting with NBTs, region files, LOD data and other things.
Loading...
Searching...
No Matches
decompress.c
1#include "compression_config.h"
2#include <clod/compression.h>
3#include <stdlib.h>
4#include <string.h>
5
6#if HAVE_LIBDEFLATE
7#include <libdeflate.h>
8#endif
9
10#if HAVE_LIBLZ4
11#include <lz4frame.h>
12#endif
13
14#if HAVE_LIBLZMA
15#include <lzma.h>
16#endif
17
18#if HAVE_LIBZSTD
19#include <zstd.h>
20#endif
21
22#if HAVE_LIBBZ2
23#include <bzlib.h>
24#endif
25
27 void *(*malloc_func)(size_t);
28 void (*free_func)(void *);
29
30#if HAVE_LIBDEFLATE
31 struct libdeflate_decompressor *libdeflate_decompressor;
32#endif
33
34#if HAVE_LIBLZ4
35 LZ4F_dctx *lz4_ctx;
36#endif
37
38#if HAVE_LIBZSTD
39 ZSTD_DCtx *zstd_dctx;
40#endif
41};
42
44 struct clod_decompressor *ctx = malloc(sizeof(struct clod_decompressor));
45 memset(ctx, 0,sizeof(*ctx));
46 // LZ4 and ZSTD are missing custom memory allocation methods.
47 ctx->malloc_func = malloc;
48 ctx->free_func = free;
49 return ctx;
50}
51
53#if HAVE_LIBDEFLATE
54 if (ctx->libdeflate_decompressor)
55 libdeflate_free_decompressor(ctx->libdeflate_decompressor);
56#endif
57
58#if HAVE_LIBLZ4
59 if (ctx->lz4_ctx)
60 LZ4F_freeDecompressionContext(ctx->lz4_ctx);
61#endif
62
63#if HAVE_LIBZSTD
64 if (ctx->zstd_dctx)
65 ZSTD_freeDCtx(ctx->zstd_dctx);
66#endif
67
68 free(ctx);
69}
70
71#if HAVE_LIBLZMA
72void *decompressor_lzma_malloc(void *user, size_t n, size_t size) {
73 auto const ctx = (struct clod_decompressor *)user;
74 return ctx->malloc_func(n * size);
75}
76void decompressor_lzma_free(void *user, void *address) {
77 auto const ctx = (struct clod_decompressor *)user;
78 ctx->free_func(address);
79}
80#endif
81
82#if HAVE_LIBBZ2
83void *decompressor_bz2_malloc(void *user, int n, int size) {
84 auto const ctx = (struct clod_decompressor *)user;
85 return ctx->malloc_func((size_t)n * (size_t)size);
86}
87void decompressor_bz2_free(void *user, void *address) {
88 auto const ctx = (struct clod_decompressor *)user;
89 ctx->free_func(address);
90}
91#endif
92
93
96 void *const dst, const size_t dst_size,
97 const void *const src, const size_t src_size,
98 size_t *const actual_size,
99 const enum clod_compression_method method
100) {
101 switch (method) {
102 case CLOD_UNCOMPRESSED: {
103 if (dst_size < src_size) {
104 if (actual_size) *actual_size = src_size;
106 }
107 if (!actual_size && dst_size != src_size) return CLOD_COMPRESSION_SHORT_OUTPUT;
108 memcpy(dst, src, src_size);
109 if (actual_size) *actual_size = src_size;
111 }
112 case CLOD_GZIP: {
113 #if HAVE_LIBDEFLATE
114 if (!ctx->libdeflate_decompressor) {
115 struct libdeflate_options opts = {0};
116 opts.sizeof_options = sizeof(opts);
117 opts.malloc_func = ctx->malloc_func;
118 opts.free_func = ctx->free_func;
119
120 ctx->libdeflate_decompressor = libdeflate_alloc_decompressor_ex(&opts);
121 if (!ctx->libdeflate_decompressor) {
123 }
124 }
125
126 const enum libdeflate_result res = libdeflate_gzip_decompress(ctx->libdeflate_decompressor,
127 src, src_size,
128 dst, dst_size,
129 actual_size
130 );
131
132 if (res == LIBDEFLATE_INSUFFICIENT_SPACE) {
133 if (actual_size) *actual_size = 0;
135 }
136
137 switch (res) {
138 case LIBDEFLATE_SUCCESS: return CLOD_COMPRESSION_SUCCESS;
139 case LIBDEFLATE_BAD_DATA: return CLOD_COMPRESSION_MALFORMED;
140 case LIBDEFLATE_SHORT_OUTPUT: return CLOD_COMPRESSION_SHORT_OUTPUT;
141 default: return CLOD_COMPRESSION_UNSUPPORTED;
142 }
143 #else
145 #endif
146 }
147 case CLOD_ZLIB: {
148 #if HAVE_LIBDEFLATE
149 if (!ctx->libdeflate_decompressor) {
150 struct libdeflate_options opts = {0};
151 opts.sizeof_options = sizeof(opts);
152 opts.malloc_func = ctx->malloc_func;
153 opts.free_func = ctx->free_func;
154
155 ctx->libdeflate_decompressor = libdeflate_alloc_decompressor_ex(&opts);
156 if (!ctx->libdeflate_decompressor) {
158 }
159 }
160
161 const enum libdeflate_result res = libdeflate_zlib_decompress(ctx->libdeflate_decompressor,
162 src, src_size,
163 dst, dst_size,
164 actual_size
165 );
166
167 if (res == LIBDEFLATE_INSUFFICIENT_SPACE) {
168 if (actual_size) *actual_size = 0;
170 }
171
172 switch (res) {
173 case LIBDEFLATE_SUCCESS: return CLOD_COMPRESSION_SUCCESS;
174 case LIBDEFLATE_BAD_DATA: return CLOD_COMPRESSION_MALFORMED;
175 case LIBDEFLATE_SHORT_OUTPUT: return CLOD_COMPRESSION_SHORT_OUTPUT;
176 default: return CLOD_COMPRESSION_UNSUPPORTED;
177 }
178 #else
180 #endif
181 }
182 case CLOD_DEFLATE: {
183 #if HAVE_LIBDEFLATE
184 if (!ctx->libdeflate_decompressor) {
185 struct libdeflate_options opts = {0};
186 opts.sizeof_options = sizeof(opts);
187 opts.malloc_func = ctx->malloc_func;
188 opts.free_func = ctx->free_func;
189
190 ctx->libdeflate_decompressor = libdeflate_alloc_decompressor_ex(&opts);
191 if (!ctx->libdeflate_decompressor) {
193 }
194 }
195
196 const enum libdeflate_result res = libdeflate_deflate_decompress(ctx->libdeflate_decompressor,
197 src, src_size,
198 dst, dst_size,
199 actual_size
200 );
201
202 if (res == LIBDEFLATE_INSUFFICIENT_SPACE) {
203 if (actual_size) *actual_size = 0;
205 }
206
207 switch (res) {
208 case LIBDEFLATE_SUCCESS: return CLOD_COMPRESSION_SUCCESS;
209 case LIBDEFLATE_BAD_DATA: return CLOD_COMPRESSION_MALFORMED;
210 case LIBDEFLATE_SHORT_OUTPUT: return CLOD_COMPRESSION_SHORT_OUTPUT;
211 default: return CLOD_COMPRESSION_UNSUPPORTED;
212 }
213 #else
215 #endif
216 }
217 case CLOD_LZ4F: {
218 #if HAVE_LIBLZ4
219 if (src_size < LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH) {
221 }
222
223 const size_t header_size = LZ4F_headerSize(src, src_size);
224 if (LZ4F_isError(header_size) || header_size > src_size) {
226 }
227
228 if (!ctx->lz4_ctx) {
229 const LZ4F_errorCode_t err = LZ4F_createDecompressionContext(&ctx->lz4_ctx, LZ4F_VERSION);
230 if (LZ4F_isError(err) || !ctx->lz4_ctx) {
232 }
233 }
234
235 size_t src_offset = 0, dst_offset = 0;
236
237 LZ4F_frameInfo_t info;
238 src_offset = src_size;
239 size_t res = LZ4F_getFrameInfo(ctx->lz4_ctx, &info, src, &src_offset);
240
241 if (LZ4F_isError(res)) {
242 LZ4F_resetDecompressionContext(ctx->lz4_ctx);
244 }
245
246 if (info.contentSize > dst_size) {
247 LZ4F_resetDecompressionContext(ctx->lz4_ctx);
248 if (actual_size) *actual_size = info.contentSize;
250 }
251
252 if (!actual_size && info.contentSize > 0 && info.contentSize < dst_size) {
253 LZ4F_resetDecompressionContext(ctx->lz4_ctx);
255 }
256
257 while (true) {
258 size_t dst_chunk_size = dst_size - dst_offset;
259 size_t src_chunk_size = src_size - src_offset;
260
261 res = LZ4F_decompress(ctx->lz4_ctx,
262 (char*)dst + dst_offset, &dst_chunk_size,
263 (const char*)src + src_offset, &src_chunk_size,
264 nullptr);
265
266 if (LZ4F_isError(res)) {
267 LZ4F_resetDecompressionContext(ctx->lz4_ctx);
269 }
270
271 dst_offset += dst_chunk_size;
272 src_offset += src_chunk_size;
273
274 if (res == 0) {
275 LZ4F_resetDecompressionContext(ctx->lz4_ctx);
276 if (!actual_size && dst_offset < dst_size) return CLOD_COMPRESSION_SHORT_OUTPUT;
277 if (actual_size) *actual_size = dst_offset;
279 }
280
281 if (src_offset >= src_size) {
282 LZ4F_resetDecompressionContext(ctx->lz4_ctx);
284 }
285
286 if (dst_offset >= dst_size) {
287 LZ4F_resetDecompressionContext(ctx->lz4_ctx);
288 if (actual_size) *actual_size = info.contentSize > 0 ? info.contentSize : 0;
290 }
291 }
292
293 #else
295 #endif
296
297 }
298 case CLOD_XZ: {
299 #if HAVE_LIBLZMA
300 const lzma_allocator allocator = {
301 .alloc = decompressor_lzma_malloc,
302 .free = decompressor_lzma_free,
303 .opaque = ctx,
304 };
305
306 lzma_stream stream = LZMA_STREAM_INIT;
307 stream.allocator = &allocator;
308
309 lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, 0);
310 if (ret != LZMA_OK) {
311 return ret == LZMA_MEM_ERROR ? CLOD_COMPRESSION_ALLOC_FAILED : CLOD_COMPRESSION_UNSUPPORTED;
312 }
313
314 stream.next_in = (uint8_t *)src;
315 stream.avail_in = src_size;
316 stream.next_out = (uint8_t *)dst;
317 stream.avail_out = dst_size;
318
319 do {
320 ret = lzma_code(&stream, LZMA_FINISH);
321 } while (ret == LZMA_OK);
322
323 lzma_end(&stream);
324
325 switch (ret) {
326 case LZMA_STREAM_END:
327 if (!actual_size && stream.avail_out != 0) return CLOD_COMPRESSION_SHORT_OUTPUT;
328 if (actual_size) *actual_size = stream.total_out;
330 case LZMA_BUF_ERROR:
331 if (stream.avail_out == 0) {
332 if (actual_size) *actual_size = 0;
334 }
336 case LZMA_MEM_ERROR: case LZMA_MEMLIMIT_ERROR:
338 case LZMA_FORMAT_ERROR: case LZMA_OPTIONS_ERROR: case LZMA_DATA_ERROR:
340 default:
342 }
343 #else
345 #endif
346 }
347 case CLOD_ZSTD: {
348 #if HAVE_LIBZSTD
349 if (!ctx->zstd_dctx) {
350 ctx->zstd_dctx = ZSTD_createDCtx();
351 if (!ctx->zstd_dctx) return CLOD_COMPRESSION_ALLOC_FAILED;
352 }
353
354 ZSTD_inBuffer in = {src, src_size, 0};
355 ZSTD_outBuffer out = {dst, dst_size, 0};
356
357 size_t ret = 1;
358 while (in.pos < in.size && ret != 0) {
359 ret = ZSTD_decompressStream(ctx->zstd_dctx, &out, &in);
360 if (ZSTD_isError(ret)) {
361 ZSTD_DCtx_reset(ctx->zstd_dctx, ZSTD_reset_session_only);
362 if (ZSTD_getErrorCode(ret) == ZSTD_error_noForwardProgress_destFull) {
363 if (actual_size) {
364 auto const frame_size = ZSTD_getFrameContentSize(src, src_size);
365 if (frame_size == ZSTD_CONTENTSIZE_UNKNOWN || frame_size == ZSTD_CONTENTSIZE_ERROR)
366 *actual_size = 0;
367 else
368 *actual_size = frame_size;
369 }
371 }
373 }
374 }
375
376 if (ret == 0) {
377 if (!actual_size && out.pos != out.size) return CLOD_COMPRESSION_SHORT_OUTPUT;
378 if (actual_size) *actual_size = out.pos;
380 }
381
383 #else
385 #endif
386 }
387 case CLOD_BZIP2: {
388 #if HAVE_LIBBZ2
389 // TODO: support sizes > UINT32_MAX
390
391 bz_stream stream = {0};
392 stream.next_in = (char *)src;
393 stream.avail_in = (unsigned)src_size;
394 stream.next_out = (char *)dst;
395 stream.avail_out = (unsigned)dst_size;
396 stream.bzalloc = decompressor_bz2_malloc;
397 stream.bzfree = decompressor_bz2_free;
398 stream.opaque = ctx;
399
400 char dummy;
401 if (dst_size == 0) {
402 stream.next_out = &dummy;
403 stream.avail_out = 1;
404 }
405
406 int res = BZ2_bzDecompressInit(&stream, 0, 0);
407 if (res == BZ_MEM_ERROR) return CLOD_COMPRESSION_ALLOC_FAILED;
408 if (res != BZ_OK) return CLOD_COMPRESSION_UNSUPPORTED;
409
410 do {
411 if (stream.avail_out == 0) {
412 BZ2_bzDecompressEnd(&stream);
413 if (actual_size) *actual_size = 0;
415 }
416 res = BZ2_bzDecompress(&stream);
417 } while (res == BZ_OK);
418
419 BZ2_bzDecompressEnd(&stream);
420
421 switch (res) {
422 case BZ_STREAM_END:
423 if (dst_size == 0) {
424 if (stream.avail_out != 1) return CLOD_COMPRESSION_SHORT_BUFFER;
425 } else {
426 if (!actual_size && stream.avail_out != 0) return CLOD_COMPRESSION_SHORT_OUTPUT;
427 }
428 if (actual_size) *actual_size = (size_t)stream.total_out_lo32 + ((size_t)stream.total_out_hi32 << 32);
430 case BZ_MEM_ERROR: return CLOD_COMPRESSION_ALLOC_FAILED;
431 case BZ_DATA_ERROR_MAGIC:
432 case BZ_DATA_ERROR: return CLOD_COMPRESSION_MALFORMED;
433 default: return CLOD_COMPRESSION_UNSUPPORTED;
434 }
435
436 #else
438 #endif
439 }
440 default: {
442 }
443 }
444}
clod_compression_result
void clod_decompressor_free(struct clod_decompressor *ctx)
Definition decompress.c:52
struct clod_decompressor * clod_decompressor_init()
Definition decompress.c:43
clod_compression_method
Definition compression.h:32
enum clod_compression_result clod_decompress(struct clod_decompressor *ctx, void *const dst, const size_t dst_size, const void *const src, const size_t src_size, size_t *const actual_size, const enum clod_compression_method method)
Definition decompress.c:95
@ CLOD_COMPRESSION_ALLOC_FAILED
@ CLOD_COMPRESSION_SHORT_OUTPUT
@ CLOD_COMPRESSION_MALFORMED
@ CLOD_COMPRESSION_UNSUPPORTED
@ CLOD_COMPRESSION_SUCCESS
@ CLOD_COMPRESSION_SHORT_BUFFER
@ CLOD_GZIP
Definition compression.h:38
@ CLOD_UNCOMPRESSED
Definition compression.h:34
@ CLOD_BZIP2
Definition compression.h:63
@ CLOD_ZSTD
Definition compression.h:59
@ CLOD_LZ4F
Definition compression.h:50
@ CLOD_XZ
Definition compression.h:54
@ CLOD_DEFLATE
Definition compression.h:46
@ CLOD_ZLIB
Definition compression.h:42