4

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
Will
  • 3,413
  • 7
  • 50
  • 107
  • Finding date.h would be a good start:) – Martin James Aug 04 '14 at 21:01
  • The problem occurs on line 655 of `Makefile.gcc` so have a look there and see if you can work out what the makefile is trying to do at that point and why it might be failing. – Harry Johnston Aug 04 '14 at 21:08
  • date.h is generated by makedefs – Baj Mile Aug 04 '14 at 21:08
  • @MartinJames: my reading is that the error occurs while the makefile is trying to *create* `date.h`. – Harry Johnston Aug 04 '14 at 21:09
  • Do you have [util/makedefs.exe](http://nethack.wikia.com/wiki/Makedefs)? What happens when you manually run `makedefs -v`? – indiv Aug 04 '14 at 21:27
  • @indiv It's present yes, but when i do like you said it just hangs. Also, it appears that the file is created at compile time based on the edit date and it's absence from the pristine folder after I opened it. – Will Aug 04 '14 at 21:30
  • @Will: Yes, `makedefs` is supposed to create date.h and do other stuff. I tried to build 3.4.3 also and `makedefs -v` crashes on me. – indiv Aug 04 '14 at 21:47
  • @indiv hooray! I mean that sucks but at least it means I'm not doing it wrong I guess? Now we just need to find out why it's crashing... – Will Aug 04 '14 at 21:48
  • @Will: The problem is that in `util\makedefs.c` `ctime(&clocktim)` is returning NULL, so `Strcpy(cbuf, ctime(&clocktim));` is crashing. Unfortunately I'm out of time to figure out why... Maybe would make a good question to ask on here if there's no easy answer on google. Work around it by hard-coding a string! Search and replace all copies of `ctime(...)` with `"Mon Aug 4 16:00:00 2014"`. E.g., `Strcpy(cbuf, "Mon Aug 4 16:00:00 2014");` – indiv Aug 04 '14 at 22:01
  • @indiv I'll run with that if I can't find it out. That was a good find thanks. – Will Aug 04 '14 at 22:15
  • The bug appears to be that `clocktim` is declared as `long` rather than as `time_t`. But it is also possible that there is a mismatch between the MinGW headers and the Windows C runtime. – Harry Johnston Aug 04 '14 at 23:38
  • Try compiling with `-D_USE_32BIT_TIME_T`. That tell the MinGW headers to use a 32-bit `time_t`. – Ross Ridge Aug 05 '14 at 01:39
  • @RossRidge: Tried what you suggested by adding -D_USE_32BIT_TIME_T to the WINPFLAG (See edit # 5). makedefs still crashes on -v. – Will Aug 05 '14 at 18:12
  • @RossRidge: Actually, ross, your suggestion did the trick. It turns out I had it on the wrong side of an IF statement and when I put it in place it NAILED IT. If you care to phrase your comment in the form of an answer I'll be happy to accept it. – Will Aug 05 '14 at 21:10

1 Answers1

4

(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.

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • An excellent description of the issue, +1. One potential complication that may be worth expanding on, though in this case it turned out not to be an issue: the C runtime shipped with Windows (msvcrt.dll) has no formal documentation, since third-party applications aren't really supposed to be using it any more. The closest available is the documentation for Visual Studio 6, since apps compiled in VS6 do use msvcrt.dll, and they need to keep running for backwards compatibility. But the VS6 documentation omits all mention of `_ctime32` and `_ctime64` and doesn't specify the size of `time_t`. – Harry Johnston Aug 06 '14 at 02:33
  • ... so, it wasn't clear to me whether the details given in the current VS documentation were accurate for msvcrt.dll or not. One alternative source for this information is the MinGW headers, as you've described; these are presumably based on information from the VS6 headers combined with trial and error and/or reverse engineering as needed. (Of course, there was still some risk that these particular functions hadn't been studied closely and that the headers would be inaccurate, but obviously that wasn't the case. And from what I've heard, they are usually pretty reliable.) – Harry Johnston Aug 06 '14 at 02:40
  • Officially at least, the MinGW headers are based solely on Microsoft's public documentation. Microsoft updates `MSVCRT.DLL` with a new version for every OS release, so it gets features, like 64-bit time values, that the old VS6 DLL doesn't have. MinGW's `time.h` used to have version macros that allowed for compatibility with the old VS6 DLL, but they seem to be broken now. Currently MinGW seems to assume one of the newer versions of `MSCVRT.DLL` – Ross Ridge Aug 06 '14 at 03:19
  • Ah, that helps explain why msvcrt.dll appears to export `ctime` as well as `_ctime32` and `_ctime64`. Presumably the latter two are used by Windows itself (and by MinGW) and the former is for VS6 compatibility. (There must still be some guesswork involved in updating the headers, if nothing else a guess as to which version of Visual Studio a given operating system's runtime is based on.) :-) – Harry Johnston Aug 06 '14 at 04:56