/*++

Copyright (c) 2002 Microsoft Corporation

Module Name:

    engine.c

Abstract:

    Token Generator for Cross Language Migration Tool

Author:

    Rerkboon Suwanasuk   01-May-2002  Created

Revision History:

    <alias> <date> <comments>

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setupapi.h>
#include <shlwapi.h>
#include "Common.h"


//-----------------------------------------------------------------------------
//
//  Function:   GenerateTokenFile
//
//  Synopsis:   Entry function for our program.
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      none
//
//-----------------------------------------------------------------------------
HRESULT GenerateTokenFile(VOID)
{
    HRESULT hr;
    BOOL    bRet;
    HINF    hTemplateFile;
    HANDLE  hFile;
    
    WCHAR   wszFullTemplateFilePath[MAX_PATH];
    WCHAR   wszFullOutputFilePath[MAX_PATH];
    WCHAR   wszLCIDSectionName[32];
    LPWSTR  lpOutputFileBuffer = NULL;
    LPWSTR  lpFileName;
    size_t  cbOutputFileBuffer;

    //
    // Set some private environment variables for use in our program
    //
    if (!SetPrivateEnvironmentVar())
    {
        wprintf(TEXT("Error! Cannot set private environment variables.\n"));
        return E_FAIL;
    }

    hTemplateFile = SetupOpenInfFile(g_wszTemplateFile,
                                     NULL,
                                     INF_STYLE_OLDNT,
                                     NULL);
    if (hTemplateFile != INVALID_HANDLE_VALUE)
    {
        //
        // Read the [SourcePath] section from template INF file
        //
        hr = ReadSourcePathData(hTemplateFile);
        if (SUCCEEDED(hr))
        {
            hr = InitOutputFile(g_wszOutputFile,
                                wszLCIDSectionName,
                                ARRAYSIZE(wszLCIDSectionName));
            if (SUCCEEDED(hr))
            {
                //
                // Resolve generic strings
                //
                hr = ResolveStringsSection(hTemplateFile, TEXT("Strings"));
                if (FAILED(hr))
                {
                    goto EXIT;
                }

                //
                // Resolve lang-specific strings
                //
                hr = ResolveStringsSection(hTemplateFile, wszLCIDSectionName);
                if (FAILED(hr))
                {
                    goto EXIT;
                }

                //
                // Remove unneeded sub string
                //
                hr = RemoveUnneededStrings(hTemplateFile);
                if (FAILED(hr))
                {
                    goto EXIT;
                }

                //
                // Extract sub string
                //
                hr = ExtractStrings(hTemplateFile);
                if (FAILED(hr))
                {
                    goto EXIT;
                }
            }
        }
        else
        {
            wprintf( TEXT("Error! Cannot read [SourcePath] section\n") );
        }

        SetupCloseInfFile(hTemplateFile);
    }
    else
    {
        wprintf(TEXT("Error! Cannot open template file: %s\n"), g_wszTemplateFile);
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

EXIT:
    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   ReadSourcePathData
//
//  Synopsis:   Read [SourcePath] section from template INF file,
//              then store all the source paths into the structure.
//              This structure will be used to find the location of
//              resource files.
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      none
//
//-----------------------------------------------------------------------------
HRESULT ReadSourcePathData(
    HINF hTemplateFile
)
{
    HRESULT    hr = E_FAIL;
    BOOL       bRet;
    size_t     nLineIndex;
    WCHAR      wszValue[MAX_PATH];
    WCHAR      wszSrcPathSection[32];
    INFCONTEXT InfContext;

    if (hTemplateFile == INVALID_HANDLE_VALUE)
    {
        return E_INVALIDARG;
    }

    hr = StringCchPrintf(wszSrcPathSection,
                         ARRAYSIZE(wszSrcPathSection),
                         TEXT("SourcePath.%.4x"),
                         g_lcidTarget);
    if (FAILED(hr))
    {
        return hr;
    }

    //
    // Extract the source paths from INF and save to global structure
    //
    g_dwSrcCount = SetupGetLineCount(hTemplateFile, wszSrcPathSection);

    for (nLineIndex = 0 ; nLineIndex < g_dwSrcCount ; nLineIndex++)
    {
        bRet = SetupGetLineByIndex(hTemplateFile,
                                   wszSrcPathSection,
                                   nLineIndex,
                                   &InfContext);
        if (bRet)
        {
            //
            // Get the name of each source path
            //
            bRet = SetupGetStringField(&InfContext,
                                       0,
                                       wszValue,
                                       ARRAYSIZE(wszValue),
                                       NULL);
            if (bRet)
            {
                hr = StringCchCopy(g_SrcPath[nLineIndex].wszSrcName,
                                   ARRAYSIZE(g_SrcPath[nLineIndex].wszSrcName),
                                   wszValue);
                if (SUCCEEDED(hr))
                {
                    //
                    // Get the path associated to the source name
                    //
                    if (SetupGetStringField(&InfContext,
                                            1,
                                            wszValue,
                                            ARRAYSIZE(wszValue),
                                            NULL))
                    {
                        hr = StringCchCopy(g_SrcPath[nLineIndex].wszPath,
                                           ARRAYSIZE(g_SrcPath[nLineIndex].wszPath),
                                           wszValue);
                    }
                }
            }
            else
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
        else
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
        }

        if (FAILED(hr))
        {
            break;
        }
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   ResolveStringsSection
//
//  Synopsis:   Resolve all strings under specified section name in template
//              file, then write the resolved strings to output file.
//
//  Returns:    HRESULT
//
//  History:    03/27/2002 Rerkboos     Created
//
//  Notes:      
//
//-----------------------------------------------------------------------------
HRESULT ResolveStringsSection(
    HINF    hTemplateFile,      // Handle to template file
    LPCWSTR lpSectionName       // Section name in template file to resolve
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    LONG    lLineCount;

    if (hTemplateFile == INVALID_HANDLE_VALUE || lpSectionName == NULL)
    {
        return E_INVALIDARG;
    }

    wprintf(TEXT("Start resolving strings in section [%s]:\n"),
            lpSectionName);

    lLineCount = SetupGetLineCount(hTemplateFile, lpSectionName);
    if (lLineCount >= 0)
    {
        INFCONTEXT context;
        LONG       lLineIndex;
        LPWSTR     lpKey;
        LPWSTR     lpValue;
        DWORD      cchKey;
        DWORD      cchValue;
        DWORD      cbWritten;

        //
        // Resolve strings under string section
        //
        for (lLineIndex = 0 ; lLineIndex < lLineCount ; lLineIndex++)
        {
            bRet = SetupGetLineByIndex(hTemplateFile,
                                       lpSectionName,
                                       lLineIndex,
                                       &context);
            if (bRet)
            {
                hr = ResolveLine(&context, &lpKey, &cchKey, &lpValue, &cchValue);
                if (SUCCEEDED(hr))
                {
                    hr = WriteToOutputFile(g_wszOutputFile, lpKey, lpValue);
                    if (FAILED(hr))
                    {
                        wprintf(TEXT("[FAIL] - Cannot write to output file\n"));
                        break;
                    }

                    MEMFREE(lpKey);
                    MEMFREE(lpValue);
                }
                else
                {
                    wprintf(TEXT("[FAIL] - Cannot process line %d in template file\n"),
                            lLineIndex);
                    hr = HRESULT_FROM_WIN32(GetLastError());
                    break;
                }
            }
            else
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                break;
            }
        }
    }
    else 
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    wprintf(TEXT("Finish resolving strings in section [%s]: hr = 0x%X\n\n"),
            lpSectionName,
            hr);

    return hr;
}


//-----------------------------------------------------------------------------
//
//  Function:   ResolveLine
//
//  Synopsis:   Resolve a string value from input INF context.
//
//  Returns:    HRESULT
//
//  History:    03/27/2002 Rerkboos     Created
//
//  Notes:      This function will allocate memory for lplpKey and lplpValue,
//              Caller needs to free memory using HeapFree().
//
//-----------------------------------------------------------------------------
HRESULT ResolveLine(
    PINFCONTEXT lpContext,      // INF line context
    LPWSTR      *lplpKey,       // Pointer to newly allocated buffer for Key name
    LPDWORD     lpcchKey,       // Size of allocated buffer for Key name
    LPWSTR      *lplpValue,     // Pointer to newly allocated buffer for Value
    LPDWORD     lpcchValue      // Size of allocated buffer for Value
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    DWORD   cchKey;
    DWORD   cchRequired;

    if (lplpKey == NULL || lpcchKey == NULL ||
        lplpValue == NULL || lpcchValue == NULL)
    {
        return E_INVALIDARG;
    }

    bRet = SetupGetStringField(lpContext, 0, NULL, 0, &cchKey);
    if (bRet)
    {
        *lplpKey = MEMALLOC(cchKey * sizeof(WCHAR));
        if (*lplpKey)
        {
            *lpcchKey = cchKey;

            SetupGetStringField(lpContext, 0, *lplpKey, cchKey, &cchRequired);

            hr = ResolveValue(lpContext, lplpValue, lpcchValue);
            if (FAILED(hr))
            {
                wprintf(TEXT("[FAIL] Cannot resolve Key [%s], hr = 0x%X\n"),
                        *lplpKey,
                        hr);
                MEMFREE(*lplpKey);
            }
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   InitOutputFile
//
//  Synopsis:   Initialize output file. Output file is always a Unicode text
//              file. This function will create a section name [Strings.XXXX]
//              where XXXX is locale ID that user put in command line
//
//  Returns:    S_OK if succeeded
//
//  History:    03/27/2002 Rerkboos Created
//
//  Notes:      
//
//-----------------------------------------------------------------------------
HRESULT InitOutputFile(
    LPCWSTR lpFileName,         // Output file name to be created
    LPWSTR  lpSectionName,      // Buffer to store destination section name
    DWORD   cchSectionName      // Size of buffer in WCHAR
)
{
    HRESULT hr = S_OK;
    BOOL    bRet;
    HANDLE  hFile;

    if (lpSectionName == NULL)
    {
        return E_INVALIDARG;
    }

    hFile = CreateFile(lpFileName,
                       GENERIC_WRITE,
                       0,
                       NULL,
                       CREATE_ALWAYS,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        DWORD cbWritten;
        WCHAR wszSectionHeader[32];

        // Unicode BOM = 0xFEFF
        wszSectionHeader[0] = 0xFEFF;

        // Create a section name - [Strings.XXXX] where XXXX is locale ID in Hex
        hr = StringCchPrintf(wszSectionHeader + 1,
                             ARRAYSIZE(wszSectionHeader) - 1,
                             TEXT("[Strings.%.4x]\r\n"),
                             g_lcidTarget);
        if (SUCCEEDED(hr))
        {
            // Write Unicode BOM and String section header to output file
            bRet = WriteFile(hFile,
                             wszSectionHeader,
                             lstrlen(wszSectionHeader) * sizeof(WCHAR),
                             &cbWritten,
                             NULL);
            if (!bRet)
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }

        CloseHandle(hFile);
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    // If no error occurred, return target String section back to caller
    if (SUCCEEDED(hr))
    {
        hr = StringCchPrintf(lpSectionName,
                             cchSectionName,
                             TEXT("Strings.%.4x"),
                             g_lcidTarget);
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   WriteToOutputFile
//
//  Synopsis:   Write Key and Value to output file
//
//  Returns:    S_OK if succeeded
//
//  History:    03/27/2002 Rerkboos Created
//
//  Notes:      
//
//-----------------------------------------------------------------------------
HRESULT WriteToOutputFile(
    LPCWSTR lpFileName,         // Output file name
    LPCWSTR lpKey,              // Key name
    LPCWSTR lpValue             // Value name
)
{
    HRESULT hr = S_OK;
    BOOL    bRet;
    LPWSTR  lpQuoted;
    DWORD   cchQuoted;

    static WCHAR wszOutputLCIDSection[32];

    //
    // Create the target string section name in output file
    //
    if (wszOutputLCIDSection[0] == TEXT('\0'))
    {
        hr = StringCchPrintf(wszOutputLCIDSection,
                             ARRAYSIZE(wszOutputLCIDSection),
                             TEXT("Strings.%.4x"),
                             g_lcidTarget);
        if (FAILED(hr))
        {
            return hr;
        }
    }

    //
    // Write the key and value to output file
    //
    cchQuoted = lstrlen(lpValue) + lstrlen(TEXT("\"\"")) + 1;
    lpQuoted = (LPWSTR) MEMALLOC(cchQuoted * sizeof(WCHAR));
    if (lpQuoted)
    {
        hr = StringCchPrintf(lpQuoted,
                             cchQuoted,
                             TEXT("\"%s\""),
                             lpValue);
        if (SUCCEEDED(hr))
        {
            bRet = WritePrivateProfileString(wszOutputLCIDSection,
                                            lpKey,
                                            lpQuoted,
                                            lpFileName);
            if (!bRet)
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }

        MEMFREE(lpQuoted);
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   ResolveValue
//
//  Synopsis:   Resolve the string value from various resource.
//              Following is the format in template file
//
//                  Key = INF, [Src], [INF File], [Section], [Key Name]
//                  Key = DLL, [Src], [DLL File], [Resource ID]
//                  Key = MSG, [Src], [DLL File], [Message ID]
//                  Key = STR, [String value]
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer.
//
//-----------------------------------------------------------------------------
HRESULT ResolveValue(
    PINFCONTEXT pInfContext,    // INF line context of template file
    LPWSTR      *lplpValue,     // Address of pointer to newly allocated Value buffer
    LPDWORD     lpcchValue      // Address of pionter to size of Value buffer in WCHAR
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    LPWSTR  lpToken[8];
    LONG    lTokenCount;
    DWORD   cchRequired;
    DWORD   cchBuffer;
    LPWSTR  lpBuffer;
    WCHAR   wszSourceFile[MAX_PATH];

    if (lplpValue == NULL || lpcchValue == NULL)
    {
        return E_INVALIDARG;
    }

    // Query the required size of buffer to store the data
    bRet = SetupGetMultiSzField(pInfContext, 1, NULL, 0, &cchRequired);
    if (!bRet)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // Allocate the buffer to store a line from INF
    cchBuffer = cchRequired;
    lpBuffer = (LPWSTR) MEMALLOC(cchBuffer * sizeof(WCHAR));
    if (lpBuffer)
    {
        // Get the data from field 1 to the end of line to allocated buffer
        bRet = SetupGetMultiSzField(pInfContext, 1, lpBuffer, cchBuffer, &cchRequired);
        if (bRet)
        {
            lTokenCount = TokenizeMultiSzString(lpBuffer, cchBuffer, lpToken, 8);
            if (lTokenCount >= 0)
            {
                if (CompareEngString(lpToken[0], TEXT_INF) == CSTR_EQUAL)
                {
                    // INF, [Src], [INF File], [Section], [Key Name]
                    // [0]   [1]       [2]        [3]        [4]
                    if (lTokenCount == 5)
                    {
                        hr = ResolveSourceFile(lpToken[1],
                                               lpToken[2],
                                               wszSourceFile,
                                               ARRAYSIZE(wszSourceFile));
                        if (SUCCEEDED(hr))
                        {
                            hr = GetStringFromINF(wszSourceFile,   // Inf file name
                                                  lpToken[3],      // Section name in Inf
                                                  lpToken[4],      // Key name
                                                  lplpValue,
                                                  lpcchValue);
                        }
                    }
                    else
                    {
                        // Data format is invalid
                        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
                    }
                }
                else if (CompareEngString(lpToken[0], TEXT_DLL) == CSTR_EQUAL)
                {
                    // DLL, [Src], [DLL File], [Resource ID]
                    // [0]   [1]      [2]           [3]
                    if (lTokenCount == 4)
                    {
                        hr = ResolveSourceFile(lpToken[1],
                                               lpToken[2],
                                               wszSourceFile,
                                               ARRAYSIZE(wszSourceFile));
                        if (SUCCEEDED(hr))
                        {
                            hr = GetStringFromDLL(wszSourceFile,   // Dll file name
                                                  _wtoi(lpToken[3]),
                                                  lplpValue,
                                                  lpcchValue);
                        }
                    }
                    else
                    {
                        // Data format is invalid
                        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
                    }
                }
                else if (CompareEngString(lpToken[0], TEXT_MSG) == CSTR_EQUAL)
                {
                    // MSG, [Src], [DLL File], [Message ID]
                    // [0]   [1]      [2]           [3]
                    if (lTokenCount == 4)
                    {
                        hr = ResolveSourceFile(lpToken[1],
                                               lpToken[2],
                                               wszSourceFile,
                                               ARRAYSIZE(wszSourceFile));
                        if (SUCCEEDED(hr))
                        {
                            hr = GetStringFromMSG(wszSourceFile,   // File name
                                                  _wtoi(lpToken[3]),    // Message ID
                                                  g_lcidTarget,
                                                  lplpValue,
                                                  lpcchValue);
                        }
                    }
                    else
                    {
                        // Data format is invalid
                        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
                    }
                }
                else if (CompareEngString(lpToken[0], TEXT_STR) == CSTR_EQUAL)
                {
                    // STR, [String]
                    // [0]     [1]
                    if (lTokenCount == 2)
                    {
                        hr = GetStringFromSTR(lpToken[1], lplpValue, lpcchValue);
                    }
                    else
                    {
                        // Data format is invalid
                        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
                    }
                }
                else
                {
                    *lplpValue = NULL;
                    *lpcchValue = 0;
                    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
                }
            }
        }

        MEMFREE(lpBuffer);
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}



HRESULT ResolveSourceFile(
    LPCWSTR lpSrcPathName,
    LPCWSTR lpSrcFileName,
    LPWSTR  lpFullOutputPath,
    DWORD   cchFullOutputPath
)
{
    HRESULT hr;
    WCHAR   wszCabPath[MAX_PATH];
    WCHAR   wszCab[MAX_PATH];
    WCHAR   wszFileInCab[MAX_PATH];
    WCHAR   wszExpandedCabPath[MAX_PATH];
    DWORD   dwErr;

    if (lpFullOutputPath == NULL)
    {
        return E_INVALIDARG;
    }

    //
    // Resolve source file name and directory
    //
    hr = GetPathFromSourcePathName(lpSrcPathName, wszCabPath, ARRAYSIZE(wszCabPath));
    if (SUCCEEDED(hr))
    {
        hr = GetCabFileName(lpSrcFileName,
                            wszCab,
                            ARRAYSIZE(wszCab),
                            wszFileInCab,
                            ARRAYSIZE(wszFileInCab));
        if (SUCCEEDED(hr))
        {
            dwErr = ExpandEnvironmentStrings(wszCabPath,
                                             wszExpandedCabPath,
                                             ARRAYSIZE(wszExpandedCabPath));
            if (dwErr > 0)
            {
                hr = CopyCompressedFile(wszCabPath,
                                        wszCab,
                                        wszFileInCab,
                                        lpFullOutputPath,
                                        cchFullOutputPath);
            }
            else
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }

    return hr;
}


//-----------------------------------------------------------------------------
//
//  Function:   GetStringFromINF
//
//  Synopsis:   Extract the string value from INF file
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer (HeapFree)
//
//-----------------------------------------------------------------------------
HRESULT GetStringFromINF(
    LPCWSTR lpInfFile,      // Full path name to Inf file
    LPCWSTR lpSection,      // Section name
    LPCWSTR lpKey,          // Key name
    LPWSTR  *lplpValue,     // Address of pointer to point to the allocated buffer
    LPDWORD lpcchValue      //
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    WCHAR   wszFullPath[MAX_PATH];
    HINF    hInf;
    DWORD   dwCharCount;

    if (lplpValue == NULL)
    {
        return E_INVALIDARG;
    }
    
    *lplpValue  = NULL;
    *lpcchValue = 0;

    hInf = SetupOpenInfFile(lpInfFile,
                            NULL,
                            INF_STYLE_WIN4,
                            NULL);
    if (hInf != INVALID_HANDLE_VALUE)
    {
        // Find out the size of buffer needed to store the string
        bRet = SetupGetLineText(NULL,
                                hInf,
                                lpSection,
                                lpKey,
                                NULL,           // No returned buffer
                                0,              // No returned buffer size
                                lpcchValue);   // Required size including null terminator
        if (bRet)
        {
            // Allocate memory to store the string
            *lplpValue = (LPWSTR) MEMALLOC(*lpcchValue * sizeof(WCHAR));
            if (*lplpValue != NULL)
            {
                //
                // Read the string from INF file
                //
                bRet = SetupGetLineText(NULL,
                                        hInf,
                                        lpSection,
                                        lpKey,
                                        *lplpValue,
                                        *lpcchValue,
                                        NULL);
                if (bRet)
                {
                    hr = S_OK;
                }
            }
            else
            {
                SetLastError(ERROR_OUTOFMEMORY);
            }
        }

        SetupCloseInfFile(hInf);
    }

    // If something was wrong, clean up
    if (hr != S_OK)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        
        *lpcchValue = 0;
        if (*lplpValue != NULL)
        {
            MEMFREE(*lplpValue);
        }
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   GetStringFromDLL
//
//  Synopsis:   Extract the string value from String resource in DLL
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer
//
//-----------------------------------------------------------------------------
HRESULT GetStringFromDLL(
    LPCWSTR lpDLLFile,      // Full path to Dll file
    UINT    uStrID,         // String ID
    LPWSTR  *lplpValue,    // Address of pointer to point to the allocated buffer
    LPDWORD lpcchValue     //
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    HMODULE hDLL;
    int     cchCopied;

    if (lplpValue == NULL || lpcchValue == NULL)
    {
        return E_INVALIDARG;
    }

    *lplpValue  = NULL;
    *lpcchValue = 0;

    // Load resource DLL
    hDLL = LoadLibraryEx(lpDLLFile, NULL, LOAD_LIBRARY_AS_DATAFILE);
    if (hDLL)
    {
        // Allocate memory to store the string
        // There is no function to calculate buffer size needed, maximum is 65535 (from MSDN)
        // Initially allocate 1024 WCHARs, Reallocate again if it's not big enough
        *lpcchValue = 1024;
        *lplpValue  = (LPWSTR) MEMALLOC(*lpcchValue * sizeof(WCHAR));
        if (*lplpValue != NULL)
        {
            //
            // Load the string from DLL
            //
            cchCopied = LoadString(hDLL, uStrID, *lplpValue, (int) *lpcchValue);
            if (cchCopied > 0)
            {
                hr = S_OK;

                while (cchCopied == (int) (*lpcchValue - 1))
                {
                    // Allocated buffer is too small, reallocate more
                    LPWSTR lpOldBuffer;

                    lpOldBuffer = *lplpValue;
                    *lpcchValue += 1024;

                    *lplpValue = MEMREALLOC(lpOldBuffer, *lpcchValue);
                    if (*lplpValue == NULL)
                    {
                        // Error reallocating more memory
                        *lplpValue = lpOldBuffer;
                        SetLastError(ERROR_OUTOFMEMORY);
                        hr = E_FAIL;
                        break;
                    }
                    else
                    {
                        hr = S_OK;
                    }

                    cchCopied = LoadString(hDLL, uStrID, *lplpValue, (int) *lpcchValue);
                }
            }
        }
        else
        {
            SetLastError(ERROR_OUTOFMEMORY);
        }

        FreeLibrary(hDLL);
    }

    // If something was wrong, clean up
    if (hr != S_OK)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        
        *lpcchValue = 0;
        if (*lplpValue != NULL)
        {
            MEMFREE(*lplpValue);
        }
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   GetStringFromMSG
//
//  Synopsis:   Extract the string value from message table
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer
//
//-----------------------------------------------------------------------------
HRESULT GetStringFromMSG(
    LPCWSTR lpDLLFile,      // Full path to resource DLL
    DWORD   dwMsgID,        // Message ID
    DWORD   dwLangID,       // Language ID
    LPWSTR  *lplpValue,    // 
    LPDWORD lpcchValue     // 
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    WCHAR   wszFullPath[MAX_PATH];
    HMODULE hDLL;
    LPWSTR  lpTmpBuffer;
    int     nRet;

    if (lplpValue == NULL || lpcchValue == NULL)
    {
        return E_INVALIDARG;
    }

    *lplpValue = NULL;
    *lpcchValue = 0;

    // Load the resource DLL
    hDLL = LoadLibraryEx(lpDLLFile, NULL, DONT_RESOLVE_DLL_REFERENCES);
    if (hDLL != NULL)
    {
        // Load the string from message table in DLL
        // FormatMessage will allocate buffer for the data using LocalAlloc()
        // need to free with LocalFree()
        bRet = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | 
                               FORMAT_MESSAGE_ALLOCATE_BUFFER |
                               FORMAT_MESSAGE_IGNORE_INSERTS |
                               FORMAT_MESSAGE_MAX_WIDTH_MASK,
                             hDLL,
                             dwMsgID,
                             dwLangID,
                             (LPWSTR) &lpTmpBuffer,
                             0,          // use 0 to query the required buffer size
                             NULL);
        if (bRet)
        {
            // Trim all unnecessary leading and trailing spaces
            RTrim(lpTmpBuffer);

            // Allocate the buffer for returned data
            *lpcchValue = lstrlen(lpTmpBuffer) + 1;
            *lplpValue  = (LPWSTR) MEMALLOC(*lpcchValue * sizeof(WCHAR));
            if (*lplpValue)
            {
                hr = StringCchCopy(*lplpValue, *lpcchValue, lpTmpBuffer);
            }
            else
            {
                SetLastError(ERROR_OUTOFMEMORY);
            }

            LocalFree(lpTmpBuffer);
        }

        FreeLibrary(hDLL);
    }

    // If something was wrong, clean up
    if (hr != S_OK)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        
        *lpcchValue = 0;
        if (*lplpValue != NULL)
        {
            MEMFREE(*lplpValue);
        }
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   GetStringFromSTR
//
//  Synopsis:   Copy the hardcoded string into the newly allocated buffer
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer
//
//-----------------------------------------------------------------------------
HRESULT GetStringFromSTR(
    LPCWSTR lpString,
    LPWSTR  *lplpValue,    // 
    LPDWORD lpcchValue     // 
)
{
    HRESULT hr = E_FAIL;

    if (lplpValue == NULL || lpcchValue == NULL)
    {
        return E_INVALIDARG;
    }

    // Allocate buffer and copy string to buffer
    *lpcchValue = lstrlen(lpString) + 1;
    *lplpValue = (LPWSTR) MEMALLOC(*lpcchValue * sizeof(WCHAR));
    if (*lplpValue)
    {
        hr = StringCchCopy(*lplpValue, *lpcchValue, lpString);
    }

    // If something was wrong, clean up
    if (hr != S_OK)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());

        *lpcchValue = 0;
        if (*lplpValue != NULL)
        {
            MEMFREE(*lplpValue);
        }
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   SetPrivateEnvironmentVar
//
//  Synopsis:   Set private environment variables to use within our program
//
//  Returns:    TRUE if succeeded, FALSE otherwise
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      none
//
//-----------------------------------------------------------------------------
BOOL SetPrivateEnvironmentVar()
{
    HRESULT hr;
    BOOL    bRet;
    WCHAR   wszValue[MAX_PATH];

    //
    // Set LANGID_DEC to decimal value of the system default UI language
    //
    hr = StringCchPrintf(wszValue,
                         ARRAYSIZE(wszValue),
                         TEXT("%04d"),
                         g_lcidTarget);
    if (SUCCEEDED(hr))
    {
        bRet = SetEnvironmentVariable(TEXT("LANGID_DEC"), wszValue);
    }

    //
    // Set LANGID_HEX to hexadecimal value of the system default UI language
    //
    hr = StringCchPrintf(wszValue,
                         ARRAYSIZE(wszValue),
                         TEXT("%04x"),
                         g_lcidTarget);
    if (SUCCEEDED(hr))
    {
        bRet = SetEnvironmentVariable(TEXT("LANGID_HEX"), wszValue);
    }

    return bRet;
}



//-----------------------------------------------------------------------------
//
//  Function:   RemoveUnneededStrings
//
//  Synopsis:   Remove all unneeded sub strings from the value of
//              specified keys under [Remove] section
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer
//
//-----------------------------------------------------------------------------
HRESULT RemoveUnneededStrings(
    HINF    hTemplateFile   // Handle of template file
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    LONG    lLineCount;
    LONG    lLineIndex;

    lLineCount = SetupGetLineCount(hTemplateFile, TEXT("Remove"));
    for (lLineIndex = 0 ; lLineIndex < lLineCount ; lLineIndex++)
    {
        INFCONTEXT Context;

        bRet = SetupGetLineByIndex(hTemplateFile,
                                   TEXT("Remove"),
                                   lLineIndex,
                                   &Context);
        if (bRet)
        {
            WCHAR wszKey[64];
            WCHAR wszType[8];
            WCHAR wszValue[MAX_PATH];
            DWORD cchRequired;

            // Get type of unneeded string
            bRet = SetupGetStringField(&Context,
                                       0,
                                       wszKey,
                                       ARRAYSIZE(wszKey),
                                       &cchRequired) &&
                   SetupGetStringField(&Context,
                                       1,
                                       wszType,
                                       ARRAYSIZE(wszType),
                                       &cchRequired) &&
                   SetupGetStringField(&Context,
                                       2,
                                       wszValue,
                                       ARRAYSIZE(wszValue),
                                       &cchRequired);
            if (bRet)
            {
                if (CompareEngString(wszType, TEXT("STR")) == CSTR_EQUAL)
                {
                    // STR type
                    hr = RemoveUnneededString(wszKey, wszValue);
                }
                else if (CompareEngString(wszType, TEXT("EXP")) == CSTR_EQUAL)
                {
                    // EXP type
                    WCHAR wszUnneededString[MAX_PATH];

                    hr = GetExpString(wszUnneededString,
                                      ARRAYSIZE(wszUnneededString),
                                      wszValue);
                    if (SUCCEEDED(hr))
                    {
                        hr = RemoveUnneededString(wszKey, wszUnneededString);
                    }
                }
                else
                {
                    SetLastError(ERROR_INVALID_DATA);
                }
            }
        }
    }

    if (hr == E_FAIL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}



//-----------------------------------------------------------------------------
//
//  Function:   RemoveUnneededString
//
//  Synopsis:   Remove an unneeded sub string from the value of specified key
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer
//
//-----------------------------------------------------------------------------
HRESULT RemoveUnneededString(
    LPCWSTR lpKey,              // Key name
    LPCWSTR lpUnneededString    // Unneeded sub string
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    WCHAR   wszOldValue[MAX_PATH];
    WCHAR   wszNewValue[MAX_PATH];
    DWORD   cchCopied;

    cchCopied = GetPrivateProfileString(g_wszTargetLCIDSection,
                                        lpKey,
                                        TEXT(""),
                                        wszOldValue,
                                        ARRAYSIZE(wszOldValue),
                                        g_wszOutputFile);
    if (cchCopied > 0)
    {
        if (StrStrI(wszOldValue, lpUnneededString))
        {
            // Unneeded string found
            hr = StringSubstitute(wszNewValue,
                                  ARRAYSIZE(wszNewValue),
                                  wszOldValue,
                                  lpUnneededString,
                                  TEXT(""));
            if (SUCCEEDED(hr))
            {
                WCHAR wszQuoted[MAX_PATH];

                hr = StringCchPrintf(wszQuoted,
                                     ARRAYSIZE(wszQuoted),
                                     TEXT("\"%s\""),
                                     wszNewValue);
                if (SUCCEEDED(hr))
                {
                    bRet = WritePrivateProfileString(g_wszTargetLCIDSection,
                                                     lpKey,
                                                     wszQuoted,
                                                     g_wszOutputFile);
                    if (bRet)
                    {
                        hr = S_OK;
                    }
                    else
                    {
                        hr = HRESULT_FROM_WIN32(GetLastError());
                    }
                }
            }
        }
        else
        {
            // Unneeded string not found
            hr = S_FALSE;
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}


//-----------------------------------------------------------------------------
//
//  Function:   ExtractStrings
//
//  Synopsis:   Extract sub strings from the value of
//              specified keys under [Extract] section
//
//  Returns:    HRESULT
//
//  History:    08/01/2002 Geoffguo Created
//
//  Notes:      Caller need to free the allocated buffer
//
//-----------------------------------------------------------------------------
HRESULT ExtractStrings(
    HINF    hTemplateFile   // Handle of template file
)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    LONG    lLineCount;
    LONG    lLineIndex;
    WCHAR   wszKey[64];
    WCHAR   wszValueName[64];
    WCHAR   wszMatch[64];
    WCHAR   wszLeftDelimitor[8];
    WCHAR   wszRightDelimitor[8];
    DWORD   cchRequired;

    lLineCount = SetupGetLineCount(hTemplateFile, TEXT("Extract"));
    for (lLineIndex = 0 ; lLineIndex < lLineCount ; lLineIndex++)
    {
        INFCONTEXT Context;

        bRet = SetupGetLineByIndex(hTemplateFile,
                                   TEXT("Extract"),
                                   lLineIndex,
                                   &Context);
        if (bRet)
        {
            // Get type of unneeded string
            bRet = SetupGetStringField(&Context,
                                       0,
                                       wszValueName,
                                       ARRAYSIZE(wszValueName),
                                       &cchRequired) &&
                   SetupGetStringField(&Context,
                                       1,
                                       wszKey,
                                       ARRAYSIZE(wszKey),
                                       &cchRequired) &&
                   SetupGetStringField(&Context,
                                       2,
                                       wszMatch,
                                       ARRAYSIZE(wszMatch),
                                       &cchRequired) &&
                   SetupGetStringField(&Context,
                                       3,
                                       wszLeftDelimitor,
                                       ARRAYSIZE(wszLeftDelimitor),
                                       &cchRequired) &&
                   SetupGetStringField(&Context,
                                       4,
                                       wszRightDelimitor,
                                       ARRAYSIZE(wszRightDelimitor),
                                       &cchRequired);
            hr = ExtractString(wszKey,
                               wszValueName,
                               wszMatch,
                               wszLeftDelimitor,
                               wszRightDelimitor);
        }
    }

    bRet = WritePrivateProfileString(g_wszTargetLCIDSection,
                                     wszKey,
                                     NULL,
                                     g_wszOutputFile);
    if (bRet)
        hr = S_OK;

    if (hr == E_FAIL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}


//-----------------------------------------------------------------------------
//
//  Function:   RemoveUnneededString
//
//  Synopsis:   Remove an unneeded sub string from the value of specified key
//
//  Returns:    HRESULT
//
//  History:    02/07/2002 Rerkboos Created
//
//  Notes:      Caller need to free the allocated buffer
//
//-----------------------------------------------------------------------------
HRESULT ExtractString(
    LPCWSTR lpKey,              // Key name
    LPCWSTR lpValueName,
    LPCWSTR lpMatch,
    LPCWSTR lpLeftDelimitor,
    LPCWSTR lpRightDelimitor)
{
    HRESULT hr = E_FAIL;
    BOOL    bRet;
    LPWSTR  lpMatchedStr;
    WCHAR   wszOldValue[MAX_PATH*20];
    WCHAR   wszNewValue[MAX_PATH];
    DWORD   cchCopied;

    cchCopied = GetPrivateProfileString(g_wszTargetLCIDSection,
                                        lpKey,
                                        TEXT(""),
                                        wszOldValue,
                                        ARRAYSIZE(wszOldValue),
                                        g_wszOutputFile);
    if (cchCopied > 0)
    {
        if (lpMatchedStr = StrStrI(wszOldValue, lpMatch))
        {
            hr = ExtractSubString(wszNewValue,
                                  ARRAYSIZE(wszNewValue),
                                  lpMatchedStr,
                                  lpLeftDelimitor,
                                  lpRightDelimitor);
            if (SUCCEEDED(hr))
            {
                WCHAR wszQuoted[MAX_PATH];

                hr = StringCchPrintf(wszQuoted,
                                     ARRAYSIZE(wszQuoted),
                                     TEXT("\"%s\""),
                                     wszNewValue);
                if (SUCCEEDED(hr))
                {
                    bRet = WritePrivateProfileString(g_wszTargetLCIDSection,
                                                     lpValueName,
                                                     wszQuoted,
                                                     g_wszOutputFile);
                    if (bRet)
                    {
                        hr = S_OK;
                    }
                    else
                    {
                        hr = HRESULT_FROM_WIN32(GetLastError());
                    }
                }
            }
        }
        else
        {
            // Unneeded string not found
            hr = S_FALSE;
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}

HRESULT GetExpString(
    LPWSTR  lpUnneededString,       // Returned buffer
    DWORD   cchUnneededString,      // Size of buffer in WCHAR
    LPCWSTR lpString                // String with %Key%
)
{
    HRESULT hr = E_FAIL;
    LPCWSTR lpBegin;
    LPCWSTR lpEnd;

    if (lpUnneededString == NULL || lpString == NULL)
    {
        return E_INVALIDARG;
    }

    //
    // Find the key name from Source string
    //
    lpBegin = StrChr(lpString, TEXT('%'));
    if (lpBegin)
    {
        // Begin of key name    
        lpBegin++;

        // End of key name
        lpEnd = StrChr(lpBegin, TEXT('%'));
        if (lpEnd)
        {
            DWORD cchLen;
            WCHAR wszKey[MAX_PATH];

            cchLen = (DWORD) (lpEnd - lpBegin);
            if (cchLen >= cchUnneededString)
            {
                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            }
            else
            {
                // Got the key name, needs one more char for a '\0'
                if (lstrcpyn(wszKey, lpBegin, cchLen + 1))
                {
                    WCHAR wszOldSubStr[MAX_PATH];
                    WCHAR wszNewSubStr[MAX_PATH];
                    DWORD cchCopied;

                    // Get the value of key name from output file
                    cchCopied = GetPrivateProfileString(g_wszTargetLCIDSection,
                                                        wszKey,
                                                        TEXT(""),
                                                        wszNewSubStr,
                                                        ARRAYSIZE(wszNewSubStr),
                                                        g_wszOutputFile);
                    if (cchCopied > 0)
                    {
                        hr = StringCchPrintf(wszOldSubStr,
                                             ARRAYSIZE(wszOldSubStr),
                                             TEXT("%%%s%%"),
                                             wszKey);
                        if (SUCCEEDED(hr))
                        {
                            // Substitute %Key% with the new value
                            hr = StringSubstitute(lpUnneededString,
                                                  cchUnneededString,
                                                  lpString,
                                                  wszOldSubStr,
                                                  wszNewSubStr);
                        }
                    }
                }
            }
        }
    }

    if (hr == E_FAIL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}