Commit | Line | Data |
---|---|---|
3719602c PC |
1 | /*************************************************************************** |
2 | ||
3 | libchdr_lzma_codec.c | |
4 | ||
5 | MAME Compressed Hunks of Data file format | |
6 | ||
7 | **************************************************************************** | |
8 | ||
9 | Copyright Aaron Giles | |
10 | All rights reserved. | |
11 | ||
12 | Redistribution and use in source and binary forms, with or without | |
13 | modification, are permitted provided that the following conditions are | |
14 | met: | |
15 | ||
16 | * Redistributions of source code must retain the above copyright | |
17 | notice, this list of conditions and the following disclaimer. | |
18 | * Redistributions in binary form must reproduce the above copyright | |
19 | notice, this list of conditions and the following disclaimer in | |
20 | the documentation and/or other materials provided with the | |
21 | distribution. | |
22 | * Neither the name 'MAME' nor the names of its contributors may be | |
23 | used to endorse or promote products derived from this software | |
24 | without specific prior written permission. | |
25 | ||
26 | THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR | |
27 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
29 | DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, | |
30 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
31 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
33 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
34 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
35 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
36 | POSSIBILITY OF SUCH DAMAGE. | |
37 | ||
38 | ***************************************************************************/ | |
39 | ||
40 | #include <stddef.h> | |
41 | #include <stdint.h> | |
42 | #include <stdio.h> | |
43 | #include <stdlib.h> | |
44 | #include <string.h> | |
45 | #include <zlib.h> | |
46 | #include <libchdr/chd.h> | |
47 | #include <libchdr/minmax.h> | |
48 | #include <libchdr/cdrom.h> | |
49 | #include <libchdr/lzma.h> | |
50 | #include <libchdr/huffman.h> | |
51 | ||
52 | #include <retro_inline.h> | |
53 | #include <streams/file_stream.h> | |
54 | ||
55 | #define TRUE 1 | |
56 | #define FALSE 0 | |
57 | ||
58 | /*************************************************************************** | |
59 | * LZMA ALLOCATOR HELPER | |
60 | *************************************************************************** | |
61 | */ | |
62 | ||
63 | /*------------------------------------------------- | |
64 | * lzma_fast_alloc - fast malloc for lzma, which | |
65 | * allocates and frees memory frequently | |
66 | *------------------------------------------------- | |
67 | */ | |
68 | ||
69 | /* Huge alignment values for possible SIMD optimization by compiler (NEON, SSE, AVX) */ | |
70 | #define LZMA_MIN_ALIGNMENT_BITS 512 | |
71 | #define LZMA_MIN_ALIGNMENT_BYTES (LZMA_MIN_ALIGNMENT_BITS / 8) | |
72 | ||
73 | static void *lzma_fast_alloc(void *p, size_t size) | |
74 | { | |
75 | int scan; | |
76 | uint32_t *addr = NULL; | |
77 | lzma_allocator *codec = (lzma_allocator *)(p); | |
78 | uintptr_t vaddr = 0; | |
79 | ||
80 | /* compute the size, rounding to the nearest 1k */ | |
81 | size = (size + 0x3ff) & ~0x3ff; | |
82 | ||
83 | /* reuse a hunk if we can */ | |
84 | for (scan = 0; scan < MAX_LZMA_ALLOCS; scan++) | |
85 | { | |
86 | uint32_t *ptr = codec->allocptr[scan]; | |
87 | if (ptr != NULL && size == *ptr) | |
88 | { | |
89 | /* set the low bit of the size so we don't match next time */ | |
90 | *ptr |= 1; | |
91 | ||
92 | /* return aligned address of the block */ | |
93 | return codec->allocptr2[scan]; | |
94 | } | |
95 | } | |
96 | ||
97 | /* alloc a new one and put it into the list */ | |
98 | addr = (uint32_t *)malloc(size + sizeof(uint32_t) + LZMA_MIN_ALIGNMENT_BYTES); | |
99 | if (addr==NULL) | |
100 | return NULL; | |
101 | for (scan = 0; scan < MAX_LZMA_ALLOCS; scan++) | |
102 | { | |
103 | if (codec->allocptr[scan] == NULL) | |
104 | { | |
105 | /* store block address */ | |
106 | codec->allocptr[scan] = addr; | |
107 | ||
108 | /* compute aligned address, store it */ | |
109 | vaddr = (uintptr_t)addr; | |
110 | vaddr = (vaddr + sizeof(uint32_t) + (LZMA_MIN_ALIGNMENT_BYTES-1)) & (~(LZMA_MIN_ALIGNMENT_BYTES-1)); | |
111 | codec->allocptr2[scan] = (uint32_t*)vaddr; | |
112 | break; | |
113 | } | |
114 | } | |
115 | ||
116 | /* set the low bit of the size so we don't match next time */ | |
117 | *addr = size | 1; | |
118 | ||
119 | /* return aligned address */ | |
120 | return (void*)vaddr; | |
121 | } | |
122 | ||
123 | /*------------------------------------------------- | |
124 | * lzma_fast_free - fast free for lzma, which | |
125 | * allocates and frees memory frequently | |
126 | *------------------------------------------------- | |
127 | */ | |
128 | static void lzma_fast_free(void *p, void *address) | |
129 | { | |
130 | int scan; | |
131 | uint32_t *ptr = NULL; | |
132 | lzma_allocator *codec = NULL; | |
133 | ||
134 | if (address == NULL) | |
135 | return; | |
136 | ||
137 | codec = (lzma_allocator *)(p); | |
138 | ||
139 | /* find the hunk */ | |
140 | ptr = (uint32_t *)address; | |
141 | for (scan = 0; scan < MAX_LZMA_ALLOCS; scan++) | |
142 | { | |
143 | if (ptr == codec->allocptr2[scan]) | |
144 | { | |
145 | /* clear the low bit of the size to allow matches */ | |
146 | *codec->allocptr[scan] &= ~1; | |
147 | return; | |
148 | } | |
149 | } | |
150 | } | |
151 | ||
152 | /*------------------------------------------------- | |
153 | * lzma_allocator_init | |
154 | *------------------------------------------------- | |
155 | */ | |
156 | ||
157 | void lzma_allocator_init(void* p) | |
158 | { | |
159 | lzma_allocator *codec = (lzma_allocator *)(p); | |
160 | ||
161 | /* reset pointer list */ | |
162 | memset(codec->allocptr, 0, sizeof(codec->allocptr)); | |
163 | codec->Alloc = lzma_fast_alloc; | |
164 | codec->Free = lzma_fast_free; | |
165 | } | |
166 | ||
167 | /*------------------------------------------------- | |
168 | * lzma_allocator_free | |
169 | *------------------------------------------------- | |
170 | */ | |
171 | ||
172 | void lzma_allocator_free(void* p ) | |
173 | { | |
174 | lzma_allocator *codec = (lzma_allocator *)(p); | |
175 | ||
176 | /* free our memory */ | |
177 | int i; | |
178 | for (i = 0 ; i < MAX_LZMA_ALLOCS ; i++) | |
179 | { | |
180 | if (codec->allocptr[i] != NULL) | |
181 | free(codec->allocptr[i]); | |
182 | } | |
183 | } | |
184 | ||
185 | /*************************************************************************** | |
186 | * LZMA DECOMPRESSOR | |
187 | *************************************************************************** | |
188 | */ | |
189 | ||
190 | /*------------------------------------------------- | |
191 | * lzma_codec_init - constructor | |
192 | *------------------------------------------------- | |
193 | */ | |
194 | ||
195 | chd_error lzma_codec_init(void* codec, uint32_t hunkbytes) | |
196 | { | |
197 | CLzmaEncProps encoder_props; | |
198 | CLzmaEncHandle enc; | |
199 | uint8_t decoder_props[LZMA_PROPS_SIZE]; | |
200 | lzma_allocator* alloc; | |
201 | size_t props_size; | |
202 | lzma_codec_data* lzma_codec = (lzma_codec_data*) codec; | |
203 | ||
204 | /* construct the decoder */ | |
205 | LzmaDec_Construct(&lzma_codec->decoder); | |
206 | ||
207 | /* FIXME: this code is written in a way that makes it impossible to safely upgrade the LZMA SDK | |
208 | * This code assumes that the current version of the encoder imposes the same requirements on the | |
209 | * decoder as the encoder used to produce the file. This is not necessarily true. The format | |
210 | * needs to be changed so the encoder properties are written to the file. | |
211 | ||
212 | * configure the properties like the compressor did */ | |
213 | LzmaEncProps_Init(&encoder_props); | |
214 | encoder_props.level = 9; | |
215 | encoder_props.reduceSize = hunkbytes; | |
216 | LzmaEncProps_Normalize(&encoder_props); | |
217 | ||
218 | /* convert to decoder properties */ | |
219 | alloc = &lzma_codec->allocator; | |
220 | lzma_allocator_init(alloc); | |
221 | enc = LzmaEnc_Create((ISzAlloc*)alloc); | |
222 | if (!enc) | |
223 | return CHDERR_DECOMPRESSION_ERROR; | |
224 | if (LzmaEnc_SetProps(enc, &encoder_props) != SZ_OK) | |
225 | { | |
226 | LzmaEnc_Destroy(enc, (ISzAlloc*)&alloc, (ISzAlloc*)&alloc); | |
227 | return CHDERR_DECOMPRESSION_ERROR; | |
228 | } | |
229 | props_size = sizeof(decoder_props); | |
230 | if (LzmaEnc_WriteProperties(enc, decoder_props, &props_size) != SZ_OK) | |
231 | { | |
232 | LzmaEnc_Destroy(enc, (ISzAlloc*)alloc, (ISzAlloc*)alloc); | |
233 | return CHDERR_DECOMPRESSION_ERROR; | |
234 | } | |
235 | LzmaEnc_Destroy(enc, (ISzAlloc*)alloc, (ISzAlloc*)alloc); | |
236 | ||
237 | /* do memory allocations */ | |
238 | if (LzmaDec_Allocate(&lzma_codec->decoder, decoder_props, LZMA_PROPS_SIZE, (ISzAlloc*)alloc) != SZ_OK) | |
239 | return CHDERR_DECOMPRESSION_ERROR; | |
240 | ||
241 | /* Okay */ | |
242 | return CHDERR_NONE; | |
243 | } | |
244 | ||
245 | /*------------------------------------------------- | |
246 | * lzma_codec_free | |
247 | *------------------------------------------------- | |
248 | */ | |
249 | ||
250 | void lzma_codec_free(void* codec) | |
251 | { | |
252 | lzma_codec_data* lzma_codec = (lzma_codec_data*) codec; | |
253 | lzma_allocator* alloc = &lzma_codec->allocator; | |
254 | ||
255 | /* free memory */ | |
256 | LzmaDec_Free(&lzma_codec->decoder, (ISzAlloc*)&lzma_codec->allocator); | |
257 | lzma_allocator_free(alloc); | |
258 | } | |
259 | ||
260 | /*------------------------------------------------- | |
261 | * decompress - decompress data using the LZMA | |
262 | * codec | |
263 | *------------------------------------------------- | |
264 | */ | |
265 | ||
266 | chd_error lzma_codec_decompress(void* codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) | |
267 | { | |
268 | ELzmaStatus status; | |
269 | SRes res; | |
270 | size_t consumedlen, decodedlen; | |
271 | /* initialize */ | |
272 | lzma_codec_data* lzma_codec = (lzma_codec_data*) codec; | |
273 | LzmaDec_Init(&lzma_codec->decoder); | |
274 | ||
275 | /* decode */ | |
276 | consumedlen = complen; | |
277 | decodedlen = destlen; | |
278 | res = LzmaDec_DecodeToBuf(&lzma_codec->decoder, dest, &decodedlen, src, &consumedlen, LZMA_FINISH_END, &status); | |
279 | if ((res != SZ_OK && res != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) || consumedlen != complen || decodedlen != destlen) | |
280 | return CHDERR_DECOMPRESSION_ERROR; | |
281 | return CHDERR_NONE; | |
282 | } | |
283 | ||
284 | /* cdlz */ | |
285 | chd_error cdlz_codec_init(void* codec, uint32_t hunkbytes) | |
286 | { | |
287 | chd_error ret; | |
288 | cdlz_codec_data* cdlz = (cdlz_codec_data*) codec; | |
289 | ||
290 | /* allocate buffer */ | |
291 | cdlz->buffer = (uint8_t*)malloc(sizeof(uint8_t) * hunkbytes); | |
292 | if (cdlz->buffer == NULL) | |
293 | return CHDERR_OUT_OF_MEMORY; | |
294 | ||
295 | ret = lzma_codec_init(&cdlz->base_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA); | |
296 | if (ret != CHDERR_NONE) | |
297 | return ret; | |
298 | ||
299 | #ifdef WANT_SUBCODE | |
300 | ret = zlib_codec_init(&cdlz->subcode_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA); | |
301 | if (ret != CHDERR_NONE) | |
302 | return ret; | |
303 | #endif | |
304 | ||
305 | return CHDERR_NONE; | |
306 | } | |
307 | ||
308 | void cdlz_codec_free(void* codec) | |
309 | { | |
310 | cdlz_codec_data* cdlz = (cdlz_codec_data*) codec; | |
311 | ||
312 | lzma_codec_free(&cdlz->base_decompressor); | |
313 | #ifdef WANT_SUBCODE | |
314 | zlib_codec_free(&cdlz->subcode_decompressor); | |
315 | #endif | |
316 | if (cdlz->buffer) | |
317 | free(cdlz->buffer); | |
318 | } | |
319 | ||
320 | chd_error cdlz_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) | |
321 | { | |
322 | #ifdef WANT_RAW_DATA_SECTOR | |
323 | uint8_t *sector; | |
324 | #endif | |
325 | uint32_t framenum; | |
326 | cdlz_codec_data* cdlz = (cdlz_codec_data*)codec; | |
327 | ||
328 | /* determine header bytes */ | |
329 | uint32_t frames = destlen / CD_FRAME_SIZE; | |
330 | uint32_t complen_bytes = (destlen < 65536) ? 2 : 3; | |
331 | uint32_t ecc_bytes = (frames + 7) / 8; | |
332 | uint32_t header_bytes = ecc_bytes + complen_bytes; | |
333 | ||
334 | /* extract compressed length of base */ | |
335 | uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1]; | |
336 | if (complen_bytes > 2) | |
337 | complen_base = (complen_base << 8) | src[ecc_bytes + 2]; | |
338 | ||
339 | /* reset and decode */ | |
340 | lzma_codec_decompress(&cdlz->base_decompressor, &src[header_bytes], complen_base, &cdlz->buffer[0], frames * CD_MAX_SECTOR_DATA); | |
341 | #ifdef WANT_SUBCODE | |
342 | if (header_bytes + complen_base >= complen) | |
343 | return CHDERR_DECOMPRESSION_ERROR; | |
344 | zlib_codec_decompress(&cdlz->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdlz->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA); | |
345 | #endif | |
346 | ||
347 | /* reassemble the data */ | |
348 | for (framenum = 0; framenum < frames; framenum++) | |
349 | { | |
350 | memcpy(&dest[framenum * CD_FRAME_SIZE], &cdlz->buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA); | |
351 | #ifdef WANT_SUBCODE | |
352 | memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &cdlz->buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA); | |
353 | #endif | |
354 | ||
355 | #ifdef WANT_RAW_DATA_SECTOR | |
356 | /* reconstitute the ECC data and sync header */ | |
357 | sector = (uint8_t *)&dest[framenum * CD_FRAME_SIZE]; | |
358 | if ((src[framenum / 8] & (1 << (framenum % 8))) != 0) | |
359 | { | |
360 | memcpy(sector, s_cd_sync_header, sizeof(s_cd_sync_header)); | |
361 | ecc_generate(sector); | |
362 | } | |
363 | #endif | |
364 | } | |
365 | return CHDERR_NONE; | |
366 | } |