2 * Human-readable config file management for PicoDrive
12 static char *mystrip(char *str);
18 #include <Pico/Pico.h>
20 extern menu_entry opt_entries[];
21 extern menu_entry opt2_entries[];
22 extern menu_entry cdopt_entries[];
23 extern menu_entry ctrlopt_entries[];
24 extern const int opt_entry_count;
25 extern const int opt2_entry_count;
26 extern const int cdopt_entry_count;
27 extern const int ctrlopt_entry_count;
29 extern menu_entry opt3_entries[];
30 extern const int opt3_entry_count;
33 static menu_entry *cfg_opts[] =
44 static const int *cfg_opt_counts[] =
58 static int seek_sect(FILE *f, const char *section)
63 len = strlen(section);
64 // seek to the section needed
67 tmp = fgets(line, sizeof(line), f);
68 if (tmp == NULL) break;
70 if (line[0] != '[') continue; // not section start
71 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
79 static void custom_write(FILE *f, const menu_entry *me, int no_def)
86 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_ALT_RENDERER) &&
87 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
88 if (PicoOpt&POPT_ALT_RENDERER)
94 else if (currentConfig.EmuOpt&0x80)
101 str = "8bit accurate";
102 fprintf(f, "Renderer = %s", str);
106 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
108 switch (currentConfig.scaling) {
109 default: str = "OFF"; break;
110 case 1: str = "hw horizontal"; break;
111 case 2: str = "hw horiz. + vert."; break;
112 case 3: str = "sw horizontal"; break;
114 fprintf(f, "Scaling = %s", str);
117 case MA_OPT_FRAMESKIP:
118 if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
119 if (currentConfig.Frameskip < 0)
120 strcpy(str24, "Auto");
121 else sprintf(str24, "%i", currentConfig.Frameskip);
122 fprintf(f, "Frameskip = %s", str24);
124 case MA_OPT_SOUND_QUALITY:
125 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_EN_STEREO) &&
126 defaultConfig.s_PsndRate == PsndRate) return;
127 str = (PicoOpt&POPT_EN_STEREO)?"stereo":"mono";
128 fprintf(f, "Sound Quality = %i %s", PsndRate, str);
131 if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
132 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
133 strncpy(str24, me_region_name(PicoRegionOverride, PicoAutoRgnOrder), 23); str24[23] = 0;
134 fprintf(f, "Region = %s", mystrip(str24));
136 case MA_OPT_CONFIRM_STATES:
137 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
138 switch ((currentConfig.EmuOpt >> 9) & 5) {
139 default: str = "OFF"; break;
140 case 1: str = "writes"; break;
141 case 4: str = "loads"; break;
142 case 5: str = "both"; break;
144 fprintf(f, "Confirm savestate = %s", str);
146 case MA_OPT_CPU_CLOCKS:
147 if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
149 fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
151 fprintf(f, "PSP CPU clock = %i", currentConfig.CPUclock);
155 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
156 fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
158 case MA_OPT2_SQUIDGEHACK:
159 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
160 fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
162 case MA_CDOPT_READAHEAD:
163 if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
164 sprintf(str24, "%i", PicoCDBuffers * 2);
165 fprintf(f, "ReadAhead buffer = %s", str24);
169 if (no_def && defaultConfig.scale == currentConfig.scale) return;
170 fprintf(f, "Scale factor = %.2f", currentConfig.scale);
172 case MA_OPT3_HSCALE32:
173 if (no_def && defaultConfig.hscale32 == currentConfig.hscale32) return;
174 fprintf(f, "Hor. scale (for low res. games) = %.2f", currentConfig.hscale32);
176 case MA_OPT3_HSCALE40:
177 if (no_def && defaultConfig.hscale40 == currentConfig.hscale40) return;
178 fprintf(f, "Hor. scale (for hi res. games) = %.2f", currentConfig.hscale40);
180 case MA_OPT3_FILTERING:
181 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
182 fprintf(f, "Bilinear filtering = %i", currentConfig.scaling);
185 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
186 fprintf(f, "Gamma adjustment = %i", currentConfig.gamma);
188 case MA_OPT3_BLACKLVL:
189 if (no_def && defaultConfig.gamma2 == currentConfig.gamma2) return;
190 fprintf(f, "Black level = %i", currentConfig.gamma2);
193 if (no_def && (defaultConfig.EmuOpt&0x12000) == (currentConfig.gamma2&0x12000)) return;
194 strcpy(str24, "never");
195 if (currentConfig.EmuOpt & 0x2000)
196 strcpy(str24, (currentConfig.EmuOpt & 0x10000) ? "sometimes" : "always");
197 fprintf(f, "Wait for vsync = %s", str24);
201 lprintf("unhandled custom_write: %i\n", me->id);
208 static const char *joyKeyNames[32] =
210 "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
211 "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12",
212 "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
213 "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
216 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
217 const int def_binds[32], const char * const names[32], int no_defaults)
222 for (t = 0; t < 32; t++)
224 act[0] = act[31] = 0;
225 if (no_defaults && binds[t] == def_binds[t])
227 if (strcmp(names[t], "???") == 0) continue;
229 if (strcmp(names[t], "SELECT") == 0) continue;
231 if (binds[t] == 0 && def_binds[t] != 0) {
232 fprintf(fn, "%s %s =" NL, bind_str, names[t]); // no binds
236 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
237 if (me_ctrl_actions[i].mask & binds[t]) {
238 strncpy(act, me_ctrl_actions[i].name, 31);
239 fprintf(fn, "%s %s = player%i %s" NL, bind_str, names[t],
240 ((binds[t]>>16)&1)+1, mystrip(act));
244 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
245 if (emuctrl_actions[i].mask & binds[t]) {
246 strncpy(act, emuctrl_actions[i].name, 31);
247 fprintf(fn, "%s %s = %s" NL, bind_str, names[t], mystrip(act));
254 static int default_var(const menu_entry *me)
258 case MA_OPT_ACC_TIMING:
259 case MA_OPT_ACC_SPRITES:
260 case MA_OPT_ARM940_SOUND:
261 case MA_OPT_6BUTTON_PAD:
262 case MA_OPT2_ENABLE_Z80:
263 case MA_OPT2_ENABLE_YM2612:
264 case MA_OPT2_ENABLE_SN76496:
265 case MA_OPT2_SVP_DYNAREC:
268 case MA_CDOPT_SAVERAM:
269 case MA_CDOPT_SCALEROT_CHIP:
270 case MA_CDOPT_BETTER_SYNC:
271 return defaultConfig.s_PicoOpt;
273 case MA_OPT_SHOW_FPS:
274 case MA_OPT_ENABLE_SOUND:
275 case MA_OPT_SRAM_STATES:
276 case MA_OPT2_A_SN_GAMMA:
278 case MA_OPT2_GZIP_STATES:
279 case MA_OPT2_NO_LAST_ROM:
280 case MA_OPT2_RAMTIMINGS:
282 return defaultConfig.EmuOpt;
284 case MA_CTRL_TURBO_RATE:
285 return defaultConfig.turbo_rate;
287 case MA_OPT_SAVE_SLOT:
293 int config_writesect(const char *fname, const char *section)
295 FILE *fo = NULL, *fn = NULL; // old and new
296 int no_defaults = 0; // avoid saving defaults
299 char line[128], *tmp;
305 fo = fopen(fname, "r");
307 fn = fopen(fname, "w");
311 ret = seek_sect(fo, section);
313 // sect not found, we can simply append
314 fclose(fo); fo = NULL;
315 fn = fopen(fname, "a");
321 rename(fname, "tmp.cfg");
322 fo = fopen("tmp.cfg", "r");
323 fn = fopen(fname, "w");
324 if (fo == NULL || fn == NULL) goto write;
326 // copy everything until sect
327 tlen = strlen(section);
330 tmp = fgets(line, sizeof(line), fo);
331 if (tmp == NULL) break;
333 if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
338 // now skip to next sect
341 tmp = fgets(line, sizeof(line), fo);
342 if (tmp == NULL) break;
343 if (line[0] == '[') {
344 fseek(fo, -strlen(line), SEEK_CUR);
350 fclose(fo); fo = NULL;
356 fn = fopen(fname, "w");
365 fprintf(fn, "[%s]" NL, section);
367 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
370 tlen = *(cfg_opt_counts[t]);
371 for (i = 0; i < tlen; i++, me++)
373 if (!me->need_to_save) continue;
374 if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
375 custom_write(fn, me, no_defaults);
376 else if (me->beh == MB_ONOFF) {
377 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
378 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
379 } else if (me->beh == MB_RANGE) {
380 if (!no_defaults || (*(int *)me->var ^ default_var(me)))
381 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
387 keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, no_defaults);
388 keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 1);
389 keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 1);
390 keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 1);
391 keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 1);
395 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
402 // copy whatever is left
405 tmp = fgets(line, sizeof(line), fo);
406 if (tmp == NULL) break;
419 int config_writelrom(const char *fname)
421 char line[128], *tmp, *optr = NULL;
422 char *old_data = NULL;
426 if (strlen(lastRomFile) == 0) return -1;
428 f = fopen(fname, "r");
431 fseek(f, 0, SEEK_END);
433 fseek(f, 0, SEEK_SET);
434 old_data = malloc(size + size/8);
435 if (old_data != NULL)
440 tmp = fgets(line, sizeof(line), f);
441 if (tmp == NULL) break;
443 if (strncasecmp(line, "LastUsedROM", 11) == 0)
445 sprintf(optr, "%s", line);
446 optr += strlen(optr);
452 f = fopen(fname, "w");
453 if (f == NULL) return -1;
455 if (old_data != NULL) {
456 fwrite(old_data, 1, optr - old_data, f);
459 fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
464 /* --------------------------------------------------------------------------*/
466 int config_readlrom(const char *fname)
468 char line[128], *tmp;
469 int i, len, ret = -1;
472 f = fopen(fname, "r");
473 if (f == NULL) return -1;
475 // seek to the section needed
478 tmp = fgets(line, sizeof(line), f);
479 if (tmp == NULL) break;
481 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
483 for (i = 0; i < len; i++)
484 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
485 tmp = strchr(line, '=');
486 if (tmp == NULL) break;
490 len = sizeof(lastRomFile);
491 strncpy(lastRomFile, tmp, len);
492 lastRomFile[len-1] = 0;
501 static int custom_read(menu_entry *me, const char *var, const char *val)
508 case MA_OPT_RENDERER:
509 if (strcasecmp(var, "Renderer") != 0) return 0;
510 if (strcasecmp(val, "8bit fast") == 0 || strcasecmp(val, "fast") == 0) {
511 PicoOpt |= POPT_ALT_RENDERER;
513 else if (strcasecmp(val, "16bit accurate") == 0 || strcasecmp(val, "accurate") == 0) {
514 PicoOpt &= ~POPT_ALT_RENDERER;
515 currentConfig.EmuOpt |= 0x80;
517 else if (strcasecmp(val, "8bit accurate") == 0) {
518 PicoOpt &= ~POPT_ALT_RENDERER;
519 currentConfig.EmuOpt &= ~0x80;
527 if (strcasecmp(var, "Scaling") != 0) return 0;
528 if (strcasecmp(val, "OFF") == 0) {
529 currentConfig.scaling = 0;
530 } else if (strcasecmp(val, "hw horizontal") == 0) {
531 currentConfig.scaling = 1;
532 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
533 currentConfig.scaling = 2;
534 } else if (strcasecmp(val, "sw horizontal") == 0) {
535 currentConfig.scaling = 3;
543 case MA_OPT_FRAMESKIP:
544 if (strcasecmp(var, "Frameskip") != 0) return 0;
545 if (strcasecmp(val, "Auto") == 0)
546 currentConfig.Frameskip = -1;
547 else currentConfig.Frameskip = atoi(val);
550 case MA_OPT_SOUND_QUALITY:
551 if (strcasecmp(var, "Sound Quality") != 0) return 0;
552 PsndRate = strtoul(val, &tmp, 10);
553 if (PsndRate < 8000 || PsndRate > 44100)
555 while (*tmp == ' ') tmp++;
556 if (strcasecmp(tmp, "stereo") == 0) {
557 PicoOpt |= POPT_EN_STEREO;
558 } else if (strcasecmp(tmp, "mono") == 0) {
559 PicoOpt &= ~POPT_EN_STEREO;
565 if (strcasecmp(var, "Region") != 0) return 0;
566 if (strncasecmp(val, "Auto: ", 6) == 0)
568 const char *p = val + 5, *end = val + strlen(val);
570 PicoRegionOverride = PicoAutoRgnOrder = 0;
571 for (i = 0; p < end && i < 3; i++)
573 while (*p == ' ') p++;
574 if (p[0] == 'J' && p[1] == 'P') {
575 PicoAutoRgnOrder |= 1 << (i*4);
576 } else if (p[0] == 'U' && p[1] == 'S') {
577 PicoAutoRgnOrder |= 4 << (i*4);
578 } else if (p[0] == 'E' && p[1] == 'U') {
579 PicoAutoRgnOrder |= 8 << (i*4);
581 while (*p != ' ' && *p != 0) p++;
585 else if (strcasecmp(val, "Auto") == 0) {
586 PicoRegionOverride = 0;
587 } else if (strcasecmp(val, "Japan NTSC") == 0) {
588 PicoRegionOverride = 1;
589 } else if (strcasecmp(val, "Japan PAL") == 0) {
590 PicoRegionOverride = 2;
591 } else if (strcasecmp(val, "USA") == 0) {
592 PicoRegionOverride = 4;
593 } else if (strcasecmp(val, "Europe") == 0) {
594 PicoRegionOverride = 8;
599 case MA_OPT_CONFIRM_STATES:
600 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
601 if (strcasecmp(val, "OFF") == 0) {
602 currentConfig.EmuOpt &= ~(5<<9);
603 } else if (strcasecmp(val, "writes") == 0) {
604 currentConfig.EmuOpt &= ~(5<<9);
605 currentConfig.EmuOpt |= 1<<9;
606 } else if (strcasecmp(val, "loads") == 0) {
607 currentConfig.EmuOpt &= ~(5<<9);
608 currentConfig.EmuOpt |= 4<<9;
609 } else if (strcasecmp(val, "both") == 0) {
610 currentConfig.EmuOpt &= ~(5<<9);
611 currentConfig.EmuOpt |= 5<<9;
616 case MA_OPT_CPU_CLOCKS:
618 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
620 if (strcasecmp(var, "PSP CPU clock") != 0) return 0;
622 currentConfig.CPUclock = atoi(val);
626 if (strcasecmp(var, "Gamma correction") != 0) return 0;
627 currentConfig.gamma = (int) (atof(val) * 100.0);
630 case MA_OPT2_SQUIDGEHACK:
631 if (strcasecmp(var, "Squidgehack") != 0) return 0;
633 if (tmpi) *(int *)me->var |= me->mask;
634 else *(int *)me->var &= ~me->mask;
637 case MA_CDOPT_READAHEAD:
638 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
639 PicoCDBuffers = atoi(val) / 2;
644 if (strcasecmp(var, "Scale factor") != 0) return 0;
645 currentConfig.scale = atof(val);
647 case MA_OPT3_HSCALE32:
648 if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
649 currentConfig.hscale32 = atof(val);
651 case MA_OPT3_HSCALE40:
652 if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
653 currentConfig.hscale40 = atof(val);
655 case MA_OPT3_FILTERING:
656 if (strcasecmp(var, "Bilinear filtering") != 0) return 0;
657 currentConfig.scaling = atoi(val);
660 if (strcasecmp(var, "Gamma adjustment") != 0) return 0;
661 currentConfig.gamma = atoi(val);
663 case MA_OPT3_BLACKLVL:
664 if (strcasecmp(var, "Black level") != 0) return 0;
665 currentConfig.gamma2 = atoi(val);
668 if (strcasecmp(var, "Wait for vsync") != 0) return 0;
669 if (strcasecmp(val, "never") == 0) {
670 currentConfig.EmuOpt &= ~0x12000;
671 } else if (strcasecmp(val, "sometimes") == 0) {
672 currentConfig.EmuOpt |= 0x12000;
673 } else if (strcasecmp(val, "always") == 0) {
674 currentConfig.EmuOpt &= ~0x12000;
675 currentConfig.EmuOpt |= 0x02000;
681 lprintf("unhandled custom_read: %i\n", me->id);
687 static unsigned int keys_encountered = 0;
689 static void keys_parse(const char *var, const char *val, int binds[32], const char * const names[32])
694 for (t = 0; t < 32; t++)
696 if (strcmp(names[t], var) == 0) break;
699 lprintf("unhandled bind \"%s\"\n", var);
703 if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
705 keys_encountered |= 1<<t;
709 if (strncasecmp(val, "player", 6) == 0)
711 player = atoi(val + 6) - 1;
712 if (player > 1) goto fail;
713 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
714 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
715 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
720 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
721 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
722 binds[t] |= emuctrl_actions[i].mask;
728 lprintf("unhandled action \"%s\"\n", val);
733 #define try_joy_parse(num) { \
734 if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
735 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
740 static void parse(const char *var, const char *val)
743 int t, i, tlen, tmp, ret = 0;
745 if (strcasecmp(var, "LastUsedROM") == 0)
746 return; /* handled elsewhere */
748 if (strcasecmp(var, "Sound Volume") == 0) {
749 currentConfig.volume = atoi(val);
754 if (strncasecmp(var, "bind ", 5) == 0) {
755 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
763 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
766 tlen = *(cfg_opt_counts[t]);
767 for (i = 0; i < tlen && ret == 0; i++, me++)
769 if (!me->need_to_save) continue;
770 if (me->name != NULL) {
771 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
772 if (me->beh == MB_ONOFF) {
774 if (tmp) *(int *)me->var |= me->mask;
775 else *(int *)me->var &= ~me->mask;
777 } else if (me->beh == MB_RANGE) {
779 if (tmp < me->min) tmp = me->min;
780 if (tmp > me->max) tmp = me->max;
781 *(int *)me->var = tmp;
785 ret = custom_read(me, var, val);
788 if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
792 int config_havesect(const char *fname, const char *section)
797 f = fopen(fname, "r");
798 if (f == NULL) return 0;
800 ret = seek_sect(f, section);
805 int config_readsect(const char *fname, const char *section)
807 char line[128], *var, *val;
811 f = fopen(fname, "r");
812 if (f == NULL) return -1;
816 ret = seek_sect(f, section);
818 lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
824 keys_encountered = 0;
828 ret = config_get_var_val(f, line, sizeof(line), &var, &val);
830 if (ret == -1) continue;
841 static char *mystrip(char *str)
846 for (i = 0; i < len; i++)
847 if (str[i] != ' ') break;
848 if (i > 0) memmove(str, str + i, len - i + 1);
851 for (i = len - 1; i >= 0; i--)
852 if (str[i] != ' ') break;
861 * -1 - failed to parse line
863 int config_get_var_val(void *file, char *line, int lsize, char **rvar, char **rval)
865 char *var, *val, *tmp;
869 tmp = fgets(line, lsize, f);
870 if (tmp == NULL) return 0;
872 if (line[0] == '[') return 0; // other section
874 // strip comments, linefeed, spaces..
876 for (i = 0; i < len; i++)
877 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
880 if (len <= 0) return -1;;
883 for (i = 0; i < len; i++)
884 if (line[i] == '=') break;
885 if (i >= len || strchr(&line[i+1], '=') != NULL) {
886 lprintf("config_readsect: can't parse: %s\n", line);
896 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
897 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);