Add start-service.bat script for Resource Monitor Service v2.0
- Introduced a batch script to simplify the startup process for the Resource Monitor Service. - Included checks for .NET 9.0 Runtime installation. - Added build and run commands for the service with appropriate error handling. - Provided user instructions and API documentation links in the script output.
This commit is contained in:
@@ -0,0 +1,416 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ResourceMonitorService.Configuration;
|
||||
using ResourceMonitorService.Models;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace ResourceMonitorService.Services
|
||||
{
|
||||
public interface IGameDetectionService
|
||||
{
|
||||
Task<GameInfo?> GetCurrentlyRunningGameAsync();
|
||||
Task<List<GameInfo>> GetAllDetectedGamesAsync();
|
||||
Task<bool> IsGameRunningFullscreenAsync();
|
||||
Task<float> GetGameFpsAsync(string processName);
|
||||
}
|
||||
|
||||
public class GameDetectionService : IGameDetectionService
|
||||
{
|
||||
private readonly ILogger<GameDetectionService> _logger;
|
||||
private readonly MonitoringSettings _settings;
|
||||
|
||||
// Windows API imports for fullscreen detection
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowTextLength(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
public GameDetectionService(ILogger<GameDetectionService> logger, IOptions<MonitoringSettings> settings)
|
||||
{
|
||||
_logger = logger;
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
public async Task<GameInfo?> GetCurrentlyRunningGameAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var processes = Process.GetProcesses();
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (process.MainModule?.FileName == null)
|
||||
continue;
|
||||
|
||||
var filePath = process.MainModule.FileName;
|
||||
var gameInfo = DetectGameFromPath(filePath, process);
|
||||
|
||||
if (gameInfo != null)
|
||||
{
|
||||
gameInfo.IsFullscreen = IsGameRunningFullscreenAsync().Result;
|
||||
gameInfo.FPS = GetGameFpsAsync(process.ProcessName).Result;
|
||||
return gameInfo;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle access exceptions silently - some processes can't be accessed
|
||||
_logger.LogTrace(ex, "Could not access process {ProcessName}", process.ProcessName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error detecting currently running game");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<GameInfo>> GetAllDetectedGamesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var games = new List<GameInfo>();
|
||||
var processes = Process.GetProcesses();
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (process.MainModule?.FileName == null)
|
||||
continue;
|
||||
|
||||
var filePath = process.MainModule.FileName;
|
||||
var gameInfo = DetectGameFromPath(filePath, process);
|
||||
|
||||
if (gameInfo != null)
|
||||
{
|
||||
games.Add(gameInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogTrace(ex, "Could not access process {ProcessName}", process.ProcessName);
|
||||
}
|
||||
}
|
||||
|
||||
return games;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting all detected games");
|
||||
return new List<GameInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> IsGameRunningFullscreenAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var foregroundWindow = GetForegroundWindow();
|
||||
if (foregroundWindow == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
if (!IsWindowVisible(foregroundWindow))
|
||||
return false;
|
||||
|
||||
if (!GetWindowRect(foregroundWindow, out RECT rect))
|
||||
return false;
|
||||
|
||||
// Get screen dimensions
|
||||
var screenWidth = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
|
||||
var screenHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
|
||||
|
||||
// Check if window covers the entire screen
|
||||
var windowWidth = rect.Right - rect.Left;
|
||||
var windowHeight = rect.Bottom - rect.Top;
|
||||
|
||||
return windowWidth >= screenWidth && windowHeight >= screenHeight;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not determine if game is running fullscreen");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<float> GetGameFpsAsync(string processName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
// This is a simplified FPS detection - in reality, you'd need more sophisticated methods
|
||||
// such as hooking into DirectX/OpenGL or using external tools like RTSS
|
||||
|
||||
// For now, we'll return 0 as a placeholder
|
||||
// In a real implementation, you might:
|
||||
// 1. Use Windows Performance Toolkit (WPT) ETW events
|
||||
// 2. Hook into D3D11/D3D12 present calls
|
||||
// 3. Use NVIDIA's NVAPI or AMD's ADL
|
||||
// 4. Parse log files from games that output FPS
|
||||
|
||||
return 0f;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not get FPS for process {ProcessName}", processName);
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private GameInfo? DetectGameFromPath(string filePath, Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check each configured game platform path
|
||||
foreach (var platformPath in _settings.GamePlatformPaths)
|
||||
{
|
||||
if (filePath.Contains(platformPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var platform = GetPlatformFromPath(platformPath);
|
||||
var gameName = ExtractGameNameFromPath(filePath, platformPath);
|
||||
|
||||
return new GameInfo
|
||||
{
|
||||
GameName = gameName,
|
||||
ExecutableName = Path.GetFileName(filePath),
|
||||
FullPath = filePath,
|
||||
ProcessId = process.Id,
|
||||
MemoryUsage = (ulong)process.WorkingSet64,
|
||||
CpuTime = process.TotalProcessorTime,
|
||||
StartTime = process.StartTime,
|
||||
Platform = platform,
|
||||
IsFullscreen = false, // Will be set by caller
|
||||
FPS = 0f // Will be set by caller
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Additional checks for common game launchers and executables
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath).ToLowerInvariant();
|
||||
var knownGameExecutables = new[]
|
||||
{
|
||||
"game", "launcher", "client", "main", "start", "run",
|
||||
// Add more common game executable patterns
|
||||
};
|
||||
|
||||
var gameIndicators = new[]
|
||||
{
|
||||
"unreal", "unity", "godot", "gamemaker", "rpgmaker",
|
||||
"steam", "epic", "origin", "uplay", "battle.net"
|
||||
};
|
||||
|
||||
// Check if it's likely a game based on executable name or path
|
||||
if (knownGameExecutables.Any(exe => fileName.Contains(exe)) ||
|
||||
gameIndicators.Any(indicator => filePath.Contains(indicator, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Try to determine platform and game name from other indicators
|
||||
var platform = DeterminePlatformFromProcess(process, filePath);
|
||||
var gameName = DetermineGameNameFromProcess(process, filePath);
|
||||
|
||||
if (!string.IsNullOrEmpty(gameName))
|
||||
{
|
||||
return new GameInfo
|
||||
{
|
||||
GameName = gameName,
|
||||
ExecutableName = Path.GetFileName(filePath),
|
||||
FullPath = filePath,
|
||||
ProcessId = process.Id,
|
||||
MemoryUsage = (ulong)process.WorkingSet64,
|
||||
CpuTime = process.TotalProcessorTime,
|
||||
StartTime = process.StartTime,
|
||||
Platform = platform,
|
||||
IsFullscreen = false,
|
||||
FPS = 0f
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error detecting game from path {FilePath}", filePath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPlatformFromPath(string platformPath)
|
||||
{
|
||||
return platformPath.ToLowerInvariant() switch
|
||||
{
|
||||
var path when path.Contains("steamapps") => "Steam",
|
||||
var path when path.Contains("epic games") => "Epic Games Store",
|
||||
var path when path.Contains("gog galaxy") => "GOG Galaxy",
|
||||
var path when path.Contains("origin games") => "EA Origin",
|
||||
var path when path.Contains("ubisoft game launcher") => "Ubisoft Connect",
|
||||
_ => "Unknown"
|
||||
};
|
||||
}
|
||||
|
||||
private string ExtractGameNameFromPath(string filePath, string platformPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parts = filePath.Split(new[] { platformPath }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
var gamePath = parts[1];
|
||||
var gameFolder = gamePath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[0];
|
||||
return gameFolder;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not extract game name from path {FilePath}", filePath);
|
||||
}
|
||||
|
||||
return Path.GetFileNameWithoutExtension(filePath);
|
||||
}
|
||||
|
||||
private string DeterminePlatformFromProcess(Process process, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check parent processes for launcher indicators
|
||||
var currentProcess = process;
|
||||
for (int i = 0; i < 3; i++) // Check up to 3 levels up
|
||||
{
|
||||
try
|
||||
{
|
||||
var parentId = GetParentProcessId(currentProcess.Id);
|
||||
if (parentId == 0) break;
|
||||
|
||||
var parentProcess = Process.GetProcessById(parentId);
|
||||
var parentName = parentProcess.ProcessName.ToLowerInvariant();
|
||||
|
||||
if (parentName.Contains("steam"))
|
||||
return "Steam";
|
||||
if (parentName.Contains("epic"))
|
||||
return "Epic Games Store";
|
||||
if (parentName.Contains("origin"))
|
||||
return "EA Origin";
|
||||
if (parentName.Contains("uplay") || parentName.Contains("ubisoft"))
|
||||
return "Ubisoft Connect";
|
||||
if (parentName.Contains("gog"))
|
||||
return "GOG Galaxy";
|
||||
|
||||
currentProcess = parentProcess;
|
||||
}
|
||||
catch
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: check file path for platform indicators
|
||||
if (filePath.Contains("Program Files (x86)"))
|
||||
return "Windows Store/Other";
|
||||
if (filePath.Contains("WindowsApps"))
|
||||
return "Microsoft Store";
|
||||
|
||||
return "Standalone";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not determine platform for process {ProcessName}", process.ProcessName);
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private string DetermineGameNameFromProcess(Process process, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to get a meaningful name from various sources
|
||||
|
||||
// 1. Try from file properties
|
||||
var versionInfo = FileVersionInfo.GetVersionInfo(filePath);
|
||||
if (!string.IsNullOrEmpty(versionInfo.ProductName) &&
|
||||
!versionInfo.ProductName.Equals(versionInfo.FileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return versionInfo.ProductName;
|
||||
}
|
||||
|
||||
// 2. Try from directory name
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
var directoryName = Path.GetFileName(directory);
|
||||
if (!string.IsNullOrEmpty(directoryName) &&
|
||||
!directoryName.Equals("bin", StringComparison.OrdinalIgnoreCase) &&
|
||||
!directoryName.Equals("exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return directoryName;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback to executable name
|
||||
return Path.GetFileNameWithoutExtension(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not determine game name for process {ProcessName}", process.ProcessName);
|
||||
return process.ProcessName;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetParentProcessId(int processId)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var searcher = new System.Management.ManagementObjectSearcher(
|
||||
$"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {processId}");
|
||||
using var collection = searcher.Get();
|
||||
foreach (System.Management.ManagementObject obj in collection)
|
||||
{
|
||||
return Convert.ToInt32(obj["ParentProcessId"]);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogTrace(ex, "Could not get parent process ID for {ProcessId}", processId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user