1/*
2Copyright (C) 2005-2006 NSRT Team ( http://nsrt.edgeemu.com )
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6version 2 as published by the Free Software Foundation.
7
8This program is distributed in the hope that it will be useful,
9but WITHOUT ANY WARRANTY; without even the implied warranty of
10MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11GNU General Public License for more details.
12
13You should have received a copy of the GNU General Public License
14along with this program; if not, write to the Free Software
15Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16*/
17
18#include <sstream>
19#include "jma.h"
20using namespace std;
21
22#include "portable.h"
23#include "7z.h"
24#include "crc32.h"
25
26namespace JMA
27{
28 const char jma_magic[] = { 'J', 'M', 'A', 0, 'N' };
29 const unsigned int jma_header_length = 5;
30 const unsigned char jma_version = 1;
31 const unsigned int jma_version_length = 1;
32 const unsigned int jma_total_header_length = 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 header[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::extract_file(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