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

#include <stdio.h>
#include <malloc.h>
#include "symfile.h"

// Reads in the first MAPDEF into the address provided to it, and
// does some basic sanity test to see if the file is "legitimate"

static int ReadAndVerifySymFileHeader(FILE *symfile, MAPDEF *mapdef)
{
    unsigned short nextMap;

    // Seek to the file beginning, and read in the MAPDEF
    fseek(symfile, 0, SEEK_SET);
    if ( !fread(mapdef, sizeof(MAPDEF), 1, symfile) )
        return 0;
    
    // A very limited form of sanity checking.  The first two test
    // assume the .SYM file is for a Windows program.  Those two 
    // tests are not relevant for DOS
    if ( (mapdef->pSegEntry > 255)
        || (mapdef->cSegs > 255)
        || (mapdef->bFlags & 1)
        || (mapdef->bFlags > 2) )
        return 0;
    
    // Seek to the next header, which is always the "last MAPDEF"
    // Read in the "next" field, and verify that it's 0     
    fseek(symfile, mapdef->ppNextMap * 16L, SEEK_SET);
    fread(&nextMap, sizeof(nextMap), 1, symfile);
    if ( nextMap != 0 )
        return 0;
    
    return 1;
}

// Positions the file pointer at the SEGDEF corresponding to the
// logical segment parameter.
static int PositionToCorrectSEGDEF
(
    FILE *symfile,
    unsigned short seg,
    unsigned short cSegs,
    unsigned short ppSegDef
)
{
    SEGDEF segdef;
    
    // Iterate through all the segdefs
    while ( cSegs-- )
    {
        // Seek to the "next" segdef, and read it in
        fseek(symfile, ppSegDef * 16L, SEEK_SET);
        fread(&segdef, sizeof(segdef), 1, symfile);
        
        // If it's the right SEGDEF, then back up the file
        // pointer, and return success.
        if ( segdef.segNumber == seg )
        {
            fseek( symfile, 0L-sizeof(segdef), SEEK_CUR);
            return 1;
        }
        else
            ppSegDef = segdef.ppNextSeg;    // Go to the next SEGDEF
    }

    // If we got here, then we didn't find it.  Return failure
    return 0;
}

// Uses the SEGDEF to look for the nearest symbol that's less than
// or equal to to the passed in offset.  Returns symbol name in "buffer"
static void GetSymbolNameFromSEGDEF
(
    FILE *symfile,
    char *buffer,
    unsigned short off
)
{
    SEGDEF segdef;
    SYMDEF symdef;
    unsigned long segdefBase;
    unsigned short * symbols;
    unsigned short i;
    char tempString[256];

    // Remember the SEFDEF offset, then read it in
    segdefBase = ftell(symfile);
    fread(&segdef, sizeof(segdef), 1, symfile);

    // Allocate space to hold the the "offset array"
    symbols = calloc(segdef.cSymbols, sizeof(unsigned short) );
    if ( !symbols )
        return;

    // Read in the "offset array"
    fseek(symfile, segdefBase + segdef.pSymDef, SEEK_SET);
    fread(symbols, segdef.cSymbols, sizeof(unsigned short), symfile);

    // Read in each SYMDEF, using the "offset array".  Stop when we
    // find one that occurs after the passed in offset.
    for ( i=0; i < segdef.cSymbols; i++ )
    {
        fseek(symfile, segdefBase + symbols[i], SEEK_SET);
        fread(&symdef, sizeof(symdef), 1, symfile);
        
        if ( symdef.offset > off )
            break;
    }

    // If i==0, then the offset we're looking for is before any
    // of the symbols.  If i > 0, then back up one symbol, and
    // format its information into the "buffer" parameter.
    if ( i != 0 )
    {
        fseek(symfile, segdefBase + symbols[i-1], SEEK_SET);
        fread(&symdef, sizeof(symdef), 1, symfile);
        fread(tempString, symdef.cbSymName, 1, symfile);
        tempString[symdef.cbSymName] = 0;
        sprintf(buffer, "%s + %04X", tempString, off - symdef.offset);
    }
    
    // Free the space allocated for the "offset array"
    free(symbols);
}

// Given a .SYM file pointer, and a logical address, look up the nearest
// symbol to that address.
void FindSymbol
(
    char *buffer,
    FILE *symfile,
    unsigned short seg,
    unsigned short off
)
{
    MAPDEF  mapdef;
    unsigned short nextMap;
    
    buffer[0] = 0;

    // Make sure it's a valid .SYM file.  Stop if not.
    if ( !ReadAndVerifySymFileHeader(symfile, &mapdef) )
        return;
    
    // Position the file pointer to the right segment/SEGDEF.
    if ( !PositionToCorrectSEGDEF(symfile, seg, mapdef.cSegs,
                                    mapdef.ppSegDef))
        return;

    // Extract the symbol name.  The result is in "buffer"
    GetSymbolNameFromSEGDEF(symfile, buffer, off);
}