1 | /* |
2 | Copyright (C) 2005-2006 NSRT Team ( http://nsrt.edgeemu.com ) |
3 | |
4 | This program is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU General Public License |
6 | version 2 as published by the Free Software Foundation. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
16 | */ |
17 | |
18 | #include <sstream> |
19 | #include "jma.h" |
20 | using namespace std; |
21 | |
22 | #include "portable.h" |
23 | #include "7z.h" |
24 | #include "crc32.h" |
25 | |
26 | namespace JMA |
27 | { |
28 | const char jma_magic[] = { 'J', 'M', 'A', 0, 'N' }; |
29 | const unsigned int = 5; |
30 | const unsigned char jma_version = 1; |
31 | const unsigned int jma_version_length = 1; |
32 | const unsigned int = jma_header_length + jma_version_length + UINT_SIZE; |
33 | |
34 | //Convert DOS/zip/JMA integer time to to time_t |
35 | time_t uint_to_time(unsigned short date, unsigned short time) |
36 | { |
37 | tm formatted_time; |
38 | |
39 | formatted_time.tm_mday = date & 0x1F; |
40 | formatted_time.tm_mon = ((date >> 5) & 0xF) - 1; |
41 | formatted_time.tm_year = ((date >> 9) & 0x7f) + 80; |
42 | formatted_time.tm_sec = (time & 0x1F) * 2; |
43 | formatted_time.tm_min = (time >> 5) & 0x3F; |
44 | formatted_time.tm_hour = (time >> 11) & 0x1F; |
45 | |
46 | return(mktime(&formatted_time)); |
47 | } |
48 | |
49 | |
50 | //Retreive the file block, what else? |
51 | void jma_open::retrieve_file_block() |
52 | { |
53 | unsigned char uint_buffer[UINT_SIZE]; |
54 | unsigned char ushort_buffer[USHORT_SIZE]; |
55 | |
56 | //File block size is the last UINT in the file |
57 | stream.seekg(-UINT_SIZE,ios::end); |
58 | stream.read((char *)uint_buffer, UINT_SIZE); |
59 | size_t file_block_size = charp_to_uint(uint_buffer); |
60 | |
61 | //Currently at the end of the file, so that's the file size |
62 | size_t jma_file_size = (size_t) stream.tellg(); |
63 | |
64 | //The file block can't be larger than the JMA file without it's header. |
65 | //This if can probably be improved |
66 | if (file_block_size >= jma_file_size-jma_total_header_length) |
67 | { |
68 | throw(JMA_BAD_FILE); |
69 | } |
70 | |
71 | //Seek to before file block so we can read the file block |
72 | stream.seekg(-((int)file_block_size+UINT_SIZE),ios::end); |
73 | |
74 | //This is needed if the file block is compressed |
75 | stringstream decompressed_file_block; |
76 | //Pointer to where to read file block from (file or decompressed buffer) |
77 | istream *file_block_stream; |
78 | |
79 | //Setup file info buffer and byte to read with |
80 | jma_file_info file_info; |
81 | char byte; |
82 | |
83 | stream.get(byte); |
84 | if (!byte) //If file block is compressed |
85 | { |
86 | //Compressed size isn't counting the byte we just read or the UINT for compressed size |
87 | size_t compressed_size = file_block_size - (1+UINT_SIZE); |
88 | |
89 | //Read decompressed size / true file block size |
90 | stream.read((char *)uint_buffer, UINT_SIZE); |
91 | file_block_size = charp_to_uint(uint_buffer); |
92 | |
93 | //Setup access methods for decompression |
94 | ISequentialInStream_Istream compressed_data(stream); |
95 | ISequentialOutStream_Ostream decompressed_data(decompressed_file_block); |
96 | |
97 | //Decompress the data |
98 | if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, file_block_size)) |
99 | { |
100 | throw(JMA_DECOMPRESS_FAILED); |
101 | } |
102 | |
103 | //Go to beginning, setup pointer to buffer |
104 | decompressed_file_block.seekg(0, ios::beg); |
105 | file_block_stream = &decompressed_file_block; |
106 | } |
107 | else |
108 | { |
109 | stream.putback(byte); //Putback byte, byte is part of filename, not compressed indicator |
110 | file_block_stream = &stream; |
111 | } |
112 | |
113 | |
114 | //Minimum file name length is 2 bytes, a char and a null |
115 | //Minimum comment length is 1 byte, a null |
116 | //There are currently 2 UINTs and 2 USHORTs per file |
117 | while (file_block_size >= 2+1+UINT_SIZE*2+USHORT_SIZE*2) //This does allow for a gap, but that's okay |
118 | { |
119 | //First stored in the file block is the file name null terminated |
120 | file_info.name = "" ; |
121 | |
122 | file_block_stream->get(byte); |
123 | while (byte) |
124 | { |
125 | file_info.name += byte; |
126 | file_block_stream->get(byte); |
127 | } |
128 | |
129 | //There must be a file name or the file is bad |
130 | if (!file_info.name.length()) |
131 | { |
132 | throw(JMA_BAD_FILE); |
133 | } |
134 | |
135 | //Same trick as above for the comment |
136 | file_info.comment = "" ; |
137 | |
138 | file_block_stream->get(byte); |
139 | while (byte) |
140 | { |
141 | file_info.comment += byte; |
142 | file_block_stream->get(byte); |
143 | } |
144 | |
145 | //Next is a UINT representing the file's size |
146 | file_block_stream->read((char *)uint_buffer, UINT_SIZE); |
147 | file_info.size = charp_to_uint(uint_buffer); |
148 | |
149 | //Followed by CRC32 |
150 | file_block_stream->read((char *)uint_buffer, UINT_SIZE); |
151 | file_info.crc32 = charp_to_uint(uint_buffer); |
152 | |
153 | //Special USHORT representation of file's date |
154 | file_block_stream->read((char *)ushort_buffer, USHORT_SIZE); |
155 | file_info.date = charp_to_ushort(ushort_buffer); |
156 | |
157 | //Special USHORT representation of file's time |
158 | file_block_stream->read((char *)ushort_buffer, USHORT_SIZE); |
159 | file_info.time = charp_to_ushort(ushort_buffer); |
160 | |
161 | file_info.buffer = 0; //Pointing to null till we decompress files |
162 | |
163 | files.push_back(file_info); //Put file info into our structure |
164 | |
165 | //Subtract size of the file info we just read |
166 | file_block_size -= file_info.name.length()+file_info.comment.length()+2+UINT_SIZE*2+USHORT_SIZE*2; |
167 | } |
168 | } |
169 | |
170 | //Constructor for opening JMA files for reading |
171 | jma_open::jma_open(const char *compressed_file_name) |
172 | { |
173 | decompressed_buffer = 0; |
174 | compressed_buffer = 0; |
175 | |
176 | stream.open(compressed_file_name, ios::in | ios::binary); |
177 | if (!stream.is_open()) |
178 | { |
179 | throw(JMA_NO_OPEN); |
180 | } |
181 | |
182 | //Header is "JMA\0N" |
183 | unsigned char [jma_header_length]; |
184 | stream.read((char *)header, jma_header_length); |
185 | if (memcmp(jma_magic, header, jma_header_length)) |
186 | { |
187 | throw(JMA_BAD_FILE); |
188 | } |
189 | |
190 | //Not the cleanest code but logical |
191 | stream.read((char *)header, 5); |
192 | if (*header <= jma_version) |
193 | { |
194 | chunk_size = charp_to_uint(header+1); //Chunk size is a UINT that follows version # |
195 | retrieve_file_block(); |
196 | } |
197 | else |
198 | { |
199 | throw(JMA_UNSUPPORTED_VERSION); |
200 | } |
201 | } |
202 | |
203 | //Destructor only has to close the stream if neccesary |
204 | jma_open::~jma_open() |
205 | { |
206 | if (stream.is_open()) |
207 | { |
208 | stream.close(); |
209 | } |
210 | } |
211 | |
212 | //Return a vector containing useful info about the files in the JMA |
213 | vector<jma_public_file_info> jma_open::get_files_info() |
214 | { |
215 | vector<jma_public_file_info> file_info_vector; |
216 | jma_public_file_info file_info; |
217 | |
218 | for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++) |
219 | { |
220 | file_info.name = i->name; |
221 | file_info.comment = i->comment; |
222 | file_info.size = i->size; |
223 | file_info.datetime = uint_to_time(i->date, i->time); |
224 | file_info.crc32 = i->crc32; |
225 | file_info_vector.push_back(file_info); |
226 | } |
227 | |
228 | return(file_info_vector); |
229 | } |
230 | |
231 | //Skip forward a given number of chunks |
232 | void jma_open::chunk_seek(unsigned int chunk_num) |
233 | { |
234 | //Check the stream is open |
235 | if (!stream.is_open()) |
236 | { |
237 | throw(JMA_NO_OPEN); |
238 | } |
239 | |
240 | //Clear possible errors so the seek will work |
241 | stream.clear(); |
242 | |
243 | //Move forward over header |
244 | stream.seekg(jma_total_header_length, ios::beg); |
245 | |
246 | unsigned char int4_buffer[UINT_SIZE]; |
247 | |
248 | while (chunk_num--) |
249 | { |
250 | //Read in size of chunk |
251 | stream.read((char *)int4_buffer, UINT_SIZE); |
252 | |
253 | //Skip chunk plus it's CRC32 |
254 | stream.seekg(charp_to_uint(int4_buffer)+UINT_SIZE, ios::cur); |
255 | } |
256 | } |
257 | |
258 | //Return a vector of pointers to each file in the JMA, the buffer to hold all the files |
259 | //must be initilized outside. |
260 | vector<unsigned char *> jma_open::get_all_files(unsigned char *buffer) |
261 | { |
262 | //If there's no stream we can't read from it, so exit |
263 | if (!stream.is_open()) |
264 | { |
265 | throw(JMA_NO_OPEN); |
266 | } |
267 | |
268 | //Seek to the first chunk |
269 | chunk_seek(0); |
270 | |
271 | //Set the buffer that decompressed data goes to |
272 | decompressed_buffer = buffer; |
273 | |
274 | //If the JMA is not solid |
275 | if (chunk_size) |
276 | { |
277 | unsigned char int4_buffer[UINT_SIZE]; |
278 | size_t size = get_total_size(files); |
279 | |
280 | //For each chunk in the file... |
281 | for (size_t remaining_size = size; remaining_size; remaining_size -= chunk_size) |
282 | { |
283 | //Read the compressed size |
284 | stream.read((char *)int4_buffer, UINT_SIZE); |
285 | size_t compressed_size = charp_to_uint(int4_buffer); |
286 | |
287 | compressed_buffer = new unsigned char[compressed_size]; |
288 | |
289 | //Read all the compressed data in |
290 | stream.read((char *)compressed_buffer, compressed_size); |
291 | |
292 | //Read the expected CRC of compressed data from the file |
293 | stream.read((char *)int4_buffer, UINT_SIZE); |
294 | |
295 | //If it doesn't match, throw error and cleanup memory |
296 | if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer)) |
297 | { |
298 | delete[] compressed_buffer; |
299 | throw(JMA_BAD_FILE); |
300 | } |
301 | |
302 | //Decompress the data, cleanup memory on failure |
303 | if (!decompress_lzma_7z(compressed_buffer, compressed_size, |
304 | decompressed_buffer+size-remaining_size, |
305 | (remaining_size > chunk_size) ? chunk_size : remaining_size)) |
306 | { |
307 | delete[] compressed_buffer; |
308 | throw(JMA_DECOMPRESS_FAILED); |
309 | } |
310 | delete[] compressed_buffer; |
311 | |
312 | if (remaining_size <= chunk_size) //If we just decompressed the remainder |
313 | { |
314 | break; |
315 | } |
316 | } |
317 | } |
318 | else //Solidly compressed JMA |
319 | { |
320 | unsigned char int4_buffer[UINT_SIZE]; |
321 | |
322 | //Read the size of the compressed data |
323 | stream.read((char *)int4_buffer, UINT_SIZE); |
324 | size_t compressed_size = charp_to_uint(int4_buffer); |
325 | |
326 | //Get decompressed size |
327 | size_t size = get_total_size(files); |
328 | |
329 | //Setup access methods for decompression |
330 | ISequentialInStream_Istream compressed_data(stream); |
331 | ISequentialOutStream_Array decompressed_data(reinterpret_cast<char*>(decompressed_buffer), size); |
332 | |
333 | //Decompress the data |
334 | if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, size)) |
335 | { |
336 | throw(JMA_DECOMPRESS_FAILED); |
337 | } |
338 | |
339 | /* |
340 | //Allocate memory of the right size to hold the compressed data in the JMA |
341 | try |
342 | { |
343 | compressed_buffer = new unsigned char[compressed_size]; |
344 | } |
345 | catch (bad_alloc xa) |
346 | { |
347 | throw(JMA_NO_MEM_ALLOC); |
348 | } |
349 | |
350 | //Copy the compressed data into memory |
351 | stream.read((char *)compressed_buffer, compressed_size); |
352 | size_t size = get_total_size(files); |
353 | |
354 | //Read the CRC of the compressed data |
355 | stream.read((char *)int4_buffer, UINT_SIZE); |
356 | |
357 | //If it doesn't match, complain |
358 | if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer)) |
359 | { |
360 | delete[] compressed_buffer; |
361 | throw(JMA_BAD_FILE); |
362 | } |
363 | |
364 | //Decompress the data |
365 | if (!decompress_lzma_7z(compressed_buffer, compressed_size, decompressed_buffer, size)) |
366 | { |
367 | delete[] compressed_buffer; |
368 | throw(JMA_DECOMPRESS_FAILED); |
369 | } |
370 | delete[] compressed_buffer; |
371 | */ |
372 | } |
373 | |
374 | vector<unsigned char *> file_pointers; |
375 | size_t size = 0; |
376 | |
377 | //For each file, add it's pointer to the vector, size is pointer offset in the buffer |
378 | for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++) |
379 | { |
380 | i->buffer = decompressed_buffer+size; |
381 | file_pointers.push_back(decompressed_buffer+size); |
382 | size += i->size; |
383 | } |
384 | |
385 | //Return the vector of pointers |
386 | return(file_pointers); |
387 | } |
388 | |
389 | //Extracts the file with a given name found in the archive to the given buffer |
390 | void jma_open::(string& name, unsigned char *buffer) |
391 | { |
392 | if (!stream.is_open()) |
393 | { |
394 | throw(JMA_NO_OPEN); |
395 | } |
396 | |
397 | size_t size_to_skip = 0; |
398 | size_t our_file_size = 0; |
399 | |
400 | //Search through the vector of file information |
401 | for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++) |
402 | { |
403 | if (i->name == name) |
404 | { |
405 | //Set the variable so we can tell we found it |
406 | our_file_size = i->size; |
407 | break; |
408 | } |
409 | |
410 | //Keep a running total of size |
411 | size_to_skip += i->size; |
412 | } |
413 | |
414 | if (!our_file_size) //File with the specified name was not found in the archive |
415 | { |
416 | throw(JMA_FILE_NOT_FOUND); |
417 | } |
418 | |
419 | //If the JMA only contains one file, we can skip a lot of overhead |
420 | if (files.size() == 1) |
421 | { |
422 | get_all_files(buffer); |
423 | return; |
424 | } |
425 | |
426 | if (chunk_size) //we are using non-solid archive.. |
427 | { |
428 | unsigned int chunks_to_skip = size_to_skip / chunk_size; |
429 | |
430 | //skip over requisite number of chunks |
431 | chunk_seek(chunks_to_skip); |
432 | |
433 | //Allocate memory for compressed and decompressed data |
434 | unsigned char *comp_buffer = 0, *decomp_buffer = 0; |
435 | |
436 | //Compressed data size is <= non compressed size |
437 | unsigned char *combined_buffer = new unsigned char[chunk_size*2]; |
438 | comp_buffer = combined_buffer; |
439 | decomp_buffer = combined_buffer+chunk_size; |
440 | |
441 | size_t first_chunk_offset = size_to_skip % chunk_size; |
442 | unsigned char int4_buffer[UINT_SIZE]; |
443 | for (size_t i = 0; i < our_file_size;) |
444 | { |
445 | //Get size |
446 | stream.read((char *)int4_buffer, UINT_SIZE); |
447 | size_t compressed_size = charp_to_uint(int4_buffer); |
448 | |
449 | //Read all the compressed data in |
450 | stream.read((char *)comp_buffer, compressed_size); |
451 | |
452 | //Read the CRC of the compressed data |
453 | stream.read((char *)int4_buffer, UINT_SIZE); |
454 | |
455 | //If it doesn't match, complain |
456 | if (CRC32lib::CRC32(comp_buffer, compressed_size) != charp_to_uint(int4_buffer)) |
457 | { |
458 | delete[] comp_buffer; |
459 | throw(JMA_BAD_FILE); |
460 | } |
461 | |
462 | //Decompress chunk |
463 | if (!decompress_lzma_7z(comp_buffer, compressed_size, decomp_buffer, chunk_size)) |
464 | { |
465 | delete[] comp_buffer; |
466 | throw(JMA_DECOMPRESS_FAILED); |
467 | } |
468 | |
469 | size_t copy_amount = our_file_size-i > chunk_size-first_chunk_offset ? chunk_size-first_chunk_offset : our_file_size-i; |
470 | |
471 | memcpy(buffer+i, decomp_buffer+first_chunk_offset, copy_amount); |
472 | first_chunk_offset = 0; //Set to zero since this is only for the first iteration |
473 | i += copy_amount; |
474 | } |
475 | delete[] comp_buffer; |
476 | } |
477 | else //Solid JMA |
478 | { |
479 | unsigned char *decomp_buffer = 0; |
480 | decomp_buffer = new unsigned char[get_total_size(files)]; |
481 | |
482 | get_all_files(decomp_buffer); |
483 | |
484 | memcpy(buffer, decomp_buffer+size_to_skip, our_file_size); |
485 | |
486 | delete[] decomp_buffer; |
487 | } |
488 | } |
489 | |
490 | bool jma_open::is_solid() |
491 | { |
492 | return(chunk_size ? false : true); |
493 | } |
494 | |
495 | const char *jma_error_text(jma_errors error) |
496 | { |
497 | switch (error) |
498 | { |
499 | case JMA_NO_CREATE: |
500 | return("JMA could not be created" ); |
501 | |
502 | case JMA_NO_MEM_ALLOC: |
503 | return("Memory for JMA could be allocated" ); |
504 | |
505 | case JMA_NO_OPEN: |
506 | return("JMA could not be opened" ); |
507 | |
508 | case JMA_BAD_FILE: |
509 | return("Invalid/Corrupt JMA" ); |
510 | |
511 | case JMA_UNSUPPORTED_VERSION: |
512 | return("JMA version not supported" ); |
513 | |
514 | case JMA_COMPRESS_FAILED: |
515 | return("JMA compression failed" ); |
516 | |
517 | case JMA_DECOMPRESS_FAILED: |
518 | return("JMA decompression failed" ); |
519 | |
520 | case JMA_FILE_NOT_FOUND: |
521 | return("File not found in JMA" ); |
522 | } |
523 | return("Unknown error" ); |
524 | } |
525 | |
526 | } |
527 | |
528 | |
529 | |