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 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}