// GPUProbe.cpp : Defines the initialization routines for the DLL.
//
// created by Unwinder
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"

#include <float.h>
#include <afxdllx.h>
#include <shlwapi.h>

#include "GPUProbe.h"
#include "RivaTunerExports.h"
#include "MonitoringSourceDesc.h"
/////////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
static AFX_EXTENSION_MODULE GPUProbeDLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
// script command opcodes
/////////////////////////////////////////////////////////////////////////////
#define SRC_OPERAND_TYPE_MASK	0x0000000F
#define SRC_OPERAND_TYPE_NONE	0x00000000
#define SRC_OPERAND_TYPE_ACC	0x00000001
#define SRC_OPERAND_TYPE_REG	0x00000002
#define SRC_OPERAND_TYPE_GPU	0x00000003
#define SRC_OPERAND_TYPE_IMM	0x00000004

#define DST_OPERAND_TYPE_MASK	0x000000F0
#define DST_OPERAND_TYPE_NONE	0x00000000
#define DST_OPERAND_TYPE_ACC	0x00000010
#define DST_OPERAND_TYPE_REG	0x00000020
#define DST_OPERAND_TYPE_GPU	0x00000030

#define OPERATION_TYPE_MASK		0x0000FF00
#define OPERATION_TYPE_MOV		0x00000000
#define OPERATION_TYPE_AND		0x00000100
#define OPERATION_TYPE_OR		0x00000200
#define OPERATION_TYPE_XOR		0x00000300
#define OPERATION_TYPE_SHR		0x00000400
#define OPERATION_TYPE_SHL		0x00000500
#define OPERATION_TYPE_ADD		0x00000600
#define OPERATION_TYPE_SUB		0x00000700
#define OPERATION_TYPE_MUL		0x00000800
#define OPERATION_TYPE_DIV		0x00000900
#define OPERATION_TYPE_CMP		0x00000A00
#define OPERATION_TYPE_SETZ		0x00000B00
#define OPERATION_TYPE_SETNZ	0x00000C00
/////////////////////////////////////////////////////////////////////////////
// flag bits
/////////////////////////////////////////////////////////////////////////////
#define FLAG_ZERO				0x00000001
/////////////////////////////////////////////////////////////////////////////
#define MAX_REG					256
#define MAX_BUF					256
/////////////////////////////////////////////////////////////////////////////
typedef struct GPU_PROBE_SCRIPT_COMMAND
{
	DWORD	dwParams;
		//command params number
	LPDWORD	lpParams;
		//ptr to array of command params
} GPU_PROBE_SCRIPT_COMMAND, *LPGPU_PROBE_SCRIPT_COMMAND;
/////////////////////////////////////////////////////////////////////////////
typedef struct GPU_PROBE_SCRIPT
{
	DWORD						dwCommands;
		//size of script
	LPGPU_PROBE_SCRIPT_COMMAND	lpCommands;
		//array of script commands
} GPU_PROBE_SCRIPT, *LPGPU_PROBE_SCRIPT;
/////////////////////////////////////////////////////////////////////////////
typedef struct GPU_PROBE_SOURCE
{
	// RivaTuner specific source description fields

	char	szName[MAX_PATH];
		//data source name
	char	szDim[MAX_PATH];
		//data source dimension
	float	fltMaxLimit;
		//default maximum graph limit 
	float	fltMinLimit;
		//default minimum graph limit 
	float					fltGridDim;
		//default grid dimension (amount of grid lines between min and max limits)
	char	szDesc[MAX_PATH];
		//source description
	char	szGroup[MAX_PATH];
		//data source group name used for grouping data by it in OSD, tooltip etc

	// 	Scripts related fields

	GPU_PROBE_SCRIPT		scriptGetDesc;
		//GetSourceDesc script
	GPU_PROBE_SCRIPT		scriptGetData;
		//GetSourceData script

} GPU_PROBE_SOURCE, *LPGPU_PROBE_SOURCE;
/////////////////////////////////////////////////////////////////////////////
READ_REGISTER_ULONG_PROC	g_pReadRegisterUlong	= NULL;
WRITE_REGISTER_ULONG_PROC	g_pWriteRegisterUlong	= NULL;

LPGPU_PROBE_SOURCE			g_lpSources				= NULL;
DWORD						g_dwSources				= 0;

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

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

		new CDynLinkLibrary(GPUProbeDLL);

		g_hModule = hInstance;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(GPUProbeDLL);

		UninitSources();
	}

	return 1;
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to deallocate previously allocated
// data associated with specified command
//////////////////////////////////////////////////////////////////////
void UninitCommand(LPGPU_PROBE_SCRIPT_COMMAND lpCommand)
{
	if (lpCommand && lpCommand->lpParams)
	{
		delete [] lpCommand->lpParams;

		lpCommand->lpParams	= NULL;
		lpCommand->dwParams	= 0;
	}
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to deallocate previously allocated
// data associated with specified script
//////////////////////////////////////////////////////////////////////
void UninitScript(LPGPU_PROBE_SCRIPT lpScript)
{
	if (lpScript && lpScript->lpCommands)
	{
		for (DWORD dwCommand=0; dwCommand<lpScript->dwCommands; dwCommand++)
			UninitCommand(&lpScript->lpCommands[dwCommand]);

		delete [] lpScript->lpCommands;

		lpScript->lpCommands = NULL;
		lpScript->dwCommands = 0;
	}
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to deallocate previously allocated
// sources
//////////////////////////////////////////////////////////////////////
void UninitSources()
{
	if (g_lpSources)
	{
		for (DWORD dwSource=0; dwSource<g_dwSources; dwSource++)
		{
			UninitScript(&g_lpSources[dwSource].scriptGetDesc);
			UninitScript(&g_lpSources[dwSource].scriptGetData);
		}

		delete [] g_lpSources;
	}

	g_lpSources = NULL;
	g_dwSources = 0;
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to initialize script command by
// specified command text
//////////////////////////////////////////////////////////////////////
void InitCommand(LPGPU_PROBE_SCRIPT_COMMAND lpCommand, LPCSTR lpSrc)
{
	char szBuf[MAX_BUF];
		//local string buffer

	// pass 1 : determine amount of operands in source string

	strcpy(szBuf, lpSrc);
		//init token string by copying source string (we cannot use
		//source string because strtok modifies input string)

	LPSTR lpToken = strtok(szBuf, " ");
		//get ptr to the first token

	while  (lpToken)
		//process all tokens
	{
		lpCommand->dwParams++;

		lpToken = strtok(NULL, " ");
	}

	//now we can allocate memory for command opcode and parameters

	lpCommand->lpParams = new DWORD[lpCommand->dwParams];
		//allocate memory
	ZeroMemory(lpCommand->lpParams, lpCommand->dwParams * sizeof(DWORD));
		//zap it

	LPDWORD lpParams = lpCommand->lpParams;
		//save ptr to command opcode and parameters, we'll use it
		//during initialization

	// pass 2 : initialize allocated command opcode and parameters

	strcpy(szBuf, lpSrc);
		//reinit token string by copying source string (we cannot use
		//source string because strtok modifies input string, and the
		//previous buffer is already modified by strtok)

	lpToken = strtok(szBuf, " ");
		//get ptr to the first token

	while  (lpToken)
		//process all tokens
	{
		sscanf(lpToken, "%X", lpParams++);
		
		lpToken = strtok(NULL, " ");
	}
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to initialize script by specified
// section name, command name and configuration file path
//////////////////////////////////////////////////////////////////////
void InitScript(LPGPU_PROBE_SCRIPT lpScript, LPCSTR lpSection, LPCSTR lpName, LPCSTR lpCfgPath)
{
	char szBuf[MAX_BUF];
		//local string buffer

	CString strName;
	strName.Format("%sCmd", lpName);
		//format command name

	GetPrivateProfileString(lpSection, strName, "", szBuf, MAX_BUF, lpCfgPath);
	if (strlen(szBuf))
		//allow commands to be wrapped to other section
	{
		InitScript(lpScript, szBuf, "", lpCfgPath);
		return;
	}

	// pass 1 : determine amount of commands in the script

	for (DWORD dwCommand=0; dwCommand<INT_MAX; dwCommand++)
		//loop through all commands
	{
		strName.Format("%sCmd%d", lpName, dwCommand);
			//format command name
		GetPrivateProfileString(lpSection, strName, "", szBuf, MAX_BUF, lpCfgPath);

			//read command 
		if (!strlen(szBuf))
			//break the loop if command doesn't exist
			break;

		lpScript->dwCommands++;
	}

	//now we can allocate memory for commands

	lpScript->lpCommands = new GPU_PROBE_SCRIPT_COMMAND[lpScript->dwCommands];
		//allocate memory
	ZeroMemory(lpScript->lpCommands, lpScript->dwCommands * sizeof(GPU_PROBE_SCRIPT_COMMAND));
		//zap it

	// pass 2 : initialize allocated commands

	for (dwCommand=0; dwCommand<lpScript->dwCommands; dwCommand++)
		//loop through all commands
	{
		strName.Format("%sCmd%d", lpName, dwCommand);
			//format command name
		GetPrivateProfileString(lpSection, strName, "", szBuf, MAX_BUF, lpCfgPath);
			//read command into buffer

		InitCommand(&lpScript->lpCommands[dwCommand], szBuf);
			//init command 
	}
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to allocate and initizlize sources 
// using .cfg file source definitions
//////////////////////////////////////////////////////////////////////
void InitSources()
{
	char szBuf[MAX_BUF];
		//local string buffer

	if (!g_lpSources)
		//perform initialization only once
	{
		char szCfgPath[MAX_PATH];
		GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
		
		PathRenameExtension(szCfgPath, ".cfg");
			//get fully qualified path to .cfg

		// pass 1 : determine amount of sources defined in .cfg file 

		for (DWORD dwSrc=0; dwSrc<INT_MAX; dwSrc++)
			//loop through all sources
		{
			CString strSrc;	strSrc.Format("Source%d", dwSrc);
				//format source section name

			GetPrivateProfileString(strSrc, "Name", "", szBuf, MAX_BUF, szCfgPath);
				//read source name from .cfg
			if (!strlen(szBuf))
				//break the loop if source doesn't exist (i.e. name is empty)
				break;

			g_dwSources++;
		}

		// now we can allocate memory for sources
	
		g_lpSources	= new GPU_PROBE_SOURCE[g_dwSources];
			//allocate memory
		ZeroMemory(g_lpSources, g_dwSources * sizeof(GPU_PROBE_SOURCE));
			//zap it

		// pass 2 : initialize allocated sources

		for (dwSrc=0; dwSrc<g_dwSources; dwSrc++)
			//loop through all sources
		{
			CString strSrc;	strSrc.Format("Source%d", dwSrc);
				//format source section name

			GetPrivateProfileString(strSrc, "Name"		, "", g_lpSources[dwSrc].szName	, 32		, szCfgPath);
				//read source name
			GetPrivateProfileString(strSrc, "Dim"		, "", g_lpSources[dwSrc].szDim	, 16		, szCfgPath);
				//read source dimension
			GetPrivateProfileString(strSrc, "MaxLimit"	, "", szBuf						, MAX_BUF	, szCfgPath);
				//read maximum limit
			sscanf(szBuf, "%f", &g_lpSources[dwSrc].fltMaxLimit);
				//scan maximum limit string and convert it to float
			GetPrivateProfileString(strSrc, "MinLimit"	, "", szBuf						, MAX_BUF	, szCfgPath);
				//read minimum limit
			sscanf(szBuf, "%f", &g_lpSources[dwSrc].fltMinLimit);
				//scan minimum limit string and convert it to float
			GetPrivateProfileString(strSrc, "GridDim"	, "", szBuf						, MAX_BUF	, szCfgPath);
				//read grid dimension
			sscanf(szBuf, "%f", &g_lpSources[dwSrc].fltGridDim);
				//scan grid dimension string and convert it to float
			GetPrivateProfileString(strSrc, "Desc"		, "", g_lpSources[dwSrc].szDesc	, 64		, szCfgPath);
				//read source description
			GetPrivateProfileString(strSrc, "Group"		, "", g_lpSources[dwSrc].szGroup, 16		, szCfgPath);
				//read source description

			InitScript(&g_lpSources[dwSrc].scriptGetDesc, strSrc, "GetDesc", szCfgPath);
				//init GetSourceDesc script
			InitScript(&g_lpSources[dwSrc].scriptGetData, strSrc, "GetData", szCfgPath);
				//init GetSourceData script
		}
	}
}
/////////////////////////////////////////////////////////////////////////////
// This helper function is used to parse specified script
/////////////////////////////////////////////////////////////////////////////
DWORD ParseScript(LPGPU_PROBE_SCRIPT lpScript)
{
	DWORD dwAcc				=	0;
		//init accumulator
	DWORD dwFlags			=	0;
		//init flags
	DWORD dwReg[MAX_REG]	= { 0 };
		//init general purpose registers
	
	for (DWORD dwCommand=0; dwCommand<lpScript->dwCommands; dwCommand++)
		//process commands
	{
		// During processing each command we'll do 4 steps:
		//
		// 1) get destination operand 
		// 2) get source operand 
		// 3) combine operands
		// 4) write destination operand back

		LPDWORD lpParams			= lpScript->lpCommands[dwCommand].lpParams;
			//get command parameters ptr
		LPDWORD lpOpcode			= lpParams++;
			//get command opcode ptr

		// command processing step 1: get destination operand 
	
		DWORD	dwDstOperand		= 0;
			//initialize destination operand
		LPDWORD	lpDstOperandParams	= NULL;
			//initialize destination operand parameters
		switch (*lpOpcode & DST_OPERAND_TYPE_MASK)
			//get destination operand depending on specified opcode
		{
		case DST_OPERAND_TYPE_ACC:
			dwDstOperand = dwAcc;
			break;
		case DST_OPERAND_TYPE_REG:
			lpDstOperandParams	= lpParams++;
			dwDstOperand		= dwReg[lpDstOperandParams[0] & (MAX_REG - 1)];
			break;
		case DST_OPERAND_TYPE_GPU:
			lpDstOperandParams	= lpParams++;
			dwDstOperand		= g_pReadRegisterUlong(lpDstOperandParams[0]);
			break;
		}

		// command processing step 2: get source operand 

		DWORD	dwSrcOperand		= 0;
			//initialize source operand
		LPDWORD	lpSrcOperandParams	= NULL;
			//initialize source operand parameters
		switch (*lpOpcode & SRC_OPERAND_TYPE_MASK)
			//get source operand depending on specified opcode
		{
		case SRC_OPERAND_TYPE_ACC:
			dwSrcOperand		= dwAcc;
			break;
		case SRC_OPERAND_TYPE_REG:
			lpSrcOperandParams	= lpParams++;
			dwSrcOperand		= dwReg[lpSrcOperandParams[0] & (MAX_REG - 1)];
			break;
		case SRC_OPERAND_TYPE_GPU:
			lpSrcOperandParams	= lpParams++;
			dwSrcOperand		= g_pReadRegisterUlong(lpSrcOperandParams[0]);
			break;
		case SRC_OPERAND_TYPE_IMM:
			lpSrcOperandParams	= lpParams++;
			dwSrcOperand		= lpSrcOperandParams[0];
			break;
		}

		// command processing step 3: combine operands

		switch (*lpOpcode & OPERATION_TYPE_MASK)
			//combine source and destination operands depending on specified opcode
		{
		case OPERATION_TYPE_MOV:
			dwDstOperand	= dwSrcOperand;
			break;
		case OPERATION_TYPE_AND:
			dwDstOperand	= dwDstOperand & dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_OR:
			dwDstOperand = dwDstOperand | dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_XOR:
			dwDstOperand = dwDstOperand ^ dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_SHR:
			dwDstOperand = dwDstOperand >> dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_SHL:
			dwDstOperand = dwDstOperand << dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_ADD:
			dwDstOperand = dwDstOperand + dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_SUB:
			dwDstOperand = dwDstOperand - dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_MUL:
			dwDstOperand = dwDstOperand * dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_DIV:
			dwDstOperand = dwDstOperand / dwSrcOperand;
			if (dwDstOperand)
				dwFlags		= dwFlags & ~FLAG_ZERO;
			else
				dwFlags		= dwFlags |	 FLAG_ZERO;
			break;
		case OPERATION_TYPE_CMP:
			if (dwSrcOperand == dwDstOperand)
				dwFlags		= dwFlags |	 FLAG_ZERO;
			else
				dwFlags		= dwFlags & ~FLAG_ZERO;
			break;
		case OPERATION_TYPE_SETZ:
			dwDstOperand = (dwFlags & FLAG_ZERO) ? 1 : 0;
			break;
		case OPERATION_TYPE_SETNZ:
			dwDstOperand = (dwFlags & FLAG_ZERO) ? 0 : 1;
			break;
		}

		// command processing step 4: write destination operand back

		switch (*lpOpcode & DST_OPERAND_TYPE_MASK)
			//write destination operand back depending on specified opcode
		{
		case DST_OPERAND_TYPE_ACC:
			dwAcc = dwDstOperand;
			break;
		case DST_OPERAND_TYPE_REG:
			dwReg[*lpDstOperandParams & (MAX_REG - 1)] = dwDstOperand;
			break;
		case DST_OPERAND_TYPE_GPU:
			g_pWriteRegisterUlong(*lpDstOperandParams, dwDstOperand);
			break;
		}
	}

	return dwAcc;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
/////////////////////////////////////////////////////////////////////////////
GPUPROBE_API DWORD GetSourcesNum()
{
	InitSources();
		//init sources defined in .cfg file

	return g_dwSources;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get descriptor
// for the specified data source
//////////////////////////////////////////////////////////////////////
GPUPROBE_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_pReadRegisterUlong	= (READ_REGISTER_ULONG_PROC)GetProcAddress(hHost, "ReadRegisterUlong");
	if (!g_pReadRegisterUlong)
		return FALSE;

	g_pWriteRegisterUlong	= (WRITE_REGISTER_ULONG_PROC)GetProcAddress(hHost, "WriteRegisterUlong");
	if (!g_pWriteRegisterUlong)
		return FALSE;

	InitSources();
		//init sources defined in .cfg file

	if (g_lpSources && (dwIndex < g_dwSources))
		//now we can return requested information to RivaTuner
	{
		strcpy(pDesc->szName	, g_lpSources[dwIndex].szName	);
		strcpy(pDesc->szDim		, g_lpSources[dwIndex].szDim	);

		pDesc->fltMaxLimit	= g_lpSources[dwIndex].fltMaxLimit;
		pDesc->fltMinLimit	= g_lpSources[dwIndex].fltMinLimit;
		pDesc->fltGridDim	= g_lpSources[dwIndex].fltGridDim;

		strcpy(pDesc->szDesc	, g_lpSources[dwIndex].szDesc	);
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, g_lpSources[dwIndex].szGroup	);

		if (g_lpSources[dwIndex].scriptGetDesc.dwCommands)
			return ParseScript(&g_lpSources[dwIndex].scriptGetDesc) != 0;

		return TRUE;
	}

	return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to poll data sources
/////////////////////////////////////////////////////////////////////////////
GPUPROBE_API FLOAT GetSourceData(DWORD dwIndex)
{
	FLOAT	result	= FLT_MAX;

	if (g_lpSources && (dwIndex < g_dwSources))
		//check if specified source index is valid and parse GetData script
		//if available
		result = (FLOAT)ParseScript(&g_lpSources[dwIndex].scriptGetData);

	return result;
}
/////////////////////////////////////////////////////////////////////////////
