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}