/* Copyright (C) 2005-2006 NSRT Team ( http://nsrt.edgeemu.com ) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include "jma.h" using namespace std; #include "portable.h" #include "7z.h" #include "crc32.h" namespace JMA { const char jma_magic[] = { 'J', 'M', 'A', 0, 'N' }; const unsigned int jma_header_length = 5; const unsigned char jma_version = 1; const unsigned int jma_version_length = 1; const unsigned int jma_total_header_length = jma_header_length + jma_version_length + UINT_SIZE; //Convert DOS/zip/JMA integer time to to time_t time_t uint_to_time(unsigned short date, unsigned short time) { tm formatted_time; formatted_time.tm_mday = date & 0x1F; formatted_time.tm_mon = ((date >> 5) & 0xF) - 1; formatted_time.tm_year = ((date >> 9) & 0x7f) + 80; formatted_time.tm_sec = (time & 0x1F) * 2; formatted_time.tm_min = (time >> 5) & 0x3F; formatted_time.tm_hour = (time >> 11) & 0x1F; return(mktime(&formatted_time)); } //Retreive the file block, what else? void jma_open::retrieve_file_block() throw(jma_errors) { unsigned char uint_buffer[UINT_SIZE]; unsigned char ushort_buffer[USHORT_SIZE]; //File block size is the last UINT in the file stream.seekg(-UINT_SIZE,ios::end); stream.read((char *)uint_buffer, UINT_SIZE); size_t file_block_size = charp_to_uint(uint_buffer); //Currently at the end of the file, so that's the file size size_t jma_file_size = (size_t) stream.tellg(); //The file block can't be larger than the JMA file without it's header. //This if can probably be improved if (file_block_size >= jma_file_size-jma_total_header_length) { throw(JMA_BAD_FILE); } //Seek to before file block so we can read the file block stream.seekg(-((int)file_block_size+UINT_SIZE),ios::end); //This is needed if the file block is compressed stringstream decompressed_file_block; //Pointer to where to read file block from (file or decompressed buffer) istream *file_block_stream; //Setup file info buffer and byte to read with jma_file_info file_info; char byte; stream.get(byte); if (!byte) //If file block is compressed { //Compressed size isn't counting the byte we just read or the UINT for compressed size size_t compressed_size = file_block_size - (1+UINT_SIZE); //Read decompressed size / true file block size stream.read((char *)uint_buffer, UINT_SIZE); file_block_size = charp_to_uint(uint_buffer); //Setup access methods for decompression ISequentialInStream_Istream compressed_data(stream); ISequentialOutStream_Ostream decompressed_data(decompressed_file_block); //Decompress the data if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, file_block_size)) { throw(JMA_DECOMPRESS_FAILED); } //Go to beginning, setup pointer to buffer decompressed_file_block.seekg(0, ios::beg); file_block_stream = &decompressed_file_block; } else { stream.putback(byte); //Putback byte, byte is part of filename, not compressed indicator file_block_stream = &stream; } //Minimum file name length is 2 bytes, a char and a null //Minimum comment length is 1 byte, a null //There are currently 2 UINTs and 2 USHORTs per file while (file_block_size >= 2+1+UINT_SIZE*2+USHORT_SIZE*2) //This does allow for a gap, but that's okay { //First stored in the file block is the file name null terminated file_info.name = ""; file_block_stream->get(byte); while (byte) { file_info.name += byte; file_block_stream->get(byte); } //There must be a file name or the file is bad if (!file_info.name.length()) { throw(JMA_BAD_FILE); } //Same trick as above for the comment file_info.comment = ""; file_block_stream->get(byte); while (byte) { file_info.comment += byte; file_block_stream->get(byte); } //Next is a UINT representing the file's size file_block_stream->read((char *)uint_buffer, UINT_SIZE); file_info.size = charp_to_uint(uint_buffer); //Followed by CRC32 file_block_stream->read((char *)uint_buffer, UINT_SIZE); file_info.crc32 = charp_to_uint(uint_buffer); //Special USHORT representation of file's date file_block_stream->read((char *)ushort_buffer, USHORT_SIZE); file_info.date = charp_to_ushort(ushort_buffer); //Special USHORT representation of file's time file_block_stream->read((char *)ushort_buffer, USHORT_SIZE); file_info.time = charp_to_ushort(ushort_buffer); file_info.buffer = 0; //Pointing to null till we decompress files files.push_back(file_info); //Put file info into our structure //Subtract size of the file info we just read file_block_size -= file_info.name.length()+file_info.comment.length()+2+UINT_SIZE*2+USHORT_SIZE*2; } } //Constructor for opening JMA files for reading jma_open::jma_open(const char *compressed_file_name) throw (jma_errors) { decompressed_buffer = 0; compressed_buffer = 0; stream.open(compressed_file_name, ios::in | ios::binary); if (!stream.is_open()) { throw(JMA_NO_OPEN); } //Header is "JMA\0N" unsigned char header[jma_header_length]; stream.read((char *)header, jma_header_length); if (memcmp(jma_magic, header, jma_header_length)) { throw(JMA_BAD_FILE); } //Not the cleanest code but logical stream.read((char *)header, 5); if (*header <= jma_version) { chunk_size = charp_to_uint(header+1); //Chunk size is a UINT that follows version # retrieve_file_block(); } else { throw(JMA_UNSUPPORTED_VERSION); } } //Destructor only has to close the stream if neccesary jma_open::~jma_open() { if (stream.is_open()) { stream.close(); } } //Return a vector containing useful info about the files in the JMA vector jma_open::get_files_info() { vector file_info_vector; jma_public_file_info file_info; for (vector::iterator i = files.begin(); i != files.end(); i++) { file_info.name = i->name; file_info.comment = i->comment; file_info.size = i->size; file_info.datetime = uint_to_time(i->date, i->time); file_info.crc32 = i->crc32; file_info_vector.push_back(file_info); } return(file_info_vector); } //Skip forward a given number of chunks void jma_open::chunk_seek(unsigned int chunk_num) throw(jma_errors) { //Check the stream is open if (!stream.is_open()) { throw(JMA_NO_OPEN); } //Clear possible errors so the seek will work stream.clear(); //Move forward over header stream.seekg(jma_total_header_length, ios::beg); unsigned char int4_buffer[UINT_SIZE]; while (chunk_num--) { //Read in size of chunk stream.read((char *)int4_buffer, UINT_SIZE); //Skip chunk plus it's CRC32 stream.seekg(charp_to_uint(int4_buffer)+UINT_SIZE, ios::cur); } } //Return a vector of pointers to each file in the JMA, the buffer to hold all the files //must be initilized outside. vector jma_open::get_all_files(unsigned char *buffer) throw(jma_errors) { //If there's no stream we can't read from it, so exit if (!stream.is_open()) { throw(JMA_NO_OPEN); } //Seek to the first chunk chunk_seek(0); //Set the buffer that decompressed data goes to decompressed_buffer = buffer; //If the JMA is not solid if (chunk_size) { unsigned char int4_buffer[UINT_SIZE]; size_t size = get_total_size(files); //For each chunk in the file... for (size_t remaining_size = size; remaining_size; remaining_size -= chunk_size) { //Read the compressed size stream.read((char *)int4_buffer, UINT_SIZE); size_t compressed_size = charp_to_uint(int4_buffer); //Allocate memory of the correct size to hold the compressed data in the JMA //Throw error on failure as that is unrecoverable from try { compressed_buffer = new unsigned char[compressed_size]; } catch (bad_alloc xa) { throw(JMA_NO_MEM_ALLOC); } //Read all the compressed data in stream.read((char *)compressed_buffer, compressed_size); //Read the expected CRC of compressed data from the file stream.read((char *)int4_buffer, UINT_SIZE); //If it doesn't match, throw error and cleanup memory if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer)) { delete[] compressed_buffer; throw(JMA_BAD_FILE); } //Decompress the data, cleanup memory on failure if (!decompress_lzma_7z(compressed_buffer, compressed_size, decompressed_buffer+size-remaining_size, (remaining_size > chunk_size) ? chunk_size : remaining_size)) { delete[] compressed_buffer; throw(JMA_DECOMPRESS_FAILED); } delete[] compressed_buffer; if (remaining_size <= chunk_size) //If we just decompressed the remainder { break; } } } else //Solidly compressed JMA { unsigned char int4_buffer[UINT_SIZE]; //Read the size of the compressed data stream.read((char *)int4_buffer, UINT_SIZE); size_t compressed_size = charp_to_uint(int4_buffer); //Get decompressed size size_t size = get_total_size(files); //Setup access methods for decompression ISequentialInStream_Istream compressed_data(stream); ISequentialOutStream_Array decompressed_data(reinterpret_cast(decompressed_buffer), size); //Decompress the data if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, size)) { throw(JMA_DECOMPRESS_FAILED); } /* //Allocate memory of the right size to hold the compressed data in the JMA try { compressed_buffer = new unsigned char[compressed_size]; } catch (bad_alloc xa) { throw(JMA_NO_MEM_ALLOC); } //Copy the compressed data into memory stream.read((char *)compressed_buffer, compressed_size); size_t size = get_total_size(files); //Read the CRC of the compressed data stream.read((char *)int4_buffer, UINT_SIZE); //If it doesn't match, complain if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer)) { delete[] compressed_buffer; throw(JMA_BAD_FILE); } //Decompress the data if (!decompress_lzma_7z(compressed_buffer, compressed_size, decompressed_buffer, size)) { delete[] compressed_buffer; throw(JMA_DECOMPRESS_FAILED); } delete[] compressed_buffer; */ } vector file_pointers; size_t size = 0; //For each file, add it's pointer to the vector, size is pointer offset in the buffer for (vector::iterator i = files.begin(); i != files.end(); i++) { i->buffer = decompressed_buffer+size; file_pointers.push_back(decompressed_buffer+size); size += i->size; } //Return the vector of pointers return(file_pointers); } //Extracts the file with a given name found in the archive to the given buffer void jma_open::extract_file(string& name, unsigned char *buffer) throw(jma_errors) { if (!stream.is_open()) { throw(JMA_NO_OPEN); } size_t size_to_skip = 0; size_t our_file_size = 0; //Search through the vector of file information for (vector::iterator i = files.begin(); i != files.end(); i++) { if (i->name == name) { //Set the variable so we can tell we found it our_file_size = i->size; break; } //Keep a running total of size size_to_skip += i->size; } if (!our_file_size) //File with the specified name was not found in the archive { throw(JMA_FILE_NOT_FOUND); } //If the JMA only contains one file, we can skip a lot of overhead if (files.size() == 1) { get_all_files(buffer); return; } if (chunk_size) //we are using non-solid archive.. { unsigned int chunks_to_skip = size_to_skip / chunk_size; //skip over requisite number of chunks chunk_seek(chunks_to_skip); //Allocate memory for compressed and decompressed data unsigned char *comp_buffer = 0, *decomp_buffer = 0; try { //Compressed data size is <= non compressed size unsigned char *combined_buffer = new unsigned char[chunk_size*2]; comp_buffer = combined_buffer; decomp_buffer = combined_buffer+chunk_size; } catch (bad_alloc xa) { throw(JMA_NO_MEM_ALLOC); } size_t first_chunk_offset = size_to_skip % chunk_size; unsigned char int4_buffer[UINT_SIZE]; for (size_t i = 0; i < our_file_size;) { //Get size stream.read((char *)int4_buffer, UINT_SIZE); size_t compressed_size = charp_to_uint(int4_buffer); //Read all the compressed data in stream.read((char *)comp_buffer, compressed_size); //Read the CRC of the compressed data stream.read((char *)int4_buffer, UINT_SIZE); //If it doesn't match, complain if (CRC32lib::CRC32(comp_buffer, compressed_size) != charp_to_uint(int4_buffer)) { delete[] comp_buffer; throw(JMA_BAD_FILE); } //Decompress chunk if (!decompress_lzma_7z(comp_buffer, compressed_size, decomp_buffer, chunk_size)) { delete[] comp_buffer; throw(JMA_DECOMPRESS_FAILED); } size_t copy_amount = our_file_size-i > chunk_size-first_chunk_offset ? chunk_size-first_chunk_offset : our_file_size-i; memcpy(buffer+i, decomp_buffer+first_chunk_offset, copy_amount); first_chunk_offset = 0; //Set to zero since this is only for the first iteration i += copy_amount; } delete[] comp_buffer; } else //Solid JMA { unsigned char *decomp_buffer = 0; try { decomp_buffer = new unsigned char[get_total_size(files)]; } catch (bad_alloc xa) { throw(JMA_NO_MEM_ALLOC); } get_all_files(decomp_buffer); memcpy(buffer, decomp_buffer+size_to_skip, our_file_size); delete[] decomp_buffer; } } bool jma_open::is_solid() { return(chunk_size ? false : true); } const char *jma_error_text(jma_errors error) { switch (error) { case JMA_NO_CREATE: return("JMA could not be created"); case JMA_NO_MEM_ALLOC: return("Memory for JMA could be allocated"); case JMA_NO_OPEN: return("JMA could not be opened"); case JMA_BAD_FILE: return("Invalid/Corrupt JMA"); case JMA_UNSUPPORTED_VERSION: return("JMA version not supported"); case JMA_COMPRESS_FAILED: return("JMA compression failed"); case JMA_DECOMPRESS_FAILED: return("JMA decompression failed"); case JMA_FILE_NOT_FOUND: return("File not found in JMA"); } return("Unknown error"); } }