all repos — mgba @ ac2097f0b69965835397aa8d10be2525d3a34c00

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