2 * Human-readable config file management for PicoDrive
15 static char *mystrip(char *str);
21 #include <pico/pico.h>
23 extern menu_entry opt_entries[];
24 extern menu_entry opt2_entries[];
25 extern menu_entry cdopt_entries[];
26 extern menu_entry ctrlopt_entries[];
27 extern const int opt_entry_count;
28 extern const int opt2_entry_count;
29 extern const int cdopt_entry_count;
30 extern const int ctrlopt_entry_count;
32 extern menu_entry opt3_entries[];
33 extern const int opt3_entry_count;
36 static menu_entry *cfg_opts[] =
47 static const int *cfg_opt_counts[] =
61 static int seek_sect(FILE *f, const char *section)
66 len = strlen(section);
67 // seek to the section needed
70 tmp = fgets(line, sizeof(line), f);
71 if (tmp == NULL) break;
73 if (line[0] != '[') continue; // not section start
74 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
82 static void custom_write(FILE *f, const menu_entry *me, int no_def)
89 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_ALT_RENDERER) &&
90 !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
91 if (PicoOpt&POPT_ALT_RENDERER)
97 else if (currentConfig.EmuOpt&0x80)
104 str = "8bit accurate";
105 fprintf(f, "Renderer = %s", str);
109 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
111 switch (currentConfig.scaling) {
112 default: str = "OFF"; break;
113 case 1: str = "hw horizontal"; break;
114 case 2: str = "hw horiz. + vert."; break;
115 case 3: str = "sw horizontal"; break;
117 fprintf(f, "Scaling = %s", str);
120 case MA_OPT_FRAMESKIP:
121 if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
122 if (currentConfig.Frameskip < 0)
123 strcpy(str24, "Auto");
124 else sprintf(str24, "%i", currentConfig.Frameskip);
125 fprintf(f, "Frameskip = %s", str24);
127 case MA_OPT_SOUND_QUALITY:
128 if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_EN_STEREO) &&
129 defaultConfig.s_PsndRate == PsndRate) return;
130 str = (PicoOpt&POPT_EN_STEREO)?"stereo":"mono";
131 fprintf(f, "Sound Quality = %i %s", PsndRate, str);
134 if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
135 defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
136 strncpy(str24, me_region_name(PicoRegionOverride, PicoAutoRgnOrder), 23); str24[23] = 0;
137 fprintf(f, "Region = %s", mystrip(str24));
139 case MA_OPT_CONFIRM_STATES:
140 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
141 switch ((currentConfig.EmuOpt >> 9) & 5) {
142 default: str = "OFF"; break;
143 case 1: str = "writes"; break;
144 case 4: str = "loads"; break;
145 case 5: str = "both"; break;
147 fprintf(f, "Confirm savestate = %s", str);
149 case MA_OPT_CPU_CLOCKS:
150 if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
152 fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
154 fprintf(f, "PSP CPU clock = %i", currentConfig.CPUclock);
158 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
159 fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
161 case MA_OPT2_SQUIDGEHACK:
162 if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return;
163 fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4);
165 case MA_CDOPT_READAHEAD:
166 if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return;
167 sprintf(str24, "%i", PicoCDBuffers * 2);
168 fprintf(f, "ReadAhead buffer = %s", str24);
172 if (no_def && defaultConfig.scale == currentConfig.scale) return;
173 fprintf(f, "Scale factor = %.2f", currentConfig.scale);
175 case MA_OPT3_HSCALE32:
176 if (no_def && defaultConfig.hscale32 == currentConfig.hscale32) return;
177 fprintf(f, "Hor. scale (for low res. games) = %.2f", currentConfig.hscale32);
179 case MA_OPT3_HSCALE40:
180 if (no_def && defaultConfig.hscale40 == currentConfig.hscale40) return;
181 fprintf(f, "Hor. scale (for hi res. games) = %.2f", currentConfig.hscale40);
183 case MA_OPT3_FILTERING:
184 if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
185 fprintf(f, "Bilinear filtering = %i", currentConfig.scaling);
188 if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
189 fprintf(f, "Gamma adjustment = %i", currentConfig.gamma);
191 case MA_OPT3_BLACKLVL:
192 if (no_def && defaultConfig.gamma2 == currentConfig.gamma2) return;
193 fprintf(f, "Black level = %i", currentConfig.gamma2);
196 if (no_def && (defaultConfig.EmuOpt&0x12000) == (currentConfig.gamma2&0x12000)) return;
197 strcpy(str24, "never");
198 if (currentConfig.EmuOpt & 0x2000)
199 strcpy(str24, (currentConfig.EmuOpt & 0x10000) ? "sometimes" : "always");
200 fprintf(f, "Wait for vsync = %s", str24);
204 lprintf("unhandled custom_write: %i\n", me->id);
212 static const char *joyKeyNames[32] =
214 "UP", "DOWN", "LEFT", "RIGHT", "b1", "b2", "b3", "b4",
215 "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12",
216 "b13", "b14", "b15", "b16", "b17", "b19", "b19", "b20",
217 "b21", "b22", "b23", "b24", "b25", "b26", "b27", "b28"
221 static void keys_write(FILE *fn, const char *bind_str, const int binds[32],
222 const int def_binds[32], const char * const names[32], int key_count, int no_defaults)
225 char act[48], name[32];
227 for (t = 0; t < key_count; t++)
229 act[0] = act[31] = 0;
230 if (no_defaults && binds[t] == def_binds[t])
233 if (strcmp(names[t], "SELECT") == 0) continue;
235 if (t >= 32 || names[t] == NULL || strcmp(names[t], "???") == 0) {
236 if ((t >= '0' && t <= '9') || (t >= 'a' && t <= 'z') || (t >= 'A' && t <= 'Z'))
237 sprintf(name, "%c", t);
239 sprintf(name, "\\x%02x", t);
242 strcpy(name, names[t]);
244 if (binds[t] == 0 && def_binds[t] != 0) {
245 fprintf(fn, "%s %s =" NL, bind_str, name); // no binds
249 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
250 if (me_ctrl_actions[i].mask & binds[t]) {
251 strncpy(act, me_ctrl_actions[i].name, 31);
252 fprintf(fn, "%s %s = player%i %s" NL, bind_str, name,
253 ((binds[t]>>16)&1)+1, mystrip(act));
257 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
258 if (emuctrl_actions[i].mask & binds[t]) {
259 strncpy(act, emuctrl_actions[i].name, 31);
260 fprintf(fn, "%s %s = %s" NL, bind_str, name, mystrip(act));
267 static int default_var(const menu_entry *me)
271 case MA_OPT_ACC_TIMING:
272 case MA_OPT_ACC_SPRITES:
273 case MA_OPT_ARM940_SOUND:
274 case MA_OPT_6BUTTON_PAD:
275 case MA_OPT2_ENABLE_Z80:
276 case MA_OPT2_ENABLE_YM2612:
277 case MA_OPT2_ENABLE_SN76496:
278 case MA_OPT2_SVP_DYNAREC:
281 case MA_CDOPT_SAVERAM:
282 case MA_CDOPT_SCALEROT_CHIP:
283 case MA_CDOPT_BETTER_SYNC:
284 return defaultConfig.s_PicoOpt;
286 case MA_OPT_SHOW_FPS:
287 case MA_OPT_ENABLE_SOUND:
288 case MA_OPT_SRAM_STATES:
289 case MA_OPT2_A_SN_GAMMA:
291 case MA_OPT2_GZIP_STATES:
292 case MA_OPT2_NO_LAST_ROM:
293 case MA_OPT2_RAMTIMINGS:
295 return defaultConfig.EmuOpt;
297 case MA_CTRL_TURBO_RATE: return defaultConfig.turbo_rate;
298 case MA_OPT_SCALING: return defaultConfig.scaling;
299 case MA_OPT_ROTATION: return defaultConfig.rotation;
301 case MA_OPT_SAVE_SLOT:
307 int config_writesect(const char *fname, const char *section)
309 FILE *fo = NULL, *fn = NULL; // old and new
310 int no_defaults = 0; // avoid saving defaults
313 char line[128], *tmp;
319 fo = fopen(fname, "r");
321 fn = fopen(fname, "w");
325 ret = seek_sect(fo, section);
327 // sect not found, we can simply append
328 fclose(fo); fo = NULL;
329 fn = fopen(fname, "a");
335 rename(fname, "tmp.cfg");
336 fo = fopen("tmp.cfg", "r");
337 fn = fopen(fname, "w");
338 if (fo == NULL || fn == NULL) goto write;
340 // copy everything until sect
341 tlen = strlen(section);
344 tmp = fgets(line, sizeof(line), fo);
345 if (tmp == NULL) break;
347 if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']')
352 // now skip to next sect
355 tmp = fgets(line, sizeof(line), fo);
356 if (tmp == NULL) break;
357 if (line[0] == '[') {
358 fseek(fo, -strlen(line), SEEK_CUR);
364 fclose(fo); fo = NULL;
370 fn = fopen(fname, "w");
379 fprintf(fn, "[%s]" NL, section);
381 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
384 tlen = *(cfg_opt_counts[t]);
385 for (i = 0; i < tlen; i++, me++)
387 if (!me->need_to_save) continue;
388 if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
389 custom_write(fn, me, no_defaults);
390 else if (me->beh == MB_ONOFF) {
391 if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
392 fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
393 } else if (me->beh == MB_RANGE) {
394 if (!no_defaults || (*(int *)me->var ^ default_var(me)))
395 fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
401 keys_write(fn, "bind", currentConfig.KeyBinds, defaultConfig.KeyBinds, keyNames, PLAT_MAX_KEYS, no_defaults);
403 keys_write(fn, "bind_joy0", currentConfig.JoyBinds[0], defaultConfig.JoyBinds[0], joyKeyNames, 32, 1);
404 keys_write(fn, "bind_joy1", currentConfig.JoyBinds[1], defaultConfig.JoyBinds[1], joyKeyNames, 32, 1);
405 keys_write(fn, "bind_joy2", currentConfig.JoyBinds[2], defaultConfig.JoyBinds[2], joyKeyNames, 32, 1);
406 keys_write(fn, "bind_joy3", currentConfig.JoyBinds[3], defaultConfig.JoyBinds[3], joyKeyNames, 32, 1);
411 fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
418 // copy whatever is left
421 tmp = fgets(line, sizeof(line), fo);
422 if (tmp == NULL) break;
435 int config_writelrom(const char *fname)
437 char line[128], *tmp, *optr = NULL;
438 char *old_data = NULL;
442 if (strlen(loadedRomFName) == 0) return -1;
444 f = fopen(fname, "r");
447 fseek(f, 0, SEEK_END);
449 fseek(f, 0, SEEK_SET);
450 old_data = malloc(size + size/8);
451 if (old_data != NULL)
456 tmp = fgets(line, sizeof(line), f);
457 if (tmp == NULL) break;
459 if (strncasecmp(line, "LastUsedROM", 11) == 0)
461 sprintf(optr, "%s", line);
462 optr += strlen(optr);
468 f = fopen(fname, "w");
469 if (f == NULL) return -1;
471 if (old_data != NULL) {
472 fwrite(old_data, 1, optr - old_data, f);
475 fprintf(f, "LastUsedROM = %s" NL, loadedRomFName);
480 /* --------------------------------------------------------------------------*/
482 int config_readlrom(const char *fname)
484 char line[128], *tmp;
485 int i, len, ret = -1;
488 f = fopen(fname, "r");
489 if (f == NULL) return -1;
491 // seek to the section needed
494 tmp = fgets(line, sizeof(line), f);
495 if (tmp == NULL) break;
497 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
499 for (i = 0; i < len; i++)
500 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
501 tmp = strchr(line, '=');
502 if (tmp == NULL) break;
506 len = sizeof(loadedRomFName);
507 strncpy(loadedRomFName, tmp, len);
508 loadedRomFName[len-1] = 0;
517 static int custom_read(menu_entry *me, const char *var, const char *val)
524 case MA_OPT_RENDERER:
525 if (strcasecmp(var, "Renderer") != 0) return 0;
526 if (strcasecmp(val, "8bit fast") == 0 || strcasecmp(val, "fast") == 0) {
527 PicoOpt |= POPT_ALT_RENDERER;
529 else if (strcasecmp(val, "16bit accurate") == 0 || strcasecmp(val, "accurate") == 0) {
530 PicoOpt &= ~POPT_ALT_RENDERER;
531 currentConfig.EmuOpt |= 0x80;
533 else if (strcasecmp(val, "8bit accurate") == 0) {
534 PicoOpt &= ~POPT_ALT_RENDERER;
535 currentConfig.EmuOpt &= ~0x80;
543 if (strcasecmp(var, "Scaling") != 0) return 0;
544 if (strcasecmp(val, "OFF") == 0) {
545 currentConfig.scaling = 0;
546 } else if (strcasecmp(val, "hw horizontal") == 0) {
547 currentConfig.scaling = 1;
548 } else if (strcasecmp(val, "hw horiz. + vert.") == 0) {
549 currentConfig.scaling = 2;
550 } else if (strcasecmp(val, "sw horizontal") == 0) {
551 currentConfig.scaling = 3;
559 case MA_OPT_FRAMESKIP:
560 if (strcasecmp(var, "Frameskip") != 0) return 0;
561 if (strcasecmp(val, "Auto") == 0)
562 currentConfig.Frameskip = -1;
563 else currentConfig.Frameskip = atoi(val);
566 case MA_OPT_SOUND_QUALITY:
567 if (strcasecmp(var, "Sound Quality") != 0) return 0;
568 PsndRate = strtoul(val, &tmp, 10);
569 if (PsndRate < 8000 || PsndRate > 44100)
571 while (*tmp == ' ') tmp++;
572 if (strcasecmp(tmp, "stereo") == 0) {
573 PicoOpt |= POPT_EN_STEREO;
574 } else if (strcasecmp(tmp, "mono") == 0) {
575 PicoOpt &= ~POPT_EN_STEREO;
581 if (strcasecmp(var, "Region") != 0) return 0;
582 if (strncasecmp(val, "Auto: ", 6) == 0)
584 const char *p = val + 5, *end = val + strlen(val);
586 PicoRegionOverride = PicoAutoRgnOrder = 0;
587 for (i = 0; p < end && i < 3; i++)
589 while (*p == ' ') p++;
590 if (p[0] == 'J' && p[1] == 'P') {
591 PicoAutoRgnOrder |= 1 << (i*4);
592 } else if (p[0] == 'U' && p[1] == 'S') {
593 PicoAutoRgnOrder |= 4 << (i*4);
594 } else if (p[0] == 'E' && p[1] == 'U') {
595 PicoAutoRgnOrder |= 8 << (i*4);
597 while (*p != ' ' && *p != 0) p++;
601 else if (strcasecmp(val, "Auto") == 0) {
602 PicoRegionOverride = 0;
603 } else if (strcasecmp(val, "Japan NTSC") == 0) {
604 PicoRegionOverride = 1;
605 } else if (strcasecmp(val, "Japan PAL") == 0) {
606 PicoRegionOverride = 2;
607 } else if (strcasecmp(val, "USA") == 0) {
608 PicoRegionOverride = 4;
609 } else if (strcasecmp(val, "Europe") == 0) {
610 PicoRegionOverride = 8;
615 case MA_OPT_CONFIRM_STATES:
616 if (strcasecmp(var, "Confirm savestate") != 0) return 0;
617 if (strcasecmp(val, "OFF") == 0) {
618 currentConfig.EmuOpt &= ~(5<<9);
619 } else if (strcasecmp(val, "writes") == 0) {
620 currentConfig.EmuOpt &= ~(5<<9);
621 currentConfig.EmuOpt |= 1<<9;
622 } else if (strcasecmp(val, "loads") == 0) {
623 currentConfig.EmuOpt &= ~(5<<9);
624 currentConfig.EmuOpt |= 4<<9;
625 } else if (strcasecmp(val, "both") == 0) {
626 currentConfig.EmuOpt &= ~(5<<9);
627 currentConfig.EmuOpt |= 5<<9;
632 case MA_OPT_CPU_CLOCKS:
634 if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0;
636 if (strcasecmp(var, "PSP CPU clock") != 0) return 0;
638 currentConfig.CPUclock = atoi(val);
642 if (strcasecmp(var, "Gamma correction") != 0) return 0;
643 currentConfig.gamma = (int) (atof(val) * 100.0);
646 case MA_OPT2_SQUIDGEHACK:
647 if (strcasecmp(var, "Squidgehack") != 0) return 0;
649 if (tmpi) *(int *)me->var |= me->mask;
650 else *(int *)me->var &= ~me->mask;
653 case MA_CDOPT_READAHEAD:
654 if (strcasecmp(var, "ReadAhead buffer") != 0) return 0;
655 PicoCDBuffers = atoi(val) / 2;
660 if (strcasecmp(var, "Scale factor") != 0) return 0;
661 currentConfig.scale = atof(val);
663 case MA_OPT3_HSCALE32:
664 if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
665 currentConfig.hscale32 = atof(val);
667 case MA_OPT3_HSCALE40:
668 if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
669 currentConfig.hscale40 = atof(val);
671 case MA_OPT3_FILTERING:
672 if (strcasecmp(var, "Bilinear filtering") != 0) return 0;
673 currentConfig.scaling = atoi(val);
676 if (strcasecmp(var, "Gamma adjustment") != 0) return 0;
677 currentConfig.gamma = atoi(val);
679 case MA_OPT3_BLACKLVL:
680 if (strcasecmp(var, "Black level") != 0) return 0;
681 currentConfig.gamma2 = atoi(val);
684 if (strcasecmp(var, "Wait for vsync") != 0) return 0;
685 if (strcasecmp(val, "never") == 0) {
686 currentConfig.EmuOpt &= ~0x12000;
687 } else if (strcasecmp(val, "sometimes") == 0) {
688 currentConfig.EmuOpt |= 0x12000;
689 } else if (strcasecmp(val, "always") == 0) {
690 currentConfig.EmuOpt &= ~0x12000;
691 currentConfig.EmuOpt |= 0x02000;
697 lprintf("unhandled custom_read: %i\n", me->id);
703 static unsigned int keys_encountered = 0;
705 static void keys_parse(const char *var, const char *val, int binds[32],
706 const char * const names[32], int max_keys)
711 for (t = 0; t < 32; t++)
713 if (names[t] && strcmp(names[t], var) == 0) break;
717 int len = strlen(var);
718 if (len == 1) t = var[0];
719 else if (len >= 4 && var[0] == '\\' && var[1] == 'x') {
721 t = (int)strtoul(var + 2, &p, 16);
722 if (*p != 0) t = max_keys; // parse failed
725 t = max_keys; // invalid
727 if (t < 0 || t >= max_keys) {
728 lprintf("unhandled bind \"%s\"\n", var);
732 // unbind old, but only when key is first encountered
733 if (t < 32 && binds == currentConfig.KeyBinds && !(keys_encountered & (1<<t))) {
735 keys_encountered |= 1<<t;
739 if (strncasecmp(val, "player", 6) == 0)
741 player = atoi(val + 6) - 1;
742 if (player > 1) goto fail;
743 for (i = 0; i < sizeof(me_ctrl_actions) / sizeof(me_ctrl_actions[0]); i++) {
744 if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0) {
745 binds[t] |= me_ctrl_actions[i].mask | (player<<16);
750 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
751 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
752 binds[t] |= emuctrl_actions[i].mask;
758 lprintf("unhandled action \"%s\"\n", val);
763 #define try_joy_parse(num) { \
764 if (strncasecmp(var, "bind_joy"#num " ", 10) == 0) { \
765 keys_parse(var + 10, val, currentConfig.JoyBinds[num], joyKeyNames, 32); \
770 static void parse(const char *var, const char *val)
773 int t, i, tlen, tmp, ret = 0;
775 if (strcasecmp(var, "LastUsedROM") == 0)
776 return; /* handled elsewhere */
778 if (strcasecmp(var, "Sound Volume") == 0) {
779 currentConfig.volume = atoi(val);
784 if (strncasecmp(var, "bind ", 5) == 0) {
785 keys_parse(var + 5, val, currentConfig.KeyBinds, keyNames, PLAT_MAX_KEYS);
795 for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
798 tlen = *(cfg_opt_counts[t]);
799 for (i = 0; i < tlen && ret == 0; i++, me++)
801 if (!me->need_to_save) continue;
802 if (me->name != NULL) {
803 if (strcasecmp(var, me->name) != 0) continue; // surely not this one
804 if (me->beh == MB_ONOFF) {
806 if (tmp) *(int *)me->var |= me->mask;
807 else *(int *)me->var &= ~me->mask;
809 } else if (me->beh == MB_RANGE) {
811 if (tmp < me->min) tmp = me->min;
812 if (tmp > me->max) tmp = me->max;
813 *(int *)me->var = tmp;
817 ret = custom_read(me, var, val);
820 if (!ret) lprintf("config_readsect: unhandled var: \"%s\"\n", var);
824 int config_havesect(const char *fname, const char *section)
829 f = fopen(fname, "r");
830 if (f == NULL) return 0;
832 ret = seek_sect(f, section);
837 int config_readsect(const char *fname, const char *section)
839 char line[128], *var, *val;
843 f = fopen(fname, "r");
844 if (f == NULL) return -1;
848 ret = seek_sect(f, section);
850 lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
856 keys_encountered = 0;
860 ret = config_get_var_val(f, line, sizeof(line), &var, &val);
862 if (ret == -1) continue;
873 static char *mystrip(char *str)
878 for (i = 0; i < len; i++)
879 if (str[i] != ' ') break;
880 if (i > 0) memmove(str, str + i, len - i + 1);
883 for (i = len - 1; i >= 0; i--)
884 if (str[i] != ' ') break;
893 * -1 - failed to parse line
895 int config_get_var_val(void *file, char *line, int lsize, char **rvar, char **rval)
897 char *var, *val, *tmp;
901 tmp = fgets(line, lsize, f);
902 if (tmp == NULL) return 0;
904 if (line[0] == '[') return 0; // other section
906 // strip comments, linefeed, spaces..
908 for (i = 0; i < len; i++)
909 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
912 if (len <= 0) return -1;;
915 for (i = 0; i < len; i++)
916 if (line[i] == '=') break;
917 if (i >= len || strchr(&line[i+1], '=') != NULL) {
918 lprintf("config_readsect: can't parse: %s\n", line);
928 if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
929 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);