using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Microsoft.Win32; namespace VRCX { public partial class AppApi { /// /// Gets the VRChat application data location by reading the config file and checking the cache directory. /// If the cache directory is not found in the config file, it returns the default cache path. /// /// The VRChat application data location. public string GetVRChatAppDataLocation() { var json = ReadConfigFile(); if (!string.IsNullOrEmpty(json)) { var obj = JsonConvert.DeserializeObject(json); if (obj["cache_directory"] != null) { var cacheDir = (string)obj["cache_directory"]; if (!string.IsNullOrEmpty(cacheDir) && Directory.Exists(cacheDir)) { return cacheDir; } } } return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat"; } public string GetVRChatPhotosLocation() { var json = ReadConfigFile(); if (!string.IsNullOrEmpty(json)) { var obj = JsonConvert.DeserializeObject(json); if (obj["picture_output_folder"] != null) { var photosDir = (string)obj["picture_output_folder"]; if (!string.IsNullOrEmpty(photosDir) && Directory.Exists(photosDir)) { return photosDir; } } } return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "VRChat"); } private string GetSteamUserdataPathFromRegistry() { string steamUserdataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Steam\userdata"); try { using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Valve\Steam")) { if (key != null) { object o = key.GetValue("InstallPath"); if (o != null) { steamUserdataPath = Path.Combine(o.ToString(), @"userdata"); } } } } catch { } return steamUserdataPath; } public string GetVRChatScreenshotsLocation() { // program files steam userdata screenshots var steamUserdataPath = GetSteamUserdataPathFromRegistry(); var screenshotPath = string.Empty; var latestWriteTime = DateTime.MinValue; if (!Directory.Exists(steamUserdataPath)) return screenshotPath; var steamUserDirs = Directory.GetDirectories(steamUserdataPath); foreach (var steamUserDir in steamUserDirs) { var screenshotDir = Path.Combine(steamUserDir, @"760\remote\438100\screenshots"); if (!Directory.Exists(screenshotDir)) continue; var lastWriteTime = File.GetLastWriteTime(screenshotDir); if (lastWriteTime <= latestWriteTime) continue; latestWriteTime = lastWriteTime; screenshotPath = screenshotDir; } return screenshotPath; } /// /// Gets the VRChat cache location by combining the VRChat application data location with the cache directory name. /// /// The VRChat cache location. public string GetVRChatCacheLocation() { return Path.Combine(GetVRChatAppDataLocation(), "Cache-WindowsPlayer"); } public bool OpenVrcxAppDataFolder() { var path = Program.AppDataDirectory; if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public bool OpenVrcAppDataFolder() { var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat"; if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public bool OpenVrcPhotosFolder() { var path = GetVRChatPhotosLocation(); if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public bool OpenVrcScreenshotsFolder() { var path = GetVRChatScreenshotsLocation(); if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public bool OpenCrashVrcCrashDumps() { var path = Path.Combine(Path.GetTempPath(), "VRChat", "VRChat", "Crashes"); if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } /// /// Opens the folder containing user-defined shortcuts, if it exists. /// public void OpenShortcutFolder() { var path = AutoAppLaunchManager.Instance.AppShortcutDirectory; if (!Directory.Exists(path)) return; OpenFolderAndSelectItem(path, true); } /// /// Opens the folder containing the specified file or folder path and selects the item in the folder. /// /// The path to the file or folder to select in the folder. /// Whether the specified path is a folder or not. Defaults to false. public void OpenFolderAndSelectItem(string path, bool isFolder = false) { path = Path.GetFullPath(path); // I don't think it's quite meant for it, but SHOpenFolderAndSelectItems can open folders by passing the folder path as the item to select, as a child to itself, somehow. So we'll check to see if 'path' is a folder as well. if (!File.Exists(path) && !Directory.Exists(path)) return; var folderPath = isFolder ? path : Path.GetDirectoryName(path); IntPtr pidlFolder; IntPtr pidlFile; uint psfgaoOut; // Convert our managed strings to PIDLs. PIDLs are essentially pointers to the actual file system objects, separate from the "display name", which is the human-readable path to the file/folder. We're parsing the display name into a PIDL here. // The windows shell uses PIDLs to identify objects in winapi calls, so we'll need to use them to open the folder and select the file. Cool stuff! var result = WinApi.SHParseDisplayName(folderPath, IntPtr.Zero, out pidlFolder, 0, out psfgaoOut); if (result != 0) { OpenFolderAndSelectItemFallback(path); return; } result = WinApi.SHParseDisplayName(path, IntPtr.Zero, out pidlFile, 0, out psfgaoOut); if (result != 0) { // Free the PIDL we allocated earlier if we failed to parse the display name of the file. Marshal.FreeCoTaskMem(pidlFolder); OpenFolderAndSelectItemFallback(path); return; } IntPtr[] files = { pidlFile }; try { // Open the containing folder and select our file. SHOpenFolderAndSelectItems will respect existing explorer instances, open a new one if none exist, will properly handle paths > 120 chars, and work with third-party filesystem viewers that hook into winapi calls. // It can select multiple items, but we only need to select one. WinApi.SHOpenFolderAndSelectItems(pidlFolder, (uint)files.Length, files, 0); } catch { OpenFolderAndSelectItemFallback(path); } finally { // Free the PIDLs we allocated earlier Marshal.FreeCoTaskMem(pidlFolder); Marshal.FreeCoTaskMem(pidlFile); } } public void OpenFolderAndSelectItemFallback(string path) { if (!File.Exists(path) && !Directory.Exists(path)) return; if (Directory.Exists(path)) { Process.Start("explorer.exe", path); } else { // open folder with file highlighted Process.Start("explorer.exe", $"/select,\"{path}\""); } } } }