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 wpath[MAX_PATH];
216 WCHAR wprojectName[MAX_PATH];
217 WCHAR* 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 if (PATH_SEP[0] != '\\') {
224 WCHAR* pathSep;
225 for (pathSep = wpath; pathSep = wcschr(pathSep, L'\\');) {
226 pathSep[0] = PATH_SEP[0];
227 }
228 }
229 WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
230#elif defined(PSP2)
231 snprintf(out, outLength, "ux0:data/%s", projectName);
232 sceIoMkdir(out, 0777);
233#elif defined(GEKKO) || defined(__SWITCH__)
234 snprintf(out, outLength, "/%s", projectName);
235 mkdir(out, 0777);
236#elif defined(_3DS)
237 snprintf(out, outLength, "/%s", projectName);
238 FSUSER_CreateDirectory(sdmcArchive, fsMakePath(PATH_ASCII, out), 0);
239#elif defined(__HAIKU__)
240 char path[B_PATH_NAME_LENGTH];
241 find_directory(B_USER_SETTINGS_DIRECTORY, 0, false, path, B_PATH_NAME_LENGTH);
242 snprintf(out, outLength, "%s/%s", path, binaryName);
243 mkdir(out, 0755);
244#else
245 char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
246 if (xdgConfigHome && xdgConfigHome[0] == '/') {
247 snprintf(out, outLength, "%s/%s", xdgConfigHome, binaryName);
248 mkdir(out, 0755);
249 return;
250 }
251 char* home = getenv("HOME");
252 snprintf(out, outLength, "%s/.config", home);
253 mkdir(out, 0755);
254 snprintf(out, outLength, "%s/.config/%s", home, binaryName);
255 mkdir(out, 0755);
256#endif
257}
258
259void mCoreConfigPortablePath(char* out, size_t outLength) {
260#ifdef _WIN32
261 wchar_t wpath[MAX_PATH];
262 HMODULE hModule = GetModuleHandleW(NULL);
263 GetModuleFileNameW(hModule, wpath, MAX_PATH);
264 PathRemoveFileSpecW(wpath);
265 if (PATH_SEP[0] != '\\') {
266 WCHAR* pathSep;
267 for (pathSep = wpath; pathSep = wcschr(pathSep, L'\\');) {
268 pathSep[0] = PATH_SEP[0];
269 }
270 }
271 WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
272 StringCchCatA(out, outLength, PATH_SEP "portable.ini");
273#elif defined(PSP2) || defined(GEKKO) || defined(__SWITCH__) || defined(_3DS)
274 out[0] = '\0';
275#else
276 getcwd(out, outLength);
277 strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
278#endif
279}
280
281bool mCoreConfigIsPortable(void) {
282 struct VFile* portable;
283 char portableDir[PATH_MAX];
284 mCoreConfigPortablePath(portableDir, sizeof(portableDir));
285 if (portableDir[0]) {
286 portable = VFileOpen(portableDir, O_RDONLY);
287 if (portable) {
288 portable->close(portable);
289 return true;
290 }
291 }
292 return false;
293}
294
295#endif
296
297const char* mCoreConfigGetValue(const struct mCoreConfig* config, const char* key) {
298 return _lookupValue(config, key);
299}
300
301bool mCoreConfigGetIntValue(const struct mCoreConfig* config, const char* key, int* value) {
302 return _lookupIntValue(config, key, value);
303}
304
305bool mCoreConfigGetUIntValue(const struct mCoreConfig* config, const char* key, unsigned* value) {
306 return _lookupUIntValue(config, key, value);
307}
308
309bool mCoreConfigGetFloatValue(const struct mCoreConfig* config, const char* key, float* value) {
310 return _lookupFloatValue(config, key, value);
311}
312
313void mCoreConfigSetValue(struct mCoreConfig* config, const char* key, const char* value) {
314 ConfigurationSetValue(&config->configTable, config->port, key, value);
315}
316
317void mCoreConfigSetIntValue(struct mCoreConfig* config, const char* key, int value) {
318 ConfigurationSetIntValue(&config->configTable, config->port, key, value);
319}
320
321void mCoreConfigSetUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
322 ConfigurationSetUIntValue(&config->configTable, config->port, key, value);
323}
324
325void mCoreConfigSetFloatValue(struct mCoreConfig* config, const char* key, float value) {
326 ConfigurationSetFloatValue(&config->configTable, config->port, key, value);
327}
328
329void mCoreConfigSetDefaultValue(struct mCoreConfig* config, const char* key, const char* value) {
330 ConfigurationSetValue(&config->defaultsTable, config->port, key, value);
331}
332
333void mCoreConfigSetDefaultIntValue(struct mCoreConfig* config, const char* key, int value) {
334 ConfigurationSetIntValue(&config->defaultsTable, config->port, key, value);
335}
336
337void mCoreConfigSetDefaultUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
338 ConfigurationSetUIntValue(&config->defaultsTable, config->port, key, value);
339}
340
341void mCoreConfigSetDefaultFloatValue(struct mCoreConfig* config, const char* key, float value) {
342 ConfigurationSetFloatValue(&config->defaultsTable, config->port, key, value);
343}
344
345void mCoreConfigSetOverrideValue(struct mCoreConfig* config, const char* key, const char* value) {
346 ConfigurationSetValue(&config->overridesTable, config->port, key, value);
347}
348
349void mCoreConfigSetOverrideIntValue(struct mCoreConfig* config, const char* key, int value) {
350 ConfigurationSetIntValue(&config->overridesTable, config->port, key, value);
351}
352
353void mCoreConfigSetOverrideUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
354 ConfigurationSetUIntValue(&config->overridesTable, config->port, key, value);
355}
356
357void mCoreConfigSetOverrideFloatValue(struct mCoreConfig* config, const char* key, float value) {
358 ConfigurationSetFloatValue(&config->overridesTable, config->port, key, value);
359}
360
361void mCoreConfigCopyValue(struct mCoreConfig* config, const struct mCoreConfig* src, const char* key) {
362 const char* value = mCoreConfigGetValue(src, key);
363 if (!value) {
364 return;
365 }
366 mCoreConfigSetValue(config, key, value);
367}
368
369void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) {
370 _lookupCharValue(config, "bios", &opts->bios);
371 _lookupCharValue(config, "shader", &opts->shader);
372 _lookupIntValue(config, "logLevel", &opts->logLevel);
373 _lookupIntValue(config, "frameskip", &opts->frameskip);
374 _lookupIntValue(config, "volume", &opts->volume);
375 _lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity);
376 _lookupFloatValue(config, "fpsTarget", &opts->fpsTarget);
377 unsigned audioBuffers;
378 if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {
379 opts->audioBuffers = audioBuffers;
380 }
381 _lookupUIntValue(config, "sampleRate", &opts->sampleRate);
382
383 int fakeBool;
384 if (_lookupIntValue(config, "useBios", &fakeBool)) {
385 opts->useBios = fakeBool;
386 }
387 if (_lookupIntValue(config, "audioSync", &fakeBool)) {
388 opts->audioSync = fakeBool;
389 }
390 if (_lookupIntValue(config, "videoSync", &fakeBool)) {
391 opts->videoSync = fakeBool;
392 }
393 if (_lookupIntValue(config, "lockAspectRatio", &fakeBool)) {
394 opts->lockAspectRatio = fakeBool;
395 }
396 if (_lookupIntValue(config, "lockIntegerScaling", &fakeBool)) {
397 opts->lockIntegerScaling = fakeBool;
398 }
399 if (_lookupIntValue(config, "interframeBlending", &fakeBool)) {
400 opts->interframeBlending = fakeBool;
401 }
402 if (_lookupIntValue(config, "resampleVideo", &fakeBool)) {
403 opts->resampleVideo = fakeBool;
404 }
405 if (_lookupIntValue(config, "suspendScreensaver", &fakeBool)) {
406 opts->suspendScreensaver = fakeBool;
407 }
408 if (_lookupIntValue(config, "mute", &fakeBool)) {
409 opts->mute = fakeBool;
410 }
411 if (_lookupIntValue(config, "skipBios", &fakeBool)) {
412 opts->skipBios = fakeBool;
413 }
414 if (_lookupIntValue(config, "rewindEnable", &fakeBool)) {
415 opts->rewindEnable = fakeBool;
416 }
417
418 _lookupIntValue(config, "fullscreen", &opts->fullscreen);
419 _lookupIntValue(config, "width", &opts->width);
420 _lookupIntValue(config, "height", &opts->height);
421
422 _lookupCharValue(config, "savegamePath", &opts->savegamePath);
423 _lookupCharValue(config, "savestatePath", &opts->savestatePath);
424 _lookupCharValue(config, "screenshotPath", &opts->screenshotPath);
425 _lookupCharValue(config, "patchPath", &opts->patchPath);
426 _lookupCharValue(config, "cheatsPath", &opts->cheatsPath);
427}
428
429void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) {
430 ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios);
431 ConfigurationSetValue(&config->defaultsTable, 0, "shader", opts->shader);
432 ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios);
433 ConfigurationSetIntValue(&config->defaultsTable, 0, "useBios", opts->useBios);
434 ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel);
435 ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip);
436 ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);
437 ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity);
438 ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
439 ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers);
440 ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);
441 ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync);
442 ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync);
443 ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen);
444 ConfigurationSetIntValue(&config->defaultsTable, 0, "width", opts->width);
445 ConfigurationSetIntValue(&config->defaultsTable, 0, "height", opts->height);
446 ConfigurationSetIntValue(&config->defaultsTable, 0, "volume", opts->volume);
447 ConfigurationSetIntValue(&config->defaultsTable, 0, "mute", opts->mute);
448 ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio);
449 ConfigurationSetIntValue(&config->defaultsTable, 0, "lockIntegerScaling", opts->lockIntegerScaling);
450 ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo);
451 ConfigurationSetIntValue(&config->defaultsTable, 0, "suspendScreensaver", opts->suspendScreensaver);
452}
453
454static void _configEnum(const char* key, const char* value, void* user) {
455 struct mCoreConfigEnumerateData* data = user;
456 if (!data->prefix || startswith(key, data->prefix)) {
457 data->handler(key, value, data->level, data->user);
458 }
459}
460
461void mCoreConfigEnumerate(const struct mCoreConfig* config, const char* prefix, void (*handler)(const char* key, const char* value, enum mCoreConfigLevel type, void* user), void* user) {
462 struct mCoreConfigEnumerateData handlerData = { handler, prefix, user, mCONFIG_LEVEL_DEFAULT };
463 ConfigurationEnumerate(&config->defaultsTable, config->port, _configEnum, &handlerData);
464 handlerData.level = mCONFIG_LEVEL_CUSTOM;
465 ConfigurationEnumerate(&config->configTable, config->port, _configEnum, &handlerData);
466 handlerData.level = mCONFIG_LEVEL_OVERRIDE;
467 ConfigurationEnumerate(&config->overridesTable, config->port, _configEnum, &handlerData);
468}
469
470// These two are basically placeholders in case the internal layout changes, e.g. for loading separate files
471struct Configuration* mCoreConfigGetInput(struct mCoreConfig* config) {
472 return &config->configTable;
473}
474
475struct Configuration* mCoreConfigGetOverrides(struct mCoreConfig* config) {
476 return &config->configTable;
477}
478
479const struct Configuration* mCoreConfigGetOverridesConst(const struct mCoreConfig* config) {
480 return &config->configTable;
481}
482
483void mCoreConfigFreeOpts(struct mCoreOptions* opts) {
484 free(opts->bios);
485 free(opts->shader);
486 free(opts->savegamePath);
487 free(opts->savestatePath);
488 free(opts->screenshotPath);
489 free(opts->patchPath);
490 free(opts->cheatsPath);
491 opts->bios = 0;
492 opts->shader = 0;
493 opts->savegamePath = 0;
494 opts->savestatePath = 0;
495 opts->screenshotPath = 0;
496 opts->patchPath = 0;
497 opts->cheatsPath = 0;
498}