/*---------------------------------------------------------------------------*/
//       Author : hiyohiyo
//         Mail : hiyohiyo@crystalmark.info
//          Web : https://crystalmark.info/
//      License : MIT License
/*---------------------------------------------------------------------------*/

#include "stdafx.h"
#include "OsInfoFx.h"
#include "SystemInfoFx.h"
#include "UtilityFx.h"


//------------------------------------------------
// Get System Information by WMI
//------------------------------------------------

//warning : enum3, enum class
#if _MSC_VER > 1310
#pragma warning(disable : 26812)
#endif

#include <comdef.h>
#include <comutil.h>
#include <wbemcli.h>
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "wbemuuid.lib")

#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
#ifndef safeCloseHandle
#define safeCloseHandle(h) { if( h != NULL ) { ::CloseHandle(h); h = NULL; } }
#endif
#ifndef safeVirtualFree
#define safeVirtualFree(h,b,c) { if( h != NULL ) { ::VirtualFree(h, b, c); h = NULL; } }
#endif

#if _MSC_VER > 1310
DWORD CountSetBits(ULONG_PTR bitMask) {
	DWORD LSHIFT = sizeof(ULONG_PTR) * 8 - 1;
	DWORD bitSetCount = 0;
	ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT;
	for (DWORD i = 0; i <= LSHIFT; ++i) {
		bitSetCount += ((bitMask & bitTest) ? 1 : 0);
		bitTest >>= 1;
	}
	return bitSetCount;
}

typedef ULONGLONG(WINAPI* FuncGetLogicalProcessorInformationEx)(LOGICAL_PROCESSOR_RELATIONSHIP, PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
typedef BOOL(WINAPI* FuncGetLogicalProcessorInformation)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);

#endif

void GetProcessorInfo(int* cores, int* threads)
{
	*cores = 0;
	*threads = 0;

#if _MSC_VER > 1310
	HMODULE hModule = GetModuleHandle(_T("kernel32.dll"));
	FuncGetLogicalProcessorInformationEx pGetLogicalProcessorInformationEx = NULL;
	FuncGetLogicalProcessorInformation pGetLogicalProcessorInformation = NULL;

	if (hModule)
	{
		pGetLogicalProcessorInformationEx = (FuncGetLogicalProcessorInformationEx)GetProcAddress(hModule, "GetLogicalProcessorInformationEx");
		pGetLogicalProcessorInformation = (FuncGetLogicalProcessorInformation)GetProcAddress(hModule, "GetLogicalProcessorInformation");

	}

	// for Windows 7 or later
	if (pGetLogicalProcessorInformationEx != NULL)
	{
		DWORD length = 0;
		pGetLogicalProcessorInformationEx(RelationAll, nullptr, &length);

		BYTE* buffer = new BYTE[length];
		if (pGetLogicalProcessorInformationEx(RelationAll, reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(buffer), &length)) {
			DWORD offset = 0;
			while (offset < length) {
				PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(buffer + offset);

				if (info->Relationship == RelationProcessorCore) {
					*cores += 1;
					for (int group = 0; group < info->Processor.GroupCount; ++group) {
						*threads += CountSetBits(info->Processor.GroupMask[group].Mask);
					}
				}

				offset += info->Size;
			}
		}
		delete[] buffer;
	}
	// for Windows XP SP3/Vista
	else if(pGetLogicalProcessorInformation != NULL)
	{
		DWORD length = 0;
		pGetLogicalProcessorInformation(NULL, &length);

		SYSTEM_LOGICAL_PROCESSOR_INFORMATION* buffer = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)];
		pGetLogicalProcessorInformation(&buffer[0], &length);

		for (DWORD i = 0; i != length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) {
			if (buffer[i].Relationship == RelationProcessorCore) {
				*cores += 1;
				ULONG_PTR mask = buffer[i].ProcessorMask;
				while (mask) {
					if (mask & 1) {
						*threads += 1;
					}
					mask >>= 1;
				}
			}
		}
		delete[] buffer;
	}
	else // - Windows XP SP2
	{
		SYSTEM_INFO si = { 0 };
		GetSystemInfo(&si);

		*cores = si.dwNumberOfProcessors;
		*threads = si.dwNumberOfProcessors;
	}
#else
	SYSTEM_INFO si = {0};
	GetSystemInfo(&si);

	*cores = si.dwNumberOfProcessors;
	*threads = si.dwNumberOfProcessors;
#endif
}

#if defined(_M_IX86) || defined(_M_X64)

void getProcessorBrandString(char* brandString);
void getCpuName(char* cpuName);
unsigned int getCacheInfoIntel(int test);
CStringA getCpuModelName(CStringA vendor, unsigned int family, unsigned int model, unsigned int stepping, unsigned int type, unsigned int L2Cashe, unsigned int fpu);

#if _MSC_VER > 1310
#include <intrin.h> // for __cpuid

void GetCpuid(unsigned int param, unsigned int* _eax, unsigned int* _ebx, unsigned int* _ecx, unsigned int* _edx)
{
	int cpuInfo[4] = { 0 };
	__cpuid(cpuInfo, param);

	*_eax = cpuInfo[0];
	*_ebx = cpuInfo[1];
	*_ecx = cpuInfo[2];
	*_edx = cpuInfo[3];
}
#else
BOOL IsCpuidSupported()
{
	bool supported = false;
	__asm {
		pushfd
		pop     eax
		mov     ebx, eax
		xor eax, 0x200000
		push    eax
		popfd
		pushfd
		pop     eax
		xor eax, ebx
		test    eax, 0x200000
		jz      not_supported
		mov     supported, 1
		not_supported:
		push    ebx
		popfd
	}

	return supported;
}

void GetCpuid(unsigned int param, unsigned int* _eax, unsigned int* _ebx, unsigned int* _ecx, unsigned int* _edx)
{
	static BOOL bIsCpuid = IsCpuidSupported();
	if (!bIsCpuid) { return; }

	unsigned int a, b, c, d;
	__asm {
		MOV EAX, param
		CPUID
		MOV a, EAX
		MOV b, EBX
		MOV c, ECX
		MOV d, EDX
	}
	*_eax = a;
	*_ebx = b;
	*_ecx = c;
	*_edx = d;
}
#endif

void getProcessorBrandString(char* brandString)
{
	unsigned int eax = 0;
	unsigned int ebx = 0;
	unsigned int ecx = 0;
	unsigned int edx = 0;

	__try
	{
		// Check if CPUID supports brand string
		GetCpuid(0x80000000, &eax, &ebx, &ecx, &edx);
		if (eax < 0x80000004) {
			strcpy(brandString, "");
			return;
		}

		// Get brand string
		for (int i = 0x80000002; i <= 0x80000004; ++i) {
			GetCpuid(i, &eax, &ebx, &ecx, &edx);
			memcpy(brandString + (i - 0x80000002) * 16, &eax, 4);
			memcpy(brandString + (i - 0x80000002) * 16 + 4, &ebx, 4);
			memcpy(brandString + (i - 0x80000002) * 16 + 8, &ecx, 4);
			memcpy(brandString + (i - 0x80000002) * 16 + 12, &edx, 4);
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		strcpy(brandString, "");
	}
}

void GetHypervisorVendorString(char* vendorString)
{
	unsigned int eax = 0;
	unsigned int ebx = 0;
	unsigned int ecx = 0;
	unsigned int edx = 0;

	__try
	{
		GetCpuid(0x40000000, &eax, &ebx, &ecx, &edx);
		memcpy(vendorString, &ebx, 4);
		memcpy(vendorString + 4, &ecx, 4);
		memcpy(vendorString + 8, &edx, 4);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		strcpy(vendorString, "");
	}
}

#if _MSC_VER <= 1310

BOOL Is486orAbove();
BOOL IsCyrixCPU();
BOOL IsAmd486();
BOOL readCcr(BYTE addr, BYTE* value);
CStringA getCyrixModelName();

inline unsigned char inpb(unsigned short port)
{
	unsigned char val;
	__asm {
		mov dx, port
		in  al, dx
		mov val, al
	}
	return val;
}

inline void outpb(unsigned short port, unsigned char data)
{
	__asm {
		mov dx, port
		mov al, data
		out dx, al
	}
}

// https://github.com/captainys/TOWNSEMU/issues/147#issuecomment-2764633838
BOOL IsFMTOWNS()
{
	static BOOL b = -1;
	if (b == -1)
	{
		b = FALSE;

		if (IsPC98())
		{
			return FALSE;
		}

		if (! IsWin95First())
		{
			return FALSE;
		}

		if (GetUserDefaultLCID() != 0x0411) // Japanese
		{
			return FALSE;
		}

		__try
		{
			BYTE in30 = (BYTE)inpb(0x30);
			BYTE in31 = (BYTE)inpb(0x31);
			if (in30 && in30 != 0xFF && in31 && in31 != 0xFF)
			{
				b = TRUE;
				return TRUE;
			}
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			return FALSE;
		}
	}

	return b;
}

BOOL Is486orAbove()
{
	BOOL result = FALSE;

	__asm {
		pushfd
		pop eax
		mov ecx, eax
		xor eax, 0x40000
		push eax
		popfd

		pushfd
		pop eax
		cmp eax, ecx
		jz is_not_80486

		mov result, 1
		jmp end

		is_not_80486 :
			mov result, 0
			end :
	}

	return result;
}

BOOL IsCyrixCPU()
{
	BOOL result = FALSE;

	__asm {
		xor ax, ax
		sahf
		mov ax, 5
		mov bx, 2
		div bl
		lahf
		cmp ah, 2
		jne not_cyrix

		mov result, 1
		jmp end

		not_cyrix:
			mov result, 0
			end:
	}

	return result;
}

BOOL IsAmd486()
{
	__try
	{
		outpb(0xC0, 0x55);
		BYTE val = (BYTE)inpb(0xC0);
		if (val == 0x55)
		{
			return TRUE;
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return FALSE;
	}

	return FALSE;
}

BOOL readCcr(BYTE addr, BYTE* value)
{
	__try
	{
		outpb(0x22, addr);
		*value = inpb(0x23);
		return TRUE;
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return FALSE;
	}
}

// http://www.bitsavers.org/components/cyrix/appnotes/Cyrix_CPU_Detection_Guide_1997.pdf
CStringA getCyrixModelName()
{
	BYTE dir0 = 0;

	if(! readCcr(0xFE, &dir0))
	{
		return "Cyrix Unknown CPU";
	}
		 if (0x00 <= dir0 && dir0 <= 0x1F) { return "Cyrix Cx486"; }
	else if (0x20 <= dir0 && dir0 <= 0x2F) { return "Cyrix 5x86"; }
	else if (0x30 <= dir0 && dir0 <= 0x3F) { return "Cyrix 6x86"; }
	else if (0x40 <= dir0 && dir0 <= 0x4F) { return "Cyrix MediaGX"; }
	else if (0x50 <= dir0 && dir0 <= 0x5F) { return "Cyrix 6x86MX/MII"; }
	else
	{
		 return "Cyrix Unknown CPU";
	}
}

#endif

void getCpuName(char* cpuName)
{
	unsigned int eax = 0;
	unsigned int ebx = 0;
	unsigned int ecx = 0;
	unsigned int edx = 0;
	unsigned int maxCpuId = 0;

#if _MSC_VER <= 1310
	if(! IsCpuidSupported())
	{
		CStringA modelName = "";

		if (! Is486orAbove())
		{
			modelName = "386 Generation Processor";
		}
		else if (IsFMTOWNS())
		{
			
		}
		else if (IsCyrixCPU())
		{
			modelName = "Cyrix Unknown CPU";
			if (IsWin9x() && !IsPC98() && !IsFMTOWNS()) // PC-98 and FM TOWNS are not supported. 
			{
				modelName = getCyrixModelName();
			}
		}
		else if (IsWin9x())
		{
			if (IsAmd486())
			{
				modelName = "AMD Am486/Am5x86";
			}
		}

		if (modelName.IsEmpty())
		{
			SYSTEM_INFO si = { 0 };
			GetSystemInfo(&si);

			switch (si.dwProcessorType)
			{
			case PROCESSOR_INTEL_386:
				modelName = "386 Generation Processor";
				break;
			case PROCESSOR_INTEL_486:
				modelName = "486 Generation Processor";
				break;
			case PROCESSOR_INTEL_PENTIUM:
				modelName = "586 Generation Processor";
				break;
			case PROCESSOR_ARCHITECTURE_UNKNOWN:
			default:
				modelName = "Unknown Processor";
				break;
			}
		}

		sprintf(cpuName, "%s", modelName.GetString());
		return;
	}
#endif

	char vendorString[13] = {0};
	GetCpuid(0x00, &eax, &ebx, &ecx, &edx);
	memcpy(vendorString, &ebx, 4);
	memcpy(vendorString + 4, &edx, 4);
	memcpy(vendorString + 8, &ecx, 4);

	GetCpuid(0x80000000, &eax, &ebx, &ecx, &edx);
	maxCpuId = eax;

	if (maxCpuId >= 0x80000004)
	{
		getProcessorBrandString(cpuName);
	}
	else 
	{
		CStringA vendor = vendorString;
		GetCpuid(0x1, &eax, &ebx, &ecx, &edx);

		unsigned int version = eax;
		unsigned int family = (version >> 8) & 0xF;
		unsigned int model = (version >> 4) & 0xF;
		unsigned int stepping = version & 0xF;
		unsigned int type = (version >> 12) & 0x3;

		unsigned int fpu = 1; // Under Construction

		unsigned int cacheL2 = 0;
		unsigned int temp = 0;
		if (vendor.Find("GenuineIntel") == 0)
		{
			GetCpuid(0x2, &eax, &ebx, &ecx, &edx);
			temp = getCacheInfoIntel((eax & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((eax & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((eax & 0x0000FF00) >> 8);  if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((eax & 0x000000FF) >> 0);  if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ebx & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ebx & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ebx & 0x0000FF00) >> 8);  if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ebx & 0x000000FF) >> 0);  if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ecx & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ecx & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ecx & 0x0000FF00) >> 8);  if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((ecx & 0x000000FF) >> 0);  if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((edx & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((edx & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((edx & 0x0000FF00) >> 8);  if (temp > 0) { cacheL2 = temp; }
			temp = getCacheInfoIntel((edx & 0x000000FF) >> 0);  if (temp > 0) { cacheL2 = temp; }
		}

		if(maxCpuId >= 0x80000006)
		{
			GetCpuid(0x80000006, &eax, &ebx, &ecx, &edx);
			cacheL2 = ((ebx >> 12) & 0xF) * 256;
		}

		CStringA modelName = getCpuModelName(vendor, family, model, stepping, type, cacheL2, fpu);

		sprintf(cpuName, "%s", modelName.GetString());
	}
}

unsigned int getCacheInfoIntel(int test)
{
	unsigned int CacheL2 = 0;
	switch (test)
	{
	case 0x39:CacheL2 = 128;break;
	case 0x3A:CacheL2 = 192;break;
	case 0x3B:CacheL2 = 128;break;
	case 0x3C:CacheL2 = 256;break;
	case 0x3D:CacheL2 = 384;break;
	case 0x3E:CacheL2 = 512;break;
	case 0x41:CacheL2 = 128;break;
	case 0x42:CacheL2 = 256;break;
	case 0x43:CacheL2 = 512;break;
	case 0x44:CacheL2 = 1024;break;
	case 0x45:CacheL2 = 2048;break;
	case 0x48:CacheL2 = 3072;break;
	case 0x49:CacheL2 = 4096;break;
	case 0x4E:CacheL2 = 6144;break;
	case 0x78:CacheL2 = 1024;break;
	case 0x79:CacheL2 = 128;break;
	case 0x7A:CacheL2 = 256;break;
	case 0x7B:CacheL2 = 512;break;
	case 0x7C:CacheL2 = 1024;break;
	case 0x7D:CacheL2 = 2048;break;
	case 0x7E:CacheL2 = 256;break;
	case 0x7F:CacheL2 = 512;break;
	case 0x81:CacheL2 = 128;break;
	case 0x82:CacheL2 = 256;break;
	case 0x83:CacheL2 = 512;break;
	case 0x84:CacheL2 = 1024;break;
	case 0x85:CacheL2 = 2048;break;
	case 0x86:CacheL2 = 512;break;
	case 0x87:CacheL2 = 1024;break;
	default:;
	}

	return CacheL2;
}

CStringA getCpuModelName(CStringA vendor, unsigned int family, unsigned int model, unsigned int stepping, unsigned int type, unsigned int cacheL2, unsigned int fpu)
{
	CStringA modelName;
	int F = family;
	int M = model;
	int S = stepping;
	int CacheL2 = cacheL2;
	char* n = NULL;
	char* v = NULL;

	if (vendor.Find("GenuineIntel") == 0)
	{
		v = "Intel";
		switch (family)
		{
		case 0x06:
			switch (model)
			{
				/////////////
				// Cascades
				/////////////
			case 0xA:
				n = "Pentium III Xeon";
				break;
				///////////
				// Banias
				///////////
			case 9:
				if (CacheL2 == 1024) {
					n = "Pentium M";
				} else {
					n = "Celeron M";
				}
				break;
				///////////////
				// Coppermine
				///////////////
			case 8:
				if (CacheL2 >= 256) {
					n = "Pentium III";
				} else if (CacheL2 <= 128) {
					n = "Celeron";
				}
				break;
			///////////
			// Katmai
			///////////
			case 7:
				if (CacheL2 == 1024) {
					n = "Pentium III Xeon";
				} else {
					n = "Pentium III";
				}
				break;
			//////////////////////
			// Dixon & Mendocino
			//////////////////////
			case 6:
				if ((S == 0xA || S == 0xD) && CacheL2 == 256) {
					n = "Mobile Pentium II";
				} else if ((S == 0xA || S == 0xD) && CacheL2 == 128) {
					n = "Mobile Celeron";
				} else {
					n = "Celeron";
				}
				break;
			//////////////
			// Deschutes
			//////////////
			case 5:
				if (CacheL2 > 512) {
					n = "Pentium II Xeon";
				} else if (CacheL2 == 512 && type == 0x1) {
					n = "Pentium II OverDrive";
				} else if (CacheL2 == 512) {
					n = "Pentium II";
				} else if (CacheL2 == 0) {
					n = "Celeron";
				}
				break;
			case 4:
				n = "OverDrive";
				break;
			case 3:
				if (type == 0x1) {
					n = "Pentium II OverDrive";
				} else {
					n = "Pentium II";
				}
				break;
			///////
			// P6
			///////
			case 2:
				n = "Pentium Pro";
				break;
			case 1:
				n = "Pentium Pro";
				break;
			case 0:
				n = "Pentium Pro";
				break;
			}
			break;
		////////////
		// Pentium 
		////////////
		case 5:
			switch (model) {
			case 8:
				n = "Pentium MMX";
				break;
			case 7:
				n = "Pentium";
				break;
			case 6:
				n = "Pentium OverDrive";
				break;
			case 5:
				n = "Pentium OverDrive";
				break;
			case 4:
				if (type == 1) {
					n = "Pentium MMX OverDrive";
				} else {
					n = "Pentium MMX";
				}
				break;
			case 3:
				n = "Pentium OverDrive";
				break;
			case 2:
				if (type == 1) {
					n = "Pentium OverDrive";
				} else {
					n = "Pentium";
				}
				break;
			case 1:
			case 0:
				if (type == 1) {
					n = "Pentium OverDrive";
				} else {
					n = "Pentium";
				}
				break;
			default:
				n = "Pentium";
				break;
			}
			break;
		////////
		// 486
		////////
		case 4:
			switch (model) {
			case 9:
				n = "486DX4 WB";
				break;
			case 8:
				n = "486DX4";
				break;
			case 7:
				n = "486DX2 WB";
				break;
			case 5:
				n = "486SX2";
				break;
			case 4:
				n = "486SL";
				break;
			case 3:
				n = "486DX2";
				break;
			case 2:
				n = "486SX";
				break;
			case 1:
			case 0:
				n = "486DX";
				break;
			default:
				n = "486";
				break;
			}
			break;
		case 3:
			n = "386";
			break;
		default:
			n = "Unknown CPU";
			break;
		}
	}
	else if (vendor.Find("AuthenticAMD") == 0)
	{
		v = "AMD";
		switch (family)
		{
		case 5:
			switch (model) {
			case 0xD:
			case 0xC:
				if (CacheL2 == 256) {
					n = "K6-III+";
				} else {
					n = "K6-2+";
				}
				break;
			case 0xA:
				n = "Geode LX";
				break;
			case 9:
				n = "K6-III";
				break;
			case 8:
				n = "K6-2";
				break;
			case 7:
				n = "K6";
				break;
			case 3:
			case 2:
			case 1:
			case 0:
				n = "K5";
				break;
			default:
				n = "Unknown CPU";
				break;
			}
			break;
		case 4:
			switch (model) {
			case 0xF:
			case 0xE:
				n = "Am5x86";
				break;
			case 9:
			case 8:
				n = "Am486DX4/Am5x86";
				break;
			case 7:
			case 3:
				if (fpu)
				{
					n = "Am486DX2";
				}
				else
				{
					n = "Am486SX2";
				}
				break;
			default:
				n = "Unknown CPU";
				break;
			}
			break;
		default:
			n = "Unknown CPU";
			break;
		}
	}
	else if (vendor.Find("CentaurHauls") == 0 || vendor.Find("VIA VIA VIA") == 0)
	{
		v = "";
		switch (family)
		{
		case 6:
			switch (model)
			{
			case 0xF:
				n = "VIA Nano";
				break;
			case 0xD:
			case 0xC:
			case 0xB:
			case 0xA:
				n = "VIA C7";
				break;
			case 9:
			case 8:
			case 7:
				n = "VIA C3";
				break;
			case 6:
				n = "VIA Cyrix III";
				break;
			case 5:
				if (stepping == 0)
				{
					n = "VIA 6x86MX";
				}
				else
				{
					n = "VIA Cyrix III";
				}				
				break;
			case 4:
				n = "VIA Cyrix III";
				break;
			case 3:
				n = "VIA 6x86MX";
				break;
			case 2:
				n = "VIA Cyrix III";
				break;
			case 0:
				n = "VIA 6x86MX";
				break;
			default:
				n = "VIA Unknown CPU";
				break;
			}
			break;
		case 5:
			switch (model)
			{
			case 9:
				n = "IDT WinChip 3";
				break;
			case 8:
				n = "IDT WinChip 2";
				break;
			case 4:
				n = "IDT WinChip C6";
				break;
			default:
				n = "VIA Unknown CPU";
				break;
			}
			break;
		default:
			n = "VIA Unknown CPU";
			break;
		}
	}
	else if (vendor.Find("CyrixInstead") == 0)
	{
		v = "Cyrix";
		switch (family)
		{
		case 6:
			switch (model)
			{
			case 0:
				n = "MII";
				break;
			default:
				n = "Unknown CPU";
				break;
			}
			break;
		case 5:
			switch (model)
			{
			case 9:
				n = "Geode";
				break;
			case 4:
				n = "MediaGX";
				break;
			case 2:
				n = "6x86";
				break;
			default:
				n = "Unknown CPU";
				break;
			}
			break;
		case 4:
			switch (model)
			{
			case 4:
				n = "MediaGX";
				break;
			default:
				n = "Unknown CPU";
				break;
			}
			break;
		default:
			n = "Unknown CPU";
			break;
		}
	}
	else if (vendor.Find("SiS SiS SiS") == 0)
	{
		v = "SiS";
		n = "55x";
	}
	else if (vendor.Find("Geode by NSC") == 0)
	{
		v = "NSC";
		n = "Geode";
	}
	else if (vendor.Find("NexGenDriven") == 0)
	{
		v = "NexGen";
		n = "Nx586";
	}
	else if (vendor.Find("RiseRiseRise") == 0)
	{
		v = "Rise";
		n = "mP6";
	}
	else if (vendor.Find("UMC UMC UMC") == 0)
	{
		v = "UMC";
		n = "Green CPU";
	}
	else
	{
		v = NULL;
		n = NULL;
	}

	if (v != NULL && n != NULL)
	{
		modelName.Format("%s %s", v, n);
		return modelName;
	}
	else
	{
		return "";
	}	
}
#endif

void GetCpuInfo(CString& cpuInfo, CString& cpuName, int* clock, int* cores, int* threads)
{
	CString query = _T("Select * from Win32_Processor");

	IWbemLocator*			pIWbemLocator = NULL;
	IWbemServices*			pIWbemServices = NULL;
	IEnumWbemClassObject*	pEnumCOMDevs = NULL;
	IWbemClassObject*		pCOMDev = NULL;
	ULONG					uReturned = 0;
	BOOL					flag = FALSE;

	try
	{
		if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
			IID_IWbemLocator, (LPVOID*)&pIWbemLocator)))
		{
			long securityFlag = 0;
#if _MSC_VER > 1310
			if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; }
#endif
			if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")),
				NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices)))
			{
#if _MSC_VER > 1310
				if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
					NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE)))
#endif
				{
					if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")),
						_bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs)))
					{
						while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1)
						{
							CString name;
							UINT32 speed = 0;
							// UINT32 cores = 0;
							// UINT32 threads = 0;

#if defined(_M_IX86) || defined(_M_X64)
							char brandString[49] = { 0 };
							getCpuName(brandString);
							//getProcessorBrandString(brandString);
							name = brandString;
							name.TrimLeft();
							name.TrimRight();
							cpuName = name;
#endif
							VARIANT pVal;
							VariantInit(&pVal);
							if (name.IsEmpty() && pCOMDev->Get(L"Name", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								name = pVal.bstrVal;
								name.TrimLeft();
								name.TrimRight();
								cpuName = name;
								VariantClear(&pVal);
							}
							if (pCOMDev->Get(L"MaxClockSpeed", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								speed = pVal.intVal;
								if(clock != NULL) { *clock = (int)speed; }
								VariantClear(&pVal);
							}

							/*
							if (pCOMDev->Get(L"NumberOfCores", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								*cores = pVal.intVal;
								VariantClear(&pVal);
							}
							if (pCOMDev->Get(L"NumberOfLogicalProcessors", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								*threads = pVal.intVal;
								VariantClear(&pVal);
							}
							*/

							GetProcessorInfo(cores, threads);
							
							if (speed > 0 && *cores > 0 && *threads > 0)
							{
								if (*cores > 1 && *threads > 1)
								{
									cpuInfo.Format(_T("%s %dMHz (%dcores/%dthreads)"), (LPCTSTR)name, speed, *cores, *threads);
								}
								else if (*cores == 1 && *threads == 1)
								{
									cpuInfo.Format(_T("%s %dMHz (%dcore/%dthread)"), (LPCTSTR)name, speed, *cores, *threads);
								}
								else if (*cores == 1)
								{
									cpuInfo.Format(_T("%s %dMHz (%dcore/%dthreads)"), (LPCTSTR)name, speed, *cores, *threads);
								}
							}
							else if (*cores > 0 && *threads > 0)
							{
								if (*cores > 1 && *threads > 1)
								{
									cpuInfo.Format(_T("%s (%dcores/%dthreads)"), (LPCTSTR)name, *cores, *threads);
								}
								else if (*cores == 1 && *threads == 1)
								{
									cpuInfo.Format(_T("%s (%dcore/%dthread)"), (LPCTSTR)name, *cores, *threads);
								}
								else if (*cores == 1)
								{
									cpuInfo.Format(_T("%s (%dcore/%dthreads)"), (LPCTSTR)name, *cores, *threads);
								}
							}
							else
							{
								cpuInfo = name;
							}

							break;
						}
					}
				}
			}
		}
	}
	catch (...)
	{

	}

	SAFE_RELEASE(pCOMDev);
	SAFE_RELEASE(pEnumCOMDevs);
	SAFE_RELEASE(pIWbemServices);
	SAFE_RELEASE(pIWbemLocator);

	if (cpuInfo.IsEmpty())
	{
		TCHAR str[256] = {};
		DWORD value = 0;
		DWORD type = REG_SZ;
		ULONG size = 256 * sizeof(TCHAR);
		HKEY  hKey = NULL;

#if defined(_M_IX86) || defined(_M_X64)
		char brandString[49] = { 0 };
		getCpuName(brandString);
		//getProcessorBrandString(brandString);
		cpuInfo = brandString;
		cpuInfo.TrimLeft();
		cpuInfo.TrimRight();
#endif

		if (cpuInfo.IsEmpty() && !IsWin9x())
		{
			if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
			{
				if (RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
				{
					cpuInfo = str;
					cpuInfo.TrimLeft();
					cpuInfo.TrimRight();
				}
			}
		}

#ifdef _M_IX86

#endif
		cpuName = cpuInfo;

		if (!IsWin9x())
		{
			if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
			{
				type = REG_DWORD;
				size = sizeof(DWORD);
				if (RegQueryValueEx(hKey, _T("~MHz"), NULL, &type, (LPBYTE)&value, &size) == ERROR_SUCCESS)
				{
					cpuInfo.Format(_T("%s %dMHz"), (LPCTSTR)cpuInfo, value);
					if (clock != NULL) { *clock = (int)value; }
				}
				RegCloseKey(hKey);
			}
		}

		GetProcessorInfo(cores, threads);
		if (*cores > 0 && *threads > 0)
		{
			CString cstr;
			if (*cores > 1 && *threads > 1)
			{
				cstr.Format(_T(" (%dcores/%dthreads)"), *cores, *threads);
			}
			else if(*cores == 1 && *threads == 1)
			{
				cstr.Format(_T(" (%dcore/%dthread)"), *cores, *threads);
			}
			else if (*cores == 1)
			{
				cstr.Format(_T(" (%dcore/%dthreads)"), *cores, *threads);
			}
			
			cpuInfo += cstr;
		}		
	}

	cpuName.Replace(_T("(R)"), _T(""));
	cpuName.Replace(_T("(r)"), _T(""));
	cpuName.Replace(_T("(TM)"), _T(""));
	cpuName.Replace(_T("(tm)"), _T(""));
	cpuInfo.Replace(_T("(R)"), _T(""));
	cpuInfo.Replace(_T("(TM)"), _T(""));
	cpuInfo.Replace(_T("(t)"), _T(""));
	cpuInfo.Replace(_T("(tm)"), _T(""));
}

void GetGpuInfo(CString& gpuInfo)
{
#if _MSC_VER > 1310
	#pragma comment(lib, "dxgi.lib")

	HMODULE hModule = LoadLibraryEx(_T("dxgi.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
	typedef HRESULT(WINAPI* FuncCreateDXGIFactory)(REFIID, void**);
	FuncCreateDXGIFactory pCreateDXGIFactory = NULL;

	if (hModule != NULL)
	{
		pCreateDXGIFactory = (FuncCreateDXGIFactory)GetProcAddress(hModule, "CreateDXGIFactory");
	}

	if (pCreateDXGIFactory != NULL)
	{
		IDXGIFactory* pFactory = nullptr;
		if (SUCCEEDED(pCreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pFactory)))
		{
			IDXGIAdapter* pAdapter = nullptr;
			DXGI_ADAPTER_DESC adapterDesc;

			for (UINT i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; ++i)
			{
				pAdapter->GetDesc(&adapterDesc);

				CString cstr;
				cstr.Format(L"%s [%dMB]", adapterDesc.Description, (int)(adapterDesc.DedicatedVideoMemory / (1024 * 1024)));

				if (cstr.Find(L"Microsoft Basic Render Driver") == 0)
				{
					pAdapter->Release();
					continue;
				}

				if (gpuInfo.IsEmpty())
				{
					gpuInfo = cstr;
				}
				else if (gpuInfo.Find(cstr) >= 0)
				{
					// Duplication
				}
				else
				{
					gpuInfo += _T(" | ") + cstr;
				}
				pAdapter->Release();
			}
			pFactory->Release();
		}
	}

	if (! gpuInfo.IsEmpty())
	{
		gpuInfo.Replace(_T("(R)"), _T(""));
		gpuInfo.Replace(_T("(TM)"), _T(""));
		return;
	}
#endif

	CString query = _T("Select * from Win32_VideoController");

	IWbemLocator*			pIWbemLocator = NULL;
	IWbemServices*			pIWbemServices = NULL;
	IEnumWbemClassObject*	pEnumCOMDevs = NULL;
	IWbemClassObject*		pCOMDev = NULL;
	ULONG					uReturned = 0;
	BOOL					flag = FALSE;

	try
	{
		if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
			IID_IWbemLocator, (LPVOID*)&pIWbemLocator)))
		{
			long securityFlag = 0;
#if _MSC_VER > 1310
			if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; }
#endif
			if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")),
				NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices)))
			{
#if _MSC_VER > 1310
				if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
					NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE)))
#endif
				{
					if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")),
						_bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs)))
					{
						while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1)
						{
							CString name;

							VARIANT pVal;
							VariantInit(&pVal);
							if (pCOMDev->Get(L"Name", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								name = pVal.bstrVal;
								name.TrimLeft();
								name.TrimRight();
								VariantClear(&pVal);
							}

							// exclusion
							// if (name.Find(_T("Virtual")) >= 0 || name.Find(_T("Remote")) >= 0 || name.Find(_T("ASPEED")) == 0)
							if (gpuInfo.IsEmpty())
							{
								gpuInfo = name;
							}
							else
							{
								gpuInfo += _T(" | ") + name;
							}
						}
					}
				}
			}
		}
	}
	catch (...)
	{

	}

	SAFE_RELEASE(pCOMDev);
	SAFE_RELEASE(pEnumCOMDevs);
	SAFE_RELEASE(pIWbemServices);
	SAFE_RELEASE(pIWbemLocator);

#ifndef UNICODE
	if (IsWin9x())
	{
		TCHAR str[256] = { 0 };
		GetPrivateProfileStringFx(_T("boot.description"), _T("display.drv"), _T(""), str, 256, _T("system.ini"));
		gpuInfo = str;
	}
#endif

	if (gpuInfo.IsEmpty())
	{
		TCHAR str[256] = {};
		DWORD value = 0;
		DWORD type = REG_SZ;
		ULONG size = 256 * sizeof(TCHAR);
		HKEY  hKey = NULL;

		// GPU
		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\0000"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
		{
			type = REG_SZ;
			size = 256 * sizeof(TCHAR);
			if (RegQueryValueEx(hKey, _T("DriverDesc"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
			{
				gpuInfo = str;
				gpuInfo.TrimLeft();
				gpuInfo.TrimRight();
			}
			RegCloseKey(hKey);
		}
		if (gpuInfo.IsEmpty())
		{
			if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\0001"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
			{
				type = REG_SZ;
				size = 256 * sizeof(TCHAR);
				if (RegQueryValueEx(hKey, _T("DriverDesc"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
				{
					gpuInfo = str;
					gpuInfo.TrimLeft();
					gpuInfo.TrimRight();
				}
				RegCloseKey(hKey);
			}
		}
		if (gpuInfo.IsEmpty())
		{
			TCHAR str[256] = {};
			DWORD value = 0;
			DWORD type = REG_SZ;
			ULONG size = 256 * sizeof(TCHAR);
			HKEY  hKey = NULL;
			HKEY  hKey2 = NULL;
			type = REG_SZ;
			size = 256 * sizeof(TCHAR);
			CString videoKey, key;
			if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\VIDEO"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
			{
				for (int i = 0; i < 10; i++)
				{
					videoKey.Format(_T("\\Device\\Video%d"), i);
					key = _T("");
					if (RegQueryValueEx(hKey, videoKey, NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
					{
						key = str;
					}

					if (!key.IsEmpty())
					{
						key.MakeLower().Replace(_T("\\registry\\machine\\"), _T(""));
						if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey2) == ERROR_SUCCESS)
						{
							type = REG_SZ;
							size = 256 * sizeof(TCHAR);
							if (RegQueryValueEx(hKey2, _T("DriverDesc"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
							{
								CString cstr = str;
								cstr.TrimLeft();
								cstr.TrimRight();

								if (gpuInfo.IsEmpty())
								{
									gpuInfo = cstr;
								}
								else
								{
									gpuInfo += _T(" | ") + cstr;
								}
								RegCloseKey(hKey2);
								break;
							}
							
							type = REG_BINARY;
							if (RegQueryValueEx(hKey2, _T("HardwareInformation.AdapterString"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
							{
							#ifndef UNICODE
								char buf[256] = {};
								WideCharToMultiByte(CP_ACP, 0, (WCHAR*)str, size, buf, 256, 0, 0);
								CString cstr = buf;
							#else
								CString cstr = str;
							#endif
								cstr.TrimLeft();
								cstr.TrimRight();

								if (gpuInfo.IsEmpty())
								{
									gpuInfo = cstr;
								}
								else
								{
									gpuInfo += _T(" | ") + cstr;
								}
								RegCloseKey(hKey2);
								break;
							}
							else
							{
								_tcscpy(str, key);
								#if _MSC_VER > 1310
								PathRemoveFileSpec(str);
								CString cstr = PathFindFileName(str);
								#else
								PathRemoveFileSpecFx(str);
								CString cstr = PathFindFileNameFx(str);
								#endif
								cstr += _T(" compatible device");
								if (gpuInfo.IsEmpty())
								{
									gpuInfo = cstr;
								}
								else
								{
									gpuInfo += _T(" | ") + cstr;
								}
								RegCloseKey(hKey2);
								break;
							}
						}
					}
				}
				RegCloseKey(hKey);
			}
		}
	}

	gpuInfo.Replace(_T("(R)"), _T(""));
	gpuInfo.Replace(_T("(TM)"), _T(""));
}

void GetBaseBoardInfo(CString& baseBoardInfo)
{
	CString query = _T("Select * from Win32_BaseBoard");

	IWbemLocator*			pIWbemLocator = NULL;
	IWbemServices*			pIWbemServices = NULL;
	IEnumWbemClassObject*	pEnumCOMDevs = NULL;
	IWbemClassObject*		pCOMDev = NULL;
	ULONG					uReturned = 0;
	BOOL					flag = FALSE;

	try
	{
		if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
			IID_IWbemLocator, (LPVOID*)&pIWbemLocator)))
		{
			long securityFlag = 0;
#if _MSC_VER > 1310
			if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; }
#endif
			if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")),
				NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices)))
			{
#if _MSC_VER > 1310
				if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
					NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE)))
#endif
				{
					if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")),
						_bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs)))
					{
						while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1)
						{
							CString manufacturer;
							CString product;

							VARIANT pVal;
							VariantInit(&pVal);
							if (pCOMDev->Get(L"Manufacturer", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								manufacturer = pVal.bstrVal;
								manufacturer.TrimLeft();
								manufacturer.TrimRight();
								VariantClear(&pVal);
							}
							if (pCOMDev->Get(L"Product", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								product = pVal.bstrVal;
								product.TrimLeft();
								product.TrimRight();
								VariantClear(&pVal);
							}
							baseBoardInfo = manufacturer + _T(" ") + product;

							baseBoardInfo.Replace(_T("To Be Filled By O.E.M."), _T(""));
							baseBoardInfo.Replace(_T("To be filled by O.E.M."), _T(""));
							baseBoardInfo.Replace(_T("Not Available"), _T(""));
							baseBoardInfo.TrimLeft();
							baseBoardInfo.TrimRight();
						}
					}
				}
			}
		}
	}
	catch (...)
	{

	}

	SAFE_RELEASE(pCOMDev);
	SAFE_RELEASE(pEnumCOMDevs);
	SAFE_RELEASE(pIWbemServices);
	SAFE_RELEASE(pIWbemLocator);

	if (baseBoardInfo.IsEmpty())
	{
		TCHAR str[256] = {};
		DWORD value = 0;
		DWORD type = REG_SZ;
		ULONG size = 256 * sizeof(TCHAR);
		HKEY  hKey = NULL;

		// BaseBoard/System
		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\BIOS"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
		{
			size = 256 * sizeof(TCHAR);
			if (RegQueryValueEx(hKey, _T("BaseBoardManufacturer"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
			{
				baseBoardInfo = str;

			}
			size = 256 * sizeof(TCHAR);
			if (RegQueryValueEx(hKey, _T("BaseBoardProduct"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
			{
				baseBoardInfo += _T(" ");
				baseBoardInfo += str;
			}

			baseBoardInfo.Replace(_T("To Be Filled By O.E.M."), _T(""));
			baseBoardInfo.Replace(_T("To be filled by O.E.M."), _T(""));
			baseBoardInfo.Replace(_T("Not Available"), _T(""));
			baseBoardInfo.TrimLeft();
			baseBoardInfo.TrimRight();

			RegCloseKey(hKey);
		}
	}
}

void GetComputerSystemInfo(CString& computerSystemInfo)
{
	CString query = _T("Select * from Win32_ComputerSystem");

	IWbemLocator*			pIWbemLocator = NULL;
	IWbemServices*			pIWbemServices = NULL;
	IEnumWbemClassObject*	pEnumCOMDevs = NULL;
	IWbemClassObject*		pCOMDev = NULL;
	ULONG					uReturned = 0;
	BOOL					flag = FALSE;

	try
	{
		if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
			IID_IWbemLocator, (LPVOID*)&pIWbemLocator)))
		{
			long securityFlag = 0;
#if _MSC_VER > 1310
			if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; }
#endif
			if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")),
				NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices)))
			{
#if _MSC_VER > 1310
				if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
					NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE)))
#endif
				{
					if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")),
						_bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs)))
					{
						while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1)
						{
							CString manufacturer;
							CString model;

							VARIANT pVal;
							VariantInit(&pVal);
							if (pCOMDev->Get(L"Manufacturer", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								manufacturer = pVal.bstrVal;
								manufacturer.TrimLeft();
								manufacturer.TrimRight();
								VariantClear(&pVal);
							}
							if (pCOMDev->Get(L"Model", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL)
							{
								model = pVal.bstrVal;
								model.TrimLeft();
								model.TrimRight();
								VariantClear(&pVal);
							}
							computerSystemInfo = manufacturer + _T(" ") + model;

							computerSystemInfo.Replace(_T("To Be Filled By O.E.M."), _T(""));
							computerSystemInfo.Replace(_T("To be filled by O.E.M."), _T(""));
							computerSystemInfo.Replace(_T("System manufacturer"), _T(""));
							computerSystemInfo.Replace(_T("System Product Name"), _T(""));
							computerSystemInfo.Replace(_T("Not Available"), _T(""));
							computerSystemInfo.TrimLeft();
							computerSystemInfo.TrimRight();
						}
					}
				}
			}
		}
	}
	catch (...)
	{

	}

	SAFE_RELEASE(pCOMDev);
	SAFE_RELEASE(pEnumCOMDevs);
	SAFE_RELEASE(pIWbemServices);
	SAFE_RELEASE(pIWbemLocator);

	if (computerSystemInfo.IsEmpty())
	{
		TCHAR str[256] = {};
		DWORD value = 0;
		DWORD type = REG_SZ;
		ULONG size = 256 * sizeof(TCHAR);
		HKEY  hKey = NULL;

		// BaseBoard/System
		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\BIOS"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
		{
			size = 256 * sizeof(TCHAR);
			if (RegQueryValueEx(hKey, _T("SystemManufacturer"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
			{
				computerSystemInfo = str;
			}
			size = 256 * sizeof(TCHAR);
			if (RegQueryValueEx(hKey, _T("SystemProductName"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS)
			{
				computerSystemInfo += _T(" ");
				computerSystemInfo += str;
			}

			computerSystemInfo.Replace(_T("To Be Filled By O.E.M."), _T(""));
			computerSystemInfo.Replace(_T("To be filled by O.E.M."), _T(""));
			computerSystemInfo.Replace(_T("Not Available"), _T(""));
			computerSystemInfo.Replace(_T("System Product Name"), _T(""));
			computerSystemInfo.Replace(_T("System manufacturer"), _T(""));
			computerSystemInfo.TrimLeft();
			computerSystemInfo.TrimRight();

			RegCloseKey(hKey);
		}
	}

#if _MSC_VER <= 1310
	if (IsPC98())
	{
		computerSystemInfo = _T("[PC-98] ") + computerSystemInfo;
	}
	else if (IsFMTOWNS())
	{
		computerSystemInfo = _T("[FM TOWNS] ") + computerSystemInfo;
	}
#endif
}

void GetScreenInfo(CString& screenInfo, int* width, int* height, int* color, CString& smoothing)
{
	int screenWidth = GetSystemMetrics(SM_CXSCREEN);
	int screenHeight = GetSystemMetrics(SM_CYSCREEN);
	if(width != NULL){*width = screenWidth;}
	if(height != NULL){*height = screenHeight;}

	HDC hDC = ::GetDC(NULL);
	int bitsPerPixel = ::GetDeviceCaps(hDC, BITSPIXEL);
	int planes = ::GetDeviceCaps(hDC, PLANES);
	int colorDepth = bitsPerPixel * planes;
	if(color != NULL){*color = colorDepth;}
	::ReleaseDC(NULL, hDC);

	DWORD fontSmoothingType = 0;
	SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &fontSmoothingType, 0);
	screenInfo.Format(_T("%dx%d %dbit"), screenWidth, screenHeight, colorDepth);

	if (fontSmoothingType == FE_FONTSMOOTHINGSTANDARD)
	{
		screenInfo += _T(" (Smoothing)");
		smoothing = _T("Smoothing");
	}
	else if (fontSmoothingType == FE_FONTSMOOTHINGCLEARTYPE)
	{
		screenInfo += _T(" (ClearType)");
		smoothing = _T("ClearType");
	}
}

void GetMemoryInfo(CString& memoryInfo, int* size)
{
#if _MSC_VER > 1310
	MEMORYSTATUSEX memory = {};
	memory.dwLength = sizeof(memory);
	GlobalMemoryStatusEx(&memory);

	if ((int)(memory.ullTotalPhys / 1024 / 1024 / 1024 + 1) > 1)
	{
		memoryInfo.Format(_T("%.1fGB"), (memory.ullTotalPhys / 1024.0 / 1024 / 1024));

	}
	else
	{
		memoryInfo.Format(_T("%dMB"), (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1));
	}
	if(size != NULL){*size = (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1);}
#else

	// for Windows 2000
	typedef BOOL(WINAPI* FuncGlobalMemoryStatusEx)(LPMEMORYSTATUSEX);
	HMODULE hModule = GetModuleHandle(_T("kernel32.dll"));
	FuncGlobalMemoryStatusEx pGlobalMemoryStatusEx = NULL;
	if (hModule)
	{
		pGlobalMemoryStatusEx = (FuncGlobalMemoryStatusEx)GetProcAddress(hModule, "GlobalMemoryStatusEx");
	}

	if (pGlobalMemoryStatusEx != NULL) // for Windows 2000
	{
		MEMORYSTATUSEX memory;
		memory.dwLength = sizeof(memory);
		pGlobalMemoryStatusEx(&memory);

		if ((int)(memory.ullTotalPhys / 1024 / 1024 / 1024 + 1) > 1)
		{
			memoryInfo.Format(_T("%.1fGB"), (memory.ullTotalPhys / 1024.0 / 1024 / 1024));

		}
		else
		{
			memoryInfo.Format(_T("%dMB"), (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1));
		}
		if (size != NULL) { *size = (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1); }
	}
	else
	{
		MEMORYSTATUS memory;
		memory.dwLength = sizeof(memory);
		GlobalMemoryStatus(&memory);

		if ((int)(memory.dwTotalPhys / 1024 / 1024 / 1024 + 1) > 1)
		{
			memoryInfo.Format(_T("%.1fGB"), (memory.dwTotalPhys / 1024.0 / 1024 / 1024));

		}
		else
		{
			memoryInfo.Format(_T("%dMB"), (int)(memory.dwTotalPhys / 1024.0 / 1024 + 1));
}
		if (size != NULL) { *size = (int)(memory.dwTotalPhys / 1024.0 / 1024 + 1); }
	}
#endif
}

#if _MSC_VER <= 1310
/// https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/124207
BOOL IsCoProcessorPresent()
{
#define MY_ERROR_WRONG_OS 0x20000000
	HKEY hKey;
	SYSTEM_INFO SystemInfo;

	// return FALSE if we are not running under Windows NT
	// this should be expanded to cover alternative Win32 platforms

	if (!(GetVersion() & 0x7FFFFFFF))
	{
		SetLastError(MY_ERROR_WRONG_OS);
		return(FALSE);
	}

	// we return TRUE if we're not running on x86
	// other CPUs have built in floating-point, with no registry entry

	GetSystemInfo(&SystemInfo);

	if ((SystemInfo.dwProcessorType != PROCESSOR_INTEL_386) &&
		(SystemInfo.dwProcessorType != PROCESSOR_INTEL_486) &&
		(SystemInfo.dwProcessorType != PROCESSOR_INTEL_PENTIUM))
	{
		return(TRUE);
	}

	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\FloatingPointProcessor"),
		0, KEY_EXECUTE,	&hKey) != ERROR_SUCCESS)
	{
		// GetLastError() will indicate ERROR_RESOURCE_DATA_NOT_FOUND
		// if we can't find the key.  This indicates no coprocessor present
		return(FALSE);
	}

	RegCloseKey(hKey);
	return(TRUE);
}
#endif

