Search code examples
cwindows-7compiler-errorsmingwnethack

How do I compile NetHack in Windows 7?


I like NetHack and I want to dink around with the source a bit for fun. Before I do that I would like to be able to get it to compile out of the box but I'm having no small amount of difficulty getting that to happen.

I downloaded the source code from here and I followed the instructions here but it didn't work.

I ended up getting the following

C:\nethack-3.4.3\src>mingw32-make -f Makefile.gcc install
creating directory o
gcc -c -mms-bitfields -I../include  -g -DWIN32CON -oo/makedefs.o ../util/makedefs.c
gcc -c -mms-bitfields -I../include  -g -DWIN32CON -DDLB   -oo/monst.o  ../src/monst.c
gcc -c -mms-bitfields -I../include  -g -DWIN32CON -DDLB   -oo/objects.o      ../src/objects.c
..\util\makedefs -v
Makefile.gcc:655: recipe for target '../include/date.h' failed
mingw32-make: *** [../include/date.h] Error -1073741819

I looked at the line it was talking about but it didn't really tell me anything. I did notice that the date.h file being created in the include directory was always empty but that doesn't help me very much either. I read the Install.nt README and the directions seemed pretty clear-cut. However since I didn't change anything I don't know why it would fail to compile...

I consider myself to be a competent programmer but I know next to nothing when it comes to makefiles and compiling C code into an executable application so I'm pretty well lost here. I downloaded and installed the MinGW... everything, by which I mean that there is nothing left uninstalled when I run the MinGW installer.

What am I doing wrong here?

EDIT : As date.h was being mentioned:

#
#  date.h should be remade every time any of the source or include
#  files is modified.
#

$(INCL)/date.h $(OPTIONS_FILE): $(U)makedefs.exe
    $(subst /,\,$(U)makedefs -v)

I did notice it seems to be making some kind of call to OPTIONS_FILE, which seems to be commented out. I will uncomment it and see what happens.

#$(OPTIONS_FILE): $(U)makedefs.exe
#$(subst /,\,$(U)makedefs -v)

EDIT 2 That didn't work. Is it possible that I have to manually create/update the date.h file? If so, what do I put into it? Sounds like a question for Google...

EDIT 3 I found this for a much older version and tried to change it up but it didn't work either...

EDIT 4 Someone mentioned Makedefs which seems to be the thing crashing. I found the C function that appears to be causing the problem:

void
do_date()
{
    long clocktim = 0;
    char *c, cbuf[60], buf[BUFSZ];
    const char *ul_sfx;

    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);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp,"/*\tSCCS Id: @(#)date.h\t3.4\t2002/02/03 */\n\n");
    Fprintf(ofp,Dont_Edit_Code);

#ifdef KR1ED
    (void) time(&clocktim);
    Strcpy(cbuf, ctime(&clocktim));
#else
    (void) time((time_t *)&clocktim);
    Strcpy(cbuf, ctime((time_t *)&clocktim));
#endif
    for (c = cbuf; *c; c++) if (*c == '\n') break;
    *c = '\0';  /* strip off the '\n' */
    Fprintf(ofp,"#define BUILD_DATE \"%s\"\n", cbuf);
    Fprintf(ofp,"#define BUILD_TIME (%ldL)\n", clocktim);
    Fprintf(ofp,"\n");
#ifdef NHSTDC
    ul_sfx = "UL";
#else
    ul_sfx = "L";
#endif
    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);
#ifdef IGNORED_FEATURES
    Fprintf(ofp,"#define IGNORED_FEATURES 0x%08lx%s\n",
        (unsigned long) IGNORED_FEATURES, ul_sfx);
#endif
    Fprintf(ofp,"#define VERSION_SANITY1 0x%08lx%s\n",
        version.entity_count, ul_sfx);
    Fprintf(ofp,"#define VERSION_SANITY2 0x%08lx%s\n",
        version.struct_sizes, ul_sfx);
    Fprintf(ofp,"\n");
    Fprintf(ofp,"#define VERSION_STRING \"%s\"\n", version_string(buf));
    Fprintf(ofp,"#define VERSION_ID \\\n \"%s\"\n",
        version_id_string(buf, cbuf));
    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;
}

Also I should mention when it gets to this point in the compile process, immediately there is this image: enter image description here

So we've narrowed down the problem (I think?) to the makedefs helper program that is breaking things so now I guess the next step would be to find out why?

EDIT 5: It's been suggested that a special parameter should be used when compiling Makedefs.c. I've taken a look at the Makefile to find out where the compile takes place and I think I've found where that is happening but I don't really know what's going on here.

$(U)makedefs.exe: $(MAKEOBJS)
    @$(link) $(LFLAGSU) -o$@ $(MAKEOBJS)

$(O)makedefs.o: $(CONFIG_H) $(INCL)/monattk.h $(INCL)/monflag.h \
     $(INCL)/objclass.h $(INCL)/monsym.h $(INCL)/qtext.h \
     $(INCL)/patchlevel.h $(U)makedefs.c $(O)obj.tag
    $(cc) $(CFLAGSU) -o$@ $(U)makedefs.c

I know that $(*) is a variable or the Makefile equivalent of a variable.

$(U) points to $(UTIL)/, and $(UTIL) points to ../util. $(MAKEOBJS) points to $(O)makedefs.o $(O)monst.o $(O)objects.o. $(O) points to $(OBJ)/ which points to o so that would make $(O)makedefs.o be the same as o/makedefs.o which makes sense considering the behavior I've observed on semi-successful runs (Several files are compiled before the big freeze).

Anyway, $(link) points to gcc. $(LFLAGSU) points to $(LFLAGSBASEC) which points to $(linkdebug) which points to -g.

$(CONFIG_H) points to a large number of header files:

CONFIG_H = $(INCL)/config.h $(INCL)/config1.h $(INCL)/tradstdc.h \
           $(INCL)/global.h $(INCL)/coord.h $(INCL)/vmsconf.h \
           $(INCL)/system.h $(INCL)/unixconf.h $(INCL)/os2conf.h \
           $(INCL)/micro.h $(INCL)/pcconf.h $(INCL)/tosconf.h \
           $(INCL)/amiconf.h $(INCL)/macconf.h $(INCL)/beconf.h \
           $(INCL)/ntconf.h $(INCL)/nhlan.h

$(INCL) points to ../include. $(CFLAGSU) points to $(CFLAGSBASE) $(WINPFLAG). $(CFLAGSBASE) points to -c $(cflags) -I$(INCL) $(WINPINC) $(cdebug) $(cflags) points to -mms-bitfields $(WINPINC) points to -I$(WIN32) $(WIN32) points to ../win/win32 $(cdebug) points to -g $(WINPFLAG) points to -DTILES -DMSWIN_GRAPHICS -D_WIN32_IE=0x0400 . . . And there it is. I think that's what I need to modify to make this work with what was mentioned by RossRidge -D_USE_32BIT_TIME_T.

However since I've come that far I do want to find out what some of this stuff means. When looking at the first line I see $(U)makedefs.exe :. To me that appears to be a declaration of the target for the compiled output file? Is that correct? Also, what is the meaning of the @ before the $(link) $(LFLAGSU) and after the -o$? And what is the meaning of the $ after the -o?

Anyway, I want to try what I figured out and see if it works at all. ... Aaaand adding -D_USE_32BIT_TIME_T to WINPFLAG didn't work.

FINAL(ish) EDIT: Turns out RossRidge was correct in his suggestion to use the -D_USE_32BIT_TIME_T flag. MY mistake was putting it in the wrong place. If you take a look at the Makefile.gcc that comes in the box, look at line 165 (which is in an IF statement). You want to tack -D_USE_32BIT_TIME_T at the end of that. BUT you will also want to tack it at the end of line 176 which is on the ELSE end of that IF statement. So that entire block would look something like this instead (Not a huge change, but still significant enough to make it crash if you don't do it and you're running under my situation):

################################################
#                                              #
# Nothing below here should have to be changed.#
#                                              #
################################################

ifeq  "$(GRAPHICAL)" "Y"
WINPORT  = $(O)tile.o $(O)mhaskyn.o $(O)mhdlg.o \
    $(O)mhfont.o $(O)mhinput.o $(O)mhmain.o $(O)mhmap.o \
    $(O)mhmenu.o $(O)mhmsgwnd.o $(O)mhrip.o $(O)mhsplash.o \
    $(O)mhstatus.o $(O)mhtext.o $(O)mswproc.o $(O)winhack.o
WINPFLAG   = -DTILES -DMSWIN_GRAPHICS -D_WIN32_IE=0x0400 -D_USE_32BIT_TIME_T
NHRES   = $(O)winres.o
WINPINC = -I$(WIN32)
WINPHDR = $(WIN32)/mhaskyn.h $(WIN32)/mhdlg.h $(WIN32)/mhfont.h \
    $(WIN32)/mhinput.h $(WIN32)/mhmain.h $(WIN32)/mhmap.h \
    $(WIN32)/mhmenu.h $(WIN32)/mhmsg.h $(WIN32)/mhmsgwnd.h \
    $(WIN32)/mhrip.h $(WIN32)/mhstatus.h \
    $(WIN32)/mhtext.h $(WIN32)/resource.h $(WIN32)/winMS.h
WINPLIBS =  -lcomctl32 -lwinmm
else
WINPORT = $(O)nttty.o
WINPFLAG= -DWIN32CON -D_USE_32BIT_TIME_T
WINPHDR =
NHRES   = $(O)console.o
WINPINC =
WINPLIBS = -lwinmm
endif

Solution

  • (I don't know if I deserve credit for the answer, since I wouldn't have been aware of what the problem was without Harry Johnston's and indiv's comments, but I'll try to expand on the comments into a full answer.)

    As indiv explained, the reason why makedefs.exe crashes is because ctime returns NULL. Normally you wouldn't expect ctime to do this, so we need to check the documentation find out under what circumstances it will return an error. Since MinGW is being used to do the compiling we need to look at Microsoft's Visual C++ documentation. This is because MinGW doesn't have it's own C runtime, it just uses Microsoft's.

    Looking at at the Visual Studio C Run-Time Library Reference entry for ctime we find:

    Return Value

    A pointer to the character string result. NULL will be returned if:

    • time represents a date before midnight, January 1, 1970, UTC.
    • If you use _ctime32 or _wctime32 and time represents a date after 03:14:07 January 19, 2038.
    • If you use _ctime64 or _wctime64 and time represents a date after 23:59:59, December 31, 3000, UTC.

    Now it's pretty safe to assume the original poster hasn't set his system clock to a time far into the future or long in the past. So why would ctime be using the wrong time? Harry Johnston pointed out that the code was using long instead of time_t to store time values in. This isn't too surprising. Nethack is really old code, and originally Unix stored its time in long values, using time_t for time values came later. Nethack would've had to be dealing with old systems that didn't have time_t for a significant chunl of its active development period.

    That explains why the Nethack source is using the wrong type, but it doesn't quite explain why it's passing the wrong value to ctime. The fact we don't see a description of the return value for ctime itself, just _ctime32 and _ctime64 gives us a clue. If time_t is a 64-bit type, then using long instead will be a problem. On Windows long is only 32-bits, so it would mean ctime is being passed a number that's one part time value, one part random bits. Reading on in the documentation confirms this is the case, and gives us a possible solution:

    ctime is an inline function which evaluates to _ctime64 and time_t is equivalent to __time64_t. If you need to force the compiler to interpret time_t as the old 32-bit time_t, you can define _USE_32BIT_TIME_T. Doing this will cause ctime to evaluate to _ctime32. This is not recommended because your application may fail after January 18, 2038, and it is not allowed on 64-bit platforms

    Now since defining _USE_32BIT_TIME_T only affects how the C headers are compiled, and since MinGW supplies it's own C headers, it's possible that MinGW doesn't support this. A quick check of MinGW's time.h reveals that it does, so the simple solution to use the -D_USE_32BIT_TIME_T compiler option to define this macro.