From c5c5ee1f3679b5fc883bcc1190d12f7a47f34b46 Mon Sep 17 00:00:00 2001 From: realdody <65219829+realdody@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:24:05 +0200 Subject: [PATCH] [logging] Fetch hardware info, add to video window title and log (#117) --- src/SharpEmu.CLI/Program.cs | 1 + src/SharpEmu.Libs/VideoOut/VideoOutExports.cs | 4 +- src/SharpEmu.Logging/HostSystemInfo.cs | 178 ++++++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/SharpEmu.Logging/HostSystemInfo.cs diff --git a/src/SharpEmu.CLI/Program.cs b/src/SharpEmu.CLI/Program.cs index caf710d..39b3221 100644 --- a/src/SharpEmu.CLI/Program.cs +++ b/src/SharpEmu.CLI/Program.cs @@ -95,6 +95,7 @@ internal static partial class Program SharpEmuLog.MinimumLevel = logLevel; Log.Info(BuildInfo.Banner); + Log.Info(HostSystemInfo.Summary); ebootPath = Path.GetFullPath(ebootPath); Console.Error.WriteLine($"[DEBUG] Full path: {ebootPath}"); diff --git a/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs b/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs index 3f96f1f..b77505a 100644 --- a/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs +++ b/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs @@ -3,6 +3,7 @@ using SharpEmu.HLE; using SharpEmu.Libs.Kernel; +using SharpEmu.Logging; using System.Buffers.Binary; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -133,9 +134,10 @@ public static class VideoOutExports var application = parts.Count == 0 ? "VideoOut" : string.Join(' ', parts); var versionSuffix = string.IsNullOrWhiteSpace(version) ? string.Empty : $" v{version.Trim()}"; var commitSuffix = string.IsNullOrWhiteSpace(emulatorCommitSha) ? string.Empty : $" \u00b7 {emulatorCommitSha.Trim()}"; + var hardwareSuffix = $" \u00b7 {HostSystemInfo.CpuName} \u00b7 {HostSystemInfo.GpuName}"; lock (_stateGate) { - _windowTitle = $"SharpEmu{commitSuffix} - {application}{versionSuffix}"; + _windowTitle = $"SharpEmu{commitSuffix} - {application}{versionSuffix}{hardwareSuffix}"; } } diff --git a/src/SharpEmu.Logging/HostSystemInfo.cs b/src/SharpEmu.Logging/HostSystemInfo.cs new file mode 100644 index 0000000..e1493a6 --- /dev/null +++ b/src/SharpEmu.Logging/HostSystemInfo.cs @@ -0,0 +1,178 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using Microsoft.Win32; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace SharpEmu.Logging; + +/// Provides best-effort information about the host system. +public static class HostSystemInfo +{ + private static readonly Lazy CpuNameValue = new(GetCpuName); + private static readonly Lazy GpuNameValue = new(GetPreferredGpuName); + private static readonly Lazy MemoryDescriptionValue = new(GetMemoryDescription); + + /// Host CPU name, or a safe fallback when it cannot be determined. + public static string CpuName => CpuNameValue.Value; + + /// Preferred physical GPU name, or a safe fallback when it cannot be determined. + public static string GpuName => GpuNameValue.Value; + + /// Returns a concise description of the host hardware for diagnostic logs. + public static string Summary => + $"Host hardware: CPU: {CpuName}; GPU: {GpuName}; RAM: {MemoryDescriptionValue.Value}."; + + private static string GetCpuName() + { + if (!OperatingSystem.IsWindows()) + { + return $"{Environment.ProcessorCount} logical processors"; + } + + try + { + using var key = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0"); + if (key?.GetValue("ProcessorNameString") is string name && !string.IsNullOrWhiteSpace(name)) + { + return Regex.Replace( + name.Trim(), + @"\s+\d+-Core Processor$", + string.Empty, + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + } + catch (Exception) + { + // Hardware information is diagnostic only. + } + + return $"{Environment.ProcessorCount} logical processors"; + } + + private static string GetPreferredGpuName() + { + if (!OperatingSystem.IsWindows()) + { + return "unknown"; + } + + try + { + var preferredName = "unknown"; + var preferredScore = int.MinValue; + for (uint index = 0; ; index++) + { + var device = new DisplayDevice + { + cb = Marshal.SizeOf(), + }; + + if (!EnumDisplayDevices(null, index, ref device, 0)) + { + break; + } + + var name = device.DeviceString?.Trim(); + var score = ScoreGpu(name); + if (score > preferredScore) + { + preferredName = name!; + preferredScore = score; + } + } + + return preferredScore > 0 ? preferredName : "unknown"; + } + catch (Exception) + { + // Hardware information is diagnostic only. + return "unknown"; + } + } + + private static int ScoreGpu(string? name) + { + if (string.IsNullOrWhiteSpace(name) || + name.Contains("virtual", StringComparison.OrdinalIgnoreCase) || + name.Contains("remote", StringComparison.OrdinalIgnoreCase) || + name.Contains("basic display", StringComparison.OrdinalIgnoreCase)) + { + return -1; + } + + if (name.Contains("nvidia", StringComparison.OrdinalIgnoreCase) || + name.Contains("geforce", StringComparison.OrdinalIgnoreCase) || + name.Contains("amd", StringComparison.OrdinalIgnoreCase) || + name.Contains("radeon", StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + + return 1; + } + + private static string GetMemoryDescription() + { + if (OperatingSystem.IsWindows()) + { + try + { + var status = new MemoryStatusEx + { + dwLength = (uint)Marshal.SizeOf(), + }; + if (GlobalMemoryStatusEx(ref status)) + { + var megabytes = status.ullTotalPhys / (1024 * 1024); + var gigabytes = status.ullTotalPhys / (1024d * 1024 * 1024); + return $"{megabytes:N0} MB ({gigabytes:N1} GB)"; + } + } + catch (Exception) + { + // Hardware information is diagnostic only. + } + } + + return "unknown"; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct DisplayDevice + { + public int cb; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string? DeviceName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string? DeviceString; + public int StateFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string? DeviceId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string? DeviceKey; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MemoryStatusEx + { + public uint dwLength; + public uint dwMemoryLoad; + public ulong ullTotalPhys; + public ulong ullAvailPhys; + public ulong ullTotalPageFile; + public ulong ullAvailPageFile; + public ulong ullTotalVirtual; + public ulong ullAvailVirtual; + public ulong ullAvailExtendedVirtual; + } + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EnumDisplayDevices(string? deviceName, uint deviceNum, ref DisplayDevice displayDevice, uint flags); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GlobalMemoryStatusEx(ref MemoryStatusEx buffer); +}