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