//=================================
//  w_assert() by Matt Pietrek, 1992
//  File: W_ASSERT.C
//=================================

#include <windows.h>
#include <toolhelp.h>
#include <stdio.h>
#include <dir.h>
#include "w_assert.h"

#define START_ASSERT_OUTPUT     0
#define CONTINUE_ASSERT_OUTPUT  1
#define END_ASSERT_OUTPUT       2

// Prototype for FindSymbol() in FINDSYM.C

void FindSymbol
(
    char *buffer,
    FILE *symfile,
    unsigned short seg,
    unsigned short off
);

// Given a module handle, and a logical segment/offset, look for a
// matching .SYM file, and get the symbolic name closest to the address

static void GetSymbolicName
(
    char *stringBuffer,
    HMODULE hModule,
    WORD seg,
    WORD off
)
{
    char symbolString[256];     // Where FindSymbol will put its info
    char fileName[MAXPATH];     // Path to EXE/DLL & .SYM file
    char drive[MAXDRIVE];
    char dir[MAXDIR];
    char file[MAXFILE];
    char ext[MAXEXT], symExt[MAXEXT];
    FILE *symFile;
    
    // Null out the return buffer, in case we have to abort early
    stringBuffer[0] = 0;

    // Get the path corresponding to the "hModule"
    if ( !GetModuleFileName(hModule, fileName, sizeof(fileName)))
        return;
    
    // Change the hModule path to have a .SYM extension
    fnsplit(fileName, drive, dir, file, ext);
    strcpy(symExt, ".SYM");
    fnmerge(fileName, drive, dir, file, symExt);
    
    // If we couldn't open the .SYM file, then get out
    if ( (symFile = fopen(fileName, "rb")) == NULL )
        return;

    // Call FINDSYM.C to look up the symbolic name
    FindSymbol(symbolString, symFile, seg, off);

    fclose(symFile);
    
    // Format the output string as "filename  seg:offset  symbolname"
    sprintf(stringBuffer, "%-8s%-3s  %04X:%04X  %s",
        file, ext, seg, off, symbolString);
}

// Our simple, generic output routine

static void __w_assertfail_output(char *msg, int mode)
{
    strcat(msg, "\r\n");
    OutputDebugString(msg);
}

// Called by the w_assert() macro.  Performs traditional assert()
// actions, then walks the stack and displays the results

void __w_assertfail( char *msg, char *cond, char *file, int line)
{
    HINSTANCE hInstanceToolhelp;
    STACKTRACEENTRY ste;
    char stringBuffer[256];
    BOOL ok;
    BOOL WINAPI (*lpfnStackTraceNext)(STACKTRACEENTRY FAR *);
    BOOL WINAPI (*lpfnStackTraceCSIPFirst)(STACKTRACEENTRY FAR *,
        WORD, WORD, WORD, WORD);

    // Take care of the traditional duties of assert()
    sprintf(stringBuffer, msg, cond, file, line);
    __w_assertfail_output(stringBuffer, START_ASSERT_OUTPUT);

    // Load TOOLHELP.DLL.  Get out if couldn't load
    if ( (hInstanceToolhelp = LoadLibrary("TOOLHELP.DLL")) < 32 )
        abort();

    // Get the entry points of the two stack walking functions in
    // TOOLHELP.  Get out if not found.  Note that GetProcAddress
    // takes an hModule, but a hInstance works just as well.
    lpfnStackTraceCSIPFirst = GetProcAddress(
        (HINSTANCE)hInstanceToolhelp, "STACKTRACECSIPFIRST" );
    lpfnStackTraceNext = GetProcAddress(
        (HINSTANCE)hInstanceToolhelp, "STACKTRACENEXT" );
    if ( !lpfnStackTraceCSIPFirst || !lpfnStackTraceNext )
        abort();
            
    // Set up and call StackTraceCSIPFirst
    ste.dwSize = sizeof(ste);    
    if( (ok = lpfnStackTraceCSIPFirst(&ste, _SS, _CS, 0, _BP)) == FALSE )
        abort();

    // Get the next stack frame, and loop until no more frames
    ok = lpfnStackTraceNext(&ste);
    
    while ( ok )
    {
        GetSymbolicName(stringBuffer, ste.hModule, ste.wSegment, ste.wIP);
        __w_assertfail_output(stringBuffer, CONTINUE_ASSERT_OUTPUT);
        
        ok = lpfnStackTraceNext(&ste);
    }

    // Free the TOOLHELP library
    FreeLibrary(hInstanceToolhelp);
    
    // Nothing else to say.  Tell the output function that we're done
    __w_assertfail_output("", END_ASSERT_OUTPUT);

    // assert() call abort, so we do to.
    abort();
}

