all repos — mgba @ a5f3718f81fb62fa956e2994d5c98dcedc2c21ae

mGBA Game Boy Advance Emulator

src/core/config.c (view raw)

  1/* Copyright (c) 2013-2016 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/config.h>
  7
  8#include <mgba/core/version.h>
  9#include <mgba-util/formatting.h>
 10#include <mgba-util/string.h>
 11#include <mgba-util/vfs.h>
 12
 13#include <sys/stat.h>
 14
 15#ifdef _WIN32
 16#include <windows.h>
 17#include <shlwapi.h>
 18#include <shlobj.h>
 19#include <strsafe.h>
 20#endif
 21
 22#ifdef PSP2
 23#include <psp2/io/stat.h>
 24#endif
 25
 26#ifdef _3DS
 27#include <mgba-util/platform/3ds/3ds-vfs.h>
 28#endif
 29
 30#ifdef __HAIKU__
 31#include <FindDirectory.h>
 32#endif
 33
 34#define SECTION_NAME_MAX 128
 35
 36struct mCoreConfigEnumerateData {
 37	void (*handler)(const char* key, const char* value, enum mCoreConfigLevel type, void* user);
 38	const char* prefix;
 39	void* user;
 40	enum mCoreConfigLevel level;
 41};
 42
 43static const char* _lookupValue(const struct mCoreConfig* config, const char* key) {
 44	const char* value;
 45	if (config->port) {
 46		value = ConfigurationGetValue(&config->overridesTable, config->port, key);
 47		if (value) {
 48			return value;
 49		}
 50	}
 51	value = ConfigurationGetValue(&config->overridesTable, 0, key);
 52	if (value) {
 53		return value;
 54	}
 55	if (config->port) {
 56		value = ConfigurationGetValue(&config->configTable, config->port, key);
 57		if (value) {
 58			return value;
 59		}
 60	}
 61	value = ConfigurationGetValue(&config->configTable, 0, key);
 62	if (value) {
 63		return value;
 64	}
 65	if (config->port) {
 66		value = ConfigurationGetValue(&config->defaultsTable, config->port, key);
 67		if (value) {
 68			return value;
 69		}
 70	}
 71	return ConfigurationGetValue(&config->defaultsTable, 0, key);
 72}
 73
 74static bool _lookupCharValue(const struct mCoreConfig* config, const char* key, char** out) {
 75	const char* value = _lookupValue(config, key);
 76	if (!value) {
 77		return false;
 78	}
 79	if (*out) {
 80		free(*out);
 81	}
 82	*out = strdup(value);
 83	return true;
 84}
 85
 86static bool _lookupIntValue(const struct mCoreConfig* config, const char* key, int* out) {
 87	const char* charValue = _lookupValue(config, key);
 88	if (!charValue) {
 89		return false;
 90	}
 91	char* end;
 92	long value = strtol(charValue, &end, 10);
 93	if (end == &charValue[1] && *end == 'x') {
 94		value = strtol(charValue, &end, 16);
 95	}
 96	if (*end) {
 97		return false;
 98	}
 99	*out = value;
100	return true;
101}
102
103static bool _lookupUIntValue(const struct mCoreConfig* config, const char* key, unsigned* out) {
104	const char* charValue = _lookupValue(config, key);
105	if (!charValue) {
106		return false;
107	}
108	char* end;
109	unsigned long value = strtoul(charValue, &end, 10);
110	if (*end) {
111		return false;
112	}
113	*out = value;
114	return true;
115}
116
117static bool _lookupFloatValue(const struct mCoreConfig* config, const char* key, float* out) {
118	const char* charValue = _lookupValue(config, key);
119	if (!charValue) {
120		return false;
121	}
122	char* end;
123	float value = strtof_u(charValue, &end);
124	if (*end) {
125		return false;
126	}
127	*out = value;
128	return true;
129}
130
131void mCoreConfigInit(struct mCoreConfig* config, const char* port) {
132	ConfigurationInit(&config->configTable);
133	ConfigurationInit(&config->defaultsTable);
134	ConfigurationInit(&config->overridesTable);
135	if (port) {
136		config->port = malloc(strlen("ports.") + strlen(port) + 1);
137		snprintf(config->port, strlen("ports.") + strlen(port) + 1, "ports.%s", port);
138	} else {
139		config->port = 0;
140	}
141}
142
143void mCoreConfigDeinit(struct mCoreConfig* config) {
144	ConfigurationDeinit(&config->configTable);
145	ConfigurationDeinit(&config->defaultsTable);
146	ConfigurationDeinit(&config->overridesTable);
147	free(config->port);
148}
149
150#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
151bool mCoreConfigLoad(struct mCoreConfig* config) {
152	char path[PATH_MAX];
153	mCoreConfigDirectory(path, PATH_MAX);
154	strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
155	return mCoreConfigLoadPath(config, path);
156}
157
158bool mCoreConfigSave(const struct mCoreConfig* config) {
159	char path[PATH_MAX];
160	mCoreConfigDirectory(path, PATH_MAX);
161	strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
162	return mCoreConfigSavePath(config, path);
163}
164
165bool mCoreConfigLoadPath(struct mCoreConfig* config, const char* path) {
166	return ConfigurationRead(&config->configTable, path);
167}
168
169bool mCoreConfigSavePath(const struct mCoreConfig* config, const char* path) {
170	return ConfigurationWrite(&config->configTable, path);
171}
172
173bool mCoreConfigLoadVFile(struct mCoreConfig* config, struct VFile* vf) {
174	return ConfigurationReadVFile(&config->configTable, vf);
175}
176
177bool mCoreConfigSaveVFile(const struct mCoreConfig* config, struct VFile* vf) {
178	return ConfigurationWriteVFile(&config->configTable, vf);
179}
180
181void mCoreConfigMakePortable(const struct mCoreConfig* config) {
182	struct VFile* portable = NULL;
183	char out[PATH_MAX];
184	mCoreConfigPortablePath(out, sizeof(out));
185	if (!out[0]) {
186		// Cannot be made portable
187		return;
188	}
189	portable = VFileOpen(out, O_WRONLY | O_CREAT);
190	if (portable) {
191		portable->close(portable);
192		mCoreConfigSave(config);
193	}
194}
195
196void mCoreConfigDirectory(char* out, size_t outLength) {
197	struct VFile* portable;
198	char portableDir[PATH_MAX];
199	mCoreConfigPortablePath(portableDir, sizeof(portableDir));
200	if (portableDir[0]) {
201		portable = VFileOpen(portableDir, O_RDONLY);
202		if (portable) {
203			portable->close(portable);
204			if (outLength < PATH_MAX) {
205				char outTmp[PATH_MAX];
206				separatePath(portableDir, outTmp, NULL, NULL);
207				strlcpy(out, outTmp, outLength);
208			} else {
209				separatePath(portableDir, out, NULL, NULL);
210			}
211			return;
212		}
213	}
214#ifdef _WIN32
215	wchar_t wpath[MAX_PATH];
216	wchar_t wprojectName[MAX_PATH];
217	wchar_t* home;
218	MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH);
219	SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &home);
220	StringCchPrintfW(wpath, MAX_PATH, L"%ws\\%ws", home, wprojectName);
221	CoTaskMemFree(home);
222	CreateDirectoryW(wpath, NULL);
223	WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
224#elif defined(PSP2)
225	snprintf(out, outLength, "ux0:data/%s", projectName);
226	sceIoMkdir(out, 0777);
227#elif defined(GEKKO) || defined(__SWITCH__)
228	snprintf(out, outLength, "/%s", projectName);
229	mkdir(out, 0777);
230#elif defined(_3DS)
231	snprintf(out, outLength, "/%s", projectName);
232	FSUSER_CreateDirectory(sdmcArchive, fsMakePath(PATH_ASCII, out), 0);
233#elif defined(__HAIKU__)
234	char path[B_PATH_NAME_LENGTH];
235	find_directory(B_USER_SETTINGS_DIRECTORY, 0, false, path, B_PATH_NAME_LENGTH);
236	snprintf(out, outLength, "%s/%s", path, binaryName);
237	mkdir(out, 0755);
238#else
239	char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
240	if (xdgConfigHome && xdgConfigHome[0] == '/') {
241		snprintf(out, outLength, "%s/%s", xdgConfigHome, binaryName);
242		mkdir(out, 0755);
243		return;
244	}
245	char* home = getenv("HOME");
246	snprintf(out, outLength, "%s/.config", home);
247	mkdir(out, 0755);
248	snprintf(out, outLength, "%s/.config/%s", home, binaryName);
249	mkdir(out, 0755);
250#endif
251}
252
253void mCoreConfigPortablePath(char* out, size_t outLength) {
254#ifdef _WIN32
255	wchar_t wpath[MAX_PATH];
256	HMODULE hModule = GetModuleHandleW(NULL);
257	GetModuleFileNameW(hModule, wpath, MAX_PATH);
258	PathRemoveFileSpecW(wpath);
259	WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
260	StringCchCatA(out, outLength, "\\portable.ini");
261#elif defined(PSP2) || defined(GEKKO) || defined(__SWITCH__) || defined(_3DS)
262	out[0] = '\0';
263#else
264	getcwd(out, outLength);
265	strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
266#endif
267}
268
269bool mCoreConfigIsPortable(void) {
270	struct VFile* portable;
271	char portableDir[PATH_MAX];
272	mCoreConfigPortablePath(portableDir, sizeof(portableDir));
273	if (portableDir[0]) {
274		portable = VFileOpen(portableDir, O_RDONLY);
275		if (portable) {
276			portable->close(portable);
277			return true;
278		}
279	}
280	return false;
281}
282
283#endif
284
285const char* mCoreConfigGetValue(const struct mCoreConfig* config, const char* key) {
286	return _lookupValue(config, key);
287}
288
289bool mCoreConfigGetIntValue(const struct mCoreConfig* config, const char* key, int* value) {
290	return _lookupIntValue(config, key, value);
291}
292
293bool mCoreConfigGetUIntValue(const struct mCoreConfig* config, const char* key, unsigned* value) {
294	return _lookupUIntValue(config, key, value);
295}
296
297bool mCoreConfigGetFloatValue(const struct mCoreConfig* config, const char* key, float* value) {
298	return _lookupFloatValue(config, key, value);
299}
300
301void mCoreConfigSetValue(struct mCoreConfig* config, const char* key, const char* value) {
302	ConfigurationSetValue(&config->configTable, config->port, key, value);
303}
304
305void mCoreConfigSetIntValue(struct mCoreConfig* config, const char* key, int value) {
306	ConfigurationSetIntValue(&config->configTable, config->port, key, value);
307}
308
309void mCoreConfigSetUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
310	ConfigurationSetUIntValue(&config->configTable, config->port, key, value);
311}
312
313void mCoreConfigSetFloatValue(struct mCoreConfig* config, const char* key, float value) {
314	ConfigurationSetFloatValue(&config->configTable, config->port, key, value);
315}
316
317void mCoreConfigSetDefaultValue(struct mCoreConfig* config, const char* key, const char* value) {
318	ConfigurationSetValue(&config->defaultsTable, config->port, key, value);
319}
320
321void mCoreConfigSetDefaultIntValue(struct mCoreConfig* config, const char* key, int value) {
322	ConfigurationSetIntValue(&config->defaultsTable, config->port, key, value);
323}
324
325void mCoreConfigSetDefaultUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
326	ConfigurationSetUIntValue(&config->defaultsTable, config->port, key, value);
327}
328
329void mCoreConfigSetDefaultFloatValue(struct mCoreConfig* config, const char* key, float value) {
330	ConfigurationSetFloatValue(&config->defaultsTable, config->port, key, value);
331}
332
333void mCoreConfigSetOverrideValue(struct mCoreConfig* config, const char* key, const char* value) {
334	ConfigurationSetValue(&config->overridesTable, config->port, key, value);
335}
336
337void mCoreConfigSetOverrideIntValue(struct mCoreConfig* config, const char* key, int value) {
338	ConfigurationSetIntValue(&config->overridesTable, config->port, key, value);
339}
340
341void mCoreConfigSetOverrideUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
342	ConfigurationSetUIntValue(&config->overridesTable, config->port, key, value);
343}
344
345void mCoreConfigSetOverrideFloatValue(struct mCoreConfig* config, const char* key, float value) {
346	ConfigurationSetFloatValue(&config->overridesTable, config->port, key, value);
347}
348
349void mCoreConfigCopyValue(struct mCoreConfig* config, const struct mCoreConfig* src, const char* key) {
350	const char* value = mCoreConfigGetValue(src, key);
351	if (!value) {
352		return;
353	}
354	mCoreConfigSetValue(config, key, value);
355}
356
357void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) {
358	_lookupCharValue(config, "bios", &opts->bios);
359	_lookupCharValue(config, "shader", &opts->shader);
360	_lookupIntValue(config, "logLevel", &opts->logLevel);
361	_lookupIntValue(config, "frameskip", &opts->frameskip);
362	_lookupIntValue(config, "volume", &opts->volume);
363	_lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity);
364	_lookupFloatValue(config, "fpsTarget", &opts->fpsTarget);
365	unsigned audioBuffers;
366	if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {
367		opts->audioBuffers = audioBuffers;
368	}
369	_lookupUIntValue(config, "sampleRate", &opts->sampleRate);
370
371	int fakeBool;
372	if (_lookupIntValue(config, "useBios", &fakeBool)) {
373		opts->useBios = fakeBool;
374	}
375	if (_lookupIntValue(config, "audioSync", &fakeBool)) {
376		opts->audioSync = fakeBool;
377	}
378	if (_lookupIntValue(config, "videoSync", &fakeBool)) {
379		opts->videoSync = fakeBool;
380	}
381	if (_lookupIntValue(config, "lockAspectRatio", &fakeBool)) {
382		opts->lockAspectRatio = fakeBool;
383	}
384	if (_lookupIntValue(config, "lockIntegerScaling", &fakeBool)) {
385		opts->lockIntegerScaling = fakeBool;
386	}
387	if (_lookupIntValue(config, "interframeBlending", &fakeBool)) {
388		opts->interframeBlending = fakeBool;
389	}
390	if (_lookupIntValue(config, "resampleVideo", &fakeBool)) {
391		opts->resampleVideo = fakeBool;
392	}
393	if (_lookupIntValue(config, "suspendScreensaver", &fakeBool)) {
394		opts->suspendScreensaver = fakeBool;
395	}
396	if (_lookupIntValue(config, "mute", &fakeBool)) {
397		opts->mute = fakeBool;
398	}
399	if (_lookupIntValue(config, "skipBios", &fakeBool)) {
400		opts->skipBios = fakeBool;
401	}
402	if (_lookupIntValue(config, "rewindEnable", &fakeBool)) {
403		opts->rewindEnable = fakeBool;
404	}
405
406	_lookupIntValue(config, "fullscreen", &opts->fullscreen);
407	_lookupIntValue(config, "width", &opts->width);
408	_lookupIntValue(config, "height", &opts->height);
409
410	_lookupCharValue(config, "savegamePath", &opts->savegamePath);
411	_lookupCharValue(config, "savestatePath", &opts->savestatePath);
412	_lookupCharValue(config, "screenshotPath", &opts->screenshotPath);
413	_lookupCharValue(config, "patchPath", &opts->patchPath);
414	_lookupCharValue(config, "cheatsPath", &opts->cheatsPath);
415}
416
417void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) {
418	ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios);
419	ConfigurationSetValue(&config->defaultsTable, 0, "shader", opts->shader);
420	ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios);
421	ConfigurationSetIntValue(&config->defaultsTable, 0, "useBios", opts->useBios);
422	ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel);
423	ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip);
424	ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);
425	ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity);
426	ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
427	ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers);
428	ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);
429	ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync);
430	ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync);
431	ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen);
432	ConfigurationSetIntValue(&config->defaultsTable, 0, "width", opts->width);
433	ConfigurationSetIntValue(&config->defaultsTable, 0, "height", opts->height);
434	ConfigurationSetIntValue(&config->defaultsTable, 0, "volume", opts->volume);
435	ConfigurationSetIntValue(&config->defaultsTable, 0, "mute", opts->mute);
436	ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio);
437	ConfigurationSetIntValue(&config->defaultsTable, 0, "lockIntegerScaling", opts->lockIntegerScaling);
438	ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo);
439	ConfigurationSetIntValue(&config->defaultsTable, 0, "suspendScreensaver", opts->suspendScreensaver);
440}
441
442static void _configEnum(const char* key, const char* value, void* user) {
443	struct mCoreConfigEnumerateData* data = user;
444	if (!data->prefix || startswith(key, data->prefix)) {
445		data->handler(key, value, data->level, data->user);
446	}
447}
448
449void mCoreConfigEnumerate(const struct mCoreConfig* config, const char* prefix, void (*handler)(const char* key, const char* value, enum mCoreConfigLevel type, void* user), void* user) {
450	struct mCoreConfigEnumerateData handlerData = { handler, prefix, user, mCONFIG_LEVEL_DEFAULT };
451	ConfigurationEnumerate(&config->defaultsTable, config->port, _configEnum, &handlerData);
452	handlerData.level = mCONFIG_LEVEL_CUSTOM;
453	ConfigurationEnumerate(&config->configTable, config->port, _configEnum, &handlerData);
454	handlerData.level = mCONFIG_LEVEL_OVERRIDE;
455	ConfigurationEnumerate(&config->overridesTable, config->port, _configEnum, &handlerData);
456}
457
458// These two are basically placeholders in case the internal layout changes, e.g. for loading separate files
459struct Configuration* mCoreConfigGetInput(struct mCoreConfig* config) {
460	return &config->configTable;
461}
462
463struct Configuration* mCoreConfigGetOverrides(struct mCoreConfig* config) {
464	return &config->configTable;
465}
466
467const struct Configuration* mCoreConfigGetOverridesConst(const struct mCoreConfig* config) {
468	return &config->configTable;
469}
470
471void mCoreConfigFreeOpts(struct mCoreOptions* opts) {
472	free(opts->bios);
473	free(opts->shader);
474	free(opts->savegamePath);
475	free(opts->savestatePath);
476	free(opts->screenshotPath);
477	free(opts->patchPath);
478	free(opts->cheatsPath);
479	opts->bios = 0;
480	opts->shader = 0;
481	opts->savegamePath = 0;
482	opts->savestatePath = 0;
483	opts->screenshotPath = 0;
484	opts->patchPath = 0;
485	opts->cheatsPath = 0;
486}