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