all repos — mgba @ 7fe7749797aab4d0b18948cd38315e0c518c6e3e

mGBA Game Boy Advance Emulator

src/util/nointro.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include <mgba-util/nointro.h>
  7
  8#include <mgba-util/table.h>
  9#include <mgba-util/vector.h>
 10#include <mgba-util/vfs.h>
 11
 12#define KEY_STACK_SIZE 8
 13
 14struct NoIntroDB {
 15	struct Table categories;
 16	struct Table gameCrc;
 17};
 18
 19struct NoIntroItem {
 20	union {
 21		struct Table hash;
 22		char* string;
 23	};
 24	enum NoIntroItemType {
 25		NI_HASH,
 26		NI_STRING
 27	} type;
 28};
 29
 30DECLARE_VECTOR(NoIntroCategory, struct NoIntroItem*);
 31DEFINE_VECTOR(NoIntroCategory, struct NoIntroItem*);
 32
 33static void _indexU32x(struct NoIntroDB* db, struct Table* table, const char* categoryKey, const char* key) {
 34	struct NoIntroCategory* category = HashTableLookup(&db->categories, categoryKey);
 35	if (!category) {
 36		return;
 37	}
 38	TableInit(table, 256, 0);
 39	char* tmpKey = strdup(key);
 40	const char* keyStack[KEY_STACK_SIZE] = { tmpKey };
 41	size_t i;
 42	for (i = 1; i < KEY_STACK_SIZE; ++i) {
 43		char* next = strchr(keyStack[i - 1], '.');
 44		if (!next) {
 45			break;
 46		}
 47		next[0] = '\0';
 48		keyStack[i] = next + 1;
 49	}
 50	for (i = 0; i < NoIntroCategorySize(category); ++i) {
 51		struct NoIntroItem* item = *NoIntroCategoryGetPointer(category, i);
 52		if (!item) {
 53			continue;
 54		}
 55		struct NoIntroItem* keyloc = item;
 56		size_t s;
 57		for (s = 0; s < KEY_STACK_SIZE && keyStack[s]; ++s) {
 58			if (keyloc->type != NI_HASH) {
 59				keyloc = 0;
 60				break;
 61			}
 62			keyloc = HashTableLookup(&keyloc->hash, keyStack[s]);
 63			if (!keyloc) {
 64				break;
 65			}
 66		}
 67		if (!keyloc || keyloc->type != NI_STRING) {
 68			continue;
 69		}
 70		char* end;
 71		uint32_t key = strtoul(keyloc->string, &end, 16);
 72		if (!end || *end) {
 73			continue;
 74		}
 75		TableInsert(table, key, item);
 76	}
 77	free(tmpKey);
 78}
 79
 80static void _itemDeinit(void* value) {
 81	struct NoIntroItem* item = value;
 82	switch (item->type) {
 83	case NI_STRING:
 84		free(item->string);
 85		break;
 86	case NI_HASH:
 87		HashTableDeinit(&item->hash);
 88		break;
 89	}
 90	free(item);
 91}
 92
 93static void _dbDeinit(void* value) {
 94	struct NoIntroCategory* category = value;
 95	size_t i;
 96	for (i = 0; i < NoIntroCategorySize(category); ++i) {
 97		struct NoIntroItem* item = *NoIntroCategoryGetPointer(category, i);
 98		switch (item->type) {
 99		case NI_STRING:
100			free(item->string);
101			break;
102		case NI_HASH:
103			HashTableDeinit(&item->hash);
104			break;
105		}
106		free(item);
107	}
108	NoIntroCategoryDeinit(category);
109}
110
111static bool _itemToGame(const struct NoIntroItem* item, struct NoIntroGame* game) {
112	if (item->type != NI_HASH) {
113		return false;
114	}
115	struct NoIntroItem* subitem;
116	struct NoIntroItem* rom;
117
118	memset(game, 0, sizeof(*game));
119	subitem = HashTableLookup(&item->hash, "name");
120	if (subitem && subitem->type == NI_STRING) {
121		game->name = subitem->string;
122	}
123	subitem = HashTableLookup(&item->hash, "description");
124	if (subitem && subitem->type == NI_STRING) {
125		game->description = subitem->string;
126	}
127
128	rom = HashTableLookup(&item->hash, "rom");
129	if (!rom || rom->type != NI_HASH) {
130		return false;
131	}
132	subitem = HashTableLookup(&rom->hash, "name");
133	if (subitem && subitem->type == NI_STRING) {
134		game->romName = subitem->string;
135	}
136	subitem = HashTableLookup(&rom->hash, "size");
137	if (subitem && subitem->type == NI_STRING) {
138		char* end;
139		game->size = strtoul(subitem->string, &end, 0);
140		if (!end || *end) {
141			game->size = 0;
142		}
143	}
144	// TODO: md5, sha1
145	subitem = HashTableLookup(&rom->hash, "flags");
146	if (subitem && subitem->type == NI_STRING && strcmp(subitem->string, "verified")) {
147		game->verified = true;
148	}
149
150	return true;
151}
152
153struct NoIntroDB* NoIntroDBLoad(struct VFile* vf) {
154	struct NoIntroDB* db = malloc(sizeof(*db));
155	HashTableInit(&db->categories, 0, _dbDeinit);
156	char line[512];
157	struct {
158		char* key;
159		struct NoIntroItem* item;
160	} keyStack[KEY_STACK_SIZE];
161	memset(keyStack, 0, sizeof(keyStack));
162	struct Table* parent = 0;
163
164	size_t stackDepth = 0;
165	while (true) {
166		ssize_t bytesRead = vf->readline(vf, line, sizeof(line));
167		if (!bytesRead) {
168			break;
169		}
170		ssize_t i;
171		const char* token;
172		for (i = 0; i < bytesRead; ++i) {
173			while (isspace((int) line[i]) && i < bytesRead) {
174				++i;
175			}
176			if (i >= bytesRead) {
177				break;
178			}
179			token = &line[i];
180			while (!isspace((int) line[i]) && i < bytesRead) {
181				++i;
182			}
183			if (i >= bytesRead) {
184				break;
185			}
186			switch (token[0]) {
187			case '(':
188				if (!keyStack[stackDepth].key) {
189					goto error;
190				}
191				keyStack[stackDepth].item = malloc(sizeof(*keyStack[stackDepth].item));
192				keyStack[stackDepth].item->type = NI_HASH;
193				HashTableInit(&keyStack[stackDepth].item->hash, 8, _itemDeinit);
194				if (parent) {
195					HashTableInsert(parent, keyStack[stackDepth].key, keyStack[stackDepth].item);
196				} else {
197					struct NoIntroCategory* category = HashTableLookup(&db->categories, keyStack[stackDepth].key);
198					if (!category) {
199						category = malloc(sizeof(*category));
200						NoIntroCategoryInit(category, 0);
201						HashTableInsert(&db->categories, keyStack[stackDepth].key, category);
202					}
203					*NoIntroCategoryAppend(category) = keyStack[stackDepth].item;
204				}
205				parent = &keyStack[stackDepth].item->hash;
206				++stackDepth;
207				if (stackDepth >= KEY_STACK_SIZE) {
208					goto error;
209				}
210				keyStack[stackDepth].key = 0;
211				break;
212			case ')':
213				if (keyStack[stackDepth].key || !stackDepth) {
214					goto error;
215				}
216				--stackDepth;
217				if (stackDepth) {
218					parent = &keyStack[stackDepth - 1].item->hash;
219				} else {
220					parent = 0;
221				}
222				free(keyStack[stackDepth].key);
223				keyStack[stackDepth].key = 0;
224				break;
225			case '"':
226				++token;
227				for (; line[i] != '"' && i < bytesRead; ++i);
228				// Fall through
229			default:
230				line[i] = '\0';
231				if (!keyStack[stackDepth].key) {
232					keyStack[stackDepth].key = strdup(token);
233				} else {
234					struct NoIntroItem* item = malloc(sizeof(*keyStack[stackDepth].item));
235					item->type = NI_STRING;
236					item->string = strdup(token);
237					if (parent) {
238						HashTableInsert(parent, keyStack[stackDepth].key, item);
239					} else {
240						struct NoIntroCategory* category = HashTableLookup(&db->categories, keyStack[stackDepth].key);
241						if (!category) {
242							category = malloc(sizeof(*category));
243							NoIntroCategoryInit(category, 0);
244							HashTableInsert(&db->categories, keyStack[stackDepth].key, category);
245						}
246						*NoIntroCategoryAppend(category) = item;
247					}
248					free(keyStack[stackDepth].key);
249					keyStack[stackDepth].key = 0;
250				}
251				break;
252			}
253		}
254	}
255
256	_indexU32x(db, &db->gameCrc, "game", "rom.crc");
257
258	return db;
259
260error:
261	HashTableDeinit(&db->categories);
262	free(db);
263	return 0;
264}
265
266void NoIntroDBDestroy(struct NoIntroDB* db) {
267	HashTableDeinit(&db->categories);
268}
269
270bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game) {
271	if (!db) {
272		return false;
273	}
274	struct NoIntroItem* item = TableLookup(&db->gameCrc, crc32);
275	if (item) {
276		return _itemToGame(item, game);
277	}
278	return false;
279}