63c8f00ccbe2cdba30f3d40371993529cb3c937d
[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 static void keys_write(FILE *fn, const char *bind_str, int dev_id, const int *binds, int no_defaults)
213 {
214         char act[48];
215         int key_count, t, i;
216         const int *def_binds;
217
218         key_count = in_get_dev_bind_count(dev_id);
219         def_binds = in_get_dev_def_binds(dev_id);
220
221         for (t = 0; t < key_count; t++)
222         {
223                 const char *name;
224                 act[0] = act[31] = 0;
225
226                 if (no_defaults && binds[t] == def_binds[t])
227                         continue;
228
229                 name = in_get_key_name(dev_id, t);
230 #ifdef __GP2X__
231                 if (strcmp(name, "SELECT") == 0) continue;
232 #endif
233
234                 if (binds[t] == 0 && def_binds[t] != 0) {
235                         fprintf(fn, "%s %s =" NL, bind_str, name);
236                         continue;
237                 }
238
239                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
240                         if (me_ctrl_actions[i].mask & binds[t]) {
241                                 strncpy(act, me_ctrl_actions[i].name, 31);
242                                 fprintf(fn, "%s %s = player%i %s" NL, bind_str, name,
243                                         ((binds[t]>>16)&1)+1, mystrip(act));
244                         }
245                 }
246
247                 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
248                         if (emuctrl_actions[i].mask & binds[t]) {
249                                 strncpy(act, emuctrl_actions[i].name, 31);
250                                 fprintf(fn, "%s %s = %s" NL, bind_str, name, mystrip(act));
251                         }
252                 }
253         }
254 }
255
256
257 static int default_var(const menu_entry *me)
258 {
259         switch (me->id)
260         {
261                 case MA_OPT_ACC_TIMING:
262                 case MA_OPT_ACC_SPRITES:
263                 case MA_OPT_ARM940_SOUND:
264                 case MA_OPT_6BUTTON_PAD:
265                 case MA_OPT2_ENABLE_Z80:
266                 case MA_OPT2_ENABLE_YM2612:
267                 case MA_OPT2_ENABLE_SN76496:
268                 case MA_OPT2_SVP_DYNAREC:
269                 case MA_CDOPT_CDDA:
270                 case MA_CDOPT_PCM:
271                 case MA_CDOPT_SAVERAM:
272                 case MA_CDOPT_SCALEROT_CHIP:
273                 case MA_CDOPT_BETTER_SYNC:
274                         return defaultConfig.s_PicoOpt;
275
276                 case MA_OPT_SHOW_FPS:
277                 case MA_OPT_ENABLE_SOUND:
278                 case MA_OPT_SRAM_STATES:
279                 case MA_OPT2_A_SN_GAMMA:
280                 case MA_OPT2_VSYNC:
281                 case MA_OPT2_GZIP_STATES:
282                 case MA_OPT2_NO_LAST_ROM:
283                 case MA_OPT2_RAMTIMINGS:
284                 case MA_CDOPT_LEDS:
285                         return defaultConfig.EmuOpt;
286
287                 case MA_CTRL_TURBO_RATE: return defaultConfig.turbo_rate;
288                 case MA_OPT_SCALING:     return defaultConfig.scaling;
289                 case MA_OPT_ROTATION:    return defaultConfig.rotation;
290
291                 case MA_OPT_SAVE_SLOT:
292                 default:
293                         return 0;
294         }
295 }
296
297 int config_writesect(const char *fname, const char *section)
298 {
299         FILE *fo = NULL, *fn = NULL; // old and new
300         int no_defaults = 0; // avoid saving defaults
301         menu_entry *me;
302         int t, i, tlen, ret;
303         char line[128], *tmp;
304
305         if (section != NULL)
306         {
307                 no_defaults = 1;
308
309                 fo = fopen(fname, "r");
310                 if (fo == NULL) {
311                         fn = fopen(fname, "w");
312                         goto write;
313                 }
314
315                 ret = seek_sect(fo, section);
316                 if (!ret) {
317                         // sect not found, we can simply append
318                         fclose(fo); fo = NULL;
319                         fn = fopen(fname, "a");
320                         goto write;
321                 }
322
323                 // use 2 files..
324                 fclose(fo);
325                 rename(fname, "tmp.cfg");
326                 fo = fopen("tmp.cfg", "r");
327                 fn = fopen(fname, "w");
328                 if (fo == NULL || fn == NULL) goto write;
329
330                 // copy everything until sect
331                 tlen = strlen(section);
332                 while (!feof(fo))
333                 {
334                         tmp = fgets(line, sizeof(line), fo);
335                         if (tmp == NULL) break;
336
337                         if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
338                                 break;
339                         fputs(line, fn);
340                 }
341
342                 // now skip to next sect
343                 while (!feof(fo))
344                 {
345                         tmp = fgets(line, sizeof(line), fo);
346                         if (tmp == NULL) break;
347                         if (line[0] == '[') {
348                                 fseek(fo, -strlen(line), SEEK_CUR);
349                                 break;
350                         }
351                 }
352                 if (feof(fo))
353                 {
354                         fclose(fo); fo = NULL;
355                         remove("tmp.cfg");
356                 }
357         }
358         else
359         {
360                 fn = fopen(fname, "w");
361         }
362
363 write:
364         if (fn == NULL) {
365                 if (fo) fclose(fo);
366                 return -1;
367         }
368         if (section != NULL)
369                 fprintf(fn, "[%s]" NL, section);
370
371         for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
372         {
373                 me = cfg_opts[t];
374                 tlen = *(cfg_opt_counts[t]);
375                 for (i = 0; i < tlen; i++, me++)
376                 {
377                         if (!me->need_to_save) continue;
378                         if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
379                                 custom_write(fn, me, no_defaults);
380                         else if (me->beh == MB_ONOFF) {
381                                 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
382                                         fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
383                         } else if (me->beh == MB_RANGE) {
384                                 if (!no_defaults || (*(int *)me->var ^ default_var(me)))
385                                         fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
386                         }
387                 }
388         }
389
390         /* input: save device names */
391         for (t = 0; t < IN_MAX_DEVS; t++)
392         {
393                 const int  *binds = in_get_dev_binds(t);
394                 const char *name =  in_get_dev_name(t, 0);
395                 if (binds == NULL || name == NULL)
396                         continue;
397
398                 fprintf(fn, "input%d = %s" NL, t, name);
399         }
400
401         /* input: save binds */
402         for (t = 0; t < IN_MAX_DEVS; t++)
403         {
404                 const int *binds = in_get_dev_binds(t);
405                 const char *name = in_get_dev_name(t, 0);
406                 char strbind[16];
407                 int count;
408
409                 if (binds == NULL || name == NULL)
410                         continue;
411
412                 sprintf(strbind, "bind%d", t);
413                 if (t == 0) strbind[4] = 0;
414
415                 count = in_get_dev_bind_count(t);
416                 keys_write(fn, strbind, t, binds, no_defaults);
417         }
418
419 #ifndef PSP
420         if (section == NULL)
421                 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
422 #endif
423
424         fprintf(fn, NL);
425
426         if (fo != NULL)
427         {
428                 // copy whatever is left
429                 while (!feof(fo))
430                 {
431                         tmp = fgets(line, sizeof(line), fo);
432                         if (tmp == NULL) break;
433
434                         fputs(line, fn);
435                 }
436                 fclose(fo);
437                 remove("tmp.cfg");
438         }
439
440         fclose(fn);
441         return 0;
442 }
443
444
445 int config_writelrom(const char *fname)
446 {
447         char line[128], *tmp, *optr = NULL;
448         char *old_data = NULL;
449         int size;
450         FILE *f;
451
452         if (strlen(loadedRomFName) == 0) return -1;
453
454         f = fopen(fname, "r");
455         if (f != NULL)
456         {
457                 fseek(f, 0, SEEK_END);
458                 size = ftell(f);
459                 fseek(f, 0, SEEK_SET);
460                 old_data = malloc(size + size/8);
461                 if (old_data != NULL)
462                 {
463                         optr = old_data;
464                         while (!feof(f))
465                         {
466                                 tmp = fgets(line, sizeof(line), f);
467                                 if (tmp == NULL) break;
468                                 mystrip(line);
469                                 if (strncasecmp(line, "LastUsedROM", 11) == 0)
470                                         continue;
471                                 sprintf(optr, "%s", line);
472                                 optr += strlen(optr);
473                         }
474                 }
475                 fclose(f);
476         }
477
478         f = fopen(fname, "w");
479         if (f == NULL) return -1;
480
481         if (old_data != NULL) {
482                 fwrite(old_data, 1, optr - old_data, f);
483                 free(old_data);
484         }
485         fprintf(f, "LastUsedROM = %s" NL, loadedRomFName);
486         fclose(f);
487         return 0;
488 }
489
490 /* --------------------------------------------------------------------------*/
491
492 int config_readlrom(const char *fname)
493 {
494         char line[128], *tmp;
495         int i, len, ret = -1;
496         FILE *f;
497
498         f = fopen(fname, "r");
499         if (f == NULL) return -1;
500
501         // seek to the section needed
502         while (!feof(f))
503         {
504                 tmp = fgets(line, sizeof(line), f);
505                 if (tmp == NULL) break;
506
507                 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
508                 len = strlen(line);
509                 for (i = 0; i < len; i++)
510                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
511                 tmp = strchr(line, '=');
512                 if (tmp == NULL) break;
513                 tmp++;
514                 mystrip(tmp);
515
516                 len = sizeof(loadedRomFName);
517                 strncpy(loadedRomFName, tmp, len);
518                 loadedRomFName[len-1] = 0;
519                 ret = 0;
520                 break;
521         }
522         fclose(f);
523         return ret;
524 }
525
526
527 static int custom_read(menu_entry *me, const char *var, const char *val)
528 {
529         char *tmp;
530         int tmpi;
531
532         switch (me->id)
533         {
534                 case MA_OPT_RENDERER:
535                         if (strcasecmp(var, "Renderer") != 0) return 0;
536                         if      (strcasecmp(val, "8bit fast") == 0 || strcasecmp(val, "fast") == 0) {
537                                 PicoOpt |=  POPT_ALT_RENDERER;
538                         }
539                         else if (strcasecmp(val, "16bit accurate") == 0 || strcasecmp(val, "accurate") == 0) {
540                                 PicoOpt &= ~POPT_ALT_RENDERER;
541                                 currentConfig.EmuOpt |=  0x80;
542                         }
543                         else if (strcasecmp(val, "8bit accurate") == 0) {
544                                 PicoOpt &= ~POPT_ALT_RENDERER;
545                                 currentConfig.EmuOpt &= ~0x80;
546                         }
547                         else
548                                 return 0;
549                         return 1;
550
551                 case MA_OPT_SCALING:
552 #ifdef __GP2X__
553                         if (strcasecmp(var, "Scaling") != 0) return 0;
554                         if        (strcasecmp(val, "OFF") == 0) {
555                                 currentConfig.scaling = 0;
556                         } else if (strcasecmp(val, "hw horizontal") == 0) {
557                                 currentConfig.scaling = 1;
558                         } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
559                                 currentConfig.scaling = 2;
560                         } else if (strcasecmp(val, "sw horizontal") == 0) {
561                                 currentConfig.scaling = 3;
562                         } else
563                                 return 0;
564                         return 1;
565 #else
566                         return 0;
567 #endif
568
569                 case MA_OPT_FRAMESKIP:
570                         if (strcasecmp(var, "Frameskip") != 0) return 0;
571                         if (strcasecmp(val, "Auto") == 0)
572                              currentConfig.Frameskip = -1;
573                         else currentConfig.Frameskip = atoi(val);
574                         return 1;
575
576                 case MA_OPT_SOUND_QUALITY:
577                         if (strcasecmp(var, "Sound Quality") != 0) return 0;
578                         PsndRate = strtoul(val, &tmp, 10);
579                         if (PsndRate < 8000 || PsndRate > 44100)
580                                 PsndRate = 22050;
581                         while (*tmp == ' ') tmp++;
582                         if        (strcasecmp(tmp, "stereo") == 0) {
583                                 PicoOpt |=  POPT_EN_STEREO;
584                         } else if (strcasecmp(tmp, "mono") == 0) {
585                                 PicoOpt &= ~POPT_EN_STEREO;
586                         } else
587                                 return 0;
588                         return 1;
589
590                 case MA_OPT_REGION:
591                         if (strcasecmp(var, "Region") != 0) return 0;
592                         if       (strncasecmp(val, "Auto: ", 6) == 0)
593                         {
594                                 const char *p = val + 5, *end = val + strlen(val);
595                                 int i;
596                                 PicoRegionOverride = PicoAutoRgnOrder = 0;
597                                 for (i = 0; p < end && i < 3; i++)
598                                 {
599                                         while (*p == ' ') p++;
600                                         if        (p[0] == 'J' && p[1] == 'P') {
601                                                 PicoAutoRgnOrder |= 1 << (i*4);
602                                         } else if (p[0] == 'U' && p[1] == 'S') {
603                                                 PicoAutoRgnOrder |= 4 << (i*4);
604                                         } else if (p[0] == 'E' && p[1] == 'U') {
605                                                 PicoAutoRgnOrder |= 8 << (i*4);
606                                         }
607                                         while (*p != ' ' && *p != 0) p++;
608                                         if (*p == 0) break;
609                                 }
610                         }
611                         else   if (strcasecmp(val, "Auto") == 0) {
612                                 PicoRegionOverride = 0;
613                         } else if (strcasecmp(val, "Japan NTSC") == 0) {
614                                 PicoRegionOverride = 1;
615                         } else if (strcasecmp(val, "Japan PAL") == 0) {
616                                 PicoRegionOverride = 2;
617                         } else if (strcasecmp(val, "USA") == 0) {
618                                 PicoRegionOverride = 4;
619                         } else if (strcasecmp(val, "Europe") == 0) {
620                                 PicoRegionOverride = 8;
621                         } else
622                                 return 0;
623                         return 1;
624
625                 case MA_OPT_CONFIRM_STATES:
626                         if (strcasecmp(var, "Confirm savestate") != 0) return 0;
627                         if        (strcasecmp(val, "OFF") == 0) {
628                                 currentConfig.EmuOpt &= ~(5<<9);
629                         } else if (strcasecmp(val, "writes") == 0) {
630                                 currentConfig.EmuOpt &= ~(5<<9);
631                                 currentConfig.EmuOpt |=   1<<9;
632                         } else if (strcasecmp(val, "loads") == 0) {
633                                 currentConfig.EmuOpt &= ~(5<<9);
634                                 currentConfig.EmuOpt |=   4<<9;
635                         } else if (strcasecmp(val, "both") == 0) {
636                                 currentConfig.EmuOpt &= ~(5<<9);
637                                 currentConfig.EmuOpt |=   5<<9;
638                         } else
639                                 return 0;
640                         return 1;
641
642                 case MA_OPT_CPU_CLOCKS:
643 #ifdef __GP2X__
644                         if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
645 #elif defined(PSP)
646                         if (strcasecmp(var, "PSP CPU clock") != 0) return 0;
647 #endif
648                         currentConfig.CPUclock = atoi(val);
649                         return 1;
650
651                 case MA_OPT2_GAMMA:
652                         if (strcasecmp(var, "Gamma correction") != 0) return 0;
653                         currentConfig.gamma = (int) (atof(val) * 100.0);
654                         return 1;
655
656                 case MA_OPT2_SQUIDGEHACK:
657                         if (strcasecmp(var, "Squidgehack") != 0) return 0;
658                         tmpi = atoi(val);
659                         if (tmpi) *(int *)me->var |=  me->mask;
660                         else      *(int *)me->var &= ~me->mask;
661                         return 1;
662
663                 case MA_CDOPT_READAHEAD:
664                         if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
665                         PicoCDBuffers = atoi(val) / 2;
666                         return 1;
667
668                 /* PSP */
669                 case MA_OPT3_SCALE:
670                         if (strcasecmp(var, "Scale factor") != 0) return 0;
671                         currentConfig.scale = atof(val);
672                         return 1;
673                 case MA_OPT3_HSCALE32:
674                         if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
675                         currentConfig.hscale32 = atof(val);
676                         return 1;
677                 case MA_OPT3_HSCALE40:
678                         if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
679                         currentConfig.hscale40 = atof(val);
680                         return 1;
681                 case MA_OPT3_FILTERING:
682                         if (strcasecmp(var, "Bilinear filtering") != 0) return 0;
683                         currentConfig.scaling = atoi(val);
684                         return 1;
685                 case MA_OPT3_GAMMAA:
686                         if (strcasecmp(var, "Gamma adjustment") != 0) return 0;
687                         currentConfig.gamma = atoi(val);
688                         return 1;
689                 case MA_OPT3_BLACKLVL:
690                         if (strcasecmp(var, "Black level") != 0) return 0;
691                         currentConfig.gamma2 = atoi(val);
692                         return 1;
693                 case MA_OPT3_VSYNC:
694                         if (strcasecmp(var, "Wait for vsync") != 0) return 0;
695                         if        (strcasecmp(val, "never") == 0) {
696                                 currentConfig.EmuOpt &= ~0x12000;
697                         } else if (strcasecmp(val, "sometimes") == 0) {
698                                 currentConfig.EmuOpt |=  0x12000;
699                         } else if (strcasecmp(val, "always") == 0) {
700                                 currentConfig.EmuOpt &= ~0x12000;
701                                 currentConfig.EmuOpt |=  0x02000;
702                         } else
703                                 return 0;
704                         return 1;
705
706                 default:
707                         lprintf("unhandled custom_read: %i\n", me->id);
708                         return 0;
709         }
710 }
711
712
713 static unsigned int keys_encountered = 0;
714
715 static int parse_bind_val(const char *val)
716 {
717         int i;
718
719         if (val[0] == 0)
720                 return 0;
721         
722         if (strncasecmp(val, "player", 6) == 0)
723         {
724                 unsigned int player;
725                 player = atoi(val + 6) - 1;
726                 if (player > 1)
727                         return -1;
728
729                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
730                         if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0)
731                                 return me_ctrl_actions[i].mask | (player<<16);
732                 }
733         }
734         for (i = 0; emuctrl_actions[i].name != NULL; i++) {
735                 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0)
736                         return emuctrl_actions[i].mask;
737         }
738
739         return -1;
740 }
741
742 static void keys_parse(const char *key, const char *val, int dev_id)
743 {
744         int binds;
745
746         binds = parse_bind_val(val);
747         if (binds == -1) {
748                 lprintf("config: unhandled action \"%s\"\n", val);
749                 return;
750         }
751
752         in_config_bind_key(dev_id, key, binds);
753 }
754
755 static int get_numvar_num(const char *var)
756 {
757         char *p = NULL;
758         int num;
759         
760         if (var[0] == ' ')
761                 return 0;
762
763         num = strtoul(var, &p, 10);
764         if (*p == 0 || *p == ' ')
765                 return num;
766
767         return -1;
768 }
769
770 /* map dev number in confing to input dev number */
771 static unsigned char input_dev_map[IN_MAX_DEVS];
772
773 static void parse(const char *var, const char *val)
774 {
775         menu_entry *me;
776         int t, i, tlen, tmp, ret = 0;
777
778         if (strcasecmp(var, "LastUsedROM") == 0)
779                 return; /* handled elsewhere */
780
781         if (strcasecmp(var, "Sound Volume") == 0) {
782                 currentConfig.volume = atoi(val);
783                 return;
784         }
785
786         /* input: device name */
787         if (strncasecmp(var, "input", 5) == 0) {
788                 int num = get_numvar_num(var + 5);
789                 if (num >= 0 && num < IN_MAX_DEVS)
790                         input_dev_map[num] = in_config_parse_dev(val);
791                 else
792                         lprintf("config: failed to parse: %s\n", var);
793                 return;
794         }
795
796         // key binds
797         if (strncasecmp(var, "bind", 4) == 0) {
798                 const char *p = var + 4;
799                 int num = get_numvar_num(p);
800                 if (num < 0 || num >= IN_MAX_DEVS) {
801                         lprintf("config: failed to parse: %s\n", var);
802                         return;
803                 }
804
805                 num = input_dev_map[num];
806                 if (num < 0 || num >= IN_MAX_DEVS) {
807                         lprintf("config: invalid device id: %s\n", var);
808                         return;
809                 }
810
811                 while (*p && *p != ' ') p++;
812                 while (*p && *p == ' ') p++;
813                 keys_parse(p, val, num);
814                 return;
815         }
816
817         for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
818         {
819                 me = cfg_opts[t];
820                 tlen = *(cfg_opt_counts[t]);
821                 for (i = 0; i < tlen && ret == 0; i++, me++)
822                 {
823                         if (!me->need_to_save) continue;
824                         if (me->name != NULL) {
825                                 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
826                                 if (me->beh == MB_ONOFF) {
827                                         tmp = atoi(val);
828                                         if (tmp) *(int *)me->var |=  me->mask;
829                                         else     *(int *)me->var &= ~me->mask;
830                                         return;
831                                 } else if (me->beh == MB_RANGE) {
832                                         tmp = atoi(val);
833                                         if (tmp < me->min) tmp = me->min;
834                                         if (tmp > me->max) tmp = me->max;
835                                         *(int *)me->var = tmp;
836                                         return;
837                                 }
838                         }
839                         ret = custom_read(me, var, val);
840                 }
841         }
842         if (!ret) lprintf("config_readsect: unhandled var: \"%s\"\n", var);
843 }
844
845
846 int config_havesect(const char *fname, const char *section)
847 {
848         FILE *f;
849         int ret;
850
851         f = fopen(fname, "r");
852         if (f == NULL) return 0;
853
854         ret = seek_sect(f, section);
855         fclose(f);
856         return ret;
857 }
858
859 int config_readsect(const char *fname, const char *section)
860 {
861         char line[128], *var, *val;
862         FILE *f;
863         int ret;
864
865         f = fopen(fname, "r");
866         if (f == NULL) return -1;
867
868         if (section != NULL)
869         {
870                 ret = seek_sect(f, section);
871                 if (!ret) {
872                         lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
873                         fclose(f);
874                         return -1;
875                 }
876         }
877
878         keys_encountered = 0;
879         memset(input_dev_map, 0xff, sizeof(input_dev_map));
880
881         in_config_start();
882         while (!feof(f))
883         {
884                 ret = config_get_var_val(f, line, sizeof(line), &var, &val);
885                 if (ret ==  0) break;
886                 if (ret == -1) continue;
887
888                 parse(var, val);
889         }
890         in_config_end();
891
892         fclose(f);
893         return 0;
894 }
895
896 #endif // _MSC_VER
897
898 static char *mystrip(char *str)
899 {
900         int i, len;
901
902         len = strlen(str);
903         for (i = 0; i < len; i++)
904                 if (str[i] != ' ') break;
905         if (i > 0) memmove(str, str + i, len - i + 1);
906
907         len = strlen(str);
908         for (i = len - 1; i >= 0; i--)
909                 if (str[i] != ' ') break;
910         str[i+1] = 0;
911
912         return str;
913 }
914
915 /* returns:
916  *  0 - EOF, end
917  *  1 - parsed ok
918  * -1 - failed to parse line
919  */
920 int config_get_var_val(void *file, char *line, int lsize, char **rvar, char **rval)
921 {
922         char *var, *val, *tmp;
923         FILE *f = file;
924         int len, i;
925
926         tmp = fgets(line, lsize, f);
927         if (tmp == NULL) return 0;
928
929         if (line[0] == '[') return 0; // other section
930
931         // strip comments, linefeed, spaces..
932         len = strlen(line);
933         for (i = 0; i < len; i++)
934                 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
935         mystrip(line);
936         len = strlen(line);
937         if (len <= 0) return -1;;
938
939         // get var and val
940         for (i = 0; i < len; i++)
941                 if (line[i] == '=') break;
942         if (i >= len || strchr(&line[i+1], '=') != NULL) {
943                 lprintf("config_readsect: can't parse: %s\n", line);
944                 return -1;
945         }
946         line[i] = 0;
947         var = line;
948         val = &line[i+1];
949         mystrip(var);
950         mystrip(val);
951
952 #ifndef _MSC_VER
953         if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
954                 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);
955                 return -1;;
956         }
957 #endif
958
959         *rvar = var;
960         *rval = val;
961         return 1;
962 }
963