all repos — mgba @ 413881fec20b27c486ade2a69e86050211636b29

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}
 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	struct Table* parent = 0;
162
163	size_t stackDepth = 0;
164	while (true) {
165		ssize_t bytesRead = vf->readline(vf, line, sizeof(line));
166		if (!bytesRead) {
167			break;
168		}
169		ssize_t i;
170		const char* token;
171		for (i = 0; i < bytesRead; ++i) {
172			while (isspace((int) line[i]) && i < bytesRead) {
173				++i;
174			}
175			if (i >= bytesRead) {
176				break;
177			}
178			token = &line[i];
179			while (!isspace((int) line[i]) && i < bytesRead) {
180				++i;
181			}
182			if (i >= bytesRead) {
183				break;
184			}
185			switch (token[0]) {
186			case '(':
187				if (!keyStack[stackDepth].key) {
188					goto error;
189				}
190				keyStack[stackDepth].item = malloc(sizeof(*keyStack[stackDepth].item));
191				keyStack[stackDepth].item->type = NI_HASH;
192				HashTableInit(&keyStack[stackDepth].item->hash, 8, _itemDeinit);
193				if (parent) {
194					HashTableInsert(parent, keyStack[stackDepth].key, keyStack[stackDepth].item);
195				} else {
196					struct NoIntroCategory* category = HashTableLookup(&db->categories, keyStack[stackDepth].key);
197					if (!category) {
198						category = malloc(sizeof(*category));
199						NoIntroCategoryInit(category, 0);
200						HashTableInsert(&db->categories, keyStack[stackDepth].key, category);
201					}
202					*NoIntroCategoryAppend(category) = keyStack[stackDepth].item;
203				}
204				parent = &keyStack[stackDepth].item->hash;
205				++stackDepth;
206				if (stackDepth >= KEY_STACK_SIZE) {
207					goto error;
208				}
209				keyStack[stackDepth].key = 0;
210				break;
211			case ')':
212				if (keyStack[stackDepth].key || !stackDepth) {
213					goto error;
214				}
215				--stackDepth;
216				if (stackDepth) {
217					parent = &keyStack[stackDepth - 1].item->hash;
218				} else {
219					parent = 0;
220				}
221				free(keyStack[stackDepth].key);
222				keyStack[stackDepth].key = 0;
223				break;
224			case '"':
225				++token;
226				for (; line[i] != '"' && i < bytesRead; ++i);
227				// Fall through
228			default:
229				line[i] = '\0';
230				if (!keyStack[stackDepth].key) {
231					keyStack[stackDepth].key = strdup(token);
232				} else {
233					struct NoIntroItem* item = malloc(sizeof(*keyStack[stackDepth].item));
234					item->type = NI_STRING;
235					item->string = strdup(token);
236					if (parent) {
237						HashTableInsert(parent, keyStack[stackDepth].key, item);
238					} else {
239						struct NoIntroCategory* category = HashTableLookup(&db->categories, keyStack[stackDepth].key);
240						if (!category) {
241							category = malloc(sizeof(*category));
242							NoIntroCategoryInit(category, 0);
243							HashTableInsert(&db->categories, keyStack[stackDepth].key, category);
244						}
245						*NoIntroCategoryAppend(category) = item;
246					}
247					free(keyStack[stackDepth].key);
248					keyStack[stackDepth].key = 0;
249				}
250				break;
251			}
252		}
253	}
254
255	_indexU32x(db, &db->gameCrc, "game", "rom.crc");
256
257	return db;
258
259error:
260	HashTableDeinit(&db->categories);
261	free(db);
262	return 0;
263}
264
265void NoIntroDBDestroy(struct NoIntroDB* db) {
266	HashTableDeinit(&db->categories);
267}
268
269bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game) {
270	if (!db) {
271		return false;
272	}
273	struct NoIntroItem* item = TableLookup(&db->gameCrc, crc32);
274	if (item) {
275		return _itemToGame(item, game);
276	}
277	return false;
278}