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