using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using CefSharp; using Microsoft.Win32; namespace VRCX { public partial class AppApi { private void OnProcessStateChanged(MonitoredProcess monitoredProcess) { if (!monitoredProcess.HasName("VRChat") && !monitoredProcess.HasName("vrserver")) return; CheckGameRunning(); } /// /// Checks if the VRChat game and SteamVR are currently running and updates the browser's JavaScript function $app.updateIsGameRunning with the results. /// public void CheckGameRunning() { var isGameRunning = false; var isSteamVRRunning = false; if (ProcessMonitor.Instance.IsProcessRunning("VRChat")) { isGameRunning = true; } if (ProcessMonitor.Instance.IsProcessRunning("vrserver")) { isSteamVRRunning = true; } var isHmdAfk = VRCXVR.Instance.IsHmdAfk; // TODO: fix this throwing an exception for being called before the browser is ready. somehow it gets past the checks if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame) MainForm.Instance.Browser.ExecuteScriptAsync("$app.updateIsGameRunning", isGameRunning, isSteamVRRunning, isHmdAfk); } /// /// Kills the VRChat process if it is currently running. /// /// The number of processes that were killed (0 or 1). public int QuitGame() { var processes = Process.GetProcessesByName("vrchat"); if (processes.Length == 1) processes[0].Kill(); return processes.Length; } /// /// Kills the install.exe process after exiting game. /// /// Whether the process is killed (true or false). public bool KillInstall() { bool isSuccess = false; var processes = Process.GetProcessesByName("install"); foreach (var p in processes) { // "E:\SteamLibrary\steamapps\common\VRChat\install.exe" var match = Regex.Match(GetProcessName(p.Id), "(.+?\\\\VRChat.*)(!?\\\\install.exe)"); if (match.Success) { // Sometimes install.exe is suspended ResumeProcess(p.Id); p.Kill(); isSuccess = true; break; } } return isSuccess; } [DllImport("ntdll.dll")] private static extern uint NtResumeProcess([In] IntPtr processHandle); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool QueryFullProcessImageName(IntPtr hProcess, uint dwFlags, [Out, MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpExeName, ref uint lpdwSize); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint processAccess, bool inheritHandle, int processId); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle([In] IntPtr handle); public static void ResumeProcess(int processId) { IntPtr hProc = IntPtr.Zero; try { // Gets the handle to the Process // 0x800 mean required to suspend or resume a process. hProc = OpenProcess(0x800, false, processId); if (hProc != IntPtr.Zero) NtResumeProcess(hProc); } finally { // close handle. if (hProc != IntPtr.Zero) CloseHandle(hProc); } } public static string GetProcessName(int pid) { IntPtr hProc = IntPtr.Zero; try { // 0x400 mean required to retrieve certain information about a process, such as its token, exit code, and priority class. // 0x10 mean required to read memory in a process using ReadProcessMemory. hProc = OpenProcess(0x400 | 0x10, false, pid); if (hProc != IntPtr.Zero) { int lengthSb = 4000; uint lpSize = 65535; var sb = new StringBuilder(lengthSb); string result = String.Empty; if (QueryFullProcessImageName(hProc, 0, sb, ref lpSize)) { result = sb.ToString(); } return result; } } finally { if (hProc != IntPtr.Zero) CloseHandle(hProc); } return String.Empty; } /// /// Starts the VRChat game process with the specified command-line arguments. /// /// The command-line arguments to pass to the VRChat game. public bool StartGame(string arguments) { // try stream first try { using var key = Registry.ClassesRoot.OpenSubKey(@"steam\shell\open\command"); // "C:\Program Files (x86)\Steam\steam.exe" -- "%1" var match = Regex.Match(key.GetValue(string.Empty) as string, "^\"(.+?)\\\\steam.exe\""); if (match.Success) { var path = match.Groups[1].Value; // var _arguments = Uri.EscapeDataString(arguments); Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = $"{path}\\steam.exe", UseShellExecute = false, Arguments = $"-applaunch 438100 {arguments}" }) ?.Close(); return true; } } catch { logger.Warn("Failed to start VRChat from Steam"); } // fallback try { using var key = Registry.ClassesRoot.OpenSubKey(@"VRChat\shell\open\command"); // "C:\Program Files (x86)\Steam\steamapps\common\VRChat\launch.exe" "%1" %* var match = Regex.Match(key.GetValue(string.Empty) as string, "(?!\")(.+?\\\\VRChat.*)(!?\\\\launch.exe\")"); if (match.Success) { var path = match.Groups[1].Value; return StartGameFromPath(path, arguments); } } catch { logger.Warn("Failed to start VRChat from registry"); } return false; } /// /// Starts the VRChat game process with the specified command-line arguments from the given path. /// /// The path to the VRChat game executable. /// The command-line arguments to pass to the VRChat game. /// True if the game was started successfully, false otherwise. public bool StartGameFromPath(string path, string arguments) { if (!path.EndsWith(".exe")) path = Path.Combine(path, "launch.exe"); if (!path.EndsWith("launch.exe") || !File.Exists(path)) return false; Process.Start(new ProcessStartInfo { WorkingDirectory = Path.GetDirectoryName(path), FileName = path, UseShellExecute = false, Arguments = arguments })?.Close(); return true; } } }