// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors. // All rights reserved. // // This work is licensed under the terms of the MIT license. // For a copy, see . using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Windows.Forms; using Windows.UI.Notifications; using CefSharp; using librsync.net; using Microsoft.Win32; using NLog; namespace VRCX { public partial class AppApi { public static readonly AppApi Instance; private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly MD5 _hasher = MD5.Create(); private static bool dialogOpen; static AppApi() { Instance = new AppApi(); ProcessMonitor.Instance.ProcessStarted += Instance.OnProcessStateChanged; ProcessMonitor.Instance.ProcessExited += Instance.OnProcessStateChanged; } /// /// Computes the MD5 hash of the file represented by the specified base64-encoded string. /// /// The base64-encoded string representing the file. /// The MD5 hash of the file as a base64-encoded string. public string MD5File(string Blob) { var fileData = Convert.FromBase64CharArray(Blob.ToCharArray(), 0, Blob.Length); using (var md5 = MD5.Create()) { var md5Hash = md5.ComputeHash(fileData); return Convert.ToBase64String(md5Hash); } } /// /// Computes the signature of the file represented by the specified base64-encoded string using the librsync library. /// /// The base64-encoded string representing the file. /// The signature of the file as a base64-encoded string. public string SignFile(string Blob) { var fileData = Convert.FromBase64String(Blob); using (var sig = Librsync.ComputeSignature(new MemoryStream(fileData))) using (var memoryStream = new MemoryStream()) { sig.CopyTo(memoryStream); var sigBytes = memoryStream.ToArray(); return Convert.ToBase64String(sigBytes); } } /// /// Returns the length of the file represented by the specified base64-encoded string. /// /// The base64-encoded string representing the file. /// The length of the file in bytes. public string FileLength(string Blob) { var fileData = Convert.FromBase64String(Blob); return fileData.Length.ToString(); } /// /// Shows the developer tools for the main browser window. /// public void ShowDevTools() { MainForm.Instance.Browser.ShowDevTools(); } /// /// Deletes all cookies from the global cef cookie manager. /// public void DeleteAllCookies() { Cef.GetGlobalCookieManager().DeleteCookies(); } /// /// Opens the specified URL in the default browser. /// /// The URL to open. public void OpenLink(string url) { if (url.StartsWith("http://") || url.StartsWith("https://")) { Process.Start(url).Close(); } } // broken since adding ExecuteVrFeedFunction( // public void ShowVRForm() // { // try // { // MainForm.Instance.BeginInvoke(new MethodInvoker(() => // { // if (VRForm.Instance == null) // { // new VRForm().Show(); // } // })); // } // catch // { // } // } public void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) { VRCXVR.Instance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand); } public void RefreshVR() { VRCXVR.Instance.Restart(); } public void RestartVR() { VRCXVR.Instance.Restart(); } /// /// Returns an array of arrays containing information about the connected VR devices. /// Each sub-array contains the type of device and its current state /// /// An array of arrays containing information about the connected VR devices. public string[][] GetVRDevices() { return VRCXVR.Instance.GetDevices(); } /// /// Returns the current CPU usage as a percentage. /// /// The current CPU usage as a percentage. public float CpuUsage() { return CpuMonitor.Instance.CpuUsage; } /// /// Retrieves an image from the VRChat API and caches it for future use. The function will return the cached image if it already exists. /// /// The URL of the image to retrieve. /// The ID of the file associated with the image. /// The version of the file associated with the image. /// A string representing the file location of the cached image. public string GetImage(string url, string fileId, string version) { return ImageCache.GetImage(url, fileId, version); } /// /// Displays a desktop notification with the specified bold text, optional text, and optional image. /// /// The bold text to display in the notification. /// The optional text to display in the notification. /// The optional image to display in the notification. public void DesktopNotification(string BoldText, string Text = "", string Image = "") { var toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02); var stringElements = toastXml.GetElementsByTagName("text"); var imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico"); if (!string.IsNullOrEmpty(Image)) { imagePath = Image; } stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText)); stringElements[1].AppendChild(toastXml.CreateTextNode(Text)); var imageElements = toastXml.GetElementsByTagName("image"); imageElements[0].Attributes.GetNamedItem("src").NodeValue = imagePath; var toast = new ToastNotification(toastXml); ToastNotificationManager.CreateToastNotifier("VRCX").Show(toast); } /// /// Restarts the VRCX application for an update by launching a new process with the "/Upgrade" argument and exiting the current process. /// public void RestartApplication() { var VRCXProcess = new Process(); VRCXProcess.StartInfo.FileName = Path.Combine(Program.BaseDirectory, "VRCX.exe"); VRCXProcess.StartInfo.UseShellExecute = false; VRCXProcess.StartInfo.Arguments = "/Upgrade"; VRCXProcess.Start(); Environment.Exit(0); } /// /// Checks if the VRCX update executable exists in the AppData directory. /// /// True if the update executable exists, false otherwise. public bool CheckForUpdateExe() { if (File.Exists(Path.Combine(Program.AppDataDirectory, "update.exe"))) return true; return false; } /// /// Sends an IPC packet to announce the start of VRCX. /// public void IPCAnnounceStart() { IPCServer.Send(new IPCPacket { Type = "VRCXLaunch" }); } /// /// Sends an IPC packet with a specified message type and data. /// /// The message type to send. /// The data to send. public void SendIpc(string type, string data) { IPCServer.Send(new IPCPacket { Type = "VrcxMessage", MsgType = type, Data = data }); } public void ExecuteAppFunction(string function, string json) { if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame) MainForm.Instance.Browser.ExecuteScriptAsync($"$app.{function}", json); } public void ExecuteVrFeedFunction(string function, string json) { if (VRCXVR._browser1 == null) return; if (VRCXVR._browser1.IsLoading) VRCXVR.Instance.Restart(); VRCXVR._browser1.ExecuteScriptAsync($"$app.{function}", json); } public void ExecuteVrOverlayFunction(string function, string json) { if (VRCXVR._browser2 == null) return; if (VRCXVR._browser2.IsLoading) VRCXVR.Instance.Restart(); VRCXVR._browser2.ExecuteScriptAsync($"$app.{function}", json); } /// /// Gets the launch command from the startup arguments and clears the launch command. /// /// The launch command. public string GetLaunchCommand() { var command = StartupArgs.LaunchCommand; StartupArgs.LaunchCommand = string.Empty; return command; } /// /// Focuses the main window of the VRCX application. /// public void FocusWindow() { MainForm.Instance.Invoke(new Action(() => { MainForm.Instance.Focus_Window(); })); } /// /// Returns the file path of the custom user CSS file, if it exists. /// /// The file path of the custom user CSS file, or an empty string if it doesn't exist. public string CustomCssPath() { var output = string.Empty; var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VRCX\\custom.css"); if (File.Exists(filePath)) output = filePath; return output; } /// /// Returns the file path of the custom user js file, if it exists. /// /// The file path of the custom user js file, or an empty string if it doesn't exist. public string CustomScriptPath() { var output = string.Empty; var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VRCX\\custom.js"); if (File.Exists(filePath)) output = filePath; return output; } public string CurrentCulture() { return CultureInfo.CurrentCulture.ToString(); } public string CurrentLanguage() { return CultureInfo.InstalledUICulture.Name; } public string GetVersion() { return Program.Version; } /// /// Returns whether or not the VRChat client was last closed gracefully. According to the log file, anyway. /// /// True if the VRChat client was last closed gracefully, false otherwise. public bool VrcClosedGracefully() { return LogWatcher.Instance.VrcClosedGracefully; } public void ChangeTheme(int value) { WinformThemer.SetGlobalTheme(value); } public void DoFunny() { WinformThemer.DoFunny(); } /// /// Returns the number of milliseconds that the system has been running. /// /// The number of milliseconds that the system has been running. public double GetUptime() { using (var uptime = new PerformanceCounter("System", "System Up Time")) { uptime.NextValue(); return TimeSpan.FromSeconds(uptime.NextValue()).TotalMilliseconds; } } /// /// Returns a color value derived from the given user ID. /// This is, essentially, and is used for, random colors. /// /// The user ID to derive the color value from. /// A color value derived from the given user ID. public int GetColourFromUserID(string userId) { var hash = _hasher.ComputeHash(Encoding.UTF8.GetBytes(userId)); return (hash[3] << 8) | hash[4]; } /// /// Returns a dictionary of color values derived from the given list of user IDs. /// /// The list of user IDs to derive the color values from. /// A dictionary of color values derived from the given list of user IDs. public Dictionary GetColourBulk(List userIds) { var output = new Dictionary(); foreach (string userId in userIds) { output.Add(userId, GetColourFromUserID(userId)); } return output; } /// /// Retrieves the current text from the clipboard. /// /// The current text from the clipboard. public string GetClipboard() { var clipboard = string.Empty; var thread = new Thread(() => clipboard = Clipboard.GetText()); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); return clipboard; } /// /// Sets whether or not the application should start up automatically with Windows. /// /// True to enable automatic startup, false to disable it. public void SetStartup(bool enabled) { try { using (var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true)) { if (enabled) { var path = Application.ExecutablePath; key.SetValue("VRCX", $"\"{path}\" --startup"); } else { key.DeleteValue("VRCX", false); } } } catch { } } // what the fuck even is this // refactor when // #AppApiLivesDontMatter public void SetAppLauncherSettings(bool enabled, bool killOnExit) { AutoAppLaunchManager.Instance.Enabled = enabled; AutoAppLaunchManager.Instance.KillChildrenOnExit = killOnExit; } /// /// Copies an image file to the clipboard if it exists and is of a supported image file type. /// /// The path to the image file to copy to the clipboard. public void CopyImageToClipboard(string path) { // check if the file exists and is any image file type if (File.Exists(path) && (path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".jpeg") || path.EndsWith(".gif") || path.EndsWith(".bmp") || path.EndsWith(".webp"))) { MainForm.Instance.BeginInvoke(new MethodInvoker(() => { var image = Image.FromFile(path); Clipboard.SetImage(image); })); } } /// /// Flashes the window of the main form. /// public void FlashWindow() { MainForm.Instance.BeginInvoke(new MethodInvoker(() => { WinformThemer.Flash(MainForm.Instance); })); } /// /// Sets the user agent string for the browser. /// public void SetUserAgent() { using (var client = MainForm.Instance.Browser.GetDevToolsClient()) { _ = client.Network.SetUserAgentOverrideAsync(Program.Version); } } public string GetFileBase64(string path) { if (File.Exists(path)) { return Convert.ToBase64String(File.ReadAllBytes(path)); } return null; } } }