1 /* NetHack 3.6 files.c $NHDT-Date: 1524413723 2018/04/22 16:15:23 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.235 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Derek S. Ray, 2015. */
4 /* NetHack may be freely redistributed. See license for details. */
8 /* JNetHack Copyright */
9 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000 */
10 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2018 */
11 /* JNetHack may be freely redistributed. See license for details. */
17 #include "wintty.h" /* more() */
20 #if (!defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)) \
26 #ifdef _MSC_VER /* MSC 6.0 defines errno quite differently */
42 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
44 #ifndef COMPRESS_EXTENSION
45 #define COMPRESS_EXTENSION ".gz"
49 #if defined(UNIX) && defined(QT_GRAPHICS)
50 #include <sys/types.h>
55 #if defined(UNIX) || defined(VMS) || !defined(NO_SIGNAL)
59 #if defined(MSDOS) || defined(OS2) || defined(TOS) || defined(WIN32)
66 #ifndef O_BINARY /* used for micros, no-op for others */
70 #ifdef PREFIXES_IN_USE
72 static char fqn_filename_buffer[FQN_NUMBUF][FQN_MAX_FILENAME];
75 #if !defined(MFLOPPY) && !defined(VMS) && !defined(WIN32)
76 char bones[] = "bonesnn.xxx";
77 char lock[PL_NSIZ + 14] = "1lock"; /* long enough for uid+name+.99 */
80 char bones[FILENAME]; /* pathname of bones files */
81 char lock[FILENAME]; /* pathname of level files */
84 char bones[] = "bonesnn.xxx;1";
85 char lock[PL_NSIZ + 17] = "1lock"; /* long enough for _uid+name+.99;1 */
88 char bones[] = "bonesnn.xxx";
89 char lock[PL_NSIZ + 25]; /* long enough for username+-+name+.99 */
93 #if defined(UNIX) || defined(__BEOS__)
94 #define SAVESIZE (PL_NSIZ + 13) /* save/99999player.e */
97 #define SAVESIZE (PL_NSIZ + 22) /* [.save]<uid>player.e;1 */
100 #define SAVESIZE (PL_NSIZ + 40) /* username-player.NetHack-saved-game */
102 #define SAVESIZE FILENAME /* from macconf.h or pcconf.h */
107 #if !defined(SAVE_EXTENSION)
109 #define SAVE_EXTENSION ".sav"
112 #define SAVE_EXTENSION ".NetHack-saved-game"
116 char SAVEF[SAVESIZE]; /* holds relative path of save file from playground */
118 char SAVEP[SAVESIZE]; /* holds path of directory for save file */
121 #ifdef HOLD_LOCKFILE_OPEN
122 struct level_ftrack {
124 int fd; /* file descriptor for level file */
125 int oflag; /* open flags */
126 boolean nethack_thinks_it_is_open; /* Does NetHack think it's open? */
131 #endif /*HOLD_LOCKFILE_OPEN*/
133 #define WIZKIT_MAX 128
134 static char wizkit[WIZKIT_MAX];
135 STATIC_DCL FILE *NDECL(fopen_wizkit_file);
136 STATIC_DCL void FDECL(wizkit_addinv, (struct obj *));
139 extern char PATH[]; /* see sys/amiga/amidos.c */
140 extern char bbs_id[];
143 #include <proto/dos.h>
146 #include <libraries/dos.h>
147 extern void FDECL(amii_set_text_font, (char *, int));
150 #if defined(WIN32) || defined(MSDOS)
153 #define Delay(a) msleep(a)
157 #define DeleteFile unlink
163 #define unlink macunlink
166 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) \
167 || defined(__MWERKS__)
168 #define PRAGMA_UNUSED
172 extern char *sounddir;
175 extern int n_dgns; /* from dungeon.c */
177 #if defined(UNIX) && defined(QT_GRAPHICS)
182 STATIC_PTR int FDECL(CFDECLSPEC strcmp_wrap, (const void *, const void *));
184 STATIC_DCL char *FDECL(set_bonesfile_name, (char *, d_level *));
185 STATIC_DCL char *NDECL(set_bonestemp_name);
187 STATIC_DCL void FDECL(redirect, (const char *, const char *, FILE *,
190 #if defined(COMPRESS) || defined(ZLIB_COMP)
191 STATIC_DCL void FDECL(docompress_file, (const char *, BOOLEAN_P));
193 #if defined(ZLIB_COMP)
194 STATIC_DCL boolean FDECL(make_compressed_name, (const char *, char *));
197 STATIC_DCL char *FDECL(make_lockname, (const char *, char *));
199 STATIC_DCL void FDECL(set_configfile_name, (const char *));
200 STATIC_DCL FILE *FDECL(fopen_config_file, (const char *, int));
201 STATIC_DCL int FDECL(get_uchars, (char *, uchar *, BOOLEAN_P,
203 boolean FDECL(proc_wizkit_line, (char *));
204 boolean FDECL(parse_config_line, (char *));
205 STATIC_DCL boolean FDECL(parse_conf_file, (FILE *, boolean (*proc)(char *)));
206 STATIC_DCL FILE *NDECL(fopen_sym_file);
207 boolean FDECL(proc_symset_line, (char *));
208 STATIC_DCL void FDECL(set_symhandling, (char *, int));
209 #ifdef NOCWD_ASSUMPTIONS
210 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
212 STATIC_DCL boolean FDECL(config_error_nextline, (const char *));
213 STATIC_DCL void NDECL(free_config_sections);
214 STATIC_DCL char *FDECL(choose_random_part, (char *, CHAR_P));
215 STATIC_DCL boolean FDECL(is_config_section, (const char *));
216 STATIC_DCL boolean FDECL(handle_config_section, (char *));
218 STATIC_DCL boolean FDECL(copy_bytes, (int, int));
220 #ifdef HOLD_LOCKFILE_OPEN
221 STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int));
225 static char *config_section_chosen = (char *) 0;
226 static char *config_section_current = (char *) 0;
232 * legal zero-terminated list of acceptable file name characters
233 * quotechar lead-in character used to quote illegal characters as
236 * callerbuf buffer to house result
237 * bufsz size of callerbuf
240 * The hex digits 0-9 and A-F are always part of the legal set due to
241 * their use in the encoding scheme, even if not explicitly included in
245 * The following call:
246 * (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
247 * '%', "This is a % test!", buf, 512);
248 * results in this encoding:
249 * "This%20is%20a%20%25%20test%21"
252 fname_encode(legal, quotechar, s, callerbuf, bufsz)
260 static char hexdigits[] = "0123456789ABCDEF";
267 /* Do we have room for one more character or encoding? */
268 if ((bufsz - cnt) <= 4)
271 if (*sp == quotechar) {
272 (void) sprintf(op, "%c%02X", quotechar, *sp);
275 } else if ((index(legal, *sp) != 0) || (index(hexdigits, *sp) != 0)) {
280 } else if (is_kanji1(s, sp - s)) {
287 (void) sprintf(op, "%c%02X", quotechar, *sp);
300 * quotechar lead-in character used to quote illegal characters as
303 * callerbuf buffer to house result
304 * bufsz size of callerbuf
307 fname_decode(quotechar, s, callerbuf, bufsz)
313 int k, calc, cnt = 0;
314 static char hexdigits[] = "0123456789ABCDEF";
322 /* Do we have room for one more character? */
323 if ((bufsz - cnt) <= 2)
325 if (*sp == quotechar) {
327 for (k = 0; k < 16; ++k)
328 if (*sp == hexdigits[k])
331 return callerbuf; /* impossible, so bail */
334 for (k = 0; k < 16; ++k)
335 if (*sp == hexdigits[k])
338 return callerbuf; /* impossible, so bail */
352 #ifdef PREFIXES_IN_USE
353 #define UNUSED_if_not_PREFIXES_IN_USE /*empty*/
355 #define UNUSED_if_not_PREFIXES_IN_USE UNUSED
360 fqname(basenam, whichprefix, buffnum)
362 int whichprefix UNUSED_if_not_PREFIXES_IN_USE;
363 int buffnum UNUSED_if_not_PREFIXES_IN_USE;
365 #ifndef PREFIXES_IN_USE
368 if (!basenam || whichprefix < 0 || whichprefix >= PREFIX_COUNT)
370 if (!fqn_prefix[whichprefix])
372 if (buffnum < 0 || buffnum >= FQN_NUMBUF) {
373 impossible("Invalid fqn_filename_buffer specified: %d", buffnum);
376 if (strlen(fqn_prefix[whichprefix]) + strlen(basenam)
377 >= FQN_MAX_FILENAME) {
378 impossible("fqname too long: %s + %s", fqn_prefix[whichprefix],
380 return basenam; /* XXX */
382 Strcpy(fqn_filename_buffer[buffnum], fqn_prefix[whichprefix]);
383 return strcat(fqn_filename_buffer[buffnum], basenam);
388 validate_prefix_locations(reasonbuf)
389 char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */
391 #if defined(NOCWD_ASSUMPTIONS)
393 const char *filename;
394 int prefcnt, failcount = 0;
395 char panicbuf1[BUFSZ], panicbuf2[BUFSZ];
401 #if defined(NOCWD_ASSUMPTIONS)
402 for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++) {
403 /* don't test writing to configdir or datadir; they're readonly */
404 if (prefcnt == SYSCONFPREFIX || prefcnt == CONFIGPREFIX
405 || prefcnt == DATAPREFIX)
407 filename = fqname("validate", prefcnt, 3);
408 if ((fp = fopen(filename, "w"))) {
410 (void) unlink(filename);
414 Strcat(reasonbuf, ", ");
415 Strcat(reasonbuf, fqn_prefix_names[prefcnt]);
417 /* the paniclog entry gets the value of errno as well */
418 Sprintf(panicbuf1, "Invalid %s", fqn_prefix_names[prefcnt]);
419 #if defined(NHSTDC) && !defined(NOTSTDC)
420 if (!(details = strerror(errno)))
423 Sprintf(panicbuf2, "\"%s\", (%d) %s", fqn_prefix[prefcnt], errno,
425 paniclog(panicbuf1, panicbuf2);
436 /* fopen a file, with OS-dependent bells and whistles */
437 /* NOTE: a simpler version of this routine also exists in util/dlb_main.c */
439 fopen_datafile(filename, mode, prefix)
440 const char *filename, *mode;
445 filename = fqname(filename, prefix, prefix == TROUBLEPREFIX ? 3 : 0);
446 fp = fopen(filename, mode);
450 /* ---------- BEGIN LEVEL FILE HANDLING ----------- */
453 /* Set names for bones[] and lock[] */
458 Strcpy(levels, permbones);
459 Strcpy(bones, permbones);
461 append_slash(permbones);
462 append_slash(levels);
464 strncat(levels, bbs_id, PATHLEN);
467 Strcat(bones, "bonesnn.*");
468 Strcpy(lock, levels);
470 Strcat(lock, alllevels);
476 /* Construct a file name for a level-type file, which is of the form
477 * something.level (with any old level stripped off).
478 * This assumes there is space on the end of 'file' to append
479 * a two digit number. This is true for 'level'
480 * but be careful if you use it for other things -dgk
483 set_levelfile_name(file, lev)
489 tf = rindex(file, '.');
492 Sprintf(tf, ".%d", lev);
500 create_levelfile(lev, errbuf)
509 set_levelfile_name(lock, lev);
510 fq_lock = fqname(lock, LEVELPREFIX, 0);
512 #if defined(MICRO) || defined(WIN32)
513 /* Use O_TRUNC to force the file to be shortened if it already
514 * exists and is currently longer.
516 #ifdef HOLD_LOCKFILE_OPEN
518 fd = open_levelfile_exclusively(
519 fq_lock, lev, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
522 fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
525 fd = maccreat(fq_lock, LEVL_TYPE);
527 fd = creat(fq_lock, FCMASK);
529 #endif /* MICRO || WIN32 */
532 level_info[lev].flags |= LFILE_EXISTS;
533 else if (errbuf) /* failure explanation */
534 Sprintf(errbuf, "Cannot create file \"%s\" for level %d (errno %d).",
541 open_levelfile(lev, errbuf)
550 set_levelfile_name(lock, lev);
551 fq_lock = fqname(lock, LEVELPREFIX, 0);
553 /* If not currently accessible, swap it in. */
554 if (level_info[lev].where != ACTIVE)
558 fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE);
560 #ifdef HOLD_LOCKFILE_OPEN
562 fd = open_levelfile_exclusively(fq_lock, lev, O_RDONLY | O_BINARY);
565 fd = open(fq_lock, O_RDONLY | O_BINARY, 0);
568 /* for failure, return an explanation that our caller can use;
569 settle for `lock' instead of `fq_lock' because the latter
570 might end up being too big for nethack's BUFSZ */
571 if (fd < 0 && errbuf)
573 Sprintf(errbuf, "Cannot open file \"%s\" for level %d (errno %d).",
576 Sprintf(errbuf, "
\92n
\89º%d
\8aK
\82Ì
\83t
\83@
\83C
\83\8b\"%s\"
\82ð
\8aJ
\82¯
\82È
\82¢(errno %d)
\81D",
584 delete_levelfile(lev)
588 * Level 0 might be created by port specific code that doesn't
589 * call create_levfile(), so always assume that it exists.
591 if (lev == 0 || (level_info[lev].flags & LFILE_EXISTS)) {
592 set_levelfile_name(lock, lev);
593 #ifdef HOLD_LOCKFILE_OPEN
597 (void) unlink(fqname(lock, LEVELPREFIX, 0));
598 level_info[lev].flags &= ~LFILE_EXISTS;
605 #ifdef HANGUPHANDLING
606 if (program_state.preserve_locks)
609 #if !defined(PC_LOCKING) && defined(MFLOPPY) && !defined(AMIGA)
610 eraseall(levels, alllevels);
612 eraseall(permbones, alllevels);
618 (void) signal(SIGINT, SIG_IGN);
620 #if defined(UNIX) || defined(VMS)
621 sethanguphandler((void FDECL((*), (int) )) SIG_IGN);
623 /* can't access maxledgerno() before dungeons are created -dlc */
624 for (x = (n_dgns ? maxledgerno() : 0); x >= 0; x--)
625 delete_levelfile(x); /* not all levels need be present */
627 #endif /* ?PC_LOCKING,&c */
630 #if defined(SELECTSAVED)
631 /* qsort comparison routine */
632 STATIC_OVL int CFDECLSPEC
637 #if defined(UNIX) && defined(QT_GRAPHICS)
638 return strncasecmp(*(char **) p, *(char **) q, 16);
640 return strncmpi(*(char **) p, *(char **) q, 16);
645 #ifdef HOLD_LOCKFILE_OPEN
647 open_levelfile_exclusively(name, lev, oflag)
657 if (lftrack.fd >= 0) {
658 /* check for compatible access */
659 if (lftrack.oflag == oflag) {
661 reslt = lseek(fd, 0L, SEEK_SET);
663 panic("open_levelfile_exclusively: lseek failed %d", errno);
664 lftrack.nethack_thinks_it_is_open = TRUE;
667 fd = sopen(name, oflag, SH_DENYRW, FCMASK);
669 lftrack.oflag = oflag;
670 lftrack.nethack_thinks_it_is_open = TRUE;
673 fd = sopen(name, oflag, SH_DENYRW, FCMASK);
675 lftrack.oflag = oflag;
677 lftrack.nethack_thinks_it_is_open = TRUE;
690 lftrack.nethack_thinks_it_is_open = FALSE;
703 if (lftrack.fd == fd) {
704 really_close(); /* close it, but reopen it to hold it */
705 fd = open_levelfile(0, (char *) 0);
706 lftrack.nethack_thinks_it_is_open = FALSE;
711 #else /* !HOLD_LOCKFILE_OPEN */
719 #endif /* ?HOLD_LOCKFILE_OPEN */
721 /* ---------- END LEVEL FILE HANDLING ----------- */
723 /* ---------- BEGIN BONES FILE HANDLING ----------- */
725 /* set up "file" to be file name for retrieving bones, and return a
726 * bonesid to be read/written in the bones file.
729 set_bonesfile_name(file, lev)
737 * "bonD0.nn" = bones for level nn in the main dungeon;
738 * "bonM0.T" = bones for Minetown;
739 * "bonQBar.n" = bones for level n in the Barbarian quest;
741 * "bon3M0.T" = > same as above, but for bones pool #3.
744 * Return value for content validation skips "bon" and the
745 * pool number (if present), making it feasible for the admin
746 * to manually move a bones file from one pool to another by
751 if (sysopt.bones_pools > 1) {
752 unsigned poolnum = min((unsigned) sysopt.bones_pools, 10);
754 poolnum = (unsigned) ubirthday % poolnum; /* 0..9 */
755 Sprintf(eos(file), "%u", poolnum);
758 dptr = eos(file); /* this used to be after the following Sprintf()
759 and the return value was (dptr - 2) */
760 /* when this naming scheme was adopted, 'filecode' was one letter;
761 3.3.0 turned it into a three letter string (via roles[] in role.c);
762 from that version through 3.6.0, 'dptr' pointed past the filecode
763 and the return value of (dptr - 2) was wrong for bones produced
764 in the quest branch, skipping the boneid character 'Q' and the
765 first letter of the role's filecode; bones loading still worked
766 because the bonesid used for validation had the same error */
767 Sprintf(dptr, "%c%s", dungeons[lev->dnum].boneid,
768 In_quest(lev) ? urole.filecode : "0");
769 if ((sptr = Is_special(lev)) != 0)
770 Sprintf(eos(dptr), ".%c", sptr->boneid);
772 Sprintf(eos(dptr), ".%d", lev->dlevel);
779 /* set up temporary file name for writing bones, to avoid another game's
780 * trying to read from an uncompleted bones file. we want an uncontentious
781 * name, so use one in the namespace reserved for this game's level files.
782 * (we are not reading or writing level files while writing bones files, so
783 * the same array may be used instead of copying.)
790 tf = rindex(lock, '.');
801 create_bonesfile(lev, bonesid, errbuf)
811 *bonesid = set_bonesfile_name(bones, lev);
812 file = set_bonestemp_name();
813 file = fqname(file, BONESPREFIX, 0);
815 #if defined(MICRO) || defined(WIN32)
816 /* Use O_TRUNC to force the file to be shortened if it already
817 * exists and is currently longer.
819 fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
822 fd = maccreat(file, BONE_TYPE);
824 fd = creat(file, FCMASK);
827 if (fd < 0 && errbuf) /* failure explanation */
828 Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).", lock,
831 #if defined(VMS) && !defined(SECURE)
833 Re-protect bones file with world:read+write+execute+delete access.
834 umask() doesn't seem very reliable; also, vaxcrtl won't let us set
835 delete access without write access, which is what's really wanted.
836 Can't simply create it with the desired protection because creat
837 ANDs the mask with the user's default protection, which usually
838 denies some or all access to world.
840 (void) chmod(file, FCMASK | 007); /* allow other users full access */
841 #endif /* VMS && !SECURE */
847 /* remove partial bonesfile in process of creation */
851 const char *tempname;
853 tempname = set_bonestemp_name();
854 tempname = fqname(tempname, BONESPREFIX, 0);
855 (void) unlink(tempname);
859 /* move completed bones file to proper name */
861 commit_bonesfile(lev)
864 const char *fq_bones, *tempname;
867 (void) set_bonesfile_name(bones, lev);
868 fq_bones = fqname(bones, BONESPREFIX, 0);
869 tempname = set_bonestemp_name();
870 tempname = fqname(tempname, BONESPREFIX, 1);
872 #if (defined(SYSV) && !defined(SVR4)) || defined(GENIX)
873 /* old SYSVs don't have rename. Some SVR3's may, but since they
874 * also have link/unlink, it doesn't matter. :-)
876 (void) unlink(fq_bones);
877 ret = link(tempname, fq_bones);
878 ret += unlink(tempname);
880 ret = rename(tempname, fq_bones);
882 if (wizard && ret != 0)
883 pline("couldn't rename %s to %s.", tempname, fq_bones);
887 open_bonesfile(lev, bonesid)
891 const char *fq_bones;
894 *bonesid = set_bonesfile_name(bones, lev);
895 fq_bones = fqname(bones, BONESPREFIX, 0);
896 nh_uncompress(fq_bones); /* no effect if nonexistent */
898 fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE);
900 fd = open(fq_bones, O_RDONLY | O_BINARY, 0);
906 delete_bonesfile(lev)
909 (void) set_bonesfile_name(bones, lev);
910 return !(unlink(fqname(bones, BONESPREFIX, 0)) < 0);
913 /* assume we're compressing the recently read or created bonesfile, so the
914 * file name is already set properly */
918 nh_compress(fqname(bones, BONESPREFIX, 0));
921 /* ---------- END BONES FILE HANDLING ----------- */
923 /* ---------- BEGIN SAVE FILE HANDLING ----------- */
925 /* set savefile name in OS-dependent manner from pre-existing plname,
926 * avoiding troublesome characters */
928 set_savefile_name(regularize_it)
929 boolean regularize_it;
932 Sprintf(SAVEF, "[.save]%d%s", getuid(), plname);
934 regularize(SAVEF + 7);
938 Strcpy(SAVEF, SAVEP);
940 strncat(SAVEF, bbs_id, PATHLEN);
943 int i = strlen(SAVEP);
945 /* plname has to share space with SAVEP and ".sav" */
946 (void) strncat(SAVEF, plname, FILENAME - i - 4);
948 (void) strncat(SAVEF, plname, 8);
951 regularize(SAVEF + i);
953 Strcat(SAVEF, SAVE_EXTENSION);
957 static const char okchars[] =
958 "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
959 const char *legal = okchars;
960 char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ];
962 /* Obtain the name of the logged on user and incorporate
963 * it into the name. */
964 Sprintf(fnamebuf, "%s-%s", get_username(0), plname);
966 ++legal; /* skip '*' wildcard character */
967 (void) fname_encode(legal, '%', fnamebuf, encodedfnamebuf, BUFSZ);
968 Sprintf(SAVEF, "%s%s", encodedfnamebuf, SAVE_EXTENSION);
970 #else /* not VMS or MICRO or WIN32 */
971 Sprintf(SAVEF, "save/%d%s", (int) getuid(), plname);
973 regularize(SAVEF + 5); /* avoid . or / in name */
981 save_savefile_name(fd)
984 _pragma_ignore(-Wunused-result)
985 (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF));
991 /* change pre-existing savefile name to indicate an error savefile */
997 char *semi_colon = rindex(SAVEF, ';');
1002 Strcat(SAVEF, ".e;1");
1005 Strcat(SAVEF, "-e");
1007 Strcat(SAVEF, ".e");
1013 /* create save file, overwriting one if it already exists */
1017 const char *fq_save;
1020 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1021 #if defined(MICRO) || defined(WIN32)
1022 fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK);
1025 fd = maccreat(fq_save, SAVE_TYPE);
1027 fd = creat(fq_save, FCMASK);
1029 #if defined(VMS) && !defined(SECURE)
1031 Make sure the save file is owned by the current process. That's
1032 the default for non-privileged users, but for priv'd users the
1033 file will be owned by the directory's owner instead of the user.
1036 (void) chown(fq_save, getuid(), getgid());
1037 #define getuid() vms_getuid()
1038 #endif /* VMS && !SECURE */
1044 /* open savefile for reading */
1048 const char *fq_save;
1051 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1053 fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE);
1055 fd = open(fq_save, O_RDONLY | O_BINARY, 0);
1060 /* delete savefile */
1064 (void) unlink(fqname(SAVEF, SAVEPREFIX, 0));
1065 return 0; /* for restore_saved_game() (ex-xxxmain.c) test */
1068 /* try to open up a save file and prepare to restore it */
1070 restore_saved_game()
1072 const char *fq_save;
1076 set_savefile_name(TRUE);
1078 if (!saveDiskPrompt(1))
1080 #endif /* MFLOPPY */
1081 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1083 nh_uncompress(fq_save);
1084 if ((fd = open_savefile()) < 0)
1087 if (validate(fd, fq_save) != 0) {
1088 (void) nhclose(fd), fd = -1;
1089 (void) delete_savefile();
1094 #if defined(SELECTSAVED)
1096 plname_from_file(filename)
1097 const char *filename;
1102 Strcpy(SAVEF, filename);
1103 #ifdef COMPRESS_EXTENSION
1104 SAVEF[strlen(SAVEF) - strlen(COMPRESS_EXTENSION)] = '\0';
1106 nh_uncompress(SAVEF);
1107 if ((fd = open_savefile()) >= 0) {
1108 if (validate(fd, filename) == 0) {
1109 char tplname[PL_NSIZ];
1110 get_plname_from_file(fd, tplname);
1111 result = dupstr(tplname);
1119 /* --------- obsolete - used to be ifndef STORE_PLNAME_IN_FILE ----*/
1120 #if defined(UNIX) && defined(QT_GRAPHICS)
1121 /* Name not stored in save file, so we have to extract it from
1122 the filename, which loses information
1123 (eg. "/", "_", and "." characters are lost. */
1126 char name[64]; /* more than PL_NSIZ */
1127 #ifdef COMPRESS_EXTENSION
1128 #define EXTSTR COMPRESS_EXTENSION
1133 if ( sscanf( filename, "%*[^/]/%d%63[^.]" EXTSTR, &uid, name ) == 2 ) {
1135 /* "_" most likely means " ", which certainly looks nicer */
1136 for (k=0; name[k]; k++)
1137 if ( name[k] == '_' )
1139 return dupstr(name);
1141 #endif /* UNIX && QT_GRAPHICS */
1145 /* --------- end of obsolete code ----*/
1146 #endif /* 0 - WAS STORE_PLNAME_IN_FILE*/
1148 #endif /* defined(SELECTSAVED) */
1153 #if defined(SELECTSAVED)
1159 const char *fq_save;
1161 Strcpy(plname, "*");
1162 set_savefile_name(FALSE);
1163 #if defined(ZLIB_COMP)
1164 Strcat(SAVEF, COMPRESS_EXTENSION);
1166 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1169 foundfile = foundfile_buffer();
1170 if (findfirst((char *) fq_save)) {
1173 } while (findnext());
1176 result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1177 (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1178 if (findfirst((char *) fq_save)) {
1182 r = plname_from_file(foundfile);
1186 } while (findnext());
1191 #if defined(UNIX) && defined(QT_GRAPHICS)
1192 /* posixly correct version */
1193 int myuid = getuid();
1196 if ((dir = opendir(fqname("save", SAVEPREFIX, 0)))) {
1197 for (n = 0; readdir(dir); n++)
1203 if (!(dir = opendir(fqname("save", SAVEPREFIX, 0))))
1205 result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1206 (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1207 for (i = 0, j = 0; i < n; i++) {
1209 char name[64]; /* more than PL_NSIZ */
1210 struct dirent *entry = readdir(dir);
1214 if (sscanf(entry->d_name, "%d%63s", &uid, name) == 2) {
1216 char filename[BUFSZ];
1219 Sprintf(filename, "save/%d%s", uid, name);
1220 r = plname_from_file(filename);
1231 Strcpy(plname, "*");
1232 set_savefile_name(FALSE);
1233 j = vms_get_saved_games(SAVEF, &result);
1238 qsort(result, j, sizeof (char *), strcmp_wrap);
1241 } else if (result) { /* could happen if save files are obsolete */
1242 free_saved_games(result);
1244 #endif /* SELECTSAVED */
1249 free_saved_games(saved)
1256 free((genericptr_t) saved[i++]);
1257 free((genericptr_t) saved);
1261 /* ---------- END SAVE FILE HANDLING ----------- */
1263 /* ---------- BEGIN FILE COMPRESSION HANDLING ----------- */
1268 redirect(filename, mode, stream, uncomp)
1269 const char *filename, *mode;
1273 if (freopen(filename, mode, stream) == (FILE *) 0) {
1274 (void) fprintf(stderr, "freopen of %s for %scompress failed\n",
1275 filename, uncomp ? "un" : "");
1276 nh_terminate(EXIT_FAILURE);
1281 * using system() is simpler, but opens up security holes and causes
1282 * problems on at least Interactive UNIX 3.0.1 (SVR3.2), where any
1283 * setuid is renounced by /bin/sh, so the files cannot be accessed.
1285 * cf. child() in unixunix.c.
1288 docompress_file(filename, uncomp)
1289 const char *filename;
1294 const char *args[10];
1295 #ifdef COMPRESS_OPTIONS
1301 boolean istty = !strncmpi(windowprocs.name, "tty", 3);
1304 Strcpy(cfn, filename);
1305 #ifdef COMPRESS_EXTENSION
1306 Strcat(cfn, COMPRESS_EXTENSION);
1308 /* when compressing, we know the file exists */
1310 if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0)
1317 args[++i] = "-d"; /* uncompress */
1318 #ifdef COMPRESS_OPTIONS
1320 /* we can't guarantee there's only one additional option, sigh */
1322 boolean inword = FALSE;
1324 Strcpy(opts, COMPRESS_OPTIONS);
1327 if ((*opt == ' ') || (*opt == '\t')) {
1332 } else if (!inword) {
1340 args[++i] = (char *) 0;
1343 /* If we don't do this and we are right after a y/n question *and*
1344 * there is an error message from the compression, the 'y' or 'n' can
1345 * end up being displayed after the error message.
1351 if (f == 0) { /* child */
1353 /* any error messages from the compression must come out after
1354 * the first line, because the more() to let the user read
1355 * them will have to clear the first line. This should be
1356 * invisible if there are no error messages.
1361 /* run compressor without privileges, in case other programs
1362 * have surprises along the line of gzip once taking filenames
1365 /* assume all compressors will compress stdin to stdout
1366 * without explicit filenames. this is true of at least
1367 * compress and gzip, those mentioned in config.h.
1370 redirect(cfn, RDBMODE, stdin, uncomp);
1371 redirect(filename, WRBMODE, stdout, uncomp);
1373 redirect(filename, RDBMODE, stdin, uncomp);
1374 redirect(cfn, WRBMODE, stdout, uncomp);
1376 _pragma_ignore(-Wunused-result)
1377 (void) setgid(getgid());
1378 (void) setuid(getuid());
1380 (void) execv(args[0], (char *const *) args);
1382 (void) fprintf(stderr, "Exec to %scompress %s failed.\n",
1383 uncomp ? "un" : "", filename);
1384 nh_terminate(EXIT_FAILURE);
1385 } else if (f == -1) {
1387 pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename);
1391 (void) signal(SIGINT, SIG_IGN);
1392 (void) signal(SIGQUIT, SIG_IGN);
1393 (void) wait((int *) &i);
1394 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
1396 (void) signal(SIGQUIT, SIG_DFL);
1398 /* I don't think we can really cope with external compression
1399 * without signals, so we'll declare that compress failed and
1400 * go on. (We could do a better job by forcing off external
1401 * compression if there are no signals, but we want this for
1402 * testing with FailSafeC
1407 /* (un)compress succeeded: remove file left behind */
1411 (void) unlink(filename);
1413 /* (un)compress failed; remove the new, bad file */
1415 raw_printf("Unable to uncompress %s", filename);
1416 (void) unlink(filename);
1418 /* no message needed for compress case; life will go on */
1422 /* Give them a chance to read any error messages from the
1423 * compression--these would go to stdout or stderr and would get
1424 * overwritten only in tty mode. It's still ugly, since the
1425 * messages are being written on top of the screen, but at least
1426 * the user can read them.
1428 if (istty && iflags.window_inited) {
1429 clear_nhwindow(WIN_MESSAGE);
1431 /* No way to know if this is feasible */
1437 #endif /* COMPRESS */
1439 #if defined(COMPRESS) || defined(ZLIB_COMP)
1440 #define UNUSED_if_not_COMPRESS /*empty*/
1442 #define UNUSED_if_not_COMPRESS UNUSED
1447 nh_compress(filename)
1448 const char *filename UNUSED_if_not_COMPRESS;
1450 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1451 #ifdef PRAGMA_UNUSED
1452 #pragma unused(filename)
1455 docompress_file(filename, FALSE);
1459 /* uncompress file if it exists */
1461 nh_uncompress(filename)
1462 const char *filename UNUSED_if_not_COMPRESS;
1464 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1465 #ifdef PRAGMA_UNUSED
1466 #pragma unused(filename)
1469 docompress_file(filename, TRUE);
1473 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
1475 make_compressed_name(filename, cfn)
1476 const char *filename;
1479 #ifndef SHORT_FILENAMES
1480 /* Assume free-form filename with no 8.3 restrictions */
1481 strcpy(cfn, filename);
1482 strcat(cfn, COMPRESS_EXTENSION);
1485 #ifdef SAVE_EXTENSION
1486 char *bp = (char *) 0;
1488 strcpy(cfn, filename);
1489 if ((bp = strstri(cfn, SAVE_EXTENSION))) {
1490 strsubst(bp, SAVE_EXTENSION, ".saz");
1493 /* find last occurrence of bon */
1495 while (bp-- > cfn) {
1496 if (strstri(bp, "bon")) {
1497 strsubst(bp, "bon", "boz");
1502 #endif /* SAVE_EXTENSION */
1504 #endif /* SHORT_FILENAMES */
1508 docompress_file(filename, uncomp)
1509 const char *filename;
1512 gzFile compressedfile;
1513 FILE *uncompressedfile;
1518 if (!make_compressed_name(filename, cfn))
1522 /* Open the input and output files */
1523 /* Note that gzopen takes "wb" as its mode, even on systems where
1524 fopen takes "r" and "w" */
1526 uncompressedfile = fopen(filename, RDBMODE);
1527 if (!uncompressedfile) {
1528 pline("Error in zlib docompress_file %s", filename);
1531 compressedfile = gzopen(cfn, "wb");
1532 if (compressedfile == NULL) {
1534 pline("zlib failed to allocate memory");
1536 panic("Error in docompress_file %d", errno);
1538 fclose(uncompressedfile);
1542 /* Copy from the uncompressed to the compressed file */
1545 len = fread(buf, 1, sizeof(buf), uncompressedfile);
1546 if (ferror(uncompressedfile)) {
1547 pline("Failure reading uncompressed file");
1548 pline("Can't compress %s.", filename);
1549 fclose(uncompressedfile);
1550 gzclose(compressedfile);
1555 break; /* End of file */
1557 len2 = gzwrite(compressedfile, buf, len);
1559 pline("Failure writing compressed file");
1560 pline("Can't compress %s.", filename);
1561 fclose(uncompressedfile);
1562 gzclose(compressedfile);
1568 fclose(uncompressedfile);
1569 gzclose(compressedfile);
1571 /* Delete the file left behind */
1573 (void) unlink(filename);
1575 } else { /* uncomp */
1577 /* Open the input and output files */
1578 /* Note that gzopen takes "rb" as its mode, even on systems where
1579 fopen takes "r" and "w" */
1581 compressedfile = gzopen(cfn, "rb");
1582 if (compressedfile == NULL) {
1584 pline("zlib failed to allocate memory");
1585 } else if (errno != ENOENT) {
1586 panic("Error in zlib docompress_file %s, %d", filename,
1591 uncompressedfile = fopen(filename, WRBMODE);
1592 if (!uncompressedfile) {
1593 pline("Error in zlib docompress file uncompress %s", filename);
1594 gzclose(compressedfile);
1598 /* Copy from the compressed to the uncompressed file */
1601 len = gzread(compressedfile, buf, sizeof(buf));
1602 if (len == (unsigned) -1) {
1603 pline("Failure reading compressed file");
1604 pline("Can't uncompress %s.", filename);
1605 fclose(uncompressedfile);
1606 gzclose(compressedfile);
1607 (void) unlink(filename);
1611 break; /* End of file */
1613 fwrite(buf, 1, len, uncompressedfile);
1614 if (ferror(uncompressedfile)) {
1615 pline("Failure writing uncompressed file");
1616 pline("Can't uncompress %s.", filename);
1617 fclose(uncompressedfile);
1618 gzclose(compressedfile);
1619 (void) unlink(filename);
1624 fclose(uncompressedfile);
1625 gzclose(compressedfile);
1627 /* Delete the file left behind */
1631 #endif /* RLC 09 Mar 1999: End ZLIB patch */
1633 /* ---------- END FILE COMPRESSION HANDLING ----------- */
1635 /* ---------- BEGIN FILE LOCKING HANDLING ----------- */
1637 static int nesting = 0;
1639 #if defined(NO_FILE_LINKS) || defined(USE_FCNTL) /* implies UNIX */
1640 static int lockfd; /* for lock_file() to pass to unlock_file() */
1643 struct flock sflock; /* for unlocking, same as above */
1646 #define HUP if (!program_state.done_hup)
1650 make_lockname(filename, lockname)
1651 const char *filename;
1654 #if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \
1656 #ifdef NO_FILE_LINKS
1657 Strcpy(lockname, LOCKDIR);
1658 Strcat(lockname, "/");
1659 Strcat(lockname, filename);
1661 Strcpy(lockname, filename);
1665 char *semi_colon = rindex(lockname, ';');
1669 Strcat(lockname, ".lock;1");
1671 Strcat(lockname, "_lock");
1674 #else /* !(UNIX || VMS || AMIGA || WIN32 || MSDOS) */
1675 #ifdef PRAGMA_UNUSED
1676 #pragma unused(filename)
1682 #endif /* !USE_FCNTL */
1686 lock_file(filename, whichprefix, retryct)
1687 const char *filename;
1691 #if defined(PRAGMA_UNUSED) && !(defined(UNIX) || defined(VMS)) \
1692 && !(defined(AMIGA) || defined(WIN32) || defined(MSDOS))
1693 #pragma unused(retryct)
1696 char locknambuf[BUFSZ];
1697 const char *lockname;
1702 impossible("TRIED TO NEST LOCKS");
1707 lockname = make_lockname(filename, locknambuf);
1708 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1709 lockname = fqname(lockname, LOCKPREFIX, 2);
1712 filename = fqname(filename, whichprefix, 0);
1714 lockfd = open(filename, O_RDWR);
1716 HUP raw_printf("Cannot open file %s. Is NetHack installed correctly?",
1721 sflock.l_type = F_WRLCK;
1722 sflock.l_whence = SEEK_SET;
1727 #if defined(UNIX) || defined(VMS)
1729 while (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1731 #ifdef NO_FILE_LINKS
1732 while ((lockfd = open(lockname, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1) {
1734 while (link(filename, lockname) == -1) {
1741 "Waiting for release of fcntl lock on %s. (%d retries left).",
1745 HUP(void) raw_print("I give up. Sorry.");
1746 HUP raw_printf("Some other process has an unnatural grip on %s.",
1752 register int errnosv = errno;
1754 switch (errnosv) { /* George Barbanis */
1758 "Waiting for access to %s. (%d retries left).", filename,
1760 #if defined(SYSV) || defined(ULTRIX) || defined(VMS)
1765 HUP(void) raw_print("I give up. Sorry.");
1766 HUP raw_printf("Perhaps there is an old %s around?",
1774 HUP raw_printf("Can't find file %s to lock!", filename);
1778 HUP raw_printf("No write permission to lock %s!", filename);
1781 #ifdef VMS /* c__translate(vmsfiles.c) */
1783 /* could be misleading, but usually right */
1784 HUP raw_printf("Can't lock %s due to directory protection.",
1790 /* take a wild guess at the underlying cause */
1791 HUP perror(lockname);
1792 HUP raw_printf("Cannot lock %s.", filename);
1794 "(Perhaps you are running NetHack from inside the distribution package?).");
1798 HUP perror(lockname);
1799 HUP raw_printf("Cannot lock %s for unknown reason (%d).",
1804 #endif /* USE_FCNTL */
1806 #endif /* UNIX || VMS */
1808 #if (defined(AMIGA) || defined(WIN32) || defined(MSDOS)) \
1809 && !defined(USE_FCNTL)
1811 #define OPENFAILURE(fd) (!fd)
1814 #define OPENFAILURE(fd) (fd < 0)
1817 while (--retryct && OPENFAILURE(lockptr)) {
1818 #if defined(WIN32) && !defined(WIN_CE)
1819 lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE);
1821 (void) DeleteFile(lockname); /* in case dead process was here first */
1823 lockptr = Open(lockname, MODE_NEWFILE);
1825 lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE);
1828 if (OPENFAILURE(lockptr)) {
1829 raw_printf("Waiting for access to %s. (%d retries left).",
1835 raw_printf("I give up. Sorry.");
1839 #endif /* AMIGA || WIN32 || MSDOS */
1843 #ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */
1847 #define unlink(foo) vms_unlink(foo)
1850 /* unlock file, which must be currently locked by lock_file */
1852 unlock_file(filename)
1853 const char *filename;
1856 char locknambuf[BUFSZ];
1857 const char *lockname;
1862 sflock.l_type = F_UNLCK;
1863 if (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1864 HUP raw_printf("Can't remove fcntl lock on %s.", filename);
1865 (void) close(lockfd);
1868 lockname = make_lockname(filename, locknambuf);
1869 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1870 lockname = fqname(lockname, LOCKPREFIX, 2);
1873 #if defined(UNIX) || defined(VMS)
1874 if (unlink(lockname) < 0)
1875 HUP raw_printf("Can't unlink %s.", lockname);
1876 #ifdef NO_FILE_LINKS
1877 (void) nhclose(lockfd);
1880 #endif /* UNIX || VMS */
1882 #if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1885 DeleteFile(lockname);
1887 #endif /* AMIGA || WIN32 || MSDOS */
1888 #endif /* USE_FCNTL */
1894 /* ---------- END FILE LOCKING HANDLING ----------- */
1896 /* ---------- BEGIN CONFIG FILE HANDLING ----------- */
1898 const char *default_configfile =
1902 #if defined(MAC) || defined(__BEOS__)
1905 #if defined(MSDOS) || defined(WIN32)
1913 /* used for messaging */
1914 char configfile[BUFSZ];
1917 /* conflict with speed-dial under windows
1918 * for XXX.cnf file so support of NetHack.cnf
1919 * is for backward compatibility only.
1920 * Preferred name (and first tried) is now defaults.nh but
1921 * the game will try the old name if there
1922 * is no defaults.nh.
1924 const char *backward_compat_configfile = "nethack.cnf";
1927 /* remember the name of the file we're accessing;
1928 if may be used in option reject messages */
1930 set_configfile_name(fname)
1933 (void) strncpy(configfile, fname, sizeof configfile - 1);
1934 configfile[sizeof configfile - 1] = '\0';
1938 #define fopenp fopen
1942 fopen_config_file(filename, src)
1943 const char *filename;
1947 #if defined(UNIX) || defined(VMS)
1948 char tmp_config[BUFSZ];
1952 if (src == SET_IN_SYS) {
1953 /* SYSCF_FILE; if we can't open it, caller will bail */
1954 if (filename && *filename) {
1955 set_configfile_name(fqname(filename, SYSCONFPREFIX, 0));
1956 fp = fopenp(configfile, "r");
1961 /* If src != SET_IN_SYS, "filename" is an environment variable, so it
1962 * should hang around. If set, it is expected to be a full path name
1965 if (filename && *filename) {
1966 set_configfile_name(filename);
1968 if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */
1969 /* nasty sneaky attempt to read file through
1970 * NetHack's setuid permissions -- this is the only
1971 * place a file name may be wholly under the player's
1972 * control (but SYSCF_FILE is not under the player's
1973 * control so it's OK).
1975 raw_printf("Access to %s denied (%d).", configfile, errno);
1977 /* fall through to standard names */
1980 if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
1982 #if defined(UNIX) || defined(VMS)
1984 /* access() above probably caught most problems for UNIX */
1985 raw_printf("Couldn't open requested config file %s (%d).",
1991 /* fall through to standard names */
1993 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
1994 set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0));
1995 if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
1997 } else if (strcmp(default_configfile, configfile)) {
1998 set_configfile_name(default_configfile);
1999 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2003 set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0));
2004 if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
2006 } else if (strcmp(backward_compat_configfile, configfile)) {
2007 set_configfile_name(backward_compat_configfile);
2008 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2013 /* constructed full path names don't need fqname() */
2015 /* no punctuation, so might be a logical name */
2016 set_configfile_name(fqname("nethackini", CONFIGPREFIX, 0));
2017 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2019 set_configfile_name("sys$login:nethack.ini");
2020 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2023 envp = nh_getenv("HOME");
2024 if (!envp || !*envp)
2025 Strcpy(tmp_config, "NetHack.cnf");
2027 Sprintf(tmp_config, "%s%s%s", envp,
2028 !index(":]>/", envp[strlen(envp) - 1]) ? "/" : "",
2030 set_configfile_name(tmp_config);
2031 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2033 #else /* should be only UNIX left */
2034 envp = nh_getenv("HOME");
2035 #if 1 /*JP*//*".jnethackrc"
\82ð
\97D
\90æ
\82µ
\82Ä
\93Ç
\82Ý
\8d\9e\82Ý*/
2037 Strcpy(tmp_config, ".jnethackrc");
2039 Sprintf(tmp_config, "%s/%s", envp, ".jnethackrc");
2041 set_configfile_name(tmp_config);
2042 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2046 Strcpy(tmp_config, ".nethackrc");
2048 Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
2050 set_configfile_name(tmp_config);
2051 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2053 #if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX */
2054 /* try an alternative */
2056 /* OSX-style configuration settings */
2057 Sprintf(tmp_config, "%s/%s", envp,
2058 "Library/Preferences/NetHack Defaults");
2059 set_configfile_name(tmp_config);
2060 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2062 /* may be easier for user to edit if filename as '.txt' suffix */
2063 Sprintf(tmp_config, "%s/%s", envp,
2064 "Library/Preferences/NetHack Defaults.txt");
2065 set_configfile_name(tmp_config);
2066 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2069 #endif /*__APPLE__*/
2070 if (errno != ENOENT) {
2071 const char *details;
2073 /* e.g., problems when setuid NetHack can't search home
2074 directory restricted to user */
2075 #if defined(NHSTDC) && !defined(NOTSTDC)
2076 if ((details = strerror(errno)) == 0)
2079 raw_printf("Couldn't open default config file %s %s(%d).",
2080 configfile, details, errno);
2083 #endif /* !VMS => Unix */
2084 #endif /* !(MICRO || MAC || __BEOS__ || WIN32) */
2089 * Retrieve a list of integers from buf into a uchar array.
2091 * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
2092 * location is unchanged. Callers must handle zeros if modlist is FALSE.
2095 get_uchars(bufp, list, modlist, size, name)
2096 char *bufp; /* current pointer */
2097 uchar *list; /* return list */
2098 boolean modlist; /* TRUE: list is being modified in place */
2099 int size; /* return list size */
2100 const char *name; /* name of option for error message */
2102 unsigned int num = 0;
2104 boolean havenum = FALSE;
2113 /* if modifying in place, don't insert zeros */
2114 if (num || !modlist)
2120 if (count == size || !*bufp)
2136 num = num * 10 + (*bufp - '0');
2146 raw_printf("Syntax error in %s", name);
2154 #ifdef NOCWD_ASSUMPTIONS
2156 adjust_prefix(bufp, prefixid)
2164 /* Backward compatibility, ignore trailing ;n */
2165 if ((ptr = index(bufp, ';')) != 0)
2167 if (strlen(bufp) > 0) {
2168 fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
2169 Strcpy(fqn_prefix[prefixid], bufp);
2170 append_slash(fqn_prefix[prefixid]);
2175 /* Choose at random one of the sep separated parts from str. Mangles str. */
2177 choose_random_part(str,sep)
2190 if (*str == sep) nsep++;
2195 while ((csep > 0) && *str) {
2197 if (*str == sep) csep--;
2200 if (*str == sep) str++;
2202 while (*str && *str != sep) {
2214 free_config_sections()
2216 if (config_section_chosen) {
2217 free(config_section_chosen);
2218 config_section_chosen = NULL;
2220 if (config_section_current) {
2221 free(config_section_current);
2222 config_section_current = NULL;
2227 is_config_section(str)
2230 const char *a = rindex(str, ']');
2232 return (a && *str == '[' && *(a+1) == '\0' && (int)(a - str) > 0);
2236 handle_config_section(buf)
2239 if (is_config_section(buf)) {
2241 if (config_section_current) {
2242 free(config_section_current);
2244 config_section_current = dupstr(&buf[1]);
2245 send = rindex(config_section_current, ']');
2247 debugpline1("set config section: '%s'", config_section_current);
2251 if (config_section_current) {
2252 if (!config_section_chosen)
2254 if (strcmp(config_section_current, config_section_chosen))
2260 #define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
2262 /* find the '=' or ':' */
2269 bufp = index(buf, '=');
2270 altp = index(buf, ':');
2271 if (!bufp || (altp && altp < bufp))
2278 parse_config_line(origbuf)
2281 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
2282 static boolean ramdisk_specified = FALSE;
2287 char *bufp, buf[4 * BUFSZ];
2288 uchar translate[MAXPCHARS];
2290 boolean retval = TRUE;
2291 int src = iflags.parse_config_file_src;
2293 /* convert any tab to space, condense consecutive spaces into one,
2294 remove leading and trailing spaces (exception: if there is nothing
2295 but spaces, one of them will be kept even though it leads/trails) */
2296 mungspaces(strcpy(buf, origbuf));
2298 /* find the '=' or ':' */
2299 bufp = find_optparam(buf);
2301 config_error_add("Not a config statement, missing '='");
2304 /* skip past '=', then space between it and value, if any */
2309 /* Go through possible variables */
2310 /* some of these (at least LEVELS and SAVE) should now set the
2311 * appropriate fqn_prefix[] rather than specialized variables
2313 if (match_varname(buf, "OPTIONS", 4)) {
2314 /* hack: un-mungspaces to allow consecutive spaces in
2315 general options until we verify that this is unnecessary;
2316 '=' or ':' is guaranteed to be present */
2317 bufp = find_optparam(origbuf);
2318 ++bufp; /* skip '='; parseoptions() handles spaces */
2320 if (!parseoptions(bufp, TRUE, TRUE))
2322 } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
2323 add_autopickup_exception(bufp);
2324 } else if (match_varname(buf, "BINDINGS", 4)) {
2325 if (!parsebindings(bufp))
2327 } else if (match_varname(buf, "AUTOCOMPLETE", 5)) {
2328 parseautocomplete(bufp, TRUE);
2329 } else if (match_varname(buf, "MSGTYPE", 7)) {
2330 if (!msgtype_parse_add(bufp))
2332 #ifdef NOCWD_ASSUMPTIONS
2333 } else if (match_varname(buf, "HACKDIR", 4)) {
2334 adjust_prefix(bufp, HACKPREFIX);
2335 } else if (match_varname(buf, "LEVELDIR", 4)
2336 || match_varname(buf, "LEVELS", 4)) {
2337 adjust_prefix(bufp, LEVELPREFIX);
2338 } else if (match_varname(buf, "SAVEDIR", 4)) {
2339 adjust_prefix(bufp, SAVEPREFIX);
2340 } else if (match_varname(buf, "BONESDIR", 5)) {
2341 adjust_prefix(bufp, BONESPREFIX);
2342 } else if (match_varname(buf, "DATADIR", 4)) {
2343 adjust_prefix(bufp, DATAPREFIX);
2344 } else if (match_varname(buf, "SCOREDIR", 4)) {
2345 adjust_prefix(bufp, SCOREPREFIX);
2346 } else if (match_varname(buf, "LOCKDIR", 4)) {
2347 adjust_prefix(bufp, LOCKPREFIX);
2348 } else if (match_varname(buf, "CONFIGDIR", 4)) {
2349 adjust_prefix(bufp, CONFIGPREFIX);
2350 } else if (match_varname(buf, "TROUBLEDIR", 4)) {
2351 adjust_prefix(bufp, TROUBLEPREFIX);
2352 #else /*NOCWD_ASSUMPTIONS*/
2354 } else if (match_varname(buf, "HACKDIR", 4)) {
2355 (void) strncpy(hackdir, bufp, PATHLEN - 1);
2357 } else if (match_varname(buf, "RAMDISK", 3)) {
2358 /* The following ifdef is NOT in the wrong
2359 * place. For now, we accept and silently
2362 if (strlen(bufp) >= PATHLEN)
2363 bufp[PATHLEN - 1] = '\0';
2364 Strcpy(levels, bufp);
2365 ramdisk = (strcmp(permbones, levels) != 0);
2366 ramdisk_specified = TRUE;
2369 } else if (match_varname(buf, "LEVELS", 4)) {
2370 if (strlen(bufp) >= PATHLEN)
2371 bufp[PATHLEN - 1] = '\0';
2372 Strcpy(permbones, bufp);
2373 if (!ramdisk_specified || !*levels)
2374 Strcpy(levels, bufp);
2375 ramdisk = (strcmp(permbones, levels) != 0);
2376 } else if (match_varname(buf, "SAVE", 4)) {
2378 extern int saveprompt;
2382 if ((ptr = index(bufp, ';')) != 0) {
2385 if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') {
2390 #if defined(SYSFLAGS) && defined(MFLOPPY)
2392 saveprompt = sysflags.asksavedisk;
2395 (void) strncpy(SAVEP, bufp, SAVESIZE - 1);
2396 append_slash(SAVEP);
2398 #endif /*NOCWD_ASSUMPTIONS*/
2400 } else if (match_varname(buf, "NAME", 4)) {
2401 (void) strncpy(plname, bufp, PL_NSIZ - 1);
2402 } else if (match_varname(buf, "ROLE", 4)
2403 || match_varname(buf, "CHARACTER", 4)) {
2404 if ((len = str2role(bufp)) >= 0)
2405 flags.initrole = len;
2406 } else if (match_varname(buf, "DOGNAME", 3)) {
2407 (void) strncpy(dogname, bufp, PL_PSIZ - 1);
2408 } else if (match_varname(buf, "CATNAME", 3)) {
2409 (void) strncpy(catname, bufp, PL_PSIZ - 1);
2412 } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) {
2414 free((genericptr_t) sysopt.wizards);
2415 sysopt.wizards = dupstr(bufp);
2416 if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
2417 /* pre-format WIZARDS list now; it's displayed during a panic
2418 and since that panic might be due to running out of memory,
2419 we don't want to risk attempting to allocate any memory then */
2420 if (sysopt.fmtd_wizard_list)
2421 free((genericptr_t) sysopt.fmtd_wizard_list);
2422 sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
2424 } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) {
2425 if (sysopt.shellers)
2426 free((genericptr_t) sysopt.shellers);
2427 sysopt.shellers = dupstr(bufp);
2428 } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) {
2429 if (sysopt.explorers)
2430 free((genericptr_t) sysopt.explorers);
2431 sysopt.explorers = dupstr(bufp);
2432 } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) {
2433 /* if showdebug() has already been called (perhaps we've added
2434 some debugpline() calls to option processing) and has found
2435 a value for getenv("DEBUGFILES"), don't override that */
2436 if (sysopt.env_dbgfl <= 0) {
2437 if (sysopt.debugfiles)
2438 free((genericptr_t) sysopt.debugfiles);
2439 sysopt.debugfiles = dupstr(bufp);
2441 } else if (src == SET_IN_SYS && match_varname(buf, "DUMPLOGFILE", 7)) {
2443 if (sysopt.dumplogfile)
2444 free((genericptr_t) sysopt.dumplogfile);
2445 sysopt.dumplogfile = dupstr(bufp);
2447 } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) {
2448 if (sysopt.genericusers)
2449 free((genericptr_t) sysopt.genericusers);
2450 sysopt.genericusers = dupstr(bufp);
2451 } else if (src == SET_IN_SYS && match_varname(buf, "BONES_POOLS", 10)) {
2452 /* max value of 10 guarantees (N % bones.pools) will be one digit
2453 so we don't lose control of the length of bones file names */
2455 sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10);
2456 /* note: right now bones_pools==0 is the same as bones_pools==1,
2457 but we could change that and make bones_pools==0 become an
2458 indicator to suppress bones usage altogether */
2459 } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
2461 free((genericptr_t) sysopt.support);
2462 sysopt.support = dupstr(bufp);
2463 } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) {
2465 free((genericptr_t) sysopt.recover);
2466 sysopt.recover = dupstr(bufp);
2467 } else if (src == SET_IN_SYS
2468 && match_varname(buf, "CHECK_SAVE_UID", 14)) {
2470 sysopt.check_save_uid = n;
2471 } else if (src == SET_IN_SYS
2472 && match_varname(buf, "CHECK_PLNAME", 12)) {
2474 sysopt.check_plname = n;
2475 } else if (match_varname(buf, "SEDUCE", 6)) {
2476 n = !!atoi(bufp); /* XXX this could be tighter */
2477 /* allow anyone to turn it off, but only sysconf to turn it on*/
2478 if (src != SET_IN_SYS && n != 0) {
2479 config_error_add("Illegal value in SEDUCE");
2483 sysopt_seduce_set(sysopt.seduce);
2484 } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
2486 /* XXX to get more than 25, need to rewrite all lock code */
2487 if (n < 0 || n > 25) {
2488 config_error_add("Illegal value in MAXPLAYERS (maximum is 25).");
2491 sysopt.maxplayers = n;
2492 } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
2495 config_error_add("Illegal value in PERSMAX (minimum is 1).");
2499 } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
2501 if (n != 0 && n != 1) {
2502 config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1).");
2505 sysopt.pers_is_uid = n;
2506 } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
2509 config_error_add("Illegal value in ENTRYMAX (minimum is 10).");
2512 sysopt.entrymax = n;
2513 } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
2516 config_error_add("Illegal value in POINTSMIN (minimum is 1).");
2519 sysopt.pointsmin = n;
2520 } else if (src == SET_IN_SYS
2521 && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
2525 "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
2528 sysopt.tt_oname_maxrank = n;
2530 /* SYSCF PANICTRACE options */
2531 } else if (src == SET_IN_SYS
2532 && match_varname(buf, "PANICTRACE_LIBC", 15)) {
2534 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
2535 if (n < 0 || n > 2) {
2536 config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
2540 sysopt.panictrace_libc = n;
2541 } else if (src == SET_IN_SYS
2542 && match_varname(buf, "PANICTRACE_GDB", 14)) {
2544 #if defined(PANICTRACE)
2545 if (n < 0 || n > 2) {
2546 config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2).");
2550 sysopt.panictrace_gdb = n;
2551 } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
2552 #if defined(PANICTRACE) && !defined(VMS)
2553 if (!file_exists(bufp)) {
2554 config_error_add("File specified in GDBPATH does not exist.");
2559 free((genericptr_t) sysopt.gdbpath);
2560 sysopt.gdbpath = dupstr(bufp);
2561 } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
2562 #if defined(PANICTRACE) && !defined(VMS)
2563 if (!file_exists(bufp)) {
2564 config_error_add("File specified in GREPPATH does not exist.");
2568 if (sysopt.greppath)
2569 free((genericptr_t) sysopt.greppath);
2570 sysopt.greppath = dupstr(bufp);
2573 } else if (match_varname(buf, "BOULDER", 3)) {
2574 (void) get_uchars(bufp, &iflags.bouldersym, TRUE, 1,
2576 } else if (match_varname(buf, "MENUCOLOR", 9)) {
2577 if (!add_menu_coloring(bufp))
2579 } else if (match_varname(buf, "HILITE_STATUS", 6)) {
2580 #ifdef STATUS_HILITES
2581 if (!parse_status_hl1(bufp, TRUE))
2584 } else if (match_varname(buf, "WARNINGS", 5)) {
2585 (void) get_uchars(bufp, translate, FALSE, WARNCOUNT,
2587 assign_warnings(translate);
2588 } else if (match_varname(buf, "SYMBOLS", 4)) {
2589 if (!parsesymbols(bufp)) {
2590 config_error_add("Error in SYMBOLS definition '%s'", bufp);
2593 switch_symbols(TRUE);
2594 } else if (match_varname(buf, "WIZKIT", 6)) {
2595 (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
2597 } else if (match_varname(buf, "FONT", 4)) {
2600 if (t = strchr(buf + 5, ':')) {
2602 amii_set_text_font(buf + 5, atoi(t + 1));
2605 } else if (match_varname(buf, "PATH", 4)) {
2606 (void) strncpy(PATH, bufp, PATHLEN - 1);
2607 } else if (match_varname(buf, "DEPTH", 5)) {
2608 extern int amii_numcolors;
2609 int val = atoi(bufp);
2611 amii_numcolors = 1L << min(DEPTH, val);
2613 } else if (match_varname(buf, "DRIPENS", 7)) {
2617 for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0;
2618 i < 20 && (t = strtok((char *) 0, ",/")), ++i) {
2619 sscanf(t, "%d", &val);
2620 sysflags.amii_dripens[i] = val;
2623 } else if (match_varname(buf, "SCREENMODE", 10)) {
2624 extern long amii_scrnmode;
2626 if (!stricmp(bufp, "req"))
2627 amii_scrnmode = 0xffffffff; /* Requester */
2628 else if (sscanf(bufp, "%x", &amii_scrnmode) != 1)
2630 } else if (match_varname(buf, "MSGPENS", 7)) {
2631 extern int amii_msgAPen, amii_msgBPen;
2632 char *t = strtok(bufp, ",/");
2635 sscanf(t, "%d", &amii_msgAPen);
2636 if (t = strtok((char *) 0, ",/"))
2637 sscanf(t, "%d", &amii_msgBPen);
2639 } else if (match_varname(buf, "TEXTPENS", 8)) {
2640 extern int amii_textAPen, amii_textBPen;
2641 char *t = strtok(bufp, ",/");
2644 sscanf(t, "%d", &amii_textAPen);
2645 if (t = strtok((char *) 0, ",/"))
2646 sscanf(t, "%d", &amii_textBPen);
2648 } else if (match_varname(buf, "MENUPENS", 8)) {
2649 extern int amii_menuAPen, amii_menuBPen;
2650 char *t = strtok(bufp, ",/");
2653 sscanf(t, "%d", &amii_menuAPen);
2654 if (t = strtok((char *) 0, ",/"))
2655 sscanf(t, "%d", &amii_menuBPen);
2657 } else if (match_varname(buf, "STATUSPENS", 10)) {
2658 extern int amii_statAPen, amii_statBPen;
2659 char *t = strtok(bufp, ",/");
2662 sscanf(t, "%d", &amii_statAPen);
2663 if (t = strtok((char *) 0, ",/"))
2664 sscanf(t, "%d", &amii_statBPen);
2666 } else if (match_varname(buf, "OTHERPENS", 9)) {
2667 extern int amii_otherAPen, amii_otherBPen;
2668 char *t = strtok(bufp, ",/");
2671 sscanf(t, "%d", &amii_otherAPen);
2672 if (t = strtok((char *) 0, ",/"))
2673 sscanf(t, "%d", &amii_otherBPen);
2675 } else if (match_varname(buf, "PENS", 4)) {
2676 extern unsigned short amii_init_map[AMII_MAXCOLORS];
2680 for (i = 0, t = strtok(bufp, ",/");
2681 i < AMII_MAXCOLORS && t != (char *) 0;
2682 t = strtok((char *) 0, ",/"), ++i) {
2683 sscanf(t, "%hx", &amii_init_map[i]);
2685 amii_setpens(amii_numcolors = i);
2686 } else if (match_varname(buf, "FGPENS", 6)) {
2687 extern int foreg[AMII_MAXCOLORS];
2691 for (i = 0, t = strtok(bufp, ",/");
2692 i < AMII_MAXCOLORS && t != (char *) 0;
2693 t = strtok((char *) 0, ",/"), ++i) {
2694 sscanf(t, "%d", &foreg[i]);
2696 } else if (match_varname(buf, "BGPENS", 6)) {
2697 extern int backg[AMII_MAXCOLORS];
2701 for (i = 0, t = strtok(bufp, ",/");
2702 i < AMII_MAXCOLORS && t != (char *) 0;
2703 t = strtok((char *) 0, ",/"), ++i) {
2704 sscanf(t, "%d", &backg[i]);
2708 } else if (match_varname(buf, "SOUNDDIR", 8)) {
2709 sounddir = dupstr(bufp);
2710 } else if (match_varname(buf, "SOUND", 5)) {
2711 add_sound_mapping(bufp);
2713 } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
2715 extern char *qt_tilewidth;
2717 if (qt_tilewidth == NULL)
2718 qt_tilewidth = dupstr(bufp);
2720 } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
2722 extern char *qt_tileheight;
2724 if (qt_tileheight == NULL)
2725 qt_tileheight = dupstr(bufp);
2727 } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
2729 extern char *qt_fontsize;
2731 if (qt_fontsize == NULL)
2732 qt_fontsize = dupstr(bufp);
2734 } else if (match_varname(buf, "QT_COMPACT", 10)) {
2736 extern int qt_compact_mode;
2738 qt_compact_mode = atoi(bufp);
2741 config_error_add("Unknown config statement");
2749 can_read_file(filename)
2750 const char *filename;
2752 return (boolean) (access(filename, 4) == 0);
2754 #endif /* USER_SOUNDS */
2756 struct _config_error_frame {
2759 boolean origline_shown;
2762 char origline[4 * BUFSZ];
2764 struct _config_error_frame *next;
2767 struct _config_error_frame *config_error_data = (struct _config_error_frame *)0;
2770 config_error_init(from_file, sourcename, secure)
2772 const char *sourcename;
2775 struct _config_error_frame *tmp = (struct _config_error_frame *)
2776 alloc(sizeof(struct _config_error_frame));
2779 tmp->num_errors = 0;
2780 tmp->origline_shown = FALSE;
2781 tmp->fromfile = from_file;
2782 tmp->secure = secure;
2783 tmp->origline[0] = '\0';
2784 if (sourcename && sourcename[0]) {
2785 (void) strncpy(tmp->source, sourcename, sizeof(tmp->source)-1);
2786 tmp->source[sizeof(tmp->source)-1] = '\0';
2788 tmp->source[0] = '\0';
2790 tmp->next = config_error_data;
2791 config_error_data = tmp;
2795 config_error_nextline(line)
2798 struct _config_error_frame *ced = config_error_data;
2803 if (ced->num_errors && ced->secure)
2807 ced->origline_shown = FALSE;
2808 if (line && line[0]) {
2809 (void) strncpy(ced->origline, line, sizeof(ced->origline)-1);
2810 ced->origline[sizeof(ced->origline)-1] = '\0';
2812 ced->origline[0] = '\0';
2818 void config_error_add
2819 VA_DECL(const char *, str)
2822 char lineno[QBUFSZ];
2825 VA_INIT(str, char *);
2827 Vsprintf(buf, str, VA_ARGS);
2829 if (!config_error_data) {
2830 pline("%s.", *buf ? buf : "Unknown error");
2835 config_error_data->num_errors++;
2836 if (!config_error_data->origline_shown
2837 && !config_error_data->secure) {
2838 pline("\n%s", config_error_data->origline);
2839 config_error_data->origline_shown = TRUE;
2841 if (config_error_data->line_num > 0
2842 && !config_error_data->secure) {
2843 Sprintf(lineno, "Line %i: ", config_error_data->line_num);
2847 config_error_data->secure ? "Error:" : " *",
2849 *buf ? buf : "Unknown error");
2858 struct _config_error_frame *tmp = config_error_data;
2860 if (!config_error_data)
2862 n = config_error_data->num_errors;
2864 pline("\n%i error%s in %s.\n", n,
2866 *config_error_data->source
2867 ? config_error_data->source : configfile);
2871 config_error_data = tmp->next;
2879 read_config_file(filename, src)
2880 const char *filename;
2886 if (!(fp = fopen_config_file(filename, src)))
2889 /* begin detection of duplicate configfile options */
2890 set_duplicate_opt_detection(1);
2891 free_config_sections();
2892 iflags.parse_config_file_src = src;
2894 rv = parse_conf_file(fp, parse_config_line);
2897 free_config_sections();
2898 /* turn off detection of duplicate configfile options */
2899 set_duplicate_opt_detection(0);
2907 #if defined(VMS) || defined(UNIX)
2908 char tmp_wizkit[BUFSZ];
2912 envp = nh_getenv("WIZKIT");
2914 (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2919 if (access(wizkit, 4) == -1) {
2920 /* 4 is R_OK on newer systems */
2921 /* nasty sneaky attempt to read file through
2922 * NetHack's setuid permissions -- this is a
2923 * place a file name may be wholly under the player's
2926 raw_printf("Access to %s denied (%d).", wizkit, errno);
2928 /* fall through to standard names */
2931 if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) {
2933 #if defined(UNIX) || defined(VMS)
2935 /* access() above probably caught most problems for UNIX */
2936 raw_printf("Couldn't open requested config file %s (%d).", wizkit,
2942 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2943 if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
2947 envp = nh_getenv("HOME");
2949 Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
2951 Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
2952 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2954 #else /* should be only UNIX left */
2955 envp = nh_getenv("HOME");
2957 Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
2959 Strcpy(tmp_wizkit, wizkit);
2960 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2962 else if (errno != ENOENT) {
2963 /* e.g., problems when setuid NetHack can't search home
2964 * directory restricted to user */
2965 raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit,
2974 /* add to hero's inventory if there's room, otherwise put item on floor */
2979 if (!obj || obj == &zeroobj)
2982 /* subset of starting inventory pre-ID */
2984 if (Role_if(PM_PRIEST))
2986 /* same criteria as lift_object()'s check for available inventory slot */
2987 if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52
2988 && !merge_choice(invent, obj)) {
2989 /* inventory overflow; can't just place & stack object since
2990 hero isn't in position yet, so schedule for arrival later */
2991 add_to_migration(obj);
2992 obj->ox = 0; /* index of main dungeon */
2993 obj->oy = 1; /* starting level number */
2995 (long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
3003 proc_wizkit_line(buf)
3006 struct obj *otmp = readobjnam(buf, (struct obj *) 0);
3009 if (otmp != &zeroobj)
3010 wizkit_addinv(otmp);
3012 /* .60 limits output line width to 79 chars */
3013 config_error_add("Bad wizkit item: \"%.60s\"", buf);
3024 if (!wizard || !(fp = fopen_wizkit_file()))
3027 program_state.wizkit_wishing = 1;
3028 config_error_init(TRUE, "WIZKIT", FALSE);
3030 parse_conf_file(fp, proc_wizkit_line);
3033 config_error_done();
3034 program_state.wizkit_wishing = 0;
3041 * Read from file fp, handling comments, empty lines, config sections,
3042 * CHOOSE, and line continuation, calling proc for every valid line.
3044 * Continued lines are merged together with one space in between.
3047 parse_conf_file(fp, proc)
3049 boolean FDECL((*proc), (char *));
3051 char inbuf[4 * BUFSZ];
3052 boolean rv = TRUE; /* assume successful parse */
3054 boolean skip = FALSE, morelines = FALSE;
3055 char *buf = (char *) 0;
3057 free_config_sections();
3059 while (fgets(inbuf, (int) (sizeof inbuf), fp)) {
3060 ep = index(inbuf, '\n');
3061 if (skip) { /* in case previous line was too long */
3063 skip = FALSE; /* found newline; next line is normal */
3066 config_error_add("Line too long, skipping");
3067 skip = TRUE; /* newline missing; discard next fgets */
3069 char *tmpbuf = (char *) 0;
3071 boolean ignoreline = FALSE;
3072 boolean oldline = FALSE;
3074 *ep = '\0'; /* remove newline */
3076 /* line continuation (trailing '\') */
3077 morelines = (--ep >= inbuf && *ep == '\\');
3081 /* trim off spaces at end of line */
3082 while (--ep >= inbuf && (*ep == ' ' || *ep == '\t' || *ep == '\r'))
3085 if (!config_error_nextline(inbuf)) {
3088 free(buf), buf = (char *) 0;
3093 while (*ep == ' ' || *ep == '\t') ep++;
3095 /* lines beginning with '#' are comments. ignore empty lines. */
3096 if (!*ep || *ep == '#')
3102 /* merge now read line with previous ones, if necessary */
3104 len = strlen(inbuf) + 1;
3107 tmpbuf = (char *) alloc(len);
3109 Sprintf(tmpbuf, "%s %s", buf, inbuf);
3112 Strcpy(tmpbuf, inbuf);
3116 if (morelines || (ignoreline && !oldline))
3119 if (handle_config_section(ep)) {
3125 /* from here onwards, we'll handle buf only */
3127 if (match_varname(buf, "CHOOSE", 6)) {
3129 char *bufp = find_optparam(buf);
3131 config_error_add("Format is CHOOSE=section1,section2,...");
3138 if (config_section_chosen)
3139 free(config_section_chosen);
3140 section = choose_random_part(bufp, ',');
3142 config_section_chosen = dupstr(section);
3144 config_error_add("No config section to choose");
3164 free_config_sections();
3168 extern struct symsetentry *symset_list; /* options.c */
3169 extern struct symparse loadsyms[]; /* drawing.c */
3170 extern const char *known_handling[]; /* drawing.c */
3171 extern const char *known_restrictions[]; /* drawing.c */
3172 static int symset_count = 0; /* for pick-list building only */
3173 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
3174 static int symset_which_set = 0;
3182 fp = fopen_datafile(SYMBOLS, "r", HACKPREFIX);
3187 * Returns 1 if the chose symset was found and loaded.
3188 * 0 if it wasn't found in the sym file or other problem.
3191 read_sym_file(which_set)
3196 if (!(fp = fopen_sym_file()))
3200 chosen_symset_start = chosen_symset_end = FALSE;
3201 symset_which_set = which_set;
3203 config_error_init(TRUE, "symbols", FALSE);
3205 parse_conf_file(fp, proc_symset_line);
3208 if (!chosen_symset_start && !chosen_symset_end) {
3209 /* name caller put in symset[which_set].name was not found;
3210 if it looks like "Default symbols", null it out and return
3211 success to use the default; otherwise, return failure */
3212 if (symset[which_set].name
3213 && (fuzzymatch(symset[which_set].name, "Default symbols",
3215 || !strcmpi(symset[which_set].name, "default")))
3216 clear_symsetentry(which_set, TRUE);
3217 config_error_done();
3218 return (symset[which_set].name == 0) ? 1 : 0;
3220 if (!chosen_symset_end)
3221 config_error_add("Missing finish for symset \"%s\"",
3222 symset[which_set].name ? symset[which_set].name
3225 config_error_done();
3231 proc_symset_line(buf)
3234 return !((boolean) parse_sym_line(buf, symset_which_set));
3237 /* returns 0 on error */
3239 parse_sym_line(buf, which_set)
3244 struct symparse *symp = (struct symparse *) 0;
3245 char *bufp, *commentp, *altp;
3247 /* convert each instance of whitespace (tabs, consecutive spaces)
3248 into a single space; leading and trailing spaces are stripped */
3251 /* remove trailing comment, if any (this isn't strictly needed for
3252 individual symbols, and it won't matter if "X#comment" without
3253 separating space slips through; for handling or set description,
3254 symbol set creator is responsible for preceding '#' with a space
3255 and that comment itself doesn't contain " #") */
3256 if ((commentp = rindex(buf, '#')) != 0 && commentp[-1] == ' ')
3257 commentp[-1] = '\0';
3259 /* find the '=' or ':' */
3260 bufp = index(buf, '=');
3261 altp = index(buf, ':');
3262 if (!bufp || (altp && altp < bufp))
3265 if (strncmpi(buf, "finish", 6) == 0) {
3266 /* end current graphics set */
3267 if (chosen_symset_start)
3268 chosen_symset_end = TRUE;
3269 chosen_symset_start = FALSE;
3272 config_error_add("No \"finish\"");
3275 /* skip '=' and space which follows, if any */
3280 symp = match_sym(buf);
3282 config_error_add("Unknown sym keyword");
3286 if (!symset[which_set].name) {
3287 /* A null symset name indicates that we're just
3288 building a pick-list of possible symset
3289 values from the file, so only do that */
3290 if (symp->range == SYM_CONTROL) {
3291 struct symsetentry *tmpsp;
3293 switch (symp->idx) {
3296 (struct symsetentry *) alloc(sizeof (struct symsetentry));
3297 tmpsp->next = (struct symsetentry *) 0;
3299 symset_list = tmpsp;
3303 tmpsp->next = symset_list;
3304 symset_list = tmpsp;
3306 tmpsp->idx = symset_count;
3307 tmpsp->name = dupstr(bufp);
3308 tmpsp->desc = (char *) 0;
3310 /* initialize restriction bits */
3315 /* handler type identified */
3316 tmpsp = symset_list; /* most recent symset */
3317 tmpsp->handling = H_UNK;
3319 while (known_handling[i]) {
3320 if (!strcmpi(known_handling[i], bufp)) {
3321 tmpsp->handling = i;
3322 break; /* while loop */
3327 case 3: /* description:something */
3328 tmpsp = symset_list; /* most recent symset */
3329 if (tmpsp && !tmpsp->desc)
3330 tmpsp->desc = dupstr(bufp);
3333 /* restrictions: xxxx*/
3334 tmpsp = symset_list; /* most recent symset */
3335 for (i = 0; known_restrictions[i]; ++i) {
3336 if (!strcmpi(known_restrictions[i], bufp)) {
3345 break; /* while loop */
3354 if (symp->range == SYM_CONTROL) {
3355 switch (symp->idx) {
3357 /* start of symset */
3358 if (!strcmpi(bufp, symset[which_set].name)) {
3359 /* matches desired one */
3360 chosen_symset_start = TRUE;
3361 /* these init_*() functions clear symset fields too */
3362 if (which_set == ROGUESET)
3364 else if (which_set == PRIMARY)
3370 if (chosen_symset_start)
3371 chosen_symset_end = TRUE;
3372 chosen_symset_start = FALSE;
3375 /* handler type identified */
3376 if (chosen_symset_start)
3377 set_symhandling(bufp, which_set);
3379 /* case 3: (description) is ignored here */
3380 case 4: /* color:off */
3381 if (chosen_symset_start) {
3383 if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
3384 || !strcmpi(bufp, "on"))
3385 symset[which_set].nocolor = 0;
3386 else if (!strcmpi(bufp, "false")
3387 || !strcmpi(bufp, "no")
3388 || !strcmpi(bufp, "off"))
3389 symset[which_set].nocolor = 1;
3393 case 5: /* restrictions: xxxx*/
3394 if (chosen_symset_start) {
3397 while (known_restrictions[n]) {
3398 if (!strcmpi(known_restrictions[n], bufp)) {
3401 symset[which_set].primary = 1;
3404 symset[which_set].rogue = 1;
3407 break; /* while loop */
3414 } else { /* !SYM_CONTROL */
3415 val = sym_val(bufp);
3416 if (chosen_symset_start) {
3417 if (which_set == PRIMARY) {
3418 update_l_symset(symp, val);
3419 } else if (which_set == ROGUESET) {
3420 update_r_symset(symp, val);
3429 set_symhandling(handling, which_set)
3435 symset[which_set].handling = H_UNK;
3436 while (known_handling[i]) {
3437 if (!strcmpi(known_handling[i], handling)) {
3438 symset[which_set].handling = i;
3445 /* ---------- END CONFIG FILE HANDLING ----------- */
3447 /* ---------- BEGIN SCOREBOARD CREATION ----------- */
3450 #define UNUSED_if_not_OS2_CODEVIEW /*empty*/
3452 #define UNUSED_if_not_OS2_CODEVIEW UNUSED
3455 /* verify that we can write to scoreboard file; if not, try to create one */
3458 check_recordfile(dir)
3459 const char *dir UNUSED_if_not_OS2_CODEVIEW;
3461 #if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW)
3464 const char *fq_record;
3467 #if defined(UNIX) || defined(VMS)
3468 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3469 fd = open(fq_record, O_RDWR, 0);
3471 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
3472 if (!file_is_stmlf(fd)) {
3474 "Warning: scoreboard file '%s' is not in stream_lf format",
3479 (void) nhclose(fd); /* RECORD is accessible */
3480 } else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
3481 (void) nhclose(fd); /* RECORD newly created */
3482 #if defined(VMS) && !defined(SECURE)
3483 /* Re-protect RECORD with world:read+write+execute+delete access. */
3484 (void) chmod(fq_record, FCMASK | 007);
3485 #endif /* VMS && !SECURE */
3487 raw_printf("Warning: cannot write scoreboard file '%s'", fq_record);
3490 #endif /* !UNIX && !VMS */
3491 #if defined(MICRO) || defined(WIN32)
3494 #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
3495 /* how does this work when there isn't an explicit path or fopenp
3496 * for later access to the file via fopen_datafile? ? */
3497 (void) strncpy(tmp, dir, PATHLEN - 1);
3498 tmp[PATHLEN - 1] = '\0';
3499 if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
3501 Strcat(tmp, RECORD);
3505 Strcpy(tmp, RECORD);
3506 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3509 /* If dir is NULL it indicates create but
3510 only if it doesn't already exist */
3515 fd = open(fq_record, O_RDWR);
3516 if (!(fd == -1 && errno == ENOENT)) {
3520 /* explanation for failure other than missing file */
3521 Sprintf(buf, "error \"%s\", (errno %d).",
3523 paniclog("scorefile", buf);
3527 Sprintf(buf, "missing \"%s\", creating new scorefile.",
3529 paniclog("scorefile", buf);
3533 if ((fd = open(fq_record, O_RDWR)) < 0) {
3534 /* try to create empty 'record' */
3535 #if defined(AZTEC_C) || defined(_DCC) \
3536 || (defined(__GNUC__) && defined(__AMIGA__))
3537 /* Aztec doesn't use the third argument */
3538 /* DICE doesn't like it */
3539 fd = open(fq_record, O_CREAT | O_RDWR);
3541 fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE);
3544 raw_printf("Warning: cannot write record '%s'", tmp);
3550 /* open succeeded => 'record' exists */
3553 #else /* MICRO || WIN32*/
3556 /* Create the "record" file, if necessary */
3557 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3558 fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
3563 #endif /* MICRO || WIN32*/
3566 /* ---------- END SCOREBOARD CREATION ----------- */
3568 /* ---------- BEGIN PANIC/IMPOSSIBLE LOG ----------- */
3572 paniclog(type, reason)
3573 const char *type; /* panic, impossible, trickery */
3574 const char *reason; /* explanation */
3580 if (!program_state.in_paniclog) {
3581 program_state.in_paniclog = 1;
3582 lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
3584 #ifdef PANICLOG_FMT2
3585 (void) fprintf(lfile, "%ld %s: %s %s\n",
3586 ubirthday, (plname ? plname : "(none)"),
3589 time_t now = getnow();
3591 char playmode = wizard ? 'D' : discover ? 'X' : '-';
3593 (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
3594 version_string(buf), yyyymmdd(now), hhmmss(now),
3595 uid, playmode, type, reason);
3596 #endif /* !PANICLOG_FMT2 */
3597 (void) fclose(lfile);
3599 program_state.in_paniclog = 0;
3601 #endif /* PANICLOG */
3605 /* ---------- END PANIC/IMPOSSIBLE LOG ----------- */
3609 /* ---------- BEGIN INTERNAL RECOVER ----------- */
3614 int lev, savelev, hpid, pltmpsiz;
3616 struct version_info version_data;
3618 char savename[SAVESIZE], errbuf[BUFSZ];
3619 struct savefile_info sfi;
3620 char tmpplbuf[PL_NSIZ];
3622 for (lev = 0; lev < 256; lev++)
3625 /* level 0 file contains:
3626 * pid of creating process (ignored here)
3627 * level number for current level of save file
3628 * name of save file nethack would have created
3633 gfd = open_levelfile(0, errbuf);
3635 raw_printf("%s\n", errbuf);
3638 if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
3639 raw_printf("\n%s\n%s\n",
3640 "Checkpoint data incompletely written or subsequently clobbered.",
3641 "Recovery impossible.");
3642 (void) nhclose(gfd);
3645 if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
3646 != sizeof(savelev)) {
3648 "\nCheckpointing was not in effect for %s -- recovery impossible.\n",
3650 (void) nhclose(gfd);
3653 if ((read(gfd, (genericptr_t) savename, sizeof savename)
3655 || (read(gfd, (genericptr_t) &version_data, sizeof version_data)
3656 != sizeof version_data)
3657 || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
3658 || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3659 != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ)
3660 || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) {
3661 raw_printf("\nError reading %s -- can't recover.\n", lock);
3662 (void) nhclose(gfd);
3666 /* save file should contain:
3670 * current level (including pets)
3671 * (non-level-based) game state
3674 set_savefile_name(TRUE);
3675 sfd = create_savefile();
3677 raw_printf("\nCannot recover savefile %s.\n", SAVEF);
3678 (void) nhclose(gfd);
3682 lfd = open_levelfile(savelev, errbuf);
3684 raw_printf("\n%s\n", errbuf);
3685 (void) nhclose(gfd);
3686 (void) nhclose(sfd);
3691 if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
3692 != sizeof version_data) {
3693 raw_printf("\nError writing %s; recovery failed.", SAVEF);
3694 (void) nhclose(gfd);
3695 (void) nhclose(sfd);
3696 (void) nhclose(lfd);
3701 if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) {
3702 raw_printf("\nError writing %s; recovery failed (savefile_info).\n",
3704 (void) nhclose(gfd);
3705 (void) nhclose(sfd);
3706 (void) nhclose(lfd);
3711 if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3712 != sizeof pltmpsiz) {
3713 raw_printf("Error writing %s; recovery failed (player name size).\n",
3715 (void) nhclose(gfd);
3716 (void) nhclose(sfd);
3717 (void) nhclose(lfd);
3722 if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) {
3723 raw_printf("Error writing %s; recovery failed (player name).\n",
3725 (void) nhclose(gfd);
3726 (void) nhclose(sfd);
3727 (void) nhclose(lfd);
3732 if (!copy_bytes(lfd, sfd)) {
3733 (void) nhclose(gfd);
3734 (void) nhclose(sfd);
3735 (void) nhclose(lfd);
3739 (void) nhclose(lfd);
3740 processed[savelev] = 1;
3742 if (!copy_bytes(gfd, sfd)) {
3743 (void) nhclose(gfd);
3744 (void) nhclose(sfd);
3748 (void) nhclose(gfd);
3751 for (lev = 1; lev < 256; lev++) {
3752 /* level numbers are kept in xchars in save.c, so the
3753 * maximum level number (for the endlevel) must be < 256
3755 if (lev != savelev) {
3756 lfd = open_levelfile(lev, (char *) 0);
3758 /* any or all of these may not exist */
3760 write(sfd, (genericptr_t) &levc, sizeof(levc));
3761 if (!copy_bytes(lfd, sfd)) {
3762 (void) nhclose(lfd);
3763 (void) nhclose(sfd);
3767 (void) nhclose(lfd);
3772 (void) nhclose(sfd);
3774 #ifdef HOLD_LOCKFILE_OPEN
3778 * We have a successful savefile!
3779 * Only now do we erase the level files.
3781 for (lev = 0; lev < 256; lev++) {
3782 if (processed[lev]) {
3783 const char *fq_lock;
3785 set_levelfile_name(lock, lev);
3786 fq_lock = fqname(lock, LEVELPREFIX, 3);
3787 (void) unlink(fq_lock);
3794 copy_bytes(ifd, ofd)
3801 nfrom = read(ifd, buf, BUFSIZ);
3802 nto = write(ofd, buf, nfrom);
3805 } while (nfrom == BUFSIZ);
3809 /* ---------- END INTERNAL RECOVER ----------- */
3810 #endif /*SELF_RECOVER*/
3812 /* ---------- OTHER ----------- */
3822 * All we really care about is the end result - can we read the file?
3823 * So just check that directly.
3825 * Not tested on most of the old platforms (which don't attempt
3826 * to implement SYSCF).
3827 * Some ports don't like open()'s optional third argument;
3828 * VMS overrides open() usage with a macro which requires it.
3831 # if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
3832 fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
3834 fd = open(SYSCF_FILE, O_RDONLY);
3837 fd = open(SYSCF_FILE, O_RDONLY, 0);
3844 raw_printf("Unable to open SYSCF_FILE.\n");
3848 #endif /* SYSCF_FILE */
3852 /* used by debugpline() to decide whether to issue a message
3853 * from a particular source file; caller passes __FILE__ and we check
3854 * whether it is in the source file list supplied by SYSCF's DEBUGFILES
3856 * pass FALSE to override wildcard matching; useful for files
3857 * like dungeon.c and questpgr.c, which generate a ridiculous amount of
3858 * output if DEBUG is defined and effectively block the use of a wildcard */
3860 debugcore(filename, wildcards)
3861 const char *filename;
3864 const char *debugfiles, *p;
3866 if (!filename || !*filename)
3867 return FALSE; /* sanity precaution */
3869 if (sysopt.env_dbgfl == 0) {
3870 /* check once for DEBUGFILES in the environment;
3871 if found, it supersedes the sysconf value
3872 [note: getenv() rather than nh_getenv() since a long value
3873 is valid and doesn't pose any sort of overflow risk here] */
3874 if ((p = getenv("DEBUGFILES")) != 0) {
3875 if (sysopt.debugfiles)
3876 free((genericptr_t) sysopt.debugfiles);
3877 sysopt.debugfiles = dupstr(p);
3878 sysopt.env_dbgfl = 1;
3880 sysopt.env_dbgfl = -1;
3883 debugfiles = sysopt.debugfiles;
3884 /* usual case: sysopt.debugfiles will be empty */
3885 if (!debugfiles || !*debugfiles)
3888 /* strip filename's path if present */
3890 if ((p = rindex(filename, '/')) != 0)
3894 filename = vms_basename(filename);
3895 /* vms_basename strips off 'type' suffix as well as path and version;
3896 we want to put suffix back (".c" assumed); since it always returns
3897 a pointer to a static buffer, we can safely modify its result */
3898 Strcat((char *) filename, ".c");
3902 * Wildcard match will only work if there's a single pattern (which
3903 * might be a single file name without any wildcarding) rather than
3904 * a space-separated list.
3905 * [to NOT do: We could step through the space-separated list and
3906 * attempt a wildcard match against each element, but that would be
3907 * overkill for the intended usage.]
3909 if (wildcards && pmatch(debugfiles, filename))
3912 /* check whether filename is an element of the list */
3913 if ((p = strstr(debugfiles, filename)) != 0) {
3914 int l = (int) strlen(filename);
3916 if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
3917 && (p[l] == ' ' || p[l] == '\0'))
3925 /* ---------- BEGIN TRIBUTE ----------- */
3930 #define SECTIONSCOPE 1
3931 #define TITLESCOPE 2
3932 #define PASSAGESCOPE 3
3934 #define MAXPASSAGES SIZE(context.novel.pasg) /* 20 */
3936 static int FDECL(choose_passage, (int, unsigned));
3938 /* choose a random passage that hasn't been chosen yet; once all have
3939 been chosen, reset the tracking to make all passages available again */
3941 choose_passage(passagecnt, oid)
3942 int passagecnt; /* total of available passages */
3943 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
3950 /* if a different book or we've used up all the passages already,
3951 reset in order to have all 'passagecnt' passages available */
3952 if (oid != context.novel.id || context.novel.count == 0) {
3953 int i, range = passagecnt, limit = MAXPASSAGES;
3955 context.novel.id = oid;
3956 if (range <= limit) {
3957 /* collect all of the N indices */
3958 context.novel.count = passagecnt;
3959 for (idx = 0; idx < MAXPASSAGES; idx++)
3960 context.novel.pasg[idx] = (xchar) ((idx < passagecnt)
3963 /* collect MAXPASSAGES of the N indices */
3964 context.novel.count = MAXPASSAGES;
3965 for (idx = i = 0; i < passagecnt; ++i, --range)
3966 if (range > 0 && rn2(range) < limit) {
3967 context.novel.pasg[idx++] = (xchar) (i + 1);
3973 idx = rn2(context.novel.count);
3974 res = (int) context.novel.pasg[idx];
3975 /* move the last slot's passage index into the slot just used
3976 and reduce the number of passages available */
3977 context.novel.pasg[idx] = context.novel.pasg[--context.novel.count];
3981 /* Returns True if you were able to read something. */
3983 read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid)
3984 const char *tribsection, *tribtitle;
3985 int tribpassage, bufsz;
3987 unsigned oid; /* book identifier */
3990 char line[BUFSZ], lastline[BUFSZ];
3993 int linect = 0, passagecnt = 0, targetpassage = 0;
3995 const char *badtranslation = "an incomprehensible foreign translation";
3997 const char *badtranslation = "
\95s
\8a®
\91S
\82È
\8aO
\8d\91\8cê
\96|
\96ó";
3998 boolean matchedsection = FALSE, matchedtitle = FALSE;
3999 winid tribwin = WIN_ERR;
4000 boolean grasped = FALSE;
4001 boolean foundpassage = FALSE;
4006 /* check for mandatories */
4007 if (!tribsection || !tribtitle) {
4010 pline("It's %s of \"%s\"!", badtranslation, tribtitle);
4012 pline("
\82±
\82ê
\82Í
\81u%s
\81v
\82Ì%s
\82¾
\81I", tribtitle, badtranslation);
4016 debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
4019 fp = dlb_fopen(TRIBUTEFILE, "r");
4021 /* this is actually an error - cannot open tribute file! */
4024 pline("You feel too overwhelmed to continue!");
4026 pline("
\82 \82È
\82½
\82Í
\91±
\82¯
\82ç
\82ê
\82È
\82¢
\82Ù
\82Ç
\88³
\93|
\82³
\82ê
\82½
\81I");
4031 * Syntax (not case-sensitive):
4034 * In the books section:
4035 * %title booktitle (n)
4036 * where booktitle=book title without quotes
4037 * (n)= total number of passages present for this title
4039 * where k=sequential passage number
4041 * %e ends the passage/book/section
4042 * If in a passage, it marks the end of that passage.
4043 * If in a book, it marks the end of that book.
4044 * If in a section, it marks the end of that section.
4049 *line = *lastline = '\0';
4050 while (dlb_fgets(line, sizeof line, fp) != 0) {
4052 (void) strip_newline(line);
4055 if (!strncmpi(&line[1], "section ", sizeof "section " - 1)) {
4056 char *st = &line[9]; /* 9 from "%section " */
4058 scope = SECTIONSCOPE;
4059 matchedsection = !strcmpi(st, tribsection) ? TRUE : FALSE;
4060 } else if (!strncmpi(&line[1], "title ", sizeof "title " - 1)) {
4061 char *st = &line[7]; /* 7 from "%title " */
4064 if ((p1 = index(st, '(')) != 0) {
4066 (void) mungspaces(st);
4067 if ((p2 = index(p1, ')')) != 0) {
4069 passagecnt = atoi(p1);
4071 if (matchedsection && !strcmpi(st, tribtitle)) {
4072 matchedtitle = TRUE;
4073 targetpassage = !tribpassage
4074 ? choose_passage(passagecnt, oid)
4075 : (tribpassage <= passagecnt)
4078 matchedtitle = FALSE;
4082 } else if (!strncmpi(&line[1], "passage ",
4083 sizeof "passage " - 1)) {
4085 char *st = &line[9]; /* 9 from "%passage " */
4088 passagenum = atoi(st);
4089 if (passagenum > 0 && passagenum <= passagecnt) {
4090 scope = PASSAGESCOPE;
4091 if (matchedtitle && passagenum == targetpassage) {
4092 foundpassage = TRUE;
4094 tribwin = create_nhwindow(NHW_MENU);
4095 if (tribwin == WIN_ERR)
4100 } else if (!strncmpi(&line[1], "e ", sizeof "e " - 1)) {
4103 if (scope == TITLESCOPE)
4104 matchedtitle = FALSE;
4105 if (scope == SECTIONSCOPE)
4106 matchedsection = FALSE;
4110 debugpline1("tribute file error: bad %% command, line %d.",
4115 /* comment only, next! */
4120 /* outputting multi-line passage to text window */
4121 putstr(tribwin, 0, line);
4123 Strcpy(lastline, line);
4125 /* fetching one-line passage into buffer */
4126 copynchars(nowin_buf, line, bufsz - 1);
4127 goto cleanup; /* don't wait for "%e passage" */
4134 (void) dlb_fclose(fp);
4136 /* one-line buffer */
4137 grasped = *nowin_buf ? TRUE : FALSE;
4139 if (tribwin != WIN_ERR) { /* implies 'foundpassage' */
4140 /* multi-line window, normal case;
4141 if lastline is empty, there were no non-empty lines between
4142 "%passage n" and "%e passage" so we leave 'grasped' False */
4144 display_nhwindow(tribwin, FALSE);
4145 /* put the final attribution line into message history,
4146 analogous to the summary line from long quest messages */
4147 if (index(lastline, '['))
4148 mungspaces(lastline); /* to remove leading spaces */
4149 else /* construct one if necessary */
4150 Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
4151 putmsghistory(lastline, FALSE);
4154 destroy_nhwindow(tribwin);
4157 /* multi-line window, problem */
4159 pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
4161 pline("
\82±
\82ê
\82Í
\81u%s
\81v
\82Ì%s
\82Ì
\82æ
\82¤
\82¾
\81I", tribtitle, badtranslation);
4167 Death_quote(buf, bufsz)
4171 unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
4173 return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
4176 /* ---------- END TRIBUTE ----------- */