/* ScummVM Tools
 * Copyright (C) 2007 The ScummVM project
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *
 */

#include "create_kyradat.h"

#include "md5.h"

enum {
	kKyraDatVersion = 16,
	kIndexSize = 12
};

// tables

#include "misc.h"
#include "eng.h"
#include "esp.h"
#include "fre.h"
#include "ger.h"
#include "towns.h"

bool extractRaw(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch = 0);
bool extractStrings(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch = 0);
bool extractRooms(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch = 0);
bool extractShapes(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch = 0);

void createFilename(char *dstFilename, const int lang, const int special, const char *filename);
void createLangFilename(char *dstFilename, const int lang, const int special, const char *filename);

const ExtractType extractTypeTable[] = {
	{ kTypeLanguageList, extractStrings, createLangFilename },
	{ kTypeStringList, extractStrings, createFilename },
	{ kTypeRoomList, extractRooms, createFilename },
	{ kTypeShapeList, extractShapes, createFilename },
	{ kTypeRawData, extractRaw, createFilename },
	{ -1, 0 }
};

const ExtractFilename extractFilenames[] = {
	// INTRO / OUTRO sequences
	{ kForestSeq, kTypeRawData, "FOREST.SEQ" },
	{ kKallakWritingSeq, kTypeRawData, "KALLAK-WRITING.SEQ" },
	{ kKyrandiaLogoSeq, kTypeRawData, "KYRANDIA-LOGO.SEQ" },
	{ kKallakMalcolmSeq, kTypeRawData, "KALLAK-MALCOLM.SEQ" },
	{ kMalcolmTreeSeq, kTypeRawData, "MALCOLM-TREE.SEQ" },
	{ kWestwoodLogoSeq, kTypeRawData, "WESTWOOD-LOGO.SEQ" },
	{ kDemo1Seq, kTypeRawData, "DEMO1.SEQ" },
	{ kDemo2Seq, kTypeRawData, "DEMO2.SEQ" },
	{ kDemo3Seq, kTypeRawData, "DEMO3.SEQ" },
	{ kDemo4Seq, kTypeRawData, "DEMO4.SEQ" },
	{ kOutroReunionSeq, kTypeRawData, "REUNION.SEQ" },

	// INTRO / OUTRO strings
	{ kIntroCPSStrings, kTypeStringList, "INTRO-CPS.TXT" },
	{ kIntroCOLStrings, kTypeStringList, "INTRO-COL.TXT" },
	{ kIntroWSAStrings, kTypeStringList, "INTRO-WSA.TXT" },
	{ kIntroStrings, kTypeLanguageList, "INTRO-STRINGS" },
	{ kOutroHomeString, kTypeLanguageList, "HOME" },

	// INGAME strings
	{ kItemNames, kTypeLanguageList, "ITEMLIST" },
	{ kTakenStrings, kTypeLanguageList, "TAKEN" },
	{ kPlacedStrings, kTypeLanguageList, "PLACED" },
	{ kDroppedStrings, kTypeLanguageList, "DROPPED" },
	{ kNoDropStrings, kTypeLanguageList, "NODROP" },
	{ kPutDownString, kTypeLanguageList, "PUTDOWN" },
	{ kWaitAmuletString, kTypeLanguageList, "WAITAMUL" },
	{ kBlackJewelString, kTypeLanguageList, "BLACKJEWEL" },
	{ kPoisonGoneString, kTypeLanguageList, "POISONGONE" },
	{ kHealingTipString, kTypeLanguageList, "HEALINGTIP" },
	{ kThePoisonStrings, kTypeLanguageList, "THEPOISON" },
	{ kFluteStrings, kTypeLanguageList, "FLUTE" },
	{ kWispJewelStrings, kTypeLanguageList, "WISPJEWEL" },
	{ kMagicJewelStrings, kTypeLanguageList, "MAGICJEWEL" },
	{ kFlaskFullString, kTypeLanguageList, "FLASKFULL" },
	{ kFullFlaskString, kTypeLanguageList, "FULLFLASK" },
	{ kVeryCleverString, kTypeLanguageList, "VERYCLEVER" },
	{ kNewGameString, kTypeLanguageList, "NEWGAME" },

	// GUI strings table
	{ kGUIStrings, kTypeLanguageList, "GUISTRINGS" },
	{ kConfigStrings, kTypeLanguageList, "CONFIGSTRINGS" },
	
	// ROOM table/filenames
	{ kRoomList, kTypeRoomList, "ROOM-TABLE.ROOM" },
	{ kRoomFilenames, kTypeStringList, "ROOM-FILENAMES.TXT" },

	// SHAPE tables
	{ kDefaultShapes, kTypeShapeList, "SHAPES-DEFAULT.SHP" },
	{ kHealing1Shapes, kTypeShapeList, "HEALING.SHP" },
	{ kHealing2Shapes, kTypeShapeList, "HEALING2.SHP" },
	{ kPoisonDeathShapes, kTypeShapeList, "POISONDEATH.SHP" },
	{ kFluteShapes, kTypeShapeList, "FLUTE.SHP" },
	{ kWinter1Shapes, kTypeShapeList, "WINTER1.SHP" },
	{ kWinter2Shapes, kTypeShapeList, "WINTER2.SHP" },
	{ kWinter3Shapes, kTypeShapeList, "WINTER3.SHP" },
	{ kDrinkShapes, kTypeShapeList, "DRINK.SHP" },
	{ kWispShapes, kTypeShapeList, "WISP.SHP" },
	{ kMagicAnimShapes, kTypeShapeList, "MAGICANIM.SHP" },
	{ kBranStoneShapes, kTypeShapeList, "BRANSTONE.SHP" },

	// IMAGE filename table
	{ kCharacterImageFilenames, kTypeStringList, "CHAR-IMAGE.TXT" },

	// AMULET anim
	{ kAmuleteAnimSeq, kTypeRawData, "AMULETEANIM.SEQ" },

	// PALETTE table
	{ kPaletteList1, kTypeRawData, "PALTABLE1.PAL" },
	{ kPaletteList2, kTypeRawData, "PALTABLE2.PAL" },
	{ kPaletteList3, kTypeRawData, "PALTABLE3.PAL" },
	{ kPaletteList4, kTypeRawData, "PALTABLE4.PAL" },
	{ kPaletteList5, kTypeRawData, "PALTABLE5.PAL" },
	{ kPaletteList6, kTypeRawData, "PALTABLE6.PAL" },
	{ kPaletteList7, kTypeRawData, "PALTABLE7.PAL" },
	{ kPaletteList8, kTypeRawData, "PALTABLE8.PAL" },
	{ kPaletteList9, kTypeRawData, "PALTABLE9.PAL" },
	{ kPaletteList10, kTypeRawData, "PALTABLE10.PAL" },
	{ kPaletteList11, kTypeRawData, "PALTABLE11.PAL" },
	{ kPaletteList12, kTypeRawData, "PALTABLE12.PAL" },
	{ kPaletteList13, kTypeRawData, "PALTABLE13.PAL" },
	{ kPaletteList14, kTypeRawData, "PALTABLE14.PAL" },
	{ kPaletteList15, kTypeRawData, "PALTABLE15.PAL" },
	{ kPaletteList16, kTypeRawData, "PALTABLE16.PAL" },
	{ kPaletteList17, kTypeRawData, "PALTABLE17.PAL" },
	{ kPaletteList18, kTypeRawData, "PALTABLE18.PAL" },
	{ kPaletteList19, kTypeRawData, "PALTABLE19.PAL" },
	{ kPaletteList20, kTypeRawData, "PALTABLE20.PAL" },
	{ kPaletteList21, kTypeRawData, "PALTABLE21.PAL" },
	{ kPaletteList22, kTypeRawData, "PALTABLE22.PAL" },
	{ kPaletteList23, kTypeRawData, "PALTABLE23.PAL" },
	{ kPaletteList24, kTypeRawData, "PALTABLE24.PAL" },
	{ kPaletteList25, kTypeRawData, "PALTABLE25.PAL" },
	{ kPaletteList26, kTypeRawData, "PALTABLE26.PAL" },
	{ kPaletteList27, kTypeRawData, "PALTABLE27.PAL" },
	{ kPaletteList28, kTypeRawData, "PALTABLE28.PAL" },
	{ kPaletteList29, kTypeRawData, "PALTABLE29.PAL" },
	{ kPaletteList30, kTypeRawData, "PALTABLE30.PAL" },
	{ kPaletteList31, kTypeRawData, "PALTABLE31.PAL" },
	{ kPaletteList32, kTypeRawData, "PALTABLE32.PAL" },
	{ kPaletteList33, kTypeRawData, "PALTABLE33.PAL" },
	
	// FM-TOWNS specific
	{ kKyra1TownsSFXTable, kTypeRawData, "SFXTABLE" },
	{ kCreditsStrings, kTypeRawData, "CREDITS" },
	{ kMenuSKB, kTypeStringList, "MENUSKB" },
	{ kSjisVTable, kTypeRawData, "SJISTABLE" },

	{ -1, 0, 0 }
};

const ExtractFilename *getFilenameDesc(const int id) {
	for (const ExtractFilename *i = extractFilenames; i->id != -1; ++i) {
		if (i->id == id)
			return i;
	}
	return 0;
}

// type processing

const ExtractType *findExtractType(const int type) {
	for (const ExtractType *i = extractTypeTable; i->type != -1; ++i) {
		if (i->type == type)
			return i;
	}
	return 0;
}

// filename processing

bool getFilename(char *dstFilename, const Game *g, const int id) {
	const ExtractFilename *i = getFilenameDesc(id);

	if (!i)
		return false;

	const ExtractType *type = findExtractType(i->type);
	type->createFilename(dstFilename, g->lang, g->special, i->filename);
	return true;
}

void createFilename(char *dstFilename, const int lang, const int special, const char *filename) {
	strcpy(dstFilename, filename);
	
	for (const SpecialExtension *specialE = specialTable; specialE->special != -1; ++specialE) {
		if (specialE->special == special) {
			strcat(dstFilename, ".");
			strcat(dstFilename, specialE->ext);
			break;
		}
	}
}

void createLangFilename(char *dstFilename, const int lang, const int special, const char *filename) {
	strcpy(dstFilename, filename);
	
	for (const Language *langE = languageTable; langE->lang != -1; ++langE) {
		if (langE->lang == lang) {
			strcat(dstFilename, ".");
			strcat(dstFilename, langE->ext);
			break;
		}
	}
	
	for (const SpecialExtension *specialE = specialTable; specialE->special != -1; ++specialE) {
		if (specialE->special == special) {
			strcat(dstFilename, ".");
			strcat(dstFilename, specialE->ext);
			break;
		}
	}
}

// entry checking

int hashEntries(const int *entries) {
	int hash = 0;
	for (const int *i = entries; *i != -1; ++i) {
		hash += *i;
	}
	return hash;
}

bool hasEntry(const ExtractEntry *entries, const int id) {
	for (const ExtractEntry *i = entries; i->id != -1; ++i) {
		if (i->id == id)
			return true;
	}
	return false;
}

int hashEntries(const Game *game, const GameNeed *need, const PAKFile *file) {
	int hash = 0;
	char filename[128];
	for (const int *i = need->entries; *i != -1; ++i) {
		if (hasEntry(game->entries, *i)) {
			hash += *i;
			continue;
		}
		
		if (file) {
			filename[0] = 0;

			if (!getFilename(filename, game, *i))
				error("couldn't find filename for id %d", *i);
			
			PAKFile::cFileList *list = file->getFileList();
			if (list && list->findEntry(filename) != 0)
				hash += *i;
		}
	}

	return hash;
} 

bool hasNeededEntries(const Game *game, const PAKFile *file) {
	for (const GameNeed *need = gameNeedTable; need->game != -1; ++need) {
		if (need->game == game->game && need->special == game->special) {
			if (hashEntries(need->entries) == hashEntries(game, need, file))
				return true;
		}
	}

	return false;
}

// extraction

bool extractRaw(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch) {
	uint8 *buffer = 0;

	if (fmtPatch == 2) {
		buffer = new uint8[0x12602];
		assert(buffer);
		memcpy(buffer, data, 0x7EE5);
		memcpy(buffer + 0x7EE5, data + 0x7EE7, 0x7FFF);
		memcpy(buffer + 0xFEE4, data + 0xFEE8, 0x271E);
	} else {
		buffer = new uint8[size];
		assert(buffer);
		memcpy(buffer, data, size);
	}

	return out.addFile(filename, buffer, size);
}

bool extractStrings(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch) {
	uint32 entries = 0;
	uint32 targetsize = size + 4;
	for (uint32 i = 0; i < size; ++i) {
		if (!data[i]) {
			++entries;
			if (g->special == kFMTownsVersionE || g->special == kFMTownsVersionJ) {
				// prevents creation of empty entries (which we have mostly between all strings in the fm-towns version)
				while (!data[++i]) {
					if (i == size)
						break;
					targetsize--;
				}
				if (fmtPatch == 1) {
					// Here is the first step of the extra treatment for all fm-towns string arrays that 
					// contain more than one string and which the original code
					// addresses via stringname[boolJapanese].
					// We simply skip every other string
					if (i == size)
						continue;
					uint32 size = strlen((const char*) data + i);
					i += size; targetsize = --targetsize - size;
					while (!data[++i]) {
						if (i == size)
							break;
						targetsize--;
					}
				}
			}
		}
	}
	
	if (fmtPatch == 2) {
		if (g->special == kFMTownsVersionE)
			targetsize--;
		if (g->special == kFMTownsVersionJ)
			targetsize += 2;		
		entries += (g->special - 1);
	}
	
	uint8 *buffer = new uint8[targetsize];
	assert(buffer);
	uint8 *output = buffer;
	uint8 *input = (uint8*) data;

	WRITE_BE_UINT32(output, entries); output += 4;
	if (g->special == kFMTownsVersionE || g->special == kFMTownsVersionJ) {
		const byte * c = data + size;
		do {
			if (fmtPatch == 2 && input - data == 0x3C0 && input[0x10] == 0x32) {
				memcpy(output, input, 0x0F);
				input += 0x11; output += 0x0F;
			}
			strcpy((char*) output, (const char*) input);
			uint32 stringsize = strlen((const char*)output) + 1;
			input += stringsize; output += stringsize;
			// skip empty entries
			while (!*input) {
				// Write one empty string into intro strings file
				if (fmtPatch == 2) {
					if ((g->special == kFMTownsVersionE && input - data == 0x260) ||
						(g->special == kFMTownsVersionJ && input - data == 0x2BD) ||
						(g->special == kFMTownsVersionJ && input - data == 0x2BE))
							*output++ = *input;
				}

				if (++input == c)
					break;
			}

			if (fmtPatch == 1) {
				// Here is the extra treatment for all fm-towns string arrays that 
				// contain more than one string and which the original code
				// addresses via stringname[boolJapanese].
				// We simply skip every other string
				if (input == c)
					continue;
				input += strlen((const char*)input);
				while (!*input) {
					if (++input == c)
						break;
				}
			}

		} while (input < c);
	} else {
		memcpy(output, data, size);
	}

	return out.addFile(filename, buffer, targetsize);
}

bool extractRooms(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch) {
	// different entry size for the fm-towns version
	const int countRooms = (g->special == kFMTownsVersionE) ? (size / 0x69) : (size / 0x51);

	uint8 *buffer = new uint8[countRooms * 9 + 4];
	assert(buffer);
	uint8 *output = buffer;

	WRITE_BE_UINT32(output, countRooms); output += 4;

	const byte *src = data;
	for (int i = 0; i < countRooms; ++i) {
		*output++ = *src++;
		WRITE_BE_UINT16(output, READ_LE_UINT16(src)); output += 2; src += 2;
		WRITE_BE_UINT16(output, READ_LE_UINT16(src)); output += 2; src += 2;
		WRITE_BE_UINT16(output, READ_LE_UINT16(src)); output += 2; src += 2;
		WRITE_BE_UINT16(output, READ_LE_UINT16(src)); output += 2; src += 2;
		// different entry size for the fm-towns version
		src += (g->special == kFMTownsVersionE) ? 0x60 : (0x51 - 9);
	}

	return out.addFile(filename, buffer, countRooms * 9 + 4);
}

bool extractShapes(PAKFile &out, const Game *g, const byte *data, const uint32 size, const char *filename, int fmtPatch) {
	byte *buffer = new byte[size + 1 * 4];
	assert(buffer);
	byte *output = buffer;

	const int count = size / 0x07;
	WRITE_BE_UINT32(output, count); output += 4;
	memcpy(output, data, size);

	return out.addFile(filename, buffer, size + 1 * 4);
}

// index generation

enum {
	GF_FLOPPY	= 1 <<  0,
	GF_TALKIE	= 1 <<  1,
	GF_FMTOWNS	= 1 <<  2,
	GF_DEMO		= 1 <<  3,
	GF_ENGLISH	= 1 <<  4,
	GF_FRENCH	= 1 <<  5,
	GF_GERMAN	= 1 <<  6,
	GF_SPANISH	= 1 <<  7,
	GF_ITALIAN	= 1 <<  8,
	GF_JAPANESE = 1 <<  9,
	// ...
	GF_LNGUNK	= 1 << 16,
	GF_AMIGA	= 1 << 17
};

uint32 getFeatures(const Game *g) {
	uint32 features = 0;

	if (g->special == kTalkieVersion)
		features |= GF_TALKIE;
	else if (g->special == kDemoVersion)
		features |= GF_DEMO;
	else if (g->special == kFMTownsVersionE || g->special == kFMTownsVersionJ)
		features |= GF_FMTOWNS;
	else
		features |= GF_FLOPPY;

	if (g->lang == EN_ANY)
		features |= GF_ENGLISH;
	else if (g->lang == DE_DEU)
		features |= GF_GERMAN;
	else if (g->lang == FR_FRA)
		features |= GF_FRENCH;
	else if (g->lang == ES_ESP)
		features |= GF_SPANISH;
	else if (g->lang == IT_ITA)
		features |= GF_ITALIAN;
	else if (g->lang == JA_JPN)
		features |= GF_JAPANESE;
	
	return features;
}

bool updateIndex(byte *dst, const int dstSize, const Game *g) {
	if ((size_t)dstSize < kIndexSize)
		return false;

	WRITE_BE_UINT32(dst, kKyraDatVersion); dst += 4;
	WRITE_BE_UINT32(dst, g->game); dst += 4;
	uint32 features = READ_BE_UINT32(dst);
	features |= getFeatures(g);
	WRITE_BE_UINT32(dst, features); dst += 4;

	return true;
}

bool checkIndex(const byte *s, const int srcSize) {
	if ((size_t)srcSize < sizeof(uint32))
		return false;	
	uint32 version = READ_BE_UINT32(s);
	return (version == kKyraDatVersion);
}

bool updateIndex(PAKFile &out, const Game *g) {
	char filename[32];
	createFilename(filename, -1, g->special, "INDEX");
	
	byte *index = new byte[kIndexSize];
	assert(index);
	memset(index, 0, kIndexSize);
	
	uint32 size = 0;
	const uint8 *data = out.getFileData(filename, &size);
	if (data)
		memcpy(index, data, size);
	
	if (!updateIndex(index, kIndexSize, g)) {
		delete [] index;
		return false;
	}
	
	out.removeFile(filename);
	if (!out.addFile(filename, index, kIndexSize)) {
		fprintf(stderr, "ERROR: couldn't update %s file", filename);
		delete [] index;
		return false;
	}
	
	return true;
}

bool checkIndex(PAKFile &out, const Game *g) {
	char filename[32];
	createFilename(filename, -1, g->special, "INDEX");

	uint32 size = 0;
	const uint8 *data = out.getFileData(filename, &size);
	if (!data)
		return true;

	return checkIndex(data, size);
}

// main processing

void printHelp(const char *f) {
	printf("Usage:\n");
	printf("%s output inputfiles ...", f);
}

bool process(PAKFile &out, const Game *g, const byte *data, const uint32 size);
const Game *findGame(const byte *buffer, const uint32 size);

int main(int argc, char *argv[]) {
	if (argc < 3) {
		printHelp(argv[0]);
		return -1;
	}
	
	PAKFile out;
	out.loadFile(argv[1], false);

	for (int i = 2; i < argc; ++i) {
		FILE *input = fopen(argv[i], "rb");

		if (!input) {
			warning("skipping missing file '%s'", argv[i]);
			continue;
		}

		uint32 size = fileSize(input);
		fseek(input, 0, SEEK_SET);
		
		byte *buffer = new uint8[size];
		assert(buffer);
		
		if (fread(buffer, 1, size, input) != size) {
			warning("couldn't read from file '%s', skipping it", argv[i]);
			delete [] buffer;
			fclose(input);
			continue;
		}
		fclose(input);
		
		const Game *g = findGame(buffer, size);
		if (!g) {
			warning("skipping unknown file '%s'", argv[i]);
			delete [] buffer;
			continue;
		}
		
		if (!hasNeededEntries(g, &out)) {
			warning("file '%s' is missing offset entries and thus can't be processed", argv[i]);
			delete [] buffer;
			continue;
		}
		
		if (!process(out, g, buffer, size))
			fprintf(stderr, "ERROR: couldn't process file '%s'", argv[i]);
		
		if (g->special == kFMTownsVersionE) {
			// The English and non language specific data has now been extracted
			// so we switch to Japanese and extract the rest
			if (!hasNeededEntries(++g, &out)) {
				warning("file '%s' is missing offset entries and thus can't be processed", argv[i]);
				delete [] buffer;
				continue;
			}
			if (!process(out, g, buffer, size))
				fprintf(stderr, "ERROR: couldn't process file '%s'", argv[i]);
		}
		
		delete [] buffer;
	}

	if (!out.saveFile(argv[1]))
		error("couldn't save changes to '%s'", argv[1]);

	uint8 digest[16];
	if (!md5_file(argv[1], digest, 0))
		error("couldn't calc. md5 for file '%s'", argv[1]);
	FILE *f = fopen(argv[1], "ab");
	if (!f)
		error("couldn't open file '%s'", argv[1]);
	if (fwrite(digest, 1, 16, f) != 16)
		error("couldn't write md5sum to file '%s'", argv[1]);
	fclose(f);
	
	return 0;
}

bool process(PAKFile &out, const Game *g, const byte *data, const uint32 size) {
	char filename[128];

	if (!checkIndex(out, g)) {
		fprintf(stderr, "ERROR: corrupted INDEX file\n");
		return false;
	}

	for (const ExtractEntry *i = g->entries; i->id != -1; ++i) {
		if (!getFilename(filename, g, i->id)) {
			fprintf(stderr, "ERROR: couldn't get filename for id %d\n", i->id);
			return false;
		}
		
		const ExtractFilename *fDesc = getFilenameDesc(i->id);

		if (!fDesc) {
			fprintf(stderr, "ERROR: couldn't find file description for id %d\n", i->id);
			return false;
		}

		const ExtractType *tDesc = findExtractType(fDesc->type);
		
		if (!tDesc) {
			fprintf(stderr, "ERROR: couldn't find type description for id %d\n", i->id);
			return false;
		}
		
		PAKFile::cFileList *list = out.getFileList();
		if (list && list->findEntry(filename) != 0)
			continue;
		
		int patch = 0;
		if (g->special == kFMTownsVersionE || g->special == kFMTownsVersionJ) {
			// FM Towns files that need addional patches
			if (i->id == kTakenStrings || i->id == kNoDropStrings || i->id == kPoisonGoneString ||
				i->id == kThePoisonStrings || i->id == kFluteStrings || i->id == kWispJewelStrings)
				patch = 1;
			else if (i->id == kIntroStrings || i->id == kKyra1TownsSFXTable)
				patch = 2;						
		}
		
		if (!tDesc->extract(out, g, data + i->startOff, i->endOff - i->startOff, filename, patch)) {
			fprintf(stderr, "ERROR: couldn't extract id %d\n", i->id);
			return false;
		}
	}

	if (!updateIndex(out, g)) {
		error("couldn't update INDEX file, stop processing of all files");
		return false;
	}

	return true;
}

// game data detection

const Game *gameDescs[] = {
	kyra1EngGames,
	kyra1EspGames,
	kyra1FreGames,
	kyra1GerGames,
	kyra1TownsGames,
	0
};

const Game *findGame(const byte *buffer, const uint32 size) {
	md5_context ctx;
	uint8 digest[16];
	char md5str[33];

	md5_starts(&ctx);
	md5_update(&ctx, buffer, size);
	md5_finish(&ctx, digest);
	
	for (int j = 0; j < 16; ++j) {
		sprintf(md5str + j*2, "%02x", (int)digest[j]);
	}
	
	for (const Game **i = gameDescs; *i != 0; ++i) {
		for (const Game *p = *i; p->game != -1; ++p) {
			if (strcmp(md5str, p->md5) == 0)
				return p;
		}
	}

	printf("file is not supported (unknown md5 \"%s\")\n", md5str);
	return 0;
}