/* Copyright (c) 2013-2015 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nointro.h" #include "util/crc32.h" #include "util/table.h" #include "util/vector.h" #include "util/vfs.h" #define KEY_STACK_SIZE 8 struct NoIntroDB { struct Table categories; struct Table gameCrc; }; struct NoIntroItem { union { struct Table hash; char* string; }; enum NoIntroItemType { NI_HASH, NI_STRING } type; }; DECLARE_VECTOR(NoIntroCategory, struct NoIntroItem*); DEFINE_VECTOR(NoIntroCategory, struct NoIntroItem*); static void _indexU32x(struct NoIntroDB* db, struct Table* table, const char* categoryKey, const char* key) { struct NoIntroCategory* category = HashTableLookup(&db->categories, categoryKey); if (!category) { return; } TableInit(table, 256, 0); char* tmpKey = strdup(key); const char* keyStack[KEY_STACK_SIZE] = { tmpKey }; size_t i; for (i = 1; i < KEY_STACK_SIZE; ++i) { char* next = strchr(keyStack[i - 1], '.'); if (!next) { break; } next[0] = '\0'; keyStack[i] = next + 1; } for (i = 0; i < NoIntroCategorySize(category); ++i) { struct NoIntroItem* item = *NoIntroCategoryGetPointer(category, i); if (!item) { continue; } struct NoIntroItem* keyloc = item; size_t s; for (s = 0; s < KEY_STACK_SIZE && keyStack[s]; ++s) { if (keyloc->type != NI_HASH) { keyloc = 0; break; } keyloc = HashTableLookup(&keyloc->hash, keyStack[s]); if (!keyloc) { break; } } if (!keyloc || keyloc->type != NI_STRING) { continue; } char* end; uint32_t key = strtoul(keyloc->string, &end, 16); if (!end || *end) { continue; } TableInsert(table, key, item); } free(tmpKey); } static void _itemDeinit(void* value) { struct NoIntroItem* item = value; switch (item->type) { case NI_STRING: free(item->string); break; case NI_HASH: HashTableDeinit(&item->hash); break; } free(item); } static void _dbDeinit(void* value) { struct NoIntroCategory* category = value; size_t i; for (i = 0; i < NoIntroCategorySize(category); ++i) { struct NoIntroItem* item = *NoIntroCategoryGetPointer(category, i); switch (item->type) { case NI_STRING: free(item->string); break; case NI_HASH: HashTableDeinit(&item->hash); break; } free(item); } NoIntroCategoryDeinit(category); } static bool _itemToGame(const struct NoIntroItem* item, struct NoIntroGame* game) { if (item->type != NI_HASH) { return false; } struct NoIntroItem* subitem; struct NoIntroItem* rom; memset(game, 0, sizeof(*game)); subitem = HashTableLookup(&item->hash, "name"); if (subitem && subitem->type == NI_STRING) { game->name = subitem->string; } subitem = HashTableLookup(&item->hash, "description"); if (subitem && subitem->type == NI_STRING) { game->description = subitem->string; } rom = HashTableLookup(&item->hash, "rom"); if (!rom || rom->type != NI_HASH) { return false; } subitem = HashTableLookup(&rom->hash, "name"); if (subitem && subitem->type == NI_STRING) { game->romName = subitem->string; } subitem = HashTableLookup(&rom->hash, "size"); if (subitem && subitem->type == NI_STRING) { char* end; game->size = strtoul(subitem->string, &end, 0); if (!end || *end) { game->size = 0; } } // TODO: md5, sha1 subitem = HashTableLookup(&rom->hash, "flags"); if (subitem && subitem->type == NI_STRING && strcmp(subitem->string, "verified")) { game->verified = true; } return true; } struct NoIntroDB* NoIntroDBLoad(struct VFile* vf) { struct NoIntroDB* db = malloc(sizeof(*db)); HashTableInit(&db->categories, 0, _dbDeinit); char line[512]; struct { char* key; struct NoIntroItem* item; } keyStack[KEY_STACK_SIZE]; memset(keyStack, 0, sizeof(keyStack)); struct Table* parent = 0; size_t stackDepth = 0; while (true) { ssize_t bytesRead = vf->readline(vf, line, sizeof(line)); if (!bytesRead) { break; } ssize_t i; const char* token; for (i = 0; i < bytesRead; ++i) { while (isspace((int) line[i]) && i < bytesRead) { ++i; } if (i >= bytesRead) { break; } token = &line[i]; while (!isspace((int) line[i]) && i < bytesRead) { ++i; } if (i >= bytesRead) { break; } switch (token[0]) { case '(': if (!keyStack[stackDepth].key) { goto error; } keyStack[stackDepth].item = malloc(sizeof(*keyStack[stackDepth].item)); keyStack[stackDepth].item->type = NI_HASH; HashTableInit(&keyStack[stackDepth].item->hash, 8, _itemDeinit); if (parent) { HashTableInsert(parent, keyStack[stackDepth].key, keyStack[stackDepth].item); } else { struct NoIntroCategory* category = HashTableLookup(&db->categories, keyStack[stackDepth].key); if (!category) { category = malloc(sizeof(*category)); NoIntroCategoryInit(category, 0); HashTableInsert(&db->categories, keyStack[stackDepth].key, category); } *NoIntroCategoryAppend(category) = keyStack[stackDepth].item; } parent = &keyStack[stackDepth].item->hash; ++stackDepth; if (stackDepth >= KEY_STACK_SIZE) { goto error; } keyStack[stackDepth].key = 0; break; case ')': if (keyStack[stackDepth].key || !stackDepth) { goto error; } --stackDepth; if (stackDepth) { parent = &keyStack[stackDepth - 1].item->hash; } else { parent = 0; } free(keyStack[stackDepth].key); keyStack[stackDepth].key = 0; break; case '"': ++token; for (; line[i] != '"' && i < bytesRead; ++i); // Fall through default: line[i] = '\0'; if (!keyStack[stackDepth].key) { keyStack[stackDepth].key = strdup(token); } else { struct NoIntroItem* item = malloc(sizeof(*keyStack[stackDepth].item)); item->type = NI_STRING; item->string = strdup(token); if (parent) { HashTableInsert(parent, keyStack[stackDepth].key, item); } else { struct NoIntroCategory* category = HashTableLookup(&db->categories, keyStack[stackDepth].key); if (!category) { category = malloc(sizeof(*category)); NoIntroCategoryInit(category, 0); HashTableInsert(&db->categories, keyStack[stackDepth].key, category); } *NoIntroCategoryAppend(category) = item; } free(keyStack[stackDepth].key); keyStack[stackDepth].key = 0; } break; } } } _indexU32x(db, &db->gameCrc, "game", "rom.crc"); return db; error: HashTableDeinit(&db->categories); free(db); return 0; } void NoIntroDBDestroy(struct NoIntroDB* db) { HashTableDeinit(&db->categories); } bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game) { if (!db) { return false; } struct NoIntroItem* item = TableLookup(&db->gameCrc, crc32); if (item) { return _itemToGame(item, game); } return false; }