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