Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improved system information collection #897

Merged
merged 3 commits into from Feb 26, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
307 changes: 141 additions & 166 deletions Source/ORTS.Common/SystemInfo.cs
@@ -1,232 +1,178 @@
// COPYRIGHT 2015 by the Open Rails project.
//
// COPYRIGHT 2009 - 2023 by the Open Rails project.
//
// This file is part of Open Rails.
//
//
// Open Rails 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.
//
//
// Open Rails 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 Open Rails. If not, see <http://www.gnu.org/licenses/>.

using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using Microsoft.Xna.Framework.Graphics;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using SharpDX.DXGI;

namespace ORTS.Common
{
public static class SystemInfo
{
public static void WriteSystemDetails(TextWriter output)
static readonly Regex NameAndVersionRegex = new Regex("^(.*?) +([0-9.]+)$");
static readonly NativeStructs.MemoryStatusExtended MemoryStatusExtended = new NativeStructs.MemoryStatusExtended()
{
output.WriteLine("Date/time = {0} ({1:u})", DateTime.Now, DateTime.UtcNow);
WriteEnvironment(output);
WriteAvailableRuntimes(output);
output.WriteLine("Runtime = {0} ({1}bit)", Environment.Version, IntPtr.Size * 8);
WriteGraphicsAdapter(output);
}
Size = 64
};

static void WriteEnvironment(TextWriter output)
static SystemInfo()
{
var buffer = new NativeStructs.MemoryStatusExtended { Size = 64 };
NativeMethods.GlobalMemoryStatusEx(buffer);
try
Application = new Platform
{
foreach (ManagementObject bios in new ManagementClass("Win32_BIOS").GetInstances())
{
output.WriteLine("BIOS = {0} ({1})", (string)bios["Description"], (string)bios["Manufacturer"]);
}
}
catch (Exception error)
{
Trace.WriteLine(error);
}
try
{
foreach (ManagementObject processor in new ManagementClass("Win32_Processor").GetInstances())
{
output.Write("Processor = {0} ({2} threads, {1} cores, {3:F1} GHz)", (string)processor["Name"], (uint)processor["NumberOfCores"], (uint)processor["NumberOfLogicalProcessors"], (float)(uint)processor["MaxClockSpeed"] / 1000);
foreach (ManagementObject cpuCache in processor.GetRelated("Win32_CacheMemory"))
{
output.Write(" ({0} {1:F0} KB)", (string)cpuCache["Purpose"], (float)(uint)cpuCache["InstalledSize"]);
}
output.WriteLine();
}
}
catch (Exception error)
{
Trace.WriteLine(error);
}
output.WriteLine("Memory = {0:F1} GB", (float)buffer.TotalPhysical / 1024 / 1024 / 1024);
try
{
foreach (ManagementObject display in new ManagementClass("Win32_VideoController").GetInstances())
{
// ? used as display["AdapterRAM"] may be null on a virtual machine (e.g. VMWare)
output.WriteLine("Video = {0} ({1:F1} GB RAM){2}", (string)display["Description"], (float?)(uint?)display["AdapterRAM"] / 1024 / 1024 / 1024, GetPnPDeviceDrivers(display));
}
}
catch (Exception error)
{
Trace.WriteLine(error);
}
try
{
foreach (var screen in Screen.AllScreens)
{
output.WriteLine("Display = {0} ({3} x {4}, {5}-bit{6}, {1} x {2})", screen.DeviceName, screen.Bounds.X, screen.Bounds.Y, screen.Bounds.Width, screen.Bounds.Height, screen.BitsPerPixel, screen.Primary ? ", primary" : "");
}
}
catch (Exception error)
Name = System.Windows.Forms.Application.ProductName,
Version = VersionInfo.VersionOrBuild,
Architecture = RuntimeInformation.ProcessArchitecture.ToString(),
};

var runtime = NameAndVersionRegex.Match(RuntimeInformation.FrameworkDescription.Trim());
Runtime = new Platform
{
Trace.WriteLine(error);
}
Name = runtime.Groups[1].Value,
Version = runtime.Groups[2].Value,
};

try
{
foreach (ManagementObject sound in new ManagementClass("Win32_SoundDevice").GetInstances())
// Almost nothing will correctly identify Windows 11 at this point, so we have to use WMI.
var operatingSystem = new ManagementClass("Win32_OperatingSystem").GetInstances().Cast<ManagementObject>().First();
OperatingSystem = new Platform
{
output.WriteLine("Sound = {0}{1}", (string)sound["Description"], GetPnPDeviceDrivers(sound));
}
Name = (string)operatingSystem["Caption"],
Version = (string)operatingSystem["Version"],
Architecture = RuntimeInformation.OSArchitecture.ToString(),
Language = CultureInfo.CurrentUICulture.IetfLanguageTag,
Languages = (string[])operatingSystem["MUILanguages"],
};
}
catch (Exception error)
catch (ManagementException error)
{
Trace.WriteLine(error);
}

NativeMethods.GlobalMemoryStatusEx(MemoryStatusExtended);
InstalledMemoryMB = (int)(MemoryStatusExtended.TotalPhysical / 1024 / 1024);

try
{
foreach (ManagementObject disk in new ManagementClass("Win32_LogicalDisk").GetInstances())
CPUs = new ManagementClass("Win32_Processor").GetInstances().Cast<ManagementObject>().Select(processor => new CPU
{
output.Write("Disk = {0} ({1}, {2}", (string)disk["Name"], (string)disk["Description"], (string)disk["FileSystem"]);
if (disk["Size"] != null && disk["FreeSpace"] != null)
output.WriteLine(", {0:F1} GB, {1:F1} GB free)", (float)(ulong)disk["Size"] / 1024 / 1024 / 1024, (float)(ulong)disk["FreeSpace"] / 1024 / 1024 / 1024);
else
output.WriteLine(")");
}
Name = (string)processor["Name"],
Manufacturer = (string)processor["Manufacturer"],
ThreadCount = (uint)processor["ThreadCount"],
MaxClockMHz = (uint)processor["MaxClockSpeed"],
}).ToList();
}
catch (Exception error)
catch (ManagementException error)
{
Trace.WriteLine(error);
}

// The WMI data for AdapterRAM is unreliable, so we have to use DXGI to get the real numbers.
// Alas, DXGI doesn't give us the manufacturer name for the adapter, so we combine it with WMI.
var descriptions = new Factory1().Adapters.Select(adapter => adapter.Description).ToArray();
try
{
foreach (ManagementObject os in new ManagementClass("Win32_OperatingSystem").GetInstances())
GPUs = new ManagementClass("Win32_VideoController").GetInstances().Cast<ManagementObject>().Select(adapter => new GPU
{
output.WriteLine("OS = {0} {1} ({2})", (string)os["Caption"], (string)os["OSArchitecture"], (string)os["Version"]);
}
Name = (string)adapter["Name"],
Manufacturer = (string)adapter["AdapterCompatibility"],
MemoryMB = (uint)((long)descriptions.FirstOrDefault(desc => desc.Description == (string)adapter["Name"]).DedicatedVideoMemory / 1024 / 1024),
}).ToList();
}
catch (Exception error)
catch (ManagementException error)
{
Trace.WriteLine(error);
}
}

static string GetPnPDeviceDrivers(ManagementObject device)
{
var output = new StringBuilder();
foreach (ManagementObject pnpDevice in device.GetRelated("Win32_PnPEntity"))
{
foreach (ManagementObject dataFile in pnpDevice.GetRelated("CIM_DataFile"))
var featureLevels = new uint[] {
NativeMethods.D3D_FEATURE_LEVEL_12_2,
NativeMethods.D3D_FEATURE_LEVEL_12_1,
NativeMethods.D3D_FEATURE_LEVEL_12_0,
NativeMethods.D3D_FEATURE_LEVEL_11_1,
NativeMethods.D3D_FEATURE_LEVEL_11_0,
NativeMethods.D3D_FEATURE_LEVEL_10_1,
NativeMethods.D3D_FEATURE_LEVEL_10_0,
NativeMethods.D3D_FEATURE_LEVEL_9_3,
NativeMethods.D3D_FEATURE_LEVEL_9_2,
NativeMethods.D3D_FEATURE_LEVEL_9_1,
};
foreach (var featureLevel in featureLevels)
{
var levels = new uint[] { featureLevel };
try
{
output.AppendFormat(" ({0} {1})", (string)dataFile["FileName"], (string)dataFile["Version"]);
var rv = NativeMethods.D3D11CreateDevice(IntPtr.Zero, NativeMethods.D3D_DRIVER_TYPE_HARDWARE, IntPtr.Zero, 0, levels, levels.Length, NativeMethods.D3D11_SDK_VERSION, IntPtr.Zero, out uint level, IntPtr.Zero);
if (level == featureLevel) Direct3DFeatureLevels.Add(string.Format("{0}_{1}", level >> 12 & 0xF, level >> 8 & 0xF));
}
catch (EntryPointNotFoundException) { }
catch (DllNotFoundException) { }
}
return output.ToString();
}

static void WriteAvailableRuntimes(TextWriter output)
public static readonly Platform Application;
public static readonly Platform Runtime;
public static readonly Platform OperatingSystem;
public static readonly int InstalledMemoryMB;
public static readonly List<CPU> CPUs = new List<CPU>();
public static readonly List<GPU> GPUs = new List<GPU>();
public static readonly List<string> Direct3DFeatureLevels = new List<string>();

public static void WriteSystemDetails(TextWriter output)
{
output.Write("Runtimes =");
try
{
// This remote access is necessary to ensure we get the correct bitness view of the registry.
using (var frameworksKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "").OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP", false))
{
foreach (var versionKeyName in frameworksKey.GetSubKeyNames())
{
if (!versionKeyName.StartsWith("v"))
continue;

using (var versionKey = frameworksKey.OpenSubKey(versionKeyName))
{
var fullVersion = WriteInstalledRuntimes(output, versionKeyName, versionKey);
if (fullVersion != "")
continue;

foreach (var skuKeyName in versionKey.GetSubKeyNames())
{
using (var skuKey = versionKey.OpenSubKey(skuKeyName))
{
WriteInstalledRuntimes(output, versionKeyName + " " + skuKeyName, skuKey);
}
}
}
}
}
output.WriteLine();
}
catch (Exception error)
{
Trace.WriteLine(error);
}
output.WriteLine("Date/time = {0} ({1:u})",
DateTime.Now, DateTime.UtcNow);
output.WriteLine("Application = {0} {1} ({2})", Application.Name, Application.Version, Application.Architecture);
output.WriteLine("Runtime = {0} {1}", Runtime.Name, Runtime.Version);
output.WriteLine("System = {0} {1} ({2}; {3}; {4})", OperatingSystem.Name, OperatingSystem.Version, OperatingSystem.Architecture, OperatingSystem.Language, string.Join(",", OperatingSystem.Languages ?? new string[0]));
output.WriteLine("Memory = {0:N0} MB", InstalledMemoryMB);
foreach (var cpu in CPUs) output.WriteLine("CPU = {0} ({1}; {2} threads; {3:N0} MHz)", cpu.Name, cpu.Manufacturer, cpu.ThreadCount, cpu.MaxClockMHz);
foreach (var gpu in GPUs) output.WriteLine("GPU = {0} ({1}; {2:N0} MB)", gpu.Name, gpu.Manufacturer, gpu.MemoryMB);
output.WriteLine("Direct3D = {0}", string.Join(",", Direct3DFeatureLevels));
}

static string WriteInstalledRuntimes(TextWriter output, string versionKeyName, RegistryKey versionKey)
public struct Platform
{
var installed = SafeReadKey(versionKey, "Install", -1);
var fullVersion = SafeReadKey(versionKey, "Version", "");
var servicePack = SafeReadKey(versionKey, "SP", -1);

if (installed == 1 && servicePack != -1)
{
output.Write(" {0} SP{2} ", versionKeyName.Substring(1), fullVersion, servicePack);
}
else if (installed == 1)
{
output.Write(" {0} ", versionKeyName.Substring(1), fullVersion);
}
return fullVersion;
public string Name;
public string Version;
public string Architecture;
public string Language;
public string[] Languages;
}

static void WriteGraphicsAdapter(TextWriter output)
public struct CPU
{
try {
foreach (var adapter in GraphicsAdapter.Adapters)
{
try
{
output.WriteLine("{0} = {1}", adapter.DeviceName, adapter.Description);
}
catch (Exception) { }
}
}
catch (Exception error)
{
output.WriteLine(error);
}
public string Name;
public string Manufacturer;
public uint ThreadCount;
public uint MaxClockMHz;
}

static T SafeReadKey<T>(RegistryKey key, string name, T defaultValue)
public struct GPU
{
try
{
return (T)key.GetValue(name, defaultValue);
}
catch
{
return defaultValue;
}
public string Name;
public string Manufacturer;
public uint MemoryMB;
}

static class NativeStructs
Expand All @@ -250,6 +196,35 @@ static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GlobalMemoryStatusEx([In, Out] NativeStructs.MemoryStatusExtended buffer);

public const uint D3D11_SDK_VERSION = 7;

public const uint D3D_DRIVER_TYPE_HARDWARE = 1;

public const uint D3D_FEATURE_LEVEL_9_1 = 0x9100;
public const uint D3D_FEATURE_LEVEL_9_2 = 0x9200;
public const uint D3D_FEATURE_LEVEL_9_3 = 0x9300;
public const uint D3D_FEATURE_LEVEL_10_0 = 0xA000;
public const uint D3D_FEATURE_LEVEL_10_1 = 0xA100;
public const uint D3D_FEATURE_LEVEL_11_0 = 0xB000;
public const uint D3D_FEATURE_LEVEL_11_1 = 0xB100;
public const uint D3D_FEATURE_LEVEL_12_0 = 0xC000;
public const uint D3D_FEATURE_LEVEL_12_1 = 0xC100;
public const uint D3D_FEATURE_LEVEL_12_2 = 0xC200;

[DllImport("d3d11.dll", ExactSpelling = true)]
public static extern uint D3D11CreateDevice(
IntPtr adapter,
uint driverType,
IntPtr software,
uint flags,
[In] uint[] featureLevels,
int featureLevelCount,
uint sdkVersion,
IntPtr device,
out uint featureLevel,
IntPtr immediateContext
);
}
}
}