]> granicus.if.org Git - nethack/commitdiff
Improve config file error reporting
authorPasi Kallinen <paxed@alt.org>
Sat, 9 Sep 2017 10:04:03 +0000 (13:04 +0300)
committerPasi Kallinen <paxed@alt.org>
Sat, 9 Sep 2017 10:04:08 +0000 (13:04 +0300)
Show the original line from the config file, followed by the line number and
a specific error message. Also show all errors from the config file before
waiting for key press.

doc/fixes36.1
include/extern.h
src/cmd.c
src/files.c
src/options.c

index 85c517573974587a33641fa7532f9dbea0d4301b..4032a9dc2b20995eb43ef809d9ff1347c45d0979 100644 (file)
@@ -420,6 +420,7 @@ surviving a gas spore's explosion would leave that explosion as a stale reason
 add database entry for "martial arts"
 starting inventory for rogues specified +9 lock pick, where +/-N is meaningless
 fix pile mark when picking up all-but-one items while invisible
+improve config file error reporting
 
 
 Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository
index 18c3dabbc889b47fdff80057814283dc2011a0c0..4af9fd272d391375c639dc13097161cf057bc77b 100644 (file)
@@ -198,7 +198,7 @@ E int NDECL(enter_explore_mode);
 E void FDECL(enlightenment, (int, int));
 E void FDECL(youhiding, (BOOLEAN_P, int));
 E void FDECL(show_conduct, (int));
-E void FDECL(bind_key, (UCHAR_P, const char *));
+E boolean FDECL(bind_key, (UCHAR_P, const char *));
 E void NDECL(dokeylist);
 E int FDECL(xytod, (SCHAR_P, SCHAR_P));
 E void FDECL(dtoxy, (coord *, int));
@@ -767,6 +767,9 @@ E void FDECL(unlock_file, (const char *));
 #ifdef USER_SOUNDS
 E boolean FDECL(can_read_file, (const char *));
 #endif
+E void FDECL(config_error_init, (BOOLEAN_P, const char *));
+E void FDECL(config_error_add, (const char *, ...)) PRINTF_F(1, 2);
+E int NDECL(config_error_done);
 E boolean FDECL(read_config_file, (const char *, int));
 E void FDECL(check_recordfile, (const char *));
 E void NDECL(read_wizkit);
@@ -1679,7 +1682,7 @@ E boolean FDECL(match_optname, (const char *, const char *, int, BOOLEAN_P));
 E void NDECL(initoptions);
 E void NDECL(initoptions_init);
 E void NDECL(initoptions_finish);
-E void FDECL(parseoptions, (char *, BOOLEAN_P, BOOLEAN_P));
+E boolean FDECL(parseoptions, (char *, BOOLEAN_P, BOOLEAN_P));
 E int NDECL(doset);
 E int NDECL(dotogglepickup);
 E void NDECL(option_help);
@@ -1687,7 +1690,7 @@ E void FDECL(next_opt, (winid, const char *));
 E int FDECL(fruitadd, (char *, struct fruit *));
 E int FDECL(choose_classes_menu, (const char *, int, BOOLEAN_P,
                                   char *, char *));
-E void FDECL(parsebindings, (char *));
+E boolean FDECL(parsebindings, (char *));
 E void FDECL(add_menu_cmd_alias, (CHAR_P, CHAR_P));
 E char FDECL(get_menu_cmd_key, (CHAR_P));
 E char FDECL(map_menu_cmd, (CHAR_P));
index 7437242dddeed158dac95842565fdd59d0279bcc..a6f4a3fcb23cc2e9cf7252eb529bc5065a04beef 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -3056,7 +3056,7 @@ uchar key;
     return (char *) 0;
 }
 
-void
+boolean
 bind_key(key, command)
 uchar key;
 const char *command;
@@ -3066,19 +3066,17 @@ const char *command;
     /* special case: "nothing" is reserved for unbinding */
     if (!strcmp(command, "nothing")) {
         Cmd.commands[key] = (struct ext_func_tab *) 0;
-        return;
+        return TRUE;
     }
 
     for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) {
         if (strcmp(command, extcmd->ef_txt))
             continue;
         Cmd.commands[key] = extcmd;
-        return;
+        return TRUE;
     }
 
-    pline(
-        "Bad command %s matched with key %c (ASCII %i).  Ignoring command.\n",
-          command, key, key);
+    return FALSE;
 }
 
 /* initialize all keyboard commands */
@@ -3091,27 +3089,27 @@ commands_init()
         if (extcmd->key)
             Cmd.commands[extcmd->key] = extcmd;
 
-    bind_key(C('l'), "redraw"); /* if number_pad is set */
+    (void) bind_key(C('l'), "redraw"); /* if number_pad is set */
     /*       'b', 'B' : go sw */
     /*       'F' : fight (one time) */
     /*       'g', 'G' : multiple go */
     /*       'h', 'H' : go west */
-    bind_key('h',    "help"); /* if number_pad is set */
-    bind_key('j',    "jump"); /* if number_pad is on */
+    (void) bind_key('h',    "help"); /* if number_pad is set */
+    (void) bind_key('j',    "jump"); /* if number_pad is on */
     /*       'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N' move commands */
-    bind_key('k',    "kick"); /* if number_pad is on */
-    bind_key('l',    "loot"); /* if number_pad is on */
-    bind_key(C('n'), "annotate"); /* if number_pad is on */
-    bind_key(M('n'), "name");
-    bind_key(M('N'), "name");
-    bind_key('u',    "untrap"); /* if number_pad is on */
+    (void) bind_key('k',    "kick"); /* if number_pad is on */
+    (void) bind_key('l',    "loot"); /* if number_pad is on */
+    (void) bind_key(C('n'), "annotate"); /* if number_pad is on */
+    (void) bind_key(M('n'), "name");
+    (void) bind_key(M('N'), "name");
+    (void) bind_key('u',    "untrap"); /* if number_pad is on */
 
     /* alt keys: */
-    bind_key(M('O'), "overview");
-    bind_key(M('2'), "twoweapon");
+    (void) bind_key(M('O'), "overview");
+    (void) bind_key(M('2'), "twoweapon");
 
     /* wait_on_space */
-    bind_key(' ',    "wait");
+    (void) bind_key(' ',    "wait");
 }
 
 int
@@ -4007,7 +4005,7 @@ boolean initial;
         }
         backed_dir_cmd = TRUE;
         for (i = 0; i < 8; i++)
-            bind_key(Cmd.dirchars[i], "nothing");
+            (void) bind_key(Cmd.dirchars[i], "nothing");
     }
 }
 
index 7f285b21c9db334509816bdde56eb7a574dd445c..bba7f0e062b5d7086f0e5c8bf305a9e712abf820 100644 (file)
@@ -2,6 +2,8 @@
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
 /* NetHack may be freely redistributed.  See license for details. */
 
+#define NEED_VARARGS
+
 #include "hack.h"
 #include "dlb.h"
 
@@ -198,6 +200,7 @@ STATIC_DCL void FDECL(set_symhandling, (char *, int));
 #ifdef NOCWD_ASSUMPTIONS
 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
 #endif
+STATIC_DCL void FDECL(config_error_nextline, (const char *));
 STATIC_DCL void NDECL(free_config_sections);
 STATIC_DCL char *FDECL(choose_random_part, (char *, CHAR_P));
 STATIC_DCL boolean FDECL(is_config_section, (const char *));
@@ -2240,6 +2243,7 @@ int src;
     char *bufp, *altp, buf[4 * BUFSZ];
     uchar translate[MAXPCHARS];
     int len;
+    int retval = 1;
 
     /* convert any tab to space, condense consecutive spaces into one,
        remove leading and trailing spaces (exception: if there is nothing
@@ -2257,8 +2261,10 @@ int src;
     altp = index(buf, ':');
     if (!bufp || (altp && altp < bufp))
         bufp = altp;
-    if (!bufp)
+    if (!bufp) {
+        config_error_add("Not a config statement, missing '='");
         return 0;
+    }
     /* skip past '=', then space between it and value, if any */
     ++bufp;
     if (*bufp == ' ')
@@ -2278,7 +2284,8 @@ int src;
             bufp = altp;
         ++bufp; /* skip '='; parseoptions() handles spaces */
 
-        parseoptions(bufp, TRUE, TRUE);
+        if (!parseoptions(bufp, TRUE, TRUE))
+            retval = 0;
     } else if (match_varname(buf, "CHOOSE", 6)) {
         char *section;
         if (config_section_chosen)
@@ -2289,11 +2296,13 @@ int src;
     } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
         add_autopickup_exception(bufp);
     } else if (match_varname(buf, "BINDINGS", 4)) {
-        parsebindings(bufp);
+        if (!parsebindings(bufp))
+            retval = 0;
     } else if (match_varname(buf, "AUTOCOMPLETE", 5)) {
         parseautocomplete(bufp, TRUE);
     } else if (match_varname(buf, "MSGTYPE", 7)) {
-        (void) msgtype_parse_add(bufp);
+        if (!msgtype_parse_add(bufp))
+            retval = 0;
 #ifdef NOCWD_ASSUMPTIONS
     } else if (match_varname(buf, "HACKDIR", 4)) {
         adjust_prefix(bufp, HACKPREFIX);
@@ -2539,7 +2548,8 @@ int src;
         (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE, 1,
                           "BOULDER");
     } else if (match_varname(buf, "MENUCOLOR", 9)) {
-        (void) add_menu_coloring(bufp);
+        if (!add_menu_coloring(bufp))
+            retval = 0;
     } else if (match_varname(buf, "WARNINGS", 5)) {
         (void) get_uchars(fp, buf, bufp, translate, FALSE, WARNCOUNT,
                           "WARNINGS");
@@ -2719,7 +2729,7 @@ int src;
 #endif
     } else
         return 0;
-    return 1;
+    return retval;
 }
 
 #ifdef USER_SOUNDS
@@ -2731,6 +2741,81 @@ const char *filename;
 }
 #endif /* USER_SOUNDS */
 
+static int config_err_line_num = 0;
+static int config_err_num_errors = 0;
+static boolean config_err_origline_shown = FALSE;
+static boolean config_err_fromfile = FALSE;
+static char config_err_origline[4 * BUFSZ];
+static char config_err_source[BUFSZ];
+
+void
+config_error_init(from_file, sourcename)
+boolean from_file;
+const char *sourcename;
+{
+    config_err_line_num = 0;
+    config_err_num_errors = 0;
+    config_err_origline_shown = FALSE;
+    config_err_fromfile = from_file;
+    config_err_origline[0] = '\0';
+    if (sourcename && sourcename[0])
+        Strcpy(config_err_source, sourcename);
+    else
+        config_err_source[0] = '\0';
+}
+
+STATIC_OVL void
+config_error_nextline(line)
+const char *line;
+{
+    config_err_line_num++;
+    config_err_origline_shown = FALSE;
+    if (line && line[0])
+        Strcpy(config_err_origline, line);
+    else
+        config_err_origline[0] = '\0';
+}
+
+/*VARARGS1*/
+void config_error_add
+VA_DECL(const char *, str)
+/*const char *errmsg;*/
+{
+    VA_START(str);
+    VA_INIT(str, char *);
+    char buf[BUFSZ];
+    char lineno[QBUFSZ];
+
+    Vsprintf(buf, str, VA_ARGS);
+
+    config_err_num_errors++;
+    if (!config_err_origline_shown) {
+        pline("\n%s", config_err_origline);
+        config_err_origline_shown = TRUE;
+    }
+    if (config_err_line_num > 0) {
+        Sprintf(lineno, "Line %i: ", config_err_line_num);
+    } else
+        lineno[0] = '\0';
+    pline(" * %s%s.", lineno, (buf && buf[0]) ? buf : "Unknown error");
+
+    VA_END();
+}
+
+int
+config_error_done()
+{
+    int n = config_err_num_errors;
+    if (n) {
+        pline("\n%i error%s in %s.\n", n,
+                   (n > 1) ? "s" : "",
+                   *config_err_source ? config_err_source : configfile);
+        wait_synch();
+    }
+    config_error_init(FALSE, "");
+    return n;
+}
+
 boolean
 read_config_file(filename, src)
 const char *filename;
@@ -2748,6 +2833,8 @@ int src;
     free_config_sections();
 
     while (fgets(buf, sizeof buf, fp)) {
+        strip_newline(buf);
+        config_error_nextline(buf);
 #ifdef notyet
 /*
 XXX Don't call read() in parse_config_line, read as callback or reassemble
@@ -2755,17 +2842,8 @@ line at this level.
 OR: Forbid multiline stuff for alternate config sources.
 */
 #endif
-        if (!parse_config_line(fp, strip_newline(buf), src)) {
-            static const char badoptionline[] = "Bad option line: \"%s\"";
-
-            /* truncate buffer if it's long; this is actually conservative */
-            if (strlen(buf) > BUFSZ - sizeof badoptionline)
-                buf[BUFSZ - sizeof badoptionline] = '\0';
-
-            raw_printf(badoptionline, buf);
-            wait_synch();
+        if (!parse_config_line(fp, buf, src))
             rv = FALSE;
-        }
     }
     (void) fclose(fp);
 
index f0de0cab02c5007337ddc6862b7e42fc18995713..f7a79493d0cf654f195655d27b0a4cfdf383674b 100644 (file)
@@ -506,8 +506,6 @@ static boolean initial, from_file;
 STATIC_DCL void FDECL(nmcpy, (char *, const char *, int));
 STATIC_DCL void FDECL(escapes, (const char *, char *));
 STATIC_DCL void FDECL(rejectoption, (const char *));
-STATIC_DCL void FDECL(badoptmsg, (const char *, const char *));
-STATIC_DCL void FDECL(badoption, (const char *));
 STATIC_DCL char *FDECL(string_for_opt, (char *, BOOLEAN_P));
 STATIC_DCL char *FDECL(string_for_env_opt, (const char *, char *, BOOLEAN_P));
 STATIC_DCL void FDECL(bad_negation, (const char *, BOOLEAN_P));
@@ -517,6 +515,7 @@ STATIC_DCL int FDECL(feature_alert_opts, (char *, const char *));
 STATIC_DCL boolean FDECL(duplicate_opt_detection, (const char *, int));
 STATIC_DCL void FDECL(complain_about_duplicate, (const char *, int));
 
+STATIC_DCL int FDECL(match_str2attr, (const char *));
 STATIC_DCL const char *FDECL(attr2attrname, (int));
 STATIC_DCL int NDECL(query_color);
 STATIC_DCL int FDECL(query_attr, (const char *));
@@ -638,11 +637,14 @@ initoptions()
 #ifdef SYSCF_FILE
     /* If SYSCF_FILE is specified, it _must_ exist... */
     assure_syscf_file();
+    config_error_init(TRUE, SYSCF_FILE);
+
     /* ... and _must_ parse correctly. */
     if (!read_config_file(SYSCF_FILE, SET_IN_SYS)) {
-        raw_printf("Error(s) found in SYSCF_FILE, quitting.");
-        nh_terminate(EXIT_FAILURE);
+        if (config_error_done())
+            nh_terminate(EXIT_FAILURE);
     }
+    config_error_done();
     /*
      * TODO [maybe]: parse the sysopt entries which are space-separated
      * lists of usernames into arrays with one name per element.
@@ -788,18 +790,29 @@ initoptions_finish()
             if (*opts == '@')
                 opts++; /* @filename */
             /* looks like a filename */
-            if (strlen(opts) < BUFSZ / 2)
+            if (strlen(opts) < BUFSZ / 2) {
+                config_error_init(TRUE, opts);
                 read_config_file(opts, SET_IN_FILE);
+                config_error_done();
+            }
         } else {
+            config_error_init(TRUE, (char *) 0);
             read_config_file((char *) 0, SET_IN_FILE);
+            config_error_done();
             /* let the total length of options be long;
              * parseoptions() will check each individually
              */
-            parseoptions(opts, TRUE, FALSE);
+            config_error_init(FALSE, "NETHACKOPTIONS");
+            (void) parseoptions(opts, TRUE, FALSE);
+            config_error_done();
         }
     } else
 #endif
-        read_config_file((char *) 0, SET_IN_FILE);
+        {
+            config_error_init(TRUE, (char *) 0);
+            read_config_file((char *) 0, SET_IN_FILE);
+            config_error_done();
+        }
 
     (void) fruitadd(pl_fruit, (struct fruit *) 0);
     /*
@@ -937,43 +950,24 @@ const char *optname;
 #endif
 }
 
-STATIC_OVL void
-badoptmsg(opts, reason)
-const char *opts;
-const char *reason; /* "Bad syntax" or "Missing value" */
-{
-    const char *linesplit = "";
-
-    if (!initial) {
-        if (!strncmp(opts, "h", 1) || !strncmp(opts, "?", 1))
-            option_help();
-        else
-            pline("%s: %s.  Enter \"?g\" for help.", reason, opts);
-        return;
-#ifdef MAC
-    } else {
-        return;
-#endif
-    }
-
-#ifdef WIN32
-    linesplit = "\n";
-#endif
-    if (from_file)
-        raw_printf("%s in OPTIONS in %s: %s%s.\n",
-                   reason, configfile, linesplit, opts);
-    else
-        raw_printf("%s in NETHACKOPTIONS: %s%s.\n",
-                   reason, linesplit, opts);
-    wait_synch();
-}
+/*
 
-STATIC_OVL void
-badoption(opts)
-const char *opts;
-{
-    badoptmsg(opts, "Bad syntax");
-}
+# errors:
+OPTIONS=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+OPTIONS
+OPTIONS=
+MSGTYPE=stop"You swap places with "
+MSGTYPE=st.op "You swap places with "
+MSGTYPE=stop "You swap places with \"
+MENUCOLOR=" blessed "green&none
+MENUCOLOR=" holy " = green&reverse
+MENUCOLOR=" cursed " = red&uline
+MENUCOLOR=" unholy " = reed
+OPTIONS=!legacy:true,fooo
+OPTIONS=align:!pin
+OPTIONS=gender
+
+*/
 
 STATIC_OVL char *
 string_for_opt(opts, val_optional)
@@ -989,7 +983,7 @@ boolean val_optional;
 
     if (!colon || !*++colon) {
         if (!val_optional)
-            badoptmsg(opts, "Missing value");
+            config_error_add("Missing parameter for '%s'", opts);
         return (char *) 0;
     }
     return colon;
@@ -1115,7 +1109,7 @@ const char *optn;
             Sprintf(buf,
                     "\n%s=%s Invalid reference to a future version ignored",
                     optn, op);
-            badoption(buf);
+            config_error_add(buf);
         }
         return 0;
     }
@@ -1324,6 +1318,10 @@ char *str;
         }
     if (i == SIZE(colornames) && (*str >= '0' && *str <= '9'))
         c = atoi(str);
+
+    if (c == CLR_MAX)
+        config_error_add("Unknown color '%s'", str);
+
     return c;
 }
 
@@ -1339,6 +1337,25 @@ int attr;
     return (char *) 0;
 }
 
+STATIC_OVL int
+match_str2attr(str)
+const char *str;
+{
+    int i, a = -1;
+
+    for (i = 0; i < SIZE(attrnames); i++)
+        if (attrnames[i].name
+            && fuzzymatch(str, attrnames[i].name, " -_", TRUE)) {
+            a = attrnames[i].attr;
+            break;
+        }
+
+    if (a == -1)
+        config_error_add("Unknown text attribute '%s'", str);
+
+    return a;
+}
+
 STATIC_OVL int
 query_color()
 {
@@ -1381,6 +1398,8 @@ const char *prompt;
     start_menu(tmpwin);
     any = zeroany;
     for (i = 0; i < SIZE(attrnames); i++) {
+        if (!attrnames[i].name)
+            break;
         any.a_int = i + 1;
         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, attrnames[i].attr,
                  attrnames[i].name, MENU_UNSELECTED);
@@ -1465,10 +1484,11 @@ char *pattern;
         static const char *re_error = "MSGTYPE regex error";
 
         if (!iflags.window_inited)
-            raw_printf("\n%s: %s\n", re_error, regex_error_desc(tmp->regex));
-        else
+            config_error_add("%s: %s", re_error, regex_error_desc(tmp->regex));
+        else {
             pline("%s: %s", re_error, regex_error_desc(tmp->regex));
-        wait_synch();
+            wait_synch();
+        }
         regex_free(tmp->regex);
         free((genericptr_t) tmp);
         return FALSE;
@@ -1587,6 +1607,10 @@ char *str;
             }
         if (typ != -1)
             return msgtype_add(typ, pattern);
+        else
+            config_error_add("Unknown message type '%s'", msgtype);
+    } else {
+        config_error_add("Malformed MSGTYPE");
     }
     return FALSE;
 }
@@ -1624,14 +1648,19 @@ int c, a;
 
 /* parse '"regex_string"=color&attr' and add it to menucoloring */
 boolean
-add_menu_coloring(str)
-char *str;
+add_menu_coloring(tmpstr)
+char *tmpstr;
 {
     int i, c = NO_COLOR, a = ATR_NONE;
     char *tmps, *cs, *amp;
+    char str[BUFSZ];
+
+    Sprintf(str, "%s", tmpstr);
 
-    if (!str || (cs = index(str, '=')) == 0)
+    if (!tmpstr || (cs = index(str, '=')) == 0) {
+        config_error_add("Malformed MENUCOLOR");
         return FALSE;
+    }
 
     tmps = cs + 1; /* advance past '=' */
     mungspaces(tmps);
@@ -1644,17 +1673,9 @@ char *str;
 
     if (amp) {
         tmps = amp + 1; /* advance past '&' */
-        /* unlike colors, none of he attribute names has any embedded spaces,
-           but use of fuzzymatch() allows us ignore the presence of leading
-           and/or trailing (and also embedded) spaces in the user's string;
-           dash and underscore skipping could be omitted but does no harm */
-        for (i = 0; i < SIZE(attrnames); i++)
-            if (fuzzymatch(tmps, attrnames[i].name, " -_", TRUE)) {
-                a = attrnames[i].attr;
-                break;
-            }
-        if (i == SIZE(attrnames) && (*tmps >= '0' && *tmps <= '9'))
-            a = atoi(tmps);
+        a = match_str2attr(tmps);
+        if (a == -1)
+            return FALSE;
     }
 
     /* the regexp portion here has not been condensed by mungspaces() */
@@ -1766,8 +1787,10 @@ char **opp;
             val_negated = !val_negated;
         }
         if (val_negated) {
-            if (!setrolefilter(op))
-                badoption(opts);
+            if (!setrolefilter(op)) {
+                config_error_add("Unknown negated parameter '%s'", op);
+                return FALSE;
+            }
         } else {
             if (duplicate_opt_detection(opts, 1))
                 complain_about_duplicate(opts, 1);
@@ -1795,7 +1818,7 @@ char c;
     return FALSE;
 }
 
-void
+boolean
 parseoptions(opts, tinitial, tfrom_file)
 register char *opts;
 boolean tinitial, tfrom_file;
@@ -1805,16 +1828,19 @@ boolean tinitial, tfrom_file;
     boolean negated, duplicate;
     int i;
     const char *fullname;
+    boolean retval = TRUE;
 
     initial = tinitial;
     from_file = tfrom_file;
     if ((op = index(opts, ',')) != 0) {
         *op++ = 0;
-        parseoptions(op, initial, from_file);
+        if (!parseoptions(op, initial, from_file))
+            retval = FALSE;
     }
     if (strlen(opts) > BUFSZ / 2) {
-        badoption("option too long");
-        return;
+        config_error_add("Option too long, max length is %i characters",
+                        (BUFSZ / 2));
+        return FALSE;
     }
 
     /* strip leading and trailing white space */
@@ -1824,8 +1850,10 @@ boolean tinitial, tfrom_file;
     while (--op >= opts && isspace((uchar) *op))
         *op = '\0';
 
-    if (!*opts)
-        return;
+    if (!*opts) {
+        config_error_add("Empty statement");
+        return FALSE;
+    }
     negated = FALSE;
     while ((*opts == '!') || !strncmpi(opts, "no", 2)) {
         if (*opts == '!')
@@ -1849,7 +1877,7 @@ boolean tinitial, tfrom_file;
             pline("That is not anatomically possible.");
         else
             flags.initgend = flags.female = !negated;
-        return;
+        return FALSE;
     }
 
     if (match_optname(opts, "male", 4, FALSE)) {
@@ -1859,14 +1887,14 @@ boolean tinitial, tfrom_file;
             pline("That is not anatomically possible.");
         else
             flags.initgend = flags.female = negated;
-        return;
+        return FALSE;
     }
 
 #if defined(MICRO) && !defined(AMIGA)
     /* included for compatibility with old NetHack.cnf files */
     if (match_optname(opts, "IBM_", 4, FALSE)) {
         iflags.BIOS = !negated;
-        return;
+        return retval;
     }
 #endif /* MICRO */
 
@@ -1878,10 +1906,12 @@ boolean tinitial, tfrom_file;
     fullname = "align";
     if (match_optname(opts, fullname, sizeof("align") - 1, TRUE)) {
         if (parse_role_opts(negated, fullname, opts, &op)) {
-            if ((flags.initalign = str2align(op)) == ROLE_NONE)
-                badoption(opts);
+            if ((flags.initalign = str2align(op)) == ROLE_NONE) {
+                config_error_add("Unknown %s '%s'", fullname, op);
+                return FALSE;
+            }
         }
-        return;
+        return retval;
     }
 
     /* role:string or character:string */
@@ -1889,36 +1919,39 @@ boolean tinitial, tfrom_file;
     if (match_optname(opts, fullname, 4, TRUE)
         || match_optname(opts, (fullname = "character"), 4, TRUE)) {
         if (parse_role_opts(negated, fullname, opts, &op)) {
-            if ((flags.initrole = str2role(op)) == ROLE_NONE)
-                badoption(opts);
-            else /* Backwards compatibility */
+            if ((flags.initrole = str2role(op)) == ROLE_NONE) {
+                config_error_add("Unknown %s '%s'", fullname, op);
+                return FALSE;
+            } else /* Backwards compatibility */
                 nmcpy(pl_character, op, PL_NSIZ);
         }
-        return;
+        return retval;
     }
 
     /* race:string */
     fullname = "race";
     if (match_optname(opts, fullname, 4, TRUE)) {
         if (parse_role_opts(negated, fullname, opts, &op)) {
-            if ((flags.initrace = str2race(op)) == ROLE_NONE)
-                badoption(opts);
-            else /* Backwards compatibility */
+            if ((flags.initrace = str2race(op)) == ROLE_NONE) {
+                config_error_add("Unknown %s '%s'", fullname, op);
+                return FALSE;
+            } else /* Backwards compatibility */
                 pl_race = *op;
         }
-        return;
+        return retval;
     }
 
     /* gender:string */
     fullname = "gender";
     if (match_optname(opts, fullname, 4, TRUE)) {
         if (parse_role_opts(negated, fullname, opts, &op)) {
-            if ((flags.initgend = str2gend(op)) == ROLE_NONE)
-                badoption(opts);
-            else
+            if ((flags.initgend = str2gend(op)) == ROLE_NONE) {
+                config_error_add("Unknown %s '%s'", fullname, op);
+                return FALSE;
+            } else
                 flags.female = flags.initgend;
         }
-        return;
+        return retval;
     }
 
     /* We always check for duplicates on the remaining compound options,
@@ -1956,48 +1989,52 @@ boolean tinitial, tfrom_file;
                     preferred_pet = '\0';
                     break;
                 default:
-                    pline("Unrecognized pet type '%s'.", op);
+                    config_error_add("Unrecognized pet type '%s'.", op);
+                    return FALSE;
                     break;
                 }
         } else if (negated)
             preferred_pet = 'n';
-        return;
+        return retval;
     }
 
     fullname = "catname";
     if (match_optname(opts, fullname, 3, TRUE)) {
         if (duplicate)
             complain_about_duplicate(opts, 1);
-        if (negated)
+        if (negated) {
             bad_negation(fullname, FALSE);
-        else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
+            return FALSE;
+        } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
             nmcpy(catname, op, PL_PSIZ);
         sanitize_name(catname);
-        return;
+        return retval;
     }
 
     fullname = "dogname";
     if (match_optname(opts, fullname, 3, TRUE)) {
         if (duplicate)
             complain_about_duplicate(opts, 1);
-        if (negated)
+        if (negated) {
             bad_negation(fullname, FALSE);
-        else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
+            return FALSE;
+        } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
             nmcpy(dogname, op, PL_PSIZ);
         sanitize_name(dogname);
-        return;
+        return retval;
     }
 
     fullname = "horsename";
     if (match_optname(opts, fullname, 5, TRUE)) {
         if (duplicate)
             complain_about_duplicate(opts, 1);
-        if (negated)
+        if (negated) {
             bad_negation(fullname, FALSE);
-        else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
+            return FALSE;
+        } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
             nmcpy(horsename, op, PL_PSIZ);
         sanitize_name(horsename);
-        return;
+        return retval;
     }
 
     fullname = "number_pad";
@@ -2016,13 +2053,13 @@ boolean tinitial, tfrom_file;
             }
         } else if (negated) {
             bad_negation("number_pad", TRUE);
-            return;
+            return FALSE;
         } else {
             int mode = atoi(op);
 
             if (mode < -1 || mode > 4 || (mode == 0 && *op != '0')) {
-                badoption(opts);
-                return;
+                config_error_add("Illegal %s parameter '%s'", fullname, op);
+                return FALSE;
             } else if (mode <= 0) {
                 iflags.num_pad = FALSE;
                 /* German keyboard; y and z keys swapped */
@@ -2040,7 +2077,7 @@ boolean tinitial, tfrom_file;
         }
         reset_commands(FALSE);
         number_pad(iflags.num_pad ? 1 : 0);
-        return;
+        return retval;
     }
 
     fullname = "roguesymset";
@@ -2049,6 +2086,7 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
+            return FALSE;
         } else if ((op = string_for_opt(opts, FALSE)) != 0) {
             symset[ROGUESET].name = dupstr(op);
             if (!read_sym_file(ROGUESET)) {
@@ -2062,7 +2100,7 @@ boolean tinitial, tfrom_file;
                 need_redraw = TRUE;
             }
         }
-        return;
+        return retval;
     }
 
     fullname = "symset";
@@ -2071,6 +2109,7 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
+            return FALSE;
         } else if ((op = string_for_opt(opts, FALSE)) != 0) {
             symset[PRIMARY].name = dupstr(op);
             if (!read_sym_file(PRIMARY)) {
@@ -2083,7 +2122,7 @@ boolean tinitial, tfrom_file;
                 need_redraw = TRUE;
             }
         }
-        return;
+        return retval;
     }
 
     fullname = "runmode";
@@ -2101,10 +2140,12 @@ boolean tinitial, tfrom_file;
                 flags.runmode = RUN_STEP;
             else if (!strncmpi(op, "crawl", strlen(op)))
                 flags.runmode = RUN_CRAWL;
-            else
-                badoption(opts);
+            else {
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
+            }
         }
-        return;
+        return retval;
     }
 
     /* menucolor:"regex_string"=color */
@@ -2114,8 +2155,8 @@ boolean tinitial, tfrom_file;
             bad_negation(fullname, FALSE);
         else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
             if (!add_menu_coloring(op))
-                badoption(opts);
-        return;
+                return FALSE;
+        return retval;
     }
 
     fullname = "msghistory";
@@ -2125,9 +2166,11 @@ boolean tinitial, tfrom_file;
         op = string_for_env_opt(fullname, opts, negated);
         if ((negated && !op) || (!negated && op)) {
             iflags.msg_history = negated ? 0 : atoi(op);
-        } else if (negated)
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
 
     fullname = "msg_window";
@@ -2144,7 +2187,7 @@ boolean tinitial, tfrom_file;
         } else {
             if (negated) {
                 bad_negation(fullname, TRUE);
-                return;
+                return FALSE;
             }
             tmp = lowc(*op);
         }
@@ -2161,11 +2204,13 @@ boolean tinitial, tfrom_file;
         case 'r': /* full page (reversed) */
             iflags.prevmsg_window = 'r';
             break;
-        default:
-            badoption(opts);
+        default: {
+            config_error_add("Unknown %s parameter '%s'", fullname, op);
+            return FALSE;
+        }
         }
 #endif
-        return;
+        return retval;
     }
 
     /* WINCAP
@@ -2201,8 +2246,8 @@ boolean tinitial, tfrom_file;
             else if (!strncmpi(fontopts, "_size_status", 11))
                 opttype = STATUS_OPTION;
             else {
-                badoption(opts);
-                return;
+                config_error_add("Unknown %s parameter '%s'", fullname, opts);
+                return FALSE;
             }
             if (duplicate)
                 complain_about_duplicate(opts, 1);
@@ -2226,19 +2271,22 @@ boolean tinitial, tfrom_file;
                     break;
                 }
             }
-            return;
+            return retval;
         } else {
-            badoption(opts);
+            config_error_add("Unknown %s parameter '%s'", fullname, opts);
+            return FALSE;
         }
         if (opttype > 0 && (op = string_for_opt(opts, FALSE)) != 0) {
             wc_set_font_name(opttype, op);
 #ifdef MAC
             set_font_name(opttype, op);
 #endif
-            return;
-        } else if (negated)
+            return retval;
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
 
 #ifdef CHANGE_COLOR
@@ -2257,7 +2305,7 @@ boolean tinitial, tfrom_file;
         if (match_optname(opts, "hicolor", 3, TRUE)) {
             if (negated) {
                 bad_negation("hicolor", FALSE);
-                return;
+                return FALSE;
             }
             color_number = CLR_MAX + 4; /* HARDCODED inverse number */
             color_incr = -1;
@@ -2266,15 +2314,17 @@ boolean tinitial, tfrom_file;
         {
             if (negated) {
                 bad_negation("palette", FALSE);
-                return;
+                return FALSE;
             }
             color_number = 0;
             color_incr = 1;
         }
 #ifdef WIN32
         op = string_for_opt(opts, TRUE);
-        if (!alternative_palette(op))
-            badoption(opts);
+        if (!alternative_palette(op)) {
+            config_error_add("Error in palette parameter '%s'", op);
+            return FALSE;
+        }
 #else
         if ((op = string_for_opt(opts, FALSE)) != (char *) 0) {
             char *pt = op;
@@ -2320,7 +2370,7 @@ boolean tinitial, tfrom_file;
         if (!initial) {
             need_redraw = TRUE;
         }
-        return;
+        return retval;
     }
 #endif /* CHANGE_COLOR */
 
@@ -2334,13 +2384,13 @@ boolean tinitial, tfrom_file;
         if (negated) {
             if (op) {
                 bad_negation("fruit", TRUE);
-                return;
+                return FALSE;
             }
             op = &empty_str;
             goto goodfruit;
         }
         if (!op)
-            return;
+            return FALSE;
         if (!initial) {
             struct fruit *f;
             int fnum = 0;
@@ -2353,8 +2403,8 @@ boolean tinitial, tfrom_file;
                     forig = fruit_from_name(pl_fruit, FALSE, (int *) 0);
 
                 if (!forig && fnum >= 100) {
-                    pline("Doing that so many times isn't very fruitful.");
-                    return;
+                    config_error_add("Doing that so many times isn't very fruitful.");
+                    return retval;
                 }
             }
         }
@@ -2377,7 +2427,7 @@ boolean tinitial, tfrom_file;
          * no fruit option at all.  Also, we don't want people
          * setting multiple fruits in their options.)
          */
-        return;
+        return retval;
     }
 
     fullname = "whatis_coord";
@@ -2386,7 +2436,7 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             iflags.getpos_coords = GPCOORDS_NONE;
-            return;
+            return retval;
         } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0) {
             static char gpcoords[] = { GPCOORDS_NONE, GPCOORDS_COMPASS,
                                        GPCOORDS_COMFULL, GPCOORDS_MAP,
@@ -2395,10 +2445,12 @@ boolean tinitial, tfrom_file;
 
             if (c && index(gpcoords, c))
                 iflags.getpos_coords = c;
-            else
-                badoption(opts);
+            else {
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
+            }
         }
-        return;
+        return retval;
     }
 
     fullname = "whatis_filter";
@@ -2407,7 +2459,7 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             iflags.getloc_filter = GFILTER_NONE;
-            return;
+            return retval;
         } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0) {
             char c = lowc(*op);
 
@@ -2421,22 +2473,25 @@ boolean tinitial, tfrom_file;
             case 'a':
                 iflags.getloc_filter = GFILTER_AREA;
                 break;
-            default:
-                badoption(opts);
+            default: {
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
+            }
             }
         }
-        return;
+        return retval;
     }
 
     fullname = "warnings";
     if (match_optname(opts, fullname, 5, TRUE)) {
         if (duplicate)
             complain_about_duplicate(opts, 1);
-        if (negated)
+        if (negated) {
             bad_negation(fullname, FALSE);
-        else
+            return FALSE;
+        } else
             warning_opts(opts, fullname);
-        return;
+        return retval;
     }
 
 #ifdef BACKWARD_COMPAT
@@ -2448,12 +2503,12 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         }
         /* if (!(opts = string_for_env_opt(fullname, opts, FALSE)))
          */
         if (!(opts = string_for_opt(opts, FALSE)))
-            return;
+            return FALSE;
         escapes(opts, opts);
         if (def_char_to_monclass(opts[0]) != MAXMCLASSES)
             clash = 1;
@@ -2462,7 +2517,7 @@ boolean tinitial, tfrom_file;
         if (clash) {
             /* symbol chosen matches a used monster or warning
                symbol which is not good - reject it*/
-            pline(
+            config_error_add(
                 "Badoption - boulder symbol '%c' conflicts with a %s symbol.",
                 opts[0], (clash == 1) ? "monster" : "warning");
         } else {
@@ -2477,7 +2532,7 @@ boolean tinitial, tfrom_file;
             update_bouldersym();
             need_redraw = TRUE;
         }
-        return;
+        return retval;
     }
 #endif
 
@@ -2486,11 +2541,12 @@ boolean tinitial, tfrom_file;
     if (match_optname(opts, fullname, 4, TRUE)) {
         if (duplicate)
             complain_about_duplicate(opts, 1);
-        if (negated)
+        if (negated) {
             bad_negation(fullname, FALSE);
-        else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
+            return FALSE;
+        } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0)
             nmcpy(plname, op, PL_NSIZ);
-        return;
+        return retval;
     }
 
     /* altkeyhandler:string */
@@ -2500,13 +2556,14 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
+            return FALSE;
         } else if ((op = string_for_opt(opts, negated)) != 0) {
 #ifdef WIN32
             (void) strncpy(iflags.altkeyhandler, op, MAX_ALTKEYHANDLER - 5);
             load_keyboard_handler();
 #endif
         }
-        return;
+        return retval;
     }
 
     /* WINCAP
@@ -2523,11 +2580,13 @@ boolean tinitial, tfrom_file;
                 iflags.wc_align_status = ALIGN_RIGHT;
             else if (!strncmpi(op, "bottom", sizeof("bottom") - 1))
                 iflags.wc_align_status = ALIGN_BOTTOM;
-            else
-                badoption(opts);
+            else {
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
+            }
         } else if (negated)
             bad_negation(fullname, TRUE);
-        return;
+        return retval;
     }
     /* WINCAP
      * align_message:[left|top|right|bottom] */
@@ -2545,11 +2604,13 @@ boolean tinitial, tfrom_file;
                 iflags.wc_align_message = ALIGN_RIGHT;
             else if (!strncmpi(op, "bottom", sizeof("bottom") - 1))
                 iflags.wc_align_message = ALIGN_BOTTOM;
-            else
-                badoption(opts);
+            else {
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
+            }
         } else if (negated)
             bad_negation(fullname, TRUE);
-        return;
+        return retval;
     }
     /* the order to list the pack */
     fullname = "packorder";
@@ -2558,13 +2619,13 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if (!(op = string_for_opt(opts, FALSE)))
-            return;
+            return FALSE;
 
         if (!change_inv_order(op))
-            badoption(opts);
-        return;
+            return FALSE;
+        return retval;
     }
 
     /* user can change required response for some prompts (quit, die, hit),
@@ -2611,8 +2672,8 @@ boolean tinitial, tfrom_file;
                 if (i == SIZE(paranoia)) {
                     /* didn't match anything, so arg is bad;
                        any flags already set will stay set */
-                    badoption(opts);
-                    break;
+                    config_error_add("Unknown %s parameter '%s'", fullname, op);
+                    return FALSE;
                 }
                 /* move on to next token */
                 if (pp)
@@ -2621,7 +2682,7 @@ boolean tinitial, tfrom_file;
                     break; /* no next token */
             } /* for(;;) */
         }
-        return;
+        return retval;
     }
 
     /* accept deprecated boolean; superseded by paranoid_confirm:pray */
@@ -2631,7 +2692,7 @@ boolean tinitial, tfrom_file;
             flags.paranoia_bits &= ~PARANOID_PRAY;
         else
             flags.paranoia_bits |= PARANOID_PRAY;
-        return;
+        return retval;
     }
 
     /* maximum burden picked up before prompt (Warren Cheung) */
@@ -2641,7 +2702,7 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0) {
             switch (lowc(*op)) {
             case 'u': /* Unencumbered */
@@ -2664,10 +2725,11 @@ boolean tinitial, tfrom_file;
                 flags.pickup_burden = OVERLOADED;
                 break;
             default:
-                badoption(opts);
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
             }
         }
-        return;
+        return retval;
     }
 
     /* types of objects to pick up automatically */
@@ -2688,7 +2750,7 @@ boolean tinitial, tfrom_file;
                    value is a synonym for autopickup of all types
                    (and during initialization, we can't prompt yet) */
                 flags.pickup = !negated;
-                return;
+                return retval;
             }
             oc_to_str(flags.inv_order, ocl);
             use_menu = TRUE;
@@ -2712,7 +2774,7 @@ boolean tinitial, tfrom_file;
         }
         if (negated) {
             bad_negation("pickup_types", TRUE);
-            return;
+            return FALSE;
         }
         while (*op == ' ')
             op++;
@@ -2729,10 +2791,12 @@ boolean tinitial, tfrom_file;
                     badopt = TRUE;
                 op++;
             }
-            if (badopt)
-                badoption(opts);
+            if (badopt) {
+                config_error_add("Unknown %s parameter '%s'", "pickup_types", op);
+                return FALSE;
+            }
         }
-        return;
+        return retval;
     }
 
     /* pile limit: when walking over objects, number which triggers
@@ -2751,7 +2815,7 @@ boolean tinitial, tfrom_file;
         /* sanity check */
         if (flags.pile_limit < 0)
             flags.pile_limit = PILE_LIMIT_DFLT;
-        return;
+        return retval;
     }
 
     /* play mode: normal, explore/discovery, or debug/wizard */
@@ -2762,10 +2826,10 @@ boolean tinitial, tfrom_file;
         if (negated)
             bad_negation(fullname, FALSE);
         if (duplicate || negated)
-            return;
+            return FALSE;
         op = string_for_opt(opts, FALSE);
         if (!op)
-            return;
+            return FALSE;
         if (!strncmpi(op, "normal", 6) || !strcmpi(op, "play")) {
             wizard = discover = FALSE;
         } else if (!strncmpi(op, "explore", 6)
@@ -2774,9 +2838,10 @@ boolean tinitial, tfrom_file;
         } else if (!strncmpi(op, "debug", 5) || !strncmpi(op, "wizard", 6)) {
             wizard = TRUE, discover = FALSE;
         } else {
-            raw_printf("Invalid value for \"%s\":%s.", fullname, op);
+            config_error_add("Invalid value for \"%s\":%s.", fullname, op);
+            return FALSE;
         }
-        return;
+        return retval;
     }
 
     /* WINCAP
@@ -2791,11 +2856,13 @@ boolean tinitial, tfrom_file;
                 iflags.wc_player_selection = VIA_DIALOG;
             else if (!strncmpi(op, "prompt", sizeof("prompt") - 1))
                 iflags.wc_player_selection = VIA_PROMPTS;
-            else
-                badoption(opts);
+            else {
+                config_error_add("Unknown %s parameter '%s'", "pickup_types", op);
+                return FALSE;
+            }
         } else if (negated)
             bad_negation(fullname, TRUE);
-        return;
+        return retval;
     }
 
     /* things to disclose at end of game */
@@ -2829,7 +2896,7 @@ boolean tinitial, tfrom_file;
         op = string_for_opt(opts, TRUE);
         if (op && negated) {
             bad_negation("disclose", TRUE);
-            return;
+            return FALSE;
         }
         /* "disclose" without a value means "all with prompting"
            and negated means "none without prompting" */
@@ -2840,7 +2907,7 @@ boolean tinitial, tfrom_file;
                 flags.end_disclose[num] = negated
                                               ? DISCLOSE_NO_WITHOUT_PROMPT
                                               : DISCLOSE_PROMPT_DEFAULT_YES;
-            return;
+            return retval;
         }
 
         num = 0;
@@ -2885,9 +2952,11 @@ boolean tinitial, tfrom_file;
                 badopt = TRUE;
             op++;
         }
-        if (badopt)
-            badoption(opts);
-        return;
+        if (badopt) {
+            config_error_add("Unknown %s parameter '%s'", "disclose", op);
+            return FALSE;
+        }
+        return retval;
     }
 
     /* scores:5t[op] 5a[round] o[wn] */
@@ -2896,10 +2965,10 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation("scores", FALSE);
-            return;
+            return FALSE;
         }
         if (!(op = string_for_opt(opts, FALSE)))
-            return;
+            return FALSE;
 
         while (*op) {
             int inum = 1;
@@ -2929,15 +2998,15 @@ boolean tinitial, tfrom_file;
                 flags.end_own = !negated;
                 break;
             default:
-                badoption(opts);
-                return;
+                config_error_add("Unknown %s parameter '%s'", "scores", op);
+                return FALSE;
             }
             while (letter(*++op) || *op == ' ')
                 continue;
             if (*op == '/')
                 op++;
         }
-        return;
+        return retval;
     }
 
     fullname = "sortloot";
@@ -2953,11 +3022,11 @@ boolean tinitial, tfrom_file;
                 flags.sortloot = c;
                 break;
             default:
-                badoption(opts);
-                return;
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
             }
         }
-        return;
+        return retval;
     }
 
     fullname = "suppress_alert";
@@ -2965,11 +3034,12 @@ boolean tinitial, tfrom_file;
         if (duplicate)
             complain_about_duplicate(opts, 1);
         op = string_for_opt(opts, negated);
-        if (negated)
+        if (negated) {
             bad_negation(fullname, FALSE);
-        else if (op)
+            return FALSE;
+        } else if (op)
             (void) feature_alert_opts(op, fullname);
-        return;
+        return retval;
     }
 
 #ifdef VIDEOSHADES
@@ -2981,13 +3051,13 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if (!(opts = string_for_env_opt(fullname, opts, FALSE))) {
-            return;
+            return FALSE;
         }
-        if (!assign_videocolors(opts))
-            badoption(opts);
-        return;
+        if (!assign_videocolors(opts)) /* TODO: error msg */
+            return FALSE;
+        return retval;
     }
     /* videoshades:string */
     fullname = "videoshades";
@@ -2996,13 +3066,13 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if (!(opts = string_for_env_opt(fullname, opts, FALSE))) {
-            return;
+            return FALSE;
         }
-        if (!assign_videoshades(opts))
-            badoption(opts);
-        return;
+        if (!assign_videoshades(opts)) /* TODO: error msg */
+            return FALSE;
+        return retval;
     }
 #endif /* VIDEOSHADES */
 #ifdef MSDOS
@@ -3014,13 +3084,13 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if (!(opts = string_for_env_opt(fullname, opts, FALSE))) {
-            return;
+            return FALSE;
         }
-        if (!assign_video(opts))
-            badoption(opts);
-        return;
+        if (!assign_video(opts)) /* TODO: error msg */
+            return FALSE;
+        return retval;
     }
 #endif /* NO_TERMS */
     /* soundcard:string -- careful not to match boolean 'sound' */
@@ -3030,13 +3100,13 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if (!(opts = string_for_env_opt(fullname, opts, FALSE))) {
-            return;
+            return FALSE;
         }
-        if (!assign_soundcard(opts))
-            badoption(opts);
-        return;
+        if (!assign_soundcard(opts)) /* TODO: error msg */
+            return FALSE;
+        return retval;
     }
 #endif /* MSDOS */
 
@@ -3074,11 +3144,15 @@ boolean tinitial, tfrom_file;
             else if (!strncmpi(op, "fit_to_screen",
                                sizeof("fit_to_screen") - 1))
                 iflags.wc_map_mode = MAP_MODE_ASCII_FIT_TO_SCREEN;
-            else
-                badoption(opts);
-        } else if (negated)
+            else {
+                config_error_add("Unknown %s parameter '%s'", fullname, op);
+                return FALSE;
+            }
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
     /* WINCAP
      * scroll_amount:nn */
@@ -3089,9 +3163,11 @@ boolean tinitial, tfrom_file;
         op = string_for_opt(opts, negated);
         if ((negated && !op) || (!negated && op)) {
             iflags.wc_scroll_amount = negated ? 1 : atoi(op);
-        } else if (negated)
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
     /* WINCAP
      * scroll_margin:nn */
@@ -3102,22 +3178,25 @@ boolean tinitial, tfrom_file;
         op = string_for_opt(opts, negated);
         if ((negated && !op) || (!negated && op)) {
             iflags.wc_scroll_margin = negated ? 5 : atoi(op);
-        } else if (negated)
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
     fullname = "subkeyvalue";
     if (match_optname(opts, fullname, 5, TRUE)) {
         /* no duplicate complaint here */
         if (negated) {
             bad_negation(fullname, FALSE);
+            return FALSE;
         } else {
 #if defined(WIN32)
             op = string_for_opt(opts, 0);
             map_subkeyvalue(op);
 #endif
         }
-        return;
+        return retval;
     }
     /* WINCAP
      * tile_width:nn */
@@ -3128,9 +3207,11 @@ boolean tinitial, tfrom_file;
         op = string_for_opt(opts, negated);
         if ((negated && !op) || (!negated && op)) {
             iflags.wc_tile_width = negated ? 0 : atoi(op);
-        } else if (negated)
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
     /* WINCAP
      * tile_file:name */
@@ -3143,7 +3224,7 @@ boolean tinitial, tfrom_file;
                 free(iflags.wc_tile_file);
             iflags.wc_tile_file = dupstr(op);
         }
-        return;
+        return retval;
     }
     /* WINCAP
      * tile_height:nn */
@@ -3154,9 +3235,11 @@ boolean tinitial, tfrom_file;
         op = string_for_opt(opts, negated);
         if ((negated && !op) || (!negated && op)) {
             iflags.wc_tile_height = negated ? 0 : atoi(op);
-        } else if (negated)
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
     /* WINCAP
      * vary_msgcount:nn */
@@ -3167,9 +3250,11 @@ boolean tinitial, tfrom_file;
         op = string_for_opt(opts, negated);
         if ((negated && !op) || (!negated && op)) {
             iflags.wc_vary_msgcount = negated ? 0 : atoi(op);
-        } else if (negated)
+        } else if (negated) {
             bad_negation(fullname, TRUE);
-        return;
+            return FALSE;
+        }
+        return retval;
     }
     fullname = "windowtype";
     if (match_optname(opts, fullname, 3, TRUE)) {
@@ -3177,26 +3262,26 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0) {
             char buf[WINTYPELEN];
             nmcpy(buf, op, WINTYPELEN);
             choose_windows(buf);
         }
-        return;
+        return retval;
     }
 #ifdef WINCHAIN
     fullname = "windowchain";
     if (match_optname(opts, fullname, 3, TRUE)) {
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0) {
             char buf[WINTYPELEN];
             nmcpy(buf, op, WINTYPELEN);
             addto_windowchain(buf);
         }
-        return;
+        return retval;
     }
 #endif
 
@@ -3209,11 +3294,11 @@ boolean tinitial, tfrom_file;
         if (duplicate)
             complain_about_duplicate(opts, 1);
         if ((op = string_for_opt(opts, FALSE)) != 0) {
-            if (!wc_set_window_colors(op))
-                badoption(opts);
+            if (!wc_set_window_colors(op)) /* TODO: error msg*/
+                return FALSE;
         } else if (negated)
             bad_negation(fullname, TRUE);
-        return;
+        return retval;
     }
 
     /* menustyle:traditional or combination or full or partial */
@@ -3225,7 +3310,7 @@ boolean tinitial, tfrom_file;
             complain_about_duplicate(opts, 1);
         if (!(op = string_for_opt(opts, !val_required))) {
             if (val_required)
-                return; /* string_for_opt gave feedback */
+                return FALSE; /* string_for_opt gave feedback */
             tmp = negated ? 'n' : 'f';
         } else {
             tmp = lowc(*op);
@@ -3249,28 +3334,29 @@ boolean tinitial, tfrom_file;
             flags.menu_style = MENU_PARTIAL;
             break;
         default:
-            badoption(opts);
+            config_error_add("Unknown %s parameter '%s'", "menustyle", op);
+            return FALSE;
         }
-        return;
+        return retval;
     }
 
     fullname = "menu_headings";
     if (match_optname(opts, fullname, 12, TRUE)) {
+        int tmpattr;
         if (duplicate)
             complain_about_duplicate(opts, 1);
         if (negated) {
             bad_negation(fullname, FALSE);
-            return;
+            return FALSE;
         } else if (!(opts = string_for_env_opt(fullname, opts, FALSE))) {
-            return;
+            return FALSE;
         }
-        for (i = 0; i < SIZE(attrnames); i++)
-            if (!strcmpi(opts, attrnames[i].name)) {
-                iflags.menu_headings = attrnames[i].attr;
-                return;
-            }
-        badoption(opts);
-        return;
+        tmpattr = match_str2attr(opts);
+        if (tmpattr == -1)
+            return FALSE;
+        else
+            iflags.menu_headings = tmpattr;
+        return retval;
     }
 
     /* check for menu command mapping */
@@ -3287,12 +3373,13 @@ boolean tinitial, tfrom_file;
                 escapes(op, op_buf);
                 c = *op_buf;
 
-                if (illegal_menu_cmd_key(c))
-                    badoption(opts);
-                else
+                if (illegal_menu_cmd_key(c)) {
+                    /* TODO FIXME */
+                    return FALSE;
+                } else
                     add_menu_cmd_alias(c, default_menu_cmd_info[i].cmd);
             }
-            return;
+            return retval;
         }
     }
 #if defined(STATUS_VIA_WINDOWPORT) && defined(STATUS_HILITES)
@@ -3303,15 +3390,14 @@ boolean tinitial, tfrom_file;
         op = string_for_opt(opts, TRUE);
         if (op && negated) {
             clear_status_hilites(tfrom_file);
-            return;
+            return retval;
         } else if (!op) {
-            /* a value is mandatory */
-            badoption(opts);
-            return;
+            config_error_add("Value is mandatory for hilite_status");
+            return FALSE;
         }
-        if (!set_status_hilites(op, tfrom_file))
-            badoption(opts);
-        return;
+        if (!set_status_hilites(op, tfrom_file)) /* TODO: error msg? */
+            return FALSE;
+        return retval;
     }
 #endif
 
@@ -3339,7 +3425,7 @@ boolean tinitial, tfrom_file;
                 wait_synch();
             }
         }
-        return;
+        return retval;
     }
     fullname = "IBMgraphics";
     if (match_optname(opts, fullname, 3, TRUE)) {
@@ -3372,7 +3458,7 @@ boolean tinitial, tfrom_file;
                     assign_graphics(ROGUESET);
             }
         }
-        return;
+        return retval;
     }
 #endif
 #ifdef MAC_GRAPHICS_ENV
@@ -3401,7 +3487,7 @@ boolean tinitial, tfrom_file;
                     assign_graphics(ROGUESET);
             }
         }
-        return;
+        return retval;
     }
 #endif
 
@@ -3415,28 +3501,28 @@ boolean tinitial, tfrom_file;
                 if (!initial && !negated)
                     pline_The("\"%s\" option is not available.",
                               boolopt[i].name);
-                return;
+                return retval;
             }
             /* options that must come from config file */
             if (!initial && (boolopt[i].optflags == SET_IN_FILE)) {
                 rejectoption(boolopt[i].name);
-                return;
+                return retval;
             }
 
             op = string_for_opt(opts, TRUE);
 
             if (op) {
                 if (negated) {
-                    badoption(opts);
-                    return;
+                    config_error_add("Negated boolean '%s' should not have a parameter", boolopt[i].name);
+                    return FALSE;
                 }
                 if (!strcmp(op, "true") || !strcmp(op, "yes")) {
                     negated = FALSE;
                 } else if (!strcmp(op, "false") || !strcmp(op, "no")) {
                     negated = TRUE;
                 } else {
-                    badoption(opts);
-                    return;
+                    config_error_add("Illegal parameter for a boolean");
+                    return FALSE;
                 }
             }
 
@@ -3455,7 +3541,7 @@ boolean tinitial, tfrom_file;
 #endif
             /* only do processing below if setting with doset() */
             if (initial)
-                return;
+                return retval;
 
             if (boolopt[i].addr == &flags.time
 #ifdef SCORE_ON_BOTL
@@ -3501,69 +3587,73 @@ boolean tinitial, tfrom_file;
 #endif
 #endif /* TEXTCOLOR */
             }
-            return;
+            return retval;
         }
     }
 
     /* Is it a symbol? */
     if (strstr(opts, "S_") == opts && parsesymbols(opts)) {
         switch_symbols(TRUE);
-        return;
+        return retval;
     }
 
     /* out of valid options */
-    badoption(opts);
+    config_error_add("Unknown option '%s'", opts);
+    return FALSE;
 }
 
 /* parse key:command */
-void
+boolean
 parsebindings(bindings)
 char* bindings;
 {
     char *bind;
     char key;
     int i;
+    boolean ret = FALSE;
 
     /* break off first binding from the rest; parse the rest */
     if ((bind = index(bindings, ',')) != 0) {
         *bind++ = 0;
-        parsebindings(bind);
+        ret |= parsebindings(bind);
     }
 
     /* parse a single binding: first split around : */
     if (! (bind = index(bindings, ':')))
-        return; /* it's not a binding */
+        return FALSE; /* it's not a binding */
     *bind++ = 0;
 
     /* read the key to be bound */
     key = txt2key(bindings);
     if (!key) {
-        raw_printf("Bad binding %s.", bindings);
-        wait_synch();
-        return;
+        config_error_add("Unknown key binding key '%s'", bindings);
+        return FALSE;
     }
 
     bind = trimspaces(bind);
 
     /* is it a special key? */
     if (bind_specialkey(key, bind))
-        return;
+        return TRUE;
 
     /* is it a menu command? */
     for (i = 0; i < SIZE(default_menu_cmd_info); i++) {
         if (!strcmp(default_menu_cmd_info[i].name, bind)) {
             if (illegal_menu_cmd_key(key)) {
-                char tmp[BUFSZ];
-                Sprintf(tmp, "Bad menu key %s:%s", visctrl(key), bind);
-                badoption(tmp);
+                config_error_add("Bad menu key %s:%s", visctrl(key), bind);
+                return FALSE;
             } else
                 add_menu_cmd_alias(key, default_menu_cmd_info[i].cmd);
-            return;
+            return TRUE;
         }
     }
 
     /* extended command? */
-    bind_key(key, bind);
+    if (!bind_key(key, bind)) {
+        config_error_add("Unknown key binding command '%s'", bind);
+        return FALSE;
+    }
+    return TRUE;
 }
 
 static NEARDATA const char *menutype[] = { "traditional", "combination",