// CPU.cpp : Defines the initialization routines for the DLL.
//
// created by Unwinder
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <float.h>
#include <afxdllx.h>
#include <shlwapi.h>
/////////////////////////////////////////////////////////////////////////////
#include "CPU.h"
#include "RivaTunerExports.h"
#include "MonitoringSourceDesc.h"
#include "CPUClockSetupDlg.h"
#include "CPUUsageSetupDlg.h"
/////////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
static AFX_EXTENSION_MODULE CPUDLL	= { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
// define max amount of processors supported by our plugin
/////////////////////////////////////////////////////////////////////////////
#define	MAX_CPU									8
/////////////////////////////////////////////////////////////////////////////
// define constants / structures and function prototype for NTDLL.dll
// NtQuerySystemInformation function which will be used for CPU usage 
// calculation
/////////////////////////////////////////////////////////////////////////////
#define SystemProcessorPerformanceInformation	8

typedef HRESULT (WINAPI *NTQUERYSYSTEMINFORMATION)(UINT, PVOID, ULONG, PULONG);

typedef struct SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION 
{
    LARGE_INTEGER	IdleTime;
    LARGE_INTEGER	KernelTime;
    LARGE_INTEGER	UserTime;
    LARGE_INTEGER	Reserved1[2];
    ULONG			Reserved2;
} SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;
/////////////////////////////////////////////////////////////////////////////
// define constants and function prototypes for UXTHEME API which will
// be used to make our UI dialogs look modern
/////////////////////////////////////////////////////////////////////////////
typedef BOOL (WINAPI* ISAPPTHEMED)();
typedef HRESULT (__stdcall*  ENABLETHEMEDIALOGTEXTURE)(HWND, DWORD);

#define ETDT_ENABLE								2
#define ETDT_USETABTEXTURE						4
#define ETDT_ENABLETAB							(ETDT_ENABLE  | ETDT_USETABTEXTURE)
/////////////////////////////////////////////////////////////////////////////
// define and init global variables
/////////////////////////////////////////////////////////////////////////////
LOCALIZEWND_PROC			g_pLocalizeWnd					= NULL;
LOCALIZESTR_PROC			g_pLocalizeStr					= NULL;

DWORD						g_dwNumberOfProcessors			= 0;
NTQUERYSYSTEMINFORMATION	g_pNtQuerySystemInformation		= NULL;

LARGE_INTEGER				g_timeStamp[MAX_CPU]			= {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
LARGE_INTEGER				g_perfFrequency					= {0,0};
LARGE_INTEGER				g_perfCounterForClock[MAX_CPU]	= {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
DWORD						g_dwTickCountForClock[MAX_CPU]	= {0,0,0,0,0,0,0,0};
FLOAT						g_fltClock[MAX_CPU]				= {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX};
BOOL						g_bCalcClockViaHiResTimer		= TRUE;

LARGE_INTEGER				g_idleTime[MAX_CPU]				= {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
LARGE_INTEGER				g_perfCounterForUsage[MAX_CPU]	= {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
DWORD						g_dwTickCountForUsage[MAX_CPU]	= {0,0,0,0,0,0,0,0};
FLOAT						g_fltUsage[MAX_CPU]				= {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX};
BOOL						g_bCalcUsageViaHiResTimer		= TRUE;

HBRUSH						g_hBrush						= 0;
HINSTANCE					g_hModule						= 0;
/////////////////////////////////////////////////////////////////////////////
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	UNREFERENCED_PARAMETER(lpReserved);

	if (dwReason == DLL_PROCESS_ATTACH)
	{
		if (!AfxInitExtensionModule(CPUDLL, hInstance))
			return 0;

		new CDynLinkLibrary(CPUDLL);

		g_hModule	= hInstance;

		//create global brush for OnCtlControl handlers in our dialogs
		g_hBrush	= CreateSolidBrush(RGB(0, 0, 128));

		//get fully qualified path to .cfg
		char szCfgPath[MAX_PATH];
		GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
		
		PathRenameExtension(szCfgPath, ".cfg");

		//load plugin settings
		g_bCalcClockViaHiResTimer = GetPrivateProfileInt("Settings", "CalcClockViaHiResTimer", 1, szCfgPath);
		g_bCalcUsageViaHiResTimer = GetPrivateProfileInt("Settings", "CalcUsageViaHiResTimer", 1, szCfgPath);
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(CPUDLL);

		//destroy global brush we've created

		if (g_hBrush)
			DeleteObject(g_hBrush);
	}
	return 1;
}
/////////////////////////////////////////////////////////////////////////////
// This helper funtrion is used to localize string if host application 
// supports localization
/////////////////////////////////////////////////////////////////////////////
LPCSTR LocalizeStr(LPCSTR lpStr)
{
	if (g_pLocalizeStr)
		return g_pLocalizeStr(lpStr);

	return lpStr;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
/////////////////////////////////////////////////////////////////////////////
CPU_API DWORD GetSourcesNum()
{
	return 2 * MAX_CPU;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get descriptor
// for the specified data source
/////////////////////////////////////////////////////////////////////////////
CPU_API BOOL GetSourceDesc(DWORD dwIndex, LPMONITORING_SOURCE_DESC pDesc)
{
	//get host application module handle

	HMODULE hHost = GetModuleHandle(NULL);
	if (!hHost)
		return FALSE;

	//get ptr to host application ID routine and return error if the host doesn't 
	//export it

	//we'll use this condition to verify if we're under RivaTuner v2.0 RC 15.7
	//(older versions are not supported) or any other compatible host

	GET_HOST_APP_ID_PROC pGetHostAppID = (GET_HOST_APP_ID_PROC)GetProcAddress(hHost, "GetHostAppID");
	if (!pGetHostAppID)
		return FALSE;

	//get ptrs to required plugin API functions

	g_pLocalizeWnd		= (LOCALIZEWND_PROC)GetProcAddress(hHost, "LocalizeWnd");
	g_pLocalizeStr		= (LOCALIZESTR_PROC)GetProcAddress(hHost, "LocalizeStr");

	//init global variables, which will be used for CPU clock / CPU usage calculation 

	if (!g_dwNumberOfProcessors)
		//init number of processors if it is not initialized yet
	{
		SYSTEM_INFO info;
		GetSystemInfo(&info); 

		g_dwNumberOfProcessors = info.dwNumberOfProcessors;
	}

	if (!g_pNtQuerySystemInformation)
		//init ptr to NtQuerySystemInformation if it is not initialized yet
		g_pNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandle("NTDLL"), "NtQuerySystemInformation");


	if (!g_perfFrequency.QuadPart)
		//init high-resolution timer if it is not initialized yet
		QueryPerformanceFrequency(&g_perfFrequency);

	if (dwIndex >= g_dwNumberOfProcessors * 2)
		//check specified data source index and return FALSE if it is invalid
		return FALSE;

	switch (dwIndex & 1)
	{
	case 0:

		//CPU clock data source

		if (g_dwNumberOfProcessors > 1)
		{
			sprintf(pDesc->szName, "CPU%d \\ CPU clock", dwIndex>>1);
			if (pDesc->dwVersion >= 0x00010002)
				sprintf(pDesc->szGroup, "CPU%d", dwIndex>>1);
		}
		else
		{
			strcpy(pDesc->szName, "CPU clock");
			if (pDesc->dwVersion >= 0x00010002)
				strcpy(pDesc->szGroup, "CPU");
		}
		strcpy(pDesc->szDim		, "MHz");


		pDesc->fltMaxLimit	= 5000.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		if (g_dwNumberOfProcessors > 1)
		{
			CString strDesc;
			strDesc.Format(LocalizeStr(" CPU%d clock frequency calculated via RDTSC "), dwIndex>>1);
			strncpy(pDesc->szDesc, strDesc, sizeof(pDesc->szDesc) - 1);
		}
		else
			strcpy(pDesc->szDesc, " CPU clock frequency calculated via RDTSC ");
		return TRUE;

	case 1:

		//CPU usage data source

		if (g_dwNumberOfProcessors > 1)
		{
			sprintf(pDesc->szName, "CPU%d \\ CPU usage", dwIndex>>1);
			if (pDesc->dwVersion >= 0x00010002)
				sprintf(pDesc->szGroup, "CPU%d", dwIndex>>1);
		}
		else
		{
			strcpy(pDesc->szName, "CPU usage");
			if (pDesc->dwVersion >= 0x00010002)
				strcpy(pDesc->szGroup, "CPU");
		}
		strcpy(pDesc->szDim		, "%");

		pDesc->fltMaxLimit	= 100.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		if (g_dwNumberOfProcessors > 1)
		{
			CString strDesc;
			strDesc.Format(LocalizeStr(" CPU%d usage calculated via NtQuerySystemInformation "), dwIndex>>1);
			strncpy(pDesc->szDesc, strDesc, sizeof(pDesc->szDesc) - 1);
		}
		else
			strcpy(pDesc->szDesc, " CPU usage calculated via NtQuerySystemInformation ");
		return TRUE;
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get capabilities
// for the specified data source
//////////////////////////////////////////////////////////////////////
CPU_API BOOL GetSourceCaps(DWORD dwIndex, DWORD dwCaps, LPDWORD lpResult)
{
	//all sources are system wide monitoring so it is safe to ignore passed
	//index and check just the capability

	switch (dwCaps)
	{
	case  MONITORING_SOURCE_CAPS_SYSTEM_WIDE:
		*lpResult = 1;
		return TRUE;
	}

	return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to check if a source
// provides any additional settings and for customizing it when <More>
// button is clicked in the source's properties window
//
// The function is called with hWnd = NULL during checking and with parent
// window's hWnd during customization
/////////////////////////////////////////////////////////////////////////////
CPU_API BOOL SetupSource(DWORD dwIndex, HWND hWnd)
{
	if (hWnd)
		//hWnd is not NULL, so we must display our dialogs to customize 
		//properties for the specified data source
	{
		//get fully qualitied path to .cfg

		char szCfgPath[MAX_PATH];
		GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
		
		PathRenameExtension(szCfgPath, ".cfg");

		if (dwIndex & 1)
			//customize CPU usage data sources
		{
			CCPUUsageSetupDlg dlg(CWnd::FromHandle(hWnd));
				//create customization dialog

			dlg.m_bCalcUsageViaHiResTimer = g_bCalcUsageViaHiResTimer;
				//pass data source settings to the dialog

			if (IDOK == dlg.DoModal())
				//save modified data source settings to .cfg if necessary
			{
				g_bCalcUsageViaHiResTimer = dlg.m_bCalcUsageViaHiResTimer;
				WritePrivateProfileString("Settings", "CalcUsageViaHiResTimer", g_bCalcUsageViaHiResTimer ? " 1" : " 0", szCfgPath);

				//we must reset all counters to ensure that the next
				//GetSourceData call won't return wrongly calculated
				//value

				memset(g_idleTime, 0, sizeof(g_idleTime));
				memset(g_perfCounterForUsage, 0, sizeof(g_perfCounterForUsage));
				memset(g_dwTickCountForUsage, 0, sizeof(g_dwTickCountForUsage));
			}
		}
		else
			//customize CPU clock data sources
		{
			CCPUClockSetupDlg dlg(CWnd::FromHandle(hWnd));
				//create customization dialog

			dlg.m_bCalcClockViaHiResTimer = g_bCalcClockViaHiResTimer;
				//pass data source settings to the dialog

			if (IDOK == dlg.DoModal())
				//save modified data source settings to .cfg if necessary
			{
				g_bCalcClockViaHiResTimer = dlg.m_bCalcClockViaHiResTimer;
				WritePrivateProfileString("Settings", "CalcClockViaHiResTimer", g_bCalcClockViaHiResTimer ? " 1" : " 0", szCfgPath);

				//we must reset all counters to ensure that the next
				//GetSourceData call won't return wrongly calculated
				//value

				memset(g_timeStamp, 0, sizeof(g_timeStamp));
				memset(g_perfCounterForClock, 0, sizeof(g_perfCounterForClock));
				memset(g_dwTickCountForClock, 0, sizeof(g_dwTickCountForClock));
			}
		}
	}

	//hWnd is NULL, simply return TRUE as all the sources exported by our plugin
	//have additional propertoes

	return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function will be called by our UI dialogs to make them look
// modern under WindowsXP
/////////////////////////////////////////////////////////////////////////////
void EnableThemedBgnd(HWND hWnd)
{
	HMODULE hUXThemeDLL = LoadLibrary("UXTHEME.DLL");

	if (hUXThemeDLL)
	{
		ISAPPTHEMED pIsAppThemed = (ISAPPTHEMED)GetProcAddress(hUXThemeDLL, "IsAppThemed");

		if (pIsAppThemed)
			if (pIsAppThemed())
			{
				ENABLETHEMEDIALOGTEXTURE pEnableThemeDialogTexture = (ENABLETHEMEDIALOGTEXTURE)::GetProcAddress(hUXThemeDLL, "EnableThemeDialogTexture");

				if (pEnableThemeDialogTexture)
					pEnableThemeDialogTexture(hWnd, ETDT_ENABLETAB);
			}

		FreeLibrary(hUXThemeDLL);
	}
}
/////////////////////////////////////////////////////////////////////////////
// This helper function calculates usage for specified CPU
/////////////////////////////////////////////////////////////////////////////
float CalcUsage(DWORD dwCPU)
{
	//validate specified CPU index and return FLT_MAX on error

	if (dwCPU >= g_dwNumberOfProcessors)
		return FLT_MAX;

	if (g_bCalcUsageViaHiResTimer)
		//calculate CPU usage using high resolution timer
	{
		if (g_perfFrequency.QuadPart)
			//we can perform calcualtion only if high-resolution timer is available
		{
			LARGE_INTEGER perfCounter;
			QueryPerformanceCounter(&perfCounter);
				//query high-resolution time counter

			if (perfCounter.QuadPart - g_perfCounterForUsage[dwCPU].QuadPart >= g_perfFrequency.QuadPart)
				//update usage once per second
			{
				SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION info[MAX_CPU];

				if (SUCCEEDED(g_pNtQuerySystemInformation(SystemProcessorPerformanceInformation, &info, sizeof(info), NULL)))
					//query CPU usage
				{
					if (g_idleTime[dwCPU].QuadPart)
						//ensure that this function was already called at least once
						//and we have the previous idle time value
					{
						g_fltUsage[dwCPU] = 100.0f - 0.00001f * (info[dwCPU].IdleTime.QuadPart - g_idleTime[dwCPU].QuadPart) * g_perfFrequency.QuadPart / (perfCounter.QuadPart - g_perfCounterForUsage[dwCPU].QuadPart);
							//calculate new CPU usage value by estimating amount of time
							//CPU was in idle during the last second

						//clip calculated CPU usage to [0-100] range to filter calculation non-ideality

						if (g_fltUsage[dwCPU] < 0.0f)
							g_fltUsage[dwCPU] = 0.0f;

						if (g_fltUsage[dwCPU] > 100.0f)
							g_fltUsage[dwCPU] = 100.0f;

					}

					g_idleTime[dwCPU]				= info[dwCPU].IdleTime;
						//save new idle time for specified CPU
					g_perfCounterForUsage[dwCPU]	= perfCounter;
						//save new performance counter for specified CPU
				}
			}
		}
	}
	else
		//calculate CPU usage using standard timer
	{
		DWORD dwTickCount = GetTickCount();
			//get standard timer tick count

		if (dwTickCount - g_dwTickCountForUsage[dwCPU] >= 1000)
			//update usage once per second
		{
			SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION info[MAX_CPU];

			if (SUCCEEDED(g_pNtQuerySystemInformation(SystemProcessorPerformanceInformation, &info, sizeof(info), NULL)))
					//query CPU usage
			{
				if (g_idleTime[dwCPU].QuadPart)
					//ensure that this function was already called at least once
					//and we have the previous idle time value
				{
					g_fltUsage[dwCPU] = 100.0f - 0.01f * (info[dwCPU].IdleTime.QuadPart - g_idleTime[dwCPU].QuadPart) / (dwTickCount - g_dwTickCountForUsage[dwCPU]);
						//calculate new CPU usage value by estimating amount of time
						//CPU was in idle during the last second

					//clip calculated CPU usage to [0-100] range to filter calculation non-ideality

					if (g_fltUsage[dwCPU] < 0.0f)
						g_fltUsage[dwCPU] = 0.0f;

					if (g_fltUsage[dwCPU] > 100.0f)
						g_fltUsage[dwCPU] = 100.0f;
				}

				g_idleTime[dwCPU]				= info[dwCPU].IdleTime;
					//save new idle time for specified CPU
				g_dwTickCountForUsage[dwCPU]	= dwTickCount;
					//save new tick count for specified CPU
			}
		}
	}

	return g_fltUsage[dwCPU];
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads timestamp counter
/////////////////////////////////////////////////////////////////////////////
LARGE_INTEGER ReadTimeStampCounter()
{
	LARGE_INTEGER timeStamp;

	_asm 
	{
		rdtsc
		mov timeStamp.LowPart , eax
		mov timeStamp.HighPart, edx
	}

	return timeStamp;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function calculates clock frequency for specified CPU
/////////////////////////////////////////////////////////////////////////////
float CalcClock(DWORD dwCPU)
{
	//validate specified CPU index and return FLT_MAX on error

	if (dwCPU >= g_dwNumberOfProcessors)
		return FLT_MAX;


	//get process affinity mask

	HANDLE hProcess = GetCurrentProcess();

	DWORD dwProcessAffinityMask, dwSystemAffinityMask;
	GetProcessAffinityMask(hProcess, &dwProcessAffinityMask, &dwSystemAffinityMask);

	if (g_dwNumberOfProcessors > 1)
		//if we're under multi-CPU system then we need to force OS to execute our code
		//on specified CPU in order to read proper TSC
	{
		SetProcessAffinityMask(hProcess, 1<<dwCPU);
			//force OS to execute our code on specified CPU
		Sleep(0);
			//give it some time for CPU switching
	}

	if (g_bCalcClockViaHiResTimer)
		//calculate CPU clock using high-resolution timer
	{
		if (g_perfFrequency.QuadPart)
		{
			LARGE_INTEGER perfCounter;
			QueryPerformanceCounter(&perfCounter);
				//query high-resolution time counter

			LARGE_INTEGER timeStamp = ReadTimeStampCounter();
				//query timestamp counter

			if (perfCounter.QuadPart - g_perfCounterForClock[dwCPU].QuadPart >= g_perfFrequency.QuadPart)
				//update clock once per second
			{
				if (g_perfCounterForClock[dwCPU].QuadPart)
					g_fltClock[dwCPU] = 0.000001f * (timeStamp.QuadPart - g_timeStamp[dwCPU].QuadPart) * g_perfFrequency.QuadPart / (perfCounter.QuadPart - g_perfCounterForClock[dwCPU].QuadPart);

				g_timeStamp[dwCPU]				= timeStamp;
				g_perfCounterForClock[dwCPU]	= perfCounter;
			}
		}
	}
	else
		//calculate CPU clock using standard timer
	{
		DWORD dwTickCount = GetTickCount();
			//get tick count

		LARGE_INTEGER timeStamp = ReadTimeStampCounter();
			//query timestamp counter

		if (dwTickCount - g_dwTickCountForClock[dwCPU] >= 1000)
			//update clock once per second
		{
			if (g_dwTickCountForClock[dwCPU])
				g_fltClock[dwCPU]	= 0.001f * (timeStamp.QuadPart - g_timeStamp[dwCPU].QuadPart) / (dwTickCount - g_dwTickCountForClock[dwCPU]);

			g_timeStamp[dwCPU]				= timeStamp;
			g_dwTickCountForClock[dwCPU]	= dwTickCount;
		}
	}

	if (dwSystemAffinityMask != 1)
		SetProcessAffinityMask(hProcess, dwProcessAffinityMask);
			//restore the previous process affinity mask 

	return g_fltClock[dwCPU];
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to poll data sources
/////////////////////////////////////////////////////////////////////////////
CPU_API FLOAT GetSourceData(DWORD dwIndex)
{
	if (dwIndex & 1)
		return CalcUsage(dwIndex>>1);

	return CalcClock(dwIndex>>1);
}
/////////////////////////////////////////////////////////////////////////////
