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
1324 lines
34 KiB
C
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], <mp, "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 */
|