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