/*++

Copyright (c) 1995-1997  Microsoft Corporation

Module Name:

    ver.cxx

Abstract:

    This module contains an NTSD debugger extension for dumping module
    version resources.

Author:

    Keith Moore (keithmo) 16-Sep-1997

Revision History:

--*/

#include "inetdbgp.h"

PSTR VersionLabels[] =
     {
         "CompanyName",
         "FileDescription",
         "FileVersion",
         "InternalName",
         "LegalCopyright",
         "OriginalFilename",
         "ProductName",
         "ProductVersion"
     };
#define NUM_LABELS ( sizeof(VersionLabels) / sizeof(VersionLabels[0]) )

typedef struct _ENUM_CONTEXT {
    PSTR ModuleName;
    INT NameLength;
} ENUM_CONTEXT, *PENUM_CONTEXT;


/************************************************************
 * Dump File Version Info
 ************************************************************/

PIMAGE_RESOURCE_DIRECTORY
FindResourceDir(
    IN PIMAGE_RESOURCE_DIRECTORY BaseResourceDir,
    IN PIMAGE_RESOURCE_DIRECTORY TargetResourceDir,
    IN USHORT ResourceId
    )

/*++

Routine Description:

    Finds the specified resource directory.

Arguments:

    BaseResourceDir - The (remote) address of the *start* of the resource
        section.

    TargetResourceDir - The (remote) address of the resource directory
        to search.

    ResourceId - The resource ID we're looking for.

Return Value:

    PIMAGE_RESOURCE_DIRECTORY - Pointer to the resource directory
        corresponding to ResourceId if successful, NULL otherwise.

--*/

{

    IMAGE_RESOURCE_DIRECTORY localDir;
    IMAGE_RESOURCE_DIRECTORY_ENTRY localEntry;
    PIMAGE_RESOURCE_DIRECTORY_ENTRY remoteEntry;
    USHORT i;

    //
    // Read the target resource directory.
    //

    if( !ReadMemory(
            (ULONG_PTR)TargetResourceDir,
            &localDir,
            sizeof(localDir),
            NULL
            ) ) {
        return NULL;
    }

    //
    // Scan it.
    //

    remoteEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)( TargetResourceDir + 1 );

    for( i = localDir.NumberOfNamedEntries + localDir.NumberOfIdEntries ;
         i > 0 ;
         i--, remoteEntry++ ) {

        //
        // Read the directory entry.
        //

        if( !ReadMemory(
                (ULONG_PTR)remoteEntry,
                &localEntry,
                sizeof(localEntry),
                NULL
                ) ) {
            return NULL;
        }

        //
        // If the entry is a directory and the IDs match, then return it.
        //

        if( localEntry.DataIsDirectory == 0 ) {
            continue;
        }

        if( localEntry.NameIsString == 0 &&
            localEntry.Id == ResourceId ) {

            return (PIMAGE_RESOURCE_DIRECTORY)
                       ( (ULONG_PTR)BaseResourceDir + localEntry.OffsetToDirectory );

        }

    }

    return NULL;

}   // FindResourceDir

PIMAGE_RESOURCE_DATA_ENTRY
FindResourceData(
    IN PIMAGE_RESOURCE_DIRECTORY BaseResourceDir,
    IN PIMAGE_RESOURCE_DIRECTORY TargetResourceDir,
    IN USHORT ResourceId
    )

/*++

Routine Description:

    Finds the specified resource data item.

Arguments:

    BaseResourceDir - The (remote) address of the *start* of the resource
        section.

    TargetResourceDir - The (remote) address of the resource directory
        to search.

    ResourceId - The resource ID we're looking for. This may be zero
        to return any resource.

Return Value:

    PIMAGE_RESOURCE_DATA_ENTRY - Pointer to the resource data entry
        corresponding to ResourceId if successful, NULL otherwise.

--*/

{

    IMAGE_RESOURCE_DIRECTORY localDir;
    IMAGE_RESOURCE_DIRECTORY_ENTRY localEntry;
    PIMAGE_RESOURCE_DIRECTORY_ENTRY remoteEntry;
    USHORT i;

    //
    // Read the target resource directory.
    //

    if( !ReadMemory(
            (ULONG_PTR)TargetResourceDir,
            &localDir,
            sizeof(localDir),
            NULL
            ) ) {
        return NULL;
    }

    //
    // Scan it.
    //

    remoteEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)( TargetResourceDir + 1 );

    for( i = localDir.NumberOfNamedEntries + localDir.NumberOfIdEntries ;
         i > 0 ;
         i--, remoteEntry++ ) {

        //
        // Read the directory entry.
        //

        if( !ReadMemory(
                (ULONG_PTR)remoteEntry,
                &localEntry,
                sizeof(localEntry),
                NULL
                ) ) {
            return NULL;
        }

        //
        // If the entry is not a directory and the IDs match (or the
        // requested ID is zero, meaning any ID) then return it.
        //

        if( localEntry.DataIsDirectory != 0 ) {
            continue;
        }

        if( localEntry.NameIsString == 0 &&
            ( localEntry.Id == ResourceId ||
              ResourceId == 0 ) ) {

            return (PIMAGE_RESOURCE_DATA_ENTRY)
                       ( (ULONG_PTR)BaseResourceDir + localEntry.OffsetToDirectory );

        }

    }

    return NULL;

}   // FindResourceData


BOOL
DumpVersionResource(
    IN PVOID VersionResource
    )

/*++

Routine Description:

    Dumps a version resource block.

Arguments:

    VersionResource - The version resource to dump.

Return Value:

    BOOL - TRUE if successful, FALSE if the version resource block
        was corrupt or unreadable.

--*/

{

    ULONG charSet;
    LPVOID version;
    UINT versionLength;
    INT i;
    VS_FIXEDFILEINFO * fixedFileInfo;
    CHAR label[MAX_PATH];

    //
    // Get the language/character-set pair.
    //

    if( !VerQueryValueA(
            VersionResource,
            "\\VarFileInfo\\Translation",
            &version,
            &versionLength
            ) ) {
        return FALSE;
    }

    charSet = *(LPDWORD)version;
    charSet = (DWORD)MAKELONG( HIWORD(charSet), LOWORD(charSet) );

    //
    // Get the root block so we can determine if this is a free or
    // checked build.
    //

    if( !VerQueryValue(
             VersionResource,
             "\\",
             &version,
             &versionLength
             ) ) {
        return FALSE;
    }

    fixedFileInfo = (VS_FIXEDFILEINFO *)version;

    dprintf(
        "%-19s = 0x%08lx (%s)\n",
        "dwFileFlags",
        fixedFileInfo->dwFileFlags,
        ( ( fixedFileInfo->dwFileFlags & VS_FF_DEBUG ) != 0 )
            ? "CHECKED"
            : "FREE"
        );

    //
    // Dump the various version strings.
    //

    for( i = 0 ; i < NUM_LABELS ; i++ ) {

        wsprintfA(
            label,
            "\\StringFileInfo\\%08lX\\%s",
            charSet,
            VersionLabels[i]
            );

        if( VerQueryValue(
                VersionResource,
                label,
                &version,
                &versionLength
                ) ) {
            dprintf(
                "%-19s = %s\n",
                VersionLabels[i],
                version
                );
        }

    }

    dprintf( "\n" );

    return TRUE;

}   // DumpVersionResource


VOID
FindAndDumpVersionResourceByAddress(
    IN ULONG_PTR ModuleAddress,
    IN PSTR ModuleName
    )

/*++

Routine Description:

    Locates and dumps the version resource for the module based at
    the specified address.

Arguments:

    ModuleAddress - The base address of the module to dump.

    ModuleName - The module name, for display purposes.

Return Value:

    None.

--*/

{

    IMAGE_DOS_HEADER dosHeader;
    IMAGE_NT_HEADERS ntHeaders;
    PIMAGE_OPTIONAL_HEADER optionalHeader;
    PIMAGE_DATA_DIRECTORY dataDir;
    PIMAGE_RESOURCE_DIRECTORY baseResourceDir;
    PIMAGE_RESOURCE_DIRECTORY tmpResourceDir;
    PIMAGE_RESOURCE_DATA_ENTRY dataEntry;
    IMAGE_RESOURCE_DATA_ENTRY localDataEntry;
    PVOID versionResource;

    //
    // Setup locals so we know how to cleanup on exit.
    //

    versionResource = NULL;

    //
    // Read & validate the image headers.
    //

    if( !ReadMemory(
            ModuleAddress,
            &dosHeader,
            sizeof(dosHeader),
            NULL
            ) ) {

        dprintf(
            "inetdbg.ver: cannot read DOS header @ 0x%p\n",
            ModuleAddress
            );

        goto cleanup;

    }

    if( dosHeader.e_magic != IMAGE_DOS_SIGNATURE ) {

        dprintf(
            "inetdbg.ver: module @ 0x%p has invalid DOS header\n",
            ModuleAddress
            );

        goto cleanup;

    }

    if( !ReadMemory(
            ModuleAddress + dosHeader.e_lfanew,
            &ntHeaders,
            sizeof(ntHeaders),
            NULL
            ) ) {

        dprintf(
            "inetdbg.ver: cannot read NT headers @ 0x%p\n",
            ModuleAddress
            );

        goto cleanup;

    }

    if( ntHeaders.Signature != IMAGE_NT_SIGNATURE ) {

        dprintf(
            "inetdbg.ver: module @ 0x%p has invalid NT headers\n",
            ModuleAddress
            );

        goto cleanup;

    }

    optionalHeader = &ntHeaders.OptionalHeader;

    if( optionalHeader->Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC ) {

        dprintf(
            "inetdbg.ver: module @ 0x%p has invalid optional header\n",
            ModuleAddress
            );

        goto cleanup;

    }

    //
    // Locate the resource.
    //

    dataDir = &optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];

    if( dataDir->VirtualAddress == 0 ||
        dataDir->Size == 0 ) {

        dprintf(
            "inetdbg.ver: module @ 0x%p has no resource information\n",
            ModuleAddress
            );

        goto cleanup;

    }

    baseResourceDir = (PIMAGE_RESOURCE_DIRECTORY)
                          ( ModuleAddress + dataDir->VirtualAddress );

    //
    // Now go and find the resource in the image. Since resources are
    // stored heirarchally, we're basically for the resource path:
    //
    //     VS_FILE_INFO\VS_VERSION_INFO\LanguageId
    //
    // For the language ID, we'll first try 0x409 (English) and if
    // that fails, we'll take any language.
    //

    dataEntry = NULL;

    tmpResourceDir = FindResourceDir(
                         baseResourceDir,
                         baseResourceDir,
                         (USHORT)VS_FILE_INFO
                         );

    if( tmpResourceDir != NULL ) {

        tmpResourceDir = FindResourceDir(
                             baseResourceDir,
                             tmpResourceDir,
                             (USHORT)VS_VERSION_INFO
                             );

        if( tmpResourceDir != NULL ) {

            dataEntry = FindResourceData(
                            baseResourceDir,
                            tmpResourceDir,
                            0x409
                            );

            if( dataEntry == NULL ) {

                dataEntry = FindResourceData(
                                baseResourceDir,
                                tmpResourceDir,
                                0
                                );

            }

        }

    }

    if( dataEntry == NULL ) {

        dprintf(
            "inetdbg.ver: cannot find version resource\n"
            );

        goto cleanup;

    }

    //
    // Actually read the dir entry.
    //

    if( !ReadMemory(
            (ULONG_PTR)dataEntry,
            &localDataEntry,
            sizeof(localDataEntry),
            NULL
            ) ) {

        dprintf(
            "inetdbg.ver: error reading resource\n"
            );

        goto cleanup;

    }

    //
    // Now we can allocate & read the resource.
    //

    versionResource = malloc( localDataEntry.Size );

    if( versionResource == NULL ) {

        dprintf(
            "inetdbg.ver: not enough memory\n"
            );

        goto cleanup;

    }

    if( !ReadMemory(
            ModuleAddress + localDataEntry.OffsetToData,
            versionResource,
            localDataEntry.Size,
            NULL
            ) ) {

        dprintf(
            "inetdbg.ver: error reading resource\n"
            );

        goto cleanup;

    }

    //
    // Dump it.
    //

    dprintf(
        "Module @ 0x%p = %s\n",
        ModuleAddress,
        ModuleName
        );

    if( !DumpVersionResource( versionResource ) ) {

        dprintf(
            "Cannot interpret version resource\n"
            );

        goto cleanup;

    }

cleanup:

    if( versionResource != NULL ) {
        free( versionResource );
    }

}   // FindAndDumpVersionResourceByAddress


BOOLEAN
CALLBACK
VerpEnumProc(
    IN PVOID Param,
    IN PMODULE_INFO ModuleInfo
    )
{

    PENUM_CONTEXT context;
    INT baseNameLength;

    context = (PENUM_CONTEXT)Param;
    baseNameLength = strlen( ModuleInfo->BaseName );

    //
    // If the user wants all modules, or if the specified module matches
    // the "tail" of the module name, dump it.
    //

    if( context->ModuleName == NULL ||
        ( baseNameLength >= context->NameLength &&
          !_stricmp(
              context->ModuleName,
              ModuleInfo->BaseName + baseNameLength - context->NameLength
              ) ) ) {

        FindAndDumpVersionResourceByAddress(
            ModuleInfo->DllBase,
            ModuleInfo->BaseName
            );

    }

    return TRUE;

}   // VerpEnumProc


VOID
FindAndDumpVersionResourceByName(
    IN PSTR ModuleName
    )

/*++

Routine Description:

    Locates and dumps the version resource for the specified module.

Arguments:

    ModuleName - The name of the module to dump. If this is NULL then
        all modules are dumped.

Return Value:

    None.

--*/

{

    ENUM_CONTEXT context;

    context.ModuleName = ModuleName;

    if( ModuleName == NULL ) {
        context.NameLength = 0;
    } else {
        context.NameLength = strlen( ModuleName );
    }

    if( !EnumModules(
            VerpEnumProc,
            (PVOID)&context
            ) ) {
        dprintf( "error retrieving module list\n" );
    }

}   // FindAndDumpVersionResourceByName


DECLARE_API( ver )

/*++

Routine Description:

    This function is called as an NTSD extension to format and dump
    module version info.

Arguments:

    hCurrentProcess - Supplies a handle to the current process (at the
        time the extension was called).

    hCurrentThread - Supplies a handle to the current thread (at the
        time the extension was called).

    CurrentPc - Supplies the current pc at the time the extension is
        called.

    lpExtensionApis - Supplies the address of the functions callable
        by this extension.

    lpArgumentString - Supplies the asciiz string that describes the
        ansi string to be dumped.

Return Value:

    None.

--*/

{

    ULONG module;
    PSTR endPointer;

    INIT_API();

    //
    // Skip leading blanks.
    //

    while( *lpArgumentString == ' ' ||
           *lpArgumentString == '\t' ) {
        lpArgumentString++;
    }

    if( *lpArgumentString == '\0' ) {

        //
        // No argument passed, dump all modules.
        //

        FindAndDumpVersionResourceByName( NULL );

    } else {

        module = strtoul( lpArgumentString, &endPointer, 16 );

        if( *endPointer != ' ' && *endPointer != '\t' && *endPointer != '\0' ) {

            //
            // Assume the argument is actually a module name, not
            // a base address.
            //

            FindAndDumpVersionResourceByName( lpArgumentString );

        } else {

            FindAndDumpVersionResourceByAddress( module, NULL );

        }

    }

}   // DECLARE_API( ver )

