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}