Files
nethack/util/sfctool.c
nhmall a654d08c3b save/restore changes - part 3
This is the third of a series of savefile-related changes.

    This adds early-days experimental support for a completely optional
    'sfctool' utility (savefile conversion tool), to be able to export
    a savefile's contents into a more portable format. There are likely
    to be bugs at this stage. In this initial first-attempt, the export
    format is a very simple ascii output.

    NetHack can be built entirely, without also building this tool.
    NetHack has no dependencies on the tool.

    Attempts were made to minimize duplication of existing NetHack code.
    To achieve that, unfortunately, #ifdef SFCTOOL and #ifndef SFCTOOL
    had to be sprinkled around through some of the existing NetHack
    source code, so that it could be re-used for building the utility.

    The process for building the sfctool typically recompiles the source
    files with #define SFCTOOL and a distinct object file with SF- is
    produced.

sfctool notes:

    Universal ctags is used and required to produce the sfctool utility.

    Some targets were added to the Unix and Windows Makefiles to
    facilitate the build process.

         make sfctool

    That should build a copy in util.

    Note: At present, the Unix Makefiles do not copy sfctool over to the
          NetHack playground during 'make install' or 'make update'.
          Until that gets resolved by someone, The tool will
          have to be manually copied there by the builder/admin if
          desired.
          cp util/sfctool ~/nh/install/games/lib/nethackdir/sfctool

    Also, a separate Visual Studio sfctool.sln solution was written and
    placed in sys/windows/vs. That has has only very limited testing.

    Usage:

      i)  To convert an existing savefile to an exportascii format
          that co-resides with the savefile:

          sfctool -c savefile

          That *must* be executed on the same platform / architecture /
          data model that produced the save file in the first place.

     ii)  To unconvert an existing exportascii format export file to a
          historical format savefile that can then be used by NetHack:

          sfctool -u savefile

          That must be executed on the same target platform / architecture /
          data model that was used to build the NetHack that will
          utilize the save file that results.

     A Windows example:

          sfctool -c Fred.NetHack-saved-game

          That should result in creation of Fred.NetHack-saved-game.exportascii
          from existing savefile:
              %USERPROFILE%\AppData\Local\NetHack\3.7\Fred.NetHack-saved-game

     A Unix example:

          sfctool -c 1000wizard

          That should result in creation of 1000wizard.exportascii.gz
          from existing savefile in the playground save directory:
              1000wizard.gz

  Current Mechanics:
     1. Makefile recipe, or script uses universal ctags to produce
        util/sf.tags.

     2. util/sftags is built and executed to read util/sf.tags and
        generate: include/sfproto.h and src/sfdata.c.

     3. util/sfctool is built from the following:
        generated file compiled with -DSFCTOOL:
                    src/sfdata.c       -> sfdata.o
        existing files compiled with -DSFCTOOL:
                    util/sfctool.c     -> sfctool.o
                    util/sfexpasc.c    -> sfexpasc.o
                    src/alloc.c        -> sf-alloc.o
                    src/monst.c        -> sf-monst.o
                    src/objects.c      -> sf-objects.o
                    src/sfbase.c       -> sfbase.o
                    src/sfstruct.c     -> sfstruct.o
                    src/nhlua.c        -> sf-nhlua.o
                    util/panic.c       -> panic.o
                    src/date.c         -> sf-date.o
                    src/decl.c         -> sf-decl.o
                    src/artifact.c     -> sf-artifact.o
                    src/dungeon.c      -> sf-dungeon.o
                    src/end.c          -> sf-end.o
                    src/engrave.c      -> sf-engrave.o
                    src/cfgfiles.c     -> sf-cfgfiles.o
                    src/files.c        -> sf-files.o
                    src/light.c        -> sf-light.o
                    src/mdlib.c        -> sf-mdlib.o
                    src/mkmaze.c       -> sf-mkmaze.o
                    src/mkroom.c       -> sf-mkroom.o
                    src/o_init.c       -> sf-o_init.o
                    src/region.c       -> sf-region.o
                    src/restore.c      -> sf-restore.o
                    src/rumors.c       -> sf-rumors.o
                    src/sys.c          -> sf-sys.o
                    src/timeout.c      -> sf-timeout.o
                    src/track.c        -> sf-track.o
                    src/version.c      -> sf-version.o
                    src/worm.c         -> sf-worm.o
                    src/strutil.c      -> strutil.o
2025-05-25 20:38:17 -04:00

1324 lines
34 KiB
C

/* NetHack 3.7 sfctool.c */
/* Copyright (c) Michael Allison, 2025. */
/* NetHack may be freely redistributed. See license for details. */
/*
* Utility for reading a binary save file in the native historical
* format of the accompanying NetHack executable, and writing it out
* in an export format, possibly destined for a different platform,
* architecture, or data model.
*
* The resulting export file will only be useful for transport
* between different platforms and architectures that share the exact
* same version of NetHack with the same features. That is, the same
* fields must be present in the NetHack data structures, in the same
* sequence. The fields do not have to be the same size or use the same
* data model.
*
*/
#if defined(WIN32) && !defined(__GNUC__)
#include "win32api.h"
#endif
#include "hack.h"
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#ifdef UNIX
#include <fcntl.h>
#define O_BINARY 0
#endif
#include "integer.h"
#include "sfprocs.h"
#ifdef WIN32
#include <UserEnv.h>
#endif
#ifndef RDTMODE
#define RDTMODE "r"
#endif
#ifndef WRTMODE
#if (defined(MSDOS) || defined(WIN32))
#define WRTMODE "w+b"
#else
#define WRTMODE "w+"
#endif
#endif
#ifndef RDBMODE
#if (defined(MICRO) || defined(WIN32))
#define RDBMODE "rb"
#else
#define RDBMODE "r"
#endif
#endif
#ifndef WRBMODE
#if (defined(MICRO) || defined(WIN32))
#define WRBMODE "w+b"
#else
#define WRBMODE "w+"
#endif
#endif
#ifdef PANICTRACE
#error PANICTRACE is defined
#endif
#ifdef CRASHREPORT
#error CRASHREPORT is defined
#endif
/* functions in this file */
static int process_savefile(const char *, enum saveformats, char *, enum saveformats, boolean);
static void my_sf_init(void);
static NHFILE *open_srcfile(const char *, enum saveformats);
static NHFILE *create_dstfile(char *, enum saveformats);
static const char *style_to_text(enum saveformats style);
static void read_sysconf(void);
static int length_without_val(const char *user_string, int len);
void zero_nhfile(NHFILE *);
NHFILE *new_nhfile(void);
void free_nhfile(NHFILE *);
void my_close_nhfile(NHFILE *);
int delete_savefile(void);
int nhclose(int fd);
int util_strncmpi(const char *s1, const char *s2, size_t sz);
#ifdef UNIX
#define nethack_exit exit
void nh_terminate(int) NORETURN; /* bwrite() calls this */
static void chdirx(const char *);
#else
extern void nethack_exit(int) NORETURN;
#ifdef WIN32
boolean get_user_home_folder(char *homebuf, size_t sz);
int GUILaunched;
#endif /* WIN32 */
#endif
#define Fprintf (void) fprintf
/* Global data */
struct link_compat1 {
volatile int done_hup;
};
/* worm segment structure */
struct wseg {
struct wseg *nseg;
coordxy wx, wy; /* the segment's position */
};
enum { UNCONVERTED = 0, CONVERTED };
char *unconverted_filename = 0;
char *converted_filename = 0;
/* from sfstruct.c */
extern struct restore_info restoreinfo;
/* from sfbase.c */
extern struct sf_structlevel_procs sfoprocs[NUM_SAVEFORMATS], sfiprocs[NUM_SAVEFORMATS];
extern struct sf_structlevel_procs zerosfoprocs, zerosfiprocs;
extern struct sf_fieldlevel_procs sfoflprocs[NUM_SAVEFORMATS], sfiflprocs[NUM_SAVEFORMATS];
extern struct sf_structlevel_procs zerosfodlprocs, zerosfidlprocs;
extern boolean close_check(int);
extern void bclose(int);
extern void config_error_init(boolean, const char *, boolean); /* files.c */
extern boolean get_user_home_folder(char *, size_t);
extern void make_version(void);
char plname[PL_NSIZ_PLUS];
struct version_info vers_info;
int renidx = -1;
const char *const rensuffixes[] = {
"IL32LLP64", /* (3) Windows x64 savefile on x86 */
"I32LP64", /* (4) Unix 64 savefile on x86 */
"ILP32LL64", /* (5) x86 savefile on Unix 64 */
"ILP32LL64", /* (6) x86 savefile on Windows x64 */
"I32LP64", /* (7) Unix 64 savefile on Windows x64 */
"IL32LLP64", /* (8) Windows x64 savefile on Unix 64 */
"OTHER", /* (9) */
};
#ifdef WIN32
extern boolean get_user_home_folder(char *homebuf, size_t sz); /* files.c */
extern void set_default_prefix_locations(const char *programPath);
#endif
enum saveformats convertstyle = exportascii;
boolean chosen_unconvert = FALSE, explicit_option = FALSE;
const char *thisdatamodel;
static char srclogfilenm[BUFSZ], dstlogfilenm[BUFSZ];
/*********
* main *
*********/
int
main(int argc UNUSED, char *argv[])
{
int arg;
char folderbuf[5000];
const char *suffix = (convertstyle == exportascii) ? ".exportascii" : "";
boolean add_folder = TRUE;
#ifdef WIN32
size_t sz;
#endif
if (argc < 3)
exit(EXIT_FAILURE);
runtime_info_init(); /* mdlib.c */
#ifdef UNIX
folderbuf[0] = '.';
folderbuf[1] = '/';
folderbuf[2] = '\0';
#ifdef CHDIR
chdirx(HACKDIR);
#endif
#endif
#ifdef UNIX
Strcpy(folderbuf, "save/");
#endif
#ifdef WIN32
if (!get_user_home_folder(folderbuf, sizeof folderbuf))
exit(EXIT_FAILURE);
sz = strlen(folderbuf);
(void) snprintf(eos(folderbuf), sizeof folderbuf - sz,
"\\AppData\\Local\\NetHack\\3.7\\");
//initoptions_init(); // This allows OPTIONS in syscf on Windows.
set_default_prefix_locations(argv[0]);
#endif
read_sysconf();
for (arg = 1; arg < argc; ++arg) {
if (arg == 1 && !strcmp(argv[arg], "-u")) {
explicit_option = TRUE;
chosen_unconvert = TRUE;
continue;
}
if (arg == 1 && !strcmp(argv[arg], "-c")) {
if (explicit_option && chosen_unconvert) {
fprintf(stderr, "\nsfctool error - conflicting options.\n");
exit(EXIT_FAILURE); /* both -u and -c not allowed */
}
explicit_option = TRUE;
chosen_unconvert = FALSE;
continue;
}
if (arg == 2) {
size_t ln = strlen(argv[arg]);
boolean addseparator = FALSE;
if (!contains_directory(argv[arg])) {
char finalchar = *(eos(folderbuf) - 1);
if (!(finalchar == '\\' || finalchar == '/')) {
ln += 1;
addseparator = TRUE;
}
} else {
add_folder = FALSE;
}
if (explicit_option) {
if (add_folder)
ln += strlen(folderbuf);
unconverted_filename = (char *) alloc((int) ln + 1);
Snprintf(unconverted_filename, ln + 1, "%s%s%s",
add_folder ? folderbuf : "",
addseparator ? "/" : "", argv[arg]);
ln += strlen(suffix);
converted_filename = (char *) alloc((int) ln + 1);
Snprintf(converted_filename, ln + 1, "%s%s",
unconverted_filename, suffix);
} else {
fprintf(stderr, "\nsfctool error - missing -c or -u before "
"save filename.\n");
exit(EXIT_FAILURE); /* need both filenames */
}
}
}
if (!converted_filename || !unconverted_filename) {
fprintf(stderr, "\nsfctool error - missing %sconverted file name.\n",
!converted_filename ? "" : "un");
exit(EXIT_FAILURE); /* need both filenames */
}
thisdatamodel = datamodel();
my_sf_init();
if (chosen_unconvert) {
process_savefile(converted_filename, convertstyle,
unconverted_filename, historical, chosen_unconvert);
} else {
process_savefile(unconverted_filename, historical, converted_filename,
convertstyle, chosen_unconvert);
}
}
/* ======================================================================== */
/* Process the src savefile and create the dst file */
/* Return 1 for success, 0 for failure */
/* ======================================================================== */
static int
process_savefile(const char *srcfnam, enum saveformats srcstyle,
char *dstfnam, enum saveformats cvtstyle, boolean unconvert)
{
NHFILE *nhfp[2]; /* one for UNCONVERTED, one for CONVERTED */
int srcidx = unconvert ? CONVERTED : UNCONVERTED,
dstidx = unconvert ? UNCONVERTED : CONVERTED,
sfstatus = 0, i;
char indicator, file_csc_count;
extern struct version_info vers_info;
extern uchar cscbuf[];
/* nh_uncompress(fq_save); */
if ((nhfp[srcidx] = open_srcfile(srcfnam, srcstyle)) == 0)
return 0;
sfstatus = validate(nhfp[srcidx], srcfnam, FALSE);
if (sfstatus > SF_UPTODATE
&& ((sfstatus <= SF_CRITICAL_BYTE_COUNT_MISMATCH) || !unconvert)) {
fprintf(stderr,
"This savefile is not compatible with %sutility.\n%s\n",
!unconvert ? "the datamodel of this particular " : "this ",
srcfnam);
return 0;
}
if (sfstatus >= SF_DM_IL32LLP64_ON_ILP32LL64) {
renidx = sfstatus - SF_DM_IL32LLP64_ON_ILP32LL64;
} else if (sfstatus == SF_UPTODATE) {
renidx = -2;
}
if ((nhfp[dstidx] = create_dstfile(dstfnam, cvtstyle)) == 0) {
close_nhfile(nhfp[dstidx]);
nh_compress(unconverted_filename);
return 0;
}
if (unconvert) {
nhfp[srcidx]->nhfpconvert = nhfp[UNCONVERTED];
} else {
/* converting */
nhfp[srcidx]->nhfpconvert = nhfp[CONVERTED];
}
if (unconvert)
fprintf(stdout, "\n\nunconverting %s to %s %s\n",
style_to_text(srcstyle), style_to_text(cvtstyle),
thisdatamodel);
else
fprintf(stdout, "\n\nconverting %s %s to %s\n",
style_to_text(srcstyle), thisdatamodel, style_to_text(cvtstyle));
rewind_nhfile(nhfp[srcidx]);
#ifdef SAVEFILE_DEBUGGING
nhfp[srcidx]->fplog = fopen(srclogfilenm, "w");
#endif
if (unconvert) {
nhfp[srcidx]->mode |= UNCONVERTING;
} else {
/* converting */
nhfp[srcidx]->mode |= CONVERTING;
}
nhfp[srcidx]->rcount = 0;
Sfi_char(nhfp[srcidx], &indicator, "indicate-format", 1);
Sfi_char(nhfp[srcidx], &file_csc_count, "count-critical_sizes", 1);
for (i = 0; i < (int) file_csc_count; ++i) {
Sfi_uchar(nhfp[srcidx], &cscbuf[i], "critical_sizes");
}
rewind_nhfile(nhfp[dstidx]);
#ifdef SAVEFILE_DEBUGGING
nhfp[dstidx]->fplog = fopen(dstlogfilenm, "w");
#endif
/*
* store_critical_bytes() will take care of inserting the
* indicate-format, count-critical_sizes, and critical_sizes for
* this platform/data-model destination, instead of copying those
* values from the savefile that was converted.
*/
store_critical_bytes(nhfp[dstidx]);
Sfi_version_info(nhfp[srcidx], &vers_info, "version_info");
svm.moves = 1L; /* match u_init.c */
/********************
* player name info *
********************/
get_plname_from_file(nhfp[srcidx], plname, TRUE);
{
/********************
* lev 0 *
********************/
xint8 lev = 0;
getlev(nhfp[srcidx], lev, FALSE);
}
{
/********************
* gamestate *
********************/
/* unsigned int stuckid, steedid; */
(void) restgamestate(nhfp[srcidx]);
}
{
/********************
* Do all levels *
********************/
xint8 ltmp;
restoreinfo.mread_flags = 1; /* return despite error */
while (1) {
ltmp = -1;
Sfi_xint8(nhfp[srcidx], &ltmp, "gamestate-level_number");
if (nhfp[srcidx]->eof || ltmp == -1)
break;
getlev(nhfp[srcidx], 0, ltmp);
}
restoreinfo.mread_flags = 0;
}
nhfp[srcidx]->mode &= ~(CONVERTING | UNCONVERTING);
nhfp[srcidx]->nhfpconvert = (NHFILE *) 0;
close_nhfile(nhfp[srcidx]);
close_nhfile(nhfp[dstidx]);
nh_compress(dstfnam);
nh_compress(srcfnam);
return 1;
}
/* open srcfile for reading */
static NHFILE *
open_srcfile(const char *fnam, enum saveformats mystyle)
{
int fd;
const char *fq_name;
NHFILE *nhfp = (NHFILE *) 0;
nhfp = new_nhfile();
if (nhfp) {
nhfp->mode = READING;
nhfp->structlevel = (mystyle == historical);
nhfp->fieldlevel = (mystyle > historical);
nhfp->ftype = NHF_SAVEFILE;
nhfp->fnidx = mystyle;
nhfp->fd = -1;
nhfp->addinfo =
(nhfp->fieldlevel && (mystyle = exportascii))
? TRUE
: FALSE;
(void) snprintf(srclogfilenm, sizeof srclogfilenm, "srcfile.%s.log",
(mystyle == historical) ? "historical" : "exportascii");
}
fq_name = fqname(fnam, SAVEPREFIX, 0);
nh_uncompress(fq_name);
if (nhfp && nhfp->structlevel) {
fd = open(fq_name, O_RDONLY | O_BINARY, 0);
if (fd < 0) {
zero_nhfile(nhfp);
free_nhfile(nhfp);
fprintf(stderr,
"\nsfctool error - unable to open historical-style "
"source file %s.\n",
fnam);
nhfp = (NHFILE *) 0;
nh_compress(fq_name);
} else {
nhfp->fd = fd;
#if defined(MSDOS)
setmode(nhfp->fd, O_BINARY);
#endif
}
}
if (nhfp && nhfp->fieldlevel) {
/* char savenamebuf[BUFSZ]; */
nhfp->fpdef = fopen(fnam, RDBMODE);
if (!nhfp->fpdef) {
zero_nhfile(nhfp);
free_nhfile(nhfp);
fprintf(stderr,
"\nsfctool error - unable to open fieldlevel-style "
"source file %s.\n",
fnam);
nhfp = (NHFILE *) 0;
nh_compress(fq_name);
}
}
return nhfp;
}
/* create dst file, overwriting one if it already exists */
static NHFILE *
create_dstfile(char *fnam, enum saveformats mystyle)
{
int fd, ret;
unsigned ln = 0;
FILE *cf;
NHFILE *nhfp = (NHFILE *) 0;
const char *fq_name;
char dstfnam[2048];
char *dsttmp;
boolean dst_file_exists = FALSE, ren_file_exists = FALSE;
nhUse(ret);
Snprintf(dstfnam, sizeof dstfnam, "%s", fnam);
if ((cf = fopen(dstfnam, RDBMODE)) != (FILE *) 0) {
dst_file_exists = TRUE;
(void) fclose(cf);
}
if (dst_file_exists) {
if (chosen_unconvert) {
ln = strlen(fnam);
if (renidx >= 0) {
ln += strlen(rensuffixes[renidx]) + 1; /* +1 for '.' */
} else if (renidx == -2) {
ln += strlen(thisdatamodel) + 1; /* +1 for '.' */
} else {
ln += strlen(thisdatamodel) + 1
+ 4; /* +1 for '.'; +4 for "not_" */
}
dsttmp = (char *) alloc(ln + 1);
Strcpy(dsttmp, fnam);
Strcat(dsttmp, ".");
if (renidx >= 0) {
Strcat(dsttmp, rensuffixes[renidx]);
} else if (renidx == -2) {
Strcat(dsttmp, thisdatamodel);
} else {
Strcat(dsttmp, "not_");
Strcat(dsttmp, thisdatamodel);
}
if ((cf = fopen(dsttmp, RDBMODE)) != (FILE *) 0) {
ren_file_exists = TRUE;
(void) fclose(cf);
}
if (ren_file_exists) {
(void) unlink(dsttmp);
}
ret = rename(fnam, dsttmp);
free((genericptr_t) dsttmp), dsttmp = 0;
} else {
if ((cf = fopen(dstfnam, RDBMODE)) != (FILE *) 0) {
ren_file_exists = TRUE;
(void) fclose(cf);
}
if (ren_file_exists) {
(void) unlink(dstfnam);
}
}
}
nhfp = new_nhfile();
if (nhfp) {
nhfp->mode = WRITING;
nhfp->ftype = NHF_SAVEFILE;
nhfp->structlevel = (mystyle == historical);
nhfp->fieldlevel = (mystyle > historical);
nhfp->fnidx = mystyle;
nhfp->fd = -1;
(void) snprintf(dstlogfilenm, sizeof dstlogfilenm, "dstfile.%s.log",
(mystyle == historical) ? "historical" : "exportascii");
}
if (nhfp && nhfp->structlevel) {
fq_name = fqname(dstfnam, SAVEPREFIX, 0);
#if defined(MICRO) || defined(WIN32)
fd = open(dstfnam, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK);
#else
fd = creat(dstfnam, FCMASK);
#endif
if (fd < 0) {
zero_nhfile(nhfp);
free_nhfile(nhfp);
fprintf(
stderr,
"Unable to create historical-style destination file %s.\n",
fnam);
nhfp = (NHFILE *) 0;
} else {
nhfp->fd = fd;
#if defined(MSDOS)
setmode(nhfp->fd, O_BINARY);
#endif
}
} else if (nhfp && nhfp->fieldlevel) {
fq_name = fqname(dstfnam, SAVEPREFIX, 0);
nhfp->fpdef = fopen(fq_name, WRBMODE);
if (!nhfp->fpdef) {
zero_nhfile(nhfp);
free_nhfile(nhfp);
fprintf(
stderr,
"Unable to create fieldlevel-style destination file %s.\n",
fnam);
nhfp = (NHFILE *) 0;
}
}
return nhfp;
}
static const char *
style_to_text(enum saveformats style)
{
const char *txt;
switch (style) {
case historical:
txt = "historical";
break;
case exportascii:
txt = "exportascii";
break;
case invalid:
default:
txt = "invalid";
break;
}
return txt;
}
void
my_sf_init(void)
{
decl_globals_init();
sfoprocs[invalid] = zerosfoprocs;
sfiprocs[invalid] = zerosfiprocs;
sfoprocs[historical] = historical_sfo_procs;
sfiprocs[historical] = historical_sfi_procs;
sfoflprocs[exportascii] = exportascii_sfo_procs;
sfiflprocs[exportascii] = exportascii_sfi_procs;
}
/* delete savefile */
int
delete_savefile(void)
{
return 0; /* for restore_saved_game() (ex-xxxmain.c) test */
}
static void
read_sysconf(void)
{
#ifdef SYSCF
/* someday there may be other SYSCF alternatives besides text file */
#ifdef SYSCF_FILE
/* If SYSCF_FILE is specified, it _must_ exist... */
assure_syscf_file();
config_error_init(TRUE, SYSCF_FILE, FALSE);
if (!read_config_file(SYSCF_FILE, set_in_sysconf)) {
if (config_error_done() && !iflags.initoptions_noterminate)
nh_terminate(EXIT_FAILURE);
}
config_error_done();
/*
* TODO [maybe]: parse the sysopt entries which are space-separated
* lists of usernames into arrays with one name per element.
*/
#endif
#endif /* SYSCF */
}
DISABLE_WARNING_FORMAT_NONLITERAL
/* provided for linkage only */
void
error(const char *s, ...)
{
va_list the_args;
va_start(the_args, s);
printf(s, the_args);
va_end(the_args);
exit(EXIT_FAILURE);
}
void
pline(const char *s, ...)
{
va_list the_args;
va_start(the_args, s);
printf(s, the_args);
va_end(the_args);
}
void
impossible(const char *s, ...)
{
va_list the_args;
va_start(the_args, s);
printf(s, the_args);
va_end(the_args);
exit(EXIT_FAILURE);
}
RESTORE_WARNING_FORMAT_NONLITERAL
/* TIME_type: type of the argument to time(); we actually use &(time_t) */
#if defined(BSD) && !defined(POSIX_TYPES)
#define TIME_type long *
#else
#define TIME_type time_t *
#endif
/* LOCALTIME_type: type of the argument to localtime() */
#if (defined(ULTRIX) && !(defined(ULTRIX_PROTO) || defined(NHSTDC))) \
|| (defined(BSD) && !defined(POSIX_TYPES))
#define LOCALTIME_type long *
#else
#define LOCALTIME_type time_t *
#endif
#if defined(AMIGA) && !defined(AZTEC_C) && !defined(__SASC_60) \
&& !defined(_DCC) && !defined(__GNUC__)
extern struct tm *localtime(time_t *);
#endif
static struct tm *getlt(void);
time_t
getnow(void)
{
time_t datetime = 0;
(void) time((TIME_type) &datetime);
return datetime;
}
static struct tm *
getlt(void)
{
time_t date = getnow();
return localtime((LOCALTIME_type) &date);
}
int
getyear(void)
{
return (1900 + getlt()->tm_year);
}
time_t
time_from_yyyymmddhhmmss(char *buf)
{
int k;
time_t timeresult = (time_t) 0;
struct tm t, *lt;
char *d, *p, y[5], mo[3], md[3], h[3], mi[3], s[3];
if (buf && strlen(buf) == 14) {
d = buf;
p = y; /* year */
for (k = 0; k < 4; ++k)
*p++ = *d++;
*p = '\0';
p = mo; /* month */
for (k = 0; k < 2; ++k)
*p++ = *d++;
*p = '\0';
p = md; /* day */
for (k = 0; k < 2; ++k)
*p++ = *d++;
*p = '\0';
p = h; /* hour */
for (k = 0; k < 2; ++k)
*p++ = *d++;
*p = '\0';
p = mi; /* minutes */
for (k = 0; k < 2; ++k)
*p++ = *d++;
*p = '\0';
p = s; /* seconds */
for (k = 0; k < 2; ++k)
*p++ = *d++;
*p = '\0';
lt = getlt();
if (lt) {
t = *lt;
t.tm_year = atoi(y) - 1900;
t.tm_mon = atoi(mo) - 1;
t.tm_mday = atoi(md);
t.tm_hour = atoi(h);
t.tm_min = atoi(mi);
t.tm_sec = atoi(s);
timeresult = mktime(&t);
}
return timeresult;
}
return (time_t) 0;
}
char *
yyyymmddhhmmss(time_t date)
{
long datenum;
static char datestr[15];
struct tm *lt;
if (date == 0)
lt = getlt();
else
#if (defined(ULTRIX) && !(defined(ULTRIX_PROTO) || defined(NHSTDC))) \
|| defined(BSD)
lt = localtime((long *) (&date));
#else
lt = localtime(&date);
#endif
/* just in case somebody's localtime supplies (year % 100)
rather than the expected (year - 1900) */
if (lt->tm_year < 70)
datenum = (long) lt->tm_year + 2000L;
else
datenum = (long) lt->tm_year + 1900L;
Snprintf(datestr, sizeof datestr, "%04ld%02d%02d%02d%02d%02d",
datenum, lt->tm_mon + 1,
lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec);
return datestr;
}
DISABLE_WARNING_FORMAT_NONLITERAL
void
raw_printf(const char *line, ...)
{
va_list the_args;
va_start(the_args, line);
fprintf(stdout, line, the_args);
va_end(the_args);
}
RESTORE_WARNING_FORMAT_NONLITERAL
#ifdef UNIX
/* normalize file name - we don't like .'s, /'s, spaces */
void
regularize(char *s)
{
register char *lp;
while ((lp = strchr(s, '.')) != 0 || (lp = strchr(s, '/')) != 0
|| (lp = strchr(s, ' ')) != 0)
*lp = '_';
#if defined(SYSV) && !defined(AIX_31) && !defined(SVR4) && !defined(LINUX) \
&& !defined(__APPLE__)
/* avoid problems with 14 character file name limit */
#ifdef COMPRESS
/* leave room for .e from error and .Z from compress appended to
* save files */
{
#ifdef COMPRESS_EXTENSION
int i = 12 - strlen(COMPRESS_EXTENSION);
#else
int i = 10; /* should never happen... */
#endif
if (strlen(s) > i)
s[i] = '\0';
}
#else
if (strlen(s) > 11)
/* leave room for .nn appended to level files */
s[11] = '\0';
#endif
#endif
}
#endif
int
util_strncmpi(const char *s1, const char *s2, size_t sz)
{
register char t1, t2;
while (sz--) {
if (!*s2)
return (*s1 != 0); /* s1 >= s2 */
else if (!*s1)
return -1; /* s1 < s2 */
t1 = lowc(*s1++);
t2 = lowc(*s2++);
if (t1 != t2)
return (t1 > t2) ? 1 : -1;
}
return 0; /* s1 == s2 */
}
/* should be called with either EXIT_SUCCESS or EXIT_FAILURE */
void
nh_terminate(int status)
{
nethack_exit(status);
}
#ifndef UNIX
void
nethack_exit(int code)
{
exit(code);
}
#endif /* UNIX */
#ifdef UNIX
#ifdef CHDIR
static void
chdirx(const char *dir)
{
if (dir) {
#ifdef SECURE
(void) setgid(getgid());
(void) setuid(getuid()); /* Ron Wessels */
#endif
} else {
/* non-default data files is a sign that scores may not be
* compatible, or perhaps that a binary not fitting this
* system's layout is being used.
*/
#ifdef VAR_PLAYGROUND
int len = strlen(VAR_PLAYGROUND);
gf.fqn_prefix[SCOREPREFIX] = (char *) alloc(len + 2);
Strcpy(gf.fqn_prefix[SCOREPREFIX], VAR_PLAYGROUND);
if (gf.fqn_prefix[SCOREPREFIX][len - 1] != '/') {
gf.fqn_prefix[SCOREPREFIX][len] = '/';
gf.fqn_prefix[SCOREPREFIX][len + 1] = '\0';
}
#endif
}
#ifdef HACKDIR
if (dir == (const char *) 0)
dir = HACKDIR;
#endif
if (dir && chdir(dir) < 0) {
perror(dir);
error("Cannot chdir to %s.", dir);
}
}
#endif /* CHDIR */
#endif /* UNIX */
#ifdef WIN32
/*
* Strip out troublesome file system characters.
*/
void nt_regularize(char* s) /* normalize file name */
{
unsigned char *lp;
for (lp = (unsigned char *) s; *lp; lp++)
if (*lp == '?' || *lp == '"' || *lp == '\\' || *lp == '/'
|| *lp == '>' || *lp == '<' || *lp == '*' || *lp == '|'
|| *lp == ':' || (*lp > 127))
*lp = '_';
}
#endif /* WIN32 */
/* duplicated code from options.c */
/* most environment variables will eventually be printed in an error
* message if they don't work, and most error message paths go through
* BUFSZ buffers, which could be overflowed by a maliciously long
* environment variable. If a variable can legitimately be long, or
* if it's put in a smaller buffer, the responsible code will have to
* bounds-check itself.
*/
char *
nh_getenv(const char *ev)
{
char *getev = getenv(ev);
if (getev && strlen(getev) <= (BUFSZ / 2))
return getev;
else
return (char *) 0;
}
void
done1(int sig_unused UNUSED)
{
#ifndef NO_SIGNAL
(void) signal(SIGINT, SIG_IGN);
#endif
if (flags.ignintr) {
#ifndef NO_SIGNAL
(void) signal(SIGINT, (SIG_RET_TYPE) done1);
#endif
}
}
/*
* I hate having to duplicate this code here, but it is much simpler to
* add these here than take steps to link with mkobj.c, do_name.c, priest.c,
* vault.c, shknam.c, minion.c, dog.c, etc.
*/
/* allocate space for a monster's name; removes old name if there is one */
void
new_mgivenname(struct monst *mon,
int lth) /* desired length (caller handles adding 1
for terminator) */
{
if (lth) {
/* allocate mextra if necessary; otherwise get rid of old name */
if (!mon->mextra)
mon->mextra = newmextra();
else
free_mgivenname(mon); /* already has mextra, might also have name */
MGIVENNAME(mon) = (char *) alloc((unsigned) lth);
} else {
/* zero length: the new name is empty; get rid of the old name */
if (has_mgivenname(mon))
free_mgivenname(mon);
}
}
/* release a monster's name; retains mextra even if all fields are now null */
void
free_mgivenname(struct monst *mon)
{
if (has_mgivenname(mon)) {
free((genericptr_t) MGIVENNAME(mon));
MGIVENNAME(mon) = (char *) 0;
}
}
void
newegd(struct monst *mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!EGD(mtmp)) {
EGD(mtmp) = (struct egd *) alloc(sizeof (struct egd));
(void) memset((genericptr_t) EGD(mtmp), 0, sizeof (struct egd));
}
}
void
free_egd(struct monst *mtmp)
{
if (mtmp->mextra && EGD(mtmp)) {
free((genericptr_t) EGD(mtmp));
EGD(mtmp) = (struct egd *) 0;
}
mtmp->isgd = 0;
}
void
newepri(struct monst *mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!EPRI(mtmp)) {
EPRI(mtmp) = (struct epri *) alloc(sizeof(struct epri));
(void) memset((genericptr_t) EPRI(mtmp), 0, sizeof(struct epri));
}
}
void
free_epri(struct monst *mtmp)
{
if (mtmp->mextra && EPRI(mtmp)) {
free((genericptr_t) EPRI(mtmp));
EPRI(mtmp) = (struct epri *) 0;
}
mtmp->ispriest = 0;
}
void
neweshk(struct monst* mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!ESHK(mtmp))
ESHK(mtmp) = (struct eshk *) alloc(sizeof(struct eshk));
(void) memset((genericptr_t) ESHK(mtmp), 0, sizeof(struct eshk));
ESHK(mtmp)->bill_p = (struct bill_x *) 0;
}
void
free_eshk(struct monst* mtmp)
{
if (mtmp->mextra && ESHK(mtmp)) {
free((genericptr_t) ESHK(mtmp));
ESHK(mtmp) = (struct eshk *) 0;
}
mtmp->isshk = 0;
}
void
newemin(struct monst *mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!EMIN(mtmp)) {
EMIN(mtmp) = (struct emin *) alloc(sizeof(struct emin));
(void) memset((genericptr_t) EMIN(mtmp), 0, sizeof(struct emin));
}
}
void
free_emin(struct monst *mtmp)
{
if (mtmp->mextra && EMIN(mtmp)) {
free((genericptr_t) EMIN(mtmp));
EMIN(mtmp) = (struct emin *) 0;
}
mtmp->isminion = 0;
}
void
newedog(struct monst *mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!EDOG(mtmp)) {
EDOG(mtmp) = (struct edog *) alloc(sizeof(struct edog));
(void) memset((genericptr_t) EDOG(mtmp), 0, sizeof(struct edog));
}
}
void
free_edog(struct monst *mtmp)
{
if (mtmp->mextra && EDOG(mtmp)) {
free((genericptr_t) EDOG(mtmp));
EDOG(mtmp) = (struct edog *) 0;
}
mtmp->mtame = 0;
}
void
newebones(struct monst *mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!EBONES(mtmp)) {
EBONES(mtmp) = (struct ebones *) alloc(sizeof(struct ebones));
(void) memset((genericptr_t) EBONES(mtmp), 0, sizeof(struct ebones));
}
}
void
free_ebones(struct monst *mtmp)
{
if (mtmp->mextra && EBONES(mtmp)) {
free((genericptr_t) EBONES(mtmp));
EBONES(mtmp) = (struct ebones *) 0;
}
}
static const struct mextra zeromextra = DUMMY;
static void
init_mextra(struct mextra *mex)
{
*mex = zeromextra;
mex->mcorpsenm = NON_PM;
}
struct mextra *
newmextra(void)
{
struct mextra *mextra;
mextra = (struct mextra *) alloc(sizeof(struct mextra));
init_mextra(mextra);
return mextra;
}
void
newomonst(struct obj* otmp)
{
if (!otmp->oextra)
otmp->oextra = newoextra();
if (!OMONST(otmp)) {
struct monst *m = newmonst();
*m = cg.zeromonst;
OMONST(otmp) = m;
}
}
void
free_omonst(struct obj* otmp)
{
if (otmp->oextra) {
struct monst *m = OMONST(otmp);
if (m) {
if (m->mextra)
dealloc_mextra(m);
free((genericptr_t) m);
OMONST(otmp) = (struct monst *) 0;
}
}
}
void
newomid(struct obj* otmp)
{
if (!otmp->oextra)
otmp->oextra = newoextra();
OMID(otmp) = 0;
}
void
free_omid(struct obj* otmp)
{
OMID(otmp) = 0;
}
void
new_omailcmd(struct obj* otmp, const char * response_cmd)
{
if (!otmp->oextra)
otmp->oextra = newoextra();
if (OMAILCMD(otmp))
free_omailcmd(otmp);
OMAILCMD(otmp) = dupstr(response_cmd);
}
void
free_omailcmd(struct obj* otmp)
{
if (otmp->oextra && OMAILCMD(otmp)) {
free((genericptr_t) OMAILCMD(otmp));
OMAILCMD(otmp) = (char *) 0;
}
}
static const struct oextra zerooextra = DUMMY;
static void
init_oextra(struct oextra* oex)
{
*oex = zerooextra;
}
struct oextra *
newoextra(void)
{
struct oextra *oextra;
oextra = (struct oextra *) alloc(sizeof (struct oextra));
init_oextra(oextra);
return oextra;
}
void
dealloc_mextra(struct monst* m)
{
struct mextra *x = m->mextra;
if (x) {
if (x->mgivenname)
free((genericptr_t) x->mgivenname);
if (x->egd)
free((genericptr_t) x->egd);
if (x->epri)
free((genericptr_t) x->epri);
if (x->eshk)
free((genericptr_t) x->eshk);
if (x->emin)
free((genericptr_t) x->emin);
if (x->edog)
free((genericptr_t) x->edog);
if (x->ebones)
free((genericptr_t) x->ebones);
/* [no action needed for x->mcorpsenm] */
free((genericptr_t) x);
m->mextra = (struct mextra *) 0;
}
}
void
dealloc_monst(struct monst* mon)
{
if (mon->mextra)
dealloc_mextra(mon);
free((genericptr_t) mon);
}
/* allocate space for an object's name; removes old name if there is one */
void
new_oname(struct obj *obj,
int lth) /* desired length (caller handles adding 1
for terminator) */
{
if (lth) {
/* allocate oextra if necessary; otherwise get rid of old name */
if (!obj->oextra)
obj->oextra = newoextra();
else
free_oname(obj); /* already has oextra, might also have name */
ONAME(obj) = (char *) alloc((unsigned) lth);
} else {
/* zero length: the new name is empty; get rid of the old name */
if (has_oname(obj))
free_oname(obj);
}
}
/* release an object's name; retains oextra even if all fields are now null */
void
free_oname(struct obj *obj)
{
if (has_oname(obj)) {
free((genericptr_t) ONAME(obj));
ONAME(obj) = (char *) 0;
}
}
#ifdef WIN32
void
win32_abort(void)
{
abort();
}
#endif
static int
length_without_val(const char *user_string, int len)
{
const char *p = strchr(user_string, ':'), *q = strchr(user_string, '=');
if (!p || (q && q < p))
p = q;
if (p) {
/* 'user_string' hasn't necessarily been through mungspaces()
so might have tabs or consecutive spaces */
while (p > user_string && isspace((uchar) * (p - 1)))
p--;
len = (int) (p - user_string);
}
return len;
}
/* check whether a user-supplied option string is a proper leading
substring of a particular option name; option string might have
a colon or equals sign and arbitrary value appended to it */
boolean
match_optname(const char *user_string, const char *optn_name, int min_length,
boolean val_allowed)
{
int len = (int) strlen(user_string);
if (val_allowed)
len = length_without_val(user_string, len);
return (boolean) (len >= min_length
&& !strncmpi(optn_name, user_string, len));
}
/* sfctool.c */