scummvm/tools/create_kyradat/create_kyradat.cpp
Max Horn b0fe833208 Fixed const warning
svn-id: r29021
2007-09-22 11:47:51 +00:00

761 lines
22 KiB
C++

/* 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 = 17,
kIndexSize = 12
};
// tables
#include "misc.h"
#include "eng.h"
#include "esp.h"
#include "fre.h"
#include "ger.h"
#include "towns.h"
#include "amiga.h"
const Game kyra1FanTranslations[] = {
{ kKyra1, IT_ITA, kTalkieVersion, "d0f1752098236083d81b9497bd2b6989", kyra1FreCD },
GAME_DUMMY_ENTRY
};
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, 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]) {
if (g->special == kAmigaVersion) {
if (!((i + 1) & 0x1))
++entries;
} else {
++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 len = strlen((const char*) data + i);
i += len;
#if 1
// FIXME: Not sure whether this correct; the original code was ambiguious, see below
targetsize = targetsize - 1 - len;
#else
targetsize = --targetsize - len; // FIXME: This operation is undefined
#endif
while (!data[++i]) {
if (i == len)
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;
const uint8 *input = (const 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 if (g->special == kAmigaVersion) {
// we need to strip some aligment zeros out here
int dstPos = 0;
for (uint32 i = 0; i < size; ++i) {
if (!data[i] && ((i+1) & 0x1))
continue;
*output++ = data[i];
++dstPos;
}
targetsize = dstPos + 4;
} 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 roomEntrySize = (g->special == kFMTownsVersionE || g->special == kFMTownsVersionJ) ? (0x69) : ((g->special == kAmigaVersion) ? 0x52 : 0x51);
const int countRooms = size / roomEntrySize;
uint8 *buffer = new uint8[countRooms * 9 + 4];
assert(buffer);
uint8 *output = buffer;
WRITE_BE_UINT32(output, countRooms); output += 4;
const byte *src = data;
if (g->special == kAmigaVersion) {
for (int i = 0; i < countRooms; ++i) {
*output++ = *src++; assert(*src == 0); ++src;
memcpy(output, src, 8); output += 0x8;
src += roomEntrySize - 0x2;
}
} else {
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;
src += roomEntrySize - 0x9;
}
}
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 if (g->special == kAmigaVersion)
features |= GF_AMIGA;
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,
kyra1AmigaGames,
kyra1FanTranslations,
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;
}