aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/level9/detection.cpp
diff options
context:
space:
mode:
authorPaul Gilbert2019-10-25 20:11:51 -0700
committerPaul Gilbert2019-10-26 11:13:29 -0700
commit0facfc2b2497cc61e86582f72dbab1dad1b1b83b (patch)
tree89cb954251c7b8a6eb0fd954107255e62ba2573c /engines/glk/level9/detection.cpp
parent4332df2bda8d8f3942d81db3051f780d2f1bf02a (diff)
downloadscummvm-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.cpp183
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