all repos — mgba @ cd34e1af24ffc9e7c0314b837789044fc2302701

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 "nointro.h"
  7
  8#include "util/crc32.h"
  9#include "util/table.h"
 10#include "util/vector.h"
 11#include "util/vfs.h"
 12
 13#define KEY_STACK_SIZE 8
 14
 15struct NoIntroDB {
 16	struct Table categories;
 17	struct Table gameCrc;
 18};
 19
 20struct NoIntroItem {
 21	union {
 22		struct Table hash;
 23		char* string;
 24	};
 25	enum NoIntroItemType {
 26		NI_HASH,
 27		NI_STRING
 28	} type;
 29};
 30
 31DECLARE_VECTOR(NoIntroCategory, struct NoIntroItem*);
 32DEFINE_VECTOR(NoIntroCategory, struct NoIntroItem*);
 33
 34static void _indexU32x(struct NoIntroDB* db, struct Table* table, const char* categoryKey, const char* key) {
 35	struct NoIntroCategory* category = HashTableLookup(&db->categories, categoryKey);
 36	if (!category) {
 37		return;
 38	}
 39	TableInit(table, 256, 0);
 40	char* tmpKey = strdup(key);
 41	const char* keyStack[KEY_STACK_SIZE] = { tmpKey };
 42	size_t i;
 43	for (i = 1; i < KEY_STACK_SIZE; ++i) {
 44		char* next = strchr(keyStack[i - 1], '.');
 45		if (!next) {
 46			break;
 47		}
 48		next[0] = '\0';
 49		keyStack[i] = next + 1;
 50	}
 51	for (i = 0; i < NoIntroCategorySize(category); ++i) {
 52		struct NoIntroItem* item = *NoIntroCategoryGetPointer(category, i);
 53		if (!item) {
 54			continue;
 55		}
 56		struct NoIntroItem* keyloc = item;
 57		size_t s;
 58		for (s = 0; s < KEY_STACK_SIZE && keyStack[s]; ++s) {
 59			if (keyloc->type != NI_HASH) {
 60				keyloc = 0;
 61				break;
 62			}
 63			keyloc = HashTableLookup(&keyloc->hash, keyStack[s]);
 64			if (!keyloc) {
 65				break;
 66			}
 67		}
 68		if (!keyloc || keyloc->type != NI_STRING) {
 69			continue;
 70		}
 71		char* end;
 72		uint32_t key = strtoul(keyloc->string, &end, 16);
 73		if (!end || *end) {
 74			continue;
 75		}
 76		TableInsert(table, key, item);
 77	}
 78	free(tmpKey);
 79}
 80
 81static void _itemDeinit(void* value) {
 82	struct NoIntroItem* item = value;
 83	switch (item->type) {
 84	case NI_STRING:
 85		free(item->string);
 86		break;
 87	case NI_HASH:
 88		HashTableDeinit(&item->hash);
 89		break;
 90	}
 91	free(item);
 92}
 93
 94static void _dbDeinit(void* value) {
 95	struct NoIntroCategory* category = value;
 96	size_t i;
 97	for (i = 0; i < NoIntroCategorySize(category); ++i) {
 98		struct NoIntroItem* item = *NoIntroCategoryGetPointer(category, i);
 99		switch (item->type) {
100		case NI_STRING:
101			free(item->string);
102			break;
103		case NI_HASH:
104			HashTableDeinit(&item->hash);
105			break;
106		}
107		free(item);
108	}
109	NoIntroCategoryDeinit(category);
110}
111
112static bool _itemToGame(const struct NoIntroItem* item, struct NoIntroGame* game) {
113	if (item->type != NI_HASH) {
114		return false;
115	}
116	struct NoIntroItem* subitem;
117	struct NoIntroItem* rom;
118
119	memset(game, 0, sizeof(*game));
120	subitem = HashTableLookup(&item->hash, "name");
121	if (subitem && subitem->type == NI_STRING) {
122		game->name = subitem->string;
123	}
124	subitem = HashTableLookup(&item->hash, "description");
125	if (subitem && subitem->type == NI_STRING) {
126		game->description = subitem->string;
127	}
128
129	rom = HashTableLookup(&item->hash, "rom");
130	if (!rom || rom->type != NI_HASH) {
131		return false;
132	}
133	subitem = HashTableLookup(&rom->hash, "name");
134	if (subitem && subitem->type == NI_STRING) {
135		game->romName = subitem->string;
136	}
137	subitem = HashTableLookup(&rom->hash, "size");
138	if (subitem && subitem->type == NI_STRING) {
139		char* end;
140		game->size = strtoul(subitem->string, &end, 0);
141		if (!end || *end) {
142			game->size = 0;
143		}
144	}
145	// TODO: md5, sha1
146	subitem = HashTableLookup(&rom->hash, "flags");
147	if (subitem && subitem->type == NI_STRING && strcmp(subitem->string, "verified")) {
148		game->verified = true;
149	}
150
151	return true;
152}
153
154struct NoIntroDB* NoIntroDBLoad(struct VFile* vf) {
155	struct NoIntroDB* db = malloc(sizeof(*db));
156	HashTableInit(&db->categories, 0, _dbDeinit);
157	char line[512];
158	struct {
159		char* key;
160		struct NoIntroItem* item;
161	} keyStack[KEY_STACK_SIZE] = {};
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}