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 extern menu_entry opt3_entries[];
22 extern const int opt3_entry_count;
25 static menu_entry *cfg_opts[] =
35 static const int *cfg_opt_counts[] =
48 static char *mystrip(char *str)
53 for (i = 0; i < len; i++)
54 if (str[i] != ' ') break;
55 if (i > 0) memmove(str, str + i, len - i + 1);
58 for (i = len - 1; i >= 0; i--)
59 if (str[i] != ' ') break;
66 static int seek_sect(FILE *f, const char *section)
71 len = strlen(section);
72 // seek to the section needed
75 tmp = fgets(line, sizeof(line), f);
76 if (tmp == NULL) break;
78 if (line[0] != '[') continue; // not section start
79 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
87 static void custom_write(FILE *f, const menu_entry *me, int no_def)
94 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_ALT_RENDERER) &&
95 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
96 if (PicoOpt&POPT_ALT_RENDERER)
102 else if (currentConfig.EmuOpt&0x80)
109 str = "8bit accurate";
110 fprintf(f, "Renderer = %s", str);
114 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
116 switch (currentConfig.scaling) {
117 default: str = "OFF"; break;
118 case 1: str = "hw horizontal"; break;
119 case 2: str = "hw horiz. + vert."; break;
120 case 3: str = "sw horizontal"; break;
121 case 1: str = "ON"; break;
123 fprintf(f, "Scaling = %s", str);
126 case MA_OPT_FRAMESKIP:
127 if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
128 if (currentConfig.Frameskip < 0)
129 strcpy(str24, "Auto");
130 else sprintf(str24, "%i", currentConfig.Frameskip);
131 fprintf(f, "Frameskip = %s", str24);
133 case MA_OPT_SOUND_QUALITY:
134 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_EN_STEREO) &&
135 defaultConfig.s_PsndRate == PsndRate) return;
136 str = (PicoOpt&POPT_EN_STEREO)?"stereo":"mono";
137 fprintf(f, "Sound Quality = %i %s", PsndRate, str);
140 if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
141 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
142 strncpy(str24, me_region_name(PicoRegionOverride, PicoAutoRgnOrder), 23); str24[23] = 0;
143 fprintf(f, "Region = %s", mystrip(str24));
145 case MA_OPT_CONFIRM_STATES:
146 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
147 switch ((currentConfig.EmuOpt >> 9) & 5) {
148 default: str = "OFF"; break;
149 case 1: str = "writes"; break;
150 case 4: str = "loads"; break;
151 case 5: str = "both"; break;
153 fprintf(f, "Confirm savestate = %s", str);
155 case MA_OPT_CPU_CLOCKS:
156 if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
158 fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
160 fprintf(f, "PSP CPU clock = %i", currentConfig.CPUclock);
164 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
165 fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
167 case MA_OPT2_SQUIDGEHACK:
168 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
169 fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
171 case MA_CDOPT_READAHEAD:
172 if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
173 sprintf(str24, "%i", PicoCDBuffers * 2);
174 fprintf(f, "ReadAhead buffer = %s", str24);
178 if (no_def && defaultConfig.scale == currentConfig.scale) return;
179 fprintf(f, "Scale factor = %.2f", currentConfig.scale);
181 case MA_OPT3_HSCALE32:
182 if (no_def && defaultConfig.hscale32 == currentConfig.hscale32) return;
183 fprintf(f, "Hor. scale (for low res. games) = %.2f", currentConfig.hscale32);
185 case MA_OPT3_HSCALE40:
186 if (no_def && defaultConfig.hscale40 == currentConfig.hscale40) return;
187 fprintf(f, "Hor. scale (for hi res. games) = %.2f", currentConfig.hscale40);
189 case MA_OPT3_FILTERING:
190 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
191 fprintf(f, "Bilinear filtering = %i", currentConfig.scaling);
194 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
195 fprintf(f, "Gamma adjustment = %i", currentConfig.gamma);
197 case MA_OPT3_BLACKLVL:
198 if (no_def && defaultConfig.gamma2 == currentConfig.gamma2) return;
199 fprintf(f, "Black level = %i", currentConfig.gamma2);
202 if (no_def && (defaultConfig.EmuOpt&0x12000) == (currentConfig.gamma2&0x12000)) return;
203 strcpy(str24, "never");
204 if (currentConfig.EmuOpt & 0x2000)
205 strcpy(str24, (currentConfig.EmuOpt & 0x10000) ? "sometimes" : "always");
206 fprintf(f, "Wait for vsync = %s", str24);
210 lprintf("unhandled custom_write: %i\n", me->id);
217 static const char *joyKeyNames[32] =
219 "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
220 "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12",
221 "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
222 "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
225 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
226 const int def_binds[32], const char * const names[32], int no_defaults)
231 for (t = 0; t < 32; t++)
233 act[0] = act[31] = 0;
234 if (no_defaults && binds[t] == def_binds[t])
236 if (strcmp(names[t], "???") == 0) continue;
238 if (strcmp(names[t], "SELECT") == 0) continue;
240 if (binds[t] == 0 && def_binds[t] != 0) {
241 fprintf(fn, "%s %s =" NL, bind_str, names[t]); // no binds
245 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
246 if (me_ctrl_actions[i].mask & binds[t]) {
247 strncpy(act, me_ctrl_actions[i].name, 31);
248 fprintf(fn, "%s %s = player%i %s" NL, bind_str, names[t],
249 ((binds[t]>>16)&1)+1, mystrip(act));
253 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
254 if (emuctrl_actions[i].mask & binds[t]) {
255 strncpy(act, emuctrl_actions[i].name, 31);
256 fprintf(fn, "%s %s = %s" NL, bind_str, names[t], mystrip(act));
263 static int default_var(const menu_entry *me)
267 case MA_OPT_ACC_TIMING:
268 case MA_OPT_ACC_SPRITES:
269 case MA_OPT_ARM940_SOUND:
270 case MA_OPT_6BUTTON_PAD:
271 case MA_OPT2_ENABLE_Z80:
272 case MA_OPT2_ENABLE_YM2612:
273 case MA_OPT2_ENABLE_SN76496:
274 case MA_OPT2_SVP_DYNAREC:
277 case MA_CDOPT_SAVERAM:
278 case MA_CDOPT_SCALEROT_CHIP:
279 case MA_CDOPT_BETTER_SYNC:
280 return defaultConfig.s_PicoOpt;
282 case MA_OPT_SHOW_FPS:
283 case MA_OPT_ENABLE_SOUND:
284 case MA_OPT_SRAM_STATES:
285 case MA_OPT2_A_SN_GAMMA:
287 case MA_OPT2_GZIP_STATES:
288 case MA_OPT2_NO_LAST_ROM:
289 case MA_OPT2_RAMTIMINGS:
291 return defaultConfig.EmuOpt;
293 case MA_OPT_SAVE_SLOT:
299 int config_writesect(const char *fname, const char *section)
301 FILE *fo = NULL, *fn = NULL; // old and new
302 int no_defaults = 0; // avoid saving defaults
305 char line[128], *tmp;
311 fo = fopen(fname, "r");
313 fn = fopen(fname, "w");
317 ret = seek_sect(fo, section);
319 // sect not found, we can simply append
320 fclose(fo); fo = NULL;
321 fn = fopen(fname, "a");
327 rename(fname, "tmp.cfg");
328 fo = fopen("tmp.cfg", "r");
329 fn = fopen(fname, "w");
330 if (fo == NULL || fn == NULL) goto write;
332 // copy everything until sect
333 tlen = strlen(section);
336 tmp = fgets(line, sizeof(line), fo);
337 if (tmp == NULL) break;
339 if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
344 // now skip to next sect
347 tmp = fgets(line, sizeof(line), fo);
348 if (tmp == NULL) break;
349 if (line[0] == '[') {
350 fseek(fo, -strlen(line), SEEK_CUR);
356 fclose(fo); fo = NULL;
362 fn = fopen(fname, "w");
371 fprintf(fn, "[%s]" NL, section);
373 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
376 tlen = *(cfg_opt_counts[t]);
377 for (i = 0; i < tlen; i++, me++)
379 if (!me->need_to_save) continue;
380 if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
381 custom_write(fn, me, no_defaults);
382 else if (me->beh == MB_ONOFF) {
383 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
384 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
385 } else if (me->beh == MB_RANGE) {
386 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
387 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
393 keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, no_defaults);
394 keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 1);
395 keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 1);
396 keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 1);
397 keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 1);
401 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
408 // copy whatever is left
411 tmp = fgets(line, sizeof(line), fo);
412 if (tmp == NULL) break;
425 int config_writelrom(const char *fname)
427 char line[128], *tmp, *optr = NULL;
428 char *old_data = NULL;
432 if (strlen(lastRomFile) == 0) return -1;
434 f = fopen(fname, "r");
437 fseek(f, 0, SEEK_END);
439 fseek(f, 0, SEEK_SET);
440 old_data = malloc(size + size/8);
441 if (old_data != NULL)
446 tmp = fgets(line, sizeof(line), f);
447 if (tmp == NULL) break;
449 if (strncasecmp(line, "LastUsedROM", 11) == 0)
451 sprintf(optr, "%s", line);
452 optr += strlen(optr);
458 f = fopen(fname, "w");
459 if (f == NULL) return -1;
461 if (old_data != NULL) {
462 fwrite(old_data, 1, optr - old_data, f);
465 fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
470 /* --------------------------------------------------------------------------*/
472 int config_readlrom(const char *fname)
474 char line[128], *tmp;
475 int i, len, ret = -1;
478 f = fopen(fname, "r");
479 if (f == NULL) return -1;
481 // seek to the section needed
484 tmp = fgets(line, sizeof(line), f);
485 if (tmp == NULL) break;
487 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
489 for (i = 0; i < len; i++)
490 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
491 tmp = strchr(line, '=');
492 if (tmp == NULL) break;
496 len = sizeof(lastRomFile);
497 strncpy(lastRomFile, tmp, len);
498 lastRomFile[len-1] = 0;
507 static int custom_read(menu_entry *me, const char *var, const char *val)
514 case MA_OPT_RENDERER:
515 if (strcasecmp(var, "Renderer") != 0) return 0;
516 if (strcasecmp(val, "8bit fast") == 0 || strcasecmp(val, "fast") == 0) {
517 PicoOpt |= POPT_ALT_RENDERER;
519 else if (strcasecmp(val, "16bit accurate") == 0 || strcasecmp(val, "accurate") == 0) {
520 PicoOpt &= ~POPT_ALT_RENDERER;
521 currentConfig.EmuOpt |= 0x80;
523 else if (strcasecmp(val, "8bit accurate") == 0) {
524 PicoOpt &= ~POPT_ALT_RENDERER;
525 currentConfig.EmuOpt &= ~0x80;
533 if (strcasecmp(var, "Scaling") != 0) return 0;
534 if (strcasecmp(val, "OFF") == 0) {
535 currentConfig.scaling = 0;
536 } else if (strcasecmp(val, "hw horizontal") == 0) {
537 currentConfig.scaling = 1;
538 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
539 currentConfig.scaling = 2;
540 } else if (strcasecmp(val, "sw horizontal") == 0) {
541 currentConfig.scaling = 3;
549 case MA_OPT_FRAMESKIP:
550 if (strcasecmp(var, "Frameskip") != 0) return 0;
551 if (strcasecmp(val, "Auto") == 0)
552 currentConfig.Frameskip = -1;
553 else currentConfig.Frameskip = atoi(val);
556 case MA_OPT_SOUND_QUALITY:
557 if (strcasecmp(var, "Sound Quality") != 0) return 0;
558 PsndRate = strtoul(val, &tmp, 10);
559 if (PsndRate < 8000 || PsndRate > 44100)
561 while (*tmp == ' ') tmp++;
562 if (strcasecmp(tmp, "stereo") == 0) {
563 PicoOpt |= POPT_EN_STEREO;
564 } else if (strcasecmp(tmp, "mono") == 0) {
565 PicoOpt &= ~POPT_EN_STEREO;
571 if (strcasecmp(var, "Region") != 0) return 0;
572 if (strncasecmp(val, "Auto: ", 6) == 0)
574 const char *p = val + 5, *end = val + strlen(val);
576 PicoRegionOverride = PicoAutoRgnOrder = 0;
577 for (i = 0; p < end && i < 3; i++)
579 while (*p == ' ') p++;
580 if (p[0] == 'J' && p[1] == 'P') {
581 PicoAutoRgnOrder |= 1 << (i*4);
582 } else if (p[0] == 'U' && p[1] == 'S') {
583 PicoAutoRgnOrder |= 4 << (i*4);
584 } else if (p[0] == 'E' && p[1] == 'U') {
585 PicoAutoRgnOrder |= 8 << (i*4);
587 while (*p != ' ' && *p != 0) p++;
591 else if (strcasecmp(val, "Auto") == 0) {
592 PicoRegionOverride = 0;
593 } else if (strcasecmp(val, "Japan NTSC") == 0) {
594 PicoRegionOverride = 1;
595 } else if (strcasecmp(val, "Japan PAL") == 0) {
596 PicoRegionOverride = 2;
597 } else if (strcasecmp(val, "USA") == 0) {
598 PicoRegionOverride = 4;
599 } else if (strcasecmp(val, "Europe") == 0) {
600 PicoRegionOverride = 8;
605 case MA_OPT_CONFIRM_STATES:
606 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
607 if (strcasecmp(val, "OFF") == 0) {
608 currentConfig.EmuOpt &= ~(5<<9);
609 } else if (strcasecmp(val, "writes") == 0) {
610 currentConfig.EmuOpt &= ~(5<<9);
611 currentConfig.EmuOpt |= 1<<9;
612 } else if (strcasecmp(val, "loads") == 0) {
613 currentConfig.EmuOpt &= ~(5<<9);
614 currentConfig.EmuOpt |= 4<<9;
615 } else if (strcasecmp(val, "both") == 0) {
616 currentConfig.EmuOpt &= ~(5<<9);
617 currentConfig.EmuOpt |= 5<<9;
622 case MA_OPT_CPU_CLOCKS:
624 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
626 if (strcasecmp(var, "PSP CPU clock") != 0) return 0;
628 currentConfig.CPUclock = atoi(val);
632 if (strcasecmp(var, "Gamma correction") != 0) return 0;
633 currentConfig.gamma = (int) (atof(val) * 100.0);
636 case MA_OPT2_SQUIDGEHACK:
637 if (strcasecmp(var, "Squidgehack") != 0) return 0;
639 if (tmpi) *(int *)me->var |= me->mask;
640 else *(int *)me->var &= ~me->mask;
643 case MA_CDOPT_READAHEAD:
644 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
645 PicoCDBuffers = atoi(val) / 2;
650 if (strcasecmp(var, "Scale factor") != 0) return 0;
651 currentConfig.scale = atof(val);
653 case MA_OPT3_HSCALE32:
654 if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
655 currentConfig.hscale32 = atof(val);
657 case MA_OPT3_HSCALE40:
658 if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
659 currentConfig.hscale40 = atof(val);
661 case MA_OPT3_FILTERING:
662 if (strcasecmp(var, "Bilinear filtering") != 0) return 0;
663 currentConfig.scaling = atoi(val);
666 if (strcasecmp(var, "Gamma adjustment") != 0) return 0;
667 currentConfig.gamma = atoi(val);
669 case MA_OPT3_BLACKLVL:
670 if (strcasecmp(var, "Black level") != 0) return 0;
671 currentConfig.gamma2 = atoi(val);
674 if (strcasecmp(var, "Wait for vsync") != 0) return 0;
675 if (strcasecmp(val, "never") == 0) {
676 currentConfig.EmuOpt &= ~0x12000;
677 } else if (strcasecmp(val, "sometimes") == 0) {
678 currentConfig.EmuOpt |= 0x12000;
679 } else if (strcasecmp(val, "always") == 0) {
680 currentConfig.EmuOpt &= ~0x12000;
681 currentConfig.EmuOpt |= 0x02000;
687 lprintf("unhandled custom_read: %i\n", me->id);
693 static unsigned int keys_encountered = 0;
695 static void keys_parse(const char *var, const char *val, int binds[32], const char * const names[32])
700 for (t = 0; t < 32; t++)
702 if (strcmp(names[t], var) == 0) break;
705 lprintf("unhandled bind \"%s\"\n", var);
709 if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
711 keys_encountered |= 1<<t;
715 if (strncasecmp(val, "player", 6) == 0)
717 player = atoi(val + 6) - 1;
718 if (player > 1) goto fail;
719 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
720 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
721 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
726 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
727 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
728 binds[t] |= emuctrl_actions[i].mask;
734 lprintf("unhandled action \"%s\"\n", val);
739 #define try_joy_parse(num) { \
740 if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
741 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
746 static void parse(const char *var, const char *val)
749 int t, i, tlen, tmp, ret = 0;
751 if (strcasecmp(var, "LastUsedROM") == 0)
752 return; /* handled elsewhere */
754 if (strcasecmp(var, "Sound Volume") == 0) {
755 currentConfig.volume = atoi(val);
760 if (strncasecmp(var, "bind ", 5) == 0) {
761 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
769 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
772 tlen = *(cfg_opt_counts[t]);
773 for (i = 0; i < tlen && ret == 0; i++, me++)
775 if (!me->need_to_save) continue;
776 if (me->name != NULL) {
777 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
778 if (me->beh == MB_ONOFF) {
780 if (tmp) *(int *)me->var |= me->mask;
781 else *(int *)me->var &= ~me->mask;
783 } else if (me->beh == MB_RANGE) {
785 if (tmp < me->min) tmp = me->min;
786 if (tmp > me->max) tmp = me->max;
787 *(int *)me->var = tmp;
791 ret = custom_read(me, var, val);
794 if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
798 int config_havesect(const char *fname, const char *section)
803 f = fopen(fname, "r");
804 if (f == NULL) return 0;
806 ret = seek_sect(f, section);
812 int config_readsect(const char *fname, const char *section)
814 char line[128], *var, *val, *tmp;
818 f = fopen(fname, "r");
819 if (f == NULL) return -1;
823 ret = seek_sect(f, section);
825 lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
831 keys_encountered = 0;
835 tmp = fgets(line, sizeof(line), f);
836 if (tmp == NULL) break;
838 if (line[0] == '[') break; // other section
840 // strip comments, linefeed, spaces..
842 for (i = 0; i < len; i++)
843 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
846 if (len <= 0) continue;
849 for (i = 0; i < len; i++)
850 if (line[i] == '=') break;
851 if (i >= len || strchr(&line[i+1], '=') != NULL) {
852 lprintf("config_readsect: can't parse: %s\n", line);
860 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
861 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);