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