diff options
author | Paul Gilbert | 2019-10-25 20:11:51 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-10-26 11:13:29 -0700 |
commit | 0facfc2b2497cc61e86582f72dbab1dad1b1b83b (patch) | |
tree | 89cb954251c7b8a6eb0fd954107255e62ba2573c /engines/glk/level9/detection.cpp | |
parent | 4332df2bda8d8f3942d81db3051f780d2f1bf02a (diff) | |
download | scummvm-rg350-0facfc2b2497cc61e86582f72dbab1dad1b1b83b.tar.gz scummvm-rg350-0facfc2b2497cc61e86582f72dbab1dad1b1b83b.tar.bz2 scummvm-rg350-0facfc2b2497cc61e86582f72dbab1dad1b1b83b.zip |
GLK: LEVEL9: Moved pre-existing detection code into separate class
Diffstat (limited to 'engines/glk/level9/detection.cpp')
-rw-r--r-- | engines/glk/level9/detection.cpp | 183 |
1 files changed, 175 insertions, 8 deletions
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp index 9904091d97..2648db85d6 100644 --- a/engines/glk/level9/detection.cpp +++ b/engines/glk/level9/detection.cpp @@ -22,6 +22,7 @@ #include "glk/level9/detection.h" #include "glk/level9/detection_tables.h" +#include "glk/level9/os_glk.h" #include "glk/blorb.h" #include "common/debug.h" #include "common/file.h" @@ -31,6 +32,176 @@ namespace Glk { namespace Level9 { +GameDetection::GameDetection(byte *&startData, size_t &fileSize) : + _startData(startData), _fileSize(fileSize), _crcInitialized(false), _gameName(nullptr) { + Common::fill(&_crcTable[0], &_crcTable[256], 0); +} + +gln_game_tableref_t GameDetection::gln_gameid_identify_game() { + uint16 length, crc; + byte checksum; + int is_version2; + gln_game_tableref_t game; + gln_patch_tableref_t patch; + + /* If the data file appears too short for a header, give up now. */ + if (_fileSize < 30) + return nullptr; + + /* + * Find the version of the game, and the length of game data. This logic + * is taken from L9cut, with calcword() replaced by simple byte comparisons. + * If the length exceeds the available data, fail. + */ + assert(_startData); + is_version2 = _startData[4] == 0x20 && _startData[5] == 0x00 + && _startData[10] == 0x00 && _startData[11] == 0x80 + && _startData[20] == _startData[22] + && _startData[21] == _startData[23]; + + length = is_version2 + ? _startData[28] | _startData[29] << BITS_PER_CHAR + : _startData[0] | _startData[1] << BITS_PER_CHAR; + if (length >= _fileSize) + return nullptr; + + /* Calculate or retrieve the checksum, in a version specific way. */ + if (is_version2) { + int index; + + checksum = 0; + for (index = 0; index < length + 1; index++) + checksum += _startData[index]; + } + else + checksum = _startData[length]; + + /* + * Generate a CRC for this data. When L9cut calculates a CRC, it's using a + * copy taken up to length + 1 and then padded with two NUL bytes, so we + * mimic that here. + */ + crc = gln_get_buffer_crc(_startData, length + 1, 2); + + /* + * See if this is a patched file. If it is, look up the game based on the + * original CRC and checksum. If not, use the current CRC and checksum. + */ + patch = gln_gameid_lookup_patch(length, checksum, crc); + game = gln_gameid_lookup_game(length, + patch ? patch->orig_checksum : checksum, + patch ? patch->orig_crc : crc, + false); + + /* If no game identified, retry without the CRC. This is guesswork. */ + if (!game) + game = gln_gameid_lookup_game(length, checksum, crc, true); + + return game; +} + +// CRC table initialization polynomial +static const uint16 GLN_CRC_POLYNOMIAL = 0xa001; + +uint16 GameDetection::gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) { + + const char *buffer = (const char *)void_buffer; + uint16 crc; + size_t index; + + /* Build the static CRC lookup table on first call. */ + if (!_crcInitialized) { + for (index = 0; index < BYTE_MAX + 1; index++) { + int bit; + + crc = (uint16)index; + for (bit = 0; bit < BITS_PER_CHAR; bit++) + crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1; + + _crcTable[index] = crc; + } + + _crcInitialized = true; + + /* CRC lookup table self-test, after is_initialized set -- recursion. */ + assert(gln_get_buffer_crc("123456789", 9, 0) == 0xbb3d); + } + + /* Start with zero in the crc, then update using table entries. */ + crc = 0; + for (index = 0; index < length; index++) + crc = _crcTable[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_CHAR); + + /* Add in any requested NUL padding bytes. */ + for (index = 0; index < padding; index++) + crc = _crcTable[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR); + + return crc; +} + +gln_game_tableref_t GameDetection::gln_gameid_lookup_game(uint16 length, byte checksum, uint16 crc, int ignore_crc) const { + gln_game_tableref_t game; + + for (game = GLN_GAME_TABLE; game->length; game++) { + if (game->length == length && game->checksum == checksum + && (ignore_crc || game->crc == crc)) + break; + } + + return game->length ? game : nullptr; +} + +gln_patch_tableref_t GameDetection::gln_gameid_lookup_patch(uint16 length, byte checksum, uint16 crc) const { + gln_patch_tableref_t patch; + + for (patch = GLN_PATCH_TABLE; patch->length; patch++) { + if (patch->length == length && patch->patch_checksum == checksum + && patch->patch_crc == crc) + break; + } + + return patch->length ? patch : nullptr; +} + +const char *GameDetection::gln_gameid_get_game_name() { + /* + * If no game name yet known, attempt to identify the game. If it can't + * be identified, set the cached game name to "" -- this special value + * indicates that the game is an unknown one, but suppresses repeated + * attempts to identify it on successive calls. + */ + if (!_gameName) { + gln_game_tableref_t game; + + /* + * If the interpreter hasn't yet loaded a game, startdata is nullptr + * (uninitialized, global). In this case, we return nullptr, allowing + * for retries until a game is loaded. + */ + if (!_startData) + return nullptr; + + game = gln_gameid_identify_game(); + _gameName = game ? game->name : ""; + } + + /* Return the game's name, or nullptr if it was unidentifiable. */ + assert(_gameName); + return strlen(_gameName) > 0 ? _gameName : nullptr; +} + +/** + * Clear the saved game name, forcing a new lookup when next queried. This + * function should be called by actions that may cause the interpreter to + * change game file, for example os_set_filenumber(). + */ +void GameDetection::gln_gameid_game_name_reset() { + _gameName = nullptr; +} + + +/*----------------------------------------------------------------------*/ + void Level9MetaEngine::getSupportedGames(PlainGameList &games) { for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) { games.push_back(*pd); @@ -62,7 +233,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames & continue; Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000); - size_t filesize = gameFile.size(); + size_t _fileSize = gameFile.size(); gameFile.seek(0); bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI); gameFile.close(); @@ -72,12 +243,12 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames & // Check for known games const GlkDetectionEntry *p = LEVEL9_GAMES; - while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize)) + while (p->_gameId && (md5 != p->_md5 || _fileSize != p->_filesize)) ++p; if (!p->_gameId) { const PlainGameDescriptor &desc = LEVEL9_GAME_LIST[0]; - gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize)); + gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, _fileSize)); } else { PlainGameDescriptor gameDesc = findGame(p->_gameId); gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language)); @@ -88,11 +259,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames & } void Level9MetaEngine::detectClashes(Common::StringMap &map) { - for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) { - if (map.contains(pd->gameId)) - error("Duplicate game Id found - %s", pd->gameId); - map[pd->gameId] = ""; - } + // No implementation } } // End of namespace Level9 |