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);
326 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
332 // copy whatever is left
335 tmp = fgets(line, sizeof(line), fo);
336 if (tmp == NULL) break;
349 int config_writelrom(const char *fname)
351 char line[128], *tmp, *optr = NULL;
352 char *old_data = NULL;
356 if (strlen(lastRomFile) == 0) return -1;
358 f = fopen(fname, "r");
361 fseek(f, 0, SEEK_END);
363 fseek(f, 0, SEEK_SET);
364 old_data = malloc(size + size/8);
365 if (old_data != NULL)
370 tmp = fgets(line, sizeof(line), f);
371 if (tmp == NULL) break;
373 if (strncasecmp(line, "LastUsedROM", 11) == 0)
375 sprintf(optr, "%s", line);
376 optr += strlen(optr);
382 f = fopen(fname, "w");
383 if (f == NULL) return -1;
385 if (old_data != NULL) {
386 fwrite(old_data, 1, optr - old_data, f);
389 fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
394 /* --------------------------------------------------------------------------*/
396 int config_readlrom(const char *fname)
398 char line[128], *tmp;
399 int i, len, ret = -1;
402 f = fopen(fname, "r");
403 if (f == NULL) return -1;
405 // seek to the section needed
408 tmp = fgets(line, sizeof(line), f);
409 if (tmp == NULL) break;
411 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
413 for (i = 0; i < len; i++)
414 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
415 tmp = strchr(line, '=');
416 if (tmp == NULL) break;
420 len = sizeof(lastRomFile);
421 strncpy(lastRomFile, tmp, len);
422 lastRomFile[len-1] = 0;
431 static int custom_read(menu_entry *me, const char *var, const char *val)
438 case MA_OPT_RENDERER:
439 if (strcasecmp(var, "Renderer") != 0) return 0;
440 if (strcasecmp(val, "8bit fast") == 0) {
443 else if (strcasecmp(val, "16bit accurate") == 0) {
445 currentConfig.EmuOpt |= 0x80;
447 else if (strcasecmp(val, "8bit accurate") == 0) {
449 currentConfig.EmuOpt &= ~0x80;
456 if (strcasecmp(var, "Scaling") != 0) return 0;
457 if (strcasecmp(val, "OFF") == 0) {
458 currentConfig.scaling = 0;
459 } else if (strcasecmp(val, "hw horizontal") == 0) {
460 currentConfig.scaling = 1;
461 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
462 currentConfig.scaling = 2;
463 } else if (strcasecmp(val, "sw horizontal") == 0) {
464 currentConfig.scaling = 3;
469 case MA_OPT_FRAMESKIP:
470 if (strcasecmp(var, "Frameskip") != 0) return 0;
471 if (strcasecmp(val, "Auto") == 0)
472 currentConfig.Frameskip = -1;
473 else currentConfig.Frameskip = atoi(val);
476 case MA_OPT_SOUND_QUALITY:
477 if (strcasecmp(var, "Sound Quality") != 0) return 0;
478 PsndRate = strtoul(val, &tmp, 10);
479 if (PsndRate < 8000 || PsndRate > 44100)
481 while (*tmp == ' ') tmp++;
482 if (strcasecmp(tmp, "stereo") == 0) {
484 } else if (strcasecmp(tmp, "mono") == 0) {
491 if (strcasecmp(var, "Region") != 0) return 0;
492 if (strncasecmp(val, "Auto: ", 6) == 0)
494 const char *p = val + 5, *end = val + strlen(val);
496 PicoRegionOverride = PicoAutoRgnOrder = 0;
497 for (i = 0; p < end && i < 3; i++)
499 while (*p == ' ') p++;
500 if (p[0] == 'J' && p[1] == 'P') {
501 PicoAutoRgnOrder |= 1 << (i*4);
502 } else if (p[0] == 'U' && p[1] == 'S') {
503 PicoAutoRgnOrder |= 4 << (i*4);
504 } else if (p[0] == 'E' && p[1] == 'U') {
505 PicoAutoRgnOrder |= 8 << (i*4);
507 while (*p != ' ' && *p != 0) p++;
511 else if (strcasecmp(val, "Auto") == 0) {
512 PicoRegionOverride = 0;
513 } else if (strcasecmp(val, "Japan NTSC") == 0) {
514 PicoRegionOverride = 1;
515 } else if (strcasecmp(val, "Japan PAL") == 0) {
516 PicoRegionOverride = 2;
517 } else if (strcasecmp(val, "USA") == 0) {
518 PicoRegionOverride = 4;
519 } else if (strcasecmp(val, "Europe") == 0) {
520 PicoRegionOverride = 8;
525 case MA_OPT_CONFIRM_STATES:
526 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
527 if (strcasecmp(val, "OFF") == 0) {
528 currentConfig.EmuOpt &= ~(5<<9);
529 } else if (strcasecmp(val, "writes") == 0) {
530 currentConfig.EmuOpt &= ~(5<<9);
531 currentConfig.EmuOpt |= 1<<9;
532 } else if (strcasecmp(val, "loads") == 0) {
533 currentConfig.EmuOpt &= ~(5<<9);
534 currentConfig.EmuOpt |= 4<<9;
535 } else if (strcasecmp(val, "both") == 0) {
536 currentConfig.EmuOpt &= ~(5<<9);
537 currentConfig.EmuOpt |= 5<<9;
542 case MA_OPT_CPU_CLOCKS:
543 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
544 currentConfig.CPUclock = atoi(val);
548 if (strcasecmp(var, "Gamma correction") != 0) return 0;
549 currentConfig.gamma = (int) (atof(val) * 100.0);
552 case MA_OPT2_SQUIDGEHACK:
553 if (strcasecmp(var, "Squidgehack") != 0) return 0;
555 if (tmpi) *(int *)me->var |= me->mask;
556 else *(int *)me->var &= ~me->mask;
559 case MA_CDOPT_READAHEAD:
560 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
561 PicoCDBuffers = atoi(val) / 2;
565 lprintf("unhandled custom_read: %i\n", me->id);
571 static unsigned int keys_encountered = 0;
573 static void keys_parse(const char *var, const char *val, int binds[32], const char *names[32])
578 for (t = 0; t < 32; t++)
580 if (strcmp(names[t], var) == 0) break;
583 lprintf("unhandled bind \"%s\"\n", var);
587 if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
589 keys_encountered |= 1<<t;
593 if (strncasecmp(val, "player", 6) == 0)
595 player = atoi(val + 6) - 1;
596 if (player > 1) goto fail;
597 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
598 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
599 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
604 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
605 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
606 binds[t] |= emuctrl_actions[i].mask;
612 lprintf("unhandled action \"%s\"\n", val);
617 #define try_joy_parse(num) { \
618 if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
619 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
624 static void parse(const char *var, const char *val)
627 int t, i, tlen, tmp, ret = 0;
629 if (strcasecmp(var, "LastUsedROM") == 0)
630 return; /* handled elsewhere */
632 if (strcasecmp(var, "Sound Volume") == 0) {
633 currentConfig.volume = atoi(val);
638 if (strncasecmp(var, "bind ", 5) == 0) {
639 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
647 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
650 tlen = *(cfg_opt_counts[t]);
651 for (i = 0; i < tlen && ret == 0; i++, me++)
653 if (!me->need_to_save) continue;
654 if (me->name != NULL) {
655 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
656 if (me->beh == MB_ONOFF) {
658 if (tmp) *(int *)me->var |= me->mask;
659 else *(int *)me->var &= ~me->mask;
661 } else if (me->beh == MB_RANGE) {
663 if (tmp < me->min) tmp = me->min;
664 if (tmp > me->max) tmp = me->max;
665 *(int *)me->var = tmp;
669 ret = custom_read(me, var, val);
672 if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
676 int config_havesect(const char *fname, const char *section)
681 f = fopen(fname, "r");
682 if (f == NULL) return 0;
684 ret = seek_sect(f, section);
690 int config_readsect(const char *fname, const char *section)
692 char line[128], *var, *val, *tmp;
696 f = fopen(fname, "r");
697 if (f == NULL) return -1;
701 ret = seek_sect(f, section);
703 lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
709 keys_encountered = 0;
713 tmp = fgets(line, sizeof(line), f);
714 if (tmp == NULL) break;
716 if (line[0] == '[') break; // other section
718 // strip comments, linefeed, spaces..
720 for (i = 0; i < len; i++)
721 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
724 if (len <= 0) continue;
727 for (i = 0; i < len; i++)
728 if (line[i] == '=') break;
729 if (i >= len || strchr(&line[i+1], '=') != NULL) {
730 lprintf("config_readsect: can't parse: %s\n", line);
738 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
739 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);