sh2 overclock and logging stuff, menu refactoring
[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, k, i;
57         const int *def_binds;
58
59         key_count = in_get_dev_info(dev_id, IN_INFO_BIND_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; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); 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
272                 if (me->beh == MB_OPT_ONOFF || me->beh == MB_OPT_CUSTONOFF) {
273                         if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
274                                 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
275                 }
276                 else if (me->beh == MB_OPT_RANGE || me->beh == MB_OPT_CUSTRANGE) {
277                         if (!no_defaults || (*(int *)me->var ^ default_var(me)))
278                                 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
279                 }
280                 else if (me->beh == MB_OPT_ENUM && me->data != NULL) {
281                         const char **names = (const char **)me->data;
282                         for (t = 0; names[t] != NULL; t++)
283                                 if (*(int *)me->var == t && (!no_defaults || (*(int *)me->var ^ default_var(me)))) {
284                                         strncpy(line, names[t], sizeof(line));
285                                         goto write_line;
286                                 }
287                 }
288                 else if (me->name != NULL && me->generate_name != NULL) {
289                         if (!no_defaults || !is_cust_val_default(me)) {
290                                 strncpy(line, me->generate_name(0, &dummy), sizeof(line));
291                                 goto write_line;
292                         }
293                 }
294                 else
295                         lprintf("config: unhandled write: %i\n", me->id);
296                 continue;
297
298 write_line:
299                 line[sizeof(line) - 1] = 0;
300                 mystrip(line);
301                 fprintf(fn, "%s = %s" NL, me->name, line);
302         }
303
304         /* input: save device names */
305         for (t = 0; t < IN_MAX_DEVS; t++)
306         {
307                 const int  *binds = in_get_dev_binds(t);
308                 const char *name =  in_get_dev_name(t, 0, 0);
309                 if (binds == NULL || name == NULL)
310                         continue;
311
312                 fprintf(fn, "input%d = %s" NL, t, name);
313         }
314
315         /* input: save binds */
316         for (t = 0; t < IN_MAX_DEVS; t++)
317         {
318                 const int *binds = in_get_dev_binds(t);
319                 const char *name = in_get_dev_name(t, 0, 0);
320                 char strbind[16];
321                 int count;
322
323                 if (binds == NULL || name == NULL)
324                         continue;
325
326                 sprintf(strbind, "bind%d", t);
327                 if (t == 0) strbind[4] = 0;
328
329                 count = in_get_dev_info(t, IN_INFO_BIND_COUNT);
330                 keys_write(fn, strbind, t, binds, no_defaults);
331         }
332
333 #ifndef PSP
334         if (section == NULL)
335                 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
336 #endif
337
338         fprintf(fn, NL);
339
340         if (fo != NULL)
341         {
342                 // copy whatever is left
343                 while (!feof(fo))
344                 {
345                         tmp = fgets(line, sizeof(line), fo);
346                         if (tmp == NULL) break;
347
348                         fputs(line, fn);
349                 }
350                 fclose(fo);
351                 remove("tmp.cfg");
352         }
353
354         fclose(fn);
355         return 0;
356 }
357
358
359 int config_writelrom(const char *fname)
360 {
361         char line[128], *tmp, *optr = NULL;
362         char *old_data = NULL;
363         int size;
364         FILE *f;
365
366         if (strlen(rom_fname_loaded) == 0) return -1;
367
368         f = fopen(fname, "r");
369         if (f != NULL)
370         {
371                 fseek(f, 0, SEEK_END);
372                 size = ftell(f);
373                 fseek(f, 0, SEEK_SET);
374                 old_data = malloc(size + size/8);
375                 if (old_data != NULL)
376                 {
377                         optr = old_data;
378                         while (!feof(f))
379                         {
380                                 tmp = fgets(line, sizeof(line), f);
381                                 if (tmp == NULL) break;
382                                 mystrip(line);
383                                 if (strncasecmp(line, "LastUsedROM", 11) == 0)
384                                         continue;
385                                 sprintf(optr, "%s", line);
386                                 optr += strlen(optr);
387                         }
388                 }
389                 fclose(f);
390         }
391
392         f = fopen(fname, "w");
393         if (f == NULL) return -1;
394
395         if (old_data != NULL) {
396                 fwrite(old_data, 1, optr - old_data, f);
397                 free(old_data);
398         }
399         fprintf(f, "LastUsedROM = %s" NL, rom_fname_loaded);
400         fclose(f);
401         return 0;
402 }
403
404 /* --------------------------------------------------------------------------*/
405
406 int config_readlrom(const char *fname)
407 {
408         char line[128], *tmp;
409         int i, len, ret = -1;
410         FILE *f;
411
412         f = fopen(fname, "r");
413         if (f == NULL) return -1;
414
415         // seek to the section needed
416         while (!feof(f))
417         {
418                 tmp = fgets(line, sizeof(line), f);
419                 if (tmp == NULL) break;
420
421                 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
422                 len = strlen(line);
423                 for (i = 0; i < len; i++)
424                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
425                 tmp = strchr(line, '=');
426                 if (tmp == NULL) break;
427                 tmp++;
428                 mystrip(tmp);
429
430                 len = sizeof(rom_fname_loaded);
431                 strncpy(rom_fname_loaded, tmp, len);
432                 rom_fname_loaded[len-1] = 0;
433                 ret = 0;
434                 break;
435         }
436         fclose(f);
437         return ret;
438 }
439
440
441 static int custom_read(menu_entry *me, const char *var, const char *val)
442 {
443         char *tmp;
444
445         switch (me->id)
446         {
447                 case MA_OPT_FRAMESKIP:
448                         if (strcasecmp(var, "Frameskip") != 0) return 0;
449                         if (strcasecmp(val, "Auto") == 0)
450                              currentConfig.Frameskip = -1;
451                         else currentConfig.Frameskip = atoi(val);
452                         return 1;
453
454                 case MA_OPT_SOUND_QUALITY:
455                         if (strcasecmp(var, "Sound Quality") != 0) return 0;
456                         PsndRate = strtoul(val, &tmp, 10);
457                         if (PsndRate < 8000 || PsndRate > 44100)
458                                 PsndRate = 22050;
459                         if (*tmp == 'H' || *tmp == 'h') tmp++;
460                         if (*tmp == 'Z' || *tmp == 'z') tmp++;
461                         while (*tmp == ' ') tmp++;
462                         if        (strcasecmp(tmp, "stereo") == 0) {
463                                 PicoOpt |=  POPT_EN_STEREO;
464                         } else if (strcasecmp(tmp, "mono") == 0) {
465                                 PicoOpt &= ~POPT_EN_STEREO;
466                         } else
467                                 return 0;
468                         return 1;
469
470                 case MA_OPT_REGION:
471                         if (strcasecmp(var, "Region") != 0) return 0;
472                         if       (strncasecmp(val, "Auto: ", 6) == 0)
473                         {
474                                 const char *p = val + 5, *end = val + strlen(val);
475                                 int i;
476                                 PicoRegionOverride = PicoAutoRgnOrder = 0;
477                                 for (i = 0; p < end && i < 3; i++)
478                                 {
479                                         while (*p == ' ') p++;
480                                         if        (p[0] == 'J' && p[1] == 'P') {
481                                                 PicoAutoRgnOrder |= 1 << (i*4);
482                                         } else if (p[0] == 'U' && p[1] == 'S') {
483                                                 PicoAutoRgnOrder |= 4 << (i*4);
484                                         } else if (p[0] == 'E' && p[1] == 'U') {
485                                                 PicoAutoRgnOrder |= 8 << (i*4);
486                                         }
487                                         while (*p != ' ' && *p != 0) p++;
488                                         if (*p == 0) break;
489                                 }
490                         }
491                         else   if (strcasecmp(val, "Auto") == 0) {
492                                 PicoRegionOverride = 0;
493                         } else if (strcasecmp(val, "Japan NTSC") == 0) {
494                                 PicoRegionOverride = 1;
495                         } else if (strcasecmp(val, "Japan PAL") == 0) {
496                                 PicoRegionOverride = 2;
497                         } else if (strcasecmp(val, "USA") == 0) {
498                                 PicoRegionOverride = 4;
499                         } else if (strcasecmp(val, "Europe") == 0) {
500                                 PicoRegionOverride = 8;
501                         } else
502                                 return 0;
503                         return 1;
504
505                 case MA_OPT2_GAMMA:
506                         if (strcasecmp(var, "Gamma correction") != 0) return 0;
507                         currentConfig.gamma = (int) (atof(val) * 100.0);
508                         return 1;
509
510                 case MA_CDOPT_READAHEAD:
511                         if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
512                         PicoCDBuffers = atoi(val) / 2;
513                         return 1;
514
515                 case MA_32XOPT_MSH2_CYCLES:
516                 case MA_32XOPT_SSH2_CYCLES: {
517                         int *mul = (me->id == MA_32XOPT_MSH2_CYCLES) ? &p32x_msh2_multiplier : &p32x_ssh2_multiplier;
518                         *mul = ((unsigned int)atoi(val) << SH2_MULTI_SHIFT) / 7670;
519                         return 1;
520                 }
521
522                 /* PSP */
523                 case MA_OPT3_SCALE:
524                         if (strcasecmp(var, "Scale factor") != 0) return 0;
525                         currentConfig.scale = atof(val);
526                         return 1;
527                 case MA_OPT3_HSCALE32:
528                         if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
529                         currentConfig.hscale32 = atof(val);
530                         return 1;
531                 case MA_OPT3_HSCALE40:
532                         if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
533                         currentConfig.hscale40 = atof(val);
534                         return 1;
535                 case MA_OPT3_VSYNC:
536                         // XXX: use enum
537                         if (strcasecmp(var, "Wait for vsync") != 0) return 0;
538                         if        (strcasecmp(val, "never") == 0) {
539                                 currentConfig.EmuOpt &= ~0x12000;
540                         } else if (strcasecmp(val, "sometimes") == 0) {
541                                 currentConfig.EmuOpt |=  0x12000;
542                         } else if (strcasecmp(val, "always") == 0) {
543                                 currentConfig.EmuOpt &= ~0x12000;
544                                 currentConfig.EmuOpt |=  0x02000;
545                         } else
546                                 return 0;
547                         return 1;
548
549                 default:
550                         lprintf("unhandled custom_read %i: %s\n", me->id, var);
551                         return 0;
552         }
553 }
554
555
556 static unsigned int keys_encountered = 0;
557
558 static int parse_bind_val(const char *val, int *type)
559 {
560         int i;
561
562         *type = IN_BINDTYPE_NONE;
563         if (val[0] == 0)
564                 return 0;
565         
566         if (strncasecmp(val, "player", 6) == 0)
567         {
568                 int player, shift = 0;
569                 player = atoi(val + 6) - 1;
570
571                 if (player > 1)
572                         return -1;
573                 if (player == 1)
574                         shift = 16;
575
576                 *type = IN_BINDTYPE_PLAYER12;
577                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
578                         if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0)
579                                 return me_ctrl_actions[i].mask << shift;
580                 }
581         }
582         for (i = 0; emuctrl_actions[i].name != NULL; i++) {
583                 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
584                         *type = IN_BINDTYPE_EMU;
585                         return emuctrl_actions[i].mask;
586                 }
587         }
588
589         return -1;
590 }
591
592 static void keys_parse(const char *key, const char *val, int dev_id)
593 {
594         int acts, type;
595
596         acts = parse_bind_val(val, &type);
597         if (acts == -1) {
598                 lprintf("config: unhandled action \"%s\"\n", val);
599                 return;
600         }
601
602         in_config_bind_key(dev_id, key, acts, type);
603 }
604
605 static int get_numvar_num(const char *var)
606 {
607         char *p = NULL;
608         int num;
609         
610         if (var[0] == ' ')
611                 return 0;
612
613         num = strtoul(var, &p, 10);
614         if (*p == 0 || *p == ' ')
615                 return num;
616
617         return -1;
618 }
619
620 /* map dev number in confing to input dev number */
621 static unsigned char input_dev_map[IN_MAX_DEVS];
622
623 static void parse(const char *var, const char *val)
624 {
625         menu_entry *me;
626         int tmp;
627
628         if (strcasecmp(var, "LastUsedROM") == 0)
629                 return; /* handled elsewhere */
630
631         if (strcasecmp(var, "Sound Volume") == 0) {
632                 currentConfig.volume = atoi(val);
633                 return;
634         }
635
636         /* input: device name */
637         if (strncasecmp(var, "input", 5) == 0) {
638                 int num = get_numvar_num(var + 5);
639                 if (num >= 0 && num < IN_MAX_DEVS)
640                         input_dev_map[num] = in_config_parse_dev(val);
641                 else
642                         lprintf("config: failed to parse: %s\n", var);
643                 return;
644         }
645
646         // key binds
647         if (strncasecmp(var, "bind", 4) == 0) {
648                 const char *p = var + 4;
649                 int num = get_numvar_num(p);
650                 if (num < 0 || num >= IN_MAX_DEVS) {
651                         lprintf("config: failed to parse: %s\n", var);
652                         return;
653                 }
654
655                 num = input_dev_map[num];
656                 if (num < 0 || num >= IN_MAX_DEVS) {
657                         lprintf("config: invalid device id: %s\n", var);
658                         return;
659                 }
660
661                 while (*p && *p != ' ') p++;
662                 while (*p && *p == ' ') p++;
663                 keys_parse(p, val, num);
664                 return;
665         }
666
667         for (me = me_list_get_first(); me != NULL; me = me_list_get_next())
668         {
669                 char *p;
670
671                 if (!me->need_to_save)
672                         continue;
673                 if (me->name != NULL && me->name[0] != 0) {
674                         if (strcasecmp(var, me->name) != 0)
675                                 continue; /* surely not this one */
676                         if (me->beh == MB_OPT_ONOFF) {
677                                 tmp = strtol(val, &p, 0);
678                                 if (*p != 0)
679                                         goto bad_val;
680                                 if (tmp) *(int *)me->var |=  me->mask;
681                                 else     *(int *)me->var &= ~me->mask;
682                                 return;
683                         }
684                         else if (me->beh == MB_OPT_RANGE) {
685                                 tmp = strtol(val, &p, 0);
686                                 if (*p != 0)
687                                         goto bad_val;
688                                 if (tmp < me->min) tmp = me->min;
689                                 if (tmp > me->max) tmp = me->max;
690                                 *(int *)me->var = tmp;
691                                 return;
692                         }
693                         else if (me->beh == MB_OPT_ENUM) {
694                                 const char **names, *p1;
695                                 int i;
696
697                                 names = (const char **)me->data;
698                                 if (names == NULL)
699                                         goto bad_val;
700                                 for (i = 0; names[i] != NULL; i++) {
701                                         for (p1 = names[i]; *p1 == ' '; p1++)
702                                                 ;
703                                         if (strcasecmp(p1, val) == 0) {
704                                                 *(int *)me->var = i;
705                                                 return;
706                                         }
707                                 }
708                                 goto bad_val;
709                         }
710                 }
711                 if (!custom_read(me, var, val))
712                         break;
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