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}