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