using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using CommonHelpers; using Microsoft.Win32; namespace SteamController.Helpers { internal static class SteamConfiguration { public const String SteamKey = @"Software\Valve\Steam"; public const String RunningAppIDValue = @"RunningAppID"; public const String SteamExeValue = @"SteamExe"; public const String SteamPathValue = @"SteamPath"; public const String BigPictureInForegroundValue = @"BigPictureInForeground"; public const String ActiveProcessKey = @"Software\Valve\Steam\ActiveProcess"; public const String PIDValue = @"pid"; public const String RelativeConfigPath = @"config/config.vdf"; private static readonly Regex ControllerBlacklistRegex = new Regex("^(\\s*\"controller_blacklist\"\\s*\")([^\"]*)(\"\\s*)$"); public static bool IsRunning { get { using (var process = SteamProcess) { return process is not null; } } } public static bool? IsBigPictureMode { get { var value = GetValue(SteamKey, BigPictureInForegroundValue); return value.HasValue ? value != 0 : null; } } public static bool? IsRunningGame { get { var value = GetValue(SteamKey, RunningAppIDValue); return value.HasValue ? value != 0 : null; } } public static bool IsGamePadUI { get { var steamWindow = FindWindow("SDL_app", "SP"); if (steamWindow == IntPtr.Zero) return false; return GetForegroundWindow() == steamWindow; } } public static bool IsPossibleGamePadUI { get { IntPtr hWnd = GetForegroundWindow(); if (hWnd == IntPtr.Zero) return false; StringBuilder className = new StringBuilder(256); if (GetClassName(hWnd, className, className.Capacity) == 0) return false; if (className.ToString() != "SDL_app") return false; return ForegroundProcess.Find(hWnd)?.ProcessName == "steamwebhelper"; } } public static String? SteamExe { get { return GetValue2(SteamKey, SteamExeValue); } } public static String? SteamPath { get { return GetValue2(SteamKey, SteamPathValue); } } private static Process? SteamProcess { get { var value = GetValue(ActiveProcessKey, PIDValue); if (value is null) return null; try { var process = Process.GetProcessById(value.Value); if (!process.ProcessName.Equals("Steam", StringComparison.CurrentCultureIgnoreCase)) return null; if (process.HasExited) return null; return process; } catch { return null; } } } public static String? GetConfigPath(String configPath) { var path = SteamPath; if (path is null) return null; return Path.Join(SteamPath, configPath); } public static bool ShutdownSteam() { var steamExe = SteamExe; if (steamExe is null) return false; var process = Process.Start(new ProcessStartInfo() { FileName = steamExe, ArgumentList = { "-shutdown" }, WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, CreateNoWindow = true }); return process is not null; } public static bool KillSteam() { var process = SteamProcess; if (process is null) return true; try { using (process) { process.Kill(); } return true; } catch (System.ComponentModel.Win32Exception) { return false; } } public static bool WaitForSteamClose(int timeout) { var waitTill = DateTimeOffset.Now.AddMilliseconds(timeout); while (DateTimeOffset.Now < waitTill) { if (!IsRunning) return true; Application.DoEvents(); Thread.Sleep(50); } return false; } public static HashSet? GetControllerBlacklist() { try { var configPath = GetConfigPath(RelativeConfigPath); if (configPath is null) return null; foreach (var line in File.ReadLines(configPath).Reverse()) { var match = ControllerBlacklistRegex.Match(line); if (!match.Success) continue; // matches `"controller_blacklist" ""` var value = match.Groups[2].Captures[0].Value; return value.Split(',', StringSplitOptions.RemoveEmptyEntries).ToHashSet(); } return new HashSet(); } catch (DirectoryNotFoundException) { // Steam was installed, but got removed return null; } catch (IOException e) { Log.TraceException("STEAM", "Config", e); return null; } } public static bool? IsControllerBlacklisted(ushort vendorId, ushort productId) { var controllers = GetControllerBlacklist(); if (controllers is null) return null; var id = String.Format("{0:x}/{1:x}", vendorId, productId); return controllers.Contains(id); } public static bool BackupSteamConfig(String path) { var configPath = GetConfigPath(path); if (configPath is null) return true; try { var suffix = DateTime.Now.ToString("yyyyMMddHHmmss"); File.Copy(configPath, String.Format("{0}.{1}.bak", configPath, suffix)); return true; } catch (DirectoryNotFoundException) { // Steam was installed, but got removed return false; } catch (IOException e) { Log.TraceException("STEAM", "Config", e); return false; } } public static bool BackupSteamConfig() { return BackupSteamConfig(RelativeConfigPath); } public static bool UpdateControllerBlacklist(ushort vendorId, ushort productId, bool add) { if (IsRunning) return false; var configPath = GetConfigPath(RelativeConfigPath); if (configPath is null) return false; try { var lines = File.ReadLines(configPath).ToList(); var id = String.Format("{0:x}/{1:x}", vendorId, productId); for (int i = 0; i < lines.Count; i++) { if (lines[i] == "}") { if (add) { // append controller_blacklist lines.Insert(i, String.Format("\t\"controller_blacklist\"\t\t\"{0}\"", id)); break; } } var match = ControllerBlacklistRegex.Match(lines[i]); if (!match.Success) continue; var value = match.Groups[2].Captures[0].Value; var controllers = value.Split(',', StringSplitOptions.RemoveEmptyEntries).ToHashSet(); if (add) controllers.Add(id); else controllers.Remove(id); lines[i] = String.Format("{0}{1}{2}", match.Groups[1].Captures[0].Value, String.Join(',', controllers), match.Groups[3].Captures[0].Value ); break; } File.WriteAllLines(configPath, lines); return true; } catch (DirectoryNotFoundException) { // Steam was installed, but got removed return false; } catch (IOException e) { Log.TraceException("STEAM", "Config", e); return false; } } public static bool? IsConfigFileOverwritten(String path, byte[] content) { try { var configPath = GetConfigPath(path); if (configPath is null) return null; byte[] diskContent = File.ReadAllBytes(configPath); return content.SequenceEqual(diskContent); } catch (DirectoryNotFoundException) { // Steam was installed, but got removed return null; } catch (IOException e) { Log.TraceException("STEAM", "Config", e); return null; } } public static bool? ResetConfigFile(String path) { try { var configPath = GetConfigPath(path); if (configPath is null) return null; File.Copy(configPath + ".orig", configPath, true); return true; } catch (DirectoryNotFoundException) { // Steam was installed, but got removed return null; } catch (UnauthorizedAccessException) { return false; } catch (System.Security.SecurityException) { return false; } catch (IOException e) { Log.TraceException("STEAM", "Config", e); return null; } } public static bool? OverwriteConfigFile(String path, byte[] content, bool backup) { try { var configPath = GetConfigPath(path); if (configPath is null) return null; try { byte[] diskContent = File.ReadAllBytes(configPath); if (content.Equals(diskContent)) return false; } catch (IOException) { } if (backup) File.Copy(configPath, configPath + ".orig", true); File.WriteAllBytes(configPath, content); return true; } catch (UnauthorizedAccessException) { return false; } catch (System.Security.SecurityException) { return false; } catch (DirectoryNotFoundException) { // Steam was installed, but got removed return false; } catch (IOException e) { Log.TraceException("STEAM", "Config", e); return null; } } private static T? GetValue(string key, string value) where T : struct { try { using (var registryKey = Registry.CurrentUser.OpenSubKey(key)) { return registryKey?.GetValue(value) as T?; } } catch (Exception) { return null; } } private static T? GetValue2(string key, string value) where T : class { try { using (var registryKey = Registry.CurrentUser.OpenSubKey(key)) { return registryKey?.GetValue(value) as T; } } catch (Exception) { return null; } } [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); } }