/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    version.c

Abstract:

    This file implements a set of enumeration routines to access
    version info in a Win32 binary.

Author:

    Jim Schmidt (jimschm) 03-Dec-1997

Revision History:

    <alias>  <date>  <comments>

--*/

#include "pch.h"

#define DBG_ACTION "Action"

//
// Globals
//

PCSTR g_DefaultTranslationsA[] = {
    "04090000",
    "040904E4",
    "040904B0",
    NULL
};

PCWSTR g_DefaultTranslationsW[] = {
    L"04090000",
    L"040904E4",
    L"040904B0",
    NULL
};

//
// Prototypes
//

PCSTR
pEnumVersionValueCommonA (
    IN OUT  PVERSION_STRUCTA VersionStruct
    );

PCWSTR
pEnumVersionValueCommonW (
    IN OUT  PVERSION_STRUCTW VersionStruct
    );


//
// Implementation
//

BOOL
CreateVersionStructA (
    OUT     PVERSION_STRUCTA VersionStruct,
    IN      PCSTR FileSpec
    )

/*++

Routine Description:

  CreateVersionStruct is called to load a version structure from a file
  and to obtain the fixed version stamp info that is language-independent.

  The caller must call DestroyVersionStruct after the VersionStruct is no
  longer needed.

Arguments:

  VersionStruct - Receives the version stamp info to be used by other
                  functions in this module

  FileSpec - Specifies the file to obtain version info from

Return Value:

  TRUE if the routine was able to get version info, or FALSE if an
  error occurred.

--*/

{
    //
    // Init the struct
    //

    ZeroMemory (VersionStruct, sizeof (VERSION_STRUCTA));
    VersionStruct->FileSpec = FileSpec;

    //
    // Allocate enough memory for the version stamp
    //

    VersionStruct->Size = GetFileVersionInfoSizeA (
                                (PSTR) FileSpec,
                                &VersionStruct->Handle
                                );
    if (!VersionStruct->Size) {
        DEBUGMSG ((DBG_WARNING, "File %s does not have version info", FileSpec));
        return FALSE;
    }

    //
    // fix for version info bug:
    // allocate both buffers at once; this way the first buffer will not point to invalid
    // memory when a reallocation occurs because of the second grow
    //
    VersionStruct->VersionBuffer = GrowBuffer (&VersionStruct->GrowBuf, VersionStruct->Size * 2);

    if (!VersionStruct->VersionBuffer) {
        return FALSE;
    }

    VersionStruct->StringBuffer = VersionStruct->GrowBuf.Buf + VersionStruct->Size;

    //
    // Now get the version info from the file
    //

    if (!GetFileVersionInfoA (
             (PSTR) FileSpec,
             VersionStruct->Handle,
             VersionStruct->Size,
             VersionStruct->VersionBuffer
             )) {
        DestroyVersionStructA (VersionStruct);
        return FALSE;
    }

    //
    // Extract the fixed info
    //

    VerQueryValueA (
        VersionStruct->VersionBuffer,
        "\\",
        &VersionStruct->FixedInfo,
        &VersionStruct->FixedInfoSize
        );

    return TRUE;
}

ULONGLONG
VerGetFileVer (
    IN      PVERSION_STRUCTA VersionStruct
    )
{
    ULONGLONG result = 0;
    if (VersionStruct->FixedInfoSize >= sizeof (VS_FIXEDFILEINFO)) {
        *((PDWORD) (&result)) = VersionStruct->FixedInfo->dwFileVersionLS;
        *(((PDWORD) (&result)) + 1) = VersionStruct->FixedInfo->dwFileVersionMS;
    }
    return result;
}

ULONGLONG
VerGetProductVer (
    IN      PVERSION_STRUCTA VersionStruct
    )
{
    ULONGLONG result = 0;
    if (VersionStruct->FixedInfoSize >= sizeof (VS_FIXEDFILEINFO)) {
        *((PDWORD) (&result)) = VersionStruct->FixedInfo->dwProductVersionLS;
        *(((PDWORD) (&result)) + 1) = VersionStruct->FixedInfo->dwProductVersionMS;
    }
    return result;
}

DWORD
VerGetFileDateLo (
    IN      PVERSION_STRUCTA VersionStruct
    )
{
    if (VersionStruct->FixedInfoSize >= sizeof (VS_FIXEDFILEINFO)) {
        return VersionStruct->FixedInfo->dwFileDateLS;
    }
    return 0;
}

DWORD
VerGetFileDateHi (
    IN      PVERSION_STRUCTA VersionStruct
    )
{
    if (VersionStruct->FixedInfoSize >= sizeof (VS_FIXEDFILEINFO)) {
        return VersionStruct->FixedInfo->dwFileDateMS;
    }
    return 0;
}

DWORD
VerGetFileVerOs (
    IN      PVERSION_STRUCTA VersionStruct
    )
{
    if (VersionStruct->FixedInfoSize >= sizeof (VS_FIXEDFILEINFO)) {
        return VersionStruct->FixedInfo->dwFileOS;
    }
    return 0;
}

DWORD
VerGetFileVerType (
    IN      PVERSION_STRUCTA VersionStruct
    )
{
    if (VersionStruct->FixedInfoSize >= sizeof (VS_FIXEDFILEINFO)) {
        return VersionStruct->FixedInfo->dwFileType;
    }
    return 0;
}

VOID
DestroyVersionStructA (
    IN      PVERSION_STRUCTA VersionStruct
    )

/*++

Routine Description:

  DestroyVersionStruct cleans up all memory allocated by the routines
  in this module.

Arguments:

  VersionStruct - Specifies the structure to clean up

Return Value:

  none

--*/

{
    //
    // Clean up all allocations made by any routine using
    // the VersionStruct
    //

    if (VersionStruct->GrowBuf.Buf) {
        FreeGrowBuffer (&VersionStruct->GrowBuf);
    }

    ZeroMemory (VersionStruct, sizeof (VERSION_STRUCTA));
}


PCSTR
EnumFirstVersionTranslationA (
    IN OUT  PVERSION_STRUCTA VersionStruct
    )

/*++

Routine Description:

  EnumFirstVersionTranslation returins the translation string needed
  to access the string table of a version stamp.

Arguments:

  VersionStruct - Specifies the structure that has been initialized
                  by InitializeVersionStruct.

Return Value:

  A pointer to a string specifying the first translation, or
  NULL if no translations exist.

--*/

{
    UINT ArraySize;

    //
    // Query version block for array of code pages/languages
    //

    if (!VerQueryValueA (
            VersionStruct->VersionBuffer,
            "\\VarFileInfo\\Translation",
            &VersionStruct->Translations,
            &ArraySize
            )) {
        //
        // No translations are available
        //

        ArraySize = 0;
    }

    //
    // Return a pointer to the first translation
    //

    VersionStruct->CurrentDefaultTranslation = 0;
    VersionStruct->MaxTranslations = ArraySize / sizeof (TRANSLATION);
    VersionStruct->CurrentTranslation = 0;

    DEBUGMSG_IF ((
        VersionStruct->MaxTranslations == 0,
        DBG_WARNING,
        "File %s has no translations",
        VersionStruct->FileSpec
        ));

    return EnumNextVersionTranslationA (VersionStruct);
}


BOOL
pIsDefaultTranslationA (
    IN      PCSTR TranslationStr
    )

/*++

Routine Description:

  pIsDefaultTranslationA returns TRUE if the specified translation
  string is enumerated by default.  This routine stops multiple
  enumeration of the same translation string.

Arguments:

  TranslationStr - Specifies the translation string to test

Return Value:

  TRUE if the translation string is the same as a default translation
  string, or FALSE if it is not.

--*/

{
    INT i;

    for (i = 0 ; g_DefaultTranslationsA[i] ; i++) {
        if (StringIMatchA (TranslationStr, g_DefaultTranslationsA[i])) {
            return TRUE;
        }
    }

    return FALSE;
}


PCSTR
EnumNextVersionTranslationA (
    IN OUT  PVERSION_STRUCTA VersionStruct
    )

/*++

Routine Description:

  EnumNextVersionTranslation continues the enumeration of translation
  strings, needed to access the string table in a version stamp.

Arguments:

  VersionStruct - Specifies the same structure passed to
                  EnumFirstVersionTranslation.

Return Value:

  A pointer to a string specifying the next translation, or
  NULL if no additional translations exist.

--*/

{
    PTRANSLATION Translation;

    if (g_DefaultTranslationsA[VersionStruct->CurrentDefaultTranslation]) {
        //
        // Return default translations first
        //

        StringCopyA (
            VersionStruct->TranslationStr,
            g_DefaultTranslationsA[VersionStruct->CurrentDefaultTranslation]
            );

        VersionStruct->CurrentDefaultTranslation++;

    } else {

        do {
            //
            // Return NULL if all translations have been enumerated
            //

            if (VersionStruct->CurrentTranslation == VersionStruct->MaxTranslations) {
                return NULL;
            }

            //
            // Otherwise build translation string and return pointer to it
            //

            Translation = &VersionStruct->Translations[VersionStruct->CurrentTranslation];

            wsprintfA (
                VersionStruct->TranslationStr,
                "%04x%04x",
                Translation->CodePage,
                Translation->Language
                );

            VersionStruct->CurrentTranslation++;

        } while (pIsDefaultTranslationA (VersionStruct->TranslationStr));
    }

    return VersionStruct->TranslationStr;
}


PCSTR
EnumFirstVersionValueA (
    IN OUT  PVERSION_STRUCTA VersionStruct,
    IN      PCSTR VersionField
    )

/*++

Routine Description:

  EnumFirstVersionValue returns the first value stored in a version
  stamp for a specific field.  If the field does not exist, the
  function returns NULL.

  An enumeration of EnumFirstVersionValue/EnumNextVersionValue
  is used to list all localized strings for a field.

Arguments:

  VersionStruct - Specifies the structure that was initialized by
                  InitializeVersionStruct.

  VersionField - Specifies the name of the version field to enumerate

Return Value:

  A pointer to the first value of the field, or NULL if the field does
  not exist.

--*/

{
    PCSTR rc;

    if (!EnumFirstVersionTranslationA (VersionStruct)) {
        return NULL;
    }

    VersionStruct->VersionField = VersionField;

    rc = pEnumVersionValueCommonA (VersionStruct);

    if (!rc) {
        rc = EnumNextVersionValueA (VersionStruct);
    }

    return rc;
}


PCSTR
EnumNextVersionValueA (
    IN OUT  PVERSION_STRUCTA VersionStruct
    )

/*++

Routine Description:

  EnumNextVersionValue returns the next value stored in a version
  stamp for a specific field.

Arguments:

  VersionStruct - Specifies the same structure passed to EnumFirstVersionField

Return Value:

  A pointer to the next value of the field, or NULL if another field
  does not exist.

--*/

{
    PCSTR rc = NULL;

    do {
        if (!EnumNextVersionTranslationA (VersionStruct)) {
            break;
        }

        rc = pEnumVersionValueCommonA (VersionStruct);

    } while (!rc);

    return rc;
}


PCSTR
pEnumVersionValueCommonA (
    IN OUT  PVERSION_STRUCTA VersionStruct
    )

/*++

Routine Description:

  pEnumVersionValueCommon is a routine that obtains the value
  of a version field.  It is used for both EnumFirstVersionValue
  and EnumNextVersionValue.

Arguments:

  VersionStruct - Specifies the structure being processed

Return Value:

  A pointer to the version value for the current translation, or
  NULL if the value does not exist for the current translation.

--*/

{
    PSTR Text;
    UINT StringLen;
    PBYTE String;
    PCSTR Result = NULL;

    //
    // Prepare sub block for VerQueryValue API
    //

    Text = AllocTextA (
               16 +
               SizeOfStringA (VersionStruct->TranslationStr) +
               SizeOfStringA (VersionStruct->VersionField)
               );

    if (!Text) {
        return NULL;
    }

    wsprintfA (
        Text,
        "\\StringFileInfo\\%s\\%s",
        VersionStruct->TranslationStr,
        VersionStruct->VersionField
        );

    __try {
        //
        // Get the value from the version stamp
        //

        if (!VerQueryValueA (
                VersionStruct->VersionBuffer,
                Text,
                &String,
                &StringLen
                )) {
            //
            // No value is available
            //

            __leave;
        }

        //
        // Copy value into buffer
        //

        _mbsnzcpy (VersionStruct->StringBuffer, (PCSTR) String, StringLen);

        Result = VersionStruct->StringBuffer;

    }
    __finally {
        FreeTextA (Text);
    }

    return Result;
}


BOOL
CreateVersionStructW (
    OUT     PVERSION_STRUCTW VersionStruct,
    IN      PCWSTR FileSpec
    )

/*++

Routine Description:

  CreateVersionStruct is called to load a version structure from a file
  and to obtain the fixed version stamp info that is language-independent.

  The caller must call DestroyVersionStruct after the VersionStruct is no
  longer needed.

Arguments:

  VersionStruct - Receives the version stamp info to be used by other
                  functions in this module

  FileSpec - Specifies the file to obtain version info from

Return Value:

  TRUE if the routine was able to get version info, or FALSE if an
  error occurred.

--*/

{
    ZeroMemory (VersionStruct, sizeof (VERSION_STRUCTW));
    VersionStruct->FileSpec = FileSpec;

    //
    // Allocate enough memory for the version stamp
    //

    VersionStruct->Size = GetFileVersionInfoSizeW (
                                (PWSTR) FileSpec,
                                &VersionStruct->Handle
                                );
    if (!VersionStruct->Size) {
        DEBUGMSG ((DBG_WARNING, "File %S does not have version info", FileSpec));
        return FALSE;
    }

    //
    // fix for version info bug:
    // allocate both buffers at once; this way the first buffer will not point to invalid
    // memory when a reallocation occurs because of the second grow
    //
    VersionStruct->VersionBuffer = GrowBuffer (&VersionStruct->GrowBuf, VersionStruct->Size * 2);

    if (!VersionStruct->VersionBuffer) {
        return FALSE;
    }

    VersionStruct->StringBuffer = VersionStruct->GrowBuf.Buf + VersionStruct->Size;

    //
    // Now get the version info from the file
    //

    if (!GetFileVersionInfoW (
             (PWSTR) FileSpec,
             VersionStruct->Handle,
             VersionStruct->Size,
             VersionStruct->VersionBuffer
             )) {
        DestroyVersionStructW (VersionStruct);
        return FALSE;
    }

    //
    // Extract the fixed info
    //

    VerQueryValueW (
        VersionStruct->VersionBuffer,
        L"\\",
        &VersionStruct->FixedInfo,
        &VersionStruct->FixedInfoSize
        );

    return TRUE;
}


VOID
DestroyVersionStructW (
    IN      PVERSION_STRUCTW VersionStruct
    )

/*++

Routine Description:

  DestroyVersionStruct cleans up all memory allocated by the routines
  in this module.

Arguments:

  VersionStruct - Specifies the structure to clean up

Return Value:

  none

--*/

{
    if (VersionStruct->GrowBuf.Buf) {
        FreeGrowBuffer (&VersionStruct->GrowBuf);
    }

    ZeroMemory (VersionStruct, sizeof (VERSION_STRUCTW));
}


PCWSTR
EnumFirstVersionTranslationW (
    IN OUT  PVERSION_STRUCTW VersionStruct
    )

/*++

Routine Description:

  EnumFirstVersionTranslation returins the translation string needed
  to access the string table of a version stamp.

Arguments:

  VersionStruct - Specifies the structure that has been initialized
                  by InitializeVersionStruct.

Return Value:

  A pointer to a string specifying the first translation, or
  NULL if no translations exist.

--*/

{
    UINT ArraySize;

    if (!VerQueryValueW (
            VersionStruct->VersionBuffer,
            L"\\VarFileInfo\\Translation",
            &VersionStruct->Translations,
            &ArraySize
            )) {
        //
        // No translations are available
        //

        ArraySize = 0;
    }

    //
    // Return a pointer to the first translation
    //

    VersionStruct->CurrentDefaultTranslation = 0;
    VersionStruct->MaxTranslations = ArraySize / sizeof (TRANSLATION);
    VersionStruct->CurrentTranslation = 0;

    DEBUGMSG_IF ((
        VersionStruct->MaxTranslations == 0,
        DBG_WARNING,
        "File %S has no translations",
        VersionStruct->FileSpec
        ));

    return EnumNextVersionTranslationW (VersionStruct);
}


BOOL
pIsDefaultTranslationW (
    IN      PCWSTR TranslationStr
    )
{
    INT i;

    for (i = 0 ; g_DefaultTranslationsW[i] ; i++) {
        if (StringIMatchW (TranslationStr, g_DefaultTranslationsW[i])) {
            return TRUE;
        }
    }
    return FALSE;
}


PCWSTR
EnumNextVersionTranslationW (
    IN OUT  PVERSION_STRUCTW VersionStruct
    )

/*++

Routine Description:

  EnumNextVersionTranslation continues the enumeration of translation
  strings, needed to access the string table in a version stamp.

Arguments:

  VersionStruct - Specifies the same structure passed to
                  EnumFirstVersionTranslation.

Return Value:

  A pointer to a string specifying the next translation, or
  NULL if no additional translations exist.

--*/

{
    PTRANSLATION Translation;

    if (g_DefaultTranslationsW[VersionStruct->CurrentDefaultTranslation]) {

        StringCopyW (
            VersionStruct->TranslationStr,
            g_DefaultTranslationsW[VersionStruct->CurrentDefaultTranslation]
            );

        VersionStruct->CurrentDefaultTranslation++;

    } else {

        do {
            if (VersionStruct->CurrentTranslation == VersionStruct->MaxTranslations) {
                return NULL;
            }

            Translation = &VersionStruct->Translations[VersionStruct->CurrentTranslation];

            wsprintfW (
                VersionStruct->TranslationStr,
                L"%04x%04x",
                Translation->CodePage,
                Translation->Language
                );

            VersionStruct->CurrentTranslation++;

        } while (pIsDefaultTranslationW (VersionStruct->TranslationStr));
    }

    return VersionStruct->TranslationStr;
}


PCWSTR
EnumFirstVersionValueW (
    IN OUT  PVERSION_STRUCTW VersionStruct,
    IN      PCWSTR VersionField
    )

/*++

Routine Description:

  EnumFirstVersionValue returns the first value stored in a version
  stamp for a specific field.  If the field does not exist, the
  function returns NULL.

  An enumeration of EnumFirstVersionValue/EnumNextVersionValue
  is used to list all localized strings for a field.

Arguments:

  VersionStruct - Specifies the structure that was initialized by
                  InitializeVersionStruct.

  VersionField - Specifies the name of the version field to enumerate

Return Value:

  A pointer to the first value of the field, or NULL if the field does
  not exist.

--*/

{
    PCWSTR rc;

    if (!EnumFirstVersionTranslationW (VersionStruct)) {
        return NULL;
    }

    VersionStruct->VersionField = VersionField;

    rc = pEnumVersionValueCommonW (VersionStruct);

    if (!rc) {
        rc = EnumNextVersionValueW (VersionStruct);
    }

    return rc;
}


PCWSTR
EnumNextVersionValueW (
    IN OUT  PVERSION_STRUCTW VersionStruct
    )

/*++

Routine Description:

  EnumNextVersionValue returns the next value stored in a version
  stamp for a specific field.

Arguments:

  VersionStruct - Specifies the same structure passed to EnumFirstVersionField

Return Value:

  A pointer to the next value of the field, or NULL if another field
  does not exist.

--*/

{
    PCWSTR rc = NULL;

    do {
        if (!EnumNextVersionTranslationW (VersionStruct)) {
            break;
        }

        rc = pEnumVersionValueCommonW (VersionStruct);

    } while (!rc);

    return rc;
}


PCWSTR
pEnumVersionValueCommonW (
    IN OUT  PVERSION_STRUCTW VersionStruct
    )

/*++

Routine Description:

  pEnumVersionValueCommon is a routine that obtains the value
  of a version field.  It is used for both EnumFirstVersionValue
  and EnumNextVersionValue.

Arguments:

  VersionStruct - Specifies the structure being processed

Return Value:

  A pointer to the version value for the current translation, or
  NULL if the value does not exist for the current translation.

--*/

{
    PWSTR Text;
    UINT StringLen;
    PBYTE String;
    PCWSTR Result = NULL;

    //
    // Prepare sub block for VerQueryValue API
    //

    Text = AllocTextW (
               18 +
               CharCountW (VersionStruct->TranslationStr) +
               CharCountW (VersionStruct->VersionField)
               );

    if (!Text) {
        return NULL;
    }

    wsprintfW (
        Text,
        L"\\StringFileInfo\\%s\\%s",
        VersionStruct->TranslationStr,
        VersionStruct->VersionField
        );

    __try {
        //
        // Get the value from the version stamp
        //

        if (!VerQueryValueW (
                VersionStruct->VersionBuffer,
                Text,
                &String,
                &StringLen
                )) {
            //
            // No value is available
            //

            return NULL;
        }

        CopyMemory (VersionStruct->StringBuffer, String, StringLen * sizeof (WCHAR));
        VersionStruct->StringBuffer [StringLen * sizeof (WCHAR)] = 0;
        Result = (PWSTR) VersionStruct->StringBuffer;

    }
    __finally {
        FreeTextW (Text);
    }

    return Result;
}


PSTR
UnicodeToCcs (
    PCWSTR Source
    )

/*++

Routine Description:

  UnicodeToCcs will walk the unicode string and convert it to ANSII by encoding all DBCS characters
  to hex values.

Arguments:

  Source - the Unicode string

Return Value:

  An encoded ANSII string.

--*/

{
    CHAR result [MEMDB_MAX];
    UINT srcIndex = 0;
    UINT destIndex = 0;

    while (Source [srcIndex]) {
        if ((Source [srcIndex] >=32) &&
            (Source [srcIndex] <=126)
            ) {
            result [destIndex] = (BYTE) Source [srcIndex];
            destIndex ++;
        }
        else {
            if ((destIndex == 0) ||
                (result [destIndex-1] != '*')
                ) {
                result [destIndex] = '*';
                destIndex ++;
            }
        }
        srcIndex ++;
    }

    result [destIndex] = 0;
    return DuplicatePathString (result, 0);
}