2 * Human-readable config file management for PicoDrive
12 #include <Pico/Pico.h>
14 extern menu_entry opt_entries[];
15 extern menu_entry opt2_entries[];
16 extern menu_entry cdopt_entries[];
17 extern const int opt_entry_count;
18 extern const int opt2_entry_count;
19 extern const int cdopt_entry_count;
21 static menu_entry *cfg_opts[] = { opt_entries, opt2_entries, cdopt_entries };
22 static const int *cfg_opt_counts[] = { &opt_entry_count, &opt2_entry_count, &cdopt_entry_count };
27 static void mystrip(char *str)
32 for (i = 0; i < len; i++)
33 if (str[i] != ' ') break;
34 if (i > 0) memmove(str, str + i, len - i + 1);
36 for (i = len - 1; i >= 0; i--)
37 if (str[i] != ' ') break;
42 static int seek_sect(FILE *f, const char *section)
47 len = strlen(section);
48 // seek to the section needed
51 tmp = fgets(line, sizeof(line), f);
52 if (tmp == NULL) break;
54 if (line[0] != '[') continue; // not section start
55 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
63 static void custom_write(FILE *f, const menu_entry *me, int no_def)
70 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&0x10) &&
71 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
74 else if (currentConfig.EmuOpt&0x80)
75 str = "16bit accurate";
77 str = "8bit accurate";
78 fprintf(f, "Renderer = %s", str);
82 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
83 switch (currentConfig.scaling) {
84 default: str = "OFF"; break;
85 case 1: str = "hw horizontal"; break;
86 case 2: str = "hw horiz. + vert."; break;
87 case 3: str = "sw horizontal"; break;
89 fprintf(f, "Scaling = %s", str);
91 case MA_OPT_FRAMESKIP:
92 if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
93 if (currentConfig.Frameskip < 0)
94 strcpy(str24, "Auto");
95 else sprintf(str24, "%i", currentConfig.Frameskip);
96 fprintf(f, "Frameskip = %s", str24);
98 case MA_OPT_SOUND_QUALITY:
99 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&8) &&
100 defaultConfig.s_PsndRate == PsndRate) return;
101 str = (PicoOpt&0x08)?"stereo":"mono";
102 fprintf(f, "Sound Quality = %i %s", PsndRate, str);
105 if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
106 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
107 fprintf(f, "Region = %s", me_region_name(PicoRegionOverride, PicoAutoRgnOrder));
109 case MA_OPT_CONFIRM_STATES:
110 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
111 switch ((currentConfig.EmuOpt >> 9) & 5) {
112 default: str = "OFF"; break;
113 case 1: str = "writes"; break;
114 case 4: str = "loads"; break;
115 case 5: str = "both"; break;
117 fprintf(f, "Confirm savestate = %s", str);
119 case MA_OPT_CPU_CLOCKS:
120 if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
121 fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
124 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
125 fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
127 case MA_OPT2_SQUIDGEHACK:
128 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
129 fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
131 case MA_CDOPT_READAHEAD:
132 if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
133 sprintf(str24, "%i", PicoCDBuffers * 2);
134 fprintf(f, "ReadAhead buffer = %s", str24);
138 lprintf("unhandled custom_write: %i\n", me->id);
145 static const char *joyKeyNames[32] =
147 "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
148 "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12",
149 "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
150 "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
153 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
154 const int def_binds[32], const char *names[32], int no_defaults)
159 for (t = 0; t < 32; t++)
161 act[0] = act[31] = 0;
162 if (no_defaults && binds[t] == def_binds[t])
164 if (strcmp(names[t], "???") == 0) continue;
166 if (strcmp(names[t], "SELECT") == 0) continue;
168 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
169 if (me_ctrl_actions[i].mask & binds[t]) {
170 sprintf(act, "player%i ", ((binds[t]>>16)&1)+1);
171 strncpy(act + 8, me_ctrl_actions[i].name, 31);
177 for (i = 0; emuctrl_actions[i].name != NULL; i++)
178 if (emuctrl_actions[i].mask & binds[t]) {
179 strncpy(act, emuctrl_actions[i].name, 31);
185 fprintf(fn, "%s %s = %s" NL, bind_str, names[t], act);
190 static int default_var(const menu_entry *me)
194 case MA_OPT_ACC_TIMING:
195 case MA_OPT_ACC_SPRITES:
196 case MA_OPT_ARM940_SOUND:
197 case MA_OPT_6BUTTON_PAD:
198 case MA_OPT2_ENABLE_Z80:
199 case MA_OPT2_ENABLE_YM2612:
200 case MA_OPT2_ENABLE_SN76496:
201 case MA_OPT2_SVP_DYNAREC:
204 case MA_CDOPT_SAVERAM:
205 case MA_CDOPT_SCALEROT_CHIP:
206 case MA_CDOPT_BETTER_SYNC:
207 return defaultConfig.s_PicoOpt;
209 case MA_OPT_SHOW_FPS:
210 case MA_OPT_ENABLE_SOUND:
211 case MA_OPT_SRAM_STATES:
212 case MA_OPT2_A_SN_GAMMA:
214 case MA_OPT2_GZIP_STATES:
215 case MA_OPT2_NO_LAST_ROM:
216 case MA_OPT2_RAMTIMINGS:
218 return defaultConfig.EmuOpt;
220 case MA_OPT_SAVE_SLOT:
226 int config_writesect(const char *fname, const char *section)
228 FILE *fo = NULL, *fn = NULL; // old and new
229 int no_defaults = 0; // avoid saving defaults
232 char line[128], *tmp;
238 fo = fopen(fname, "r");
240 fn = fopen(fname, "w");
244 ret = seek_sect(fo, section);
246 // sect not found, we can simply append
247 fclose(fo); fo = NULL;
248 fn = fopen(fname, "a");
254 rename(fname, "tmp.cfg");
255 fo = fopen("tmp.cfg", "r");
256 fn = fopen(fname, "w");
257 if (fo == NULL || fn == NULL) goto write;
259 // copy everything until sect
260 tlen = strlen(section);
263 tmp = fgets(line, sizeof(line), fo);
264 if (tmp == NULL) break;
266 if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
271 // now skip to next sect
274 tmp = fgets(line, sizeof(line), fo);
275 if (tmp == NULL) break;
276 if (line[0] == '[') {
277 fseek(fo, -strlen(line), SEEK_CUR);
283 fclose(fo); fo = NULL;
289 fn = fopen(fname, "w");
298 fprintf(fn, "[%s]" NL, section);
300 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
303 tlen = *(cfg_opt_counts[t]);
304 for (i = 0; i < tlen; i++, me++)
306 if (!me->need_to_save) continue;
307 if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
308 custom_write(fn, me, no_defaults);
309 else if (me->beh == MB_ONOFF) {
310 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
311 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
312 } else if (me->beh == MB_RANGE) {
313 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
314 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
320 keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, no_defaults);
321 keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 1);
322 keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 1);
323 keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 1);
324 keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 1);
330 // copy whatever is left
333 tmp = fgets(line, sizeof(line), fo);
334 if (tmp == NULL) break;
347 int config_writelrom(const char *fname)
349 char line[128], *tmp, *optr = NULL;
350 char *old_data = NULL;
354 if (strlen(lastRomFile) == 0) return 0;
356 f = fopen(fname, "r");
359 fseek(f, 0, SEEK_END);
361 fseek(f, 0, SEEK_SET);
362 old_data = malloc(size + size/8);
363 if (old_data != NULL)
368 tmp = fgets(line, sizeof(line), f);
369 if (tmp == NULL) break;
371 if (strncasecmp(line, "LastUsedROM", 11) == 0)
373 sprintf(optr, "%s", line);
374 optr += strlen(optr);
380 f = fopen(fname, "w");
381 if (f == NULL) return -1;
383 if (old_data != NULL) {
384 fwrite(old_data, 1, optr - old_data, f);
387 fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
392 /* --------------------------------------------------------------------------*/
394 int config_readlrom(const char *fname)
396 char line[128], *tmp;
397 int i, len, ret = -1;
400 f = fopen(fname, "r");
401 if (f == NULL) return -1;
403 // seek to the section needed
406 tmp = fgets(line, sizeof(line), f);
407 if (tmp == NULL) break;
409 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
411 for (i = 0; i < len; i++)
412 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
413 tmp = strchr(line, '=');
414 if (tmp == NULL) break;
418 len = sizeof(lastRomFile);
419 strncpy(lastRomFile, tmp, len);
420 lastRomFile[len-1] = 0;
429 static int custom_read(menu_entry *me, const char *var, const char *val)
436 case MA_OPT_RENDERER:
437 if (strcasecmp(var, "Renderer") != 0) return 0;
438 if (strcasecmp(val, "8bit fast") == 0) {
441 else if (strcasecmp(val, "16bit accurate") == 0) {
443 currentConfig.EmuOpt |= 0x80;
445 else if (strcasecmp(val, "8bit accurate") == 0) {
447 currentConfig.EmuOpt &= ~0x80;
454 if (strcasecmp(var, "Scaling") != 0) return 0;
455 if (strcasecmp(val, "OFF") == 0) {
456 currentConfig.scaling = 0;
457 } else if (strcasecmp(val, "hw horizontal") == 0) {
458 currentConfig.scaling = 1;
459 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
460 currentConfig.scaling = 2;
461 } else if (strcasecmp(val, "sw horizontal") == 0) {
462 currentConfig.scaling = 3;
467 case MA_OPT_FRAMESKIP:
468 if (strcasecmp(var, "Frameskip") != 0) return 0;
469 if (strcasecmp(val, "Auto") == 0)
470 currentConfig.Frameskip = -1;
471 else currentConfig.Frameskip = atoi(val);
474 case MA_OPT_SOUND_QUALITY:
475 if (strcasecmp(var, "Sound Quality") != 0) return 0;
476 PsndRate = strtoul(val, &tmp, 10);
477 if (PsndRate < 8000 || PsndRate > 44100)
479 while (*tmp == ' ') tmp++;
480 if (strcasecmp(tmp, "stereo") == 0) {
482 } else if (strcasecmp(tmp, "mono") == 0) {
489 if (strcasecmp(var, "Region") != 0) return 0;
490 if (strncasecmp(val, "Auto: ", 6) == 0)
492 const char *p = val + 5, *end = val + strlen(val);
494 PicoRegionOverride = PicoAutoRgnOrder = 0;
495 for (i = 0; p < end && i < 3; i++)
497 while (*p == ' ') p++;
498 if (p[0] == 'J' && p[1] == 'P') {
499 PicoAutoRgnOrder |= 1 << (i*4);
500 } else if (p[0] == 'U' && p[1] == 'S') {
501 PicoAutoRgnOrder |= 4 << (i*4);
502 } else if (p[0] == 'E' && p[1] == 'U') {
503 PicoAutoRgnOrder |= 8 << (i*4);
505 while (*p != ' ' && *p != 0) p++;
509 else if (strcasecmp(val, "Auto") == 0) {
510 PicoRegionOverride = 0;
511 } else if (strcasecmp(val, "Japan NTSC") == 0) {
512 PicoRegionOverride = 1;
513 } else if (strcasecmp(val, "Japan PAL") == 0) {
514 PicoRegionOverride = 2;
515 } else if (strcasecmp(val, "USA") == 0) {
516 PicoRegionOverride = 4;
517 } else if (strcasecmp(val, "Europe") == 0) {
518 PicoRegionOverride = 8;
523 case MA_OPT_CONFIRM_STATES:
524 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
525 if (strcasecmp(val, "OFF") == 0) {
526 currentConfig.EmuOpt &= ~(5<<9);
527 } else if (strcasecmp(val, "writes") == 0) {
528 currentConfig.EmuOpt &= ~(5<<9);
529 currentConfig.EmuOpt |= 1<<9;
530 } else if (strcasecmp(val, "loads") == 0) {
531 currentConfig.EmuOpt &= ~(5<<9);
532 currentConfig.EmuOpt |= 4<<9;
533 } else if (strcasecmp(val, "both") == 0) {
534 currentConfig.EmuOpt &= ~(5<<9);
535 currentConfig.EmuOpt |= 5<<9;
540 case MA_OPT_CPU_CLOCKS:
541 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
542 currentConfig.CPUclock = atoi(val);
546 if (strcasecmp(var, "Gamma correction") != 0) return 0;
547 currentConfig.gamma = (int) (atof(val) * 100.0);
550 case MA_OPT2_SQUIDGEHACK:
551 if (strcasecmp(var, "Squidgehack") != 0) return 0;
553 if (tmpi) *(int *)me->var |= me->mask;
554 else *(int *)me->var &= ~me->mask;
557 case MA_CDOPT_READAHEAD:
558 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
559 PicoCDBuffers = atoi(val) / 2;
563 lprintf("unhandled custom_read: %i\n", me->id);
569 static void keys_parse(const char *var, const char *val, int binds[32], const char *names[32])
571 int t, i, keys_encountered = 0;
574 for (t = 0; t < 32; t++)
576 if (strcmp(names[t], var) == 0) break;
579 lprintf("unhandled bind \"%s\"\n", var);
583 if (!(keys_encountered & (1<<t))) {
585 keys_encountered |= 1<<t;
589 if (strncasecmp(val, "player", 6) == 0)
591 player = atoi(val + 6) - 1;
592 if (player > 1) goto fail;
593 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
594 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
595 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
600 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
601 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
602 binds[t] |= emuctrl_actions[i].mask;
608 lprintf("unhandled action \"%s\"\n", val);
614 #define try_joy_parse(num) { \
615 if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
616 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
621 static void parse(const char *var, const char *val)
624 int t, i, tlen, tmp, ret = 0;
626 if (strcasecmp(var, "LastUsedROM") == 0)
627 return; /* handled elsewhere */
630 if (strncasecmp(var, "bind ", 5) == 0) {
631 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
639 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
642 tlen = *(cfg_opt_counts[t]);
643 for (i = 0; i < tlen && ret == 0; i++, me++)
645 if (!me->need_to_save) continue;
646 if (me->name != NULL) {
647 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
648 if (me->beh == MB_ONOFF) {
650 if (tmp) *(int *)me->var |= me->mask;
651 else *(int *)me->var &= ~me->mask;
653 } else if (me->beh == MB_RANGE) {
655 if (tmp < me->min) tmp = me->min;
656 if (tmp > me->max) tmp = me->max;
657 *(int *)me->var = tmp;
661 ret = custom_read(me, var, val);
664 if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
668 int config_havesect(const char *fname, const char *section)
673 f = fopen(fname, "r");
674 if (f == NULL) return 0;
676 ret = seek_sect(f, section);
682 int config_readsect(const char *fname, const char *section)
684 char line[128], *var, *val, *tmp;
688 f = fopen(fname, "r");
689 if (f == NULL) return 0;
693 ret = seek_sect(f, section);
695 lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
703 tmp = fgets(line, sizeof(line), f);
704 if (tmp == NULL) break;
706 if (line[0] == '[') break; // other section
708 // strip comments, linefeed, spaces..
710 for (i = 0; i < len; i++)
711 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
714 if (len <= 0) continue;
717 for (i = 0; i < len; i++)
718 if (line[i] == '=') break;
719 if (i >= len || strchr(&line[i+1], '=') != NULL) {
720 lprintf("config_readsect: can't parse: %s\n", line);
728 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
729 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);