/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "cpudetect.h"
#include "wwstring.h"
#include "wwdebug.h"
#include "thread.h"
#include "mpu.h"
#pragma warning (disable : 4201)	// Nonstandard extension - nameless struct
#include <windows.h>
#include "systimer.h"

struct OSInfoStruct {
	const char* Code;
	const char* SubCode;
	const char* VersionString;
	unsigned char VersionMajor;
	unsigned char VersionMinor;
	unsigned short VersionSub;
	unsigned char BuildMajor;
	unsigned char BuildMinor;
	unsigned short BuildSub;
/*	OSInfoStruct() {}
	OSInfoStruct(
		const char* code,
		const char* subcode,
		const char* versionstring,
		unsigned char versionmajor,
		unsigned char versionminor,
		unsigned short versionsub,
		unsigned char buildmajor,
		unsigned char buildminor,
		unsigned short buildsub) :
		Code(code),
		SubCode(subcode),
		VersionString(versionstring),
		VersionMajor(versionmajor),
		VersionMinor(versionminor),
		VersionSub(versionsub),
		BuildMajor(buildmajor),
		BuildMinor(buildminor),
		BuildSub(buildsub)
	{
	}
*/
};

static void Get_OS_Info(
	OSInfoStruct& os_info,
	unsigned OSVersionPlatformId,
	unsigned OSVersionNumberMajor,
	unsigned OSVersionNumberMinor,
	unsigned OSVersionBuildNumber);


StringClass CPUDetectClass::ProcessorLog;
StringClass CPUDetectClass::CompactLog;

int CPUDetectClass::ProcessorType;
int CPUDetectClass::ProcessorFamily;
int CPUDetectClass::ProcessorModel;
int CPUDetectClass::ProcessorRevision;
int CPUDetectClass::ProcessorSpeed;
__int64 CPUDetectClass::ProcessorTicksPerSecond;	// Ticks per second
double CPUDetectClass::InvProcessorTicksPerSecond;	// 1.0 / Ticks per second

unsigned CPUDetectClass::FeatureBits;
unsigned CPUDetectClass::ExtendedFeatureBits;

unsigned CPUDetectClass::L2CacheSize;
unsigned CPUDetectClass::L2CacheLineSize;
unsigned CPUDetectClass::L2CacheSetAssociative;
unsigned CPUDetectClass::L1DataCacheSize;
unsigned CPUDetectClass::L1DataCacheLineSize;
unsigned CPUDetectClass::L1DataCacheSetAssociative;
unsigned CPUDetectClass::L1InstructionCacheSize;
unsigned CPUDetectClass::L1InstructionCacheLineSize;
unsigned CPUDetectClass::L1InstructionCacheSetAssociative;
unsigned CPUDetectClass::L1InstructionTraceCacheSize;
unsigned CPUDetectClass::L1InstructionTraceCacheSetAssociative;

unsigned CPUDetectClass::TotalPhysicalMemory;
unsigned CPUDetectClass::AvailablePhysicalMemory;
unsigned CPUDetectClass::TotalPageMemory;
unsigned CPUDetectClass::AvailablePageMemory;
unsigned CPUDetectClass::TotalVirtualMemory;
unsigned CPUDetectClass::AvailableVirtualMemory;

unsigned CPUDetectClass::OSVersionNumberMajor;
unsigned CPUDetectClass::OSVersionNumberMinor;
unsigned CPUDetectClass::OSVersionBuildNumber;
unsigned CPUDetectClass::OSVersionPlatformId;
StringClass CPUDetectClass::OSVersionExtraInfo;

bool CPUDetectClass::HasCPUIDInstruction=false;
bool CPUDetectClass::HasRDTSCInstruction=false;
bool CPUDetectClass::HasSSESupport=false;
bool CPUDetectClass::HasSSE2Support=false;
bool CPUDetectClass::HasCMOVSupport=false;
bool CPUDetectClass::HasMMXSupport=false;
bool CPUDetectClass::Has3DNowSupport=false;
bool CPUDetectClass::HasExtended3DNowSupport=false;

CPUDetectClass::ProcessorManufacturerType CPUDetectClass::ProcessorManufacturer = CPUDetectClass::MANUFACTURER_UNKNOWN;
CPUDetectClass::IntelProcessorType CPUDetectClass::IntelProcessor;
CPUDetectClass::AMDProcessorType CPUDetectClass::AMDProcessor;
CPUDetectClass::VIAProcessorType CPUDetectClass::VIAProcessor;
CPUDetectClass::RiseProcessorType CPUDetectClass::RiseProcessor;

char CPUDetectClass::VendorID[20];
char CPUDetectClass::ProcessorString[48];

const char* CPUDetectClass::Get_Processor_Manufacturer_Name()
{
	static const char* ManufacturerNames[] = {
		"<Unknown>",
		"Intel",
		"UMC",
		"AMD",
		"Cyrix",
		"NextGen",
		"VIA",
		"Rise",
		"Transmeta"
	};

	return ManufacturerNames[ProcessorManufacturer];
}

#define ASM_RDTSC _asm _emit 0x0f _asm _emit 0x31

static unsigned Calculate_Processor_Speed(__int64& ticks_per_second)
{
	struct {
		unsigned timer0_h;
		unsigned timer0_l;
		unsigned timer1_h;
		unsigned timer1_l;
	} Time;

	__asm {
		ASM_RDTSC;
		mov Time.timer0_h,eax
		mov Time.timer0_l,edx
	}

	unsigned start=TIMEGETTIME();
	unsigned elapsed;
	while ((elapsed=TIMEGETTIME()-start)<200) {
		__asm {
			ASM_RDTSC;
			mov Time.timer1_h,eax
			mov Time.timer1_l,edx
		}
	}

	__int64 t=*(__int64*)&Time.timer1_h-*(__int64*)&Time.timer0_h;
	ticks_per_second=(1000/200)*t;	// Ticks per second
	return unsigned(t/(elapsed*1000));
}

void CPUDetectClass::Init_Processor_Speed()
{
	if (!Has_RDTSC_Instruction()) {
		ProcessorSpeed=0;
		return;
	}

	// Loop until two subsequent samples are within 5% of each other (max 5 iterations).
	unsigned speed1=Calculate_Processor_Speed(ProcessorTicksPerSecond);
	unsigned total_speed=speed1;
	for (int i=0;i<5;++i) {
		unsigned speed2=Calculate_Processor_Speed(ProcessorTicksPerSecond);
		float rel=float(speed1)/float(speed2);
		if (rel>=0.95f && rel<=1.05f) {
			ProcessorSpeed=(speed1+speed2)/2;
			InvProcessorTicksPerSecond=1.0/double(ProcessorTicksPerSecond);
			return;
		}
		speed1=speed2;
		total_speed+=speed2;
	}
	// If no two subsequent samples where close enough, use intermediate
	ProcessorSpeed=total_speed/6;
	InvProcessorTicksPerSecond=1.0/double(ProcessorTicksPerSecond);
}

void CPUDetectClass::Init_Processor_Manufacturer()
{
	VendorID[0] = 0;
	unsigned max_cpuid;
	CPUID(
		max_cpuid,
		(unsigned&)VendorID[0],
		(unsigned&)VendorID[8],
		(unsigned&)VendorID[4],
		0);

	ProcessorManufacturer = MANUFACTURER_UNKNOWN;

	if (stricmp(VendorID, "GenuineIntel") == 0) ProcessorManufacturer = MANUFACTURER_INTEL;
	else if (stricmp(VendorID, "AuthenticAMD") == 0) ProcessorManufacturer = MANUFACTURER_AMD;
	else if (stricmp(VendorID, "AMD ISBETTER") == 0) ProcessorManufacturer = MANUFACTURER_AMD;
	else if (stricmp(VendorID, "UMC UMC UMC") == 0) ProcessorManufacturer = MANUFACTURER_UMC;
	else if (stricmp(VendorID, "CyrixInstead") == 0) ProcessorManufacturer = MANUFACTURER_CYRIX;
	else if (stricmp(VendorID, "NexGenDriven") == 0) ProcessorManufacturer = MANUFACTURER_NEXTGEN;
	else if (stricmp(VendorID, "CentaurHauls") == 0) ProcessorManufacturer = MANUFACTURER_VIA;
	else if (stricmp(VendorID, "RiseRiseRise") == 0) ProcessorManufacturer = MANUFACTURER_RISE;
	else if (stricmp(VendorID, "GenuineTMx86") == 0) ProcessorManufacturer = MANUFACTURER_TRANSMETA;
}

void CPUDetectClass::Process_Cache_Info(unsigned value)
{
	switch (value) {
	case 0x00: // Null
		break;
	case 0x01: // Instruction TLB, 4K pages, 4-way set associative, 32 entries
		break;
	case 0x02: // Instruction TLB, 4M pages, fully associative, 2 entries
		break;
	case 0x03: // Data TLB, 4K pages, 4-way set associative, 64 entries
		break;
	case 0x04: // Data TLB, 4M pages, 4-way set associative, 8 entries
		break;
	case 0x06: // Instruction cache, 8K, 4-way set associative, 32 byte line size
		L1InstructionCacheSize=8*1024;
		L1InstructionCacheLineSize=32;
		L1InstructionCacheSetAssociative=4;
		break;
	case 0x08: // Instruction cache 16K, 4-way set associative, 32 byte line size
		L1InstructionCacheSize=16*1024;
		L1InstructionCacheLineSize=32;
		L1InstructionCacheSetAssociative=4;
		break;
	case 0x0A: // Data cache, 8K, 2-way set associative, 32 byte line size
		L1DataCacheSize=8*1024;
		L1DataCacheLineSize=32;
		L1DataCacheSetAssociative=2;
		break;
	case 0x0C: // Data cache, 16K, 4-way set associative, 32 byte line size
		L1DataCacheSize=16*1024;
		L1DataCacheLineSize=32;
		L1DataCacheSetAssociative=4;
		break;
	case 0x40: // No L2 cache (P6 family), or No L3 cache (Pentium 4 processor)
		// Nice of Intel, Pentium4 has an exception and this field is defined as "no L3 cache"
		if (ProcessorManufacturer==MANUFACTURER_INTEL && ProcessorFamily==0xf) {
		}
		else {
			L2CacheSize=0;
			L2CacheLineSize=0;
			L2CacheSetAssociative=0;
		}
		break;
	case 0x41: // Unified cache, 32 byte cache line,4-way set associative, 128K
		L2CacheSize=128*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=4;
		break;
	case 0x42: // Unified cache, 32 byte cache line, 4-way set associative, 256K
		L2CacheSize=256*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=4;
		break;
	case 0x43: // Unified cache, 32 byte cache line, 4-way set associative, 512K
		L2CacheSize=512*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=4;
		break;
	case 0x44: // Unified cache, 32 byte cache line, 4-way set associative, 1M
		L2CacheSize=1024*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=4;
		break;
	case 0x45: // Unified cache, 32 byte cache line, 4-way set associative, 2M
		L2CacheSize=2048*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=4;
		break;
	case 0x50: // Instruction TLB, 4K, 2M or 4M pages, fully associative, 64 entries
		break;
	case 0x51: // Instruction TLB, 4K, 2M or 4M pages, fully associative, 128 entries
		break;
	case 0x52: // Instruction TLB, 4K, 2M or 4M pages, fully associative, 256 entries
		break;
	case 0x5B: // Data TLB, 4K or 4M pages, fully associative, 64 entries
		break;
	case 0x5C: // Data TLB, 4K or 4M pages, fully associative, 128 entries
		break;
	case 0x5D: // Data TLB, 4K or 4M pages, fully associative, 256 entries
		break;
	case 0x66: // Data cache, sectored, 64 byte cache line, 4 way set associative, 8K
		L1DataCacheSize=8*1024;
		L1DataCacheLineSize=64;
		L1DataCacheSetAssociative=4;
		break;
	case 0x67: // Data cache, sectored, 64 byte cache line, 4 way set associative, 16K
		L1DataCacheSize=16*1024;
		L1DataCacheLineSize=64;
		L1DataCacheSetAssociative=4;
		break;
	case 0x68: // Data cache, sectored, 64 byte cache line, 4 way set associative, 32K
		L1DataCacheSize=32*1024;
		L1DataCacheLineSize=64;
		L1DataCacheSetAssociative=4;
		break;
	case 0x70: // Instruction Trace cache, 8 way set associative, 12K uOps
		L1InstructionTraceCacheSize=12*1024;
		L1InstructionTraceCacheSetAssociative=8;
		break;
	case 0x71: // Instruction Trace cache, 8 way set associative, 16K uOps
		L1InstructionTraceCacheSize=16*1024;
		L1InstructionTraceCacheSetAssociative=8;
		break;
	case 0x72: // Instruction Trace cache, 8 way set associative, 32K uOps
		L1InstructionTraceCacheSize=32*1024;
		L1InstructionTraceCacheSetAssociative=8;
		break;
	case 0x79: // Unified cache, sectored, 64 byte cache line, 8 way set associative, 128K
		L2CacheSize=128*1024;
		L2CacheLineSize=64;
		L2CacheSetAssociative=8;
		break;
	case 0x7A: // Unified cache, sectored, 64 byte cache line, 8 way set associative, 256K
		L2CacheSize=256*1024;
		L2CacheLineSize=64;
		L2CacheSetAssociative=8;
		break;
	case 0x7B: // Unified cache, sectored, 64 byte cache line, 8 way set associative, 512K
		L2CacheSize=512*1024;
		L2CacheLineSize=64;
		L2CacheSetAssociative=8;
		break;
	case 0x7C: // Unified cache, sectored, 64 byte cache line, 8 way set associative, 1M
		L2CacheSize=1024*1024;
		L2CacheLineSize=64;
		L2CacheSetAssociative=8;
		break;
	case 0x82: // Unified cache, 32 byte cache line, 8 way set associative, 256K
		L2CacheSize=256*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=8;
		break;
	case 0x83: // Unified cache, 32 byte cache line, 8 way set associative, 512K
		L2CacheSize=512*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=8;
		break;
	case 0x84: // Unified cache, 32 byte cache line, 8 way set associative, 1M
		L2CacheSize=1024*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=8;
		break;
	case 0x85: // Unified cache, 32 byte cache line, 8 way set associative, 2M
		L2CacheSize=2048*1024;
		L2CacheLineSize=32;
		L2CacheSetAssociative=8;
		break;
	}
}

void CPUDetectClass::Process_Extended_Cache_Info()
{
	CPUIDStruct max_ext(0x80000000);
	if (max_ext.Eax>=0x80000006) {
		CPUIDStruct l1info(0x80000005);
		CPUIDStruct l2info(0x80000006);

		// L1 data cache
		L1DataCacheLineSize=l1info.Ecx&0xff;
		L1DataCacheSetAssociative=(l1info.Ecx>>16)&0xff;
		L1DataCacheSize=((l1info.Ecx>>24)&0xff)*1024;

		// L1 instruction cache
		L1InstructionCacheLineSize=l1info.Edx&0xff;
		L1InstructionCacheSetAssociative=(l1info.Edx>>16)&0xff;
		L1InstructionCacheSize=((l1info.Edx>>24)&0xff)*1024;

		// L2 cache
		L2CacheSize=((l2info.Ecx>>16)&0xffff)*1024;
		L2CacheLineSize=l2info.Ecx&0xff;
		switch ((l2info.Ecx>>12)&0xf) {
		case 0: L2CacheSetAssociative=0; break;
		case 1: L2CacheSetAssociative=1; break;
		case 2: L2CacheSetAssociative=2; break;
		case 4: L2CacheSetAssociative=4; break;
		case 6: L2CacheSetAssociative=8; break;
		case 8: L2CacheSetAssociative=16; break;
		case 15: L2CacheSetAssociative=0xff; break;
		}

	}
}

void CPUDetectClass::Init_Cache()
{
	CPUIDStruct cache_id(2);
	// This code can only determine cache sizes for cpuid count 1 (see Intel manuals for more info on the subject)
	if ((cache_id.Eax&0xff)==1) {
		if (!(cache_id.Eax&0x80000000)) {
			Process_Cache_Info((cache_id.Eax>>24)&0xff);
			Process_Cache_Info((cache_id.Eax>>16)&0xff);
			Process_Cache_Info((cache_id.Eax>>8)&0xff);
		}
		if (!(cache_id.Ebx&0x80000000)) {
			Process_Cache_Info((cache_id.Ebx>>24)&0xff);
			Process_Cache_Info((cache_id.Ebx>>16)&0xff);
			Process_Cache_Info((cache_id.Ebx>>8)&0xff);
			Process_Cache_Info((cache_id.Ebx)&0xff);
		}
		if (!(cache_id.Ecx&0x80000000)) {
			Process_Cache_Info((cache_id.Ecx>>24)&0xff);
			Process_Cache_Info((cache_id.Ecx>>16)&0xff);
			Process_Cache_Info((cache_id.Ecx>>8)&0xff);
			Process_Cache_Info((cache_id.Ecx)&0xff);
		}
		if (!(cache_id.Edx&0x80000000)) {
			Process_Cache_Info((cache_id.Edx>>24)&0xff);
			Process_Cache_Info((cache_id.Edx>>16)&0xff);
			Process_Cache_Info((cache_id.Edx>>8)&0xff);
			Process_Cache_Info((cache_id.Edx)&0xff);
		}
	}
	else {
		// If standard (Intel) cache information not found, try extended info.
		Process_Extended_Cache_Info();
	}
}

void CPUDetectClass::Init_Intel_Processor_Type()
{
	switch (ProcessorFamily) {
	case 3:
		IntelProcessor=INTEL_PROCESSOR_80386;
		break;
	case 4:
		switch (ProcessorModel) {
		default:
		case 0:
		case 1:
			IntelProcessor=INTEL_PROCESSOR_80486DX;
			break;
		case 2:
			IntelProcessor=INTEL_PROCESSOR_80486SX;
			break;
		case 3:
			IntelProcessor=INTEL_PROCESSOR_80486DX2;
			break;
		case 4:
			IntelProcessor=INTEL_PROCESSOR_80486SL;
			break;
		case 5:
			IntelProcessor=INTEL_PROCESSOR_80486SX2;
			break;
		case 7:
			IntelProcessor=INTEL_PROCESSOR_80486DX2_WB;
			break;
		case 8:
			IntelProcessor=INTEL_PROCESSOR_80486DX4;
			break;
		case 9:
			IntelProcessor=INTEL_PROCESSOR_80486DX4_WB;
			break;
		}
		break;
	case 5:
		switch (ProcessorModel) {
		default:
		case 0:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM; // Early model
			break;
		case 1:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM; // 60/66, 5V
			break;
		case 2:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM; // 75+, 3.xV
			break;
		case 3:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_OVERDRIVE;
			break;
		case 4:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_MMX;
			break;
		case 5:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_OVERDRIVE;
			break;
		case 6:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_OVERDRIVE;
			break;
		case 7:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM;
			break;
		case 8:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_MMX;
			break;
		}
	case 6:
		switch (ProcessorModel) {
		default:
		case 0:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_PRO_SAMPLE;
			break;
		case 1:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_PRO;
			break;
		case 3:
			if (ProcessorType==0) {
				IntelProcessor=INTEL_PROCESSOR_PENTIUM_II_MODEL_3;
			}
			else {
				IntelProcessor=INTEL_PROCESSOR_PENTIUM_II_OVERDRIVE;
			}
			break;
		case 4:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_II_MODEL_4;
			break;
		case 5:
			// This one is either PII, PIIXeon or Celeron with no L2 cache!
			{
				CPUIDStruct cache_id(2);
				// TODO: Check for cache to determine processor type!
				if (L2CacheSize==0) {
					IntelProcessor=INTEL_PROCESSOR_CELERON_MODEL_5;
				}
				else if (L2CacheSize<=512*1024) {	// If Xeon has 512k L2 cache we will assume a PII - tough luck!
					IntelProcessor=INTEL_PROCESSOR_PENTIUM_II_MODEL_5;
				}
				else {
					IntelProcessor=INTEL_PROCESSOR_PENTIUM_II_XEON_MODEL_5;
				}
			}
			break;
		case 6:
			IntelProcessor=INTEL_PROCESSOR_CELERON_MODEL_6;
			break;
		case 7:
			if (L2CacheSize<=512*1024) {	// If Xeon has 512k L2 cache we will assume a PIII - tough luck!
				IntelProcessor=INTEL_PROCESSOR_PENTIUM_III_MODEL_7;
			}
			else {
				IntelProcessor=INTEL_PROCESSOR_PENTIUM_III_XEON_MODEL_7;
			}
			break;
		case 8:
			// This could be PentiumIII Coppermine or Celeron with SSE, or a Xeon
			{
				CPUIDStruct brand(1);
				switch (brand.Ebx&0xff) {
				case 0x1: IntelProcessor=INTEL_PROCESSOR_CELERON_MODEL_8; break;
				case 0x2:
				case 0x4: IntelProcessor=INTEL_PROCESSOR_PENTIUM_III_MODEL_8; break;
				case 0x3:
				case 0xe: IntelProcessor=INTEL_PROCESSOR_PENTIUM_III_XEON_MODEL_8; break;
				}
			}
			break;
		case 0xa:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_III_XEON_MODEL_A;
			break;
		case 0xb:
			IntelProcessor=INTEL_PROCESSOR_PENTIUM_III_MODEL_B;
			break;
		}
		break;
	case 0xf:
		IntelProcessor=INTEL_PROCESSOR_PENTIUM4;
		break;
	}
}

void CPUDetectClass::Init_AMD_Processor_Type()
{
	switch (ProcessorFamily) {
	case 4:
		switch (ProcessorModel) {
		case 3:
			AMDProcessor=AMD_PROCESSOR_486DX2;
			break;
		case 7:
			AMDProcessor=AMD_PROCESSOR_486DX4;
			break;
		case 8:
			AMDProcessor=AMD_PROCESSOR_486DX4;
			break;
		case 9:
			AMDProcessor=AMD_PROCESSOR_486DX4;
			break;
		case 0xe:
			AMDProcessor=AMD_PROCESSOR_5x86;
			break;
		case 0xf:
			AMDProcessor=AMD_PROCESSOR_5x86;
			break;
		default:
			AMDProcessor=AMD_PROCESSOR_486;
		}
		break;
	case 5:
		switch (ProcessorModel) {
		case 0:
			AMDProcessor=AMD_PROCESSOR_K5_MODEL0;
			break;
		case 1:
			AMDProcessor=AMD_PROCESSOR_K5_MODEL1;
			break;
		case 2:
			AMDProcessor=AMD_PROCESSOR_K5_MODEL2;
			break;
		case 3:
			AMDProcessor=AMD_PROCESSOR_K5_MODEL3;
			break;
		case 6:
			AMDProcessor=AMD_PROCESSOR_K6_MODEL6;
			break;
		case 7:
			AMDProcessor=AMD_PROCESSOR_K6_MODEL7;
			break;
		case 8:
			AMDProcessor=AMD_PROCESSOR_K6_2_3DNOW_MODEL8;
			break;
		case 9:
			AMDProcessor=AMD_PROCESSOR_K6_3_3DNOW_MODEL9;
			break;
		case 0xd:
			AMDProcessor=AMD_PROCESSOR_K6_3_PLUS;
			break;
		default:
			AMDProcessor=AMD_PROCESSOR_K6;
		}
	case 6:
		switch (ProcessorModel) {
		case 1:
			AMDProcessor=AMD_PROCESSOR_ATHLON_025;
			break;
		case 2:
			AMDProcessor=AMD_PROCESSOR_ATHLON_018;
			break;
		case 3:
			AMDProcessor=AMD_PROCESSOR_DURON;
			break;
		case 4:
			AMDProcessor=AMD_PROCESSOR_ATHLON_018_IL2;
			break;
		default:
			AMDProcessor=AMD_PROCESSOR_ATHLON;
			break;
		}
	}
}

void CPUDetectClass::Init_VIA_Processor_Type()
{
	switch (ProcessorFamily) {
	case 5:
		switch (ProcessorModel) {
		case 4:
			VIAProcessor=VIA_PROCESSOR_IDT_C6_WINCHIP;
			break;
		case 8:
			VIAProcessor=VIA_PROCESSOR_IDT_C6_WINCHIP2;
			break;
		case 9:
			VIAProcessor=VIA_PROCESSOR_IDT_C6_WINCHIP3;
			break;
		}
	case 6:
		switch (ProcessorModel) {
		case 4:
			VIAProcessor=VIA_PROCESSOR_CYRIX_III_SAMUEL;
			break;
		}
	}
}

void CPUDetectClass::Init_Rise_Processor_Type()
{
	switch (ProcessorFamily) {
	case 5:
		switch (ProcessorModel) {
		case 0:
			RiseProcessor=RISE_PROCESSOR_DRAGON_025;
			break;
		case 2:
			RiseProcessor=RISE_PROCESSOR_DRAGON_018;
			break;
		case 8:
			RiseProcessor=RISE_PROCESSOR_DRAGON2_025;
			break;
		case 9:
			RiseProcessor=RISE_PROCESSOR_DRAGON2_018;
			break;
		}
	}
}

void CPUDetectClass::Init_Processor_Family()
{
	unsigned signature=0;
	unsigned notneeded1;
	unsigned notneeded2;
	unsigned features;
	CPUID(
		signature,
		notneeded1,
		notneeded2,
		features,
		1);

	ProcessorType=(signature>>12)&0x0f;
	ProcessorFamily=(signature>>8)&0x0f;
	ProcessorModel=(signature>>4)&0x0f;
	ProcessorRevision=(signature)&0x0f;

	IntelProcessor=INTEL_PROCESSOR_UNKNOWN;
	AMDProcessor=AMD_PROCESSOR_UNKNOWN;
	VIAProcessor=VIA_PROCESSOR_UNKNOWN;
	RiseProcessor=RISE_PROCESSOR_UNKNOWN;

	Init_Cache();

	switch (ProcessorManufacturer) {
	case MANUFACTURER_INTEL:
		Init_Intel_Processor_Type();
		break;
	case MANUFACTURER_AMD:
		Init_AMD_Processor_Type();
		break;
	case MANUFACTURER_VIA:
		Init_VIA_Processor_Type();
		break;
	case MANUFACTURER_RISE:
		Init_Rise_Processor_Type();
		break;
	}
}

void CPUDetectClass::Init_Processor_String()
{
	if (!Has_CPUID_Instruction()) {
		ProcessorString[0]='\0';
	}

	CPUIDStruct ext_id(0x80000000);	// Check maximum extended cpuid level
//	if (ProcessorManufacturer==MANUFACTURER_INTEL && IntelProcessor<INTEL_PROCESSOR_PENTIUM4) {
//		ext_id.Eax=0;	// No support for extended cpuid in Intel cpus prior to Pentium4
//	}
	// If extended cpuid level's highest bit is set, we'll get the cpu string from 2/3/4
	if (ext_id.Eax&0x80000000) {
		CPUDetectClass::CPUID(
			(unsigned&)ProcessorString[0],
			(unsigned&)ProcessorString[4],
			(unsigned&)ProcessorString[8],
			(unsigned&)ProcessorString[12],
			0x80000002);
		CPUDetectClass::CPUID(
			(unsigned&)ProcessorString[16],
			(unsigned&)ProcessorString[20],
			(unsigned&)ProcessorString[24],
			(unsigned&)ProcessorString[28],
			0x80000003);
		CPUDetectClass::CPUID(
			(unsigned&)ProcessorString[32],
			(unsigned&)ProcessorString[36],
			(unsigned&)ProcessorString[40],
			(unsigned&)ProcessorString[44],
			0x80000004);
	}
	// If no extended cpuid available (Intel processors prior to P4), compose the string
	else {
		StringClass str(0,true);
		str=Get_Processor_Manufacturer_Name();
		if (ProcessorManufacturer==MANUFACTURER_INTEL) {
			str+=" ";
			switch (IntelProcessor) {
			default:
			case INTEL_PROCESSOR_UNKNOWN:							str+="<UNKNOWN>"; break;
			case INTEL_PROCESSOR_80386:							str+="80386"; break;
			case INTEL_PROCESSOR_80486DX:							str+="80486DX"; break;
			case INTEL_PROCESSOR_80486SX:							str+="80486SX"; break;
			case INTEL_PROCESSOR_80486DX2:						str+="80486DX2"; break;
			case INTEL_PROCESSOR_80486SL:							str+="80486SL"; break;
			case INTEL_PROCESSOR_80486SX2:						str+="80486SX2"; break;
			case INTEL_PROCESSOR_80486DX2_WB:					str+="80486DX2(WB)"; break;
			case INTEL_PROCESSOR_80486DX4:						str+="80486DX4"; break;
			case INTEL_PROCESSOR_80486DX4_WB:					str+="80486DX4(WB)"; break;
			case INTEL_PROCESSOR_PENTIUM:							str+="Pentium"; break;
			case INTEL_PROCESSOR_PENTIUM_OVERDRIVE:			str+="Pentium Overdrive"; break;
			case INTEL_PROCESSOR_PENTIUM_MMX:					str+="Pentium MMX"; break;
			case INTEL_PROCESSOR_PENTIUM_PRO_SAMPLE:			str+="Pentium Pro (Engineering Sample)"; break;
			case INTEL_PROCESSOR_PENTIUM_PRO:					str+="Pentium Pro"; break;
			case INTEL_PROCESSOR_PENTIUM_II_OVERDRIVE:		str+="Pentium II Overdrive"; break;
			case INTEL_PROCESSOR_PENTIUM_II_MODEL_3:			str+="Pentium II, model 3"; break;
			case INTEL_PROCESSOR_PENTIUM_II_MODEL_4:			str+="Pentium II, model 4"; break;
			case INTEL_PROCESSOR_CELERON_MODEL_5:				str+="Celeron, model 5"; break;
			case INTEL_PROCESSOR_PENTIUM_II_MODEL_5:			str+="Pentium II, model 5"; break;
			case INTEL_PROCESSOR_PENTIUM_II_XEON_MODEL_5:	str+="Pentium II Xeon, model 5"; break;
			case INTEL_PROCESSOR_CELERON_MODEL_6:				str+="Celeron, model 6"; break;
			case INTEL_PROCESSOR_PENTIUM_III_MODEL_7:			str+="Pentium III, model 7"; break;
			case INTEL_PROCESSOR_PENTIUM_III_XEON_MODEL_7:	str+="Pentium III Xeon, model 7"; break;
			case INTEL_PROCESSOR_CELERON_MODEL_8:				str+="Celeron, model 8"; break;
			case INTEL_PROCESSOR_PENTIUM_III_MODEL_8:			str+="Pentium III, model 8"; break;
			case INTEL_PROCESSOR_PENTIUM_III_XEON_MODEL_8:	str+="Pentium III Xeon, model 8"; break;
			case INTEL_PROCESSOR_PENTIUM_III_XEON_MODEL_A:	str+="Pentium III Xeon, model A"; break;
			case INTEL_PROCESSOR_PENTIUM_III_MODEL_B:			str+="Pentium III, model B"; break;
			case INTEL_PROCESSOR_PENTIUM4:						str+="Pentium4"; break;
			}
		}
		strncpy(ProcessorString,str.Peek_Buffer(),sizeof(ProcessorString));
	}

}

void CPUDetectClass::Init_CPUID_Instruction()
{
	unsigned long cpuid_available=0;

   // The pushfd/popfd commands are done using emits
   // because CodeWarrior seems to have problems with
   // the command (huh?)

   __asm
   {
		mov cpuid_available,0	// clear flag
		push ebx
		pushfd
		pop eax
		mov ebx,eax
		xor eax,0x00200000
		push eax
		popfd
		pushfd
		pop eax
		xor eax,ebx
		je done
		mov cpuid_available,1
done:
		push ebx
		popfd
		pop ebx
	}
	HasCPUIDInstruction=!!cpuid_available;
}

void CPUDetectClass::Init_Processor_Features()
{
	if (!CPUDetectClass::Has_CPUID_Instruction()) return;

	CPUIDStruct id(1);
	FeatureBits=id.Edx;
	HasRDTSCInstruction=(!!(FeatureBits&(1<<4)));
	HasCMOVSupport=(!!(FeatureBits&(1<<15)));
	HasMMXSupport=(!!(FeatureBits&(1<<23)));
	HasSSESupport=!!(FeatureBits&(1<<25));
	HasSSE2Support=!!(FeatureBits&(1<<26));

	Has3DNowSupport=false;
	ExtendedFeatureBits=0;
	if (ProcessorManufacturer==MANUFACTURER_AMD) {
		if (Has_CPUID_Instruction()) {
			CPUIDStruct max_ext_id(0x80000000);
			if (max_ext_id.Eax>=0x80000001) {	// Signature & features field available?
				CPUIDStruct ext_signature(0x80000001);
				ExtendedFeatureBits=ext_signature.Edx;
				Has3DNowSupport=!!(ExtendedFeatureBits&0x80000000);
				HasExtended3DNowSupport=!!(ExtendedFeatureBits&0x40000000);
			}
		}
	}
}

void CPUDetectClass::Init_Memory()
{
	MEMORYSTATUS mem;
	GlobalMemoryStatus(&mem);
	TotalPhysicalMemory=mem.dwTotalPhys;
	AvailablePhysicalMemory=mem.dwAvailPhys;
	TotalPageMemory=mem.dwTotalPageFile;
	AvailablePageMemory=mem.dwAvailPageFile;
	TotalVirtualMemory=mem.dwTotalVirtual;
	AvailableVirtualMemory=mem.dwAvailVirtual;
}

void CPUDetectClass::Init_OS()
{
	OSVERSIONINFO os;
	os.dwOSVersionInfoSize=sizeof(os);
	GetVersionEx(&os);

	OSVersionNumberMajor=os.dwMajorVersion;
	OSVersionNumberMinor=os.dwMinorVersion;
	OSVersionBuildNumber=os.dwBuildNumber;
	OSVersionPlatformId=os.dwPlatformId;
	OSVersionExtraInfo=os.szCSDVersion;
}

bool CPUDetectClass::CPUID(
	unsigned& u_eax_,
	unsigned& u_ebx_,
	unsigned& u_ecx_,
	unsigned& u_edx_,
	unsigned cpuid_type)
{
	if (!Has_CPUID_Instruction()) return false;	// Most processors since 486 have CPUID...

	unsigned u_eax;
	unsigned u_ebx;
	unsigned u_ecx;
	unsigned u_edx;

	__asm
	{
		pushad
		mov		eax,[cpuid_type]
		xor		ebx,ebx
		xor		ecx,ecx
		xor		edx,edx
		cpuid
		mov		[u_eax],eax
		mov		[u_ebx],ebx
		mov		[u_ecx],ecx
		mov		[u_edx],edx
		popad
	}

	u_eax_=u_eax;
	u_ebx_=u_ebx;
	u_ecx_=u_ecx;
	u_edx_=u_edx;

	return true;
}

#define SYSLOG(n) work.Format n ; CPUDetectClass::ProcessorLog+=work;

void CPUDetectClass::Init_Processor_Log()
{
	StringClass work(0,true);

	SYSLOG(("Operating System: "));
	switch (OSVersionPlatformId) {
	case VER_PLATFORM_WIN32s: SYSLOG(("Windows 3.1")); break;
	case VER_PLATFORM_WIN32_WINDOWS: SYSLOG(("Windows 9x")); break;
	case VER_PLATFORM_WIN32_NT: SYSLOG(("Windows NT")); break;
	}
	SYSLOG(("\r\n"));

	SYSLOG(("Operating system version %d.%d\r\n",OSVersionNumberMajor,OSVersionNumberMinor));
	SYSLOG(("Operating system build: %d.%d.%d\r\n",
		(OSVersionBuildNumber&0xff000000)>>24,
		(OSVersionBuildNumber&0xff0000)>>16,
		(OSVersionBuildNumber&0xffff)));
	SYSLOG(("OS-Info: %s\r\n",OSVersionExtraInfo));

	SYSLOG(("Processor: %s\r\n",CPUDetectClass::Get_Processor_String()));
	SYSLOG(("Clock speed: ~%dMHz\r\n",CPUDetectClass::Get_Processor_Speed()));
	StringClass cpu_type(0,true);
	switch (CPUDetectClass::Get_Processor_Type()) {
	case 0: cpu_type="Original OEM"; break;
	case 1: cpu_type="Overdrive"; break;
	case 2: cpu_type="Dual"; break;
	case 3: cpu_type="*Intel Reserved*"; break;
	}
	SYSLOG(("Processor type: %s\r\n",cpu_type));

	SYSLOG(("\r\n"));

	SYSLOG(("Total physical memory: %dMb\r\n",Get_Total_Physical_Memory()/(1024*1024)));
	SYSLOG(("Available physical memory: %dMb\r\n",Get_Available_Physical_Memory()/(1024*1024)));
	SYSLOG(("Total page file size: %dMb\r\n",Get_Total_Page_File_Size()/(1024*1024)));
	SYSLOG(("Total available page file size: %dMb\r\n",Get_Available_Page_File_Size()/(1024*1024)));
	SYSLOG(("Total virtual memory: %dMb\r\n",Get_Total_Virtual_Memory()/(1024*1024)));
	SYSLOG(("Available virtual memory: %dMb\r\n",Get_Available_Virtual_Memory()/(1024*1024)));

	SYSLOG(("\r\n"));

	SYSLOG(("CPUID: %s\r\n",CPUDetectClass::Has_CPUID_Instruction() ? "Yes" : "No"));
	SYSLOG(("RDTSC: %s\r\n",CPUDetectClass::Has_RDTSC_Instruction() ? "Yes" : "No"));
	SYSLOG(("CMOV: %s\r\n",CPUDetectClass::Has_CMOV_Instruction() ? "Yes" : "No"));
	SYSLOG(("MMX: %s\r\n",CPUDetectClass::Has_MMX_Instruction_Set() ? "Yes" : "No"));
	SYSLOG(("SSE: %s\r\n",CPUDetectClass::Has_SSE_Instruction_Set() ? "Yes" : "No"));
	SYSLOG(("SSE2: %s\r\n",CPUDetectClass::Has_SSE2_Instruction_Set() ? "Yes" : "No"));
	SYSLOG(("3DNow!: %s\r\n",CPUDetectClass::Has_3DNow_Instruction_Set() ? "Yes" : "No"));
	SYSLOG(("Extended 3DNow!: %s\r\n",CPUDetectClass::Has_Extended_3DNow_Instruction_Set() ? "Yes" : "No"));
	SYSLOG(("CPU Feature bits: 0x%x\r\n",CPUDetectClass::Get_Feature_Bits()));
	SYSLOG(("Ext. CPU Feature bits: 0x%x\r\n",CPUDetectClass::Get_Extended_Feature_Bits()));

	SYSLOG(("\r\n"));

	if (CPUDetectClass::Get_L1_Data_Cache_Size()) {
		SYSLOG(("L1 Data Cache: %d byte cache lines, %d way set associative, %dk\r\n",
			CPUDetectClass::Get_L1_Data_Cache_Line_Size(),
			CPUDetectClass::Get_L1_Data_Cache_Set_Associative(),
			CPUDetectClass::Get_L1_Data_Cache_Size()/1024));
	}
	else {
		SYSLOG(("L1 Data Cache: None\r\n"));
	}

	if (CPUDetectClass::Get_L1_Instruction_Cache_Size()) {
		SYSLOG(("L1 Instruction Cache: %d byte cache lines, %d way set associative, %dk\r\n",
			CPUDetectClass::Get_L1_Instruction_Cache_Line_Size(),
			CPUDetectClass::Get_L1_Instruction_Cache_Set_Associative(),
			CPUDetectClass::Get_L1_Instruction_Cache_Size()/1024));
	}
	else {
		SYSLOG(("L1 Instruction Cache: None\r\n"));
	}

	if (CPUDetectClass::Get_L1_Instruction_Trace_Cache_Size()) {
		SYSLOG(("L1 Instruction Trace Cache: %d way set associative, %dk µOPs\r\n",
			CPUDetectClass::Get_L1_Instruction_Cache_Set_Associative(),
			CPUDetectClass::Get_L1_Instruction_Cache_Size()/1024));
	}
	else {
		SYSLOG(("L1 Instruction Trace Cache: None\r\n"));
	}


	if (CPUDetectClass::Get_L2_Cache_Size()) {
		SYSLOG(("L2 Cache: %d byte cache lines, %d way set associative, %dk\r\n",
			CPUDetectClass::Get_L2_Cache_Line_Size(),
			CPUDetectClass::Get_L2_Cache_Set_Associative(),
			CPUDetectClass::Get_L2_Cache_Size()/1024));
	}
	else {
		SYSLOG(("L2 cache: None\r\n"));
	}
	SYSLOG(("\r\n"));
}

// OSCODE OSSUBCODE CPUMANUFACTURER CPUSPEED MEMORY CPUBITS EXTCPUBITS
#define COMPACTLOG(n) work.Format n ; CPUDetectClass::CompactLog+=work;

void CPUDetectClass::Init_Compact_Log()
{
	StringClass work(0,true);

	TIME_ZONE_INFORMATION time_zone;
	GetTimeZoneInformation(&time_zone);
	COMPACTLOG(("%d\t",time_zone.Bias));

	OSInfoStruct os_info;
	Get_OS_Info(os_info,OSVersionPlatformId,OSVersionNumberMajor,OSVersionNumberMinor,OSVersionBuildNumber);
	COMPACTLOG(("%s\t",os_info.Code));

	if (!stricmp(os_info.SubCode,"UNKNOWN")) {
		COMPACTLOG(("%d\t",OSVersionBuildNumber&0xffff));
	}
	else {
		COMPACTLOG(("%s\t",os_info.SubCode));
	}

	COMPACTLOG(("%s\t%d\t",Get_Processor_Manufacturer_Name(),Get_Processor_Speed()));

	COMPACTLOG(("%d\t",Get_Total_Physical_Memory()/(1024*1024)+1));

	COMPACTLOG(("%x\t%x\t",Get_Feature_Bits(),Get_Extended_Feature_Bits()));
}

static class CPUDetectInitClass
{
public:
	CPUDetectInitClass::CPUDetectInitClass()
	{
		CPUDetectClass::Init_CPUID_Instruction();
		// We pretty much need CPUID, but let's not crash if it doesn't exist.
		// Every processor our games run should have CPUID so it would be extremely unlikely for it not to be present.
		// One can never be sure about the clones though...
		if (CPUDetectClass::Has_CPUID_Instruction()) {
			CPUDetectClass::Init_Processor_Manufacturer();
			CPUDetectClass::Init_Processor_Family();
			CPUDetectClass::Init_Processor_String();
			CPUDetectClass::Init_Processor_Features();
			CPUDetectClass::Init_Memory();
			CPUDetectClass::Init_OS();
		}
		CPUDetectClass::Init_Processor_Speed();

		CPUDetectClass::Init_Processor_Log();
		CPUDetectClass::Init_Compact_Log();
	}
} _CPU_Detect_Init;


OSInfoStruct Windows9xVersionTable[]={
	{"WIN95",	"FINAL",		"Windows 95",								4,0,950,			4,0,950		},
	{"WIN95",	"A",			"Windows 95a OSR1 final Update",		4,0,950,			4,0,951		},
	{"WIN95",	"B20OEM",	"Windows 95B OSR 2.0 final OEM",		4,0,950,			4,0,1111		},
	{"WIN95",	"B20UPD",	"Windows 95B OSR 2.1 final Update",	4,0,950,			4,3,1212		},
	{"WIN95",	"B21OEM",	"Windows 95B OSR 2.1 final OEM",		4,1,971,			4,1,971		},
	{"WIN95",	"C25OEM",	"Windows 95C OSR 2.5 final OEM",		4,0,950,			4,3,1214		},
	{"WIN98",	"BETAPRD",	"Windows 98 Beta pre-DR",				4,10,1351,		4,10,1351 	},
	{"WIN98",	"BETADR",	"Windows 98 Beta DR",					4,10,1358,		4,10,1358 	},
	{"WIN98",	"BETAE",		"Windows 98 early Beta",				4,10,1378,		4,10,1378 	},
	{"WIN98",	"BETAE",		"Windows 98 early Beta",				4,10,1410,		4,10,1410 	},
	{"WIN98",	"BETAE",		"Windows 98 early Beta",				4,10,1423,		4,10,1423 	},
	{"WIN98",	"BETA1",		"Windows 98 Beta 1",						4,10,1500,		4,10,1500 	},
	{"WIN98",	"BETA1",		"Windows 98 Beta 1",						4,10,1508,		4,10,1508	},
	{"WIN98",	"BETA1",		"Windows 98 Beta 1",						4,10,1511,		4,10,1511 	},
	{"WIN98",	"BETA1",		"Windows 98 Beta 1",						4,10,1525,		4,10,1525 	},
	{"WIN98",	"BETA1",		"Windows 98 Beta 1",						4,10,1535,		4,10,1535 	},
	{"WIN98",	"BETA1",		"Windows 98 Beta 1",						4,10,1538,		4,10,1538 	},
	{"WIN98",	"BETA1",		"Windows 98 Beta 1",						4,10,1543,		4,10,1543 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1544,		4,10,1544 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1546,		4,10,1546 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1550,		4,10,1550 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1559,		4,10,1559 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1564,		4,10,1564 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1569,		4,10,1569 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1577,		4,10,1577 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1581,		4,10,1581 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1593,		4,10,1593 	},
	{"WIN98",	"BETA2",		"Windows 98 Beta 2",						4,10,1599,		4,10,1599 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1602,		4,10,1602 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1605,		4,10,1605 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1614,		4,10,1614 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1619,		4,10,1619 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1624,		4,10,1624 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1629,		4,10,1629 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1633,		4,10,1633 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1650,		4,10,1650 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1650,/*,3*/4,10,1650/*,3*/	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1650,/*,8*/4,10,1650/*,8*/	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1666,		4,10,1666 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1671,		4,10,1671 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1677,		4,10,1677 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1681,		4,10,1681 	},
	{"WIN98",	"BETA3",		"Windows 98 Beta 3",						4,10,1687,		4,10,1687 	},
	{"WIN98",	"RC0",		"Windows 98 RC0",							4,10,1691,		4,10,1691 	},
	{"WIN98",	"RC0",		"Windows 98 RC0",							4,10,1702,		4,10,1702 	},
	{"WIN98",	"RC0",		"Windows 98 RC0",							4,10,1708,		4,10,1708 	},
	{"WIN98",	"RC0",		"Windows 98 RC0",							4,10,1713,		4,10,1713 	},
	{"WIN98",	"RC1",		"Windows 98 RC1",							4,10,1721,/*,3*/4,10,1721/*,3*/	},
	{"WIN98",	"RC2",		"Windows 98 RC2",							4,10,1723,/*,4*/4,10,1723/*,4*/	},
	{"WIN98",	"RC2",		"Windows 98 RC2",							4,10,1726,		4,10,1726	},
	{"WIN98",	"RC3",		"Windows 98 RC3",							4,10,1900,/*,5*/4,10,1900/*,5*/	},
	{"WIN98",	"RC4",		"Windows 98 RC4",							4,10,1900,/*,8*/4,10,1900/*,8*/	},
	{"WIN98",	"RC5",		"Windows 98 RC5",							4,10,1998,		4,10,1998	},
	{"WIN98",	"FINAL",		"Windows 98",								4,10,1998,/*,6*/4,10,1998/*,6*/	},
	{"WIN98",	"SP1B1",		"Windows 98 SP1 Beta 1",				4,10,2088,		4,10,2088	},
	{"WIN98",	"OSR1B1",	"Windows 98 OSR1 Beta 1",				4,10,2106,		4,10,2106 	},
	{"WIN98",	"OSR1B1",	"Windows 98 OSR1 Beta 1",				4,10,2120,		4,10,2120 	},
	{"WIN98",	"OSR1B1",	"Windows 98 OSR1 Beta 1",				4,10,2126,		4,10,2126 	},
	{"WIN98",	"OSR1B1",	"Windows 98 OSR1 Beta 1",				4,10,2131,		4,10,2131 	},
	{"WIN98",	"SP1B2",		"Windows 98 SP1 Beta 2",				4,10,2150,/*,0*/4,10,2150/*,0*/	},
	{"WIN98",	"SP1B2",		"Windows 98 SP1 Beta 2",				4,10,2150,/*,4*/4,10,2150/*,4*/	},
	{"WIN98",	"SP1",		"Windows 98 SP1 final Update",		4,10,2000,		4,10,2000 	},
	{"WIN98",	"OSR1B2",	"Windows 98 OSR1 Beta 2",				4,10,2174,		4,10,2174 	},
	{"WIN98",	"SERC1",		"Windows 98 SE RC1",						4,10,2183,		4,10,2183 	},
	{"WIN98",	"SERC2",		"Windows 98 SE RC2",						4,10,2185,		4,10,2185 	},
	{"WIN98",	"SE",			"Windows 98 SE",							4,10,2222,		4,10,2222/*,3*/	},
	{"WINME",	"MEBDR1",	"Windows ME Beta DR1",					4,90,2332,		4,90,2332 	},
	{"WINME",	"MEBDR2",	"Windows ME Beta DR2",					4,90,2348,		4,90,2348 	},
	{"WINME",	"MEBDR3",	"Windows ME Beta DR3",					4,90,2358,		4,90,2358 	},
	{"WINME",	"MEBDR4",	"Windows ME Beta DR4",					4,90,2363,		4,90,2363 	},
	{"WINME",	"MEEB",		"Windows ME early Beta",				4,90,2368,		4,90,2368 	},
	{"WINME",	"MEEB",		"Windows ME early Beta",				4,90,2374,		4,90,2374 	},
	{"WINME",	"MEB1",		"Windows ME Beta 1",						4,90,2380,		4,90,2380 	},
	{"WINME",	"MEB1",		"Windows ME Beta 1",						4,90,2394,		4,90,2394 	},
	{"WINME",	"MEB1",		"Windows ME Beta 1",						4,90,2399,		4,90,2399 	},
	{"WINME",	"MEB1",		"Windows ME Beta 1",						4,90,2404,		4,90,2404 	},
	{"WINME",	"MEB1",		"Windows ME Beta 1",						4,90,2410,		4,90,2410	},
	{"WINME",	"MEB1",		"Windows ME Beta 1",						4,90,2416,		4,90,2416	},
	{"WINME",	"MEB1",		"Windows ME Beta 1",						4,90,2419,/*,4*/4,90,2419/*,4*/	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2429,		4,90,2429 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2434,		4,90,2434 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2443,		4,90,2443 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2447,		4,90,2447 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2455,		4,90,2455 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2460,		4,90,2460 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2465,		4,90,2465 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2470,		4,90,2470 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2474,		4,90,2474 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2481,		4,90,2481 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2487,		4,90,2487 	},
	{"WINME",	"MEB2",		"Windows ME Beta 2",						4,90,2491,		4,90,2491 	},
	{"WINME",	"MEB3",		"Windows ME Beta 3",						4,90,2499,		4,90,2499 	},
	{"WINME",	"MEB3",		"Windows ME Beta 3",						4,90,2499,/*,3*/4,90,2499/*,3*/	},
	{"WINME",	"MEB3",		"Windows ME Beta 3",						4,90,2509,		4,90,2509 	},
	{"WINME",	"MEB3",		"Windows ME Beta 3",						4,90,2513,		4,90,2513 	},
	{"WINME",	"MEB3",		"Windows ME Beta 3",						4,90,2516,		4,90,2516 	},
	{"WINME",	"RC0",		"Windows ME RC0",							4,90,2525,		4,90,2525 	},
	{"WINME",	"RC1",		"Windows ME RC1",							4,90,2525,/*,6*/4,90,2525/*,6*/	},
	{"WINME",	"RC2",		"Windows ME RC2",							4,90,2535,		4,90,2535	},
	{"WINME",	"FINAL",		"Windows ME",								4,90,3000,/*,2*/4,90,3000/*,2*/	},
};

void Get_OS_Info(
	OSInfoStruct& os_info,
	unsigned OSVersionPlatformId,
	unsigned OSVersionNumberMajor,
	unsigned OSVersionNumberMinor,
	unsigned OSVersionBuildNumber)
{
	unsigned build_major=(OSVersionBuildNumber&0xff000000)>>24;
	unsigned build_minor=(OSVersionBuildNumber&0xff0000)>>16;
	unsigned build_sub=(OSVersionBuildNumber&0xffff);

	switch (OSVersionPlatformId) {
	default:
		memset(&os_info,0,sizeof(os_info));
		os_info.Code="UNKNOWN";
		os_info.SubCode="UNKNOWN";
		os_info.VersionString="UNKNOWN";
		break;
	case VER_PLATFORM_WIN32_WINDOWS:
		{
			for(int i=0;i<sizeof(Windows9xVersionTable)/sizeof(os_info);++i) {
				if (
					Windows9xVersionTable[i].VersionMajor==OSVersionNumberMajor &&
					Windows9xVersionTable[i].VersionMinor==OSVersionNumberMinor &&
					Windows9xVersionTable[i].BuildMajor==build_major &&
					Windows9xVersionTable[i].BuildMinor==build_minor &&
					Windows9xVersionTable[i].BuildSub==build_sub) {
					os_info=Windows9xVersionTable[i];
					return;
				}
			}

			os_info.BuildMajor=build_major;
			os_info.BuildMinor=build_minor;
			os_info.BuildSub=build_sub;
			if (OSVersionNumberMajor==4) {
//				os_info.SubCode.Format("%d",build_sub);
				os_info.SubCode="UNKNOWN";
				if (OSVersionNumberMinor==0) {
					os_info.Code="WIN95";
					return;
				}
				if (OSVersionNumberMinor==10) {
					os_info.Code="WIN98";
					return;
				}
				if (OSVersionNumberMinor==90) {
					os_info.Code="WINME";
					return;
				}
				os_info.Code="WIN9X";
				return;
			}
		}
		break;
	case VER_PLATFORM_WIN32_NT:
//		os_info.SubCode.Format("%d",build_sub);
		os_info.SubCode="UNKNOWN";
		if (OSVersionNumberMajor==4) {
			os_info.Code="WINNT";
			return;
		}
		if (OSVersionNumberMajor==5) {
			if (OSVersionNumberMinor==0) {
				os_info.Code="WIN2K";
				return;
			}
			if (OSVersionNumberMinor==1) {
				os_info.Code="WINXP";
				return;
			}
			os_info.Code="WINXX";
			return;
		}
	}
}