#include <assert.h>
#include <sys/stat.h>
#include <algorithm>
#include "Types.h"
#include "StdStream.h"

static const char* g_files[] =
{
	"xa_MYROOM.adx",
	"xa_AREA_GRASS.adx",
	"xa_AREA_RELICS1.adx",
	"xa_GUARDIANS.adx",
	"xa_GOVERNOR.adx",
	"xa_PALM.adx",
	"xa_ENDRUM_COLLECTIVE.adx",
	"xa_NEWDAYZ.adx",
	"xa_MOTWOB.adx",
	"xa_TAILER.adx",
	"xa_SEED.adx",
	"xa_INFECTION_DARK.adx",
	"xa_INFECTION_FIRE.adx",
	"xa_INFECTION_ICE.adx",
	"xa_RELICS.adx",
	"xa_ANCIENT.adx",
	"xa_EVENT_MISTERY.adx",
	"xa_EVENT_VIOLENT.adx",
	"xa_EVENT_TENSION.adx",
	"xa_EVENT_UNEASINESS.adx",
	"xa_EVENT_COMEDY.adx",
	"xa_EVENT_SADNESS.adx",
	"xa_EVENT_RESOLUTION.adx",
	"xa_EVENT_BRAVEFIGHT.adx",
	"xa_EVENT_EPILOGUE1.adx",
	"xa_EVENT_EPILOGUE2.adx",
	"xa_GUARDIANS_CITY.adx",
	"xa_PALM_CITY.adx",
	"xa_NEWDAIZ_CITY.adx",
	"xa_MOTWOB_CITY.adx",
	"xa_CITY_MARRIAGE.adx",
	"xa_CITY_CARNIVAL.adx",
	"xa_CITY_NEWYEAR.adx",
	"xa_CITY_VALENTINE.adx",
	"xa_CITY_BLOSSOM.adx",
	"xa_CITY_HALLOWEEN.adx",
	"xa_CITY_CHRISTMAS.adx",
	"xa_AREA_PLANT.adx",
	"xa_AREA_TRAIN.adx",
	"xa_AREA_TEMPLE.adx",
	"xa_AREA_ISLAND.adx",
	"xa_AREA_LEAVES.adx",
	"xa_AREA_RAVINE.adx",
	"xa_AREA_DESERT.adx",
	"xa_AREA_DARKPLANET.adx",
	"xa_AREA_LINEARLINE.adx",
	"xa_AREA_RELICS2.adx",
	"xa_BOSS_ZACO1.adx",
	"xa_BOSS_ZACO2.adx",
	"xa_BOSS_DRAGON.adx",
	"xa_BOSS_GYAOS.adx",
	"xa_BOSS_MACHINE.adx",
	"xa_BOSS_MAGASHI1.adx",
	"xa_BOSS_MAGASHI2.adx",
	"xa_BOSS_ONLINE.adx",
	"xa_VEHICLE_BIKE.adx",
	"xa_VEHICLE_CAR.adx",
	"xa_VEHICLE_ANIMAL.adx",
	"xa_CHARAMAKE.adx",
	"xa_FILESELECT.adx",
	"xa_THEME_VOCAL.adx",
	"xa_THEME_LONG.adx",
	"xa_THEME_SHORT.adx",
	"xa_ENDING1_VOCAL.adx",
	"xa_ENDING1_ORCH.adx",
	"xa_ENDING1_PIANO.adx",
	"xa_ENDING2_VOCAL.adx",
	"xa_ENDING2_ORCH.adx",
	"xa_ENDING2_REMIX.adx",
	"xa_MICO1.adx",
	"xa_MICO2.adx",
	"xa_EVENT_COMEDY1.adx",
	"xa_EVENT_COMEDY2.adx",
	"xa_EVENT_COMEDY3.adx",
	"xa_EVENT_COMEDY4.adx",
	"xa_G_MARCH.adx",
	"xa_RESULT_GOOD.adx",
	"xa_RESULT_BAD.adx",
	"xa_GAMEOVER.adx",
	"xa_THEME_BALLADE.adx",
	"xa_bgm000.adx",
	"xa_bgm001.adx",
	"xa_bgm002.adx",
	"xa_bgm003.adx",
	"xa_bgm004.adx",
	"xa_bgm005.adx",
	"xa_bgm006.adx",
	"xa_bgm007.adx",
	"xa_bgm008.adx",
	"xa_bgm009.adx",
	"xa_bgm010.adx",
	"xa_bgm011.adx",
	"xa_bgm012.adx",
	"xa_bgm013.adx",
	"xa_bgm014.adx",
	"xa_AREA_CASINO.adx",
	"xa_AREA_FUJI.adx",
	"xa_AREA_NEUDAIZ_FOREST.adx",
	"xa_AREA_RUINED_PALM.adx",
	"xa_AREA_SEA.adx",
	"xa_ARIA_SEA.adx",
	"xa_BOSS_DRAGON2.adx",
	"xa_BOSS_MOTHER.adx",
	"xa_BOSS_NEW_MAGASHI.adx",
	"xa_BOSS_WARM.adx",
	"xa_EATHAN.adx",
	"xa_ILLUMINAS.adx",
	"xa_RAIA.adx",
	"xa_THEME_E3.adx",
	"xa_PSO_CAVE.adx",
	"xa_PSO_FOREST.adx",
	"xa_PSO_MACHINE.adx",
	"xa_PSO_SHIP.adx",
	NULL,
};

#define BUFFER_SIZE		0x9000

uint32 g_crcTable[256];

void DecryptToc()
{
	uint8* buffer = new uint8[BUFFER_SIZE];
	{
		Framework::CStdStream stream("DecryptData.dat", "rb");
		stream.Read(buffer, BUFFER_SIZE);
	}


	uint32 t1 = 0;		//buffer
	uint32 t3 = reinterpret_cast<uint32*>(buffer)[0];
	uint32 v1 = 0;		//buffer
	uint32 a2 = 0;
	uint32 a0 = 0x32;

loop1:
	uint32 v0 = reinterpret_cast<uint32*>(buffer + v1)[0];
	a0 -= 1;
	v1 += 4;
	a2 += v0;
	if(static_cast<int32>(a0) >= 0) goto loop1;

	v0 = 0xB638F4A6;
	v1 = 0xEFDFBF76;
	v0 = a2 - v0;
	uint32 a1 = BUFFER_SIZE >> 2;
	uint32 t0 = 1;
	v0 += v1;
	a0 = t0 < a1;
	uint32 a3 = a2;

	reinterpret_cast<uint32*>(buffer + t1)[0] = v0;
	a2 = t1 + 4;
	if(a0 == false) goto done;
	uint32 t2 = 0xF77FAA55;
	t1 = v1;

loop2:
	v0 = t0 ^ t2;
	v1 = t0 + 0x6F1;
	v0 += 1;
	v0 = v0 * v1;
	a0 = t0 + 0x3D09;
	v1 = reinterpret_cast<uint32*>(buffer + a2 + 0xC8)[0];
	a3 = a3 - t3;
	t0 += 1;
	v0 = v0 * a0;
	a3 += v1;
	a0 = t0 < a1;
	t3 = reinterpret_cast<uint32*>(buffer + a2)[0];
	v0 = a3 - v0;
	v0 += t1;
	reinterpret_cast<uint32*>(buffer + a2)[0] = v0;
	a2 += 4;
	if(a0 != false) goto loop2;

done:
	{
		Framework::CStdStream stream("toc.dat", "wb");
		stream.Write(buffer, BUFFER_SIZE);
	}

	delete [] buffer;
}

bool ExtractFiles(uint32 targetCrc, const char* fileName)
{
	bool found = false;

	uint8* buffer = new uint8[BUFFER_SIZE];
	{
		Framework::CStdStream stream("toc.dat", "rb");
		stream.Read(buffer, BUFFER_SIZE);
	}

	uint32* currentPtr = reinterpret_cast<uint32*>(buffer + 0x4008);

	Framework::CStdStream packFile("H:\\PSP_GAME\\USRDIR\\file.fpb", "rb");

	while(!found)
	{
		uint32 crc		= currentPtr[0];
		uint32 begin	= currentPtr[1] & ~0x7FF;
		uint32 sizeRem	= 0x800 - (currentPtr[1] &  0x7FF);
		uint32 end		= currentPtr[3] & ~0x7FF;
		if(end == 0) break;
		currentPtr += 2;

		assert(end >= begin);
		uint32 sectorCount = (end / 0x800) - (begin / 0x800) - 1;
		uint32 size = (sectorCount * 0x800) + sizeRem;

		if(crc == targetCrc)
		{
			char outputPath[256];
			sprintf(outputPath, "files/%s", fileName);

			//struct _stat result;
			//if(_stat(outputPath, &result) != -1)
			//{
			//	printf("Warning, file %0.8X exists two times.\r\n", crc);
			//}

			packFile.Seek(begin, Framework::STREAM_SEEK_SET);
			Framework::CStdStream output(outputPath, "wb");

//			bool isFirst = true;
			while(size != 0)
			{
				const int bufferSize = 0x4000;
				uint8 buffer[bufferSize];
				uint32 toRead = std::min<uint32>(bufferSize, size);
				packFile.Read(buffer, toRead);
				//if(isFirst)
				//{
				//	if(buffer[0] != 'A' || buffer[1] != 'F' || buffer[2] != 'S')
				//	{
				//		break;
				//	}
				//	isFirst = false;
				//}
				output.Write(buffer, toRead);
				size -= toRead;
			}

			found = true;
		}
	}

	delete [] buffer;

	return found;
}

uint32 ComputeCrc(const char* name)
{
	uint32 a0 = 0;
	int32 a1 = strlen(name);
	const uint8* a2 = reinterpret_cast<const uint8*>(name);
	uint32* a3 = 0;

	int32 v0 = 0;
	int32 v1 = 0;

	a1 -= 1;
//	a2 = a0;
	a0 = -1;
	if(a1 < 0) goto label_42c;

	a3 = g_crcTable;

label_400:
	v1 = (*a2);
	v0 = a0 & 0xFF;
	a1 -= 1;
	v0 ^= v1;
//	v0 <<= 2;
	v1 = a3[v0];
	a0 >>= 8;
	a2 += 1;
	a0 ^= v1;
	if(a1 >= 0) goto label_400;

label_42c:
	return ~a0;
}

void GenerateCrcTable()
{
	int32 v0 = 0;
	uint32 v1 = 0;
	int32 a0 = 0;
	uint32 a1 = 0;
	int32 a2 = 0;
	int32 a3 = 0;
	uint32* t0 = NULL;

	a3 = 0xEDB88320;
	t0 = g_crcTable;
	a2 = 0;

label_14c:
	v1 = a2;
	a0 = 7;
	v0 = v1 & 1;

label_158:
	a1 = v1 >> 1;
	v1 = v1 >> 1;
	if(v0 == 0) goto label_168;
	v1 = a1 ^ a3;

label_168:
	a0 -= 1;
	v0 = v1 & 1;
	if(a0 >= 0) goto label_158;

	a2 += 1;
	v0 = a2 < 0x100;
	(*t0) = v1;
	t0++;
	if(v0 != 0) goto label_14c;
}

void ExtractMidiFiles()
{
	Framework::CStdStream packFile("H:\\PSP_GAME\\USRDIR\\file.fpb", "rb");
	unsigned int currentFileId = 0;
	while(1)
	{
		char headerSig[16];
		uint64 filePos = packFile.Tell();
		printf("%d\r", filePos);
		packFile.Read(headerSig, 16);
		if(packFile.IsEOF()) break;
		if(!strncmp(headerSig, "BIND", 4))
		{
			uint32 header[4];
			packFile.Read(header, 0x10);
			uint32 smfPos = header[2];
			uint32 smfLength = header[3];

			char midiFileHeader[5];
			packFile.Seek(filePos + smfPos, Framework::STREAM_SEEK_SET);
			packFile.Read(midiFileHeader, 4);
			midiFileHeader[4] = 0;
			
			if(!strcmp(midiFileHeader, "MThd"))
			{
				//OK, dump file

				char outputPath[256];
				sprintf(outputPath, "files/%0.4d.adx", currentFileId++);
				Framework::CStdStream output(outputPath, "wb");
				packFile.Seek(filePos, Framework::STREAM_SEEK_SET);

				uint64 size = smfPos + smfLength;

				while(size != 0)
				{
					const int bufferSize = 0x4000;
					uint8 buffer[bufferSize];
					uint32 toRead = std::min<uint32>(bufferSize, size);
					packFile.Read(buffer, toRead);
					output.Write(buffer, toRead);
					size -= toRead;
				}
			}

			packFile.Seek(filePos + 0x10, Framework::STREAM_SEEK_SET);
		}
	}
}

int main(int argc, char** argv)
{
	ExtractMidiFiles();
	return 0;

	//DecryptToc();
	GenerateCrcTable();
	const char** currentFile = g_files;
	while(*currentFile != NULL)
	{
		uint32 crc = ComputeCrc(*currentFile);
		bool result = ExtractFiles(crc, *currentFile);
		if(!result)
		{
			printf("Warning: Couldn't extract file '%s'.\r\n", *currentFile);
		}
		currentFile++;
	}
}
