/**************************************************************************** * Snes9x 1.51 Nintendo Wii/Gamecube Port * * softdev July 2006 * Michniewski 2008 * Tantric September 2008 * * unzip.cpp * * File unzip routines ***************************************************************************/ #include #include #include #include #include extern "C" { #include "../sz/7zCrc.h" #include "../sz/7zIn.h" #include "../sz/7zExtract.h" } #include "snes9xGX.h" #include "dvd.h" #include "networkop.h" #include "fileop.h" #include "video.h" #include "menudraw.h" #include "gcunzip.h" /* * PKWare Zip Header - adopted into zip standard */ #define PKZIPID 0x504b0304 #define MAXROM 0x500000 #define ZIPCHUNK 2048 /* * Zip files are stored little endian * Support functions for short and int types */ u32 FLIP32 (u32 b) { unsigned int c; c = (b & 0xff000000) >> 24; c |= (b & 0xff0000) >> 8; c |= (b & 0xff00) << 8; c |= (b & 0xff) << 24; return c; } u16 FLIP16 (u16 b) { u16 c; c = (b & 0xff00) >> 8; c |= (b & 0xff) << 8; return c; } /**************************************************************************** * IsZipFile * * Returns TRUE when PKZIPID is first four characters of buffer ****************************************************************************/ int IsZipFile (char *buffer) { unsigned int *check; check = (unsigned int *) buffer; if (check[0] == PKZIPID) return 1; return 0; } /***************************************************************************** * UnZipBuffer * * It should be noted that there is a limit of 5MB total size for any ROM ******************************************************************************/ int UnZipBuffer (unsigned char *outbuffer, int method) { PKZIPHEADER pkzip; int zipoffset = 0; int zipchunk = 0; char out[ZIPCHUNK]; z_stream zs; int res; int bufferoffset = 0; int readoffset = 0; int have = 0; char readbuffer[ZIPCHUNK]; u64 discoffset = 0; int sizeread = 0; // Read Zip Header switch (method) { case METHOD_DVD: discoffset = dvddir; sizeread = dvd_safe_read (readbuffer, ZIPCHUNK, discoffset); break; default: fseek(file, 0, SEEK_SET); sizeread = fread (readbuffer, 1, ZIPCHUNK, file); break; } if(sizeread <= 0) return 0; /*** Copy PKZip header to local, used as info ***/ memcpy (&pkzip, readbuffer, sizeof (PKZIPHEADER)); pkzip.uncompressedSize = FLIP32 (pkzip.uncompressedSize); ShowProgress ("Loading...", 0, pkzip.uncompressedSize); /*** Prepare the zip stream ***/ memset (&zs, 0, sizeof (z_stream)); zs.zalloc = Z_NULL; zs.zfree = Z_NULL; zs.opaque = Z_NULL; zs.avail_in = 0; zs.next_in = Z_NULL; res = inflateInit2 (&zs, -MAX_WBITS); if (res != Z_OK) return 0; /*** Set ZipChunk for first pass ***/ zipoffset = (sizeof (PKZIPHEADER) + FLIP16 (pkzip.filenameLength) + FLIP16 (pkzip.extraDataLength)); zipchunk = ZIPCHUNK - zipoffset; /*** Now do it! ***/ do { zs.avail_in = zipchunk; zs.next_in = (Bytef *) & readbuffer[zipoffset]; /*** Now inflate until input buffer is exhausted ***/ do { zs.avail_out = ZIPCHUNK; zs.next_out = (Bytef *) & out; res = inflate (&zs, Z_NO_FLUSH); if (res == Z_MEM_ERROR) { inflateEnd (&zs); return 0; } have = ZIPCHUNK - zs.avail_out; if (have) { /*** Copy to normal block buffer ***/ memcpy (&outbuffer[bufferoffset], &out, have); bufferoffset += have; } } while (zs.avail_out == 0); // Readup the next 2k block zipoffset = 0; zipchunk = ZIPCHUNK; switch (method) { case METHOD_DVD: readoffset += ZIPCHUNK; sizeread = dvd_safe_read (readbuffer, ZIPCHUNK, discoffset+readoffset); break; default: sizeread = fread (readbuffer, 1, ZIPCHUNK, file); break; } if(sizeread <= 0) break; // read failure ShowProgress ("Loading...", bufferoffset, pkzip.uncompressedSize); } while (res != Z_STREAM_END); inflateEnd (&zs); if (res == Z_STREAM_END) { if (pkzip.uncompressedSize == (u32) bufferoffset) return bufferoffset; else return pkzip.uncompressedSize; } return 0; } /**************************************************************************** * GetFirstZipFilename * * Returns the filename of the first file in the zipped archive * The idea here is to do the least amount of work required ***************************************************************************/ char * GetFirstZipFilename (int method) { char * firstFilename = NULL; char tempbuffer[ZIPCHUNK]; char filepath[1024]; if(!MakeFilePath(filepath, FILE_ROM, method)) return NULL; // read start of ZIP if(LoadFile (tempbuffer, filepath, ZIPCHUNK, method, NOTSILENT)) { tempbuffer[28] = 0; // truncate - filename length is 2 bytes long (bytes 26-27) int namelength = tempbuffer[26]; // filename length starts 26 bytes in if(namelength > 0 && namelength < 200) // the filename is a reasonable length { firstFilename = &tempbuffer[30]; // first filename of a ZIP starts 31 bytes in firstFilename[namelength] = 0; // truncate at filename length } else { WaitPrompt("Error - Invalid ZIP file!"); } } return firstFilename; } /**************************************************************************** * 7z functions ***************************************************************************/ typedef struct _SzFileInStream { ISzInStream InStream; u64 offset; // offset of the file unsigned int len; // length of the file u64 pos; // current position of the file pointer } SzFileInStream; // 7zip error list char szerrormsg[][30] = { "7z: Data error", "7z: Out of memory", "7z: CRC Error", "7z: Not implemented", "7z: Fail", "7z: Data read failure", "7z: Archive error", "7z: Dictionary too large", }; SZ_RESULT SzRes; SzFileInStream SzArchiveStream; CArchiveDatabaseEx SzDb; ISzAlloc SzAllocImp; ISzAlloc SzAllocTempImp; UInt32 SzBlockIndex = 0xFFFFFFFF; size_t SzBufferSize; size_t SzOffset; size_t SzOutSizeProcessed; CFileItem *SzF; char sz_buffer[2048]; int szMethod = 0; /**************************************************************************** * Is7ZipFile * * Returns 1 when 7z signature is found ****************************************************************************/ int Is7ZipFile (char *buffer) { unsigned int *check; check = (unsigned int *) buffer; // 7z signature static Byte Signature[6] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; int i; for(i = 0; i < 6; i++) if(buffer[i] != Signature[i]) return 0; return 1; // 7z archive found } // display an error message void SzDisplayError(SZ_RESULT res) { WaitPrompt(szerrormsg[(res - 1)]); } // function used by the 7zip SDK to read data from SD/USB/DVD/SMB SZ_RESULT SzFileReadImp(void *object, void **buffer, size_t maxRequiredSize, size_t *processedSize) { int seekok = 0; int sizeread = 0; // the void* object is a SzFileInStream SzFileInStream *s = (SzFileInStream *) object; // calculate offset u64 offset = (u64) (s->offset + s->pos); if (maxRequiredSize > 2048) maxRequiredSize = 2048; // read data switch (szMethod) { case METHOD_DVD: sizeread = dvd_safe_read(sz_buffer, maxRequiredSize, offset); break; default: seekok = fseek(file, offset, SEEK_SET); sizeread = fread(sz_buffer, 1, maxRequiredSize, file); break; } if(seekok != 0 || sizeread <= 0) return SZE_FAILREAD; *buffer = sz_buffer; *processedSize = maxRequiredSize; s->pos += *processedSize; if(maxRequiredSize > 1024) // only show progress for large reads // this isn't quite right, but oh well ShowProgress ("Loading...", s->pos, filelist[selection].length); return SZ_OK; } // function used by the 7zip SDK to change the filepointer SZ_RESULT SzFileSeekImp(void *object, CFileSize pos) { // the void* object is a SzFileInStream SzFileInStream *s = (SzFileInStream *) object; // check if the 7z SDK wants to move the pointer to somewhere after the EOF if (pos >= s->len) return SZE_FAIL; // save new position and return s->pos = pos; return SZ_OK; } /**************************************************************************** * SzParse * * Opens a 7z file, and parses it * Right now doesn't parse 7z, since we'll always use the first file * But it could parse the entire 7z for full browsing capability ***************************************************************************/ int SzParse(char * filepath, int method) { int nbfiles = 0; // save the offset and the length of this file inside the archive stream structure SzArchiveStream.offset = filelist[selection].offset; SzArchiveStream.len = filelist[selection].length; SzArchiveStream.pos = 0; // open file switch (method) { case METHOD_SD: case METHOD_USB: case METHOD_SMB: file = fopen (filepath, "rb"); if(!file) return 0; break; } // set szMethod to current chosen load method szMethod = method; // set handler functions for reading data from SD/USB/SMB/DVD SzArchiveStream.InStream.Read = SzFileReadImp; SzArchiveStream.InStream.Seek = SzFileSeekImp; // set default 7Zip SDK handlers for allocation and freeing memory SzAllocImp.Alloc = SzAlloc; SzAllocImp.Free = SzFree; SzAllocTempImp.Alloc = SzAllocTemp; SzAllocTempImp.Free = SzFreeTemp; // prepare CRC and 7Zip database structures InitCrcTable(); SzArDbExInit(&SzDb); // open the archive SzRes = SzArchiveOpen(&SzArchiveStream.InStream, &SzDb, &SzAllocImp, &SzAllocTempImp); if (SzRes != SZ_OK) { SzDisplayError(SzRes); // free memory used by the 7z SDK SzClose(); } else // archive opened successfully { if(SzDb.Database.NumFiles > 0) { // Parses the 7z into a full file listing // erase all previous entries memset(&filelist, 0, sizeof(FILEENTRIES) * MAXFILES); // add '..' folder in case the user wants exit the 7z strncpy(filelist[0].displayname, "..", 2); filelist[0].flags = 1; filelist[0].offset = dvddir; filelist[0].length = dvddirlength; // get contents and parse them into file list structure unsigned int SzI, SzJ; SzJ = 1; for (SzI = 0; SzI < SzDb.Database.NumFiles; SzI++) { SzF = SzDb.Database.Files + SzI; // skip directories if (SzF->IsDirectory) continue; // do not exceed MAXFILES to avoid possible buffer overflows if (SzJ == (MAXFILES - 1)) break; // parse information about this file to the dvd file list structure strncpy(filelist[SzJ].filename, SzF->Name, MAXJOLIET); // copy joliet name (useless...) filelist[SzJ].filename[MAXJOLIET] = 0; // terminate string strncpy(filelist[SzJ].displayname, SzF->Name, MAXDISPLAY+1); // crop name for display filelist[SzJ].length = SzF->Size; // filesize filelist[SzJ].offset = SzI; // the extraction function identifies the file with this number filelist[SzJ].flags = 0; // only files will be displayed (-> no flags) SzJ++; } // update maxfiles and select the first entry offset = selection = 0; nbfiles = SzJ; } else { SzArDbExFree(&SzDb, SzAllocImp.Free); } } // close file switch (method) { case METHOD_SD: case METHOD_USB: case METHOD_SMB: fclose(file); break; } return nbfiles; } /**************************************************************************** * SzClose * * Closes a 7z file ***************************************************************************/ void SzClose() { if(SzDb.Database.NumFiles > 0) SzArDbExFree(&SzDb, SzAllocImp.Free); } /**************************************************************************** * SzExtractFile * * Extracts the given file # into the buffer specified * Must parse the 7z BEFORE running this function ***************************************************************************/ int SzExtractFile(int i, unsigned char *buffer) { // prepare some variables SzBlockIndex = 0xFFFFFFFF; SzOffset = 0; // Unzip the file SzRes = SzExtract2( &SzArchiveStream.InStream, &SzDb, i, // index of file &SzBlockIndex, // index of solid block &buffer, &SzBufferSize, &SzOffset, // offset of stream for required file in *outBuffer &SzOutSizeProcessed, // size of file in *outBuffer &SzAllocImp, &SzAllocTempImp); // close 7Zip archive and free memory SzClose(); // check for errors if(SzRes != SZ_OK) { // display error message SzDisplayError(SzRes); return 0; } else { return SzOutSizeProcessed; } }