1 /* NetHack 3.6 files.c $NHDT-Date: 1449296293 2015/12/05 06:18:13 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.192 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* JNetHack Copyright */
6 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000 */
7 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2016 */
8 /* JNetHack may be freely redistributed. See license for details. */
14 #include "wintty.h" /* more() */
17 #if (!defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)) \
23 #ifdef _MSC_VER /* MSC 6.0 defines errno quite differently */
39 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
41 #ifndef COMPRESS_EXTENSION
42 #define COMPRESS_EXTENSION ".gz"
46 #if defined(UNIX) && defined(QT_GRAPHICS)
47 #include <sys/types.h>
52 #if defined(UNIX) || defined(VMS) || !defined(NO_SIGNAL)
56 #if defined(MSDOS) || defined(OS2) || defined(TOS) || defined(WIN32)
63 #ifndef O_BINARY /* used for micros, no-op for others */
67 #ifdef PREFIXES_IN_USE
69 static char fqn_filename_buffer[FQN_NUMBUF][FQN_MAX_FILENAME];
72 #if !defined(MFLOPPY) && !defined(VMS) && !defined(WIN32)
73 char bones[] = "bonesnn.xxx";
74 char lock[PL_NSIZ + 14] = "1lock"; /* long enough for uid+name+.99 */
77 char bones[FILENAME]; /* pathname of bones files */
78 char lock[FILENAME]; /* pathname of level files */
81 char bones[] = "bonesnn.xxx;1";
82 char lock[PL_NSIZ + 17] = "1lock"; /* long enough for _uid+name+.99;1 */
85 char bones[] = "bonesnn.xxx";
86 char lock[PL_NSIZ + 25]; /* long enough for username+-+name+.99 */
90 #if defined(UNIX) || defined(__BEOS__)
91 #define SAVESIZE (PL_NSIZ + 13) /* save/99999player.e */
94 #define SAVESIZE (PL_NSIZ + 22) /* [.save]<uid>player.e;1 */
97 #define SAVESIZE (PL_NSIZ + 40) /* username-player.NetHack-saved-game */
99 #define SAVESIZE FILENAME /* from macconf.h or pcconf.h */
104 #if !defined(SAVE_EXTENSION)
106 #define SAVE_EXTENSION ".sav"
109 #define SAVE_EXTENSION ".NetHack-saved-game"
113 char SAVEF[SAVESIZE]; /* holds relative path of save file from playground */
115 char SAVEP[SAVESIZE]; /* holds path of directory for save file */
118 #ifdef HOLD_LOCKFILE_OPEN
119 struct level_ftrack {
121 int fd; /* file descriptor for level file */
122 int oflag; /* open flags */
123 boolean nethack_thinks_it_is_open; /* Does NetHack think it's open? */
128 #endif /*HOLD_LOCKFILE_OPEN*/
130 #define WIZKIT_MAX 128
131 static char wizkit[WIZKIT_MAX];
132 STATIC_DCL FILE *NDECL(fopen_wizkit_file);
133 STATIC_DCL void FDECL(wizkit_addinv, (struct obj *));
136 extern char PATH[]; /* see sys/amiga/amidos.c */
137 extern char bbs_id[];
140 #include <proto/dos.h>
143 #include <libraries/dos.h>
144 extern void FDECL(amii_set_text_font, (char *, int));
147 #if defined(WIN32) || defined(MSDOS)
150 #define Delay(a) msleep(a)
154 #define DeleteFile unlink
160 #define unlink macunlink
163 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) \
164 || defined(__MWERKS__)
165 #define PRAGMA_UNUSED
169 extern char *sounddir;
172 extern int n_dgns; /* from dungeon.c */
174 #if defined(UNIX) && defined(QT_GRAPHICS)
179 STATIC_PTR int FDECL(CFDECLSPEC strcmp_wrap, (const void *, const void *));
181 STATIC_DCL char *FDECL(set_bonesfile_name, (char *, d_level *));
182 STATIC_DCL char *NDECL(set_bonestemp_name);
184 STATIC_DCL void FDECL(redirect, (const char *, const char *, FILE *,
187 #if defined(COMPRESS) || defined(ZLIB_COMP)
188 STATIC_DCL void FDECL(docompress_file, (const char *, BOOLEAN_P));
190 #if defined(ZLIB_COMP)
191 STATIC_DCL boolean FDECL(make_compressed_name, (const char *, char *));
194 STATIC_DCL char *FDECL(make_lockname, (const char *, char *));
196 STATIC_DCL FILE *FDECL(fopen_config_file, (const char *, int));
197 STATIC_DCL int FDECL(get_uchars, (FILE *, char *, char *, uchar *, BOOLEAN_P,
199 int FDECL(parse_config_line, (FILE *, char *, int));
200 STATIC_DCL FILE *NDECL(fopen_sym_file);
201 STATIC_DCL void FDECL(set_symhandling, (char *, int));
202 #ifdef NOCWD_ASSUMPTIONS
203 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
206 STATIC_DCL boolean FDECL(copy_bytes, (int, int));
208 #ifdef HOLD_LOCKFILE_OPEN
209 STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int));
216 * legal zero-terminated list of acceptable file name characters
217 * quotechar lead-in character used to quote illegal characters as
220 * callerbuf buffer to house result
221 * bufsz size of callerbuf
224 * The hex digits 0-9 and A-F are always part of the legal set due to
225 * their use in the encoding scheme, even if not explicitly included in
229 * The following call:
230 * (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
231 * '%', "This is a % test!", buf, 512);
232 * results in this encoding:
233 * "This%20is%20a%20%25%20test%21"
236 fname_encode(legal, quotechar, s, callerbuf, bufsz)
244 static char hexdigits[] = "0123456789ABCDEF";
251 /* Do we have room for one more character or encoding? */
252 if ((bufsz - cnt) <= 4)
255 if (*sp == quotechar) {
256 (void) sprintf(op, "%c%02X", quotechar, *sp);
259 } else if ((index(legal, *sp) != 0) || (index(hexdigits, *sp) != 0)) {
264 } else if (is_kanji1(s, sp - s)) {
271 (void) sprintf(op, "%c%02X", quotechar, *sp);
284 * quotechar lead-in character used to quote illegal characters as
287 * callerbuf buffer to house result
288 * bufsz size of callerbuf
291 fname_decode(quotechar, s, callerbuf, bufsz)
297 int k, calc, cnt = 0;
298 static char hexdigits[] = "0123456789ABCDEF";
306 /* Do we have room for one more character? */
307 if ((bufsz - cnt) <= 2)
309 if (*sp == quotechar) {
311 for (k = 0; k < 16; ++k)
312 if (*sp == hexdigits[k])
315 return callerbuf; /* impossible, so bail */
318 for (k = 0; k < 16; ++k)
319 if (*sp == hexdigits[k])
322 return callerbuf; /* impossible, so bail */
336 #ifdef PREFIXES_IN_USE
337 #define UNUSED_if_not_PREFIXES_IN_USE /*empty*/
339 #define UNUSED_if_not_PREFIXES_IN_USE UNUSED
344 fqname(basenam, whichprefix, buffnum)
346 int whichprefix UNUSED_if_not_PREFIXES_IN_USE;
347 int buffnum UNUSED_if_not_PREFIXES_IN_USE;
349 #ifndef PREFIXES_IN_USE
352 if (!basenam || whichprefix < 0 || whichprefix >= PREFIX_COUNT)
354 if (!fqn_prefix[whichprefix])
356 if (buffnum < 0 || buffnum >= FQN_NUMBUF) {
357 impossible("Invalid fqn_filename_buffer specified: %d", buffnum);
360 if (strlen(fqn_prefix[whichprefix]) + strlen(basenam)
361 >= FQN_MAX_FILENAME) {
362 impossible("fqname too long: %s + %s", fqn_prefix[whichprefix],
364 return basenam; /* XXX */
366 Strcpy(fqn_filename_buffer[buffnum], fqn_prefix[whichprefix]);
367 return strcat(fqn_filename_buffer[buffnum], basenam);
372 validate_prefix_locations(reasonbuf)
373 char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */
375 #if defined(NOCWD_ASSUMPTIONS)
377 const char *filename;
378 int prefcnt, failcount = 0;
379 char panicbuf1[BUFSZ], panicbuf2[BUFSZ];
385 #if defined(NOCWD_ASSUMPTIONS)
386 for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++) {
387 /* don't test writing to configdir or datadir; they're readonly */
388 if (prefcnt == SYSCONFPREFIX || prefcnt == CONFIGPREFIX
389 || prefcnt == DATAPREFIX)
391 filename = fqname("validate", prefcnt, 3);
392 if ((fp = fopen(filename, "w"))) {
394 (void) unlink(filename);
398 Strcat(reasonbuf, ", ");
399 Strcat(reasonbuf, fqn_prefix_names[prefcnt]);
401 /* the paniclog entry gets the value of errno as well */
402 Sprintf(panicbuf1, "Invalid %s", fqn_prefix_names[prefcnt]);
403 #if defined(NHSTDC) && !defined(NOTSTDC)
404 if (!(details = strerror(errno)))
407 Sprintf(panicbuf2, "\"%s\", (%d) %s", fqn_prefix[prefcnt], errno,
409 paniclog(panicbuf1, panicbuf2);
420 /* fopen a file, with OS-dependent bells and whistles */
421 /* NOTE: a simpler version of this routine also exists in util/dlb_main.c */
423 fopen_datafile(filename, mode, prefix)
424 const char *filename, *mode;
429 filename = fqname(filename, prefix, prefix == TROUBLEPREFIX ? 3 : 0);
430 fp = fopen(filename, mode);
434 /* ---------- BEGIN LEVEL FILE HANDLING ----------- */
437 /* Set names for bones[] and lock[] */
442 Strcpy(levels, permbones);
443 Strcpy(bones, permbones);
445 append_slash(permbones);
446 append_slash(levels);
448 strncat(levels, bbs_id, PATHLEN);
451 Strcat(bones, "bonesnn.*");
452 Strcpy(lock, levels);
454 Strcat(lock, alllevels);
460 /* Construct a file name for a level-type file, which is of the form
461 * something.level (with any old level stripped off).
462 * This assumes there is space on the end of 'file' to append
463 * a two digit number. This is true for 'level'
464 * but be careful if you use it for other things -dgk
467 set_levelfile_name(file, lev)
473 tf = rindex(file, '.');
476 Sprintf(tf, ".%d", lev);
484 create_levelfile(lev, errbuf)
493 set_levelfile_name(lock, lev);
494 fq_lock = fqname(lock, LEVELPREFIX, 0);
496 #if defined(MICRO) || defined(WIN32)
497 /* Use O_TRUNC to force the file to be shortened if it already
498 * exists and is currently longer.
500 #ifdef HOLD_LOCKFILE_OPEN
502 fd = open_levelfile_exclusively(
503 fq_lock, lev, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
506 fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
509 fd = maccreat(fq_lock, LEVL_TYPE);
511 fd = creat(fq_lock, FCMASK);
513 #endif /* MICRO || WIN32 */
516 level_info[lev].flags |= LFILE_EXISTS;
517 else if (errbuf) /* failure explanation */
518 Sprintf(errbuf, "Cannot create file \"%s\" for level %d (errno %d).",
525 open_levelfile(lev, errbuf)
534 set_levelfile_name(lock, lev);
535 fq_lock = fqname(lock, LEVELPREFIX, 0);
537 /* If not currently accessible, swap it in. */
538 if (level_info[lev].where != ACTIVE)
542 fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE);
544 #ifdef HOLD_LOCKFILE_OPEN
546 fd = open_levelfile_exclusively(fq_lock, lev, O_RDONLY | O_BINARY);
549 fd = open(fq_lock, O_RDONLY | O_BINARY, 0);
552 /* for failure, return an explanation that our caller can use;
553 settle for `lock' instead of `fq_lock' because the latter
554 might end up being too big for nethack's BUFSZ */
555 if (fd < 0 && errbuf)
557 Sprintf(errbuf, "Cannot open file \"%s\" for level %d (errno %d).",
560 Sprintf(errbuf, "
\92n
\89º%d
\8aK
\82Ì
\83t
\83@
\83C
\83\8b\"%s\"
\82ð
\8aJ
\82¯
\82È
\82¢(errno %d)
\81D",
568 delete_levelfile(lev)
572 * Level 0 might be created by port specific code that doesn't
573 * call create_levfile(), so always assume that it exists.
575 if (lev == 0 || (level_info[lev].flags & LFILE_EXISTS)) {
576 set_levelfile_name(lock, lev);
577 #ifdef HOLD_LOCKFILE_OPEN
581 (void) unlink(fqname(lock, LEVELPREFIX, 0));
582 level_info[lev].flags &= ~LFILE_EXISTS;
589 #ifdef HANGUPHANDLING
590 if (program_state.preserve_locks)
593 #if !defined(PC_LOCKING) && defined(MFLOPPY) && !defined(AMIGA)
594 eraseall(levels, alllevels);
596 eraseall(permbones, alllevels);
602 (void) signal(SIGINT, SIG_IGN);
604 #if defined(UNIX) || defined(VMS)
605 sethanguphandler((void FDECL((*), (int) )) SIG_IGN);
607 /* can't access maxledgerno() before dungeons are created -dlc */
608 for (x = (n_dgns ? maxledgerno() : 0); x >= 0; x--)
609 delete_levelfile(x); /* not all levels need be present */
611 #endif /* ?PC_LOCKING,&c */
614 #if defined(SELECTSAVED)
615 /* qsort comparison routine */
616 STATIC_OVL int CFDECLSPEC
621 #if defined(UNIX) && defined(QT_GRAPHICS)
622 return strncasecmp(*(char **) p, *(char **) q, 16);
624 return strncmpi(*(char **) p, *(char **) q, 16);
629 #ifdef HOLD_LOCKFILE_OPEN
631 open_levelfile_exclusively(name, lev, oflag)
640 if (lftrack.fd >= 0) {
641 /* check for compatible access */
642 if (lftrack.oflag == oflag) {
644 reslt = lseek(fd, 0L, SEEK_SET);
646 panic("open_levelfile_exclusively: lseek failed %d", errno);
647 lftrack.nethack_thinks_it_is_open = TRUE;
650 fd = sopen(name, oflag, SH_DENYRW, FCMASK);
652 lftrack.oflag = oflag;
653 lftrack.nethack_thinks_it_is_open = TRUE;
656 fd = sopen(name, oflag, SH_DENYRW, FCMASK);
658 lftrack.oflag = oflag;
660 lftrack.nethack_thinks_it_is_open = TRUE;
670 lftrack.nethack_thinks_it_is_open = FALSE;
682 if (lftrack.fd == fd) {
683 really_close(); /* close it, but reopen it to hold it */
684 fd = open_levelfile(0, (char *) 0);
685 lftrack.nethack_thinks_it_is_open = FALSE;
700 /* ---------- END LEVEL FILE HANDLING ----------- */
702 /* ---------- BEGIN BONES FILE HANDLING ----------- */
704 /* set up "file" to be file name for retrieving bones, and return a
705 * bonesid to be read/written in the bones file.
708 set_bonesfile_name(file, lev)
715 Sprintf(file, "bon%c%s", dungeons[lev->dnum].boneid,
716 In_quest(lev) ? urole.filecode : "0");
718 if ((sptr = Is_special(lev)) != 0)
719 Sprintf(dptr, ".%c", sptr->boneid);
721 Sprintf(dptr, ".%d", lev->dlevel);
728 /* set up temporary file name for writing bones, to avoid another game's
729 * trying to read from an uncompleted bones file. we want an uncontentious
730 * name, so use one in the namespace reserved for this game's level files.
731 * (we are not reading or writing level files while writing bones files, so
732 * the same array may be used instead of copying.)
739 tf = rindex(lock, '.');
750 create_bonesfile(lev, bonesid, errbuf)
760 *bonesid = set_bonesfile_name(bones, lev);
761 file = set_bonestemp_name();
762 file = fqname(file, BONESPREFIX, 0);
764 #if defined(MICRO) || defined(WIN32)
765 /* Use O_TRUNC to force the file to be shortened if it already
766 * exists and is currently longer.
768 fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
771 fd = maccreat(file, BONE_TYPE);
773 fd = creat(file, FCMASK);
776 if (fd < 0 && errbuf) /* failure explanation */
777 Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).", lock,
780 #if defined(VMS) && !defined(SECURE)
782 Re-protect bones file with world:read+write+execute+delete access.
783 umask() doesn't seem very reliable; also, vaxcrtl won't let us set
784 delete access without write access, which is what's really wanted.
785 Can't simply create it with the desired protection because creat
786 ANDs the mask with the user's default protection, which usually
787 denies some or all access to world.
789 (void) chmod(file, FCMASK | 007); /* allow other users full access */
790 #endif /* VMS && !SECURE */
796 /* remove partial bonesfile in process of creation */
800 const char *tempname;
802 tempname = set_bonestemp_name();
803 tempname = fqname(tempname, BONESPREFIX, 0);
804 (void) unlink(tempname);
808 /* move completed bones file to proper name */
810 commit_bonesfile(lev)
813 const char *fq_bones, *tempname;
816 (void) set_bonesfile_name(bones, lev);
817 fq_bones = fqname(bones, BONESPREFIX, 0);
818 tempname = set_bonestemp_name();
819 tempname = fqname(tempname, BONESPREFIX, 1);
821 #if (defined(SYSV) && !defined(SVR4)) || defined(GENIX)
822 /* old SYSVs don't have rename. Some SVR3's may, but since they
823 * also have link/unlink, it doesn't matter. :-)
825 (void) unlink(fq_bones);
826 ret = link(tempname, fq_bones);
827 ret += unlink(tempname);
829 ret = rename(tempname, fq_bones);
831 if (wizard && ret != 0)
832 pline("couldn't rename %s to %s.", tempname, fq_bones);
836 open_bonesfile(lev, bonesid)
840 const char *fq_bones;
843 *bonesid = set_bonesfile_name(bones, lev);
844 fq_bones = fqname(bones, BONESPREFIX, 0);
845 nh_uncompress(fq_bones); /* no effect if nonexistent */
847 fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE);
849 fd = open(fq_bones, O_RDONLY | O_BINARY, 0);
855 delete_bonesfile(lev)
858 (void) set_bonesfile_name(bones, lev);
859 return !(unlink(fqname(bones, BONESPREFIX, 0)) < 0);
862 /* assume we're compressing the recently read or created bonesfile, so the
863 * file name is already set properly */
867 nh_compress(fqname(bones, BONESPREFIX, 0));
870 /* ---------- END BONES FILE HANDLING ----------- */
872 /* ---------- BEGIN SAVE FILE HANDLING ----------- */
874 /* set savefile name in OS-dependent manner from pre-existing plname,
875 * avoiding troublesome characters */
877 set_savefile_name(regularize_it)
878 boolean regularize_it;
881 Sprintf(SAVEF, "[.save]%d%s", getuid(), plname);
883 regularize(SAVEF + 7);
887 Strcpy(SAVEF, SAVEP);
889 strncat(SAVEF, bbs_id, PATHLEN);
892 int i = strlen(SAVEP);
894 /* plname has to share space with SAVEP and ".sav" */
895 (void) strncat(SAVEF, plname, FILENAME - i - 4);
897 (void) strncat(SAVEF, plname, 8);
900 regularize(SAVEF + i);
902 Strcat(SAVEF, SAVE_EXTENSION);
906 static const char okchars[] =
907 "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
908 const char *legal = okchars;
909 char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ];
911 /* Obtain the name of the logged on user and incorporate
912 * it into the name. */
913 Sprintf(fnamebuf, "%s-%s", get_username(0), plname);
915 ++legal; /* skip '*' wildcard character */
916 (void) fname_encode(legal, '%', fnamebuf, encodedfnamebuf, BUFSZ);
917 Sprintf(SAVEF, "%s%s", encodedfnamebuf, SAVE_EXTENSION);
919 #else /* not VMS or MICRO or WIN32 */
920 Sprintf(SAVEF, "save/%d%s", (int) getuid(), plname);
922 regularize(SAVEF + 5); /* avoid . or / in name */
930 save_savefile_name(fd)
933 #pragma GCC diagnostic push
934 #pragma GCC diagnostic ignored "-Wunused-result"
935 (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF));
936 #pragma GCC diagnostic pop
941 /* change pre-existing savefile name to indicate an error savefile */
947 char *semi_colon = rindex(SAVEF, ';');
952 Strcat(SAVEF, ".e;1");
963 /* create save file, overwriting one if it already exists */
970 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
971 #if defined(MICRO) || defined(WIN32)
972 fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK);
975 fd = maccreat(fq_save, SAVE_TYPE);
977 fd = creat(fq_save, FCMASK);
979 #if defined(VMS) && !defined(SECURE)
981 Make sure the save file is owned by the current process. That's
982 the default for non-privileged users, but for priv'd users the
983 file will be owned by the directory's owner instead of the user.
986 (void) chown(fq_save, getuid(), getgid());
987 #define getuid() vms_getuid()
988 #endif /* VMS && !SECURE */
994 /* open savefile for reading */
1001 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1003 fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE);
1005 fd = open(fq_save, O_RDONLY | O_BINARY, 0);
1010 /* delete savefile */
1014 (void) unlink(fqname(SAVEF, SAVEPREFIX, 0));
1015 return 0; /* for restore_saved_game() (ex-xxxmain.c) test */
1018 /* try to open up a save file and prepare to restore it */
1020 restore_saved_game()
1022 const char *fq_save;
1026 set_savefile_name(TRUE);
1028 if (!saveDiskPrompt(1))
1030 #endif /* MFLOPPY */
1031 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1033 nh_uncompress(fq_save);
1034 if ((fd = open_savefile()) < 0)
1037 if (validate(fd, fq_save) != 0) {
1038 (void) nhclose(fd), fd = -1;
1039 (void) delete_savefile();
1044 #if defined(SELECTSAVED)
1046 plname_from_file(filename)
1047 const char *filename;
1052 Strcpy(SAVEF, filename);
1053 #ifdef COMPRESS_EXTENSION
1054 SAVEF[strlen(SAVEF) - strlen(COMPRESS_EXTENSION)] = '\0';
1056 nh_uncompress(SAVEF);
1057 if ((fd = open_savefile()) >= 0) {
1058 if (validate(fd, filename) == 0) {
1059 char tplname[PL_NSIZ];
1060 get_plname_from_file(fd, tplname);
1061 result = dupstr(tplname);
1069 /* --------- obsolete - used to be ifndef STORE_PLNAME_IN_FILE ----*/
1070 #if defined(UNIX) && defined(QT_GRAPHICS)
1071 /* Name not stored in save file, so we have to extract it from
1072 the filename, which loses information
1073 (eg. "/", "_", and "." characters are lost. */
1076 char name[64]; /* more than PL_NSIZ */
1077 #ifdef COMPRESS_EXTENSION
1078 #define EXTSTR COMPRESS_EXTENSION
1083 if ( sscanf( filename, "%*[^/]/%d%63[^.]" EXTSTR, &uid, name ) == 2 ) {
1085 /* "_" most likely means " ", which certainly looks nicer */
1086 for (k=0; name[k]; k++)
1087 if ( name[k] == '_' )
1089 return dupstr(name);
1091 #endif /* UNIX && QT_GRAPHICS */
1095 /* --------- end of obsolete code ----*/
1096 #endif /* 0 - WAS STORE_PLNAME_IN_FILE*/
1098 #endif /* defined(SELECTSAVED) */
1103 #if defined(SELECTSAVED)
1109 const char *fq_save;
1111 Strcpy(plname, "*");
1112 set_savefile_name(FALSE);
1113 #if defined(ZLIB_COMP)
1114 Strcat(SAVEF, COMPRESS_EXTENSION);
1116 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1119 foundfile = foundfile_buffer();
1120 if (findfirst((char *) fq_save)) {
1123 } while (findnext());
1126 result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1127 (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1128 if (findfirst((char *) fq_save)) {
1132 r = plname_from_file(foundfile);
1136 } while (findnext());
1141 #if defined(UNIX) && defined(QT_GRAPHICS)
1142 /* posixly correct version */
1143 int myuid = getuid();
1146 if ((dir = opendir(fqname("save", SAVEPREFIX, 0)))) {
1147 for (n = 0; readdir(dir); n++)
1153 if (!(dir = opendir(fqname("save", SAVEPREFIX, 0))))
1155 result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1156 (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1157 for (i = 0, j = 0; i < n; i++) {
1159 char name[64]; /* more than PL_NSIZ */
1160 struct dirent *entry = readdir(dir);
1164 if (sscanf(entry->d_name, "%d%63s", &uid, name) == 2) {
1166 char filename[BUFSZ];
1169 Sprintf(filename, "save/%d%s", uid, name);
1170 r = plname_from_file(filename);
1181 Strcpy(plname, "*");
1182 set_savefile_name(FALSE);
1183 j = vms_get_saved_games(SAVEF, &result);
1188 qsort(result, j, sizeof (char *), strcmp_wrap);
1191 } else if (result) { /* could happen if save files are obsolete */
1192 free_saved_games(result);
1194 #endif /* SELECTSAVED */
1199 free_saved_games(saved)
1206 free((genericptr_t) saved[i++]);
1207 free((genericptr_t) saved);
1211 /* ---------- END SAVE FILE HANDLING ----------- */
1213 /* ---------- BEGIN FILE COMPRESSION HANDLING ----------- */
1218 redirect(filename, mode, stream, uncomp)
1219 const char *filename, *mode;
1223 if (freopen(filename, mode, stream) == (FILE *) 0) {
1224 (void) fprintf(stderr, "freopen of %s for %scompress failed\n",
1225 filename, uncomp ? "un" : "");
1226 terminate(EXIT_FAILURE);
1231 * using system() is simpler, but opens up security holes and causes
1232 * problems on at least Interactive UNIX 3.0.1 (SVR3.2), where any
1233 * setuid is renounced by /bin/sh, so the files cannot be accessed.
1235 * cf. child() in unixunix.c.
1238 docompress_file(filename, uncomp)
1239 const char *filename;
1244 const char *args[10];
1245 #ifdef COMPRESS_OPTIONS
1251 boolean istty = !strncmpi(windowprocs.name, "tty", 3);
1254 Strcpy(cfn, filename);
1255 #ifdef COMPRESS_EXTENSION
1256 Strcat(cfn, COMPRESS_EXTENSION);
1258 /* when compressing, we know the file exists */
1260 if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0)
1267 args[++i] = "-d"; /* uncompress */
1268 #ifdef COMPRESS_OPTIONS
1270 /* we can't guarantee there's only one additional option, sigh */
1272 boolean inword = FALSE;
1274 Strcpy(opts, COMPRESS_OPTIONS);
1277 if ((*opt == ' ') || (*opt == '\t')) {
1282 } else if (!inword) {
1290 args[++i] = (char *) 0;
1293 /* If we don't do this and we are right after a y/n question *and*
1294 * there is an error message from the compression, the 'y' or 'n' can
1295 * end up being displayed after the error message.
1301 if (f == 0) { /* child */
1303 /* any error messages from the compression must come out after
1304 * the first line, because the more() to let the user read
1305 * them will have to clear the first line. This should be
1306 * invisible if there are no error messages.
1311 /* run compressor without privileges, in case other programs
1312 * have surprises along the line of gzip once taking filenames
1315 /* assume all compressors will compress stdin to stdout
1316 * without explicit filenames. this is true of at least
1317 * compress and gzip, those mentioned in config.h.
1320 redirect(cfn, RDBMODE, stdin, uncomp);
1321 redirect(filename, WRBMODE, stdout, uncomp);
1323 redirect(filename, RDBMODE, stdin, uncomp);
1324 redirect(cfn, WRBMODE, stdout, uncomp);
1326 #pragma GCC diagnostic push
1327 #pragma GCC diagnostic ignored "-Wunused-result"
1328 (void) setgid(getgid());
1329 (void) setuid(getuid());
1330 #pragma GCC diagnostic pop
1331 (void) execv(args[0], (char *const *) args);
1333 (void) fprintf(stderr, "Exec to %scompress %s failed.\n",
1334 uncomp ? "un" : "", filename);
1335 terminate(EXIT_FAILURE);
1336 } else if (f == -1) {
1338 pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename);
1342 (void) signal(SIGINT, SIG_IGN);
1343 (void) signal(SIGQUIT, SIG_IGN);
1344 (void) wait((int *) &i);
1345 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
1347 (void) signal(SIGQUIT, SIG_DFL);
1349 /* I don't think we can really cope with external compression
1350 * without signals, so we'll declare that compress failed and
1351 * go on. (We could do a better job by forcing off external
1352 * compression if there are no signals, but we want this for
1353 * testing with FailSafeC
1358 /* (un)compress succeeded: remove file left behind */
1362 (void) unlink(filename);
1364 /* (un)compress failed; remove the new, bad file */
1366 raw_printf("Unable to uncompress %s", filename);
1367 (void) unlink(filename);
1369 /* no message needed for compress case; life will go on */
1373 /* Give them a chance to read any error messages from the
1374 * compression--these would go to stdout or stderr and would get
1375 * overwritten only in tty mode. It's still ugly, since the
1376 * messages are being written on top of the screen, but at least
1377 * the user can read them.
1379 if (istty && iflags.window_inited) {
1380 clear_nhwindow(WIN_MESSAGE);
1382 /* No way to know if this is feasible */
1388 #endif /* COMPRESS */
1390 #if defined(COMPRESS) || defined(ZLIB_COMP)
1391 #define UNUSED_if_not_COMPRESS /*empty*/
1393 #define UNUSED_if_not_COMPRESS UNUSED
1398 nh_compress(filename)
1399 const char *filename UNUSED_if_not_COMPRESS;
1401 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1402 #ifdef PRAGMA_UNUSED
1403 #pragma unused(filename)
1406 docompress_file(filename, FALSE);
1410 /* uncompress file if it exists */
1412 nh_uncompress(filename)
1413 const char *filename UNUSED_if_not_COMPRESS;
1415 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1416 #ifdef PRAGMA_UNUSED
1417 #pragma unused(filename)
1420 docompress_file(filename, TRUE);
1424 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
1426 make_compressed_name(filename, cfn)
1427 const char *filename;
1430 #ifndef SHORT_FILENAMES
1431 /* Assume free-form filename with no 8.3 restrictions */
1432 strcpy(cfn, filename);
1433 strcat(cfn, COMPRESS_EXTENSION);
1436 #ifdef SAVE_EXTENSION
1437 char *bp = (char *) 0;
1439 strcpy(cfn, filename);
1440 if ((bp = strstri(cfn, SAVE_EXTENSION))) {
1441 strsubst(bp, SAVE_EXTENSION, ".saz");
1444 /* find last occurrence of bon */
1446 while (bp-- > cfn) {
1447 if (strstri(bp, "bon")) {
1448 strsubst(bp, "bon", "boz");
1453 #endif /* SAVE_EXTENSION */
1455 #endif /* SHORT_FILENAMES */
1459 docompress_file(filename, uncomp)
1460 const char *filename;
1463 gzFile compressedfile;
1464 FILE *uncompressedfile;
1469 if (!make_compressed_name(filename, cfn))
1473 /* Open the input and output files */
1474 /* Note that gzopen takes "wb" as its mode, even on systems where
1475 fopen takes "r" and "w" */
1477 uncompressedfile = fopen(filename, RDBMODE);
1478 if (!uncompressedfile) {
1479 pline("Error in zlib docompress_file %s", filename);
1482 compressedfile = gzopen(cfn, "wb");
1483 if (compressedfile == NULL) {
1485 pline("zlib failed to allocate memory");
1487 panic("Error in docompress_file %d", errno);
1489 fclose(uncompressedfile);
1493 /* Copy from the uncompressed to the compressed file */
1496 len = fread(buf, 1, sizeof(buf), uncompressedfile);
1497 if (ferror(uncompressedfile)) {
1498 pline("Failure reading uncompressed file");
1499 pline("Can't compress %s.", filename);
1500 fclose(uncompressedfile);
1501 gzclose(compressedfile);
1506 break; /* End of file */
1508 len2 = gzwrite(compressedfile, buf, len);
1510 pline("Failure writing compressed file");
1511 pline("Can't compress %s.", filename);
1512 fclose(uncompressedfile);
1513 gzclose(compressedfile);
1519 fclose(uncompressedfile);
1520 gzclose(compressedfile);
1522 /* Delete the file left behind */
1524 (void) unlink(filename);
1526 } else { /* uncomp */
1528 /* Open the input and output files */
1529 /* Note that gzopen takes "rb" as its mode, even on systems where
1530 fopen takes "r" and "w" */
1532 compressedfile = gzopen(cfn, "rb");
1533 if (compressedfile == NULL) {
1535 pline("zlib failed to allocate memory");
1536 } else if (errno != ENOENT) {
1537 panic("Error in zlib docompress_file %s, %d", filename,
1542 uncompressedfile = fopen(filename, WRBMODE);
1543 if (!uncompressedfile) {
1544 pline("Error in zlib docompress file uncompress %s", filename);
1545 gzclose(compressedfile);
1549 /* Copy from the compressed to the uncompressed file */
1552 len = gzread(compressedfile, buf, sizeof(buf));
1553 if (len == (unsigned) -1) {
1554 pline("Failure reading compressed file");
1555 pline("Can't uncompress %s.", filename);
1556 fclose(uncompressedfile);
1557 gzclose(compressedfile);
1558 (void) unlink(filename);
1562 break; /* End of file */
1564 fwrite(buf, 1, len, uncompressedfile);
1565 if (ferror(uncompressedfile)) {
1566 pline("Failure writing uncompressed file");
1567 pline("Can't uncompress %s.", filename);
1568 fclose(uncompressedfile);
1569 gzclose(compressedfile);
1570 (void) unlink(filename);
1575 fclose(uncompressedfile);
1576 gzclose(compressedfile);
1578 /* Delete the file left behind */
1582 #endif /* RLC 09 Mar 1999: End ZLIB patch */
1584 /* ---------- END FILE COMPRESSION HANDLING ----------- */
1586 /* ---------- BEGIN FILE LOCKING HANDLING ----------- */
1588 static int nesting = 0;
1590 #if defined(NO_FILE_LINKS) || defined(USE_FCNTL) /* implies UNIX */
1591 static int lockfd; /* for lock_file() to pass to unlock_file() */
1594 struct flock sflock; /* for unlocking, same as above */
1597 #define HUP if (!program_state.done_hup)
1601 make_lockname(filename, lockname)
1602 const char *filename;
1605 #if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \
1607 #ifdef NO_FILE_LINKS
1608 Strcpy(lockname, LOCKDIR);
1609 Strcat(lockname, "/");
1610 Strcat(lockname, filename);
1612 Strcpy(lockname, filename);
1616 char *semi_colon = rindex(lockname, ';');
1620 Strcat(lockname, ".lock;1");
1622 Strcat(lockname, "_lock");
1625 #else /* !(UNIX || VMS || AMIGA || WIN32 || MSDOS) */
1626 #ifdef PRAGMA_UNUSED
1627 #pragma unused(filename)
1633 #endif /* !USE_FCNTL */
1637 lock_file(filename, whichprefix, retryct)
1638 const char *filename;
1642 #if defined(PRAGMA_UNUSED) && !(defined(UNIX) || defined(VMS)) \
1643 && !(defined(AMIGA) || defined(WIN32) || defined(MSDOS))
1644 #pragma unused(retryct)
1647 char locknambuf[BUFSZ];
1648 const char *lockname;
1653 impossible("TRIED TO NEST LOCKS");
1658 lockname = make_lockname(filename, locknambuf);
1659 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1660 lockname = fqname(lockname, LOCKPREFIX, 2);
1663 filename = fqname(filename, whichprefix, 0);
1665 lockfd = open(filename, O_RDWR);
1667 HUP raw_printf("Cannot open file %s. This is a program bug.",
1670 sflock.l_type = F_WRLCK;
1671 sflock.l_whence = SEEK_SET;
1676 #if defined(UNIX) || defined(VMS)
1678 while (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1680 #ifdef NO_FILE_LINKS
1681 while ((lockfd = open(lockname, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1) {
1683 while (link(filename, lockname) == -1) {
1690 "Waiting for release of fcntl lock on %s. (%d retries left).",
1694 HUP(void) raw_print("I give up. Sorry.");
1695 HUP raw_printf("Some other process has an unnatural grip on %s.",
1701 register int errnosv = errno;
1703 switch (errnosv) { /* George Barbanis */
1707 "Waiting for access to %s. (%d retries left).", filename,
1709 #if defined(SYSV) || defined(ULTRIX) || defined(VMS)
1714 HUP(void) raw_print("I give up. Sorry.");
1715 HUP raw_printf("Perhaps there is an old %s around?",
1723 HUP raw_printf("Can't find file %s to lock!", filename);
1727 HUP raw_printf("No write permission to lock %s!", filename);
1730 #ifdef VMS /* c__translate(vmsfiles.c) */
1732 /* could be misleading, but usually right */
1733 HUP raw_printf("Can't lock %s due to directory protection.",
1739 /* take a wild guess at the underlying cause */
1740 HUP perror(lockname);
1741 HUP raw_printf("Cannot lock %s.", filename);
1743 "(Perhaps you are running NetHack from inside the distribution package?).");
1747 HUP perror(lockname);
1748 HUP raw_printf("Cannot lock %s for unknown reason (%d).",
1753 #endif /* USE_FCNTL */
1755 #endif /* UNIX || VMS */
1757 #if (defined(AMIGA) || defined(WIN32) || defined(MSDOS)) \
1758 && !defined(USE_FCNTL)
1760 #define OPENFAILURE(fd) (!fd)
1763 #define OPENFAILURE(fd) (fd < 0)
1766 while (--retryct && OPENFAILURE(lockptr)) {
1767 #if defined(WIN32) && !defined(WIN_CE)
1768 lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE);
1770 (void) DeleteFile(lockname); /* in case dead process was here first */
1772 lockptr = Open(lockname, MODE_NEWFILE);
1774 lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE);
1777 if (OPENFAILURE(lockptr)) {
1778 raw_printf("Waiting for access to %s. (%d retries left).",
1784 raw_printf("I give up. Sorry.");
1788 #endif /* AMIGA || WIN32 || MSDOS */
1792 #ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */
1796 #define unlink(foo) vms_unlink(foo)
1799 /* unlock file, which must be currently locked by lock_file */
1801 unlock_file(filename)
1802 const char *filename;
1805 char locknambuf[BUFSZ];
1806 const char *lockname;
1811 sflock.l_type = F_UNLCK;
1812 if (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1813 HUP raw_printf("Can't remove fcntl lock on %s.", filename);
1814 (void) close(lockfd);
1817 lockname = make_lockname(filename, locknambuf);
1818 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1819 lockname = fqname(lockname, LOCKPREFIX, 2);
1822 #if defined(UNIX) || defined(VMS)
1823 if (unlink(lockname) < 0)
1824 HUP raw_printf("Can't unlink %s.", lockname);
1825 #ifdef NO_FILE_LINKS
1826 (void) nhclose(lockfd);
1829 #endif /* UNIX || VMS */
1831 #if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1834 DeleteFile(lockname);
1836 #endif /* AMIGA || WIN32 || MSDOS */
1837 #endif /* USE_FCNTL */
1843 /* ---------- END FILE LOCKING HANDLING ----------- */
1845 /* ---------- BEGIN CONFIG FILE HANDLING ----------- */
1847 const char *configfile =
1851 #if defined(MAC) || defined(__BEOS__)
1854 #if defined(MSDOS) || defined(WIN32)
1862 /* used for messaging */
1863 char lastconfigfile[BUFSZ];
1866 /* conflict with speed-dial under windows
1867 * for XXX.cnf file so support of NetHack.cnf
1868 * is for backward compatibility only.
1869 * Preferred name (and first tried) is now defaults.nh but
1870 * the game will try the old name if there
1871 * is no defaults.nh.
1873 const char *backward_compat_configfile = "nethack.cnf";
1877 #define fopenp fopen
1881 fopen_config_file(filename, src)
1882 const char *filename;
1886 #if defined(UNIX) || defined(VMS)
1887 char tmp_config[BUFSZ];
1891 /* If src != SET_IN_SYS, "filename" is an environment variable, so it
1892 * should hang around. If set, it is expected to be a full path name
1896 if ((src != SET_IN_SYS) && access(filename, 4) == -1) {
1897 /* 4 is R_OK on newer systems */
1898 /* nasty sneaky attempt to read file through
1899 * NetHack's setuid permissions -- this is the only
1900 * place a file name may be wholly under the player's
1901 * control (but SYSCF_FILE is not under the player's
1902 * control so it's OK).
1904 raw_printf("Access to %s denied (%d).", filename, errno);
1906 /* fall through to standard names */
1909 #ifdef PREFIXES_IN_USE
1910 if (src == SET_IN_SYS) {
1911 (void) strncpy(lastconfigfile, fqname(filename, SYSCONFPREFIX, 0),
1915 /* always honor sysconf first before anything else */
1916 (void) strncpy(lastconfigfile, filename, BUFSZ - 1);
1917 lastconfigfile[BUFSZ - 1] = '\0';
1918 if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1920 if ((fp = fopenp(filename, "r")) != (FILE *) 0) {
1922 #if defined(UNIX) || defined(VMS)
1924 /* access() above probably caught most problems for UNIX */
1925 raw_printf("Couldn't open requested config file %s (%d).",
1928 /* fall through to standard names */
1933 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
1934 if ((fp = fopenp(fqname(configfile, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
1936 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
1939 if ((fp = fopenp(fqname(backward_compat_configfile, CONFIGPREFIX, 0),
1940 "r")) != (FILE *) 0)
1942 if ((fp = fopenp(backward_compat_configfile, "r")) != (FILE *) 0)
1946 /* constructed full path names don't need fqname() */
1948 (void) strncpy(lastconfigfile, fqname("nethackini", CONFIGPREFIX, 0),
1950 lastconfigfile[BUFSZ - 1] = '\0';
1951 if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0) {
1954 (void) strncpy(lastconfigfile, "sys$login:nethack.ini", BUFSZ - 1);
1955 lastconfigfile[BUFSZ - 1] = '\0';
1956 if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0) {
1960 envp = nh_getenv("HOME");
1962 Strcpy(tmp_config, "NetHack.cnf");
1964 Sprintf(tmp_config, "%s%s", envp, "NetHack.cnf");
1966 (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1967 lastconfigfile[BUFSZ - 1] = '\0';
1968 if ((fp = fopenp(tmp_config, "r")) != (FILE *) 0)
1970 #else /* should be only UNIX left */
1971 envp = nh_getenv("HOME");
1972 #if 1 /*JP*//*".jnethackrc"
\82ð
\97D
\90æ
\82µ
\82Ä
\93Ç
\82Ý
\8d\9e\82Ý*/
1974 Strcpy(tmp_config, ".jnethackrc");
1976 Sprintf(tmp_config, "%s/%s", envp, ".jnethackrc");
1978 (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1979 if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1983 Strcpy(tmp_config, ".nethackrc");
1985 Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
1987 (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1988 lastconfigfile[BUFSZ - 1] = '\0';
1989 if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1991 #if defined(__APPLE__)
1992 /* try an alternative */
1994 Sprintf(tmp_config, "%s/%s", envp,
1995 "Library/Preferences/NetHack Defaults");
1996 (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1997 lastconfigfile[BUFSZ - 1] = '\0';
1998 if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
2000 Sprintf(tmp_config, "%s/%s", envp,
2001 "Library/Preferences/NetHack Defaults.txt");
2002 (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
2003 lastconfigfile[BUFSZ - 1] = '\0';
2004 if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
2008 if (errno != ENOENT) {
2009 const char *details;
2011 /* e.g., problems when setuid NetHack can't search home
2012 * directory restricted to user */
2014 #if defined(NHSTDC) && !defined(NOTSTDC)
2015 if ((details = strerror(errno)) == 0)
2018 raw_printf("Couldn't open default config file %s %s(%d).",
2019 lastconfigfile, details, errno);
2028 * Retrieve a list of integers from a file into a uchar array.
2030 * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
2031 * location is unchanged. Callers must handle zeros if modlist is FALSE.
2034 get_uchars(fp, buf, bufp, list, modlist, size, name)
2035 FILE *fp; /* input file pointer */
2036 char *buf; /* read buffer, must be of size BUFSZ */
2037 char *bufp; /* current pointer */
2038 uchar *list; /* return list */
2039 boolean modlist; /* TRUE: list is being modified in place */
2040 int size; /* return list size */
2041 const char *name; /* name of option for error message */
2043 unsigned int num = 0;
2045 boolean havenum = FALSE;
2054 /* if modifying in place, don't insert zeros */
2055 if (num || !modlist)
2061 if (count == size || !*bufp)
2077 num = num * 10 + (*bufp - '0');
2082 if (fp == (FILE *) 0)
2085 if (!fgets(buf, BUFSZ, fp))
2087 } while (buf[0] == '#');
2093 raw_printf("Syntax error in %s", name);
2101 #ifdef NOCWD_ASSUMPTIONS
2103 adjust_prefix(bufp, prefixid)
2111 /* Backward compatibility, ignore trailing ;n */
2112 if ((ptr = index(bufp, ';')) != 0)
2114 if (strlen(bufp) > 0) {
2115 fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
2116 Strcpy(fqn_prefix[prefixid], bufp);
2117 append_slash(fqn_prefix[prefixid]);
2122 #define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
2125 parse_config_line(fp, origbuf, src)
2130 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
2131 static boolean ramdisk_specified = FALSE;
2136 char *bufp, *altp, buf[BUFSZ];
2137 uchar translate[MAXPCHARS];
2140 /* convert any tab to space, condense consecutive spaces into one,
2141 remove leading and trailing spaces (exception: if there is nothing
2142 but spaces, one of them will be kept even though it leads/trails) */
2143 mungspaces(strcpy(buf, origbuf));
2144 /* lines beginning with '#' are comments; accept empty lines too */
2145 if (!*buf || *buf == '#' || !strcmp(buf, " "))
2148 /* find the '=' or ':' */
2149 bufp = index(buf, '=');
2150 altp = index(buf, ':');
2151 if (!bufp || (altp && altp < bufp))
2155 /* skip past '=', then space between it and value, if any */
2160 /* Go through possible variables */
2161 /* some of these (at least LEVELS and SAVE) should now set the
2162 * appropriate fqn_prefix[] rather than specialized variables
2164 if (match_varname(buf, "OPTIONS", 4)) {
2165 /* hack: un-mungspaces to allow consecutive spaces in
2166 general options until we verify that this is unnecessary;
2167 '=' or ':' is guaranteed to be present */
2168 bufp = index(origbuf, '=');
2169 altp = index(origbuf, ':');
2170 if (!bufp || (altp && altp < bufp))
2172 ++bufp; /* skip '='; parseoptions() handles spaces */
2174 parseoptions(bufp, TRUE, TRUE);
2175 if (plname[0]) /* If a name was given */
2176 plnamesuffix(); /* set the character class */
2177 } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
2178 add_autopickup_exception(bufp);
2179 } else if (match_varname(buf, "MSGTYPE", 7)) {
2180 (void) msgtype_parse_add(bufp);
2181 #ifdef NOCWD_ASSUMPTIONS
2182 } else if (match_varname(buf, "HACKDIR", 4)) {
2183 adjust_prefix(bufp, HACKPREFIX);
2184 } else if (match_varname(buf, "LEVELDIR", 4)
2185 || match_varname(buf, "LEVELS", 4)) {
2186 adjust_prefix(bufp, LEVELPREFIX);
2187 } else if (match_varname(buf, "SAVEDIR", 4)) {
2188 adjust_prefix(bufp, SAVEPREFIX);
2189 } else if (match_varname(buf, "BONESDIR", 5)) {
2190 adjust_prefix(bufp, BONESPREFIX);
2191 } else if (match_varname(buf, "DATADIR", 4)) {
2192 adjust_prefix(bufp, DATAPREFIX);
2193 } else if (match_varname(buf, "SCOREDIR", 4)) {
2194 adjust_prefix(bufp, SCOREPREFIX);
2195 } else if (match_varname(buf, "LOCKDIR", 4)) {
2196 adjust_prefix(bufp, LOCKPREFIX);
2197 } else if (match_varname(buf, "CONFIGDIR", 4)) {
2198 adjust_prefix(bufp, CONFIGPREFIX);
2199 } else if (match_varname(buf, "TROUBLEDIR", 4)) {
2200 adjust_prefix(bufp, TROUBLEPREFIX);
2201 #else /*NOCWD_ASSUMPTIONS*/
2203 } else if (match_varname(buf, "HACKDIR", 4)) {
2204 (void) strncpy(hackdir, bufp, PATHLEN - 1);
2206 } else if (match_varname(buf, "RAMDISK", 3)) {
2207 /* The following ifdef is NOT in the wrong
2208 * place. For now, we accept and silently
2211 if (strlen(bufp) >= PATHLEN)
2212 bufp[PATHLEN - 1] = '\0';
2213 Strcpy(levels, bufp);
2214 ramdisk = (strcmp(permbones, levels) != 0);
2215 ramdisk_specified = TRUE;
2218 } else if (match_varname(buf, "LEVELS", 4)) {
2219 if (strlen(bufp) >= PATHLEN)
2220 bufp[PATHLEN - 1] = '\0';
2221 Strcpy(permbones, bufp);
2222 if (!ramdisk_specified || !*levels)
2223 Strcpy(levels, bufp);
2224 ramdisk = (strcmp(permbones, levels) != 0);
2225 } else if (match_varname(buf, "SAVE", 4)) {
2227 extern int saveprompt;
2231 if ((ptr = index(bufp, ';')) != 0) {
2234 if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') {
2239 #if defined(SYSFLAGS) && defined(MFLOPPY)
2241 saveprompt = sysflags.asksavedisk;
2244 (void) strncpy(SAVEP, bufp, SAVESIZE - 1);
2245 append_slash(SAVEP);
2247 #endif /*NOCWD_ASSUMPTIONS*/
2249 } else if (match_varname(buf, "NAME", 4)) {
2250 (void) strncpy(plname, bufp, PL_NSIZ - 1);
2252 } else if (match_varname(buf, "ROLE", 4)
2253 || match_varname(buf, "CHARACTER", 4)) {
2254 if ((len = str2role(bufp)) >= 0)
2255 flags.initrole = len;
2256 } else if (match_varname(buf, "DOGNAME", 3)) {
2257 (void) strncpy(dogname, bufp, PL_PSIZ - 1);
2258 } else if (match_varname(buf, "CATNAME", 3)) {
2259 (void) strncpy(catname, bufp, PL_PSIZ - 1);
2262 } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) {
2264 free((genericptr_t) sysopt.wizards);
2265 sysopt.wizards = dupstr(bufp);
2266 if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
2267 /* pre-format WIZARDS list now; it's displayed during a panic
2268 and since that panic might be due to running out of memory,
2269 we don't want to risk attempting to allocate any memory then */
2270 if (sysopt.fmtd_wizard_list)
2271 free((genericptr_t) sysopt.fmtd_wizard_list);
2272 sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
2274 } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) {
2275 if (sysopt.shellers)
2276 free((genericptr_t) sysopt.shellers);
2277 sysopt.shellers = dupstr(bufp);
2278 } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) {
2279 if (sysopt.explorers)
2280 free((genericptr_t) sysopt.explorers);
2281 sysopt.explorers = dupstr(bufp);
2282 } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) {
2283 /* if showdebug() has already been called (perhaps we've added
2284 some debugpline() calls to option processing) and has found
2285 a value for getenv("DEBUGFILES"), don't override that */
2286 if (sysopt.env_dbgfl <= 0) {
2287 if (sysopt.debugfiles)
2288 free((genericptr_t) sysopt.debugfiles);
2289 sysopt.debugfiles = dupstr(bufp);
2291 } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
2293 free((genericptr_t) sysopt.support);
2294 sysopt.support = dupstr(bufp);
2295 } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) {
2297 free((genericptr_t) sysopt.recover);
2298 sysopt.recover = dupstr(bufp);
2299 } else if (src == SET_IN_SYS
2300 && match_varname(buf, "CHECK_SAVE_UID", 14)) {
2302 sysopt.check_save_uid = n;
2303 } else if (match_varname(buf, "SEDUCE", 6)) {
2304 n = !!atoi(bufp); /* XXX this could be tighter */
2305 /* allow anyone to turn it off, but only sysconf to turn it on*/
2306 if (src != SET_IN_SYS && n != 0) {
2307 raw_printf("Illegal value in SEDUCE");
2311 sysopt_seduce_set(sysopt.seduce);
2312 } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
2314 /* XXX to get more than 25, need to rewrite all lock code */
2315 if (n < 1 || n > 25) {
2316 raw_printf("Illegal value in MAXPLAYERS (maximum is 25).");
2319 sysopt.maxplayers = n;
2320 } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
2323 raw_printf("Illegal value in PERSMAX (minimum is 1).");
2327 } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
2329 if (n != 0 && n != 1) {
2330 raw_printf("Illegal value in PERS_IS_UID (must be 0 or 1).");
2333 sysopt.pers_is_uid = n;
2334 } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
2337 raw_printf("Illegal value in ENTRYMAX (minimum is 10).");
2340 sysopt.entrymax = n;
2341 } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
2344 raw_printf("Illegal value in POINTSMIN (minimum is 1).");
2347 sysopt.pointsmin = n;
2348 } else if (src == SET_IN_SYS
2349 && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
2353 "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
2356 sysopt.tt_oname_maxrank = n;
2358 /* SYSCF PANICTRACE options */
2359 } else if (src == SET_IN_SYS
2360 && match_varname(buf, "PANICTRACE_LIBC", 15)) {
2362 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
2363 if (n < 0 || n > 2) {
2364 raw_printf("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
2368 sysopt.panictrace_libc = n;
2369 } else if (src == SET_IN_SYS
2370 && match_varname(buf, "PANICTRACE_GDB", 14)) {
2372 #if defined(PANICTRACE)
2373 if (n < 0 || n > 2) {
2374 raw_printf("Illegal value in PANICTRACE_GDB (not 0,1,2).");
2378 sysopt.panictrace_gdb = n;
2379 } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
2380 #if defined(PANICTRACE) && !defined(VMS)
2381 if (!file_exists(bufp)) {
2382 raw_printf("File specified in GDBPATH does not exist.");
2387 free((genericptr_t) sysopt.gdbpath);
2388 sysopt.gdbpath = dupstr(bufp);
2389 } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
2390 #if defined(PANICTRACE) && !defined(VMS)
2391 if (!file_exists(bufp)) {
2392 raw_printf("File specified in GREPPATH does not exist.");
2396 if (sysopt.greppath)
2397 free((genericptr_t) sysopt.greppath);
2398 sysopt.greppath = dupstr(bufp);
2401 } else if (match_varname(buf, "BOULDER", 3)) {
2402 (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE, 1,
2404 } else if (match_varname(buf, "MENUCOLOR", 9)) {
2405 (void) add_menu_coloring(bufp);
2406 } else if (match_varname(buf, "WARNINGS", 5)) {
2407 (void) get_uchars(fp, buf, bufp, translate, FALSE, WARNCOUNT,
2409 assign_warnings(translate);
2410 } else if (match_varname(buf, "SYMBOLS", 4)) {
2411 char *op, symbuf[BUFSZ];
2415 /* check for line continuation (trailing '\') */
2417 morelines = (--op >= bufp && *op == '\\');
2420 /* strip trailing space now that '\' is gone */
2421 if (--op >= bufp && *op == ' ')
2429 if (!fgets(symbuf, BUFSZ, fp)) {
2435 } while (*bufp == '#');
2437 } while (morelines);
2438 switch_symbols(TRUE);
2439 } else if (match_varname(buf, "WIZKIT", 6)) {
2440 (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
2442 } else if (match_varname(buf, "FONT", 4)) {
2445 if (t = strchr(buf + 5, ':')) {
2447 amii_set_text_font(buf + 5, atoi(t + 1));
2450 } else if (match_varname(buf, "PATH", 4)) {
2451 (void) strncpy(PATH, bufp, PATHLEN - 1);
2452 } else if (match_varname(buf, "DEPTH", 5)) {
2453 extern int amii_numcolors;
2454 int val = atoi(bufp);
2456 amii_numcolors = 1L << min(DEPTH, val);
2458 } else if (match_varname(buf, "DRIPENS", 7)) {
2462 for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0;
2463 i < 20 && (t = strtok((char *) 0, ",/")), ++i) {
2464 sscanf(t, "%d", &val);
2465 sysflags.amii_dripens[i] = val;
2468 } else if (match_varname(buf, "SCREENMODE", 10)) {
2469 extern long amii_scrnmode;
2471 if (!stricmp(bufp, "req"))
2472 amii_scrnmode = 0xffffffff; /* Requester */
2473 else if (sscanf(bufp, "%x", &amii_scrnmode) != 1)
2475 } else if (match_varname(buf, "MSGPENS", 7)) {
2476 extern int amii_msgAPen, amii_msgBPen;
2477 char *t = strtok(bufp, ",/");
2480 sscanf(t, "%d", &amii_msgAPen);
2481 if (t = strtok((char *) 0, ",/"))
2482 sscanf(t, "%d", &amii_msgBPen);
2484 } else if (match_varname(buf, "TEXTPENS", 8)) {
2485 extern int amii_textAPen, amii_textBPen;
2486 char *t = strtok(bufp, ",/");
2489 sscanf(t, "%d", &amii_textAPen);
2490 if (t = strtok((char *) 0, ",/"))
2491 sscanf(t, "%d", &amii_textBPen);
2493 } else if (match_varname(buf, "MENUPENS", 8)) {
2494 extern int amii_menuAPen, amii_menuBPen;
2495 char *t = strtok(bufp, ",/");
2498 sscanf(t, "%d", &amii_menuAPen);
2499 if (t = strtok((char *) 0, ",/"))
2500 sscanf(t, "%d", &amii_menuBPen);
2502 } else if (match_varname(buf, "STATUSPENS", 10)) {
2503 extern int amii_statAPen, amii_statBPen;
2504 char *t = strtok(bufp, ",/");
2507 sscanf(t, "%d", &amii_statAPen);
2508 if (t = strtok((char *) 0, ",/"))
2509 sscanf(t, "%d", &amii_statBPen);
2511 } else if (match_varname(buf, "OTHERPENS", 9)) {
2512 extern int amii_otherAPen, amii_otherBPen;
2513 char *t = strtok(bufp, ",/");
2516 sscanf(t, "%d", &amii_otherAPen);
2517 if (t = strtok((char *) 0, ",/"))
2518 sscanf(t, "%d", &amii_otherBPen);
2520 } else if (match_varname(buf, "PENS", 4)) {
2521 extern unsigned short amii_init_map[AMII_MAXCOLORS];
2525 for (i = 0, t = strtok(bufp, ",/");
2526 i < AMII_MAXCOLORS && t != (char *) 0;
2527 t = strtok((char *) 0, ",/"), ++i) {
2528 sscanf(t, "%hx", &amii_init_map[i]);
2530 amii_setpens(amii_numcolors = i);
2531 } else if (match_varname(buf, "FGPENS", 6)) {
2532 extern int foreg[AMII_MAXCOLORS];
2536 for (i = 0, t = strtok(bufp, ",/");
2537 i < AMII_MAXCOLORS && t != (char *) 0;
2538 t = strtok((char *) 0, ",/"), ++i) {
2539 sscanf(t, "%d", &foreg[i]);
2541 } else if (match_varname(buf, "BGPENS", 6)) {
2542 extern int backg[AMII_MAXCOLORS];
2546 for (i = 0, t = strtok(bufp, ",/");
2547 i < AMII_MAXCOLORS && t != (char *) 0;
2548 t = strtok((char *) 0, ",/"), ++i) {
2549 sscanf(t, "%d", &backg[i]);
2553 } else if (match_varname(buf, "SOUNDDIR", 8)) {
2554 sounddir = dupstr(bufp);
2555 } else if (match_varname(buf, "SOUND", 5)) {
2556 add_sound_mapping(bufp);
2559 /* These should move to wc_ options */
2560 } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
2561 extern char *qt_tilewidth;
2563 if (qt_tilewidth == NULL)
2564 qt_tilewidth = dupstr(bufp);
2565 } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
2566 extern char *qt_tileheight;
2568 if (qt_tileheight == NULL)
2569 qt_tileheight = dupstr(bufp);
2570 } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
2571 extern char *qt_fontsize;
2573 if (qt_fontsize == NULL)
2574 qt_fontsize = dupstr(bufp);
2575 } else if (match_varname(buf, "QT_COMPACT", 10)) {
2576 extern int qt_compact_mode;
2578 qt_compact_mode = atoi(bufp);
2587 can_read_file(filename)
2588 const char *filename;
2590 return (boolean) (access(filename, 4) == 0);
2592 #endif /* USER_SOUNDS */
2595 read_config_file(filename, src)
2596 const char *filename;
2599 char buf[4 * BUFSZ], *p;
2601 boolean rv = TRUE; /* assume successful parse */
2603 if (!(fp = fopen_config_file(filename, src)))
2606 /* begin detection of duplicate configfile options */
2607 set_duplicate_opt_detection(1);
2609 while (fgets(buf, sizeof buf, fp)) {
2612 XXX Don't call read() in parse_config_line, read as callback or reassemble
2614 OR: Forbid multiline stuff for alternate config sources.
2617 if ((p = index(buf, '\n')) != 0)
2619 if (!parse_config_line(fp, buf, src)) {
2620 static const char badoptionline[] = "Bad option line: \"%s\"";
2622 /* truncate buffer if it's long; this is actually conservative */
2623 if (strlen(buf) > BUFSZ - sizeof badoptionline)
2624 buf[BUFSZ - sizeof badoptionline] = '\0';
2626 raw_printf(badoptionline, buf);
2633 /* turn off detection of duplicate configfile options */
2634 set_duplicate_opt_detection(0);
2642 #if defined(VMS) || defined(UNIX)
2643 char tmp_wizkit[BUFSZ];
2647 envp = nh_getenv("WIZKIT");
2649 (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2654 if (access(wizkit, 4) == -1) {
2655 /* 4 is R_OK on newer systems */
2656 /* nasty sneaky attempt to read file through
2657 * NetHack's setuid permissions -- this is a
2658 * place a file name may be wholly under the player's
2661 raw_printf("Access to %s denied (%d).", wizkit, errno);
2663 /* fall through to standard names */
2666 if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) {
2668 #if defined(UNIX) || defined(VMS)
2670 /* access() above probably caught most problems for UNIX */
2671 raw_printf("Couldn't open requested config file %s (%d).", wizkit,
2677 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2678 if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
2682 envp = nh_getenv("HOME");
2684 Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
2686 Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
2687 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2689 #else /* should be only UNIX left */
2690 envp = nh_getenv("HOME");
2692 Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
2694 Strcpy(tmp_wizkit, wizkit);
2695 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2697 else if (errno != ENOENT) {
2698 /* e.g., problems when setuid NetHack can't search home
2699 * directory restricted to user */
2700 raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit,
2709 /* add to hero's inventory if there's room, otherwise put item on floor */
2714 if (!obj || obj == &zeroobj)
2717 /* subset of starting inventory pre-ID */
2719 if (Role_if(PM_PRIEST))
2721 /* same criteria as lift_object()'s check for available inventory slot */
2722 if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52
2723 && !merge_choice(invent, obj)) {
2724 /* inventory overflow; can't just place & stack object since
2725 hero isn't in position yet, so schedule for arrival later */
2726 add_to_migration(obj);
2727 obj->ox = 0; /* index of main dungeon */
2728 obj->oy = 1; /* starting level number */
2730 (long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
2740 char *ep, buf[BUFSZ];
2742 boolean bad_items = FALSE, skip = FALSE;
2744 if (!wizard || !(fp = fopen_wizkit_file()))
2747 program_state.wizkit_wishing = 1;
2748 while (fgets(buf, (int) (sizeof buf), fp)) {
2749 ep = index(buf, '\n');
2750 if (skip) { /* in case previous line was too long */
2752 skip = FALSE; /* found newline; next line is normal */
2755 skip = TRUE; /* newline missing; discard next fgets */
2757 *ep = '\0'; /* remove newline */
2760 otmp = readobjnam(buf, (struct obj *) 0);
2762 if (otmp != &zeroobj)
2763 wizkit_addinv(otmp);
2765 /* .60 limits output line width to 79 chars */
2766 raw_printf("Bad wizkit item: \"%.60s\"", buf);
2772 program_state.wizkit_wishing = 0;
2779 extern struct symsetentry *symset_list; /* options.c */
2780 extern struct symparse loadsyms[]; /* drawing.c */
2781 extern const char *known_handling[]; /* drawing.c */
2782 extern const char *known_restrictions[]; /* drawing.c */
2783 static int symset_count = 0; /* for pick-list building only */
2784 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
2792 fp = fopen_datafile(SYMBOLS, "r", HACKPREFIX);
2797 * Returns 1 if the chose symset was found and loaded.
2798 * 0 if it wasn't found in the sym file or other problem.
2801 read_sym_file(which_set)
2804 char buf[4 * BUFSZ];
2807 if (!(fp = fopen_sym_file()))
2811 chosen_symset_start = chosen_symset_end = FALSE;
2812 while (fgets(buf, 4 * BUFSZ, fp)) {
2813 if (!parse_sym_line(buf, which_set)) {
2814 raw_printf("Bad symbol line: \"%.50s\"", buf);
2819 if (!chosen_symset_end && !chosen_symset_start)
2820 return (symset[which_set].name == 0) ? 1 : 0;
2821 if (!chosen_symset_end) {
2822 raw_printf("Missing finish for symset \"%s\"",
2823 symset[which_set].name ? symset[which_set].name
2830 /* returns 0 on error */
2832 parse_sym_line(buf, which_set)
2837 struct symparse *symp = (struct symparse *) 0;
2838 char *bufp, *commentp, *altp;
2840 /* convert each instance of whitespace (tabs, consecutive spaces)
2841 into a single space; leading and trailing spaces are stripped */
2843 if (!*buf || *buf == '#' || !strcmp(buf, " "))
2845 /* remove trailing comment, if any */
2846 if ((commentp = rindex(buf, '#')) != 0) {
2848 /* remove space preceding the stripped comment, if any;
2849 we know 'commentp > buf' because *buf=='#' was caught above */
2850 if (commentp[-1] == ' ')
2854 /* find the '=' or ':' */
2855 bufp = index(buf, '=');
2856 altp = index(buf, ':');
2857 if (!bufp || (altp && altp < bufp))
2860 if (strncmpi(buf, "finish", 6) == 0) {
2861 /* end current graphics set */
2862 if (chosen_symset_start)
2863 chosen_symset_end = TRUE;
2864 chosen_symset_start = FALSE;
2869 /* skip '=' and space which follows, if any */
2874 symp = match_sym(buf);
2878 if (!symset[which_set].name) {
2879 /* A null symset name indicates that we're just
2880 building a pick-list of possible symset
2881 values from the file, so only do that */
2882 if (symp->range == SYM_CONTROL) {
2883 struct symsetentry *tmpsp;
2885 switch (symp->idx) {
2888 (struct symsetentry *) alloc(sizeof (struct symsetentry));
2889 tmpsp->next = (struct symsetentry *) 0;
2891 symset_list = tmpsp;
2895 tmpsp->next = symset_list;
2896 symset_list = tmpsp;
2898 tmpsp->idx = symset_count;
2899 tmpsp->name = dupstr(bufp);
2900 tmpsp->desc = (char *) 0;
2902 /* initialize restriction bits */
2907 /* handler type identified */
2908 tmpsp = symset_list; /* most recent symset */
2909 tmpsp->handling = H_UNK;
2911 while (known_handling[i]) {
2912 if (!strcmpi(known_handling[i], bufp)) {
2913 tmpsp->handling = i;
2914 break; /* while loop */
2919 case 3: /* description:something */
2920 tmpsp = symset_list; /* most recent symset */
2921 if (tmpsp && !tmpsp->desc)
2922 tmpsp->desc = dupstr(bufp);
2925 /* restrictions: xxxx*/
2926 tmpsp = symset_list; /* most recent symset */
2927 for (i = 0; known_restrictions[i]; ++i) {
2928 if (!strcmpi(known_restrictions[i], bufp)) {
2937 break; /* while loop */
2946 if (symp->range == SYM_CONTROL) {
2947 switch (symp->idx) {
2949 /* start of symset */
2950 if (!strcmpi(bufp, symset[which_set].name)) {
2951 /* matches desired one */
2952 chosen_symset_start = TRUE;
2953 /* these init_*() functions clear symset fields too */
2954 if (which_set == ROGUESET)
2956 else if (which_set == PRIMARY)
2962 if (chosen_symset_start)
2963 chosen_symset_end = TRUE;
2964 chosen_symset_start = FALSE;
2967 /* handler type identified */
2968 if (chosen_symset_start)
2969 set_symhandling(bufp, which_set);
2971 /* case 3: (description) is ignored here */
2972 case 4: /* color:off */
2973 if (chosen_symset_start) {
2975 if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
2976 || !strcmpi(bufp, "on"))
2977 symset[which_set].nocolor = 0;
2978 else if (!strcmpi(bufp, "false")
2979 || !strcmpi(bufp, "no")
2980 || !strcmpi(bufp, "off"))
2981 symset[which_set].nocolor = 1;
2985 case 5: /* restrictions: xxxx*/
2986 if (chosen_symset_start) {
2989 while (known_restrictions[n]) {
2990 if (!strcmpi(known_restrictions[n], bufp)) {
2993 symset[which_set].primary = 1;
2996 symset[which_set].rogue = 1;
2999 break; /* while loop */
3006 } else { /* !SYM_CONTROL */
3007 val = sym_val(bufp);
3008 if (chosen_symset_start) {
3009 if (which_set == PRIMARY) {
3010 update_l_symset(symp, val);
3011 } else if (which_set == ROGUESET) {
3012 update_r_symset(symp, val);
3021 set_symhandling(handling, which_set)
3027 symset[which_set].handling = H_UNK;
3028 while (known_handling[i]) {
3029 if (!strcmpi(known_handling[i], handling)) {
3030 symset[which_set].handling = i;
3037 /* ---------- END CONFIG FILE HANDLING ----------- */
3039 /* ---------- BEGIN SCOREBOARD CREATION ----------- */
3042 #define UNUSED_if_not_OS2_CODEVIEW /*empty*/
3044 #define UNUSED_if_not_OS2_CODEVIEW UNUSED
3047 /* verify that we can write to scoreboard file; if not, try to create one */
3050 check_recordfile(dir)
3051 const char *dir UNUSED_if_not_OS2_CODEVIEW;
3053 #if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW)
3056 const char *fq_record;
3059 #if defined(UNIX) || defined(VMS)
3060 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3061 fd = open(fq_record, O_RDWR, 0);
3063 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
3064 if (!file_is_stmlf(fd)) {
3066 "Warning: scoreboard file %s is not in stream_lf format",
3071 (void) nhclose(fd); /* RECORD is accessible */
3072 } else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
3073 (void) nhclose(fd); /* RECORD newly created */
3074 #if defined(VMS) && !defined(SECURE)
3075 /* Re-protect RECORD with world:read+write+execute+delete access. */
3076 (void) chmod(fq_record, FCMASK | 007);
3077 #endif /* VMS && !SECURE */
3079 raw_printf("Warning: cannot write scoreboard file %s", fq_record);
3082 #endif /* !UNIX && !VMS */
3083 #if defined(MICRO) || defined(WIN32)
3086 #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
3087 /* how does this work when there isn't an explicit path or fopenp
3088 * for later access to the file via fopen_datafile? ? */
3089 (void) strncpy(tmp, dir, PATHLEN - 1);
3090 tmp[PATHLEN - 1] = '\0';
3091 if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
3093 Strcat(tmp, RECORD);
3097 Strcpy(tmp, RECORD);
3098 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3101 if ((fd = open(fq_record, O_RDWR)) < 0) {
3102 /* try to create empty record */
3103 #if defined(AZTEC_C) || defined(_DCC) \
3104 || (defined(__GNUC__) && defined(__AMIGA__))
3105 /* Aztec doesn't use the third argument */
3106 /* DICE doesn't like it */
3107 if ((fd = open(fq_record, O_CREAT | O_RDWR)) < 0) {
3109 if ((fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE))
3112 raw_printf("Warning: cannot write record %s", tmp);
3116 } else /* open succeeded */
3118 #else /* MICRO || WIN32*/
3121 /* Create the "record" file, if necessary */
3122 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3123 fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
3128 #endif /* MICRO || WIN32*/
3131 /* ---------- END SCOREBOARD CREATION ----------- */
3133 /* ---------- BEGIN PANIC/IMPOSSIBLE LOG ----------- */
3137 paniclog(type, reason)
3138 const char *type; /* panic, impossible, trickery */
3139 const char *reason; /* explanation */
3145 if (!program_state.in_paniclog) {
3146 program_state.in_paniclog = 1;
3147 lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
3149 time_t now = getnow();
3151 char playmode = wizard ? 'D' : discover ? 'X' : '-';
3153 (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
3154 version_string(buf), yyyymmdd(now), hhmmss(now),
3155 uid, playmode, type, reason);
3156 (void) fclose(lfile);
3158 program_state.in_paniclog = 0;
3160 #endif /* PANICLOG */
3164 /* ---------- END PANIC/IMPOSSIBLE LOG ----------- */
3168 /* ---------- BEGIN INTERNAL RECOVER ----------- */
3173 int lev, savelev, hpid, pltmpsiz;
3175 struct version_info version_data;
3177 char savename[SAVESIZE], errbuf[BUFSZ];
3178 struct savefile_info sfi;
3179 char tmpplbuf[PL_NSIZ];
3181 for (lev = 0; lev < 256; lev++)
3184 /* level 0 file contains:
3185 * pid of creating process (ignored here)
3186 * level number for current level of save file
3187 * name of save file nethack would have created
3192 gfd = open_levelfile(0, errbuf);
3194 raw_printf("%s\n", errbuf);
3197 if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
3198 raw_printf("\n%s\n%s\n",
3199 "Checkpoint data incompletely written or subsequently clobbered.",
3200 "Recovery impossible.");
3201 (void) nhclose(gfd);
3204 if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
3205 != sizeof(savelev)) {
3207 "\nCheckpointing was not in effect for %s -- recovery impossible.\n",
3209 (void) nhclose(gfd);
3212 if ((read(gfd, (genericptr_t) savename, sizeof savename)
3214 || (read(gfd, (genericptr_t) &version_data, sizeof version_data)
3215 != sizeof version_data)
3216 || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
3217 || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3218 != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ)
3219 || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) {
3220 raw_printf("\nError reading %s -- can't recover.\n", lock);
3221 (void) nhclose(gfd);
3225 /* save file should contain:
3229 * current level (including pets)
3230 * (non-level-based) game state
3233 set_savefile_name(TRUE);
3234 sfd = create_savefile();
3236 raw_printf("\nCannot recover savefile %s.\n", SAVEF);
3237 (void) nhclose(gfd);
3241 lfd = open_levelfile(savelev, errbuf);
3243 raw_printf("\n%s\n", errbuf);
3244 (void) nhclose(gfd);
3245 (void) nhclose(sfd);
3250 if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
3251 != sizeof version_data) {
3252 raw_printf("\nError writing %s; recovery failed.", SAVEF);
3253 (void) nhclose(gfd);
3254 (void) nhclose(sfd);
3259 if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) {
3260 raw_printf("\nError writing %s; recovery failed (savefile_info).\n",
3262 (void) nhclose(gfd);
3263 (void) nhclose(sfd);
3268 if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3269 != sizeof pltmpsiz) {
3270 raw_printf("Error writing %s; recovery failed (player name size).\n",
3272 (void) nhclose(gfd);
3273 (void) nhclose(sfd);
3278 if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) {
3279 raw_printf("Error writing %s; recovery failed (player name).\n",
3281 (void) nhclose(gfd);
3282 (void) nhclose(sfd);
3287 if (!copy_bytes(lfd, sfd)) {
3288 (void) nhclose(lfd);
3289 (void) nhclose(sfd);
3293 (void) nhclose(lfd);
3294 processed[savelev] = 1;
3296 if (!copy_bytes(gfd, sfd)) {
3297 (void) nhclose(lfd);
3298 (void) nhclose(sfd);
3302 (void) nhclose(gfd);
3305 for (lev = 1; lev < 256; lev++) {
3306 /* level numbers are kept in xchars in save.c, so the
3307 * maximum level number (for the endlevel) must be < 256
3309 if (lev != savelev) {
3310 lfd = open_levelfile(lev, (char *) 0);
3312 /* any or all of these may not exist */
3314 write(sfd, (genericptr_t) &levc, sizeof(levc));
3315 if (!copy_bytes(lfd, sfd)) {
3316 (void) nhclose(lfd);
3317 (void) nhclose(sfd);
3321 (void) nhclose(lfd);
3326 (void) nhclose(sfd);
3328 #ifdef HOLD_LOCKFILE_OPEN
3332 * We have a successful savefile!
3333 * Only now do we erase the level files.
3335 for (lev = 0; lev < 256; lev++) {
3336 if (processed[lev]) {
3337 const char *fq_lock;
3338 set_levelfile_name(lock, lev);
3339 fq_lock = fqname(lock, LEVELPREFIX, 3);
3340 (void) unlink(fq_lock);
3347 copy_bytes(ifd, ofd)
3354 nfrom = read(ifd, buf, BUFSIZ);
3355 nto = write(ofd, buf, nfrom);
3358 } while (nfrom == BUFSIZ);
3362 /* ---------- END INTERNAL RECOVER ----------- */
3363 #endif /*SELF_RECOVER*/
3365 /* ---------- OTHER ----------- */
3375 * All we really care about is the end result - can we read the file?
3376 * So just check that directly.
3378 * Not tested on most of the old platforms (which don't attempt
3379 * to implement SYSCF).
3380 * Some ports don't like open()'s optional third argument;
3381 * VMS overrides open() usage with a macro which requires it.
3384 fd = open(SYSCF_FILE, O_RDONLY);
3386 fd = open(SYSCF_FILE, O_RDONLY, 0);
3393 raw_printf("Unable to open SYSCF_FILE.\n");
3397 #endif /* SYSCF_FILE */
3401 /* used by debugpline() to decide whether to issue a message
3402 * from a particular source file; caller passes __FILE__ and we check
3403 * whether it is in the source file list supplied by SYSCF's DEBUGFILES
3405 * pass FALSE to override wildcard matching; useful for files
3406 * like dungeon.c and questpgr.c, which generate a ridiculous amount of
3407 * output if DEBUG is defined and effectively block the use of a wildcard */
3409 debugcore(filename, wildcards)
3410 const char *filename;
3413 const char *debugfiles, *p;
3415 if (!filename || !*filename)
3416 return FALSE; /* sanity precaution */
3418 if (sysopt.env_dbgfl == 0) {
3419 /* check once for DEBUGFILES in the environment;
3420 if found, it supersedes the sysconf value
3421 [note: getenv() rather than nh_getenv() since a long value
3422 is valid and doesn't pose any sort of overflow risk here] */
3423 if ((p = getenv("DEBUGFILES")) != 0) {
3424 if (sysopt.debugfiles)
3425 free((genericptr_t) sysopt.debugfiles);
3426 sysopt.debugfiles = dupstr(p);
3427 sysopt.env_dbgfl = 1;
3429 sysopt.env_dbgfl = -1;
3432 debugfiles = sysopt.debugfiles;
3433 /* usual case: sysopt.debugfiles will be empty */
3434 if (!debugfiles || !*debugfiles)
3437 /* strip filename's path if present */
3439 if ((p = rindex(filename, '/')) != 0)
3443 filename = vms_basename(filename);
3444 /* vms_basename strips off 'type' suffix as well as path and version;
3445 we want to put suffix back (".c" assumed); since it always returns
3446 a pointer to a static buffer, we can safely modify its result */
3447 Strcat((char *) filename, ".c");
3451 * Wildcard match will only work if there's a single pattern (which
3452 * might be a single file name without any wildcarding) rather than
3453 * a space-separated list.
3454 * [to NOT do: We could step through the space-separated list and
3455 * attempt a wildcard match against each element, but that would be
3456 * overkill for the intended usage.]
3458 if (wildcards && pmatch(debugfiles, filename))
3461 /* check whether filename is an element of the list */
3462 if ((p = strstr(debugfiles, filename)) != 0) {
3463 int l = (int) strlen(filename);
3465 if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
3466 && (p[l] == ' ' || p[l] == '\0'))
3474 /* ---------- BEGIN TRIBUTE ----------- */
3479 #define SECTIONSCOPE 1
3480 #define TITLESCOPE 2
3481 #define PASSAGESCOPE 3
3483 #define MAXPASSAGES SIZE(context.novel.pasg) /* 30 */
3485 static int FDECL(choose_passage, (int, unsigned));
3487 /* choose a random passage that hasn't been chosen yet; once all have
3488 been chosen, reset the tracking to make all passages available again */
3490 choose_passage(passagecnt, oid)
3491 int passagecnt; /* total of available passages, 1..MAXPASSAGES */
3492 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
3498 if (passagecnt > MAXPASSAGES)
3499 passagecnt = MAXPASSAGES;
3501 /* if a different book or we've used up all the passages already,
3502 reset in order to have all 'passagecnt' passages available */
3503 if (oid != context.novel.id || context.novel.count == 0) {
3504 context.novel.id = oid;
3505 context.novel.count = passagecnt;
3506 for (idx = 0; idx < MAXPASSAGES; idx++)
3507 context.novel.pasg[idx] = (xchar) ((idx < passagecnt) ? idx + 1
3511 idx = rn2(context.novel.count);
3512 res = (int) context.novel.pasg[idx];
3513 /* move the last slot's passage index into the slot just used
3514 and reduce the number of passages available */
3515 context.novel.pasg[idx] = context.novel.pasg[--context.novel.count];
3519 /* Returns True if you were able to read something. */
3521 read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid)
3522 const char *tribsection, *tribtitle;
3523 int tribpassage, bufsz;
3525 unsigned oid; /* book identifier */
3529 char line[BUFSZ], lastline[BUFSZ];
3532 int linect = 0, passagecnt = 0, targetpassage = 0;
3534 const char *badtranslation = "an incomprehensible foreign translation";
3536 const char *badtranslation = "
\95s
\8a®
\91S
\82È
\8aO
\8d\91\8cê
\96|
\96ó";
3537 boolean matchedsection = FALSE, matchedtitle = FALSE;
3538 winid tribwin = WIN_ERR;
3539 boolean grasped = FALSE;
3540 boolean foundpassage = FALSE;
3542 /* check for mandatories */
3543 if (!tribsection || !tribtitle) {
3546 pline("It's %s of \"%s\"!", badtranslation, tribtitle);
3548 pline("
\82±
\82ê
\82Í
\81u%s
\81v
\82Ì%s
\82¾
\81I", tribtitle, badtranslation);
3552 debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
3555 fp = dlb_fopen(TRIBUTEFILE, "r");
3557 /* this is actually an error - cannot open tribute file! */
3560 pline("You feel too overwhelmed to continue!");
3562 pline("
\82 \82È
\82½
\82Í
\91±
\82¯
\82ç
\82ê
\82È
\82¢
\82Ù
\82Ç
\88³
\93|
\82³
\82ê
\82½
\81I");
3567 * Syntax (not case-sensitive):
3570 * In the books section:
3571 * %title booktitle (n)
3572 * where booktitle=book title without quotes
3573 * (n)= total number of passages present for this title
3575 * where k=sequential passage number
3577 * %e ends the passage/book/section
3578 * If in a passage, it marks the end of that passage.
3579 * If in a book, it marks the end of that book.
3580 * If in a section, it marks the end of that section.
3585 *line = *lastline = '\0';
3586 while (dlb_fgets(line, sizeof line, fp) != 0) {
3588 if ((endp = index(line, '\n')) != 0)
3592 if (!strncmpi(&line[1], "section ", sizeof("section ") - 1)) {
3593 char *st = &line[9]; /* 9 from "%section " */
3595 scope = SECTIONSCOPE;
3596 if (!strcmpi(st, tribsection))
3597 matchedsection = TRUE;
3599 matchedsection = FALSE;
3600 } else if (!strncmpi(&line[1], "title ", sizeof("title ") - 1)) {
3601 char *st = &line[7]; /* 7 from "%title " */
3604 if ((p1 = index(st, '(')) != 0) {
3606 (void) mungspaces(st);
3607 if ((p2 = index(p1, ')')) != 0) {
3609 passagecnt = atoi(p1);
3610 if (passagecnt > MAXPASSAGES)
3611 passagecnt = MAXPASSAGES;
3613 if (matchedsection && !strcmpi(st, tribtitle)) {
3614 matchedtitle = TRUE;
3615 targetpassage = !tribpassage
3616 ? choose_passage(passagecnt, oid)
3617 : (tribpassage <= passagecnt)
3620 matchedtitle = FALSE;
3624 } else if (!strncmpi(&line[1], "passage ",
3625 sizeof("passage ") - 1)) {
3627 char *st = &line[9]; /* 9 from "%passage " */
3629 while (*st == ' ' || *st == '\t')
3631 if (*st && digit(*st) && (strlen(st) < 3))
3632 passagenum = atoi(st);
3633 if (passagenum && (passagenum <= passagecnt)) {
3634 scope = PASSAGESCOPE;
3635 if (matchedtitle && (passagenum == targetpassage)) {
3637 tribwin = create_nhwindow(NHW_MENU);
3639 foundpassage = TRUE;
3642 } else if (!strncmpi(&line[1], "e ", sizeof("e ") - 1)) {
3643 if (matchedtitle && scope == PASSAGESCOPE
3644 && ((!nowin_buf && tribwin != WIN_ERR)
3645 || (nowin_buf && foundpassage)))
3647 if (scope == TITLESCOPE)
3648 matchedtitle = FALSE;
3649 if (scope == SECTIONSCOPE)
3650 matchedsection = FALSE;
3654 debugpline1("tribute file error: bad %% command, line %d.",
3659 /* comment only, next! */
3662 if (matchedtitle && scope == PASSAGESCOPE) {
3663 if (!nowin_buf && tribwin != WIN_ERR) {
3664 putstr(tribwin, 0, line);
3665 Strcpy(lastline, line);
3666 } else if (nowin_buf) {
3667 if ((int) strlen(line) < bufsz - 1)
3668 Strcpy(nowin_buf, line);
3675 (void) dlb_fclose(fp);
3676 if (!nowin_buf && tribwin != WIN_ERR) {
3677 if (matchedtitle && scope == PASSAGESCOPE) {
3678 display_nhwindow(tribwin, FALSE);
3679 /* put the final attribution line into message history,
3680 analogous to the summary line from long quest messages */
3681 if (index(lastline, '['))
3682 mungspaces(lastline); /* to remove leading spaces */
3683 else /* construct one if necessary */
3684 Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
3685 putmsghistory(lastline, FALSE);
3687 destroy_nhwindow(tribwin);
3693 pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
3695 pline("
\82±
\82ê
\82Í
\81u%s
\81v
\82Ì%s
\82Ì
\82æ
\82¤
\82¾
\81I", tribtitle, badtranslation);
3704 Death_quote(buf, bufsz)
3708 unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
3710 return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
3713 /* ---------- END TRIBUTE ----------- */