1 /* NetHack 3.6 files.c $NHDT-Date: 1576626110 2019/12/17 23:41:50 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.276 $ */
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-2020 */
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
161 extern char *FDECL(translate_path_variables, (const char *, char *));
167 #define unlink macunlink
170 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) \
171 || defined(__MWERKS__)
172 #define PRAGMA_UNUSED
176 extern char *sounddir;
179 extern int n_dgns; /* from dungeon.c */
181 #if defined(UNIX) && defined(QT_GRAPHICS)
186 STATIC_PTR int FDECL(CFDECLSPEC strcmp_wrap, (const void *, const void *));
188 STATIC_DCL char *FDECL(set_bonesfile_name, (char *, d_level *));
189 STATIC_DCL char *NDECL(set_bonestemp_name);
191 STATIC_DCL void FDECL(redirect, (const char *, const char *, FILE *,
194 #if defined(COMPRESS) || defined(ZLIB_COMP)
195 STATIC_DCL void FDECL(docompress_file, (const char *, BOOLEAN_P));
197 #if defined(ZLIB_COMP)
198 STATIC_DCL boolean FDECL(make_compressed_name, (const char *, char *));
201 STATIC_DCL char *FDECL(make_lockname, (const char *, char *));
203 STATIC_DCL void FDECL(set_configfile_name, (const char *));
204 STATIC_DCL FILE *FDECL(fopen_config_file, (const char *, int));
205 STATIC_DCL int FDECL(get_uchars, (char *, uchar *, BOOLEAN_P,
207 boolean FDECL(proc_wizkit_line, (char *));
208 boolean FDECL(parse_config_line, (char *));
209 STATIC_DCL boolean FDECL(parse_conf_file, (FILE *, boolean (*proc)(char *)));
210 STATIC_DCL FILE *NDECL(fopen_sym_file);
211 boolean FDECL(proc_symset_line, (char *));
212 STATIC_DCL void FDECL(set_symhandling, (char *, int));
213 #ifdef NOCWD_ASSUMPTIONS
214 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
216 STATIC_DCL boolean FDECL(config_error_nextline, (const char *));
217 STATIC_DCL void NDECL(free_config_sections);
218 STATIC_DCL char *FDECL(choose_random_part, (char *, CHAR_P));
219 STATIC_DCL boolean FDECL(is_config_section, (const char *));
220 STATIC_DCL boolean FDECL(handle_config_section, (char *));
222 STATIC_DCL boolean FDECL(copy_bytes, (int, int));
224 #ifdef HOLD_LOCKFILE_OPEN
225 STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int));
229 static char *config_section_chosen = (char *) 0;
230 static char *config_section_current = (char *) 0;
236 * legal zero-terminated list of acceptable file name characters
237 * quotechar lead-in character used to quote illegal characters as
240 * callerbuf buffer to house result
241 * bufsz size of callerbuf
244 * The hex digits 0-9 and A-F are always part of the legal set due to
245 * their use in the encoding scheme, even if not explicitly included in
249 * The following call:
250 * (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
251 * '%', "This is a % test!", buf, 512);
252 * results in this encoding:
253 * "This%20is%20a%20%25%20test%21"
256 fname_encode(legal, quotechar, s, callerbuf, bufsz)
264 static char hexdigits[] = "0123456789ABCDEF";
271 /* Do we have room for one more character or encoding? */
272 if ((bufsz - cnt) <= 4)
275 if (*sp == quotechar) {
276 (void) sprintf(op, "%c%02X", quotechar, *sp);
279 } else if ((index(legal, *sp) != 0) || (index(hexdigits, *sp) != 0)) {
284 } else if (is_kanji1(s, sp - s)) {
291 (void) sprintf(op, "%c%02X", quotechar, *sp);
304 * quotechar lead-in character used to quote illegal characters as
307 * callerbuf buffer to house result
308 * bufsz size of callerbuf
311 fname_decode(quotechar, s, callerbuf, bufsz)
317 int k, calc, cnt = 0;
318 static char hexdigits[] = "0123456789ABCDEF";
326 /* Do we have room for one more character? */
327 if ((bufsz - cnt) <= 2)
329 if (*sp == quotechar) {
331 for (k = 0; k < 16; ++k)
332 if (*sp == hexdigits[k])
335 return callerbuf; /* impossible, so bail */
338 for (k = 0; k < 16; ++k)
339 if (*sp == hexdigits[k])
342 return callerbuf; /* impossible, so bail */
356 #ifdef PREFIXES_IN_USE
357 #define UNUSED_if_not_PREFIXES_IN_USE /*empty*/
359 #define UNUSED_if_not_PREFIXES_IN_USE UNUSED
364 fqname(basenam, whichprefix, buffnum)
366 int whichprefix UNUSED_if_not_PREFIXES_IN_USE;
367 int buffnum UNUSED_if_not_PREFIXES_IN_USE;
369 #ifdef PREFIXES_IN_USE
376 #ifndef PREFIXES_IN_USE
379 if (!basenam || whichprefix < 0 || whichprefix >= PREFIX_COUNT)
381 if (!fqn_prefix[whichprefix])
383 if (buffnum < 0 || buffnum >= FQN_NUMBUF) {
384 impossible("Invalid fqn_filename_buffer specified: %d", buffnum);
387 bufptr = fqn_prefix[whichprefix];
389 if (strchr(fqn_prefix[whichprefix], '%')
390 || strchr(fqn_prefix[whichprefix], '~'))
391 bufptr = translate_path_variables(fqn_prefix[whichprefix], tmpbuf);
393 if (strlen(bufptr) + strlen(basenam) >= FQN_MAX_FILENAME) {
394 impossible("fqname too long: %s + %s", bufptr, basenam);
395 return basenam; /* XXX */
397 Strcpy(fqn_filename_buffer[buffnum], bufptr);
398 return strcat(fqn_filename_buffer[buffnum], basenam);
399 #endif /* !PREFIXES_IN_USE */
403 validate_prefix_locations(reasonbuf)
404 char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */
406 #if defined(NOCWD_ASSUMPTIONS)
408 const char *filename;
409 int prefcnt, failcount = 0;
410 char panicbuf1[BUFSZ], panicbuf2[BUFSZ];
416 #if defined(NOCWD_ASSUMPTIONS)
417 for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++) {
418 /* don't test writing to configdir or datadir; they're readonly */
419 if (prefcnt == SYSCONFPREFIX || prefcnt == CONFIGPREFIX
420 || prefcnt == DATAPREFIX)
422 filename = fqname("validate", prefcnt, 3);
423 if ((fp = fopen(filename, "w"))) {
425 (void) unlink(filename);
429 Strcat(reasonbuf, ", ");
430 Strcat(reasonbuf, fqn_prefix_names[prefcnt]);
432 /* the paniclog entry gets the value of errno as well */
433 Sprintf(panicbuf1, "Invalid %s", fqn_prefix_names[prefcnt]);
434 #if defined(NHSTDC) && !defined(NOTSTDC)
435 if (!(details = strerror(errno)))
438 Sprintf(panicbuf2, "\"%s\", (%d) %s", fqn_prefix[prefcnt], errno,
440 paniclog(panicbuf1, panicbuf2);
451 /* fopen a file, with OS-dependent bells and whistles */
452 /* NOTE: a simpler version of this routine also exists in util/dlb_main.c */
454 fopen_datafile(filename, mode, prefix)
455 const char *filename, *mode;
460 filename = fqname(filename, prefix, prefix == TROUBLEPREFIX ? 3 : 0);
461 fp = fopen(filename, mode);
465 /* ---------- BEGIN LEVEL FILE HANDLING ----------- */
468 /* Set names for bones[] and lock[] */
473 Strcpy(levels, permbones);
474 Strcpy(bones, permbones);
476 append_slash(permbones);
477 append_slash(levels);
479 strncat(levels, bbs_id, PATHLEN);
482 Strcat(bones, "bonesnn.*");
483 Strcpy(lock, levels);
485 Strcat(lock, alllevels);
491 /* Construct a file name for a level-type file, which is of the form
492 * something.level (with any old level stripped off).
493 * This assumes there is space on the end of 'file' to append
494 * a two digit number. This is true for 'level'
495 * but be careful if you use it for other things -dgk
498 set_levelfile_name(file, lev)
504 tf = rindex(file, '.');
507 Sprintf(tf, ".%d", lev);
515 create_levelfile(lev, errbuf)
524 set_levelfile_name(lock, lev);
525 fq_lock = fqname(lock, LEVELPREFIX, 0);
527 #if defined(MICRO) || defined(WIN32)
528 /* Use O_TRUNC to force the file to be shortened if it already
529 * exists and is currently longer.
531 #ifdef HOLD_LOCKFILE_OPEN
533 fd = open_levelfile_exclusively(
534 fq_lock, lev, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
537 fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
540 fd = maccreat(fq_lock, LEVL_TYPE);
542 fd = creat(fq_lock, FCMASK);
544 #endif /* MICRO || WIN32 */
547 level_info[lev].flags |= LFILE_EXISTS;
548 else if (errbuf) /* failure explanation */
549 Sprintf(errbuf, "Cannot create file \"%s\" for level %d (errno %d).",
556 open_levelfile(lev, errbuf)
565 set_levelfile_name(lock, lev);
566 fq_lock = fqname(lock, LEVELPREFIX, 0);
568 /* If not currently accessible, swap it in. */
569 if (level_info[lev].where != ACTIVE)
573 fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE);
575 #ifdef HOLD_LOCKFILE_OPEN
577 fd = open_levelfile_exclusively(fq_lock, lev, O_RDONLY | O_BINARY);
580 fd = open(fq_lock, O_RDONLY | O_BINARY, 0);
583 /* for failure, return an explanation that our caller can use;
584 settle for `lock' instead of `fq_lock' because the latter
585 might end up being too big for nethack's BUFSZ */
586 if (fd < 0 && errbuf)
588 Sprintf(errbuf, "Cannot open file \"%s\" for level %d (errno %d).",
591 Sprintf(errbuf, "
\92n
\89º%d
\8aK
\82Ì
\83t
\83@
\83C
\83\8b\"%s\"
\82ð
\8aJ
\82¯
\82È
\82¢(errno %d)
\81D",
599 delete_levelfile(lev)
603 * Level 0 might be created by port specific code that doesn't
604 * call create_levfile(), so always assume that it exists.
606 if (lev == 0 || (level_info[lev].flags & LFILE_EXISTS)) {
607 set_levelfile_name(lock, lev);
608 #ifdef HOLD_LOCKFILE_OPEN
612 (void) unlink(fqname(lock, LEVELPREFIX, 0));
613 level_info[lev].flags &= ~LFILE_EXISTS;
620 #ifdef HANGUPHANDLING
621 if (program_state.preserve_locks)
624 #if !defined(PC_LOCKING) && defined(MFLOPPY) && !defined(AMIGA)
625 eraseall(levels, alllevels);
627 eraseall(permbones, alllevels);
633 (void) signal(SIGINT, SIG_IGN);
635 #if defined(UNIX) || defined(VMS)
636 sethanguphandler((void FDECL((*), (int) )) SIG_IGN);
638 /* can't access maxledgerno() before dungeons are created -dlc */
639 for (x = (n_dgns ? maxledgerno() : 0); x >= 0; x--)
640 delete_levelfile(x); /* not all levels need be present */
642 #endif /* ?PC_LOCKING,&c */
645 #if defined(SELECTSAVED)
646 /* qsort comparison routine */
647 STATIC_OVL int CFDECLSPEC
652 #if defined(UNIX) && defined(QT_GRAPHICS)
653 return strncasecmp(*(char **) p, *(char **) q, 16);
655 return strncmpi(*(char **) p, *(char **) q, 16);
660 #ifdef HOLD_LOCKFILE_OPEN
662 open_levelfile_exclusively(name, lev, oflag)
672 if (lftrack.fd >= 0) {
673 /* check for compatible access */
674 if (lftrack.oflag == oflag) {
676 reslt = lseek(fd, 0L, SEEK_SET);
678 panic("open_levelfile_exclusively: lseek failed %d", errno);
679 lftrack.nethack_thinks_it_is_open = TRUE;
682 fd = sopen(name, oflag, SH_DENYRW, FCMASK);
684 lftrack.oflag = oflag;
685 lftrack.nethack_thinks_it_is_open = TRUE;
688 fd = sopen(name, oflag, SH_DENYRW, FCMASK);
690 lftrack.oflag = oflag;
692 lftrack.nethack_thinks_it_is_open = TRUE;
705 lftrack.nethack_thinks_it_is_open = FALSE;
718 if (lftrack.fd == fd) {
719 really_close(); /* close it, but reopen it to hold it */
720 fd = open_levelfile(0, (char *) 0);
721 lftrack.nethack_thinks_it_is_open = FALSE;
726 #else /* !HOLD_LOCKFILE_OPEN */
734 #endif /* ?HOLD_LOCKFILE_OPEN */
736 /* ---------- END LEVEL FILE HANDLING ----------- */
738 /* ---------- BEGIN BONES FILE HANDLING ----------- */
740 /* set up "file" to be file name for retrieving bones, and return a
741 * bonesid to be read/written in the bones file.
744 set_bonesfile_name(file, lev)
752 * "bonD0.nn" = bones for level nn in the main dungeon;
753 * "bonM0.T" = bones for Minetown;
754 * "bonQBar.n" = bones for level n in the Barbarian quest;
756 * "bon3M0.T" = > same as above, but for bones pool #3.
759 * Return value for content validation skips "bon" and the
760 * pool number (if present), making it feasible for the admin
761 * to manually move a bones file from one pool to another by
766 if (sysopt.bones_pools > 1) {
767 unsigned poolnum = min((unsigned) sysopt.bones_pools, 10);
769 poolnum = (unsigned) ubirthday % poolnum; /* 0..9 */
770 Sprintf(eos(file), "%u", poolnum);
773 dptr = eos(file); /* this used to be after the following Sprintf()
774 and the return value was (dptr - 2) */
775 /* when this naming scheme was adopted, 'filecode' was one letter;
776 3.3.0 turned it into a three letter string (via roles[] in role.c);
777 from that version through 3.6.0, 'dptr' pointed past the filecode
778 and the return value of (dptr - 2) was wrong for bones produced
779 in the quest branch, skipping the boneid character 'Q' and the
780 first letter of the role's filecode; bones loading still worked
781 because the bonesid used for validation had the same error */
782 Sprintf(dptr, "%c%s", dungeons[lev->dnum].boneid,
783 In_quest(lev) ? urole.filecode : "0");
784 if ((sptr = Is_special(lev)) != 0)
785 Sprintf(eos(dptr), ".%c", sptr->boneid);
787 Sprintf(eos(dptr), ".%d", lev->dlevel);
794 /* set up temporary file name for writing bones, to avoid another game's
795 * trying to read from an uncompleted bones file. we want an uncontentious
796 * name, so use one in the namespace reserved for this game's level files.
797 * (we are not reading or writing level files while writing bones files, so
798 * the same array may be used instead of copying.)
805 tf = rindex(lock, '.');
816 create_bonesfile(lev, bonesid, errbuf)
826 *bonesid = set_bonesfile_name(bones, lev);
827 file = set_bonestemp_name();
828 file = fqname(file, BONESPREFIX, 0);
830 #if defined(MICRO) || defined(WIN32)
831 /* Use O_TRUNC to force the file to be shortened if it already
832 * exists and is currently longer.
834 fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
837 fd = maccreat(file, BONE_TYPE);
839 fd = creat(file, FCMASK);
842 if (fd < 0 && errbuf) /* failure explanation */
843 Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).", lock,
846 #if defined(VMS) && !defined(SECURE)
848 Re-protect bones file with world:read+write+execute+delete access.
849 umask() doesn't seem very reliable; also, vaxcrtl won't let us set
850 delete access without write access, which is what's really wanted.
851 Can't simply create it with the desired protection because creat
852 ANDs the mask with the user's default protection, which usually
853 denies some or all access to world.
855 (void) chmod(file, FCMASK | 007); /* allow other users full access */
856 #endif /* VMS && !SECURE */
862 /* remove partial bonesfile in process of creation */
866 const char *tempname;
868 tempname = set_bonestemp_name();
869 tempname = fqname(tempname, BONESPREFIX, 0);
870 (void) unlink(tempname);
874 /* move completed bones file to proper name */
876 commit_bonesfile(lev)
879 const char *fq_bones, *tempname;
882 (void) set_bonesfile_name(bones, lev);
883 fq_bones = fqname(bones, BONESPREFIX, 0);
884 tempname = set_bonestemp_name();
885 tempname = fqname(tempname, BONESPREFIX, 1);
887 #if (defined(SYSV) && !defined(SVR4)) || defined(GENIX)
888 /* old SYSVs don't have rename. Some SVR3's may, but since they
889 * also have link/unlink, it doesn't matter. :-)
891 (void) unlink(fq_bones);
892 ret = link(tempname, fq_bones);
893 ret += unlink(tempname);
895 ret = rename(tempname, fq_bones);
897 if (wizard && ret != 0)
898 pline("couldn't rename %s to %s.", tempname, fq_bones);
902 open_bonesfile(lev, bonesid)
906 const char *fq_bones;
909 *bonesid = set_bonesfile_name(bones, lev);
910 fq_bones = fqname(bones, BONESPREFIX, 0);
911 nh_uncompress(fq_bones); /* no effect if nonexistent */
913 fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE);
915 fd = open(fq_bones, O_RDONLY | O_BINARY, 0);
921 delete_bonesfile(lev)
924 (void) set_bonesfile_name(bones, lev);
925 return !(unlink(fqname(bones, BONESPREFIX, 0)) < 0);
928 /* assume we're compressing the recently read or created bonesfile, so the
929 * file name is already set properly */
933 nh_compress(fqname(bones, BONESPREFIX, 0));
936 /* ---------- END BONES FILE HANDLING ----------- */
938 /* ---------- BEGIN SAVE FILE HANDLING ----------- */
940 /* set savefile name in OS-dependent manner from pre-existing plname,
941 * avoiding troublesome characters */
943 set_savefile_name(regularize_it)
944 boolean regularize_it;
947 Sprintf(SAVEF, "[.save]%d%s", getuid(), plname);
949 regularize(SAVEF + 7);
953 Strcpy(SAVEF, SAVEP);
955 strncat(SAVEF, bbs_id, PATHLEN);
958 int i = strlen(SAVEP);
960 /* plname has to share space with SAVEP and ".sav" */
961 (void) strncat(SAVEF, plname, FILENAME - i - 4);
963 (void) strncat(SAVEF, plname, 8);
966 regularize(SAVEF + i);
968 Strcat(SAVEF, SAVE_EXTENSION);
972 static const char okchars[] =
973 "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
974 const char *legal = okchars;
975 char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ];
977 /* Obtain the name of the logged on user and incorporate
978 * it into the name. */
979 Sprintf(fnamebuf, "%s", plname);
981 ++legal; /* skip '*' wildcard character */
982 (void) fname_encode(legal, '%', fnamebuf, encodedfnamebuf, BUFSZ);
983 Sprintf(SAVEF, "%s%s", encodedfnamebuf, SAVE_EXTENSION);
985 #else /* not VMS or MICRO or WIN32 */
986 Sprintf(SAVEF, "save/%d%s", (int) getuid(), plname);
988 regularize(SAVEF + 5); /* avoid . or / in name */
996 save_savefile_name(fd)
999 _pragma_ignore(-Wunused-result)
1000 (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF));
1006 /* change pre-existing savefile name to indicate an error savefile */
1008 set_error_savefile()
1012 char *semi_colon = rindex(SAVEF, ';');
1017 Strcat(SAVEF, ".e;1");
1020 Strcat(SAVEF, "-e");
1022 Strcat(SAVEF, ".e");
1028 /* create save file, overwriting one if it already exists */
1032 const char *fq_save;
1035 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1036 #if defined(MICRO) || defined(WIN32)
1037 fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK);
1040 fd = maccreat(fq_save, SAVE_TYPE);
1042 fd = creat(fq_save, FCMASK);
1044 #if defined(VMS) && !defined(SECURE)
1046 Make sure the save file is owned by the current process. That's
1047 the default for non-privileged users, but for priv'd users the
1048 file will be owned by the directory's owner instead of the user.
1051 (void) chown(fq_save, getuid(), getgid());
1052 #define getuid() vms_getuid()
1053 #endif /* VMS && !SECURE */
1059 /* open savefile for reading */
1063 const char *fq_save;
1066 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1068 fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE);
1070 fd = open(fq_save, O_RDONLY | O_BINARY, 0);
1075 /* delete savefile */
1079 (void) unlink(fqname(SAVEF, SAVEPREFIX, 0));
1080 return 0; /* for restore_saved_game() (ex-xxxmain.c) test */
1083 /* try to open up a save file and prepare to restore it */
1085 restore_saved_game()
1087 const char *fq_save;
1091 set_savefile_name(TRUE);
1093 if (!saveDiskPrompt(1))
1095 #endif /* MFLOPPY */
1096 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1098 nh_uncompress(fq_save);
1099 if ((fd = open_savefile()) < 0)
1102 if (validate(fd, fq_save) != 0) {
1103 (void) nhclose(fd), fd = -1;
1104 (void) delete_savefile();
1109 #if defined(SELECTSAVED)
1111 plname_from_file(filename)
1112 const char *filename;
1117 Strcpy(SAVEF, filename);
1118 #ifdef COMPRESS_EXTENSION
1119 SAVEF[strlen(SAVEF) - strlen(COMPRESS_EXTENSION)] = '\0';
1121 nh_uncompress(SAVEF);
1122 if ((fd = open_savefile()) >= 0) {
1123 if (validate(fd, filename) == 0) {
1124 char tplname[PL_NSIZ];
1125 get_plname_from_file(fd, tplname);
1126 result = dupstr(tplname);
1134 /* --------- obsolete - used to be ifndef STORE_PLNAME_IN_FILE ----*/
1135 #if defined(UNIX) && defined(QT_GRAPHICS)
1136 /* Name not stored in save file, so we have to extract it from
1137 the filename, which loses information
1138 (eg. "/", "_", and "." characters are lost. */
1141 char name[64]; /* more than PL_NSIZ */
1142 #ifdef COMPRESS_EXTENSION
1143 #define EXTSTR COMPRESS_EXTENSION
1148 if ( sscanf( filename, "%*[^/]/%d%63[^.]" EXTSTR, &uid, name ) == 2 ) {
1150 /* "_" most likely means " ", which certainly looks nicer */
1151 for (k=0; name[k]; k++)
1152 if ( name[k] == '_' )
1154 return dupstr(name);
1156 #endif /* UNIX && QT_GRAPHICS */
1160 /* --------- end of obsolete code ----*/
1161 #endif /* 0 - WAS STORE_PLNAME_IN_FILE*/
1163 #endif /* defined(SELECTSAVED) */
1168 #if defined(SELECTSAVED)
1174 const char *fq_save;
1175 const char *fq_new_save;
1176 const char *fq_old_save;
1180 Strcpy(plname, "*");
1181 set_savefile_name(FALSE);
1182 #if defined(ZLIB_COMP)
1183 Strcat(SAVEF, COMPRESS_EXTENSION);
1185 fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1188 foundfile = foundfile_buffer();
1189 if (findfirst((char *) fq_save)) {
1192 } while (findnext());
1196 files = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1197 (void) memset((genericptr_t) files, 0, (n + 1) * sizeof(char *));
1198 if (findfirst((char *) fq_save)) {
1201 files[i++] = strdup(foundfile);
1202 } while (findnext());
1207 result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1208 (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1209 for(i = 0; i < n; i++) {
1211 r = plname_from_file(files[i]);
1215 /* rename file if it is not named as expected */
1217 set_savefile_name(FALSE);
1218 fq_new_save = fqname(SAVEF, SAVEPREFIX, 0);
1219 fq_old_save = fqname(files[i], SAVEPREFIX, 1);
1221 if(strcmp(fq_old_save, fq_new_save) != 0 &&
1222 !file_exists(fq_new_save))
1223 rename(fq_old_save, fq_new_save);
1230 free_saved_games(files);
1234 #if defined(UNIX) && defined(QT_GRAPHICS)
1235 /* posixly correct version */
1236 int myuid = getuid();
1239 if ((dir = opendir(fqname("save", SAVEPREFIX, 0)))) {
1240 for (n = 0; readdir(dir); n++)
1246 if (!(dir = opendir(fqname("save", SAVEPREFIX, 0))))
1248 result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1249 (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1250 for (i = 0, j = 0; i < n; i++) {
1252 char name[64]; /* more than PL_NSIZ */
1253 struct dirent *entry = readdir(dir);
1257 if (sscanf(entry->d_name, "%d%63s", &uid, name) == 2) {
1259 char filename[BUFSZ];
1262 Sprintf(filename, "save/%d%s", uid, name);
1263 r = plname_from_file(filename);
1274 Strcpy(plname, "*");
1275 set_savefile_name(FALSE);
1276 j = vms_get_saved_games(SAVEF, &result);
1281 qsort(result, j, sizeof (char *), strcmp_wrap);
1284 } else if (result) { /* could happen if save files are obsolete */
1285 free_saved_games(result);
1287 #endif /* SELECTSAVED */
1292 free_saved_games(saved)
1299 free((genericptr_t) saved[i++]);
1300 free((genericptr_t) saved);
1304 /* ---------- END SAVE FILE HANDLING ----------- */
1306 /* ---------- BEGIN FILE COMPRESSION HANDLING ----------- */
1311 redirect(filename, mode, stream, uncomp)
1312 const char *filename, *mode;
1316 if (freopen(filename, mode, stream) == (FILE *) 0) {
1317 (void) fprintf(stderr, "freopen of %s for %scompress failed\n",
1318 filename, uncomp ? "un" : "");
1319 nh_terminate(EXIT_FAILURE);
1324 * using system() is simpler, but opens up security holes and causes
1325 * problems on at least Interactive UNIX 3.0.1 (SVR3.2), where any
1326 * setuid is renounced by /bin/sh, so the files cannot be accessed.
1328 * cf. child() in unixunix.c.
1331 docompress_file(filename, uncomp)
1332 const char *filename;
1337 const char *args[10];
1338 #ifdef COMPRESS_OPTIONS
1344 boolean istty = WINDOWPORT("tty");
1347 Strcpy(cfn, filename);
1348 #ifdef COMPRESS_EXTENSION
1349 Strcat(cfn, COMPRESS_EXTENSION);
1351 /* when compressing, we know the file exists */
1353 if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0)
1360 args[++i] = "-d"; /* uncompress */
1361 #ifdef COMPRESS_OPTIONS
1363 /* we can't guarantee there's only one additional option, sigh */
1365 boolean inword = FALSE;
1367 Strcpy(opts, COMPRESS_OPTIONS);
1370 if ((*opt == ' ') || (*opt == '\t')) {
1375 } else if (!inword) {
1383 args[++i] = (char *) 0;
1386 /* If we don't do this and we are right after a y/n question *and*
1387 * there is an error message from the compression, the 'y' or 'n' can
1388 * end up being displayed after the error message.
1394 if (f == 0) { /* child */
1396 /* any error messages from the compression must come out after
1397 * the first line, because the more() to let the user read
1398 * them will have to clear the first line. This should be
1399 * invisible if there are no error messages.
1404 /* run compressor without privileges, in case other programs
1405 * have surprises along the line of gzip once taking filenames
1408 /* assume all compressors will compress stdin to stdout
1409 * without explicit filenames. this is true of at least
1410 * compress and gzip, those mentioned in config.h.
1413 redirect(cfn, RDBMODE, stdin, uncomp);
1414 redirect(filename, WRBMODE, stdout, uncomp);
1416 redirect(filename, RDBMODE, stdin, uncomp);
1417 redirect(cfn, WRBMODE, stdout, uncomp);
1419 _pragma_ignore(-Wunused-result)
1420 (void) setgid(getgid());
1421 (void) setuid(getuid());
1423 (void) execv(args[0], (char *const *) args);
1425 (void) fprintf(stderr, "Exec to %scompress %s failed.\n",
1426 uncomp ? "un" : "", filename);
1427 nh_terminate(EXIT_FAILURE);
1428 } else if (f == -1) {
1430 pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename);
1434 (void) signal(SIGINT, SIG_IGN);
1435 (void) signal(SIGQUIT, SIG_IGN);
1436 (void) wait((int *) &i);
1437 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
1439 (void) signal(SIGQUIT, SIG_DFL);
1441 /* I don't think we can really cope with external compression
1442 * without signals, so we'll declare that compress failed and
1443 * go on. (We could do a better job by forcing off external
1444 * compression if there are no signals, but we want this for
1445 * testing with FailSafeC
1450 /* (un)compress succeeded: remove file left behind */
1454 (void) unlink(filename);
1456 /* (un)compress failed; remove the new, bad file */
1458 raw_printf("Unable to uncompress %s", filename);
1459 (void) unlink(filename);
1461 /* no message needed for compress case; life will go on */
1465 /* Give them a chance to read any error messages from the
1466 * compression--these would go to stdout or stderr and would get
1467 * overwritten only in tty mode. It's still ugly, since the
1468 * messages are being written on top of the screen, but at least
1469 * the user can read them.
1471 if (istty && iflags.window_inited) {
1472 clear_nhwindow(WIN_MESSAGE);
1474 /* No way to know if this is feasible */
1480 #endif /* COMPRESS */
1482 #if defined(COMPRESS) || defined(ZLIB_COMP)
1483 #define UNUSED_if_not_COMPRESS /*empty*/
1485 #define UNUSED_if_not_COMPRESS UNUSED
1490 nh_compress(filename)
1491 const char *filename UNUSED_if_not_COMPRESS;
1493 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1494 #ifdef PRAGMA_UNUSED
1495 #pragma unused(filename)
1498 docompress_file(filename, FALSE);
1502 /* uncompress file if it exists */
1504 nh_uncompress(filename)
1505 const char *filename UNUSED_if_not_COMPRESS;
1507 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1508 #ifdef PRAGMA_UNUSED
1509 #pragma unused(filename)
1512 docompress_file(filename, TRUE);
1516 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
1518 make_compressed_name(filename, cfn)
1519 const char *filename;
1522 #ifndef SHORT_FILENAMES
1523 /* Assume free-form filename with no 8.3 restrictions */
1524 strcpy(cfn, filename);
1525 strcat(cfn, COMPRESS_EXTENSION);
1528 #ifdef SAVE_EXTENSION
1529 char *bp = (char *) 0;
1531 strcpy(cfn, filename);
1532 if ((bp = strstri(cfn, SAVE_EXTENSION))) {
1533 strsubst(bp, SAVE_EXTENSION, ".saz");
1536 /* find last occurrence of bon */
1538 while (bp-- > cfn) {
1539 if (strstri(bp, "bon")) {
1540 strsubst(bp, "bon", "boz");
1545 #endif /* SAVE_EXTENSION */
1547 #endif /* SHORT_FILENAMES */
1551 docompress_file(filename, uncomp)
1552 const char *filename;
1555 gzFile compressedfile;
1556 FILE *uncompressedfile;
1561 if (!make_compressed_name(filename, cfn))
1565 /* Open the input and output files */
1566 /* Note that gzopen takes "wb" as its mode, even on systems where
1567 fopen takes "r" and "w" */
1569 uncompressedfile = fopen(filename, RDBMODE);
1570 if (!uncompressedfile) {
1571 pline("Error in zlib docompress_file %s", filename);
1574 compressedfile = gzopen(cfn, "wb");
1575 if (compressedfile == NULL) {
1577 pline("zlib failed to allocate memory");
1579 panic("Error in docompress_file %d", errno);
1581 fclose(uncompressedfile);
1585 /* Copy from the uncompressed to the compressed file */
1588 len = fread(buf, 1, sizeof(buf), uncompressedfile);
1589 if (ferror(uncompressedfile)) {
1590 pline("Failure reading uncompressed file");
1591 pline("Can't compress %s.", filename);
1592 fclose(uncompressedfile);
1593 gzclose(compressedfile);
1598 break; /* End of file */
1600 len2 = gzwrite(compressedfile, buf, len);
1602 pline("Failure writing compressed file");
1603 pline("Can't compress %s.", filename);
1604 fclose(uncompressedfile);
1605 gzclose(compressedfile);
1611 fclose(uncompressedfile);
1612 gzclose(compressedfile);
1614 /* Delete the file left behind */
1616 (void) unlink(filename);
1618 } else { /* uncomp */
1620 /* Open the input and output files */
1621 /* Note that gzopen takes "rb" as its mode, even on systems where
1622 fopen takes "r" and "w" */
1624 compressedfile = gzopen(cfn, "rb");
1625 if (compressedfile == NULL) {
1627 pline("zlib failed to allocate memory");
1628 } else if (errno != ENOENT) {
1629 panic("Error in zlib docompress_file %s, %d", filename,
1634 uncompressedfile = fopen(filename, WRBMODE);
1635 if (!uncompressedfile) {
1636 pline("Error in zlib docompress file uncompress %s", filename);
1637 gzclose(compressedfile);
1641 /* Copy from the compressed to the uncompressed file */
1644 len = gzread(compressedfile, buf, sizeof(buf));
1645 if (len == (unsigned) -1) {
1646 pline("Failure reading compressed file");
1647 pline("Can't uncompress %s.", filename);
1648 fclose(uncompressedfile);
1649 gzclose(compressedfile);
1650 (void) unlink(filename);
1654 break; /* End of file */
1656 fwrite(buf, 1, len, uncompressedfile);
1657 if (ferror(uncompressedfile)) {
1658 pline("Failure writing uncompressed file");
1659 pline("Can't uncompress %s.", filename);
1660 fclose(uncompressedfile);
1661 gzclose(compressedfile);
1662 (void) unlink(filename);
1667 fclose(uncompressedfile);
1668 gzclose(compressedfile);
1670 /* Delete the file left behind */
1674 #endif /* RLC 09 Mar 1999: End ZLIB patch */
1676 /* ---------- END FILE COMPRESSION HANDLING ----------- */
1678 /* ---------- BEGIN FILE LOCKING HANDLING ----------- */
1680 static int nesting = 0;
1682 #if defined(NO_FILE_LINKS) || defined(USE_FCNTL) /* implies UNIX */
1683 static int lockfd = -1; /* for lock_file() to pass to unlock_file() */
1686 struct flock sflock; /* for unlocking, same as above */
1689 #define HUP if (!program_state.done_hup)
1693 make_lockname(filename, lockname)
1694 const char *filename;
1697 #if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \
1699 #ifdef NO_FILE_LINKS
1700 Strcpy(lockname, LOCKDIR);
1701 Strcat(lockname, "/");
1702 Strcat(lockname, filename);
1704 Strcpy(lockname, filename);
1708 char *semi_colon = rindex(lockname, ';');
1712 Strcat(lockname, ".lock;1");
1714 Strcat(lockname, "_lock");
1717 #else /* !(UNIX || VMS || AMIGA || WIN32 || MSDOS) */
1718 #ifdef PRAGMA_UNUSED
1719 #pragma unused(filename)
1725 #endif /* !USE_FCNTL */
1729 lock_file(filename, whichprefix, retryct)
1730 const char *filename;
1734 #if defined(PRAGMA_UNUSED) && !(defined(UNIX) || defined(VMS)) \
1735 && !(defined(AMIGA) || defined(WIN32) || defined(MSDOS))
1736 #pragma unused(retryct)
1739 char locknambuf[BUFSZ];
1740 const char *lockname;
1745 impossible("TRIED TO NEST LOCKS");
1750 lockname = make_lockname(filename, locknambuf);
1751 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1752 lockname = fqname(lockname, LOCKPREFIX, 2);
1755 filename = fqname(filename, whichprefix, 0);
1757 lockfd = open(filename, O_RDWR);
1759 HUP raw_printf("Cannot open file %s. Is NetHack installed correctly?",
1764 sflock.l_type = F_WRLCK;
1765 sflock.l_whence = SEEK_SET;
1770 #if defined(UNIX) || defined(VMS)
1772 while (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1774 #ifdef NO_FILE_LINKS
1775 while ((lockfd = open(lockname, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1) {
1777 while (link(filename, lockname) == -1) {
1784 "Waiting for release of fcntl lock on %s. (%d retries left.)",
1788 HUP(void) raw_print("I give up. Sorry.");
1789 HUP raw_printf("Some other process has an unnatural grip on %s.",
1795 int errnosv = errno;
1797 switch (errnosv) { /* George Barbanis */
1801 "Waiting for access to %s. (%d retries left).", filename,
1803 #if defined(SYSV) || defined(ULTRIX) || defined(VMS)
1808 HUP(void) raw_print("I give up. Sorry.");
1809 HUP raw_printf("Perhaps there is an old %s around?",
1817 HUP raw_printf("Can't find file %s to lock!", filename);
1821 HUP raw_printf("No write permission to lock %s!", filename);
1824 #ifdef VMS /* c__translate(vmsfiles.c) */
1826 /* could be misleading, but usually right */
1827 HUP raw_printf("Can't lock %s due to directory protection.",
1833 /* take a wild guess at the underlying cause */
1834 HUP perror(lockname);
1835 HUP raw_printf("Cannot lock %s.", filename);
1837 "(Perhaps you are running NetHack from inside the distribution package?).");
1841 HUP perror(lockname);
1842 HUP raw_printf("Cannot lock %s for unknown reason (%d).",
1847 #endif /* USE_FCNTL */
1849 #endif /* UNIX || VMS */
1851 #if (defined(AMIGA) || defined(WIN32) || defined(MSDOS)) \
1852 && !defined(USE_FCNTL)
1854 #define OPENFAILURE(fd) (!fd)
1857 #define OPENFAILURE(fd) (fd < 0)
1860 while (--retryct && OPENFAILURE(lockptr)) {
1861 #if defined(WIN32) && !defined(WIN_CE)
1862 lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE);
1864 (void) DeleteFile(lockname); /* in case dead process was here first */
1866 lockptr = Open(lockname, MODE_NEWFILE);
1868 lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE);
1871 if (OPENFAILURE(lockptr)) {
1872 raw_printf("Waiting for access to %s. (%d retries left).",
1878 raw_printf("I give up. Sorry.");
1882 #endif /* AMIGA || WIN32 || MSDOS */
1886 #ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */
1890 #define unlink(foo) vms_unlink(foo)
1893 /* unlock file, which must be currently locked by lock_file */
1895 unlock_file(filename)
1896 const char *filename;
1899 char locknambuf[BUFSZ];
1900 const char *lockname;
1905 sflock.l_type = F_UNLCK;
1907 if (fcntl(lockfd, F_SETLK, &sflock) == -1)
1908 HUP raw_printf("Can't remove fcntl lock on %s.", filename);
1909 (void) close(lockfd), lockfd = -1;
1912 lockname = make_lockname(filename, locknambuf);
1913 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1914 lockname = fqname(lockname, LOCKPREFIX, 2);
1917 #if defined(UNIX) || defined(VMS)
1918 if (unlink(lockname) < 0)
1919 HUP raw_printf("Can't unlink %s.", lockname);
1920 #ifdef NO_FILE_LINKS
1921 (void) nhclose(lockfd), lockfd = -1;
1924 #endif /* UNIX || VMS */
1926 #if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1929 DeleteFile(lockname);
1931 #endif /* AMIGA || WIN32 || MSDOS */
1932 #endif /* USE_FCNTL */
1938 /* ---------- END FILE LOCKING HANDLING ----------- */
1940 /* ---------- BEGIN CONFIG FILE HANDLING ----------- */
1942 const char *default_configfile =
1950 #if defined(MAC) || defined(__BEOS__)
1953 #if defined(MSDOS) || defined(WIN32)
1961 /* used for messaging */
1962 char configfile[BUFSZ];
1965 /* conflict with speed-dial under windows
1966 * for XXX.cnf file so support of NetHack.cnf
1967 * is for backward compatibility only.
1968 * Preferred name (and first tried) is now defaults.nh but
1969 * the game will try the old name if there
1970 * is no defaults.nh.
1972 const char *backward_compat_configfile = "nethack.cnf";
1975 /* remember the name of the file we're accessing;
1976 if may be used in option reject messages */
1978 set_configfile_name(fname)
1981 (void) strncpy(configfile, fname, sizeof configfile - 1);
1982 configfile[sizeof configfile - 1] = '\0';
1986 #define fopenp fopen
1990 fopen_config_file(filename, src)
1991 const char *filename;
1995 #if defined(UNIX) || defined(VMS)
1996 char tmp_config[BUFSZ];
2000 if (src == SET_IN_SYS) {
2001 /* SYSCF_FILE; if we can't open it, caller will bail */
2002 if (filename && *filename) {
2003 set_configfile_name(fqname(filename, SYSCONFPREFIX, 0));
2004 fp = fopenp(configfile, "r");
2009 /* If src != SET_IN_SYS, "filename" is an environment variable, so it
2010 * should hang around. If set, it is expected to be a full path name
2013 if (filename && *filename) {
2014 set_configfile_name(filename);
2016 if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */
2017 /* nasty sneaky attempt to read file through
2018 * NetHack's setuid permissions -- this is the only
2019 * place a file name may be wholly under the player's
2020 * control (but SYSCF_FILE is not under the player's
2021 * control so it's OK).
2023 raw_printf("Access to %s denied (%d).", configfile, errno);
2025 /* fall through to standard names */
2028 if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
2030 #if defined(UNIX) || defined(VMS)
2032 /* access() above probably caught most problems for UNIX */
2033 raw_printf("Couldn't open requested config file %s (%d).",
2039 /* fall through to standard names */
2041 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2042 set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0));
2043 if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
2045 } else if (strcmp(default_configfile, configfile)) {
2046 set_configfile_name(default_configfile);
2047 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2051 set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0));
2052 if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
2054 } else if (strcmp(backward_compat_configfile, configfile)) {
2055 set_configfile_name(backward_compat_configfile);
2056 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2061 /* constructed full path names don't need fqname() */
2063 /* no punctuation, so might be a logical name */
2064 set_configfile_name("nethackini");
2065 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2067 set_configfile_name("sys$login:nethack.ini");
2068 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2071 envp = nh_getenv("HOME");
2072 if (!envp || !*envp)
2073 Strcpy(tmp_config, "NetHack.cnf");
2075 Sprintf(tmp_config, "%s%s%s", envp,
2076 !index(":]>/", envp[strlen(envp) - 1]) ? "/" : "",
2078 set_configfile_name(tmp_config);
2079 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2081 #else /* should be only UNIX left */
2082 envp = nh_getenv("HOME");
2083 #if 1 /*JP*//*".jnethackrc"
\82ð
\97D
\90æ
\82µ
\82Ä
\93Ç
\82Ý
\8d\9e\82Ý*/
2085 Strcpy(tmp_config, ".jnethackrc");
2087 Sprintf(tmp_config, "%s/%s", envp, ".jnethackrc");
2089 set_configfile_name(tmp_config);
2090 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2094 Strcpy(tmp_config, ".nethackrc");
2096 Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
2098 set_configfile_name(tmp_config);
2099 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2101 #if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX */
2102 /* try an alternative */
2104 /* OSX-style configuration settings */
2105 Sprintf(tmp_config, "%s/%s", envp,
2106 "Library/Preferences/NetHack Defaults");
2107 set_configfile_name(tmp_config);
2108 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2110 /* may be easier for user to edit if filename has '.txt' suffix */
2111 Sprintf(tmp_config, "%s/%s", envp,
2112 "Library/Preferences/NetHack Defaults.txt");
2113 set_configfile_name(tmp_config);
2114 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2117 #endif /*__APPLE__*/
2118 if (errno != ENOENT) {
2119 const char *details;
2121 /* e.g., problems when setuid NetHack can't search home
2122 directory restricted to user */
2123 #if defined(NHSTDC) && !defined(NOTSTDC)
2124 if ((details = strerror(errno)) == 0)
2127 raw_printf("Couldn't open default config file %s %s(%d).",
2128 configfile, details, errno);
2131 #endif /* !VMS => Unix */
2132 #endif /* !(MICRO || MAC || __BEOS__ || WIN32) */
2137 * Retrieve a list of integers from buf into a uchar array.
2139 * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
2140 * location is unchanged. Callers must handle zeros if modlist is FALSE.
2143 get_uchars(bufp, list, modlist, size, name)
2144 char *bufp; /* current pointer */
2145 uchar *list; /* return list */
2146 boolean modlist; /* TRUE: list is being modified in place */
2147 int size; /* return list size */
2148 const char *name; /* name of option for error message */
2150 unsigned int num = 0;
2152 boolean havenum = FALSE;
2161 /* if modifying in place, don't insert zeros */
2162 if (num || !modlist)
2168 if (count == size || !*bufp)
2184 num = num * 10 + (*bufp - '0');
2194 raw_printf("Syntax error in %s", name);
2202 #ifdef NOCWD_ASSUMPTIONS
2204 adjust_prefix(bufp, prefixid)
2213 if (fqn_prefix_locked[prefixid])
2216 /* Backward compatibility, ignore trailing ;n */
2217 if ((ptr = index(bufp, ';')) != 0)
2219 if (strlen(bufp) > 0) {
2220 fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
2221 Strcpy(fqn_prefix[prefixid], bufp);
2222 append_slash(fqn_prefix[prefixid]);
2227 /* Choose at random one of the sep separated parts from str. Mangles str. */
2229 choose_random_part(str,sep)
2248 while ((csep > 0) && *str) {
2257 while (*str && *str != sep) {
2269 free_config_sections()
2271 if (config_section_chosen) {
2272 free(config_section_chosen);
2273 config_section_chosen = NULL;
2275 if (config_section_current) {
2276 free(config_section_current);
2277 config_section_current = NULL;
2282 is_config_section(str)
2285 const char *a = rindex(str, ']');
2287 return (a && *str == '[' && *(a+1) == '\0' && (int)(a - str) > 0);
2291 handle_config_section(buf)
2294 if (is_config_section(buf)) {
2296 if (config_section_current) {
2297 free(config_section_current);
2299 config_section_current = dupstr(&buf[1]);
2300 send = rindex(config_section_current, ']');
2302 debugpline1("set config section: '%s'", config_section_current);
2306 if (config_section_current) {
2307 if (!config_section_chosen)
2309 if (strcmp(config_section_current, config_section_chosen))
2315 #define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
2317 /* find the '=' or ':' */
2324 bufp = index(buf, '=');
2325 altp = index(buf, ':');
2326 if (!bufp || (altp && altp < bufp))
2333 parse_config_line(origbuf)
2336 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
2337 static boolean ramdisk_specified = FALSE;
2340 int n, src = iflags.parse_config_file_src;
2342 char *bufp, buf[4 * BUFSZ];
2343 uchar translate[MAXPCHARS];
2345 boolean retval = TRUE;
2347 while (*origbuf == ' ' || *origbuf == '\t') /* skip leading whitespace */
2348 ++origbuf; /* (caller probably already did this) */
2349 (void) strncpy(buf, origbuf, sizeof buf - 1);
2350 buf[sizeof buf - 1] = '\0'; /* strncpy not guaranteed to NUL terminate */
2351 /* convert any tab to space, condense consecutive spaces into one,
2352 remove leading and trailing spaces (exception: if there is nothing
2353 but spaces, one of them will be kept even though it leads/trails) */
2356 /* find the '=' or ':' */
2357 bufp = find_optparam(buf);
2359 config_error_add("Not a config statement, missing '='");
2362 /* skip past '=', then space between it and value, if any */
2367 /* Go through possible variables */
2368 /* some of these (at least LEVELS and SAVE) should now set the
2369 * appropriate fqn_prefix[] rather than specialized variables
2371 if (match_varname(buf, "OPTIONS", 4)) {
2372 /* hack: un-mungspaces to allow consecutive spaces in
2373 general options until we verify that this is unnecessary;
2374 '=' or ':' is guaranteed to be present */
2375 bufp = find_optparam(origbuf);
2376 ++bufp; /* skip '='; parseoptions() handles spaces */
2378 if (!parseoptions(bufp, TRUE, TRUE))
2380 } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
2381 add_autopickup_exception(bufp);
2382 } else if (match_varname(buf, "BINDINGS", 4)) {
2383 if (!parsebindings(bufp))
2385 } else if (match_varname(buf, "AUTOCOMPLETE", 5)) {
2386 parseautocomplete(bufp, TRUE);
2387 } else if (match_varname(buf, "MSGTYPE", 7)) {
2388 if (!msgtype_parse_add(bufp))
2390 #ifdef NOCWD_ASSUMPTIONS
2391 } else if (match_varname(buf, "HACKDIR", 4)) {
2392 adjust_prefix(bufp, HACKPREFIX);
2393 } else if (match_varname(buf, "LEVELDIR", 4)
2394 || match_varname(buf, "LEVELS", 4)) {
2395 adjust_prefix(bufp, LEVELPREFIX);
2396 } else if (match_varname(buf, "SAVEDIR", 4)) {
2397 adjust_prefix(bufp, SAVEPREFIX);
2398 } else if (match_varname(buf, "BONESDIR", 5)) {
2399 adjust_prefix(bufp, BONESPREFIX);
2400 } else if (match_varname(buf, "DATADIR", 4)) {
2401 adjust_prefix(bufp, DATAPREFIX);
2402 } else if (match_varname(buf, "SCOREDIR", 4)) {
2403 adjust_prefix(bufp, SCOREPREFIX);
2404 } else if (match_varname(buf, "LOCKDIR", 4)) {
2405 adjust_prefix(bufp, LOCKPREFIX);
2406 } else if (match_varname(buf, "CONFIGDIR", 4)) {
2407 adjust_prefix(bufp, CONFIGPREFIX);
2408 } else if (match_varname(buf, "TROUBLEDIR", 4)) {
2409 adjust_prefix(bufp, TROUBLEPREFIX);
2410 #else /*NOCWD_ASSUMPTIONS*/
2412 } else if (match_varname(buf, "HACKDIR", 4)) {
2413 (void) strncpy(hackdir, bufp, PATHLEN - 1);
2415 } else if (match_varname(buf, "RAMDISK", 3)) {
2416 /* The following ifdef is NOT in the wrong
2417 * place. For now, we accept and silently
2420 if (strlen(bufp) >= PATHLEN)
2421 bufp[PATHLEN - 1] = '\0';
2422 Strcpy(levels, bufp);
2423 ramdisk = (strcmp(permbones, levels) != 0);
2424 ramdisk_specified = TRUE;
2427 } else if (match_varname(buf, "LEVELS", 4)) {
2428 if (strlen(bufp) >= PATHLEN)
2429 bufp[PATHLEN - 1] = '\0';
2430 Strcpy(permbones, bufp);
2431 if (!ramdisk_specified || !*levels)
2432 Strcpy(levels, bufp);
2433 ramdisk = (strcmp(permbones, levels) != 0);
2434 } else if (match_varname(buf, "SAVE", 4)) {
2436 extern int saveprompt;
2440 if ((ptr = index(bufp, ';')) != 0) {
2443 if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') {
2448 #if defined(SYSFLAGS) && defined(MFLOPPY)
2450 saveprompt = sysflags.asksavedisk;
2453 (void) strncpy(SAVEP, bufp, SAVESIZE - 1);
2454 append_slash(SAVEP);
2456 #endif /*NOCWD_ASSUMPTIONS*/
2458 } else if (match_varname(buf, "NAME", 4)) {
2459 (void) strncpy(plname, bufp, PL_NSIZ - 1);
2460 } else if (match_varname(buf, "ROLE", 4)
2461 || match_varname(buf, "CHARACTER", 4)) {
2462 if ((len = str2role(bufp)) >= 0)
2463 flags.initrole = len;
2464 } else if (match_varname(buf, "DOGNAME", 3)) {
2465 (void) strncpy(dogname, bufp, PL_PSIZ - 1);
2466 } else if (match_varname(buf, "CATNAME", 3)) {
2467 (void) strncpy(catname, bufp, PL_PSIZ - 1);
2470 } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) {
2472 free((genericptr_t) sysopt.wizards);
2473 sysopt.wizards = dupstr(bufp);
2474 if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
2475 /* pre-format WIZARDS list now; it's displayed during a panic
2476 and since that panic might be due to running out of memory,
2477 we don't want to risk attempting to allocate any memory then */
2478 if (sysopt.fmtd_wizard_list)
2479 free((genericptr_t) sysopt.fmtd_wizard_list);
2480 sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
2482 } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) {
2483 if (sysopt.shellers)
2484 free((genericptr_t) sysopt.shellers);
2485 sysopt.shellers = dupstr(bufp);
2486 } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) {
2487 if (sysopt.explorers)
2488 free((genericptr_t) sysopt.explorers);
2489 sysopt.explorers = dupstr(bufp);
2490 } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) {
2491 /* if showdebug() has already been called (perhaps we've added
2492 some debugpline() calls to option processing) and has found
2493 a value for getenv("DEBUGFILES"), don't override that */
2494 if (sysopt.env_dbgfl <= 0) {
2495 if (sysopt.debugfiles)
2496 free((genericptr_t) sysopt.debugfiles);
2497 sysopt.debugfiles = dupstr(bufp);
2499 } else if (src == SET_IN_SYS && match_varname(buf, "DUMPLOGFILE", 7)) {
2501 if (sysopt.dumplogfile)
2502 free((genericptr_t) sysopt.dumplogfile);
2503 sysopt.dumplogfile = dupstr(bufp);
2505 } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) {
2506 if (sysopt.genericusers)
2507 free((genericptr_t) sysopt.genericusers);
2508 sysopt.genericusers = dupstr(bufp);
2509 } else if (src == SET_IN_SYS && match_varname(buf, "BONES_POOLS", 10)) {
2510 /* max value of 10 guarantees (N % bones.pools) will be one digit
2511 so we don't lose control of the length of bones file names */
2513 sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10);
2514 /* note: right now bones_pools==0 is the same as bones_pools==1,
2515 but we could change that and make bones_pools==0 become an
2516 indicator to suppress bones usage altogether */
2517 } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
2519 free((genericptr_t) sysopt.support);
2520 sysopt.support = dupstr(bufp);
2521 } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) {
2523 free((genericptr_t) sysopt.recover);
2524 sysopt.recover = dupstr(bufp);
2525 } else if (src == SET_IN_SYS
2526 && match_varname(buf, "CHECK_SAVE_UID", 14)) {
2528 sysopt.check_save_uid = n;
2529 } else if (src == SET_IN_SYS
2530 && match_varname(buf, "CHECK_PLNAME", 12)) {
2532 sysopt.check_plname = n;
2533 } else if (match_varname(buf, "SEDUCE", 6)) {
2534 n = !!atoi(bufp); /* XXX this could be tighter */
2535 /* allow anyone to turn it off, but only sysconf to turn it on*/
2536 if (src != SET_IN_SYS && n != 0) {
2537 config_error_add("Illegal value in SEDUCE");
2541 sysopt_seduce_set(sysopt.seduce);
2542 } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
2544 /* XXX to get more than 25, need to rewrite all lock code */
2545 if (n < 0 || n > 25) {
2546 config_error_add("Illegal value in MAXPLAYERS (maximum is 25).");
2549 sysopt.maxplayers = n;
2550 } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
2553 config_error_add("Illegal value in PERSMAX (minimum is 1).");
2557 } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
2559 if (n != 0 && n != 1) {
2560 config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1).");
2563 sysopt.pers_is_uid = n;
2564 } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
2567 config_error_add("Illegal value in ENTRYMAX (minimum is 10).");
2570 sysopt.entrymax = n;
2571 } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
2574 config_error_add("Illegal value in POINTSMIN (minimum is 1).");
2577 sysopt.pointsmin = n;
2578 } else if (src == SET_IN_SYS
2579 && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
2583 "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
2586 sysopt.tt_oname_maxrank = n;
2588 /* SYSCF PANICTRACE options */
2589 } else if (src == SET_IN_SYS
2590 && match_varname(buf, "PANICTRACE_LIBC", 15)) {
2592 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
2593 if (n < 0 || n > 2) {
2594 config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
2598 sysopt.panictrace_libc = n;
2599 } else if (src == SET_IN_SYS
2600 && match_varname(buf, "PANICTRACE_GDB", 14)) {
2602 #if defined(PANICTRACE)
2603 if (n < 0 || n > 2) {
2604 config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2).");
2608 sysopt.panictrace_gdb = n;
2609 } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
2610 #if defined(PANICTRACE) && !defined(VMS)
2611 if (!file_exists(bufp)) {
2612 config_error_add("File specified in GDBPATH does not exist.");
2617 free((genericptr_t) sysopt.gdbpath);
2618 sysopt.gdbpath = dupstr(bufp);
2619 } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
2620 #if defined(PANICTRACE) && !defined(VMS)
2621 if (!file_exists(bufp)) {
2622 config_error_add("File specified in GREPPATH does not exist.");
2626 if (sysopt.greppath)
2627 free((genericptr_t) sysopt.greppath);
2628 sysopt.greppath = dupstr(bufp);
2629 } else if (src == SET_IN_SYS
2630 && match_varname(buf, "ACCESSIBILITY", 13)) {
2632 if (n < 0 || n > 1) {
2633 config_error_add("Illegal value in ACCESSIBILITY (not 0,1).");
2636 sysopt.accessibility = n;
2638 } else if (src == SET_IN_SYS
2639 && match_varname(buf, "portable_device_paths", 8)) {
2641 if (n < 0 || n > 1) {
2642 config_error_add("Illegal value in portable_device_paths (not 0,1).");
2645 sysopt.portable_device_paths = n;
2649 } else if (match_varname(buf, "BOULDER", 3)) {
2650 (void) get_uchars(bufp, &ov_primary_syms[SYM_BOULDER + SYM_OFF_X],
2651 TRUE, 1, "BOULDER");
2652 } else if (match_varname(buf, "MENUCOLOR", 9)) {
2653 if (!add_menu_coloring(bufp))
2655 } else if (match_varname(buf, "HILITE_STATUS", 6)) {
2656 #ifdef STATUS_HILITES
2657 if (!parse_status_hl1(bufp, TRUE))
2660 } else if (match_varname(buf, "WARNINGS", 5)) {
2661 (void) get_uchars(bufp, translate, FALSE, WARNCOUNT,
2663 assign_warnings(translate);
2664 } else if (match_varname(buf, "ROGUESYMBOLS", 4)) {
2665 if (!parsesymbols(bufp, ROGUESET)) {
2666 config_error_add("Error in ROGUESYMBOLS definition '%s'", bufp);
2669 switch_symbols(TRUE);
2670 } else if (match_varname(buf, "SYMBOLS", 4)) {
2671 if (!parsesymbols(bufp, PRIMARY)) {
2672 config_error_add("Error in SYMBOLS definition '%s'", bufp);
2675 switch_symbols(TRUE);
2676 } else if (match_varname(buf, "WIZKIT", 6)) {
2677 (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
2679 } else if (match_varname(buf, "FONT", 4)) {
2682 if (t = strchr(buf + 5, ':')) {
2684 amii_set_text_font(buf + 5, atoi(t + 1));
2687 } else if (match_varname(buf, "PATH", 4)) {
2688 (void) strncpy(PATH, bufp, PATHLEN - 1);
2689 } else if (match_varname(buf, "DEPTH", 5)) {
2690 extern int amii_numcolors;
2691 int val = atoi(bufp);
2693 amii_numcolors = 1L << min(DEPTH, val);
2695 } else if (match_varname(buf, "DRIPENS", 7)) {
2699 for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0;
2700 i < 20 && (t = strtok((char *) 0, ",/")), ++i) {
2701 sscanf(t, "%d", &val);
2702 sysflags.amii_dripens[i] = val;
2705 } else if (match_varname(buf, "SCREENMODE", 10)) {
2706 extern long amii_scrnmode;
2708 if (!stricmp(bufp, "req"))
2709 amii_scrnmode = 0xffffffff; /* Requester */
2710 else if (sscanf(bufp, "%x", &amii_scrnmode) != 1)
2712 } else if (match_varname(buf, "MSGPENS", 7)) {
2713 extern int amii_msgAPen, amii_msgBPen;
2714 char *t = strtok(bufp, ",/");
2717 sscanf(t, "%d", &amii_msgAPen);
2718 if (t = strtok((char *) 0, ",/"))
2719 sscanf(t, "%d", &amii_msgBPen);
2721 } else if (match_varname(buf, "TEXTPENS", 8)) {
2722 extern int amii_textAPen, amii_textBPen;
2723 char *t = strtok(bufp, ",/");
2726 sscanf(t, "%d", &amii_textAPen);
2727 if (t = strtok((char *) 0, ",/"))
2728 sscanf(t, "%d", &amii_textBPen);
2730 } else if (match_varname(buf, "MENUPENS", 8)) {
2731 extern int amii_menuAPen, amii_menuBPen;
2732 char *t = strtok(bufp, ",/");
2735 sscanf(t, "%d", &amii_menuAPen);
2736 if (t = strtok((char *) 0, ",/"))
2737 sscanf(t, "%d", &amii_menuBPen);
2739 } else if (match_varname(buf, "STATUSPENS", 10)) {
2740 extern int amii_statAPen, amii_statBPen;
2741 char *t = strtok(bufp, ",/");
2744 sscanf(t, "%d", &amii_statAPen);
2745 if (t = strtok((char *) 0, ",/"))
2746 sscanf(t, "%d", &amii_statBPen);
2748 } else if (match_varname(buf, "OTHERPENS", 9)) {
2749 extern int amii_otherAPen, amii_otherBPen;
2750 char *t = strtok(bufp, ",/");
2753 sscanf(t, "%d", &amii_otherAPen);
2754 if (t = strtok((char *) 0, ",/"))
2755 sscanf(t, "%d", &amii_otherBPen);
2757 } else if (match_varname(buf, "PENS", 4)) {
2758 extern unsigned short amii_init_map[AMII_MAXCOLORS];
2762 for (i = 0, t = strtok(bufp, ",/");
2763 i < AMII_MAXCOLORS && t != (char *) 0;
2764 t = strtok((char *) 0, ",/"), ++i) {
2765 sscanf(t, "%hx", &amii_init_map[i]);
2767 amii_setpens(amii_numcolors = i);
2768 } else if (match_varname(buf, "FGPENS", 6)) {
2769 extern int foreg[AMII_MAXCOLORS];
2773 for (i = 0, t = strtok(bufp, ",/");
2774 i < AMII_MAXCOLORS && t != (char *) 0;
2775 t = strtok((char *) 0, ",/"), ++i) {
2776 sscanf(t, "%d", &foreg[i]);
2778 } else if (match_varname(buf, "BGPENS", 6)) {
2779 extern int backg[AMII_MAXCOLORS];
2783 for (i = 0, t = strtok(bufp, ",/");
2784 i < AMII_MAXCOLORS && t != (char *) 0;
2785 t = strtok((char *) 0, ",/"), ++i) {
2786 sscanf(t, "%d", &backg[i]);
2790 } else if (match_varname(buf, "SOUNDDIR", 8)) {
2791 sounddir = dupstr(bufp);
2792 } else if (match_varname(buf, "SOUND", 5)) {
2793 add_sound_mapping(bufp);
2795 } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
2797 extern char *qt_tilewidth;
2799 if (qt_tilewidth == NULL)
2800 qt_tilewidth = dupstr(bufp);
2802 } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
2804 extern char *qt_tileheight;
2806 if (qt_tileheight == NULL)
2807 qt_tileheight = dupstr(bufp);
2809 } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
2811 extern char *qt_fontsize;
2813 if (qt_fontsize == NULL)
2814 qt_fontsize = dupstr(bufp);
2816 } else if (match_varname(buf, "QT_COMPACT", 10)) {
2818 extern int qt_compact_mode;
2820 qt_compact_mode = atoi(bufp);
2823 config_error_add("Unknown config statement");
2831 can_read_file(filename)
2832 const char *filename;
2834 return (boolean) (access(filename, 4) == 0);
2836 #endif /* USER_SOUNDS */
2838 struct _config_error_frame {
2841 boolean origline_shown;
2844 char origline[4 * BUFSZ];
2846 struct _config_error_frame *next;
2849 static struct _config_error_frame *config_error_data = 0;
2852 config_error_init(from_file, sourcename, secure)
2854 const char *sourcename;
2857 struct _config_error_frame *tmp = (struct _config_error_frame *)
2858 alloc(sizeof (struct _config_error_frame));
2861 tmp->num_errors = 0;
2862 tmp->origline_shown = FALSE;
2863 tmp->fromfile = from_file;
2864 tmp->secure = secure;
2865 tmp->origline[0] = '\0';
2866 if (sourcename && sourcename[0]) {
2867 (void) strncpy(tmp->source, sourcename, sizeof (tmp->source) - 1);
2868 tmp->source[sizeof (tmp->source) - 1] = '\0';
2870 tmp->source[0] = '\0';
2872 tmp->next = config_error_data;
2873 config_error_data = tmp;
2877 config_error_nextline(line)
2880 struct _config_error_frame *ced = config_error_data;
2885 if (ced->num_errors && ced->secure)
2889 ced->origline_shown = FALSE;
2890 if (line && line[0]) {
2891 (void) strncpy(ced->origline, line, sizeof (ced->origline) - 1);
2892 ced->origline[sizeof (ced->origline) - 1] = '\0';
2894 ced->origline[0] = '\0';
2899 /* varargs 'config_error_add()' moved to pline.c */
2904 char lineno[QBUFSZ];
2907 buf = "Unknown error";
2909 if (!config_error_data) {
2910 /* either very early, where pline() will use raw_print(), or
2911 player gave bad value when prompted by interactive 'O' command */
2912 pline("%s%s.", !iflags.window_inited ? "config_error_add: " : "", buf);
2917 config_error_data->num_errors++;
2918 if (!config_error_data->origline_shown && !config_error_data->secure) {
2919 pline("\n%s", config_error_data->origline);
2920 config_error_data->origline_shown = TRUE;
2922 if (config_error_data->line_num > 0 && !config_error_data->secure) {
2923 Sprintf(lineno, "Line %d: ", config_error_data->line_num);
2927 pline("%s %s%s.", config_error_data->secure ? "Error:" : " *",
2935 struct _config_error_frame *tmp = config_error_data;
2937 if (!config_error_data)
2939 n = config_error_data->num_errors;
2941 pline("\n%d error%s in %s.\n", n,
2943 *config_error_data->source
2944 ? config_error_data->source : configfile);
2947 config_error_data = tmp->next;
2953 read_config_file(filename, src)
2954 const char *filename;
2960 if (!(fp = fopen_config_file(filename, src)))
2963 /* begin detection of duplicate configfile options */
2964 set_duplicate_opt_detection(1);
2965 free_config_sections();
2966 iflags.parse_config_file_src = src;
2968 rv = parse_conf_file(fp, parse_config_line);
2971 free_config_sections();
2972 /* turn off detection of duplicate configfile options */
2973 set_duplicate_opt_detection(0);
2981 #if defined(VMS) || defined(UNIX)
2982 char tmp_wizkit[BUFSZ];
2986 envp = nh_getenv("WIZKIT");
2988 (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2993 if (access(wizkit, 4) == -1) {
2994 /* 4 is R_OK on newer systems */
2995 /* nasty sneaky attempt to read file through
2996 * NetHack's setuid permissions -- this is a
2997 * place a file name may be wholly under the player's
3000 raw_printf("Access to %s denied (%d).", wizkit, errno);
3002 /* fall through to standard names */
3005 if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) {
3007 #if defined(UNIX) || defined(VMS)
3009 /* access() above probably caught most problems for UNIX */
3010 raw_printf("Couldn't open requested config file %s (%d).", wizkit,
3016 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
3017 if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
3021 envp = nh_getenv("HOME");
3023 Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
3025 Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
3026 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
3028 #else /* should be only UNIX left */
3029 envp = nh_getenv("HOME");
3031 Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
3033 Strcpy(tmp_wizkit, wizkit);
3034 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
3036 else if (errno != ENOENT) {
3037 /* e.g., problems when setuid NetHack can't search home
3038 * directory restricted to user */
3039 raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit,
3048 /* add to hero's inventory if there's room, otherwise put item on floor */
3053 if (!obj || obj == &zeroobj)
3056 /* subset of starting inventory pre-ID */
3058 if (Role_if(PM_PRIEST))
3059 obj->bknown = 1; /* ok to bypass set_bknown() */
3060 /* same criteria as lift_object()'s check for available inventory slot */
3061 if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52
3062 && !merge_choice(invent, obj)) {
3063 /* inventory overflow; can't just place & stack object since
3064 hero isn't in position yet, so schedule for arrival later */
3065 add_to_migration(obj);
3066 obj->ox = 0; /* index of main dungeon */
3067 obj->oy = 1; /* starting level number */
3069 (long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
3077 proc_wizkit_line(buf)
3082 if (strlen(buf) >= BUFSZ)
3083 buf[BUFSZ - 1] = '\0';
3084 otmp = readobjnam(buf, (struct obj *) 0);
3087 if (otmp != &zeroobj)
3088 wizkit_addinv(otmp);
3090 /* .60 limits output line width to 79 chars */
3091 config_error_add("Bad wizkit item: \"%.60s\"", buf);
3102 if (!wizard || !(fp = fopen_wizkit_file()))
3105 program_state.wizkit_wishing = 1;
3106 config_error_init(TRUE, "WIZKIT", FALSE);
3108 parse_conf_file(fp, proc_wizkit_line);
3111 config_error_done();
3112 program_state.wizkit_wishing = 0;
3119 * Read from file fp, handling comments, empty lines, config sections,
3120 * CHOOSE, and line continuation, calling proc for every valid line.
3122 * Continued lines are merged together with one space in between.
3125 parse_conf_file(fp, proc)
3127 boolean FDECL((*proc), (char *));
3129 char inbuf[4 * BUFSZ];
3130 boolean rv = TRUE; /* assume successful parse */
3132 boolean skip = FALSE, morelines = FALSE;
3133 char *buf = (char *) 0;
3134 size_t inbufsz = sizeof inbuf;
3136 free_config_sections();
3138 while (fgets(inbuf, (int) inbufsz, fp)) {
3139 ep = index(inbuf, '\n');
3140 if (skip) { /* in case previous line was too long */
3142 skip = FALSE; /* found newline; next line is normal */
3144 if (!ep) { /* newline missing */
3145 if (strlen(inbuf) < (inbufsz - 2)) {
3146 /* likely the last line of file is just
3147 missing a newline; process it anyway */
3150 config_error_add("Line too long, skipping");
3151 skip = TRUE; /* discard next fgets */
3154 *ep = '\0'; /* remove newline */
3157 char *tmpbuf = (char *) 0;
3159 boolean ignoreline = FALSE;
3160 boolean oldline = FALSE;
3162 /* line continuation (trailing '\') */
3163 morelines = (--ep >= inbuf && *ep == '\\');
3167 /* trim off spaces at end of line */
3169 && (*ep == ' ' || *ep == '\t' || *ep == '\r'))
3172 if (!config_error_nextline(inbuf)) {
3175 free(buf), buf = (char *) 0;
3180 while (*ep == ' ' || *ep == '\t')
3183 /* ignore empty lines and full-line comment lines */
3184 if (!*ep || *ep == '#')
3190 /* merge now read line with previous ones, if necessary */
3192 len = (int) strlen(ep) + 1; /* +1: final '\0' */
3194 len += (int) strlen(buf) + 1; /* +1: space */
3195 tmpbuf = (char *) alloc(len);
3198 Strcat(strcpy(tmpbuf, buf), " ");
3201 buf = strcat(tmpbuf, ep);
3202 if (strlen(buf) >= sizeof inbuf)
3203 buf[sizeof inbuf - 1] = '\0';
3206 if (morelines || (ignoreline && !oldline))
3209 if (handle_config_section(buf)) {
3215 /* from here onwards, we'll handle buf only */
3217 if (match_varname(buf, "CHOOSE", 6)) {
3219 char *bufp = find_optparam(buf);
3223 "Format is CHOOSE=section1,section2,...");
3230 if (config_section_chosen)
3231 free(config_section_chosen), config_section_chosen = 0;
3232 section = choose_random_part(bufp, ',');
3234 config_section_chosen = dupstr(section);
3236 config_error_add("No config section to choose");
3256 free_config_sections();
3260 extern struct symsetentry *symset_list; /* options.c */
3261 extern const char *known_handling[]; /* drawing.c */
3262 extern const char *known_restrictions[]; /* drawing.c */
3263 static int symset_count = 0; /* for pick-list building only */
3264 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
3265 static int symset_which_set = 0;
3273 fp = fopen_datafile(SYMBOLS, "r",
3285 * Returns 1 if the chose symset was found and loaded.
3286 * 0 if it wasn't found in the sym file or other problem.
3289 read_sym_file(which_set)
3294 symset[which_set].explicitly = FALSE;
3295 if (!(fp = fopen_sym_file()))
3298 symset[which_set].explicitly = TRUE;
3300 chosen_symset_start = chosen_symset_end = FALSE;
3301 symset_which_set = which_set;
3303 config_error_init(TRUE, "symbols", FALSE);
3305 parse_conf_file(fp, proc_symset_line);
3308 if (!chosen_symset_start && !chosen_symset_end) {
3309 /* name caller put in symset[which_set].name was not found;
3310 if it looks like "Default symbols", null it out and return
3311 success to use the default; otherwise, return failure */
3312 if (symset[which_set].name
3313 && (fuzzymatch(symset[which_set].name, "Default symbols",
3315 || !strcmpi(symset[which_set].name, "default")))
3316 clear_symsetentry(which_set, TRUE);
3317 config_error_done();
3319 /* If name was defined, it was invalid... Then we're loading fallback */
3320 if (symset[which_set].name) {
3321 symset[which_set].explicitly = FALSE;
3327 if (!chosen_symset_end)
3328 config_error_add("Missing finish for symset \"%s\"",
3329 symset[which_set].name ? symset[which_set].name
3331 config_error_done();
3336 proc_symset_line(buf)
3339 return !((boolean) parse_sym_line(buf, symset_which_set));
3342 /* returns 0 on error */
3344 parse_sym_line(buf, which_set)
3349 struct symparse *symp;
3350 char *bufp, *commentp, *altp;
3352 if (strlen(buf) >= BUFSZ)
3353 buf[BUFSZ - 1] = '\0';
3354 /* convert each instance of whitespace (tabs, consecutive spaces)
3355 into a single space; leading and trailing spaces are stripped */
3358 /* remove trailing comment, if any (this isn't strictly needed for
3359 individual symbols, and it won't matter if "X#comment" without
3360 separating space slips through; for handling or set description,
3361 symbol set creator is responsible for preceding '#' with a space
3362 and that comment itself doesn't contain " #") */
3363 if ((commentp = rindex(buf, '#')) != 0 && commentp[-1] == ' ')
3364 commentp[-1] = '\0';
3366 /* find the '=' or ':' */
3367 bufp = index(buf, '=');
3368 altp = index(buf, ':');
3369 if (!bufp || (altp && altp < bufp))
3372 if (strncmpi(buf, "finish", 6) == 0) {
3373 /* end current graphics set */
3374 if (chosen_symset_start)
3375 chosen_symset_end = TRUE;
3376 chosen_symset_start = FALSE;
3379 config_error_add("No \"finish\"");
3382 /* skip '=' and space which follows, if any */
3387 symp = match_sym(buf);
3389 config_error_add("Unknown sym keyword");
3393 if (!symset[which_set].name) {
3394 /* A null symset name indicates that we're just
3395 building a pick-list of possible symset
3396 values from the file, so only do that */
3397 if (symp->range == SYM_CONTROL) {
3398 struct symsetentry *tmpsp, *lastsp;
3400 for (lastsp = symset_list; lastsp; lastsp = lastsp->next)
3403 switch (symp->idx) {
3405 tmpsp = (struct symsetentry *) alloc(sizeof *tmpsp);
3406 tmpsp->next = (struct symsetentry *) 0;
3408 symset_list = tmpsp;
3410 lastsp->next = tmpsp;
3411 tmpsp->idx = symset_count++;
3412 tmpsp->name = dupstr(bufp);
3413 tmpsp->desc = (char *) 0;
3414 tmpsp->handling = H_UNK;
3415 /* initialize restriction bits */
3421 /* handler type identified */
3422 tmpsp = lastsp; /* most recent symset */
3423 for (i = 0; known_handling[i]; ++i)
3424 if (!strcmpi(known_handling[i], bufp)) {
3425 tmpsp->handling = i;
3426 break; /* for loop */
3430 /* description:something */
3431 tmpsp = lastsp; /* most recent symset */
3432 if (tmpsp && !tmpsp->desc)
3433 tmpsp->desc = dupstr(bufp);
3436 /* restrictions: xxxx*/
3437 tmpsp = lastsp; /* most recent symset */
3438 for (i = 0; known_restrictions[i]; ++i) {
3439 if (!strcmpi(known_restrictions[i], bufp)) {
3448 break; /* while loop */
3457 if (symp->range == SYM_CONTROL) {
3458 switch (symp->idx) {
3460 /* start of symset */
3461 if (!strcmpi(bufp, symset[which_set].name)) {
3462 /* matches desired one */
3463 chosen_symset_start = TRUE;
3464 /* these init_*() functions clear symset fields too */
3465 if (which_set == ROGUESET)
3466 init_rogue_symbols();
3467 else if (which_set == PRIMARY)
3468 init_primary_symbols();
3473 if (chosen_symset_start)
3474 chosen_symset_end = TRUE;
3475 chosen_symset_start = FALSE;
3478 /* handler type identified */
3479 if (chosen_symset_start)
3480 set_symhandling(bufp, which_set);
3482 /* case 3: (description) is ignored here */
3483 case 4: /* color:off */
3484 if (chosen_symset_start) {
3486 if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
3487 || !strcmpi(bufp, "on"))
3488 symset[which_set].nocolor = 0;
3489 else if (!strcmpi(bufp, "false")
3490 || !strcmpi(bufp, "no")
3491 || !strcmpi(bufp, "off"))
3492 symset[which_set].nocolor = 1;
3496 case 5: /* restrictions: xxxx*/
3497 if (chosen_symset_start) {
3500 while (known_restrictions[n]) {
3501 if (!strcmpi(known_restrictions[n], bufp)) {
3504 symset[which_set].primary = 1;
3507 symset[which_set].rogue = 1;
3510 break; /* while loop */
3517 } else { /* !SYM_CONTROL */
3518 val = sym_val(bufp);
3519 if (chosen_symset_start) {
3520 if (which_set == PRIMARY) {
3521 update_primary_symset(symp, val);
3522 } else if (which_set == ROGUESET) {
3523 update_rogue_symset(symp, val);
3532 set_symhandling(handling, which_set)
3538 symset[which_set].handling = H_UNK;
3539 while (known_handling[i]) {
3540 if (!strcmpi(known_handling[i], handling)) {
3541 symset[which_set].handling = i;
3548 /* ---------- END CONFIG FILE HANDLING ----------- */
3550 /* ---------- BEGIN SCOREBOARD CREATION ----------- */
3553 #define UNUSED_if_not_OS2_CODEVIEW /*empty*/
3555 #define UNUSED_if_not_OS2_CODEVIEW UNUSED
3558 /* verify that we can write to scoreboard file; if not, try to create one */
3561 check_recordfile(dir)
3562 const char *dir UNUSED_if_not_OS2_CODEVIEW;
3564 #if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW)
3567 const char *fq_record;
3570 #if defined(UNIX) || defined(VMS)
3571 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3572 fd = open(fq_record, O_RDWR, 0);
3574 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
3575 if (!file_is_stmlf(fd)) {
3577 "Warning: scoreboard file '%s' is not in stream_lf format",
3582 (void) nhclose(fd); /* RECORD is accessible */
3583 } else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
3584 (void) nhclose(fd); /* RECORD newly created */
3585 #if defined(VMS) && !defined(SECURE)
3586 /* Re-protect RECORD with world:read+write+execute+delete access. */
3587 (void) chmod(fq_record, FCMASK | 007);
3588 #endif /* VMS && !SECURE */
3590 raw_printf("Warning: cannot write scoreboard file '%s'", fq_record);
3593 #endif /* !UNIX && !VMS */
3594 #if defined(MICRO) || defined(WIN32)
3597 #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
3598 /* how does this work when there isn't an explicit path or fopenp
3599 * for later access to the file via fopen_datafile? ? */
3600 (void) strncpy(tmp, dir, PATHLEN - 1);
3601 tmp[PATHLEN - 1] = '\0';
3602 if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
3604 Strcat(tmp, RECORD);
3608 Strcpy(tmp, RECORD);
3609 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3612 /* If dir is NULL it indicates create but
3613 only if it doesn't already exist */
3618 fd = open(fq_record, O_RDWR);
3619 if (!(fd == -1 && errno == ENOENT)) {
3623 /* explanation for failure other than missing file */
3624 Sprintf(buf, "error \"%s\", (errno %d).",
3626 paniclog("scorefile", buf);
3630 Sprintf(buf, "missing \"%s\", creating new scorefile.",
3632 paniclog("scorefile", buf);
3636 if ((fd = open(fq_record, O_RDWR)) < 0) {
3637 /* try to create empty 'record' */
3638 #if defined(AZTEC_C) || defined(_DCC) \
3639 || (defined(__GNUC__) && defined(__AMIGA__))
3640 /* Aztec doesn't use the third argument */
3641 /* DICE doesn't like it */
3642 fd = open(fq_record, O_CREAT | O_RDWR);
3644 fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE);
3647 raw_printf("Warning: cannot write record '%s'", tmp);
3653 /* open succeeded => 'record' exists */
3656 #else /* MICRO || WIN32*/
3659 /* Create the "record" file, if necessary */
3660 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3661 fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
3666 #endif /* MICRO || WIN32*/
3669 /* ---------- END SCOREBOARD CREATION ----------- */
3671 /* ---------- BEGIN PANIC/IMPOSSIBLE/TESTING LOG ----------- */
3675 paniclog(type, reason)
3676 const char *type; /* panic, impossible, trickery */
3677 const char *reason; /* explanation */
3683 if (!program_state.in_paniclog) {
3684 program_state.in_paniclog = 1;
3685 lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
3687 #ifdef PANICLOG_FMT2
3688 (void) fprintf(lfile, "%ld %s: %s %s\n",
3689 ubirthday, (plname ? plname : "(none)"),
3692 time_t now = getnow();
3694 char playmode = wizard ? 'D' : discover ? 'X' : '-';
3696 (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
3697 version_string(buf), yyyymmdd(now), hhmmss(now),
3698 uid, playmode, type, reason);
3699 #endif /* !PANICLOG_FMT2 */
3700 (void) fclose(lfile);
3702 program_state.in_paniclog = 0;
3704 #endif /* PANICLOG */
3709 testinglog(filenm, type, reason)
3710 const char *filenm; /* ad hoc file name */
3712 const char *reason; /* explanation */
3719 Strcpy(fnbuf, filenm);
3720 if (index(fnbuf, '.') == 0)
3721 Strcat(fnbuf, ".log");
3722 lfile = fopen_datafile(fnbuf, "a", TROUBLEPREFIX);
3724 (void) fprintf(lfile, "%s\n%s\n", type, reason);
3725 (void) fclose(lfile);
3730 /* ---------- END PANIC/IMPOSSIBLE/TESTING LOG ----------- */
3734 /* ---------- BEGIN INTERNAL RECOVER ----------- */
3739 int lev, savelev, hpid, pltmpsiz;
3741 struct version_info version_data;
3743 char savename[SAVESIZE], errbuf[BUFSZ];
3744 struct savefile_info sfi;
3745 char tmpplbuf[PL_NSIZ];
3747 for (lev = 0; lev < 256; lev++)
3750 /* level 0 file contains:
3751 * pid of creating process (ignored here)
3752 * level number for current level of save file
3753 * name of save file nethack would have created
3758 gfd = open_levelfile(0, errbuf);
3760 raw_printf("%s\n", errbuf);
3763 if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
3764 raw_printf("\n%s\n%s\n",
3765 "Checkpoint data incompletely written or subsequently clobbered.",
3766 "Recovery impossible.");
3767 (void) nhclose(gfd);
3770 if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
3771 != sizeof(savelev)) {
3773 "\nCheckpointing was not in effect for %s -- recovery impossible.\n",
3775 (void) nhclose(gfd);
3778 if ((read(gfd, (genericptr_t) savename, sizeof savename)
3780 || (read(gfd, (genericptr_t) &version_data, sizeof version_data)
3781 != sizeof version_data)
3782 || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
3783 || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3784 != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ)
3785 || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) {
3786 raw_printf("\nError reading %s -- can't recover.\n", lock);
3787 (void) nhclose(gfd);
3791 /* save file should contain:
3795 * current level (including pets)
3796 * (non-level-based) game state
3799 set_savefile_name(TRUE);
3800 sfd = create_savefile();
3802 raw_printf("\nCannot recover savefile %s.\n", SAVEF);
3803 (void) nhclose(gfd);
3807 lfd = open_levelfile(savelev, errbuf);
3809 raw_printf("\n%s\n", errbuf);
3810 (void) nhclose(gfd);
3811 (void) nhclose(sfd);
3816 if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
3817 != sizeof version_data) {
3818 raw_printf("\nError writing %s; recovery failed.", SAVEF);
3819 (void) nhclose(gfd);
3820 (void) nhclose(sfd);
3821 (void) nhclose(lfd);
3826 if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) {
3827 raw_printf("\nError writing %s; recovery failed (savefile_info).\n",
3829 (void) nhclose(gfd);
3830 (void) nhclose(sfd);
3831 (void) nhclose(lfd);
3836 if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3837 != sizeof pltmpsiz) {
3838 raw_printf("Error writing %s; recovery failed (player name size).\n",
3840 (void) nhclose(gfd);
3841 (void) nhclose(sfd);
3842 (void) nhclose(lfd);
3847 if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) {
3848 raw_printf("Error writing %s; recovery failed (player name).\n",
3850 (void) nhclose(gfd);
3851 (void) nhclose(sfd);
3852 (void) nhclose(lfd);
3857 if (!copy_bytes(lfd, sfd)) {
3858 (void) nhclose(gfd);
3859 (void) nhclose(sfd);
3860 (void) nhclose(lfd);
3864 (void) nhclose(lfd);
3865 processed[savelev] = 1;
3867 if (!copy_bytes(gfd, sfd)) {
3868 (void) nhclose(gfd);
3869 (void) nhclose(sfd);
3873 (void) nhclose(gfd);
3876 for (lev = 1; lev < 256; lev++) {
3877 /* level numbers are kept in xchars in save.c, so the
3878 * maximum level number (for the endlevel) must be < 256
3880 if (lev != savelev) {
3881 lfd = open_levelfile(lev, (char *) 0);
3883 /* any or all of these may not exist */
3885 write(sfd, (genericptr_t) &levc, sizeof(levc));
3886 if (!copy_bytes(lfd, sfd)) {
3887 (void) nhclose(lfd);
3888 (void) nhclose(sfd);
3892 (void) nhclose(lfd);
3897 (void) nhclose(sfd);
3899 #ifdef HOLD_LOCKFILE_OPEN
3903 * We have a successful savefile!
3904 * Only now do we erase the level files.
3906 for (lev = 0; lev < 256; lev++) {
3907 if (processed[lev]) {
3908 const char *fq_lock;
3910 set_levelfile_name(lock, lev);
3911 fq_lock = fqname(lock, LEVELPREFIX, 3);
3912 (void) unlink(fq_lock);
3919 copy_bytes(ifd, ofd)
3926 nfrom = read(ifd, buf, BUFSIZ);
3927 nto = write(ofd, buf, nfrom);
3930 } while (nfrom == BUFSIZ);
3934 /* ---------- END INTERNAL RECOVER ----------- */
3935 #endif /*SELF_RECOVER*/
3937 /* ---------- OTHER ----------- */
3947 /* We are checking that the sysconf exists ... lock the path */
3948 fqn_prefix_locked[SYSCONFPREFIX] = TRUE;
3951 * All we really care about is the end result - can we read the file?
3952 * So just check that directly.
3954 * Not tested on most of the old platforms (which don't attempt
3955 * to implement SYSCF).
3956 * Some ports don't like open()'s optional third argument;
3957 * VMS overrides open() usage with a macro which requires it.
3960 # if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
3961 fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
3963 fd = open(SYSCF_FILE, O_RDONLY);
3966 fd = open(SYSCF_FILE, O_RDONLY, 0);
3973 raw_printf("Unable to open SYSCF_FILE.\n");
3977 #endif /* SYSCF_FILE */
3981 /* used by debugpline() to decide whether to issue a message
3982 * from a particular source file; caller passes __FILE__ and we check
3983 * whether it is in the source file list supplied by SYSCF's DEBUGFILES
3985 * pass FALSE to override wildcard matching; useful for files
3986 * like dungeon.c and questpgr.c, which generate a ridiculous amount of
3987 * output if DEBUG is defined and effectively block the use of a wildcard */
3989 debugcore(filename, wildcards)
3990 const char *filename;
3993 const char *debugfiles, *p;
3995 if (!filename || !*filename)
3996 return FALSE; /* sanity precaution */
3998 if (sysopt.env_dbgfl == 0) {
3999 /* check once for DEBUGFILES in the environment;
4000 if found, it supersedes the sysconf value
4001 [note: getenv() rather than nh_getenv() since a long value
4002 is valid and doesn't pose any sort of overflow risk here] */
4003 if ((p = getenv("DEBUGFILES")) != 0) {
4004 if (sysopt.debugfiles)
4005 free((genericptr_t) sysopt.debugfiles);
4006 sysopt.debugfiles = dupstr(p);
4007 sysopt.env_dbgfl = 1;
4009 sysopt.env_dbgfl = -1;
4012 debugfiles = sysopt.debugfiles;
4013 /* usual case: sysopt.debugfiles will be empty */
4014 if (!debugfiles || !*debugfiles)
4017 /* strip filename's path if present */
4019 if ((p = rindex(filename, '/')) != 0)
4023 filename = vms_basename(filename);
4024 /* vms_basename strips off 'type' suffix as well as path and version;
4025 we want to put suffix back (".c" assumed); since it always returns
4026 a pointer to a static buffer, we can safely modify its result */
4027 Strcat((char *) filename, ".c");
4031 * Wildcard match will only work if there's a single pattern (which
4032 * might be a single file name without any wildcarding) rather than
4033 * a space-separated list.
4034 * [to NOT do: We could step through the space-separated list and
4035 * attempt a wildcard match against each element, but that would be
4036 * overkill for the intended usage.]
4038 if (wildcards && pmatch(debugfiles, filename))
4041 /* check whether filename is an element of the list */
4042 if ((p = strstr(debugfiles, filename)) != 0) {
4043 int l = (int) strlen(filename);
4045 if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
4046 && (p[l] == ' ' || p[l] == '\0'))
4061 reveal_paths(VOID_ARGS)
4063 const char *fqn, *nodumpreason;
4065 #if defined(SYSCF) || !defined(UNIX) || defined(DLB)
4068 const char *gamename = (hname && *hname) ? hname : "NetHack";
4072 char *endp, *envp, cwdbuf[PATH_MAX];
4074 #ifdef PREFIXES_IN_USE
4078 raw_print("Variable playground locations:");
4079 for (i = 0; i < PREFIX_COUNT; i++)
4080 raw_printf(" [%-10s]=\"%s\"", fqn_prefix_names[i],
4081 fqn_prefix[i] ? fqn_prefix[i] : "not set");
4087 #ifdef PREFIXES_IN_USE
4088 strp = fqn_prefix_names[SYSCONFPREFIX];
4089 maxlen = BUFSZ - sizeof " (in )";
4090 if (strp && (int) strlen(strp) < maxlen)
4091 Sprintf(buf, " (in %s)", strp);
4095 raw_printf("%s system configuration file%s:", s_suffix(gamename), buf);
4101 fqn = fqname(filep, SYSCONFPREFIX, 0);
4103 set_configfile_name(fqn);
4106 raw_printf(" \"%s\"", filep);
4108 raw_printf("No system configuration file.");
4115 #ifdef PREFIXES_IN_USE
4117 strp = fqn_prefix_names[SYSCONFPREFIX];
4119 strp = fqn_prefix_names[HACKPREFIX];
4121 maxlen = BUFSZ - sizeof " (in )";
4122 if (strp && (int) strlen(strp) < maxlen)
4123 Sprintf(buf, " (in %s)", strp);
4124 #endif /* PREFIXES_IN_USE */
4125 raw_printf("The loadable symbols file%s:", buf);
4129 envp = getcwd(cwdbuf, PATH_MAX);
4131 raw_print("The loadable symbols file:");
4132 raw_printf(" \"%s/%s\"", envp, SYMBOLS);
4136 #ifdef PREFIXES_IN_USE
4138 fqn = fqname(filep, SYSCONFPREFIX, 1);
4140 fqn = fqname(filep, HACKPREFIX, 1);
4144 #endif /* PREFIXES_IN_USE */
4145 raw_printf(" \"%s\"", filep);
4148 /* dlb vs non-dlb */
4151 #ifdef PREFIXES_IN_USE
4152 strp = fqn_prefix_names[DATAPREFIX];
4153 maxlen = BUFSZ - sizeof " (in )";
4154 if (strp && (int) strlen(strp) < maxlen)
4155 Sprintf(buf, " (in %s)", strp);
4158 raw_printf("Basic data files%s are collected inside:", buf);
4160 #ifdef VERSION_IN_DLB_FILENAME
4161 Strcpy(buf, build_dlb_filename((const char *) 0));
4162 #ifdef PREFIXES_IN_USE
4163 fqn = fqname(buf, DATAPREFIX, 1);
4166 #endif /* PREFIXES_IN_USE */
4168 raw_printf(" \"%s\"", filep);
4171 raw_printf(" \"%s\"", filep);
4174 raw_printf("Basic data files%s are in many separate files.", buf);
4180 nodumpreason = "not supported";
4182 nodumpreason = "disabled";
4184 fqn = sysopt.dumplogfile;
4193 raw_print("Your end-of-game disclosure file:");
4194 (void) dump_fmtstr(fqn, buf, FALSE);
4195 buf[sizeof buf - sizeof " \"\""] = '\0';
4196 raw_printf(" \"%s\"", buf);
4198 #endif /* ?DUMPLOG */
4199 raw_printf("No end-of-game disclosure file (%s).", nodumpreason);
4202 if (sysopt.portable_device_paths) {
4203 const char *pd = get_portable_device();
4205 raw_printf("portable_device_paths (set in sysconf):");
4206 raw_printf(" \"%s\"", pd);
4210 /* personal configuration file */
4213 #ifdef PREFIXES_IN_USE
4214 strp = fqn_prefix_names[CONFIGPREFIX];
4215 maxlen = BUFSZ - sizeof " (in )";
4216 if (strp && (int) strlen(strp) < maxlen)
4217 Sprintf(buf, " (in %s)", strp);
4218 #endif /* PREFIXES_IN_USE */
4219 raw_printf("Your personal configuration file%s:", buf);
4223 if ((envp = nh_getenv("HOME")) != 0) {
4224 copynchars(buf, envp, (int) sizeof buf - 1 - 1);
4228 copynchars(endp, default_configfile,
4229 (int) (sizeof buf - 1 - strlen(buf)));
4230 #if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX aka OSX aka macOS */
4232 if (access(buf, 4) == -1) { /* 4: R_OK, -1: failure */
4233 /* read access to default failed; might be protected excessively
4234 but more likely it doesn't exist; try first alternate:
4235 "$HOME/Library/Pref..."; 'endp' points past '/' */
4236 copynchars(endp, "Library/Preferences/NetHack Defaults",
4237 (int) (sizeof buf - 1 - strlen(buf)));
4238 if (access(buf, 4) == -1) {
4239 /* first alternate failed, try second:
4240 ".../NetHack Defaults.txt"; no 'endp', just append */
4241 copynchars(eos(buf), ".txt",
4242 (int) (sizeof buf - 1 - strlen(buf)));
4243 if (access(buf, 4) == -1) {
4244 /* second alternate failed too, so revert to the
4245 original default ("$HOME/.nethackrc") for message */
4246 copynchars(endp, default_configfile,
4247 (int) (sizeof buf - 1 - strlen(buf)));
4252 #endif /* __APPLE__ */
4253 raw_printf(" \"%s\"", buf);
4255 fqn = (const char *) 0;
4256 #ifdef PREFIXES_IN_USE
4257 fqn = fqname(default_configfile, CONFIGPREFIX, 2);
4259 raw_printf(" \"%s\"", fqn ? fqn : default_configfile);
4265 /* ---------- BEGIN TRIBUTE ----------- */
4270 #define SECTIONSCOPE 1
4271 #define TITLESCOPE 2
4272 #define PASSAGESCOPE 3
4274 #define MAXPASSAGES SIZE(context.novel.pasg) /* 20 */
4276 static int FDECL(choose_passage, (int, unsigned));
4278 /* choose a random passage that hasn't been chosen yet; once all have
4279 been chosen, reset the tracking to make all passages available again */
4281 choose_passage(passagecnt, oid)
4282 int passagecnt; /* total of available passages */
4283 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
4290 /* if a different book or we've used up all the passages already,
4291 reset in order to have all 'passagecnt' passages available */
4292 if (oid != context.novel.id || context.novel.count == 0) {
4293 int i, range = passagecnt, limit = MAXPASSAGES;
4295 context.novel.id = oid;
4296 if (range <= limit) {
4297 /* collect all of the N indices */
4298 context.novel.count = passagecnt;
4299 for (idx = 0; idx < MAXPASSAGES; idx++)
4300 context.novel.pasg[idx] = (xchar) ((idx < passagecnt)
4303 /* collect MAXPASSAGES of the N indices */
4304 context.novel.count = MAXPASSAGES;
4305 for (idx = i = 0; i < passagecnt; ++i, --range)
4306 if (range > 0 && rn2(range) < limit) {
4307 context.novel.pasg[idx++] = (xchar) (i + 1);
4313 idx = rn2(context.novel.count);
4314 res = (int) context.novel.pasg[idx];
4315 /* move the last slot's passage index into the slot just used
4316 and reduce the number of passages available */
4317 context.novel.pasg[idx] = context.novel.pasg[--context.novel.count];
4321 /* Returns True if you were able to read something. */
4323 read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid)
4324 const char *tribsection, *tribtitle;
4325 int tribpassage, bufsz;
4327 unsigned oid; /* book identifier */
4330 char line[BUFSZ], lastline[BUFSZ];
4333 int linect = 0, passagecnt = 0, targetpassage = 0;
4335 const char *badtranslation = "an incomprehensible foreign translation";
4337 const char *badtranslation = "
\95s
\8a®
\91S
\82È
\8aO
\8d\91\8cê
\96|
\96ó";
4338 boolean matchedsection = FALSE, matchedtitle = FALSE;
4339 winid tribwin = WIN_ERR;
4340 boolean grasped = FALSE;
4341 boolean foundpassage = FALSE;
4346 /* check for mandatories */
4347 if (!tribsection || !tribtitle) {
4350 pline("It's %s of \"%s\"!", badtranslation, tribtitle);
4352 pline("
\82±
\82ê
\82Í
\81u%s
\81v
\82Ì%s
\82¾
\81I", tribtitle, badtranslation);
4356 debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
4359 fp = dlb_fopen(TRIBUTEFILE, "r");
4361 /* this is actually an error - cannot open tribute file! */
4364 pline("You feel too overwhelmed to continue!");
4366 pline("
\82 \82È
\82½
\82Í
\91±
\82¯
\82ç
\82ê
\82È
\82¢
\82Ù
\82Ç
\88³
\93|
\82³
\82ê
\82½
\81I");
4371 * Syntax (not case-sensitive):
4374 * In the books section:
4375 * %title booktitle (n)
4376 * where booktitle=book title without quotes
4377 * (n)= total number of passages present for this title
4379 * where k=sequential passage number
4381 * %e ends the passage/book/section
4382 * If in a passage, it marks the end of that passage.
4383 * If in a book, it marks the end of that book.
4384 * If in a section, it marks the end of that section.
4389 *line = *lastline = '\0';
4390 while (dlb_fgets(line, sizeof line, fp) != 0) {
4392 (void) strip_newline(line);
4395 if (!strncmpi(&line[1], "section ", sizeof "section " - 1)) {
4396 char *st = &line[9]; /* 9 from "%section " */
4398 scope = SECTIONSCOPE;
4399 matchedsection = !strcmpi(st, tribsection) ? TRUE : FALSE;
4400 } else if (!strncmpi(&line[1], "title ", sizeof "title " - 1)) {
4401 char *st = &line[7]; /* 7 from "%title " */
4404 if ((p1 = index(st, '(')) != 0) {
4406 (void) mungspaces(st);
4407 if ((p2 = index(p1, ')')) != 0) {
4409 passagecnt = atoi(p1);
4411 if (matchedsection && !strcmpi(st, tribtitle)) {
4412 matchedtitle = TRUE;
4413 targetpassage = !tribpassage
4414 ? choose_passage(passagecnt, oid)
4415 : (tribpassage <= passagecnt)
4418 matchedtitle = FALSE;
4422 } else if (!strncmpi(&line[1], "passage ",
4423 sizeof "passage " - 1)) {
4425 char *st = &line[9]; /* 9 from "%passage " */
4428 passagenum = atoi(st);
4429 if (passagenum > 0 && passagenum <= passagecnt) {
4430 scope = PASSAGESCOPE;
4431 if (matchedtitle && passagenum == targetpassage) {
4432 foundpassage = TRUE;
4434 tribwin = create_nhwindow(NHW_MENU);
4435 if (tribwin == WIN_ERR)
4440 } else if (!strncmpi(&line[1], "e ", sizeof "e " - 1)) {
4443 if (scope == TITLESCOPE)
4444 matchedtitle = FALSE;
4445 if (scope == SECTIONSCOPE)
4446 matchedsection = FALSE;
4450 debugpline1("tribute file error: bad %% command, line %d.",
4455 /* comment only, next! */
4460 /* outputting multi-line passage to text window */
4461 putstr(tribwin, 0, line);
4463 Strcpy(lastline, line);
4465 /* fetching one-line passage into buffer */
4466 copynchars(nowin_buf, line, bufsz - 1);
4467 goto cleanup; /* don't wait for "%e passage" */
4474 (void) dlb_fclose(fp);
4476 /* one-line buffer */
4477 grasped = *nowin_buf ? TRUE : FALSE;
4479 if (tribwin != WIN_ERR) { /* implies 'foundpassage' */
4480 /* multi-line window, normal case;
4481 if lastline is empty, there were no non-empty lines between
4482 "%passage n" and "%e passage" so we leave 'grasped' False */
4484 display_nhwindow(tribwin, FALSE);
4485 /* put the final attribution line into message history,
4486 analogous to the summary line from long quest messages */
4487 if (index(lastline, '['))
4488 mungspaces(lastline); /* to remove leading spaces */
4489 else /* construct one if necessary */
4490 Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
4491 putmsghistory(lastline, FALSE);
4494 destroy_nhwindow(tribwin);
4497 /* multi-line window, problem */
4499 pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
4501 pline("
\82±
\82ê
\82Í
\81u%s
\81v
\82Ì%s
\82Ì
\82æ
\82¤
\82¾
\81I", tribtitle, badtranslation);
4507 Death_quote(buf, bufsz)
4511 unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
4513 return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
4516 /* ---------- END TRIBUTE ----------- */