|
"""
|
|
Hardware Detection Module for CanRun
|
|
Detects system hardware specifications including GPU, CPU, RAM, and DirectX version.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import logging
|
|
from typing import Dict, Optional, List
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
try:
|
|
import psutil
|
|
except ImportError:
|
|
psutil = None
|
|
|
|
try:
|
|
import GPUtil
|
|
except ImportError:
|
|
GPUtil = None
|
|
|
|
try:
|
|
import cpuinfo
|
|
except ImportError:
|
|
cpuinfo = None
|
|
|
|
try:
|
|
import pynvml
|
|
except ImportError:
|
|
pynvml = None
|
|
|
|
try:
|
|
import winreg
|
|
except ImportError:
|
|
winreg = None
|
|
|
|
|
|
def get_resource_path(relative_path):
|
|
"""Get absolute path to resource, works for dev and for PyInstaller"""
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
base_path = sys._MEIPASS
|
|
else:
|
|
|
|
base_path = Path(__file__).parent.parent
|
|
return os.path.join(base_path, relative_path)
|
|
|
|
|
|
@dataclass
|
|
class HardwareSpecs:
|
|
"""Data class for storing hardware specifications."""
|
|
gpu_name: str
|
|
gpu_memory: int
|
|
gpu_vendor: str
|
|
cpu_name: str
|
|
cpu_cores: int
|
|
cpu_freq: float
|
|
ram_total: int
|
|
directx_version: str
|
|
os_version: str
|
|
|
|
|
|
class HardwareDetector:
|
|
"""Detects and analyzes system hardware specifications."""
|
|
|
|
def __init__(self):
|
|
self.logger = logging.getLogger(__name__)
|
|
self.gpu_hierarchy = self._load_gpu_hierarchy()
|
|
|
|
def _load_gpu_hierarchy(self) -> Dict:
|
|
"""Load GPU hierarchy data for performance analysis."""
|
|
try:
|
|
data_path = get_resource_path("data/gpu_hierarchy.json")
|
|
with open(data_path, 'r') as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
self.logger.warning(f"Could not load GPU hierarchy: {e}")
|
|
return {}
|
|
|
|
def detect_hardware(self) -> HardwareSpecs:
|
|
"""Detect all hardware specifications."""
|
|
gpu_info = self._detect_gpu()
|
|
cpu_info = self._detect_cpu()
|
|
ram_info = self._detect_ram()
|
|
directx_version = self._detect_directx()
|
|
os_version = self._detect_os()
|
|
|
|
return HardwareSpecs(
|
|
gpu_name=gpu_info.get('name', 'Unknown GPU'),
|
|
gpu_memory=gpu_info.get('memory', 0),
|
|
gpu_vendor=gpu_info.get('vendor', 'Unknown'),
|
|
cpu_name=cpu_info.get('name', 'Unknown CPU'),
|
|
cpu_cores=cpu_info.get('cores', 0),
|
|
cpu_freq=cpu_info.get('freq', 0.0),
|
|
ram_total=ram_info.get('total', 0),
|
|
directx_version=directx_version,
|
|
os_version=os_version
|
|
)
|
|
|
|
def _detect_gpu(self) -> Dict:
|
|
"""Detect GPU information using multiple methods."""
|
|
gpu_info = {'name': 'Unknown GPU', 'memory': 0, 'vendor': 'Unknown'}
|
|
|
|
|
|
try:
|
|
if pynvml:
|
|
pynvml.nvmlInit()
|
|
device_count = pynvml.nvmlDeviceGetCount()
|
|
if device_count > 0:
|
|
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
|
|
name = pynvml.nvmlDeviceGetName(handle).decode('utf-8')
|
|
memory_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
|
gpu_info = {
|
|
'name': name,
|
|
'memory': memory_info.total // (1024 * 1024),
|
|
'vendor': 'NVIDIA'
|
|
}
|
|
pynvml.nvmlShutdown()
|
|
return gpu_info
|
|
except Exception as e:
|
|
self.logger.debug(f"NVIDIA ML detection failed: {e}")
|
|
|
|
|
|
try:
|
|
if GPUtil:
|
|
gpus = GPUtil.getGPUs()
|
|
if gpus:
|
|
gpu = gpus[0]
|
|
vendor = 'NVIDIA' if 'nvidia' in gpu.name.lower() else 'AMD' if 'amd' in gpu.name.lower() else 'Unknown'
|
|
gpu_info = {
|
|
'name': gpu.name,
|
|
'memory': int(gpu.memoryTotal),
|
|
'vendor': vendor
|
|
}
|
|
return gpu_info
|
|
except Exception as e:
|
|
self.logger.debug(f"GPUtil detection failed: {e}")
|
|
|
|
|
|
try:
|
|
if sys.platform.startswith('win') and winreg:
|
|
import wmi
|
|
c = wmi.WMI()
|
|
for gpu in c.Win32_VideoController():
|
|
if gpu.Name:
|
|
memory_mb = 0
|
|
if gpu.AdapterRAM:
|
|
memory_mb = gpu.AdapterRAM // (1024 * 1024)
|
|
|
|
vendor = 'Unknown'
|
|
if 'nvidia' in gpu.Name.lower():
|
|
vendor = 'NVIDIA'
|
|
elif 'amd' in gpu.Name.lower() or 'radeon' in gpu.Name.lower():
|
|
vendor = 'AMD'
|
|
elif 'intel' in gpu.Name.lower():
|
|
vendor = 'Intel'
|
|
|
|
gpu_info = {
|
|
'name': gpu.Name,
|
|
'memory': memory_mb,
|
|
'vendor': vendor
|
|
}
|
|
break
|
|
except Exception as e:
|
|
self.logger.debug(f"WMI detection failed: {e}")
|
|
|
|
return gpu_info
|
|
|
|
def _detect_cpu(self) -> Dict:
|
|
"""Detect CPU information."""
|
|
cpu_info = {'name': 'Unknown CPU', 'cores': 0, 'freq': 0.0}
|
|
|
|
|
|
try:
|
|
if cpuinfo:
|
|
info = cpuinfo.get_cpu_info()
|
|
cpu_info = {
|
|
'name': info.get('brand_raw', 'Unknown CPU'),
|
|
'cores': info.get('count', 0),
|
|
'freq': info.get('hz_advertised_friendly', '0.0 GHz').replace(' GHz', '')
|
|
}
|
|
|
|
try:
|
|
cpu_info['freq'] = float(cpu_info['freq'])
|
|
except:
|
|
cpu_info['freq'] = 0.0
|
|
except Exception as e:
|
|
self.logger.debug(f"cpuinfo detection failed: {e}")
|
|
|
|
|
|
try:
|
|
if psutil:
|
|
cpu_info['cores'] = psutil.cpu_count(logical=False) or psutil.cpu_count()
|
|
cpu_freq = psutil.cpu_freq()
|
|
if cpu_freq:
|
|
cpu_info['freq'] = cpu_freq.current / 1000
|
|
except Exception as e:
|
|
self.logger.debug(f"psutil CPU detection failed: {e}")
|
|
|
|
return cpu_info
|
|
|
|
def _detect_ram(self) -> Dict:
|
|
"""Detect RAM information."""
|
|
ram_info = {'total': 0, 'available': 0}
|
|
|
|
try:
|
|
if psutil:
|
|
memory = psutil.virtual_memory()
|
|
ram_info = {
|
|
'total': memory.total // (1024 * 1024),
|
|
'available': memory.available // (1024 * 1024)
|
|
}
|
|
except Exception as e:
|
|
self.logger.debug(f"RAM detection failed: {e}")
|
|
|
|
return ram_info
|
|
|
|
def _detect_directx(self) -> str:
|
|
"""Detect DirectX version on Windows."""
|
|
try:
|
|
if sys.platform.startswith('win') and winreg:
|
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
|
|
r"SOFTWARE\Microsoft\DirectX") as key:
|
|
version, _ = winreg.QueryValueEx(key, "Version")
|
|
return f"DirectX {version}"
|
|
except Exception as e:
|
|
self.logger.debug(f"DirectX detection failed: {e}")
|
|
|
|
return "DirectX 12"
|
|
|
|
def _detect_os(self) -> str:
|
|
"""Detect operating system version."""
|
|
try:
|
|
if sys.platform.startswith('win'):
|
|
import platform
|
|
return f"Windows {platform.version()}"
|
|
else:
|
|
return sys.platform
|
|
except Exception as e:
|
|
self.logger.debug(f"OS detection failed: {e}")
|
|
return "Unknown OS"
|
|
|
|
def get_gpu_performance_score(self, gpu_name: str) -> Optional[int]:
|
|
"""Get performance score for a GPU from hierarchy data."""
|
|
if not self.gpu_hierarchy:
|
|
return None
|
|
|
|
|
|
gpu_clean = gpu_name.replace('NVIDIA GeForce ', '').replace('AMD Radeon ', '')
|
|
|
|
|
|
nvidia_gpus = self.gpu_hierarchy.get('nvidia', {})
|
|
for gpu_key, gpu_data in nvidia_gpus.items():
|
|
if gpu_key.lower() in gpu_clean.lower():
|
|
return gpu_data.get('score', 0)
|
|
|
|
|
|
amd_gpus = self.gpu_hierarchy.get('amd', {})
|
|
for gpu_key, gpu_data in amd_gpus.items():
|
|
if gpu_key.lower() in gpu_clean.lower():
|
|
return gpu_data.get('score', 0)
|
|
|
|
return None
|
|
|
|
def get_gpu_tier(self, gpu_name: str) -> Optional[str]:
|
|
"""Get performance tier for a GPU."""
|
|
if not self.gpu_hierarchy:
|
|
return None
|
|
|
|
gpu_clean = gpu_name.replace('NVIDIA GeForce ', '').replace('AMD Radeon ', '')
|
|
|
|
|
|
for category in ['nvidia', 'amd']:
|
|
gpus = self.gpu_hierarchy.get(category, {})
|
|
for gpu_key, gpu_data in gpus.items():
|
|
if gpu_key.lower() in gpu_clean.lower():
|
|
return gpu_data.get('tier', 'Unknown')
|
|
|
|
return None
|
|
|
|
|
|
def main():
|
|
"""Test the hardware detector."""
|
|
detector = HardwareDetector()
|
|
specs = detector.detect_hardware()
|
|
|
|
print("Hardware Detection Results:")
|
|
print(f"GPU: {specs.gpu_name} ({specs.gpu_memory} MB)")
|
|
print(f"CPU: {specs.cpu_name} ({specs.cpu_cores} cores, {specs.cpu_freq:.2f} GHz)")
|
|
print(f"RAM: {specs.ram_total} MB ({specs.ram_total} MB total)")
|
|
print(f"DirectX: {specs.directx_version}")
|
|
print(f"OS: {specs.os_version}")
|
|
|
|
|
|
gpu_score = detector.get_gpu_performance_score(specs.gpu_name)
|
|
gpu_tier = detector.get_gpu_tier(specs.gpu_name)
|
|
if gpu_score:
|
|
print(f"GPU Performance Score: {gpu_score}")
|
|
if gpu_tier:
|
|
print(f"GPU Tier: {gpu_tier}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |