/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <string.h>
#include <direct.h>
#include <stdio.h>
#include <stdlib.h>

#include "ldump.hxx"
#include "hashtbl.hxx"

#define MAXSYM     65536
#define MAXBASE    98304
#define MAX_MAN     4096

int bFilter = 0;
int bLdump3 = 0;
int bUseDirectives = 0;
int bVerbose = 0;
int bExportByName = 0;

class ExportSet : public HashTable
{
public:
    ExportSet
    (
        unsigned long lSize,
        double  dMaxLoadFactor = HashTable::m_defMaxLoadFactor,
        double  dGrowFactor = HashTable::m_defDefGrowFactor
    )
        : HashTable(lSize,false,dMaxLoadFactor,dGrowFactor) {}

    virtual ~ExportSet() {}

    LibExport *  Find (char * const& Key) const
    { return (LibExport *) HashTable::Find((char *) Key); }

    bool Insert (char * const& Key, LibExport * Object)
    { return HashTable::Insert((char *) Key, (void*) Object); }

    LibExport *  Delete (char * const&Key)
    { return (LibExport *) HashTable::Delete ((char *) Key); }
};

LibDump::LibDump( char *cFileName, int bExportByName )
                : cBName( NULL ),
                cAPrefix( NULL ),
                cLibName( NULL ),
                cFilterName( NULL ),
                cModName( NULL )
{
    fprintf( stderr, "LIB-NT File Dumper v4.00 (C) 2000 Sun Microsystems, Inc.\n\n" );
    fprintf( stderr, "%s ", cFileName );

    bExportName = bExportByName;

    unsigned long nSlots =  0xfffff;
    pBaseTab = new ExportSet( nSlots );
    pIndexTab = new ExportSet( nSlots );
    pFilterLines = new char * [MAXFILT];
    CheckLibrary(cFileName);
    bBase = 0;
    bAll = false;
    nDefStart = 0;
    nBaseLines = 0;
    nFilterLines = 0;
    bDef = true;
    cAPrefix = new char[ 1 ];
    cAPrefix[ 0 ] = 0;
    if (!bExportName)
        CheckDataBase();
}

bool LibDump::Dump()
{
    FILE *pList;
    char  aBuf[MAX_MAN];
    int   nLen;
    char  aName[MAX_MAN];

    pList = fopen( cLibName, "rb");
    if (!pList)
        DumpError(10);

    // forget about offset when working on linker directives
    if ( !bUseDirectives )
    {
        // calculating offset for name section
        unsigned char TmpBuffer[4];
        fread( TmpBuffer, 1, 4, pList);
        // anzahl bigendian mal laenge + ueberspringen der naechsten laengenangabe
        unsigned long nOffSet = (unsigned long) ( TmpBuffer[2] * 256 + TmpBuffer[3] ) * 4 + 4;
        fseek( pList, (long) nOffSet, 0);
    }

    char aTmpBuf[4096];
    // reading file containing symbols
    while( !feof( pList ) )
    {
           int i = 0;
        if ( !bUseDirectives )
        {
            // symbol komplett einlesen
            for (;;)
            {
                int c = fgetc( pList );
                if ( c == '\0' )
                {
                    break;
                }
                if ( ((c >= 33) && (c <= 126)) && ( c!=40 && c!=41) )
                    aBuf[i] = static_cast< char >(c);
                else
                {
                    aBuf[0] = '\0';
                    break;
                }
                i++;
            }
            // Namen found
            aBuf[i] = '\0';
        }
        else
        {
            fgets( aTmpBuf, 4096, pList );
            char * pEnd = 0;
            char *pFound = 0;
            aBuf[0] = '\0';
            pFound = strchr( aTmpBuf, 'E' );
            while ( pFound )
            {
                if ( strncmp( "EXPORT:", pFound, 7) == 0 )
                {
                    pFound += 7;
                    pEnd = strchr( pFound, ',');
                    if ( pEnd )
                        *pEnd = '\0';
                    strncpy( aBuf, pFound, strlen( pFound));
                    aBuf[ strlen( pFound) ] = '\0';
                    break;
                }
                else
                {
                    pFound++;
                    pFound = strchr( pFound, 'E' );
                }
            }
        }

        if (aBuf[0] =='?')
        {
            nLen = (int) strlen(aBuf);
            memset( aName, 0, sizeof( aName ) );
            int nName = 0;
            for( i = 0; i < nLen; i++ )
            {
                if ( (aBuf[i] != '\n') && (aBuf[i] != '\r') )
                {
                    aName[nName] = aBuf[i];
                    nName++;
                }
            }
            // und raus damit
            PrintSym( aName, bExportByName );
        }
        else if ( bAll == true &&
                  strncmp(aBuf, "__real@", 7) != 0 &&
                  strncmp(aBuf, "__CT",4) != 0 &&
                  strncmp(aBuf, "__TI3?", 6) != 0 )
        {
            int nPreLen = (int) strlen( cAPrefix );

            nLen = (int) strlen(aBuf);
            memset( aName, 0, sizeof( aName ) );
            int nName = 0;

            for( i = 0; i < nLen; i++ )
            {
              if ( (aBuf[i] != '\n') && (aBuf[i] != '\r') )
              {
                  aName[nName] = aBuf[i];
                  nName++;
              }
            }
            // den ersten _ raus
            nLen = (int) strlen(aName);
#ifndef _WIN64
            if (aName[0] == '_')
                strcpy( aBuf , &aName[1] );
#endif
            strncpy ( aTmpBuf, aBuf, (size_t) nPreLen );
            aTmpBuf[nPreLen] = '\0';
            if ( !strcmp( aTmpBuf, cAPrefix ))
            {
                if ( bLdump3 ) {
                    int nChar = '@';
                    char *pNeu = strchr( aBuf, nChar );
                    size_t nPos = pNeu - aBuf + 1;
                    if ( nPos > 0 )
                    {
                        char aOldBuf[MAX_MAN];
                        strcpy( aOldBuf, aBuf );
                        char pChar[MAX_MAN];
                        strncpy( pChar, aBuf, nPos - 1 );
                        pChar[nPos-1] = '\0';
                        strcpy( aBuf, pChar );
                        strcat( aBuf, "=" );
                        strcat( aBuf, aOldBuf );
                        strcpy( pChar, "" );
                    }
                }
                // und raus damit
                PrintSym( aBuf, true );
            }
        }
    }
    fclose(pList);
    return true;
}

bool LibDump::ReadFilter( char * cFilterName )
{
    FILE* pfFilter = 0;
    char  aBuf[MAX_MAN];
    char* pStr;
    int   nLen;

    pfFilter = fopen( cFilterName, "r" );

    if ( !pfFilter )
    {
        ::bFilter = 0;
        DumpError( 500 );
    }

    while( fgets( aBuf, MAX_MAN, pfFilter ) != 0 )
    {
        nLen = (int) strlen(aBuf);
        pStr = new char[(unsigned int) nLen];
        if ( !pStr )
            DumpError( 98 );
        memcpy( pStr, aBuf, (unsigned int) nLen );
        if ( *(pStr+nLen-1) == '\n' )
            *(pStr+nLen-1) = '\0';
        pFilterLines[nFilterLines] = pStr;
        nFilterLines++;
        if ( nFilterLines >= MAXFILT )
                DumpError( 510 );
    }

    fclose( pfFilter );
    return true;
}

bool LibDump::PrintSym(char *pName, bool bName )
{
    LibExport *pData;


    // Filter auswerten
    if ( Filter( pName ) )
    {
        if ( strlen( pName ) > 3 )
        {
            if ( bDef )
            {
                if (!bBase)
                    if (bExportName) {
                        fprintf( stdout, "\t%s\n", pName );
                    } else {
                        fprintf( stdout, "\t%s\t\t@%lu\n", pName, nDefStart );
                    }
                else
                {
                     pData = pBaseTab->Find( pName );
                     if ( pData )
                     {
                        pData->bExport = true;
                        if ( bName )
                            pData->bByName = true;
                        else
                            pData->bByName = false;
                        if ( bVerbose )
                            fprintf(stderr,".");
                     }
                     else
                     {
                         // neuen Export eintragen
                         pData = new LibExport;
                        pData->cExportName = new char[ strlen( pName ) + 1 ];
                        strcpy( pData->cExportName, pName );
                        pData->nOrdinal = nBaseLines++;
                        pData->bExport = true;
                        if ( bName )
                            pData->bByName = true;
                        else
                            pData->bByName = false;
                        pBaseTab->Insert( pData->cExportName, pData );
                        char *cBuffer = new char[ 30 ];
                        sprintf( cBuffer, "%lu", pData->nOrdinal );
                        pIndexTab->Insert( cBuffer, pData );
                        delete [] cBuffer;
                        if ( bVerbose )
                            fprintf(stderr,"n");
                     }
                }
            }
            else
                printf( "%s\n", pName );
            nDefStart++;
        }
    }
    return true;
}

bool LibDump::IsFromAnonymousNamespace (char *pExportName) {
    char* pattern1 = "@?A0x";

    if (strstr(pExportName, pattern1)) {
        return true;
    };
    return false;
};

bool LibDump::Filter(char *pExportName)
{
    unsigned long i;
    char pTest[256];

    // filter out symbols from anonymous namespaces
    if (IsFromAnonymousNamespace (pExportName))
        return false;

    // Kein Filter gesetzt
    if ( ::bFilter == 0 )
        return true;

    for ( i=0; i<nFilterLines; i++ )
    {
        //Zum vergleichen muá das Plus abgeschnitteb werden
        if(pFilterLines[i][0] != '+')
        {
            if ( strstr( pExportName, pFilterLines[i]))
                return false;
        }
        else
        {
            strcpy(pTest,&pFilterLines[i][1]);
            if ( strstr( pExportName, pTest))
                return true;
        }
    }
    return true;
}

bool LibDump::SetFilter(char * cFilterName)
{
    ReadFilter( cFilterName );
    return true;
}

bool LibDump::CheckLibrary(char * cName)
{
    delete [] cLibName;
    cLibName = new char[ strlen( cName ) + 1 ];
    strcpy( cLibName, cName );
    return true;
}

bool LibDump::ReadDataBase()
{
    FILE* pfBase = 0;
    char  aBuf[MAX_MAN];
    char* pStr;
    char  cBuffer[ 30 ];
    int   nLen;
    LibExport *pData;

    pfBase = fopen( cBName, "r" );

    if ( !pfBase )
    {
        bBase = 0;
        DumpError( 600 );
    }

    bool bRet = true;
    while( fgets( aBuf, MAX_MAN, pfBase ) != 0 )
    {
        nLen = (int) strlen(aBuf);
        pStr = new char[(unsigned int) nLen];
        if ( !pStr )
            DumpError( 98 );
        memcpy( pStr, aBuf, (size_t) nLen );
        if ( *(pStr+nLen-1) == '\n' )
            *(pStr+nLen-1) = '\0';
        pData = new LibExport;
        pData->cExportName = pStr;
        pData->nOrdinal = nBaseLines;
        pData->bExport=false;

        if (pBaseTab->Insert(pData->cExportName, pData ) == NULL)
            bRet = false;
        ltoa( (long) pData->nOrdinal, cBuffer, 10 );
        if (pIndexTab->Insert( cBuffer, pData ) == NULL)
            bRet = false;
        nBaseLines++;
        if ( nBaseLines >= MAXBASE )
            DumpError( 610 );
    }
    fclose( pfBase );
    return bRet;
}

class ExportSetIter : public HashTableIterator
{
public:
    ExportSetIter(HashTable const& aTable)
        : HashTableIterator(aTable) {}

    LibExport * GetFirst()
    { return (LibExport *)HashTableIterator::GetFirst(); }
    LibExport * GetNext()
    { return (LibExport *)HashTableIterator::GetNext();  }
    LibExport * GetLast()
    { return (LibExport *)HashTableIterator::GetLast();  }
    LibExport * GetPrev()
    { return (LibExport *)HashTableIterator::GetPrev();  }

private:
    void operator =(ExportSetIter &); // not defined
};

bool LibDump::PrintDataBase()
{
    if (bExportName)
        return true;
    FILE *pFp;
    pFp = fopen (cBName,"w+");
    if (!pFp)
        fprintf( stderr, "Error opening DataBase File\n" );

    LibExport *pData;
    for ( unsigned long i=0; i < nBaseLines+10; i++ )
    {
        char * cBuffer = new char[ 30 ];
        sprintf( cBuffer, "%lu", i );
        pData = pIndexTab->Find( cBuffer );
        delete [] cBuffer;
        if ( pData )
            fprintf(pFp,"%s\n",pData->cExportName);
    }
    fclose(pFp);
    return true;
}

bool LibDump::PrintDefFile()
{
#ifdef FAST
    ExportSetIter aIterator( *pBaseTab );
    for ( LibExport *pData = aIterator.GetFirst(); pData != NULL;
                                        pData = aIterator.GetNext() )
    {
        if ( pData->bExport )
        {
            if ( pData->bByName )
            {
                fprintf(stdout,"\t%s\n",
                    pData->sExportName.GetBuffer());
            }
            else
            {
                fprintf(stdout,"\t%s\t\t@%d NONAME\n",
                    pData->sExportName.GetBuffer(), pData->nOrdinal+nBegin);
            }
        }
    }
#else
    // sortiert nach Ordinals;
    LibExport *pData;
    for ( unsigned long i=0; i<nBaseLines+1; i++)
    {
        char * cBuffer = new char[ 30 ];
        sprintf( cBuffer, "%lu", i );
        pData = pIndexTab->Find( cBuffer );
        delete [] cBuffer;
        if ( pData )
            if ( pData->bExport )
            {
                if ( pData->bByName )
                {
                    if ( strlen( pData->cExportName ))
                        fprintf(stdout,"\t%s\n",
                            pData->cExportName);
                }
                else
                {
                    if ( strlen( pData->cExportName ))
                        fprintf(stdout,"\t%s\t\t@%d NONAME\n",
                            pData->cExportName, pData->nOrdinal+nBegin);
                }
            }
    }
#endif
    return true;
}

bool LibDump::CheckDataBase()
{
    // existiert eine Datenbasis ?
    if (!bBase)
    {
        cBName = new char[ 2048 ];
        char *pTmp = "defs\\";

        FILE *fp;
        _mkdir ("defs");
        strcpy(cBName,pTmp);
        strcat(cBName,getenv ("COMP_ENV"));

        fp = fopen (cBName,"r");
        if (fp)
        {
            bBase = true;
        }
        else
        {
            fp = fopen (cBName,"w+");
            bBase = true;
        }
        fclose (fp);
    }
    // lese Datenbasis !
    if (bBase)
    {
        ReadDataBase();
    }
    return true;
}

LibDump::~LibDump()
{
    delete [] cBName;
    delete [] cAPrefix;
    delete [] cFilterName;
    delete [] cModName;
}

void LibDump::SetCExport( char* pName )
{
    delete [] cAPrefix;
    cAPrefix = new char[ strlen( pName ) + 1 ];
    strcpy( cAPrefix, pName );bAll = true;
}

//******************************************************************
//* Error() - Gibt Fehlermeldumg aus
//******************************************************************

void LibDump::DumpError( unsigned long n )
{
    char *p;

    switch (n)
    {
        case 1:  p = "Input error in library file"; break;
        case 2:  p = "Position error in library file (no THEADR set)"; break;
        case 3:  p = "Overflow of symbol table"; break;
#ifdef WNT
        case 10: p = "EXP file not found"; break;
        case 11: p = "No valid EXP file"; break;
#else
        case 10: p = "Library file not found"; break;
        case 11: p = "No valid library file"; break;
#endif
        case 98: p = "Out of memory"; break;
        case 99: p = "LDUMP [-LD3] [-D] [-N] [-A] [-E nn] [-F name] Filename[.LIB]\n"
                     "-LD3   : Supports feature set of ldump3 (default: ldump/ldump2)\n"
                     "-A     : all symbols (default: only C++)\n"
                     "-E nn  : gerenration of export table beginning with number nn\n"
                     "-F name: Filter file\n"
                     "-D     : file contains \"dumpbin\" directives\n"
                     "-N     : export by name\n"
                     "-V     : be verbose\n"; break;
        case 500: p = "Unable to open filter file\n"; break;
        case 510: p = "Overflow of filter table\n"; break;
        case 600: p = "Unable to open base database file\n"; break;
        case 610: p = "Overflow in base database table\n"; break;
        default: p = "Unspecified error";
    }
    fprintf( stdout, "%s\n", p );
    exit (1);
}

/*********************************************************************
        Test Funktionen
*********************************************************************/


void usage()
{
    LibDump::DumpError(99);
}

#define STATE_NON       0x0000
#define STATE_BEGIN     0x0001
#define STATE_FILTER    0x0002
#define STATE_CEXPORT   0x0003

int
#ifdef WNT
__cdecl
#endif
main( int argc, char **argv )
{
    char *pLibName = NULL, *pFilterName = NULL, *pCExport= NULL;
    unsigned short nBegin=1;

    unsigned short nState = STATE_NON;

    if ( argc == 1 ) {
        usage();
    }

    for ( int i = 1; i < argc; i++ ) {
        if (( !strcmp( argv[ i ], "-H" )) ||
            ( !strcmp( argv[ i ], "-h" )) ||
            ( !strcmp( argv[ i ], "-?" )))
        {
            usage();
        }
        else if (( !strcmp( argv[ i ], "-LD3" )) ||
            ( !strcmp( argv[ i ], "-Ld3" )) ||
            ( !strcmp( argv[ i ], "-ld3" )) ||
            ( !strcmp( argv[ i ], "-lD3" )))
        {
            if ( nState != STATE_NON ) {
                usage();
            }
            bLdump3 = 1;
        }
        else if (( !strcmp( argv[ i ], "-E" )) || ( !strcmp( argv[ i ], "-e" ))) {
            if ( nState != STATE_NON ) {
                usage();
            }
            nState = STATE_BEGIN;
        }
        else if (( !strcmp( argv[ i ], "-F" )) || ( !strcmp( argv[ i ], "-f" ))) {
            if ( nState != STATE_NON ) {
                usage();
            }
            nState = STATE_FILTER;
        }
        else if (( !strcmp( argv[ i ], "-A" )) || ( !strcmp( argv[ i ], "-a" ))) {
            if ( nState != STATE_NON ) {
                usage();
            }
            nState = STATE_CEXPORT;
            pCExport = new char[ 1 ];
            pCExport[ 0 ] = 0;
        }
        else if (( !strcmp( argv[ i ], "-D" )) || ( !strcmp( argv[ i ], "-d" ))) {
            if ( nState != STATE_NON ) {
                usage();
            }
            bUseDirectives = 1;
        }
        else if (( !strcmp( argv[ i ], "-N" )) || ( !strcmp( argv[ i ], "-n" ))) {
            if ( nState != STATE_NON ) {
                usage();
            }
            bExportByName = 1;
        }
        else if (( !strcmp( argv[ i ], "-V" )) || ( !strcmp( argv[ i ], "-v" ))) {
            if ( nState != STATE_NON ) {
                usage();
            }
            bVerbose = 1;
        }
        else {
            switch ( nState ) {
                case STATE_BEGIN:
                    nBegin = static_cast< unsigned short >(atoi( argv[ i ] ));
                    nState = STATE_NON;
                break;
                case STATE_FILTER:
                    pFilterName = new char[ strlen( argv[ i ] ) + 1 ];
                    strcpy( pFilterName, argv[ i ] );
                    bFilter = 1;
                    nState = STATE_NON;
                break;
                case STATE_CEXPORT:
                    delete [] pCExport;
                    pCExport = new char[ strlen( argv[ i ] ) + 1 ];
                    strcpy( pCExport, argv[ i ] );
                    nState = STATE_NON;
                break;
                default:
                    pLibName = new char[ strlen( argv[ i ] ) + 1 ];
                    strcpy( pLibName, argv[ i ] );
                break;
            }
        }
    }

    if ( !pLibName ) {
        usage();
    }

    LibDump *pDump = new LibDump( pLibName, bExportByName );
    pDump->SetBeginExport(nBegin);
    if ( bFilter != 0 )
        pDump->SetFilter( pFilterName );
    if ( pCExport )
        pDump->SetCExport( pCExport );
    else {
        char *pEmpty = "";
        pDump->SetCExport( pEmpty );
    }
    pDump->Dump();
    pDump->PrintDefFile();
    pDump->PrintDataBase();
    delete pDump;
    delete [] pLibName;
    return 0;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
