9e7b1a7fef0cef6c05a37695ce43e22036d11916
[libpicofe.git] / common / config.c
1 /*
2  * Human-readable config file management for PicoDrive
3  * (c) notaz, 2008
4  */
5
6 #include <stdio.h>
7 #include <string.h>
8 #include <stdlib.h>
9 #ifdef __EPOC32__
10 #include <unistd.h>
11 #endif
12 #include "config.h"
13 #include "input.h"
14 #include "lprintf.h"
15
16 static char *mystrip(char *str);
17
18 #ifndef _MSC_VER
19
20 #include "menu.h"
21 #include "emu.h"
22 #include <pico/pico.h>
23
24 extern menu_entry opt_entries[];
25 extern menu_entry opt2_entries[];
26 extern menu_entry cdopt_entries[];
27 extern menu_entry ctrlopt_entries[];
28 extern const int opt_entry_count;
29 extern const int opt2_entry_count;
30 extern const int cdopt_entry_count;
31 extern const int ctrlopt_entry_count;
32 #ifdef PSP
33 extern menu_entry opt3_entries[];
34 extern const int opt3_entry_count;
35 #endif
36
37 static menu_entry *cfg_opts[] =
38 {
39         opt_entries,
40         opt2_entries,
41         cdopt_entries,
42         ctrlopt_entries,
43 #ifdef PSP
44         opt3_entries,
45 #endif
46 };
47
48 static const int *cfg_opt_counts[] =
49 {
50         &opt_entry_count,
51         &opt2_entry_count,
52         &cdopt_entry_count,
53         &ctrlopt_entry_count,
54 #ifdef PSP
55         &opt3_entry_count,
56 #endif
57 };
58
59 #define NL "\r\n"
60
61
62 static int seek_sect(FILE *f, const char *section)
63 {
64         char line[128], *tmp;
65         int len;
66
67         len = strlen(section);
68         // seek to the section needed
69         while (!feof(f))
70         {
71                 tmp = fgets(line, sizeof(line), f);
72                 if (tmp == NULL) break;
73
74                 if (line[0] != '[') continue; // not section start
75                 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
76                         return 1; // found it
77         }
78
79         return 0;
80 }
81
82
83 static void custom_write(FILE *f, const menu_entry *me, int no_def)
84 {
85         char *str, str24[24];
86
87         switch (me->id)
88         {
89                 case MA_OPT_RENDERER:
90                         if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_ALT_RENDERER) &&
91                                 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
92                         if (PicoOpt&POPT_ALT_RENDERER)
93                                 str =
94 #ifndef PSP
95                                 "8bit "
96 #endif
97                                 "fast";
98                         else if (currentConfig.EmuOpt&0x80)
99                                 str =
100 #ifndef PSP
101                                 "16bit "
102 #endif
103                                 "accurate";
104                         else
105                                 str = "8bit accurate";
106                         fprintf(f, "Renderer = %s", str);
107                         break;
108
109                 case MA_OPT_SCALING:
110                         if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
111 #ifdef __GP2X__
112                         switch (currentConfig.scaling) {
113                                 default: str = "OFF"; break;
114                                 case 1:  str = "hw horizontal";     break;
115                                 case 2:  str = "hw horiz. + vert."; break;
116                                 case 3:  str = "sw horizontal";     break;
117                         }
118                         fprintf(f, "Scaling = %s", str);
119 #endif
120                         break;
121                 case MA_OPT_FRAMESKIP:
122                         if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
123                         if (currentConfig.Frameskip < 0)
124                              strcpy(str24, "Auto");
125                         else sprintf(str24, "%i", currentConfig.Frameskip);
126                         fprintf(f, "Frameskip = %s", str24);
127                         break;
128                 case MA_OPT_SOUND_QUALITY:
129                         if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_EN_STEREO) &&
130                                 defaultConfig.s_PsndRate == PsndRate) return;
131                         str = (PicoOpt&POPT_EN_STEREO)?"stereo":"mono";
132                         fprintf(f, "Sound Quality = %i %s", PsndRate, str);
133                         break;
134                 case MA_OPT_REGION:
135                         if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
136                                 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
137                         strncpy(str24, me_region_name(PicoRegionOverride, PicoAutoRgnOrder), 23); str24[23] = 0;
138                         fprintf(f, "Region = %s", mystrip(str24));
139                         break;
140                 case MA_OPT_CONFIRM_STATES:
141                         if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
142                         switch ((currentConfig.EmuOpt >> 9) & 5) {
143                                 default: str = "OFF";    break;
144                                 case 1:  str = "writes"; break;
145                                 case 4:  str = "loads";  break;
146                                 case 5:  str = "both";   break;
147                         }
148                         fprintf(f, "Confirm savestate = %s", str);
149                         break;
150                 case MA_OPT_CPU_CLOCKS:
151                         if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
152 #ifdef __GP2X__
153                         fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
154 #elif defined(PSP)
155                         fprintf(f, "PSP CPU clock = %i", currentConfig.CPUclock);
156 #endif
157                         break;
158                 case MA_OPT2_GAMMA:
159                         if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
160                         fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
161                         break;
162                 case MA_OPT2_SQUIDGEHACK:
163                         if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
164                         fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
165                         break;
166                 case MA_CDOPT_READAHEAD:
167                         if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
168                         sprintf(str24, "%i", PicoCDBuffers * 2);
169                         fprintf(f, "ReadAhead buffer = %s", str24);
170                         break;
171                 /* PSP */
172                 case MA_OPT3_SCALE:
173                         if (no_def && defaultConfig.scale == currentConfig.scale) return;
174                         fprintf(f, "Scale factor = %.2f", currentConfig.scale);
175                         break;
176                 case MA_OPT3_HSCALE32:
177                         if (no_def && defaultConfig.hscale32 == currentConfig.hscale32) return;
178                         fprintf(f, "Hor. scale (for low res. games) = %.2f", currentConfig.hscale32);
179                         break;
180                 case MA_OPT3_HSCALE40:
181                         if (no_def && defaultConfig.hscale40 == currentConfig.hscale40) return;
182                         fprintf(f, "Hor. scale (for hi res. games) = %.2f", currentConfig.hscale40);
183                         break;
184                 case MA_OPT3_FILTERING:
185                         if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
186                         fprintf(f, "Bilinear filtering = %i", currentConfig.scaling);
187                         break;
188                 case MA_OPT3_GAMMAA:
189                         if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
190                         fprintf(f, "Gamma adjustment = %i", currentConfig.gamma);
191                         break;
192                 case MA_OPT3_BLACKLVL:
193                         if (no_def && defaultConfig.gamma2 == currentConfig.gamma2) return;
194                         fprintf(f, "Black level = %i", currentConfig.gamma2);
195                         break;
196                 case MA_OPT3_VSYNC:
197                         if (no_def && (defaultConfig.EmuOpt&0x12000) == (currentConfig.gamma2&0x12000)) return;
198                         strcpy(str24, "never");
199                         if (currentConfig.EmuOpt & 0x2000)
200                                 strcpy(str24, (currentConfig.EmuOpt & 0x10000) ? "sometimes" : "always");
201                         fprintf(f, "Wait for vsync = %s", str24);
202                         break;
203
204                 default:
205                         lprintf("unhandled custom_write: %i\n", me->id);
206                         return;
207         }
208         fprintf(f, NL);
209 }
210
211
212 #if PLAT_HAVE_JOY
213 static const char *joyKeyNames[32] =
214 {
215         "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
216         "b5",  "b6",  "b7",  "b8",  "b9",  "b10", "b11", "b12",
217         "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
218         "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
219 };
220 #endif
221
222 static void keys_write(FILE *fn, const char *bind_str, int dev_id, const int *binds, int no_defaults)
223 {
224         char act[48];
225         int key_count, t, i;
226         const int *def_binds;
227
228         key_count = in_get_dev_bind_count(dev_id);
229         def_binds = in_get_dev_def_binds(dev_id);
230
231         for (t = 0; t < key_count; t++)
232         {
233                 const char *name;
234                 act[0] = act[31] = 0;
235
236                 if (no_defaults && binds[t] == def_binds[t])
237                         continue;
238
239                 name = in_get_key_name(dev_id, t);
240 #ifdef __GP2X__
241                 if (strcmp(name, "SELECT") == 0) continue;
242 #endif
243
244                 if (binds[t] == 0 && def_binds[t] != 0) {
245                         fprintf(fn, "%s %s =" NL, bind_str, name);
246                         continue;
247                 }
248
249                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
250                         if (me_ctrl_actions[i].mask & binds[t]) {
251                                 strncpy(act, me_ctrl_actions[i].name, 31);
252                                 fprintf(fn, "%s %s = player%i %s" NL, bind_str, name,
253                                         ((binds[t]>>16)&1)+1, mystrip(act));
254                         }
255                 }
256
257                 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
258                         if (emuctrl_actions[i].mask & binds[t]) {
259                                 strncpy(act, emuctrl_actions[i].name, 31);
260                                 fprintf(fn, "%s %s = %s" NL, bind_str, name, mystrip(act));
261                         }
262                 }
263         }
264 }
265
266
267 static int default_var(const menu_entry *me)
268 {
269         switch (me->id)
270         {
271                 case MA_OPT_ACC_TIMING:
272                 case MA_OPT_ACC_SPRITES:
273                 case MA_OPT_ARM940_SOUND:
274                 case MA_OPT_6BUTTON_PAD:
275                 case MA_OPT2_ENABLE_Z80:
276                 case MA_OPT2_ENABLE_YM2612:
277                 case MA_OPT2_ENABLE_SN76496:
278                 case MA_OPT2_SVP_DYNAREC:
279                 case MA_CDOPT_CDDA:
280                 case MA_CDOPT_PCM:
281                 case MA_CDOPT_SAVERAM:
282                 case MA_CDOPT_SCALEROT_CHIP:
283                 case MA_CDOPT_BETTER_SYNC:
284                         return defaultConfig.s_PicoOpt;
285
286                 case MA_OPT_SHOW_FPS:
287                 case MA_OPT_ENABLE_SOUND:
288                 case MA_OPT_SRAM_STATES:
289                 case MA_OPT2_A_SN_GAMMA:
290                 case MA_OPT2_VSYNC:
291                 case MA_OPT2_GZIP_STATES:
292                 case MA_OPT2_NO_LAST_ROM:
293                 case MA_OPT2_RAMTIMINGS:
294                 case MA_CDOPT_LEDS:
295                         return defaultConfig.EmuOpt;
296
297                 case MA_CTRL_TURBO_RATE: return defaultConfig.turbo_rate;
298                 case MA_OPT_SCALING:     return defaultConfig.scaling;
299                 case MA_OPT_ROTATION:    return defaultConfig.rotation;
300
301                 case MA_OPT_SAVE_SLOT:
302                 default:
303                         return 0;
304         }
305 }
306
307 int config_writesect(const char *fname, const char *section)
308 {
309         FILE *fo = NULL, *fn = NULL; // old and new
310         int no_defaults = 0; // avoid saving defaults
311         menu_entry *me;
312         int t, i, tlen, ret;
313         char line[128], *tmp;
314
315         if (section != NULL)
316         {
317                 no_defaults = 1;
318
319                 fo = fopen(fname, "r");
320                 if (fo == NULL) {
321                         fn = fopen(fname, "w");
322                         goto write;
323                 }
324
325                 ret = seek_sect(fo, section);
326                 if (!ret) {
327                         // sect not found, we can simply append
328                         fclose(fo); fo = NULL;
329                         fn = fopen(fname, "a");
330                         goto write;
331                 }
332
333                 // use 2 files..
334                 fclose(fo);
335                 rename(fname, "tmp.cfg");
336                 fo = fopen("tmp.cfg", "r");
337                 fn = fopen(fname, "w");
338                 if (fo == NULL || fn == NULL) goto write;
339
340                 // copy everything until sect
341                 tlen = strlen(section);
342                 while (!feof(fo))
343                 {
344                         tmp = fgets(line, sizeof(line), fo);
345                         if (tmp == NULL) break;
346
347                         if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
348                                 break;
349                         fputs(line, fn);
350                 }
351
352                 // now skip to next sect
353                 while (!feof(fo))
354                 {
355                         tmp = fgets(line, sizeof(line), fo);
356                         if (tmp == NULL) break;
357                         if (line[0] == '[') {
358                                 fseek(fo, -strlen(line), SEEK_CUR);
359                                 break;
360                         }
361                 }
362                 if (feof(fo))
363                 {
364                         fclose(fo); fo = NULL;
365                         remove("tmp.cfg");
366                 }
367         }
368         else
369         {
370                 fn = fopen(fname, "w");
371         }
372
373 write:
374         if (fn == NULL) {
375                 if (fo) fclose(fo);
376                 return -1;
377         }
378         if (section != NULL)
379                 fprintf(fn, "[%s]" NL, section);
380
381         for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
382         {
383                 me = cfg_opts[t];
384                 tlen = *(cfg_opt_counts[t]);
385                 for (i = 0; i < tlen; i++, me++)
386                 {
387                         if (!me->need_to_save) continue;
388                         if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
389                                 custom_write(fn, me, no_defaults);
390                         else if (me->beh == MB_ONOFF) {
391                                 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
392                                         fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
393                         } else if (me->beh == MB_RANGE) {
394                                 if (!no_defaults || (*(int *)me->var ^ default_var(me)))
395                                         fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
396                         }
397                 }
398         }
399
400         /* input: save device names */
401         for (t = 0; t < IN_MAX_DEVS; t++)
402         {
403                 const int  *binds = in_get_dev_binds(t);
404                 const char *name =  in_get_dev_name(t);
405                 if (binds == NULL || name == NULL)
406                         continue;
407
408                 fprintf(fn, "input%d = %s" NL, t, name);
409         }
410
411         /* input: save binds */
412         for (t = 0; t < IN_MAX_DEVS; t++)
413         {
414                 const int *binds = in_get_dev_binds(t);
415                 const char *name = in_get_dev_name(t);
416                 char strbind[16];
417                 int count;
418
419                 if (binds == NULL || name == NULL)
420                         continue;
421
422                 sprintf(strbind, "bind%d", t);
423                 if (t == 0) strbind[4] = 0;
424
425                 count = in_get_dev_bind_count(t);
426                 keys_write(fn, strbind, t, binds, no_defaults);
427         }
428
429 #if 0
430         /* old stuff */
431         keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, PLAT_MAX_KEYS, no_defaults);
432 #if PLAT_HAVE_JOY
433         keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 32, 1);
434         keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 32, 1);
435         keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 32, 1);
436         keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 32, 1);
437 #endif
438 #endif
439
440 #ifndef PSP
441         if (section == NULL)
442                 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
443 #endif
444
445         fprintf(fn, NL);
446
447         if (fo != NULL)
448         {
449                 // copy whatever is left
450                 while (!feof(fo))
451                 {
452                         tmp = fgets(line, sizeof(line), fo);
453                         if (tmp == NULL) break;
454
455                         fputs(line, fn);
456                 }
457                 fclose(fo);
458                 remove("tmp.cfg");
459         }
460
461         fclose(fn);
462         return 0;
463 }
464
465
466 int config_writelrom(const char *fname)
467 {
468         char line[128], *tmp, *optr = NULL;
469         char *old_data = NULL;
470         int size;
471         FILE *f;
472
473         if (strlen(loadedRomFName) == 0) return -1;
474
475         f = fopen(fname, "r");
476         if (f != NULL)
477         {
478                 fseek(f, 0, SEEK_END);
479                 size = ftell(f);
480                 fseek(f, 0, SEEK_SET);
481                 old_data = malloc(size + size/8);
482                 if (old_data != NULL)
483                 {
484                         optr = old_data;
485                         while (!feof(f))
486                         {
487                                 tmp = fgets(line, sizeof(line), f);
488                                 if (tmp == NULL) break;
489                                 mystrip(line);
490                                 if (strncasecmp(line, "LastUsedROM", 11) == 0)
491                                         continue;
492                                 sprintf(optr, "%s", line);
493                                 optr += strlen(optr);
494                         }
495                 }
496                 fclose(f);
497         }
498
499         f = fopen(fname, "w");
500         if (f == NULL) return -1;
501
502         if (old_data != NULL) {
503                 fwrite(old_data, 1, optr - old_data, f);
504                 free(old_data);
505         }
506         fprintf(f, "LastUsedROM = %s" NL, loadedRomFName);
507         fclose(f);
508         return 0;
509 }
510
511 /* --------------------------------------------------------------------------*/
512
513 int config_readlrom(const char *fname)
514 {
515         char line[128], *tmp;
516         int i, len, ret = -1;
517         FILE *f;
518
519         f = fopen(fname, "r");
520         if (f == NULL) return -1;
521
522         // seek to the section needed
523         while (!feof(f))
524         {
525                 tmp = fgets(line, sizeof(line), f);
526                 if (tmp == NULL) break;
527
528                 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
529                 len = strlen(line);
530                 for (i = 0; i < len; i++)
531                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
532                 tmp = strchr(line, '=');
533                 if (tmp == NULL) break;
534                 tmp++;
535                 mystrip(tmp);
536
537                 len = sizeof(loadedRomFName);
538                 strncpy(loadedRomFName, tmp, len);
539                 loadedRomFName[len-1] = 0;
540                 ret = 0;
541                 break;
542         }
543         fclose(f);
544         return ret;
545 }
546
547
548 static int custom_read(menu_entry *me, const char *var, const char *val)
549 {
550         char *tmp;
551         int tmpi;
552
553         switch (me->id)
554         {
555                 case MA_OPT_RENDERER:
556                         if (strcasecmp(var, "Renderer") != 0) return 0;
557                         if      (strcasecmp(val, "8bit fast") == 0 || strcasecmp(val, "fast") == 0) {
558                                 PicoOpt |=  POPT_ALT_RENDERER;
559                         }
560                         else if (strcasecmp(val, "16bit accurate") == 0 || strcasecmp(val, "accurate") == 0) {
561                                 PicoOpt &= ~POPT_ALT_RENDERER;
562                                 currentConfig.EmuOpt |=  0x80;
563                         }
564                         else if (strcasecmp(val, "8bit accurate") == 0) {
565                                 PicoOpt &= ~POPT_ALT_RENDERER;
566                                 currentConfig.EmuOpt &= ~0x80;
567                         }
568                         else
569                                 return 0;
570                         return 1;
571
572                 case MA_OPT_SCALING:
573 #ifdef __GP2X__
574                         if (strcasecmp(var, "Scaling") != 0) return 0;
575                         if        (strcasecmp(val, "OFF") == 0) {
576                                 currentConfig.scaling = 0;
577                         } else if (strcasecmp(val, "hw horizontal") == 0) {
578                                 currentConfig.scaling = 1;
579                         } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
580                                 currentConfig.scaling = 2;
581                         } else if (strcasecmp(val, "sw horizontal") == 0) {
582                                 currentConfig.scaling = 3;
583                         } else
584                                 return 0;
585                         return 1;
586 #else
587                         return 0;
588 #endif
589
590                 case MA_OPT_FRAMESKIP:
591                         if (strcasecmp(var, "Frameskip") != 0) return 0;
592                         if (strcasecmp(val, "Auto") == 0)
593                              currentConfig.Frameskip = -1;
594                         else currentConfig.Frameskip = atoi(val);
595                         return 1;
596
597                 case MA_OPT_SOUND_QUALITY:
598                         if (strcasecmp(var, "Sound Quality") != 0) return 0;
599                         PsndRate = strtoul(val, &tmp, 10);
600                         if (PsndRate < 8000 || PsndRate > 44100)
601                                 PsndRate = 22050;
602                         while (*tmp == ' ') tmp++;
603                         if        (strcasecmp(tmp, "stereo") == 0) {
604                                 PicoOpt |=  POPT_EN_STEREO;
605                         } else if (strcasecmp(tmp, "mono") == 0) {
606                                 PicoOpt &= ~POPT_EN_STEREO;
607                         } else
608                                 return 0;
609                         return 1;
610
611                 case MA_OPT_REGION:
612                         if (strcasecmp(var, "Region") != 0) return 0;
613                         if       (strncasecmp(val, "Auto: ", 6) == 0)
614                         {
615                                 const char *p = val + 5, *end = val + strlen(val);
616                                 int i;
617                                 PicoRegionOverride = PicoAutoRgnOrder = 0;
618                                 for (i = 0; p < end && i < 3; i++)
619                                 {
620                                         while (*p == ' ') p++;
621                                         if        (p[0] == 'J' && p[1] == 'P') {
622                                                 PicoAutoRgnOrder |= 1 << (i*4);
623                                         } else if (p[0] == 'U' && p[1] == 'S') {
624                                                 PicoAutoRgnOrder |= 4 << (i*4);
625                                         } else if (p[0] == 'E' && p[1] == 'U') {
626                                                 PicoAutoRgnOrder |= 8 << (i*4);
627                                         }
628                                         while (*p != ' ' && *p != 0) p++;
629                                         if (*p == 0) break;
630                                 }
631                         }
632                         else   if (strcasecmp(val, "Auto") == 0) {
633                                 PicoRegionOverride = 0;
634                         } else if (strcasecmp(val, "Japan NTSC") == 0) {
635                                 PicoRegionOverride = 1;
636                         } else if (strcasecmp(val, "Japan PAL") == 0) {
637                                 PicoRegionOverride = 2;
638                         } else if (strcasecmp(val, "USA") == 0) {
639                                 PicoRegionOverride = 4;
640                         } else if (strcasecmp(val, "Europe") == 0) {
641                                 PicoRegionOverride = 8;
642                         } else
643                                 return 0;
644                         return 1;
645
646                 case MA_OPT_CONFIRM_STATES:
647                         if (strcasecmp(var, "Confirm savestate") != 0) return 0;
648                         if        (strcasecmp(val, "OFF") == 0) {
649                                 currentConfig.EmuOpt &= ~(5<<9);
650                         } else if (strcasecmp(val, "writes") == 0) {
651                                 currentConfig.EmuOpt &= ~(5<<9);
652                                 currentConfig.EmuOpt |=   1<<9;
653                         } else if (strcasecmp(val, "loads") == 0) {
654                                 currentConfig.EmuOpt &= ~(5<<9);
655                                 currentConfig.EmuOpt |=   4<<9;
656                         } else if (strcasecmp(val, "both") == 0) {
657                                 currentConfig.EmuOpt &= ~(5<<9);
658                                 currentConfig.EmuOpt |=   5<<9;
659                         } else
660                                 return 0;
661                         return 1;
662
663                 case MA_OPT_CPU_CLOCKS:
664 #ifdef __GP2X__
665                         if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
666 #elif defined(PSP)
667                         if (strcasecmp(var, "PSP CPU clock") != 0) return 0;
668 #endif
669                         currentConfig.CPUclock = atoi(val);
670                         return 1;
671
672                 case MA_OPT2_GAMMA:
673                         if (strcasecmp(var, "Gamma correction") != 0) return 0;
674                         currentConfig.gamma = (int) (atof(val) * 100.0);
675                         return 1;
676
677                 case MA_OPT2_SQUIDGEHACK:
678                         if (strcasecmp(var, "Squidgehack") != 0) return 0;
679                         tmpi = atoi(val);
680                         if (tmpi) *(int *)me->var |=  me->mask;
681                         else      *(int *)me->var &= ~me->mask;
682                         return 1;
683
684                 case MA_CDOPT_READAHEAD:
685                         if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
686                         PicoCDBuffers = atoi(val) / 2;
687                         return 1;
688
689                 /* PSP */
690                 case MA_OPT3_SCALE:
691                         if (strcasecmp(var, "Scale factor") != 0) return 0;
692                         currentConfig.scale = atof(val);
693                         return 1;
694                 case MA_OPT3_HSCALE32:
695                         if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
696                         currentConfig.hscale32 = atof(val);
697                         return 1;
698                 case MA_OPT3_HSCALE40:
699                         if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
700                         currentConfig.hscale40 = atof(val);
701                         return 1;
702                 case MA_OPT3_FILTERING:
703                         if (strcasecmp(var, "Bilinear filtering") != 0) return 0;
704                         currentConfig.scaling = atoi(val);
705                         return 1;
706                 case MA_OPT3_GAMMAA:
707                         if (strcasecmp(var, "Gamma adjustment") != 0) return 0;
708                         currentConfig.gamma = atoi(val);
709                         return 1;
710                 case MA_OPT3_BLACKLVL:
711                         if (strcasecmp(var, "Black level") != 0) return 0;
712                         currentConfig.gamma2 = atoi(val);
713                         return 1;
714                 case MA_OPT3_VSYNC:
715                         if (strcasecmp(var, "Wait for vsync") != 0) return 0;
716                         if        (strcasecmp(val, "never") == 0) {
717                                 currentConfig.EmuOpt &= ~0x12000;
718                         } else if (strcasecmp(val, "sometimes") == 0) {
719                                 currentConfig.EmuOpt |=  0x12000;
720                         } else if (strcasecmp(val, "always") == 0) {
721                                 currentConfig.EmuOpt &= ~0x12000;
722                                 currentConfig.EmuOpt |=  0x02000;
723                         } else
724                                 return 0;
725                         return 1;
726
727                 default:
728                         lprintf("unhandled custom_read: %i\n", me->id);
729                         return 0;
730         }
731 }
732
733
734 static unsigned int keys_encountered = 0;
735
736 static int parse_bind_val(const char *val)
737 {
738         int i;
739
740         if (val[0] == 0)
741                 return 0;
742         
743         if (strncasecmp(val, "player", 6) == 0)
744         {
745                 unsigned int player;
746                 player = atoi(val + 6) - 1;
747                 if (player > 1)
748                         return -1;
749
750                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
751                         if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0)
752                                 return me_ctrl_actions[i].mask | (player<<16);
753                 }
754         }
755         for (i = 0; emuctrl_actions[i].name != NULL; i++) {
756                 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0)
757                         return emuctrl_actions[i].mask;
758         }
759
760         return -1;
761 }
762
763 static void keys_parse(const char *key, const char *val, int dev_id)
764 {
765         int binds;
766
767         binds = parse_bind_val(val);
768         if (binds == -1) {
769                 lprintf("config: unhandled action \"%s\"\n", val);
770                 return;
771         }
772
773         in_config_bind_key(dev_id, key, binds);
774 /*
775         for (t = 0; t < 32; t++)
776         {
777                 if (names[t] && strcmp(names[t], var) == 0) break;
778         }
779         if (t == 32)
780         {
781                 int len = strlen(var);
782                 if (len == 1) t = var[0];
783                 else if (len >= 4 && var[0] == '\\' && var[1] == 'x') {
784                         char *p;
785                         t = (int)strtoul(var + 2, &p, 16);
786                         if (*p != 0) t = max_keys; // parse failed
787                 }
788                 else
789                         t = max_keys; // invalid
790         }
791         if (t < 0 || t >= max_keys) {
792                 lprintf("unhandled bind \"%s\"\n", var);
793                 return;
794         }
795
796         // unbind old, but only when key is first encountered
797         if (t < 32 && binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) {
798                 binds[t] = 0;
799                 keys_encountered |= 1<<t;
800         }
801 */
802 }
803
804
805 #define try_joy_parse(num) { \
806         if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
807                 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames, 32); \
808                 return; \
809         } \
810 }
811
812 static int get_numvar_num(const char *var)
813 {
814         char *p = NULL;
815         int num;
816         
817         if (var[0] == ' ')
818                 return 0;
819
820         num = strtoul(var, &p, 10);
821         if (*p == 0 || *p == ' ')
822                 return num;
823
824         return -1;
825 }
826
827 /* map dev number in confing to input dev number */
828 static unsigned char input_dev_map[IN_MAX_DEVS];
829
830 static void parse(const char *var, const char *val)
831 {
832         menu_entry *me;
833         int t, i, tlen, tmp, ret = 0;
834
835         if (strcasecmp(var, "LastUsedROM") == 0)
836                 return; /* handled elsewhere */
837
838         if (strcasecmp(var, "Sound Volume") == 0) {
839                 currentConfig.volume = atoi(val);
840                 return;
841         }
842
843         /* input: device name */
844         if (strncasecmp(var, "input", 5) == 0) {
845                 int num = get_numvar_num(var + 5);
846                 if (num >= 0 && num < IN_MAX_DEVS)
847                         input_dev_map[num] = in_config_parse_dev(val);
848                 else
849                         printf("failed to parse: %s\n", var);
850                 return;
851         }
852
853         // key binds
854         if (strncasecmp(var, "bind", 4) == 0) {
855                 const char *p = var + 4;
856                 int num = get_numvar_num(p);
857                 if (num < 0 || num >= IN_MAX_DEVS) {
858                         printf("failed to parse: %s\n", var);
859                         return;
860                 }
861
862                 num = input_dev_map[num];
863                 if (num < 0 || num >= IN_MAX_DEVS) {
864                         printf("invalid device id: %s\n", var);
865                         return;
866                 }
867
868                 while (*p && *p != ' ') p++;
869                 while (*p && *p == ' ') p++;
870                 keys_parse(p, val, num);
871                 return;
872         }
873
874 #if 0//PLAT_HAVE_JOY
875         try_joy_parse(0)
876         try_joy_parse(1)
877         try_joy_parse(2)
878         try_joy_parse(3)
879 #endif
880
881         for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
882         {
883                 me = cfg_opts[t];
884                 tlen = *(cfg_opt_counts[t]);
885                 for (i = 0; i < tlen && ret == 0; i++, me++)
886                 {
887                         if (!me->need_to_save) continue;
888                         if (me->name != NULL) {
889                                 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
890                                 if (me->beh == MB_ONOFF) {
891                                         tmp = atoi(val);
892                                         if (tmp) *(int *)me->var |=  me->mask;
893                                         else     *(int *)me->var &= ~me->mask;
894                                         return;
895                                 } else if (me->beh == MB_RANGE) {
896                                         tmp = atoi(val);
897                                         if (tmp < me->min) tmp = me->min;
898                                         if (tmp > me->max) tmp = me->max;
899                                         *(int *)me->var = tmp;
900                                         return;
901                                 }
902                         }
903                         ret = custom_read(me, var, val);
904                 }
905         }
906         if (!ret) lprintf("config_readsect: unhandled var: \"%s\"\n", var);
907 }
908
909
910 int config_havesect(const char *fname, const char *section)
911 {
912         FILE *f;
913         int ret;
914
915         f = fopen(fname, "r");
916         if (f == NULL) return 0;
917
918         ret = seek_sect(f, section);
919         fclose(f);
920         return ret;
921 }
922
923 int config_readsect(const char *fname, const char *section)
924 {
925         char line[128], *var, *val;
926         FILE *f;
927         int ret;
928
929         f = fopen(fname, "r");
930         if (f == NULL) return -1;
931
932         if (section != NULL)
933         {
934                 ret = seek_sect(f, section);
935                 if (!ret) {
936                         lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
937                         fclose(f);
938                         return -1;
939                 }
940         }
941
942         keys_encountered = 0;
943         memset(input_dev_map, 0xff, sizeof(input_dev_map));
944
945         in_config_start();
946         while (!feof(f))
947         {
948                 ret = config_get_var_val(f, line, sizeof(line), &var, &val);
949                 if (ret ==  0) break;
950                 if (ret == -1) continue;
951
952                 parse(var, val);
953         }
954         in_config_end();
955
956         fclose(f);
957         return 0;
958 }
959
960 #endif // _MSC_VER
961
962 static char *mystrip(char *str)
963 {
964         int i, len;
965
966         len = strlen(str);
967         for (i = 0; i < len; i++)
968                 if (str[i] != ' ') break;
969         if (i > 0) memmove(str, str + i, len - i + 1);
970
971         len = strlen(str);
972         for (i = len - 1; i >= 0; i--)
973                 if (str[i] != ' ') break;
974         str[i+1] = 0;
975
976         return str;
977 }
978
979 /* returns:
980  *  0 - EOF, end
981  *  1 - parsed ok
982  * -1 - failed to parse line
983  */
984 int config_get_var_val(void *file, char *line, int lsize, char **rvar, char **rval)
985 {
986         char *var, *val, *tmp;
987         FILE *f = file;
988         int len, i;
989
990         tmp = fgets(line, lsize, f);
991         if (tmp == NULL) return 0;
992
993         if (line[0] == '[') return 0; // other section
994
995         // strip comments, linefeed, spaces..
996         len = strlen(line);
997         for (i = 0; i < len; i++)
998                 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
999         mystrip(line);
1000         len = strlen(line);
1001         if (len <= 0) return -1;;
1002
1003         // get var and val
1004         for (i = 0; i < len; i++)
1005                 if (line[i] == '=') break;
1006         if (i >= len || strchr(&line[i+1], '=') != NULL) {
1007                 lprintf("config_readsect: can't parse: %s\n", line);
1008                 return -1;
1009         }
1010         line[i] = 0;
1011         var = line;
1012         val = &line[i+1];
1013         mystrip(var);
1014         mystrip(val);
1015
1016 #ifndef _MSC_VER
1017         if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
1018                 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);
1019                 return -1;;
1020         }
1021 #endif
1022
1023         *rvar = var;
1024         *rval = val;
1025         return 1;
1026 }
1027