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 char *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);
37 for (i = len - 1; i >= 0; i--)
38 if (str[i] != ' ') break;
45 static int seek_sect(FILE *f, const char *section)
50 len = strlen(section);
51 // seek to the section needed
54 tmp = fgets(line, sizeof(line), f);
55 if (tmp == NULL) break;
57 if (line[0] != '[') continue; // not section start
58 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
66 static void custom_write(FILE *f, const menu_entry *me, int no_def)
73 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&0x10) &&
74 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
77 else if (currentConfig.EmuOpt&0x80)
78 str = "16bit accurate";
80 str = "8bit accurate";
81 fprintf(f, "Renderer = %s", str);
85 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
86 switch (currentConfig.scaling) {
87 default: str = "OFF"; break;
88 case 1: str = "hw horizontal"; break;
89 case 2: str = "hw horiz. + vert."; break;
90 case 3: str = "sw horizontal"; break;
92 fprintf(f, "Scaling = %s", str);
94 case MA_OPT_FRAMESKIP:
95 if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
96 if (currentConfig.Frameskip < 0)
97 strcpy(str24, "Auto");
98 else sprintf(str24, "%i", currentConfig.Frameskip);
99 fprintf(f, "Frameskip = %s", str24);
101 case MA_OPT_SOUND_QUALITY:
102 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&8) &&
103 defaultConfig.s_PsndRate == PsndRate) return;
104 str = (PicoOpt&0x08)?"stereo":"mono";
105 fprintf(f, "Sound Quality = %i %s", PsndRate, str);
108 if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
109 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
110 fprintf(f, "Region = %s", me_region_name(PicoRegionOverride, PicoAutoRgnOrder));
112 case MA_OPT_CONFIRM_STATES:
113 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
114 switch ((currentConfig.EmuOpt >> 9) & 5) {
115 default: str = "OFF"; break;
116 case 1: str = "writes"; break;
117 case 4: str = "loads"; break;
118 case 5: str = "both"; break;
120 fprintf(f, "Confirm savestate = %s", str);
122 case MA_OPT_CPU_CLOCKS:
123 if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
124 fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
127 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
128 fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
130 case MA_OPT2_SQUIDGEHACK:
131 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
132 fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
134 case MA_CDOPT_READAHEAD:
135 if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
136 sprintf(str24, "%i", PicoCDBuffers * 2);
137 fprintf(f, "ReadAhead buffer = %s", str24);
141 lprintf("unhandled custom_write: %i\n", me->id);
148 static const char *joyKeyNames[32] =
150 "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
151 "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12",
152 "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
153 "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
156 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
157 const int def_binds[32], const char *names[32], int no_defaults)
162 for (t = 0; t < 32; t++)
164 act[0] = act[31] = 0;
165 if (no_defaults && binds[t] == def_binds[t])
167 if (strcmp(names[t], "???") == 0) continue;
169 if (strcmp(names[t], "SELECT") == 0) continue;
171 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
172 if (me_ctrl_actions[i].mask & binds[t]) {
173 strncpy(act, me_ctrl_actions[i].name, 31);
174 fprintf(fn, "%s %s = player%i %s" NL, bind_str, names[t],
175 ((binds[t]>>16)&1)+1, mystrip(act));
179 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
180 if (emuctrl_actions[i].mask & binds[t]) {
181 strncpy(act, emuctrl_actions[i].name, 31);
182 fprintf(fn, "%s %s = %s" NL, bind_str, names[t], mystrip(act));
189 static int default_var(const menu_entry *me)
193 case MA_OPT_ACC_TIMING:
194 case MA_OPT_ACC_SPRITES:
195 case MA_OPT_ARM940_SOUND:
196 case MA_OPT_6BUTTON_PAD:
197 case MA_OPT2_ENABLE_Z80:
198 case MA_OPT2_ENABLE_YM2612:
199 case MA_OPT2_ENABLE_SN76496:
200 case MA_OPT2_SVP_DYNAREC:
203 case MA_CDOPT_SAVERAM:
204 case MA_CDOPT_SCALEROT_CHIP:
205 case MA_CDOPT_BETTER_SYNC:
206 return defaultConfig.s_PicoOpt;
208 case MA_OPT_SHOW_FPS:
209 case MA_OPT_ENABLE_SOUND:
210 case MA_OPT_SRAM_STATES:
211 case MA_OPT2_A_SN_GAMMA:
213 case MA_OPT2_GZIP_STATES:
214 case MA_OPT2_NO_LAST_ROM:
215 case MA_OPT2_RAMTIMINGS:
217 return defaultConfig.EmuOpt;
219 case MA_OPT_SAVE_SLOT:
225 int config_writesect(const char *fname, const char *section)
227 FILE *fo = NULL, *fn = NULL; // old and new
228 int no_defaults = 0; // avoid saving defaults
231 char line[128], *tmp;
237 fo = fopen(fname, "r");
239 fn = fopen(fname, "w");
243 ret = seek_sect(fo, section);
245 // sect not found, we can simply append
246 fclose(fo); fo = NULL;
247 fn = fopen(fname, "a");
253 rename(fname, "tmp.cfg");
254 fo = fopen("tmp.cfg", "r");
255 fn = fopen(fname, "w");
256 if (fo == NULL || fn == NULL) goto write;
258 // copy everything until sect
259 tlen = strlen(section);
262 tmp = fgets(line, sizeof(line), fo);
263 if (tmp == NULL) break;
265 if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
270 // now skip to next sect
273 tmp = fgets(line, sizeof(line), fo);
274 if (tmp == NULL) break;
275 if (line[0] == '[') {
276 fseek(fo, -strlen(line), SEEK_CUR);
282 fclose(fo); fo = NULL;
288 fn = fopen(fname, "w");
297 fprintf(fn, "[%s]" NL, section);
299 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
302 tlen = *(cfg_opt_counts[t]);
303 for (i = 0; i < tlen; i++, me++)
305 if (!me->need_to_save) continue;
306 if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
307 custom_write(fn, me, no_defaults);
308 else if (me->beh == MB_ONOFF) {
309 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
310 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
311 } else if (me->beh == MB_RANGE) {
312 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
313 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
319 keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, no_defaults);
320 keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 1);
321 keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 1);
322 keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 1);
323 keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 1);
329 // copy whatever is left
332 tmp = fgets(line, sizeof(line), fo);
333 if (tmp == NULL) break;
346 int config_writelrom(const char *fname)
348 char line[128], *tmp, *optr = NULL;
349 char *old_data = NULL;
353 if (strlen(lastRomFile) == 0) return 0;
355 f = fopen(fname, "r");
358 fseek(f, 0, SEEK_END);
360 fseek(f, 0, SEEK_SET);
361 old_data = malloc(size + size/8);
362 if (old_data != NULL)
367 tmp = fgets(line, sizeof(line), f);
368 if (tmp == NULL) break;
370 if (strncasecmp(line, "LastUsedROM", 11) == 0)
372 sprintf(optr, "%s", line);
373 optr += strlen(optr);
379 f = fopen(fname, "w");
380 if (f == NULL) return -1;
382 if (old_data != NULL) {
383 fwrite(old_data, 1, optr - old_data, f);
386 fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
391 /* --------------------------------------------------------------------------*/
393 int config_readlrom(const char *fname)
395 char line[128], *tmp;
396 int i, len, ret = -1;
399 f = fopen(fname, "r");
400 if (f == NULL) return -1;
402 // seek to the section needed
405 tmp = fgets(line, sizeof(line), f);
406 if (tmp == NULL) break;
408 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
410 for (i = 0; i < len; i++)
411 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
412 tmp = strchr(line, '=');
413 if (tmp == NULL) break;
417 len = sizeof(lastRomFile);
418 strncpy(lastRomFile, tmp, len);
419 lastRomFile[len-1] = 0;
428 static int custom_read(menu_entry *me, const char *var, const char *val)
435 case MA_OPT_RENDERER:
436 if (strcasecmp(var, "Renderer") != 0) return 0;
437 if (strcasecmp(val, "8bit fast") == 0) {
440 else if (strcasecmp(val, "16bit accurate") == 0) {
442 currentConfig.EmuOpt |= 0x80;
444 else if (strcasecmp(val, "8bit accurate") == 0) {
446 currentConfig.EmuOpt &= ~0x80;
453 if (strcasecmp(var, "Scaling") != 0) return 0;
454 if (strcasecmp(val, "OFF") == 0) {
455 currentConfig.scaling = 0;
456 } else if (strcasecmp(val, "hw horizontal") == 0) {
457 currentConfig.scaling = 1;
458 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
459 currentConfig.scaling = 2;
460 } else if (strcasecmp(val, "sw horizontal") == 0) {
461 currentConfig.scaling = 3;
466 case MA_OPT_FRAMESKIP:
467 if (strcasecmp(var, "Frameskip") != 0) return 0;
468 if (strcasecmp(val, "Auto") == 0)
469 currentConfig.Frameskip = -1;
470 else currentConfig.Frameskip = atoi(val);
473 case MA_OPT_SOUND_QUALITY:
474 if (strcasecmp(var, "Sound Quality") != 0) return 0;
475 PsndRate = strtoul(val, &tmp, 10);
476 if (PsndRate < 8000 || PsndRate > 44100)
478 while (*tmp == ' ') tmp++;
479 if (strcasecmp(tmp, "stereo") == 0) {
481 } else if (strcasecmp(tmp, "mono") == 0) {
488 if (strcasecmp(var, "Region") != 0) return 0;
489 if (strncasecmp(val, "Auto: ", 6) == 0)
491 const char *p = val + 5, *end = val + strlen(val);
493 PicoRegionOverride = PicoAutoRgnOrder = 0;
494 for (i = 0; p < end && i < 3; i++)
496 while (*p == ' ') p++;
497 if (p[0] == 'J' && p[1] == 'P') {
498 PicoAutoRgnOrder |= 1 << (i*4);
499 } else if (p[0] == 'U' && p[1] == 'S') {
500 PicoAutoRgnOrder |= 4 << (i*4);
501 } else if (p[0] == 'E' && p[1] == 'U') {
502 PicoAutoRgnOrder |= 8 << (i*4);
504 while (*p != ' ' && *p != 0) p++;
508 else if (strcasecmp(val, "Auto") == 0) {
509 PicoRegionOverride = 0;
510 } else if (strcasecmp(val, "Japan NTSC") == 0) {
511 PicoRegionOverride = 1;
512 } else if (strcasecmp(val, "Japan PAL") == 0) {
513 PicoRegionOverride = 2;
514 } else if (strcasecmp(val, "USA") == 0) {
515 PicoRegionOverride = 4;
516 } else if (strcasecmp(val, "Europe") == 0) {
517 PicoRegionOverride = 8;
522 case MA_OPT_CONFIRM_STATES:
523 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
524 if (strcasecmp(val, "OFF") == 0) {
525 currentConfig.EmuOpt &= ~(5<<9);
526 } else if (strcasecmp(val, "writes") == 0) {
527 currentConfig.EmuOpt &= ~(5<<9);
528 currentConfig.EmuOpt |= 1<<9;
529 } else if (strcasecmp(val, "loads") == 0) {
530 currentConfig.EmuOpt &= ~(5<<9);
531 currentConfig.EmuOpt |= 4<<9;
532 } else if (strcasecmp(val, "both") == 0) {
533 currentConfig.EmuOpt &= ~(5<<9);
534 currentConfig.EmuOpt |= 5<<9;
539 case MA_OPT_CPU_CLOCKS:
540 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
541 currentConfig.CPUclock = atoi(val);
545 if (strcasecmp(var, "Gamma correction") != 0) return 0;
546 currentConfig.gamma = (int) (atof(val) * 100.0);
549 case MA_OPT2_SQUIDGEHACK:
550 if (strcasecmp(var, "Squidgehack") != 0) return 0;
552 if (tmpi) *(int *)me->var |= me->mask;
553 else *(int *)me->var &= ~me->mask;
556 case MA_CDOPT_READAHEAD:
557 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
558 PicoCDBuffers = atoi(val) / 2;
562 lprintf("unhandled custom_read: %i\n", me->id);
568 static unsigned int keys_encountered = 0;
570 static void keys_parse(const char *var, const char *val, int binds[32], const char *names[32])
575 for (t = 0; t < 32; t++)
577 if (strcmp(names[t], var) == 0) break;
580 lprintf("unhandled bind \"%s\"\n", var);
584 if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
586 keys_encountered |= 1<<t;
590 if (strncasecmp(val, "player", 6) == 0)
592 player = atoi(val + 6) - 1;
593 if (player > 1) goto fail;
594 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
595 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
596 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
601 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
602 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
603 binds[t] |= emuctrl_actions[i].mask;
609 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);
701 keys_encountered = 0;
705 tmp = fgets(line, sizeof(line), f);
706 if (tmp == NULL) break;
708 if (line[0] == '[') break; // other section
710 // strip comments, linefeed, spaces..
712 for (i = 0; i < len; i++)
713 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
716 if (len <= 0) continue;
719 for (i = 0; i < len; i++)
720 if (line[i] == '=') break;
721 if (i >= len || strchr(&line[i+1], '=') != NULL) {
722 lprintf("config_readsect: can't parse: %s\n", line);
730 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
731 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);