Path: blob/main/misc/emulator/xnes/snes9x/jma/jma.cpp
28798 views
/*1Copyright (C) 2005-2006 NSRT Team ( http://nsrt.edgeemu.com )23This program is free software; you can redistribute it and/or4modify it under the terms of the GNU General Public License5version 2 as published by the Free Software Foundation.67This program is distributed in the hope that it will be useful,8but WITHOUT ANY WARRANTY; without even the implied warranty of9MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10GNU General Public License for more details.1112You should have received a copy of the GNU General Public License13along with this program; if not, write to the Free Software14Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.15*/1617#include <sstream>18#include "jma.h"19using namespace std;2021#include "portable.h"22#include "7z.h"23#include "crc32.h"2425namespace JMA26{27const char jma_magic[] = { 'J', 'M', 'A', 0, 'N' };28const unsigned int jma_header_length = 5;29const unsigned char jma_version = 1;30const unsigned int jma_version_length = 1;31const unsigned int jma_total_header_length = jma_header_length + jma_version_length + UINT_SIZE;3233//Convert DOS/zip/JMA integer time to to time_t34time_t uint_to_time(unsigned short date, unsigned short time)35{36tm formatted_time;3738formatted_time.tm_mday = date & 0x1F;39formatted_time.tm_mon = ((date >> 5) & 0xF) - 1;40formatted_time.tm_year = ((date >> 9) & 0x7f) + 80;41formatted_time.tm_sec = (time & 0x1F) * 2;42formatted_time.tm_min = (time >> 5) & 0x3F;43formatted_time.tm_hour = (time >> 11) & 0x1F;4445return(mktime(&formatted_time));46}474849//Retreive the file block, what else?50void jma_open::retrieve_file_block() throw(jma_errors)51{52unsigned char uint_buffer[UINT_SIZE];53unsigned char ushort_buffer[USHORT_SIZE];5455//File block size is the last UINT in the file56stream.seekg(-UINT_SIZE,ios::end);57stream.read((char *)uint_buffer, UINT_SIZE);58size_t file_block_size = charp_to_uint(uint_buffer);5960//Currently at the end of the file, so that's the file size61size_t jma_file_size = (size_t) stream.tellg();6263//The file block can't be larger than the JMA file without it's header.64//This if can probably be improved65if (file_block_size >= jma_file_size-jma_total_header_length)66{67throw(JMA_BAD_FILE);68}6970//Seek to before file block so we can read the file block71stream.seekg(-((int)file_block_size+UINT_SIZE),ios::end);7273//This is needed if the file block is compressed74stringstream decompressed_file_block;75//Pointer to where to read file block from (file or decompressed buffer)76istream *file_block_stream;7778//Setup file info buffer and byte to read with79jma_file_info file_info;80char byte;8182stream.get(byte);83if (!byte) //If file block is compressed84{85//Compressed size isn't counting the byte we just read or the UINT for compressed size86size_t compressed_size = file_block_size - (1+UINT_SIZE);8788//Read decompressed size / true file block size89stream.read((char *)uint_buffer, UINT_SIZE);90file_block_size = charp_to_uint(uint_buffer);9192//Setup access methods for decompression93ISequentialInStream_Istream compressed_data(stream);94ISequentialOutStream_Ostream decompressed_data(decompressed_file_block);9596//Decompress the data97if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, file_block_size))98{99throw(JMA_DECOMPRESS_FAILED);100}101102//Go to beginning, setup pointer to buffer103decompressed_file_block.seekg(0, ios::beg);104file_block_stream = &decompressed_file_block;105}106else107{108stream.putback(byte); //Putback byte, byte is part of filename, not compressed indicator109file_block_stream = &stream;110}111112113//Minimum file name length is 2 bytes, a char and a null114//Minimum comment length is 1 byte, a null115//There are currently 2 UINTs and 2 USHORTs per file116while (file_block_size >= 2+1+UINT_SIZE*2+USHORT_SIZE*2) //This does allow for a gap, but that's okay117{118//First stored in the file block is the file name null terminated119file_info.name = "";120121file_block_stream->get(byte);122while (byte)123{124file_info.name += byte;125file_block_stream->get(byte);126}127128//There must be a file name or the file is bad129if (!file_info.name.length())130{131throw(JMA_BAD_FILE);132}133134//Same trick as above for the comment135file_info.comment = "";136137file_block_stream->get(byte);138while (byte)139{140file_info.comment += byte;141file_block_stream->get(byte);142}143144//Next is a UINT representing the file's size145file_block_stream->read((char *)uint_buffer, UINT_SIZE);146file_info.size = charp_to_uint(uint_buffer);147148//Followed by CRC32149file_block_stream->read((char *)uint_buffer, UINT_SIZE);150file_info.crc32 = charp_to_uint(uint_buffer);151152//Special USHORT representation of file's date153file_block_stream->read((char *)ushort_buffer, USHORT_SIZE);154file_info.date = charp_to_ushort(ushort_buffer);155156//Special USHORT representation of file's time157file_block_stream->read((char *)ushort_buffer, USHORT_SIZE);158file_info.time = charp_to_ushort(ushort_buffer);159160file_info.buffer = 0; //Pointing to null till we decompress files161162files.push_back(file_info); //Put file info into our structure163164//Subtract size of the file info we just read165file_block_size -= file_info.name.length()+file_info.comment.length()+2+UINT_SIZE*2+USHORT_SIZE*2;166}167}168169//Constructor for opening JMA files for reading170jma_open::jma_open(const char *compressed_file_name) throw (jma_errors)171{172decompressed_buffer = 0;173compressed_buffer = 0;174175stream.open(compressed_file_name, ios::in | ios::binary);176if (!stream.is_open())177{178throw(JMA_NO_OPEN);179}180181//Header is "JMA\0N"182unsigned char header[jma_header_length];183stream.read((char *)header, jma_header_length);184if (memcmp(jma_magic, header, jma_header_length))185{186throw(JMA_BAD_FILE);187}188189//Not the cleanest code but logical190stream.read((char *)header, 5);191if (*header <= jma_version)192{193chunk_size = charp_to_uint(header+1); //Chunk size is a UINT that follows version #194retrieve_file_block();195}196else197{198throw(JMA_UNSUPPORTED_VERSION);199}200}201202//Destructor only has to close the stream if neccesary203jma_open::~jma_open()204{205if (stream.is_open())206{207stream.close();208}209}210211//Return a vector containing useful info about the files in the JMA212vector<jma_public_file_info> jma_open::get_files_info()213{214vector<jma_public_file_info> file_info_vector;215jma_public_file_info file_info;216217for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++)218{219file_info.name = i->name;220file_info.comment = i->comment;221file_info.size = i->size;222file_info.datetime = uint_to_time(i->date, i->time);223file_info.crc32 = i->crc32;224file_info_vector.push_back(file_info);225}226227return(file_info_vector);228}229230//Skip forward a given number of chunks231void jma_open::chunk_seek(unsigned int chunk_num) throw(jma_errors)232{233//Check the stream is open234if (!stream.is_open())235{236throw(JMA_NO_OPEN);237}238239//Clear possible errors so the seek will work240stream.clear();241242//Move forward over header243stream.seekg(jma_total_header_length, ios::beg);244245unsigned char int4_buffer[UINT_SIZE];246247while (chunk_num--)248{249//Read in size of chunk250stream.read((char *)int4_buffer, UINT_SIZE);251252//Skip chunk plus it's CRC32253stream.seekg(charp_to_uint(int4_buffer)+UINT_SIZE, ios::cur);254}255}256257//Return a vector of pointers to each file in the JMA, the buffer to hold all the files258//must be initilized outside.259vector<unsigned char *> jma_open::get_all_files(unsigned char *buffer) throw(jma_errors)260{261//If there's no stream we can't read from it, so exit262if (!stream.is_open())263{264throw(JMA_NO_OPEN);265}266267//Seek to the first chunk268chunk_seek(0);269270//Set the buffer that decompressed data goes to271decompressed_buffer = buffer;272273//If the JMA is not solid274if (chunk_size)275{276unsigned char int4_buffer[UINT_SIZE];277size_t size = get_total_size(files);278279//For each chunk in the file...280for (size_t remaining_size = size; remaining_size; remaining_size -= chunk_size)281{282//Read the compressed size283stream.read((char *)int4_buffer, UINT_SIZE);284size_t compressed_size = charp_to_uint(int4_buffer);285286//Allocate memory of the correct size to hold the compressed data in the JMA287//Throw error on failure as that is unrecoverable from288try289{290compressed_buffer = new unsigned char[compressed_size];291}292catch (bad_alloc xa)293{294throw(JMA_NO_MEM_ALLOC);295}296297//Read all the compressed data in298stream.read((char *)compressed_buffer, compressed_size);299300//Read the expected CRC of compressed data from the file301stream.read((char *)int4_buffer, UINT_SIZE);302303//If it doesn't match, throw error and cleanup memory304if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer))305{306delete[] compressed_buffer;307throw(JMA_BAD_FILE);308}309310//Decompress the data, cleanup memory on failure311if (!decompress_lzma_7z(compressed_buffer, compressed_size,312decompressed_buffer+size-remaining_size,313(remaining_size > chunk_size) ? chunk_size : remaining_size))314{315delete[] compressed_buffer;316throw(JMA_DECOMPRESS_FAILED);317}318delete[] compressed_buffer;319320if (remaining_size <= chunk_size) //If we just decompressed the remainder321{322break;323}324}325}326else //Solidly compressed JMA327{328unsigned char int4_buffer[UINT_SIZE];329330//Read the size of the compressed data331stream.read((char *)int4_buffer, UINT_SIZE);332size_t compressed_size = charp_to_uint(int4_buffer);333334//Get decompressed size335size_t size = get_total_size(files);336337//Setup access methods for decompression338ISequentialInStream_Istream compressed_data(stream);339ISequentialOutStream_Array decompressed_data(reinterpret_cast<char*>(decompressed_buffer), size);340341//Decompress the data342if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, size))343{344throw(JMA_DECOMPRESS_FAILED);345}346347/*348//Allocate memory of the right size to hold the compressed data in the JMA349try350{351compressed_buffer = new unsigned char[compressed_size];352}353catch (bad_alloc xa)354{355throw(JMA_NO_MEM_ALLOC);356}357358//Copy the compressed data into memory359stream.read((char *)compressed_buffer, compressed_size);360size_t size = get_total_size(files);361362//Read the CRC of the compressed data363stream.read((char *)int4_buffer, UINT_SIZE);364365//If it doesn't match, complain366if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer))367{368delete[] compressed_buffer;369throw(JMA_BAD_FILE);370}371372//Decompress the data373if (!decompress_lzma_7z(compressed_buffer, compressed_size, decompressed_buffer, size))374{375delete[] compressed_buffer;376throw(JMA_DECOMPRESS_FAILED);377}378delete[] compressed_buffer;379*/380}381382vector<unsigned char *> file_pointers;383size_t size = 0;384385//For each file, add it's pointer to the vector, size is pointer offset in the buffer386for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++)387{388i->buffer = decompressed_buffer+size;389file_pointers.push_back(decompressed_buffer+size);390size += i->size;391}392393//Return the vector of pointers394return(file_pointers);395}396397//Extracts the file with a given name found in the archive to the given buffer398void jma_open::extract_file(string& name, unsigned char *buffer) throw(jma_errors)399{400if (!stream.is_open())401{402throw(JMA_NO_OPEN);403}404405size_t size_to_skip = 0;406size_t our_file_size = 0;407408//Search through the vector of file information409for (vector<jma_file_info>::iterator i = files.begin(); i != files.end(); i++)410{411if (i->name == name)412{413//Set the variable so we can tell we found it414our_file_size = i->size;415break;416}417418//Keep a running total of size419size_to_skip += i->size;420}421422if (!our_file_size) //File with the specified name was not found in the archive423{424throw(JMA_FILE_NOT_FOUND);425}426427//If the JMA only contains one file, we can skip a lot of overhead428if (files.size() == 1)429{430get_all_files(buffer);431return;432}433434if (chunk_size) //we are using non-solid archive..435{436unsigned int chunks_to_skip = size_to_skip / chunk_size;437438//skip over requisite number of chunks439chunk_seek(chunks_to_skip);440441//Allocate memory for compressed and decompressed data442unsigned char *comp_buffer = 0, *decomp_buffer = 0;443try444{445//Compressed data size is <= non compressed size446unsigned char *combined_buffer = new unsigned char[chunk_size*2];447comp_buffer = combined_buffer;448decomp_buffer = combined_buffer+chunk_size;449}450catch (bad_alloc xa)451{452throw(JMA_NO_MEM_ALLOC);453}454455size_t first_chunk_offset = size_to_skip % chunk_size;456unsigned char int4_buffer[UINT_SIZE];457for (size_t i = 0; i < our_file_size;)458{459//Get size460stream.read((char *)int4_buffer, UINT_SIZE);461size_t compressed_size = charp_to_uint(int4_buffer);462463//Read all the compressed data in464stream.read((char *)comp_buffer, compressed_size);465466//Read the CRC of the compressed data467stream.read((char *)int4_buffer, UINT_SIZE);468469//If it doesn't match, complain470if (CRC32lib::CRC32(comp_buffer, compressed_size) != charp_to_uint(int4_buffer))471{472delete[] comp_buffer;473throw(JMA_BAD_FILE);474}475476//Decompress chunk477if (!decompress_lzma_7z(comp_buffer, compressed_size, decomp_buffer, chunk_size))478{479delete[] comp_buffer;480throw(JMA_DECOMPRESS_FAILED);481}482483size_t copy_amount = our_file_size-i > chunk_size-first_chunk_offset ? chunk_size-first_chunk_offset : our_file_size-i;484485memcpy(buffer+i, decomp_buffer+first_chunk_offset, copy_amount);486first_chunk_offset = 0; //Set to zero since this is only for the first iteration487i += copy_amount;488}489delete[] comp_buffer;490}491else //Solid JMA492{493unsigned char *decomp_buffer = 0;494try495{496decomp_buffer = new unsigned char[get_total_size(files)];497}498catch (bad_alloc xa)499{500throw(JMA_NO_MEM_ALLOC);501}502503get_all_files(decomp_buffer);504505memcpy(buffer, decomp_buffer+size_to_skip, our_file_size);506507delete[] decomp_buffer;508}509}510511bool jma_open::is_solid()512{513return(chunk_size ? false : true);514}515516const char *jma_error_text(jma_errors error)517{518switch (error)519{520case JMA_NO_CREATE:521return("JMA could not be created");522523case JMA_NO_MEM_ALLOC:524return("Memory for JMA could be allocated");525526case JMA_NO_OPEN:527return("JMA could not be opened");528529case JMA_BAD_FILE:530return("Invalid/Corrupt JMA");531532case JMA_UNSUPPORTED_VERSION:533return("JMA version not supported");534535case JMA_COMPRESS_FAILED:536return("JMA compression failed");537538case JMA_DECOMPRESS_FAILED:539return("JMA decompression failed");540541case JMA_FILE_NOT_FOUND:542return("File not found in JMA");543}544return("Unknown error");545}546547}548549550551552