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 const int opt_entry_count;
24 extern const int opt2_entry_count;
25 extern const int cdopt_entry_count;
27 extern menu_entry opt3_entries[];
28 extern const int opt3_entry_count;
31 static menu_entry *cfg_opts[] =
41 static const int *cfg_opt_counts[] =
54 static int seek_sect(FILE *f, const char *section)
59 len = strlen(section);
60 // seek to the section needed
63 tmp = fgets(line, sizeof(line), f);
64 if (tmp == NULL) break;
66 if (line[0] != '[') continue; // not section start
67 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
75 static void custom_write(FILE *f, const menu_entry *me, int no_def)
82 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_ALT_RENDERER) &&
83 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
84 if (PicoOpt&POPT_ALT_RENDERER)
90 else if (currentConfig.EmuOpt&0x80)
97 str = "8bit accurate";
98 fprintf(f, "Renderer = %s", str);
102 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
104 switch (currentConfig.scaling) {
105 default: str = "OFF"; break;
106 case 1: str = "hw horizontal"; break;
107 case 2: str = "hw horiz. + vert."; break;
108 case 3: str = "sw horizontal"; break;
110 fprintf(f, "Scaling = %s", str);
113 case MA_OPT_FRAMESKIP:
114 if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
115 if (currentConfig.Frameskip < 0)
116 strcpy(str24, "Auto");
117 else sprintf(str24, "%i", currentConfig.Frameskip);
118 fprintf(f, "Frameskip = %s", str24);
120 case MA_OPT_SOUND_QUALITY:
121 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_EN_STEREO) &&
122 defaultConfig.s_PsndRate == PsndRate) return;
123 str = (PicoOpt&POPT_EN_STEREO)?"stereo":"mono";
124 fprintf(f, "Sound Quality = %i %s", PsndRate, str);
127 if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
128 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
129 strncpy(str24, me_region_name(PicoRegionOverride, PicoAutoRgnOrder), 23); str24[23] = 0;
130 fprintf(f, "Region = %s", mystrip(str24));
132 case MA_OPT_CONFIRM_STATES:
133 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
134 switch ((currentConfig.EmuOpt >> 9) & 5) {
135 default: str = "OFF"; break;
136 case 1: str = "writes"; break;
137 case 4: str = "loads"; break;
138 case 5: str = "both"; break;
140 fprintf(f, "Confirm savestate = %s", str);
142 case MA_OPT_CPU_CLOCKS:
143 if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
145 fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
147 fprintf(f, "PSP CPU clock = %i", currentConfig.CPUclock);
151 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
152 fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
154 case MA_OPT2_SQUIDGEHACK:
155 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
156 fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
158 case MA_CDOPT_READAHEAD:
159 if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
160 sprintf(str24, "%i", PicoCDBuffers * 2);
161 fprintf(f, "ReadAhead buffer = %s", str24);
165 if (no_def && defaultConfig.scale == currentConfig.scale) return;
166 fprintf(f, "Scale factor = %.2f", currentConfig.scale);
168 case MA_OPT3_HSCALE32:
169 if (no_def && defaultConfig.hscale32 == currentConfig.hscale32) return;
170 fprintf(f, "Hor. scale (for low res. games) = %.2f", currentConfig.hscale32);
172 case MA_OPT3_HSCALE40:
173 if (no_def && defaultConfig.hscale40 == currentConfig.hscale40) return;
174 fprintf(f, "Hor. scale (for hi res. games) = %.2f", currentConfig.hscale40);
176 case MA_OPT3_FILTERING:
177 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
178 fprintf(f, "Bilinear filtering = %i", currentConfig.scaling);
181 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
182 fprintf(f, "Gamma adjustment = %i", currentConfig.gamma);
184 case MA_OPT3_BLACKLVL:
185 if (no_def && defaultConfig.gamma2 == currentConfig.gamma2) return;
186 fprintf(f, "Black level = %i", currentConfig.gamma2);
189 if (no_def && (defaultConfig.EmuOpt&0x12000) == (currentConfig.gamma2&0x12000)) return;
190 strcpy(str24, "never");
191 if (currentConfig.EmuOpt & 0x2000)
192 strcpy(str24, (currentConfig.EmuOpt & 0x10000) ? "sometimes" : "always");
193 fprintf(f, "Wait for vsync = %s", str24);
197 lprintf("unhandled custom_write: %i\n", me->id);
204 static const char *joyKeyNames[32] =
206 "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
207 "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12",
208 "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
209 "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
212 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
213 const int def_binds[32], const char * const names[32], int no_defaults)
218 for (t = 0; t < 32; t++)
220 act[0] = act[31] = 0;
221 if (no_defaults && binds[t] == def_binds[t])
223 if (strcmp(names[t], "???") == 0) continue;
225 if (strcmp(names[t], "SELECT") == 0) continue;
227 if (binds[t] == 0 && def_binds[t] != 0) {
228 fprintf(fn, "%s %s =" NL, bind_str, names[t]); // no binds
232 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
233 if (me_ctrl_actions[i].mask & binds[t]) {
234 strncpy(act, me_ctrl_actions[i].name, 31);
235 fprintf(fn, "%s %s = player%i %s" NL, bind_str, names[t],
236 ((binds[t]>>16)&1)+1, mystrip(act));
240 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
241 if (emuctrl_actions[i].mask & binds[t]) {
242 strncpy(act, emuctrl_actions[i].name, 31);
243 fprintf(fn, "%s %s = %s" NL, bind_str, names[t], mystrip(act));
250 static int default_var(const menu_entry *me)
254 case MA_OPT_ACC_TIMING:
255 case MA_OPT_ACC_SPRITES:
256 case MA_OPT_ARM940_SOUND:
257 case MA_OPT_6BUTTON_PAD:
258 case MA_OPT2_ENABLE_Z80:
259 case MA_OPT2_ENABLE_YM2612:
260 case MA_OPT2_ENABLE_SN76496:
261 case MA_OPT2_SVP_DYNAREC:
264 case MA_CDOPT_SAVERAM:
265 case MA_CDOPT_SCALEROT_CHIP:
266 case MA_CDOPT_BETTER_SYNC:
267 return defaultConfig.s_PicoOpt;
269 case MA_OPT_SHOW_FPS:
270 case MA_OPT_ENABLE_SOUND:
271 case MA_OPT_SRAM_STATES:
272 case MA_OPT2_A_SN_GAMMA:
274 case MA_OPT2_GZIP_STATES:
275 case MA_OPT2_NO_LAST_ROM:
276 case MA_OPT2_RAMTIMINGS:
278 return defaultConfig.EmuOpt;
280 case MA_OPT_SAVE_SLOT:
286 int config_writesect(const char *fname, const char *section)
288 FILE *fo = NULL, *fn = NULL; // old and new
289 int no_defaults = 0; // avoid saving defaults
292 char line[128], *tmp;
298 fo = fopen(fname, "r");
300 fn = fopen(fname, "w");
304 ret = seek_sect(fo, section);
306 // sect not found, we can simply append
307 fclose(fo); fo = NULL;
308 fn = fopen(fname, "a");
314 rename(fname, "tmp.cfg");
315 fo = fopen("tmp.cfg", "r");
316 fn = fopen(fname, "w");
317 if (fo == NULL || fn == NULL) goto write;
319 // copy everything until sect
320 tlen = strlen(section);
323 tmp = fgets(line, sizeof(line), fo);
324 if (tmp == NULL) break;
326 if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
331 // now skip to next sect
334 tmp = fgets(line, sizeof(line), fo);
335 if (tmp == NULL) break;
336 if (line[0] == '[') {
337 fseek(fo, -strlen(line), SEEK_CUR);
343 fclose(fo); fo = NULL;
349 fn = fopen(fname, "w");
358 fprintf(fn, "[%s]" NL, section);
360 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
363 tlen = *(cfg_opt_counts[t]);
364 for (i = 0; i < tlen; i++, me++)
366 if (!me->need_to_save) continue;
367 if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
368 custom_write(fn, me, no_defaults);
369 else if (me->beh == MB_ONOFF) {
370 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
371 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
372 } else if (me->beh == MB_RANGE) {
373 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
374 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
380 keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, no_defaults);
381 keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 1);
382 keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 1);
383 keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 1);
384 keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 1);
388 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
395 // copy whatever is left
398 tmp = fgets(line, sizeof(line), fo);
399 if (tmp == NULL) break;
412 int config_writelrom(const char *fname)
414 char line[128], *tmp, *optr = NULL;
415 char *old_data = NULL;
419 if (strlen(lastRomFile) == 0) return -1;
421 f = fopen(fname, "r");
424 fseek(f, 0, SEEK_END);
426 fseek(f, 0, SEEK_SET);
427 old_data = malloc(size + size/8);
428 if (old_data != NULL)
433 tmp = fgets(line, sizeof(line), f);
434 if (tmp == NULL) break;
436 if (strncasecmp(line, "LastUsedROM", 11) == 0)
438 sprintf(optr, "%s", line);
439 optr += strlen(optr);
445 f = fopen(fname, "w");
446 if (f == NULL) return -1;
448 if (old_data != NULL) {
449 fwrite(old_data, 1, optr - old_data, f);
452 fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
457 /* --------------------------------------------------------------------------*/
459 int config_readlrom(const char *fname)
461 char line[128], *tmp;
462 int i, len, ret = -1;
465 f = fopen(fname, "r");
466 if (f == NULL) return -1;
468 // seek to the section needed
471 tmp = fgets(line, sizeof(line), f);
472 if (tmp == NULL) break;
474 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
476 for (i = 0; i < len; i++)
477 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
478 tmp = strchr(line, '=');
479 if (tmp == NULL) break;
483 len = sizeof(lastRomFile);
484 strncpy(lastRomFile, tmp, len);
485 lastRomFile[len-1] = 0;
494 static int custom_read(menu_entry *me, const char *var, const char *val)
501 case MA_OPT_RENDERER:
502 if (strcasecmp(var, "Renderer") != 0) return 0;
503 if (strcasecmp(val, "8bit fast") == 0 || strcasecmp(val, "fast") == 0) {
504 PicoOpt |= POPT_ALT_RENDERER;
506 else if (strcasecmp(val, "16bit accurate") == 0 || strcasecmp(val, "accurate") == 0) {
507 PicoOpt &= ~POPT_ALT_RENDERER;
508 currentConfig.EmuOpt |= 0x80;
510 else if (strcasecmp(val, "8bit accurate") == 0) {
511 PicoOpt &= ~POPT_ALT_RENDERER;
512 currentConfig.EmuOpt &= ~0x80;
520 if (strcasecmp(var, "Scaling") != 0) return 0;
521 if (strcasecmp(val, "OFF") == 0) {
522 currentConfig.scaling = 0;
523 } else if (strcasecmp(val, "hw horizontal") == 0) {
524 currentConfig.scaling = 1;
525 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
526 currentConfig.scaling = 2;
527 } else if (strcasecmp(val, "sw horizontal") == 0) {
528 currentConfig.scaling = 3;
536 case MA_OPT_FRAMESKIP:
537 if (strcasecmp(var, "Frameskip") != 0) return 0;
538 if (strcasecmp(val, "Auto") == 0)
539 currentConfig.Frameskip = -1;
540 else currentConfig.Frameskip = atoi(val);
543 case MA_OPT_SOUND_QUALITY:
544 if (strcasecmp(var, "Sound Quality") != 0) return 0;
545 PsndRate = strtoul(val, &tmp, 10);
546 if (PsndRate < 8000 || PsndRate > 44100)
548 while (*tmp == ' ') tmp++;
549 if (strcasecmp(tmp, "stereo") == 0) {
550 PicoOpt |= POPT_EN_STEREO;
551 } else if (strcasecmp(tmp, "mono") == 0) {
552 PicoOpt &= ~POPT_EN_STEREO;
558 if (strcasecmp(var, "Region") != 0) return 0;
559 if (strncasecmp(val, "Auto: ", 6) == 0)
561 const char *p = val + 5, *end = val + strlen(val);
563 PicoRegionOverride = PicoAutoRgnOrder = 0;
564 for (i = 0; p < end && i < 3; i++)
566 while (*p == ' ') p++;
567 if (p[0] == 'J' && p[1] == 'P') {
568 PicoAutoRgnOrder |= 1 << (i*4);
569 } else if (p[0] == 'U' && p[1] == 'S') {
570 PicoAutoRgnOrder |= 4 << (i*4);
571 } else if (p[0] == 'E' && p[1] == 'U') {
572 PicoAutoRgnOrder |= 8 << (i*4);
574 while (*p != ' ' && *p != 0) p++;
578 else if (strcasecmp(val, "Auto") == 0) {
579 PicoRegionOverride = 0;
580 } else if (strcasecmp(val, "Japan NTSC") == 0) {
581 PicoRegionOverride = 1;
582 } else if (strcasecmp(val, "Japan PAL") == 0) {
583 PicoRegionOverride = 2;
584 } else if (strcasecmp(val, "USA") == 0) {
585 PicoRegionOverride = 4;
586 } else if (strcasecmp(val, "Europe") == 0) {
587 PicoRegionOverride = 8;
592 case MA_OPT_CONFIRM_STATES:
593 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
594 if (strcasecmp(val, "OFF") == 0) {
595 currentConfig.EmuOpt &= ~(5<<9);
596 } else if (strcasecmp(val, "writes") == 0) {
597 currentConfig.EmuOpt &= ~(5<<9);
598 currentConfig.EmuOpt |= 1<<9;
599 } else if (strcasecmp(val, "loads") == 0) {
600 currentConfig.EmuOpt &= ~(5<<9);
601 currentConfig.EmuOpt |= 4<<9;
602 } else if (strcasecmp(val, "both") == 0) {
603 currentConfig.EmuOpt &= ~(5<<9);
604 currentConfig.EmuOpt |= 5<<9;
609 case MA_OPT_CPU_CLOCKS:
611 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
613 if (strcasecmp(var, "PSP CPU clock") != 0) return 0;
615 currentConfig.CPUclock = atoi(val);
619 if (strcasecmp(var, "Gamma correction") != 0) return 0;
620 currentConfig.gamma = (int) (atof(val) * 100.0);
623 case MA_OPT2_SQUIDGEHACK:
624 if (strcasecmp(var, "Squidgehack") != 0) return 0;
626 if (tmpi) *(int *)me->var |= me->mask;
627 else *(int *)me->var &= ~me->mask;
630 case MA_CDOPT_READAHEAD:
631 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
632 PicoCDBuffers = atoi(val) / 2;
637 if (strcasecmp(var, "Scale factor") != 0) return 0;
638 currentConfig.scale = atof(val);
640 case MA_OPT3_HSCALE32:
641 if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
642 currentConfig.hscale32 = atof(val);
644 case MA_OPT3_HSCALE40:
645 if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
646 currentConfig.hscale40 = atof(val);
648 case MA_OPT3_FILTERING:
649 if (strcasecmp(var, "Bilinear filtering") != 0) return 0;
650 currentConfig.scaling = atoi(val);
653 if (strcasecmp(var, "Gamma adjustment") != 0) return 0;
654 currentConfig.gamma = atoi(val);
656 case MA_OPT3_BLACKLVL:
657 if (strcasecmp(var, "Black level") != 0) return 0;
658 currentConfig.gamma2 = atoi(val);
661 if (strcasecmp(var, "Wait for vsync") != 0) return 0;
662 if (strcasecmp(val, "never") == 0) {
663 currentConfig.EmuOpt &= ~0x12000;
664 } else if (strcasecmp(val, "sometimes") == 0) {
665 currentConfig.EmuOpt |= 0x12000;
666 } else if (strcasecmp(val, "always") == 0) {
667 currentConfig.EmuOpt &= ~0x12000;
668 currentConfig.EmuOpt |= 0x02000;
674 lprintf("unhandled custom_read: %i\n", me->id);
680 static unsigned int keys_encountered = 0;
682 static void keys_parse(const char *var, const char *val, int binds[32], const char * const names[32])
687 for (t = 0; t < 32; t++)
689 if (strcmp(names[t], var) == 0) break;
692 lprintf("unhandled bind \"%s\"\n", var);
696 if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
698 keys_encountered |= 1<<t;
702 if (strncasecmp(val, "player", 6) == 0)
704 player = atoi(val + 6) - 1;
705 if (player > 1) goto fail;
706 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
707 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
708 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
713 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
714 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
715 binds[t] |= emuctrl_actions[i].mask;
721 lprintf("unhandled action \"%s\"\n", val);
726 #define try_joy_parse(num) { \
727 if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
728 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
733 static void parse(const char *var, const char *val)
736 int t, i, tlen, tmp, ret = 0;
738 if (strcasecmp(var, "LastUsedROM") == 0)
739 return; /* handled elsewhere */
741 if (strcasecmp(var, "Sound Volume") == 0) {
742 currentConfig.volume = atoi(val);
747 if (strncasecmp(var, "bind ", 5) == 0) {
748 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
756 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
759 tlen = *(cfg_opt_counts[t]);
760 for (i = 0; i < tlen && ret == 0; i++, me++)
762 if (!me->need_to_save) continue;
763 if (me->name != NULL) {
764 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
765 if (me->beh == MB_ONOFF) {
767 if (tmp) *(int *)me->var |= me->mask;
768 else *(int *)me->var &= ~me->mask;
770 } else if (me->beh == MB_RANGE) {
772 if (tmp < me->min) tmp = me->min;
773 if (tmp > me->max) tmp = me->max;
774 *(int *)me->var = tmp;
778 ret = custom_read(me, var, val);
781 if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
785 int config_havesect(const char *fname, const char *section)
790 f = fopen(fname, "r");
791 if (f == NULL) return 0;
793 ret = seek_sect(f, section);
798 int config_readsect(const char *fname, const char *section)
800 char line[128], *var, *val;
804 f = fopen(fname, "r");
805 if (f == NULL) return -1;
809 ret = seek_sect(f, section);
811 lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
817 keys_encountered = 0;
821 ret = config_get_var_val(f, line, sizeof(line), &var, &val);
823 if (ret == -1) continue;
834 static char *mystrip(char *str)
839 for (i = 0; i < len; i++)
840 if (str[i] != ' ') break;
841 if (i > 0) memmove(str, str + i, len - i + 1);
844 for (i = len - 1; i >= 0; i--)
845 if (str[i] != ' ') break;
854 * -1 - failed to parse line
856 int config_get_var_val(void *file, char *line, int lsize, char **rvar, char **rval)
858 char *var, *val, *tmp;
862 tmp = fgets(line, lsize, f);
863 if (tmp == NULL) return 0;
865 if (line[0] == '[') return 0; // other section
867 // strip comments, linefeed, spaces..
869 for (i = 0; i < len; i++)
870 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
873 if (len <= 0) return -1;;
876 for (i = 0; i < len; i++)
877 if (line[i] == '=') break;
878 if (i >= len || strchr(&line[i+1], '=') != NULL) {
879 lprintf("config_readsect: can't parse: %s\n", line);
889 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
890 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);