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

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

#include "Context.h"
#include "W83L785R.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 W83L785RDLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
#define MAX_GPU															16
/////////////////////////////////////////////////////////////////////////////
GET_DEVICE_ID_EX_PROC		g_pGetDeviceIDEx	= NULL;
GET_I2C_BUS_NUM_EX_PROC		g_pGetI2CBusNumEx	= NULL;
READ_I2C_EX_PROC			g_pReadI2CEx		= NULL;

CContext					g_context[MAX_GPU];

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(W83L785RDLL, hInstance))
			return 0;

		new CDynLinkLibrary(W83L785RDLL);

		g_hModule = hInstance;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(W83L785RDLL);
	}
	return 1;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function rips required string param from specified
//<Param0><Separator><Param1><Separator>...<ParamN>-styled string
/////////////////////////////////////////////////////////////////////////////
BOOL GetParam(int iParam, LPCSTR lpSrc, CString &strParam, LPCSTR lpSeparator)
{
	CString strSrc = lpSrc;

	int iFirst = 0;

	while (iParam)
	{
		iFirst = strSrc.Find(lpSeparator, iFirst);

		if (iFirst == -1)
			return FALSE;

		iFirst++;
		iParam--;
	}

	int iLast = strSrc.Find(lpSeparator ,iFirst);

	if (iLast == -1)
		iLast = strlen(lpSrc);

	strParam = strSrc.Mid(iFirst, iLast - iFirst);

	strParam.TrimRight();
	strParam.TrimLeft();

	return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function compares two strings using whildcard character
/////////////////////////////////////////////////////////////////////////////
int CompareWithWildcard(LPCSTR str0, LPCSTR str1, char wildcard)
{
	int nLen0	= strlen(str0);
	int nLen1	= strlen(str1);

	if (nLen0 > nLen1)
		return 1;

	if (nLen0 < nLen1)
		return -1;

	for (int i=0; i<nLen0; i++)
	{
		char c0 = str0[i];
		char c1 = str1[i];

		if ((c0 != wildcard) &&
			(c1 != wildcard))
			if (c0 != c1)
				return (c0 > c1) ? 1 : -1;
	}

	return 0;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads the first matching entry for the currently
// selected display adapter from the specified section of .cfg
//
// Note: the entries in .cfg can be specified with wildcard character '?'
/////////////////////////////////////////////////////////////////////////////
CString GetMatchingHardwareEntry(DWORD dwGpu, LPCSTR lpSection)
{
	char szCfgPath[MAX_PATH];
	GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
	
	PathRenameExtension(szCfgPath, ".cfg");
		//get fully qualified path to .cfg

	LPSTR	lpBuf		= new char[32768];
		//allocate buffer for section
	if (GetPrivateProfileSection(lpSection, lpBuf, 32768, szCfgPath))
		//try to read section, continue if succeeded
	{
		CString strDeviceID	= g_pGetDeviceIDEx(dwGpu);	
			//get display adapter's PCI DeviceID string

		LPSTR	lpStr	= (LPSTR)lpBuf;
 		int		nLen	= strlen(lpStr);

		CString strLValue, strRValue;

		while (nLen)
			//loop through all section's lines
		{

			if (GetParam(0, lpStr, strLValue, "=") &&
				GetParam(1, lpStr, strRValue, "="))
				//try to rip <LValue>=<RValue>-formatted line and proceed
				//if format is correct
			{
				if (!CompareWithWildcard(strLValue, strDeviceID, '?'))
					//compare LValue with PCI DeviceID string
					//take a note that we'll use wildcards to allow multiple
					//display adapters to be specified with a single .cfg line
				{
					delete [] lpBuf;
						//deallocate buffer we've allocated before

					return strRValue;
				}
			}

			lpStr	+=	nLen + 1;
			nLen	=	strlen(lpStr);
				//proceed with next section's line
		}
	}

	delete [] lpBuf;
		//deallocate buffer we've allocated before

	return "";
		//return empty string if we've failed to find matching hardware entry
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
//////////////////////////////////////////////////////////////////////
W83L785R_API DWORD GetSourcesNum()
{
	return 8;
}
//////////////////////////////////////////////////////////////////////
// This helper function formats multiGPU friendly group name
////////////////////////////////////////////////////////////////////////
CString FormatMultiGpuGroupName(LPCSTR lpName, DWORD dwGpu)
{
	CString strName;

	if (dwGpu)
		strName.Format("%s%d", lpName, dwGpu - 1);
	else
		strName = lpName;

	return strName;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get descriptor
// for the specified data source
//////////////////////////////////////////////////////////////////////
W83L785R_API BOOL GetSourceDesc(DWORD dwIndex, LPMONITORING_SOURCE_DESC pDesc)
{
	DWORD dwGpu		= dwIndex>>16;
	DWORD dwSource	= dwIndex & 0xFFFF;
		//extract GPU and data source indices from macro index

	if (dwGpu >= MAX_GPU)
		//validate GPU index
		return FALSE;

	//get host application module handle

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

	if (!g_context[dwGpu].m_dev)
		//small performance optimization: once the sensor is detected, we'll
		//precache its' location and will no longer perform I2C scanning
	{
		//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_pGetDeviceIDEx	= (GET_DEVICE_ID_EX_PROC)GetProcAddress(hHost, "GetDeviceIDEx");
		if (!g_pGetDeviceIDEx)
			return FALSE;

		g_pGetI2CBusNumEx	= (GET_I2C_BUS_NUM_EX_PROC)GetProcAddress(hHost, "GetI2CBusNumEx");
		if (!g_pGetI2CBusNumEx)
			return FALSE;

		g_pReadI2CEx		= (READ_I2C_EX_PROC)GetProcAddress(hHost, "ReadI2CEx");
		if (!g_pReadI2CEx)
			return FALSE;

		//once we've got all function ptrs, we can proceed with I2C bus scanning

		//first, we'll get number of I2C buses for the selected display device

		BYTE busNum		= g_pGetI2CBusNumEx(dwGpu);
		if (!busNum)
			return FALSE;

		//scan all I2C buses and check W83L785R sensor presence	

		for (BYTE bus=0; bus<busNum; bus++)
		{
			BYTE data;

			if (g_pReadI2CEx(dwGpu, bus, 0x2D, 0x4C, &data) && (data == 0xA3))
				//check W83L785R vendor ID (low byte)
				if (g_pReadI2CEx(dwGpu, bus, 0x2D, 0x4D, &data) && (data == 0x5C))
					//check W83L785R vendor ID (high byte)
					if (g_pReadI2CEx(dwGpu, bus, 0x2D, 0x4E, &data) && (data == 0x60))
						//check W83L785R chip ID
					{
						//we've found W83L785R and can precache its' location

						g_context[dwGpu].m_bus = bus;
						g_context[dwGpu].m_dev = 0x2D;
						break;
					}
		}

		//once the sensor is detected we should calibrate its' voltage inputs
		//to do it we'll read matching entry for our hardware from the .cfg's
		//[VoltageRatioN] section and rip voltage input ratios from it

		sscanf(GetMatchingHardwareEntry(dwGpu, "VoltageRatio0"), "%f", &g_context[dwGpu].m_fltVoltageRatio0);
		sscanf(GetMatchingHardwareEntry(dwGpu, "VoltageRatio1"), "%f", &g_context[dwGpu].m_fltVoltageRatio1);
		sscanf(GetMatchingHardwareEntry(dwGpu, "VoltageRatio2"), "%f", &g_context[dwGpu].m_fltVoltageRatio2);
		sscanf(GetMatchingHardwareEntry(dwGpu, "VoltageRatio3"), "%f", &g_context[dwGpu].m_fltVoltageRatio3);
	}

	if (!g_context[dwGpu].m_dev)
		//return FALSE if we've not detected W83L785R
		return FALSE;

	CString strSourceName;

	switch (dwSource)
		//now the sensor's location is detected,
		//so we can return requested information to RivaTuner
	{
	case 0:
		//fill temperature 0 source descriptor

		strSourceName = GetMatchingHardwareEntry(dwGpu, "TemperatureName0");
		if (strSourceName.IsEmpty())
			return FALSE;

		strcpy(pDesc->szName	, strSourceName);
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, GetMatchingHardwareEntry(dwGpu, "TemperatureGroup0"));

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

		strcpy(pDesc->szDesc, " Temperature reported by W83L785R-compatible sensor ");

		return TRUE;

	case 1:
		//fill temperature 1 source descriptor

		strSourceName = GetMatchingHardwareEntry(dwGpu, "TemperatureName1");
		if (strSourceName.IsEmpty())
			return FALSE;

		strcpy(pDesc->szName	, strSourceName);
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, GetMatchingHardwareEntry(dwGpu, "TemperatureGroup1"));

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

		strcpy(pDesc->szDesc, " Temperature reported by W83L785R-compatible sensor ");

		return TRUE;

	case 2:
		//fill fan duty cycle source descriptor

		strcpy(pDesc->szName	, "Fan duty cycle");
		strcpy(pDesc->szDim		, "%");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, "GPU");

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

		strcpy(pDesc->szDesc, " Fan duty cycle reported by W83L785R-compatible sensor ");

		return TRUE;

	case 3:
		if (g_context[dwGpu].m_fltVoltageRatio0 != 0.0f)
			//fill voltage 0 source descriptor if voltage input 0 is calibrated properly
		{
			strSourceName = GetMatchingHardwareEntry(dwGpu, "VoltageName0");
			if (strSourceName.IsEmpty())
				return FALSE;

			strcpy(pDesc->szName	, strSourceName);
			strcpy(pDesc->szDim		, "V");
			if (pDesc->dwVersion >= 0x00010002)
				strcpy(pDesc->szGroup, GetMatchingHardwareEntry(dwGpu, "VoltageGroup0"));

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

			strcpy(pDesc->szDesc, " Voltage reported by W83L785R-compatible sensor ");

			return TRUE;
		}
		break;

	case 4:
		if (g_context[dwGpu].m_fltVoltageRatio1 != 0.0f)
			//fill voltage 1 source descriptor if voltage input 1 is calibrated properly
		{
			strSourceName = GetMatchingHardwareEntry(dwGpu, "VoltageName1");
			if (strSourceName.IsEmpty())
				return FALSE;

			strcpy(pDesc->szName	, strSourceName);
			strcpy(pDesc->szDim		, "V");
			if (pDesc->dwVersion >= 0x00010002)
				strcpy(pDesc->szGroup, GetMatchingHardwareEntry(dwGpu, "VoltageGroup1"));

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

			strcpy(pDesc->szDesc, " Voltage reported by W83L785R-compatible sensor ");

			return TRUE;
		}
		break;

	case 5:
		if (g_context[dwGpu].m_fltVoltageRatio2 != 0.0f)
			//fill voltage 2 source descriptor if voltage input 2 is calibrated properly
		{
			strSourceName = GetMatchingHardwareEntry(dwGpu, "VoltageName2");
			if (strSourceName.IsEmpty())
				return FALSE;

			strcpy(pDesc->szName	, strSourceName);
			strcpy(pDesc->szDim		, "V");
			if (pDesc->dwVersion >= 0x00010002)
				strcpy(pDesc->szGroup, GetMatchingHardwareEntry(dwGpu, "VoltageGroup2"));

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

			strcpy(pDesc->szDesc, " Voltage reported by W83L785R-compatible sensor ");

			return TRUE;
		}
		break;

	case 6:
		if (g_context[dwGpu].m_fltVoltageRatio3 != 0.0f)
			//fill voltage 3 source descriptor if voltage input 3 is calibrated properly
		{
			strSourceName = GetMatchingHardwareEntry(dwGpu, "VoltageName3");
			if (strSourceName.IsEmpty())
				return FALSE;

			strcpy(pDesc->szName	, strSourceName);
			strcpy(pDesc->szDim		, "V");
			if (pDesc->dwVersion >= 0x00010002)
				strcpy(pDesc->szGroup, GetMatchingHardwareEntry(dwGpu, "VoltageGroup3"));

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

			strcpy(pDesc->szDesc, " Voltage reported by W83L785R-compatible sensor ");

			return TRUE;
		}
		break;

	case 7:
		//fill fan speed source descriptor

		strcpy(pDesc->szName	, "Fan speed");
		strcpy(pDesc->szDim		, "RPM");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, GetMatchingHardwareEntry(dwGpu, "GPU"));

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

		strcpy(pDesc->szDesc, " Fan speed reported by W83L785R-compatible sensor ");

		return TRUE;
	}

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

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

	return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to poll data sources
/////////////////////////////////////////////////////////////////////////////
W83L785R_API FLOAT GetSourceData(DWORD dwIndex)
{
	DWORD dwGpu		= dwIndex>>16;
	DWORD dwSource	= dwIndex & 0xFFFF;
		//extract GPU and data source indices from macro index

	if (dwGpu >= MAX_GPU)
		//validate GPU index
		return FLT_MAX;

	FLOAT	result	= FLT_MAX;	
	BYTE	data;

	switch (dwSource)
	{
	case 0:
		//temperature 0

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x27, &data))
			result = (char)data;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltTemperature0 = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltTemperature0;

	case 1:
		//temperature 1

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x26, &data))
			result = (char)data;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltTemperature1 = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltTemperature1;

	case 2:
		//fan duty cycle

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x81, &data))
			result = data * 100.0f / 255.0f;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltFanDutyCycle = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltFanDutyCycle;

	case 3:
		//voltage 0

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x20, &data))
			result = data * 8.0f * g_context[dwGpu].m_fltVoltageRatio0 / 1000.0f;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltVoltage0 = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltVoltage0;

	case 4:
		//voltage 1

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x21, &data))
			result = data * 8.0f * g_context[dwGpu].m_fltVoltageRatio1 / 1000.0f;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltVoltage1 = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltVoltage1;

	case 5:
		//voltage 2

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x22, &data))
			result = data * 8.0f * g_context[dwGpu].m_fltVoltageRatio2 / 1000.0f;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltVoltage2 = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltVoltage2;

	case 6:
		//voltage 3

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x23, &data))
			result = data * 8.0f * g_context[dwGpu].m_fltVoltageRatio3 / 1000.0f;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltVoltage3 = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltVoltage3;

	case 7:
		//fan speed

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x47, &data))
		{
			BYTE divisor = 1<<(data & 3);

			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x28, &data))
			{
				BYTE count = data;

				if (count)
					result = 1350000.0f / (count * divisor);
			}
		}

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltFanSpeed = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltFanSpeed;
	}

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