/*
 * ZIDRAV, file corruption repairer
 * Copyright (C) 1999  Ben Wilhelm
 *
 * 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.
 */

#include "core.h"

//#define CPU_BIGENDIAN

#define strncpyn( dest, source, len ) strncpy( dest, source, len ); dest[len] = '\0';
#define IDS_UPT_INVCDT                  17
#define IDS_UPT_INVCDP                  18

#define SwapBO32(x) ((x>>24&0xFF)|((x&0xFF0000)>>8)|((x&0xFF00)<<8)|((x&0xFF)<<24))
#ifdef CPU_BIGENDIAN
	#define write32le(fs, x) be_write32le(fs, x)
	#define read32le(fs, x) be_read32le(fs, x)
	#define int32tole(x) SwapBO32(x)
#else  // LITTLEENDIAN
	#define write32le(fs, x) fs.write(x, 4)
	#define read32le(fs, x) fs.read(x, 4)
	#define int32tole(x) x
#endif

void be_write32le(iostream &output, char *buf)
{
	int data;
	data = *(int*)buf;

	data = SwapBO32(data);

	output.write((char *)&data, 4);
}

void be_read32le(istream &input, char *buf)
{
	int data;
	input.read((char *)&data, 4);

	data = SwapBO32(data);
	*(int*)buf = data;
}

void MakeChecksumCore( istream &input, iostream &output, int blocksize, int datalen ) {

	char	*buffer;
	int		i;
	long	crc;
    short   oldprc = 0;
    
	output.write( ZC, 8 );
	output.write( CVER, 2 );
	write32le(output, (char *)&datalen);
	write32le(output, (char *)&blocksize);

	buffer = new char[blocksize];

	for( datalen % blocksize ? i = -1 : i = 0; i < datalen / blocksize; i++ ) {

        if (i >= 0)
        {
            short newprc = (short) (((float) (i * blocksize) / datalen) * 100);
            if (newprc != oldprc)
            {
                cout << (char) 13 << newprc << "%" << flush;
                oldprc = newprc;
            }
        }

		input.read( buffer, blocksize );

		if( i == datalen / blocksize - 1 && (datalen % blocksize) )	// If it's the end of the file, only make the checksum for what's left
			CreateChecksum( buffer, datalen % blocksize, &crc );
		  else
			CreateChecksum( buffer, blocksize, &crc );

		write32le(output, (char *)&crc);

	}

	delete [] buffer;

    cout << (char) 13 << "Creating internal file checksum...\n";

    if (datalen % blocksize)
    	MakeOverallChecksum( output, 18 + ( datalen / blocksize + 1 ) * 4 );
    else 
    	MakeOverallChecksum( output, 18 + ( datalen / blocksize ) * 4 );

}

void MakePatchCore( istream &cdti, istream &vstr, iostream &output, int cdtlen, int vstrlen, int * efound) {

	long filesize;
	long blocksize;

	long crc;
	long cdtcrc;

	long curbs;

	int i;

	long curolen;
	long temp;
    short   oldprc = 0;

	char *buffer;

	*efound = 0;

	if( cdtlen < 22 ) {	// 22 == minimum size - 8 for signature, 2 for version, 4 for blocksize, 4 for filesize,
						// 4 for comparison checksum - who says it can't be a .cdt of a 0-size file? :)       
        cout << "Error - .CDT invalid\n";
		return; }
    
	if( VerifyStream( cdti, cdtlen, IDS_UPT_INVCDT, CSV ) )
		return;

	cdti.seekg( 10 );

	read32le(cdti, (char *)&filesize);
	read32le(cdti, (char *)&blocksize);

	if( filesize != vstrlen ) {
        cout << "Error - verified file size is " << vstrlen 
             << " instead of " << filesize << "\n";
		return;
	}

	output.write( ZP, 8 );
	output.write( PVER, 2 );
	write32le(output, (char *)&filesize);
	curolen = 14;

	buffer = new char[ blocksize ];
    
	for( vstrlen % blocksize ? i = -1 : i = 0; i < vstrlen / blocksize; i++ ) {

        if (i >= 0)
        {
            short newprc = (short) (((float) (i * blocksize) / vstrlen) * 100);
            if (newprc != oldprc)
            {
                cout << (char) 13 << newprc << "%" << flush;
                oldprc = newprc;
            }
        }

		if( i == vstrlen / blocksize - 1 && (vstrlen % blocksize))
			curbs = vstrlen % blocksize;
		  else
			curbs = blocksize;

		vstr.read( buffer, curbs );
		read32le(cdti, (char *)&cdtcrc);

		CreateChecksum( buffer, curbs, &crc );

		if( crc != cdtcrc ) {
            if (vstrlen % blocksize)
    			temp = i * blocksize + blocksize;
            else
    			temp = i * blocksize;

            cout << (char) 13 << "Corruption found at " << temp << "\n";

			write32le(output, (char *)&temp);	// write the current offset
			write32le(output, (char *)&curbs);
			output.write( buffer, curbs );

			curolen += 8 + curbs;

			*efound = 1; 
		}

	}

	delete [] buffer;

	if( *efound ) {
		temp = -1;
		write32le(output, (char *)&temp);			// end flag

        cout << (char) 13 << "Creating internal file checksum...\n";

		curolen += 4;

		MakeOverallChecksum( output, curolen );
	}

}

void ApplyPatchCore( istream &cdpi, iostream &pstr, int cdplen, int pstrlen) {

	long filesize;
	long blocksize;

	long curbs = 0;

	long offset;

	char *buffer = NULL;

	if( cdplen < 22 ) {	// 22 == minimum size - 8 for signature, 2 for version, 4 for blocksize, 4 for filesize,
						// 4 for comparison checksum
        cout << "Error - .CDP invalid\n";
		return; }

	if( VerifyStream( cdpi, cdplen, IDS_UPT_INVCDP, PSV) )
		return;

	cdpi.seekg( 10 );

	read32le(cdpi, (char *)&filesize);

	if( filesize != pstrlen ) {
        cout << "Error - patch target file size is " << pstrlen <<
                " instead of " << filesize << "\n";
		return;
	}

	read32le(cdpi, (char *)&offset);
	read32le(cdpi, (char *)&blocksize);

	while( offset != -1 ) {

		if( blocksize > curbs ) {
			if( curbs != 0 )
				delete [] buffer;
			buffer = new char[ blocksize ];
			curbs = blocksize;
		}

		cdpi.read( buffer, blocksize );

		pstr.seekp( offset );
		pstr.write( buffer, blocksize );

		read32le(cdpi, (char *)&offset);
		read32le(cdpi, (char *)&blocksize);

	}

	delete [] buffer;

}

int VerifyStream( istream &input, int datalen, int emsg, KSigver sigver ) {

	char *buffer;

	char minibuff[9];

	long crc;

	buffer = new char[10];

	input.read( buffer, 10 );	// Get the header, don't want to accidentally load a 100 meg file unless we have to :)

	strncpyn( minibuff, buffer, 8 );	// Copy the signature in
	
	if( strcmp( minibuff, sigver.signature ) ) {
		delete [] buffer;
        switch(emsg)
        {
            case IDS_UPT_INVCDT:
                cout << "Error - .CDT invalid\n";
                break;
            case IDS_UPT_INVCDP:
                cout << "Error - .CDP invalid\n";
        }
		return 1; }

	strncpyn( minibuff, &buffer[8], 2 ); // Version number comparison

	if( strcmp( minibuff, sigver.version ) ) {
		delete [] buffer;

        cout << "Error - file is v" << minibuff[0] << "." << minibuff[1] 
             << ", program needs v" << sigver.version[0] << "." 
             << sigver.version[1] << "\n";
            
		return 1; }

	delete [] buffer;

    // FIXME: we shouldn't load whole patch/checksum file into memory! 
    // 150M patches aren't common, but they happen sometime 
	buffer = new char[ datalen ];		// want to load the whole thing

    if (!buffer)
    {
        switch(emsg)
        {
            case IDS_UPT_INVCDT:
                cout << "Error - not enough memory to load .CDT file :(\n";
                break;
            case IDS_UPT_INVCDP:
                cout << "Error - not enough memory to load .CDP file :(\n";
        }
        return 1;
    }

	input.seekg( 0 );						// go to the beginning
	input.read( buffer, datalen );		// and pull it all

	CreateChecksum( buffer, datalen - 4, &crc );

	if( crc != int32tole(*(int *)&buffer[ datalen - 4 ]) ) {
		delete [] buffer;
        switch(emsg)
        {
            case IDS_UPT_INVCDT:
                cout << "Error - bad CRC for .CDT file (probably damaged?)\n";
                break;
            case IDS_UPT_INVCDP:
                cout << "Error - bad CRC for .CDP file (probably damaged?)\n";
        }        
		return 1; }

	delete [] buffer;

	return 0;

}

void MakeOverallChecksum( iostream &st, long size ) {

	char *buffer;
	long crc;

    // FIXME: trying to load whole file... again... :(
	buffer = new char[size];

	st.flush();
	st.seekg(0);

	st.read( buffer, size );

	CreateChecksum( buffer, size, &crc );

	st.tellg();
	write32le(st, (char *)&crc);

	delete [] buffer;

}

#define DO1( buffer ) *crc = crc_table[ ( *crc ^ *(buffer++) ) & 0xff ] ^ (*crc >> 8);
#define DO2( buffer )  DO1( buffer ); DO1( buffer );
#define DO4( buffer )  DO2( buffer ); DO2( buffer );
#define DO8( buffer )  DO4( buffer ); DO4( buffer );
#define DO16( buffer )  DO8( buffer ); DO8( buffer );

void CreateChecksum( char *buffer, long size, long *crc ) {

	*crc = 0;

    *crc ^= 0xffffffffL;

    while ( size >= 16 )
    {
      DO16( buffer );
      size -= 16;
    }

    while ( size ) {
      DO1( buffer );
	  size--;
    } 

    *crc ^= 0xffffffffL;
}

