/*
    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 <string.h>
#include <stdio.h>

/*

  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
