rewrote FOTAQ game version detection code in a - if I didn't break anything - better way (no more duplicated code, more accurate game description in the launcher...)

svn-id: r24598
This commit is contained in:
Gregory Montoir 2006-11-04 12:00:31 +00:00
parent b8ed3bc87f
commit 5a902f8f7a
6 changed files with 195 additions and 196 deletions

View file

@ -126,8 +126,8 @@ bool Debugger::Cmd_GameState(int argc, const char **argv) {
} }
bool Debugger::Cmd_Info(int argc, const char **argv) { bool Debugger::Cmd_Info(int argc, const char **argv) {
DebugPrintf("Version: %s\n", _vm->resource()->JASVersion()); DebugPrintf("Version: %s\n", _vm->resource()->getJASVersion());
DebugPrintf("Audio compression: %d\n", _vm->resource()->compression()); DebugPrintf("Audio compression: %d\n", _vm->resource()->getCompression());
return true; return true;
} }

View file

@ -457,7 +457,7 @@ void Journal::drawConfigPanel() {
void Journal::drawInfoPanel() { void Journal::drawInfoPanel() {
showBob(BOB_INFO_BOX, 72, 221, FRAME_INFO_BOX); showBob(BOB_INFO_BOX, 72, 221, FRAME_INFO_BOX);
const char *ver = _vm->resource()->JASVersion(); const char *ver = _vm->resource()->getJASVersion();
switch (ver[0]) { switch (ver[0]) {
case 'P': case 'P':
_vm->display()->setTextCentered(132, "PC Hard Drive", false); _vm->display()->setTextCentered(132, "PC Hard Drive", false);

View file

@ -190,7 +190,7 @@ void Logic::initialise() {
_currentRoom = _objectData[_entryObj].room; _currentRoom = _objectData[_entryObj].room;
_entryObj = 0; _entryObj = 0;
if (memcmp(ptr, _vm->resource()->JASVersion(), 5) != 0) { if (memcmp(ptr, _vm->resource()->getJASVersion(), 5) != 0) {
warning("Unexpected queen.jas file format"); warning("Unexpected queen.jas file format");
} }

View file

@ -48,39 +48,21 @@
#include "sound/mididrv.h" #include "sound/mididrv.h"
/* Flight of the Amazon Queen */ static const PlainGameDescriptor queenGameDescriptor = {
static const PlainGameDescriptor queen_setting[] = { "queen", "Flight of the Amazon Queen"
{ "queen", "Flight of the Amazon Queen" },
{ "queen", "Flight of the Amazon Queen (Demo)" },
{ "queen", "Flight of the Amazon Queen (Interview)" },
{ 0, 0 }
}; };
GameList Engine_QUEEN_gameIDList() { GameList Engine_QUEEN_gameIDList() {
GameList games; GameList games;
games.push_back(queen_setting[0]); games.push_back(queenGameDescriptor);
return games; return games;
} }
GameDescriptor Engine_QUEEN_findGameID(const char *gameid) { GameDescriptor Engine_QUEEN_findGameID(const char *gameid) {
if (0 == scumm_stricmp(gameid, queen_setting[0].gameid)) if (0 == scumm_stricmp(gameid, queenGameDescriptor.gameid)) {
return queen_setting[0]; return queenGameDescriptor;
return GameDescriptor();
}
// FIXME/TODO: it would be nice to re-use the existing code of the
// Resource class to detect the FOTAQ version.
static GameDescriptor determineTarget(uint32 size) {
switch (size) {
case 3724538: //regular demo
case 3732177:
return queen_setting[1];
case 1915913: //interview demo
return queen_setting[2];
default: //non-demo
return queen_setting[0];
} }
return queen_setting[0]; return GameDescriptor();
} }
DetectedGameList Engine_QUEEN_detectGames(const FSList &fslist) { DetectedGameList Engine_QUEEN_detectGames(const FSList &fslist) {
@ -88,32 +70,27 @@ DetectedGameList Engine_QUEEN_detectGames(const FSList &fslist) {
// Iterate over all files in the given directory // Iterate over all files in the given directory
for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
if (!file->isDirectory()) { if (file->isDirectory()) {
const char *fileName = file->name().c_str(); continue;
}
if (0 == scumm_stricmp("queen.1", fileName) || 0 == scumm_stricmp("queen.1c", fileName)) { if (file->name().equalsIgnoreCase("queen.1") || file->name().equalsIgnoreCase("queen.1c")) {
Common::File dataFile; Common::File dataFile;
dataFile.open(*file); if (!dataFile.open(*file)) {
assert(dataFile.isOpen()); continue;
}
if (0 == scumm_stricmp("queen.1", fileName)) { //an unmodified file Queen::DetectedGameVersion version;
detectedGames.push_back(determineTarget(dataFile.size())); if (Queen::Resource::detectVersion(&version, &dataFile)) {
} else if (0 == scumm_stricmp("queen.1c", fileName)) { //oh joy, it's a rebuilt file DetectedGame dg(queenGameDescriptor.gameid, queenGameDescriptor.description, version.language, Common::kPlatformPC);
char header[9]; if (version.features & Queen::GF_DEMO) {
dataFile.read(header, 9); dg.updateDesc("Demo");
if (0 == scumm_strnicmp("QTBL", header, 4)) { //check validity } else if (version.features & Queen::GF_INTERVIEW) {
uint8 version = 0; //default to full/normal version dg.updateDesc("Interview");
} else if (version.features & Queen::GF_FLOPPY) {
if (0 == scumm_strnicmp("PE100", header + 4, 5)) //One of the 2 regular demos dg.updateDesc("Floppy");
version = 1; } else if (version.features & Queen::GF_TALKIE) {
if (0 == scumm_strnicmp("PEint", header + 4, 5)) //Interview demo dg.updateDesc("Talkie");
version = 2;
detectedGames.push_back(queen_setting[version]);
}
} }
detectedGames.push_back(dg);
dataFile.close();
break; break;
} }
} }
@ -421,7 +398,7 @@ int QueenEngine::init() {
_music = new Music(driver, this); _music = new Music(driver, this);
_music->hasNativeMT32(native_mt32); _music->hasNativeMT32(native_mt32);
_sound = Sound::giveSound(_mixer, this, _resource->compression()); _sound = Sound::giveSound(_mixer, this, _resource->getCompression());
_walk = new Walk(this); _walk = new Walk(this);
//_talkspeedScale = (MAX_TEXT_SPEED - MIN_TEXT_SPEED) / 255.0; //_talkspeedScale = (MAX_TEXT_SPEED - MIN_TEXT_SPEED) / 255.0;

View file

@ -21,6 +21,7 @@
*/ */
#include "common/stdafx.h" #include "common/stdafx.h"
#include "common/endian.h"
#include "common/config-manager.h" #include "common/config-manager.h"
#include "queen/resource.h" #include "queen/resource.h"
@ -32,7 +33,7 @@ static ResourceEntry *_resourceTablePEM10;
const char *Resource::_tableFilename = "queen.tbl"; const char *Resource::_tableFilename = "queen.tbl";
const GameVersion Resource::_gameVersions[] = { const RetailGameVersion Resource::_gameVersions[] = {
{ "PEM10", 0x00000008, 22677657 }, { "PEM10", 0x00000008, 22677657 },
{ "CEM10", 0x0000584E, 190787021 }, { "CEM10", 0x0000584E, 190787021 },
{ "PFM10", 0x0002CD93, 22157304 }, { "PFM10", 0x0002CD93, 22157304 },
@ -56,16 +57,29 @@ static int compareResourceEntry(const void *a, const void *b) {
Resource::Resource() Resource::Resource()
: _resourceEntries(0), _resourceTable(NULL) { : _resourceEntries(0), _resourceTable(NULL) {
_resourceFile = new Common::File(); memset(&_version, 0, sizeof(_version));
if (!findCompressedVersion() && !findNormalVersion())
error("Could not open resource file '%s'", "queen.1"); if (!_resourceFile.open("queen.1c")) {
if (!_resourceFile.open("queen.1")) {
error("Could not open resource file 'queen.1[c]'");
}
}
if (!detectVersion(&_version, &_resourceFile)) {
error("Unable to detect game version");
}
if (_version.features & GF_REBUILT) {
readTableEntries(&_resourceFile);
} else {
readTableFile(_version.tableOffset);
}
checkJASVersion(); checkJASVersion();
debug(5, "Detected game version: %s, which has %d resource entries", _versionString, _resourceEntries); debug(5, "Detected game version: %s, which has %d resource entries", _version.str, _resourceEntries);
} }
Resource::~Resource() { Resource::~Resource() {
_resourceFile->close(); _resourceFile.close();
delete _resourceFile;
if (_resourceTable != _resourceTablePEM10) if (_resourceTable != _resourceTablePEM10)
delete[] _resourceTable; delete[] _resourceTable;
@ -74,22 +88,17 @@ Resource::~Resource() {
ResourceEntry *Resource::resourceEntry(const char *filename) const { ResourceEntry *Resource::resourceEntry(const char *filename) const {
assert(filename[0] && strlen(filename) < 14); assert(filename[0] && strlen(filename) < 14);
char entryName[14]; Common::String entryName(filename);
char *ptr = entryName; entryName.toUppercase();
strcpy(entryName, filename);
do
*ptr = toupper(*ptr);
while (*ptr++);
ResourceEntry *re = NULL; ResourceEntry *re = NULL;
#ifndef PALMOS_MODE #ifndef PALMOS_MODE
re = (ResourceEntry *)bsearch(entryName, _resourceTable, _resourceEntries, sizeof(ResourceEntry), compareResourceEntry); re = (ResourceEntry *)bsearch(entryName.c_str(), _resourceTable, _resourceEntries, sizeof(ResourceEntry), compareResourceEntry);
#else #else
// PALMOS FIXME (?) : still doesn't work for me (????) use this instead // PALMOS FIXME (?) : still doesn't work for me (????) use this instead
uint32 cur = 0; uint32 cur = 0;
do { do {
if (!strcmp(entryName, _resourceTable[cur].filename)) { if (!strcmp(entryName.c_str(), _resourceTable[cur].filename)) {
re = &_resourceTable[cur]; re = &_resourceTable[cur];
break; break;
} }
@ -113,44 +122,78 @@ uint8 *Resource::loadFile(const char *filename, uint32 skipBytes, uint32 *size,
dstBuf = new byte[sz]; dstBuf = new byte[sz];
} }
_resourceFile->seek(re->offset + skipBytes); _resourceFile.seek(re->offset + skipBytes);
_resourceFile->read(dstBuf, sz); _resourceFile.read(dstBuf, sz);
return dstBuf; return dstBuf;
} }
bool Resource::findNormalVersion() { bool Resource::detectVersion(DetectedGameVersion *ver, Common::File *f) {
_resourceFile->open("queen.1"); memset(ver, 0, sizeof(DetectedGameVersion));
if (!_resourceFile->isOpen()) {
return false;
}
_compression = COMPRESSION_NONE; char versionStr[6];
if (f->readUint32BE() == MKID_BE('QTBL')) {
// detect game version based on resource file size ; we try to f->read(versionStr, 6);
// verify that it is indeed the version we think it is later on f->skip(2);
const GameVersion *gameVersion = detectGameVersion(_resourceFile->size()); ver->compression = f->readByte();
if (gameVersion == NULL) ver->features = GF_REBUILT;
error("Unknown/unsupported FOTAQ version"); ver->tableOffset = 0;
} else {
strcpy(_versionString, gameVersion->versionString); const RetailGameVersion *gameVersion = detectGameVersionFromSize(f->size());
if (!readTableFile(gameVersion)) { if (gameVersion == NULL) {
// check if it is the english floppy version, for which we have a hardcoded version of the table warning("Unknown/unsupported FOTAQ version");
if (!strcmp(gameVersion->versionString, _gameVersions[VER_ENG_FLOPPY].versionString)) { return false;
_resourceEntries = 1076;
_resourceTable = _resourceTablePEM10;
} else {
error("Could not find tablefile '%s'", _tableFilename);
} }
strcpy(versionStr, gameVersion->str);
ver->compression = COMPRESSION_NONE;
ver->features = 0;
ver->tableOffset = gameVersion->tableOffset;
} }
return true;
}
bool Resource::findCompressedVersion() { switch (versionStr[1]) {
_resourceFile->open("queen.1c"); case 'E':
if (!_resourceFile->isOpen()) { if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS) {
return false; ver->language = Common::RU_RUS;
} else {
ver->language = Common::EN_ANY;
}
break;
case 'G':
ver->language = Common::DE_DEU;
break;
case 'F':
ver->language = Common::FR_FRA;
break;
case 'I':
ver->language = Common::IT_ITA;
break;
case 'S':
ver->language = Common::ES_ESP;
break;
case 'H':
ver->language = Common::HB_ISR;
break;
default:
warning("Unknown language id '%c', defaulting to English", versionStr[1]);
ver->language = Common::EN_ANY;
break;
} }
readTableCompResource();
switch (versionStr[0]) {
case 'P':
ver->features |= GF_FLOPPY;
break;
case 'C':
ver->features |= GF_TALKIE;
break;
}
if (strcmp(versionStr, "PE100") == 0) {
ver->features |= GF_DEMO;
} else if (strcmp(versionStr, "PEint") == 0) {
ver->features |= GF_INTERVIEW;
}
strcpy(ver->str, versionStr);
return true; return true;
} }
@ -164,58 +207,32 @@ void Resource::checkJASVersion() {
offset += JAS_VERSION_OFFSET_INTV; offset += JAS_VERSION_OFFSET_INTV;
else else
offset += JAS_VERSION_OFFSET_PC; offset += JAS_VERSION_OFFSET_PC;
_resourceFile->seek(offset); _resourceFile.seek(offset);
char versionStr[6]; char versionStr[6];
_resourceFile->read(versionStr, 6); _resourceFile.read(versionStr, 6);
if (strcmp(_versionString, versionStr)) if (strcmp(_version.str, versionStr))
error("Verifying game version failed! (expected: '%s', found: '%s')", _versionString, versionStr); error("Verifying game version failed! (expected: '%s', found: '%s')", _version.str, versionStr);
} }
Common::Language Resource::getLanguage() const { void Resource::readTableFile(uint32 offset) {
switch (_versionString[1]) {
case 'E':
if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS)
return Common::RU_RUS;
return Common::EN_ANY;
case 'G':
return Common::DE_DEU;
case 'F':
return Common::FR_FRA;
case 'I':
return Common::IT_ITA;
case 'S':
return Common::ES_ESP;
case 'H':
return Common::HB_ISR;
default:
warning("Unknown language id '%c', defaulting to English", _versionString[1]);
return Common::EN_ANY;
}
}
bool Resource::readTableFile(const GameVersion *gameVersion) {
Common::File tableFile; Common::File tableFile;
tableFile.open(_tableFilename); tableFile.open(_tableFilename);
if (tableFile.isOpen() && tableFile.readUint32BE() == 'QTBL') { if (tableFile.isOpen() && tableFile.readUint32BE() == MKID_BE('QTBL')) {
if (tableFile.readUint32BE() != CURRENT_TBL_VERSION) if (tableFile.readUint32BE() != CURRENT_TBL_VERSION) {
warning("Incorrect version of queen.tbl, please update it"); warning("Incorrect version of queen.tbl, please update it");
tableFile.seek(gameVersion->tableOffset); }
tableFile.seek(offset);
readTableEntries(&tableFile); readTableEntries(&tableFile);
return true; } else {
// check if it is the english floppy version, for which we have a hardcoded version of the table
if (strcmp(_version.str, _gameVersions[VER_ENG_FLOPPY].str) == 0) {
_resourceEntries = 1076;
_resourceTable = _resourceTablePEM10;
} else {
error("Could not find tablefile '%s'", _tableFilename);
}
} }
return false;
}
void Resource::readTableCompResource() {
if (_resourceFile->readUint32BE() != 'QTBL')
error("Invalid table header");
_resourceFile->read(_versionString, 6);
_resourceFile->skip(2); // obsolete
_compression = _resourceFile->readByte();
readTableEntries(_resourceFile);
} }
void Resource::readTableEntries(Common::File *file) { void Resource::readTableEntries(Common::File *file) {
@ -231,11 +248,10 @@ void Resource::readTableEntries(Common::File *file) {
} }
} }
const GameVersion *Resource::detectGameVersion(uint32 size) const { const RetailGameVersion *Resource::detectGameVersionFromSize(uint32 size) {
const GameVersion *pgv = _gameVersions; for (int i = 0; i < VER_COUNT; ++i) {
for (int i = 0; i < VER_COUNT; ++i, ++pgv) { if (_gameVersions[i].dataFileSize == size) {
if (pgv->dataFileSize == size) { return &_gameVersions[i];
return pgv;
} }
} }
return NULL; return NULL;
@ -249,8 +265,8 @@ Common::File *Resource::giveCompressedSound(const char *filename, uint32 *size)
if (size != NULL) { if (size != NULL) {
*size = re->size; *size = re->size;
} }
_resourceFile->seek(re->offset); _resourceFile.seek(re->offset);
f = _resourceFile; f = &_resourceFile;
} }
return f; return f;
} }

View file

@ -29,6 +29,28 @@
namespace Queen { namespace Queen {
enum GameFeatures {
GF_DEMO = 1 << 0, // demo
GF_TALKIE = 1 << 1, // equivalent to cdrom version check
GF_FLOPPY = 1 << 2, // floppy, ie. non-talkie version
GF_INTERVIEW = 1 << 3, // interview demo
GF_REBUILT = 1 << 4 // version rebuilt with the 'compression_queen' tool
};
struct RetailGameVersion {
char str[6];
uint32 tableOffset;
uint32 dataFileSize;
};
struct DetectedGameVersion {
Common::Language language;
uint8 features;
uint8 compression;
char str[6];
uint32 tableOffset;
};
struct ResourceEntry { struct ResourceEntry {
char filename[13]; char filename[13];
uint8 bundle; uint8 bundle;
@ -36,26 +58,6 @@ struct ResourceEntry {
uint32 size; uint32 size;
}; };
struct GameVersion {
char versionString[6];
uint32 tableOffset;
uint32 dataFileSize;
};
class LineReader {
public:
LineReader(char *buffer, uint32 bufsize);
~LineReader();
char *nextLine();
private:
char *_buffer;
uint32 _bufSize;
int _current;
};
class Resource { class Resource {
public: public:
@ -71,19 +73,22 @@ public:
//! returns a reference to a sound file //! returns a reference to a sound file
Common::File *giveCompressedSound(const char *filename, uint32 *size); Common::File *giveCompressedSound(const char *filename, uint32 *size);
bool isDemo() const { return !strcmp(_versionString, "PE100"); } bool isDemo() const { return _version.features & GF_DEMO; }
bool isInterview() const { return !strcmp(_versionString, "PEint"); } bool isInterview() const { return _version.features & GF_INTERVIEW; }
bool isFloppy() const { return _versionString[0] == 'P'; } bool isFloppy() const { return _version.features & GF_FLOPPY; }
bool isCD() const { return _versionString[0] == 'C'; } bool isCD() const { return _version.features & GF_TALKIE; }
//! returns compression type for audio files //! returns compression type for audio files
uint8 compression() const { return _compression; } uint8 getCompression() const { return _version.compression; }
//! returns JAS version string (contains language, platform and version information) //! returns JAS version string (contains language, platform and version information)
const char *JASVersion() const { return _versionString; } const char *getJASVersion() const { return _version.str; }
//! returns language of the game //! returns the language of the game
Common::Language getLanguage() const; Common::Language getLanguage() const { return _version.language; }
//! detect game version
static bool detectVersion(DetectedGameVersion *ver, Common::File *f);
enum Version { enum Version {
VER_ENG_FLOPPY = 0, VER_ENG_FLOPPY = 0,
@ -115,25 +120,15 @@ public:
protected: protected:
Common::File *_resourceFile; Common::File _resourceFile;
//! compression type for audio files DetectedGameVersion _version;
uint8 _compression;
//! JAS version string of the game
char _versionString[6];
//! number of entries in resource table //! number of entries in resource table
uint32 _resourceEntries; uint32 _resourceEntries;
ResourceEntry *_resourceTable; ResourceEntry *_resourceTable;
//! look for a normal queen version (ie. queen.1)
bool findNormalVersion();
//! look for a compressed/rebuilt queen version (ie. queen.1c)
bool findCompressedVersion();
//! verify the version of the selected game //! verify the version of the selected game
void checkJASVersion(); void checkJASVersion();
@ -141,22 +136,19 @@ protected:
ResourceEntry *resourceEntry(const char *filename) const; ResourceEntry *resourceEntry(const char *filename) const;
//! extarct the resource table for the specified game version //! extarct the resource table for the specified game version
bool readTableFile(const GameVersion *gameVersion); void readTableFile(uint32 offset);
//! reads the resource table from a rebuilt datafile (ie. queen.1c)
void readTableCompResource();
//! read the resource table from the specified file //! read the resource table from the specified file
void readTableEntries(Common::File *file); void readTableEntries(Common::File *file);
//! detect game version based on queen.1 datafile size //! detect game version based on queen.1 datafile size
const GameVersion *detectGameVersion(uint32 size) const; static const RetailGameVersion *detectGameVersionFromSize(uint32 size);
//! resource table filename (queen.tbl) //! resource table filename (queen.tbl)
static const char *_tableFilename; static const char *_tableFilename;
//! known FOTAQ versions //! known FOTAQ versions
static const GameVersion _gameVersions[]; static const RetailGameVersion _gameVersions[];
#ifndef PALMOS_68K #ifndef PALMOS_68K
//! resource table for english floppy version //! resource table for english floppy version
@ -164,6 +156,20 @@ protected:
#endif #endif
}; };
class LineReader {
public:
LineReader(char *buffer, uint32 bufsize);
~LineReader();
char *nextLine();
private:
char *_buffer;
uint32 _bufSize;
int _current;
};
} // End of namespace Queen } // End of namespace Queen
#endif #endif