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;
122 fprintf(f, "Scaling = %s", str);
125 case MA_OPT_FRAMESKIP:
126 if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
127 if (currentConfig.Frameskip < 0)
128 strcpy(str24, "Auto");
129 else sprintf(str24, "%i", currentConfig.Frameskip);
130 fprintf(f, "Frameskip = %s", str24);
132 case MA_OPT_SOUND_QUALITY:
133 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_EN_STEREO) &&
134 defaultConfig.s_PsndRate == PsndRate) return;
135 str = (PicoOpt&POPT_EN_STEREO)?"stereo":"mono";
136 fprintf(f, "Sound Quality = %i %s", PsndRate, str);
139 if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
140 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
141 strncpy(str24, me_region_name(PicoRegionOverride, PicoAutoRgnOrder), 23); str24[23] = 0;
142 fprintf(f, "Region = %s", mystrip(str24));
144 case MA_OPT_CONFIRM_STATES:
145 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
146 switch ((currentConfig.EmuOpt >> 9) & 5) {
147 default: str = "OFF"; break;
148 case 1: str = "writes"; break;
149 case 4: str = "loads"; break;
150 case 5: str = "both"; break;
152 fprintf(f, "Confirm savestate = %s", str);
154 case MA_OPT_CPU_CLOCKS:
155 if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
157 fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
159 fprintf(f, "PSP CPU clock = %i", currentConfig.CPUclock);
163 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
164 fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
166 case MA_OPT2_SQUIDGEHACK:
167 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
168 fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
170 case MA_CDOPT_READAHEAD:
171 if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
172 sprintf(str24, "%i", PicoCDBuffers * 2);
173 fprintf(f, "ReadAhead buffer = %s", str24);
177 if (no_def && defaultConfig.scale == currentConfig.scale) return;
178 fprintf(f, "Scale factor = %.2f", currentConfig.scale);
180 case MA_OPT3_HSCALE32:
181 if (no_def && defaultConfig.hscale32 == currentConfig.hscale32) return;
182 fprintf(f, "Hor. scale (for low res. games) = %.2f", currentConfig.hscale32);
184 case MA_OPT3_HSCALE40:
185 if (no_def && defaultConfig.hscale40 == currentConfig.hscale40) return;
186 fprintf(f, "Hor. scale (for hi res. games) = %.2f", currentConfig.hscale40);
188 case MA_OPT3_FILTERING:
189 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
190 fprintf(f, "Bilinear filtering = %i", currentConfig.scaling);
193 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
194 fprintf(f, "Gamma adjustment = %i", currentConfig.gamma);
196 case MA_OPT3_BLACKLVL:
197 if (no_def && defaultConfig.gamma2 == currentConfig.gamma2) return;
198 fprintf(f, "Black level = %i", currentConfig.gamma2);
201 if (no_def && (defaultConfig.EmuOpt&0x12000) == (currentConfig.gamma2&0x12000)) return;
202 strcpy(str24, "never");
203 if (currentConfig.EmuOpt & 0x2000)
204 strcpy(str24, (currentConfig.EmuOpt & 0x10000) ? "sometimes" : "always");
205 fprintf(f, "Wait for vsync = %s", str24);
209 lprintf("unhandled custom_write: %i\n", me->id);
216 static const char *joyKeyNames[32] =
218 "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
219 "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12",
220 "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
221 "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
224 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
225 const int def_binds[32], const char * const names[32], int no_defaults)
230 for (t = 0; t < 32; t++)
232 act[0] = act[31] = 0;
233 if (no_defaults && binds[t] == def_binds[t])
235 if (strcmp(names[t], "???") == 0) continue;
237 if (strcmp(names[t], "SELECT") == 0) continue;
239 if (binds[t] == 0 && def_binds[t] != 0) {
240 fprintf(fn, "%s %s =" NL, bind_str, names[t]); // no binds
244 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
245 if (me_ctrl_actions[i].mask & binds[t]) {
246 strncpy(act, me_ctrl_actions[i].name, 31);
247 fprintf(fn, "%s %s = player%i %s" NL, bind_str, names[t],
248 ((binds[t]>>16)&1)+1, mystrip(act));
252 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
253 if (emuctrl_actions[i].mask & binds[t]) {
254 strncpy(act, emuctrl_actions[i].name, 31);
255 fprintf(fn, "%s %s = %s" NL, bind_str, names[t], mystrip(act));
262 static int default_var(const menu_entry *me)
266 case MA_OPT_ACC_TIMING:
267 case MA_OPT_ACC_SPRITES:
268 case MA_OPT_ARM940_SOUND:
269 case MA_OPT_6BUTTON_PAD:
270 case MA_OPT2_ENABLE_Z80:
271 case MA_OPT2_ENABLE_YM2612:
272 case MA_OPT2_ENABLE_SN76496:
273 case MA_OPT2_SVP_DYNAREC:
276 case MA_CDOPT_SAVERAM:
277 case MA_CDOPT_SCALEROT_CHIP:
278 case MA_CDOPT_BETTER_SYNC:
279 return defaultConfig.s_PicoOpt;
281 case MA_OPT_SHOW_FPS:
282 case MA_OPT_ENABLE_SOUND:
283 case MA_OPT_SRAM_STATES:
284 case MA_OPT2_A_SN_GAMMA:
286 case MA_OPT2_GZIP_STATES:
287 case MA_OPT2_NO_LAST_ROM:
288 case MA_OPT2_RAMTIMINGS:
290 return defaultConfig.EmuOpt;
292 case MA_OPT_SAVE_SLOT:
298 int config_writesect(const char *fname, const char *section)
300 FILE *fo = NULL, *fn = NULL; // old and new
301 int no_defaults = 0; // avoid saving defaults
304 char line[128], *tmp;
310 fo = fopen(fname, "r");
312 fn = fopen(fname, "w");
316 ret = seek_sect(fo, section);
318 // sect not found, we can simply append
319 fclose(fo); fo = NULL;
320 fn = fopen(fname, "a");
326 rename(fname, "tmp.cfg");
327 fo = fopen("tmp.cfg", "r");
328 fn = fopen(fname, "w");
329 if (fo == NULL || fn == NULL) goto write;
331 // copy everything until sect
332 tlen = strlen(section);
335 tmp = fgets(line, sizeof(line), fo);
336 if (tmp == NULL) break;
338 if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
343 // now skip to next sect
346 tmp = fgets(line, sizeof(line), fo);
347 if (tmp == NULL) break;
348 if (line[0] == '[') {
349 fseek(fo, -strlen(line), SEEK_CUR);
355 fclose(fo); fo = NULL;
361 fn = fopen(fname, "w");
370 fprintf(fn, "[%s]" NL, section);
372 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
375 tlen = *(cfg_opt_counts[t]);
376 for (i = 0; i < tlen; i++, me++)
378 if (!me->need_to_save) continue;
379 if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
380 custom_write(fn, me, no_defaults);
381 else if (me->beh == MB_ONOFF) {
382 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
383 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
384 } else if (me->beh == MB_RANGE) {
385 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
386 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
392 keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, no_defaults);
393 keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 1);
394 keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 1);
395 keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 1);
396 keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 1);
400 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
407 // copy whatever is left
410 tmp = fgets(line, sizeof(line), fo);
411 if (tmp == NULL) break;
424 int config_writelrom(const char *fname)
426 char line[128], *tmp, *optr = NULL;
427 char *old_data = NULL;
431 if (strlen(lastRomFile) == 0) return -1;
433 f = fopen(fname, "r");
436 fseek(f, 0, SEEK_END);
438 fseek(f, 0, SEEK_SET);
439 old_data = malloc(size + size/8);
440 if (old_data != NULL)
445 tmp = fgets(line, sizeof(line), f);
446 if (tmp == NULL) break;
448 if (strncasecmp(line, "LastUsedROM", 11) == 0)
450 sprintf(optr, "%s", line);
451 optr += strlen(optr);
457 f = fopen(fname, "w");
458 if (f == NULL) return -1;
460 if (old_data != NULL) {
461 fwrite(old_data, 1, optr - old_data, f);
464 fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
469 /* --------------------------------------------------------------------------*/
471 int config_readlrom(const char *fname)
473 char line[128], *tmp;
474 int i, len, ret = -1;
477 f = fopen(fname, "r");
478 if (f == NULL) return -1;
480 // seek to the section needed
483 tmp = fgets(line, sizeof(line), f);
484 if (tmp == NULL) break;
486 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
488 for (i = 0; i < len; i++)
489 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
490 tmp = strchr(line, '=');
491 if (tmp == NULL) break;
495 len = sizeof(lastRomFile);
496 strncpy(lastRomFile, tmp, len);
497 lastRomFile[len-1] = 0;
506 static int custom_read(menu_entry *me, const char *var, const char *val)
513 case MA_OPT_RENDERER:
514 if (strcasecmp(var, "Renderer") != 0) return 0;
515 if (strcasecmp(val, "8bit fast") == 0 || strcasecmp(val, "fast") == 0) {
516 PicoOpt |= POPT_ALT_RENDERER;
518 else if (strcasecmp(val, "16bit accurate") == 0 || strcasecmp(val, "accurate") == 0) {
519 PicoOpt &= ~POPT_ALT_RENDERER;
520 currentConfig.EmuOpt |= 0x80;
522 else if (strcasecmp(val, "8bit accurate") == 0) {
523 PicoOpt &= ~POPT_ALT_RENDERER;
524 currentConfig.EmuOpt &= ~0x80;
532 if (strcasecmp(var, "Scaling") != 0) return 0;
533 if (strcasecmp(val, "OFF") == 0) {
534 currentConfig.scaling = 0;
535 } else if (strcasecmp(val, "hw horizontal") == 0) {
536 currentConfig.scaling = 1;
537 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
538 currentConfig.scaling = 2;
539 } else if (strcasecmp(val, "sw horizontal") == 0) {
540 currentConfig.scaling = 3;
548 case MA_OPT_FRAMESKIP:
549 if (strcasecmp(var, "Frameskip") != 0) return 0;
550 if (strcasecmp(val, "Auto") == 0)
551 currentConfig.Frameskip = -1;
552 else currentConfig.Frameskip = atoi(val);
555 case MA_OPT_SOUND_QUALITY:
556 if (strcasecmp(var, "Sound Quality") != 0) return 0;
557 PsndRate = strtoul(val, &tmp, 10);
558 if (PsndRate < 8000 || PsndRate > 44100)
560 while (*tmp == ' ') tmp++;
561 if (strcasecmp(tmp, "stereo") == 0) {
562 PicoOpt |= POPT_EN_STEREO;
563 } else if (strcasecmp(tmp, "mono") == 0) {
564 PicoOpt &= ~POPT_EN_STEREO;
570 if (strcasecmp(var, "Region") != 0) return 0;
571 if (strncasecmp(val, "Auto: ", 6) == 0)
573 const char *p = val + 5, *end = val + strlen(val);
575 PicoRegionOverride = PicoAutoRgnOrder = 0;
576 for (i = 0; p < end && i < 3; i++)
578 while (*p == ' ') p++;
579 if (p[0] == 'J' && p[1] == 'P') {
580 PicoAutoRgnOrder |= 1 << (i*4);
581 } else if (p[0] == 'U' && p[1] == 'S') {
582 PicoAutoRgnOrder |= 4 << (i*4);
583 } else if (p[0] == 'E' && p[1] == 'U') {
584 PicoAutoRgnOrder |= 8 << (i*4);
586 while (*p != ' ' && *p != 0) p++;
590 else if (strcasecmp(val, "Auto") == 0) {
591 PicoRegionOverride = 0;
592 } else if (strcasecmp(val, "Japan NTSC") == 0) {
593 PicoRegionOverride = 1;
594 } else if (strcasecmp(val, "Japan PAL") == 0) {
595 PicoRegionOverride = 2;
596 } else if (strcasecmp(val, "USA") == 0) {
597 PicoRegionOverride = 4;
598 } else if (strcasecmp(val, "Europe") == 0) {
599 PicoRegionOverride = 8;
604 case MA_OPT_CONFIRM_STATES:
605 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
606 if (strcasecmp(val, "OFF") == 0) {
607 currentConfig.EmuOpt &= ~(5<<9);
608 } else if (strcasecmp(val, "writes") == 0) {
609 currentConfig.EmuOpt &= ~(5<<9);
610 currentConfig.EmuOpt |= 1<<9;
611 } else if (strcasecmp(val, "loads") == 0) {
612 currentConfig.EmuOpt &= ~(5<<9);
613 currentConfig.EmuOpt |= 4<<9;
614 } else if (strcasecmp(val, "both") == 0) {
615 currentConfig.EmuOpt &= ~(5<<9);
616 currentConfig.EmuOpt |= 5<<9;
621 case MA_OPT_CPU_CLOCKS:
623 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
625 if (strcasecmp(var, "PSP CPU clock") != 0) return 0;
627 currentConfig.CPUclock = atoi(val);
631 if (strcasecmp(var, "Gamma correction") != 0) return 0;
632 currentConfig.gamma = (int) (atof(val) * 100.0);
635 case MA_OPT2_SQUIDGEHACK:
636 if (strcasecmp(var, "Squidgehack") != 0) return 0;
638 if (tmpi) *(int *)me->var |= me->mask;
639 else *(int *)me->var &= ~me->mask;
642 case MA_CDOPT_READAHEAD:
643 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
644 PicoCDBuffers = atoi(val) / 2;
649 if (strcasecmp(var, "Scale factor") != 0) return 0;
650 currentConfig.scale = atof(val);
652 case MA_OPT3_HSCALE32:
653 if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
654 currentConfig.hscale32 = atof(val);
656 case MA_OPT3_HSCALE40:
657 if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
658 currentConfig.hscale40 = atof(val);
660 case MA_OPT3_FILTERING:
661 if (strcasecmp(var, "Bilinear filtering") != 0) return 0;
662 currentConfig.scaling = atoi(val);
665 if (strcasecmp(var, "Gamma adjustment") != 0) return 0;
666 currentConfig.gamma = atoi(val);
668 case MA_OPT3_BLACKLVL:
669 if (strcasecmp(var, "Black level") != 0) return 0;
670 currentConfig.gamma2 = atoi(val);
673 if (strcasecmp(var, "Wait for vsync") != 0) return 0;
674 if (strcasecmp(val, "never") == 0) {
675 currentConfig.EmuOpt &= ~0x12000;
676 } else if (strcasecmp(val, "sometimes") == 0) {
677 currentConfig.EmuOpt |= 0x12000;
678 } else if (strcasecmp(val, "always") == 0) {
679 currentConfig.EmuOpt &= ~0x12000;
680 currentConfig.EmuOpt |= 0x02000;
686 lprintf("unhandled custom_read: %i\n", me->id);
692 static unsigned int keys_encountered = 0;
694 static void keys_parse(const char *var, const char *val, int binds[32], const char * const names[32])
699 for (t = 0; t < 32; t++)
701 if (strcmp(names[t], var) == 0) break;
704 lprintf("unhandled bind \"%s\"\n", var);
708 if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
710 keys_encountered |= 1<<t;
714 if (strncasecmp(val, "player", 6) == 0)
716 player = atoi(val + 6) - 1;
717 if (player > 1) goto fail;
718 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
719 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
720 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
725 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
726 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
727 binds[t] |= emuctrl_actions[i].mask;
733 lprintf("unhandled action \"%s\"\n", val);
738 #define try_joy_parse(num) { \
739 if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
740 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
745 static void parse(const char *var, const char *val)
748 int t, i, tlen, tmp, ret = 0;
750 if (strcasecmp(var, "LastUsedROM") == 0)
751 return; /* handled elsewhere */
753 if (strcasecmp(var, "Sound Volume") == 0) {
754 currentConfig.volume = atoi(val);
759 if (strncasecmp(var, "bind ", 5) == 0) {
760 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
768 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
771 tlen = *(cfg_opt_counts[t]);
772 for (i = 0; i < tlen && ret == 0; i++, me++)
774 if (!me->need_to_save) continue;
775 if (me->name != NULL) {
776 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
777 if (me->beh == MB_ONOFF) {
779 if (tmp) *(int *)me->var |= me->mask;
780 else *(int *)me->var &= ~me->mask;
782 } else if (me->beh == MB_RANGE) {
784 if (tmp < me->min) tmp = me->min;
785 if (tmp > me->max) tmp = me->max;
786 *(int *)me->var = tmp;
790 ret = custom_read(me, var, val);
793 if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
797 int config_havesect(const char *fname, const char *section)
802 f = fopen(fname, "r");
803 if (f == NULL) return 0;
805 ret = seek_sect(f, section);
811 int config_readsect(const char *fname, const char *section)
813 char line[128], *var, *val, *tmp;
817 f = fopen(fname, "r");
818 if (f == NULL) return -1;
822 ret = seek_sect(f, section);
824 lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
830 keys_encountered = 0;
834 tmp = fgets(line, sizeof(line), f);
835 if (tmp == NULL) break;
837 if (line[0] == '[') break; // other section
839 // strip comments, linefeed, spaces..
841 for (i = 0; i < len; i++)
842 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
845 if (len <= 0) continue;
848 for (i = 0; i < len; i++)
849 if (line[i] == '=') break;
850 if (i >= len || strchr(&line[i+1], '=') != NULL) {
851 lprintf("config_readsect: can't parse: %s\n", line);
859 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
860 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);