/src/minizip-ng/mz_strm_ppmd.c
Line | Count | Source |
1 | | /* mz_strm_ppmd.c -- Stream for PPMd compress/decompress |
2 | | part of the minizip-ng project |
3 | | |
4 | | Copyright (C) Nathan Moinvaziri |
5 | | https://github.com/zlib-ng/minizip-ng |
6 | | |
7 | | This program is distributed under the terms of the same license as zlib. |
8 | | See the accompanying LICENSE file for the full text of the license. |
9 | | */ |
10 | | |
11 | | #include <assert.h> |
12 | | |
13 | | #include "mz.h" |
14 | | #include "mz_strm.h" |
15 | | #include "mz_strm_ppmd.h" |
16 | | |
17 | | #include "C/Ppmd8.h" |
18 | | #include "C/7zTypes.h" |
19 | | |
20 | | /***************************************************************************/ |
21 | | |
22 | | static mz_stream_vtbl mz_stream_ppmd_vtbl = { |
23 | | mz_stream_ppmd_open, mz_stream_ppmd_is_open, mz_stream_ppmd_read, mz_stream_ppmd_write, |
24 | | mz_stream_ppmd_tell, mz_stream_ppmd_seek, mz_stream_ppmd_close, mz_stream_ppmd_error, |
25 | | mz_stream_ppmd_create, mz_stream_ppmd_delete, mz_stream_ppmd_get_prop_int64, mz_stream_ppmd_set_prop_int64}; |
26 | | |
27 | | /***************************************************************************/ |
28 | | |
29 | 2.72k | #define PPMD_PRESET_DEFAULT 9 // Should match default in 7-Zip |
30 | | |
31 | | // Return values from Ppmd8_DecodeSymbol |
32 | 0 | #define PPMD_RESULT_EOF (-1) |
33 | 0 | #define PPMD_RESULT_ERROR (-2) |
34 | | |
35 | | /***************************************************************************/ |
36 | | |
37 | | typedef struct mz_in_buffer_s { |
38 | | const void *src; |
39 | | size_t size; |
40 | | size_t pos; |
41 | | } mz_in_buffer; |
42 | | |
43 | | typedef struct mz_out_buffer_s { |
44 | | void *dst; |
45 | | size_t size; |
46 | | size_t pos; |
47 | | } mz_out_buffer; |
48 | | |
49 | | typedef struct mz_ppmd_info_s { |
50 | | /* hold CPpmd8 or CPpmd7 struct pointer */ |
51 | | void *cPpmd; |
52 | | void *rc; |
53 | | mz_in_buffer *in; |
54 | | mz_out_buffer *out; |
55 | | int max_length; |
56 | | int result; |
57 | | void *t; |
58 | | } mz_ppmd_info; |
59 | | |
60 | | typedef struct { |
61 | | /* Inherits from IByteIn */ |
62 | | Byte (*read)(void *p); |
63 | | mz_in_buffer *in_buffer; |
64 | | void *t; |
65 | | } mz_buffer_reader; |
66 | | |
67 | | typedef struct { |
68 | | /* Inherits from IByteOut */ |
69 | | void (*write)(void *p, Byte b); |
70 | | mz_out_buffer *out_buffer; |
71 | | mz_ppmd_info *t; |
72 | | } mz_buffer_writer; |
73 | | |
74 | | typedef struct mz_stream_ppmd_s { |
75 | | mz_stream stream; |
76 | | CPpmd8 ppmd8; |
77 | | uint8_t buffer[INT16_MAX]; |
78 | | int64_t total_in; |
79 | | int64_t total_out; |
80 | | int64_t max_total_in; |
81 | | int32_t mode; |
82 | | int32_t error; |
83 | | int8_t initialized; |
84 | | ISzAlloc allocator; |
85 | | |
86 | | // Write specific |
87 | | mz_buffer_writer writer; |
88 | | mz_out_buffer out; |
89 | | int32_t preset; // PPMD uses the term level for this |
90 | | |
91 | | // Read Specific |
92 | | mz_buffer_reader reader; |
93 | | mz_in_buffer in; |
94 | | int8_t end_stream; |
95 | | } mz_stream_ppmd; |
96 | | |
97 | | /***************************************************************************/ |
98 | | |
99 | | /* malloc wrapper for PPMD library */ |
100 | 1.70k | static void *mz_ppmd_alloc_func(const ISzAlloc *p, size_t size) { |
101 | 1.70k | MZ_UNUSED(p); |
102 | 1.70k | return malloc(size); |
103 | 1.70k | } |
104 | | |
105 | | /* free wrapper for PPMD library */ |
106 | 3.40k | static void mz_ppmd_free_func(const ISzAlloc *p, void *address) { |
107 | 3.40k | MZ_UNUSED(p); |
108 | 3.40k | free(address); |
109 | 3.40k | } |
110 | | |
111 | | #ifndef MZ_ZIP_NO_COMPRESSION |
112 | | |
113 | 88.4M | static void writer(void *p, Byte b) { |
114 | 88.4M | mz_buffer_writer *buffer_writer = (mz_buffer_writer *)p; |
115 | 88.4M | if (buffer_writer->out_buffer->size == buffer_writer->out_buffer->pos) { |
116 | 251 | return; |
117 | 251 | } |
118 | 88.4M | *((Byte *)buffer_writer->out_buffer->dst + buffer_writer->out_buffer->pos++) = b; |
119 | 88.4M | } |
120 | | |
121 | 7.60k | static int32_t mz_stream_ppmd_flush(void *stream) { |
122 | 7.60k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
123 | | |
124 | 7.60k | if (ppmd->out.pos) { |
125 | 7.57k | if (mz_stream_write(ppmd->stream.base, ppmd->out.dst, ppmd->out.pos) != ppmd->out.pos) |
126 | 0 | return MZ_WRITE_ERROR; |
127 | 7.57k | ppmd->total_out += ppmd->out.pos; |
128 | 7.57k | ppmd->out.pos = 0; |
129 | 7.57k | } |
130 | | |
131 | 7.60k | return MZ_OK; |
132 | 7.60k | } |
133 | | |
134 | 5.09k | static void mz_setup_buffered_writer(mz_stream_ppmd *ppmd) { |
135 | 5.09k | ppmd->out.dst = ppmd->buffer; |
136 | 5.09k | ppmd->out.size = sizeof(ppmd->buffer); // INT16_MAX; |
137 | 5.09k | ppmd->out.pos = 0; |
138 | | |
139 | 5.09k | ppmd->writer.write = writer; |
140 | 5.09k | ppmd->writer.out_buffer = &ppmd->out; |
141 | 5.09k | ppmd->ppmd8.Stream.Out = (IByteOut *)&ppmd->writer; |
142 | 5.09k | } |
143 | | #endif |
144 | | |
145 | | #ifndef MZ_ZIP_NO_DECOMPRESSION |
146 | | |
147 | 0 | static Byte reader(void *p) { |
148 | 0 | mz_buffer_reader *buffer_reader = (mz_buffer_reader *)p; |
149 | 0 | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)buffer_reader->t; |
150 | 0 | uint8_t b; |
151 | 0 | int32_t status; |
152 | |
|
153 | 0 | if ((status = mz_stream_read_uint8((mz_stream_ppmd *)ppmd->stream.base, &b)) != MZ_OK) { |
154 | 0 | ppmd->error = status; |
155 | 0 | b = 0; |
156 | 0 | } else |
157 | 0 | ++ppmd->total_in; |
158 | |
|
159 | 0 | return (Byte)b; |
160 | 0 | } |
161 | | |
162 | 0 | static void mz_setup_buffered_reader(mz_stream_ppmd *ppmd) { |
163 | 0 | ppmd->in.src = ppmd->buffer; |
164 | 0 | ppmd->in.size = sizeof(ppmd->buffer); // INT16_MAX; |
165 | 0 | ppmd->in.pos = 0; |
166 | |
|
167 | 0 | ppmd->reader.read = reader; |
168 | 0 | ppmd->reader.in_buffer = &ppmd->in; |
169 | 0 | ppmd->ppmd8.Stream.In = (IByteIn *)&ppmd->reader; |
170 | |
|
171 | 0 | ppmd->reader.t = ppmd; |
172 | 0 | } |
173 | | |
174 | | #endif |
175 | | |
176 | 1.74k | int32_t mz_stream_ppmd_open(void *stream, const char *path, int32_t mode) { |
177 | 1.74k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
178 | | |
179 | 1.74k | MZ_UNUSED(path); |
180 | | |
181 | 1.74k | Ppmd8_Construct(&ppmd->ppmd8); |
182 | | |
183 | 1.74k | if (mode & MZ_OPEN_MODE_WRITE) { |
184 | | #ifdef MZ_ZIP_NO_COMPRESSION |
185 | | MZ_UNUSED(stream); |
186 | | return MZ_SUPPORT_ERROR; |
187 | | #else |
188 | | /* PPMD8_MIN_ORDER (= 2) <= order <= PPMD8_MAX_ORDER (= 16) |
189 | | * 2MB (= 2^ 1) <= memSize <= 128MB (= 2^ 7) (M = 2^ 20) |
190 | | * restor = 0 (PPMD8_RESTORE_METHOD_RESTART), |
191 | | * 1 (PPMD8_RESTORE_METHOD_CUT_OFF) |
192 | | * |
193 | | * Currently using 7-Zip-compatible values: |
194 | | * order = 3 + level |
195 | | * memory size = 2^ (level- 1) (MB) (level 9 treated as level 8.) |
196 | | * restoration method = 0 for level <= 6, 1 for level >= 7. |
197 | | * |
198 | | */ |
199 | | |
200 | | /* PPMd parameters. */ |
201 | 1.74k | unsigned order = ppmd->preset; /* 2, 3, ..., 16. */ |
202 | 1.74k | uint32_t mem_size; |
203 | 1.74k | unsigned restor; |
204 | 1.74k | uint16_t ppmd_param_word; |
205 | | |
206 | 1.74k | if (order < PPMD8_MIN_ORDER || order > PPMD8_MAX_ORDER) |
207 | 44 | return MZ_OPEN_ERROR; |
208 | | |
209 | 1.70k | mz_setup_buffered_writer(ppmd); |
210 | | |
211 | 1.70k | mem_size = 1 << ((order < 8 ? order : 8) - 1); /* 2MB, 4MB, ..., 128MB. */ |
212 | | |
213 | 1.70k | restor = (order <= 6 ? 0 : 1); |
214 | | |
215 | 1.70k | mem_size <<= 20; /* Convert B to MB. */ |
216 | | |
217 | 1.70k | if (!Ppmd8_Alloc(&ppmd->ppmd8, mem_size, &ppmd->allocator)) { |
218 | 0 | return MZ_MEM_ERROR; |
219 | 0 | } |
220 | | |
221 | 1.70k | Ppmd8_Init_RangeEnc(&ppmd->ppmd8); |
222 | 1.70k | Ppmd8_Init(&ppmd->ppmd8, order, restor); |
223 | | |
224 | | /* wPPMd = (Model order - 1) + |
225 | | * ((Sub-allocator size - 1) << 4) + |
226 | | * (Model restoration method << 12) |
227 | | * |
228 | | * 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 |
229 | | * Mdl_Res_Mth ___Sub-allocator_size-1 Mdl_Order-1 |
230 | | */ |
231 | | |
232 | | /* Form the PPMd properties word. Put out the bytes. */ |
233 | 1.70k | ppmd_param_word = ((order - 1) & 0xf) + ((((mem_size >> 20) - 1) & 0xff) << 4) + ((restor & 0xf) << 12); |
234 | | |
235 | | // write header bytes directly to output buffer, bypassing the compression code |
236 | | // These bytes will be included in the compressed size stored in the zip metadata. |
237 | | |
238 | 1.70k | ((uint8_t *)ppmd->out.dst)[0] = (uint8_t)(ppmd_param_word & 0xff); |
239 | 1.70k | ((uint8_t *)ppmd->out.dst)[1] = (uint8_t)(ppmd_param_word >> 8); |
240 | 1.70k | ppmd->out.pos += 2; |
241 | 1.70k | mz_stream_ppmd_flush(ppmd); |
242 | 1.70k | #endif |
243 | 1.70k | } else if (mode & MZ_OPEN_MODE_READ) { |
244 | | #ifdef MZ_ZIP_NO_DECOMPRESSION |
245 | | MZ_UNUSED(stream); |
246 | | return MZ_SUPPORT_ERROR; |
247 | | #else |
248 | |
|
249 | 0 | uint8_t ppmd_props[2]; /* PPMd properties. */ |
250 | 0 | uint16_t ppmd_prop_word; /* PPMd properties. */ |
251 | | |
252 | | /* Initialize the 7-Zip I/O structure. */ |
253 | 0 | mz_setup_buffered_reader(ppmd); |
254 | | |
255 | | /* Read & parse the 2 PPMD header bytes into a 16-bit word */ |
256 | 0 | if (mz_stream_read_uint8(ppmd->stream.base, &ppmd_props[0]) != MZ_OK || |
257 | 0 | mz_stream_read_uint8(ppmd->stream.base, &ppmd_props[1]) != MZ_OK) |
258 | 0 | return MZ_STREAM_ERROR; |
259 | 0 | ppmd_prop_word = ppmd_props[0] | (ppmd_props[1] << 8); |
260 | 0 | ppmd->total_in += 2; |
261 | | |
262 | | /* wPPMd = (Model order - 1) + |
263 | | * ((Sub-allocator size - 1) << 4) + |
264 | | * (Model restoration method << 12) |
265 | | * |
266 | | * 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 |
267 | | * Mdl_Res_Mth ___Sub-allocator_size-1 Mdl_Order-1 |
268 | | */ |
269 | 0 | unsigned order = (ppmd_prop_word & 0xf) + 1; |
270 | 0 | uint32_t mem_size = ((ppmd_prop_word >> 4) & 0xff) + 1; |
271 | 0 | unsigned restor = (ppmd_prop_word >> 12); |
272 | | |
273 | | /* Convert archive MB value into raw byte value. */ |
274 | 0 | mem_size <<= 20; |
275 | |
|
276 | 0 | if ((order < PPMD8_MIN_ORDER) || (order > PPMD8_MAX_ORDER)) |
277 | 0 | return MZ_STREAM_ERROR; |
278 | | |
279 | 0 | if (!Ppmd8_Alloc(&ppmd->ppmd8, mem_size, &ppmd->allocator)) |
280 | 0 | return MZ_STREAM_ERROR; |
281 | | |
282 | 0 | if (!Ppmd8_Init_RangeDec(&ppmd->ppmd8)) |
283 | 0 | return MZ_STREAM_ERROR; |
284 | | |
285 | 0 | Ppmd8_Init(&ppmd->ppmd8, order, restor); |
286 | 0 | #endif |
287 | 0 | } |
288 | | |
289 | 1.70k | ppmd->initialized = 1; |
290 | 1.70k | ppmd->mode = mode; |
291 | | |
292 | 1.70k | return MZ_OK; |
293 | 1.74k | } |
294 | | |
295 | 3.39k | int32_t mz_stream_ppmd_is_open(void *stream) { |
296 | 3.39k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
297 | 3.39k | if (ppmd->initialized != 1) |
298 | 0 | return MZ_OPEN_ERROR; |
299 | 3.39k | return MZ_OK; |
300 | 3.39k | } |
301 | | |
302 | 0 | int32_t mz_stream_ppmd_read(void *stream, void *buf, int32_t size) { |
303 | | #ifdef MZ_ZIP_NO_DECOMPRESSION |
304 | | MZ_UNUSED(stream); |
305 | | MZ_UNUSED(buf); |
306 | | MZ_UNUSED(size); |
307 | | return MZ_SUPPORT_ERROR; |
308 | | #else |
309 | 0 | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
310 | 0 | uint8_t *next_out = buf; |
311 | 0 | int32_t avail_out; |
312 | 0 | int32_t avail_in = sizeof(ppmd->buffer); |
313 | 0 | int sym = 0; |
314 | 0 | int32_t written = 0; |
315 | |
|
316 | 0 | if (ppmd->end_stream) |
317 | 0 | return MZ_OK; |
318 | | |
319 | 0 | if (ppmd->max_total_in > 0 && avail_in > (ppmd->max_total_in - ppmd->total_in)) |
320 | 0 | avail_in = (int32_t)(ppmd->max_total_in - ppmd->total_in); |
321 | | |
322 | | /* Decode input to fill the output buffer. */ |
323 | 0 | for (avail_out = size; avail_out > 0 && avail_in > 0; avail_out--, avail_in--) { |
324 | 0 | sym = Ppmd8_DecodeSymbol(&ppmd->ppmd8); |
325 | | |
326 | | /* There are two ways to terminate the loop early: |
327 | | 1. Ppmd8_DecodeSymbol returns a negative number to flag EOF or stream error. |
328 | | 2. ppmd->error gets set to true here when the call to mz_stream_read_uint8 |
329 | | in reader() does not return MZ_OK. |
330 | | */ |
331 | 0 | if (sym < 0 || ppmd->error) |
332 | 0 | break; |
333 | | |
334 | 0 | *(next_out++) = sym; |
335 | 0 | ++written; |
336 | 0 | } |
337 | | |
338 | | // sym contains the return code from Ppmd8_DecodeSymbol |
339 | 0 | if (sym == PPMD_RESULT_EOF) { |
340 | 0 | ppmd->end_stream = 1; |
341 | | |
342 | | // Drop through and return written bytes |
343 | 0 | } else if (sym == PPMD_RESULT_ERROR) { |
344 | | /* Insufficient input data. */ |
345 | 0 | return MZ_STREAM_ERROR; |
346 | 0 | } else if (ppmd->error) { |
347 | | /* Invalid end of input data */ |
348 | 0 | return ppmd->error; |
349 | 0 | } |
350 | | |
351 | 0 | ppmd->total_out += written; |
352 | 0 | return written; |
353 | 0 | #endif |
354 | 0 | } |
355 | | |
356 | 1.69k | int32_t mz_stream_ppmd_write(void *stream, const void *buf, int32_t size) { |
357 | | #ifdef MZ_ZIP_NO_COMPRESSION |
358 | | MZ_UNUSED(stream); |
359 | | MZ_UNUSED(buf); |
360 | | MZ_UNUSED(size); |
361 | | return MZ_SUPPORT_ERROR; |
362 | | #else |
363 | 1.69k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
364 | 1.69k | const uint8_t *buf_ptr = (const uint8_t *)buf; |
365 | 1.69k | int32_t bytes_written = 0; |
366 | | |
367 | 1.69k | mz_setup_buffered_writer(ppmd); |
368 | 113M | for (bytes_written = 0; bytes_written < size; bytes_written++) { |
369 | 113M | if (ppmd->out.pos == ppmd->out.size) { |
370 | 2.50k | if (mz_stream_ppmd_flush(ppmd) != MZ_OK) |
371 | 0 | return MZ_WRITE_ERROR; |
372 | 2.50k | } |
373 | | |
374 | 113M | Ppmd8_EncodeSymbol(&ppmd->ppmd8, buf_ptr[bytes_written]); |
375 | 113M | } |
376 | 1.69k | ppmd->total_in += size; |
377 | | |
378 | 1.69k | if (mz_stream_ppmd_flush(stream) != MZ_OK) |
379 | 0 | return MZ_WRITE_ERROR; |
380 | | |
381 | 1.69k | return size; |
382 | 1.69k | #endif |
383 | 1.69k | } |
384 | | |
385 | 0 | int64_t mz_stream_ppmd_tell(void *stream) { |
386 | 0 | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
387 | 0 | return ppmd->total_in; |
388 | 0 | } |
389 | | |
390 | 0 | int32_t mz_stream_ppmd_seek(void *stream, int64_t offset, int32_t origin) { |
391 | 0 | MZ_UNUSED(stream); |
392 | 0 | MZ_UNUSED(offset); |
393 | 0 | MZ_UNUSED(origin); |
394 | |
|
395 | 0 | return MZ_SEEK_ERROR; |
396 | 0 | } |
397 | | |
398 | 1.70k | int32_t mz_stream_ppmd_close(void *stream) { |
399 | 1.70k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
400 | | |
401 | 1.70k | #ifndef MZ_ZIP_NO_COMPRESSION |
402 | 1.70k | if (ppmd->mode & MZ_OPEN_MODE_WRITE) { |
403 | 1.70k | mz_setup_buffered_writer(ppmd); |
404 | | |
405 | | /* Encode end marker */ |
406 | 1.70k | Ppmd8_EncodeSymbol(&ppmd->ppmd8, -1); |
407 | | |
408 | 1.70k | Ppmd8_Flush_RangeEnc(&ppmd->ppmd8); |
409 | | |
410 | | /* Flush any remaining buffered output */ |
411 | 1.70k | mz_stream_ppmd_flush(stream); |
412 | 1.70k | } |
413 | 1.70k | #endif |
414 | | |
415 | 1.70k | Ppmd8_Free(&ppmd->ppmd8, &ppmd->allocator); |
416 | | |
417 | 1.70k | ppmd->initialized = 0; |
418 | | |
419 | 1.70k | return MZ_OK; |
420 | 1.70k | } |
421 | | |
422 | 0 | int32_t mz_stream_ppmd_error(void *stream) { |
423 | 0 | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
424 | 0 | return ppmd->error; |
425 | 0 | } |
426 | | |
427 | 3.40k | int32_t mz_stream_ppmd_get_prop_int64(void *stream, int32_t prop, int64_t *value) { |
428 | 3.40k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
429 | | |
430 | 3.40k | switch (prop) { |
431 | 1.70k | case MZ_STREAM_PROP_TOTAL_IN: |
432 | 1.70k | *value = ppmd->total_in; |
433 | 1.70k | return MZ_OK; |
434 | 1.70k | case MZ_STREAM_PROP_TOTAL_OUT: |
435 | 1.70k | *value = ppmd->total_out; |
436 | 1.70k | return MZ_OK; |
437 | 0 | case MZ_STREAM_PROP_TOTAL_IN_MAX: |
438 | 0 | *value = ppmd->max_total_in; |
439 | 0 | return MZ_OK; |
440 | 3.40k | } |
441 | | |
442 | 0 | return MZ_PARAM_ERROR; |
443 | 3.40k | } |
444 | | |
445 | 3.49k | int32_t mz_stream_ppmd_set_prop_int64(void *stream, int32_t prop, int64_t value) { |
446 | 3.49k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream; |
447 | | |
448 | 3.49k | switch (prop) { |
449 | 1.74k | case MZ_STREAM_PROP_TOTAL_IN_MAX: |
450 | 1.74k | ppmd->max_total_in = value; |
451 | 1.74k | return MZ_OK; |
452 | 1.74k | case MZ_STREAM_PROP_COMPRESS_LEVEL: |
453 | 1.74k | if (value == MZ_COMPRESS_LEVEL_DEFAULT) |
454 | 974 | ppmd->preset = PPMD_PRESET_DEFAULT; |
455 | 773 | else |
456 | 773 | ppmd->preset = (int16_t)value; |
457 | 1.74k | return MZ_OK; |
458 | 3.49k | } |
459 | | |
460 | 0 | return MZ_PARAM_ERROR; |
461 | 3.49k | } |
462 | | |
463 | 1.74k | void *mz_stream_ppmd_create(void) { |
464 | 1.74k | mz_stream_ppmd *ppmd = (mz_stream_ppmd *)calloc(1, sizeof(mz_stream_ppmd)); |
465 | 1.74k | if (ppmd) { |
466 | 1.74k | ppmd->stream.vtbl = &mz_stream_ppmd_vtbl; |
467 | 1.74k | ppmd->allocator.Alloc = mz_ppmd_alloc_func; |
468 | 1.74k | ppmd->allocator.Free = mz_ppmd_free_func; |
469 | 1.74k | ppmd->preset = PPMD_PRESET_DEFAULT; |
470 | 1.74k | } |
471 | 1.74k | return ppmd; |
472 | 1.74k | } |
473 | | |
474 | 1.74k | void mz_stream_ppmd_delete(void **stream) { |
475 | 1.74k | mz_stream_ppmd *ppmd = NULL; |
476 | 1.74k | if (!stream) |
477 | 0 | return; |
478 | 1.74k | ppmd = (mz_stream_ppmd *)*stream; |
479 | 1.74k | free(ppmd); |
480 | 1.74k | *stream = NULL; |
481 | 1.74k | } |
482 | | |
483 | 0 | void *mz_stream_ppmd_get_interface(void) { |
484 | 0 | return (void *)&mz_stream_ppmd_vtbl; |
485 | 0 | } |
486 | | |
487 | | /***************************************************************************/ |