all repos — mgba @ 0d6efaa3dc3ab5a19b0a2e7a0d93fad50a53e324

mGBA Game Boy Advance Emulator

src/core/library.c (view raw)

  1/* Copyright (c) 2013-2017 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/core/library.h>
  7
  8#include <mgba/core/core.h>
  9#include <mgba-util/vfs.h>
 10
 11#ifdef USE_SQLITE3
 12
 13#include <sqlite3.h>
 14
 15DEFINE_VECTOR(mLibraryListing, struct mLibraryEntry);
 16
 17struct mLibrary {
 18	sqlite3* db;
 19	sqlite3_stmt* insertPath;
 20	sqlite3_stmt* insertRom;
 21	sqlite3_stmt* insertRoot;
 22	sqlite3_stmt* selectRom;
 23	sqlite3_stmt* selectRoot;
 24	sqlite3_stmt* deletePath;
 25	sqlite3_stmt* deleteRoot;
 26	sqlite3_stmt* count;
 27	sqlite3_stmt* select;
 28};
 29
 30#define CONSTRAINTS_ROMONLY \
 31	"CASE WHEN :useSize THEN roms.size = :size ELSE 1 END AND " \
 32	"CASE WHEN :usePlatform THEN roms.platform = :platform ELSE 1 END AND " \
 33	"CASE WHEN :useCrc32 THEN roms.crc32 = :crc32 ELSE 1 END AND " \
 34	"CASE WHEN :useInternalCode THEN roms.internalCode = :internalCode ELSE 1 END"
 35
 36#define CONSTRAINTS \
 37	CONSTRAINTS_ROMONLY " AND " \
 38	"CASE WHEN :useFilename THEN paths.path = :path ELSE 1 END AND " \
 39	"CASE WHEN :useRoot THEN roots.path = :root ELSE 1 END"
 40
 41static void _mLibraryDeleteEntry(struct mLibrary* library, struct mLibraryEntry* entry);
 42static void _mLibraryInsertEntry(struct mLibrary* library, struct mLibraryEntry* entry);
 43static void _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf);
 44
 45static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry* constraints) {
 46	if (!constraints) {
 47		return;
 48	}
 49
 50	int useIndex, index;
 51	if (constraints->crc32) {
 52		useIndex = sqlite3_bind_parameter_index(statement, ":useCrc32");
 53		index = sqlite3_bind_parameter_index(statement, ":crc32");
 54		sqlite3_bind_int(statement, useIndex, 1);
 55		sqlite3_bind_int(statement, index, constraints->crc32);
 56	}
 57
 58	if (constraints->filesize) {
 59		useIndex = sqlite3_bind_parameter_index(statement, ":useSize");
 60		index = sqlite3_bind_parameter_index(statement, ":size");
 61		sqlite3_bind_int(statement, useIndex, 1);
 62		sqlite3_bind_int64(statement, index, constraints->filesize);
 63	}
 64
 65	if (constraints->filename) {
 66		useIndex = sqlite3_bind_parameter_index(statement, ":useFilename");
 67		index = sqlite3_bind_parameter_index(statement, ":path");
 68		sqlite3_bind_int(statement, useIndex, 1);
 69		sqlite3_bind_text(statement, index, constraints->filename, -1, SQLITE_TRANSIENT);
 70	}
 71
 72	if (constraints->base) {
 73		useIndex = sqlite3_bind_parameter_index(statement, ":useRoot");
 74		index = sqlite3_bind_parameter_index(statement, ":root");
 75		sqlite3_bind_int(statement, useIndex, 1);
 76		sqlite3_bind_text(statement, index, constraints->base, -1, SQLITE_TRANSIENT);
 77	}
 78
 79	if (constraints->internalCode[0]) {
 80		useIndex = sqlite3_bind_parameter_index(statement, ":useInternalCode");
 81		index = sqlite3_bind_parameter_index(statement, ":internalCode");
 82		sqlite3_bind_int(statement, useIndex, 1);
 83		sqlite3_bind_text(statement, index, constraints->internalCode, -1, SQLITE_TRANSIENT);
 84	}
 85
 86	if (constraints->platform != PLATFORM_NONE) {
 87		useIndex = sqlite3_bind_parameter_index(statement, ":usePlatform");
 88		index = sqlite3_bind_parameter_index(statement, ":platform");
 89		sqlite3_bind_int(statement, useIndex, 1);
 90		sqlite3_bind_int(statement, index, constraints->platform);
 91	}
 92}
 93
 94struct mLibrary* mLibraryCreateEmpty(void) {
 95	return mLibraryLoad(":memory:");
 96}
 97
 98struct mLibrary* mLibraryLoad(const char* path) {
 99	struct mLibrary* library = malloc(sizeof(*library));
100	memset(library, 0, sizeof(*library));
101
102	if (sqlite3_open_v2(path, &library->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL)) {
103		goto error;
104	}
105
106	static const char createTables[] =
107		"   PRAGMA foreign_keys = ON;"
108		"\n CREATE TABLE IF NOT EXISTS version ("
109		"\n 	tname TEXT NOT NULL PRIMARY KEY,"
110		"\n 	version INTEGER NOT NULL DEFAULT 1"
111		"\n );"
112		"\n CREATE TABLE IF NOT EXISTS roots ("
113		"\n 	rootid INTEGER NOT NULL PRIMARY KEY ASC,"
114		"\n 	path TEXT NOT NULL UNIQUE,"
115		"\n 	mtime INTEGER NOT NULL DEFAULT 0"
116		"\n );"
117		"\n CREATE TABLE IF NOT EXISTS roms ("
118		"\n 	romid INTEGER NOT NULL PRIMARY KEY ASC,"
119		"\n 	internalTitle TEXT,"
120		"\n 	internalCode TEXT,"
121		"\n 	platform INTEGER NOT NULL DEFAULT -1,"
122		"\n 	size INTEGER,"
123		"\n 	crc32 INTEGER,"
124		"\n 	md5 BLOB,"
125		"\n 	sha1 BLOB"
126		"\n );"
127		"\n CREATE TABLE IF NOT EXISTS paths ("
128		"\n 	pathid INTEGER NOT NULL PRIMARY KEY ASC,"
129		"\n 	romid INTEGER NOT NULL REFERENCES roms(romid) ON DELETE CASCADE,"
130		"\n 	path TEXT NOT NULL,"
131		"\n 	mtime INTEGER NOT NULL DEFAULT 0,"
132		"\n 	rootid INTEGER REFERENCES roots(rootid) ON DELETE CASCADE,"
133		"\n 	customTitle TEXT,"
134		"\n 	CONSTRAINT location UNIQUE (path, rootid)"
135		"\n );"
136		"\n CREATE INDEX IF NOT EXISTS crc32 ON roms (crc32);"
137		"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('version', 1);"
138		"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roots', 1);"
139		"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 1);"
140		"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('paths', 1);";
141	if (sqlite3_exec(library->db, createTables, NULL, NULL, NULL)) {
142		goto error;
143	}
144
145	static const char insertPath[] = "INSERT INTO paths (romid, path, customTitle, rootid) VALUES (?, ?, ?, ?);";
146	if (sqlite3_prepare_v2(library->db, insertPath, -1, &library->insertPath, NULL)) {
147		goto error;
148	}
149
150	static const char insertRom[] = "INSERT INTO roms (crc32, size, internalCode, platform) VALUES (:crc32, :size, :internalCode, :platform);";
151	if (sqlite3_prepare_v2(library->db, insertRom, -1, &library->insertRom, NULL)) {
152		goto error;
153	}
154
155	static const char insertRoot[] = "INSERT INTO roots (path) VALUES (?);";
156	if (sqlite3_prepare_v2(library->db, insertRoot, -1, &library->insertRoot, NULL)) {
157		goto error;
158	}
159
160	static const char deleteRoot[] = "DELETE FROM roots WHERE path = ?;";
161	if (sqlite3_prepare_v2(library->db, deleteRoot, -1, &library->deleteRoot, NULL)) {
162		goto error;
163	}
164
165	static const char deletePath[] = "DELETE FROM paths WHERE path = ?;";
166	if (sqlite3_prepare_v2(library->db, deletePath, -1, &library->deletePath, NULL)) {
167		goto error;
168	}
169
170	static const char selectRom[] = "SELECT romid FROM roms WHERE " CONSTRAINTS_ROMONLY ";";
171	if (sqlite3_prepare_v2(library->db, selectRom, -1, &library->selectRom, NULL)) {
172		goto error;
173	}
174
175	static const char selectRoot[] = "SELECT rootid FROM roots WHERE path = ? AND CASE WHEN :useMtime THEN mtime <= :mtime ELSE 1 END;";
176	if (sqlite3_prepare_v2(library->db, selectRoot, -1, &library->selectRoot, NULL)) {
177		goto error;
178	}
179
180	static const char count[] = "SELECT count(pathid) FROM paths JOIN roots USING (rootid) JOIN roms USING (romid) WHERE " CONSTRAINTS ";";
181	if (sqlite3_prepare_v2(library->db, count, -1, &library->count, NULL)) {
182		goto error;
183	}
184
185	static const char select[] = "SELECT *, paths.path AS filename, roots.path AS base FROM paths JOIN roots USING (rootid) JOIN roms USING (romid) WHERE " CONSTRAINTS " LIMIT :count OFFSET :offset;";
186	if (sqlite3_prepare_v2(library->db, select, -1, &library->select, NULL)) {
187		goto error;
188	}
189
190	return library;
191
192error:
193	mLibraryDestroy(library);
194	return NULL;
195}
196
197void mLibraryDestroy(struct mLibrary* library) {
198	sqlite3_finalize(library->insertPath);
199	sqlite3_finalize(library->insertRom);
200	sqlite3_finalize(library->insertRoot);
201	sqlite3_finalize(library->deletePath);
202	sqlite3_finalize(library->deleteRoot);
203	sqlite3_finalize(library->selectRom);
204	sqlite3_finalize(library->selectRoot);
205	sqlite3_finalize(library->select);
206	sqlite3_finalize(library->count);
207	sqlite3_close(library->db);
208}
209
210void mLibraryLoadDirectory(struct mLibrary* library, const char* base) {
211	struct VDir* dir = VDirOpenArchive(base);
212	if (!dir) {
213		dir = VDirOpen(base);
214	}
215	sqlite3_exec(library->db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
216	if (!dir) {
217		sqlite3_clear_bindings(library->deleteRoot);
218		sqlite3_reset(library->deleteRoot);
219		sqlite3_bind_text(library->deleteRoot, 1, base, -1, SQLITE_TRANSIENT);
220		sqlite3_step(library->deleteRoot);
221		sqlite3_exec(library->db, "COMMIT;", NULL, NULL, NULL);
222		return;
223	}
224
225	struct mLibraryEntry entry;
226	memset(&entry, 0, sizeof(entry));
227	entry.base = base;
228	struct mLibraryListing entries;
229	mLibraryListingInit(&entries, 0);
230	mLibraryGetEntries(library, &entries, 0, 0, &entry);
231	size_t i;
232	for (i = 0; i < mLibraryListingSize(&entries); ++i) {
233		struct mLibraryEntry* current = mLibraryListingGetPointer(&entries, i);
234		struct VFile* vf = dir->openFile(dir, current->filename, O_RDONLY);
235		_mLibraryDeleteEntry(library, current);
236		if (!vf) {
237			continue;
238		}
239		_mLibraryAddEntry(library, current->filename, base, vf);
240	}
241	mLibraryListingDeinit(&entries);
242
243	dir->rewind(dir);
244	struct VDirEntry* dirent = dir->listNext(dir);
245	while (dirent) {
246		struct VFile* vf = dir->openFile(dir, dirent->name(dirent), O_RDONLY);
247		if (!vf) {
248			dirent = dir->listNext(dir);
249			continue;
250		}
251		_mLibraryAddEntry(library, dirent->name(dirent), base, vf);
252		dirent = dir->listNext(dir);
253	}
254	dir->close(dir);
255	sqlite3_exec(library->db, "COMMIT;", NULL, NULL, NULL);
256}
257
258void _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf) {
259	struct mCore* core;
260	if (!vf) {
261		return;
262	}
263	core = mCoreFindVF(vf);
264	if (core) {
265		struct mLibraryEntry entry;
266		memset(&entry, 0, sizeof(entry));
267		core->init(core);
268		core->loadROM(core, vf);
269
270		core->getGameTitle(core, entry.internalTitle);
271		core->getGameCode(core, entry.internalCode);
272		core->checksum(core, &entry.crc32, CHECKSUM_CRC32);
273		entry.platform = core->platform(core);
274		entry.title = NULL;
275		entry.base = base;
276		entry.filename = filename;
277		entry.filesize = vf->size(vf);
278		_mLibraryInsertEntry(library, &entry);
279		// Note: this destroys the VFile
280		core->deinit(core);
281	} else {
282		vf->close(vf);
283	}
284}
285
286static void _mLibraryInsertEntry(struct mLibrary* library, struct mLibraryEntry* entry) {
287	sqlite3_clear_bindings(library->selectRom);
288	sqlite3_reset(library->selectRom);
289	struct mLibraryEntry constraints = *entry;
290	constraints.filename = NULL;
291	constraints.base = NULL;
292	_bindConstraints(library->selectRom, &constraints);
293	sqlite3_int64 romId;
294	if (sqlite3_step(library->selectRom) == SQLITE_DONE) {
295		sqlite3_clear_bindings(library->insertRom);
296		sqlite3_reset(library->insertRom);
297		_bindConstraints(library->insertRom, entry);
298		sqlite3_step(library->insertRom);
299		romId = sqlite3_last_insert_rowid(library->db);
300	} else {
301		romId = sqlite3_column_int64(library->selectRom, 0);
302	}
303
304	sqlite3_int64 rootId = 0;
305	if (entry->base) {
306		sqlite3_clear_bindings(library->selectRoot);
307		sqlite3_reset(library->selectRoot);
308		sqlite3_bind_text(library->selectRoot, 1, entry->base, -1, SQLITE_TRANSIENT);
309		if (sqlite3_step(library->selectRoot) == SQLITE_DONE) {
310			sqlite3_clear_bindings(library->insertRoot);
311			sqlite3_reset(library->insertRoot);
312			sqlite3_bind_text(library->insertRoot, 1, entry->base, -1, SQLITE_TRANSIENT);
313			sqlite3_step(library->insertRoot);
314			rootId = sqlite3_last_insert_rowid(library->db);
315		} else {
316			rootId = sqlite3_column_int64(library->selectRoot, 0);
317		}
318	}
319
320	sqlite3_clear_bindings(library->insertPath);
321	sqlite3_reset(library->insertPath);
322	sqlite3_bind_int64(library->insertPath, 1, romId);
323	sqlite3_bind_text(library->insertPath, 2, entry->filename, -1, SQLITE_TRANSIENT);
324	sqlite3_bind_text(library->insertPath, 3, entry->title, -1, SQLITE_TRANSIENT);
325	if (rootId > 0) {
326		sqlite3_bind_int64(library->insertPath, 4, rootId);
327	}
328	sqlite3_step(library->insertPath);
329}
330
331static void _mLibraryDeleteEntry(struct mLibrary* library, struct mLibraryEntry* entry) {
332	sqlite3_clear_bindings(library->deletePath);
333	sqlite3_reset(library->deletePath);
334	sqlite3_bind_text(library->deletePath, 1, entry->filename, -1, SQLITE_TRANSIENT);
335	sqlite3_step(library->insertPath);
336}
337
338size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints) {
339	sqlite3_clear_bindings(library->count);
340	sqlite3_reset(library->count);
341	_bindConstraints(library->count, constraints);
342	if (sqlite3_step(library->count) != SQLITE_ROW) {
343		return 0;
344	}
345	return sqlite3_column_int64(library->count, 0);
346}
347
348size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints) {
349	mLibraryListingClear(out); // TODO: Free memory
350	sqlite3_clear_bindings(library->select);
351	sqlite3_reset(library->select);
352	_bindConstraints(library->select, constraints);
353
354	int countIndex = sqlite3_bind_parameter_index(library->select, ":count");
355	int offsetIndex = sqlite3_bind_parameter_index(library->select, ":offset");
356	sqlite3_bind_int64(library->select, countIndex, numEntries ? numEntries : -1);
357	sqlite3_bind_int64(library->select, offsetIndex, offset);
358
359	size_t entryIndex;
360	for (entryIndex = 0; (!numEntries || entryIndex < numEntries) && sqlite3_step(library->select) == SQLITE_ROW; ++entryIndex) {
361		struct mLibraryEntry* entry = mLibraryListingAppend(out);
362		memset(entry, 0, sizeof(*entry));
363		int nCols = sqlite3_column_count(library->select);
364		int i;
365		for (i = 0; i < nCols; ++i) {
366			const char* colName = sqlite3_column_name(library->select, i);
367			if (strcmp(colName, "crc32") == 0) {
368				entry->crc32 = sqlite3_column_int(library->select, i);
369			} else if (strcmp(colName, "platform") == 0) {
370				entry->platform = sqlite3_column_int(library->select, i);
371			} else if (strcmp(colName, "size") == 0) {
372				entry->filesize = sqlite3_column_int64(library->select, i);
373			} else if (strcmp(colName, "internalCode") == 0 && sqlite3_column_type(library->select, i) == SQLITE_TEXT) {
374				strncpy(entry->internalCode, (const char*) sqlite3_column_text(library->select, i), sizeof(entry->internalCode) - 1);
375			} else if (strcmp(colName, "internalTitle") == 0 && sqlite3_column_type(library->select, i) == SQLITE_TEXT) {
376				strncpy(entry->internalTitle, (const char*) sqlite3_column_text(library->select, i), sizeof(entry->internalTitle) - 1);
377			} else if (strcmp(colName, "filename") == 0) {
378				entry->filename = strdup((const char*) sqlite3_column_text(library->select, i));
379			} else if (strcmp(colName, "base") == 0) {
380				entry->base =  strdup((const char*) sqlite3_column_text(library->select, i));
381			}
382		}
383	}
384	return mLibraryListingSize(out);
385}
386
387struct VFile* mLibraryOpenVFile(struct mLibrary* library, const struct mLibraryEntry* entry) {
388	struct mLibraryListing entries;
389	mLibraryListingInit(&entries, 0);
390	if (!mLibraryGetEntries(library, &entries, 0, 0, entry)) {
391		mLibraryListingDeinit(&entries);
392		return NULL;
393	}
394	struct VFile* vf = NULL;
395	size_t i;
396	for (i = 0; i < mLibraryListingSize(&entries); ++i) {
397		struct mLibraryEntry* e = mLibraryListingGetPointer(&entries, i);
398		struct VDir* dir = VDirOpenArchive(e->base);
399		bool isArchive = true;
400		if (!dir) {
401			dir = VDirOpen(e->base);
402			isArchive = false;
403		}
404		if (!dir) {
405			continue;
406		}
407		vf = dir->openFile(dir, e->filename, O_RDONLY);
408		if (vf && isArchive) {
409			struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf));
410			uint8_t buffer[2048];
411			ssize_t read;
412			while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) {
413				vfclone->write(vfclone, buffer, read);
414			}
415			vf->close(vf);
416			vf = vfclone;
417		}
418		dir->close(dir);
419		if (vf) {
420			break;
421		}
422	}
423	return vf;
424}
425
426#endif