#include "dblreader.h"

INT* pDBLChunkHeaders = 0;
INT iCurrentChunk = 0;
INT iFirstChunkOffset = 0;
char* dblFname = 0;

String^ MakeString( char* str ) {
	char* chr = NULL;
	String^ converted_str;

	chr = str;
	for( INT i = 0; chr[i] != '\0'; i++ ) {
		converted_str += wchar_t(str[i]);
	}
	return converted_str;
}

dbl_chunk_header_s* ReadChunkHeader( FILE* pFile, INT iOffset, dbl_header_s* pDBLHeader ) {
	if( (INT)pDBLHeader != DBL_CHUNK_NODBLHEADER ) {
		if( iCurrentChunk > pDBLHeader->iChunksNum ) return FALSE;
	}
	INT iByte = 0;
	dbl_chunk_header_s* pCurrentChunkHeader = (dbl_chunk_header_s*)malloc( sizeof(dbl_chunk_header_s) );

	fseek( pFile, iOffset, 0 );
	fread( &iByte, 2, 1, pFile );
	if( feof(pFile) ) return FALSE;
	pCurrentChunkHeader->iDataChunkType = iByte;

	fread( &iByte, 2, 1, pFile );
	pCurrentChunkHeader->iFlag = iByte;

	fread( &iByte, 4, 1, pFile );
	pCurrentChunkHeader->iDataChunkSize = iByte;
	iByte = 0;

	fread( &iByte, 2, 1, pFile );
	pCurrentChunkHeader->iDataChunkVer = iByte;
	iByte = 0;

	return pCurrentChunkHeader;
}

INT ReadDBLHeader( FILE* pFile, dbl_header_s* pHeader ) {
	INT iByte = 0, iHeaderOffset = 0;

	fseek( pFile, iHeaderOffset, 0 );
	fseek( pFile, 0, 0 );
	memset( pHeader->dblId, 0, 64 );

	iByte = 0x30;
	while( iByte >= 0x30 && iByte <= 0x39 ) { // Read till it's not a number
		fread( &iByte, 1, 1, pFile );
		strcat( pHeader->dblId, (char*)&iByte );
	}
	if( pHeader->dblId[0] < 0x30 || pHeader->dblId[1] > 0x39 ) return 0;
	iHeaderOffset = atoi( pHeader->dblId ); // We're reading the offset to the DBL header first
	fseek( pFile, iHeaderOffset+8, 0 ); // G+G = GG
	memset( pHeader->dblId, 0, 64 );

	iByte = 1;
	while( iByte != 0x00 ) { // Reading the header until the string is terminated
		fread( &iByte, 1, 1, pFile );
		strcat( pHeader->dblId, (char*)&iByte );
	}
	iByte = 0;

	fseek( pFile, iHeaderOffset+24, 0 ); //32
	fread( &iByte, 2, 1, pFile );
	pHeader->iChunksNum = iByte; // Get the number of data chunks in this DBL from the static offset
	iByte = 0;
	
	return iHeaderOffset+32; // Return the offset to the first ever data-chunk header
}

BOOL CheckDBLId( dbl_header_s* header ) {
	if( strcmp(header->dblId, "DB") &&
		strcmp(header->dblId, "WIN32") &&
		strcmp(header->dblId, "PS2") &&
		strcmp(header->dblId, "DX") &&
		strcmp(header->dblId, "XBOX") ) return FALSE;
	return TRUE;
}

BOOL ReadDBLFile( const char* file, System::Windows::Forms::RichTextBox^ logbox ) {
	char pMsg[256];
	dbl_chunk_header_s* pCurrentChunkHeader = NULL;
	FILE* pDBLFile = NULL;
	INT iByte = 0, iDataOffset = 0, iPostDataOffset = 0;
	iFirstChunkOffset = 0;
	dbl_header_s* pDBLHeader = (dbl_header_s*)malloc( sizeof(dbl_header_s) );
	iCurrentChunk = 0;

	pDBLFile = fopen( file, "rb" );
	if( !pDBLFile ) return FALSE;
	dblFname = (char*)malloc( 512 );
	strcpy( dblFname, file );
	pDBLHeader->dblId = (char*)malloc( 64 );

	iFirstChunkOffset = ReadDBLHeader( pDBLFile, pDBLHeader );
	if( !iFirstChunkOffset ) {
		MessageBoxA( NULL, "Couldn't locate the DBL header pointer!", "ERROR", MB_ICONERROR );
		return FALSE;
	}
	if( !CheckDBLId(pDBLHeader) ) {
		MessageBoxA( NULL, "Wrong DBL id!", "ERROR", MB_ICONERROR );
		return FALSE;
	}

	logbox->Text = "DBL file "+MakeString((char*)file)+" opened\n\n";
	logbox->Text += "Parsed DBL header:\n===============================";
	logbox->Text += "\nDBL ID: "+MakeString(pDBLHeader->dblId);
	logbox->Text += "\nPhantom number of chunks for the DBL: "+pDBLHeader->iChunksNum.ToString()+"\n\n\n\n";

	pDBLChunkHeaders = (INT*)malloc( 4*pDBLHeader->iChunksNum ); //pDBLChunkHeaders = (INT*)malloc( sizeof(dbl_chunk_header_s)*pDBLHeader->iChunksNum );

	iDataOffset = iFirstChunkOffset;
	while( iDataOffset ) { // Start processing data chunks
		pCurrentChunkHeader = ReadChunkHeader( pDBLFile, iDataOffset, pDBLHeader );
		if( !pCurrentChunkHeader ) break;
		switch( pCurrentChunkHeader->iFlag ) {
			case 0x0E:
				iPostDataOffset = FLAG_0E;
			break;
			case 0x06:
				iPostDataOffset = FLAG_06;
			break;
			case 0x00:
				iPostDataOffset = FLAG_00;
			break;
			case 0x0B:
				iPostDataOffset = FLAG_0B;
			break;
			default:
				sprintf( pMsg, "Unknown chunk-header flag 0x%02x!\n\tChunk #%i @ 0x%02x bytes", pCurrentChunkHeader->iFlag, iCurrentChunk+1, iDataOffset );
				MessageBoxA( NULL, pMsg, "ERROR", MB_ICONERROR );
				return FALSE;
			break;
		}

		iDataOffset = iFirstChunkOffset + pCurrentChunkHeader->iDataChunkSize+iPostDataOffset;

		if( iCurrentChunk > 0 ) {
			for( INT i = iCurrentChunk-1; i >= 0; i-- ) {
				if( !pDBLChunkHeaders[i] ) break;
				switch( ((dbl_chunk_header_s*)(pDBLChunkHeaders[i]))->iFlag ) {
					case 0x0E:
						iPostDataOffset = FLAG_0E;
					break;
					case 0x06:
						iPostDataOffset = FLAG_06;
					break;
					case 0x00:
						iPostDataOffset = FLAG_00;
					break;
					case 0x0B:
						iPostDataOffset = FLAG_0B;
					break;
				}
				iDataOffset += ((dbl_chunk_header_s*)(pDBLChunkHeaders[i]))->iDataChunkSize+iPostDataOffset; // Remove -22
			}
		}
		pCurrentChunkHeader->iDataFinalOffset = iDataOffset;
		pDBLChunkHeaders[iCurrentChunk] = (INT)pCurrentChunkHeader;

		logbox->Text += "Parsed header for the chunk #"+(iCurrentChunk+1).ToString();
		logbox->Text += "\n===============================";
		logbox->Text += "\nChunk data type: 0x"+String::Format( "{0:X}", pCurrentChunkHeader->iDataChunkType );
		logbox->Text += "\nChunk flag: 0x"+String::Format( "{0:X}", pCurrentChunkHeader->iFlag );
		logbox->Text += "\nChunk size: "+pCurrentChunkHeader->iDataChunkSize.ToString()+" bytes";
		logbox->Text += "\nOffset to the next chunk: 0x"+String::Format( "{0:X}", pCurrentChunkHeader->iDataFinalOffset );
		logbox->Text += "\nChunk version: "+pCurrentChunkHeader->iDataChunkVer.ToString()+"\n\n";

		iCurrentChunk++;
		if( iCurrentChunk > pDBLHeader->iChunksNum ) break;
	}

	fclose( pDBLFile );

	return TRUE;
}

BOOL ExtractChunks( const char* output, System::Windows::Forms::RichTextBox^ logbox ) {
	FILE* pDBLFile = 0;
	FILE* pChunkFile = 0;
	dbl_chunk_header_s* pCurrentChunkHeader = 0;
	INT iNextOffset = 0, iPostDataOffset = 0;
	BYTE iByte = 0;
	char szFilename[256];

	pDBLFile = fopen( dblFname, "rb" );
	if( !pDBLFile ) return FALSE;

	logbox->Text = "Started extracting chunks to "+MakeString((char*)output)+"\n\n\n\n";

	strcpy( szFilename, output );
	strcat( szFilename, "\\Chunk00.chk" );
	pChunkFile = fopen( szFilename, "wb" );
	fwrite( "IT IS A DUMMY FILE TO BE IGNORED! AUTO GENERATED BY DBL FUCKER", 1, 0x3E, pChunkFile );
	fclose( pChunkFile );

	for( INT i = 0; i < iCurrentChunk; i++ ) {
		sprintf( szFilename, (i+1 < 10) ? "%s\\Chunk0%i.chk" : "%s\\Chunk%i.chk", output, i+1 );
		pChunkFile = fopen( szFilename, "wb" );
		if( !pChunkFile ) return FALSE;

		pCurrentChunkHeader = (dbl_chunk_header_s*)(pDBLChunkHeaders[i]);
		if( !pCurrentChunkHeader ) break;

		logbox->Text += "Extracting chunk #"+(i+1).ToString()+" to "+MakeString(szFilename);
		logbox->Text += "\n===============================";
		logbox->Text += "\nChunk data type: 0x"+String::Format( "{0:X}", pCurrentChunkHeader->iDataChunkType );
		logbox->Text += "\nChunk flag: 0x"+String::Format( "{0:X}", pCurrentChunkHeader->iFlag );
		logbox->Text += "\nChunk size: "+pCurrentChunkHeader->iDataChunkSize.ToString()+" bytes";
		logbox->Text += "\nOffset to the next chunk: 0x"+String::Format( "{0:X}", pCurrentChunkHeader->iDataFinalOffset );
		logbox->Text += "\nChunk version: "+pCurrentChunkHeader->iDataChunkVer.ToString()+"\n\n";

		switch( pCurrentChunkHeader->iFlag ) {
			case 0x0E:
				iPostDataOffset = FLAG_0E;
			break;
			case 0x06:
				iPostDataOffset = FLAG_06;
			break;
			case 0x00:
				iPostDataOffset = FLAG_00;
			break;
			case 0x0B:
				iPostDataOffset = FLAG_0B;
			break;
			default:
				sprintf( szFilename, "Unknown chunk-header flag 0x%02x!\n\tChunk #%i @ 0x%02x bytes", pCurrentChunkHeader->iFlag, iCurrentChunk+1, iNextOffset );
				MessageBoxA( NULL, szFilename, "ERROR", MB_ICONERROR );
				return FALSE;
			break;
		}

		if( i == 0 ) iNextOffset = iFirstChunkOffset;
		fseek( pDBLFile, iNextOffset, 0 );
		for( INT y = 0; y < pCurrentChunkHeader->iDataChunkSize+iPostDataOffset; y++ ) {
			fread( &iByte, 1, 1, pDBLFile );
			fwrite( &iByte, 1, 1, pChunkFile );
		}
		iNextOffset = pCurrentChunkHeader->iDataFinalOffset;
		fclose( pChunkFile );
	}

	fclose( pDBLFile );

	return TRUE;
}

BOOL CreateDBL( const char* input, const char* output,
			   System::Windows::Forms::RichTextBox^ logbox, dbl_header_s* pParams ) {
	char szPath[256];
	char szFilename[256];
	strcpy( szPath, input );
	sprintf( szFilename, "%s\\*.chk", szPath );
	FILE* pDBLFile = fopen( output, "wb" );
	FILE* pChunkFile = NULL;
	HANDLE hFile = (HANDLE)1;
	char hPrevFile[256];
	INT iByte = 0;
	INT i = 0;
	WIN32_FIND_DATAA pFileData;

	logbox->Text = "Started parsing chunks from "+MakeString((char*)input);
	if( !pParams->iChunksNum ) { // Count the data chunks manually if we have to
		while( hFile ) {
			if( hFile == (HANDLE)1 ) hFile = FindFirstFileA(szFilename, &pFileData);
			FindNextFileA(hFile, &pFileData);
			if( !strcmp(pFileData.cFileName, hPrevFile) ) break;
			strcpy( hPrevFile, pFileData.cFileName );

			pParams->iChunksNum++;
		}
		hFile = (HANDLE)1;
	}

	logbox->Text += "\nLocated "+pParams->iChunksNum.ToString()+" chunks in the input folder";
	logbox->Text += "\nWriting the DBL header with specified parameters\n\n\n";

	iByte = 0x30; // Offset to the DBL header is 0
	fwrite( &iByte, 1, 1, pDBLFile );
	fseek( pDBLFile, 8, 0 ); // Skip the first 8 bytes of the header, they don't matter
	for( i = 0; i < 9; i++ ) { // Make sure the DBL ID is not too long
		if( pParams->dblId[i] == '\0' ) break;
		fwrite( &pParams->dblId[i], 1, 1, pDBLFile );
	}
	iByte = 0x00;
	for( ; i < 8; i++ ) { // Fill the rest with zeroes
		fwrite( &iByte, 1, 1, pDBLFile );
	}
	iByte = 256;
	fwrite( &iByte, 4, 1, pDBLFile ); // Separator which we don't know why it even exists
	iByte = 0x00;
	fwrite( &iByte, 4, 1, pDBLFile ); // Fill in the blanks with zeroes
	fwrite( &pParams->iChunksNum, 2, 1, pDBLFile ); // Add the specified number of chunks (?)
	iByte = 0x00;
	fwrite( &iByte, 4, 1, pDBLFile );
	fwrite( &iByte, 2, 1, pDBLFile ); // The rest is filled with zeroes

	while( hFile ) { // Start writing the data chunks
		if( hFile == (HANDLE)1 ) hFile = FindFirstFileA(szFilename, &pFileData);
		FindNextFileA(hFile, &pFileData);
		if( !strcmp(pFileData.cFileName, hPrevFile) ) break; // Make sure we don't keep writing the last
		strcpy( hPrevFile, pFileData.cFileName ); // file over and over again

		sprintf( szFilename, "%s\\%s", szPath, pFileData.cFileName );
		pChunkFile = fopen( szFilename, "rb" );
		if( !pChunkFile ) {
			logbox->Text += "\n!> FOPEN FAILED AT "+MakeString(pFileData.cFileName);
			return FALSE;
		}
		logbox->Text += "\nRead/write chunk @ "+MakeString(pFileData.cFileName);
		while( !feof(pChunkFile) ) { // Write each and every byte of the chunk
			if( !fread(&iByte, 1, 1, pChunkFile) ) break;
			fwrite( &iByte, 1, 1, pDBLFile );
		}
		fclose( pChunkFile );

		logbox->Text += "\nFinished";
		logbox->Text += "\n===============================";
	}

	fclose( pDBLFile );
	return TRUE;
}

dbl_chunk_header_s* LoadChunkFromFile( const char* chunk ) {
	dbl_chunk_header_s* pHeader = 0;
	FILE* pChunkFile = NULL;

	pChunkFile = fopen( chunk, "rb" );
	if( !pChunkFile ) return FALSE;
	pHeader = ReadChunkHeader( pChunkFile, 0, (dbl_header_s*)DBL_CHUNK_NODBLHEADER );
	fclose( pChunkFile );
	if( !pHeader ) return FALSE;
	return pHeader;
}

BOOL ChangeChunkHeader( const char* chunk, dbl_chunk_header_s* header, System::Windows::Forms::RichTextBox^ logbox ) {
	FILE* pChunk;

	if( !header ) {
		MessageBoxA( NULL, "dbl_chunk_header_s* header is NULL", "ERROR", MB_ICONERROR );
		return FALSE;
	}

	logbox->Text = "Attempting to re-write the header of the data-chunk file "+MakeString((char*)chunk)+":\n\n\n";

	pChunk = fopen( chunk, "rb+" );
	if( !pChunk ) {
		MessageBoxA( NULL, "Couldn't open the chunk-file", "ERROR", MB_ICONERROR );
		return FALSE;
	}

	if( header->iDataChunkType ) {
		fseek( pChunk, 0, 0 );
		fwrite( &header->iDataChunkType, 2, 1, pChunk );
		logbox->Text += "\nChanged chunk data-type to: "+String::Format( "{0:X}", header->iDataChunkType );
	}
	if( header->iFlag ) {
		fseek( pChunk, 2, 0 );
		fwrite( &header->iFlag, 2, 1, pChunk );
		logbox->Text += "\nChanged chunk flag to: "+String::Format( "{0:X}", header->iFlag );
	}
	if( header->iDataChunkSize ) {
		fseek( pChunk, 4, 0 );
		fwrite( &header->iDataChunkSize, 4, 1, pChunk );
		logbox->Text += "\nChanged chunk size to: "+String::Format( "{0:X}", header->iDataChunkSize );
	}
	if( header->iDataChunkVer ) {
		fseek( pChunk, 8, 0 );
		fwrite( &header->iDataChunkVer, 2, 1, pChunk );
		logbox->Text += "\nChanged chunk version to: "+header->iDataChunkVer.ToString();
	}

	fclose( pChunk );

	logbox->Text += "\nAll changes have been successfully written to the data-chunk file";
	logbox->Text += "\n===============================";

	return TRUE;
}

BOOL UpdateChunkSize( const char* chunk, System::Windows::Forms::RichTextBox^ logbox ) {
	char szFilename[256];
	FILE* pChunk; INT iChunkSize, iHeaderSize;
	BYTE iFlag;

	pChunk = fopen( chunk, "rb+" );
	if( !pChunk ) {
		MessageBoxA( NULL, "Couldn't open the chunk-file", "ERROR", MB_ICONERROR );
		return FALSE;
	}

	iChunkSize = iFlag = 0;

	fseek( pChunk, 2, 0 );
	fread( &iFlag, 1, 1, pChunk );
	switch( iFlag ) {
		case 0x0E:
			iHeaderSize = FLAG_0E;
		break;
		case 0x06:
			iHeaderSize = FLAG_06;
		break;
		case 0x00:
			iHeaderSize = FLAG_00;
		break;
		case 0x0B:
			iHeaderSize = FLAG_0B;
		break;
		default:
			sprintf( szFilename, "Unknown chunk-header flag 0x%02x!\n\tChunk #%i @ 0x%02x bytes", iFlag, iCurrentChunk+1, -1 );
			MessageBoxA( NULL, szFilename, "ERROR", MB_ICONERROR );
			return FALSE;
		break;
	}

	logbox->Text = "Calculating the data-chunk size...\n\n\n";

	fseek( pChunk, 0, SEEK_END );
	iChunkSize = ftell(pChunk)-iHeaderSize;
	fseek( pChunk, 4, 0 );
	fwrite( &iChunkSize, 4, 1, pChunk );

	fclose( pChunk );

	logbox->Text += "\nThe updated written size is: "+(iChunkSize+iHeaderSize).ToString()+" minus the header size";
	logbox->Text += "\n===============================";

	return TRUE;
}