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