work on 'vblank on line start' problem, var changes, mask defines
[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 "\r\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)&POPT_ALT_RENDERER) &&
74                                 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
75                         if (PicoOpt&POPT_ALT_RENDERER)
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)&POPT_EN_STEREO) &&
103                                 defaultConfig.s_PsndRate == PsndRate) return;
104                         str = (PicoOpt&POPT_EN_STEREO)?"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         if (section == NULL)
326                 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
327
328         fprintf(fn, NL);
329
330         if (fo != NULL)
331         {
332                 // copy whatever is left
333                 while (!feof(fo))
334                 {
335                         tmp = fgets(line, sizeof(line), fo);
336                         if (tmp == NULL) break;
337
338                         fputs(line, fn);
339                 }
340                 fclose(fo);
341                 remove("tmp.cfg");
342         }
343
344         fclose(fn);
345         return 0;
346 }
347
348
349 int config_writelrom(const char *fname)
350 {
351         char line[128], *tmp, *optr = NULL;
352         char *old_data = NULL;
353         int size;
354         FILE *f;
355
356         if (strlen(lastRomFile) == 0) return -1;
357
358         f = fopen(fname, "r");
359         if (f != NULL)
360         {
361                 fseek(f, 0, SEEK_END);
362                 size = ftell(f);
363                 fseek(f, 0, SEEK_SET);
364                 old_data = malloc(size + size/8);
365                 if (old_data != NULL)
366                 {
367                         optr = old_data;
368                         while (!feof(f))
369                         {
370                                 tmp = fgets(line, sizeof(line), f);
371                                 if (tmp == NULL) break;
372                                 mystrip(line);
373                                 if (strncasecmp(line, "LastUsedROM", 11) == 0)
374                                         continue;
375                                 sprintf(optr, "%s", line);
376                                 optr += strlen(optr);
377                         }
378                 }
379                 fclose(f);
380         }
381
382         f = fopen(fname, "w");
383         if (f == NULL) return -1;
384
385         if (old_data != NULL) {
386                 fwrite(old_data, 1, optr - old_data, f);
387                 free(old_data);
388         }
389         fprintf(f, "LastUsedROM = %s" NL, lastRomFile);
390         fclose(f);
391         return 0;
392 }
393
394 /* --------------------------------------------------------------------------*/
395
396 int config_readlrom(const char *fname)
397 {
398         char line[128], *tmp;
399         int i, len, ret = -1;
400         FILE *f;
401
402         f = fopen(fname, "r");
403         if (f == NULL) return -1;
404
405         // seek to the section needed
406         while (!feof(f))
407         {
408                 tmp = fgets(line, sizeof(line), f);
409                 if (tmp == NULL) break;
410
411                 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
412                 len = strlen(line);
413                 for (i = 0; i < len; i++)
414                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
415                 tmp = strchr(line, '=');
416                 if (tmp == NULL) break;
417                 tmp++;
418                 mystrip(tmp);
419
420                 len = sizeof(lastRomFile);
421                 strncpy(lastRomFile, tmp, len);
422                 lastRomFile[len-1] = 0;
423                 ret = 0;
424                 break;
425         }
426         fclose(f);
427         return ret;
428 }
429
430
431 static int custom_read(menu_entry *me, const char *var, const char *val)
432 {
433         char *tmp;
434         int tmpi;
435
436         switch (me->id)
437         {
438                 case MA_OPT_RENDERER:
439                         if (strcasecmp(var, "Renderer") != 0) return 0;
440                         if      (strcasecmp(val, "8bit fast") == 0) {
441                                 PicoOpt |=  POPT_ALT_RENDERER;
442                         }
443                         else if (strcasecmp(val, "16bit accurate") == 0) {
444                                 PicoOpt &= ~POPT_ALT_RENDERER;
445                                 currentConfig.EmuOpt |=  0x80;
446                         }
447                         else if (strcasecmp(val, "8bit accurate") == 0) {
448                                 PicoOpt &= ~POPT_ALT_RENDERER;
449                                 currentConfig.EmuOpt &= ~0x80;
450                         }
451                         else
452                                 return 0;
453                         return 1;
454
455                 case MA_OPT_SCALING:
456                         if (strcasecmp(var, "Scaling") != 0) return 0;
457                         if        (strcasecmp(val, "OFF") == 0) {
458                                 currentConfig.scaling = 0;
459                         } else if (strcasecmp(val, "hw horizontal") == 0) {
460                                 currentConfig.scaling = 1;
461                         } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
462                                 currentConfig.scaling = 2;
463                         } else if (strcasecmp(val, "sw horizontal") == 0) {
464                                 currentConfig.scaling = 3;
465                         } else
466                                 return 0;
467                         return 1;
468
469                 case MA_OPT_FRAMESKIP:
470                         if (strcasecmp(var, "Frameskip") != 0) return 0;
471                         if (strcasecmp(val, "Auto") == 0)
472                              currentConfig.Frameskip = -1;
473                         else currentConfig.Frameskip = atoi(val);
474                         return 1;
475
476                 case MA_OPT_SOUND_QUALITY:
477                         if (strcasecmp(var, "Sound Quality") != 0) return 0;
478                         PsndRate = strtoul(val, &tmp, 10);
479                         if (PsndRate < 8000 || PsndRate > 44100)
480                                 PsndRate = 22050;
481                         while (*tmp == ' ') tmp++;
482                         if        (strcasecmp(tmp, "stereo") == 0) {
483                                 PicoOpt |=  POPT_EN_STEREO;
484                         } else if (strcasecmp(tmp, "mono") == 0) {
485                                 PicoOpt &= ~POPT_EN_STEREO;
486                         } else
487                                 return 0;
488                         return 1;
489
490                 case MA_OPT_REGION:
491                         if (strcasecmp(var, "Region") != 0) return 0;
492                         if       (strncasecmp(val, "Auto: ", 6) == 0)
493                         {
494                                 const char *p = val + 5, *end = val + strlen(val);
495                                 int i;
496                                 PicoRegionOverride = PicoAutoRgnOrder = 0;
497                                 for (i = 0; p < end && i < 3; i++)
498                                 {
499                                         while (*p == ' ') p++;
500                                         if        (p[0] == 'J' && p[1] == 'P') {
501                                                 PicoAutoRgnOrder |= 1 << (i*4);
502                                         } else if (p[0] == 'U' && p[1] == 'S') {
503                                                 PicoAutoRgnOrder |= 4 << (i*4);
504                                         } else if (p[0] == 'E' && p[1] == 'U') {
505                                                 PicoAutoRgnOrder |= 8 << (i*4);
506                                         }
507                                         while (*p != ' ' && *p != 0) p++;
508                                         if (*p == 0) break;
509                                 }
510                         }
511                         else   if (strcasecmp(val, "Auto") == 0) {
512                                 PicoRegionOverride = 0;
513                         } else if (strcasecmp(val, "Japan NTSC") == 0) {
514                                 PicoRegionOverride = 1;
515                         } else if (strcasecmp(val, "Japan PAL") == 0) {
516                                 PicoRegionOverride = 2;
517                         } else if (strcasecmp(val, "USA") == 0) {
518                                 PicoRegionOverride = 4;
519                         } else if (strcasecmp(val, "Europe") == 0) {
520                                 PicoRegionOverride = 8;
521                         } else
522                                 return 0;
523                         return 1;
524
525                 case MA_OPT_CONFIRM_STATES:
526                         if (strcasecmp(var, "Confirm savestate") != 0) return 0;
527                         if        (strcasecmp(val, "OFF") == 0) {
528                                 currentConfig.EmuOpt &= ~(5<<9);
529                         } else if (strcasecmp(val, "writes") == 0) {
530                                 currentConfig.EmuOpt &= ~(5<<9);
531                                 currentConfig.EmuOpt |=   1<<9;
532                         } else if (strcasecmp(val, "loads") == 0) {
533                                 currentConfig.EmuOpt &= ~(5<<9);
534                                 currentConfig.EmuOpt |=   4<<9;
535                         } else if (strcasecmp(val, "both") == 0) {
536                                 currentConfig.EmuOpt &= ~(5<<9);
537                                 currentConfig.EmuOpt |=   5<<9;
538                         } else
539                                 return 0;
540                         return 1;
541
542                 case MA_OPT_CPU_CLOCKS:
543                         if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
544                         currentConfig.CPUclock = atoi(val);
545                         return 1;
546
547                 case MA_OPT2_GAMMA:
548                         if (strcasecmp(var, "Gamma correction") != 0) return 0;
549                         currentConfig.gamma = (int) (atof(val) * 100.0);
550                         return 1;
551
552                 case MA_OPT2_SQUIDGEHACK:
553                         if (strcasecmp(var, "Squidgehack") != 0) return 0;
554                         tmpi = atoi(val);
555                         if (tmpi) *(int *)me->var |=  me->mask;
556                         else      *(int *)me->var &= ~me->mask;
557                         return 1;
558
559                 case MA_CDOPT_READAHEAD:
560                         if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
561                         PicoCDBuffers = atoi(val) / 2;
562                         return 1;
563
564                 default:
565                         lprintf("unhandled custom_read: %i\n", me->id);
566                         return 0;
567         }
568 }
569
570
571 static unsigned int keys_encountered = 0;
572
573 static void keys_parse(const char *var, const char *val, int binds[32], const char *names[32])
574 {
575         int t, i;
576         unsigned int player;
577
578         for (t = 0; t < 32; t++)
579         {
580                 if (strcmp(names[t], var) == 0) break;
581         }
582         if (t == 32) {
583                 lprintf("unhandled bind \"%s\"\n", var);
584                 return;
585         }
586
587         if (binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) { // hack
588                 binds[t] = 0;
589                 keys_encountered |= 1<<t;
590         }
591         if (val[0] == 0)
592                 return;
593         if (strncasecmp(val, "player", 6) == 0)
594         {
595                 player = atoi(val + 6) - 1;
596                 if (player > 1) goto fail;
597                 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
598                         if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
599                                 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
600                                 return;
601                         }
602                 }
603         }
604         for (i = 0; emuctrl_actions[i].name != NULL; i++) {
605                 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
606                         binds[t] |= emuctrl_actions[i].mask;
607                         return;
608                 }
609         }
610
611 fail:
612         lprintf("unhandled action \"%s\"\n", val);
613         return;
614 }
615
616
617 #define try_joy_parse(num) { \
618         if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
619                 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames); \
620                 return; \
621         } \
622 }
623
624 static void parse(const char *var, const char *val)
625 {
626         menu_entry *me;
627         int t, i, tlen, tmp, ret = 0;
628
629         if (strcasecmp(var, "LastUsedROM") == 0)
630                 return; /* handled elsewhere */
631
632         if (strcasecmp(var, "Sound Volume") == 0) {
633                 currentConfig.volume = atoi(val);
634                 return;
635         }
636
637         // key binds
638         if (strncasecmp(var, "bind ", 5) == 0) {
639                 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames);
640                 return;
641         }
642         try_joy_parse(0)
643         try_joy_parse(1)
644         try_joy_parse(2)
645         try_joy_parse(3)
646
647         for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
648         {
649                 me = cfg_opts[t];
650                 tlen = *(cfg_opt_counts[t]);
651                 for (i = 0; i < tlen && ret == 0; i++, me++)
652                 {
653                         if (!me->need_to_save) continue;
654                         if (me->name != NULL) {
655                                 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
656                                 if (me->beh == MB_ONOFF) {
657                                         tmp = atoi(val);
658                                         if (tmp) *(int *)me->var |=  me->mask;
659                                         else     *(int *)me->var &= ~me->mask;
660                                         return;
661                                 } else if (me->beh == MB_RANGE) {
662                                         tmp = atoi(val);
663                                         if (tmp < me->min) tmp = me->min;
664                                         if (tmp > me->max) tmp = me->max;
665                                         *(int *)me->var = tmp;
666                                         return;
667                                 }
668                         }
669                         ret = custom_read(me, var, val);
670                 }
671         }
672         if (!ret) lprintf("config_readsect: unhandled var: %s\n", var);
673 }
674
675
676 int config_havesect(const char *fname, const char *section)
677 {
678         FILE *f;
679         int ret;
680
681         f = fopen(fname, "r");
682         if (f == NULL) return 0;
683
684         ret = seek_sect(f, section);
685         fclose(f);
686         return ret;
687 }
688
689
690 int config_readsect(const char *fname, const char *section)
691 {
692         char line[128], *var, *val, *tmp;
693         int len, i, ret;
694         FILE *f;
695
696         f = fopen(fname, "r");
697         if (f == NULL) return -1;
698
699         if (section != NULL)
700         {
701                 ret = seek_sect(f, section);
702                 if (!ret) {
703                         lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
704                         fclose(f);
705                         return -1;
706                 }
707         }
708
709         keys_encountered = 0;
710
711         while (!feof(f))
712         {
713                 tmp = fgets(line, sizeof(line), f);
714                 if (tmp == NULL) break;
715
716                 if (line[0] == '[') break; // other section
717
718                 // strip comments, linefeed, spaces..
719                 len = strlen(line);
720                 for (i = 0; i < len; i++)
721                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
722                 mystrip(line);
723                 len = strlen(line);
724                 if (len <= 0) continue;
725
726                 // get var and val
727                 for (i = 0; i < len; i++)
728                         if (line[i] == '=') break;
729                 if (i >= len || strchr(&line[i+1], '=') != NULL) {
730                         lprintf("config_readsect: can't parse: %s\n", line);
731                         continue;
732                 }
733                 line[i] = 0;
734                 var = line;
735                 val = &line[i+1];
736                 mystrip(var);
737                 mystrip(val);
738                 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
739                         lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);
740                         continue;
741                 }
742
743                 parse(var, val);
744         }
745
746         fclose(f);
747         return 0;
748 }
749