1392 lines
36 KiB
C
1392 lines
36 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);
|
|
static void usage(int argc, char **argv);
|
|
static const char *briefname(const char *fnam);
|
|
|
|
void init_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
|
|
ATTRNORETURN void nh_terminate(int) NORETURN; /* bwrite() calls this */
|
|
static void chdirx(const char *);
|
|
#else
|
|
ATTRNORETURN 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, char *argv[])
|
|
{
|
|
int arg;
|
|
char folderbuf[5000];
|
|
const char *suffix = (convertstyle == exportascii) ? ".exportascii" : "";
|
|
boolean add_folder = TRUE, add_extension = FALSE;
|
|
|
|
#ifdef WIN32
|
|
const char *default_extension = ".NetHack-saved-game";
|
|
size_t sz;
|
|
#else
|
|
const char *default_extension = "";
|
|
#endif
|
|
|
|
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();
|
|
thisdatamodel = datamodel(0);
|
|
if (argc < 3 && !(argc == 2 && !strcmp(argv[1], "-d"))) {
|
|
usage(argc, argv);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
for (arg = 1; arg < argc; ++arg) {
|
|
if (arg == 1 && !strcmp(argv[arg], "-d")) {
|
|
fprintf(
|
|
stdout,
|
|
"\nThe historical savefile datamodel supported by this utility is %s (%s).\n",
|
|
thisdatamodel, datamodel(1));
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
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;
|
|
}
|
|
#ifdef WIN32
|
|
/* On Windows we allow specifying the savefile name without the extention
|
|
* in the arguments */
|
|
if (strstr(argv[arg], default_extension) == 0)
|
|
add_extension = TRUE;
|
|
#endif
|
|
if (explicit_option) {
|
|
if (add_folder)
|
|
ln += strlen(folderbuf);
|
|
if (add_extension)
|
|
ln += strlen(default_extension);
|
|
unconverted_filename = (char *) alloc((int) ln + 1);
|
|
Snprintf(unconverted_filename, ln + 1, "%s%s%s%s",
|
|
add_folder ? folderbuf : "",
|
|
addseparator ? "/" : "",
|
|
argv[arg],
|
|
add_extension ? default_extension : "");
|
|
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 */
|
|
}
|
|
|
|
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); */
|
|
const char *dmfile;
|
|
|
|
if ((nhfp[srcidx] = open_srcfile(srcfnam, srcstyle)) == 0)
|
|
return 0;
|
|
sfstatus = validate(nhfp[srcidx], srcfnam, FALSE);
|
|
dmfile = what_datamodel_is_this(0,
|
|
cscbuf[1], /* short */
|
|
cscbuf[2], /* int */
|
|
cscbuf[3], /* long */
|
|
cscbuf[4], /* long long */
|
|
cscbuf[5]); /* ptr */
|
|
if (sfstatus > SF_UPTODATE
|
|
&& ((sfstatus <= SF_CRITICAL_BYTE_COUNT_MISMATCH) || !unconvert)) {
|
|
fprintf(stderr,
|
|
"The %s savefile %s%s%s not compatible with this %s%sutility.\n",
|
|
briefname(srcfnam),
|
|
dmfile ? "is a " : "",
|
|
dmfile ? dmfile : "",
|
|
dmfile ? " savefile, thus" : " is",
|
|
thisdatamodel ? thisdatamodel : "",
|
|
thisdatamodel ? " " : "");
|
|
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 savefile called %s.\n",
|
|
briefname((const char *) converted_filename),
|
|
dmfile,
|
|
briefname((const char *) unconverted_filename));
|
|
else
|
|
fprintf(stdout, "\n\nconverting %s %s format %s to %s format\n",
|
|
style_to_text(srcstyle),
|
|
thisdatamodel,
|
|
briefname((const char *) unconverted_filename),
|
|
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) {
|
|
init_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) {
|
|
init_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) {
|
|
init_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) {
|
|
init_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 *
|
|
briefname(const char *fnam)
|
|
{
|
|
const char *bn = fnam, *sep = (const char *) 0;
|
|
|
|
if ((sep = strrchr(fnam, '/')) || (sep = strrchr(fnam, '\\'))
|
|
|| (sep = strrchr(fnam, ':')))
|
|
bn = sep + 1;
|
|
return bn;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
staticfn void
|
|
usage(int argc UNUSED, char **argv)
|
|
{
|
|
char *cp = argv[0], *sep = (char *) 0;
|
|
|
|
if ((sep = strrchr(cp, '/')) || (sep = strrchr(cp, '\\'))
|
|
|| (sep = strrchr(cp, ':')))
|
|
cp = sep + 1;
|
|
fprintf(stderr,
|
|
"\nTo convert a savefile to export format:\n %s %s %s\n", cp,
|
|
"-c", "savefile");
|
|
fprintf(stderr,
|
|
"\nTo unconvert an exported savefile back into a savefile:\n %s %s %s\n", cp,
|
|
"-u", "savefile");
|
|
}
|
|
/* sfctool.c */
|