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}