/* NetHack 3.7 makedefs.c $NHDT-Date: 1702948590 2023/12/19 01:16:30 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.233 $ */ /* 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 */ #define OLD_MAKEDEFS_OPTIONS #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 "hacklib.h" #ifdef MAC #if defined(__SC__) || defined(__MRC__) /* MPW compilers */ #define MPWTOOL #include #include #else /* MAC without MPWTOOL */ #define MACsansMPWTOOL #endif #endif /* MAC */ #ifndef MPWTOOL #define SpinCursor(x) #endif #ifdef MD_USE_TMPFILE_S #include #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 *padline(char *, unsigned); static unsigned long read_rumors_file(const char *, int *, long *, unsigned long, unsigned); static void rafile(int); static void do_rnd_access_file(const char *, 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 *); /* doesn't do much (counts lines) if MAKEDEFS_FILTER_NONASCII isn't enabled */ static void filter_nonascii(char *); static void set_fgetline_context(const char *, boolean, boolean); #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 /* for MAKEDEFS_FILTER_NONASCII, but not conditionalized; extra input for fgetline(); not-needed for files that don't use that */ struct ascii_filter { const char *filename; int linenum, warncnt; boolean dofilter, tabok; }; static struct ascii_filter ascii_ctx; /* 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': rafile('1'); rafile('2'); rafile('3'); break; case '1': case '2': case '3': rafile(*options); 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 void rafile(int whichone) { switch(whichone) { /* * post-3.6.5: * File must not be empty to avoid divide by 0 * in core's rn2(), so provide a default entry. */ case '1': do_rnd_access_file(EPITAPHFILE, "epitaph", /* default epitaph: parody of the default engraving */ "No matter where I went, here I am.", MD_PAD_RUMORS); /* '_RUMORS' is correct here */ break; case '2': do_rnd_access_file(ENGRAVEFILE, "engrave", /* 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 */ break; case '3': do_rnd_access_file(BOGUSMONFILE, "bogusmon", /* default bogusmon: iconic monster that isn't in nethack */ "grue", MD_PAD_BOGONS); break; } } 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))) { (void) _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; /* FALLTHRU */ 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; } 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], xbuf[BUFSZ]; 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; } set_fgetline_context(infile, TRUE, FALSE); /* 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, xbuf), 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 *basefname, const char *deflt_content, unsigned padlength) { char *line, buf[BUFSZ], xbuf[BUFSZ], greptmp[8 + 1 + 3 + 1]; Sprintf(greptmp, "grep-%.3s.tmp", basefname); 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), xbuf), ofp); tfp = getfp(DATA_TEMPLATE, greptmp, WRTMODE, FLG_TEMPFILE); grep0(ifp, tfp, FLG_TEMPFILE); #ifndef HAS_NO_MKSTEMP ifp = tfp; #else ifp = getfp(DATA_TEMPLATE, greptmp, RDTMODE, 0); #endif set_fgetline_context(NULL, FALSE, FALSE); while ((line = fgetline(ifp)) != 0) { if (line[0] != '#' && line[0] != '\n') { (void) padline(line, padlength); (void) fputs(xcrypt(line, xbuf), ofp); } free((genericptr_t) line); } Fclose(ifp); Fclose(ofp); #ifdef HAS_NO_MKSTEMP delete_file(DATA_TEMPLATE, greptmp); #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); set_fgetline_context(NULL, FALSE, FALSE); /* 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; set_fgetline_context(infile, TRUE, TRUE); /* 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); if (!line) { fprintf(stderr, "makedefs malloc() failure\n"); exit(EXIT_FAILURE); } Sprintf(line, "rewind of \"%s\"", tempfile); if (rewind(tfp) != 0) goto dead_data; free((genericptr_t) line); set_fgetline_context(NULL, FALSE, TRUE); /* 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], xbuf[BUFSZ]; boolean in_oracle, ok; long fpos; unsigned long txt_offset, offset; int oracle_cnt; 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], xbuf), 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; set_fgetline_context(infile, TRUE, 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, xbuf), 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 */ set_fgetline_context(tempfile, FALSE, FALSE); 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; *(c + len) = '\0'; len = newlen; } filter_nonascii(c); return c; } static void filter_nonascii(char *line) { #ifdef MAKEDEFS_FILTER_NONASCII char warnbuf[BUFSZ]; unsigned char *p; int warned = 0, prevreason = -1, reason; #endif if (!line) /* end of file; uses 'line' for !MAKEDEFS_FILTER_NONASCII */ return; ascii_ctx.linenum += 1; if (!ascii_ctx.dofilter) return; #ifdef MAKEDEFS_FILTER_NONASCII for (p = (unsigned char *) line; *p; ++p) { if (*p == '\n') break; if (*p == '\t' && ascii_ctx.tabok) continue; reason = (*p > 126) ? 3 : (*p == '\t') ? 2 : (*p < ' '); if (reason != 0) { if (!warned) ascii_ctx.warncnt += 1; /* number of lines warned about */ if (++warned <= 3) { /* show up to 3 warnings for this line */ if (warned == 1) { /*assert(ascii_ctx.filename != NULL);*/ Sprintf(warnbuf, "? %s:", ascii_ctx.filename); } else { Strcpy(warnbuf, ","); } Sprintf(eos(warnbuf), " %d.%ld", ascii_ctx.linenum, (long) ((char *) p - line)); /* column */ if (reason != prevreason) { Strcat(warnbuf, (reason == 1) ? " non-printable" : (reason == 3) ? " non-ascii" : " "); /* (reason == 2) */ prevreason = reason; } Fprintf(stderr, "%s '%03o'", warnbuf, *p); } else if (warned == 3 + 1) { /* when more than 3 */ Fprintf(stderr, ", ..."); /* show an indicator */ } *p = '#'; } } if (warned > 0) Fprintf(stderr, "\n"); #endif return; } static void set_fgetline_context( const char *current_filename, boolean do_filtering, boolean tabs_are_ok) /* moot for !do_filtering */ { static const char dummyname[] = "[makedefs input]"; #ifndef MAKEDEFS_FILTER_NONASCII do_filtering = FALSE; #endif if (!current_filename) current_filename = dummyname; /* change from relative-to-dat to be relative-to-top, iff that's easy */ if (!strncmp(current_filename, "../", 3)) current_filename += 3; ascii_ctx.filename = current_filename; ascii_ctx.linenum = ascii_ctx.warncnt = 0; ascii_ctx.tabok = tabs_are_ok; ascii_ctx.dofilter = do_filtering; } #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 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; } set_fgetline_context(infile, TRUE, TRUE); /* 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 "" #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, greptmp[8 + 1 + 3 + 1]; Sprintf(greptmp, "grep-%.3s.tmp", "dun"); 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, greptmp, WRTMODE, FLG_TEMPFILE); grep0(ifp, tfp, FLG_TEMPFILE); #ifndef HAS_NO_MKSTEMP ifp = tfp; #else ifp = getfp(DATA_TEMPLATE, greptmp, RDTMODE, 0); #endif set_fgetline_context(NULL, FALSE, TRUE); 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, greptmp); #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 = 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; } FALLTHROUGH; /*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; FALLTHROUGH; /*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*/