Apparently, restoring of saved games on Windows has been broken since1f36b98b, 'selectsaved' extension from Oct 10. That change was altering the names of the files saved on disk to a new format introduced at that time, but the game was not opening a savefile with that same name, and the restore failed. The code that renamed the savefile to match the internal name was not part of1f36b98b, it already existed prior to the new internally-stored format. To get things functional, this commit disables the code that carries out the renaming of the on-disk savefile to match the internal name in the savefile entirely, at least for now. This relates to GitHub issue #1346 item 2.
5121 lines
139 KiB
C
5121 lines
139 KiB
C
/* NetHack 3.7 files.c $NHDT-Date: 1717449127 2024/06/03 21:12:07 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.399 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/*-Copyright (c) Derek S. Ray, 2015. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#define NEED_VARARGS
|
|
|
|
#include "hack.h"
|
|
#include "dlb.h"
|
|
|
|
#ifdef TTY_GRAPHICS
|
|
#include "wintty.h" /* more() */
|
|
#endif
|
|
|
|
#if (!defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)) \
|
|
|| defined(USE_FCNTL)
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#ifdef _MSC_VER /* MSC 6.0 defines errno quite differently */
|
|
#if (_MSC_VER >= 600)
|
|
#define SKIP_ERRNO
|
|
#endif
|
|
#else
|
|
#ifdef NHSTDC
|
|
#define SKIP_ERRNO
|
|
#endif
|
|
#endif
|
|
#ifndef SKIP_ERRNO
|
|
#ifdef _DCC
|
|
const
|
|
#endif
|
|
extern int errno;
|
|
#endif
|
|
|
|
#ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
|
|
#include "zlib.h"
|
|
#ifndef COMPRESS_EXTENSION
|
|
#define COMPRESS_EXTENSION ".gz"
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(UNIX) && defined(SELECTSAVED)
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#endif
|
|
|
|
#if defined(UNIX) || defined(VMS) || !defined(NO_SIGNAL)
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#if defined(MSDOS) || defined(OS2) || defined(TOS) || defined(WIN32)
|
|
#ifndef __DJGPP__
|
|
#include <sys\stat.h>
|
|
#else
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#endif
|
|
#ifndef O_BINARY /* used for micros, no-op for others */
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
#ifdef PREFIXES_IN_USE
|
|
#define FQN_NUMBUF 8
|
|
static char fqn_filename_buffer[FQN_NUMBUF][FQN_MAX_FILENAME];
|
|
#endif
|
|
|
|
#if !defined(SAVE_EXTENSION)
|
|
#ifdef MICRO
|
|
#define SAVE_EXTENSION ".sav"
|
|
#endif
|
|
#ifdef WIN32
|
|
#define SAVE_EXTENSION ".NetHack-saved-game"
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(WIN32)
|
|
#include <share.h>
|
|
#endif
|
|
|
|
#ifdef AMIGA
|
|
extern char PATH[]; /* see sys/amiga/amidos.c */
|
|
extern char bbs_id[];
|
|
#ifdef __SASC_60
|
|
#include <proto/dos.h>
|
|
#endif
|
|
|
|
#include <libraries/dos.h>
|
|
extern void amii_set_text_font(char *, int);
|
|
#endif
|
|
|
|
#if defined(WIN32) || defined(MSDOS)
|
|
#ifdef MSDOS
|
|
#define Delay(a) msleep(a)
|
|
#endif
|
|
#define Close close
|
|
#ifndef WIN_CE
|
|
#define DeleteFile unlink
|
|
#endif
|
|
#ifdef WIN32
|
|
/*from windmain.c */
|
|
extern char *translate_path_variables(const char *, char *);
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MAC
|
|
#undef unlink
|
|
#define unlink macunlink
|
|
#endif
|
|
|
|
#if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) \
|
|
|| defined(__MWERKS__)
|
|
#define PRAGMA_UNUSED
|
|
#endif
|
|
|
|
#ifdef USER_SOUNDS
|
|
extern char *sounddir; /* defined in sounds.c */
|
|
#endif
|
|
|
|
staticfn NHFILE *new_nhfile(void);
|
|
staticfn void free_nhfile(NHFILE *);
|
|
#ifdef SELECTSAVED
|
|
staticfn int QSORTCALLBACK strcmp_wrap(const void *, const void *);
|
|
#endif
|
|
staticfn char *set_bonesfile_name(char *, d_level *);
|
|
staticfn char *set_bonestemp_name(void);
|
|
#ifdef COMPRESS
|
|
staticfn void redirect(const char *, const char *, FILE *, boolean);
|
|
#endif
|
|
#if defined(COMPRESS) || defined(ZLIB_COMP)
|
|
staticfn void docompress_file(const char *, boolean);
|
|
#endif
|
|
#if defined(ZLIB_COMP)
|
|
staticfn boolean make_compressed_name(const char *, char *);
|
|
#endif
|
|
#ifndef USE_FCNTL
|
|
staticfn char *make_lockname(const char *, char *);
|
|
#endif
|
|
staticfn void set_configfile_name(const char *);
|
|
staticfn FILE *fopen_config_file(const char *, int);
|
|
staticfn int get_uchars(char *, uchar *, boolean, int, const char *);
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
staticfn void adjust_prefix(char *, int);
|
|
#endif
|
|
staticfn char *choose_random_part(char *, char);
|
|
staticfn boolean config_error_nextline(const char *);
|
|
staticfn void free_config_sections(void);
|
|
staticfn char *is_config_section(char *);
|
|
staticfn boolean handle_config_section(char *);
|
|
boolean parse_config_line(char *);
|
|
staticfn char *find_optparam(const char *);
|
|
staticfn boolean cnf_line_OPTIONS(char *);
|
|
staticfn boolean cnf_line_AUTOPICKUP_EXCEPTION(char *);
|
|
staticfn boolean cnf_line_BINDINGS(char *);
|
|
staticfn boolean cnf_line_AUTOCOMPLETE(char *);
|
|
staticfn boolean cnf_line_MSGTYPE(char *);
|
|
staticfn boolean cnf_line_HACKDIR(char *);
|
|
staticfn boolean cnf_line_LEVELDIR(char *);
|
|
staticfn boolean cnf_line_SAVEDIR(char *);
|
|
staticfn boolean cnf_line_BONESDIR(char *);
|
|
staticfn boolean cnf_line_DATADIR(char *);
|
|
staticfn boolean cnf_line_SCOREDIR(char *);
|
|
staticfn boolean cnf_line_LOCKDIR(char *);
|
|
staticfn boolean cnf_line_CONFIGDIR(char *);
|
|
staticfn boolean cnf_line_TROUBLEDIR(char *);
|
|
staticfn boolean cnf_line_NAME(char *);
|
|
staticfn boolean cnf_line_ROLE(char *);
|
|
staticfn boolean cnf_line_dogname(char *);
|
|
staticfn boolean cnf_line_catname(char *);
|
|
#ifdef SYSCF
|
|
staticfn boolean cnf_line_WIZARDS(char *);
|
|
staticfn boolean cnf_line_SHELLERS(char *);
|
|
staticfn boolean cnf_line_MSGHANDLER(char *);
|
|
staticfn boolean cnf_line_EXPLORERS(char *);
|
|
staticfn boolean cnf_line_DEBUGFILES(char *);
|
|
staticfn boolean cnf_line_DUMPLOGFILE(char *);
|
|
staticfn boolean cnf_line_GENERICUSERS(char *);
|
|
staticfn boolean cnf_line_BONES_POOLS(char *);
|
|
staticfn boolean cnf_line_SUPPORT(char *);
|
|
staticfn boolean cnf_line_RECOVER(char *);
|
|
staticfn boolean cnf_line_CHECK_SAVE_UID(char *);
|
|
staticfn boolean cnf_line_CHECK_PLNAME(char *);
|
|
staticfn boolean cnf_line_SEDUCE(char *);
|
|
staticfn boolean cnf_line_HIDEUSAGE(char *);
|
|
staticfn boolean cnf_line_MAXPLAYERS(char *);
|
|
staticfn boolean cnf_line_PERSMAX(char *);
|
|
staticfn boolean cnf_line_PERS_IS_UID(char *);
|
|
staticfn boolean cnf_line_ENTRYMAX(char *);
|
|
staticfn boolean cnf_line_POINTSMIN(char *);
|
|
staticfn boolean cnf_line_MAX_STATUENAME_RANK(char *);
|
|
staticfn boolean cnf_line_LIVELOG(char *);
|
|
staticfn boolean cnf_line_PANICTRACE_LIBC(char *);
|
|
staticfn boolean cnf_line_PANICTRACE_GDB(char *);
|
|
staticfn boolean cnf_line_GDBPATH(char *);
|
|
staticfn boolean cnf_line_GREPPATH(char *);
|
|
staticfn boolean cnf_line_CRASHREPORTURL(char *);
|
|
staticfn boolean cnf_line_SAVEFORMAT(char *);
|
|
staticfn boolean cnf_line_BONESFORMAT(char *);
|
|
staticfn boolean cnf_line_ACCESSIBILITY(char *);
|
|
staticfn boolean cnf_line_PORTABLE_DEVICE_PATHS(char *);
|
|
staticfn void parseformat(int *, char *);
|
|
#endif /* SYSCF */
|
|
staticfn boolean cnf_line_BOULDER(char *);
|
|
staticfn boolean cnf_line_MENUCOLOR(char *);
|
|
staticfn boolean cnf_line_HILITE_STATUS(char *);
|
|
staticfn boolean cnf_line_WARNINGS(char *);
|
|
staticfn boolean cnf_line_ROGUESYMBOLS(char *);
|
|
staticfn boolean cnf_line_SYMBOLS(char *);
|
|
staticfn boolean cnf_line_WIZKIT(char *);
|
|
#ifdef USER_SOUNDS
|
|
staticfn boolean cnf_line_SOUNDDIR(char *);
|
|
staticfn boolean cnf_line_SOUND(char *);
|
|
#endif
|
|
staticfn boolean cnf_line_QT_TILEWIDTH(char *);
|
|
staticfn boolean cnf_line_QT_TILEHEIGHT(char *);
|
|
staticfn boolean cnf_line_QT_FONTSIZE(char *);
|
|
staticfn boolean cnf_line_QT_COMPACT(char *);
|
|
struct _cnf_parser_state; /* defined below (far below...) */
|
|
staticfn void cnf_parser_init(struct _cnf_parser_state *parser);
|
|
staticfn void cnf_parser_done(struct _cnf_parser_state *parser);
|
|
staticfn void parse_conf_buf(struct _cnf_parser_state *parser,
|
|
boolean (*proc)(char *arg));
|
|
/* next one is in extern.h; why here too? */
|
|
boolean parse_conf_str(const char *str, boolean (*proc)(char *arg));
|
|
staticfn boolean parse_conf_file(FILE *fp, boolean (*proc)(char *arg));
|
|
staticfn FILE *fopen_wizkit_file(void);
|
|
staticfn void wizkit_addinv(struct obj *);
|
|
boolean proc_wizkit_line(char *buf);
|
|
void read_wizkit(void); /* in extern.h; why here too? */
|
|
staticfn FILE *fopen_sym_file(void);
|
|
staticfn NHFILE *viable_nhfile(NHFILE *);
|
|
|
|
/* return a file's name without its path and optionally trailing 'type' */
|
|
const char *
|
|
nh_basename(const char *fname, boolean keep_suffix)
|
|
{
|
|
#ifndef VMS
|
|
static char basebuf[80];
|
|
const char *p;
|
|
|
|
if ((p = strrchr(fname, '/')) != 0)
|
|
fname = p + 1;
|
|
#if defined(WIN32) || defined(MSDOS)
|
|
if ((p = strrchr(fname, '\\')) != 0)
|
|
fname = p + 1;
|
|
#endif
|
|
if ((p = strrchr(fname, '.')) != 0 && !keep_suffix) {
|
|
size_t ln = (size_t) (p - fname);
|
|
/* note that without path, name should be reasonable length;
|
|
it is expected to refer to a source file name or run-time
|
|
configuration file name and those aren't arbitrarily long;
|
|
if "name" part of "name.suffix" is too long for 'basebuf[]',
|
|
just return that as-is without stripping off ".suffix" */
|
|
|
|
if (ln < sizeof basebuf) {
|
|
strncpy(basebuf, fname, ln);
|
|
basebuf[ln] = '\0';
|
|
fname = basebuf;
|
|
}
|
|
}
|
|
#else /* VMS */
|
|
fname = vms_basename(fname, keep_suffix);
|
|
#endif
|
|
return fname;
|
|
}
|
|
|
|
/*
|
|
* fname_encode()
|
|
*
|
|
* Args:
|
|
* legal zero-terminated list of acceptable file name characters
|
|
* quotechar lead-in character used to quote illegal characters as
|
|
* hex digits
|
|
* s string to encode
|
|
* callerbuf buffer to house result
|
|
* bufsz size of callerbuf
|
|
*
|
|
* Notes:
|
|
* The hex digits 0-9 and A-F are always part of the legal set due to
|
|
* their use in the encoding scheme, even if not explicitly included in
|
|
* 'legal'.
|
|
*
|
|
* Sample:
|
|
* The following call:
|
|
* (void) fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
* '%', "This is a % test!", buf, 512);
|
|
* results in this encoding:
|
|
* "This%20is%20a%20%25%20test%21"
|
|
*/
|
|
char *
|
|
fname_encode(
|
|
const char *legal,
|
|
char quotechar,
|
|
char *s,
|
|
char *callerbuf,
|
|
int bufsz)
|
|
{
|
|
char *sp, *op;
|
|
int cnt = 0;
|
|
static char hexdigits[] = "0123456789ABCDEF";
|
|
|
|
sp = s;
|
|
op = callerbuf;
|
|
*op = '\0';
|
|
|
|
while (*sp) {
|
|
/* Do we have room for one more character or encoding? */
|
|
if ((bufsz - cnt) <= 4)
|
|
return callerbuf;
|
|
|
|
if (*sp == quotechar) {
|
|
(void) sprintf(op, "%c%02X", quotechar, *sp);
|
|
op += 3;
|
|
cnt += 3;
|
|
} else if ((strchr(legal, *sp) != 0)
|
|
|| (strchr(hexdigits, *sp) != 0)) {
|
|
*op++ = *sp;
|
|
*op = '\0';
|
|
cnt++;
|
|
} else {
|
|
(void) sprintf(op, "%c%02X", quotechar, *sp);
|
|
op += 3;
|
|
cnt += 3;
|
|
}
|
|
sp++;
|
|
}
|
|
return callerbuf;
|
|
}
|
|
|
|
/*
|
|
* fname_decode()
|
|
*
|
|
* Args:
|
|
* quotechar lead-in character used to quote illegal characters as
|
|
* hex digits
|
|
* s string to decode
|
|
* callerbuf buffer to house result
|
|
* bufsz size of callerbuf
|
|
*/
|
|
char *
|
|
fname_decode(char quotechar, char *s, char *callerbuf, int bufsz)
|
|
{
|
|
char *sp, *op;
|
|
int k, calc, cnt = 0;
|
|
static char hexdigits[] = "0123456789ABCDEF";
|
|
|
|
sp = s;
|
|
op = callerbuf;
|
|
*op = '\0';
|
|
calc = 0;
|
|
|
|
while (*sp) {
|
|
/* Do we have room for one more character? */
|
|
if ((bufsz - cnt) <= 2)
|
|
return callerbuf;
|
|
if (*sp == quotechar) {
|
|
sp++;
|
|
for (k = 0; k < 16; ++k)
|
|
if (*sp == hexdigits[k])
|
|
break;
|
|
if (k >= 16)
|
|
return callerbuf; /* impossible, so bail */
|
|
calc = k << 4;
|
|
sp++;
|
|
for (k = 0; k < 16; ++k)
|
|
if (*sp == hexdigits[k])
|
|
break;
|
|
if (k >= 16)
|
|
return callerbuf; /* impossible, so bail */
|
|
calc += k;
|
|
sp++;
|
|
*op++ = calc;
|
|
*op = '\0';
|
|
} else {
|
|
*op++ = *sp++;
|
|
*op = '\0';
|
|
}
|
|
cnt++;
|
|
}
|
|
return callerbuf;
|
|
}
|
|
|
|
#ifdef PREFIXES_IN_USE
|
|
#define UNUSED_if_not_PREFIXES_IN_USE /*empty*/
|
|
#else
|
|
#define UNUSED_if_not_PREFIXES_IN_USE UNUSED
|
|
#endif
|
|
|
|
/*ARGSUSED*/
|
|
const char *
|
|
fqname(const char *basenam,
|
|
int whichprefix UNUSED_if_not_PREFIXES_IN_USE,
|
|
int buffnum UNUSED_if_not_PREFIXES_IN_USE)
|
|
{
|
|
#ifdef PREFIXES_IN_USE
|
|
char *bufptr;
|
|
#endif
|
|
#ifdef WIN32
|
|
char tmpbuf[BUFSZ];
|
|
#endif
|
|
|
|
#ifndef PREFIXES_IN_USE
|
|
return basenam;
|
|
#else
|
|
if (!basenam || whichprefix < 0 || whichprefix >= PREFIX_COUNT)
|
|
return basenam;
|
|
if (!gf.fqn_prefix[whichprefix])
|
|
return basenam;
|
|
if (buffnum < 0 || buffnum >= FQN_NUMBUF) {
|
|
impossible("Invalid fqn_filename_buffer specified: %d", buffnum);
|
|
buffnum = 0;
|
|
}
|
|
bufptr = gf.fqn_prefix[whichprefix];
|
|
#ifdef WIN32
|
|
if (strchr(gf.fqn_prefix[whichprefix], '%')
|
|
|| strchr(gf.fqn_prefix[whichprefix], '~'))
|
|
bufptr = translate_path_variables(gf.fqn_prefix[whichprefix], tmpbuf);
|
|
#endif
|
|
if (strlen(bufptr) + strlen(basenam) >= FQN_MAX_FILENAME) {
|
|
impossible("fqname too long: %s + %s", bufptr, basenam);
|
|
return basenam; /* XXX */
|
|
}
|
|
Strcpy(fqn_filename_buffer[buffnum], bufptr);
|
|
return strcat(fqn_filename_buffer[buffnum], basenam);
|
|
#endif /* !PREFIXES_IN_USE */
|
|
}
|
|
|
|
/* reasonbuf must be at least BUFSZ, supplied by caller */
|
|
int
|
|
validate_prefix_locations(char *reasonbuf)
|
|
{
|
|
#if defined(NOCWD_ASSUMPTIONS)
|
|
FILE *fp;
|
|
const char *filename;
|
|
int prefcnt, failcount = 0;
|
|
char panicbuf1[BUFSZ], panicbuf2[BUFSZ];
|
|
const char *details;
|
|
#endif
|
|
|
|
if (reasonbuf)
|
|
reasonbuf[0] = '\0';
|
|
#if defined(NOCWD_ASSUMPTIONS)
|
|
for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++) {
|
|
/* don't test writing to configdir or datadir; they're readonly */
|
|
if (prefcnt == SYSCONFPREFIX || prefcnt == CONFIGPREFIX
|
|
|| prefcnt == DATAPREFIX)
|
|
continue;
|
|
filename = fqname("validate", prefcnt, 3);
|
|
if ((fp = fopen(filename, "w"))) {
|
|
fclose(fp);
|
|
(void) unlink(filename);
|
|
} else {
|
|
if (reasonbuf) {
|
|
if (failcount)
|
|
Strcat(reasonbuf, ", ");
|
|
Strcat(reasonbuf, fqn_prefix_names[prefcnt]);
|
|
}
|
|
/* the paniclog entry gets the value of errno as well */
|
|
Sprintf(panicbuf1, "Invalid %s", fqn_prefix_names[prefcnt]);
|
|
#if defined(NHSTDC) && !defined(NOTSTDC)
|
|
if (!(details = strerror(errno)))
|
|
#endif
|
|
details = "";
|
|
Sprintf(panicbuf2, "\"%s\", (%d) %s",
|
|
gf.fqn_prefix[prefcnt], errno, details);
|
|
paniclog(panicbuf1, panicbuf2);
|
|
failcount++;
|
|
}
|
|
}
|
|
if (failcount)
|
|
return 0;
|
|
else
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
/* fopen a file, with OS-dependent bells and whistles */
|
|
/* NOTE: a simpler version of this routine also exists in util/dlb_main.c */
|
|
FILE *
|
|
fopen_datafile(const char *filename, const char *mode, int prefix)
|
|
{
|
|
FILE *fp;
|
|
|
|
filename = fqname(filename, prefix, prefix == TROUBLEPREFIX ? 3 : 0);
|
|
fp = fopen(filename, mode);
|
|
return fp;
|
|
}
|
|
|
|
/* ---------- EXTERNAL FILE SUPPORT ----------- */
|
|
|
|
/* determine byte order */
|
|
static const int bei = 1;
|
|
#define IS_BIGENDIAN() ( (*(char*)&bei) == 0 )
|
|
|
|
void
|
|
zero_nhfile(NHFILE *nhfp)
|
|
{
|
|
nhfp->fd = -1;
|
|
nhfp->mode = COUNTING;
|
|
nhfp->structlevel = FALSE;
|
|
nhfp->fieldlevel = FALSE;
|
|
nhfp->addinfo = FALSE;
|
|
nhfp->bendian = IS_BIGENDIAN();
|
|
nhfp->fpdef = (FILE *) 0;
|
|
nhfp->fplog = (FILE *) 0;
|
|
nhfp->fpdebug = (FILE *) 0;
|
|
nhfp->count = 0;
|
|
nhfp->eof = FALSE;
|
|
nhfp->fnidx = 0;
|
|
}
|
|
|
|
staticfn NHFILE *
|
|
new_nhfile(void)
|
|
{
|
|
NHFILE *nhfp = (NHFILE *) alloc(sizeof(NHFILE));
|
|
|
|
zero_nhfile(nhfp);
|
|
return nhfp;
|
|
}
|
|
|
|
staticfn void
|
|
free_nhfile(NHFILE *nhfp)
|
|
{
|
|
if (nhfp) {
|
|
zero_nhfile(nhfp);
|
|
free(nhfp);
|
|
}
|
|
}
|
|
|
|
void
|
|
close_nhfile(NHFILE *nhfp)
|
|
{
|
|
if (nhfp->structlevel && nhfp->fd != -1)
|
|
(void) nhclose(nhfp->fd), nhfp->fd = -1;
|
|
zero_nhfile(nhfp);
|
|
free_nhfile(nhfp);
|
|
}
|
|
|
|
void
|
|
rewind_nhfile(NHFILE *nhfp)
|
|
{
|
|
if (nhfp->structlevel) {
|
|
#ifdef BSD
|
|
(void) lseek(nhfp->fd, 0L, 0);
|
|
#else
|
|
(void) lseek(nhfp->fd, (off_t) 0, 0);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
staticfn NHFILE *
|
|
viable_nhfile(NHFILE *nhfp)
|
|
{
|
|
/* perform some sanity checks before returning
|
|
the pointer to the nethack file descriptor */
|
|
if (nhfp) {
|
|
/* check for no open file at all,
|
|
* not a structlevel legacy file
|
|
*/
|
|
if (nhfp->structlevel && nhfp->fd < 0) {
|
|
/* not viable, start the cleanup */
|
|
zero_nhfile(nhfp);
|
|
free_nhfile(nhfp);
|
|
nhfp = (NHFILE *) 0;
|
|
}
|
|
}
|
|
return nhfp;
|
|
}
|
|
|
|
/* ---------- BEGIN LEVEL FILE HANDLING ----------- */
|
|
|
|
/* Construct a file name for a level-type file, which is of the form
|
|
* something.level (with any old level stripped off).
|
|
* This assumes there is space on the end of 'file' to append
|
|
* a two digit number. This is true for 'level'
|
|
* but be careful if you use it for other things -dgk
|
|
*/
|
|
void
|
|
set_levelfile_name(char *file, int lev)
|
|
{
|
|
char *tf;
|
|
|
|
tf = strrchr(file, '.');
|
|
if (!tf)
|
|
tf = eos(file);
|
|
Sprintf(tf, ".%d", lev);
|
|
#ifdef VMS
|
|
Strcat(tf, ";1");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
NHFILE *
|
|
create_levelfile(int lev, char errbuf[])
|
|
{
|
|
const char *fq_lock;
|
|
NHFILE *nhfp = (NHFILE *) 0;
|
|
|
|
if (errbuf)
|
|
*errbuf = '\0';
|
|
set_levelfile_name(gl.lock, lev);
|
|
fq_lock = fqname(gl.lock, LEVELPREFIX, 0);
|
|
|
|
nhfp = new_nhfile();
|
|
if (nhfp) {
|
|
nhfp->ftype = NHF_LEVELFILE;
|
|
nhfp->mode = WRITING;
|
|
nhfp->structlevel = TRUE; /* do set this TRUE for levelfiles */
|
|
nhfp->fieldlevel = FALSE; /* don't set this TRUE for levelfiles */
|
|
nhfp->addinfo = FALSE;
|
|
nhfp->style.deflt = FALSE;
|
|
nhfp->style.binary = TRUE;
|
|
nhfp->fd = -1;
|
|
nhfp->fpdef = (FILE *) 0;
|
|
#if defined(MICRO) || defined(WIN32)
|
|
/* Use O_TRUNC to force the file to be shortened if it already
|
|
* exists and is currently longer.
|
|
*/
|
|
nhfp->fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
|
|
FCMASK);
|
|
#else
|
|
#ifdef MAC
|
|
nhfp->fd = maccreat(fq_lock, LEVL_TYPE);
|
|
#else
|
|
nhfp->fd = creat(fq_lock, FCMASK);
|
|
#endif
|
|
#endif /* MICRO || WIN32 */
|
|
|
|
if (nhfp->fd >= 0)
|
|
svl.level_info[lev].flags |= LFILE_EXISTS;
|
|
else if (errbuf) /* failure explanation */
|
|
Sprintf(errbuf,
|
|
"Cannot create file \"%s\" for level %d (errno %d).",
|
|
gl.lock, lev, errno);
|
|
}
|
|
nhfp = viable_nhfile(nhfp);
|
|
return nhfp;
|
|
}
|
|
|
|
NHFILE *
|
|
open_levelfile(int lev, char errbuf[])
|
|
{
|
|
const char *fq_lock;
|
|
NHFILE *nhfp = (NHFILE *) 0;
|
|
|
|
if (errbuf)
|
|
*errbuf = '\0';
|
|
set_levelfile_name(gl.lock, lev);
|
|
fq_lock = fqname(gl.lock, LEVELPREFIX, 0);
|
|
nhfp = new_nhfile();
|
|
if (nhfp) {
|
|
nhfp->mode = READING;
|
|
nhfp->structlevel = TRUE; /* do set this TRUE for levelfiles */
|
|
nhfp->fieldlevel = FALSE; /* do not set this TRUE for levelfiles */
|
|
nhfp->addinfo = FALSE;
|
|
nhfp->style.deflt = FALSE;
|
|
nhfp->style.binary = TRUE;
|
|
nhfp->ftype = NHF_LEVELFILE;
|
|
nhfp->fd = -1;
|
|
nhfp->fpdef = (FILE *) 0;
|
|
}
|
|
if (nhfp && nhfp->structlevel) {
|
|
#ifdef MAC
|
|
nhfp->fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE);
|
|
#else
|
|
nhfp->fd = open(fq_lock, O_RDONLY | O_BINARY, 0);
|
|
#endif
|
|
|
|
/* for failure, return an explanation that our caller can use;
|
|
settle for `lock' instead of `fq_lock' because the latter
|
|
might end up being too big for nethack's BUFSZ */
|
|
if (nhfp->fd < 0 && errbuf)
|
|
Sprintf(errbuf,
|
|
"Cannot open file \"%s\" for level %d (errno %d).",
|
|
gl.lock, lev, errno);
|
|
}
|
|
nhfp = viable_nhfile(nhfp);
|
|
return nhfp;
|
|
}
|
|
|
|
void
|
|
delete_levelfile(int lev)
|
|
{
|
|
/*
|
|
* Level 0 might be created by port specific code that doesn't
|
|
* call create_levfile(), so always assume that it exists.
|
|
*/
|
|
if (lev == 0 || (svl.level_info[lev].flags & LFILE_EXISTS)) {
|
|
set_levelfile_name(gl.lock, lev);
|
|
(void) unlink(fqname(gl.lock, LEVELPREFIX, 0));
|
|
svl.level_info[lev].flags &= ~LFILE_EXISTS;
|
|
}
|
|
}
|
|
|
|
void
|
|
clearlocks(void)
|
|
{
|
|
int x;
|
|
|
|
#ifdef HANGUPHANDLING
|
|
if (program_state.preserve_locks)
|
|
return;
|
|
#endif
|
|
#ifndef NO_SIGNAL
|
|
(void) signal(SIGINT, SIG_IGN);
|
|
#if defined(UNIX) || defined(VMS)
|
|
sethanguphandler((void (*)(int)) SIG_IGN);
|
|
#endif
|
|
#endif /* NO_SIGNAL */
|
|
/* can't access maxledgerno() before dungeons are created -dlc */
|
|
for (x = (svn.n_dgns ? maxledgerno() : 0); x >= 0; x--)
|
|
delete_levelfile(x); /* not all levels need be present */
|
|
}
|
|
|
|
#if defined(SELECTSAVED)
|
|
/* qsort comparison routine */
|
|
staticfn int QSORTCALLBACK
|
|
strcmp_wrap(const void *p, const void *q)
|
|
{
|
|
return strcmp(*(char **) p, *(char **) q);
|
|
}
|
|
#endif
|
|
|
|
int
|
|
nhclose(int fd)
|
|
{
|
|
int retval = 0;
|
|
|
|
if (fd >= 0) {
|
|
if (close_check(fd))
|
|
bclose(fd);
|
|
else
|
|
retval = close(fd);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/* ---------- END LEVEL FILE HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN BONES FILE HANDLING ----------- */
|
|
|
|
/* set up "file" to be file name for retrieving bones, and return a
|
|
* bonesid to be read/written in the bones file.
|
|
*/
|
|
staticfn char *
|
|
set_bonesfile_name(char *file, d_level *lev)
|
|
{
|
|
s_level *sptr;
|
|
char *dptr;
|
|
|
|
/*
|
|
* "bonD0.nn" = bones for level nn in the main dungeon;
|
|
* "bonM0.T" = bones for Minetown;
|
|
* "bonQBar.n" = bones for level n in the Barbarian quest;
|
|
* "bon3D0.nn" = \
|
|
* "bon3M0.T" = > same as above, but for bones pool #3.
|
|
* "bon3QBar.n" = /
|
|
*
|
|
* Return value for content validation skips "bon" and the
|
|
* pool number (if present), making it feasible for the admin
|
|
* to manually move a bones file from one pool to another by
|
|
* renaming it.
|
|
*/
|
|
Strcpy(file, "bon");
|
|
#ifdef SYSCF
|
|
if (sysopt.bones_pools > 1) {
|
|
unsigned poolnum = min((unsigned) sysopt.bones_pools, 10);
|
|
|
|
poolnum = (unsigned) ubirthday % poolnum; /* 0..9 */
|
|
Sprintf(eos(file), "%u", poolnum);
|
|
}
|
|
#endif
|
|
dptr = eos(file);
|
|
/* when this naming scheme was adopted, 'filecode' was one letter;
|
|
3.3.0 turned it into a three letter string for quest levels */
|
|
Sprintf(dptr, "%c%s", svd.dungeons[lev->dnum].boneid,
|
|
In_quest(lev) ? gu.urole.filecode : "0");
|
|
if ((sptr = Is_special(lev)) != 0)
|
|
Sprintf(eos(dptr), ".%c", sptr->boneid);
|
|
else
|
|
Sprintf(eos(dptr), ".%d", lev->dlevel);
|
|
#ifdef VMS
|
|
Strcat(dptr, ";1");
|
|
#endif
|
|
return dptr;
|
|
}
|
|
|
|
/* set up temporary file name for writing bones, to avoid another game's
|
|
* trying to read from an uncompleted bones file. we want an uncontentious
|
|
* name, so use one in the namespace reserved for this game's level files.
|
|
* (we are not reading or writing level files while writing bones files, so
|
|
* the same array may be used instead of copying.)
|
|
*/
|
|
staticfn char *
|
|
set_bonestemp_name(void)
|
|
{
|
|
char *tf;
|
|
|
|
tf = strrchr(gl.lock, '.');
|
|
if (!tf)
|
|
tf = eos(gl.lock);
|
|
Sprintf(tf, ".bn");
|
|
#ifdef VMS
|
|
Strcat(tf, ";1");
|
|
#endif
|
|
return gl.lock;
|
|
}
|
|
|
|
NHFILE *
|
|
create_bonesfile(d_level *lev, char **bonesid, char errbuf[])
|
|
{
|
|
const char *file;
|
|
NHFILE *nhfp = (NHFILE *) 0;
|
|
int failed = 0;
|
|
|
|
if (errbuf)
|
|
*errbuf = '\0';
|
|
*bonesid = set_bonesfile_name(gb.bones, lev);
|
|
file = set_bonestemp_name();
|
|
file = fqname(file, BONESPREFIX, 0);
|
|
|
|
nhfp = new_nhfile();
|
|
if (nhfp) {
|
|
nhfp->structlevel = TRUE;
|
|
nhfp->fieldlevel = FALSE;
|
|
nhfp->ftype = NHF_BONESFILE;
|
|
nhfp->mode = WRITING;
|
|
if (nhfp->structlevel) {
|
|
#if defined(MICRO) || defined(WIN32)
|
|
/* Use O_TRUNC to force the file to be shortened if it already
|
|
* exists and is currently longer.
|
|
*/
|
|
nhfp->fd = open(file,
|
|
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
|
|
#else
|
|
#ifdef MAC
|
|
nhfp->fd = maccreat(file, BONE_TYPE);
|
|
#else
|
|
nhfp->fd = creat(file, FCMASK);
|
|
#endif
|
|
#endif
|
|
if (nhfp->fd < 0)
|
|
failed = errno;
|
|
}
|
|
if (failed && errbuf) /* failure explanation */
|
|
Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).",
|
|
gl.lock, *bonesid, errno);
|
|
}
|
|
#if defined(VMS) && !defined(SECURE)
|
|
/*
|
|
Re-protect bones file with world:read+write+execute+delete access.
|
|
umask() doesn't seem very reliable; also, vaxcrtl won't let us set
|
|
delete access without write access, which is what's really wanted.
|
|
Can't simply create it with the desired protection because creat
|
|
ANDs the mask with the user's default protection, which usually
|
|
denies some or all access to world.
|
|
*/
|
|
(void) chmod(file, FCMASK | 007); /* allow other users full access */
|
|
#endif /* VMS && !SECURE */
|
|
|
|
nhfp = viable_nhfile(nhfp);
|
|
return nhfp;
|
|
}
|
|
|
|
/* move completed bones file to proper name */
|
|
void
|
|
commit_bonesfile(d_level *lev)
|
|
{
|
|
const char *fq_bones, *tempname;
|
|
int ret;
|
|
|
|
(void) set_bonesfile_name(gb.bones, lev);
|
|
fq_bones = fqname(gb.bones, BONESPREFIX, 0);
|
|
tempname = set_bonestemp_name();
|
|
tempname = fqname(tempname, BONESPREFIX, 1);
|
|
|
|
#if (defined(SYSV) && !defined(SVR4)) || defined(GENIX)
|
|
/* old SYSVs don't have rename. Some SVR3's may, but since they
|
|
* also have link/unlink, it doesn't matter. :-)
|
|
*/
|
|
(void) unlink(fq_bones);
|
|
ret = link(tempname, fq_bones);
|
|
ret += unlink(tempname);
|
|
#else
|
|
ret = rename(tempname, fq_bones);
|
|
#endif
|
|
if (wizard && ret != 0)
|
|
pline("couldn't rename %s to %s.", tempname, fq_bones);
|
|
}
|
|
|
|
NHFILE *
|
|
open_bonesfile(d_level *lev, char **bonesid)
|
|
{
|
|
const char *fq_bones;
|
|
NHFILE *nhfp = (NHFILE *) 0;
|
|
|
|
*bonesid = set_bonesfile_name(gb.bones, lev);
|
|
fq_bones = fqname(gb.bones, BONESPREFIX, 0);
|
|
nh_uncompress(fq_bones); /* no effect if nonexistent */
|
|
|
|
nhfp = new_nhfile();
|
|
if (nhfp) {
|
|
nhfp->structlevel = TRUE;
|
|
nhfp->fieldlevel = FALSE;
|
|
nhfp->ftype = NHF_BONESFILE;
|
|
nhfp->mode = READING;
|
|
if (nhfp->structlevel) {
|
|
#ifdef MAC
|
|
nhfp->fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE);
|
|
#else
|
|
nhfp->fd = open(fq_bones, O_RDONLY | O_BINARY, 0);
|
|
#endif
|
|
}
|
|
}
|
|
nhfp = viable_nhfile(nhfp);
|
|
return nhfp;
|
|
}
|
|
|
|
int
|
|
delete_bonesfile(d_level *lev)
|
|
{
|
|
(void) set_bonesfile_name(gb.bones, lev);
|
|
return !(unlink(fqname(gb.bones, BONESPREFIX, 0)) < 0);
|
|
}
|
|
|
|
/* assume we're compressing the recently read or created bonesfile, so the
|
|
* file name is already set properly */
|
|
void
|
|
compress_bonesfile(void)
|
|
{
|
|
nh_compress(fqname(gb.bones, BONESPREFIX, 0));
|
|
}
|
|
|
|
/* ---------- END BONES FILE HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN SAVE FILE HANDLING ----------- */
|
|
|
|
/* set savefile name in OS-dependent manner from pre-existing svp.plname,
|
|
* avoiding troublesome characters */
|
|
void
|
|
set_savefile_name(boolean regularize_it)
|
|
{
|
|
int regoffset = 0, overflow = 0,
|
|
indicator_spot = 0; /* 0=no indicator, 1=before ext, 2=after ext */
|
|
const char *postappend = (const char *) 0,
|
|
*sfindicator = (const char *) 0;
|
|
#if defined(WIN32)
|
|
char tmp[BUFSZ];
|
|
#endif
|
|
|
|
#ifdef VMS
|
|
Sprintf(gs.SAVEF, "[.save]%d%s", getuid(), svp.plname);
|
|
regoffset = 7;
|
|
indicator_spot = 1;
|
|
postappend = ";1";
|
|
#endif
|
|
#if defined(WIN32)
|
|
if (regularize_it) {
|
|
static const char okchars[]
|
|
= "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
|
|
const char *legal = okchars;
|
|
|
|
++legal; /* skip '*' wildcard character */
|
|
(void) fname_encode(legal, '%', svp.plname, tmp, sizeof tmp);
|
|
} else {
|
|
Sprintf(tmp, "%s", svp.plname);
|
|
}
|
|
if (strlen(tmp) < (SAVESIZE - 1))
|
|
Strcpy(gs.SAVEF, tmp);
|
|
else
|
|
overflow = 1;
|
|
indicator_spot = 1;
|
|
regularize_it = FALSE;
|
|
#endif
|
|
#ifdef UNIX
|
|
Sprintf(gs.SAVEF, "save/%d%s", (int) getuid(), svp.plname);
|
|
regoffset = 5;
|
|
indicator_spot = 2;
|
|
#endif
|
|
#if defined(MSDOS)
|
|
if (strlen(gs.SAVEP) < (SAVESIZE - 1))
|
|
Strcpy(gs.SAVEF, gs.SAVEP);
|
|
if (strlen(gs.SAVEF) < (SAVESIZE - 1))
|
|
(void) strncat(gs.SAVEF, svp.plname, (SAVESIZE - strlen(gs.SAVEF)));
|
|
#endif
|
|
#if defined(MICRO) && !defined(WIN32) && !defined(MSDOS)
|
|
if (strlen(gs.SAVEP) < (SAVESIZE - 1))
|
|
Strcpy(gs.SAVEF, gs.SAVEP);
|
|
else
|
|
#ifdef AMIGA
|
|
if (strlen(gs.SAVEP) + strlen(bbs_id) < (SAVESIZE - 1))
|
|
strncat(gs.SAVEF, bbs_id, PATHLEN);
|
|
#endif
|
|
{
|
|
int i = strlen(gs.SAVEP);
|
|
#ifdef AMIGA
|
|
/* svp.plname has to share space with gs.SAVEP and ".sav" */
|
|
(void) strncat(gs.SAVEF, svp.plname,
|
|
FILENAME - i - strlen(SAVE_EXTENSION));
|
|
#else
|
|
(void) strncat(gs.SAVEF, svp.plname, 8);
|
|
#endif
|
|
regoffset = i;
|
|
}
|
|
#endif /* MICRO */
|
|
|
|
if (regularize_it)
|
|
regularize(gs.SAVEF + regoffset);
|
|
if (indicator_spot == 1 && sfindicator && !overflow) {
|
|
if (strlen(gs.SAVEF) + strlen(sfindicator) < (SAVESIZE - 1))
|
|
Strcat(gs.SAVEF, sfindicator);
|
|
else
|
|
overflow = 2;
|
|
}
|
|
#ifdef SAVE_EXTENSION
|
|
/* (0) is placed in brackets below so that the [&& !overflow] is
|
|
explicit dead code (the ">" comparison is detected as always
|
|
FALSE at compile-time). Done to appease clang's -Wunreachable-code */
|
|
if (strlen(SAVE_EXTENSION) > (0) && !overflow) {
|
|
if (strlen(gs.SAVEF) + strlen(SAVE_EXTENSION) < (SAVESIZE - 1)) {
|
|
Strcat(gs.SAVEF, SAVE_EXTENSION);
|
|
#ifdef MSDOS
|
|
sfindicator = "";
|
|
#endif
|
|
} else
|
|
overflow = 3;
|
|
}
|
|
#endif
|
|
if (indicator_spot == 2 && sfindicator && !overflow) {
|
|
if (strlen(gs.SAVEF) + strlen(sfindicator) < (SAVESIZE - 1))
|
|
Strcat(gs.SAVEF, sfindicator);
|
|
else
|
|
overflow = 4;
|
|
}
|
|
if (postappend && !overflow) {
|
|
if (strlen(gs.SAVEF) + strlen(postappend) < (SAVESIZE - 1))
|
|
Strcat(gs.SAVEF, postappend);
|
|
else
|
|
overflow = 5;
|
|
}
|
|
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
|
|
if (overflow)
|
|
impossible("set_savefile_name() couldn't complete"
|
|
" without overflow %d",
|
|
overflow);
|
|
#endif
|
|
}
|
|
|
|
#ifdef INSURANCE
|
|
void
|
|
save_savefile_name(NHFILE *nhfp)
|
|
{
|
|
if (nhfp->structlevel)
|
|
(void) write(nhfp->fd, (genericptr_t) gs.SAVEF, sizeof(gs.SAVEF));
|
|
}
|
|
#endif
|
|
|
|
#ifndef MICRO
|
|
/* change pre-existing savefile name to indicate an error savefile */
|
|
void
|
|
set_error_savefile(void)
|
|
{
|
|
#ifdef VMS
|
|
{
|
|
char *semi_colon = strrchr(gs.SAVEF, ';');
|
|
|
|
if (semi_colon)
|
|
*semi_colon = '\0';
|
|
}
|
|
Strcat(gs.SAVEF, ".e;1");
|
|
#else
|
|
#ifdef MAC
|
|
Strcat(gs.SAVEF, "-e");
|
|
#else
|
|
Strcat(gs.SAVEF, ".e");
|
|
#endif
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* create save file, overwriting one if it already exists */
|
|
NHFILE *
|
|
create_savefile(void)
|
|
{
|
|
const char *fq_save;
|
|
NHFILE *nhfp = (NHFILE *) 0;
|
|
boolean do_historical = TRUE;
|
|
|
|
fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0);
|
|
nhfp = new_nhfile();
|
|
if (nhfp) {
|
|
nhfp->structlevel = TRUE;
|
|
nhfp->fieldlevel = FALSE;
|
|
nhfp->ftype = NHF_SAVEFILE;
|
|
nhfp->mode = WRITING;
|
|
if (program_state.in_self_recover || do_historical) {
|
|
do_historical = TRUE; /* force it */
|
|
nhfp->structlevel = TRUE;
|
|
nhfp->fieldlevel = FALSE;
|
|
nhfp->addinfo = FALSE;
|
|
nhfp->style.deflt = FALSE;
|
|
nhfp->style.binary = TRUE;
|
|
nhfp->fnidx = historical;
|
|
nhfp->fd = -1;
|
|
nhfp->fpdef = (FILE *) 0;
|
|
}
|
|
if (nhfp->structlevel) {
|
|
#if defined(MICRO) || defined(WIN32)
|
|
nhfp->fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC,
|
|
FCMASK);
|
|
#else
|
|
#ifdef MAC
|
|
nhfp->fd = maccreat(fq_save, SAVE_TYPE);
|
|
#else
|
|
nhfp->fd = creat(fq_save, FCMASK);
|
|
#endif
|
|
#endif /* MICRO || WIN32 */
|
|
}
|
|
}
|
|
#if defined(VMS) && !defined(SECURE)
|
|
/*
|
|
Make sure the save file is owned by the current process. That's
|
|
the default for non-privileged users, but for priv'd users the
|
|
file will be owned by the directory's owner instead of the user.
|
|
*/
|
|
#undef getuid
|
|
(void) chown(fq_save, getuid(), getgid());
|
|
#define getuid() vms_getuid()
|
|
#endif /* VMS && !SECURE */
|
|
|
|
nhfp = viable_nhfile(nhfp);
|
|
return nhfp;
|
|
}
|
|
|
|
/* open savefile for reading */
|
|
NHFILE *
|
|
open_savefile(void)
|
|
{
|
|
const char *fq_save;
|
|
NHFILE *nhfp = (NHFILE *) 0;
|
|
boolean do_historical = TRUE;
|
|
|
|
fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0);
|
|
nhfp = new_nhfile();
|
|
if (nhfp) {
|
|
nhfp->structlevel = TRUE;
|
|
nhfp->fieldlevel = FALSE;
|
|
nhfp->ftype = NHF_SAVEFILE;
|
|
nhfp->mode = READING;
|
|
if (program_state.in_self_recover || do_historical) {
|
|
do_historical = TRUE; /* force it */
|
|
nhfp->structlevel = TRUE;
|
|
nhfp->fieldlevel = FALSE;
|
|
nhfp->addinfo = FALSE;
|
|
nhfp->style.deflt = FALSE;
|
|
nhfp->style.binary = TRUE;
|
|
nhfp->fnidx = historical;
|
|
nhfp->fd = -1;
|
|
nhfp->fpdef = (FILE *) 0;
|
|
}
|
|
if (nhfp->structlevel) {
|
|
#ifdef MAC
|
|
nhfp->fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE);
|
|
#else
|
|
nhfp->fd = open(fq_save, O_RDONLY | O_BINARY, 0);
|
|
#endif
|
|
}
|
|
}
|
|
nhfp = viable_nhfile(nhfp);
|
|
return nhfp;
|
|
}
|
|
|
|
/* delete savefile */
|
|
int
|
|
delete_savefile(void)
|
|
{
|
|
(void) unlink(fqname(gs.SAVEF, SAVEPREFIX, 0));
|
|
return 0; /* for restore_saved_game() (ex-xxxmain.c) test */
|
|
}
|
|
|
|
/* try to open up a save file and prepare to restore it */
|
|
NHFILE *
|
|
restore_saved_game(void)
|
|
{
|
|
const char *fq_save;
|
|
NHFILE *nhfp = (NHFILE *) 0;
|
|
|
|
set_savefile_name(TRUE);
|
|
fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0);
|
|
|
|
nh_uncompress(fq_save);
|
|
if ((nhfp = open_savefile()) != 0) {
|
|
if (validate(nhfp, fq_save, FALSE) != 0) {
|
|
close_nhfile(nhfp);
|
|
nhfp = (NHFILE *) 0;
|
|
(void) delete_savefile();
|
|
}
|
|
}
|
|
return nhfp;
|
|
}
|
|
|
|
/* called if there is no save file for current character */
|
|
int
|
|
check_panic_save(void)
|
|
{
|
|
int result = 0;
|
|
#ifdef CHECK_PANIC_SAVE
|
|
FILE *cf;
|
|
const char *savef;
|
|
|
|
set_error_savefile();
|
|
savef = fqname(gs.SAVEF, SAVEPREFIX, 0);
|
|
|
|
/*
|
|
* This duplicates part of docompress_file().
|
|
* We don't want to start by uncompressing just to check for the
|
|
* file's existence and then have to recompress it.
|
|
*/
|
|
|
|
#ifdef COMPRESS_EXTENSION
|
|
unsigned ln = (unsigned) (strlen(savef) + strlen(COMPRESS_EXTENSION));
|
|
char *cfn = (char *) alloc(ln + 1);
|
|
|
|
Strcpy(cfn, savef);
|
|
Strcat(cfn, COMPRESS_EXTENSION);
|
|
if ((cf = fopen(cfn, RDBMODE)) != NULL) {
|
|
(void) fclose(cf);
|
|
result = 1;
|
|
}
|
|
free((genericptr_t) cfn);
|
|
#endif /* COMPRESS_EXTENSION */
|
|
|
|
if (!result) {
|
|
/* maybe it has already been manually uncompressed */
|
|
if ((cf = fopen(savef, RDBMODE)) != NULL) {
|
|
(void) fclose(cf);
|
|
result = 1;
|
|
}
|
|
}
|
|
|
|
set_savefile_name(TRUE); /* reset to normal */
|
|
#endif /* CHECK_PANIC_SAVE */
|
|
return result;
|
|
}
|
|
|
|
#if defined(SELECTSAVED)
|
|
|
|
char *
|
|
plname_from_file(
|
|
const char *filename,
|
|
boolean without_wait_synch_per_file)
|
|
{
|
|
NHFILE *nhfp;
|
|
unsigned ln;
|
|
char *result = 0;
|
|
|
|
Strcpy(gs.SAVEF, filename);
|
|
#ifdef COMPRESS_EXTENSION
|
|
{
|
|
/* if COMPRESS_EXTENSION is present, strip it off */
|
|
int sln = (int) strlen(gs.SAVEF),
|
|
xln = (int) strlen(COMPRESS_EXTENSION);
|
|
|
|
if (sln > xln && !strcmp(&gs.SAVEF[sln - xln], COMPRESS_EXTENSION))
|
|
gs.SAVEF[sln - xln] = '\0';
|
|
}
|
|
#endif
|
|
nh_uncompress(gs.SAVEF);
|
|
if ((nhfp = open_savefile()) != 0) {
|
|
if (validate(nhfp, filename, without_wait_synch_per_file) == 0) {
|
|
/* room for "name+role+race+gend+algn X" where the space before
|
|
X is actually NUL and X is playmode: one of '-', 'X', or 'D' */
|
|
ln = (unsigned) PL_NSIZ_PLUS;
|
|
result = memset((genericptr_t) alloc(ln), '\0', ln);
|
|
get_plname_from_file(nhfp, result, FALSE);
|
|
}
|
|
close_nhfile(nhfp);
|
|
}
|
|
nh_compress(gs.SAVEF);
|
|
return result; /* file's plname[]+playmode value */
|
|
}
|
|
#endif /* defined(SELECTSAVED) */
|
|
|
|
#define SUPPRESS_WAITSYNCH_PERFILE TRUE
|
|
#define ALLOW_WAITSYNCH_PERFILE FALSE
|
|
|
|
/* get list of saved games owned by current user */
|
|
char **
|
|
get_saved_games(void)
|
|
{
|
|
char **result = NULL;
|
|
#if defined(SELECTSAVED)
|
|
#if defined(WIN32) || defined(UNIX)
|
|
int n;
|
|
#endif
|
|
int j = 0;
|
|
|
|
#ifdef WIN32
|
|
{
|
|
char *foundfile;
|
|
const char *fq_save;
|
|
#if 0
|
|
const char *fq_new_save;
|
|
const char *fq_old_save;
|
|
#endif
|
|
char **files = 0;
|
|
int i, count_failures = 0;
|
|
|
|
Strcpy(svp.plname, "*");
|
|
set_savefile_name(FALSE);
|
|
#if defined(ZLIB_COMP)
|
|
Strcat(gs.SAVEF, COMPRESS_EXTENSION);
|
|
#endif
|
|
fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0);
|
|
|
|
n = 0;
|
|
foundfile = foundfile_buffer();
|
|
if (findfirst((char *) fq_save)) {
|
|
do {
|
|
++n;
|
|
} while (findnext());
|
|
}
|
|
|
|
if (n > 0) {
|
|
files = (char **) alloc((n + 1) * sizeof (char *)); /* at most */
|
|
(void) memset((genericptr_t) files, 0, (n + 1) * sizeof (char *));
|
|
if (findfirst((char *) fq_save)) {
|
|
i = 0;
|
|
do {
|
|
files[i++] = dupstr(foundfile);
|
|
} while (findnext());
|
|
}
|
|
}
|
|
|
|
if (n > 0) {
|
|
result = (char **) alloc((n + 1) * sizeof (char *)); /* at most */
|
|
(void) memset((genericptr_t) result, 0, (n + 1) * sizeof (char *));
|
|
for(i = 0; i < n; i++) {
|
|
char *r;
|
|
r = plname_from_file(files[i], SUPPRESS_WAITSYNCH_PERFILE);
|
|
|
|
if (r) {
|
|
/* this renaming of the savefile is not compatible
|
|
* with 1f36b98b, 'selectsaved' extension from
|
|
* Oct 10, 2024. Disable the renaming for the time
|
|
* being.
|
|
*/
|
|
#if 0
|
|
/* rename file if it is not named as expected */
|
|
Strcpy(svp.plname, r);
|
|
set_savefile_name(TRUE);
|
|
fq_new_save = fqname(gs.SAVEF, SAVEPREFIX, 0);
|
|
fq_old_save = fqname(files[i], SAVEPREFIX, 1);
|
|
|
|
if (strcmp(fq_old_save, fq_new_save) != 0
|
|
&& !file_exists(fq_new_save))
|
|
(void) rename(fq_old_save, fq_new_save);
|
|
#endif
|
|
result[j++] = r;
|
|
} else {
|
|
count_failures++;
|
|
}
|
|
}
|
|
}
|
|
|
|
free_saved_games(files);
|
|
if (count_failures)
|
|
wait_synch();
|
|
}
|
|
#endif /* WIN32 */
|
|
#ifdef UNIX
|
|
/* posixly correct version */
|
|
int myuid = getuid();
|
|
DIR *dir;
|
|
|
|
if ((dir = opendir(fqname("save", SAVEPREFIX, 0)))) {
|
|
for (n = 0; readdir(dir); n++)
|
|
;
|
|
closedir(dir);
|
|
if (n > 0) {
|
|
int i;
|
|
|
|
if (!(dir = opendir(fqname("save", SAVEPREFIX, 0))))
|
|
return 0;
|
|
result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
|
|
(void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
|
|
for (i = 0, j = 0; i < n; i++) {
|
|
int uid;
|
|
char name[64]; /* more than PL_NSIZ+1 */
|
|
struct dirent *entry = readdir(dir);
|
|
|
|
if (!entry)
|
|
break;
|
|
if (sscanf(entry->d_name, "%d%63s", &uid, name) == 2) {
|
|
if (uid == myuid) {
|
|
char filename[BUFSZ];
|
|
char *r;
|
|
|
|
Sprintf(filename, "save/%d%s", uid, name);
|
|
r = plname_from_file(filename,
|
|
ALLOW_WAITSYNCH_PERFILE);
|
|
if (r)
|
|
result[j++] = r;
|
|
}
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
}
|
|
#endif /* UNIX */
|
|
#ifdef VMS
|
|
Strcpy(svp.plname, "*");
|
|
set_savefile_name(FALSE);
|
|
j = vms_get_saved_games(gs.SAVEF, &result);
|
|
#endif /* VMS */
|
|
|
|
if (j > 0) {
|
|
if (j > 1)
|
|
qsort(result, j, sizeof (char *), strcmp_wrap);
|
|
result[j] = (char *) NULL;
|
|
} else if (result) { /* could happen if save files are obsolete */
|
|
free_saved_games(result);
|
|
result = (char **) NULL;
|
|
}
|
|
#endif /* SELECTSAVED */
|
|
|
|
return result;
|
|
}
|
|
#undef SUPPRESS_WAITSYNCH_PERFILE
|
|
#undef ALLOW_WAITSYNCH_PERFILE
|
|
|
|
void
|
|
free_saved_games(char **saved)
|
|
{
|
|
if (saved) {
|
|
int i;
|
|
|
|
for (i = 0; saved[i]; ++i)
|
|
free((genericptr_t) saved[i]);
|
|
free((genericptr_t) saved);
|
|
}
|
|
}
|
|
|
|
/* ---------- END SAVE FILE HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN FILE COMPRESSION HANDLING ----------- */
|
|
|
|
#ifdef COMPRESS /* external compression */
|
|
|
|
staticfn void
|
|
redirect(
|
|
const char *filename,
|
|
const char *mode,
|
|
FILE *stream,
|
|
boolean uncomp)
|
|
{
|
|
if (freopen(filename, mode, stream) == (FILE *) 0) {
|
|
const char *details;
|
|
|
|
#if defined(NHSTDC) && !defined(NOTSTDC)
|
|
if ((details = strerror(errno)) == 0)
|
|
#endif
|
|
details = "";
|
|
(void) fprintf(stderr,
|
|
"freopen of %s for %scompress failed; (%d) %s\n",
|
|
filename, uncomp ? "un" : "", errno, details);
|
|
nh_terminate(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* using system() is simpler, but opens up security holes and causes
|
|
* problems on at least Interactive UNIX 3.0.1 (SVR3.2), where any
|
|
* setuid is renounced by /bin/sh, so the files cannot be accessed.
|
|
*
|
|
* cf. child() in unixunix.c.
|
|
*/
|
|
staticfn void
|
|
docompress_file(const char *filename, boolean uncomp)
|
|
{
|
|
char *cfn = 0;
|
|
const char *xtra;
|
|
FILE *cf;
|
|
const char *args[10];
|
|
#ifdef COMPRESS_OPTIONS
|
|
char opts[sizeof COMPRESS_OPTIONS];
|
|
#endif
|
|
int i = 0;
|
|
int f, childstatus;
|
|
unsigned ln;
|
|
#ifdef TTY_GRAPHICS
|
|
boolean istty = WINDOWPORT(tty);
|
|
#endif
|
|
|
|
#ifdef COMPRESS_EXTENSION
|
|
xtra = COMPRESS_EXTENSION;
|
|
#else
|
|
xtra = "";
|
|
#endif
|
|
ln = (unsigned) (strlen(filename) + strlen(xtra));
|
|
cfn = (char *) alloc(ln + 1);
|
|
Strcpy(cfn, filename);
|
|
Strcat(cfn, xtra);
|
|
|
|
/* when compressing, we know the file exists */
|
|
if (uncomp) {
|
|
if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0) {
|
|
free((genericptr_t) cfn);
|
|
return;
|
|
}
|
|
(void) fclose(cf);
|
|
}
|
|
|
|
args[0] = COMPRESS;
|
|
if (uncomp)
|
|
args[++i] = "-d"; /* uncompress */
|
|
#ifdef COMPRESS_OPTIONS
|
|
{
|
|
/* we can't guarantee there's only one additional option, sigh */
|
|
char *opt;
|
|
boolean inword = FALSE;
|
|
|
|
opt = strcpy(opts, COMPRESS_OPTIONS);
|
|
while (*opt) {
|
|
if ((*opt == ' ') || (*opt == '\t')) {
|
|
if (inword) {
|
|
*opt = '\0';
|
|
inword = FALSE;
|
|
}
|
|
} else if (!inword) {
|
|
args[++i] = opt;
|
|
inword = TRUE;
|
|
}
|
|
opt++;
|
|
}
|
|
}
|
|
#endif
|
|
args[++i] = (char *) 0;
|
|
|
|
#ifdef TTY_GRAPHICS
|
|
/* If we don't do this and we are right after a y/n question *and*
|
|
* there is an error message from the compression, the 'y' or 'n' can
|
|
* end up being displayed after the error message.
|
|
*/
|
|
if (istty)
|
|
mark_synch();
|
|
#endif
|
|
f = fork();
|
|
if (f == 0) { /* child */
|
|
#ifdef TTY_GRAPHICS
|
|
/* any error messages from the compression must come out after
|
|
* the first line, because the more() to let the user read
|
|
* them will have to clear the first line. This should be
|
|
* invisible if there are no error messages.
|
|
*/
|
|
if (istty)
|
|
raw_print("");
|
|
#endif
|
|
/* run compressor without privileges, in case other programs
|
|
* have surprises along the line of gzip once taking filenames
|
|
* in GZIP.
|
|
*/
|
|
/* assume all compressors will compress stdin to stdout
|
|
* without explicit filenames. this is true of at least
|
|
* compress and gzip, those mentioned in config.h.
|
|
*/
|
|
if (uncomp) {
|
|
redirect(cfn, RDBMODE, stdin, uncomp);
|
|
redirect(filename, WRBMODE, stdout, uncomp);
|
|
} else {
|
|
redirect(filename, RDBMODE, stdin, uncomp);
|
|
redirect(cfn, WRBMODE, stdout, uncomp);
|
|
}
|
|
(void) setgid(getgid());
|
|
(void) setuid(getuid());
|
|
(void) execv(args[0], (char *const *) args);
|
|
perror((char *) 0);
|
|
(void) fprintf(stderr, "Exec to %scompress %s failed.\n",
|
|
uncomp ? "un" : "", filename);
|
|
free((genericptr_t) cfn);
|
|
nh_terminate(EXIT_FAILURE);
|
|
} else if (f == -1) {
|
|
perror((char *) 0);
|
|
pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename);
|
|
free((genericptr_t) cfn);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* back in parent...
|
|
*/
|
|
#ifndef NO_SIGNAL
|
|
childstatus = 1; /* wait() should update this, ideally setting it to 0 */
|
|
(void) signal(SIGINT, SIG_IGN);
|
|
(void) signal(SIGQUIT, SIG_IGN);
|
|
errno = 0; /* avoid stale details if wait() doesn't set errno */
|
|
/* wait() returns child's pid and sets 'childstatus' to child's
|
|
exit status, or returns -1 and leaves 'childstatus' unmodified */
|
|
if ((long) wait((int *) &childstatus) == -1L) {
|
|
char numbuf[40];
|
|
const char *details = strerror(errno);
|
|
|
|
if (!details) {
|
|
Sprintf(numbuf, "(%d)", errno);
|
|
details = numbuf;
|
|
}
|
|
raw_printf("Wait when %scompressing %s failed; %s.",
|
|
uncomp ? "un" : "", filename, details);
|
|
}
|
|
(void) signal(SIGINT, (SIG_RET_TYPE) done1);
|
|
if (wizard)
|
|
(void) signal(SIGQUIT, SIG_DFL);
|
|
#else
|
|
/* I don't think we can really cope with external compression
|
|
* without signals, so we'll declare that compress failed and
|
|
* go on. (We could do a better job by forcing off external
|
|
* compression if there are no signals, but we want this for
|
|
* testing with FailSafeC
|
|
*/
|
|
childstatus = 1; /* non-zero => failure */
|
|
#endif
|
|
if (childstatus == 0) {
|
|
/* (un)compress succeeded: remove file left behind */
|
|
if (uncomp)
|
|
(void) unlink(cfn);
|
|
else
|
|
(void) unlink(filename);
|
|
} else {
|
|
/* (un)compress failed; remove the new, bad file */
|
|
if (uncomp) {
|
|
raw_printf("Unable to uncompress %s", filename);
|
|
(void) unlink(filename);
|
|
} else {
|
|
/* no message needed for compress case; life will go on */
|
|
(void) unlink(cfn);
|
|
}
|
|
#ifdef TTY_GRAPHICS
|
|
/* Give them a chance to read any error messages from the
|
|
* compression--these would go to stdout or stderr and would get
|
|
* overwritten only in tty mode. It's still ugly, since the
|
|
* messages are being written on top of the screen, but at least
|
|
* the user can read them.
|
|
*/
|
|
if (istty && iflags.window_inited) {
|
|
clear_nhwindow(WIN_MESSAGE);
|
|
more();
|
|
/* No way to know if this is feasible */
|
|
/* doredraw(); */
|
|
}
|
|
#endif
|
|
}
|
|
|
|
free((genericptr_t) cfn);
|
|
return;
|
|
}
|
|
|
|
#endif /* COMPRESS : external compression */
|
|
|
|
#if defined(COMPRESS) || defined(ZLIB_COMP)
|
|
#define UNUSED_if_not_COMPRESS /*empty*/
|
|
#else
|
|
#define UNUSED_if_not_COMPRESS UNUSED
|
|
#endif
|
|
|
|
/* compress file */
|
|
void
|
|
nh_compress(const char *filename UNUSED_if_not_COMPRESS)
|
|
{
|
|
#if defined(COMPRESS) || defined(ZLIB_COMP)
|
|
docompress_file(filename, FALSE);
|
|
#endif
|
|
}
|
|
|
|
/* uncompress file if it exists */
|
|
void
|
|
nh_uncompress(const char *filename UNUSED_if_not_COMPRESS)
|
|
{
|
|
#if defined(COMPRESS) || defined(ZLIB_COMP)
|
|
docompress_file(filename, TRUE);
|
|
#endif
|
|
}
|
|
|
|
#ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
|
|
staticfn boolean
|
|
make_compressed_name(const char *filename, char *cfn)
|
|
{
|
|
#ifndef SHORT_FILENAMES
|
|
/* Assume free-form filename with no 8.3 restrictions */
|
|
strcpy(cfn, filename);
|
|
strcat(cfn, COMPRESS_EXTENSION);
|
|
return TRUE;
|
|
#else
|
|
#ifdef SAVE_EXTENSION
|
|
char *bp = (char *) 0;
|
|
|
|
strcpy(cfn, filename);
|
|
if ((bp = strstri(cfn, SAVE_EXTENSION))) {
|
|
strsubst(bp, SAVE_EXTENSION, ".saz");
|
|
return TRUE;
|
|
} else {
|
|
/* find last occurrence of bon */
|
|
bp = eos(cfn);
|
|
while (bp-- > cfn) {
|
|
if (strstri(bp, "bon")) {
|
|
strsubst(bp, "bon", "boz");
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
#endif /* SAVE_EXTENSION */
|
|
return FALSE;
|
|
#endif /* SHORT_FILENAMES */
|
|
}
|
|
|
|
staticfn void
|
|
docompress_file(const char *filename, boolean uncomp)
|
|
{
|
|
gzFile compressedfile;
|
|
FILE *uncompressedfile;
|
|
char cfn[256];
|
|
char buf[1024];
|
|
unsigned len, len2;
|
|
|
|
if (!make_compressed_name(filename, cfn))
|
|
return;
|
|
|
|
if (!uncomp) {
|
|
/* Open the input and output files */
|
|
/* Note that gzopen takes "wb" as its mode, even on systems where
|
|
fopen takes "r" and "w" */
|
|
|
|
uncompressedfile = fopen(filename, RDBMODE);
|
|
if (!uncompressedfile) {
|
|
pline("Error in zlib docompress_file %s", filename);
|
|
return;
|
|
}
|
|
compressedfile = gzopen(cfn, "wb");
|
|
if (compressedfile == NULL) {
|
|
if (errno == 0) {
|
|
pline("zlib failed to allocate memory");
|
|
} else {
|
|
panic("Error in docompress_file %d", errno);
|
|
}
|
|
fclose(uncompressedfile);
|
|
return;
|
|
}
|
|
|
|
/* Copy from the uncompressed to the compressed file */
|
|
|
|
while (1) {
|
|
len = fread(buf, 1, sizeof(buf), uncompressedfile);
|
|
if (ferror(uncompressedfile)) {
|
|
pline("Failure reading uncompressed file");
|
|
pline("Can't compress %s.", filename);
|
|
fclose(uncompressedfile);
|
|
gzclose(compressedfile);
|
|
(void) unlink(cfn);
|
|
return;
|
|
}
|
|
if (len == 0)
|
|
break; /* End of file */
|
|
|
|
len2 = gzwrite(compressedfile, buf, len);
|
|
if (len2 == 0) {
|
|
pline("Failure writing compressed file");
|
|
pline("Can't compress %s.", filename);
|
|
fclose(uncompressedfile);
|
|
gzclose(compressedfile);
|
|
(void) unlink(cfn);
|
|
return;
|
|
}
|
|
}
|
|
|
|
fclose(uncompressedfile);
|
|
gzclose(compressedfile);
|
|
|
|
/* Delete the file left behind */
|
|
|
|
(void) unlink(filename);
|
|
|
|
} else { /* uncomp */
|
|
|
|
/* Open the input and output files */
|
|
/* Note that gzopen takes "rb" as its mode, even on systems where
|
|
fopen takes "r" and "w" */
|
|
|
|
compressedfile = gzopen(cfn, "rb");
|
|
if (compressedfile == NULL) {
|
|
if (errno == 0) {
|
|
pline("zlib failed to allocate memory");
|
|
} else if (errno != ENOENT) {
|
|
panic("Error in zlib docompress_file %s, %d", filename,
|
|
errno);
|
|
}
|
|
return;
|
|
}
|
|
uncompressedfile = fopen(filename, WRBMODE);
|
|
if (!uncompressedfile) {
|
|
pline("Error in zlib docompress file uncompress %s", filename);
|
|
gzclose(compressedfile);
|
|
return;
|
|
}
|
|
|
|
/* Copy from the compressed to the uncompressed file */
|
|
|
|
while (1) {
|
|
len = gzread(compressedfile, buf, sizeof(buf));
|
|
if (len == (unsigned) -1) {
|
|
pline("Failure reading compressed file");
|
|
pline("Can't uncompress %s.", filename);
|
|
fclose(uncompressedfile);
|
|
gzclose(compressedfile);
|
|
(void) unlink(filename);
|
|
return;
|
|
}
|
|
if (len == 0)
|
|
break; /* End of file */
|
|
|
|
fwrite(buf, 1, len, uncompressedfile);
|
|
if (ferror(uncompressedfile)) {
|
|
pline("Failure writing uncompressed file");
|
|
pline("Can't uncompress %s.", filename);
|
|
fclose(uncompressedfile);
|
|
gzclose(compressedfile);
|
|
(void) unlink(filename);
|
|
return;
|
|
}
|
|
}
|
|
|
|
fclose(uncompressedfile);
|
|
gzclose(compressedfile);
|
|
|
|
/* Delete the file left behind */
|
|
(void) unlink(cfn);
|
|
}
|
|
}
|
|
#endif /* RLC 09 Mar 1999: End ZLIB patch */
|
|
|
|
#undef UNUSED_if_not_COMPRESS
|
|
|
|
/* ---------- END FILE COMPRESSION HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN FILE LOCKING HANDLING ----------- */
|
|
|
|
#if defined(NO_FILE_LINKS) || defined(USE_FCNTL) /* implies UNIX */
|
|
static int lockfd = -1; /* for lock_file() to pass to unlock_file() */
|
|
#endif
|
|
#ifdef USE_FCNTL
|
|
static struct flock sflock; /* for unlocking, same as above */
|
|
#endif
|
|
|
|
#if defined(HANGUPHANDLING)
|
|
#define HUP if (!program_state.done_hup)
|
|
#else
|
|
#define HUP
|
|
#endif
|
|
|
|
|
|
#if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \
|
|
|| defined(MSDOS)
|
|
#define UNUSED_conditional /*empty*/
|
|
#else
|
|
#define UNUSED_conditional UNUSED
|
|
#endif
|
|
|
|
|
|
#ifndef USE_FCNTL
|
|
staticfn char *
|
|
make_lockname(const char *filename UNUSED_conditional, char *lockname)
|
|
{
|
|
#if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \
|
|
|| defined(MSDOS)
|
|
#ifdef NO_FILE_LINKS
|
|
Strcpy(lockname, LOCKDIR);
|
|
Strcat(lockname, "/");
|
|
Strcat(lockname, filename);
|
|
#else
|
|
Strcpy(lockname, filename);
|
|
#endif
|
|
#ifdef VMS
|
|
{
|
|
char *semi_colon = strrchr(lockname, ';');
|
|
if (semi_colon)
|
|
*semi_colon = '\0';
|
|
}
|
|
Strcat(lockname, ".lock;1");
|
|
#else
|
|
Strcat(lockname, "_lock");
|
|
#endif
|
|
return lockname;
|
|
#else /* !(UNIX || VMS || AMIGA || WIN32 || MSDOS) */
|
|
lockname[0] = '\0';
|
|
return (char *) 0;
|
|
#endif
|
|
}
|
|
#endif /* !USE_FCNTL */
|
|
|
|
/* lock a file */
|
|
boolean
|
|
lock_file(const char *filename, int whichprefix,
|
|
int retryct UNUSED_conditional)
|
|
{
|
|
#ifndef USE_FCNTL
|
|
char locknambuf[BUFSZ];
|
|
const char *lockname;
|
|
#endif
|
|
|
|
gn.nesting++;
|
|
if (gn.nesting > 1) {
|
|
impossible("TRIED TO NEST LOCKS");
|
|
return TRUE;
|
|
}
|
|
|
|
#ifndef USE_FCNTL
|
|
lockname = make_lockname(filename, locknambuf);
|
|
#ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
|
|
lockname = fqname(lockname, LOCKPREFIX, 2);
|
|
#endif
|
|
#endif
|
|
filename = fqname(filename, whichprefix, 0);
|
|
#ifdef USE_FCNTL
|
|
lockfd = open(filename, O_RDWR);
|
|
if (lockfd == -1) {
|
|
HUP raw_printf("Cannot open file %s. "
|
|
" Is NetHack installed correctly?",
|
|
filename);
|
|
gn.nesting--;
|
|
return FALSE;
|
|
}
|
|
sflock.l_type = F_WRLCK;
|
|
sflock.l_whence = SEEK_SET;
|
|
sflock.l_start = 0;
|
|
sflock.l_len = 0;
|
|
#endif
|
|
|
|
#if defined(UNIX) || defined(VMS)
|
|
#ifdef USE_FCNTL
|
|
while (fcntl(lockfd, F_SETLK, &sflock) == -1) {
|
|
#else
|
|
#ifdef NO_FILE_LINKS
|
|
while ((lockfd = open(lockname, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1) {
|
|
#else
|
|
while (link(filename, lockname) == -1) {
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_FCNTL
|
|
if (retryct--) {
|
|
HUP raw_printf("Waiting for release of fcntl lock on %s. "
|
|
" (%d retries left.)",
|
|
filename, retryct);
|
|
sleep(1);
|
|
} else {
|
|
HUP raw_print("I give up. Sorry.");
|
|
HUP raw_printf("Some other process has an unnatural grip on %s.",
|
|
filename);
|
|
gn.nesting--;
|
|
return FALSE;
|
|
}
|
|
#else
|
|
int errnosv = errno;
|
|
|
|
switch (errnosv) { /* George Barbanis */
|
|
case EEXIST:
|
|
if (retryct--) {
|
|
HUP raw_printf("Waiting for access to %s. "
|
|
" (%d retries left).",
|
|
filename, retryct);
|
|
#if defined(SYSV) || defined(ULTRIX) || defined(VMS)
|
|
(void)
|
|
#endif
|
|
sleep(1);
|
|
} else {
|
|
HUP raw_print("I give up. Sorry.");
|
|
HUP raw_printf("Perhaps there is an old %s around?",
|
|
lockname);
|
|
gn.nesting--;
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
case ENOENT:
|
|
HUP raw_printf("Can't find file %s to lock!", filename);
|
|
gn.nesting--;
|
|
return FALSE;
|
|
case EACCES:
|
|
HUP raw_printf("No write permission to lock %s!", filename);
|
|
gn.nesting--;
|
|
return FALSE;
|
|
#ifdef VMS /* c__translate(vmsfiles.c) */
|
|
case EPERM:
|
|
/* could be misleading, but usually right */
|
|
HUP raw_printf("Can't lock %s due to directory protection.",
|
|
filename);
|
|
gn.nesting--;
|
|
return FALSE;
|
|
#endif
|
|
case EROFS:
|
|
/* take a wild guess at the underlying cause */
|
|
HUP perror(lockname);
|
|
HUP raw_printf("Cannot lock %s.", filename);
|
|
HUP raw_printf("(Perhaps you are running NetHack from"
|
|
" inside the distribution package?).");
|
|
gn.nesting--;
|
|
return FALSE;
|
|
default:
|
|
HUP perror(lockname);
|
|
HUP raw_printf("Cannot lock %s for unknown reason (%d).",
|
|
filename, errnosv);
|
|
gn.nesting--;
|
|
return FALSE;
|
|
}
|
|
#endif /* USE_FCNTL */
|
|
}
|
|
#endif /* UNIX || VMS */
|
|
|
|
#if (defined(AMIGA) || defined(WIN32) || defined(MSDOS)) \
|
|
&& !defined(USE_FCNTL)
|
|
#ifdef AMIGA
|
|
#define OPENFAILURE(fd) (!fd)
|
|
gl.lockptr = 0;
|
|
#else
|
|
#define OPENFAILURE(fd) (fd < 0)
|
|
gl.lockptr = -1;
|
|
#endif
|
|
while (--retryct && OPENFAILURE(gl.lockptr)) {
|
|
#if defined(WIN32) && !defined(WIN_CE)
|
|
gl.lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE);
|
|
#else
|
|
(void) DeleteFile(lockname); /* in case dead process was here first */
|
|
#ifdef AMIGA
|
|
gl.lockptr = Open(lockname, MODE_NEWFILE);
|
|
#else
|
|
gl.lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE);
|
|
#endif
|
|
#endif
|
|
if (OPENFAILURE(gl.lockptr)) {
|
|
raw_printf("Waiting for access to %s. (%d retries left).",
|
|
filename, retryct);
|
|
Delay(50);
|
|
}
|
|
}
|
|
if (!retryct) {
|
|
raw_printf("I give up. Sorry.");
|
|
gn.nesting--;
|
|
return FALSE;
|
|
}
|
|
#endif /* AMIGA || WIN32 || MSDOS */
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */
|
|
#ifdef unlink
|
|
#undef unlink
|
|
#endif
|
|
#define unlink(foo) vms_unlink(foo)
|
|
#endif
|
|
|
|
/* unlock file, which must be currently locked by lock_file */
|
|
void
|
|
unlock_file(const char *filename)
|
|
{
|
|
#ifndef USE_FCNTL
|
|
char locknambuf[BUFSZ];
|
|
const char *lockname;
|
|
#endif
|
|
|
|
if (gn.nesting == 1) {
|
|
#ifdef USE_FCNTL
|
|
sflock.l_type = F_UNLCK;
|
|
if (lockfd >= 0) {
|
|
if (fcntl(lockfd, F_SETLK, &sflock) == -1)
|
|
HUP raw_printf("Can't remove fcntl lock on %s.", filename);
|
|
(void) close(lockfd), lockfd = -1;
|
|
}
|
|
#else
|
|
lockname = make_lockname(filename, locknambuf);
|
|
#ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
|
|
lockname = fqname(lockname, LOCKPREFIX, 2);
|
|
#endif
|
|
|
|
#if defined(UNIX) || defined(VMS)
|
|
if (unlink(lockname) < 0)
|
|
HUP raw_printf("Can't unlink %s.", lockname);
|
|
#ifdef NO_FILE_LINKS
|
|
(void) nhclose(lockfd), lockfd = -1;
|
|
#endif
|
|
|
|
#endif /* UNIX || VMS */
|
|
|
|
#if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
|
|
if (gl.lockptr)
|
|
Close(gl.lockptr);
|
|
DeleteFile(lockname);
|
|
gl.lockptr = 0;
|
|
#endif /* AMIGA || WIN32 || MSDOS */
|
|
#endif /* USE_FCNTL */
|
|
}
|
|
|
|
gn.nesting--;
|
|
}
|
|
|
|
#undef UNUSED_conditional
|
|
|
|
/* ---------- END FILE LOCKING HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN CONFIG FILE HANDLING ----------- */
|
|
|
|
static const char *default_configfile =
|
|
#ifdef UNIX
|
|
".nethackrc";
|
|
#else
|
|
#if defined(MAC) || defined(__BEOS__)
|
|
"NetHack Defaults";
|
|
#else
|
|
#if defined(MSDOS) || defined(WIN32)
|
|
CONFIG_FILE;
|
|
#else
|
|
"NetHack.cnf";
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
/* used for messaging. Also used in options.c */
|
|
char configfile[BUFSZ];
|
|
|
|
#ifdef MSDOS
|
|
/* conflict with speed-dial under windows
|
|
* for XXX.cnf file so support of NetHack.cnf
|
|
* is for backward compatibility only.
|
|
* Preferred name (and first tried) is now defaults.nh but
|
|
* the game will try the old name if there
|
|
* is no defaults.nh.
|
|
*/
|
|
const char *backward_compat_configfile = "nethack.cnf";
|
|
#endif
|
|
|
|
/* #saveoptions - save config options into file */
|
|
int
|
|
do_write_config_file(void)
|
|
{
|
|
FILE *fp;
|
|
char tmp[BUFSZ];
|
|
|
|
if (!configfile[0]) {
|
|
pline("Strange, could not figure out config file name.");
|
|
return ECMD_OK;
|
|
}
|
|
if (flags.suppress_alert < FEATURE_NOTICE_VER(3,7,0)) {
|
|
pline("Warning: saveoptions is highly experimental!");
|
|
wait_synch();
|
|
pline("Some settings are not saved!");
|
|
wait_synch();
|
|
pline("All manual customization and comments are removed"
|
|
" from the file!");
|
|
wait_synch();
|
|
}
|
|
#define overwrite_prompt "Overwrite config file %.*s?"
|
|
Sprintf(tmp, overwrite_prompt,
|
|
(int) (BUFSZ - sizeof overwrite_prompt - 2), configfile);
|
|
#undef overwrite_prompt
|
|
if (!paranoid_query(TRUE, tmp))
|
|
return ECMD_OK;
|
|
|
|
fp = fopen(configfile, "w");
|
|
if (fp) {
|
|
size_t len, wrote;
|
|
strbuf_t buf;
|
|
|
|
strbuf_init(&buf);
|
|
all_options_strbuf(&buf);
|
|
len = strlen(buf.str);
|
|
wrote = fwrite(buf.str, 1, len, fp);
|
|
fclose(fp);
|
|
strbuf_empty(&buf);
|
|
if (wrote != len)
|
|
pline("An error occurred, wrote only partial data (%zu/%zu).",
|
|
wrote, len);
|
|
}
|
|
return ECMD_OK;
|
|
}
|
|
|
|
/* remember the name of the file we're accessing;
|
|
if may be used in option reject messages */
|
|
staticfn void
|
|
set_configfile_name(const char *fname)
|
|
{
|
|
(void) strncpy(configfile, fname, sizeof configfile - 1);
|
|
configfile[sizeof configfile - 1] = '\0';
|
|
}
|
|
|
|
staticfn FILE *
|
|
fopen_config_file(const char *filename, int src)
|
|
{
|
|
FILE *fp;
|
|
#if defined(UNIX) || defined(VMS)
|
|
char tmp_config[BUFSZ];
|
|
char *envp;
|
|
#endif
|
|
|
|
if (src == set_in_sysconf) {
|
|
/* SYSCF_FILE; if we can't open it, caller will bail */
|
|
if (filename && *filename) {
|
|
set_configfile_name(fqname(filename, SYSCONFPREFIX, 0));
|
|
fp = fopen(configfile, "r");
|
|
} else
|
|
fp = (FILE *) 0;
|
|
return fp;
|
|
}
|
|
/* If src != set_in_sysconf, "filename" is an environment variable, so it
|
|
* should hang around. If set, it is expected to be a full path name
|
|
* (if relevant)
|
|
*/
|
|
if (filename && *filename) {
|
|
set_configfile_name(filename);
|
|
#ifdef UNIX
|
|
if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */
|
|
/* nasty sneaky attempt to read file through
|
|
* NetHack's setuid permissions -- this is the only
|
|
* place a file name may be wholly under the player's
|
|
* control (but SYSCF_FILE is not under the player's
|
|
* control so it's OK).
|
|
*/
|
|
raw_printf("Access to %s denied (%d).", configfile, errno);
|
|
wait_synch();
|
|
/* fall through to standard names */
|
|
} else
|
|
#endif
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0) {
|
|
return fp;
|
|
#if defined(UNIX) || defined(VMS)
|
|
} else {
|
|
/* access() above probably caught most problems for UNIX */
|
|
raw_printf("Couldn't open requested config file %s (%d).",
|
|
configfile, errno);
|
|
wait_synch();
|
|
#endif
|
|
}
|
|
}
|
|
/* fall through to standard names */
|
|
|
|
#if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
|
|
set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0));
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0) {
|
|
return fp;
|
|
} else if (strcmp(default_configfile, configfile)) {
|
|
set_configfile_name(default_configfile);
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
}
|
|
#ifdef MSDOS
|
|
set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0));
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0) {
|
|
return fp;
|
|
} else if (strcmp(backward_compat_configfile, configfile)) {
|
|
set_configfile_name(backward_compat_configfile);
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
}
|
|
#endif
|
|
#else
|
|
/* constructed full path names don't need fqname() */
|
|
#ifdef VMS
|
|
/* no punctuation, so might be a logical name */
|
|
set_configfile_name("nethackini");
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
set_configfile_name("sys$login:nethack.ini");
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
|
|
envp = nh_getenv("HOME");
|
|
if (!envp || !*envp)
|
|
Strcpy(tmp_config, "NetHack.cnf");
|
|
else
|
|
Sprintf(tmp_config, "%s%s%s", envp,
|
|
!strchr(":]>/", envp[strlen(envp) - 1]) ? "/" : "",
|
|
"NetHack.cnf");
|
|
set_configfile_name(tmp_config);
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
#else /* should be only UNIX left */
|
|
envp = nh_getenv("HOME");
|
|
if (!envp)
|
|
Strcpy(tmp_config, ".nethackrc");
|
|
else
|
|
Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
|
|
|
|
set_configfile_name(tmp_config);
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
#if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX */
|
|
/* try an alternative */
|
|
if (envp) {
|
|
/* OSX-style configuration settings */
|
|
Sprintf(tmp_config, "%s/%s", envp,
|
|
"Library/Preferences/NetHack Defaults");
|
|
set_configfile_name(tmp_config);
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
/* may be easier for user to edit if filename has '.txt' suffix */
|
|
Sprintf(tmp_config, "%s/%s", envp,
|
|
"Library/Preferences/NetHack Defaults.txt");
|
|
set_configfile_name(tmp_config);
|
|
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
|
|
return fp;
|
|
}
|
|
#endif /*__APPLE__*/
|
|
if (errno != ENOENT) {
|
|
const char *details;
|
|
|
|
/* e.g., problems when setuid NetHack can't search home
|
|
directory restricted to user */
|
|
#if defined(NHSTDC) && !defined(NOTSTDC)
|
|
if ((details = strerror(errno)) == 0)
|
|
#endif
|
|
details = "";
|
|
raw_printf("Couldn't open default config file %s %s(%d).",
|
|
configfile, details, errno);
|
|
wait_synch();
|
|
}
|
|
#endif /* !VMS => Unix */
|
|
#endif /* !(MICRO || MAC || __BEOS__ || WIN32) */
|
|
return (FILE *) 0;
|
|
}
|
|
|
|
/*
|
|
* Retrieve a list of integers from buf into a uchar array.
|
|
*
|
|
* NOTE: zeros are inserted unless modlist is TRUE, in which case the list
|
|
* location is unchanged. Callers must handle zeros if modlist is FALSE.
|
|
*/
|
|
staticfn int
|
|
get_uchars(char *bufp, /* current pointer */
|
|
uchar *list, /* return list */
|
|
boolean modlist, /* TRUE: list is being modified in place */
|
|
int size, /* return list size */
|
|
const char *name) /* name of option for error message */
|
|
{
|
|
unsigned int num = 0;
|
|
int count = 0;
|
|
boolean havenum = FALSE;
|
|
|
|
while (1) {
|
|
switch (*bufp) {
|
|
case ' ':
|
|
case '\0':
|
|
case '\t':
|
|
case '\n':
|
|
if (havenum) {
|
|
/* if modifying in place, don't insert zeros */
|
|
if (num || !modlist)
|
|
list[count] = num;
|
|
count++;
|
|
num = 0;
|
|
havenum = FALSE;
|
|
}
|
|
if (count == size || !*bufp)
|
|
return count;
|
|
bufp++;
|
|
break;
|
|
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
havenum = TRUE;
|
|
num = num * 10 + (*bufp - '0');
|
|
bufp++;
|
|
break;
|
|
|
|
case '\\':
|
|
goto gi_error;
|
|
break;
|
|
|
|
default:
|
|
gi_error:
|
|
raw_printf("Syntax error in %s", name);
|
|
wait_synch();
|
|
return count;
|
|
}
|
|
}
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
staticfn void
|
|
adjust_prefix(char *bufp, int prefixid)
|
|
{
|
|
char *ptr;
|
|
|
|
if (!bufp)
|
|
return;
|
|
#ifdef WIN32
|
|
if (fqn_prefix_locked[prefixid])
|
|
return;
|
|
#endif
|
|
/* Backward compatibility, ignore trailing ;n */
|
|
if ((ptr = strchr(bufp, ';')) != 0)
|
|
*ptr = '\0';
|
|
if (strlen(bufp) > 0) {
|
|
gf.fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
|
|
Strcpy(gf.fqn_prefix[prefixid], bufp);
|
|
append_slash(gf.fqn_prefix[prefixid]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Choose at random one of the sep separated parts from str. Mangles str. */
|
|
staticfn char *
|
|
choose_random_part(char *str, char sep)
|
|
{
|
|
int nsep = 1;
|
|
int csep;
|
|
int len = 0;
|
|
char *begin = str;
|
|
|
|
if (!str)
|
|
return (char *) 0;
|
|
|
|
while (*str) {
|
|
if (*str == sep)
|
|
nsep++;
|
|
str++;
|
|
}
|
|
csep = rn2(nsep);
|
|
str = begin;
|
|
while ((csep > 0) && *str) {
|
|
str++;
|
|
if (*str == sep)
|
|
csep--;
|
|
}
|
|
if (*str) {
|
|
if (*str == sep)
|
|
str++;
|
|
begin = str;
|
|
while (*str && *str != sep) {
|
|
str++;
|
|
len++;
|
|
}
|
|
*str = '\0';
|
|
if (len)
|
|
return begin;
|
|
}
|
|
return (char *) 0;
|
|
}
|
|
|
|
staticfn void
|
|
free_config_sections(void)
|
|
{
|
|
if (gc.config_section_chosen) {
|
|
free(gc.config_section_chosen);
|
|
gc.config_section_chosen = NULL;
|
|
}
|
|
if (gc.config_section_current) {
|
|
free(gc.config_section_current);
|
|
gc.config_section_current = NULL;
|
|
}
|
|
}
|
|
|
|
/* check for " [ anything-except-bracket-or-empty ] # arbitrary-comment"
|
|
with spaces optional; returns pointer to "anything-except..." (with
|
|
trailing " ] #..." stripped) if ok, otherwise Null */
|
|
staticfn char *
|
|
is_config_section(
|
|
char *str) /* trailing spaces are stripped, ']' too iff result is good */
|
|
{
|
|
char *a, *c, *z;
|
|
|
|
/* remove any spaces at start and end; won't significantly interfere
|
|
with echoing the string in a config error message, if warranted */
|
|
a = trimspaces(str);
|
|
/* first character should be open square bracket; set pointer past it */
|
|
if (*a++ != '[')
|
|
return (char *) 0;
|
|
/* last character should be close bracket, ignoring any comment */
|
|
z = strchr(a, ']');
|
|
if (!z)
|
|
return (char *) 0;
|
|
/* comment, if present, can be preceded by spaces */
|
|
for (c = z + 1; *c == ' '; ++c)
|
|
continue;
|
|
if (*c && *c != '#')
|
|
return (char *) 0;
|
|
/* we now know that result is good; there won't be a config error
|
|
message so we can modify the input string */
|
|
*z = '\0';
|
|
/* 'a' points past '[' and the string ends where ']' was; remove any
|
|
spaces between '[' and choice-start and between choice-end and ']' */
|
|
return trimspaces(a);
|
|
}
|
|
|
|
staticfn boolean
|
|
handle_config_section(char *buf)
|
|
{
|
|
char *sect = is_config_section(buf);
|
|
|
|
if (sect) {
|
|
if (gc.config_section_current)
|
|
free(gc.config_section_current), gc.config_section_current = 0;
|
|
/* is_config_section() removed brackets from 'sect' */
|
|
if (!gc.config_section_chosen) {
|
|
config_error_add("Section \"[%s]\" without CHOOSE", sect);
|
|
return TRUE;
|
|
}
|
|
if (*sect) { /* got a section name */
|
|
gc.config_section_current = dupstr(sect);
|
|
debugpline1("set config section: '%s'",
|
|
gc.config_section_current);
|
|
} else { /* empty section name => end of sections */
|
|
free_config_sections();
|
|
debugpline0("unset config section");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if (gc.config_section_current) {
|
|
if (!gc.config_section_chosen)
|
|
return TRUE;
|
|
if (strcmp(gc.config_section_current, gc.config_section_chosen))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
|
|
|
|
/* find the '=' or ':' */
|
|
staticfn char *
|
|
find_optparam(const char *buf)
|
|
{
|
|
char *bufp, *altp;
|
|
|
|
bufp = strchr(buf, '=');
|
|
altp = strchr(buf, ':');
|
|
if (!bufp || (altp && altp < bufp))
|
|
bufp = altp;
|
|
|
|
return bufp;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_OPTIONS(char *origbuf)
|
|
{
|
|
char *bufp = find_optparam(origbuf);
|
|
|
|
++bufp; /* skip '='; parseoptions() handles spaces */
|
|
return parseoptions(bufp, TRUE, TRUE);
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_AUTOPICKUP_EXCEPTION(char *bufp)
|
|
{
|
|
add_autopickup_exception(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_BINDINGS(char *bufp)
|
|
{
|
|
return parsebindings(bufp);
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_AUTOCOMPLETE(char *bufp)
|
|
{
|
|
parseautocomplete(bufp, TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_MSGTYPE(char *bufp)
|
|
{
|
|
return msgtype_parse_add(bufp);
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_HACKDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, HACKPREFIX);
|
|
#else /*NOCWD_ASSUMPTIONS*/
|
|
#ifdef MICRO
|
|
(void) strncpy(gh.hackdir, bufp, PATHLEN - 1);
|
|
#else /* MICRO */
|
|
nhUse(bufp);
|
|
#endif /* MICRO */
|
|
#endif /*NOCWD_ASSUMPTIONS*/
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_LEVELDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, LEVELPREFIX);
|
|
#else /*NOCWD_ASSUMPTIONS*/
|
|
#ifdef MICRO
|
|
if (strlen(bufp) >= PATHLEN)
|
|
bufp[PATHLEN - 1] = '\0';
|
|
Strcpy(g.permbones, bufp);
|
|
if (!ramdisk_specified || !*levels)
|
|
Strcpy(levels, bufp);
|
|
gr.ramdisk = (strcmp(g.permbones, levels) != 0);
|
|
#else /* MICRO */
|
|
nhUse(bufp);
|
|
#endif /* MICRO */
|
|
#endif /*NOCWD_ASSUMPTIONS*/
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SAVEDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, SAVEPREFIX);
|
|
#else /*NOCWD_ASSUMPTIONS*/
|
|
#ifdef MICRO
|
|
char *ptr;
|
|
|
|
if ((ptr = strchr(bufp, ';')) != 0) {
|
|
*ptr = '\0';
|
|
}
|
|
|
|
(void) strncpy(gs.SAVEP, bufp, SAVESIZE - 1);
|
|
append_slash(gs.SAVEP);
|
|
#else /* MICRO */
|
|
nhUse(bufp);
|
|
#endif /* MICRO */
|
|
#endif /*NOCWD_ASSUMPTIONS*/
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_BONESDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, BONESPREFIX);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_DATADIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, DATAPREFIX);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SCOREDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, SCOREPREFIX);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_LOCKDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, LOCKPREFIX);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_CONFIGDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, CONFIGPREFIX);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_TROUBLEDIR(char *bufp)
|
|
{
|
|
#ifdef NOCWD_ASSUMPTIONS
|
|
adjust_prefix(bufp, TROUBLEPREFIX);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_NAME(char *bufp)
|
|
{
|
|
(void) strncpy(svp.plname, bufp, PL_NSIZ - 1);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_ROLE(char *bufp)
|
|
{
|
|
int len;
|
|
|
|
if ((len = str2role(bufp)) >= 0)
|
|
flags.initrole = len;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_dogname(char *bufp)
|
|
{
|
|
(void) strncpy(gd.dogname, bufp, PL_PSIZ - 1);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_catname(char *bufp)
|
|
{
|
|
(void) strncpy(gc.catname, bufp, PL_PSIZ - 1);
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef SYSCF
|
|
|
|
staticfn boolean
|
|
cnf_line_WIZARDS(char *bufp)
|
|
{
|
|
if (sysopt.wizards)
|
|
free((genericptr_t) sysopt.wizards);
|
|
sysopt.wizards = dupstr(bufp);
|
|
if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
|
|
/* pre-format WIZARDS list now; it's displayed during a panic
|
|
and since that panic might be due to running out of memory,
|
|
we don't want to risk attempting to allocate any memory then */
|
|
if (sysopt.fmtd_wizard_list)
|
|
free((genericptr_t) sysopt.fmtd_wizard_list);
|
|
sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SHELLERS(char *bufp)
|
|
{
|
|
if (sysopt.shellers)
|
|
free((genericptr_t) sysopt.shellers);
|
|
sysopt.shellers = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_MSGHANDLER(char *bufp)
|
|
{
|
|
if (sysopt.msghandler)
|
|
free((genericptr_t) sysopt.msghandler);
|
|
sysopt.msghandler = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_EXPLORERS(char *bufp)
|
|
{
|
|
if (sysopt.explorers)
|
|
free((genericptr_t) sysopt.explorers);
|
|
sysopt.explorers = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_DEBUGFILES(char *bufp)
|
|
{
|
|
/* might already have a vaule from getenv("DEBUGFILES");
|
|
if so, ignore this value from SYSCF */
|
|
if (!sysopt.env_dbgfl) {
|
|
if (sysopt.debugfiles)
|
|
free((genericptr_t) sysopt.debugfiles);
|
|
sysopt.debugfiles = dupstr(bufp);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_DUMPLOGFILE(char *bufp)
|
|
{
|
|
#ifdef DUMPLOG
|
|
if (sysopt.dumplogfile)
|
|
free((genericptr_t) sysopt.dumplogfile);
|
|
sysopt.dumplogfile = dupstr(bufp);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif /*DUMPLOG*/
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_GENERICUSERS(char *bufp)
|
|
{
|
|
if (sysopt.genericusers)
|
|
free((genericptr_t) sysopt.genericusers);
|
|
sysopt.genericusers = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_BONES_POOLS(char *bufp)
|
|
{
|
|
/* max value of 10 guarantees (N % bones.pools) will be one digit
|
|
so we don't lose control of the length of bones file names */
|
|
int n = atoi(bufp);
|
|
|
|
sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10);
|
|
/* note: right now bones_pools==0 is the same as bones_pools==1,
|
|
but we could change that and make bones_pools==0 become an
|
|
indicator to suppress bones usage altogether */
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SUPPORT(char *bufp)
|
|
{
|
|
if (sysopt.support)
|
|
free((genericptr_t) sysopt.support);
|
|
sysopt.support = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_RECOVER(char *bufp)
|
|
{
|
|
if (sysopt.recover)
|
|
free((genericptr_t) sysopt.recover);
|
|
sysopt.recover = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_CHECK_SAVE_UID(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
sysopt.check_save_uid = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_CHECK_PLNAME(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
sysopt.check_plname = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SEDUCE(char *bufp)
|
|
{
|
|
int n = !!atoi(bufp); /* XXX this could be tighter */
|
|
#ifdef SYSCF
|
|
int src = iflags.parse_config_file_src;
|
|
boolean in_sysconf = (src == set_in_sysconf);
|
|
#else
|
|
boolean in_sysconf = FALSE;
|
|
#endif
|
|
|
|
/* allow anyone to disable it but can only enable it in sysconf
|
|
or as a no-op for the user when sysconf hasn't disabled it */
|
|
if (!in_sysconf && !sysopt.seduce && n != 0) {
|
|
config_error_add("Illegal value in SEDUCE");
|
|
n = 0;
|
|
}
|
|
sysopt.seduce = n;
|
|
sysopt_seduce_set(sysopt.seduce);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_HIDEUSAGE(char *bufp)
|
|
{
|
|
int n = !!atoi(bufp);
|
|
|
|
sysopt.hideusage = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_MAXPLAYERS(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
/* XXX to get more than 25, need to rewrite all lock code */
|
|
if (n < 0 || n > 25) {
|
|
config_error_add("Illegal value in MAXPLAYERS (maximum is 25)");
|
|
n = 5;
|
|
}
|
|
sysopt.maxplayers = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_PERSMAX(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
if (n < 1) {
|
|
config_error_add("Illegal value in PERSMAX (minimum is 1)");
|
|
n = 0;
|
|
}
|
|
sysopt.persmax = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_PERS_IS_UID(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
if (n != 0 && n != 1) {
|
|
config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1)");
|
|
n = 0;
|
|
}
|
|
sysopt.pers_is_uid = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_ENTRYMAX(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
if (n < 10) {
|
|
config_error_add("Illegal value in ENTRYMAX (minimum is 10)");
|
|
n = 10;
|
|
}
|
|
sysopt.entrymax = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_POINTSMIN(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
if (n < 1) {
|
|
config_error_add("Illegal value in POINTSMIN (minimum is 1)");
|
|
n = 100;
|
|
}
|
|
sysopt.pointsmin = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_MAX_STATUENAME_RANK(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
if (n < 1) {
|
|
config_error_add("Illegal value in MAX_STATUENAME_RANK"
|
|
" (minimum is 1)");
|
|
n = 10;
|
|
}
|
|
sysopt.tt_oname_maxrank = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_LIVELOG(char *bufp)
|
|
{
|
|
/* using 0 for base accepts "dddd" as decimal provided that first 'd'
|
|
isn't '0', "0xhhhh" as hexadecimal, and "0oooo" as octal; ignores
|
|
any trailing junk, including '8' or '9' for leading '0' octal */
|
|
long L = strtol(bufp, NULL, 0);
|
|
|
|
if (L < 0L || L > 0xffffL) {
|
|
config_error_add("Illegal value for LIVELOG"
|
|
" (must be between 0 and 0xFFFF).");
|
|
return 0;
|
|
}
|
|
sysopt.livelog = L;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_PANICTRACE_LIBC(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
#if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
|
|
if (n < 0 || n > 2) {
|
|
config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2)");
|
|
n = 0;
|
|
}
|
|
#endif
|
|
sysopt.panictrace_libc = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_PANICTRACE_GDB(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
#if defined(PANICTRACE)
|
|
if (n < 0 || n > 2) {
|
|
config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2)");
|
|
n = 0;
|
|
}
|
|
#endif
|
|
sysopt.panictrace_gdb = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_GDBPATH(char *bufp)
|
|
{
|
|
#if defined(PANICTRACE) && !defined(VMS)
|
|
if (!file_exists(bufp)) {
|
|
config_error_add("File specified in GDBPATH does not exist");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
if (sysopt.gdbpath)
|
|
free((genericptr_t) sysopt.gdbpath);
|
|
sysopt.gdbpath = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_GREPPATH(char *bufp)
|
|
{
|
|
#if defined(PANICTRACE) && !defined(VMS)
|
|
if (!file_exists(bufp)) {
|
|
config_error_add("File specified in GREPPATH does not exist");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
if (sysopt.greppath)
|
|
free((genericptr_t) sysopt.greppath);
|
|
sysopt.greppath = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_CRASHREPORTURL(char *bufp)
|
|
{
|
|
if (sysopt.crashreporturl)
|
|
free((genericptr_t) sysopt.crashreporturl);
|
|
sysopt.crashreporturl = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SAVEFORMAT(char *bufp)
|
|
{
|
|
parseformat(sysopt.saveformat, bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_BONESFORMAT(char *bufp)
|
|
{
|
|
parseformat(sysopt.bonesformat, bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_ACCESSIBILITY(char *bufp)
|
|
{
|
|
int n = atoi(bufp);
|
|
|
|
if (n < 0 || n > 1) {
|
|
config_error_add("Illegal value in ACCESSIBILITY (not 0,1)");
|
|
n = 0;
|
|
}
|
|
sysopt.accessibility = n;
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_PORTABLE_DEVICE_PATHS(char *bufp)
|
|
{
|
|
#ifdef WIN32
|
|
int n = atoi(bufp);
|
|
|
|
if (n < 0 || n > 1) {
|
|
config_error_add("Illegal value in PORTABLE_DEVICE_PATHS"
|
|
" (not 0 or 1)");
|
|
n = 0;
|
|
}
|
|
sysopt.portable_device_paths = n;
|
|
#else /* Windows-only directive encountered by non-Windows config */
|
|
nhUse(bufp);
|
|
config_error_add("PORTABLE_DEVICE_PATHS is not supported");
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
#endif /* SYSCF */
|
|
|
|
staticfn boolean
|
|
cnf_line_BOULDER(char *bufp)
|
|
{
|
|
(void) get_uchars(bufp, &go.ov_primary_syms[SYM_BOULDER + SYM_OFF_X],
|
|
TRUE, 1, "BOULDER");
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_MENUCOLOR(char *bufp)
|
|
{
|
|
return add_menu_coloring(bufp);
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_HILITE_STATUS(char *bufp)
|
|
{
|
|
#ifdef STATUS_HILITES
|
|
return parse_status_hl1(bufp, TRUE);
|
|
#else
|
|
nhUse(bufp);
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_WARNINGS(char *bufp)
|
|
{
|
|
uchar translate[MAXPCHARS];
|
|
|
|
(void) get_uchars(bufp, translate, FALSE, WARNCOUNT, "WARNINGS");
|
|
assign_warnings(translate);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_ROGUESYMBOLS(char *bufp)
|
|
{
|
|
if (parsesymbols(bufp, ROGUESET)) {
|
|
switch_symbols(TRUE);
|
|
return TRUE;
|
|
}
|
|
config_error_add("Error in ROGUESYMBOLS definition '%s'", bufp);
|
|
return FALSE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SYMBOLS(char *bufp)
|
|
{
|
|
if (parsesymbols(bufp, PRIMARYSET)) {
|
|
switch_symbols(TRUE);
|
|
return TRUE;
|
|
}
|
|
config_error_add("Error in SYMBOLS definition '%s'", bufp);
|
|
return FALSE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_WIZKIT(char *bufp)
|
|
{
|
|
(void) strncpy(gw.wizkit, bufp, WIZKIT_MAX - 1);
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef USER_SOUNDS
|
|
staticfn boolean
|
|
cnf_line_SOUNDDIR(char *bufp)
|
|
{
|
|
if (sounddir)
|
|
free((genericptr_t) sounddir);
|
|
sounddir = dupstr(bufp);
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_SOUND(char *bufp)
|
|
{
|
|
add_sound_mapping(bufp);
|
|
return TRUE;
|
|
}
|
|
#endif /*USER_SOUNDS*/
|
|
|
|
staticfn boolean
|
|
cnf_line_QT_TILEWIDTH(char *bufp)
|
|
{
|
|
#ifdef QT_GRAPHICS
|
|
extern char *qt_tilewidth;
|
|
|
|
if (qt_tilewidth == NULL)
|
|
qt_tilewidth = dupstr(bufp);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_QT_TILEHEIGHT(char *bufp)
|
|
{
|
|
#ifdef QT_GRAPHICS
|
|
extern char *qt_tileheight;
|
|
|
|
if (qt_tileheight == NULL)
|
|
qt_tileheight = dupstr(bufp);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_QT_FONTSIZE(char *bufp)
|
|
{
|
|
#ifdef QT_GRAPHICS
|
|
extern char *qt_fontsize;
|
|
|
|
if (qt_fontsize == NULL)
|
|
qt_fontsize = dupstr(bufp);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
cnf_line_QT_COMPACT(char *bufp)
|
|
{
|
|
#ifdef QT_GRAPHICS
|
|
extern int qt_compact_mode;
|
|
|
|
qt_compact_mode = atoi(bufp);
|
|
#else
|
|
nhUse(bufp);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
typedef boolean (*config_line_stmt_func)(char *);
|
|
|
|
/* normal */
|
|
#define CNFL_N(n, l) { #n, l, FALSE, FALSE, cnf_line_##n }
|
|
/* normal, alias */
|
|
#define CNFL_NA(n, l, f) { #n, l, FALSE, FALSE, cnf_line_##f }
|
|
/* sysconf only */
|
|
#define CNFL_S(n, l) { #n, l, TRUE, FALSE, cnf_line_##n }
|
|
|
|
static const struct match_config_line_stmt {
|
|
const char *name;
|
|
int len;
|
|
boolean syscnf_only;
|
|
boolean origbuf;
|
|
config_line_stmt_func fn;
|
|
} config_line_stmt[] = {
|
|
/* OPTIONS handled separately */
|
|
{ "OPTIONS", 4, FALSE, TRUE, cnf_line_OPTIONS },
|
|
CNFL_N(AUTOPICKUP_EXCEPTION, 5),
|
|
CNFL_N(BINDINGS, 4),
|
|
CNFL_N(AUTOCOMPLETE, 5),
|
|
CNFL_N(MSGTYPE, 7),
|
|
CNFL_N(HACKDIR, 4),
|
|
CNFL_N(LEVELDIR, 4),
|
|
CNFL_NA(LEVELS, 4, LEVELDIR),
|
|
CNFL_N(SAVEDIR, 4),
|
|
CNFL_N(BONESDIR, 5),
|
|
CNFL_N(DATADIR, 4),
|
|
CNFL_N(SCOREDIR, 4),
|
|
CNFL_N(LOCKDIR, 4),
|
|
CNFL_N(CONFIGDIR, 4),
|
|
CNFL_N(TROUBLEDIR, 4),
|
|
CNFL_N(NAME, 4),
|
|
CNFL_N(ROLE, 4),
|
|
CNFL_NA(CHARACTER, 4, ROLE),
|
|
CNFL_N(dogname, 3),
|
|
CNFL_N(catname, 3),
|
|
#ifdef SYSCF
|
|
CNFL_S(WIZARDS, 7),
|
|
CNFL_S(SHELLERS, 8),
|
|
CNFL_S(MSGHANDLER, 9),
|
|
CNFL_S(EXPLORERS, 7),
|
|
CNFL_S(DEBUGFILES, 5),
|
|
CNFL_S(DUMPLOGFILE, 7),
|
|
CNFL_S(GENERICUSERS, 12),
|
|
CNFL_S(BONES_POOLS, 10),
|
|
CNFL_S(SUPPORT, 7),
|
|
CNFL_S(RECOVER, 7),
|
|
CNFL_S(CHECK_SAVE_UID, 14),
|
|
CNFL_S(CHECK_PLNAME, 12),
|
|
CNFL_S(SEDUCE, 6),
|
|
CNFL_S(HIDEUSAGE, 9),
|
|
CNFL_S(MAXPLAYERS, 10),
|
|
CNFL_S(PERSMAX, 7),
|
|
CNFL_S(PERS_IS_UID, 11),
|
|
CNFL_S(ENTRYMAX, 8),
|
|
CNFL_S(POINTSMIN, 9),
|
|
CNFL_S(MAX_STATUENAME_RANK, 10),
|
|
CNFL_S(LIVELOG, 7),
|
|
CNFL_S(PANICTRACE_LIBC, 15),
|
|
CNFL_S(PANICTRACE_GDB, 14),
|
|
CNFL_S(CRASHREPORTURL, 13),
|
|
CNFL_S(GDBPATH, 7),
|
|
CNFL_S(GREPPATH, 7),
|
|
CNFL_S(SAVEFORMAT, 10),
|
|
CNFL_S(BONESFORMAT, 11),
|
|
CNFL_S(ACCESSIBILITY, 13),
|
|
CNFL_S(PORTABLE_DEVICE_PATHS, 8),
|
|
#endif /*SYSCF*/
|
|
CNFL_N(BOULDER, 3),
|
|
CNFL_N(MENUCOLOR, 9),
|
|
CNFL_N(HILITE_STATUS, 6),
|
|
CNFL_N(WARNINGS, 5),
|
|
CNFL_N(ROGUESYMBOLS, 4),
|
|
CNFL_N(SYMBOLS, 4),
|
|
CNFL_N(WIZKIT, 6),
|
|
#ifdef USER_SOUNDS
|
|
CNFL_N(SOUNDDIR, 8),
|
|
CNFL_N(SOUND, 5),
|
|
#endif /*USER_SOUNDS*/
|
|
CNFL_N(QT_TILEWIDTH, 12),
|
|
CNFL_N(QT_TILEHEIGHT, 13),
|
|
CNFL_N(QT_FONTSIZE, 11),
|
|
CNFL_N(QT_COMPACT, 10)
|
|
};
|
|
|
|
#undef CNFL_N
|
|
#undef CNFL_NA
|
|
#undef CNFL_S
|
|
|
|
boolean
|
|
parse_config_line(char *origbuf)
|
|
{
|
|
#if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
|
|
static boolean ramdisk_specified = FALSE;
|
|
#endif
|
|
#ifdef SYSCF
|
|
int src = iflags.parse_config_file_src;
|
|
boolean in_sysconf = (src == set_in_sysconf);
|
|
#endif
|
|
char *bufp, buf[4 * BUFSZ];
|
|
int i;
|
|
|
|
while (*origbuf == ' ' || *origbuf == '\t') /* skip leading whitespace */
|
|
++origbuf; /* (caller probably already did this) */
|
|
(void) strncpy(buf, origbuf, sizeof buf - 1);
|
|
buf[sizeof buf - 1] = '\0'; /* strncpy not guaranteed to NUL terminate */
|
|
/* convert any tab to space, condense consecutive spaces into one,
|
|
remove leading and trailing spaces (exception: if there is nothing
|
|
but spaces, one of them will be kept even though it leads/trails) */
|
|
mungspaces(buf);
|
|
|
|
/* find the '=' or ':' */
|
|
bufp = find_optparam(buf);
|
|
if (!bufp) {
|
|
config_error_add("Not a config statement, missing '='");
|
|
return FALSE;
|
|
}
|
|
/* skip past '=', then space between it and value, if any */
|
|
++bufp;
|
|
if (*bufp == ' ')
|
|
++bufp;
|
|
|
|
for (i = 0; i < SIZE(config_line_stmt); i++) {
|
|
#ifdef SYSCF
|
|
if (config_line_stmt[i].syscnf_only && !in_sysconf)
|
|
continue;
|
|
#endif
|
|
if (match_varname(buf, config_line_stmt[i].name,
|
|
config_line_stmt[i].len)) {
|
|
char *parm = config_line_stmt[i].origbuf ? origbuf : bufp;
|
|
|
|
return config_line_stmt[i].fn(parm);
|
|
}
|
|
}
|
|
|
|
config_error_add("Unknown config statement");
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef USER_SOUNDS
|
|
boolean
|
|
can_read_file(const char *filename)
|
|
{
|
|
return (boolean) (access(filename, 4) == 0);
|
|
}
|
|
#endif /* USER_SOUNDS */
|
|
|
|
struct _config_error_errmsg {
|
|
int line_num;
|
|
char *errormsg;
|
|
struct _config_error_errmsg *next;
|
|
};
|
|
|
|
struct _config_error_frame {
|
|
int line_num;
|
|
int num_errors;
|
|
boolean origline_shown;
|
|
boolean fromfile;
|
|
boolean secure;
|
|
char origline[4 * BUFSZ];
|
|
char source[BUFSZ];
|
|
struct _config_error_frame *next;
|
|
};
|
|
|
|
static struct _config_error_frame *config_error_data = 0;
|
|
static struct _config_error_errmsg *config_error_msg = 0;
|
|
|
|
void
|
|
config_error_init(boolean from_file, const char *sourcename, boolean secure)
|
|
{
|
|
struct _config_error_frame *tmp = (struct _config_error_frame *)
|
|
alloc(sizeof *tmp);
|
|
|
|
tmp->line_num = 0;
|
|
tmp->num_errors = 0;
|
|
tmp->origline_shown = FALSE;
|
|
tmp->fromfile = from_file;
|
|
tmp->secure = secure;
|
|
tmp->origline[0] = '\0';
|
|
if (sourcename && sourcename[0]) {
|
|
(void) strncpy(tmp->source, sourcename, sizeof (tmp->source) - 1);
|
|
tmp->source[sizeof (tmp->source) - 1] = '\0';
|
|
} else
|
|
tmp->source[0] = '\0';
|
|
|
|
tmp->next = config_error_data;
|
|
config_error_data = tmp;
|
|
program_state.config_error_ready = TRUE;
|
|
}
|
|
|
|
staticfn boolean
|
|
config_error_nextline(const char *line)
|
|
{
|
|
struct _config_error_frame *ced = config_error_data;
|
|
|
|
if (!ced)
|
|
return FALSE;
|
|
|
|
if (ced->num_errors && ced->secure)
|
|
return FALSE;
|
|
|
|
ced->line_num++;
|
|
ced->origline_shown = FALSE;
|
|
if (line && line[0]) {
|
|
(void) strncpy(ced->origline, line, sizeof (ced->origline) - 1);
|
|
ced->origline[sizeof (ced->origline) - 1] = '\0';
|
|
} else
|
|
ced->origline[0] = '\0';
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
l_get_config_errors(lua_State *L)
|
|
{
|
|
struct _config_error_errmsg *dat = config_error_msg;
|
|
struct _config_error_errmsg *tmp;
|
|
int idx = 1;
|
|
|
|
lua_newtable(L);
|
|
|
|
while (dat) {
|
|
lua_pushinteger(L, idx++);
|
|
lua_newtable(L);
|
|
nhl_add_table_entry_int(L, "line", dat->line_num);
|
|
nhl_add_table_entry_str(L, "error", dat->errormsg);
|
|
lua_settable(L, -3);
|
|
tmp = dat->next;
|
|
free(dat->errormsg);
|
|
dat->errormsg = (char *) 0;
|
|
free(dat);
|
|
dat = tmp;
|
|
}
|
|
config_error_msg = (struct _config_error_errmsg *) 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* varargs 'config_error_add()' moved to pline.c */
|
|
void
|
|
config_erradd(const char *buf)
|
|
{
|
|
char lineno[QBUFSZ];
|
|
const char *punct;
|
|
|
|
if (!buf || !*buf)
|
|
buf = "Unknown error";
|
|
|
|
/* if buf[] doesn't end in a period, exclamation point, or question mark,
|
|
we'll include a period (in the message, not appended to buf[]) */
|
|
punct = c_eos((char *) buf) - 1; /* eos(buf)-1 is valid */
|
|
punct = strchr(".!?", *punct) ? "" : ".";
|
|
|
|
if (!program_state.config_error_ready) {
|
|
/* either very early, where pline() will use raw_print(), or
|
|
player gave bad value when prompted by interactive 'O' command */
|
|
pline("%s%s%s", !iflags.window_inited ? "config_error_add: " : "",
|
|
buf, punct);
|
|
wait_synch();
|
|
return;
|
|
}
|
|
|
|
if (iflags.in_lua) {
|
|
struct _config_error_errmsg *dat
|
|
= (struct _config_error_errmsg *) alloc(sizeof *dat);
|
|
|
|
dat->next = config_error_msg;
|
|
dat->line_num = config_error_data->line_num;
|
|
dat->errormsg = dupstr(buf);
|
|
config_error_msg = dat;
|
|
return;
|
|
}
|
|
|
|
config_error_data->num_errors++;
|
|
if (!config_error_data->origline_shown && !config_error_data->secure) {
|
|
pline("\n%s", config_error_data->origline);
|
|
config_error_data->origline_shown = TRUE;
|
|
}
|
|
if (config_error_data->line_num > 0 && !config_error_data->secure) {
|
|
Sprintf(lineno, "Line %d: ", config_error_data->line_num);
|
|
} else
|
|
lineno[0] = '\0';
|
|
|
|
pline("%s %s%s%s", config_error_data->secure ? "Error:" : " *",
|
|
lineno, buf, punct);
|
|
}
|
|
|
|
int
|
|
config_error_done(void)
|
|
{
|
|
int n;
|
|
struct _config_error_frame *tmp = config_error_data;
|
|
|
|
if (!config_error_data)
|
|
return 0;
|
|
n = config_error_data->num_errors;
|
|
#ifndef USER_SOUNDS
|
|
if (gn.no_sound_notified > 0) {
|
|
/* no USER_SOUNDS; config_error_add() was called once for first
|
|
SOUND or SOUNDDIR entry seen, then skipped for any others;
|
|
include those skipped ones in the total error count */
|
|
n += (gn.no_sound_notified - 1);
|
|
gn.no_sound_notified = 0;
|
|
}
|
|
#endif
|
|
if (n) {
|
|
boolean cmdline = !strcmp(config_error_data->source, "command line");
|
|
|
|
pline("\n%d error%s %s %s.\n", n, plur(n), cmdline ? "on" : "in",
|
|
*config_error_data->source ? config_error_data->source
|
|
: configfile);
|
|
wait_synch();
|
|
}
|
|
config_error_data = tmp->next;
|
|
free(tmp);
|
|
program_state.config_error_ready = (config_error_data != 0);
|
|
return n;
|
|
}
|
|
|
|
boolean
|
|
read_config_file(const char *filename, int src)
|
|
{
|
|
FILE *fp;
|
|
boolean rv = TRUE;
|
|
|
|
if (!(fp = fopen_config_file(filename, src)))
|
|
return FALSE;
|
|
|
|
/* begin detection of duplicate configfile options */
|
|
reset_duplicate_opt_detection();
|
|
free_config_sections();
|
|
iflags.parse_config_file_src = src;
|
|
|
|
rv = parse_conf_file(fp, parse_config_line);
|
|
(void) fclose(fp);
|
|
|
|
free_config_sections();
|
|
/* turn off detection of duplicate configfile options */
|
|
reset_duplicate_opt_detection();
|
|
return rv;
|
|
}
|
|
|
|
struct _cnf_parser_state {
|
|
char *inbuf;
|
|
unsigned inbufsz;
|
|
int rv;
|
|
char *ep;
|
|
char *buf;
|
|
boolean skip, morelines;
|
|
boolean cont;
|
|
boolean pbreak;
|
|
};
|
|
|
|
/* Initialize config parser data */
|
|
staticfn void
|
|
cnf_parser_init(struct _cnf_parser_state *parser)
|
|
{
|
|
parser->rv = TRUE; /* assume successful parse */
|
|
parser->ep = parser->buf = (char *) 0;
|
|
parser->skip = FALSE;
|
|
parser->morelines = FALSE;
|
|
parser->inbufsz = 4 * BUFSZ;
|
|
parser->inbuf = (char *) alloc(parser->inbufsz);
|
|
parser->cont = FALSE;
|
|
parser->pbreak = FALSE;
|
|
memset(parser->inbuf, 0, parser->inbufsz);
|
|
}
|
|
|
|
/* caller has finished with 'parser' (except for 'rv' so leave that intact) */
|
|
staticfn void
|
|
cnf_parser_done(struct _cnf_parser_state *parser)
|
|
{
|
|
parser->ep = 0; /* points into parser->inbuf, so becoming stale */
|
|
if (parser->inbuf)
|
|
free(parser->inbuf), parser->inbuf = 0;
|
|
if (parser->buf)
|
|
free(parser->buf), parser->buf = 0;
|
|
}
|
|
|
|
/*
|
|
* Parse config buffer, handling comments, empty lines, config sections,
|
|
* CHOOSE, and line continuation, calling proc for every valid line.
|
|
*
|
|
* Continued lines are merged together with one space in between.
|
|
*/
|
|
staticfn void
|
|
parse_conf_buf(struct _cnf_parser_state *p, boolean (*proc)(char *arg))
|
|
{
|
|
p->cont = FALSE;
|
|
p->pbreak = FALSE;
|
|
p->ep = strchr(p->inbuf, '\n');
|
|
if (p->skip) { /* in case previous line was too long */
|
|
if (p->ep)
|
|
p->skip = FALSE; /* found newline; next line is normal */
|
|
} else {
|
|
if (!p->ep) { /* newline missing */
|
|
if (strlen(p->inbuf) < (p->inbufsz - 2)) {
|
|
/* likely the last line of file is just
|
|
missing a newline; process it anyway */
|
|
p->ep = eos(p->inbuf);
|
|
} else {
|
|
config_error_add("Line too long, skipping");
|
|
p->skip = TRUE; /* discard next fgets */
|
|
}
|
|
} else {
|
|
*p->ep = '\0'; /* remove newline */
|
|
}
|
|
if (p->ep) {
|
|
char *tmpbuf = (char *) 0;
|
|
int len;
|
|
boolean ignoreline = FALSE;
|
|
boolean oldline = FALSE;
|
|
|
|
/* line continuation (trailing '\') */
|
|
p->morelines = (--p->ep >= p->inbuf && *p->ep == '\\');
|
|
if (p->morelines)
|
|
*p->ep = '\0';
|
|
|
|
/* trim off spaces at end of line */
|
|
while (p->ep >= p->inbuf
|
|
&& (*p->ep == ' ' || *p->ep == '\t' || *p->ep == '\r'))
|
|
*p->ep-- = '\0';
|
|
|
|
if (!config_error_nextline(p->inbuf)) {
|
|
p->rv = FALSE;
|
|
if (p->buf)
|
|
free(p->buf), p->buf = (char *) 0;
|
|
p->pbreak = TRUE;
|
|
return;
|
|
}
|
|
|
|
p->ep = p->inbuf;
|
|
while (*p->ep == ' ' || *p->ep == '\t')
|
|
++p->ep;
|
|
|
|
/* ignore empty lines and full-line comment lines */
|
|
if (!*p->ep || *p->ep == '#')
|
|
ignoreline = TRUE;
|
|
|
|
if (p->buf)
|
|
oldline = TRUE;
|
|
|
|
/* merge now read line with previous ones, if necessary */
|
|
if (!ignoreline) {
|
|
len = (int) strlen(p->ep) + 1; /* +1: final '\0' */
|
|
if (p->buf)
|
|
len += (int) strlen(p->buf) + 1; /* +1: space */
|
|
tmpbuf = (char *) alloc(len);
|
|
*tmpbuf = '\0';
|
|
if (p->buf) {
|
|
Strcat(strcpy(tmpbuf, p->buf), " ");
|
|
free(p->buf), p->buf = 0;
|
|
}
|
|
p->buf = strcat(tmpbuf, p->ep);
|
|
if (strlen(p->buf) >= p->inbufsz)
|
|
p->buf[p->inbufsz - 1] = '\0';
|
|
}
|
|
|
|
if (p->morelines || (ignoreline && !oldline))
|
|
return;
|
|
|
|
if (handle_config_section(p->buf)) {
|
|
free(p->buf), p->buf = (char *) 0;
|
|
return;
|
|
}
|
|
|
|
/* from here onwards, we'll handle buf only */
|
|
|
|
if (match_varname(p->buf, "CHOOSE", 6)) {
|
|
char *section;
|
|
char *bufp = find_optparam(p->buf);
|
|
|
|
if (!bufp) {
|
|
config_error_add("Format is CHOOSE=section1"
|
|
",section2,...");
|
|
p->rv = FALSE;
|
|
free(p->buf), p->buf = (char *) 0;
|
|
return;
|
|
}
|
|
bufp++;
|
|
if (gc.config_section_chosen)
|
|
free(gc.config_section_chosen),
|
|
gc.config_section_chosen = 0;
|
|
section = choose_random_part(bufp, ',');
|
|
if (section) {
|
|
gc.config_section_chosen = dupstr(section);
|
|
} else {
|
|
config_error_add("No config section to choose");
|
|
p->rv = FALSE;
|
|
}
|
|
free(p->buf), p->buf = (char *) 0;
|
|
return;
|
|
}
|
|
|
|
if (!(*proc)(p->buf))
|
|
p->rv = FALSE;
|
|
|
|
free(p->buf), p->buf = (char *) 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
boolean
|
|
parse_conf_str(const char *str, boolean (*proc)(char *arg))
|
|
{
|
|
size_t len;
|
|
struct _cnf_parser_state parser;
|
|
|
|
cnf_parser_init(&parser);
|
|
free_config_sections();
|
|
config_error_init(FALSE, "parse_conf_str", FALSE);
|
|
while (str && *str) {
|
|
len = 0;
|
|
while (*str && len < (parser.inbufsz-1)) {
|
|
parser.inbuf[len] = *str;
|
|
len++;
|
|
str++;
|
|
if (parser.inbuf[len-1] == '\n')
|
|
break;
|
|
}
|
|
parser.inbuf[len] = '\0';
|
|
parse_conf_buf(&parser, proc);
|
|
if (parser.pbreak)
|
|
break;
|
|
}
|
|
cnf_parser_done(&parser);
|
|
|
|
free_config_sections();
|
|
config_error_done();
|
|
return parser.rv;
|
|
}
|
|
|
|
/* parse_conf_file
|
|
*
|
|
* Read from file fp, calling parse_conf_buf for each line.
|
|
*/
|
|
staticfn boolean
|
|
parse_conf_file(FILE *fp, boolean (*proc)(char *arg))
|
|
{
|
|
struct _cnf_parser_state parser;
|
|
|
|
cnf_parser_init(&parser);
|
|
free_config_sections();
|
|
|
|
while (fgets(parser.inbuf, parser.inbufsz, fp)) {
|
|
parse_conf_buf(&parser, proc);
|
|
if (parser.pbreak)
|
|
break;
|
|
}
|
|
cnf_parser_done(&parser);
|
|
|
|
free_config_sections();
|
|
return parser.rv;
|
|
}
|
|
|
|
#ifdef SYSCF
|
|
staticfn void
|
|
parseformat(int *arr, char *str)
|
|
{
|
|
const char *legal[] = { "historical", "lendian", "ascii" };
|
|
int i, kwi = 0, words = 0;
|
|
char *p = str, *keywords[2];
|
|
|
|
while (*p) {
|
|
while (*p && isspace((uchar) *p)) {
|
|
*p = '\0';
|
|
p++;
|
|
}
|
|
if (*p) {
|
|
words++;
|
|
if (kwi < 2)
|
|
keywords[kwi++] = p;
|
|
}
|
|
while (*p && !isspace((uchar) *p))
|
|
p++;
|
|
}
|
|
if (!words) {
|
|
impossible("missing format list");
|
|
return;
|
|
}
|
|
while (--kwi >= 0)
|
|
if (kwi < 2) {
|
|
for (i = 0; i < SIZE(legal); ++i) {
|
|
if (!strcmpi(keywords[kwi], legal[i]))
|
|
arr[kwi] = i + 1;
|
|
}
|
|
}
|
|
}
|
|
#endif /* SYSCF */
|
|
|
|
/* ---------- END CONFIG FILE HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN WIZKIT FILE HANDLING ----------- */
|
|
|
|
staticfn FILE *
|
|
fopen_wizkit_file(void)
|
|
{
|
|
FILE *fp;
|
|
#if defined(VMS) || defined(UNIX)
|
|
char tmp_wizkit[BUFSZ];
|
|
#endif
|
|
char *envp;
|
|
|
|
envp = nh_getenv("WIZKIT");
|
|
if (envp && *envp)
|
|
(void) strncpy(gw.wizkit, envp, WIZKIT_MAX - 1);
|
|
if (!gw.wizkit[0])
|
|
return (FILE *) 0;
|
|
|
|
#ifdef UNIX
|
|
if (access(gw.wizkit, 4) == -1) {
|
|
/* 4 is R_OK on newer systems */
|
|
/* nasty sneaky attempt to read file through
|
|
* NetHack's setuid permissions -- this is a
|
|
* place a file name may be wholly under the player's
|
|
* control
|
|
*/
|
|
raw_printf("Access to %s denied (%d).", gw.wizkit, errno);
|
|
wait_synch();
|
|
/* fall through to standard names */
|
|
} else
|
|
#endif
|
|
if ((fp = fopen(gw.wizkit, "r")) != (FILE *) 0) {
|
|
return fp;
|
|
#if defined(UNIX) || defined(VMS)
|
|
} else {
|
|
/* access() above probably caught most problems for UNIX */
|
|
raw_printf("Couldn't open requested wizkit file %s (%d).", gw.wizkit,
|
|
errno);
|
|
wait_synch();
|
|
#endif
|
|
}
|
|
|
|
#if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
|
|
if ((fp = fopen(fqname(gw.wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
|
|
return fp;
|
|
#else
|
|
#ifdef VMS
|
|
envp = nh_getenv("HOME");
|
|
if (envp)
|
|
Sprintf(tmp_wizkit, "%s%s", envp, gw.wizkit);
|
|
else
|
|
Sprintf(tmp_wizkit, "%s%s", "sys$login:", gw.wizkit);
|
|
if ((fp = fopen(tmp_wizkit, "r")) != (FILE *) 0)
|
|
return fp;
|
|
#else /* should be only UNIX left */
|
|
envp = nh_getenv("HOME");
|
|
if (envp)
|
|
Sprintf(tmp_wizkit, "%s/%s", envp, gw.wizkit);
|
|
else
|
|
Strcpy(tmp_wizkit, gw.wizkit);
|
|
if ((fp = fopen(tmp_wizkit, "r")) != (FILE *) 0)
|
|
return fp;
|
|
else if (errno != ENOENT) {
|
|
/* e.g., problems when setuid NetHack can't search home
|
|
* directory restricted to user */
|
|
raw_printf("Couldn't open default gw.wizkit file %s (%d).",
|
|
tmp_wizkit, errno);
|
|
wait_synch();
|
|
}
|
|
#endif
|
|
#endif
|
|
return (FILE *) 0;
|
|
}
|
|
|
|
/* add to hero's inventory if there's room, otherwise put item on floor */
|
|
staticfn void
|
|
wizkit_addinv(struct obj *obj)
|
|
{
|
|
if (!obj || obj == &hands_obj)
|
|
return;
|
|
|
|
/* subset of starting inventory pre-ID */
|
|
obj->dknown = 1;
|
|
if (Role_if(PM_CLERIC))
|
|
obj->bknown = 1; /* ok to bypass set_bknown() */
|
|
/* same criteria as lift_object()'s check for available inventory slot */
|
|
if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= invlet_basic
|
|
&& !merge_choice(gi.invent, obj)) {
|
|
/* inventory overflow; can't just place & stack object since
|
|
hero isn't in position yet, so schedule for arrival later */
|
|
add_to_migration(obj);
|
|
obj->ox = 0; /* index of main dungeon */
|
|
obj->oy = 1; /* starting level number */
|
|
obj->owornmask =
|
|
(long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
|
|
} else {
|
|
(void) addinv(obj);
|
|
}
|
|
}
|
|
|
|
boolean
|
|
proc_wizkit_line(char *buf)
|
|
{
|
|
struct obj *otmp;
|
|
|
|
if (strlen(buf) >= BUFSZ)
|
|
buf[BUFSZ - 1] = '\0';
|
|
otmp = readobjnam(buf, (struct obj *) 0);
|
|
|
|
if (otmp) {
|
|
if (otmp != &hands_obj)
|
|
wizkit_addinv(otmp);
|
|
} else {
|
|
/* .60 limits output line width to 79 chars */
|
|
config_error_add("Bad wizkit item: \"%.60s\"", buf);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
read_wizkit(void)
|
|
{
|
|
FILE *fp;
|
|
|
|
if (!wizard || !(fp = fopen_wizkit_file()))
|
|
return;
|
|
|
|
program_state.wizkit_wishing = 1;
|
|
config_error_init(TRUE, "WIZKIT", FALSE);
|
|
|
|
parse_conf_file(fp, proc_wizkit_line);
|
|
(void) fclose(fp);
|
|
|
|
config_error_done();
|
|
program_state.wizkit_wishing = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
/* ---------- END WIZKIT FILE HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN SYMSET FILE HANDLING ----------- */
|
|
|
|
extern const char *const known_handling[]; /* symbols.c */
|
|
extern const char *const known_restrictions[]; /* symbols.c */
|
|
|
|
staticfn FILE *
|
|
fopen_sym_file(void)
|
|
{
|
|
FILE *fp;
|
|
|
|
fp = fopen_datafile(SYMBOLS, "r",
|
|
#ifdef WIN32
|
|
SYSCONFPREFIX
|
|
#else
|
|
HACKPREFIX
|
|
#endif
|
|
);
|
|
|
|
return fp;
|
|
}
|
|
|
|
/*
|
|
* Returns 1 if the chosen symset was found and loaded.
|
|
* 0 if it wasn't found in the sym file or other problem.
|
|
*/
|
|
int
|
|
read_sym_file(int which_set)
|
|
{
|
|
FILE *fp;
|
|
|
|
gs.symset[which_set].explicitly = FALSE;
|
|
if (!(fp = fopen_sym_file()))
|
|
return 0;
|
|
|
|
gs.symset[which_set].explicitly = TRUE;
|
|
gc.chosen_symset_start = gc.chosen_symset_end = FALSE;
|
|
gs.symset_which_set = which_set;
|
|
gs.symset_count = 0;
|
|
|
|
config_error_init(TRUE, "symbols", FALSE);
|
|
|
|
parse_conf_file(fp, proc_symset_line);
|
|
(void) fclose(fp);
|
|
|
|
if (!gc.chosen_symset_start && !gc.chosen_symset_end) {
|
|
/* name caller put in symset[which_set].name was not found;
|
|
if it looks like "Default symbols", null it out and return
|
|
success to use the default; otherwise, return failure */
|
|
if (gs.symset[which_set].name
|
|
&& (fuzzymatch(gs.symset[which_set].name, "Default symbols",
|
|
" -_", TRUE)
|
|
|| !strcmpi(gs.symset[which_set].name, "default")))
|
|
clear_symsetentry(which_set, TRUE);
|
|
config_error_done();
|
|
|
|
/* If name was defined, it was invalid. Then we're loading fallback */
|
|
if (gs.symset[which_set].name) {
|
|
gs.symset[which_set].explicitly = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
if (!gc.chosen_symset_end)
|
|
config_error_add("Missing finish for symset \"%s\"",
|
|
gs.symset[which_set].name ? gs.symset[which_set].name
|
|
: "unknown");
|
|
config_error_done();
|
|
return 1;
|
|
}
|
|
|
|
/* ---------- END SYMSET FILE HANDLING ----------- */
|
|
|
|
/* ---------- BEGIN SCOREBOARD CREATION ----------- */
|
|
|
|
#ifdef OS2_CODEVIEW
|
|
#define UNUSED_if_not_OS2_CODEVIEW /*empty*/
|
|
#else
|
|
#define UNUSED_if_not_OS2_CODEVIEW UNUSED
|
|
#endif
|
|
|
|
/* verify that we can write to scoreboard file; if not, try to create one */
|
|
/*ARGUSED*/
|
|
void
|
|
check_recordfile(const char *dir UNUSED_if_not_OS2_CODEVIEW)
|
|
{
|
|
const char *fq_record;
|
|
int fd;
|
|
|
|
#if defined(UNIX) || defined(VMS)
|
|
fq_record = fqname(RECORD, SCOREPREFIX, 0);
|
|
fd = open(fq_record, O_RDWR, 0);
|
|
if (fd >= 0) {
|
|
#ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
|
|
if (!file_is_stmlf(fd)) {
|
|
raw_printf("Warning: scoreboard file '%s'"
|
|
" is not in stream_lf format",
|
|
fq_record);
|
|
wait_synch();
|
|
}
|
|
#endif
|
|
(void) nhclose(fd); /* RECORD is accessible */
|
|
} else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
|
|
(void) nhclose(fd); /* RECORD newly created */
|
|
#if defined(VMS) && !defined(SECURE)
|
|
/* Re-protect RECORD with world:read+write+execute+delete access. */
|
|
(void) chmod(fq_record, FCMASK | 007);
|
|
#endif /* VMS && !SECURE */
|
|
} else {
|
|
raw_printf("Warning: cannot write scoreboard file '%s'", fq_record);
|
|
wait_synch();
|
|
}
|
|
#endif /* !UNIX && !VMS */
|
|
#if defined(MICRO) || defined(WIN32)
|
|
char tmp[PATHLEN];
|
|
|
|
#ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
|
|
/* how does this work when there isn't an explicit path or fopen
|
|
* for later access to the file via fopen_datafile? ? */
|
|
(void) strncpy(tmp, dir, PATHLEN - 1);
|
|
tmp[PATHLEN - 1] = '\0';
|
|
if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
|
|
append_slash(tmp);
|
|
Strcat(tmp, RECORD);
|
|
}
|
|
fq_record = tmp;
|
|
#else
|
|
Strcpy(tmp, RECORD);
|
|
fq_record = fqname(RECORD, SCOREPREFIX, 0);
|
|
#endif
|
|
#ifdef WIN32
|
|
/* If dir is NULL it indicates create but
|
|
only if it doesn't already exist */
|
|
if (!dir) {
|
|
char buf[BUFSZ];
|
|
|
|
buf[0] = '\0';
|
|
fd = open(fq_record, O_RDWR);
|
|
if (!(fd == -1 && errno == ENOENT)) {
|
|
if (fd >= 0) {
|
|
(void) nhclose(fd);
|
|
} else {
|
|
/* explanation for failure other than missing file */
|
|
Sprintf(buf, "error \"%s\", (errno %d).",
|
|
fq_record, errno);
|
|
paniclog("scorefile", buf);
|
|
}
|
|
return;
|
|
}
|
|
Sprintf(buf, "missing \"%s\", creating new scorefile.",
|
|
fq_record);
|
|
paniclog("scorefile", buf);
|
|
}
|
|
#endif
|
|
|
|
if ((fd = open(fq_record, O_RDWR)) < 0) {
|
|
/* try to create empty 'record' */
|
|
#if defined(AZTEC_C) || defined(_DCC) \
|
|
|| (defined(__GNUC__) && defined(__AMIGA__))
|
|
/* Aztec doesn't use the third argument */
|
|
/* DICE doesn't like it */
|
|
fd = open(fq_record, O_CREAT | O_RDWR);
|
|
#else
|
|
fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE);
|
|
#endif
|
|
if (fd <= 0) {
|
|
raw_printf("Warning: cannot write record '%s'", tmp);
|
|
wait_synch();
|
|
} else {
|
|
(void) nhclose(fd);
|
|
}
|
|
} else {
|
|
/* open succeeded => 'record' exists */
|
|
(void) nhclose(fd);
|
|
}
|
|
#else /* MICRO || WIN32*/
|
|
|
|
#ifdef MAC
|
|
/* Create the "record" file, if necessary */
|
|
fq_record = fqname(RECORD, SCOREPREFIX, 0);
|
|
fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
|
|
if (fd != -1)
|
|
macclose(fd);
|
|
#endif /* MAC */
|
|
|
|
#endif /* MICRO || WIN32*/
|
|
}
|
|
|
|
#undef UNUSED_if_not_OS2_CODEVIEW
|
|
|
|
/* ---------- END SCOREBOARD CREATION ----------- */
|
|
|
|
/* ---------- BEGIN PANIC/IMPOSSIBLE/TESTING LOG ----------- */
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
paniclog(
|
|
const char *type, /* panic, impossible, trickery, [lua] */
|
|
const char *reason) /* explanation */
|
|
{
|
|
#ifdef PANICLOG
|
|
FILE *lfile;
|
|
|
|
if (!program_state.in_paniclog) {
|
|
program_state.in_paniclog = 1;
|
|
lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
|
|
if (lfile) {
|
|
#ifdef PANICLOG_FMT2
|
|
(void) fprintf(lfile, "%ld %s: %s %s\n",
|
|
ubirthday, (svp.plname[0] ? svp.plname : "(none)"),
|
|
type, reason);
|
|
#else
|
|
char buf[BUFSZ];
|
|
time_t now = getnow();
|
|
int uid = getuid();
|
|
char playmode = wizard ? 'D' : discover ? 'X' : '-';
|
|
|
|
(void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
|
|
version_string(buf, sizeof buf),
|
|
yyyymmdd(now), hhmmss(now),
|
|
uid, playmode, type, reason);
|
|
#endif /* !PANICLOG_FMT2 */
|
|
(void) fclose(lfile);
|
|
}
|
|
program_state.in_paniclog = 0;
|
|
}
|
|
#endif /* PANICLOG */
|
|
return;
|
|
}
|
|
|
|
void
|
|
testinglog(const char *filenm, /* ad hoc file name */
|
|
const char *type,
|
|
const char *reason) /* explanation */
|
|
{
|
|
FILE *lfile;
|
|
char fnbuf[BUFSZ];
|
|
|
|
if (!filenm)
|
|
return;
|
|
Strcpy(fnbuf, filenm);
|
|
if (strchr(fnbuf, '.') == 0)
|
|
Strcat(fnbuf, ".log");
|
|
lfile = fopen_datafile(fnbuf, "a", TROUBLEPREFIX);
|
|
if (lfile) {
|
|
(void) fprintf(lfile, "%s\n%s\n", type, reason);
|
|
(void) fclose(lfile);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* ---------- END PANIC/IMPOSSIBLE/TESTING LOG ----------- */
|
|
|
|
#ifdef SELF_RECOVER
|
|
|
|
/* ---------- BEGIN INTERNAL RECOVER ----------- */
|
|
boolean
|
|
recover_savefile(void)
|
|
{
|
|
NHFILE *gnhfp, *lnhfp, *snhfp;
|
|
int lev, savelev, hpid, pltmpsiz, filecmc;
|
|
xint8 levc;
|
|
struct version_info version_data;
|
|
int processed[256];
|
|
char savename[SAVESIZE], errbuf[BUFSZ], indicator;
|
|
struct savefile_info sfi;
|
|
char tmpplbuf[PL_NSIZ_PLUS];
|
|
const char *savewrite_failure = (const char *) 0;
|
|
|
|
for (lev = 0; lev < 256; lev++)
|
|
processed[lev] = 0;
|
|
|
|
/* level 0 file contains:
|
|
* pid of creating process (ignored here)
|
|
* level number for current level of save file
|
|
* name of save file nethack would have created
|
|
* savefile info
|
|
* player name
|
|
* and game state
|
|
*/
|
|
|
|
gnhfp = open_levelfile(0, errbuf);
|
|
if (!gnhfp) {
|
|
raw_printf("%s\n", errbuf);
|
|
return FALSE;
|
|
}
|
|
if (read(gnhfp->fd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
|
|
raw_printf("\n%s\n%s\n",
|
|
"Checkpoint data incompletely written"
|
|
" or subsequently clobbered.",
|
|
"Recovery impossible.");
|
|
close_nhfile(gnhfp);
|
|
return FALSE;
|
|
}
|
|
if (read(gnhfp->fd, (genericptr_t) &savelev, sizeof(savelev))
|
|
!= sizeof(savelev)) {
|
|
raw_printf("\n%s %s %s\n",
|
|
"Checkpointing was not in effect for",
|
|
gl.lock,
|
|
"-- recovery impossible.");
|
|
close_nhfile(gnhfp);
|
|
return FALSE;
|
|
}
|
|
if ((read(gnhfp->fd, (genericptr_t) savename, sizeof savename)
|
|
!= sizeof savename)
|
|
|| (read(gnhfp->fd, (genericptr_t) &indicator, sizeof indicator)
|
|
!= sizeof indicator)
|
|
|| (read(gnhfp->fd, (genericptr_t) &filecmc, sizeof filecmc)
|
|
!= sizeof filecmc)
|
|
|| (read(gnhfp->fd, (genericptr_t) &version_data, sizeof version_data)
|
|
!= sizeof version_data)
|
|
|| (read(gnhfp->fd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
|
|
|| (read(gnhfp->fd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
|
|
!= sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ_PLUS)
|
|
|| (read(gnhfp->fd, (genericptr_t) &tmpplbuf, pltmpsiz)
|
|
!= pltmpsiz)) {
|
|
raw_printf("\nError reading %s -- can't recover.\n", gl.lock);
|
|
close_nhfile(gnhfp);
|
|
return FALSE;
|
|
}
|
|
|
|
/* save file should contain:
|
|
* format indicator and cmc
|
|
* version info
|
|
* savefile info
|
|
* player name
|
|
* current level (including pets)
|
|
* (non-level-based) game state
|
|
* other levels
|
|
*/
|
|
|
|
/*
|
|
* Set a flag for the savefile routines to know the
|
|
* circumstances and act accordingly:
|
|
* program_state.in_self_recover
|
|
*/
|
|
program_state.in_self_recover = TRUE;
|
|
set_savefile_name(TRUE);
|
|
snhfp = create_savefile();
|
|
if (!snhfp) {
|
|
raw_printf("\nCannot recover savefile %s.\n", gs.SAVEF);
|
|
close_nhfile(gnhfp);
|
|
return FALSE;
|
|
}
|
|
|
|
lnhfp = open_levelfile(savelev, errbuf);
|
|
if (!lnhfp) {
|
|
raw_printf("\n%s\n", errbuf);
|
|
close_nhfile(gnhfp);
|
|
close_nhfile(snhfp);
|
|
delete_savefile();
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Our savefile output format might _not_ be structlevel.
|
|
* We have to check and use the correct output routine here.
|
|
*/
|
|
/*store_formatindicator(snhfp); */
|
|
store_version(snhfp);
|
|
|
|
if (snhfp->structlevel) {
|
|
if (write(snhfp->fd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
|
|
savewrite_failure = "savefileinfo";
|
|
}
|
|
if (savewrite_failure)
|
|
goto cleanup;
|
|
|
|
if (snhfp->structlevel) {
|
|
if (write(snhfp->fd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
|
|
!= sizeof pltmpsiz)
|
|
savewrite_failure = "player name size";
|
|
}
|
|
if (savewrite_failure)
|
|
goto cleanup;
|
|
|
|
if (snhfp->structlevel) {
|
|
if (write(snhfp->fd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)
|
|
savewrite_failure = "player name";
|
|
}
|
|
if (savewrite_failure)
|
|
goto cleanup;
|
|
|
|
if (!copy_bytes(lnhfp->fd, snhfp->fd)) {
|
|
close_nhfile(gnhfp);
|
|
close_nhfile(snhfp);
|
|
close_nhfile(lnhfp);
|
|
delete_savefile();
|
|
return FALSE;
|
|
}
|
|
close_nhfile(lnhfp);
|
|
processed[savelev] = 1;
|
|
|
|
if (!copy_bytes(gnhfp->fd, snhfp->fd)) {
|
|
close_nhfile(gnhfp);
|
|
close_nhfile(snhfp);
|
|
delete_savefile();
|
|
return FALSE;
|
|
}
|
|
close_nhfile(gnhfp);
|
|
processed[0] = 1;
|
|
|
|
for (lev = 1; lev < 256; lev++) {
|
|
/* level numbers are kept in xint8's in save.c, so the
|
|
* maximum level number (for the endlevel) must be < 256
|
|
*/
|
|
if (lev != savelev) {
|
|
lnhfp = open_levelfile(lev, (char *) 0);
|
|
if (lnhfp) {
|
|
/* any or all of these may not exist */
|
|
levc = (xint8) lev;
|
|
(void) write(snhfp->fd, (genericptr_t) &levc, sizeof(levc));
|
|
if (!copy_bytes(lnhfp->fd, snhfp->fd)) {
|
|
close_nhfile(lnhfp);
|
|
close_nhfile(snhfp);
|
|
delete_savefile();
|
|
return FALSE;
|
|
}
|
|
close_nhfile(lnhfp);
|
|
processed[lev] = 1;
|
|
}
|
|
}
|
|
}
|
|
close_nhfile(snhfp);
|
|
/*
|
|
* We have a successful savefile!
|
|
* Only now do we erase the level files.
|
|
*/
|
|
for (lev = 0; lev < 256; lev++) {
|
|
if (processed[lev]) {
|
|
const char *fq_lock;
|
|
|
|
set_levelfile_name(gl.lock, lev);
|
|
fq_lock = fqname(gl.lock, LEVELPREFIX, 3);
|
|
(void) unlink(fq_lock);
|
|
}
|
|
}
|
|
cleanup:
|
|
if (savewrite_failure) {
|
|
raw_printf("\nError writing %s; recovery failed (%s).\n",
|
|
gs.SAVEF, savewrite_failure);
|
|
close_nhfile(gnhfp);
|
|
close_nhfile(snhfp);
|
|
close_nhfile(lnhfp);
|
|
program_state.in_self_recover = FALSE;
|
|
delete_savefile();
|
|
return FALSE;
|
|
}
|
|
/* we don't clear program_state.in_self_recover here, we
|
|
leave it as a flag to reload the structlevel savefile
|
|
in the caller. The caller should then clear it. */
|
|
return TRUE;
|
|
}
|
|
|
|
/* ---------- END INTERNAL RECOVER ----------- */
|
|
#endif /*SELF_RECOVER*/
|
|
|
|
/* ---------- OTHER ----------- */
|
|
|
|
#ifdef SYSCF
|
|
#ifdef SYSCF_FILE
|
|
void
|
|
assure_syscf_file(void)
|
|
{
|
|
int fd;
|
|
|
|
#ifdef WIN32
|
|
/* We are checking that the sysconf exists ... lock the path */
|
|
fqn_prefix_locked[SYSCONFPREFIX] = TRUE;
|
|
#endif
|
|
/*
|
|
* All we really care about is the end result - can we read the file?
|
|
* So just check that directly.
|
|
*
|
|
* Not tested on most of the old platforms (which don't attempt
|
|
* to implement SYSCF).
|
|
* Some ports don't like open()'s optional third argument;
|
|
* VMS overrides open() usage with a macro which requires it.
|
|
*/
|
|
#ifndef VMS
|
|
# if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
|
|
fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
|
|
# else
|
|
fd = open(SYSCF_FILE, O_RDONLY);
|
|
# endif
|
|
#else
|
|
fd = open(SYSCF_FILE, O_RDONLY, 0);
|
|
#endif
|
|
if (fd >= 0) {
|
|
/* readable */
|
|
close(fd);
|
|
return;
|
|
}
|
|
if (gd.deferred_showpaths)
|
|
do_deferred_showpaths(1); /* does not return */
|
|
raw_printf("Unable to open SYSCF_FILE.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
#endif /* SYSCF_FILE */
|
|
#endif /* SYSCF */
|
|
|
|
ATTRNORETURN void
|
|
do_deferred_showpaths(int code)
|
|
{
|
|
gd.deferred_showpaths = FALSE;
|
|
reveal_paths(code);
|
|
|
|
/* cleanup before heading to an exit */
|
|
freedynamicdata();
|
|
dlb_cleanup();
|
|
l_nhcore_done();
|
|
|
|
#ifdef UNIX
|
|
after_opt_showpaths(gd.deferred_showpaths_dir);
|
|
#else
|
|
#ifndef WIN32
|
|
#ifdef CHDIR
|
|
chdirx(gd.deferred_showpaths_dir, 0);
|
|
#endif
|
|
#endif
|
|
#if defined(WIN32) || defined(MICRO) || defined(OS2)
|
|
nethack_exit(EXIT_SUCCESS);
|
|
#else
|
|
exit(EXIT_SUCCESS);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* used by debugpline() to decide whether to issue a message
|
|
* from a particular source file; caller passes __FILE__ and we check
|
|
* whether it is in the source file list supplied by SYSCF's DEBUGFILES
|
|
*
|
|
* pass FALSE to override wildcard matching; useful for files
|
|
* like dungeon.c and questpgr.c, which generate a ridiculous amount of
|
|
* output if DEBUG is defined and effectively block the use of a wildcard */
|
|
boolean
|
|
debugcore(const char *filename, boolean wildcards)
|
|
{
|
|
const char *debugfiles, *p;
|
|
|
|
/* debugpline() messages might disclose information that the player
|
|
doesn't normally get to see, so only display them in wizard mode */
|
|
if (!wizard)
|
|
return FALSE;
|
|
|
|
if (!filename || !*filename)
|
|
return FALSE; /* sanity precaution */
|
|
|
|
debugfiles = sysopt.debugfiles;
|
|
/* usual case: sysopt.debugfiles will be empty */
|
|
if (!debugfiles || !*debugfiles)
|
|
return FALSE;
|
|
|
|
/* strip filename's path if present */
|
|
filename = nh_basename(filename, TRUE);
|
|
|
|
/*
|
|
* Wildcard match will only work if there's a single pattern (which
|
|
* might be a single file name without any wildcarding) rather than
|
|
* a space-separated list.
|
|
* [to NOT do: We could step through the space-separated list and
|
|
* attempt a wildcard match against each element, but that would be
|
|
* overkill for the intended usage.]
|
|
*/
|
|
if (wildcards && pmatch(debugfiles, filename))
|
|
return TRUE;
|
|
|
|
/* check whether filename is an element of the list */
|
|
if ((p = strstr(debugfiles, filename)) != 0) {
|
|
int l = (int) strlen(filename);
|
|
|
|
if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
|
|
&& (p[l] == ' ' || p[l] == '\0'))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#endif /*DEBUG*/
|
|
|
|
#ifdef UNIX
|
|
#ifndef PATH_MAX
|
|
#include <limits.h>
|
|
#endif
|
|
#endif
|
|
|
|
#define SYSCONFFILE "system configuration file"
|
|
|
|
void
|
|
reveal_paths(int code)
|
|
{
|
|
#if defined(SYSCF)
|
|
boolean skip_sysopt = FALSE;
|
|
#endif
|
|
const char *fqn, *nodumpreason;
|
|
|
|
char buf[BUFSZ];
|
|
#if defined(SYSCF) || !defined(UNIX) || defined(DLB)
|
|
const char *filep;
|
|
#ifdef SYSCF
|
|
const char *gamename = (gh.hname && *gh.hname) ? gh.hname : "NetHack";
|
|
#endif
|
|
#endif
|
|
#if defined(PREFIXES_IN_USE)
|
|
const char *cstrp;
|
|
#endif
|
|
#ifdef UNIX
|
|
char *endp, *envp, cwdbuf[PATH_MAX];
|
|
#endif
|
|
#ifdef PREFIXES_IN_USE
|
|
int i, maxlen = 0;
|
|
|
|
raw_print("Variable playground locations:");
|
|
for (i = 0; i < PREFIX_COUNT; i++)
|
|
raw_printf(" [%-10s]=\"%s\"", fqn_prefix_names[i],
|
|
gf.fqn_prefix[i] ? gf.fqn_prefix[i] : "not set");
|
|
#endif
|
|
|
|
/* sysconf file */
|
|
|
|
#ifdef SYSCF
|
|
#ifdef PREFIXES_IN_USE
|
|
cstrp = fqn_prefix_names[SYSCONFPREFIX];
|
|
maxlen = BUFSZ - sizeof " (in )";
|
|
if (cstrp && (int) strlen(cstrp) < maxlen)
|
|
Sprintf(buf, " (in %s)", cstrp);
|
|
#else
|
|
buf[0] = '\0';
|
|
#endif
|
|
raw_printf("%s %s%s:", s_suffix(gamename),
|
|
SYSCONFFILE, buf);
|
|
#ifdef SYSCF_FILE
|
|
filep = SYSCF_FILE;
|
|
#else
|
|
filep = "sysconf";
|
|
#endif
|
|
fqn = fqname(filep, SYSCONFPREFIX, 0);
|
|
if (fqn) {
|
|
set_configfile_name(fqn);
|
|
filep = configfile;
|
|
}
|
|
raw_printf(" \"%s\"", filep);
|
|
if (code == 1) {
|
|
raw_printf("NOTE: The %s above is missing or inaccessible!",
|
|
SYSCONFFILE);
|
|
skip_sysopt = TRUE;
|
|
}
|
|
#else /* !SYSCF */
|
|
raw_printf("No system configuration file.");
|
|
#endif /* ?SYSCF */
|
|
|
|
/* symbols file */
|
|
|
|
buf[0] = '\0';
|
|
#ifndef UNIX
|
|
#ifdef PREFIXES_IN_USE
|
|
#ifdef WIN32
|
|
cstrp = fqn_prefix_names[SYSCONFPREFIX];
|
|
#else
|
|
cstrp = fqn_prefix_names[HACKPREFIX];
|
|
#endif /* WIN32 */
|
|
maxlen = BUFSZ - sizeof " (in )";
|
|
if (cstrp && (int) strlen(cstrp) < maxlen)
|
|
Sprintf(buf, " (in %s)", cstrp);
|
|
#endif /* PREFIXES_IN_USE */
|
|
raw_printf("The loadable symbols file%s:", buf);
|
|
#endif /* UNIX */
|
|
|
|
#ifdef UNIX
|
|
envp = getcwd(cwdbuf, PATH_MAX);
|
|
if (envp) {
|
|
raw_print("The loadable symbols file:");
|
|
raw_printf(" \"%s/%s\"", envp, SYMBOLS);
|
|
}
|
|
#else /* UNIX */
|
|
filep = SYMBOLS;
|
|
#ifdef PREFIXES_IN_USE
|
|
#ifdef WIN32
|
|
fqn = fqname(filep, SYSCONFPREFIX, 1);
|
|
#else
|
|
fqn = fqname(filep, HACKPREFIX, 1);
|
|
#endif /* WIN32 */
|
|
if (fqn)
|
|
filep = fqn;
|
|
#endif /* PREFIXES_IN_USE */
|
|
raw_printf(" \"%s\"", filep);
|
|
#endif /* UNIX */
|
|
|
|
/* dlb vs non-dlb */
|
|
|
|
buf[0] = '\0';
|
|
#ifdef PREFIXES_IN_USE
|
|
cstrp = fqn_prefix_names[DATAPREFIX];
|
|
maxlen = BUFSZ - sizeof " (in )";
|
|
if (cstrp && (int) strlen(cstrp) < maxlen)
|
|
Sprintf(buf, " (in %s)", cstrp);
|
|
#endif
|
|
#ifdef DLB
|
|
raw_printf("Basic data files%s are collected inside:", buf);
|
|
filep = DLBFILE;
|
|
#ifdef VERSION_IN_DLB_FILENAME
|
|
Strcpy(buf, build_dlb_filename((const char *) 0));
|
|
#ifdef PREFIXES_IN_USE
|
|
fqn = fqname(buf, DATAPREFIX, 1);
|
|
if (fqn)
|
|
filep = fqn;
|
|
#endif /* PREFIXES_IN_USE */
|
|
#endif
|
|
raw_printf(" \"%s\"", filep);
|
|
#ifdef DLBFILE2
|
|
filep = DLBFILE2;
|
|
raw_printf(" \"%s\"", filep);
|
|
#endif
|
|
#else /* !DLB */
|
|
raw_printf("Basic data files%s are in many separate files.", buf);
|
|
#endif /* ?DLB */
|
|
|
|
/* dumplog */
|
|
|
|
fqn = (char *) 0;
|
|
#ifndef DUMPLOG
|
|
nodumpreason = "not supported";
|
|
#else
|
|
nodumpreason = "disabled";
|
|
#ifdef SYSCF
|
|
if (!skip_sysopt) {
|
|
fqn = sysopt.dumplogfile;
|
|
if (!fqn)
|
|
nodumpreason = "DUMPLOGFILE is not set in " SYSCONFFILE;
|
|
} else {
|
|
nodumpreason = SYSCONFFILE " is missing; no DUMPLOGFILE setting";
|
|
}
|
|
#else /* !SYSCF */
|
|
#ifdef DUMPLOG_FILE
|
|
fqn = DUMPLOG_FILE;
|
|
#endif
|
|
#endif /* ?SYSCF */
|
|
if (fqn && *fqn) {
|
|
raw_print("Your end-of-game disclosure file:");
|
|
(void) dump_fmtstr(fqn, buf, FALSE);
|
|
buf[sizeof buf - sizeof " \"\""] = '\0';
|
|
raw_printf(" \"%s\"", buf);
|
|
} else {
|
|
raw_printf("No end-of-game disclosure file (%s).", nodumpreason);
|
|
}
|
|
#endif /* ?DUMPLOG */
|
|
|
|
#ifdef SYSCF
|
|
#ifdef WIN32
|
|
if (!skip_sysopt) {
|
|
if (sysopt.portable_device_paths) {
|
|
const char *pd = get_portable_device();
|
|
|
|
/* an empty value for pd indicates that portable_device_paths
|
|
got set TRUE in a sysconf file other than the one containing
|
|
the executable; disregard it */
|
|
if (strlen(pd) > 0) {
|
|
raw_printf("portable_device_paths (set in sysconf):");
|
|
raw_printf(" \"%s\"", pd);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/* personal configuration file */
|
|
|
|
buf[0] = '\0';
|
|
#ifdef PREFIXES_IN_USE
|
|
cstrp = fqn_prefix_names[CONFIGPREFIX];
|
|
maxlen = BUFSZ - sizeof " (in )";
|
|
if (cstrp && (int) strlen(cstrp) < maxlen)
|
|
Sprintf(buf, " (in %s)", cstrp);
|
|
#endif /* PREFIXES_IN_USE */
|
|
raw_printf("Your personal configuration file%s:", buf);
|
|
|
|
#ifdef UNIX
|
|
buf[0] = '\0';
|
|
if ((envp = nh_getenv("HOME")) != 0) {
|
|
copynchars(buf, envp, (int) sizeof buf - 1 - 1);
|
|
Strcat(buf, "/");
|
|
}
|
|
endp = eos(buf);
|
|
copynchars(endp, default_configfile,
|
|
(int) (sizeof buf - 1 - strlen(buf)));
|
|
#if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX aka OSX aka macOS */
|
|
if (envp) {
|
|
if (access(buf, 4) == -1) { /* 4: R_OK, -1: failure */
|
|
/* read access to default failed; might be protected excessively
|
|
but more likely it doesn't exist; try first alternate:
|
|
"$HOME/Library/Pref..."; 'endp' points past '/' */
|
|
copynchars(endp, "Library/Preferences/NetHack Defaults",
|
|
(int) (sizeof buf - 1 - strlen(buf)));
|
|
if (access(buf, 4) == -1) {
|
|
/* first alternate failed, try second:
|
|
".../NetHack Defaults.txt"; no 'endp', just append */
|
|
copynchars(eos(buf), ".txt",
|
|
(int) (sizeof buf - 1 - strlen(buf)));
|
|
if (access(buf, 4) == -1) {
|
|
/* second alternate failed too, so revert to the
|
|
original default ("$HOME/.nethackrc") for message */
|
|
copynchars(endp, default_configfile,
|
|
(int) (sizeof buf - 1 - strlen(buf)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* __APPLE__ */
|
|
raw_printf(" \"%s\"", buf);
|
|
#else /* !UNIX */
|
|
fqn = (const char *) 0;
|
|
#ifdef PREFIXES_IN_USE
|
|
fqn = fqname(default_configfile, CONFIGPREFIX, 2);
|
|
#endif
|
|
raw_printf(" \"%s\"", fqn ? fqn : default_configfile);
|
|
#endif /* ?UNIX */
|
|
|
|
raw_print("");
|
|
#if defined(WIN32) && !defined(WIN32CON)
|
|
wait_synch();
|
|
#endif
|
|
#ifndef DUMPLOG
|
|
#ifdef SYSCF
|
|
nhUse(skip_sysopt);
|
|
#endif
|
|
nhUse(nodumpreason);
|
|
#endif
|
|
}
|
|
|
|
/* ---------- BEGIN TRIBUTE ----------- */
|
|
|
|
/* 3.6 tribute code
|
|
*/
|
|
|
|
#define SECTIONSCOPE 1
|
|
#define TITLESCOPE 2
|
|
#define PASSAGESCOPE 3
|
|
|
|
#define MAXPASSAGES SIZE(svc.context.novel.pasg) /* 20 */
|
|
|
|
staticfn int choose_passage(int, unsigned);
|
|
|
|
/* choose a random passage that hasn't been chosen yet; once all have
|
|
been chosen, reset the tracking to make all passages available again */
|
|
staticfn int
|
|
choose_passage(int passagecnt, /* total of available passages */
|
|
unsigned oid) /* book.o_id, used to determine whether
|
|
re-reading same book */
|
|
{
|
|
int idx, res;
|
|
|
|
if (passagecnt < 1)
|
|
return 0;
|
|
|
|
/* if a different book or we've used up all the passages already,
|
|
reset in order to have all 'passagecnt' passages available */
|
|
if (oid != svc.context.novel.id || svc.context.novel.count == 0) {
|
|
int i, range = passagecnt, limit = MAXPASSAGES;
|
|
|
|
svc.context.novel.id = oid;
|
|
if (range <= limit) {
|
|
/* collect all of the N indices */
|
|
svc.context.novel.count = passagecnt;
|
|
for (idx = 0; idx < MAXPASSAGES; idx++)
|
|
svc.context.novel.pasg[idx] = (xint16) ((idx < passagecnt)
|
|
? idx + 1 : 0);
|
|
} else {
|
|
/* collect MAXPASSAGES of the N indices */
|
|
svc.context.novel.count = MAXPASSAGES;
|
|
for (idx = i = 0; i < passagecnt; ++i, --range)
|
|
if (range > 0 && rn2(range) < limit) {
|
|
svc.context.novel.pasg[idx++] = (xint16) (i + 1);
|
|
--limit;
|
|
}
|
|
}
|
|
}
|
|
|
|
idx = rn2(svc.context.novel.count);
|
|
res = (int) svc.context.novel.pasg[idx];
|
|
/* move the last slot's passage index into the slot just used
|
|
and reduce the number of passages available */
|
|
svc.context.novel.pasg[idx]
|
|
= svc.context.novel.pasg[--svc.context.novel.count];
|
|
return res;
|
|
}
|
|
|
|
/* Returns True if you were able to read something. */
|
|
boolean
|
|
read_tribute(const char *tribsection, const char *tribtitle,
|
|
int tribpassage, char *nowin_buf, int bufsz,
|
|
unsigned oid) /* book identifier */
|
|
{
|
|
dlb *fp;
|
|
char line[BUFSZ], lastline[BUFSZ];
|
|
|
|
int scope = 0;
|
|
int linect = 0, passagecnt = 0, targetpassage = 0;
|
|
const char *badtranslation = "an incomprehensible foreign translation";
|
|
boolean matchedsection = FALSE, matchedtitle = FALSE;
|
|
winid tribwin = WIN_ERR;
|
|
boolean grasped = FALSE;
|
|
boolean foundpassage = FALSE;
|
|
|
|
if (nowin_buf)
|
|
*nowin_buf = '\0';
|
|
|
|
/* check for mandatories */
|
|
if (!tribsection || !tribtitle) {
|
|
if (!nowin_buf)
|
|
pline("It's %s of \"%s\"!", badtranslation, tribtitle);
|
|
return grasped;
|
|
}
|
|
|
|
debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
|
|
tribpassage);
|
|
|
|
fp = dlb_fopen(TRIBUTEFILE, "r");
|
|
if (!fp) {
|
|
/* this is actually an error - cannot open tribute file! */
|
|
if (!nowin_buf)
|
|
You_feel("too overwhelmed to continue!");
|
|
return grasped;
|
|
}
|
|
|
|
/*
|
|
* Syntax (not case-sensitive):
|
|
* %section books
|
|
*
|
|
* In the books section:
|
|
* %title booktitle (n)
|
|
* where booktitle=book title without quotes
|
|
* (n)= total number of passages present for this title
|
|
* %passage k
|
|
* where k=sequential passage number
|
|
*
|
|
* %e ends the passage/book/section
|
|
* If in a passage, it marks the end of that passage.
|
|
* If in a book, it marks the end of that book.
|
|
* If in a section, it marks the end of that section.
|
|
*
|
|
* %section death
|
|
*/
|
|
|
|
*line = *lastline = '\0';
|
|
while (dlb_fgets(line, sizeof line, fp) != 0) {
|
|
linect++;
|
|
(void) strip_newline(line);
|
|
switch (line[0]) {
|
|
case '%':
|
|
if (!strncmpi(&line[1], "section ", sizeof "section " - 1)) {
|
|
char *st = &line[9]; /* 9 from "%section " */
|
|
|
|
scope = SECTIONSCOPE;
|
|
matchedsection = !strcmpi(st, tribsection) ? TRUE : FALSE;
|
|
} else if (!strncmpi(&line[1], "title ", sizeof "title " - 1)) {
|
|
char *st = &line[7]; /* 7 from "%title " */
|
|
char *p1, *p2;
|
|
|
|
if ((p1 = strchr(st, '(')) != 0) {
|
|
*p1++ = '\0';
|
|
(void) mungspaces(st);
|
|
if ((p2 = strchr(p1, ')')) != 0) {
|
|
*p2 = '\0';
|
|
passagecnt = atoi(p1);
|
|
scope = TITLESCOPE;
|
|
if (matchedsection && !strcmpi(st, tribtitle)) {
|
|
matchedtitle = TRUE;
|
|
targetpassage = !tribpassage
|
|
? choose_passage(passagecnt, oid)
|
|
: (tribpassage <= passagecnt)
|
|
? tribpassage : 0;
|
|
} else {
|
|
matchedtitle = FALSE;
|
|
}
|
|
}
|
|
}
|
|
} else if (!strncmpi(&line[1], "passage ",
|
|
sizeof "passage " - 1)) {
|
|
int passagenum = 0;
|
|
char *st = &line[9]; /* 9 from "%passage " */
|
|
|
|
mungspaces(st);
|
|
passagenum = atoi(st);
|
|
if (passagenum > 0 && passagenum <= passagecnt) {
|
|
scope = PASSAGESCOPE;
|
|
if (matchedtitle && passagenum == targetpassage) {
|
|
foundpassage = TRUE;
|
|
if (!nowin_buf) {
|
|
tribwin = create_nhwindow(NHW_MENU);
|
|
if (tribwin == WIN_ERR)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
} else if (!strncmpi(&line[1], "e ", sizeof "e " - 1)) {
|
|
if (foundpassage)
|
|
goto cleanup;
|
|
if (scope == TITLESCOPE)
|
|
matchedtitle = FALSE;
|
|
if (scope == SECTIONSCOPE)
|
|
matchedsection = FALSE;
|
|
if (scope)
|
|
--scope;
|
|
} else {
|
|
debugpline1("tribute file error: bad %% command, line %d.",
|
|
linect);
|
|
}
|
|
break;
|
|
case '#':
|
|
/* comment only, next! */
|
|
break;
|
|
default:
|
|
if (foundpassage) {
|
|
if (!nowin_buf) {
|
|
/* outputting multi-line passage to text window */
|
|
putstr(tribwin, 0, line);
|
|
if (*line)
|
|
Strcpy(lastline, line);
|
|
} else {
|
|
/* fetching one-line passage into buffer */
|
|
copynchars(nowin_buf, line, bufsz - 1);
|
|
goto cleanup; /* don't wait for "%e passage" */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
(void) dlb_fclose(fp);
|
|
if (nowin_buf) {
|
|
/* one-line buffer */
|
|
grasped = *nowin_buf ? TRUE : FALSE;
|
|
} else {
|
|
if (tribwin != WIN_ERR) { /* implies 'foundpassage' */
|
|
/* multi-line window, normal case;
|
|
if lastline is empty, there were no non-empty lines between
|
|
"%passage n" and "%e passage" so we leave 'grasped' False */
|
|
if (*lastline) {
|
|
char *p;
|
|
|
|
display_nhwindow(tribwin, FALSE);
|
|
/* put the final attribution line into message history,
|
|
analogous to the summary line from long quest messages */
|
|
if (strchr(lastline, '['))
|
|
mungspaces(lastline); /* to remove leading spaces */
|
|
else /* construct one if necessary */
|
|
Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
|
|
if ((p = strrchr(lastline, ']')) != 0)
|
|
Sprintf(p, "; passage #%d]", targetpassage);
|
|
putmsghistory(lastline, FALSE);
|
|
grasped = TRUE;
|
|
}
|
|
destroy_nhwindow(tribwin);
|
|
}
|
|
if (!grasped)
|
|
/* multi-line window, problem */
|
|
pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
|
|
}
|
|
return grasped;
|
|
}
|
|
|
|
boolean
|
|
Death_quote(char *buf, int bufsz)
|
|
{
|
|
unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
|
|
|
|
return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
|
|
}
|
|
|
|
/* ---------- END TRIBUTE ----------- */
|
|
|
|
#ifdef LIVELOG
|
|
#define LLOG_SEP "\t" /* livelog field separator, as a string literal */
|
|
#define LLOG_EOL "\n" /* end-of-line, for abstraction consistency */
|
|
|
|
/* Locks the live log file and writes 'buffer'
|
|
* iff the ll_type matches sysopt.livelog mask.
|
|
* lltype is included in LL entry for post-process filtering also.
|
|
*/
|
|
void
|
|
livelog_add(long ll_type, const char *str)
|
|
{
|
|
FILE *livelogfile;
|
|
time_t now;
|
|
int gindx, aindx;
|
|
|
|
if (!(ll_type & sysopt.livelog))
|
|
return;
|
|
|
|
if (lock_file(LIVELOGFILE, SCOREPREFIX, 10)) {
|
|
if (!(livelogfile = fopen_datafile(LIVELOGFILE, "a", SCOREPREFIX))) {
|
|
pline("Cannot open live log file!");
|
|
unlock_file(LIVELOGFILE);
|
|
return;
|
|
}
|
|
|
|
now = getnow();
|
|
gindx = flags.female ? 1 : 0;
|
|
/* note on alignment designation:
|
|
aligns[] uses [0] lawful, [1] neutral, [2] chaotic;
|
|
u.ualign.type uses -1 chaotic, 0 neutral, 1 lawful;
|
|
so subtracting from 1 converts from either to the other */
|
|
aindx = 1 - u.ualign.type;
|
|
/* format relies on STD C's implicit concatenation of
|
|
adjacent string literals */
|
|
(void) fprintf(livelogfile,
|
|
"lltype=%ld" LLOG_SEP "name=%s" LLOG_SEP
|
|
"role=%s" LLOG_SEP "race=%s" LLOG_SEP
|
|
"gender=%s" LLOG_SEP "align=%s" LLOG_SEP
|
|
"turns=%ld" LLOG_SEP "starttime=%ld" LLOG_SEP
|
|
"curtime=%ld" LLOG_SEP "message=%s" LLOG_EOL,
|
|
(ll_type & sysopt.livelog), svp.plname,
|
|
gu.urole.filecode, gu.urace.filecode,
|
|
genders[gindx].filecode, aligns[aindx].filecode,
|
|
svm.moves, timet_to_seconds(ubirthday),
|
|
timet_to_seconds(now), str);
|
|
(void) fclose(livelogfile);
|
|
unlock_file(LIVELOGFILE);
|
|
}
|
|
}
|
|
#undef LLOG_SEP
|
|
#undef LLOG_EOL
|
|
|
|
#else
|
|
|
|
void
|
|
livelog_add(long ll_type UNUSED, const char *str UNUSED)
|
|
{
|
|
/* nothing here */
|
|
}
|
|
|
|
#endif /* !LIVELOG */
|
|
|
|
/*files.c*/
|