// stdafx.cpp : source file that includes just the standard includes
//  protovr.pch will be the pre-compiled header
//  stdafx.obj will contain the pre-compiled type information
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include "aboutdlg.h"
#include "setupdlg.h"
#include "adetect.h"


CPUDETECT CExeModule::m_cd;

LPTSTR CExeModule::m_szOsInfo   = NULL;
LPTSTR CExeModule::m_szCpuInfo  = NULL;

CExeModule::~CExeModule()
{
    DELETE_PTR(m_szOsInfo);
    DELETE_PTR(m_szCpuInfo);
}

HRESULT CExeModule::LoadPreferences()
{
    TCHAR data[32];
    DWORD size = sizeof(data);

    CProfile cfg(_T(VR_PROFILE));

    cfg.Open(_T("common"));
    cfg.QueryValue(m_IsSplash, _T("ShowSplash"));
    cfg.QueryValue(data, _T("Language"), &size);

    m_IsResource = 0;
    ZeroMemory(m_res, sizeof(m_res));

    CString strLangName(data);
    CString str;

    CAboutDlg dlg(true);            // show language selector

    if (m_IsSplash)
    {
        if (strLangName.CompareNoCase(_T("default")))
        {
            str.Format(_T("%s.dll"), strLangName);
            HMODULE hLib = ::LoadLibrary(str);

            if (hLib)
            {
                m_hInstResource = (HINSTANCE)hLib;
                m_IsResource = 1;
            }
        }

        dlg.DoModal();

        m_IsSplash  = dlg.m_IsSplash;
        strLangName = dlg.m_ll[dlg.m_LangNum];

        cfg.SetValue(m_IsSplash, _T("ShowSplash"));
        cfg.SetValue(strLangName, _T("Language"));

        if (m_IsResource && !strLangName.CompareNoCase(_T("default")))
        {
            // switch back to default
            ::FreeLibrary(m_hInstResource);
            m_hInstResource = GetModuleInstance();
            m_IsResource = 0;
        }
    }
    
    // request current language support and catch appropriate resource dll
    // otherwise  module internal resources are used.
    //
    if (!m_IsResource )
    {
        if (strLangName.CompareNoCase(_T("default")))
        {
            str.Format(_T("%s.dll"), strLangName);
            HMODULE hLib = ::LoadLibrary(str);

            if (hLib)
            {
                m_hInstResource = (HINSTANCE)hLib;
                m_IsResource = 1;
            }
        }
    }

    MEMORYSTATUS ms;
    UINT cpuclk, vendor, cache;

    DisplaySystemVersion();
    
    GetCpuCaps(CPU_NAME_STRING, &vendor);
    GetCpuCaps(CPU_L2_CACHE_SIZE, &cache);
    GetCpuFrequency(&cpuclk);

    str.Format(IDS_PATTERN_CPU, vendor, (cpuclk + 500000)/1000000, cache);

    ATLTRY(m_szCpuInfo = new TCHAR[str.GetLength() + 1]);
    CHECK_NULL(m_szCpuInfo, E_OUTOFMEMORY);

    _tcscpy(m_szCpuInfo, str);
    
    GetCpuCaps(HAS_MMX, &m_IsMMX);
    GetCpuCaps(HAS_EMMX, &m_IsEMMX);
    GetCpuCaps(HAS_SSE, &m_IsSSE);
    GetCpuCaps(HAS_SSE2, &m_IsSSE2);

    ::GlobalMemoryStatus(&ms);

    m_MemTotalPhys = ms.dwTotalPhys / 1024;
    m_MemAvailPhys = ms.dwAvailPhys / 1024;

#if defined(__USE_COM__)
    //HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    HRESULT hr = ::CoInitialize(NULL);
    ATLASSERT(SUCCEEDED(hr));
#endif

    if (FAILED(hr = GetDisplayModeList()))
    {
        return Error((LPCTSTR)IDS_E_DISPLAY, hr);
    }

#if defined(__USE_COM__)
    ::CoUninitialize();
#endif
    
    m_IsInitialized = false;    // application is not initialized yet.
    m_SelPageNum    = 1;

    return S_OK;
}

HRESULT CExeModule::Initialize(HWND hWnd)
{
    if (m_IsInitialized)
    {
        ATLTRACE(_T("CExeModule::Initialize() Application already initialized.\n"));
        return S_FALSE;
    }

    m_hWnd = hWnd;

    // register object for idle updates
    CMessageLoop* pLoop = GetMessageLoop();
    ATLASSERT(pLoop != NULL);
    pLoop->AddMessageFilter(this);
    pLoop->AddIdleHandler(this);

    m_IsTestStarted = false;
    TCHAR szLib[MAX_PATH];
    DWORD size = sizeof(szLib);
    CString str;
    str.Format(_T("commandset%d"), m_db.tp.tis);
    
    CProfile cfg(_T(VR_PROFILE));

    cfg.Open(_T("modules"));
    if (FAILED(cfg.QueryValue(szLib, str, &size)))
    {
        ATLTRACE(_T("Cannot read profile: %s\n"), str);
        _tcscpy(szLib, _T("task_fpu.dll"));
    }

    m_hTask = LoadLibrary(szLib);

    PTCreateObject CreateObject = (PTCreateObject)GetProcAddress(m_hTask, "CreateObject");
    if (CreateObject)
    {
        if (m_TaskObject = CreateObject())
        {
            m_TaskObject->Connect(hWnd);
            m_TaskObject->SetData(&m_db);
            if (FAILED(m_TaskObject->Initialize()))
            {
                ATLTRACE(_T("CExeModule::Initialize() Cannot Initialize TaskObject.\n"));
                return E_FAIL;
            }
            m_IsInitialized = true;

            return S_OK;
        }
        ATLTRACE(_T("CExeModule::Initialize() cannot create TaskObject.\n"));
    }

    return E_FAIL;
}

HRESULT CExeModule::Terminate()
{
    if (!m_IsInitialized)
    {
        ATLTRACE(_T("CExeModule::Terminate() Application is not initialized.\n"));
        return S_FALSE;
    }

    CMessageLoop* pLoop = GetMessageLoop();
    ATLASSERT(pLoop != NULL);
    pLoop->RemoveIdleHandler(this);
    pLoop->RemoveMessageFilter(this);

    if (m_TaskObject)
    {
        HRESULT hr = S_OK;
        int n = -1;

        // selected instruction set for renderer..
        n = (IS_SSE == m_db.rp.is) ? 1 : 0;
        
        // selected instruction set for math solver..
        if (IS_SSE2 == m_db.tp.tis)
        {
            n += 2;
        }

        if (n >= 0 && SUCCEEDED(hr = m_TaskObject->GetData(&m_res[n])))
        {
            // @NOTE: we get S_FALSE if test was interrupted
            if (S_OK == hr)
                m_IsResults = true;
        }
        
        m_TaskObject->Terminate();
        m_TaskObject->Disconnect();
        m_TaskObject = NULL;
    }

    if (m_hTask)
    {
        ::FreeLibrary(m_hTask);
        m_hTask = NULL;
    }

    m_hWnd          = NULL;
    m_IsInitialized = false;    // application is no more initialized.
    return S_OK;
}

LRESULT CExeModule::ProcessMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lr = 0;

    if (m_TaskObject)
    {
        ATLTRACE(_T("CExeModule::ProcessMessage() (msg=%x)\n"), uMsg);
        m_TaskObject->ProcessMessage(m_hWnd, uMsg, wParam, lParam, &lr);
    }
    return lr;
}

int CExeModule::Setup()
{
    static TCHAR s_model[MAX_PATH];
    CSetupDlg dlg(m_SelPageNum);

    if (m_IsResults)
    {
        dlg.m_page0.m_IsSplash      = m_IsSplash;
        dlg.m_page2.m_ViewSizeX     = m_db.rp.vp.cx;
        dlg.m_page2.m_ViewSizeY     = m_db.rp.vp.cy;
        dlg.m_page2.m_Windowed      = m_db.rp.win;
        dlg.m_page2.m_Camera        = m_db.rp.camera;
        dlg.m_page2.m_Special       = (m_db.rp.flag & RENDER_OFFSCREEN)? 1:0;
        dlg.m_page2.m_Shadow        = (m_db.rp.flag & RENDER_SHADOWS)? 1:0;
        dlg.m_page2.m_ShadowQuality = (m_db.rp.flag & RENDER_SHADOWS_SUPER)? 1:0;
        dlg.m_page2.m_Texture       = (m_db.rp.flag & RENDER_TEXTURE)? 1:0;
        dlg.m_page3.m_StartTime     = m_db.tp.ttp.cx;
        dlg.m_page3.m_TestTime      = m_db.tp.ttp.cy - m_db.tp.ttp.cx;
        dlg.m_page3.m_TimeStep      = m_db.tp.tts;
        dlg.m_page3.m_IterNum       = m_db.tp.tin;
        dlg.m_page3.m_Index         = m_ModelIndex;

        // set selected instruction set for renderer..
        switch (m_db.rp.is)
        {
        case IS_FPU_MMX:  dlg.m_page2.m_CommandSet = 0; break;
        case IS_FPU_EMMX: dlg.m_page2.m_CommandSet = 1; break;
        case IS_SSE:      dlg.m_page2.m_CommandSet = 2; break;
        }
        // set selected instruction set for math solver..
        switch (m_db.tp.tis)
        {
        case IS_FPU_MMX: dlg.m_page3.m_CommandSet = 0; break;
        case IS_SSE2:    dlg.m_page3.m_CommandSet = 1; break;
        case IS_CUSTOM:  dlg.m_page3.m_CommandSet = 2; break;
        }
    }

    dlg.DoModal();

    m_IsSplash   = dlg.m_page0.m_IsSplash;
    m_ScreenMode = dlg.m_page2.m_ScreenMode;
    m_SelPageNum = dlg.m_Page;

    CProfile cfg(_T(VR_PROFILE));

    cfg.Open(_T("common"));
    if (FAILED(cfg.SetValue(m_IsSplash, _T("ShowSplash"))))
    {
        ATLTRACE(_T("Cannot write profile: %s\n"), _T("ShowSplash"));
    }

    if (IDOK != dlg.m_nRet)
        return dlg.m_nRet;

    ZeroMemory(&m_db, sizeof(m_db));
    // set selected display mode
    m_db.rp.di      = m_dml[m_ScreenMode];
    m_db.rp.win     = dlg.m_page2.m_Windowed;
    m_db.rp.camera  = dlg.m_page2.m_Camera;
    // set viewport size
    m_db.rp.vp.cx   = dlg.m_page2.m_ViewSizeX;
    m_db.rp.vp.cy   = dlg.m_page2.m_ViewSizeY;
    // set selected instruction set for renderer..
    switch (dlg.m_page2.m_CommandSet)
    {
    case 0: m_db.rp.is = IS_FPU_MMX;  break;
    case 1: m_db.rp.is = IS_FPU_EMMX; break;
    case 2: m_db.rp.is = IS_SSE;      break;
    }
    // set rendering attributes..
    m_db.rp.flag = 0;
    if (dlg.m_page2.m_Shadow)
    {
        m_db.rp.flag = RENDER_SHADOWS;
        if (dlg.m_page2.m_ShadowQuality)
            m_db.rp.flag |= RENDER_SHADOWS_SUPER;
    }
    if (dlg.m_page2.m_Texture)
        m_db.rp.flag |= RENDER_TEXTURE;
    if (dlg.m_page2.m_Special)
        m_db.rp.flag |= RENDER_OFFSCREEN;

    // set selected instruction set for math solver..
    switch (dlg.m_page3.m_CommandSet)
    {
    case 0: m_db.tp.tis = IS_FPU_MMX; break;
    case 1: m_db.tp.tis = IS_SSE2;    break;
    case 2: m_db.tp.tis = IS_CUSTOM;  break;
    }
    m_db.tp.ttp.cx = dlg.m_page3.m_StartTime;
    m_db.tp.ttp.cy = dlg.m_page3.m_StartTime + dlg.m_page3.m_TestTime;
    m_db.tp.tts    = dlg.m_page3.m_TimeStep;
    m_db.tp.tin    = dlg.m_page3.m_IterNum;
    m_db.tp.model  = s_model;   // model name buffer
    m_ModelIndex   = dlg.m_page3.m_Index;

    if (IS_CUSTOM == m_db.tp.tis)
    {
        DWORD size = MAX_PATH;
        CProfile cfg(_T(VR_PROFILE));

        cfg.Open(_T("common"));
        cfg.QueryValue(s_model, _T("DemoName"), &size);
    }
    else if (dlg.m_page3.m_Model < 0)
        s_model[0] = '\0';      // no model selected or list is empty
    else
        _tcscpy(s_model, (LPCTSTR)dlg.m_page3.m_tfl[dlg.m_page3.m_Model]);

    return dlg.m_nRet;
}

BOOL CExeModule::PreTranslateMessage(MSG* pMsg)
{
    if (m_hWnd && m_hWnd != pMsg->hwnd)
        return FALSE;       // do not translate another window msgs

    if (pMsg->message >= WM_MOUSEFIRST && pMsg->message <= WM_MOUSELAST ||
        pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST)
    {
        ATLTRACE(_T("CExeModule::PreTranslateMessage() (msg=%x)\n"), pMsg->message);
        if (m_TaskObject)
        {
            LRESULT lr = 0;
            m_TaskObject->ProcessMessage(m_hWnd, pMsg->message
                                        , pMsg->wParam, pMsg->lParam, &lr);
            if (lr != 0)
                return TRUE;    // translated by task module
        }
    }
    else if (WM_CLOSE == pMsg->message)
    {
        ATLTRACE(_T("CExeModule::PreTranslateMessage() App closed.\n"));
        m_IsTestStarted = false;

        Terminate();        // Terminate it BEFORE MainWindow closing.

        return FALSE;       // not translated
    }
    else if (WM_START == pMsg->message)
    {
        m_IsTestStarted = true;
        return TRUE;        // special message translated
    }
    else if (WM_STOP == pMsg->message)
    {
        m_IsTestStarted = false;
        return TRUE;        // special message translated
    }

    return FALSE;           // not translated
}

BOOL CExeModule::OnIdle()
{
    if (m_IsTestStarted && m_TaskObject)
    {
        LRESULT lr = 0;
        m_TaskObject->ProcessMessage(m_hWnd, WM_STEP, (WPARAM)0
                                    , (LPARAM)0/*GetTickCount()*/, &lr);
        // @NOTE: just a case. we stop test session 
        //        after receiving a non-zero result value
        if (lr != 0)
        {
            m_IsTestStarted = false;
            PostMessage(m_hWnd, WM_CLOSE, 0, 0);
        }
    }

    return TRUE;
}

HRESULT CExeModule::Error(LPCTSTR pText, HRESULT hr)
{
    CString str;
    str.Format(pText, hr);
    Error(str);
    
    return hr;
}

HRESULT CExeModule::Error(LPCTSTR pText)
{
    CHECK_NULL(pText, E_FAIL);

    CString str(pText);
    ::MessageBox(NULL, str, _T("Fatal Error"), MB_OK|MB_ICONSTOP);
    // this method never fails, but returns E_FAIL just for convenience
    return E_FAIL;
}

LPRESULTS CExeModule::GetLastResult()
{
    CHECK(m_IsResults, NULL);
    
    // selected instruction set for renderer..
    int n = (IS_SSE == m_db.rp.is) ? 1 : 0;
        
    // selected instruction set for math solver..
    if (IS_SSE2 == m_db.tp.tis)
    {
        n += 2;
    }

    return &m_res[n];
}

DWORD CExeModule::GetTickCount()
{
    static LARGE_INTEGER frequency;
    static LARGE_INTEGER prevPerformanceCount;

    static DWORD prevTick;
    static DWORD command = 0;
    
    switch (command)
    {
    case 0:
        if (FALSE == ::QueryPerformanceFrequency(&frequency))
        {
            prevTick = ::GetTickCount();
            command = 2;
            break;
        }
        else
        {
            ::QueryPerformanceCounter(&prevPerformanceCount);
            command = 1;
            frequency.QuadPart /= 1000; // ticks per ms
        }
        // @WARNING: fall through next case
    case 1:
        {
            LARGE_INTEGER performanceCount, pc;

            if (::QueryPerformanceCounter(&performanceCount))
            {
                pc.QuadPart = performanceCount.QuadPart 
                            / frequency.QuadPart;
                return pc.LowPart;
            }
        }
    case 2:
        return ::GetTickCount();
    }

    return 0;
}

/**
 * Purpose:
 *   This method returns CPU clock frequency.
 *
 * @param: [in]flag - tells to get the value measured before.
 * @param: [out]val - pointer to clock frequency in Hz.
 *
 * @return: S_OK if the installed hardware supports a high-resolution 
 *          performance counter, E_FAIL otherwise.
 */

HRESULT CExeModule::GetCpuFrequency(UINT *val, bool flag)
{
    SYSTEM_INFO si;
    ULONGLONG T1, T2;

    HRESULT hr = E_FAIL;
    
    if (flag && (*val = m_CpuFrequency))
        return S_OK;

    ::GetSystemInfo(&si);

    ::Sleep(12);
    ::SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
    ::SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

    if (si.dwNumberOfProcessors > 1)
    {
    // @KLUDGE: Multiprocessor system is not supported at the moment

        SYSTEMTIME  st;
        DWORD mask = 1;
        // Set a thread affinity mask to 1 to allow thread run 
        // on the first processor.
        ::SetThreadAffinityMask(GetCurrentThread(), mask);

        __asm {
                rdtsc
                mov dword ptr [T1+0], eax
                mov dword ptr [T1+4], edx
        }
        
        ::GetSystemTime(&st);

        // @TODO: add multiprocessor system support here...

        for (DWORD i = 1; i < si.dwNumberOfProcessors; i++)
            mask = mask * 2 + 1;

        ::SetThreadAffinityMask(GetCurrentThread(), mask);
        hr = E_NOTIMPL;
    } 
    else do
    {
        LARGE_INTEGER FR, C1, C2, S1;
        UINT tsc;

        GetCpuCaps(HAS_TSC, &tsc);  // Time Stamp Counter
        if (0 == tsc)
            break;

        if (!::QueryPerformanceFrequency(&FR))
            break;

        ::QueryPerformanceCounter(&C1);
        C2.QuadPart = C1.QuadPart;

        while (C2.LowPart == C1.LowPart)
        {
            ::QueryPerformanceCounter(&C2);
            __asm {
                    rdtsc
                    mov dword ptr [T1+0], eax
                    mov dword ptr [T1+4], edx
            }
        }
        C2.QuadPart = C1.QuadPart;

        while (C2.LowPart - C1.LowPart < FR.LowPart)    // wait for 1000ms
        {
            ::QueryPerformanceCounter(&C2);
            __asm {
                    rdtsc
                    mov dword ptr [T2+0], eax
                    mov dword ptr [T2+4], edx
            }
        }
        S1.QuadPart = (T2 - T1) * FR.QuadPart / (C2.QuadPart - C1.QuadPart);
        m_CpuFrequency = S1.LowPart;
        hr = S_OK;

    } while (0);

    ::SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
    ::SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);

    *val = m_CpuFrequency;
    return hr;
}

/**
 * Puspose:
 *   This method returns information about the capabilies of the CPU 
 *   on which it is called. The input enumeration covers both processor 
 *   feature bits (the HAS_* values) and "synthesized" information.
 *
 *   THE HAS_* QUERIES SHOULD ALWAYS BE USED IN PREFERENCE TO DIRECTLY 
 *   CHECKING THE CPU TYPE WHEN LOOKING FOR FEATURES. For instance, 
 *   it is *always* better to check for HAS_3DNOW directly, rather than 
 *   rely on checking for a K6_2, K6_3, or Athlon. Likewise, HAS_MMX 
 *   should always be used in preference to other methods of checking 
 *   for MMX instructions.
 *
 *   The features bits are checked against either the base feature bits 
 *   (cpuid function 1, edx) or the extended feature bits (cpuid function 
 *   0x80000001, edx), as appropriate. The return val is 1 for feature 
 *   present or 0 for feature not present.
 *   The synthesized information is created by interpreting the cpuid
 *   results in some way.
 *   The full set of feature bits (both base and extended) are implemented 
 *   in this version. The L1/L2 Cache querying is not yet implemented, 
 *   but all other known AMD cpuid capabilities are.
 *
 *   Note that this routine caches the feature bits when first called, 
 *   so checking multiple features is relatively efficient after the first 
 *   invocation. However, it is not recommended practice to use GetCpuCaps() 
 *   inside time-critical code.
 *
 * @param: [in]caps - which capability to query 
 *                    (see enum CPUCAPS for an exhaustive list)
 * @param: [out]val - pointer to result val 
 *
 * @return: S_OK if cpuid is supported, S_FALSE otherwise.
 */

#define family(p)   (((p) >> 8) & 0xf)
#define model(p)    (((p) >> 4) & 0xf)
#define stepping(p) (((p) >> 0) & 0xf)
#define feature(p)  ((m_cd.features >> (p)) & 1)
#define feature2(p) ((m_cd.ext_features >> (p)) & 1)
#define cache1(p,s) ((m_cd.cache_l1[(p)] >> (s)) & 0xff)
#define cache2(p,s,m) ((m_cd.cache_l2[(p)] >> (s)) & m)

HRESULT CExeModule::GetCpuCaps(UINT cap, UINT *val)
{
    static BRANDTABLE brndtab[] =
    {
// @COMMENT: disable some non-informative brand IDs
//        9, 0x01, _T("Intel® Celeron™ processor"),
//        10, 0x02, _T("Intel® Pentium® III processor"),
        12, 0x03, _T("Intel® Pentium® III Xeon™ processor"),
//        10, 0x04, _T("Intel® Pentium® III processor"),
        13, 0x08, _T("Intel® Pentium® 4 processor"),
        15, 0x0e, _T("Intel® Xeon™ processor"),
    };
    static BRANDTABLE nametab[] =
    {
        0, UNKNOWN, _T("New or Unknown"),
        1, AMD_Am486, _T("AMD 486"),
        1, AMD_5x86, _T("AMD 5x86"),
        1, AMD_K5, _T("AMD K5"),
        1, AMD_5K86, _T("AMD 5K86"),
        2, AMD_K6, _T("AMD K6"),
        2, AMD_K6_2, _T("AMD K6 II"),
        3, AMD_K6_3, _T("AMD K6 III"),
        5, AMD_ATHLON, _T("AMD Athlon"),
        6, AMD_ATHLON4, _T("AMD Athlon 4"),
        7, AMD_ATHLON_MP, _T("AMD Athlon MP"),
        8, AMD_ATHLON_XP, _T("AMD Athlon XP"),
        4, AMD_DURON, _T("AMD Duron"),

        1, INTEL_486DX, _T("Intel® 486DX"),
        1, INTEL_486SX, _T("Intel® 486SX"),
        1, INTEL_Pentium, _T("Intel® Pentium®"),
        1, INTEL_Pentium_MMX, _T("Intel® Pentium® MMX"),
        1, INTEL_Pentium_Pro, _T("Intel® Pentium® Pro"),
        1, INTEL_Pentium_II_Klamath, _T("Intel® Pentium® II (Klamath)"),
        1, INTEL_Pentium_II_Deschutes, _T("Intel® Pentium® II (Deschutes)"),
        1, INTEL_Pentium_II_Mobile, _T("Intel® Pentium® II Mobile"),
        9, INTEL_Celeron, _T("Intel® Celeron™ processor"),
        9, INTEL_Celeron_Covington, _T("Intel® Celeron™ (Covington)"),
        9, INTEL_Celeron_Mendocino, _T("Intel® Celeron™ (Mendocino)"),
        10,INTEL_Pentium_III_Katmai, _T("Intel® Pentium® III (Katmai)"),
        10,INTEL_Pentium_III_Coppermine, _T("Intel® Pentium® III (Coppermine)"),
        10,INTEL_Pentium_III_Tualatin, _T("Intel® Pentium® III (Tualatin)"),
        11,INTEL_Pentium_III_Mobile, _T("Intel® Pentium® III Mobile"),
        12,INTEL_Pentium_III_Xeon, _T("Intel® Pentium® III Xeon"),
        13,INTEL_Pentium_IV, _T("Intel® Pentium® 4"),
        14,INTEL_Pentium_IV_Mobile, _T("Intel® Pentium® 4 Mobile"),
        15,INTEL_Pentium_Xeon, _T("Intel® Pentium® Xeon"),

        1, CYRIX_MediaGX, _T("Cyrix MediaGX"),
        1, CYRIX_5x86, _T("Cyrix 5x86"),
        1, CYRIX_6x86, _T("Cyrix 6x86"),
        1, CYRIX_MediaGXm, _T("Cyrix MediaGXm"),
        1, CYRIX_6x86MX, _T("Cyrix 6x86MX"),

        1, UMC_U5D, _T("UMC U5D"),
        1, UMC_U5S, _T("UMC U5S"),

        1, CENTAUR_IDTC6, _T("Centaur IDTC6"),
        1, CENTAUR_IDTC2, _T("Centaur IDTC2"),
        16,VIA_SAMUEL1, _T("VIA Samuel"),
        16,VIA_SAMUEL2, _T("VIA Samuel 2"),
        16,VIA_EZRA, _T("VIA Ezra")
    };

    static int IsDetected = 0;
    UINT tmp;

    if (0 == IsDetected)
    {
        ZeroMemory((PVOID)&m_cd, sizeof(CPUDETECT));
        IsDetected = CpuDetectHelper(&m_cd);
    }

    if (-1 == IsDetected)
        return S_FALSE; // No cpuid, so no cpuid functions are supported

    // Otherwise, perform the requested tests
    switch (cap)
    {

    // Synthesized Capabilities
    case HAS_CPUID:
        *val = 1;     // Always true if this code gets executed
        break;

    case CPU_VENDOR_STRING:
        *val = (UINT)m_cd.vendor;
        break;

    case CPU_NAME_STRING:
        if (!m_cd.namestr[0] && m_cd.vendor[15])    // skip nul gap
        {
            BYTE id = m_cd.vendor[15];
            for (int i=0; i<COUNTOF(brndtab); i++)
            {
                if (brndtab[i].id == id)
                {
                    lstrcpy(m_cd.namestr, brndtab[i].name);
                    break;
                }
            }
        }
        // @COMMENT: disable some non-informative brand IDs (see above)
        /*else*/ if (!m_cd.namestr[0])
        {
            GetCpuCaps(CPU_TYPE, &tmp);
            for (int i=0; i<COUNTOF(nametab); i++)
            {
                if (nametab[i].id == tmp)
                {
                    lstrcpy(m_cd.namestr, nametab[i].name);
                    break;
                }
            }
        }
        *val = (UINT)(LPUINT)m_cd.namestr;
        break; // CPU_NAME_STRING

    case CPU_VENDOR_LOGO:
        {
            *val = 0;
            GetCpuCaps(CPU_TYPE, &tmp);
            for (int i=0; i<COUNTOF(nametab); i++)
            {
                if (nametab[i].id == tmp)
                {
                    *val = nametab[i].logo; break;
                }
            }
        }
        break;

    case CPU_VENDOR:
        if      (!strncmp(m_cd.vendor, "AuthenticAMD", 12)) *val = VENDOR_AMD;
        else if (!strncmp(m_cd.vendor, "GenuineIntel", 12)) *val = VENDOR_INTEL;
        else if (!strncmp(m_cd.vendor, "CyrixInstead", 12)) *val = VENDOR_CYRIX;
        else if (!strncmp(m_cd.vendor, "CentaurHauls", 12)) *val = VENDOR_CENTAUR;
        else if (!strncmp(m_cd.vendor, "NexGenDriven", 12)) *val = VENDOR_NEXGEN;
        else if (!strncmp(m_cd.vendor, "UMC UMC UMC ", 12)) *val = VENDOR_UMC;
        else if (!strncmp(m_cd.vendor, "RiseRiseRise", 12)) *val = VENDOR_RISE;
        else if (!strncmp(m_cd.vendor, "GenuineTMx86", 12)) *val = VENDOR_TRANSMETA;
        else
            *val = VENDOR_UNKNOWN;
        break; // CPU_VENDOR

    case CPU_TYPE:
        {
        // Return a member of the CPUTYPES enumeration
        // DON'T use this for determining presence of chip features, 
        // such as MMX and 3DNow! Instead, use GetCpuCaps(HAS_MMX) and 
        // GetCpuCaps(HAS_3DNOW), which will accurately detect the presence 
        // of these features on all chips which support them.

        GetCpuCaps(CPU_VENDOR, &tmp);
        *val = UNKNOWN;

        switch (tmp)
        {
        case VENDOR_AMD:
            switch (family(m_cd.processor))
            {
            case 4: // Am486/AM5x86
                switch (model(m_cd.processor))
                {
                case 3:
                case 7:
                case 8:
                case 9:  *val = AMD_Am486; break;
                case 14:
                case 15: *val = AMD_5x86; break;
                }
                break;

            case 5: // K6
                switch (model(m_cd.processor))
                {
                case 0:
                case 1:
                case 2:
                case 3:  *val = AMD_5K86; break;
                case 6: 
                case 7:  *val = AMD_K6; break;
                case 8:  *val = AMD_K6_2; break;
                case 9:  // K6-III starts here, all subsequent K6 family
                case 10: // processors are recognized as K6-III. If new 
                case 11: // product releases invalidate this, the new 
                case 12: // CPU models will be referenced in here.
                case 13:
                case 14:
                case 15: *val = AMD_K6_3; break;
                }
                break;

            case 6: // Athlon
                GetCpuCaps(HAS_AMD_MP, &tmp);
                switch (model(m_cd.processor))
                {
                case 1:
                case 2: *val = AMD_ATHLON; break;
                case 4: *val = AMD_ATHLON/*AMD_ATHLON4*/; break;
                case 6: *val = (tmp) 
                             ? AMD_ATHLON_MP 
                             : AMD_ATHLON_XP; break;
                case 3:
                case 7: *val = AMD_DURON; break;
                }
                break;
            }
            break; // VENDOR_AMD

        case VENDOR_INTEL:
            switch (family(m_cd.processor))
            {
            case 4:
                switch (model(m_cd.processor))
                {
                case 0:
                case 1:
                case 3:
                case 4:
                case 7:
                case 8:
                case 9: *val = INTEL_486DX; break;
                case 2:
                case 5: *val = INTEL_486SX; break;
                }
                break;

            case 5:
                switch (model(m_cd.processor))
                {
                case 0:
                case 1:
                case 2:
                case 3:
                case 7: *val = INTEL_Pentium; break;
                case 4:
                case 8: *val = INTEL_Pentium_MMX; break;
                }
                break;

            case 6:
                GetCpuCaps(CPU_L2_CACHE_SIZE, &tmp);
                switch (model(m_cd.processor))
                {
                case 0:
                case 1: *val = INTEL_Pentium_Pro; break;
                case 3: *val = INTEL_Pentium_II_Klamath; break;
                case 5: *val = (0 == tmp)
                             ? INTEL_Celeron_Covington
                             : (512 >= tmp)
                             ? INTEL_Pentium_II_Deschutes
                             : INTEL_Pentium_Xeon; break;
                case 6: *val = (128 == tmp)
                             ? INTEL_Celeron_Mendocino
                             : INTEL_Pentium_II_Mobile; break;
                case 7: *val = (512 < tmp) 
                             ? INTEL_Pentium_Xeon
                             : INTEL_Pentium_III_Katmai; break;
                case 8: 
                    {
                        BYTE id = m_cd.vendor[15];
                        *val = (1 == id)
                             ? INTEL_Celeron
                             : (3 == id) 
                             ? INTEL_Pentium_Xeon
                             : INTEL_Pentium_III_Coppermine; break;
                    }
                }
                break;

            case 15:    *val = INTEL_Pentium_IV; break;
            }
            break; // VENDOR_INTEL

        case VENDOR_CYRIX:
            switch (family(m_cd.processor))
            {
            case 4:
                switch (model(m_cd.processor))
                {
                case 4: *val = CYRIX_MediaGX;   break;
                case 9: *val = CYRIX_5x86; break;
                }
                break;
            case 5:
                switch (model(m_cd.processor))
                {
                case 2: *val = CYRIX_6x86; break;
                case 4: *val = CYRIX_MediaGXm; break;
                }
                break;
            case 6:
                switch (model(m_cd.processor))
                {
                case 0: *val = CYRIX_6x86MX; break;
                }
                break;
            }
            break; // VENDOR_CYRIX

        case VENDOR_UMC:
            switch (family(m_cd.processor))
            {
            case 4:
                switch (model(m_cd.processor))
                {
                case 1: *val = UMC_U5D; break;
                case 2: *val = UMC_U5S; break;
                }
                break;
            }
            break;

        case VENDOR_CENTAUR:
            switch (family(m_cd.processor))
            {
            case 5:
                switch (model(m_cd.processor))
                {
                case 4: *val = CENTAUR_IDTC6; break;
                case 8: *val = CENTAUR_IDTC2; break;
                }
                break;
            case 6:
                switch (model(m_cd.processor))
                {
                case 6: *val = VIA_SAMUEL1; break;
                case 7: *val = (stepping(m_cd.processor) < 8)
                             ? VIA_SAMUEL2
                             : VIA_EZRA; break;
                }
                break;
            }
            break; // VENDOR_CENTAUR

        } // switch CPU_VENDOR
        }
        break;  // CPU_TYPE

// Symbolic constants for capability flags in cpuid standard feature flags

    // Feature Bit Test Capabilities
    case HAS_FPU:       *val = feature(0);  break;  // FPU
    case HAS_VME:       *val = feature(1);  break;  // VME
    case HAS_DEBUG:     *val = feature(2);  break;  // Debugger extensions
    case HAS_PSE:       *val = feature(3);  break;  // Page Size Extensions
    case HAS_TSC:       *val = feature(4);  break;  // Time Stamp Counter
    case HAS_MSR:       *val = feature(5);  break;  // Model Specific Registers
    case HAS_PAE:       *val = feature(6);  break;  // PAE
    case HAS_MCE:       *val = feature(7);  break;  // Machine Check Extensions
    case HAS_CMPXCHG8:  *val = feature(8);  break;  // CMPXCHG8 instruction
    case HAS_APIC:      *val = feature(9);  break;  // APIC
    case HAS_SYSENTER:  *val = feature(11); break;  // SYSENTER instruction
    case HAS_MTRR:      *val = feature(12); break;  // Memory Type Range Registers 
    case HAS_GPE:       *val = feature(13); break;  // Global Paging Extensions
    case HAS_MCA:       *val = feature(14); break;  // Machine Check Architecture
    case HAS_CMOV:      *val = feature(15); break;  // CMOV instruction
    case HAS_PAT:       *val = feature(16); break;  // Page Attribue Table
    case HAS_PSE36:     *val = feature(17); break;  // PSE36 (Page Size Extensions)
    case HAS_PSN:       *val = feature(18); break;  // PSN (96-bit Processor Serial Number)
    case HAS_MMX:       *val = feature(23); break;  // MMX
    case HAS_FXSR:      *val = feature(24); break;  // FXSAVE/FXRSTOR instruction
    case HAS_SSE:       *val = feature(25); break;  // SSE
    case HAS_SSE2:      *val = feature(26); break;  // SSE2

    case HAS_SSE_FP:
        // Check for actual presence of SSE FP operations
        GetCpuCaps(HAS_SSE, &tmp);
        if (tmp)
        {
            try
            {
                __asm _emit 0x0f
                __asm _emit 0x56
                __asm _emit 0xc0                    // orps xmm0, xmm0
                *val = 1;
            }
            catch (...) 
            {
                *val = 0;
                return S_FALSE;
            }
        }
        break; // HAS_SSE_FP

    case HAS_SSE_MMX:   *val = feature2(22); break; // bit 22 (ext) = SSE MMX Extensions
    case HAS_EMMX:      *val = feature(25)
                             | feature2(22); break; // bits 25|22(ext) = MMX Extensions
    // AMD extended information
    case HAS_3DNOW_EXT: *val = feature2(30); break; // bit 30 (ext) = Extended 3DNow!
    case HAS_3DNOW:     *val = feature2(31); break; // bit 31 (ext) = 3DNow!
    case HAS_AMD_MP:    *val = feature2(19); break; // bit 19 (ext) = Multiprocessing

    default:
        {
        // These are CPU-specific, so guard their access
        GetCpuCaps(CPU_VENDOR, &tmp);
        if (VENDOR_INTEL == tmp)
        {
            switch (cap)
            {
            case CPU_L1_EDTLB_ASSOC:    *val = cache1(0,24); break;
            case CPU_L1_EDTLB_ENTRIES:  *val = cache1(0,16); break;
            case CPU_L1_EITLB_ASSOC:    *val = cache1(0, 8); break;
            case CPU_L1_EITLB_ENTRIES:  *val = cache1(0, 0); break;

            case CPU_L1_DTLB_ASSOC:     *val = cache1(1,24); break;
            case CPU_L1_DTLB_ENTRIES:   *val = cache1(1,16); break;
            case CPU_L1_ITLB_ASSOC:     *val = cache1(1, 8); break;
            case CPU_L1_ITLB_ENTRIES:   *val = cache1(1, 0); break;

            case CPU_L1_DCACHE_SIZE:    *val = cache1(2,24); break;
            case CPU_L1_DCACHE_ASSOC:   *val = cache1(2,16); break;
            case CPU_L1_DCACHE_LINES:   *val = cache1(2, 8); break;
            case CPU_L1_DCACHE_LSIZE:   *val = cache1(2, 0); break;

            case CPU_L1_ICACHE_SIZE:    *val = cache1(3,24); break;
            case CPU_L1_ICACHE_ASSOC:   *val = cache1(3,16); break;
            case CPU_L1_ICACHE_LINES:   *val = cache1(3, 8); break;
            case CPU_L1_ICACHE_LSIZE:   *val = cache1(3, 0); break;

            case CPU_L2_CACHE_SIZE:     *val = cache2(2,16,0xffff); break;
            case CPU_L2_CACHE_ASSOC:    *val = cache2(2,12,0x000f); break;
            case CPU_L2_CACHE_LINES:    *val = cache2(2, 8,0x000f); break;
            case CPU_L2_CACHE_LSIZE:    *val = cache2(2, 0,0x00ff); break;
            }
        } // VENDOR_INTEL
        else if (VENDOR_AMD == tmp)
        {
            // K5/K6 supports a restricted range
            switch (cap)
            {
            case CPU_L1_DTLB_ASSOC:     *val = cache1(1,24); break;
            case CPU_L1_DTLB_ENTRIES:   *val = cache1(1,16); break;
            case CPU_L1_ITLB_ASSOC:     *val = cache1(1, 8); break;
            case CPU_L1_ITLB_ENTRIES:   *val = cache1(1, 0); break;

            case CPU_L1_DCACHE_SIZE:    *val = cache1(2,24); break;
            case CPU_L1_DCACHE_ASSOC:   *val = cache1(2,16); break;
            case CPU_L1_DCACHE_LINES:   *val = cache1(2, 8); break;
            case CPU_L1_DCACHE_LSIZE:   *val = cache1(2, 0); break;

            case CPU_L1_ICACHE_SIZE:    *val = cache1(3,24); break;
            case CPU_L1_ICACHE_ASSOC:   *val = cache1(3,16); break;
            case CPU_L1_ICACHE_LINES:   *val = cache1(3, 8); break;
            case CPU_L1_ICACHE_LSIZE:   *val = cache1(3, 0); break;

            case CPU_L2_CACHE_SIZE:     *val = cache2(2,16,0xffff); break;
            case CPU_L2_CACHE_ASSOC:    *val = cache2(2,12,0x000f); break;
            case CPU_L2_CACHE_LINES:    *val = cache2(2, 8,0x000f); break;
            case CPU_L2_CACHE_LSIZE:    *val = cache2(2, 0,0x00ff); break;
            }

            GetCpuCaps(CPU_TYPE, &tmp);
            if (AMD_ATHLON <= tmp)
            {
                // Athlon supports these additional parameters
                switch (cap)
                {
                case CPU_L1_EDTLB_ASSOC:    *val = cache1(0,24); break;
                case CPU_L1_EDTLB_ENTRIES:  *val = cache1(0,16); break;
                case CPU_L1_EITLB_ASSOC:    *val = cache1(0, 8); break;
                case CPU_L1_EITLB_ENTRIES:  *val = cache1(0, 0); break;

                case CPU_L2_DTLB_ASSOC:     *val = cache2(0,28,0x000f); break;
                case CPU_L2_DTLB_ENTRIES:   *val = cache2(0,16,0x0fff); break;
                case CPU_L2_UTLB_ASSOC:     *val = cache2(0,12,0x000f); break;
                case CPU_L2_UTLB_ENTRIES:   *val = cache2(0, 0,0x0fff); break;

                case CPU_L2_EDTLB_ASSOC:    *val = cache2(1,28,0x000f); break;
                case CPU_L2_EDTLB_ENTRIES:  *val = cache2(1,16,0x0fff); break;
                case CPU_L2_EUTLB_ASSOC:    *val = cache2(1,12,0x000f); break;
                case CPU_L2_EUTLB_ENTRIES:  *val = cache2(1, 0,0x0fff); break;
                }
            }
        } // VENDOR_AMD
        else if (VENDOR_CENTAUR == tmp)
        {
            switch (cap)
            {
            case CPU_L2_CACHE_SIZE:     *val = cache2(2,24,0x00ff); break;
            case CPU_L2_CACHE_ASSOC:    *val = cache2(2,16,0x00ff); break;
            case CPU_L2_CACHE_LINES:    *val = cache2(2, 8,0x00ff); break;
            case CPU_L2_CACHE_LSIZE:    *val = cache2(2, 0,0x00ff); break;
            }
        }
        }
        break; // default
    }

    return S_OK;
}

#undef family
#undef model
#undef feature
#undef feature2
#undef cache1
#undef cache2

/**
 * Purpose:
 *   This routine is separate from GetCPUCaps() for ease of comprehension.
 *   It also encapsulates the only parts of the algorithm 
 *   that are compiler specific.
 *
 * @param: [out]pcd - pointer to filled CPUDETECT structure
 *
 * @return: -1 if cpuid intruction is not supported by current CPU,
 *           1 otherwise.
 */

int CExeModule::CpuDetectHelper(LPCPUDETECT pcd)
{
    int features = 0;
    int ext_features = 0;
    int processor = 0;
    int vendor[4]   = {0};
    int namestr[12] = {0};
    int cache_l1[4] = {0};
    int cache_l2[4] = {0};

    int result;

    // Use C++ EH to determine cpuid presence
    try
    {
        __asm xor   eax, eax   // function 0 == manufacturer string
        __asm xor   ebx, ebx
        __asm xor   ecx, ecx
        __asm xor   edx, edx
        __asm cpuid
    }
    catch (...) { return -1; }

    __asm
    {
        mov     [vendor+0], ebx      // Stash the manufacturer string for later
        mov     [vendor+4], edx
        mov     [vendor+8], ecx
        test    eax, eax            // 0 is highest function, then don't query more info
        mov     [result], 1         // but the init DID happen
        jz      no_features
        xor     esi, esi
        cmp     ebx, 'uneG'
        jne     not_Intel
        cmp     edx, 'Ieni'
        jne     not_Intel
        cmp     ecx, 'letn'
        jne     not_Intel

        mov     esi, 1              // this is Intel processor
    }
        
        ParseIntelDescriptors(cache_l1, cache_l2);

    __asm
    {
not_Intel:
// Load up the features and (where appropriate) extended features flags
        mov     eax, 1              // Check for processor features
        cpuid
        mov     [processor], eax    // Store processor family/model/step
        mov     [features], edx     // Store features bits
        shl     ebx, 24             // keep nul gap
        mov     [vendor+12], ebx    // Store brand id (Intel only)

        mov     eax, 080000000h     // Check for support of extended functions.
        cpuid

        // Check which extended functions can be called
        cmp     eax, 080000001h     // Extended Feature Bits
        jb      no_features         // jump if not supported
        cmp     eax, 080000004h     // CPU Name string
        jb      just_extfeat        // jump if not supported
        cmp     eax, 080000005h     // L1 Cache information
        jb      just_name           // jump if not supported
        cmp     eax, 080000006h     // L2 Cache information
        jb      just_L1             // jump if not supported

        // Query and save L2 cache information
        mov     eax, 080000006h     // L2 Cache Information
        cpuid                       // Interpretation is CPU specific, but
                                    // fetching is not.
        mov     [cache_l2+ 0], eax
        mov     [cache_l2+ 4], ebx
        mov     [cache_l2+ 8], ecx
        mov     [cache_l2+12], edx

just_L1:
        // Query and save L1 cache informatin
        mov     eax, 080000005h     // L1 Cache Information
        cpuid                       // Interpretation is CPU specific, but
                                    // fetching is not.
        mov     [cache_l1+ 0], eax
        mov     [cache_l1+ 4], ebx
        mov     [cache_l1+ 8], ecx
        mov     [cache_l1+12], edx

just_name:
        // Query and save the CPU name string
        mov     eax, 080000002h
        cpuid
        mov     [namestr+ 0], eax
        mov     [namestr+ 4], ebx
        mov     [namestr+ 8], ecx
        mov     [namestr+12], edx
        mov     eax, 080000003h
        cpuid
        mov     [namestr+16], eax
        mov     [namestr+20], ebx
        mov     [namestr+24], ecx
        mov     [namestr+28], edx
        mov     eax, 080000004h
        cpuid
        mov     [namestr+32], eax
        mov     [namestr+36], ebx
        mov     [namestr+40], ecx
        mov     [namestr+44], edx

just_extfeat:
        test    esi, esi            // is this Intel
        jnz     no_features
        // Query and save the extended features bits
        mov     eax, 080000001h     // Select function 0x80000001
        cpuid
        mov     [ext_features], edx // Store extended features bits

no_features:
    }

    pcd->features = features;
    pcd->ext_features = ext_features;
    pcd->processor = processor;
    memcpy(pcd->vendor, vendor, sizeof(vendor));
    memcpy(pcd->namestr, namestr, sizeof(namestr));
    memcpy(pcd->cache_l1, cache_l1, sizeof(cache_l1));
    memcpy(pcd->cache_l2, cache_l2, sizeof(cache_l2));

    return result;
}

#define bind1(a,b,c,d) ((int)(((BYTE)(d))|(((WORD)(BYTE)(c))<<8) \
                       |(((DWORD)(BYTE)(b))<<16)|(((DWORD)(BYTE)(a))<<24)))
#define bind2(a,b,c,d) ((int)(((BYTE)(d))|(((WORD)(BYTE)(c)&15)<<8) \
                       |(((DWORD)(BYTE)(b)&15)<<12)|(((DWORD)(WORD)(a))<<16)))

#define cache1(n,a,b,c,d) (cache_l1[(n)] |= bind1(a,b,c,d))
#define cache2(n,a,b,c,d) (cache_l2[(n)] |= bind2(a,b,c,d))

void CExeModule::ParseIntelDescriptors(int *cache_l1, int* cache_l2)
{
    int descriptors[4];

    __asm
    {
        mov     eax, 2              // Setup to read cache descriptor
        cpuid                       // this code supports one iteration only
        mov     [descriptors+ 0], eax
        mov     [descriptors+ 4], ebx
        mov     [descriptors+ 8], ecx
        mov     [descriptors+12], edx
    }

    LPBYTE p = (LPBYTE)descriptors;
    for (int i=15; i>0; i--)
    {
        switch (p[i])
        {
        case 0x01: cache1(1, 0, 0, 4,32); break;    // ITLB, 4K pages, 4-way set associative, 32 entries
        case 0x02: cache1(0, 0, 0,15, 2); break;    // ITLB, 4M pages, fully associative, 2 entries
        case 0x03: cache1(1, 4,64, 0, 0); break;    // DTLB, 4K pages, 4-way set associative, 64 entries
        case 0x04: cache1(0, 4, 8, 0, 0); break;    // DTLB, 4M pages, 4-way set associative, 8 entries
        case 0x06: cache1(3, 8, 4, 0,32); break;    // ICache, 8K, 4-way set associative, 32 byte line size
        case 0x08: cache1(3,16, 4, 0,32); break;    // ICache,16K, 4-way set associative, 32 byte line size
        case 0x0a: cache1(2, 8, 2, 0,32); break;    // DCache, 8K, 2-way set associative, 32 byte line size
        case 0x0c: cache1(2,16, 4, 0,32); break;    // DCache,16K, 4-way set associative, 32 byte line size

        case 0x41: cache2(2, 128,4,0,32); break;    // UCache, 32 byte cache line, 4-way set associative, 128K
        case 0x42: cache2(2, 256,4,0,32); break;    // UCache, 32 byte cache line, 4-way set associative, 256K
        case 0x43: cache2(2, 512,4,0,32); break;    // UCache, 32 byte cache line, 4-way set associative, 512K
        case 0x44: cache2(2,1024,4,0,32); break;    // UCache, 32 byte cache line, 4-way set associative, 1M
        case 0x45: cache2(2,2048,4,0,32); break;    // UCache, 32 byte cache line, 4-way set associative, 2M

        case 0x50: cache1(1, 0, 0,15, 64); 
                   cache1(0, 0, 0,15, 64); break;   // ITLB, 4K/2M/4M pages, fully associative, 64 entries
        case 0x51: cache1(1, 0, 0,15,128); 
                   cache1(0, 0, 0,15,128); break;   // ITLB, 4K/2M/4M pages, fully associative, 128 entries
        case 0x52: cache1(1, 0, 0,15,255); 
                   cache1(0, 0, 0,15,255); break;   // ITLB, 4K/2M/4M pages, fully associative, 256 entries
        case 0x5b: cache1(1,15, 64, 0, 0);
                   cache1(0,15, 64, 0, 0); break;   // DTLB, 4K/4M pages, fully associative, 64 entries
        case 0x5c: cache1(1,15,128, 0, 0); 
                   cache1(0,15,128, 0, 0); break;   // DTLB, 4K/4M pages, fully associative, 128 entries
        case 0x5d: cache1(1,15,255, 0, 0); 
                   cache1(0,15,255, 0, 0); break;   // DTLB, 4K/4M pages, fully associative, 256 entries

        case 0x66: cache1(2, 8, 4, 0,64); break;    // DCache, sectored, 64 byte cache line, 4 way set associative, 8K
        case 0x67: cache1(2,16, 4, 0,64); break;    // DCache, sectored, 64 byte cache line, 4 way set associative, 16K
        case 0x68: cache1(2,32, 4, 0,64); break;    // DCache, sectored, 64 byte cache line, 4 way set associative, 32K
        case 0x70: cache1(3,12, 8, 0, 0); break;    // ITCache, 8 way set associative, 12K uOps
        case 0x71: cache1(3,16, 8, 0, 0); break;    // ITCache, 8 way set associative, 16K uOps
        case 0x72: cache1(3,32, 8, 0, 0); break;    // ITCache, 8 way set associative, 32K uOps

        case 0x79: cache2(2, 128,8,0,64); break;    // UCache, sectored, 64 byte cache line, 8 way set associative, 128K
        case 0x7a: cache2(2, 256,8,0,64); break;    // UCache, sectored, 64 byte cache line, 8 way set associative, 256K
        case 0x7b: cache2(2, 512,8,0,64); break;    // UCache, sectored, 64 byte cache line, 8 way set associative, 512K
        case 0x7c: cache2(2,1024,8,0,64); break;    // UCache, sectored, 64 byte cache line, 8 way set associative, 1M
        case 0x82: cache2(2, 256,8,0,32); break;    // UCache, 32 byte cache line, 8 way set associative, 256K
        case 0x83: cache2(2, 512,8,0,32); break;    // UCache, 32 byte cache line, 8 way set associative, 512K
        case 0x84: cache2(2,1024,8,0,32); break;    // UCache, 32 byte cache line, 8 way set associative, 1M
        case 0x85: cache2(2,2048,8,0,32); break;    // UCache, 32 byte cache line, 8 way set associative, 2M
        }
    }
}

#undef bind1
#undef bind2
#undef cache1
#undef cache2


HRESULT CExeModule::DisplaySystemVersion()
{
    OSVERSIONINFOEX vi = {0};
    BOOL IsOsviEx;

    CString str = _T("Microsoft® Windows");

    // Try calling GetVersionEx using the OSVERSIONINFOEX structure.
    // If that fails, try using the OSVERSIONINFO structure.

    vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    if (!(IsOsviEx = GetVersionEx((LPOSVERSIONINFO)&vi)))
    {
        // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.

        vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        if (!GetVersionEx((LPOSVERSIONINFO)&vi)) 
            return E_FAIL;
    }
    CString ver;

    switch (vi.dwPlatformId)
    {
    case VER_PLATFORM_WIN32_NT:

        // Test for the product.
        if (5 == vi.dwMajorVersion && 1 == vi.dwMinorVersion)
            str += _T(" XP");

        else if (5 == vi.dwMajorVersion && 0 == vi.dwMinorVersion)
            str += _T(" 2000");

        else if (4 >= vi.dwMajorVersion)
            str += _T(" NT");

        // Test for product type.

#if(WINVER >= 0x0500)
#pragma message(">>> WINVER 0x0500 in use")
        if (IsOsviEx)
        {
            if (VER_NT_WORKSTATION == vi.wProductType)
            {
                if (vi.wSuiteMask & VER_SUITE_PERSONAL)
                    str += _T(" Home Edition");
                else
                    str += _T(" Professional");
            }
            else if (VER_NT_SERVER == vi.wProductType)
            {
                if (vi.wSuiteMask & VER_SUITE_DATACENTER)
                    str += _T(" DataCenter Server");
                else if (vi.wSuiteMask & VER_SUITE_ENTERPRISE)
                    str += _T(" Advanced Server");
                else
                    str += _T(" Server");
            }
        }
        else
#endif // WINVER >= 0x0500
        {
            static TCHAR productkey[] = 
                _T("SYSTEM\\CurrentControlSet\\Control\\ProductOptions");

            HKEY hRoot = HKEY_LOCAL_MACHINE;
            CRegKey rkey;
            TCHAR szProductType[80];
            DWORD size;

            if (ERROR_SUCCESS == rkey.Open(hRoot, productkey))
            {
                rkey.QueryValue(szProductType, _T("ProductType"), &size);

                if (!lstrcmpi(_T("WINNT"), szProductType))
                    str += _T(" Workstation");
                else if (!lstrcmpi(_T("SERVERNT"), szProductType))
                    str += _T(" Server");
            }
        }

        // Display version, service pack (if any), and build number.

        ver.Format(IDS_PATTERN_OS, vi.dwMajorVersion, vi.dwMinorVersion
                  , vi.dwBuildNumber, (*vi.szCSDVersion == '\0')
                  ? _T("") : _T(" : "), vi.szCSDVersion);
        break;

    case VER_PLATFORM_WIN32_WINDOWS:

        switch (vi.dwMinorVersion)
        {
        case 0:
            str += _T(" 95");
            switch (toupper(vi.szCSDVersion[0]))
            {
            case 'A': str += _T(" OSR1"); break;
            case 'B': str += _T(" OSR2"); break;
            case 'C': str += _T(" OSR2.5"); break;
            }
            break;
        case 10:
            if (1998 == vi.dwBuildNumber)
                str += _T(" 98"); 
            else
                str += _T(" 98 SE"); 
            break;
        case 90:
            str += _T(" Me"); break;
        }
        
        ver.Format(IDS_PATTERN_OS, vi.dwMajorVersion, vi.dwMinorVersion
                  , LOWORD(vi.dwBuildNumber), _T(""), vi.szCSDVersion);
        break;

    case VER_PLATFORM_WIN32s:

        str += _T(" Win32s"); break;
        ver.Format(IDS_PATTERN_OS, vi.dwMajorVersion
                  , vi.dwMinorVersion, LOWORD(vi.dwBuildNumber));
    }

    str += ver;
    ATLTRY(m_szOsInfo = new TCHAR[str.GetLength() + 1]);
    CHECK_NULL(m_szOsInfo, E_OUTOFMEMORY);
    
    _tcscpy(m_szOsInfo, str);

    return S_OK; 
}

HRESULT CExeModule::GetDisplayModeList()
{
#if !defined(__USE_COM__)
    IDirectDraw7 *pDDraw = NULL;

    // Create a DirectDraw active device based using the NULL..
    if (FAILED(::DirectDrawCreateEx(NULL, (void**)&pDDraw, IID_IDirectDraw7, NULL)))
        return E_FAIL;
    
    if (FAILED(pDDraw->EnumDisplayModes(DDEDM_REFRESHRATES, NULL
                                       , &m_dml, EnumModesCallback)))
        return E_FAIL;
    RELEASE(pDDraw);
#else // __USE_COM__ is defined
#pragma message(">>> COM Used")
    
    CComPtr<IDirectDraw7> spDDraw;
    HRESULT hr;

    if (FAILED(spDDraw.CoCreateInstance(OLESTR("DirectDraw7"))))
        return E_FAIL;

    if (FAILED(hr = spDDraw->Initialize(NULL)))
        return hr;

    if (FAILED(spDDraw->EnumDisplayModes(DDEDM_REFRESHRATES, NULL
                                        , &m_dml, EnumModesCallback)))
        return E_FAIL;

#endif

    // Sort the list of display modes (width->height->freq)
    qsort(m_dml.GetData(), m_dml.GetSize(), sizeof(DISPLAYINFO), SortModesCallback);

    DISPLAYINFO di;
    TCHAR data[16];
    DWORD size = sizeof(data);

    CProfile cfg(_T(VR_PROFILE));

    cfg.Open(_T("common"));
    cfg.QueryValue(data, _T("Videomode"), &size);

    if (_stscanf(data, _T("%dx%d"), &di.width, &di.height) < 2)
    {
        di.width  = DEFAULT_DISPLAY_WIDTH;
        di.height = DEFAULT_DISPLAY_HEIGHT;
    }

    for (int i=0; i < m_dml.GetSize(); i++)
    {
            if (di.width == m_dml[i].width &&
                di.height == m_dml[i].height)
                m_ScreenMode = i;
    }

    return S_OK;
}

HRESULT WINAPI CExeModule::EnumModesCallback(LPDDSURFACEDESC2 pddsd
                                            , void *context)
{
    CDisplayModeList *pdml = (CDisplayModeList*)context;
    DISPLAYINFO di;

    if (MIN_DISPLAY_WIDTH <= pddsd->dwWidth &&
        MIN_DISPLAY_HEIGHT <= pddsd->dwHeight &&
       SUPPORTED_COLORDEPTH == pddsd->ddpfPixelFormat.dwRGBBitCount &&
       (MIN_DISPLAY_FREQUENCY <= pddsd->dwRefreshRate || 2 > pddsd->dwRefreshRate))
       // 0, 1 - reserved in Win9x (optimal, default)
    {
        di.width     = pddsd->dwWidth;
        di.height    = pddsd->dwHeight;
        di.frequency = pddsd->dwRefreshRate;
        di.depth     = pddsd->ddpfPixelFormat.dwRGBBitCount;
            
        pdml->Add(di);
    }

    return DDENUMRET_OK;    // continue enumeration
}

///////////////////////////////////////////////////////////////////////
// Callback function for sorting display modes 
// (used by GetDisplayModeList).
//
int CExeModule::SortModesCallback(const void *arg1, const void *arg2)
{
    LPDISPLAYINFO p1 = (LPDISPLAYINFO)arg1;
    LPDISPLAYINFO p2 = (LPDISPLAYINFO)arg2;

    if (p1->width < p2->width) return -1;
    if (p1->width > p2->width) return +1;
    if (p1->height < p2->height) return -1;
    if (p1->height > p2->height) return +1;
    if (p1->frequency < p2->frequency) return -1;
    if (p1->frequency > p2->frequency) return +1;

    return 0;
}
