//---------------------------------------------------------------------------
//  PackThem.cpp - packs up theme files into a theme DLL 
//---------------------------------------------------------------------------
#include "stdafx.h"
#include <uxthemep.h>
#include <utils.h>
#include "SimpStr.h"
#include "Scanner.h"
#include "shlwapip.h"
#include "parser.h"
#include "TmSchema.h"
#include "signing.h"
#include "localsign.h"
#include "ThemeLdr.h"
#include "TmUtils.h"
#include "StringTable.h"

HRESULT ParseTheme(LPCWSTR pszThemeName);

//---------------------------------------------------------------------------
struct FILEINFO
{
    CWideString wsName;
    BOOL fIniFile;
};
//---------------------------------------------------------------------------
#define MAX_COLORS   50
#define MAX_SIZES    20
#define TEMP_FILENAME_BASE    L"$temp$"
#define kRESFILECHAR L'$'
//---------------------------------------------------------------------------
enum PACKFILETYPE
{
    PACK_INIFILE,
    PACK_IMAGEFILE,
    PACK_NTLFILE,
    PACK_OTHER
};
//---------------------------------------------------------------------------
CSimpleArray<FILEINFO> FileInfo;

CSimpleArray<CWideString> ColorSchemes;
CSimpleArray<CWideString> ColorDisplays;
CSimpleArray<CWideString> ColorToolTips;

CSimpleArray<int> MinDepths;

CSimpleArray<CWideString> SizeNames;
CSimpleArray<CWideString> SizeDisplays;
CSimpleArray<CWideString> SizeToolTips;

typedef struct
{
    CWideString sName;
    int iFirstIndex;
    UINT cItems;
} sSubstTable;
CSimpleArray<sSubstTable> SubstNames;
CSimpleArray<CWideString> SubstIds;
CSimpleArray<CWideString> SubstValues;

CSimpleArray<CWideString> BaseResFileNames;
CSimpleArray<CWideString> ResFileNames;
CSimpleArray<CWideString> OrigFileNames;

CSimpleArray<CWideString> PropValuePairs;
//---------------------------------------------------------------------------
SHORT Combos[MAX_SIZES][MAX_COLORS];

int g_iMaxColor;
int g_iMaxSize;
int g_LineCount = 0;
int iTempBitmapNum = 1;

BOOL g_fQuietRun = FALSE;             // don't show needless output
BOOL g_fKeepTempFiles = FALSE;
FILE *ConsoleFile = NULL;

WCHAR g_szInputDir[_MAX_PATH+1];
WCHAR g_szTempPath[_MAX_PATH+1];
WCHAR g_szBaseIniName[_MAX_PATH+1];
WCHAR g_szCurrentClass[_MAX_PATH+1];

//---------------------------------------------------------------------------
#define DOCPROPCNT (1+TMT_LAST_RCSTRING_NAME - TMT_FIRST_RCSTRING_NAME)

CWideString DocProperties[DOCPROPCNT];
//---------------------------------------------------------------------------
HRESULT ReportError(HRESULT hr, LPWSTR pszDefaultMsg) 
{
    WCHAR szErrorMsg[2*_MAX_PATH+1];
    PARSE_ERROR_INFO Info = {sizeof(Info)};
    
    BOOL fGotMsg = FALSE;

    if (THEME_PARSING_ERROR(hr))
    {
        if (SUCCEEDED(_GetThemeParseErrorInfo(&Info)))
        {
            lstrcpy(szErrorMsg, Info.szMsg);
            fGotMsg = TRUE;
        }
    }

    if (! fGotMsg)
    {
        lstrcpy(szErrorMsg, pszDefaultMsg);
    }

    if (*Info.szFileName)        // input file error
    {
        fwprintf(ConsoleFile, L"%s(%d): error - %s\n", 
            Info.szFileName, Info.iLineNum, szErrorMsg);
        fwprintf(ConsoleFile, L"%s\n", Info.szSourceLine);
    }
    else                    // general error 
    {
        fwprintf(ConsoleFile, L"%s(): error - %s\n", 
            g_szInputDir, szErrorMsg);
    }

    SET_LAST_ERROR(hr);
    return hr;
}
//---------------------------------------------------------------------------
void MakeResName(LPCWSTR pszName, LPWSTR pszResName, bool bUseClassName = false)
{
    WCHAR szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szBaseName[_MAX_FNAME], szExt[_MAX_EXT];

    //---- isolate base name (no path) ----
    _wsplitpath(pszName, szDrive, szDir, szBaseName, szExt);

    if (szBaseName[0] == kRESFILECHAR) // Don't put $ in resource names
    {
        wcscpy(szBaseName, szBaseName + 1);
    }

    //---- replace the "." with a '_' ----
    //---- if a file section name without .ini, append _INI so that the extracted files has .ini extension
    if (*szExt)
    {
        wsprintf(pszResName, L"%s%s_%s", bUseClassName ? g_szCurrentClass : L"", szBaseName, szExt+1);
    } else
    {
        wsprintf(pszResName, L"%s%s_INI", bUseClassName ? g_szCurrentClass : L"", szBaseName);
    }

    //---- all uppercase ----
    CharUpperBuff(pszResName, lstrlen(pszResName));

    //---- replace any spaces with underscores ----
    WCHAR *q = pszResName;       
    while (*q)
    {
        if (*q == ' ')
            *q = '_';
        q++;
    }
}
//---------------------------------------------------------------------------
HRESULT BuildThemeDll(LPCWSTR pszRcName, LPCWSTR pszResName, LPCWSTR pszDllName)
{
    if (! g_fQuietRun)
        fwprintf(ConsoleFile, L"compiling resources\n");

    HRESULT hr = SyncCmdLineRun(L"rc.exe", pszRcName);
    if (FAILED(hr))
        return ReportError(hr, L"Error during resource compiliation");

    //---- run LINK to create the DLL ----
    WCHAR params[2*_MAX_PATH+1];

    if (! g_fQuietRun)
        fwprintf(ConsoleFile, L"linking theme dll\n");

    wsprintf(params, L"/out:%s /machine:ix86 /dll /noentry %s", pszDllName, pszResName);
    hr = SyncCmdLineRun(L"link.exe", params);
    if (FAILED(hr))
        return ReportError(hr, L"Error during DLL linking");

    return S_OK;
}
//---------------------------------------------------------------------------
void OutputDashLine(FILE *outfile)
{
    fwprintf(outfile, L"//----------------------------------------------------------------------\n");
}
//---------------------------------------------------------------------------
inline void ValueLine(FILE *outfile, LPCWSTR pszName, LPCWSTR pszValue)
{
    fwprintf(outfile, L"            VALUE \"%s\", \"%s\\0\"\n", pszName, pszValue);
}
//---------------------------------------------------------------------------
HRESULT OutputVersionInfo(FILE *outfile, LPCWSTR pszFileName, LPCWSTR pszBaseName)
{
    fwprintf(outfile, L"1 PACKTHEM_VERSION\n");
    fwprintf(outfile, L"BEGIN\n");
    fwprintf(outfile, L"    %d\n", PACKTHEM_VERSION);
    fwprintf(outfile, L"END\n");
    OutputDashLine(outfile);

    WCHAR *Company = L"Microsoft";
    WCHAR *Copyright = L"Copyright � 2000";
    WCHAR szDescription[2*_MAX_PATH+1];
    
    wsprintf(szDescription, L"%s Theme for Windows", pszBaseName);

    fwprintf(outfile, L"1 VERSIONINFO\n");
    fwprintf(outfile, L"    FILEVERSION 1,0,0,1\n");
    fwprintf(outfile, L"    PRODUCTVERSION 1,0,0,1\n");
    fwprintf(outfile, L"    FILEFLAGSMASK 0x3fL\n");
    fwprintf(outfile, L"    FILEFLAGS 0x0L\n");
    fwprintf(outfile, L"    FILEOS 0x40004L\n");
    fwprintf(outfile, L"    FILETYPE 0x1L\n");
    fwprintf(outfile, L"    FILESUBTYPE 0x0L\n");

    fwprintf(outfile, L"BEGIN\n");
    fwprintf(outfile, L"    BLOCK \"StringFileInfo\"\n");
    fwprintf(outfile, L"    BEGIN\n");
    fwprintf(outfile, L"        BLOCK \"040904b0\"\n");

    fwprintf(outfile, L"        BEGIN\n");
    
    ValueLine(outfile, L"Comments", L"");
    ValueLine(outfile, L"CompanyName", Company);
    ValueLine(outfile, L"FileDescription", szDescription);
    ValueLine(outfile, L"FileVersion", L"1, 0, 0, 1");
    ValueLine(outfile, L"InternalName", pszFileName);
    ValueLine(outfile, L"LegalCopyright", Copyright);
    ValueLine(outfile, L"LegalTrademarks", L"");
    ValueLine(outfile, L"OriginalFilename", pszFileName);
    ValueLine(outfile, L"PrivateBuild", L"");
    ValueLine(outfile, L"ProductName", szDescription);
    ValueLine(outfile, L"ProductVersion", L"1, 0, 0, 1");
    ValueLine(outfile, L"SpecialBuild", L"");

    fwprintf(outfile, L"        END\n");
    fwprintf(outfile, L"    END\n");
    fwprintf(outfile, L"    BLOCK \"VarFileInfo\"\n");
    fwprintf(outfile, L"    BEGIN\n");
    fwprintf(outfile, L"        VALUE \"Translation\", 0x409, 1200\n");
    fwprintf(outfile, L"    END\n");
    fwprintf(outfile, L"END\n");

    OutputDashLine(outfile);
    return S_OK;
}
//---------------------------------------------------------------------------
HRESULT RemoveTempFiles(LPCWSTR szRcName, LPCWSTR szResName)
{
    DeleteFile(szRcName);
    DeleteFile(szResName);

    //---- find & delete all temp files in temp directory ----
    HANDLE hFile = NULL;
    BOOL   bFile = TRUE;
    WIN32_FIND_DATA wfd;
    WCHAR szPattern[_MAX_PATH+1];
    WCHAR szTempName[_MAX_PATH+1];

    wsprintf(szPattern, L"%s\\%s*.*", g_szTempPath, TEMP_FILENAME_BASE); 

    for( hFile = FindFirstFile( szPattern, &wfd ); hFile != INVALID_HANDLE_VALUE && bFile;
         bFile = FindNextFile( hFile, &wfd ) )
    {
        wsprintf(szTempName, L"%s\\%s", g_szTempPath, wfd.cFileName);
    
        DeleteFile(szTempName);
    }

    if (hFile)      
    {
        FindClose( hFile );
    }

    // Remove files generated by the substitution tables
    for (int i = 0; i < SubstNames.GetSize(); i++)
    {
        wsprintf(szTempName, L"%s\\$%s.ini", g_szTempPath, SubstNames[i].sName);
        DeleteFile(szTempName);
    }

    return S_OK;
}
//---------------------------------------------------------------------------
int GetSubstTableIndex(LPCWSTR pszTableName)
{
    // Search for an existing subst table
    for (int i = 0; i < SubstNames.GetSize(); i++)
    {
        if (0 == AsciiStrCmpI(SubstNames[i].sName, pszTableName))
            return i;
    }
    return -1;
}
//---------------------------------------------------------------------------
HRESULT GetSubstValue(LPCWSTR pszIniFileName, LPCWSTR pszName, LPWSTR pszResult)
{
    UINT cTablesCount = SubstNames.GetSize();

    if (pszIniFileName && pszIniFileName[0] == kRESFILECHAR)
    {
        pszIniFileName++;
    }

    for (UINT i = 0; i < cTablesCount; i++)      
    {
        if (0 == AsciiStrCmpI(SubstNames[i].sName, pszIniFileName))
        {
            for (UINT j = SubstNames[i].iFirstIndex; j < SubstNames[i].iFirstIndex + SubstNames[i].cItems; j++) 
            {
                if (0 == AsciiStrCmpI(SubstIds[j], pszName))
                {
                    lstrcpy(pszResult, SubstValues[j]);
                    return S_OK;
                }
            }
        }
    }

    return MakeError32(E_FAIL);      // unknown sizename
}
//---------------------------------------------------------------------------
LPWSTR FindSymbolToken(LPWSTR pSrc, int nLen)
{
    LPWSTR p = wcschr(pSrc, INI_MACRO_SYMBOL);

    // Skip single #s
    while (p != NULL && (p - pSrc < nLen - 1) && *(p + 1) != INI_MACRO_SYMBOL)
    {
        p = wcschr(p + 1, INI_MACRO_SYMBOL);
    }
    return p;
}

LPWSTR ReallocTextBuffer(LPWSTR pSrc, UINT *pnLen)
{
    *pnLen *= 2; // Double the size each time

    LPWSTR pszNew = (LPWSTR) LocalReAlloc(pSrc, *pnLen * sizeof(WCHAR), 0);
    if (!pszNew)
    {
        LocalFree(pSrc);
        return NULL;
    }
    return pszNew;
}

LPWSTR SubstituteSymbols(LPWSTR szTableName, LPWSTR pszText)
{
    UINT nLen = wcslen(pszText);
    UINT nNewLen = nLen * 2; // Reserve some additional space
    UINT iSymbol;
    UINT nBlockSize;
    LPWSTR pszNew = (LPWSTR) LocalAlloc(0, nNewLen * sizeof(WCHAR));
    LPWSTR pSrc = FindSymbolToken(pszText, nLen);
    LPWSTR pOldSrc = pszText;
    LPWSTR pDest = pszNew;
    WCHAR szSymbol[MAX_INPUT_LINE+1];
    HRESULT hr;

    if (!pszNew)
        return NULL;

    while (pSrc != NULL)
    {
        nBlockSize = UINT(pSrc - pOldSrc); 
        // Check for enough space after substitution
        if (pDest + nBlockSize >= pszNew + nNewLen &&
            NULL == (pszNew = ReallocTextBuffer(pszNew, &nNewLen)))
        {
            return NULL;
        }

        // Copy from the last # to the new one
        wcsncpy(pDest, pOldSrc, nBlockSize);
        pDest += nBlockSize;
        pSrc += 2; // Skip the ##

        // Copy the symbol name
        iSymbol = 0;
        while (IsCharAlphaNumericW(*pSrc) || (*pSrc == '_') || (*pSrc == '-'))
        {
            szSymbol[iSymbol++] = *pSrc++;
        }
        szSymbol[iSymbol] = 0;

        // Get the symbol value
        hr = GetSubstValue(szTableName, szSymbol, szSymbol);
        if (FAILED(hr))
        {
            // There's a problem, abort and return the buffer untouched
            LocalFree(pszNew);

            WCHAR szErrorText[MAX_INPUT_LINE + 1];
            wsprintf(szErrorText, L"Substitution symbol not found: %s", szSymbol);

            ReportError(hr, szErrorText);
            return NULL;
        }

        // Make sure we have enough room for one symbol
        if (pDest + MAX_INPUT_LINE + 1 >= pszNew + nNewLen &&
            NULL == (pszNew = ReallocTextBuffer(pszNew, &nNewLen)))
        {
            return NULL;
        }

        // Copy the symbol value to the new text
        iSymbol = 0;
        while (szSymbol[iSymbol] != 0)
        {
            *pDest++ = szSymbol[iSymbol++];
        }

        // Advance to the next iteration
        pOldSrc = pSrc;
        pSrc = FindSymbolToken(pSrc, nLen - UINT(pSrc - pszText));
    }

    if (pDest == pszNew)
    {
        // We did nothing, return NULL
        LocalFree(pszNew);
        return NULL;
    }

    // Copy the remainder text (after the last #)
    if (pDest + wcslen(pOldSrc) >= pszNew + nNewLen &&
        NULL == (pszNew = ReallocTextBuffer(pszNew, &nNewLen)))
    {
        return NULL;
    }
    wcscpy(pDest, pOldSrc);

    return pszNew;
}

//---------------------------------------------------------------------------
HRESULT OutputResourceLine(LPCWSTR pszFilename, FILE *outfile, PACKFILETYPE ePackFileType)
{
    HRESULT hr;

    //---- did we already process this filename? ----
    UINT cNames = FileInfo.GetSize();
    for (UINT c=0; c < cNames; c++)
    {
        if (lstrcmpi(FileInfo[c].wsName, pszFilename)==0)
            return S_OK;
    }

    WCHAR szTempName[_MAX_PATH+1];
    WCHAR szResName[_MAX_PATH];
    WCHAR szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szBaseName[_MAX_FNAME], szExt[_MAX_EXT];
    WCHAR *filetype;
    LPWSTR pszText;
    BOOL fWasAnsi;
    BOOL fFileChecked = FALSE;
    
    WCHAR szOrigName[_MAX_PATH];
    lstrcpy(szOrigName, pszFilename);

    _wsplitpath(pszFilename, szDrive, szDir, szBaseName, szExt);

    if (ePackFileType == PACK_INIFILE)
    {
        //---- translate to UNICODE, if needed ----
        hr = AllocateTextFile(pszFilename, &pszText, &fWasAnsi);
        if (FAILED(hr))
            return hr;
    
        if (szBaseName[0] == kRESFILECHAR)
        {
            wcscpy(szBaseName, szBaseName + 1);
        }

        // If this an INI file with a subst table, process the substitution
        for (int i = 0; i < SubstNames.GetSize(); i++)
        {
            if (0 == AsciiStrCmpI(SubstNames[i].sName, szBaseName))
            {
                SetLastError(0);
                LPWSTR pszNewText = SubstituteSymbols(szBaseName, pszText);
                if (pszNewText != NULL)
                {
                    LPWSTR pszTemp = pszText;

                    pszText = pszNewText;
                    LocalFree(pszTemp);
                }
                hr = GetLastError();
                if (SUCCEEDED(hr))
                {
                    HRESULT hr = TextToFile(pszFilename, pszText); // Local hr, ignore failure later

                    if (SUCCEEDED(hr))
                    {
                        fWasAnsi = FALSE; // We don't need another temp file
                    }
                }
                break;
            }
        }

        if (SUCCEEDED(hr) && fWasAnsi)       // write out as temp file
        {
            DWORD len = lstrlen(g_szTempPath);
            
            if ((len) && (g_szTempPath[len-1] == '\\'))
                wsprintf(szTempName, L"%s%s%d%s", g_szTempPath, TEMP_FILENAME_BASE, iTempBitmapNum++, L".uni");
            else
                wsprintf(szTempName, L"%s\\%s%d%s", g_szTempPath, TEMP_FILENAME_BASE, iTempBitmapNum++, L".uni");

            hr = TextToFile(szTempName, pszText);
            pszFilename = szTempName;       // use this name in .rc file
        }

        LocalFree(pszText);

        if (FAILED(hr)) 
            return hr;

        fFileChecked = TRUE;
    }
    
    if (! fFileChecked)
    {
        //---- ensure the file is accessible ----
        if (_waccess(pszFilename, 0) != 0)
        {
            fwprintf(ConsoleFile, L"Error - cannot access file: %s\n", pszFilename);

            return MakeError32(E_FAIL);          // cannot access (open) file
        }
    }

    bool bUseClassName = false;

    if (ePackFileType == PACK_IMAGEFILE)
    {
        filetype = L"BITMAP";
        bUseClassName = true;
    }
    else if (ePackFileType == PACK_NTLFILE)
    {
        filetype = L"NTL";
    }
    else if (AsciiStrCmpI(szExt, L".ini")==0)
    {
        filetype = L"TEXTFILE";
    }
    else if (AsciiStrCmpI(szExt, L".wav")==0)
    {
        filetype = L"WAVE";
        bUseClassName = true;
    }
    else
    {
        filetype = L"CUSTOM";
        bUseClassName = true;
    }
     
    MakeResName(szOrigName, szResName, bUseClassName);
    
    //---- replace all single backslashes with double ones ----
    WCHAR DblName[_MAX_PATH+1];
    WCHAR *d = DblName;
    LPCWSTR p = pszFilename;
    while (*p)
    {
        if (*p == '\\')
            *d++ = '\\';

        *d++ = *p++;
    }
    *d = 0;

    //---- output the line to the .rc file ----
    fwprintf(outfile, L"%-30s \t %s DISCARDABLE \"%s\"\n", szResName, filetype, DblName);

    FILEINFO fileinfo;
    fileinfo.wsName = pszFilename;
    fileinfo.fIniFile = (ePackFileType == PACK_INIFILE);

    FileInfo.Add(fileinfo);

    g_LineCount++;

    return S_OK;
}
//---------------------------------------------------------------------------
void ClearCombos()
{
    for (int s=0; s < MAX_SIZES; s++)
    {
        for (int c=0; c < MAX_COLORS; c++)
        {
            Combos[s][c] = -1;          // -1 means no file supports this combo
        }
    }

    g_iMaxColor = -1;
    g_iMaxSize = -1;
}
//---------------------------------------------------------------------------
HRESULT OutputCombos(FILE *outfile)
{
    if ((g_iMaxColor < 0) || (g_iMaxSize < 0))      // no combos found
        return ReportError(E_FAIL, L"No size/color combinations found");

    fwprintf(outfile, L"COMBO COMBODATA\n");
    fwprintf(outfile, L"BEGIN\n");
    fwprintf(outfile, L"    %d, %d    // cColors, cSizes\n", g_iMaxColor+1, g_iMaxSize+1);

    for (int s=0; s <= g_iMaxSize; s++)
    {
        for (int c=0; c <= g_iMaxColor; c++)
        {
            fwprintf(outfile, L"    %d, ", Combos[s][c]);
        }

        fwprintf(outfile, L"   // size=%d row\n", s);
    }

    fwprintf(outfile, L"END\n");
    OutputDashLine(outfile);

    return S_OK;
}
//---------------------------------------------------------------------------
HRESULT GetFileIndex(LPCWSTR pszName, int *piIndex)
{
    int cCount = ResFileNames.GetSize();

    for (int i=0; i < cCount; i++)      
    {
        if (lstrcmpi(ResFileNames[i], pszName)==0)
        {
            *piIndex = i;
            return S_OK;
        }
    }

    return MakeError32(E_FAIL);      // unknown filename
}
//---------------------------------------------------------------------------
HRESULT GetColorIndex(LPCWSTR pszName, int *piIndex)
{
    int cCount = ColorSchemes.GetSize();

    for (int i=0; i < cCount; i++)      
    {
        if (lstrcmpi(ColorSchemes[i], pszName)==0)
        {
            *piIndex = i;
            return S_OK;
        }
    }

    return MakeError32(E_FAIL);      // unknown colorname
}
//---------------------------------------------------------------------------
HRESULT GetSizeIndex(LPCWSTR pszName, int *piIndex)
{
    int cCount = SizeNames.GetSize();

    for (int i=0; i < cCount; i++)      
    {
        if (lstrcmpi(SizeNames[i], pszName)==0)
        {
            *piIndex = i;
            return S_OK;
        }
    }

    return MakeError32(E_FAIL);      // unknown sizename
}
//---------------------------------------------------------------------------
HRESULT ApplyCombos(LPCWSTR pszResFileName, LPCWSTR pszColors, LPCWSTR pszSizes)
{
    //---- get index of pszResFileName ----
    int iFileNum;
    HRESULT hr = GetFileIndex(pszResFileName, &iFileNum);
    if (FAILED(hr))
        return hr;
    
    //---- parse colors in pszColors ----
    CScanner scan(pszColors);
    WCHAR szName[_MAX_PATH+1];
    int iColors[MAX_COLORS];
    int cColors = 0;

    while (1)
    {
        if (! scan.GetId(szName))
            return MakeError32(E_FAIL);      // bad color list

        //---- get index of szName ----
        int index;
        HRESULT hr = GetColorIndex(szName, &index);
        if (FAILED(hr))
            return hr;

        if (cColors == MAX_COLORS)
            return MakeError32(E_FAIL);      // too many colors specified

        iColors[cColors++] = index;

        if (scan.EndOfLine())
            break;

        if (! scan.GetChar(L','))
            return MakeError32(E_FAIL);      // names must be comma separated
    }


    //---- parse sizes in pszSizes ----
    scan.AttachLine(pszSizes);
    int iSizes[MAX_SIZES];
    int cSizes = 0;

    while (1)
    {
        if (! scan.GetId(szName))
            return MakeError32(E_FAIL);      // bad color list

        //---- get index of szName ----
        int index;
        HRESULT hr = GetSizeIndex(szName, &index);
        if (FAILED(hr))
            return hr;

        if (cSizes == MAX_SIZES)
            return MakeError32(E_FAIL);      // too many sizes specified

        iSizes[cSizes++] = index;

        if (scan.EndOfLine())
            break;

        if (! scan.GetChar(L','))
            return MakeError32(E_FAIL);      // names must be comma separated
    }

    //---- now form all combos of specified colors & sizes ----
    for (int c=0; c < cColors; c++)     // for each color
    {
        int color = iColors[c];

        for (int s=0; s < cSizes; s++)      // for each size
        {
            int size = iSizes[s];

            Combos[size][color] = (SHORT)iFileNum;

            //---- update our max's ----
            if (size > g_iMaxSize)
                g_iMaxSize = size;

            if (color > g_iMaxColor)
                g_iMaxColor = color;
        }
    }

    return S_OK;
}
//---------------------------------------------------------------------------
void WriteProperty(CSimpleArray<CWideString> &csa, LPCWSTR pszSection, LPCWSTR pszPropName,
    LPCWSTR pszValue)
{
    WCHAR buff[MAX_PATH*2];

    wsprintf(buff, L"%s@[%s]%s=%s", g_szBaseIniName, pszSection, pszPropName, pszValue);

    csa.Add(CWideString(buff));
}
//---------------------------------------------------------------------------
BOOL FnCallBack(enum THEMECALLBACK tcbType, LPCWSTR pszName, LPCWSTR pszName2, 
     LPCWSTR pszName3, int iIndex, LPARAM lParam)
{
    HRESULT hr = S_OK;
    int nDefaultDepth = 15;

    switch (tcbType)
    {
        case TCB_FILENAME:
            WCHAR szFullName[_MAX_PATH+1];

            hr = AddPathIfNeeded(pszName, g_szInputDir, szFullName, ARRAYSIZE(szFullName));
            if (FAILED(hr))
            {
                SET_LAST_ERROR(hr);
                return FALSE;
            }

            if ((iIndex == TMT_IMAGEFILE) || (iIndex == TMT_GLYPHIMAGEFILE) || (iIndex == TMT_STOCKIMAGEFILE))
                hr = OutputResourceLine(szFullName, (FILE *)lParam, PACK_IMAGEFILE);
            else if ((iIndex >= TMT_IMAGEFILE1) && (iIndex <= TMT_IMAGEFILE5))
                hr = OutputResourceLine(szFullName, (FILE *)lParam, PACK_IMAGEFILE);
#if 0           // not yet implemented
            else if (iIndex == TMT_NTLFILE)
                hr = OutputResourceLine(szFullName, (FILE *)lParam, PACK_NTLFILE);
#endif
            else
                hr = MakeError32(E_FAIL);        // unexpected type

            if (FAILED(hr))
            {
                SET_LAST_ERROR(hr);
                return FALSE;
            }
            break;

        case TCB_FONT:
            WriteProperty(PropValuePairs, pszName2, pszName3, pszName);
            break;

        case TCB_MIRRORIMAGE:
            {
                LPCWSTR p;
            
                if (lParam)
                    p = L"1";
                else
                    p = L"0";
    
                WriteProperty(PropValuePairs, pszName2, pszName3, p);
            }
            break;

        case TCB_LOCALIZABLE_RECT:
            {
                WCHAR szBuff[100];
                RECT *prc = (RECT *)lParam;

                wsprintf(szBuff, L"%d, %d, %d, %d", prc->left, prc->top, prc->right, prc->bottom);

                WriteProperty(PropValuePairs, pszName2, pszName3, szBuff);
            }
            break;

        case TCB_COLORSCHEME:
            ColorSchemes.Add(CWideString(pszName));
            ColorDisplays.Add(CWideString(pszName2));
            ColorToolTips.Add(CWideString(pszName3));
            break;

        case TCB_SIZENAME:
            SizeNames.Add(CWideString(pszName));
            SizeDisplays.Add(CWideString(pszName2));
            SizeToolTips.Add(CWideString(pszName3));
            break;

        case TCB_SUBSTTABLE:
        {
            int iTableIndex = GetSubstTableIndex(pszName);

            if (iTableIndex == -1) // Not found, add one
            {
                sSubstTable s;
                s.sName = pszName;
                s.iFirstIndex = -1;
                s.cItems = 0;

                SubstNames.Add(s);
                iTableIndex = SubstNames.GetSize() - 1;
            }
            if (0 == AsciiStrCmpI(pszName2, SUBST_TABLE_INCLUDE))
            {
                int iSecondTableIndex = GetSubstTableIndex(pszName3);

                if (iSecondTableIndex == -1)
                {
                    SET_LAST_ERROR(MakeError32(ERROR_NOT_FOUND));
                    return FALSE;
                }
                else
                {
                    // Copy the symbols in the new table
                    for (UINT iSymbol = SubstNames[iSecondTableIndex].iFirstIndex; 
                        iSymbol < SubstNames[iSecondTableIndex].iFirstIndex + SubstNames[iSecondTableIndex].cItems;
                        iSymbol++)
                    {
                        if (SubstNames[iTableIndex].iFirstIndex == -1)
                        {
                            SubstNames[iTableIndex].iFirstIndex = SubstValues.GetSize();
                        }
                        SubstNames[iTableIndex].cItems++;
                        SubstIds.Add(CWideString(SubstIds[iSymbol]));
                        SubstValues.Add(CWideString(SubstValues[iSymbol]));
                    }
                }
            } 
            else if (pszName2 != NULL && pszName3 != NULL)
            {
                // If the table was pre-created, update it
                if (SubstNames[iTableIndex].iFirstIndex == -1)
                {
                    SubstNames[iTableIndex].iFirstIndex = SubstValues.GetSize();
                }
                SubstNames[iTableIndex].cItems++;
                SubstIds.Add(CWideString(pszName2));
                SubstValues.Add(CWideString(pszName3));
            }
            break;
        }

        case TCB_NEEDSUBST:
            GetSubstValue(pszName, pszName2, (LPWSTR) pszName3);
            break;

        case TCB_CDFILENAME:
            WCHAR szResName[_MAX_PATH+1];
            MakeResName(pszName, szResName);

            ResFileNames.Add(CWideString(szResName));
            MinDepths.Add(nDefaultDepth);
            BaseResFileNames.Add(CWideString(pszName));
            OrigFileNames.Add(CWideString(pszName2));
            break;

        case TCB_CDFILECOMBO:
            MakeResName(pszName, szResName);

            hr = ApplyCombos(szResName, pszName2, pszName3);
            if (FAILED(hr))
            {
                SET_LAST_ERROR(hr);
                return FALSE;
            }
            break;
 
        case TCB_DOCPROPERTY:
            if ((iIndex < 0) || (iIndex >= ARRAYSIZE(DocProperties)))
                return FALSE;
            DocProperties[iIndex] = pszName;
            break;

        case TCB_MINCOLORDEPTH:
            MakeResName(pszName, szResName);

            int iRes;
            
            if (SUCCEEDED(GetFileIndex(szResName, &iRes)))
            {
                MinDepths[iRes] = iIndex;
            }
            break;
    }

    SET_LAST_ERROR(hr);
    return TRUE;
}
//---------------------------------------------------------------------------
HRESULT OpenOutFile(FILE *&outfile, LPCWSTR pszRcName, LPCWSTR pszBaseName)
{
    if (! outfile)          // first time thru
    {
        //---- open out file ----
        outfile = _wfopen(pszRcName, L"wt");
        if (! outfile)
        {
            fwprintf(ConsoleFile, L"Error - cannot open file: %s\n", pszRcName);

            return MakeError32(E_FAIL);
        }

        OutputDashLine(outfile);
        fwprintf(outfile, L"// %s.rc - used to build the %s theme DLL\n", pszBaseName, pszBaseName);
        OutputDashLine(outfile);
    }

    return S_OK;
}
//---------------------------------------------------------------------------
HRESULT ProcessContainerFile(LPCWSTR pszDir, LPCWSTR pszInputName, FILE *&outfile)
{
    HRESULT hr;
    
    //---- output .ini filename as a resource ----
    WCHAR fullname[_MAX_PATH+1];
    wsprintf(fullname, L"%s\\%s", pszDir, pszInputName);

    if (! g_fQuietRun)
        fwprintf(ConsoleFile, L"processing container file: %s\n", fullname);

    hr = OutputResourceLine(fullname, outfile, PACK_INIFILE);
    if (FAILED(hr))
    {
        ReportError(hr, L"Error reading themes.ini file");
        goto exit;
    }

    OutputDashLine(outfile);
    int oldcnt = g_LineCount;

    //---- scan the themes.ini files for color, size, & file sections; write 'em to the .rc file ----
    DWORD flags = PTF_CONTAINER_PARSE | PTF_CALLBACK_COLORSECTION | PTF_CALLBACK_SIZESECTION
        | PTF_CALLBACK_FILESECTION | PTF_CALLBACK_DOCPROPERTIES | PTF_CALLBACK_SUBSTTABLE;


    WCHAR szErrMsg[4096];

    hr = _ParseThemeIniFile(fullname, flags, FnCallBack, (LPARAM)outfile);
    if (FAILED(hr))
    {
        ReportError(hr, L"Error parsing themes.ini file");
        goto exit;
    }

    if (g_LineCount > oldcnt)
        OutputDashLine(outfile);

exit:
    return hr;
}
//---------------------------------------------------------------------------
HRESULT ProcessClassDataFile(LPCWSTR pszFileName, FILE *&outfile, LPCWSTR pszResFileName, LPCWSTR pszInputDir)
{
    HRESULT hr;
    WCHAR szFullName[MAX_PATH];
    WCHAR szTempName[MAX_PATH];
    LPWSTR pBS = NULL;

    hr = hr_lstrcpy(g_szCurrentClass, pszFileName, ARRAYSIZE(g_szCurrentClass));        // make avail to everybody
    if (SUCCEEDED(hr))
    {
        pBS = wcschr(g_szCurrentClass, L'\\');

        if (pBS)
        {
            *pBS = L'_';
            *(pBS + 1) = L'\0';
        }
    }
    if (pBS == NULL) // If there's no '\', don't use the class name
    {
        g_szCurrentClass[0] = 0;
    }

    hr = hr_lstrcpy(g_szInputDir, pszInputDir, ARRAYSIZE(g_szInputDir));        // make avail to everybody
    if (FAILED(hr))
        goto exit;

    hr = AddPathIfNeeded(pszFileName, pszInputDir, szFullName, ARRAYSIZE(szFullName));
    if (FAILED(hr))
        goto exit;

    //---- extract base ini name ----
    WCHAR szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szExt[_MAX_EXT];
    _wsplitpath(szFullName, szDrive, szDir, g_szBaseIniName, szExt);
    
    if (! g_fQuietRun)
        fwprintf(ConsoleFile, L"processing classdata file: %s\n", pszFileName);

    //---- Create a temporary INI file with the substituted values
    UINT cTablesCount = SubstNames.GetSize();
    for (UINT i = 0; i < cTablesCount; i++)
    {
        if (0 == AsciiStrCmpI(SubstNames[i].sName, pszResFileName))
        {
            // Section used in the string table
            wcscpy(g_szBaseIniName, SubstNames[i].sName);
            // Create the temp file
            DWORD len = lstrlen(g_szTempPath);
            
            if ((len) && (g_szTempPath[len-1] == '\\'))
                wsprintf(szTempName, L"%s$%s%s", g_szTempPath, pszResFileName, szExt);
            else
                wsprintf(szTempName, L"%s\\$%s%s", g_szTempPath, pszResFileName, szExt);
         
            if (lstrcmpi(szFullName, szTempName))
            {
                CopyFile(szFullName, szTempName, FALSE);
                SetFileAttributes(szTempName, FILE_ATTRIBUTE_NORMAL);
                wcscpy(szFullName, szTempName);
            }
            break;
        }
    }

    //---- output .ini filename as a resource ----
    hr = OutputResourceLine(szFullName, outfile, PACK_INIFILE);
    if (FAILED(hr))
        goto exit;

    OutputDashLine(outfile);
    int oldcnt;
    oldcnt = g_LineCount;

    //---- scan the classdata .ini file for valid filenames & fonts; write 'em to the .rc file ----
    WCHAR szErrMsg[4096];
    DWORD flags;
    flags = PTF_CLASSDATA_PARSE | PTF_CALLBACK_FILENAMES | PTF_CALLBACK_LOCALIZATIONS 
        | PTF_CALLBACK_MINCOLORDEPTH;

    hr = _ParseThemeIniFile(szFullName, flags, FnCallBack, (LPARAM)outfile);
    if (FAILED(hr))
    {
        ReportError(hr, L"Error parsing classdata .ini file");
        goto exit;
    }

    if (g_LineCount > oldcnt)
        OutputDashLine(outfile);

    hr = S_OK;

exit:
    return hr;
}
//---------------------------------------------------------------------------
HRESULT ProcessClassDataFiles(FILE *&outfile, LPCWSTR pszInputDir)
{
    int cNames = OrigFileNames.GetSize();

    for (int i=0; i < cNames; i++)
    {
        HRESULT hr = ProcessClassDataFile(OrigFileNames[i], outfile, BaseResFileNames[i], pszInputDir);
        if (FAILED(hr))
            return hr;
    }

    return S_OK;
}
//---------------------------------------------------------------------------
HRESULT OutputStringTable(FILE *outfile, CWideString *ppszStrings, UINT cStrings, int iBaseNum,
    LPCWSTR pszTitle, BOOL fLocalizable=TRUE, BOOL fMinDepths=FALSE)
{
    if (! cStrings)
        return S_OK;

    if (fLocalizable)
    {
        fwprintf(outfile, L"STRINGTABLE DISCARDABLE       // %s\n", pszTitle);
    }
    else            // custom resource type
    {
        fwprintf(outfile, L"1 %s DISCARDABLE\n", pszTitle);
    }

    fwprintf(outfile, L"BEGIN\n");

    for (UINT c=0; c < cStrings; c++)
    {
        LPCWSTR p = ppszStrings[c];
        if (! p)
            p = L"";

        if (fLocalizable)
            fwprintf(outfile, L"    %d \t\"%s\"\n", iBaseNum, p);
        else
        {
            if (fMinDepths)
            {
                fwprintf(outfile, L"    %d,\n", MinDepths[c]);
            }
            else
            {
                fwprintf(outfile, L"    L\"%s\\0\",\n", p);
            }

            if (c == cStrings-1)        // last entry
            {
                if (fMinDepths)
                {
                    fwprintf(outfile, L"    0\n");
                }
                else
                {
                    fwprintf(outfile, L"    L\"\\0\",\n");
                }
            }
        }

        iBaseNum++;
    }

    fwprintf(outfile, L"END\n");
    OutputDashLine(outfile);

    return S_OK;
}
//---------------------------------------------------------------------------
HRESULT OutputAllStringTables(FILE *outfile)
{
    //---- output all non-localizable strings ----
    if (ColorSchemes.GetSize())
    {
        OutputStringTable(outfile, &ColorSchemes[0], ColorSchemes.GetSize(), 
            0, L"COLORNAMES", FALSE);
    }

    if (SizeNames.GetSize())
    {
        OutputStringTable(outfile, &SizeNames[0], SizeNames.GetSize(), 
            0, L"SIZENAMES", FALSE);
    }

    if (ResFileNames.GetSize())
    {
        OutputStringTable(outfile, &ResFileNames[0], ResFileNames.GetSize(), 
            0, L"FILERESNAMES", FALSE);
    }

    if (MinDepths.GetSize())
    {
        OutputStringTable(outfile, &ResFileNames[0], ResFileNames.GetSize(), 
            0, L"MINDEPTH", FALSE, TRUE);
    }

    if (OrigFileNames.GetSize())
    {
        OutputStringTable(outfile, &OrigFileNames[0], OrigFileNames.GetSize(), 
            0, L"ORIGFILENAMES", FALSE);
    }

    //---- output all localizable strings ----
    if (ColorDisplays.GetSize())
    {
        OutputStringTable(outfile, &ColorDisplays[0], ColorDisplays.GetSize(), 
            RES_BASENUM_COLORDISPLAYS, L"Color Display Names");
    }

    if (ColorToolTips.GetSize())
    {
        OutputStringTable(outfile, &ColorToolTips[0], ColorToolTips.GetSize(), 
            RES_BASENUM_COLORTOOLTIPS, L"Color ToolTips");
    }

    if (SizeDisplays.GetSize())
    {
        OutputStringTable(outfile, &SizeDisplays[0], SizeDisplays.GetSize(), 
            RES_BASENUM_SIZEDISPLAYS, L"Size Display Names");
    }

    if (SizeToolTips.GetSize())
    {
        OutputStringTable(outfile, &SizeToolTips[0], SizeToolTips.GetSize(), 
            RES_BASENUM_SIZETOOLTIPS, L"Size ToolTips");
    }

    OutputStringTable(outfile, &DocProperties[0], ARRAYSIZE(DocProperties), 
        RES_BASENUM_DOCPROPERTIES, L"Doc PropValuePairs");

    OutputStringTable(outfile, &PropValuePairs[0], PropValuePairs.GetSize(), 
        RES_BASENUM_PROPVALUEPAIRS, L"PropValuePairs");

    return S_OK;
}
//---------------------------------------------------------------------------
BOOL WriteBitmapHeader(CSimpleFile &cfOut, BYTE *pBytes, DWORD dwBytes)
{
    BOOL fOK = FALSE;
    BYTE pbHdr1[] = {0x42, 0x4d};
    BYTE pbHdr2[] = {0x0, 0x0, 0x0, 0x0};
    int iFileLen;

    //---- add bitmap hdr at front ----
    HRESULT hr = cfOut.Write(pbHdr1, sizeof(pbHdr1));
    if (FAILED(hr))
    {
        ReportError(hr, L"Cannot write to output file");
        goto exit;
    }

    //---- add length of data ----
    iFileLen = dwBytes + sizeof(BITMAPFILEHEADER);
    hr = cfOut.Write(&iFileLen, sizeof(int));
    if (FAILED(hr))
    {
        ReportError(hr, L"Cannot write to output file");
        goto exit;
    }

    hr = cfOut.Write(pbHdr2, sizeof(pbHdr2));
    if (FAILED(hr))
    {
        ReportError(hr, L"Cannot write to output file");
        goto exit;
    }

    //---- offset to bits (who's idea was *this* field?) ----
    int iOffset, iColorTableSize;
    DWORD dwSize;

    iOffset = sizeof(BITMAPFILEHEADER);
    dwSize = *(DWORD *)pBytes;
    iOffset += dwSize; 
    iColorTableSize = 0;

    switch (dwSize)
    {
        case sizeof(BITMAPCOREHEADER):
            BITMAPCOREHEADER *hdr1;
            hdr1 = (BITMAPCOREHEADER *)pBytes;
            if (hdr1->bcBitCount == 1)
                iColorTableSize = 2*sizeof(RGBTRIPLE);
            else if (hdr1->bcBitCount == 4)
                iColorTableSize = 16*sizeof(RGBTRIPLE);
            else if (hdr1->bcBitCount == 8)
                iColorTableSize = 256*sizeof(RGBTRIPLE);
            break;

        case sizeof(BITMAPINFOHEADER):
        case sizeof(BITMAPV4HEADER):
        case sizeof(BITMAPV5HEADER):
            BITMAPINFOHEADER *hdr2;
            hdr2 = (BITMAPINFOHEADER *)pBytes;
            if (hdr2->biClrUsed)
                iColorTableSize = hdr2->biClrUsed*sizeof(RGBQUAD);
            else
            {
                if (hdr2->biBitCount == 1)
                    iColorTableSize = 2*sizeof(RGBQUAD);
                else if (hdr2->biBitCount == 4)
                    iColorTableSize = 16*sizeof(RGBQUAD);
                else if (hdr2->biBitCount == 8)
                    iColorTableSize = 256*sizeof(RGBQUAD);
            }
            break;
    }

    iOffset += iColorTableSize;
    hr = cfOut.Write(&iOffset, sizeof(int));
    if (FAILED(hr))
    {
        ReportError(hr, L"Cannot write to output file");
        goto exit;
    }

    fOK = TRUE;

exit:
    return fOK;
}
//---------------------------------------------------------------------------
BOOL CALLBACK ResEnumerator(HMODULE hModule, LPCWSTR pszType, LPWSTR pszResName, LONG_PTR lParam)
{
    HRESULT hr;
    BOOL fAnsi = (BOOL)lParam;
    BOOL fText = FALSE;
    RESOURCE BYTE *pBytes = NULL;
    CSimpleFile cfOut;
    DWORD dwBytes;

    if (pszType != RT_BITMAP)
        fText = TRUE;

    hr = GetPtrToResource(hModule, pszType, pszResName, (void **)&pBytes, &dwBytes);
    if (FAILED(hr))
    {
        ReportError(hr, L"error reading file resources");
        goto exit;
    }

    //---- convert name to filename ----
    WCHAR szFileName[_MAX_PATH+1];
    lstrcpy(szFileName, pszResName);
    WCHAR *q;
    q = wcsrchr(szFileName, '_');
    if (q)
        *q = '.';
        
    if (! fText)
        fAnsi = FALSE;          // don't translate if binary data

    hr = cfOut.Create(szFileName, fAnsi);
    if (FAILED(hr))
    {
        ReportError(hr, L"Cannot create output file");
        goto exit;
    }

    if (! fText)
    {
        if (! WriteBitmapHeader(cfOut, pBytes, dwBytes))
            goto exit;
    }

    hr = cfOut.Write(pBytes, dwBytes);
    if (FAILED(hr))
    {
        ReportError(hr, L"Cannot write to output file");
        goto exit;
    }
    
exit:
    return (SUCCEEDED(hr));
}
//---------------------------------------------------------------------------
void WriteBitmap(LPWSTR pszFileName, BITMAPINFOHEADER* pbmi, DWORD* pdwData)
{
    DWORD dwLen = pbmi->biWidth * pbmi->biHeight;

    CSimpleFile cfOut;
    cfOut.Create(pszFileName, FALSE);

    BITMAPFILEHEADER bmfh = {0};
    bmfh.bfType = 'MB';
    bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (dwLen * sizeof(DWORD));
    bmfh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
    cfOut.Write(&bmfh, sizeof(BITMAPFILEHEADER));
    cfOut.Write(pbmi, sizeof(BITMAPINFOHEADER));
    cfOut.Write(pdwData, dwLen * sizeof(DWORD));
}

HRESULT ColorShift(LPWSTR pszFileName, int cchFileName)
{
    HDC hdc = GetDC(NULL);
    if (hdc)
    {
        HBITMAP hbm = (HBITMAP)LoadImage(0, pszFileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
        if (hbm)
        {
            BITMAP bm;
            GetObject(hbm, sizeof(bm), &bm);

            DWORD dwLen = bm.bmWidth * bm.bmHeight;
            DWORD* pPixelQuads = new DWORD[dwLen];
            if (pPixelQuads)
            {
                BITMAPINFO bi = {0};
                bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
                bi.bmiHeader.biWidth = bm.bmWidth;
                bi.bmiHeader.biHeight = bm.bmHeight;
                bi.bmiHeader.biPlanes = 1;
                bi.bmiHeader.biBitCount = 32;
                bi.bmiHeader.biCompression = BI_RGB;

                if (GetDIBits(hdc, hbm, 0, bm.bmHeight, pPixelQuads, &bi, DIB_RGB_COLORS))
                {
                    pszFileName[lstrlen(pszFileName) - 4] = 0;

                    WCHAR szFileNameR[MAX_PATH];
                    wsprintf(szFileNameR, L"%sR.bmp", pszFileName);
                    WCHAR szFileNameG[MAX_PATH];
                    wsprintf(szFileNameG, L"%sG.bmp", pszFileName);
                    WCHAR szFileNameB[MAX_PATH];
                    wsprintf(szFileNameB, L"%sB.bmp", pszFileName);
                    
                    WriteBitmap(szFileNameB, &bi.bmiHeader, pPixelQuads);

                    DWORD *pdw = pPixelQuads;
                    for (DWORD i = 0; i < dwLen; i++)
                    {
                        COLORREF crTemp = *pdw;
                        if (crTemp != RGB(255, 0, 255))
                        {
                            crTemp = (crTemp & 0xff000000) | RGB(GetGValue(crTemp), GetBValue(crTemp), GetRValue(crTemp));
                        }
                        *pdw = crTemp;
                        pdw++;
                    }

                    WriteBitmap(szFileNameR, &bi.bmiHeader, pPixelQuads);

                    pdw = pPixelQuads;
                    for (DWORD i = 0; i < dwLen; i++)
                    {
                        COLORREF crTemp = *pdw;
                        if (crTemp != RGB(255, 0, 255))
                        {
                            crTemp = (crTemp & 0xff000000) | RGB(GetGValue(crTemp), GetBValue(crTemp), GetRValue(crTemp));
                        }
                        *pdw = crTemp;
                        pdw++;
                    }

                    WriteBitmap(szFileNameG, &bi.bmiHeader, pPixelQuads);
                }

                delete[] pPixelQuads;
            }
            DeleteObject(hbm);
        }
        ReleaseDC(NULL, hdc);
    }

    return S_OK;
}

//---------------------------------------------------------------------------
HRESULT UnpackTheme(LPCWSTR pszFileName, BOOL fAnsi)
{
    HRESULT hr = S_OK;

    //---- load the file as a resource only DLL ----
    RESOURCE HINSTANCE hInst = LoadLibraryEx(pszFileName, NULL, LOAD_LIBRARY_AS_DATAFILE);
    if (hInst)
    {
        //---- enum all bitmaps & write as files ----
        if (! EnumResourceNames(hInst, RT_BITMAP, ResEnumerator, LPARAM(fAnsi)))
            hr = GetLastError();

        //---- enum all .ini files & write as files ----
        if (! EnumResourceNames(hInst, L"TEXTFILE", ResEnumerator, LPARAM(fAnsi)))
            hr = GetLastError();

        //---- close the file ----
        if (hInst)
            FreeLibrary(hInst);
    }

    return hr;
}
//---------------------------------------------------------------------------
HRESULT PackTheme(LPCWSTR pszInputDir, LPWSTR pszOutputName, DWORD cchSize)
{
    //---- is it a valid dir ----
    DWORD dwMask = GetFileAttributes(pszInputDir);
    if ((dwMask == 0xffffffff) || (! (dwMask & FILE_ATTRIBUTE_DIRECTORY)))
    {
        fwprintf(ConsoleFile, L"\nError - not a valid directory name: %s\n", pszInputDir);
        return MakeError32(E_FAIL);
    }

    //---- build: szDllName ----
    WCHAR szDllName[_MAX_PATH+1];
    BOOL fOutputDir = FALSE;

    if (! *pszOutputName)                     // not specified - build from pszInputDir
    {
        WCHAR szFullDir[_MAX_PATH+1];
        WCHAR *pszBaseName;

        DWORD val = GetFullPathName(pszInputDir, ARRAYSIZE(szFullDir), szFullDir, &pszBaseName);
        if (! val)
            return MakeErrorLast();

        //---- make output dir same as input dir ----
        wsprintf(szDllName, L"%s\\%s%s", pszInputDir, pszBaseName, THEMEDLL_EXT);
    }
    else        // get full name of output file
    {
        DWORD val = GetFullPathName(pszOutputName, ARRAYSIZE(szDllName), szDllName, NULL);
        if (! val)
            return MakeErrorLast();

        fOutputDir = TRUE;            // don't remove temp files
    }

    // Give the caller the path so the file can be signed.
    lstrcpyn(pszOutputName, szDllName, cchSize);

    //--- delete the old target in case we have errors ----
    DeleteFile(pszOutputName);

    //---- build: g_szTempPath, szDllRoot, szRcName, and szResName ----
    WCHAR szDllRoot[_MAX_PATH+1];
    WCHAR szResName[_MAX_PATH+1];
    WCHAR szRcName[_MAX_PATH+1];
    WCHAR szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szBaseName[_MAX_FNAME], szExt[_MAX_EXT];

    _wsplitpath(szDllName, szDrive, szDir, szBaseName, szExt);

    _wmakepath(szDllRoot, L"", L"", szBaseName, szExt);
    _wmakepath(szRcName, szDrive, szDir, szBaseName, L".rc");
    _wmakepath(szResName, szDrive, szDir, szBaseName, L".res");

    if (fOutputDir)
        _wmakepath(g_szTempPath, szDrive, szDir, L"", L"");
    else
        lstrcpy(g_szTempPath, L".");

    FILE *outfile = NULL;
    OpenOutFile(outfile, szRcName, szBaseName);

    ClearCombos();

    //---- process the main container file ----
    HRESULT hr = ProcessContainerFile(pszInputDir, CONTAINER_NAME, outfile);
    if (FAILED(hr))
        goto exit;

    //---- process all classdata files that were defined in container file ----
    hr = ProcessClassDataFiles(outfile, pszInputDir);
    if (FAILED(hr))
        goto exit;

    //---- output all string tables ----
    hr = OutputAllStringTables(outfile);
    if (FAILED(hr))
        goto exit;

    hr = OutputCombos(outfile);
    if (FAILED(hr))
        goto exit;

    hr = OutputVersionInfo(outfile, szDllRoot, szBaseName);
    if (FAILED(hr))
        goto exit;

    fclose(outfile);
    outfile = NULL;

    hr = BuildThemeDll(szRcName, szResName, szDllName);

exit:
    if (outfile)
        fclose(outfile);

    if (ConsoleFile != stdout)
        fclose(ConsoleFile);

    if (! g_fKeepTempFiles)
        RemoveTempFiles(szRcName, szResName);

    if (SUCCEEDED(hr))
    {
        if (! g_fQuietRun)
            fwprintf(ConsoleFile, L"Created %s\n", szDllName);
        return S_OK;
    }

    if (! g_fQuietRun)
        fwprintf(ConsoleFile, L"Error occured - theme DLL not created\n");
    return hr; 
}
//---------------------------------------------------------------------------
void PrintUsage()
{
    fwprintf(ConsoleFile, L"\nUsage: \n\n");
    fwprintf(ConsoleFile, L"    packthem [-o <output name> ] [-k] [-q] <dirname>\n");
    fwprintf(ConsoleFile, L"      -m    specifies the (full path) name of the image file you want to color shift\n");
    fwprintf(ConsoleFile, L"      -o    specifies the (full path) name of the output file\n");
    fwprintf(ConsoleFile, L"      -k    specifies that temp. files should be kept (not deleted)\n");
    fwprintf(ConsoleFile, L"      -d    do not sign the file when building it\n");
    fwprintf(ConsoleFile, L"      -q    quite mode (don't print header and progress msgs)\n\n");

    fwprintf(ConsoleFile, L"    packthem -u [-a] <packed filename> \n");
    fwprintf(ConsoleFile, L"      -u    unpacks the packed file into its separate files in current dir\n");
    fwprintf(ConsoleFile, L"      -a    writes .ini files as ANSI (defaults to UNICODE)\n\n");

    fwprintf(ConsoleFile, L"    packthem -p [-q] <packed filename> \n");
    fwprintf(ConsoleFile, L"      -p    Parses the localized packed file and reports errors\n\n");

    fwprintf(ConsoleFile, L"    packthem [-c] [-q] <packed filename> \n");
    fwprintf(ConsoleFile, L"      -c    check the signature of the already created file\n\n");

    fwprintf(ConsoleFile, L"    packthem [-s] [-q] <packed filename> \n");
    fwprintf(ConsoleFile, L"      -s    sign the already created file\n\n");

    fwprintf(ConsoleFile, L"    packthem [-g] [-q] <packed filename> \n");
    fwprintf(ConsoleFile, L"      -g    generate public and private keys\n\n");
}
//---------------------------------------------------------------------------
enum eOperation
{
    opPack = 1,
    opUnPack,
    opSign,
    opCheckSignature,
    opGenerateKeys,
    opParse,
    opColorShift
};
//---------------------------------------------------------------------------
extern "C" WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE previnst, 
    LPTSTR pszCmdLine, int nShowCmd)
{
    //---- initialize globals from themeldr.lib ----
    ThemeLibStartUp(FALSE);

    //---- initialize our globals ----
    HRESULT hr = S_OK;
    int nWeek = -1;

    UtilsStartUp();        
    LogStartUp();

    WCHAR szOutputName[_MAX_PATH+1] = {0};
    int retval = 1;             // error, until prove otherwise

    BOOL fAnsi = FALSE; 
    BOOL fSkipSigning = FALSE;
    eOperation Operation = opPack;

    LPCWSTR p = pszCmdLine;
    szOutputName[0] = 0;    // Much faster than ={0};

    //---- default to console until something else is specified ----
    if (! ConsoleFile)
    {
        ConsoleFile = stdout;
    }

    while ((*p == '-') || (*p == '/'))
    {
        p++;
        WCHAR sw = *p;

        if (isupper(sw))
            sw = (WCHAR)tolower(sw);

        if (sw == 'e')
        {
            ConsoleFile = _wfopen(L"packthem.err", L"wt");
            g_fQuietRun = TRUE;
            p++;
        }
        else if (sw == 'o')
        {
            WCHAR *q = szOutputName;
            p++;        // skip over switch
            while (iswspace(*p))
                p++;
            while ((*p) && (! iswspace(*p)))
                *q++ = *p++;

            *q = 0;     // terminate the output name
        }
        else if (sw == 'k')
        {
            g_fKeepTempFiles = TRUE;
            p++;
        }
        else if (sw == 'q')
        {
            g_fQuietRun = TRUE;
            p++;
        }
        else if (sw == 'm')
        {
            Operation = opColorShift;

            WCHAR *q = szOutputName;
            p++;        // skip over switch
            while (iswspace(*p))
                p++;
            while ((*p) && (! iswspace(*p)))
                *q++ = *p++;

            *q = 0;     // terminate the output name
        }
        else if (sw == 'u')
        {
            Operation = opUnPack;
            p++;
        }
        else if (sw == 'd')
        {
            fSkipSigning = TRUE;
            p++;
        }
        else if (sw == 'c')
        {
            Operation = opCheckSignature;
            p++;
        }
        else if (sw == 'g')
        {
            Operation = opGenerateKeys;
            p++;
        }
        else if (sw == 's')
        {
            Operation = opSign;
            p++;
        }
        else if (sw == 'a')
        {
            fAnsi = TRUE;
            p++;
        }
        else if (sw == 'w')
        {
            fAnsi = TRUE;
            p++;

            LARGE_INTEGER uli;
            WCHAR szWeek[3];

            szWeek[0] = p[0];
            szWeek[1] = p[1];
            szWeek[2] = 0;
            if (StrToInt64ExInternalW(szWeek, 0, &(uli.QuadPart)) &&
                (uli.QuadPart > 0))
            {
                nWeek = (int)uli.LowPart;
            }

            while ((L' ' != p[0]) && (0 != p[0]))
            {
                p++;
            }
        }
        else if (sw == 'p')
        {
            Operation = opParse;
            p++;
        }
        else if (sw == '?')
        {
            PrintUsage();
            retval = 0;
            goto exit;
        }
        else
        {
            fwprintf(ConsoleFile, L"Error - unrecognized switch: %s\n", p);
            goto exit;
        }

        while (iswspace(*p))
            p++;
    }

    LPCWSTR pszInputDir;
    pszInputDir = p;

    if (! g_fQuietRun)
    {
        fwprintf(ConsoleFile, L"Microsoft (R) Theme Packager (Version %d)\n", PACKTHEM_VERSION);
        fwprintf(ConsoleFile, L"Copyright (C) Microsoft Corp 2000. All rights reserved.\n");
    }

    //---- any cmdline arg specified? ----
    if (Operation != opColorShift)
    {
        if ((! pszInputDir) || (! *pszInputDir))
        {
            PrintUsage(); 
            goto exit;
        }
    }

    switch (Operation)
    {
    case opPack:
        hr = PackTheme(pszInputDir, szOutputName, ARRAYSIZE(szOutputName));
        if (SUCCEEDED(hr) && !fSkipSigning)
        {
            hr = SignTheme(szOutputName, nWeek);
            if (!g_fQuietRun)
            {
                if (SUCCEEDED(hr))
                {
                    wprintf(L"Creating the signature succeeded\n");
                }
                else
                {
                    wprintf(L"The signature failed to be created.  hr=%#08lx\n", hr);
                }
            }
        }
        break;
    case opUnPack:
        hr = UnpackTheme(pszInputDir, fAnsi);
        break;
    case opSign:
        // We don't sign it again if the signature is already valid.
        if (FAILED(CheckThemeFileSignature(pszInputDir)))
        {
            // Needs signing.
            hr = SignTheme(pszInputDir, nWeek);
            if (!g_fQuietRun)
            {
                if (SUCCEEDED(hr))
                {
                    wprintf(L"Creating the signature succeeded\n");
                }
                else
                {
                    wprintf(L"The signature failed to be created.  hr=%#08lx\n", hr);
                }
            }
        }
        else
        {
            if (!g_fQuietRun)
            {
                wprintf(L"The file was already signed and the signature is still valid.");
            }
        }
        break;
    case opCheckSignature:
        hr = CheckThemeFileSignature(pszInputDir);
        if (!g_fQuietRun)
        {
            if (SUCCEEDED(hr))
            {
                wprintf(L"The signature is valid\n");
            }
            else
            {
                wprintf(L"The signature is not valid.  hr=%#08lx\n", hr);
            }
        }
        break;
    case opGenerateKeys:
        hr = GenerateKeys(pszInputDir);
        break;
    case opParse:
        hr = ParseTheme(pszInputDir);
        if (FAILED(hr))
        {
            ReportError(hr, L"Error during parsing");
            goto exit;
        } else
        {
            wprintf(L"No errors parsing theme file\n");
        }
        break;
    case opColorShift:
        hr = ColorShift(szOutputName, ARRAYSIZE(szOutputName));
        break;

    default:
        if (FAILED(hr))
        {
            hr = E_FAIL;
            goto exit;
        }
        break;
    };

    retval = 0;     // all OK

exit:
    UtilsShutDown();
    LogShutDown();

    return retval;
}
//---------------------------------------------------------------------------
HRESULT LoadClassDataIni(HINSTANCE hInst, LPCWSTR pszColorName,
    LPCWSTR pszSizeName, LPWSTR pszFoundIniName, DWORD dwMaxIniNameChars, LPWSTR *ppIniData)
{
    COLORSIZECOMBOS *combos;
    HRESULT hr = FindComboData(hInst, &combos);
    if (FAILED(hr))
        return hr;

    int iSizeIndex = 0;
    int iColorIndex = 0;

    if ((pszColorName) && (* pszColorName))
    {
        hr = GetColorSchemeIndex(hInst, pszColorName, &iColorIndex);
        if (FAILED(hr))
            return hr;
    }

    if ((pszSizeName) && (* pszSizeName))
    {
        hr = GetSizeIndex(hInst, pszSizeName, &iSizeIndex);
        if (FAILED(hr))
            return hr;
    }

    int filenum = COMBOENTRY(combos, iColorIndex, iSizeIndex);
    if (filenum == -1)
        return MakeError32(ERROR_NOT_FOUND);

    //---- locate resname for classdata file "filenum" ----
    hr = GetResString(hInst, L"FILERESNAMES", filenum, pszFoundIniName, dwMaxIniNameChars);
    if (SUCCEEDED(hr))
    {
        hr = AllocateTextResource(hInst, pszFoundIniName, ppIniData);
    }

    return hr;
}
//---------------------------------------------------------------------------
// Parse the theme to detect localization errors
HRESULT ParseTheme(LPCWSTR pszThemeName)
{
    // Dummy callback class needed by the parser
    class CParserCallBack: public IParserCallBack
    {
        HRESULT AddIndex(LPCWSTR pszAppName, LPCWSTR pszClassName, 
            int iPartNum, int iStateNum, int iIndex, int iLen) { return S_OK; };
        HRESULT AddData(SHORT sTypeNum, PRIMVAL ePrimVal, const void *pData, DWORD dwLen) { return S_OK; };
        int GetNextDataIndex() { return 0; };
    };

    CParserCallBack *pParserCallBack = NULL;
    CThemeParser *pParser = NULL;

    HRESULT hr;
    HINSTANCE hInst = NULL;
    WCHAR *pDataIni = NULL;
    WCHAR szClassDataName[_MAX_PATH+1];

    //---- load the Color Scheme from "themes.ini" ----
    hr = LoadThemeLibrary(pszThemeName, &hInst);
    if (FAILED(hr))
        goto exit;
    
    pParser = new CThemeParser(FALSE);
    if (! pParser)
    {
        hr = MakeError32(E_OUTOFMEMORY);
        goto exit;
    }

    pParserCallBack = new CParserCallBack;
    if (!pParserCallBack)
    {
        hr = MakeError32(E_OUTOFMEMORY);
        goto exit;
    }

    THEMENAMEINFO tniColors;
    THEMENAMEINFO tniSizes;

    for (DWORD c = 0; ; c++)
    {
        if (FAILED(_EnumThemeColors(hInst, pszThemeName, NULL, c, &tniColors, FALSE)))
            break;

        for (DWORD s = 0 ; ; s++)
        {
            if (FAILED(_EnumThemeSizes(hInst, pszThemeName, tniColors.szName, s, &tniSizes, FALSE)))
                break;

            //---- load the classdata file resource into memory ----
            hr = LoadClassDataIni(hInst, tniColors.szName, tniSizes.szName, szClassDataName, ARRAYSIZE(szClassDataName), &pDataIni);
            if (FAILED(hr))
                goto exit;

            //---- parse & build binary theme ----
            hr = pParser->ParseThemeBuffer(pDataIni, szClassDataName, NULL, hInst, 
                pParserCallBack, FnCallBack, NULL, PTF_CLASSDATA_PARSE);
            if (FAILED(hr))
                goto exit;

            if (pDataIni)
            {
                delete [] pDataIni;
                pDataIni = NULL;
            }
        }
    }

exit:

    if (hInst)
        FreeLibrary(hInst);
    
    if (pDataIni)
        delete [] pDataIni;

    if (pParser)
        delete pParser;

    if (pParserCallBack)
        delete pParserCallBack;

    return hr;
}