Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
RishiRecon
GitHub Repository: RishiRecon/exploits
Path: blob/main/misc/emulator/xnes/snes9x/jma/jma.cpp
28798 views
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 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() throw(jma_errors)
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) throw (jma_errors)
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) throw(jma_errors)
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) throw(jma_errors)
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
//Allocate memory of the correct size to hold the compressed data in the JMA
288
//Throw error on failure as that is unrecoverable from
289
try
290
{
291
compressed_buffer = new unsigned char[compressed_size];
292
}
293
catch (bad_alloc xa)
294
{
295
throw(JMA_NO_MEM_ALLOC);
296
}
297
298
//Read all the compressed data in
299
stream.read((char *)compressed_buffer, compressed_size);
300
301
//Read the expected CRC of compressed data from the file
302
stream.read((char *)int4_buffer, UINT_SIZE);
303
304
//If it doesn't match, throw error and cleanup memory
305
if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer))
306
{
307
delete[] compressed_buffer;
308
throw(JMA_BAD_FILE);
309
}
310
311
//Decompress the data, cleanup memory on failure
312
if (!decompress_lzma_7z(compressed_buffer, compressed_size,
313
decompressed_buffer+size-remaining_size,
314
(remaining_size > chunk_size) ? chunk_size : remaining_size))
315
{
316
delete[] compressed_buffer;
317
throw(JMA_DECOMPRESS_FAILED);
318
}
319
delete[] compressed_buffer;
320
321
if (remaining_size <= chunk_size) //If we just decompressed the remainder
322
{
323
break;
324
}
325
}
326
}
327
else //Solidly compressed JMA
328
{
329
unsigned char int4_buffer[UINT_SIZE];
330
331
//Read the size of the compressed data
332
stream.read((char *)int4_buffer, UINT_SIZE);
333
size_t compressed_size = charp_to_uint(int4_buffer);
334
335
//Get decompressed size
336
size_t size = get_total_size(files);
337
338
//Setup access methods for decompression
339
ISequentialInStream_Istream compressed_data(stream);
340
ISequentialOutStream_Array decompressed_data(reinterpret_cast<char*>(decompressed_buffer), size);
341
342
//Decompress the data
343
if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, size))
344
{
345
throw(JMA_DECOMPRESS_FAILED);
346
}
347
348
/*
349
//Allocate memory of the right size to hold the compressed data in the JMA
350
try
351
{
352
compressed_buffer = new unsigned char[compressed_size];
353
}
354
catch (bad_alloc xa)
355
{
356
throw(JMA_NO_MEM_ALLOC);
357
}
358
359
//Copy the compressed data into memory
360
stream.read((char *)compressed_buffer, compressed_size);
361
size_t size = get_total_size(files);
362
363
//Read the CRC of the compressed data
364
stream.read((char *)int4_buffer, UINT_SIZE);
365
366
//If it doesn't match, complain
367
if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer))
368
{
369
delete[] compressed_buffer;
370
throw(JMA_BAD_FILE);
371
}
372
373
//Decompress the data
374
if (!decompress_lzma_7z(compressed_buffer, compressed_size, decompressed_buffer, size))
375
{
376
delete[] compressed_buffer;
377
throw(JMA_DECOMPRESS_FAILED);
378
}
379
delete[] compressed_buffer;
380
*/
381
}
382
383
vector<unsigned char *> file_pointers;
384
size_t size = 0;
385
386
//For each file, add it's pointer to the vector, size is pointer offset in the buffer
387
for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++)
388
{
389
i->buffer = decompressed_buffer+size;
390
file_pointers.push_back(decompressed_buffer+size);
391
size += i->size;
392
}
393
394
//Return the vector of pointers
395
return(file_pointers);
396
}
397
398
//Extracts the file with a given name found in the archive to the given buffer
399
void jma_open::extract_file(string& name, unsigned char *buffer) throw(jma_errors)
400
{
401
if (!stream.is_open())
402
{
403
throw(JMA_NO_OPEN);
404
}
405
406
size_t size_to_skip = 0;
407
size_t our_file_size = 0;
408
409
//Search through the vector of file information
410
for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++)
411
{
412
if (i->name == name)
413
{
414
//Set the variable so we can tell we found it
415
our_file_size = i->size;
416
break;
417
}
418
419
//Keep a running total of size
420
size_to_skip += i->size;
421
}
422
423
if (!our_file_size) //File with the specified name was not found in the archive
424
{
425
throw(JMA_FILE_NOT_FOUND);
426
}
427
428
//If the JMA only contains one file, we can skip a lot of overhead
429
if (files.size() == 1)
430
{
431
get_all_files(buffer);
432
return;
433
}
434
435
if (chunk_size) //we are using non-solid archive..
436
{
437
unsigned int chunks_to_skip = size_to_skip / chunk_size;
438
439
//skip over requisite number of chunks
440
chunk_seek(chunks_to_skip);
441
442
//Allocate memory for compressed and decompressed data
443
unsigned char *comp_buffer = 0, *decomp_buffer = 0;
444
try
445
{
446
//Compressed data size is <= non compressed size
447
unsigned char *combined_buffer = new unsigned char[chunk_size*2];
448
comp_buffer = combined_buffer;
449
decomp_buffer = combined_buffer+chunk_size;
450
}
451
catch (bad_alloc xa)
452
{
453
throw(JMA_NO_MEM_ALLOC);
454
}
455
456
size_t first_chunk_offset = size_to_skip % chunk_size;
457
unsigned char int4_buffer[UINT_SIZE];
458
for (size_t i = 0; i < our_file_size;)
459
{
460
//Get size
461
stream.read((char *)int4_buffer, UINT_SIZE);
462
size_t compressed_size = charp_to_uint(int4_buffer);
463
464
//Read all the compressed data in
465
stream.read((char *)comp_buffer, compressed_size);
466
467
//Read the CRC of the compressed data
468
stream.read((char *)int4_buffer, UINT_SIZE);
469
470
//If it doesn't match, complain
471
if (CRC32lib::CRC32(comp_buffer, compressed_size) != charp_to_uint(int4_buffer))
472
{
473
delete[] comp_buffer;
474
throw(JMA_BAD_FILE);
475
}
476
477
//Decompress chunk
478
if (!decompress_lzma_7z(comp_buffer, compressed_size, decomp_buffer, chunk_size))
479
{
480
delete[] comp_buffer;
481
throw(JMA_DECOMPRESS_FAILED);
482
}
483
484
size_t copy_amount = our_file_size-i > chunk_size-first_chunk_offset ? chunk_size-first_chunk_offset : our_file_size-i;
485
486
memcpy(buffer+i, decomp_buffer+first_chunk_offset, copy_amount);
487
first_chunk_offset = 0; //Set to zero since this is only for the first iteration
488
i += copy_amount;
489
}
490
delete[] comp_buffer;
491
}
492
else //Solid JMA
493
{
494
unsigned char *decomp_buffer = 0;
495
try
496
{
497
decomp_buffer = new unsigned char[get_total_size(files)];
498
}
499
catch (bad_alloc xa)
500
{
501
throw(JMA_NO_MEM_ALLOC);
502
}
503
504
get_all_files(decomp_buffer);
505
506
memcpy(buffer, decomp_buffer+size_to_skip, our_file_size);
507
508
delete[] decomp_buffer;
509
}
510
}
511
512
bool jma_open::is_solid()
513
{
514
return(chunk_size ? false : true);
515
}
516
517
const char *jma_error_text(jma_errors error)
518
{
519
switch (error)
520
{
521
case JMA_NO_CREATE:
522
return("JMA could not be created");
523
524
case JMA_NO_MEM_ALLOC:
525
return("Memory for JMA could be allocated");
526
527
case JMA_NO_OPEN:
528
return("JMA could not be opened");
529
530
case JMA_BAD_FILE:
531
return("Invalid/Corrupt JMA");
532
533
case JMA_UNSUPPORTED_VERSION:
534
return("JMA version not supported");
535
536
case JMA_COMPRESS_FAILED:
537
return("JMA compression failed");
538
539
case JMA_DECOMPRESS_FAILED:
540
return("JMA decompression failed");
541
542
case JMA_FILE_NOT_FOUND:
543
return("File not found in JMA");
544
}
545
return("Unknown error");
546
}
547
548
}
549
550
551
552