diff --git a/include/mgba/core/library.h b/include/mgba/core/library.h index ededd5463..cfdd4cc60 100644 --- a/include/mgba/core/library.h +++ b/include/mgba/core/library.h @@ -24,6 +24,8 @@ struct mLibraryEntry { enum mPlatform platform; size_t filesize; uint32_t crc32; + uint8_t md5[16]; + uint8_t sha1[20]; }; #ifdef USE_SQLITE3 diff --git a/src/core/library.c b/src/core/library.c index 1c92a0cbc..638f49880 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -34,6 +34,8 @@ struct mLibrary { "CASE WHEN :useSize THEN roms.size = :size ELSE 1 END AND " \ "CASE WHEN :usePlatform THEN roms.platform = :platform ELSE 1 END AND " \ "CASE WHEN :useCrc32 THEN roms.crc32 = :crc32 ELSE 1 END AND " \ + "CASE WHEN :useMd5 THEN roms.md5 = :md5 ELSE 1 END AND " \ + "CASE WHEN :useSha1 THEN roms.sha1 = :sha1 ELSE 1 END AND " \ "CASE WHEN :useInternalCode THEN roms.internalCode = :internalCode ELSE 1 END" #define CONSTRAINTS \ @@ -58,6 +60,20 @@ static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry sqlite3_bind_int(statement, index, constraints->crc32); } + if (memcmp(constraints->md5, &(uint8_t[16]) {0}, 16) != 0) { + useIndex = sqlite3_bind_parameter_index(statement, ":useMd5"); + index = sqlite3_bind_parameter_index(statement, ":md5"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_blob(statement, index, constraints->md5, 16, NULL); + } + + if (memcmp(constraints->sha1, &(uint8_t[20]) {0}, 20) != 0) { + useIndex = sqlite3_bind_parameter_index(statement, ":useSha1"); + index = sqlite3_bind_parameter_index(statement, ":sha1"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_blob(statement, index, constraints->sha1, 20, NULL); + } + if (constraints->filesize) { useIndex = sqlite3_bind_parameter_index(statement, ":useSize"); index = sqlite3_bind_parameter_index(statement, ":size"); @@ -139,6 +155,8 @@ struct mLibrary* mLibraryLoad(const char* path) { "\n CONSTRAINT location UNIQUE (path, rootid)" "\n );" "\n CREATE INDEX IF NOT EXISTS crc32 ON roms (crc32);" + "\n CREATE INDEX IF NOT EXISTS md5 ON roms (md5);" + "\n CREATE INDEX IF NOT EXISTS sha1 ON roms (sha1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('version', 1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roots', 1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 1);" @@ -152,7 +170,7 @@ struct mLibrary* mLibraryLoad(const char* path) { goto error; } - static const char insertRom[] = "INSERT INTO roms (crc32, size, internalCode, platform) VALUES (:crc32, :size, :internalCode, :platform);"; + static const char insertRom[] = "INSERT INTO roms (crc32, md5, sha1, size, internalCode, platform) VALUES (:crc32, :md5, :sha1, :size, :internalCode, :platform);"; if (sqlite3_prepare_v2(library->db, insertRom, -1, &library->insertRom, NULL)) { goto error; } @@ -297,6 +315,8 @@ bool _mLibraryAddEntry(struct mLibrary* library, const char* filename, const cha snprintf(entry.internalCode, sizeof(entry.internalCode), "%s-%s", info.system, info.code); strlcpy(entry.internalTitle, info.title, sizeof(entry.internalTitle)); core->checksum(core, &entry.crc32, mCHECKSUM_CRC32); + core->checksum(core, &entry.md5, mCHECKSUM_MD5); + core->checksum(core, &entry.sha1, mCHECKSUM_SHA1); entry.platform = core->platform(core); entry.title = NULL; entry.base = base; @@ -402,10 +422,28 @@ size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, int i; for (i = 0; i < nCols; ++i) { const char* colName = sqlite3_column_name(library->select, i); - if (strcmp(colName, "crc32") == 0) { + if (strcmp(colName, "sha1") == 0) { + const void* buf = sqlite3_column_blob(library->select, i); + if (buf && sqlite3_column_bytes(library->select, i) == sizeof(entry->sha1)) { + memcpy(entry->sha1, buf, sizeof(entry->sha1)); + struct NoIntroGame game; + if (!entry->title && NoIntroDBLookupGameBySHA1(library->gameDB, entry->sha1, &game)) { + entry->title = strdup(game.name); + } + } + } else if (strcmp(colName, "md5") == 0) { + const void* buf = sqlite3_column_blob(library->select, i); + if (buf && sqlite3_column_bytes(library->select, i) == sizeof(entry->md5)) { + memcpy(entry->md5, buf, sizeof(entry->md5)); + struct NoIntroGame game; + if (!entry->title && NoIntroDBLookupGameByMD5(library->gameDB, entry->md5, &game)) { + entry->title = strdup(game.name); + } + } + } else if (strcmp(colName, "crc32") == 0) { entry->crc32 = sqlite3_column_int(library->select, i); struct NoIntroGame game; - if (NoIntroDBLookupGameByCRC(library->gameDB, entry->crc32, &game)) { + if (!entry->title && NoIntroDBLookupGameByCRC(library->gameDB, entry->crc32, &game)) { entry->title = strdup(game.name); } } else if (strcmp(colName, "platform") == 0) { diff --git a/src/feature/sqlite3/no-intro.c b/src/feature/sqlite3/no-intro.c index 463a2fe5e..8b0474c8c 100644 --- a/src/feature/sqlite3/no-intro.c +++ b/src/feature/sqlite3/no-intro.c @@ -14,6 +14,8 @@ struct NoIntroDB { sqlite3* db; sqlite3_stmt* crc32; + sqlite3_stmt* md5; + sqlite3_stmt* sha1; }; struct NoIntroDB* NoIntroDBLoad(const char* path) { @@ -54,8 +56,18 @@ struct NoIntroDB* NoIntroDBLoad(const char* path) { goto error; } - static const char selectRom[] = "SELECT * FROM games JOIN roms USING (gid) WHERE roms.crc32 = ?;"; - if (sqlite3_prepare_v2(db->db, selectRom, -1, &db->crc32, NULL)) { + static const char selectCrc32[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.crc32 = ?;"; + if (sqlite3_prepare_v2(db->db, selectCrc32, -1, &db->crc32, NULL)) { + goto error; + } + + static const char selectMd5[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.md5 = ?;"; + if (sqlite3_prepare_v2(db->db, selectMd5, -1, &db->md5, NULL)) { + goto error; + } + + static const char selectSha1[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.sha1 = ?;"; + if (sqlite3_prepare_v2(db->db, selectSha1, -1, &db->sha1, NULL)) { goto error; } @@ -300,12 +312,34 @@ void NoIntroDBDestroy(struct NoIntroDB* db) { if (db->crc32) { sqlite3_finalize(db->crc32); } + if (db->md5) { + sqlite3_finalize(db->md5); + } + if (db->sha1) { + sqlite3_finalize(db->sha1); + } if (db->db) { sqlite3_close(db->db); } free(db); } +void _extractGame(sqlite3_stmt* stmt, struct NoIntroGame* game) { + game->name = (const char*) sqlite3_column_text(stmt, 0); + game->romName = (const char*) sqlite3_column_text(stmt, 1); + game->size = sqlite3_column_int(stmt, 2); + game->crc32 = sqlite3_column_int(stmt, 3); + const void* buf = sqlite3_column_blob(stmt, 4); + if (buf && sqlite3_column_bytes(stmt, 4) == sizeof(game->md5)) { + memcpy(game->md5, buf, sizeof(game->md5)); + } + buf = sqlite3_column_blob(stmt, 5); + if (buf && sqlite3_column_bytes(stmt, 5) == sizeof(game->sha1)) { + memcpy(game->sha1, buf, sizeof(game->sha1)); + } + game->verified = sqlite3_column_int(stmt, 6); +} + bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game) { if (!db) { return false; @@ -316,11 +350,34 @@ bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct if (sqlite3_step(db->crc32) != SQLITE_ROW) { return false; } - game->name = (const char*) sqlite3_column_text(db->crc32, 1); - game->romName = (const char*) sqlite3_column_text(db->crc32, 3); - game->size = sqlite3_column_int(db->crc32, 4); - game->crc32 = sqlite3_column_int(db->crc32, 5); - // TODO: md5/sha1 - game->verified = sqlite3_column_int(db->crc32, 8); + _extractGame(db->crc32, game); + return true; +} + +bool NoIntroDBLookupGameByMD5(const struct NoIntroDB* db, const uint8_t* md5, struct NoIntroGame* game) { + if (!db) { + return false; + } + sqlite3_clear_bindings(db->md5); + sqlite3_reset(db->md5); + sqlite3_bind_blob(db->md5, 1, md5, 16, NULL); + if (sqlite3_step(db->md5) != SQLITE_ROW) { + return false; + } + _extractGame(db->md5, game); + return true; +} + +bool NoIntroDBLookupGameBySHA1(const struct NoIntroDB* db, const uint8_t* sha1, struct NoIntroGame* game) { + if (!db) { + return false; + } + sqlite3_clear_bindings(db->sha1); + sqlite3_reset(db->sha1); + sqlite3_bind_blob(db->sha1, 1, sha1, 20, NULL); + if (sqlite3_step(db->sha1) != SQLITE_ROW) { + return false; + } + _extractGame(db->sha1, game); return true; } diff --git a/src/feature/sqlite3/no-intro.h b/src/feature/sqlite3/no-intro.h index 648da8042..ba3bc128f 100644 --- a/src/feature/sqlite3/no-intro.h +++ b/src/feature/sqlite3/no-intro.h @@ -27,6 +27,8 @@ struct NoIntroDB* NoIntroDBLoad(const char* path); bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf); void NoIntroDBDestroy(struct NoIntroDB* db); bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game); +bool NoIntroDBLookupGameByMD5(const struct NoIntroDB* db, const uint8_t* md5, struct NoIntroGame* game); +bool NoIntroDBLookupGameBySHA1(const struct NoIntroDB* db, const uint8_t* sha1, struct NoIntroGame* game); CXX_GUARD_END diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index 40760dccf..43be10fe1 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -48,32 +48,42 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) if (crc32) { m_ui.crc->setText(QString::number(crc32, 16)); -#ifdef USE_SQLITE3 - if (db) { - NoIntroGame game{}; - if (NoIntroDBLookupGameByCRC(db, crc32, &game)) { - m_ui.name->setText(game.name); - } else { - m_ui.name->setText(tr("(unknown)")); - } - } else { - m_ui.name->setText(tr("(no database present)")); - } -#else - m_ui.name->hide(); -#endif } else { m_ui.crc->setText(tr("(unknown)")); - m_ui.name->setText(tr("(unknown)")); } - m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7], - md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF])); + if (memcmp(md5, &(const uint8_t[16]) {}, 16) != 0) { + m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7], + md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF])); + } else { + m_ui.md5->setText(tr("(unknown)")); + } - m_ui.sha1->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - sha1[ 0], sha1[ 1], sha1[ 2], sha1[ 3], sha1[ 4], sha1[ 5], sha1[ 6], sha1[ 7], sha1[ 8], sha1[ 9], - sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15], sha1[16], sha1[17], sha1[18], sha1[19])); + if (memcmp(sha1, &(const uint8_t[20]) {}, 20) != 0) { + m_ui.sha1->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + sha1[ 0], sha1[ 1], sha1[ 2], sha1[ 3], sha1[ 4], sha1[ 5], sha1[ 6], sha1[ 7], sha1[ 8], sha1[ 9], + sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15], sha1[16], sha1[17], sha1[18], sha1[19])); + } else { + m_ui.sha1->setText(tr("(unknown)")); + } + +#ifdef USE_SQLITE3 + if (db) { + NoIntroGame game{}; + if (memcmp(sha1, &(const uint8_t[20]) {}, 20) != 0 && NoIntroDBLookupGameBySHA1(db, sha1, &game)) { + m_ui.name->setText(game.name); + } else if (crc32 && NoIntroDBLookupGameByCRC(db, crc32, &game)) { + m_ui.name->setText(game.name); + } else { + m_ui.name->setText(tr("(unknown)")); + } + } else { + m_ui.name->setText(tr("(no database present)")); + } +#else + m_ui.name->hide(); +#endif QString savePath = controller->savePath(); if (!savePath.isEmpty()) {