Files
nethack/util/makedefs.c
nhmall a42d3dadce Revert "remove pasted comment"
This reverts commit c33c49295d.
2023-11-19 17:36:00 -05:00

2279 lines
66 KiB
C

/* NetHack 3.7 makedefs.c $NHDT-Date: 1693083328 2023/08/26 20:55:28 $ $NHDT-Branch: keni-crashweb2 $:$NHDT-Revision: 1.226 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Kenneth Lorber, Kensington, Maryland, 2015. */
/* Copyright (c) M. Stephenson, 1990, 1991. */
/* Copyright (c) Dean Luick, 1990. */
/* NetHack may be freely redistributed. See license for details. */
#define MAKEDEFS_C /* use to conditionally include file sections */
#include "config.h"
#include "permonst.h"
#include "objclass.h"
#include "sym.h"
#include "artilist.h"
#include "dungeon.h"
#include "obj.h"
#include "monst.h"
#include "you.h"
#include "context.h"
#include "flag.h"
#include "dlb.h"
#include <ctype.h>
#ifdef MAC
#if defined(__SC__) || defined(__MRC__) /* MPW compilers */
#define MPWTOOL
#include <CursorCtl.h>
#include <string.h>
#else /* MAC without MPWTOOL */
#define MACsansMPWTOOL
#endif
#endif /* MAC */
#ifndef MPWTOOL
#define SpinCursor(x)
#endif
#ifdef MD_USE_TMPFILE_S
#include <errno.h>
#endif
#define Fprintf (void) fprintf
#define Fclose (void) fclose
#define Unlink (void) unlink
#if !defined(AMIGA) || defined(AZTEC_C)
#define rewind(fp) fseek((fp), 0L, SEEK_SET) /* guarantee a return value */
#endif
/* names of files to be generated */
#define ORACLE_FILE "oracles"
#define DATA_FILE "data"
#define RUMOR_FILE "rumors"
/* These are affiliated with outdated options
but we define them for us in messages */
#define DATE_FILE "date.h"
#define MONST_FILE "pm.h"
#define ONAME_FILE "onames.h"
#ifndef OPTIONS_FILE
#define OPTIONS_FILE "options"
#endif
#define DGN_I_FILE "dungeon.def"
#define DGN_O_FILE "dungeon.pdf"
#define QTXT_I_FILE "quest.txt"
#define QTXT_O_FILE "quest.dat"
#define GITINFO_FILE "gitinfo.txt"
/* locations for those files */
#ifdef AMIGA
#define FILE_PREFIX
#define SOURCE_TEMPLATE "NH:src/%s"
#define DATA_TEMPLATE "NH:slib/%s"
#define DATA_IN_TEMPLATE "NH:dat/%s"
#if defined(OLD_MAKEDEFS_OPTIONS)
#define INCLUDE_TEMPLATE "NH:include/t.%s"
#define DGN_TEMPLATE "NH:dat/%s" /* where dungeon.pdf file goes */
#endif /* OLD_MAKEDEFS_OPTIONS */
#else /* not AMIGA */
#if defined(MAC) && !defined(__MACH__)
/* MacOS 9 or earlier */
#define SOURCE_TEMPLATE ":src:%s"
#if __SC__ || __MRC__
#define DATA_TEMPLATE ":Dungeon:%s"
#else
#define DATA_TEMPLATE ":lib:%s"
#endif /* __SC__ || __MRC__ */
#define DATA_IN_TEMPLATE ":dat:%s"
#if defined(OLD_MAKEDEFS_OPTIONS)
#define INCLUDE_TEMPLATE ":include:%s"
#define DGN_TEMPLATE ":dat:%s" /* where dungeon.pdf file goes */
#endif /* OLD_MAKEDEFS_OPTIONS */
#else /* neither AMIGA nor MAC */
#ifdef OS2
#define SOURCE_TEMPLATE "..\\src\\%s"
#define DATA_TEMPLATE "..\\dat\\%s"
#define DATA_IN_TEMPLATE "..\\dat\\%s"
#if defined(OLD_MAKEDEFS_OPTIONS)
#define INCLUDE_TEMPLATE "..\\include\\%s"
#define DGN_TEMPLATE "..\\dat\\%s" /* where dungeon.pdf file goes */
#endif /* OLD_MAKEDEFS_OPTIONS */
#else /* not AMIGA, MAC, or OS2 */
#define SOURCE_TEMPLATE "../src/%s"
#define DATA_TEMPLATE "../dat/%s"
#define DATA_IN_TEMPLATE "../dat/%s"
#if defined(OLD_MAKEDEFS_OPTIONS)
#define INCLUDE_TEMPLATE "../include/%s"
#define DGN_TEMPLATE "../dat/%s" /* where dungeon.pdf file goes */
#endif /* OLD_MAKEDEFS_OPTIONS */
#endif /* else !OS2 */
#endif /* else !MAC */
#endif /* else !AMIGA */
static const char
*Dont_Edit_Data =
"#\tThis data file is generated by 'makedefs'. Do not edit. \n";
#if defined(OLD_MAKEDEFS_OPTIONS)
static const char
*Reference_file =
"/* This file is generated by 'makedefs' for reference purposes only. */\n"
"/* It is no longer used in the NetHack build. */\n";
#endif /* OLD_MAKEDEFS_OPTIONS */
static struct version_info version;
#define FLG_TEMPFILE 0x01 /* flag for temp file */
#define MAXFNAMELEN 600
static char filename[MAXFNAMELEN];
#ifdef FILE_PREFIX
/* if defined, a first argument not starting with - is
* taken as a text string to be prepended to any
* output filename generated */
char *file_prefix = "";
#endif
#ifdef MACsansMPWTOOL
int main(void);
#else
int main(int, char **);
#endif
void do_makedefs(char *);
void do_data(void);
void do_rumors(void);
void do_oracles(void);
#if defined(OLD_MAKEDEFS_OPTIONS)
void do_date(void);
void do_dungeon(void);
void do_options(void);
void do_monstr(void);
void do_objs(void);
void do_permonst(void);
void do_questtxt(void);
#else
static const char *oldfunctionality(char);
#endif /* OLD_MAKEDEFS_OPTIONS */
extern void monst_globals_init(void); /* monst.c */
extern void objects_globals_init(void); /* objects.c */
static char *name_file(const char *, const char *);
static FILE *getfp(const char *, const char *, const char *, int);
static void do_ext_makedefs(int, char **);
static char *xcrypt(const char *);
static char *padline(char *, unsigned);
static unsigned long read_rumors_file(const char *, int *,
long *, unsigned long, unsigned);
static void do_rnd_access_file(const char *, const char *, unsigned);
static boolean d_filter(char *);
static boolean h_filter(char *);
static void opt_out_words(char *, int *);
static char *fgetline(FILE*);
#if defined(OLD_MAKEDEFS_OPTIONS)
static char *tmpdup(const char *);
static char *macronamelimit(char *, int);
static void windowing_sanity(void);
static boolean get_gitinfo(char *, char *);
static boolean use_enum = TRUE;
#endif
/* input, output, tmp */
static FILE *ifp, *ofp, *tfp;
#if defined(__BORLANDC__) && !defined(_WIN32)
extern unsigned _stklen = STKSIZ;
#endif
unsigned FITSuint_(unsigned long long, const char *, int);
/*
* Some of the routines in this source file were moved into .../src/mdlib
* to facilitate the use of a cross-compiler generation of some of the
* information for the target environment during the game compile portion
* under the cross-compiler and/or at runtime in some cases.
*/
#include "../src/mdlib.c"
ATTRNORETURN static void makedefs_exit(int) NORETURN;
ATTRNORETURN static void
makedefs_exit(int how)
{
exit(how);
/*NOTREACHED*/
}
#ifdef MACsansMPWTOOL
int
main(void)
{
const char *def_options = "odemvpqrshz";
char buf[100];
int len;
printf("Enter options to run: [%s] ", def_options);
fflush(stdout);
fgets(buf, 100, stdin);
len = strlen(buf);
if (len <= 1)
Strcpy(buf, def_options);
else
buf[len - 1] = 0; /* remove return */
if (buf[0] == '-' && buf[1] == '-') {
#if 0
split up buf into words
do_ext_makedefs(fakeargc, fakeargv);
#else
printf("extended makedefs not implemented for Mac OS9\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
#endif
}
do_makedefs(buf);
makedefs_exit(EXIT_SUCCESS);
/*NOTREACHED*/
return 0;
}
#else /* ! MAC */
DISABLE_WARNING_UNREACHABLE_CODE
int
main(int argc, char *argv[])
{
if ((argc == 1) ||
((argc != 2)
#ifdef FILE_PREFIX
&& (argc != 3)
#endif
&& !(argv[1][0] == '-' && argv[1][1] == '-'))) {
Fprintf(stderr, "Bad arg count (%d).\n", argc - 1);
(void) fflush(stderr);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
#ifdef FILE_PREFIX
if (argc >= 2 && argv[1][0] != '-') {
file_prefix = argv[1];
argc--;
argv++;
}
#endif
if (argv[1][0] == '-' && argv[1][1] == '-') {
do_ext_makedefs(argc, argv);
} else {
do_makedefs(&argv[1][1]);
}
makedefs_exit(EXIT_SUCCESS);
/*NOTREACHED*/
return 0;
}
#endif
RESTORE_WARNINGS
void
do_makedefs(char *options)
{
boolean more_than_one;
objects_globals_init();
monst_globals_init();
/* construct the current version number */
make_version();
more_than_one = strlen(options) > 1;
while (*options) {
if (more_than_one)
Fprintf(stderr, "makedefs -%c\n", *options);
switch (*options) {
case 'd':
case 'D':
do_data();
break;
case 'h':
case 'H':
do_oracles();
break;
case 'r':
case 'R':
do_rumors();
break;
case 's':
case 'S':
/*
* post-3.6.5:
* File must not be empty to avoid divide by 0
* in core's rn2(), so provide a default entry.
*/
do_rnd_access_file(EPITAPHFILE,
/* default epitaph: parody of the default engraving */
"No matter where I went, here I am.",
MD_PAD_RUMORS); /* '_RUMORS' is correct here */
do_rnd_access_file(ENGRAVEFILE,
/* default engraving: popularized by "The Adventures of
Buckaroo Bonzai Across the 8th Dimension" but predates
that 1984 movie; some attribute it to Confucius */
"No matter where you go, there you are.",
MD_PAD_RUMORS); /* '_RUMORS' used here too */
do_rnd_access_file(BOGUSMONFILE,
/* default bogusmon: iconic monster that isn't in nethack */
"grue", MD_PAD_BOGONS);
break;
#if defined(OLD_MAKEDEFS_OPTIONS)
case 'o':
case 'O':
do_objs();
break;
case 'e':
case 'E':
do_dungeon();
break;
case 'v':
case 'V':
do_date();
do_options();
break;
case 'p':
case 'P':
do_permonst();
break;
case 'q':
case 'Q':
do_questtxt();
break;
#else
case 'o': case 'O': case 'e': case 'E': case 'v': case 'V':
case 'p': case 'P': case 'q': case 'Q':
Fprintf(stderr, "Old makedefs option.\n"
"Rebuild makedefs with '-DOLD_MAKEDEFS_OPTIONS'"
" for '%c' (%s) support.\n",
*options, oldfunctionality(*options));
(void) fflush(stderr);
/*NOTREACHED*/
break;
#endif /* OLD_MAKEDEFS_OPTIONS */
default:
Fprintf(stderr, "Unknown option '%c'.\n", *options);
(void) fflush(stderr);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
options++;
}
if (more_than_one)
Fprintf(stderr, "Completed.\n"); /* feedback */
return;
}
#if !defined(OLD_MAKEDEFS_OPTIONS)
static const char *
oldfunctionality(char sought)
{
static struct ofn_s {
char lcoflet;
char ucoflet;
const char *ofnam;
} ofn[] = {
{ 'e', 'E', DGN_O_FILE },
{ 'o', 'O', ONAME_FILE },
{ 'p', 'P', MONST_FILE },
{ 'q', 'Q', QTXT_O_FILE },
{ 'v', 'V', DATE_FILE " & " OPTIONS_FILE },
};
int i;
for (i = 0; i < SIZE(ofn); ++i) {
if (sought == ofn[i].lcoflet || sought == ofn[i].ucoflet)
return ofn[i].ofnam;
}
return "unknown";
}
#endif /* !OLD_MAKEDEFS_OPTIONS */
static char namebuf[1000];
DISABLE_WARNING_FORMAT_NONLITERAL
static char *
name_file(const char* template, const char* tag)
{
Sprintf(namebuf, template, tag);
return namebuf;
}
#ifdef HAS_NO_MKSTEMP
static void delete_file(const char *template, const char *);
static void
delete_file(const char *template, const char *tag)
{
char *name = name_file(template, tag);
Unlink(name);
}
#endif
static FILE *
getfp(const char* template, const char* tag, const char* mode, int flg)
{
char *name = name_file(template, tag);
FILE *rv = (FILE *) 0;
boolean istemp = (flg & FLG_TEMPFILE) != 0;
#if !defined(HAS_NO_MKSTEMP) && !defined(MD_USE_TMPFILE_S)
char tmpfbuf[MAXFNAMELEN];
int tmpfd;
#endif
#ifdef MD_USE_TMPFILE_S
errno_t err;
#endif
#if defined(MD_USE_TMPFILE_S)
if (istemp) {
err = tmpfile_s(&rv);
#if defined(MSDOS) || defined(WIN32)
if (!err && (!strcmp(mode, WRTMODE) || !strcmp(mode, RDTMODE))) {
_setmode(fileno(rv), O_TEXT);
}
#endif
} else
#else /* MD_USE_TMPFILE_S */
#ifndef HAS_NO_MKSTEMP
if (istemp) {
(void) snprintf(tmpfbuf, sizeof tmpfbuf, DATA_TEMPLATE, "mdXXXXXX");
tmpfd = mkstemp(tmpfbuf);
if (tmpfd >= 0) {
rv = fdopen(tmpfd, WRTMODE); /* temp file is always read+write */
Unlink(tmpfbuf);
}
} else
#endif
#endif /* MD_USE_TMPFILE_S */
rv = fopen(name, mode);
if (!rv) {
Fprintf(stderr, "Can't open '%s' (mode=%s).\n",
#if !defined(HAS_NO_MKSTEMP) && !defined(MD_USE_TMPFILE_S)
istemp ? tmpfbuf :
#endif
name, mode);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
return rv;
}
RESTORE_WARNING_FORMAT_NONLITERAL
static boolean debug = FALSE;
static FILE *inputfp;
static FILE *outputfp;
struct grep_var {
const char *name;
int is_defined; /* 0 undef; 1 defined */
};
/* struct grep_var grep_vars[] and TODO_* constants in include file: */
#include "mdgrep.h"
static void do_grep_showvars(void);
static struct grep_var *grepsearch(const char *);
static int grep_check_id(const char *);
static void grep_show_wstack(const char *);
static char *do_grep_control(char *);
static void do_grep(void);
static void grep0(FILE *, FILE *, int);
static int grep_trace = 0;
#define IS_OPTION(str) if (!strcmp(&argv[0][2], str))
#define CONTINUE \
argv++, argc--; \
continue
#define CONSUME \
argv++, argc--; \
if (argc == 0) { \
Fprintf(stderr, "missing option\n"); \
makedefs_exit(EXIT_FAILURE); \
/*NOTREACHED*/ \
}
static void
do_ext_makedefs(int argc, char **argv)
{
int todo = 0;
argc--;
argv++; /* skip program name */
while (argc) {
if (argv[0][0] != '-')
break;
if (argv[0][1] != '-') {
Fprintf(stderr, "Can't mix - and -- options.\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
IS_OPTION("svs") {
/* short version string for packaging - note no \n */
char buf[100];
char delim[10];
argv++; /* not CONSUME */
delim[0] = '\0';
if (argv[0])
strcpy(delim, argv[0]);
Fprintf(stdout, "%s", mdlib_version_string(buf, delim));
makedefs_exit(EXIT_SUCCESS);
/*NOTREACHED*/
}
IS_OPTION("debug") {
debug = TRUE;
CONTINUE;
}
IS_OPTION("make") {
CONSUME;
do_makedefs(argv[0]);
makedefs_exit(EXIT_SUCCESS);
/*NOTREACHED*/
}
IS_OPTION("input") {
CONSUME;
if (!strcmp(argv[0], "-")) {
inputfp = stdin;
} else {
inputfp = fopen(argv[0], RDTMODE);
if (!inputfp) {
Fprintf(stderr, "Can't open '%s'.\n", argv[0]);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
}
CONTINUE;
}
IS_OPTION("output") {
CONSUME;
if (!strcmp(argv[0], "-")) {
outputfp = stdout;
} else {
outputfp = fopen(argv[0], WRTMODE);
if (!outputfp) {
Fprintf(stderr, "Can't open '%s'.\n", argv[0]);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
}
CONTINUE;
}
IS_OPTION("grep") {
if (todo) {
Fprintf(stderr, "Can't do grep and something else.\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
todo = TODO_GREP;
CONTINUE;
}
IS_OPTION("grep-showvars") {
do_grep_showvars();
makedefs_exit(EXIT_SUCCESS);
/*NOTREACHED*/
}
IS_OPTION("grep-trace") {
grep_trace = 1;
CONTINUE;
}
IS_OPTION("grep-define") {
struct grep_var *p;
CONSUME;
p = grepsearch(argv[0]);
if (p) {
p->is_defined = 1;
} else {
Fprintf(stderr, "Unknown symbol '%s'\n", argv[0]);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
CONTINUE;
}
IS_OPTION("grep-undef") {
struct grep_var *p;
CONSUME;
p = grepsearch(argv[0]);
if (p) {
p->is_defined = 0;
} else {
Fprintf(stderr, "Unknown symbol '%s'\n", argv[0]);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
CONTINUE;
}
IS_OPTION("grep-defined"){
struct grep_var *p;
CONSUME;
p = grepsearch(argv[0]);
// NB: Exit status is ready for the shell:
// 0=defined, 1=not defined
makedefs_exit(!(p && p->is_defined));
}
#ifdef notyet
IS_OPTION("help") {
}
#endif
Fprintf(stderr, "Unknown option '%s'.\n", argv[0]);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (argc) {
Fprintf(stderr, "unexpected argument '%s'.\n", argv[0]);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
switch (todo) {
default:
Fprintf(stderr, "Confused about what to do?\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
break;
case 0:
Fprintf(stderr, "Nothing to do?\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
break;
case TODO_GREP:
do_grep();
break;
}
return;
}
#undef IS_OPTION
#undef CONTINUE
#undef CONSUME
/*
* Filtering syntax:
* Any line NOT starting with a caret is either suppressed or passed
* through unchanged depending on the current conditional state.
*
* The default conditional state is printing on.
*
* Conditionals may be nested.
*
* makedefs will exit with a EXIT_FAILURE if any errors are detected;
* as many errors as possible are detected before giving up.
*
* Unknown identifiers are treated as TRUE and also as an error to
* allow processing to continue past the unknown identifier (note
* that "#undef" is different than unknown).
*
* Any line starting with a caret is a control line; as in C, zero or
* more spaces may be embedded in the line almost anywhere; the caret
* MUST be in column 1.
* (XXX for the moment, no white space is allowed after the caret because
* existing lines in the docs look like that.)
*
* Control lines:
* ^^ a line starting with a (single) literal caret
* ^# a comment - the line is ignored
* ^?ID if defined(ID)
* ^!ID if !defined(ID)
* ^: else
* ^. endif
*/
#define GREP_MAGIC '^'
#define GREP_STACK_SIZE 100
#ifdef notyet
static int grep_rewrite = 0; /* need to (possibly) rewrite lines */
#endif
static int grep_writing = 1; /* need to copy lines to output */
static int grep_errors = 0;
static int grep_sp = 0;
#define ST_LD(old, opp) (((old) ? 1 : 0) | ((opp) ? 2 : 0))
#define ST_OLD(v) (((v) & 1) != 0)
#define ST_OPP(v) (((v) & 2) != 0)
#define ST_ELSE 4
static int grep_stack[GREP_STACK_SIZE] = { ST_LD(1, 0) };
static int grep_lineno = 0;
static void
do_grep_showvars(void)
{
int x;
for (x = 0; x < SIZE(grep_vars) - 1; x++) {
printf("%d\t%s\n", grep_vars[x].is_defined, grep_vars[x].name);
}
}
static struct grep_var *
grepsearch(const char* name)
{
/* XXX make into binary search */
int x = 0;
while (x < SIZE(grep_vars) - 1) {
if (!strcmp(grep_vars[x].name, name))
return &grep_vars[x];
x++;
}
return 0;
}
static int
grep_check_id(const char* id)
{
struct grep_var *rv;
while (*id && isspace((uchar) *id))
id++;
if (!*id) {
Fprintf(stderr, "missing identifier in line %d", grep_lineno);
grep_errors++;
return 0;
}
rv = grepsearch(id);
if (rv) {
if (grep_trace) {
Fprintf(outputfp, "ID %d %s\n", rv->is_defined, id);
}
return rv->is_defined;
}
if (grep_trace) {
Fprintf(outputfp, "ID U %s\n", id);
}
Fprintf(stderr, "unknown identifier '%s' in line %d.\n", id, grep_lineno);
grep_errors++;
return 2; /* So new features can be checked before makedefs
* is rebuilt. */
}
static void
grep_show_wstack(const char* tag)
{
int x;
if (!grep_trace)
return;
Fprintf(outputfp, "%s w=%d sp=%d\t", tag, grep_writing, grep_sp);
for (x = grep_sp; x >= 0 && x > grep_sp - 6; x--) {
Fprintf(outputfp, "[%d]=%d ", x, grep_stack[x]);
}
Fprintf(outputfp, "\n");
}
static char *
do_grep_control(char *buf)
{
int isif = 1;
char *buf0 = buf;
#if 1
if (isspace((uchar) buf[0]))
return &buf[-1]; /* XXX see docs above */
#else
while (buf[0] && isspace((uchar) buf[0]))
buf++;
#endif
switch (buf[0]) {
case '#': /* comment */
break;
case '.': /* end of if level */
if (grep_sp == 0) {
Fprintf(stderr, "unmatched ^. (endif) at line %d.\n",
grep_lineno);
grep_errors++;
} else {
grep_writing = ST_OLD(grep_stack[grep_sp--]);
grep_show_wstack("pop");
}
break;
case '!': /* if not ID */
isif = 0;
/* FALLTHROUGH */
case '?': /* if ID */
if (grep_sp == GREP_STACK_SIZE - 2) {
Fprintf(stderr, "stack overflow at line %d.", grep_lineno);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (grep_writing) {
isif = grep_check_id(&buf[1]) ? isif : !isif;
grep_stack[++grep_sp] = ST_LD(grep_writing, !isif);
grep_writing = isif;
} else {
grep_stack[++grep_sp] = ST_LD(0, 0);
/* grep_writing = 0; */
}
grep_show_wstack("push");
break;
case ':': /* else */
if (ST_ELSE & grep_stack[grep_sp]) {
Fprintf(stderr, "multiple : for same conditional at line %d.\n",
grep_lineno);
grep_errors++;
}
grep_writing = ST_OPP(grep_stack[grep_sp]);
grep_stack[grep_sp] |= ST_ELSE;
break;
#if defined(notyet)
case '(': /* start of expression */
#endif
case GREP_MAGIC: /* ^^ -> ^ */
return buf0;
default: {
char str[10];
if (isprint((uchar) buf[0])) {
str[0] = buf[0];
str[1] = '\0';
} else {
sprintf(str, "0x%02x", buf[0]);
}
Fprintf(stderr, "unknown control ^%s at line %d.\n", str,
grep_lineno);
grep_errors++;
} break;
}
return NULL;
}
#ifdef notyet
static void
do_grep_rewrite(buf)
char *buf;
{
/* no language features use this yet */
return;
}
#endif
static void grep0(FILE *, FILE *, int);
static void
do_grep(void)
{
if (!inputfp) {
Fprintf(stderr, "--grep requires --input\n");
}
if (!outputfp) {
Fprintf(stderr, "--grep requires --output\n");
}
if (!inputfp || !outputfp) {
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
grep0(inputfp, outputfp, 0);
}
static void
grep0(FILE *inputfp0, FILE* outputfp0, int flg)
{
#ifndef HAS_NO_MKSTEMP
/* if grep0 is passed FLG_TEMPFILE flag, it will
leave the output file open when it returns.
The caller will have to take care of calling
fclose() when it is done with the file */
boolean istemp = (flg & FLG_TEMPFILE) != 0;
#else
flg; // unused
#endif
char buf[16384]; /* looong, just in case */
while (!feof(inputfp0) && !ferror(inputfp0)) {
char *tmp;
char *buf1;
if (fgets(buf, sizeof(buf), inputfp0) == 0)
break;
if ((tmp = strchr(buf, '\n')))
*tmp = '\0';
grep_lineno++;
if (grep_trace) {
Fprintf(outputfp0, "%04d %c >%s\n", grep_lineno,
grep_writing ? ' ' : '#', buf);
}
if (buf[0] == GREP_MAGIC) {
buf1 = do_grep_control(&buf[1]);
if (!buf1)
continue;
} else {
buf1 = buf;
}
#ifdef notyet
if (grep_rewrite)
do_grep_rewrite(buf1);
#endif
if (grep_writing)
Fprintf(outputfp0, "%s\n", buf1);
}
if (ferror(inputfp0)) {
Fprintf(stderr, "read error!\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (ferror(outputfp0)) {
Fprintf(stderr, "write error!\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
fclose(inputfp0);
#ifndef HAS_NO_MKSTEMP
if (istemp)
rewind(outputfp0);
else
#endif
fclose(outputfp0);
if (grep_sp) {
Fprintf(stderr, "%d unterminated conditional level%s\n", grep_sp,
grep_sp == 1 ? "" : "s");
grep_errors++;
}
if (grep_errors) {
Fprintf(stderr, "%d error%s detected.\n", grep_errors,
grep_errors == 1 ? "" : "s");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
return;
}
/* trivial text encryption routine which can't be broken with `tr' */
static char *
xcrypt(const char* str)
{ /* duplicated in src/hacklib.c */
static char buf[BUFSZ];
register const char *p;
register char *q;
register int bitmask;
for (bitmask = 1, p = str, q = buf; *p; q++) {
*q = *p++;
if (*q & (32 | 64))
*q ^= bitmask;
if ((bitmask <<= 1) >= 32)
bitmask = 1;
}
*q = '\0';
return buf;
}
static char *
padline(char *line, unsigned padlength)
{
/*
* Rumor selection is accomplished by seeking to a random
* position in the file, advancing to newline, and taking
* the next line. Therefore, rumors which follow long-line
* rumors are most likely to be chosen and rumors which
* follow short-line rumors are least likely to be chosen.
* We ameliorate the latter by padding the shortest lines,
* increasing the chance of the random seek landing in them.
* The core's get_rnd_text() handles long lines in a way
* that results in even selection distribution.
*
* Random epitaphs, engravings, and hallucinatory monster
* names are in the same boat.
*/
char *endp;
unsigned len = (unsigned) strlen(line); /* includes newline */
if (len <= padlength) {
endp = strchr(line, '\n'); /* fgetline() guarantees a newline even if
* the input file's last line lacks one */
/* this is safe provided that padlength+1 is less than the allocation
amount used in fgetline(); currently 144 (BUFSZ/2+16) */
while (len++ < padlength) {
*endp++ = '_';
}
*endp++ = '\n';
*endp = '\0';
}
return line;
}
/* common code for do_rumors(). Return 0 on error. */
static unsigned long
read_rumors_file(
const char *file_ext,
int *rumor_count,
long *rumor_size,
unsigned long old_rumor_offset,
unsigned padlength)
{
char infile[MAXFNAMELEN];
char *line;
unsigned long rumor_offset;
Sprintf(infile, DATA_IN_TEMPLATE, RUMOR_FILE);
Strcat(infile, file_ext);
if (!(ifp = fopen(infile, RDTMODE))) {
perror(infile);
return 0L;
}
/* copy the rumors */
while ((line = fgetline(ifp)) != 0) {
(void) padline(line, padlength); /* make shortest lines be longer */
(*rumor_count)++;
#if 0
/*[if we forced binary output, this would be sufficient]*/
*rumor_size += strlen(line); /* includes newline */
#endif
(void) fputs(xcrypt(line), tfp);
free((genericptr_t) line);
}
/* record the current position; next rumors section will start here */
rumor_offset = (unsigned long) ftell(tfp);
Fclose(ifp); /* all done with rumors.file_ext */
/* the calculated value for *_rumor_count assumes that
a single-byte line terminator is in use; for platforms
which use two byte CR+LF, we need to override that value
[it's much simpler to do so unconditionally, rendering
the loop's accumulation above obsolete] */
*rumor_size = (long) (rumor_offset - old_rumor_offset);
return rumor_offset;
}
static void
do_rnd_access_file(
const char *fname,
const char *deflt_content,
unsigned padlength)
{
char *line, buf[BUFSZ];
Sprintf(filename, DATA_IN_TEMPLATE, fname);
Strcat(filename, ".txt");
if (!(ifp = fopen(filename, RDTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), DATA_TEMPLATE, fname);
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
Fprintf(ofp, "%s", Dont_Edit_Data);
/* write out the default content entry unconditionally instead of
waiting to see whether there are no regular output lines; if it
matches a regular entry (bogusmon "grue"), that entry will become
more likely to be picked than normal but it's nothing to worry about */
Strcpy(buf, deflt_content);
if (!strchr(buf, '\n')) /* lines from the file include trailing newline +*/
Strcat(buf, "\n"); /* so make sure that the default one does too */
(void) fputs(xcrypt(padline(buf, padlength)), ofp);
tfp = getfp(DATA_TEMPLATE, "grep.tmp", WRTMODE, FLG_TEMPFILE);
grep0(ifp, tfp, FLG_TEMPFILE);
#ifndef HAS_NO_MKSTEMP
ifp = tfp;
#else
ifp = getfp(DATA_TEMPLATE, "grep.tmp", RDTMODE, 0);
#endif
while ((line = fgetline(ifp)) != 0) {
if (line[0] != '#' && line[0] != '\n') {
(void) padline(line, padlength);
(void) fputs(xcrypt(line), ofp);
}
free((genericptr_t) line);
}
Fclose(ifp);
Fclose(ofp);
#ifdef HAS_NO_MKSTEMP
delete_file(DATA_TEMPLATE, "grep.tmp");
#endif
return;
}
DISABLE_WARNING_FORMAT_NONLITERAL
void
do_rumors(void)
{
char *line;
static const char rumors_header[] =
"%s%04d,%06ld,%06lx;%04d,%06ld,%06lx;0,0,%06lx\n";
char tempfile[MAXFNAMELEN];
int true_rumor_count, false_rumor_count;
long true_rumor_size, false_rumor_size;
unsigned long true_rumor_offset, false_rumor_offset, eof_offset;
Sprintf(tempfile, DATA_TEMPLATE, "rumors.tmp");
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), DATA_TEMPLATE, RUMOR_FILE);
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (!(tfp = fopen(tempfile, WRTMODE))) {
perror(tempfile);
Fclose(ofp);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
true_rumor_count = false_rumor_count = 0;
true_rumor_size = false_rumor_size = 0L;
true_rumor_offset = false_rumor_offset = eof_offset = 0L;
/* output a dummy header record; we'll replace it in final output */
Fprintf(tfp, rumors_header, Dont_Edit_Data, true_rumor_count,
true_rumor_size, true_rumor_offset, false_rumor_count,
false_rumor_size, false_rumor_offset, eof_offset);
/* record the current position; true rumors will start here */
true_rumor_offset = (unsigned long) ftell(tfp);
false_rumor_offset = (unsigned long) read_rumors_file(".tru", &true_rumor_count,
&true_rumor_size, true_rumor_offset,
MD_PAD_RUMORS);
if (!false_rumor_offset)
goto rumors_failure;
eof_offset = read_rumors_file(".fal", &false_rumor_count,
&false_rumor_size, false_rumor_offset,
MD_PAD_RUMORS);
if (!eof_offset)
goto rumors_failure;
/* get ready to transfer the contents of temp file to output file */
line = (char *) alloc(BUFSZ + MAXFNAMELEN);
Sprintf(line, "rewind of \"%s\"", tempfile);
if (rewind(tfp) != 0) {
perror(line);
free((genericptr_t) line);
goto rumors_failure;
}
free((genericptr_t) line);
/* output the header record */
Fprintf(ofp, rumors_header, Dont_Edit_Data, true_rumor_count,
true_rumor_size, true_rumor_offset, false_rumor_count,
false_rumor_size, false_rumor_offset, eof_offset);
/* skip the temp file's dummy header */
if (!(line = fgetline(tfp))) { /* "Don't Edit" */
perror(tempfile);
goto rumors_failure;
}
free((genericptr_t) line);
if (!(line = fgetline(tfp))) { /* count,size,offset */
perror(tempfile);
goto rumors_failure;
}
free((genericptr_t) line);
/* copy the rest of the temp file into the final output file */
while ((line = fgetline(tfp)) != 0) {
(void) fputs(line, ofp);
free((genericptr_t) line);
}
/* all done; delete temp file */
Fclose(tfp);
Unlink(tempfile);
Fclose(ofp);
return;
rumors_failure:
Fclose(ofp);
Unlink(filename); /* kill empty or incomplete output file */
Fclose(tfp);
Unlink(tempfile); /* and temporary file */
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
RESTORE_WARNING_FORMAT_NONLITERAL
void
do_data(void)
{
char infile[60], tempfile[60];
boolean ok;
long txt_offset;
int entry_cnt, line_cnt;
char *line;
Sprintf(tempfile, DATA_TEMPLATE, "database.tmp");
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), DATA_TEMPLATE, DATA_FILE);
Sprintf(infile, DATA_IN_TEMPLATE, DATA_FILE);
#ifdef SHORT_FILENAMES
Strcat(infile, ".bas");
#else
Strcat(infile, ".base");
#endif
if (!(ifp = fopen(infile, RDTMODE))) { /* data.base */
perror(infile);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (!(ofp = fopen(filename, WRTMODE))) { /* data */
perror(filename);
Fclose(ifp);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (!(tfp = fopen(tempfile, WRTMODE))) { /* database.tmp */
perror(tempfile);
Fclose(ifp);
Fclose(ofp);
Unlink(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
/* output a dummy header record; we'll rewind and overwrite it later */
Fprintf(ofp, "%s%08lx\n", Dont_Edit_Data, 0L);
entry_cnt = line_cnt = 0;
/* read through the input file and split it into two sections */
while ((line = fgetline(ifp)) != 0) {
if (d_filter(line)) {
free((genericptr_t) line);
continue;
}
if (*line > ' ') { /* got an entry name */
/* first finish previous entry */
if (line_cnt)
Fprintf(ofp, "%d\n", line_cnt), line_cnt = 0;
/* output the entry name */
(void) fputs(line, ofp);
entry_cnt++; /* update number of entries */
} else if (entry_cnt) { /* got some descriptive text */
/* update previous entry with current text offset */
if (!line_cnt)
Fprintf(ofp, "%ld,", ftell(tfp));
/* save the text line in the scratch file */
(void) fputs(line, tfp);
line_cnt++; /* update line counter */
}
free((genericptr_t) line);
}
/* output an end marker and then record the current position */
if (line_cnt)
Fprintf(ofp, "%d\n", line_cnt);
Fprintf(ofp, ".\n%ld,%d\n", ftell(tfp), 0);
txt_offset = ftell(ofp);
Fclose(ifp); /* all done with original input file */
/* reprocess the scratch file; 1st format an error msg, just in case */
line = malloc(BUFSZ + MAXFNAMELEN);
Sprintf(line, "rewind of \"%s\"", tempfile);
if (rewind(tfp) != 0)
goto dead_data;
free((genericptr_t) line);
/* copy all lines of text from the scratch file into the output file */
while ((line = fgetline(tfp)) != 0) {
(void) fputs(line, ofp);
free((genericptr_t) line);
}
/* finished with scratch file */
Fclose(tfp);
Unlink(tempfile); /* remove it */
/* update the first record of the output file; prepare error msg 1st */
line = (char *) alloc(BUFSZ + MAXFNAMELEN);
Sprintf(line, "rewind of \"%s\"", filename);
ok = (rewind(ofp) == 0);
if (ok) {
Sprintf(line, "header rewrite of \"%s\"", filename);
ok = (fprintf(ofp, "%s%08lx\n", Dont_Edit_Data,
(unsigned long) txt_offset) >= 0);
}
if (!ok) {
dead_data:
perror(line); /* report the problem */
free((genericptr_t) line);
/* close and kill the aborted output file, then give up */
Fclose(ofp);
Unlink(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
free((genericptr_t) line);
/* all done */
Fclose(ofp);
return;
}
/* routine to decide whether to discard something from oracles.txt */
static boolean
h_filter(char *line)
{
static boolean skip = FALSE;
char *tag;
SpinCursor(3);
if (*line == '#')
return TRUE; /* ignore comment lines */
tag = (char *) alloc((unsigned) strlen(line)); /* don't need +1 here */
if (sscanf(line, "----- %s", tag) == 1) {
skip = FALSE;
} else if (skip && !strncmp(line, "-----", 5))
skip = FALSE;
free((genericptr_t) tag);
return skip;
}
/* routine to decide whether to discard something from data.base */
static boolean
d_filter(char *line)
{
if (*line == '#')
return TRUE; /* ignore comment lines */
return FALSE;
}
static const char *special_oracle[] = {
"\"...it is rather disconcerting to be confronted with the",
"following theorem from [Baker, Gill, and Solovay, 1975].",
"",
"Theorem 7.18 There exist recursive languages A and B such that",
" (1) P(A) == NP(A), and",
" (2) P(B) != NP(B)",
"",
"This provides impressive evidence that the techniques that are",
("currently available will not suffice for proving that P != NP or"
" "),
"that P == NP.\" [Garey and Johnson, p. 185.]"
};
/*
The oracle file consists of a "do not edit" comment, a decimal count N
and set of N+1 hexadecimal fseek offsets, followed by N multiple-line
records, separated by "---" lines. The first oracle is a special case.
The input data contains just those multi-line records, separated by
"-----" lines.
*/
void
do_oracles(void)
{
char infile[60], tempfile[60];
boolean in_oracle, ok;
long fpos;
unsigned long txt_offset, offset;
int oracle_cnt;
register int i;
char *line;
Sprintf(tempfile, DATA_TEMPLATE, "oracles.tmp");
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), DATA_TEMPLATE, ORACLE_FILE);
Sprintf(infile, DATA_IN_TEMPLATE, ORACLE_FILE);
Strcat(infile, ".txt");
if (!(ifp = fopen(infile, RDTMODE))) {
perror(infile);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
Fclose(ifp);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
if (!(tfp = fopen(tempfile, WRTMODE))) { /* oracles.tmp */
perror(tempfile);
Fclose(ifp);
Fclose(ofp);
Unlink(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
/* output a dummy header record; we'll rewind and overwrite it later */
Fprintf(ofp, "%s%5d\n", Dont_Edit_Data, 0);
/* handle special oracle; it must come first */
(void) fputs("---\n", tfp);
offset = (unsigned long) ftell(tfp);
Fprintf(ofp, "%05lx\n", offset); /* start pos of special oracle */
for (i = 0; i < SIZE(special_oracle); i++) {
(void) fputs(xcrypt(special_oracle[i]), tfp);
(void) fputc('\n', tfp);
}
SpinCursor(3);
oracle_cnt = 1;
(void) fputs("---\n", tfp);
offset = (unsigned long) ftell(tfp);
Fprintf(ofp, "%05lx\n", offset); /* start pos of first oracle */
in_oracle = FALSE;
while ((line = fgetline(ifp)) != 0) {
SpinCursor(3);
if (h_filter(line)) {
free((genericptr_t) line);
continue;
}
if (!strncmp(line, "-----", 5)) {
if (!in_oracle) {
free((genericptr_t) line);
continue;
}
in_oracle = FALSE;
oracle_cnt++;
(void) fputs("---\n", tfp);
offset = (unsigned long) ftell(tfp);
Fprintf(ofp, "%05lx\n", offset); /* start pos of this oracle */
} else {
in_oracle = TRUE;
(void) fputs(xcrypt(line), tfp);
}
free((genericptr_t) line);
}
if (in_oracle) { /* need to terminate last oracle */
oracle_cnt++;
(void) fputs("---\n", tfp);
offset = (unsigned long) ftell(tfp);
Fprintf(ofp, "%05lx\n", offset); /* eof position */
}
/* record the current position */
txt_offset = (unsigned long) ftell(ofp);
Fclose(ifp); /* all done with original input file */
/* reprocess the scratch file; 1st format an error msg, just in case */
line = (char *) alloc(BUFSZ + MAXFNAMELEN);
Sprintf(line, "rewind of \"%s\"", tempfile);
if (rewind(tfp) != 0)
goto dead_data;
free((genericptr_t) line);
/* copy all lines of text from the scratch file into the output file */
while ((line = fgetline(tfp)) != 0) {
(void) fputs(line, ofp);
free((genericptr_t) line);
}
/* finished with scratch file */
Fclose(tfp);
Unlink(tempfile); /* remove it */
/* update the first record of the output file; prepare error msg 1st */
line = (char *) alloc(BUFSZ + MAXFNAMELEN);
Sprintf(line, "rewind of \"%s\"", filename);
ok = (rewind(ofp) == 0);
if (ok) {
Sprintf(line, "header rewrite of \"%s\"", filename);
ok = (fprintf(ofp, "%s%5d\n", Dont_Edit_Data, oracle_cnt) >= 0);
}
if (ok) {
Sprintf(line, "data rewrite of \"%s\"", filename);
for (i = 0; i <= oracle_cnt; i++) {
#ifndef VMS /* alpha/vms v1.0; this fflush seems to confuse ftell */
if (!(ok = (fflush(ofp) == 0)))
break;
#endif
if (!(ok = (fpos = ftell(ofp)) >= 0))
break;
if (!(ok = (fseek(ofp, fpos, SEEK_SET) >= 0)))
break;
if (!(ok = (fscanf(ofp, "%5lx", &offset) == 1)))
break;
#ifdef MAC
#ifdef __MWERKS__
/*
MetroWerks CodeWarrior Pro 1's (AKA CW12) version of MSL
(ANSI C Libraries) needs this rewind or else the fprintf
stops working. This may also be true for CW11, but has
never been checked.
*/
rewind(ofp);
#endif
#endif
if (!(ok = (fseek(ofp, fpos, SEEK_SET) >= 0)))
break;
offset += txt_offset;
if (!(ok = (fprintf(ofp, "%05lx\n", offset) >= 0)))
break;
}
}
if (!ok) {
dead_data:
perror(line); /* report the problem */
free((genericptr_t) line);
/* close and kill the aborted output file, then give up */
Fclose(ofp);
Unlink(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
free((genericptr_t) line);
/* all done */
Fclose(ofp);
return;
}
/* Read one line from input, up to and including the next newline
* character. Returns a pointer to the heap-allocated string, or a
* null pointer if no characters were read.
* 3.7: redone to use nethack's alloc() rather than libc's malloc()
* and realloc().
*/
static char *
fgetline(FILE *fd)
{
static const int inc = (BUFSZ / 2) + 16; /* fgets() wants signed int */
unsigned len = (unsigned) inc, newlen; /* alloc() wants unsigned int */
char *c = (char *) alloc(len), *cprime, *ret;
*c = '\0';
for (;;) {
ret = fgets(c + len - inc, inc, fd);
if (!ret) {
/* don't just assume ret==Null indicates an error; last line
might lack terminating newline; if previous fgets() read it,
instead of returning we would have expanded the buffer and
tried for more, then got Null due to end of file having
already been reached */
if (feof(fd) && *c && strlen(c) < len) {
Strcat(c, "\n"); /* append missing newline */
} else {
free((genericptr_t) c), c = NULL;
}
break; /* either with or without added newline, we're done */
} else if (strchr(c, '\n')) {
/* normal case: we have a full line */
break;
}
/* didn't fit in c[0..len-1]; expand buffer and read some more
[this was much simpler (and possibly slightly more efficient)
with realloc() but less safe because return values from malloc()
and realloc() were not being checked for Null, and efficiency is
a red herring because growing the buffer will be extremely rare] */
newlen = len + (unsigned) inc;
cprime = (char *) alloc(newlen);
(void) memcpy(cprime, c, len);
free((genericptr_t) c);
c = cprime;
len = newlen;
}
return c;
}
#if defined(OLD_MAKEDEFS_OPTIONS)
void
do_date(void)
{
#ifdef KR1ED
long clocktim = 0;
#else
time_t clocktim = 0;
#endif
char githash[BUFSZ], gitbranch[BUFSZ];
char *c, cbuf[60], buf[BUFSZ];
const char *ul_sfx;
#if defined(CROSSCOMPILE) && !defined(CROSSCOMPILE_TARGET)
const char *xpref = "HOST_";
#else
const char *xpref = (const char *) 0;
#endif /* CROSSCOMPILE && !CROSSCOMPILE_TARGET */
/* before creating date.h, make sure that xxx_GRAPHICS and
DEFAULT_WINDOW_SYS have been set up in a viable fashion */
windowing_sanity();
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), INCLUDE_TEMPLATE, DATE_FILE);
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
/* NB: We've moved on from SCCS, but this way this line
* won't get clobbered when downstream projects import
* this file into something more modern. */
Fprintf(ofp, "%s", Reference_file);
(void) time(&clocktim);
#ifdef REPRODUCIBLE_BUILD
{
/*
* Use date+time of latest source file revision (set up in
* our environment rather than derived by scanning sources)
* instead of current date+time, so that later rebuilds of
* the same sources specifying the same configuration will
* produce the same result.
*
* Changing the configuration should be done by modifying
* config.h or <port>conf.h and setting SOURCE_DATE_EPOCH
* based on whichever changed most recently, not by using
* make CFLAGS='-Dthis -Dthat'
* to make alterations on the fly.
*
* Limited validation is performed to prevent dates in the
* future (beyond a leeway of 24 hours) or distant past.
*
* Assumes the value of time_t is in seconds, which is
* fundamental for Unix and mandated by POSIX. For any ports
* where that isn't true, leaving REPRODUCIBLE_BUILD disabled
* is probably preferrable to hacking this code....
*/
static struct tm nh360; /* static init should yield UTC timezone */
unsigned long sd_num, sd_earliest, sd_latest;
const char *sd_str = getenv("SOURCE_DATE_EPOCH");
if (sd_str) {
sd_num = strtoul(sd_str, (char **) 0, 10);
/*
* Note: this does not need to be updated for future
* releases. It serves as a sanity check for potentially
* mis-set environment, not a hard baseline for when the
* current version could have first been built.
*/
/* oldest date we'll accept: 7-Dec-2015 (release of 3.6.0) */
nh360.tm_mday = 7;
nh360.tm_mon = 12 - 1;
nh360.tm_year = 2015 - 1900;
sd_earliest = (unsigned long) mktime(&nh360);
/* 'youngest' date we'll accept: 24 hours in the future */
sd_latest = (unsigned long) clocktim + 24L * 60L * 60L;
if (sd_num >= sd_earliest && sd_num <= sd_latest) {
/* use SOURCE_DATE_EPOCH value */
clocktim = (time_t) sd_num;
date_via_env = TRUE;
} else {
Fprintf(stderr, "? Invalid value for SOURCE_DATE_EPOCH (%lu)",
sd_num);
if (sd_num > 0L && sd_num < sd_earliest)
Fprintf(stderr, ", older than %lu", sd_earliest);
else if (sd_num > sd_latest)
Fprintf(stderr, ", newer than %lu", sd_latest);
Fprintf(stderr, ".\n");
Fprintf(stderr, ": Reverting to current date+time (%lu).\n",
(unsigned long) clocktim);
(void) fflush(stderr);
}
} else {
/* REPRODUCIBLE_BUILD enabled but SOURCE_DATE_EPOCH is missing */
Fprintf(stderr, "? No value for SOURCE_DATE_EPOCH.\n");
Fprintf(stderr, ": Using current date+time (%lu).\n",
(unsigned long) clocktim);
(void) fflush(stderr);
}
#if !defined(NOSTRFTIME)
if (!strftime(cbuf, sizeof cbuf, "%c", gmtime(&clocktim)))
cbuf[0] = '\0';
#else
Strcpy(cbuf, asctime(gmtime(&clocktim)));
#endif /* NOSTRFTIME */
}
#else
/* ordinary build: use current date+time */
#if !defined(NOSTRFTIME)
if (!strftime(cbuf, sizeof cbuf, "%c", localtime(&clocktim)))
cbuf[0] = '\0';
#else
Strcpy(cbuf, ctime(&clocktim));
#endif /* NOSTRFTIME */
#endif /* REPRODUCIBLE_BUILD */
if ((c = strchr(cbuf, '\n')) != 0)
*c = '\0'; /* strip off the '\n' */
#ifdef NHSTDC
ul_sfx = "UL";
#else
ul_sfx = "L";
#endif
#if !defined(CROSSCOMPILE) || !defined(CROSSCOMPILE_TARGET)
Fprintf(ofp,
"\n#if !defined(CROSSCOMPILE) || !defined(CROSSCOMPILE_TARGET)\n");
#endif /* CROSSCOMPILE || !CROSSCOMPILE_TARGET */
if (date_via_env)
Fprintf(ofp, "#define SOURCE_DATE_EPOCH (%lu%s) /* via getenv() */\n",
(unsigned long) clocktim, ul_sfx);
Fprintf(ofp, "#define BUILD_DATE \"%s\"\n", cbuf);
if (date_via_env)
Fprintf(ofp, "#define BUILD_TIME SOURCE_DATE_EPOCH\n");
else
Fprintf(ofp, "#define BUILD_TIME (%lu%s)\n",
(unsigned long) clocktim, ul_sfx);
Fprintf(ofp, "\n");
Fprintf(ofp, "#define VERSION_NUMBER 0x%08lx%s\n", version.incarnation,
ul_sfx);
Fprintf(ofp, "#define VERSION_FEATURES 0x%08lx%s\n", version.feature_set,
ul_sfx);
{
unsigned long ignored_features = md_ignored_features();
Fprintf(ofp, "#define IGNORED_FEATURES 0x%08lx%s\n",
ignored_features, ul_sfx);
}
Fprintf(ofp, "#define VERSION_SANITY1 0x%08lx%s\n", version.entity_count,
ul_sfx);
#ifndef __EMSCRIPTEN__
Fprintf(ofp, "#define VERSION_SANITY2 0x%08lx%s\n", version.struct_sizes1,
ul_sfx);
Fprintf(ofp, "#define VERSION_SANITY3 0x%08lx%s\n", version.struct_sizes2,
ul_sfx);
#else /* __EMSCRIPTEN__ */
Fprintf(ofp, "#define VERSION_SANITY2 0x%08llx%s\n", version.struct_sizes1,
ul_sfx);
Fprintf(ofp, "#define VERSION_SANITY3 0x%08llx%s\n", version.struct_sizes2,
ul_sfx);
#endif /* !__EMSCRIPTEN__ */
Fprintf(ofp, "\n");
Fprintf(ofp, "#define VERSION_STRING \"%s\"\n",
mdlib_version_string(buf, "."));
Fprintf(ofp, "#define VERSION_ID \\\n \"%s\"\n",
version_id_string(buf, sizeof buf, cbuf));
Fprintf(ofp, "#define COPYRIGHT_BANNER_C \\\n \"%s\"\n",
bannerc_string(buf, sizeof buf, cbuf));
if (get_gitinfo(githash, gitbranch)) {
Fprintf(ofp, "#define NETHACK_GIT_SHA \"%s\"\n", githash);
Fprintf(ofp, "#define NETHACK_GIT_BRANCH \"%s\"\n", gitbranch);
}
if (xpref && get_gitinfo(githash, gitbranch)) {
Fprintf(ofp, "#else /* !CROSSCOMPILE || !CROSSCOMPILE_TARGET */\n");
Fprintf(ofp, "#define NETHACK_%sGIT_SHA \"%s\"\n",
xpref, githash);
Fprintf(ofp, "#define NETHACK_%sGIT_BRANCH \"%s\"\n",
xpref, gitbranch);
}
#if !defined(CROSSCOMPILE) || !defined(CROSSCOMPILE_TARGET)
Fprintf(ofp, "#endif /* !CROSSCOMPILE || !CROSSCOMPILE_TARGET */\n");
#endif
Fprintf(ofp, "\n");
#ifdef AMIGA
{
struct tm *tm = localtime((time_t *) &clocktim);
Fprintf(ofp, "#define AMIGA_VERSION_STRING ");
Fprintf(ofp, "\"\\0$VER: NetHack %d.%d.%d (%d.%d.%d)\"\n",
VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL,
tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900);
}
#endif
Fclose(ofp);
return;
}
static boolean
get_gitinfo(char *githash, char *gitbranch)
{
FILE *gifp;
size_t len;
char infile[MAXFNAMELEN];
char *line, *strval, *opt, *c, *end;
boolean havebranch = FALSE, havehash = FALSE;
if (!githash || !gitbranch) return FALSE;
Sprintf(infile, DATA_IN_TEMPLATE, GITINFO_FILE);
if (!(gifp = fopen(infile, RDTMODE))) {
/* perror(infile); */
return FALSE;
}
/* read the gitinfo file */
while ((line = fgetline(gifp)) != 0) {
strval = strchr(line, '=');
if (strval && strlen(strval) < (BUFSZ-1)) {
opt = line;
*strval++ = '\0';
/* strip off the '\n' */
if ((c = strchr(strval, '\n')) != 0)
*c = '\0';
if ((c = strchr(opt, '\n')) != 0)
*c = '\0';
/* strip leading and trailing white space */
while (*strval == ' ' || *strval == '\t')
strval++;
end = eos(strval);
while (--end >= strval && (*end == ' ' || *end == '\t'))
*end = '\0';
while (*opt == ' ' || *opt == '\t')
opt++;
end = eos(opt);
while (--end >= opt && (*end == ' ' || *end == '\t'))
*end = '\0';
len = strlen(opt);
if ((len >= strlen("gitbranch"))
&& !case_insensitive_comp(opt, "gitbranch")) {
Strcpy(gitbranch, strval);
havebranch = TRUE;
}
if ((len >= strlen("githash"))
&& !case_insensitive_comp(opt, "githash")) {
Strcpy(githash, strval);
havehash = TRUE;
}
}
free((genericptr_t) line);
}
Fclose(gifp);
if (havebranch && havehash)
return TRUE;
return FALSE;
}
void
do_options(void)
{
const char *optline;
int infocontext = 0;
windowing_sanity();
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), DATA_TEMPLATE, OPTIONS_FILE);
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
while ((optline = do_runtime_info(&infocontext)) != 0)
Fprintf(ofp, "%s\n", optline);
Fclose(ofp);
return;
}
static void
windowing_sanity(void)
{
#ifndef DEFAULT_WINDOW_SYS
/* pre-standard compilers didn't support #error; wait til run-time */
Fprintf(stderr,
"Configuration error: DEFAULT_WINDOW_SYS is not defined.\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
/* put in a dummy value so that do_options() will compile and makedefs
will build, otherwise the message above won't ever get delivered */
#define DEFAULT_WINDOW_SYS "<undefined>"
#else /*DEFAULT_WINDOW_SYS*/
if (!window_opts[0].id) {
Fprintf(stderr, "Configuration error: no windowing systems "
"(TTY_GRAPHICS, &c) enabled.\n");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
{
int i;
for (i = 0; window_opts[i].id; ++i)
if (!strcmp(window_opts[i].id, DEFAULT_WINDOW_SYS))
break;
if (!window_opts[i].id) { /* went through whole list without a match */
Fprintf(stderr, "Configuration error: DEFAULT_WINDOW_SYS (%s)\n",
DEFAULT_WINDOW_SYS);
Fprintf(stderr,
" does not match any enabled windowing system (%s%s).\n",
window_opts[0].id, window_opts[1].id ? ", &c" : "");
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
}
#endif /*DEFAULT_WINDOW_SYS*/
return;
}
/*
*
New format (v3.1) of 'data' file which allows much faster lookups [pr]
"do not edit" first record is a comment line
01234567 hexadecimal formatted offset to text area
name-a first name of interest
123,4 offset to name's text, and number of lines for it
name-b next name of interest
name-c multiple names which share same description also
456,7 share a single offset,count line
. sentinel to mark end of names
789,0 dummy record containing offset, count of EOF
text-a 4 lines of descriptive text for name-a
text-a at file position 0x01234567L + 123L
text-a
text-a
text-b/text-c 7 lines of text for names-b and -c
text-b/text-c at fseek(0x01234567L + 456L)
...
*
*/
void
do_dungeon(void)
{
char *line;
Sprintf(filename, DATA_IN_TEMPLATE, DGN_I_FILE);
if (!(ifp = fopen(filename, RDTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), DGN_TEMPLATE, DGN_O_FILE);
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
Fprintf(ofp, "%s", Dont_Edit_Data);
tfp = getfp(DATA_TEMPLATE, "grep.tmp", WRTMODE, FLG_TEMPFILE);
grep0(ifp, tfp, FLG_TEMPFILE);
#ifndef HAS_NO_MKSTEMP
ifp = tfp;
#else
ifp = getfp(DATA_TEMPLATE, "grep.tmp", RDTMODE, 0);
#endif
while ((line = fgetline(ifp)) != 0) {
SpinCursor(3);
if (line[0] == '#') {
free((genericptr_t) line);
continue; /* discard comments */
}
(void) fputs(line, ofp);
free((genericptr_t) line);
}
Fclose(ifp);
Fclose(ofp);
#ifdef HAS_NO_MKSTEMP
delete_file(DATA_TEMPLATE, "grep.tmp");
#endif
return;
}
/* obsolete */
void
do_permonst(void)
{
int i;
char *c, *nam;
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), INCLUDE_TEMPLATE, MONST_FILE);
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
Fprintf(ofp, "%s", Reference_file);
Fprintf(ofp, "#ifndef PM_H\n#define PM_H\n");
if (use_enum) {
Fprintf(ofp, "\nenum monnums {");
#if 0
/* need #define ENUM_PM for the full NetHack build to include these */
Fprintf(ofp, "\n NON_PM = -1,");
Fprintf(ofp, "\n LOW_PM = 0,");
#endif
}
for (i = 0; mons[i].mlet; i++) {
SpinCursor(3);
if (use_enum)
Fprintf(ofp, "\n PM_");
else
Fprintf(ofp, "\n#define\tPM_");
if (mons[i].mlet == S_HUMAN
&& !strncmp(mons[i].pmnames[NEUTRAL], "were", 4))
Fprintf(ofp, "HUMAN_");
for (nam = c = tmpdup(mons[i].pmnames[NEUTRAL]); *c; c++)
if (*c >= 'a' && *c <= 'z')
*c -= (char) ('a' - 'A');
else if (*c < 'A' || *c > 'Z')
*c = '_';
if (use_enum)
Fprintf(ofp, "%s = %d,", nam, i);
else
Fprintf(ofp, "%s\t%d", nam, i);
}
if (use_enum) {
Fprintf(ofp, "\n\n NUMMONS = %d", i);
Fprintf(ofp, "\n};\n");
} else {
Fprintf(ofp, "\n\n#define\tNUMMONS\t%d\n", i);
}
Fprintf(ofp, "\n#endif /* PM_H */\n");
Fclose(ofp);
return;
}
/* Start of Quest text file processing. */
void
do_questtxt(void)
{
printf("DEPRECATION WARNINGS:\n");
printf("'makedefs -q' is no longer required. Remove all references\n");
printf(" to it from the build process.\n");
printf("'dat/quest.txt' is no longer part of the source tree.\n");
return;
}
/* limit a name to 30 characters (26 if "xyz_" prefix precedes it) */
static char *
macronamelimit(char *name, int pref)
{
static char macronametemp[32];
unsigned len = pref ? 26 : 30;
#if 0 /* avoid potential warning about strncpy() not guaranteeing '\0' */
(void) strncpy(macronametemp, name, len);
macronametemp[len] = '\0';
#else /* strncat() does guarantee terminating '\0' */
macronametemp[0] = '\0';
(void) strncat(macronametemp, name, len);
#endif
return macronametemp;
}
/* obsolete */
void
do_objs(void)
{
int i, sum UNUSED = 0;
char *c, *objnam;
int nspell = 0;
int prefix = 0;
char class = '\0';
boolean sumerr = FALSE;
int n_glass_gems = 0;
int start_glass_gems = 0;
filename[0] = '\0';
#ifdef FILE_PREFIX
Strcat(filename, file_prefix);
#endif
Sprintf(eos(filename), INCLUDE_TEMPLATE, ONAME_FILE);
if (!(ofp = fopen(filename, WRTMODE))) {
perror(filename);
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
Fprintf(ofp, "%s", Reference_file);
Fprintf(ofp, "#ifndef ONAMES_H\n#define ONAMES_H\n\n");
for (i = 0; !i || objects[i].oc_class != ILLOBJ_CLASS; i++) {
SpinCursor(3);
objects[i].oc_name_idx = objects[i].oc_descr_idx = (short) i;
if (!(objnam = tmpdup(OBJ_NAME(objects[i]))))
continue;
/* done with current class? */
if (objects[i].oc_class != class) {
#if 0 /* [class total oc_prob of 1000 is no longer enforced] */
/* make sure probabilities add up to 1000 */
if (sum && sum != 1000) {
Fprintf(stderr, "prob error for class %d (%d%%)", class, sum);
(void) fflush(stderr);
sumerr = TRUE;
}
#endif /*0*/
class = objects[i].oc_class;
sum = 0;
}
for (c = objnam; *c; c++)
if (*c >= 'a' && *c <= 'z')
*c -= (char) ('a' - 'A');
else if (*c < 'A' || *c > 'Z')
*c = '_';
switch (class) {
case WAND_CLASS:
Fprintf(ofp, "#define\tWAN_");
prefix = 1;
break;
case RING_CLASS:
Fprintf(ofp, "#define\tRIN_");
prefix = 1;
break;
case POTION_CLASS:
Fprintf(ofp, "#define\tPOT_");
prefix = 1;
break;
case SPBOOK_CLASS:
Fprintf(ofp, "#define\tSPE_");
prefix = 1;
nspell++;
break;
case SCROLL_CLASS:
Fprintf(ofp, "#define\tSCR_");
prefix = 1;
break;
case AMULET_CLASS:
/* avoid trouble with stupid C preprocessors */
Fprintf(ofp, "#define\t");
if (objects[i].oc_material == PLASTIC) {
Fprintf(ofp, "FAKE_AMULET_OF_YENDOR\t%d\n", i);
prefix = -1;
break;
}
break;
case GEM_CLASS:
/* avoid trouble with stupid C preprocessors */
if (objects[i].oc_material == GLASS) {
Fprintf(ofp, "/* #define\t%s\t%d */\n", objnam, i);
prefix = -1;
if (!n_glass_gems)
start_glass_gems = i;
if (i != n_glass_gems + start_glass_gems) {
Fprintf(stderr, "glass gems not sequential\n");
(void) fflush(stderr);
sumerr = TRUE;
}
n_glass_gems++;
break;
}
/*FALLTHRU*/
case VENOM_CLASS:
/* fall-through from gem class is ok; objects[] used to have
{ "{acid,blinding} venom", "splash of venom" }
but those have been changed to
{ "splash of {acid,blinding} venom", "splash of venom" }
so strip the extra "splash of " off to keep same macros */
if (!strncmp(objnam, "SPLASH_OF_", 10))
objnam += 10;
/*FALLTHRU*/
default:
Fprintf(ofp, "#define\t");
break;
}
if (prefix >= 0)
Fprintf(ofp, "%s\t%d\n", macronamelimit(objnam, prefix), i);
prefix = 0;
sum += objects[i].oc_prob;
if (sumerr)
break;
}
#if 0 /* [class total oc_prob of 1000 is no longer enforced] */
/* check last set of probabilities */
if (!sumerr && sum && sum != 1000) {
Fprintf(stderr, "prob error for class %d (%d%%)", class, sum);
(void) fflush(stderr);
sumerr = TRUE;
}
#endif /*0*/
Fprintf(ofp, "\n");
Fprintf(ofp, "#define\tNUM_GLASS_GEMS\t%d\n", n_glass_gems);
Fprintf(ofp, "#define\tLAST_GEM\t(JADE)\n");
Fprintf(ofp, "#define\tMAXSPELL\t%d\n", nspell + 1);
Fprintf(ofp, "#define\tNUM_OBJECTS\t%d\n", i);
Fprintf(ofp, "\n/* Artifacts (unique objects) */\n\n");
for (i = 1; artifact_names[i]; i++) {
SpinCursor(3);
for (c = objnam = tmpdup(artifact_names[i]); *c; c++)
if (*c >= 'a' && *c <= 'z')
*c -= (char) ('a' - 'A');
else if (*c < 'A' || *c > 'Z')
*c = '_';
if (!strncmp(objnam, "THE_", 4))
objnam += 4;
/* fudge _platinum_ YENDORIAN EXPRESS CARD */
if (!strncmp(objnam, "PLATINUM_", 9))
objnam += 9;
Fprintf(ofp, "#define\tART_%s\t%d\n", macronamelimit(objnam, 1), i);
}
Fprintf(ofp, "#define\tNROFARTIFACTS\t%d\n", i - 1);
Fprintf(ofp, "\n#endif /* ONAMES_H */\n");
Fclose(ofp);
if (sumerr) {
makedefs_exit(EXIT_FAILURE);
/*NOTREACHED*/
}
return;
}
static char *
tmpdup(const char* str)
{
static char buf[128];
if (!str)
return (char *) 0;
(void) strncpy(buf, str, 127);
return buf;
}
#endif /* OLD_MAKEDEFS_OPTIONS */
/* the STRICT_REF_DEF checking is way out of date... */
#ifdef STRICT_REF_DEF
NEARDATA struct flag flags;
#ifdef ATTRIB_H
struct attribs attrmax, attrmin;
#endif
#endif /* STRICT_REF_DEF */
/*makedefs.c*/