// Util.cpp : Helper functions and classes
#include "private.h"
#include "mlmain.h"
#include <setupapi.h>
#include <tchar.h>

const CLSID CLSID_Japanese =  {0x76C19B30,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_Korean   =  {0x76C19B31,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_PanEuro  =  {0x76C19B32,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_TradChinese =	{0x76C19B33,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_SimpChinese =	{0x76C19B34,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_Thai        =	{0x76C19B35,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_Hebrew      = {0x76C19B36,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_Vietnamese  = {0x76C19B37,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_Arabic      = {0x76C19B38,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};
const CLSID CLSID_Auto        = {0x76C19B50,0xF0C8,0x11cf,{0x87,0xCC,0x00,0x20,0xAF,0xEE,0xCF,0x20}};

TCHAR szFonts[]=TEXT("fonts");

static TCHAR s_szJaFont[] = TEXT("msgothic.ttf,msgothic.ttc");
// a-ehuang: mail-Hyo Kyoung Kim-Kevin Gjerstad-9/14/98
// OLD: static TCHAR s_szKorFont[] = TEXT("gulimche.ttf, gulim.ttf,gulim.ttc");
static TCHAR s_szKorFont[] = TEXT("gulim.ttf,gulim.ttc,gulimche.ttf");
// end-of-change
static TCHAR s_szZhtFont[] = TEXT("mingliu.ttf,mingliu.ttc");
static TCHAR s_szZhcFont[] = TEXT("mssong.ttf,simsun.ttc,mshei.ttf");
static TCHAR s_szThaiFont[] = TEXT("angsa.ttf,angsa.ttf,angsab.ttf,angsai.ttf,angsaz.ttf,upcil.ttf,upcib.ttf,upcibi.ttf, cordia.ttf, cordiab.ttf, cordiai.ttf, coradiaz.ttf");
static TCHAR s_szPeFont[] = TEXT("larial.ttf,larialbd.ttf,larialbi.ttf,lariali.ttf,lcour.ttf,lcourbd.ttf,lcourbi.ttf,lcouri.ttf,ltimes.ttf,ltimesbd.ttf,ltimesbi.ttf,ltimesi.ttf,symbol.ttf");
static TCHAR s_szArFont[] = TEXT("andlso.ttf, artrbdo.ttf, artro.ttf, simpbdo.ttf, simpfxo.ttf, tradbdo.ttf, trado.ttf");
static TCHAR s_szViFont[] = TEXT("VARIAL.TTF, VARIALBD.TTF, VARIALBI.TTF, VARIALI.TTF, VCOUR.TTF, VCOURBD.TTF, VCOURBI.TTF, VCOURI.TTF, VTIMES.TTF, VTIMESBD.TTF, VTIMESBI.TTF, VTIMESI.TTF");
static TCHAR s_szIwFont[] = TEXT("DAVID.TTF, DAVIDBD.TTF, DAVIDTR.TTF, MRIAM.TTF, MRIAMC.TTF, MRIAMFX.TTF, MRIAMTR.TTF, ROD.TTF");

#define IS_DBCSCODEPAGE(j) \
    (((j) == 932)   || \
    ((j) == 936)   || \
    ((j) == 949)   || \
    ((j) == 950))   

#define IS_COMPLEXSCRIPT_CODEPAGE(j) \
    (((j) == 874)   || \
    ((j) == 1255)   || \
    ((j) == 1256)   || \
    ((j) == 1258))   

#ifdef NEWMLSTR


#include "util.h"

/////////////////////////////////////////////////////////////////////////////
// Helper functions

HRESULT RegularizePosLen(long lStrLen, long* plPos, long* plLen)
{
    ASSERT_WRITE_PTR(plPos);
    ASSERT_WRITE_PTR(plLen);

    long lPos = *plPos;
    long lLen = *plLen;

    if (lPos < 0)
        lPos = lStrLen;
    else
        lPos = min(lPos, lStrLen);

    if (lLen < 0)
        lLen = lStrLen - lPos;
    else
        lLen = min(lLen, lStrLen - lPos);

    *plPos = lPos;
    *plLen = lLen;

    return S_OK;
}

HRESULT LocaleToCodePage(LCID locale, UINT* puCodePage)
{
    HRESULT hr = S_OK;

    if (puCodePage)
    {
        TCHAR szCodePage[8];

        if (::GetLocaleInfo(locale, LOCALE_IDEFAULTANSICODEPAGE, szCodePage, ARRAYSIZE(szCodePage)) > 0)
            *puCodePage = _ttoi(szCodePage);
        else
            hr = E_FAIL; // NLS failed
    }

    return hr;
}

HRESULT StartEndConnection(IUnknown* const pUnkCPC, const IID* const piid, IUnknown* const pUnkSink, DWORD* const pdwCookie, DWORD dwCookie)
{
    ASSERT_READ_PTR(pUnkCPC);
    ASSERT_READ_PTR(piid);
    if (pdwCookie)
        ASSERT_WRITE_PTR(pUnkSink);
    ASSERT_READ_PTR_OR_NULL(pdwCookie);

    HRESULT hr;
    IConnectionPointContainer* pcpc;

    if (SUCCEEDED(hr = pUnkCPC->QueryInterface(IID_IConnectionPointContainer, (void**)&pcpc)))
    {
        ASSERT_READ_PTR(pcpc);

        IConnectionPoint* pcp;

        if (SUCCEEDED(hr = pcpc->FindConnectionPoint(*piid, &pcp)))
        {
            ASSERT_READ_PTR(pcp);

            if (pdwCookie)
                hr = pcp->Advise(pUnkSink, pdwCookie);
            else
                hr = pcp->Unadvise(dwCookie);

            pcp->Release();
        }

        pcpc->Release();
    }

    return hr;
}

/////////////////////////////////////////////////////////////////////////////
// CMLAlloc

CMLAlloc::CMLAlloc(void)
{
    if (FAILED(::CoGetMalloc(1, &m_pIMalloc)))
        m_pIMalloc = NULL;
}

CMLAlloc::~CMLAlloc(void)
{
    if (m_pIMalloc)
        m_pIMalloc->Release();
}

void* CMLAlloc::Alloc(ULONG cb)
{
    if (m_pIMalloc)
        return m_pIMalloc->Alloc(cb);
    else
        return ::malloc(cb);
}

void* CMLAlloc::Realloc(void* pv, ULONG cb)
{
    if (m_pIMalloc)
        return m_pIMalloc->Realloc(pv, cb);
    else
        return ::realloc(pv, cb);
}

void CMLAlloc::Free(void* pv)
{
    if (m_pIMalloc)
        m_pIMalloc->Free(pv);
    else
        ::free(pv);
}

/////////////////////////////////////////////////////////////////////////////
// CMLList

HRESULT CMLList::Add(void** ppv)
{
    if (!m_pFree) // No free cell
    {
        // Determine new size of the buffer
        const int cNewCell = (m_cbCell * m_cCell + m_cbIncrement + m_cbCell - 1) / m_cbCell;
        ASSERT(cNewCell > m_cCell);
        const long lNewSize = cNewCell * m_cbCell;

        // Allocate the buffer
        void *pNewBuf;
        if (!m_pBuf)
        {
            pNewBuf = MemAlloc(lNewSize);
        }
        else
        {
            pNewBuf = MemRealloc((void*)m_pBuf, lNewSize);
            ASSERT(m_pBuf == pNewBuf);
        }
        ASSERT_WRITE_BLOCK_OR_NULL((BYTE*)pNewBuf, lNewSize);

        if (pNewBuf)
        {
            // Add new cells to free link
            m_pFree = m_pBuf[m_cCell].m_pNext;
            for (int iCell = m_cCell; iCell + 1 < cNewCell; iCell++)
                m_pBuf[iCell].m_pNext = &m_pBuf[iCell + 1];
            m_pBuf[iCell].m_pNext = NULL;

            m_pBuf = (CCell*)pNewBuf;
            m_cCell = cNewCell;
        }
    }

    if (m_pFree)
    {
        // Get a new element from free link
        CCell* const pNewCell = m_pFree;
        m_pFree = pNewCell->m_pNext;
        *ppv = pNewCell;
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_OUTOFMEMORY;
    }
}

HRESULT CMLList::Remove(void* pv)
{
    AssertPV(pv);
#ifdef DEBUG
    for (CCell* pWalk = m_pFree; pWalk && pWalk != pv; pWalk = pWalk->m_pNext)
        ;
    ASSERT(!pWalk); // pv is already in free link
#endif

    CCell* const pCell = (CCell* const)pv;
    pCell->m_pNext = m_pFree;
    m_pFree = pCell;

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CMLListLru

HRESULT CMLListLru::Add(void** ppv)
{
    HRESULT hr;
    CCell* pCell;
    
    if (SUCCEEDED(hr = CMLList::Add((void**)&pCell)))
    {
        // Add the cell at the bottom of LRU link
        for (CCell** ppCell = &m_pTop; *ppCell; ppCell = &(*ppCell)->m_pNext)
            ;
        *ppCell = pCell;
        pCell->m_pNext = NULL;
    }

    *ppv = (void*)pCell;
    return hr;
}

HRESULT CMLListLru::Remove(void* pv)
{
    AssertPV(pv);

    // Look for previous cell of given
    for (CCell** ppWalk = &m_pTop; *ppWalk != pv && *ppWalk; ppWalk = &(*ppWalk)->m_pNext)
        ;
    ASSERT(!*ppWalk); // Not found in LRU link

    if (*ppWalk)
    {
        // Remove from LRU link
        CCell* const pCell = *ppWalk;
        *ppWalk = pCell->m_pNext;
    }

    // Add to free link
    return CMLList::Remove(pv);
}

/////////////////////////////////////////////////////////////////////////////
// CMLListFast

HRESULT CMLListFast::Add(void** ppv)
{
    HRESULT hr;
    CCell* pCell;
    
    if (SUCCEEDED(hr = CMLList::Add((void**)&pCell)))
    {
        // Add to top of double link
        pCell->m_pNext = m_pTop;
        CCell* const pPrev = m_pTop->m_pPrev;
        pCell->m_pPrev = pPrev;
        m_pTop = pCell;
        pPrev->m_pNext = pCell;
    }

    *ppv = (void*)pCell;
    return hr;
}

HRESULT CMLListFast::Remove(void* pv)
{
    AssertPV(pv);

    // Remove from double link
    CCell* const pCell = (CCell*)pv;
    CCell* const pPrev = pCell->m_pPrev;
    CCell* const pNext = (CCell*)pCell->m_pNext;
    pPrev->m_pNext = pNext;
    pNext->m_pPrev = pPrev;

    // Add to free link
    return CMLList::Remove(pv);
}

#endif // NEWMLSTR

HRESULT 
_FaultInIEFeature(HWND hwnd, uCLSSPEC *pclsspec, QUERYCONTEXT *pQ, DWORD dwFlags)
{
    HRESULT hr = E_FAIL;
    typedef HRESULT (WINAPI *PFNJIT)(
        HWND hwnd, 
        uCLSSPEC *pclsspec, 
        QUERYCONTEXT *pQ, 
        DWORD dwFlags);
    static PFNJIT  pfnJIT = NULL;

    if (!pfnJIT && !g_hUrlMon)
    {
        g_hUrlMon = LoadLibrary(TEXT("urlmon.DLL"));
        if (g_hUrlMon)
            pfnJIT = (PFNJIT)GetProcAddress(g_hUrlMon, "FaultInIEFeature");
    }
    
    if (pfnJIT)
       hr = pfnJIT(hwnd, pclsspec, pQ, dwFlags);
       
    return hr;
}


HRESULT InstallIEFeature(HWND hWnd, CLSID *clsid, DWORD dwfIODControl)
{
   
    HRESULT     hr  = REGDB_E_CLASSNOTREG;
    uCLSSPEC    classpec;
    DWORD       dwfIEF = 0;
    
    classpec.tyspec=TYSPEC_CLSID;
    classpec.tagged_union.clsid=*clsid;

    if (dwfIODControl & CPIOD_PEEK)
        dwfIEF |= FIEF_FLAG_PEEK;

    if (dwfIODControl & CPIOD_FORCE_PROMPT)
        dwfIEF |= FIEF_FLAG_FORCE_JITUI;

    hr = _FaultInIEFeature(hWnd, &classpec, NULL, dwfIEF);

    if (hr != S_OK) {
        hr = REGDB_E_CLASSNOTREG;
    }
    return hr;
}

HRESULT _GetJITClsIDForCodePage(UINT uiCP, CLSID *clsid)
{
    switch(uiCP)
    {
        case 932: // JA
            *clsid = CLSID_Japanese;
            break;
        case 949: // KOR
            *clsid = CLSID_Korean;
            break;
        case 950: // ZHT
            *clsid = CLSID_TradChinese;
            break;
        case 936: // ZHC
            *clsid = CLSID_SimpChinese;
            break;
        case 874:
            *clsid = CLSID_Thai;
            break;
        case 1255:
            *clsid = CLSID_Hebrew;
            break;
        case 1256:
            *clsid = CLSID_Arabic;
            break;
        case 1258:
            *clsid = CLSID_Vietnamese;
            break;            
        case 1250:    // PANEURO
        case 1251: 
        case 1253:
        case 1254:
        case 1257:
            *clsid = CLSID_PanEuro;
            break;
        case 50001:
            *clsid = CLSID_Auto;
            break;
        default:
            return E_INVALIDARG;
    }
    
    return S_OK;
}

// Only good for family code pages.
HRESULT _ValidateCPInfo(UINT uiCP)
{
    HRESULT hr = E_FAIL;
    if (g_pMimeDatabase) // just a paranoid
    {
        switch(uiCP)
        {
            case 932: // JA
            case 949: // KOR
            case 874: // Thai
            case 950: // ZHT
            case 936: // ZHC
            case 1255: // Hebrew
            case 1256: // Arabic
            case 1258: // Vietnamese
            case 50001: // CP_AUTO
                // just validate what's given
                hr = g_pMimeDatabase->ValidateCP(uiCP);
                break;
            case 1250:    // PANEURO
            case 1251:
            case 1253:
            case 1254:
            case 1257:
                // have to validate
                // all of these
                hr = g_pMimeDatabase->ValidateCP(1250);
                if (SUCCEEDED(hr))
                    hr = g_pMimeDatabase->ValidateCP(1251);
                if (SUCCEEDED(hr))
                    hr = g_pMimeDatabase->ValidateCP(1253);
                if (SUCCEEDED(hr))
                    hr = g_pMimeDatabase->ValidateCP(1254);
                if (SUCCEEDED(hr))
                    hr = g_pMimeDatabase->ValidateCP(1257);
                break;
            default:
                return E_INVALIDARG;
        }
    }
    return hr;
}

// assumes the corresponding fontfile name for now
HRESULT _AddFontForCP(UINT uiCP)
{
   TCHAR szFontsPath[MAX_PATH];
   LPTSTR szFontFile;
   HRESULT hr = S_OK;
   BOOL bAtLeastOneFontAdded = FALSE;
   
   switch(uiCP)
   {
        case 932: // JA
            szFontFile = s_szJaFont;
            break;
        case 949: // KOR
            szFontFile = s_szKorFont;
            break;
        case 950: // ZHT
            szFontFile = s_szZhtFont;
            break;
        case 936: // ZHC
            szFontFile = s_szZhcFont;
            break;
        case 874:
            szFontFile = s_szThaiFont;
            break; 
        case 1255:
            szFontFile = s_szIwFont;
            break; 
        case 1256:
            szFontFile = s_szArFont;
            break; 
        case 1258:
            szFontFile = s_szViFont;
            break; 
        case 1251:    // PANEURO
        case 1253:
        case 1254:
        case 1257:
            szFontFile = s_szPeFont;
            break;
        default:
            hr = E_INVALIDARG;
    } 
   
   // addfontresource, then broadcast WM_FONTCHANGE
   if (SUCCEEDED(hr))
   {      
       if (MLGetWindowsDirectory(szFontsPath, ARRAYSIZE(szFontsPath)))
       {
           TCHAR  szFontFilePath[MAX_PATH];
           LPTSTR psz, pszT;

           MLPathCombine(szFontsPath, szFontsPath, szFonts);

           for (psz = szFontFile; *psz; psz = pszT + 1)
           {
               pszT = MLStrChr(psz, TEXT(','));
               if (pszT)
               {
                   *pszT=TEXT('\0');
               }

               MLPathCombine(szFontFilePath, szFontsPath, psz);
               if (AddFontResource(szFontFilePath))
               {
                   bAtLeastOneFontAdded = TRUE;
               }

               if (!pszT)
                  break;
           }
           if (!bAtLeastOneFontAdded)
               hr = E_FAIL;
       }
       else
           hr = E_FAIL;
   }

   // Clients will take care of WM_FONTCHANGE notification
   return hr;
}

int _LoadStringExA(
    HMODULE    hModule,
    UINT      wID,
    LPSTR     lpBuffer,            
    int       cchBufferMax,        
    WORD      wLangId)
{
    int iRet = 0;

    LPWSTR lpwStr = (LPWSTR) LocalAlloc(LPTR, cchBufferMax*sizeof(WCHAR));

    if (lpwStr)
    {
        iRet = _LoadStringExW(hModule, wID, lpwStr, cchBufferMax, wLangId);

        if (iRet)
            iRet = WideCharToMultiByte(CP_ACP, 0, lpwStr, iRet, lpBuffer, cchBufferMax, NULL, NULL);

        if(iRet >= cchBufferMax)
            iRet = cchBufferMax-1;

        lpBuffer[iRet] = 0;

        LocalFree(lpwStr);
    }

    return iRet;
}

// Extend LoadString() to to _LoadStringExW() to take LangId parameter
int _LoadStringExW(
    HMODULE    hModule,
    UINT      wID,
    LPWSTR    lpBuffer,            // Unicode buffer
    int       cchBufferMax,        // cch in Unicode buffer
    WORD      wLangId)
{
    HRSRC hResInfo;
    HANDLE hStringSeg;
    LPWSTR lpsz;
    int    cch;

    
    // Make sure the parms are valid.     
    if (lpBuffer == NULL || cchBufferMax == 0) 
    {
        return 0;
    }

    cch = 0;
    
    // String Tables are broken up into 16 string segments.  Find the segment
    // containing the string we are interested in.     
    if (hResInfo = FindResourceExW(hModule, (LPCWSTR)RT_STRING,
                                   (LPWSTR)IntToPtr(((USHORT)wID >> 4) + 1), wLangId)) 
    {        
        // Load that segment.        
        hStringSeg = LoadResource(hModule, hResInfo);
        
        // Lock the resource.        
        if (lpsz = (LPWSTR)LockResource(hStringSeg)) 
        {            
            // Move past the other strings in this segment.
            // (16 strings in a segment -> & 0x0F)             
            wID &= 0x0F;
            while (TRUE) 
            {
                cch = *((WORD *)lpsz++);   // PASCAL like string count
                                            // first UTCHAR is count if TCHARs
                if (wID-- == 0) break;
                lpsz += cch;                // Step to start if next string
             }
            
                            
            // Account for the NULL                
            cchBufferMax--;
                
            // Don't copy more than the max allowed.                
            if (cch > cchBufferMax)
                cch = cchBufferMax-1;
                
            // Copy the string into the buffer.                
            CopyMemory(lpBuffer, lpsz, cch*sizeof(WCHAR));

            // Attach Null terminator.
            lpBuffer[cch] = 0;

        }
    }

    return cch;
}


typedef struct tagCPLGID
{
    UINT    uiCodepage;
    TCHAR   szLgId[3];
    TCHAR   szLgIdHex[3]; // Darn it! NT should be persistent on presenting language group numbers
                          // It uses decimal string in INF and hex string in registry. 
                          // We have to add this field to save conversion, this is fine for a small array of data.  
} CPLGID;

const CPLGID CpLgId[] =
{
    {1252,  TEXT("1"),  TEXT("1")},  // WESTERN EUROPE
    {1250,  TEXT("2"),  TEXT("2")},  // CENTRAL EUROPE
    {1257,  TEXT("3"),  TEXT("3")},  // BALTIC
    {1253,  TEXT("4"),  TEXT("4")},  // GREEK
    {1251,  TEXT("5"),  TEXT("5")},  // CYRILLIC
    {1254,  TEXT("6"),  TEXT("6")},  // TURKISH
    {932,   TEXT("7"),  TEXT("7")},  // JAPANESE
    {949,   TEXT("8"),  TEXT("8")},  // KOREAN
    {950,   TEXT("9"),  TEXT("9")},  // TRADITIONAL CHINESE
    {936,  TEXT("10"),  TEXT("a")},  // SIMPLIFIED CHINESE
    {874,  TEXT("11"),  TEXT("b")},  // THAI
    {1255, TEXT("12"),  TEXT("c")},  // HEBREW
    {1256, TEXT("13"),  TEXT("d")},  // ARABIC
    {1258, TEXT("14"),  TEXT("e")},  // VIETNAMESE
    // ISCII encodings don't really have code pages or family code page
    // Code pages number are made up in W2K for conveniences, 
    // So, we have to list them all to install the same Indian language group
    {57002,TEXT("15"),  TEXT("f")},  // INDIAN 
    {57003,TEXT("15"),  TEXT("f")},  // INDIAN
    {57004,TEXT("15"),  TEXT("f")},  // INDIAN
    {57005,TEXT("15"),  TEXT("f")},  // INDIAN
    {57006,TEXT("15"),  TEXT("f")},  // INDIAN
    {57007,TEXT("15"),  TEXT("f")},  // INDIAN
    {57008,TEXT("15"),  TEXT("f")},  // INDIAN
    {57009,TEXT("15"),  TEXT("f")},  // INDIAN
    {57010,TEXT("15"),  TEXT("f")},  // INDIAN
    {57011,TEXT("15"),  TEXT("f")},  // INDIAN
};

typedef BOOL (WINAPI *PFNISNTADMIN) ( DWORD dwReserved, DWORD *lpdwReserved );

typedef INT (WINAPI *PFNSETUPPROMPTREBOOT) (
        HSPFILEQ FileQueue,  // optional, handle to a file queue
        HWND Owner,          // parent window of this dialog box
        BOOL ScanOnly        // optional, do not prompt user
        );

typedef PSP_FILE_CALLBACK PFNSETUPDEFAULTQUEUECALLBACK;

typedef VOID (WINAPI *PFNSETUPCLOSEINFFILE) (
    HINF InfHandle
    );

typedef BOOL (WINAPI *PFNSETUPINSTALLFROMINFSECTION) (
    HWND                Owner,
    HINF                InfHandle,
    LPCTSTR             SectionName,
    UINT                Flags,
    HKEY                RelativeKeyRoot,   OPTIONAL
    LPCTSTR             SourceRootPath,    OPTIONAL
    UINT                CopyFlags,
    PSP_FILE_CALLBACK   MsgHandler,
    PVOID               Context,
    HDEVINFO            DeviceInfoSet,     OPTIONAL
    PSP_DEVINFO_DATA    DeviceInfoData     OPTIONAL
    );

typedef HINF (WINAPI *PFNSETUPOPENINFFILE) (
    LPCTSTR FileName,
    LPCTSTR InfClass,    OPTIONAL
    DWORD   InfStyle,
    PUINT   ErrorLine    OPTIONAL
    );

typedef PVOID (WINAPI *PFNSETUPINITDEFAULTQUEUECALLBACK) (
    IN HWND OwnerWindow
    );

typedef BOOL (WINAPI *PFNSETUPOPENAPPENDINFFILE) (
  PCTSTR FileName, // optional, name of the file to append
  HINF InfHandle,  // handle of the file to append to
  PUINT ErrorLine  // optional, receives error information
);
 


HRESULT IsNTLangpackAvailable(UINT uiCP)
{
    HRESULT hr = S_FALSE;

    // check if there is a valid W2K language group
    for (int i=0; i < ARRAYSIZE(CpLgId); i++)
    {
        if (uiCP == CpLgId[i].uiCodepage)
        {
            hr = S_OK;
            break;
        }
    }

    // check if it is already installed, if so, we don't install it again
    if (S_OK == hr)
    {
        HKEY hkey;

        if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
                         REGSTR_PATH_NT5LPK_INSTALL,
                         0, KEY_READ, &hkey)) 
        {
            DWORD dwType = REG_SZ;
            TCHAR szLpkInstall[16] = {0};
            DWORD dwSize = sizeof(szLpkInstall);

            if (ERROR_SUCCESS == RegQueryValueEx(hkey, CpLgId[i].szLgIdHex, 0, 
                                                 &dwType, (LPBYTE)&szLpkInstall, &dwSize))
            {
                if (!lstrcmp(szLpkInstall, TEXT("1")))
                    hr = S_FALSE;
            }
            RegCloseKey(hkey);
        }
    }

    return hr;
}

HRESULT _InstallNT5Langpack(HWND hwnd, UINT uiCP)
{
    HRESULT             hr = E_FAIL;
    HINF                hIntlInf = NULL;
    TCHAR               szIntlInf[MAX_PATH];
    TCHAR               szIntlInfSection[MAX_PATH];
    PVOID               QueueContext = NULL;   

    HINSTANCE           hDllAdvPack = NULL;
    HINSTANCE           hDllSetupApi = NULL;

    PFNSETUPINSTALLFROMINFSECTION       lpfnSetupInstallFromInfSection = NULL;
    PFNSETUPCLOSEINFFILE                lpfnSetupCloseInfFile = NULL;
    PFNSETUPDEFAULTQUEUECALLBACK        lpfnSetupDefaultQueueCallback = NULL;
    PFNSETUPOPENINFFILE                 lpfnSetupOpenInfFile = NULL;
    PFNISNTADMIN                        lpfnIsNTAdmin = NULL;
    PFNSETUPINITDEFAULTQUEUECALLBACK    lpfnSetupInitDefaultQueueCallback = NULL;
    PFNSETUPOPENAPPENDINFFILE           lpfnSetupOpenAppendInfFile = NULL;

    for (int i=0; i < ARRAYSIZE(CpLgId); i++)
    {
        if (uiCP == CpLgId[i].uiCodepage)
        {
            _tcscpy(szIntlInfSection, TEXT("LG_INSTALL_"));
            _tcscat(szIntlInfSection, CpLgId[i].szLgId);
            break;
        }
    }

    if (i >= ARRAYSIZE(CpLgId))
    {
        goto LANGPACK_EXIT;
    }

    hDllAdvPack = LoadLibrary(TEXT("advpack.dll"));
    hDllSetupApi = LoadLibrary(TEXT("setupapi.dll"));

    if (!hDllAdvPack || !hDllSetupApi)
    {
        goto LANGPACK_EXIT;
    }

    lpfnIsNTAdmin = (PFNISNTADMIN) GetProcAddress( hDllAdvPack, "IsNTAdmin");
    lpfnSetupCloseInfFile = (PFNSETUPCLOSEINFFILE) GetProcAddress( hDllSetupApi, "SetupCloseInfFile");
    lpfnSetupInitDefaultQueueCallback = (PFNSETUPINITDEFAULTQUEUECALLBACK) GetProcAddress(hDllSetupApi, "SetupInitDefaultQueueCallback");
#ifdef UNICODE
    lpfnSetupOpenInfFile = (PFNSETUPOPENINFFILE) GetProcAddress( hDllSetupApi, "SetupOpenInfFileW"));
    lpfnSetupInstallFromInfSection = (PFNSETUPINSTALLFROMINFSECTION) GetProcAddress( hDllSetupApi, "SetupInstallFromInfSectionW");
    lpfnSetupDefaultQueueCallback = (PFNSETUPDEFAULTQUEUECALLBACK) GetProcAddress(hDllSetupApi, "SetupDefaultQueueCallbackW");
    lpfnSetupOpenAppendInfFile = (PFNSETUPDEFAULTQUEUECALLBACK) GetProcAddress(hDllSetupApi, "SetupOpenAppendInfFileW");
#else
    lpfnSetupOpenInfFile = (PFNSETUPOPENINFFILE) GetProcAddress( hDllSetupApi, "SetupOpenInfFileA");
    lpfnSetupInstallFromInfSection = (PFNSETUPINSTALLFROMINFSECTION) GetProcAddress( hDllSetupApi, "SetupInstallFromInfSectionA");
    lpfnSetupDefaultQueueCallback = (PFNSETUPDEFAULTQUEUECALLBACK) GetProcAddress(hDllSetupApi, "SetupDefaultQueueCallbackA");
    lpfnSetupOpenAppendInfFile = (PFNSETUPOPENAPPENDINFFILE) GetProcAddress(hDllSetupApi, "SetupOpenAppendInfFileA");
#endif

    if (!lpfnIsNTAdmin || !lpfnSetupOpenInfFile || !lpfnSetupCloseInfFile || !lpfnSetupDefaultQueueCallback 
        || !lpfnSetupInstallFromInfSection || !lpfnSetupInitDefaultQueueCallback || !lpfnSetupOpenAppendInfFile)
    {
        goto LANGPACK_EXIT;
    }

    if (!lpfnIsNTAdmin(0, NULL))
    {
        WCHAR wszLangInstall[MAX_PATH];
        WCHAR wszNoAdmin[1024];
        LANGID LangId = GetNT5UILanguage();

        // Fall back to English (US) if we don't have a specific language resource
        if (!_LoadStringExW(g_hInst, IDS_LANGPACK_INSTALL, wszLangInstall, ARRAYSIZE(wszLangInstall), LangId) ||
            !_LoadStringExW(g_hInst, IDS_NO_ADMIN, wszNoAdmin, ARRAYSIZE(wszNoAdmin), LangId))
        {
            LangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
            _LoadStringExW(g_hInst, IDS_LANGPACK_INSTALL, wszLangInstall, ARRAYSIZE(wszLangInstall), LangId);
            _LoadStringExW(g_hInst, IDS_NO_ADMIN, wszNoAdmin, ARRAYSIZE(wszNoAdmin), LangId);
        }
        ULONG_PTR uCookie = 0;
        SHActivateContext(&uCookie);
        MessageBoxW(hwnd, wszNoAdmin, wszLangInstall, MB_OK);
        if (uCookie)
        {
            SHDeactivateContext(uCookie);
        }
        goto LANGPACK_EXIT;
    }

    QueueContext = lpfnSetupInitDefaultQueueCallback(hwnd);

    MLGetWindowsDirectory(szIntlInf, MAX_PATH);
    MLPathCombine(szIntlInf, szIntlInf, TEXT("inf\\intl.inf"));

    hIntlInf = lpfnSetupOpenInfFile(szIntlInf, NULL, INF_STYLE_WIN4, NULL);

    if (!lpfnSetupOpenAppendInfFile(NULL, hIntlInf, NULL))
    {
        lpfnSetupCloseInfFile(hIntlInf);
        goto LANGPACK_EXIT;
    }

    if (INVALID_HANDLE_VALUE != hIntlInf)
    {
        if (lpfnSetupInstallFromInfSection( hwnd,
                                    hIntlInf,
                                    szIntlInfSection,
                                    SPINST_FILES,
                                    NULL,
                                    NULL,
                                    SP_COPY_NEWER,
                                    lpfnSetupDefaultQueueCallback,
                                    QueueContext,
                                    NULL,
                                    NULL ))
        {
            if (lpfnSetupInstallFromInfSection( hwnd,
                                    hIntlInf,
                                    szIntlInfSection,
                                    SPINST_ALL & ~SPINST_FILES,
                                    NULL,
                                    NULL,
                                    0,
                                    lpfnSetupDefaultQueueCallback,
                                    QueueContext,
                                    NULL,
                                    NULL ))
            {
                hr = S_OK;
            }
        }
    
        lpfnSetupCloseInfFile(hIntlInf);
    }

LANGPACK_EXIT:

    if(hDllSetupApi)
        FreeLibrary(hDllSetupApi);
    if(hDllAdvPack)
        FreeLibrary(hDllAdvPack);
    
    //
    // Bug #289905, On Whistler, language pack will be installed with a groups of languages,
    // So, MLang need to validate codepage and fonts for all languages in the same group
    // After intl.cpl is modified for font validation, we'll remove this hardcoded language group.
    //
    if (hr == S_OK)
    {
        // This has to match Whistler language group
        UINT uiDBCSCps[] = {932, 936, 949, 950, 0};
        UINT uiCompCps[] = {874, 1255, 1256, 1258, 0};
        UINT uiOtherCps[] = {uiCP, 0};
            
        UINT *pCps = uiOtherCps;
        
        if (MLIsOS(OS_WHISTLERORGREATER))
        {
            if (IS_DBCSCODEPAGE(uiCP))
                pCps = uiDBCSCps;
            else if (IS_COMPLEXSCRIPT_CODEPAGE(uiCP))
                pCps = uiCompCps;
        }               
            
        while (*pCps)
        {
            hr = _ValidateCPInfo(*pCps);
            if (SUCCEEDED(hr))
            {                
                _AddFontForCP(*pCps);
            }
            pCps++;
        }
    }    

    return hr;
}

BOOL _IsValidCodePage(UINT uiCodePage)
{
    BOOL bRet;

    if (50001 == uiCodePage)
    {
        HANDLE hFile = NULL;
        CHAR szFileName[MAX_PATH];
        LPSTR psz;
        
        if (GetModuleFileNameA(g_hInst, szFileName, ARRAYSIZE(szFileName)) == 0)
            return GetLastError();
        
        if ( (psz = strrchr (szFileName, '\\')) != NULL ||
            (psz = strrchr (szFileName, ':')) != NULL )
        {
            *++psz = 0;
        }
        else
            *szFileName = 0;
        
        strcat (szFileName, DETECTION_DATA_FILENAME);        

        if (INVALID_HANDLE_VALUE == (hFile = CreateFileA(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)))
        {
            bRet = FALSE;
        }
        else
        {
            bRet = TRUE;
            CloseHandle(hFile);
        }
    }
    else
    {
        bRet = IsValidCodePage(uiCodePage);
    }
    return bRet;
}

WCHAR *MLStrCpyNW(WCHAR *strDest, const WCHAR *strSource, int nCount)
{
    return wcsncpy(strDest, strSource, nCount);
}

WCHAR *MLStrCpyW(WCHAR *strDest, const WCHAR *strSource)
{
    return wcscpy(strDest, strSource);
}

LPTSTR MLStrChr( const TCHAR *string, int c )
{
    return _tcschr(string, c);
}

LPTSTR MLStrCpyN(LPTSTR strDest, const LPTSTR strSource, UINT nCount)
{
    return _tcsncpy(strDest, strSource, nCount);
}

LPTSTR MLStrStr(const LPTSTR Str, const LPTSTR subStr)
{
    return _tcsstr(Str, subStr);
}

LPTSTR MLPathCombine(LPTSTR szPath, LPTSTR szPath1, LPTSTR szPath2)
{
    int len;

    if (!szPath) 
        return NULL;

    if (szPath != szPath1)
    {
        _tcscpy(szPath, szPath1);
    }

    len = _tcslen(szPath1);

    if (szPath[len-1] != TEXT('\\'))
    {
        szPath[len++] = TEXT('\\');
        szPath[len] = 0;
    }

    return _tcscat(szPath, szPath2);
}

DWORD HexToNum(LPTSTR lpsz)
{
    DWORD   dw = 0L;
    TCHAR   c;

    while(*lpsz)
    {
        c = *lpsz++;

        if (c >= TEXT('A') && c <= TEXT('F'))
        {
            c -= TEXT('A') - 0xa;
        }
        else if (c >= TEXT('0') && c <= TEXT('9'))
        {
            c -= TEXT('0');
        }
        else if (c >= TEXT('a') && c <= TEXT('f'))
        {
            c -= TEXT('a') - 0xa;
        }
        else
        {
            break;
        }
        dw *= 0x10;
        dw += c;
    }
    return(dw);
}


// Following code is borrowed from shlwapi
BOOL AnsiFromUnicode(
     LPSTR * ppszAnsi,
     LPCWSTR pwszWide,        // NULL to clean up
     LPSTR pszBuf,
     int cchBuf)
{
    BOOL bRet;

    // Convert the string?
    if (pwszWide)
    {
        // Yes; determine the converted string length
        int cch;
        LPSTR psz;

        cch = WideCharToMultiByte(CP_ACP, 0, pwszWide, -1, NULL, 0, NULL, NULL);

        // String too big, or is there no buffer?
        if (cch > cchBuf || NULL == pszBuf)
        {
            // Yes; allocate space
            cchBuf = cch + 1;
            psz = (LPSTR)LocalAlloc(LPTR, CbFromCchA(cchBuf));
        }
        else
        {
            // No; use the provided buffer
            ASSERT(pszBuf);
            psz = pszBuf;
        }

        if (psz)
        {
            // Convert the string
            cch = WideCharToMultiByte(CP_ACP, 0, pwszWide, -1, psz, cchBuf, NULL, NULL);
            bRet = (0 < cch);
        }
        else
        {
            bRet = FALSE;
        }

        *ppszAnsi = psz;
    }
    else
    {
        // No; was this buffer allocated?
        if (*ppszAnsi && pszBuf != *ppszAnsi)
        {
            // Yes; clean up
            LocalFree((HLOCAL)*ppszAnsi);
            *ppszAnsi = NULL;
        }
        bRet = TRUE;
    }

    return bRet;
}

int MLStrCmpI(IN LPCTSTR pwsz1, IN LPCTSTR pwsz2)
{
#ifdef UNICODE
    return MLStrCmpIW(pwsz1, pwsz2);
#else
    return lstrcmpiA(pwsz1, pwsz2);
#endif
}

int MLStrCmpNI(IN LPCTSTR pstr1, IN LPCTSTR pstr2, IN int count)
{
#ifdef UNICODE
    return MLStrCmpNIW(pstr1, pstr2, count);
#else
    return MLStrCmpNIA(pstr1, pstr2, count);
#endif
}

int MLStrCmpIW(
    IN LPCWSTR pwsz1,
    IN LPCWSTR pwsz2)
{
    int iRet;
    
    ASSERT(IS_VALID_STRING_PTRW(pwsz1, -1));
    ASSERT(IS_VALID_STRING_PTRW(pwsz2, -1));
    
    if (g_bIsNT)
    {        
        iRet = CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, pwsz1, -1, pwsz2, -1) - CSTR_EQUAL;
    }
    else
    {
        CHAR sz1[512];
        CHAR sz2[512];
        LPSTR psz1;
        LPSTR psz2;

        iRet = -1;      // arbitrary on failure

        if (pwsz1 && pwsz2)
        {
            if (AnsiFromUnicode(&psz1, pwsz1, sz1, SIZECHARS(sz1)))
            {
                if (AnsiFromUnicode(&psz2, pwsz2, sz2, SIZECHARS(sz2)))
                {
                    iRet = CompareStringA(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, psz1, -1, psz2, -1) - CSTR_EQUAL;
                    AnsiFromUnicode(&psz2, NULL, sz2, 0);       // Free
                }
                AnsiFromUnicode(&psz1, NULL, sz1, 0);       // Free
            }
        }
    }

    return iRet;
}

#ifdef UNIX

#ifdef BIG_ENDIAN
#define READNATIVEWORD(x) MAKEWORD(*(char*)(x), *(char*)((char*)(x) + 1))
#else 
#define READNATIVEWORD(x) MAKEWORD(*(char*)((char*)(x) + 1), *(char*)(x))
#endif

#else

#define READNATIVEWORD(x) (*(UNALIGNED WORD *)x)

#endif

int WINAPI MLStrToIntA(
    LPCSTR lpSrc)
{
    int n = 0;
    BOOL bNeg = FALSE;

    if (*lpSrc == '-') {
        bNeg = TRUE;
        lpSrc++;
    }

    while (IS_DIGITA(*lpSrc)) {
        n *= 10;
        n += *lpSrc - '0';
        lpSrc++;
    }
    return bNeg ? -n : n;
}


int WINAPI MLStrToIntW(
    LPCWSTR lpSrc)
{
    int n = 0;
    BOOL bNeg = FALSE;

    if (*lpSrc == L'-') {
        bNeg = TRUE;
        lpSrc++;
    }

    while (IS_DIGITW(*lpSrc)) {
        n *= 10;
        n += *lpSrc - L'0';
        lpSrc++;
    }
    return bNeg ? -n : n;
}

/*
 * ChrCmpI - Case insensitive character comparison for DBCS
 * Assumes   w1, wMatch are characters to be compared;
 *           HIBYTE of wMatch is 0 if not a DBC
 * Return    FALSE if match, TRUE if not
 */
BOOL ChrCmpIA(WORD w1, WORD wMatch)
{
    char sz1[3], sz2[3];

    if (IsDBCSLeadByte(sz1[0] = LOBYTE(w1)))
    {
        sz1[1] = HIBYTE(w1);
        sz1[2] = '\0';
    }
    else
        sz1[1] = '\0';

#if defined(MWBIG_ENDIAN)
    sz2[0] = LOBYTE(wMatch);
    sz2[1] = HIBYTE(wMatch);
#else
    *(WORD *)sz2 = wMatch;
#endif
    sz2[2] = '\0';
    return lstrcmpiA(sz1, sz2);
}

BOOL ChrCmpIW(WCHAR w1, WCHAR wMatch)
{
    WCHAR sz1[2], sz2[2];

    sz1[0] = w1;
    sz1[1] = '\0';
    sz2[0] = wMatch;
    sz2[1] = '\0';

    return lstrcmpiW(sz1, sz2);
}


/*
 * StrCmpNI     - Compare n bytes, case insensitive
 *
 * returns   See lstrcmpi return values.
 */
int MLStrCmpNIA(LPCSTR lpStr1, LPCSTR lpStr2, int nChar)
{
    int i;
    LPCSTR lpszEnd = lpStr1 + nChar;

    for ( ; (lpszEnd > lpStr1) && (*lpStr1 || *lpStr2); (lpStr1 = AnsiNext(lpStr1)), (lpStr2 = AnsiNext(lpStr2))) {
        WORD w1;
        WORD w2;

        // If either pointer is at the null terminator already,
        // we want to copy just one byte to make sure we don't read 
        // past the buffer (might be at a page boundary).

        w1 = (*lpStr1) ? READNATIVEWORD(lpStr1) : 0;
        w2 = (UINT)(IsDBCSLeadByte(*lpStr2)) ? (UINT)READNATIVEWORD(lpStr2) : (WORD)(BYTE)(*lpStr2);

        i = ChrCmpIA(w1, w2);
        if (i)
            return i;
    }
    return 0;
}

int MLStrCmpNIW(LPCWSTR lpStr1, LPCWSTR lpStr2, int nChar)
{
    int i;
    LPCWSTR lpszEnd = lpStr1 + nChar;

    for ( ; (lpszEnd > lpStr1) && (*lpStr1 || *lpStr2); lpStr1++, lpStr2++) {
        i = ChrCmpIW(*lpStr1, *lpStr2);
        if (i) {
            return i;
        }
    }
    return 0;
}


HRESULT _IsCodePageInstallable(UINT uiCodePage)
{
    MIMECPINFO cpInfo;
    UINT       uiFamCp;
    HRESULT    hr;

    if (NULL != g_pMimeDatabase)
        hr = g_pMimeDatabase->GetCodePageInfo(uiCodePage, 0x409, &cpInfo);
    else
        hr = E_OUTOFMEMORY;

    if (FAILED(hr))
        return E_INVALIDARG;

    uiFamCp = cpInfo.uiFamilyCodePage;
    if (g_bIsNT5)
    {
        hr = IsNTLangpackAvailable(uiFamCp);
    }
    else
    {
        CLSID      clsid;
        // clsid is just used for place holder
        hr = _GetJITClsIDForCodePage(uiFamCp, &clsid);
    }
    return hr;
}

//
// CML2 specific utilities
//

//
// CMultiLanguage2::EnsureIEStatus()
//
// ensures CML2::m_pIEStat
//
HRESULT CMultiLanguage2::EnsureIEStatus(void)
{
    HRESULT hr = S_OK;
    // initialize IE status cache
    if (!m_pIEStat)
    {
        m_pIEStat = new CIEStatus();

        if (m_pIEStat)
        {
            hr = m_pIEStat->Init();
        }
    }

    return hr;
}

//
// CIEStatus::Init()
//
// initializes the IE status;
//
HRESULT CMultiLanguage2::CIEStatus::Init(void)
{
    HRESULT hr = S_OK;
    HKEY hkey;
    // Get JIT satus
    if (RegOpenKeyEx(HKEY_CURRENT_USER, 
                      REGSTR_PATH_MAIN,
                     0, KEY_READ, &hkey) == ERROR_SUCCESS) 
    {
        DWORD dwVal, dwType;
        DWORD dwSize = sizeof(dwVal);
        RegQueryValueEx(hkey, TEXT("nojitsetup"), 0, &dwType, (LPBYTE)&dwVal, &dwSize);
        RegCloseKey(hkey);

        if (dwType == REG_DWORD && dwSize == sizeof(dwVal))
        {
            if (dwVal > 0 ) 
                _IEFlags.fJITDisabled = TRUE;
            else
                _IEFlags.fJITDisabled = FALSE;
        }
    }
    else
        hr = E_FAIL;
    // any other status to get initialized
    // ...
    
    return hr;
}

#define NT5LPK_DLG_STRING "MLang.NT5LpkDlg"

//
// LangpackDlgProc()
//
// Message handler for the NT5 langpack dialog.
//
INT_PTR CALLBACK LangpackDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{

    ASSERT(g_bIsNT5);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            HWND hwndCheckBox;            
            RECT rc1, rc2;
            MIMECPINFO cpInfo;

            SetProp(hDlg, NT5LPK_DLG_STRING, (HANDLE)lParam);
    
            if ((NULL != g_pMimeDatabase) &&
                SUCCEEDED(g_pMimeDatabase->GetCodePageInfo(HIWORD(lParam), GetNT5UILanguage(), &cpInfo)))
            {
                for (int i=0; i<MAX_MIMECP_NAME && cpInfo.wszDescription[i]; i++)
                {
                    if (cpInfo.wszDescription[i] == L'(')
                    {
                        cpInfo.wszDescription[i] = 0;
                        break;
                    }
                }
                // Use W version regardlessly since we're only running this on NT5             
                SetDlgItemTextW(hDlg, IDC_STATIC_LANG, cpInfo.wszDescription);
            }
            
            // Center the dialog in the area of parent window
            if (GetWindowRect(GetParent(hDlg), &rc1) && GetWindowRect(hDlg, &rc2))
            {
                MoveWindow(hDlg, (rc1.right+rc2.left+rc1.left-rc2.right)/2, (rc1.bottom+rc2.top+rc1.top-rc2.bottom)/2, rc2.right-rc2.left, rc2.bottom-rc2.top, FALSE);
            }            

            hwndCheckBox = GetDlgItem(hDlg, IDC_CHECK_LPK);

            // Set CheckBox state according to current registry setting
            PostMessage(hwndCheckBox, BM_SETCHECK, LOWORD(lParam)? BST_UNCHECKED:BST_CHECKED, 0);
            break;

        case WM_COMMAND:
            if (LOWORD(wParam) != IDC_CHECK_LPK)
            {
                HKEY hkey;
                DWORD dwInstallOut = (BST_CHECKED == SendMessage(GetDlgItem(hDlg, IDC_CHECK_LPK), BM_GETCHECK, 0, 0)? 0:1);
                DWORD dwInstallIn = LOWORD(GetProp(hDlg, NT5LPK_DLG_STRING));
                
                if ((dwInstallOut != dwInstallIn) &&
                    ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, 
                         REGSTR_PATH_INTERNATIONAL,
                         NULL, KEY_READ|KEY_SET_VALUE, &hkey)) 
                {
                    DWORD dwType = REG_DWORD;
                    DWORD dwSize = sizeof(DWORD);
                    RegSetValueEx(hkey, REG_KEY_NT5LPK, 0, REG_DWORD, (LPBYTE)&dwInstallOut, sizeof(dwInstallOut));
                    RegCloseKey(hkey);
                }                
                EndDialog(hDlg, LOWORD(wParam) == IDOK? 1: 0);
            }
            break;

        case WM_HELP:
            // Do we need help file for this simply dialog?
            // If needed, we can always add it at later time.
            break;


        default:
            return FALSE;
    }
    return TRUE;
}

// To get real Windows directory on Terminal Server, 
// Instead of using internal Kernel32 API GetSystemWindowsDirectory with LoadLibrary/GetProcAddress,
// We cast GetSystemDirectory return to Windows directory
UINT MLGetWindowsDirectory(
    LPTSTR lpBuffer,    // address of buffer for Windows directory
    UINT uSize          // size of directory buffer
    )
{
    UINT uLen;

    if (g_bIsNT)
    {        
        if (uLen = GetSystemDirectory(lpBuffer, uSize))
        {        
            if (lpBuffer[uLen-1] == TEXT('\\'))
                uLen--;

            while (uLen-- > 0)
            {
                if (lpBuffer[uLen] == TEXT('\\'))
                {
                    lpBuffer[uLen] = NULL;                
                    break;
                }
            }
        }
    }
    else    
        uLen = GetWindowsDirectory(lpBuffer, uSize);

    return uLen;
}

// To speed up basic ANSI string compare,
// We avoid using lstrcmpi in LOW-ASCII case
int LowAsciiStrCmpNIA(LPCSTR  lpstr1, LPCSTR lpstr2, int count)
{
    int delta;

    while (count-- > 0)
    {        
        delta = *lpstr1 - *lpstr2;
        if (delta && 
            !(IS_CHARA(*lpstr1) && IS_CHARA(*lpstr2) && (delta == 0x20 || delta == -0x20)))
            return delta;
        lpstr1++;
        lpstr2++;
    }

    return 0;
}

//
//  GetNT5UILanguage(void)
//
LANGID GetNT5UILanguage(void)
{
    if (g_bIsNT5)
    {
        static LANGID (CALLBACK* pfnGetUserDefaultUILanguage)(void) = NULL;

        if (pfnGetUserDefaultUILanguage == NULL)
        {
            HMODULE hmod = GetModuleHandle(TEXT("KERNEL32"));

            if (hmod)
                pfnGetUserDefaultUILanguage = (LANGID (CALLBACK*)(void))GetProcAddress(hmod, "GetUserDefaultUILanguage");
        }
        if (pfnGetUserDefaultUILanguage)
            return pfnGetUserDefaultUILanguage();
    }

    return 0;
}

// Special characters that we should filter out
WCHAR wszBestFit[] = {0x00A6, 0x00A9, 0x00AB, 0x00AD, 0x00AE, 0x00B7, 0x00BB, 0x02C6, 0x02DC, 0x2013, 
                      0x2014, 0x2018, 0x2019, 0x201A, 0x201C,0x201D, 0x201E, 0x2022, 0x2026, 0x2039, 0x203A,0x2122, 0x0000};

DWORD OutBoundDetectPreScan(LPWSTR lpWideCharStr, UINT cchWideChar, WCHAR *pwszCopy, WCHAR *lpBestFitChar)
{
    DWORD dwRet = 0;
    WCHAR *lpStart;

    if (!lpBestFitChar)
        lpBestFitChar = wszBestFit;

    lpStart = lpBestFitChar;

    if (pwszCopy)
    {
        MLStrCpyNW(pwszCopy, lpWideCharStr, cchWideChar);
        lpWideCharStr = pwszCopy;
    }     

    if (lpWideCharStr)
    {
        for (UINT ui=0; ui<cchWideChar; ui++)
        {
            if (IS_CJK_CHAR(*lpWideCharStr))
                dwRet |= FS_CJK;
            else if (IS_HINDI_CHAR(*lpWideCharStr))
                dwRet |= FS_HINDI;     
            else if (IS_PUA_CHAR(*lpWideCharStr))
                dwRet |= FS_PUA;
            else if (pwszCopy)
            {
                while (*lpBestFitChar)
                {
                    if (*lpWideCharStr == *lpBestFitChar)
                        *lpWideCharStr = 0x20;
                    lpBestFitChar++;
                }
                lpBestFitChar = lpStart;
            }
            lpWideCharStr++;
        }
    }
    
    return dwRet;
}

//
// Whistler bug #90433 WEIWU 07/06/00
//
// Outlook has a bug its RTFHTML, this component doesn't CoInitialize/
// CoUninitialize COM, but, it uses MLang COM services, it depends on 
// other threads (components) to deal with COM, when COM is unloaded by those threads. 
// Invoking the interface pointer causes AV. RTFHTML should CoInit/CoUnInit by 
// itself.
//
// The reason for this to be working before is - MLang was depending on ATL 
// for objects management, ATL create heap for MLang object allocations, MLang 
// notifies ATL to destroy the heap at DLL detach time and RTFHTML's IsBadReadPtr
// () caught the invalid pointer. Now in Whistler, MLang includes crtfree.h 
// which overwrites ATL memory management functions (same as other shell 
// components), so, nothing is allocated from the process heap, this is fine for 
// MLang since it assumes that clients use COM correctly. 
//
// Now, we add this function to check Outlook version, if it is the buggy Outlook,
// We'll load mlang.dll itself DllGetClassObject() to increase the Dll ref count
//
BOOL NeedToLoadMLangForOutlook(void)
{
    TCHAR szModulePath[MAX_PATH];
    CHAR chBuffer[4096];
    DWORD dwHandle;
    VS_FIXEDFILEINFO * pszVersion;
    static BOOL bMLangLoaded = FALSE;

    if (!bMLangLoaded)
    {
        if (GetModuleFileName(GetModuleHandle(NULL), szModulePath, ARRAYSIZE(szModulePath)))
        {
            if (MLStrStr(szModulePath, TEXT("OUTLOOK.EXE")))
            {
                UINT cb = GetFileVersionInfoSize(szModulePath, &dwHandle);

                if (cb <= sizeof(chBuffer) &&
                    GetFileVersionInfo(szModulePath, dwHandle, sizeof(chBuffer), (void *)chBuffer) &&
                    VerQueryValue((void *)chBuffer, TEXT("\\"), (void **) &pszVersion, &cb) &&
                    (HIWORD(pszVersion->dwProductVersionMS) <= 0x09))
                {
                    bMLangLoaded = TRUE;
                    return TRUE;
                }
            }
        }
    }

    return FALSE;
}

//
// staticIsOS() doesn't support newer Whistler OS flags.
// Borrow code from shlwapi - IsOS()
//
BOOL MLIsOS(DWORD dwOS)
{
    BOOL bRet;
    static OSVERSIONINFOEXA s_osvi = {0};
    static BOOL s_bVersionCached = FALSE;

    if (!s_bVersionCached)
    {
        s_bVersionCached = TRUE;
        s_osvi.dwOSVersionInfoSize = sizeof(s_osvi);
        if (!GetVersionExA((OSVERSIONINFOA*)&s_osvi))
        {
            // If it failed, it must be a down level platform
            s_osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
            GetVersionExA((OSVERSIONINFOA*)&s_osvi);
        }
    }

    switch (dwOS)
    {
    case OS_TERMINALREMOTEADMIN:
        // this checks to see if TS has been installed in the "Remote Administration" mode. This is
        // the default for server installs on win2k and whistler
        bRet = ((VER_SUITE_TERMINAL & s_osvi.wSuiteMask) &&
                (VER_SUITE_SINGLEUSERTS & s_osvi.wSuiteMask));
        break;

    case 4: // used to be OS_NT5, is the same as OS_WIN2000ORGREATER so use that instead
    case OS_WIN2000ORGREATER:
        bRet = (VER_PLATFORM_WIN32_NT == s_osvi.dwPlatformId &&
                s_osvi.dwMajorVersion >= 5);
        break;

    // NOTE: The flags in this section are bogus and SHOULD NOT BE USED (but downlevel shell32 uses them, so don't RIP there)
    case OS_WIN2000PRO:
        RIPMSG(!MLIsOS(OS_WHISTLERORGREATER), "IsOS: use OS_PROFESSIONAL instead of OS_WIN2000PRO !");
        bRet = (VER_NT_WORKSTATION == s_osvi.wProductType &&
                s_osvi.dwMajorVersion == 5);
        break;
    case OS_WIN2000ADVSERVER:
        RIPMSG(!MLIsOS(OS_WHISTLERORGREATER), "IsOS: use OS_ADVSERVER instead of OS_WIN2000ADVSERVER !");
        bRet = ((VER_NT_SERVER == s_osvi.wProductType ||
                VER_NT_DOMAIN_CONTROLLER == s_osvi.wProductType) &&
                s_osvi.dwMajorVersion == 5 &&
                (VER_SUITE_ENTERPRISE & s_osvi.wSuiteMask) &&
                !(VER_SUITE_DATACENTER & s_osvi.wSuiteMask));
        break;
    case OS_WIN2000DATACENTER:
        RIPMSG(!MLIsOS(OS_WHISTLERORGREATER), "IsOS: use OS_DATACENTER instead of OS_WIN2000DATACENTER !");
        bRet = ((VER_NT_SERVER == s_osvi.wProductType ||
                VER_NT_DOMAIN_CONTROLLER == s_osvi.wProductType) &&
                s_osvi.dwMajorVersion == 5 &&
                (VER_SUITE_DATACENTER & s_osvi.wSuiteMask));
        break;
    case OS_WIN2000SERVER:
        RIPMSG(!MLIsOS(OS_WHISTLERORGREATER), "IsOS: use OS_SERVER instead of OS_WIN2000SERVER !");
        bRet = ((VER_NT_SERVER == s_osvi.wProductType ||
                VER_NT_DOMAIN_CONTROLLER == s_osvi.wProductType) &&
                !(VER_SUITE_DATACENTER & s_osvi.wSuiteMask) && 
                !(VER_SUITE_ENTERPRISE & s_osvi.wSuiteMask)  && 
                s_osvi.dwMajorVersion == 5);
        break;
    // END bogus Flags

    case OS_EMBEDDED:
        bRet = (VER_SUITE_EMBEDDEDNT & s_osvi.wSuiteMask);
        break;

    case OS_WINDOWS:
        bRet = (VER_PLATFORM_WIN32_WINDOWS == s_osvi.dwPlatformId);
        break;

    case OS_NT:
        bRet = (VER_PLATFORM_WIN32_NT == s_osvi.dwPlatformId);
        break;


    case OS_WIN98ORGREATER:
        bRet = (VER_PLATFORM_WIN32_WINDOWS == s_osvi.dwPlatformId &&
                (s_osvi.dwMajorVersion > 4 || 
                 s_osvi.dwMajorVersion == 4 && s_osvi.dwMinorVersion >= 10));
        break;

    case OS_WIN98_GOLD:
        bRet = (VER_PLATFORM_WIN32_WINDOWS == s_osvi.dwPlatformId &&
                s_osvi.dwMajorVersion == 4 && s_osvi.dwMinorVersion == 10 &&
                LOWORD(s_osvi.dwBuildNumber) == 1998);
        break;


    case OS_MILLENNIUMORGREATER:
        bRet = (VER_PLATFORM_WIN32_WINDOWS == s_osvi.dwPlatformId &&
                ((s_osvi.dwMajorVersion == 4 && s_osvi.dwMinorVersion >= 90) ||
                s_osvi.dwMajorVersion > 4));
        break;


    case OS_WHISTLERORGREATER:
        bRet = (VER_PLATFORM_WIN32_NT == s_osvi.dwPlatformId &&
                ((s_osvi.dwMajorVersion > 5) ||
                (s_osvi.dwMajorVersion == 5 && (s_osvi.dwMinorVersion > 0 ||
                (s_osvi.dwMinorVersion == 0 && LOWORD(s_osvi.dwBuildNumber) > 2195)))));
        break;

    case OS_PERSONAL:
        bRet = (VER_PLATFORM_WIN32_NT == s_osvi.dwPlatformId &&
                (VER_SUITE_PERSONAL & s_osvi.wSuiteMask));
        break;


    default:
        bRet = FALSE;
        break;
    }

    return bRet;
}