OSDN Git Service

update year to 2018
[jnethack/source.git] / src / files.c
index dcc5663..b57df26 100644 (file)
@@ -1,7 +1,15 @@
-/* NetHack 3.6 files.c $NHDT-Date: 1449296293 2015/12/05 06:18:13 $  $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.192 $ */
+/* NetHack 3.6 files.c $NHDT-Date: 1524413723 2018/04/22 16:15:23 $  $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.235 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
+/*-Copyright (c) Derek S. Ray, 2015. */
 /* NetHack may be freely redistributed.  See license for details. */
 
+#define NEED_VARARGS
+
+/* JNetHack Copyright */
+/* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000  */
+/* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2018            */
+/* JNetHack may be freely redistributed.  See license for details. */
+
 #include "hack.h"
 #include "dlb.h"
 
@@ -188,15 +196,24 @@ STATIC_DCL boolean FDECL(make_compressed_name, (const char *, char *));
 #ifndef USE_FCNTL
 STATIC_DCL char *FDECL(make_lockname, (const char *, char *));
 #endif
+STATIC_DCL void FDECL(set_configfile_name, (const char *));
 STATIC_DCL FILE *FDECL(fopen_config_file, (const char *, int));
-STATIC_DCL int FDECL(get_uchars, (FILE *, char *, char *, uchar *, BOOLEAN_P,
+STATIC_DCL int FDECL(get_uchars, (char *, uchar *, BOOLEAN_P,
                                   int, const char *));
-int FDECL(parse_config_line, (FILE *, char *, int));
+boolean FDECL(proc_wizkit_line, (char *));
+boolean FDECL(parse_config_line, (char *));
+STATIC_DCL boolean FDECL(parse_conf_file, (FILE *, boolean (*proc)(char *)));
 STATIC_DCL FILE *NDECL(fopen_sym_file);
+boolean FDECL(proc_symset_line, (char *));
 STATIC_DCL void FDECL(set_symhandling, (char *, int));
 #ifdef NOCWD_ASSUMPTIONS
 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
 #endif
+STATIC_DCL boolean 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 *));
+STATIC_DCL boolean FDECL(handle_config_section, (char *));
 #ifdef SELF_RECOVER
 STATIC_DCL boolean FDECL(copy_bytes, (int, int));
 #endif
@@ -204,6 +221,10 @@ STATIC_DCL boolean FDECL(copy_bytes, (int, int));
 STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int));
 #endif
 
+
+static char *config_section_chosen = (char *) 0;
+static char *config_section_current = (char *) 0;
+
 /*
  * fname_encode()
  *
@@ -628,6 +649,7 @@ const char *name;
 int lev, oflag;
 {
     int reslt, fd;
+
     if (!lftrack.init) {
         lftrack.init = 1;
         lftrack.fd = -1;
@@ -660,13 +682,17 @@ int lev, oflag;
 void
 really_close()
 {
-    int fd = lftrack.fd;
+    int fd;
+    
+    if (lftrack.init) {
+        fd = lftrack.fd;
 
-    lftrack.nethack_thinks_it_is_open = FALSE;
-    lftrack.fd = -1;
-    lftrack.oflag = 0;
-    if (fd != -1)
-        (void) close(fd);
+        lftrack.nethack_thinks_it_is_open = FALSE;
+        lftrack.fd = -1;
+        lftrack.oflag = 0;
+        if (fd != -1)
+            (void) close(fd);
+    }
     return;
 }
 
@@ -682,7 +708,7 @@ int fd;
     }
     return close(fd);
 }
-#else
+#else /* !HOLD_LOCKFILE_OPEN */
 
 int
 nhclose(fd)
@@ -690,7 +716,7 @@ int fd;
 {
     return close(fd);
 }
-#endif
+#endif /* ?HOLD_LOCKFILE_OPEN */
 
 /* ----------  END LEVEL FILE HANDLING ----------- */
 
@@ -707,17 +733,47 @@ d_level *lev;
     s_level *sptr;
     char *dptr;
 
-    Sprintf(file, "bon%c%s", dungeons[lev->dnum].boneid,
+    /*
+     * "bonD0.nn"   = bones for level nn in the main dungeon;
+     * "bonM0.T"    = bones for Minetown;
+     * "bonQBar.n"  = bones for level n in the Barbarian quest;
+     * "bon3D0.nn"  = \
+     * "bon3M0.T"   =  > same as above, but for bones pool #3.
+     * "bon3QBar.n" = /
+     *
+     * Return value for content validation skips "bon" and the
+     * pool number (if present), making it feasible for the admin
+     * to manually move a bones file from one pool to another by
+     * renaming it.
+     */
+    Strcpy(file, "bon");
+#ifdef SYSCF
+    if (sysopt.bones_pools > 1) {
+        unsigned poolnum = min((unsigned) sysopt.bones_pools, 10);
+
+        poolnum = (unsigned) ubirthday % poolnum; /* 0..9 */
+        Sprintf(eos(file), "%u", poolnum);
+    }
+#endif
+    dptr = eos(file); /* this used to be after the following Sprintf()
+                         and the return value was (dptr - 2) */
+    /* when this naming scheme was adopted, 'filecode' was one letter;
+       3.3.0 turned it into a three letter string (via roles[] in role.c);
+       from that version through 3.6.0, 'dptr' pointed past the filecode
+       and the return value of (dptr - 2)  was wrong for bones produced
+       in the quest branch, skipping the boneid character 'Q' and the
+       first letter of the role's filecode; bones loading still worked
+       because the bonesid used for validation had the same error */
+    Sprintf(dptr, "%c%s", dungeons[lev->dnum].boneid,
             In_quest(lev) ? urole.filecode : "0");
-    dptr = eos(file);
     if ((sptr = Is_special(lev)) != 0)
-        Sprintf(dptr, ".%c", sptr->boneid);
+        Sprintf(eos(dptr), ".%c", sptr->boneid);
     else
-        Sprintf(dptr, ".%d", lev->dlevel);
+        Sprintf(eos(dptr), ".%d", lev->dlevel);
 #ifdef VMS
     Strcat(dptr, ";1");
 #endif
-    return (dptr - 2);
+    return dptr;
 }
 
 /* set up temporary file name for writing bones, to avoid another game's
@@ -925,7 +981,10 @@ void
 save_savefile_name(fd)
 int fd;
 {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
     (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF));
+#pragma GCC diagnostic pop
 }
 #endif
 
@@ -1215,7 +1274,7 @@ boolean uncomp;
     if (freopen(filename, mode, stream) == (FILE *) 0) {
         (void) fprintf(stderr, "freopen of %s for %scompress failed\n",
                        filename, uncomp ? "un" : "");
-        terminate(EXIT_FAILURE);
+        nh_terminate(EXIT_FAILURE);
     }
 }
 
@@ -1315,13 +1374,16 @@ boolean uncomp;
             redirect(filename, RDBMODE, stdin, uncomp);
             redirect(cfn, WRBMODE, stdout, uncomp);
         }
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
         (void) setgid(getgid());
         (void) setuid(getuid());
+#pragma GCC diagnostic pop
         (void) execv(args[0], (char *const *) args);
         perror((char *) 0);
         (void) fprintf(stderr, "Exec to %scompress %s failed.\n",
                        uncomp ? "un" : "", filename);
-        terminate(EXIT_FAILURE);
+        nh_terminate(EXIT_FAILURE);
     } else if (f == -1) {
         perror((char *) 0);
         pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename);
@@ -1653,8 +1715,10 @@ int retryct;
 #ifdef USE_FCNTL
     lockfd = open(filename, O_RDWR);
     if (lockfd == -1) {
-        HUP raw_printf("Cannot open file %s. This is a program bug.",
+        HUP raw_printf("Cannot open file %s. Is NetHack installed correctly?",
                        filename);
+        nesting--;
+        return FALSE;
     }
     sflock.l_type = F_WRLCK;
     sflock.l_whence = SEEK_SET;
@@ -1833,7 +1897,7 @@ const char *filename;
 
 /* ----------  BEGIN CONFIG FILE HANDLING ----------- */
 
-const char *configfile =
+const char *default_configfile =
 #ifdef UNIX
     ".nethackrc";
 #else
@@ -1849,7 +1913,7 @@ const char *configfile =
 #endif
 
 /* used for messaging */
-char lastconfigfile[BUFSZ];
+char configfile[BUFSZ];
 
 #ifdef MSDOS
 /* conflict with speed-dial under windows
@@ -1862,6 +1926,16 @@ char lastconfigfile[BUFSZ];
 const char *backward_compat_configfile = "nethack.cnf";
 #endif
 
+/* remember the name of the file we're accessing;
+   if may be used in option reject messages */
+STATIC_OVL void
+set_configfile_name(fname)
+const char *fname;
+{
+    (void) strncpy(configfile, fname, sizeof configfile - 1);
+    configfile[sizeof configfile - 1] = '\0';
+}
+
 #ifndef MFLOPPY
 #define fopenp fopen
 #endif
@@ -1877,84 +1951,86 @@ int src;
     char *envp;
 #endif
 
+    if (src == SET_IN_SYS) {
+        /* SYSCF_FILE; if we can't open it, caller will bail */
+        if (filename && *filename) {
+            set_configfile_name(fqname(filename, SYSCONFPREFIX, 0));
+            fp = fopenp(configfile, "r");
+        } else
+            fp = (FILE *) 0;
+        return  fp;
+    }
     /* If src != SET_IN_SYS, "filename" is an environment variable, so it
      * should hang around. If set, it is expected to be a full path name
-     * (if relevant) */
-    if (filename) {
+     * (if relevant)
+     */
+    if (filename && *filename) {
+        set_configfile_name(filename);
 #ifdef UNIX
-        if ((src != SET_IN_SYS) && access(filename, 4) == -1) {
-            /* 4 is R_OK on newer systems */
+        if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */
             /* nasty sneaky attempt to read file through
              * NetHack's setuid permissions -- this is the only
              * place a file name may be wholly under the player's
              * control (but SYSCF_FILE is not under the player's
              * control so it's OK).
              */
-            raw_printf("Access to %s denied (%d).", filename, errno);
+            raw_printf("Access to %s denied (%d).", configfile, errno);
             wait_synch();
             /* fall through to standard names */
         } else
 #endif
-#ifdef PREFIXES_IN_USE
-            if (src == SET_IN_SYS) {
-            (void) strncpy(lastconfigfile, fqname(filename, SYSCONFPREFIX, 0),
-                           BUFSZ - 1);
-        } else
-#endif
-            /* always honor sysconf first before anything else */
-            (void) strncpy(lastconfigfile, filename, BUFSZ - 1);
-        lastconfigfile[BUFSZ - 1] = '\0';
-        if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
-            return  fp;
-        if ((fp = fopenp(filename, "r")) != (FILE *) 0) {
+        if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
             return  fp;
 #if defined(UNIX) || defined(VMS)
         } else {
             /* access() above probably caught most problems for UNIX */
             raw_printf("Couldn't open requested config file %s (%d).",
-                       filename, errno);
+                       configfile, errno);
             wait_synch();
-/* fall through to standard names */
 #endif
         }
     }
+    /* fall through to standard names */
 
 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
-    if ((fp = fopenp(fqname(configfile, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
-        return fp;
-    if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
+    set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0));
+    if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
         return fp;
+    } else if (strcmp(default_configfile, configfile)) {
+        set_configfile_name(default_configfile);
+        if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
+            return fp;
+    }
 #ifdef MSDOS
-    if ((fp = fopenp(fqname(backward_compat_configfile, CONFIGPREFIX, 0),
-                     "r")) != (FILE *) 0)
-        return fp;
-    if ((fp = fopenp(backward_compat_configfile, "r")) != (FILE *) 0)
+    set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0));
+    if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
         return fp;
+    } else if (strcmp(backward_compat_configfile, configfile)) {
+        set_configfile_name(backward_compat_configfile);
+        if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
+            return fp;
+    }
 #endif
 #else
 /* constructed full path names don't need fqname() */
 #ifdef VMS
-    (void) strncpy(lastconfigfile, fqname("nethackini", CONFIGPREFIX, 0),
-                   BUFSZ - 1);
-    lastconfigfile[BUFSZ - 1] = '\0';
-    if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0) {
+    /* no punctuation, so might be a logical name */
+    set_configfile_name(fqname("nethackini", CONFIGPREFIX, 0));
+    if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
         return fp;
-    }
-    (void) strncpy(lastconfigfile, "sys$login:nethack.ini", BUFSZ - 1);
-    lastconfigfile[BUFSZ - 1] = '\0';
-    if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0) {
+    set_configfile_name("sys$login:nethack.ini");
+    if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
         return fp;
-    }
 
     envp = nh_getenv("HOME");
-    if (!envp)
+    if (!envp || !*envp)
         Strcpy(tmp_config, "NetHack.cnf");
     else
-        Sprintf(tmp_config, "%s%s", envp, "NetHack.cnf");
-
-    (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
-    lastconfigfile[BUFSZ - 1] = '\0';
-    if ((fp = fopenp(tmp_config, "r")) != (FILE *) 0)
+        Sprintf(tmp_config, "%s%s%s", envp,
+                !index(":]>/", envp[strlen(envp) - 1]) ? "/" : "",
+                "NetHack.cnf");
+    set_configfile_name(tmp_config);
+    if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
         return fp;
 #else /* should be only UNIX left */
     envp = nh_getenv("HOME");
@@ -1964,8 +2040,8 @@ int src;
     else
         Sprintf(tmp_config, "%s/%s", envp, ".jnethackrc");
 
-    (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
-    if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
+    set_configfile_name(tmp_config);
+    if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
         return fp;
 #endif
     if (!envp)
@@ -1973,56 +2049,52 @@ int src;
     else
         Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
 
-    (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
-    lastconfigfile[BUFSZ - 1] = '\0';
-    if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
+    set_configfile_name(tmp_config);
+    if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
         return fp;
-#if defined(__APPLE__)
+#if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX */
     /* try an alternative */
     if (envp) {
+        /* OSX-style configuration settings */
         Sprintf(tmp_config, "%s/%s", envp,
                 "Library/Preferences/NetHack Defaults");
-        (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
-        lastconfigfile[BUFSZ - 1] = '\0';
-        if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
+        set_configfile_name(tmp_config);
+        if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
             return fp;
+        /* may be easier for user to edit if filename as '.txt' suffix */
         Sprintf(tmp_config, "%s/%s", envp,
                 "Library/Preferences/NetHack Defaults.txt");
-        (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
-        lastconfigfile[BUFSZ - 1] = '\0';
-        if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
+        set_configfile_name(tmp_config);
+        if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
             return fp;
     }
-#endif
+#endif /*__APPLE__*/
     if (errno != ENOENT) {
         const char *details;
 
-/* e.g., problems when setuid NetHack can't search home
- * directory restricted to user */
-
+        /* e.g., problems when setuid NetHack can't search home
+           directory restricted to user */
 #if defined(NHSTDC) && !defined(NOTSTDC)
         if ((details = strerror(errno)) == 0)
 #endif
             details = "";
         raw_printf("Couldn't open default config file %s %s(%d).",
-                   lastconfigfile, details, errno);
+                   configfile, details, errno);
         wait_synch();
     }
-#endif /* Unix */
-#endif
+#endif /* !VMS => Unix */
+#endif /* !(MICRO || MAC || __BEOS__ || WIN32) */
     return (FILE *) 0;
 }
 
 /*
- * Retrieve a list of integers from a file into a uchar array.
+ * Retrieve a list of integers from buf into a uchar array.
  *
  * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
  *  location is unchanged.  Callers must handle zeros if modlist is FALSE.
  */
 STATIC_OVL int
-get_uchars(fp, buf, bufp, list, modlist, size, name)
-FILE *fp;         /* input file pointer */
-char *buf;        /* read buffer, must be of size BUFSZ */
+get_uchars(bufp, list, modlist, size, name)
 char *bufp;       /* current pointer */
 uchar *list;      /* return list */
 boolean modlist;  /* TRUE: list is being modified in place */
@@ -2068,13 +2140,7 @@ const char *name; /* name of option for error message */
             break;
 
         case '\\':
-            if (fp == (FILE *) 0)
-                goto gi_error;
-            do {
-                if (!fgets(buf, BUFSZ, fp))
-                    goto gi_error;
-            } while (buf[0] == '#');
-            bufp = buf;
+            goto gi_error;
             break;
 
         default:
@@ -2108,13 +2174,111 @@ int prefixid;
 }
 #endif
 
+/* Choose at random one of the sep separated parts from str. Mangles str. */
+STATIC_OVL char *
+choose_random_part(str,sep)
+char *str;
+char sep;
+{
+    int nsep = 1;
+    int csep;
+    int len = 0;
+    char *begin = str;
+
+    if (!str)
+        return (char *) 0;
+
+    while (*str) {
+       if (*str == sep) nsep++;
+       str++;
+    }
+    csep = rn2(nsep);
+    str = begin;
+    while ((csep > 0) && *str) {
+       str++;
+       if (*str == sep) csep--;
+    }
+    if (*str) {
+       if (*str == sep) str++;
+       begin = str;
+       while (*str && *str != sep) {
+           str++;
+           len++;
+       }
+       *str = '\0';
+       if (len)
+            return begin;
+    }
+    return (char *) 0;
+}
+
+STATIC_OVL void
+free_config_sections()
+{
+    if (config_section_chosen) {
+        free(config_section_chosen);
+        config_section_chosen = NULL;
+    }
+    if (config_section_current) {
+        free(config_section_current);
+        config_section_current = NULL;
+    }
+}
+
+STATIC_OVL boolean
+is_config_section(str)
+const char *str;
+{
+    const char *a = rindex(str, ']');
+
+    return (a && *str == '[' && *(a+1) == '\0' && (int)(a - str) > 0);
+}
+
+STATIC_OVL boolean
+handle_config_section(buf)
+char *buf;
+{
+    if (is_config_section(buf)) {
+        char *send;
+        if (config_section_current) {
+            free(config_section_current);
+        }
+        config_section_current = dupstr(&buf[1]);
+        send = rindex(config_section_current, ']');
+        *send = '\0';
+        debugpline1("set config section: '%s'", config_section_current);
+        return TRUE;
+    }
+
+    if (config_section_current) {
+        if (!config_section_chosen)
+            return TRUE;
+        if (strcmp(config_section_current, config_section_chosen))
+            return TRUE;
+    }
+    return FALSE;
+}
+
 #define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
 
-int
-parse_config_line(fp, origbuf, src)
-FILE *fp;
+/* find the '=' or ':' */
+char *
+find_optparam(buf)
+const char *buf;
+{
+    char *bufp, *altp;
+
+    bufp = index(buf, '=');
+    altp = index(buf, ':');
+    if (!bufp || (altp && altp < bufp))
+        bufp = altp;
+
+    return bufp;
+}
+
+boolean
+parse_config_line(origbuf)
 char *origbuf;
-int src;
 {
 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
     static boolean ramdisk_specified = FALSE;
@@ -2122,25 +2286,23 @@ int src;
 #ifdef SYSCF
     int n;
 #endif
-    char *bufp, *altp, buf[BUFSZ];
+    char *bufp, buf[4 * BUFSZ];
     uchar translate[MAXPCHARS];
     int len;
+    boolean retval = TRUE;
+    int src = iflags.parse_config_file_src;
 
     /* convert any tab to space, condense consecutive spaces into one,
        remove leading and trailing spaces (exception: if there is nothing
        but spaces, one of them will be kept even though it leads/trails) */
     mungspaces(strcpy(buf, origbuf));
-    /* lines beginning with '#' are comments; accept empty lines too */
-    if (!*buf || *buf == '#' || !strcmp(buf, " "))
-        return 1;
 
     /* find the '=' or ':' */
-    bufp = index(buf, '=');
-    altp = index(buf, ':');
-    if (!bufp || (altp && altp < bufp))
-        bufp = altp;
-    if (!bufp)
-        return 0;
+    bufp = find_optparam(buf);
+    if (!bufp) {
+        config_error_add("Not a config statement, missing '='");
+        return FALSE;
+    }
     /* skip past '=', then space between it and value, if any */
     ++bufp;
     if (*bufp == ' ')
@@ -2154,19 +2316,21 @@ int src;
         /* hack: un-mungspaces to allow consecutive spaces in
            general options until we verify that this is unnecessary;
            '=' or ':' is guaranteed to be present */
-        bufp = index(origbuf, '=');
-        altp = index(origbuf, ':');
-        if (!bufp || (altp && altp < bufp))
-            bufp = altp;
+        bufp = find_optparam(origbuf);
         ++bufp; /* skip '='; parseoptions() handles spaces */
 
-        parseoptions(bufp, TRUE, TRUE);
-        if (plname[0])      /* If a name was given */
-            plnamesuffix(); /* set the character class */
+        if (!parseoptions(bufp, TRUE, TRUE))
+            retval = FALSE;
     } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
         add_autopickup_exception(bufp);
+    } else if (match_varname(buf, "BINDINGS", 4)) {
+        if (!parsebindings(bufp))
+            retval = FALSE;
+    } 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 = FALSE;
 #ifdef NOCWD_ASSUMPTIONS
     } else if (match_varname(buf, "HACKDIR", 4)) {
         adjust_prefix(bufp, HACKPREFIX);
@@ -2237,7 +2401,6 @@ int src;
 
     } else if (match_varname(buf, "NAME", 4)) {
         (void) strncpy(plname, bufp, PL_NSIZ - 1);
-        plnamesuffix();
     } else if (match_varname(buf, "ROLE", 4)
                || match_varname(buf, "CHARACTER", 4)) {
         if ((len = str2role(bufp)) >= 0)
@@ -2277,6 +2440,24 @@ int src;
                 free((genericptr_t) sysopt.debugfiles);
             sysopt.debugfiles = dupstr(bufp);
         }
+    } else if (src == SET_IN_SYS && match_varname(buf, "DUMPLOGFILE", 7)) {
+#ifdef DUMPLOG
+        if (sysopt.dumplogfile)
+            free((genericptr_t) sysopt.dumplogfile);
+        sysopt.dumplogfile = dupstr(bufp);
+#endif
+    } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) {
+        if (sysopt.genericusers)
+            free((genericptr_t) sysopt.genericusers);
+        sysopt.genericusers = dupstr(bufp);
+    } else if (src == SET_IN_SYS && match_varname(buf, "BONES_POOLS", 10)) {
+        /* max value of 10 guarantees (N % bones.pools) will be one digit
+           so we don't lose control of the length of bones file names */
+        n = atoi(bufp);
+        sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10);
+        /* note: right now bones_pools==0 is the same as bones_pools==1,
+           but we could change that and make bones_pools==0 become an
+           indicator to suppress bones usage altogether */
     } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
         if (sysopt.support)
             free((genericptr_t) sysopt.support);
@@ -2289,58 +2470,62 @@ int src;
                && match_varname(buf, "CHECK_SAVE_UID", 14)) {
         n = atoi(bufp);
         sysopt.check_save_uid = n;
+    } else if (src == SET_IN_SYS
+               && match_varname(buf, "CHECK_PLNAME", 12)) {
+        n = atoi(bufp);
+        sysopt.check_plname = n;
     } else if (match_varname(buf, "SEDUCE", 6)) {
         n = !!atoi(bufp); /* XXX this could be tighter */
         /* allow anyone to turn it off, but only sysconf to turn it on*/
         if (src != SET_IN_SYS && n != 0) {
-            raw_printf("Illegal value in SEDUCE");
-            return 0;
+            config_error_add("Illegal value in SEDUCE");
+            return FALSE;
         }
         sysopt.seduce = n;
         sysopt_seduce_set(sysopt.seduce);
     } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
         n = atoi(bufp);
         /* XXX to get more than 25, need to rewrite all lock code */
-        if (n < 1 || n > 25) {
-            raw_printf("Illegal value in MAXPLAYERS (maximum is 25).");
-            return 0;
+        if (n < 0 || n > 25) {
+            config_error_add("Illegal value in MAXPLAYERS (maximum is 25).");
+            return FALSE;
         }
         sysopt.maxplayers = n;
     } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
         n = atoi(bufp);
         if (n < 1) {
-            raw_printf("Illegal value in PERSMAX (minimum is 1).");
-            return 0;
+            config_error_add("Illegal value in PERSMAX (minimum is 1).");
+            return FALSE;
         }
         sysopt.persmax = n;
     } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
         n = atoi(bufp);
         if (n != 0 && n != 1) {
-            raw_printf("Illegal value in PERS_IS_UID (must be 0 or 1).");
-            return 0;
+            config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1).");
+            return FALSE;
         }
         sysopt.pers_is_uid = n;
     } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
         n = atoi(bufp);
         if (n < 10) {
-            raw_printf("Illegal value in ENTRYMAX (minimum is 10).");
-            return 0;
+            config_error_add("Illegal value in ENTRYMAX (minimum is 10).");
+            return FALSE;
         }
         sysopt.entrymax = n;
     } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
         n = atoi(bufp);
         if (n < 1) {
-            raw_printf("Illegal value in POINTSMIN (minimum is 1).");
-            return 0;
+            config_error_add("Illegal value in POINTSMIN (minimum is 1).");
+            return FALSE;
         }
         sysopt.pointsmin = n;
     } else if (src == SET_IN_SYS
                && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
         n = atoi(bufp);
         if (n < 1) {
-            raw_printf(
+            config_error_add(
                 "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
-            return 0;
+            return FALSE;
         }
         sysopt.tt_oname_maxrank = n;
 
@@ -2350,8 +2535,8 @@ int src;
         n = atoi(bufp);
 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
         if (n < 0 || n > 2) {
-            raw_printf("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
-            return 0;
+            config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
+            return FALSE;
         }
 #endif
         sysopt.panictrace_libc = n;
@@ -2360,16 +2545,16 @@ int src;
         n = atoi(bufp);
 #if defined(PANICTRACE)
         if (n < 0 || n > 2) {
-            raw_printf("Illegal value in PANICTRACE_GDB (not 0,1,2).");
-            return 0;
+            config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2).");
+            return FALSE;
         }
 #endif
         sysopt.panictrace_gdb = n;
     } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
 #if defined(PANICTRACE) && !defined(VMS)
         if (!file_exists(bufp)) {
-            raw_printf("File specified in GDBPATH does not exist.");
-            return 0;
+            config_error_add("File specified in GDBPATH does not exist.");
+            return FALSE;
         }
 #endif
         if (sysopt.gdbpath)
@@ -2378,8 +2563,8 @@ int src;
     } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
 #if defined(PANICTRACE) && !defined(VMS)
         if (!file_exists(bufp)) {
-            raw_printf("File specified in GREPPATH does not exist.");
-            return 0;
+            config_error_add("File specified in GREPPATH does not exist.");
+            return FALSE;
         }
 #endif
         if (sysopt.greppath)
@@ -2388,42 +2573,25 @@ int src;
 #endif /* SYSCF */
 
     } else if (match_varname(buf, "BOULDER", 3)) {
-        (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE, 1,
+        (void) get_uchars(bufp, &iflags.bouldersym, TRUE, 1,
                           "BOULDER");
     } else if (match_varname(buf, "MENUCOLOR", 9)) {
-        (void) add_menu_coloring(bufp);
+        if (!add_menu_coloring(bufp))
+            retval = FALSE;
+    } else if (match_varname(buf, "HILITE_STATUS", 6)) {
+#ifdef STATUS_HILITES
+        if (!parse_status_hl1(bufp, TRUE))
+            retval = FALSE;
+#endif
     } else if (match_varname(buf, "WARNINGS", 5)) {
-        (void) get_uchars(fp, buf, bufp, translate, FALSE, WARNCOUNT,
+        (void) get_uchars(bufp, translate, FALSE, WARNCOUNT,
                           "WARNINGS");
         assign_warnings(translate);
     } else if (match_varname(buf, "SYMBOLS", 4)) {
-        char *op, symbuf[BUFSZ];
-        boolean morelines;
-
-        do {
-            /* check for line continuation (trailing '\') */
-            op = eos(bufp);
-            morelines = (--op >= bufp && *op == '\\');
-            if (morelines) {
-                *op = '\0';
-                /* strip trailing space now that '\' is gone */
-                if (--op >= bufp && *op == ' ')
-                    *op = '\0';
-            }
-            /* parse here */
-            parsesymbols(bufp);
-            if (morelines) {
-                do {
-                    *symbuf = '\0';
-                    if (!fgets(symbuf, BUFSZ, fp)) {
-                        morelines = FALSE;
-                        break;
-                    }
-                    mungspaces(symbuf);
-                    bufp = symbuf;
-                } while (*bufp == '#');
-            }
-        } while (morelines);
+        if (!parsesymbols(bufp)) {
+            config_error_add("Error in SYMBOLS definition '%s'", bufp);
+            retval = FALSE;
+        }
         switch_symbols(TRUE);
     } else if (match_varname(buf, "WIZKIT", 6)) {
         (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
@@ -2544,31 +2712,38 @@ int src;
     } else if (match_varname(buf, "SOUND", 5)) {
         add_sound_mapping(bufp);
 #endif
-#ifdef QT_GRAPHICS
-        /* These should move to wc_ options */
     } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
+#ifdef QT_GRAPHICS
         extern char *qt_tilewidth;
 
         if (qt_tilewidth == NULL)
             qt_tilewidth = dupstr(bufp);
+#endif
     } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
+#ifdef QT_GRAPHICS
         extern char *qt_tileheight;
 
         if (qt_tileheight == NULL)
             qt_tileheight = dupstr(bufp);
+#endif
     } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
+#ifdef QT_GRAPHICS
         extern char *qt_fontsize;
 
         if (qt_fontsize == NULL)
             qt_fontsize = dupstr(bufp);
+#endif
     } else if (match_varname(buf, "QT_COMPACT", 10)) {
+#ifdef QT_GRAPHICS
         extern int qt_compact_mode;
 
         qt_compact_mode = atoi(bufp);
 #endif
-    } else
-        return 0;
-    return 1;
+    } else {
+        config_error_add("Unknown config statement");
+        return FALSE;
+    }
+    return retval;
 }
 
 #ifdef USER_SOUNDS
@@ -2580,45 +2755,148 @@ const char *filename;
 }
 #endif /* USER_SOUNDS */
 
+struct _config_error_frame {
+    int line_num;
+    int num_errors;
+    boolean origline_shown;
+    boolean fromfile;
+    boolean secure;
+    char origline[4 * BUFSZ];
+    char source[BUFSZ];
+    struct _config_error_frame *next;
+};
+
+struct _config_error_frame *config_error_data = (struct _config_error_frame *)0;
+
+void
+config_error_init(from_file, sourcename, secure)
+boolean from_file;
+const char *sourcename;
+boolean secure;
+{
+    struct _config_error_frame *tmp = (struct _config_error_frame *)
+        alloc(sizeof(struct _config_error_frame));
+
+    tmp->line_num = 0;
+    tmp->num_errors = 0;
+    tmp->origline_shown = FALSE;
+    tmp->fromfile = from_file;
+    tmp->secure = secure;
+    tmp->origline[0] = '\0';
+    if (sourcename && sourcename[0]) {
+        (void) strncpy(tmp->source, sourcename, sizeof(tmp->source)-1);
+        tmp->source[sizeof(tmp->source)-1] = '\0';
+    } else
+        tmp->source[0] = '\0';
+
+    tmp->next = config_error_data;
+    config_error_data = tmp;
+}
+
+STATIC_OVL boolean
+config_error_nextline(line)
+const char *line;
+{
+    struct _config_error_frame *ced = config_error_data;
+
+    if (!ced)
+        return FALSE;
+
+    if (ced->num_errors && ced->secure)
+        return FALSE;
+
+    ced->line_num++;
+    ced->origline_shown = FALSE;
+    if (line && line[0]) {
+        (void) strncpy(ced->origline, line, sizeof(ced->origline)-1);
+        ced->origline[sizeof(ced->origline)-1] = '\0';
+    } else
+        ced->origline[0] = '\0';
+
+    return TRUE;
+}
+
+/*VARARGS1*/
+void config_error_add
+VA_DECL(const char *, str)
+{
+    char buf[BUFSZ];
+    char lineno[QBUFSZ];
+
+    VA_START(str);
+    VA_INIT(str, char *);
+
+    Vsprintf(buf, str, VA_ARGS);
+
+    if (!config_error_data) {
+        pline("%s.", *buf ? buf : "Unknown error");
+        wait_synch();
+        return;
+    }
+
+    config_error_data->num_errors++;
+    if (!config_error_data->origline_shown
+        && !config_error_data->secure) {
+        pline("\n%s", config_error_data->origline);
+        config_error_data->origline_shown = TRUE;
+    }
+    if (config_error_data->line_num > 0
+        && !config_error_data->secure) {
+        Sprintf(lineno, "Line %i: ", config_error_data->line_num);
+    } else
+        lineno[0] = '\0';
+    pline("%s %s%s.",
+          config_error_data->secure ? "Error:" : " *",
+          lineno,
+          *buf ? buf : "Unknown error");
+
+    VA_END();
+}
+
+int
+config_error_done()
+{
+    int n;
+    struct _config_error_frame *tmp = config_error_data;
+
+    if (!config_error_data)
+        return 0;
+    n = config_error_data->num_errors;
+    if (n) {
+        pline("\n%i error%s in %s.\n", n,
+                   (n > 1) ? "s" : "",
+                   *config_error_data->source
+              ? config_error_data->source : configfile);
+        wait_synch();
+    }
+
+    config_error_data = tmp->next;
+
+    free(tmp);
+
+    return n;
+}
+
 boolean
 read_config_file(filename, src)
 const char *filename;
 int src;
 {
-    char buf[4 * BUFSZ], *p;
     FILE *fp;
-    boolean rv = TRUE; /* assume successful parse */
+    boolean rv = TRUE;
 
     if (!(fp = fopen_config_file(filename, src)))
         return FALSE;
 
     /* begin detection of duplicate configfile options */
     set_duplicate_opt_detection(1);
+    free_config_sections();
+    iflags.parse_config_file_src = src;
 
-    while (fgets(buf, sizeof buf, fp)) {
-#ifdef notyet
-/*
-XXX Don't call read() in parse_config_line, read as callback or reassemble
-line at this level.
-OR: Forbid multiline stuff for alternate config sources.
-*/
-#endif
-        if ((p = index(buf, '\n')) != 0)
-            *p = '\0';
-        if (!parse_config_line(fp, 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();
-            rv = FALSE;
-        }
-    }
+    rv = parse_conf_file(fp, parse_config_line);
     (void) fclose(fp);
 
+    free_config_sections();
     /* turn off detection of duplicate configfile options */
     set_duplicate_opt_detection(0);
     return rv;
@@ -2722,47 +3000,171 @@ struct obj *obj;
     }
 }
 
+
+boolean
+proc_wizkit_line(buf)
+char *buf;
+{
+    struct obj *otmp = readobjnam(buf, (struct obj *) 0);
+
+    if (otmp) {
+        if (otmp != &zeroobj)
+            wizkit_addinv(otmp);
+    } else {
+        /* .60 limits output line width to 79 chars */
+        config_error_add("Bad wizkit item: \"%.60s\"", buf);
+        return FALSE;
+    }
+    return TRUE;
+}
+
 void
 read_wizkit()
 {
     FILE *fp;
-    char *ep, buf[BUFSZ];
-    struct obj *otmp;
-    boolean bad_items = FALSE, skip = FALSE;
 
     if (!wizard || !(fp = fopen_wizkit_file()))
         return;
 
     program_state.wizkit_wishing = 1;
-    while (fgets(buf, (int) (sizeof buf), fp)) {
-        ep = index(buf, '\n');
+    config_error_init(TRUE, "WIZKIT", FALSE);
+
+    parse_conf_file(fp, proc_wizkit_line);
+    (void) fclose(fp);
+
+    config_error_done();
+    program_state.wizkit_wishing = 0;
+
+    return;
+}
+
+/* parse_conf_file
+ *
+ * Read from file fp, handling comments, empty lines, config sections,
+ * CHOOSE, and line continuation, calling proc for every valid line.
+ *
+ * Continued lines are merged together with one space in between.
+ */
+STATIC_OVL boolean
+parse_conf_file(fp, proc)
+FILE *fp;
+boolean FDECL((*proc), (char *));
+{
+    char inbuf[4 * BUFSZ];
+    boolean rv = TRUE; /* assume successful parse */
+    char *ep;
+    boolean skip = FALSE, morelines = FALSE;
+    char *buf = (char *) 0;
+
+    free_config_sections();
+
+    while (fgets(inbuf, (int) (sizeof inbuf), fp)) {
+        ep = index(inbuf, '\n');
         if (skip) { /* in case previous line was too long */
             if (ep)
                 skip = FALSE; /* found newline; next line is normal */
         } else {
-            if (!ep)
+            if (!ep) {
+                config_error_add("Line too long, skipping");
                 skip = TRUE; /* newline missing; discard next fgets */
-            else
+            } else {
+                char *tmpbuf = (char *) 0;
+                int len;
+                boolean ignoreline = FALSE;
+                boolean oldline = FALSE;
+
                 *ep = '\0'; /* remove newline */
 
-            if (buf[0]) {
-                otmp = readobjnam(buf, (struct obj *) 0);
-                if (otmp) {
-                    if (otmp != &zeroobj)
-                        wizkit_addinv(otmp);
-                } else {
-                    /* .60 limits output line width to 79 chars */
-                    raw_printf("Bad wizkit item: \"%.60s\"", buf);
-                    bad_items = TRUE;
+                /* line continuation (trailing '\') */
+                morelines = (--ep >= inbuf && *ep == '\\');
+                if (morelines)
+                    *ep = '\0';
+
+                /* trim off spaces at end of line */
+                while (--ep >= inbuf && (*ep == ' ' || *ep == '\t' || *ep == '\r'))
+                    *ep = '\0';
+
+                if (!config_error_nextline(inbuf)) {
+                    rv = FALSE;
+                    if (buf)
+                        free(buf), buf = (char *) 0;
+                    break;
+                }
+
+                ep = inbuf;
+                while (*ep == ' ' || *ep == '\t') ep++;
+
+                /* lines beginning with '#' are comments. ignore empty lines. */
+                if (!*ep || *ep == '#')
+                    ignoreline = TRUE;
+
+                if (buf)
+                    oldline = TRUE;
+
+                /* merge now read line with previous ones, if necessary */
+                if (!ignoreline) {
+                    len = strlen(inbuf) + 1;
+                    if (buf)
+                        len += strlen(buf);
+                    tmpbuf = (char *) alloc(len);
+                    if (buf) {
+                        Sprintf(tmpbuf, "%s %s", buf, inbuf);
+                        free(buf);
+                    } else
+                        Strcpy(tmpbuf, inbuf);
+                    buf = tmpbuf;
                 }
+
+                if (morelines || (ignoreline && !oldline))
+                    continue;
+
+                if (handle_config_section(ep)) {
+                    free(buf);
+                    buf = (char *) 0;
+                    continue;
+                }
+
+                /* from here onwards, we'll handle buf only */
+
+                if (match_varname(buf, "CHOOSE", 6)) {
+                    char *section;
+                    char *bufp = find_optparam(buf);
+                    if (!bufp) {
+                        config_error_add("Format is CHOOSE=section1,section2,...");
+                        rv = FALSE;
+                        free(buf);
+                        buf = (char *) 0;
+                        continue;
+                    }
+                    bufp++;
+                    if (config_section_chosen)
+                        free(config_section_chosen);
+                    section = choose_random_part(bufp, ',');
+                    if (section)
+                        config_section_chosen = dupstr(section);
+                    else {
+                        config_error_add("No config section to choose");
+                        rv = FALSE;
+                    }
+                    free(buf);
+                    buf = (char *) 0;
+                    continue;
+                }
+
+                if (!proc(buf))
+                    rv = FALSE;
+
+                free(buf);
+                buf = (char *) 0;
             }
         }
     }
-    program_state.wizkit_wishing = 0;
-    if (bad_items)
-        wait_synch();
-    (void) fclose(fp);
-    return;
+
+    if (buf)
+        free(buf);
+
+    free_config_sections();
+    return rv;
 }
 
 extern struct symsetentry *symset_list;  /* options.c */
@@ -2771,6 +3173,7 @@ extern const char *known_handling[];     /* drawing.c */
 extern const char *known_restrictions[]; /* drawing.c */
 static int symset_count = 0;             /* for pick-list building only */
 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
+static int symset_which_set = 0;
 
 STATIC_OVL
 FILE *
@@ -2790,7 +3193,6 @@ int
 read_sym_file(which_set)
 int which_set;
 {
-    char buf[4 * BUFSZ];
     FILE *fp;
 
     if (!(fp = fopen_sym_file()))
@@ -2798,24 +3200,42 @@ int which_set;
 
     symset_count = 0;
     chosen_symset_start = chosen_symset_end = FALSE;
-    while (fgets(buf, 4 * BUFSZ, fp)) {
-        if (!parse_sym_line(buf, which_set)) {
-            raw_printf("Bad symbol line:  \"%.50s\"", buf);
-            wait_synch();
-        }
-    }
+    symset_which_set = which_set;
+
+    config_error_init(TRUE, "symbols", FALSE);
+
+    parse_conf_file(fp, proc_symset_line);
     (void) fclose(fp);
-    if (!chosen_symset_end && !chosen_symset_start)
+
+    if (!chosen_symset_start && !chosen_symset_end) {
+        /* name caller put in symset[which_set].name was not found;
+           if it looks like "Default symbols", null it out and return
+           success to use the default; otherwise, return failure */
+        if (symset[which_set].name
+            && (fuzzymatch(symset[which_set].name, "Default symbols",
+                           " -_", TRUE)
+                || !strcmpi(symset[which_set].name, "default")))
+            clear_symsetentry(which_set, TRUE);
+        config_error_done();
         return (symset[which_set].name == 0) ? 1 : 0;
-    if (!chosen_symset_end) {
-        raw_printf("Missing finish for symset \"%s\"",
+    }
+    if (!chosen_symset_end)
+        config_error_add("Missing finish for symset \"%s\"",
                    symset[which_set].name ? symset[which_set].name
                                           : "unknown");
-        wait_synch();
-    }
+
+    config_error_done();
+
     return 1;
 }
 
+boolean
+proc_symset_line(buf)
+char *buf;
+{
+    return !((boolean) parse_sym_line(buf, symset_which_set));
+}
+
 /* returns 0 on error */
 int
 parse_sym_line(buf, which_set)
@@ -2829,16 +3249,14 @@ int which_set;
     /* convert each instance of whitespace (tabs, consecutive spaces)
        into a single space; leading and trailing spaces are stripped */
     mungspaces(buf);
-    if (!*buf || *buf == '#' || !strcmp(buf, " "))
-        return 1;
-    /* remove trailing comment, if any */
-    if ((commentp = rindex(buf, '#')) != 0) {
-        *commentp = '\0';
-        /* remove space preceding the stripped comment, if any;
-           we know 'commentp > buf' because *buf=='#' was caught above */
-        if (commentp[-1] == ' ')
-            *--commentp = '\0';
-    }
+
+    /* remove trailing comment, if any (this isn't strictly needed for
+       individual symbols, and it won't matter if "X#comment" without
+       separating space slips through; for handling or set description,
+       symbol set creator is responsible for preceding '#' with a space
+       and that comment itself doesn't contain " #") */
+    if ((commentp = rindex(buf, '#')) != 0 && commentp[-1] == ' ')
+        commentp[-1] = '\0';
 
     /* find the '=' or ':' */
     bufp = index(buf, '=');
@@ -2853,6 +3271,7 @@ int which_set;
             chosen_symset_start = FALSE;
             return 1;
         }
+        config_error_add("No \"finish\"");
         return 0;
     }
     /* skip '=' and space which follows, if any */
@@ -2861,8 +3280,10 @@ int which_set;
         ++bufp;
 
     symp = match_sym(buf);
-    if (!symp)
+    if (!symp) {
+        config_error_add("Unknown sym keyword");
         return 0;
+    }
 
     if (!symset[which_set].name) {
         /* A null symset name indicates that we're just
@@ -3052,8 +3473,8 @@ const char *dir UNUSED_if_not_OS2_CODEVIEW;
 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
         if (!file_is_stmlf(fd)) {
             raw_printf(
-                "Warning: scoreboard file %s is not in stream_lf format",
-                fq_record);
+                   "Warning: scoreboard file '%s' is not in stream_lf format",
+                       fq_record);
             wait_synch();
         }
 #endif
@@ -3065,7 +3486,7 @@ const char *dir UNUSED_if_not_OS2_CODEVIEW;
         (void) chmod(fq_record, FCMASK | 007);
 #endif /* VMS && !SECURE */
     } else {
-        raw_printf("Warning: cannot write scoreboard file %s", fq_record);
+        raw_printf("Warning: cannot write scoreboard file '%s'", fq_record);
         wait_synch();
     }
 #endif /* !UNIX && !VMS */
@@ -3086,24 +3507,51 @@ const char *dir UNUSED_if_not_OS2_CODEVIEW;
     Strcpy(tmp, RECORD);
     fq_record = fqname(RECORD, SCOREPREFIX, 0);
 #endif
+#ifdef WIN32
+    /* If dir is NULL it indicates create but
+       only if it doesn't already exist */
+    if (!dir) {
+        char buf[BUFSZ];
+
+        buf[0] = '\0';
+        fd = open(fq_record, O_RDWR);
+        if (!(fd == -1 && errno == ENOENT)) {
+            if (fd >= 0) {
+                (void) nhclose(fd);
+            } else {
+                /* explanation for failure other than missing file */
+                Sprintf(buf, "error   \"%s\", (errno %d).",
+                        fq_record, errno);
+                paniclog("scorefile", buf);
+            }
+            return;
+        }
+        Sprintf(buf, "missing \"%s\", creating new scorefile.",
+                fq_record);
+        paniclog("scorefile", buf);
+    }
+#endif
 
     if ((fd = open(fq_record, O_RDWR)) < 0) {
-/* try to create empty record */
+        /* try to create empty 'record' */
 #if defined(AZTEC_C) || defined(_DCC) \
     || (defined(__GNUC__) && defined(__AMIGA__))
         /* Aztec doesn't use the third argument */
         /* DICE doesn't like it */
-        if ((fd = open(fq_record, O_CREAT | O_RDWR)) < 0) {
+        fd = open(fq_record, O_CREAT | O_RDWR);
 #else
-        if ((fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE))
-            < 0) {
+        fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE);
 #endif
-            raw_printf("Warning: cannot write record %s", tmp);
+        if (fd <= 0) {
+            raw_printf("Warning: cannot write record '%s'", tmp);
             wait_synch();
-        } else
+        } else {
             (void) nhclose(fd);
-    } else /* open succeeded */
+        }
+    } else {
+        /* open succeeded => 'record' exists */
         (void) nhclose(fd);
+    }
 #else /* MICRO || WIN32*/
 
 #ifdef MAC
@@ -3135,6 +3583,11 @@ const char *reason; /* explanation */
         program_state.in_paniclog = 1;
         lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
         if (lfile) {
+#ifdef PANICLOG_FMT2
+            (void) fprintf(lfile, "%ld %s: %s %s\n",
+                           ubirthday, (plname ? plname : "(none)"),
+                           type, reason);
+#else
             time_t now = getnow();
             int uid = getuid();
             char playmode = wizard ? 'D' : discover ? 'X' : '-';
@@ -3142,6 +3595,7 @@ const char *reason; /* explanation */
             (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
                            version_string(buf), yyyymmdd(now), hhmmss(now),
                            uid, playmode, type, reason);
+#endif /* !PANICLOG_FMT2 */
             (void) fclose(lfile);
         }
         program_state.in_paniclog = 0;
@@ -3241,6 +3695,7 @@ recover_savefile()
         raw_printf("\nError writing %s; recovery failed.", SAVEF);
         (void) nhclose(gfd);
         (void) nhclose(sfd);
+        (void) nhclose(lfd);
         delete_savefile();
         return FALSE;
     }
@@ -3250,6 +3705,7 @@ recover_savefile()
                    SAVEF);
         (void) nhclose(gfd);
         (void) nhclose(sfd);
+        (void) nhclose(lfd);
         delete_savefile();
         return FALSE;
     }
@@ -3260,6 +3716,7 @@ recover_savefile()
                    SAVEF);
         (void) nhclose(gfd);
         (void) nhclose(sfd);
+        (void) nhclose(lfd);
         delete_savefile();
         return FALSE;
     }
@@ -3269,13 +3726,15 @@ recover_savefile()
                    SAVEF);
         (void) nhclose(gfd);
         (void) nhclose(sfd);
+        (void) nhclose(lfd);
         delete_savefile();
         return FALSE;
     }
 
     if (!copy_bytes(lfd, sfd)) {
-        (void) nhclose(lfd);
+        (void) nhclose(gfd);
         (void) nhclose(sfd);
+        (void) nhclose(lfd);
         delete_savefile();
         return FALSE;
     }
@@ -3283,7 +3742,7 @@ recover_savefile()
     processed[savelev] = 1;
 
     if (!copy_bytes(gfd, sfd)) {
-        (void) nhclose(lfd);
+        (void) nhclose(gfd);
         (void) nhclose(sfd);
         delete_savefile();
         return FALSE;
@@ -3324,6 +3783,7 @@ recover_savefile()
     for (lev = 0; lev < 256; lev++) {
         if (processed[lev]) {
             const char *fq_lock;
+
             set_levelfile_name(lock, lev);
             fq_lock = fqname(lock, LEVELPREFIX, 3);
             (void) unlink(fq_lock);
@@ -3370,7 +3830,11 @@ assure_syscf_file()
      * VMS overrides open() usage with a macro which requires it.
      */
 #ifndef VMS
+# if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
+    fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
+# else
     fd = open(SYSCF_FILE, O_RDONLY);
+# endif
 #else
     fd = open(SYSCF_FILE, O_RDONLY, 0);
 #endif
@@ -3469,7 +3933,7 @@ boolean wildcards;
 #define TITLESCOPE 2
 #define PASSAGESCOPE 3
 
-#define MAXPASSAGES SIZE(context.novel.pasg) /* 30 */
+#define MAXPASSAGES SIZE(context.novel.pasg) /* 20 */
 
 static int FDECL(choose_passage, (int, unsigned));
 
@@ -3477,24 +3941,35 @@ static int FDECL(choose_passage, (int, unsigned));
    been chosen, reset the tracking to make all passages available again */
 static int
 choose_passage(passagecnt, oid)
-int passagecnt; /* total of available passages, 1..MAXPASSAGES */
+int passagecnt; /* total of available passages */
 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
 {
     int idx, res;
 
     if (passagecnt < 1)
         return 0;
-    if (passagecnt > MAXPASSAGES)
-        passagecnt = MAXPASSAGES;
 
     /* if a different book or we've used up all the passages already,
        reset in order to have all 'passagecnt' passages available */
     if (oid != context.novel.id || context.novel.count == 0) {
+        int i, range = passagecnt, limit = MAXPASSAGES;
+
         context.novel.id = oid;
-        context.novel.count = passagecnt;
-        for (idx = 0; idx < MAXPASSAGES; idx++)
-            context.novel.pasg[idx] = (xchar) ((idx < passagecnt) ? idx + 1
-                                                                  : 0);
+        if (range <= limit) {
+            /* collect all of the N indices */
+            context.novel.count = passagecnt;
+            for (idx = 0; idx < MAXPASSAGES; idx++)
+                context.novel.pasg[idx] = (xchar) ((idx < passagecnt)
+                                                   ? idx + 1 : 0);
+        } else {
+            /* collect MAXPASSAGES of the N indices */
+            context.novel.count = MAXPASSAGES;
+            for (idx = i = 0; i < passagecnt; ++i, --range)
+                if (range > 0 && rn2(range) < limit) {
+                    context.novel.pasg[idx++] = (xchar) (i + 1);
+                    --limit;
+                }
+        }
     }
 
     idx = rn2(context.novel.count);
@@ -3514,21 +3989,29 @@ char *nowin_buf;
 unsigned oid; /* book identifier */
 {
     dlb *fp;
-    char *endp;
     char line[BUFSZ], lastline[BUFSZ];
 
     int scope = 0;
     int linect = 0, passagecnt = 0, targetpassage = 0;
+/*JP
     const char *badtranslation = "an incomprehensible foreign translation";
+*/
+    const char *badtranslation = "\95s\8a®\91S\82È\8aO\8d\91\8cê\96|\96ó";
     boolean matchedsection = FALSE, matchedtitle = FALSE;
     winid tribwin = WIN_ERR;
     boolean grasped = FALSE;
     boolean foundpassage = FALSE;
 
+    if (nowin_buf)
+        *nowin_buf = '\0';
+
     /* check for mandatories */
     if (!tribsection || !tribtitle) {
         if (!nowin_buf)
+/*JP
             pline("It's %s of \"%s\"!", badtranslation, tribtitle);
+*/
+            pline("\82±\82ê\82Í\81u%s\81v\82Ì%s\82¾\81I", tribtitle, badtranslation);
         return grasped;
     }
 
@@ -3539,7 +4022,10 @@ unsigned oid; /* book identifier */
     if (!fp) {
         /* this is actually an error - cannot open tribute file! */
         if (!nowin_buf)
+/*JP
             pline("You feel too overwhelmed to continue!");
+*/
+            pline("\82 \82È\82½\82Í\91±\82¯\82ç\82ê\82È\82¢\82Ù\82Ç\88³\93|\82³\82ê\82½\81I");
         return grasped;
     }
 
@@ -3565,19 +4051,15 @@ unsigned oid; /* book identifier */
     *line = *lastline = '\0';
     while (dlb_fgets(line, sizeof line, fp) != 0) {
         linect++;
-        if ((endp = index(line, '\n')) != 0)
-            *endp = 0;
+        (void) strip_newline(line);
         switch (line[0]) {
         case '%':
-            if (!strncmpi(&line[1], "section ", sizeof("section ") - 1)) {
+            if (!strncmpi(&line[1], "section ", sizeof "section " - 1)) {
                 char *st = &line[9]; /* 9 from "%section " */
 
                 scope = SECTIONSCOPE;
-                if (!strcmpi(st, tribsection))
-                    matchedsection = TRUE;
-                else
-                    matchedsection = FALSE;
-            } else if (!strncmpi(&line[1], "title ", sizeof("title ") - 1)) {
+                matchedsection = !strcmpi(st, tribsection) ? TRUE : FALSE;
+            } else if (!strncmpi(&line[1], "title ", sizeof "title " - 1)) {
                 char *st = &line[7]; /* 7 from "%title " */
                 char *p1, *p2;
 
@@ -3587,8 +4069,6 @@ unsigned oid; /* book identifier */
                     if ((p2 = index(p1, ')')) != 0) {
                         *p2 = '\0';
                         passagecnt = atoi(p1);
-                        if (passagecnt > MAXPASSAGES)
-                            passagecnt = MAXPASSAGES;
                         scope = TITLESCOPE;
                         if (matchedsection && !strcmpi(st, tribtitle)) {
                             matchedtitle = TRUE;
@@ -3602,27 +4082,25 @@ unsigned oid; /* book identifier */
                     }
                 }
             } else if (!strncmpi(&line[1], "passage ",
-                                 sizeof("passage ") - 1)) {
+                                 sizeof "passage " - 1)) {
                 int passagenum = 0;
                 char *st = &line[9]; /* 9 from "%passage " */
 
-                while (*st == ' ' || *st == '\t')
-                    st++;
-                if (*st && digit(*st) && (strlen(st) < 3))
-                    passagenum = atoi(st);
-                if (passagenum && (passagenum <= passagecnt)) {
+                mungspaces(st);
+                passagenum = atoi(st);
+                if (passagenum > 0 && passagenum <= passagecnt) {
                     scope = PASSAGESCOPE;
-                    if (matchedtitle && (passagenum == targetpassage)) {
-                        if (!nowin_buf)
+                    if (matchedtitle && passagenum == targetpassage) {
+                        foundpassage = TRUE;
+                        if (!nowin_buf) {
                             tribwin = create_nhwindow(NHW_MENU);
-                        else
-                            foundpassage = TRUE;
+                            if (tribwin == WIN_ERR)
+                                goto cleanup;
+                        }
                     }
                 }
-            } else if (!strncmpi(&line[1], "e ", sizeof("e ") - 1)) {
-                if (matchedtitle && scope == PASSAGESCOPE
-                    && ((!nowin_buf && tribwin != WIN_ERR)
-                        || (nowin_buf && foundpassage)))
+            } else if (!strncmpi(&line[1], "e ", sizeof "e " - 1)) {
+                if (foundpassage)
                     goto cleanup;
                 if (scope == TITLESCOPE)
                     matchedtitle = FALSE;
@@ -3639,13 +4117,16 @@ unsigned oid; /* book identifier */
             /* comment only, next! */
             break;
         default:
-            if (matchedtitle && scope == PASSAGESCOPE) {
-                if (!nowin_buf && tribwin != WIN_ERR) {
+            if (foundpassage) {
+                if (!nowin_buf) {
+                    /* outputting multi-line passage to text window */
                     putstr(tribwin, 0, line);
-                    Strcpy(lastline, line);
-                } else if (nowin_buf) {
-                    if ((int) strlen(line) < bufsz - 1)
-                        Strcpy(nowin_buf, line);
+                    if (*line)
+                        Strcpy(lastline, line);
+                } else {
+                    /* fetching one-line passage into buffer */
+                    copynchars(nowin_buf, line, bufsz - 1);
+                    goto cleanup; /* don't wait for "%e passage" */
                 }
             }
         }
@@ -3653,26 +4134,33 @@ unsigned oid; /* book identifier */
 
 cleanup:
     (void) dlb_fclose(fp);
-    if (!nowin_buf && tribwin != WIN_ERR) {
-        if (matchedtitle && scope == PASSAGESCOPE) {
-            display_nhwindow(tribwin, FALSE);
-            /* put the final attribution line into message history,
-               analogous to the summary line from long quest messages */
-            if (index(lastline, '['))
-                mungspaces(lastline); /* to remove leading spaces */
-            else /* construct one if necessary */
-                Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
-            putmsghistory(lastline, FALSE);
-        }
-        destroy_nhwindow(tribwin);
-        tribwin = WIN_ERR;
-        grasped = TRUE;
+    if (nowin_buf) {
+        /* one-line buffer */
+        grasped = *nowin_buf ? TRUE : FALSE;
     } else {
-        if (!nowin_buf)
-            pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
-        else
-            if (foundpassage)
+        if (tribwin != WIN_ERR) { /* implies 'foundpassage' */
+            /* multi-line window, normal case;
+               if lastline is empty, there were no non-empty lines between
+               "%passage n" and "%e passage" so we leave 'grasped' False */
+            if (*lastline) {
+                display_nhwindow(tribwin, FALSE);
+                /* put the final attribution line into message history,
+                   analogous to the summary line from long quest messages */
+                if (index(lastline, '['))
+                    mungspaces(lastline); /* to remove leading spaces */
+                else /* construct one if necessary */
+                    Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
+                putmsghistory(lastline, FALSE);
                 grasped = TRUE;
+            }
+            destroy_nhwindow(tribwin);
+        }
+        if (!grasped)
+            /* multi-line window, problem */
+/*JP
+            pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
+*/
+            pline("\82±\82ê\82Í\81u%s\81v\82Ì%s\82Ì\82æ\82¤\82¾\81I", tribtitle, badtranslation);
     }
     return grasped;
 }