all repos — mgba @ 67905d281bfecbb06f51f2ca5ac939df378734a5

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