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