all repos — mgba @ 40e9dfcf5c3f512fa332a48d5a478329971b0aca

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