/* Unmass - unpacker for game archives. Copyright (C) 2002-2007 Mirex This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _cmass_archive_h_ #define _cmass_archive_h_ #include #include /* Virtual base class for all archive handlers Implements basic operations on the large mass file as OpenFile() and CloseFile() For example on usage of this base class see bottom of this header file. There is example of class which is derived from CMassArchive */ class CMassArchive { private: // protected copy constructor CMassArchive( const CMassArchive & ); public: CMassArchive(); ~CMassArchive() { CloseFile(); } public: // returns error string, which is filled with error message if function call fails const char* GetErrorStr( void ) { return error; } // Opens the mass file //@returns: 1 on success, 0 on failure int OpenFile( const char *filename ); // closes the file and releases all memory void CloseFile(); // returns file type identifier for example "FI(Final Fantasy 8)" virtual char *GetIdent( void ) = 0; // returns file extension, for example "fi" virtual char *GetExtension( void ) = 0; // tests the file if it contains proper header data // call after OpenFile() //@return: 1 if header is correct, 0 if this isn't proper file. virtual int CheckFileType( void ) = 0; // returns file format flags. Can be combination of [ma_flag_*] values virtual int GetFlags( void ) { return ma_flag_simple_archive; } // retrieves main file info, offsets to library and other important data // call after OpenFile() and CheckFileType() virtual void GetFileMainInfo( void ) = 0; // returns number of files in the file. Call after GetFileMainInfo() long GetFilesCount() { return files_count; } // if you want to add files into mass file you have to reopen it for writing // it has no use if file format does not support adding new files //@returns: 1 on success, 0 on failure int ReopenForWriting( void ); // Reads file library to get all file records into local array one by one. // It is something like pre-reading of the records for faster access to them. // You have to call this function [GetFilesCount()] times to read all file records. // Call this function only after GetFileMainInfo() //@returns: 1 on success, 0 on failure int ReadRecords( void ); // Retrieves single file record into [FileRec] structure // call only after multiple calls to ReadRecords() //@num: file index in range from 0 to GetFilesCount() //@returns: 1 on success, 0 on failure int GetRec( long num ); // reads single library record into [FileRec]. Usually its efficient to read // all the records in consecutive order, by using -1 parameter // User should preload all the file records by multiple calls to ReadRecords() // and then retrieving records by calls to GetRec(). It should be more efficient than // calling ReadRec() //@ num - index of the record. -1 = next record. virtual int ReadRec( long num ) = 0; // Extracts part of the single file data into the buffer // Has to be called only after the FileRec structure is filled, that means // after successful call to GetRec() // function won't work on formats with flag 'ma_flag_extract_only_whole_file' (for example ff8) //@offset - offset in the single file. should be smaller than FileRec.size //@size - size of data to read. offset + size should be smaller than FileRec.size //@output - output buffer where data will be written //@return - number of bytes read virtual unsigned long Extract( unsigned long offset, unsigned long size, unsigned char *output ); // Extracts whole single file data into the FILE // Has to be called only after the FileRec structure is filled, that means // after successful call to GetRec() //@returns: 1 on success, 0 on failure // function is not implemented and not used on most of the file formats // it should be used if and only if the flag 'ma_flag_extract_only_whole_file' // is set, for example on ff8 archives where file is compressed virtual int ExtractWholeFile( FILE *fout ); // === if file format supports adding of new files you can use following functions === // Adds file record into file library. It takes information from struct [FileRec] // takes [FileRec.name], [FileRec.size], fills [FileRec.index] // also modifies [FileRec] so don't change it untill you write all data // with AddFileData() //@returns: 1 on success, 0 on failure virtual int AddFileRecord( void ) { SetErrorStr( "fn not implemented in this type" ); return 0; } // Sets single file data to the file. Uses information from [FileRec], so function // should be used only directly after call AddFileRecord() //@offset - offset in the single file. should be smaller than FileRec.size //@size - size of data to read. offset + size should be smaller than FileRec.size //@data - buffer with data //@returns: 1 on success, 0 on failure virtual int AddFileData( unsigned long offset, unsigned long size, char* data ); // Keeps the single file data inside mass file archive, but clears its library record // this is usually fast and simple way to delete the file. //@returns: 1 on success, 0 on failure virtual int UnlinkFile( unsigned long index ) { SetErrorStr( "fn not implemented in this type" ); return 0; } // Creates empty archive //@returns: 1 on success, 0 on failure virtual int CreateNewArchive( FILE *newf ) { SetErrorStr( "fn not implemented in this type" ); return 0; } public: enum { FileNameWithPathMaxLen = 256, TypeMaxLen = 12, error_string_maxlen = 255, } ; // archive flags enum { ma_flag_simple_archive = 0, //any archive ma_flag_fills_type = 1, //fills FileRec.type; ma_flag_extract_only_whole_file = 2, //archive cannot extract part of file. ma_flag_add_file = 4, //can add new files to archive ma_flag_unlink_file = 8, //can unlink files (remove record, not data) ma_flag_delete_file = 16, //can delete files ma_flag_create_archive = 32, //can repack/recreate archive } ; // single item of mass file library - library record struct s_FileRec { int set; // if 0 then structure is not filled correctly long index; // file index, in range 0 to GetFilesCount() char name[ FileNameWithPathMaxLen ]; // file name long offset; // data offset long size; // data size long flag, flaga[ 2 ]; //set on special ocassions (compressed, other) // sometimes you can't find filename or file extension in the archive library // but even then not everything is lost. Sometimes you can find out file // type by its data. In this case you can fill the file type extension // here into [type]. Formats that have to do this have their // GetFlags() function returning also flag [ma_flag_fills_type] char type[ TypeMaxLen ]; void Reset( void ) { memset( this, 0, sizeof( s_FileRec ) ); } void SetName( const char* n ) { strncpy( name, n, FileNameWithPathMaxLen ); } } FileRec; protected: // information about large file. filled inside OpenFile() // might come handy to archive functions char MassFileNameFull[ FileNameWithPathMaxLen ]; char MassFileName [ FileNameWithPathMaxLen ]; char MassFileExt [ FileNameWithPathMaxLen ]; char MassFilePath [ FileNameWithPathMaxLen ]; //mass file handle. should be opened, or NULL FILE *massf; // information about large file internal information long filesize; //mass disk file size long files_count; //number of files stored in mass file long lib_start; //offset where records start long lib_size; //size of the records library long data_start; //offset where files data start // help variable long pos; // variables for resuming of reading of next file record, used when // you use the ReadRec( -1 ) long next_rec_num, next_rec_pos; bool next_set; // true if file is opened for writing. you can open it for writing // by calling ReopenForWriting(). Needed when you want to add files to the archive // (if it is supported by archive) bool file_open_for_write; // variable containing error message char error[ error_string_maxlen + 1 ]; // sets error string void SetErrorStr( const char* str ) { strncpy( error, str, error_string_maxlen ); } // list of library records s_FileRec *pFileList; // size of the array int FileListSize; // temporary variable for reading library records int FileListItems; protected: //do not call, called from AddFileRecord int GrowFileList( void ); } ; /* ========= ma_ file example h & cpp ============ you should modify lines starting with 'x' ========================= Header template ================================ #ifndef _cmass_xxx_h #define _cmass_xxx_h #include "ma.h" class CMassXXX : public CMassArchive { public: char *GetIdent( void ); char *GetExtension( void ); int CheckFileType( void ); void GetFileMainInfo( void ); int ReadRec( long num ); //num = -1 => next x define variables here x x // x struct s_xxxRec { x char name[ FileNameWithPathMaxLen ]; x unsigned long offset; x char info[3]; x } xxxRec; //len:27 x enum { SizeOfXXXRec = 27 }; } ; #endif ========================= CPP Code template ================================= #include "ma_XXX.h" char *CMassXXX::GetIdent( void ) { x static char Ident[] = "xxxx extension (xxx name of game)"; return Ident; } char *CMassXXX::GetExtension( void ) { x static char Ext[] = "xxxx"; return Ext; } int CMassXXX::CheckFileType( void ) { if ( massf == NULL ) return 0; x char buf[ 100 ]; x fseek( massf, 0, SEEK_SET ); x fread( buf, 12, 1, massf ); x if ( memcmp( buf, "\0\0SQUARESOFT", 12 ) == 0 ) return 1; else return 0; } void CMassXXX::GetFileMainInfo( void ) { files_count = 0; if ( massf == NULL ) return; fseek( massf, 0, SEEK_END ); filesize = ftell( massf ); x fseek( massf, 12, SEEK_SET ); x fread( &files_count, 4, 1, massf ); x lib_start = 16; } int CMassXXX::ReadRec( long num ) { SetErrorStr( "ReadRec" ); if ( massf == NULL ) return 0; if (( num < -1 ) || ( num >= files_count )) return 0; // you cannot use -1 if past last record if (( num == -1 ) && ( next_rec_num >= files_count )) return 0; if (( num == -1 ) && ( next_set == false )) num = 0; FileRec.Reset(); if (( next_set ) && ( next_rec_num == num )) num = -1; //seek if ( num == -1 ) { if ( fseek( massf, next_rec_pos, SEEK_SET ) != 0 ) return 0; x record with inconstant size: pos = next_rec_num - 1; num = next_rec_num; } else { x record with constant size: x pos = lib_start + SizeOfXXXRec * num; if ( fseek( massf, pos, SEEK_SET ) != 0 ) return 0; x records with inconstant size: fseek( massf, lib_start, SEEK_SET ); pos = 0; } //read record with constant size: x if ( fread( &XXXRec, SizeOfXXXRec, 1, massf ) != 1 ) return 0; records with inconstant size: do { x read record pos++; } while ( pos < num ); //store position of the next record for next reading of record (-1) next_rec_pos = ftell( massf ); if ( num == -1 ) next_rec_num ++; else next_rec_num = num + 1; next_set = (( num + 1 ) < files_count ); //get single file info into the FileRec x strcpy( FileRec.name, xxxRec.name ); x FileRec.offset = xxxRec.offset; x FileRec.size = xxxRec.size; x FileRec.set = 1; x FileRec.index = next_rec_num-1; return 1; } */ #endif