bugfixes, refactoring
[picodrive.git] / platform / common / config.c
1 /*
2  * Human-readable config file management for PicoDrive
3  * (c)
4  */
5
6 #include <string.h>
7 #include <stdlib.h>
8 #include "config.h"
9 #include "menu.h"
10 #include "emu.h"
11 #include "lprintf.h"
12 #include <Pico/Pico.h>
13
14 extern menu_entry opt_entries[];
15 extern menu_entry opt2_entries[];
16 extern menu_entry cdopt_entries[];
17 extern const int opt_entry_count;
18 extern const int opt2_entry_count;
19 extern const int cdopt_entry_count;
20
21 static menu_entry *cfg_opts[] = { opt_entries, opt2_entries, cdopt_entries };
22 static const int *cfg_opt_counts[] = { &opt_entry_count, &opt2_entry_count, &cdopt_entry_count };
23
24 #define NL "\n"
25
26
27 static char *mystrip(char *str)
28 {
29         int i, len;
30
31         len = strlen(str);
32         for (i = 0; i < len; i++)
33                 if (str[i] != ' ') break;
34         if (i > 0) memmove(str, str + i, len - i + 1);
35
36         len = strlen(str);
37         for (i = len - 1; i >= 0; i--)
38                 if (str[i] != ' ') break;
39         str[i+1] = 0;
40
41         return str;
42 }
43
44
45 static int seek_sect(FILE *f, const char *section)
46 {
47         char line[128], *tmp;
48         int len;
49
50         len = strlen(section);
51         // seek to the section needed
52         while (!feof(f))
53         {
54                 tmp = fgets(line, sizeof(line), f);
55                 if (tmp == NULL) break;
56
57                 if (line[0] != '[') continue; // not section start
58                 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
59                         return 1; // found it
60         }
61
62         return 0;
63 }
64
65
66 static void custom_write(FILE *f, const menu_entry *me, int no_def)
67 {
68         char *str, str24[24];
69
70         switch (me->id)
71         {
72                 case MA_OPT_RENDERER:
73                         if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&0x10) &&
74                                 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
75                         if (PicoOpt&0x10)
76                                 str = "8bit fast";
77                         else if (currentConfig.EmuOpt&0x80)
78                                 str = "16bit accurate";
79                         else
80                                 str = "8bit accurate";
81                         fprintf(f, "Renderer = %s", str);
82                         break;
83
84                 case MA_OPT_SCALING:
85                         if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
86                         switch (currentConfig.scaling) {
87                                 default: str = "OFF"; break;
88                                 case 1:  str = "hw horizontal";     break;
89                                 case 2:  str = "hw horiz. + vert."; break;
90                                 case 3:  str = "sw horizontal";     break;
91                         }
92                         fprintf(f, "Scaling = %s", str);
93                         break;
94                 case MA_OPT_FRAMESKIP:
95                         if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
96                         if (currentConfig.Frameskip < 0)
97                              strcpy(str24, "Auto");
98                         else sprintf(str24, "%i", currentConfig.Frameskip);
99                         fprintf(f, "Frameskip = %s", str24);
100                         break;
101                 case MA_OPT_SOUND_QUALITY:
102                         if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&8) &&
103                                 defaultConfig.s_PsndRate == PsndRate) return;
104                         str = (PicoOpt&0x08)?"stereo":"mono";
105                         fprintf(f, "Sound Quality = %i %s", PsndRate, str);
106                         break;
107                 case MA_OPT_REGION:
108                         if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
109                                 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
110                         fprintf(f, "Region = %s", me_region_name(PicoRegionOverride, PicoAutoRgnOrder));
111                         break;
112                 case MA_OPT_CONFIRM_STATES:
113                         if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
114                         switch ((currentConfig.EmuOpt >> 9) & 5) {
115                                 default: str = "OFF";    break;
116                                 case 1:  str = "writes"; break;
117                                 case 4:  str = "loads";  break;
118                                 case 5:  str = "both";   break;
119                         }
120                         fprintf(f, "Confirm savestate = %s", str);
121                         break;
122                 case MA_OPT_CPU_CLOCKS:
123                         if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
124                         fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
125                         break;
126                 case MA_OPT2_GAMMA:
127                         if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
128                         fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
129                         break;
130                 case MA_OPT2_SQUIDGEHACK:
131                         if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
132                         fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
133                         break;
134                 case MA_CDOPT_READAHEAD:
135                         if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
136                         sprintf(str24, "%i", PicoCDBuffers * 2);
137                         fprintf(f, "ReadAhead buffer = %s", str24);
138                         break;
139
140                 default:
141                         lprintf("unhandled custom_write: %i\n", me->id);
142                         return;
143         }
144         fprintf(f, NL);
145 }
146
147
148 static const char *joyKeyNames[32] =
149 {
150         "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
151         "b5",  "b6",  "b7",  "b8",  "b9",  "b10", "b11", "b12",
152         "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
153         "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
154 };
155
156 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
157                 const int def_binds[32], const char *names[32], int no_defaults)
158 {
159         int t, i;
160         char act[48];
161
162         for (t = 0; t < 32; t++)
163         {
164                 act[0] = act[31] = 0;
165                 if (no_defaults && binds[t] == def_binds[t])
166                         continue;
167                 if (strcmp(names[t], "???") == 0) continue;
168 #ifdef __GP2X__
169                 if (strcmp(names[t], "SELECT") == 0) continue;
170 #endif
171                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
172                         if (me_ctrl_actions[i].mask & binds[t]) {
173                                 strncpy(act, me_ctrl_actions[i].name, 31);
174                                 fprintf(fn, "%s %s = player%i %s" NL, bind_str, names[t],
175                                         ((binds[t]>>16)&1)+1, mystrip(act));
176                         }
177                 }
178
179                 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
180                         if (emuctrl_actions[i].mask & binds[t]) {
181                                 strncpy(act, emuctrl_actions[i].name, 31);
182                                 fprintf(fn, "%s %s = %s" NL, bind_str, names[t], mystrip(act));
183                         }
184                 }
185         }
186 }
187
188
189 static int default_var(const menu_entry *me)
190 {
191         switch (me->id)
192         {
193                 case MA_OPT_ACC_TIMING:
194                 case MA_OPT_ACC_SPRITES:
195                 case MA_OPT_ARM940_SOUND:
196                 case MA_OPT_6BUTTON_PAD:
197                 case MA_OPT2_ENABLE_Z80:
198                 case MA_OPT2_ENABLE_YM2612:
199                 case MA_OPT2_ENABLE_SN76496:
200                 case MA_OPT2_SVP_DYNAREC:
201                 case MA_CDOPT_CDDA:
202                 case MA_CDOPT_PCM:
203                 case MA_CDOPT_SAVERAM:
204                 case MA_CDOPT_SCALEROT_CHIP:
205                 case MA_CDOPT_BETTER_SYNC:
206                         return defaultConfig.s_PicoOpt;
207
208                 case MA_OPT_SHOW_FPS:
209                 case MA_OPT_ENABLE_SOUND:
210                 case MA_OPT_SRAM_STATES:
211                 case MA_OPT2_A_SN_GAMMA:
212                 case MA_OPT2_VSYNC:
213                 case MA_OPT2_GZIP_STATES:
214                 case MA_OPT2_NO_LAST_ROM:
215                 case MA_OPT2_RAMTIMINGS:
216                 case MA_CDOPT_LEDS:
217                         return defaultConfig.EmuOpt;
218
219                 case MA_OPT_SAVE_SLOT:
220                 default:
221                         return 0;
222         }
223 }
224
225 int config_writesect(const char *fname, const char *section)
226 {
227         FILE *fo = NULL, *fn = NULL; // old and new
228         int no_defaults = 0; // avoid saving defaults
229         menu_entry *me;
230         int t, i, tlen, ret;
231         char line[128], *tmp;
232
233         if (section != NULL)
234         {
235                 no_defaults = 1;
236
237                 fo = fopen(fname, "r");
238                 if (fo == NULL) {
239                         fn = fopen(fname, "w");
240                         goto write;
241                 }
242
243                 ret = seek_sect(fo, section);
244                 if (!ret) {
245                         // sect not found, we can simply append
246                         fclose(fo); fo = NULL;
247                         fn = fopen(fname, "a");
248                         goto write;
249                 }
250
251                 // use 2 files..
252                 fclose(fo);
253                 rename(fname, "tmp.cfg");
254                 fo = fopen("tmp.cfg", "r");
255                 fn = fopen(fname, "w");
256                 if (fo == NULL || fn == NULL) goto write;
257
258                 // copy everything until sect
259                 tlen = strlen(section);
260                 while (!feof(fo))
261                 {
262                         tmp = fgets(line, sizeof(line), fo);
263                         if (tmp == NULL) break;
264
265                         if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
266                                 break;
267                         fputs(line, fn);
268                 }
269
270                 // now skip to next sect
271                 while (!feof(fo))
272                 {
273                         tmp = fgets(line, sizeof(line), fo);
274                         if (tmp == NULL) break;
275                         if (line[0] == '[') {
276                                 fseek(fo, -strlen(line), SEEK_CUR);
277                                 break;
278                         }
279                 }
280                 if (feof(fo))
281                 {
282                         fclose(fo); fo = NULL;
283                         remove("tmp.cfg");
284                 }
285         }
286         else
287         {
288                 fn = fopen(fname, "w");
289         }
290
291 write:
292         if (fn == NULL) {
293                 if (fo) fclose(fo);
294                 return -1;
295         }
296         if (section != NULL)
297                 fprintf(fn, "[%s]" NL, section);
298
299         for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
300         {
301                 me = cfg_opts[t];
302                 tlen = *(cfg_opt_counts[t]);
303                 for (i = 0; i < tlen; i++, me++)
304                 {
305                         if (!me->need_to_save) continue;
306                         if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
307                                 custom_write(fn, me, no_defaults);
308                         else if (me->beh == MB_ONOFF) {
309                                 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
310                                         fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
311                         } else if (me->beh == MB_RANGE) {
312                                 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
313                                         fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
314                         }
315                 }
316         }
317
318         // save key config
319         keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, no_defaults);
320         keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 1);
321         keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 1);
322         keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 1);
323         keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 1);
324
325         fprintf(fn, NL);
326
327         if (fo != NULL)
328         {
329                 // copy whatever is left
330                 while (!feof(fo))
331                 {
332                         tmp = fgets(line, sizeof(line), fo);
333                         if (tmp == NULL) break;
334
335                         fputs(line, fn);
336                 }
337                 fclose(fo);
338                 remove("tmp.cfg");
339         }
340
341         fclose(fn);
342         return 0;
343 }
344
345
346 int config_writelrom(const char *fname)
347 {
348         char line[128], *tmp, *optr = NULL;
349         char *old_data = NULL;
350         int size;
351         FILE *f;
352
353         if (strlen(lastRomFile) == 0) return 0;
354
355         f = fopen(fname, "r");
356         if (f != NULL)
357         {
358                 fseek(f, 0, SEEK_END);
359                 size = ftell(f);
360                 fseek(f, 0, SEEK_SET);
361                 old_data = malloc(size + size/8);
362                 if (old_data != NULL)
363                 {
364                         optr = old_data;
365                         while (!feof(f))
366                         {
367                                 tmp = fgets(line, sizeof(line), f);
368                                 if (tmp == NULL) break;
369                                 mystrip(line);
370                                 if (strncasecmp(line, "LastUsedROM", 11) == 0)
371                                         continue;
372                                 sprintf(optr, "%s", line);
373                                 optr += strlen(optr);
374                         }
375                 }
376                 fclose(f);
377         }
378
379         f = fopen(fname, "w");
380         if (f == NULL) return -1;
381
382         if (old_data != NULL) {
383                 fwrite(old_data, 1, optr - old_data, f);
384                 free(old_data);
385         }
386         fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
387         fclose(f);
388         return 0;
389 }
390
391 /* --------------------------------------------------------------------------*/
392
393 int config_readlrom(const char *fname)
394 {
395         char line[128], *tmp;
396         int i, len, ret = -1;
397         FILE *f;
398
399         f = fopen(fname, "r");
400         if (f == NULL) return -1;
401
402         // seek to the section needed
403         while (!feof(f))
404         {
405                 tmp = fgets(line, sizeof(line), f);
406                 if (tmp == NULL) break;
407
408                 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
409                 len = strlen(line);
410                 for (i = 0; i < len; i++)
411                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
412                 tmp = strchr(line, '=');
413                 if (tmp == NULL) break;
414                 tmp++;
415                 mystrip(tmp);
416
417                 len = sizeof(lastRomFile);
418                 strncpy(lastRomFile, tmp, len);
419                 lastRomFile[len-1] = 0;
420                 ret = 0;
421                 break;
422         }
423         fclose(f);
424         return ret;
425 }
426
427
428 static int custom_read(menu_entry *me, const char *var, const char *val)
429 {
430         char *tmp;
431         int tmpi;
432
433         switch (me->id)
434         {
435                 case MA_OPT_RENDERER:
436                         if (strcasecmp(var, "Renderer") != 0) return 0;
437                         if      (strcasecmp(val, "8bit fast") == 0) {
438                                 PicoOpt |=  0x10;
439                         }
440                         else if (strcasecmp(val, "16bit accurate") == 0) {
441                                 PicoOpt &= ~0x10;
442                                 currentConfig.EmuOpt |=  0x80;
443                         }
444                         else if (strcasecmp(val, "8bit accurate") == 0) {
445                                 PicoOpt &= ~0x10;
446                                 currentConfig.EmuOpt &= ~0x80;
447                         }
448                         else
449                                 return 0;
450                         return 1;
451
452                 case MA_OPT_SCALING:
453                         if (strcasecmp(var, "Scaling") != 0) return 0;
454                         if        (strcasecmp(val, "OFF") == 0) {
455                                 currentConfig.scaling = 0;
456                         } else if (strcasecmp(val, "hw horizontal") == 0) {
457                                 currentConfig.scaling = 1;
458                         } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
459                                 currentConfig.scaling = 2;
460                         } else if (strcasecmp(val, "sw horizontal") == 0) {
461                                 currentConfig.scaling = 3;
462                         } else
463                                 return 0;
464                         return 1;
465
466                 case MA_OPT_FRAMESKIP:
467                         if (strcasecmp(var, "Frameskip") != 0) return 0;
468                         if (strcasecmp(val, "Auto") == 0)
469                              currentConfig.Frameskip = -1;
470                         else currentConfig.Frameskip = atoi(val);
471                         return 1;
472
473                 case MA_OPT_SOUND_QUALITY:
474                         if (strcasecmp(var, "Sound Quality") != 0) return 0;
475                         PsndRate = strtoul(val, &tmp, 10);
476                         if (PsndRate < 8000 || PsndRate > 44100)
477                                 PsndRate = 22050;
478                         while (*tmp == ' ') tmp++;
479                         if        (strcasecmp(tmp, "stereo") == 0) {
480                                 PicoOpt |=  8;
481                         } else if (strcasecmp(tmp, "mono") == 0) {
482                                 PicoOpt &= ~8;
483                         } else
484                                 return 0;
485                         return 1;
486
487                 case MA_OPT_REGION:
488                         if (strcasecmp(var, "Region") != 0) return 0;
489                         if       (strncasecmp(val, "Auto: ", 6) == 0)
490                         {
491                                 const char *p = val + 5, *end = val + strlen(val);
492                                 int i;
493                                 PicoRegionOverride = PicoAutoRgnOrder = 0;
494                                 for (i = 0; p < end && i < 3; i++)
495                                 {
496                                         while (*p == ' ') p++;
497                                         if        (p[0] == 'J' && p[1] == 'P') {
498                                                 PicoAutoRgnOrder |= 1 << (i*4);
499                                         } else if (p[0] == 'U' && p[1] == 'S') {
500                                                 PicoAutoRgnOrder |= 4 << (i*4);
501                                         } else if (p[0] == 'E' && p[1] == 'U') {
502                                                 PicoAutoRgnOrder |= 8 << (i*4);
503                                         }
504                                         while (*p != ' ' && *p != 0) p++;
505                                         if (*p == 0) break;
506                                 }
507                         }
508                         else   if (strcasecmp(val, "Auto") == 0) {
509                                 PicoRegionOverride = 0;
510                         } else if (strcasecmp(val, "Japan NTSC") == 0) {
511                                 PicoRegionOverride = 1;
512                         } else if (strcasecmp(val, "Japan PAL") == 0) {
513                                 PicoRegionOverride = 2;
514                         } else if (strcasecmp(val, "USA") == 0) {
515                                 PicoRegionOverride = 4;
516                         } else if (strcasecmp(val, "Europe") == 0) {
517                                 PicoRegionOverride = 8;
518                         } else
519                                 return 0;
520                         return 1;
521
522                 case MA_OPT_CONFIRM_STATES:
523                         if (strcasecmp(var, "Confirm savestate") != 0) return 0;
524                         if        (strcasecmp(val, "OFF") == 0) {
525                                 currentConfig.EmuOpt &= ~(5<<9);
526                         } else if (strcasecmp(val, "writes") == 0) {
527                                 currentConfig.EmuOpt &= ~(5<<9);
528                                 currentConfig.EmuOpt |=   1<<9;
529                         } else if (strcasecmp(val, "loads") == 0) {
530                                 currentConfig.EmuOpt &= ~(5<<9);
531                                 currentConfig.EmuOpt |=   4<<9;
532                         } else if (strcasecmp(val, "both") == 0) {
533                                 currentConfig.EmuOpt &= ~(5<<9);
534                                 currentConfig.EmuOpt |=   5<<9;
535                         } else
536                                 return 0;
537                         return 1;
538
539                 case MA_OPT_CPU_CLOCKS:
540                         if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
541                         currentConfig.CPUclock = atoi(val);
542                         return 1;
543
544                 case MA_OPT2_GAMMA:
545                         if (strcasecmp(var, "Gamma correction") != 0) return 0;
546                         currentConfig.gamma = (int) (atof(val) * 100.0);
547                         return 1;
548
549                 case MA_OPT2_SQUIDGEHACK:
550                         if (strcasecmp(var, "Squidgehack") != 0) return 0;
551                         tmpi = atoi(val);
552                         if (tmpi) *(int *)me->var |=  me->mask;
553                         else      *(int *)me->var &= ~me->mask;
554                         return 1;
555
556                 case MA_CDOPT_READAHEAD:
557                         if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
558                         PicoCDBuffers = atoi(val) / 2;
559                         return 1;
560
561                 default:
562                         lprintf("unhandled custom_read: %i\n", me->id);
563                         return 0;
564         }
565 }
566
567
568 static unsigned int keys_encountered = 0;
569
570 static void keys_parse(const char *var, const char *val, int binds[32], const char *names[32])
571 {
572         int t, i;
573         unsigned int player;
574
575         for (t = 0; t < 32; t++)
576         {
577                 if (strcmp(names[t], var) == 0) break;
578         }
579         if (t == 32) {
580                 lprintf("unhandled bind \"%s\"\n", var);
581                 return;
582         }
583
584         if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
585                 binds[t] = 0;
586                 keys_encountered |= 1<<t;
587         }
588         if (val[0] == 0)
589                 return;
590         if (strncasecmp(val, "player", 6) == 0)
591         {
592                 player = atoi(val + 6) - 1;
593                 if (player > 1) goto fail;
594                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
595                         if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
596                                 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
597                                 return;
598                         }
599                 }
600         }
601         for (i = 0; emuctrl_actions[i].name != NULL; i++) {
602                 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
603                         binds[t] |= emuctrl_actions[i].mask;
604                         return;
605                 }
606         }
607
608 fail:
609         lprintf("unhandled action \"%s\"\n", val);
610         return;
611 }
612
613
614 #define try_joy_parse(num) { \
615         if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
616                 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
617                 return; \
618         } \
619 }
620
621 static void parse(const char *var, const char *val)
622 {
623         menu_entry *me;
624         int t, i, tlen, tmp, ret = 0;
625
626         if (strcasecmp(var, "LastUsedROM") == 0)
627                 return; /* handled elsewhere */
628
629         // key binds
630         if (strncasecmp(var, "bind ", 5) == 0) {
631                 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
632                 return;
633         }
634         try_joy_parse(0)
635         try_joy_parse(1)
636         try_joy_parse(2)
637         try_joy_parse(3)
638
639         for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
640         {
641                 me = cfg_opts[t];
642                 tlen = *(cfg_opt_counts[t]);
643                 for (i = 0; i < tlen && ret == 0; i++, me++)
644                 {
645                         if (!me->need_to_save) continue;
646                         if (me->name != NULL) {
647                                 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
648                                 if (me->beh == MB_ONOFF) {
649                                         tmp = atoi(val);
650                                         if (tmp) *(int *)me->var |=  me->mask;
651                                         else     *(int *)me->var &= ~me->mask;
652                                         return;
653                                 } else if (me->beh == MB_RANGE) {
654                                         tmp = atoi(val);
655                                         if (tmp < me->min) tmp = me->min;
656                                         if (tmp > me->max) tmp = me->max;
657                                         *(int *)me->var = tmp;
658                                         return;
659                                 }
660                         }
661                         ret = custom_read(me, var, val);
662                 }
663         }
664         if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
665 }
666
667
668 int config_havesect(const char *fname, const char *section)
669 {
670         FILE *f;
671         int ret;
672
673         f = fopen(fname, "r");
674         if (f == NULL) return 0;
675
676         ret = seek_sect(f, section);
677         fclose(f);
678         return ret;
679 }
680
681
682 int config_readsect(const char *fname, const char *section)
683 {
684         char line[128], *var, *val, *tmp;
685         int len, i, ret;
686         FILE *f;
687
688         f = fopen(fname, "r");
689         if (f == NULL) return 0;
690
691         if (section != NULL)
692         {
693                 ret = seek_sect(f, section);
694                 if (!ret) {
695                         lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
696                         fclose(f);
697                         return -1;
698                 }
699         }
700
701         keys_encountered = 0;
702
703         while (!feof(f))
704         {
705                 tmp = fgets(line, sizeof(line), f);
706                 if (tmp == NULL) break;
707
708                 if (line[0] == '[') break; // other section
709
710                 // strip comments, linefeed, spaces..
711                 len = strlen(line);
712                 for (i = 0; i < len; i++)
713                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
714                 mystrip(line);
715                 len = strlen(line);
716                 if (len <= 0) continue;
717
718                 // get var and val
719                 for (i = 0; i < len; i++)
720                         if (line[i] == '=') break;
721                 if (i >= len || strchr(&line[i+1], '=') != NULL) {
722                         lprintf("config_readsect: can't parse: %s\n", line);
723                         continue;
724                 }
725                 line[i] = 0;
726                 var = line;
727                 val = &line[i+1];
728                 mystrip(var);
729                 mystrip(val);
730                 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
731                         lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);
732                         continue;
733                 }
734
735                 parse(var, val);
736         }
737
738         fclose(f);
739         return 0;
740 }
741